DUB — это стандартный менеджер пакетов и система автоматизации сборки для D. До появления DUB в 2012 году все в сообществе D использовали разные инструменты сборки — такие как классический Make, DSSS, CDC, xfBuild и пользовательские скрипты сборки, в том числе написанные на D. сам. Я использовал свой собственный, названный Cook, который был моим самым первым публичным проектом на D и начинался как однофайловый скрипт сборки, который можно было добавить в любой проект и запустить с помощью RDMD. Со временем все это устарело, и теперь мир D невозможно представить без DUB. Сегодня я собираюсь показать некоторые из его скрытых функций, которые я использую в сложных проектах.

Специфические настройки платформы

Очень часто используются разные варианты сборки в разных средах. Например, вы можете захотеть связать пользовательские файлы *.lib под Windows или указать некоторые необычные параметры компоновщика. То же самое верно и для x86/x86_64. DUB позволяет выполнить некоторую условную компиляцию, используя дополнительные настройки для конкретной платформы.

Из спецификации dub.json:
Настройки, зависящие от платформы, поддерживаются за счет использования суффиксов имен полей. Суффиксы представляют собой список идентификаторов операционной системы/архитектуры/компилятора, разделенных тире, как определено в справочнике по языку D, но переведены в нижний регистр. Порядок этих суффиксов следующий: ос-архитектура-компилятор, где любая из этих частей может быть опущена.

Пример:

"lflags-windows-x86-dmd": [
    "/SUBSYSTEM:WINDOWS:5.01"
]
"lflags-windows-x86_64-dmd": [
    "/SUBSYSTEM:WINDOWS", "/ENTRY:mainCRTStartup"
],
"lflags-windows-x86_64-ldc": [
    "-SUBSYSTEM:WINDOWS", "-ENTRY:mainCRTStartup"
],
"lflags-linux-gdc": ["-lz"]

подпакеты

Вы когда-нибудь поддерживали проект, состоящий из нескольких отдельных программ? Если да, то нет необходимости делать каждый из них отдельным пакетом — DUB поддерживает так называемые подпакеты.

Из спецификации dub.json:
Пакет может содержать произвольное количество дополнительных общедоступных пакетов. Эти пакеты можно определить в разделе «subPackages основного файла dub.json. На них можно сослаться, объединив их имя с именем основного пакета, используя двоеточие в качестве разделителя (например, имя основного пакета: имя подпакета). Типичное использование этой функции — разделить библиотеку на несколько частей, не разбивая ее на разные репозитории кода».

Пример:

{
    "name": "demo",
    "dependencies": {
        "demo:sample1": "*",
        "demo:sample2": "*"
    },
    "subPackages": [
        {
            "name": "sample1",
            "targetType": "executable",
            "targetName": "sample1",
            "sourcePaths": ["src/sample1"],
            "importPaths": ["src/sample1"],
            "dependencies": {
                ...
            }
        },
        {
            "name": "sample2",
            "targetType": "executable",
            "targetName": "sample2",
            "sourcePaths": ["src/sample2"],
            "importPaths": ["src/sample2"],
            "dependencies": {
                ...
            }
        }
    ]
}

подконфигурации

Точно так же DUB допускает несколько конфигураций одного и того же пакета. Это полезно, чтобы разрешить настраиваемую сборку зависимости.

Пример:

{
    "name": "myGameEngine",
    "configurations": [
        {
            "name": "opengl",
            "libs": ["gl"]
        },
        {
            "name": "d3d",
            "platforms": ["windows"],
            "libs": ["d3d11"]
        }
    ]
}

Теперь любой пакет, использующий myGameEngine, может выбрать определенную конфигурацию:

{
    "dependencies": {
        "myGameEngine": ">=1.0.0"
    },
    "subConfigurations": {
        "myGameEngine": "opengl"
    }
}

stringImportPaths

Одной из менее известных функций D является чтение строковых литералов из файлов во время компиляции (импорт строк), что очень удобно для хранения больших фрагментов текста:

string s = import("text.txt");

Однако недостатком является то, что вы должны явно указать список каталогов, в которых компилятор должен искать эти файлы, используя ключ -J, иначе компиляция завершится ошибкой. DUB поддерживает для этого специальное свойство конфигурации:

"stringImportPaths": ["myTextDir"]

копировать файлы

Еще одна малоиспользуемая замечательная функция DUB — ​​возможность копировать файлы из пакета в проект.

Из спецификации dub.json:
список глобусов, соответствующих файлам или каталогам, которые нужно скопировать в targetPath. Совпадающие каталоги копируются рекурсивно, то есть copyFiles: [путь/к/каталогу]» рекурсивно копирует каталог, а copyFiles: [путь/к/каталогу/*]» копирует только файлы внутри каталога.

Вместе с postBuildCommands это открывает дверь для автоматического развертывания: вы можете копировать общие библиотеки, файлы конфигурации, создавать документацию и т. д. Для работы с общими библиотеками вы можете комбинировать copyFiles со спецификаторами платформы:

"copyFiles-windows-x86": ["lib/x86/*.dll"],                           "copyFiles-windows-x86_64": ["lib/x64/*.dll"],

Пакетные переопределения

Это чрезвычайно полезно для работы над несколькими взаимозависимыми проектами. Представьте, что вы пишете игру и движок для нее. Движок представляет собой пакет DUB и находится в отдельном репозитории. Как вы должны разработать его локально, не отправляя изменения на удаленный сервер каждый раз, когда вы хотите протестировать нестабильные функции, используя свою игру/песочницу? Похоже, единственное решение — включить в игру весь движок. Но с DUB у вас есть вариант получше — переопределение пакета:

dub add-override myGameEngine ~master path/to/local/repo

Теперь все ваши локальные проекты, использующие myGameEngine ~master, будут получать локальную копию, а не «официальную». Убедитесь, что в вашей игре есть следующая зависимость:

"dependencies": {
    "myGameEngine": "~master"
}

Когда вы закончите тестирование движка и довольны своими изменениями, вы можете немедленно зафиксировать их из локальной копии, как обычно, а затем удалить переопределение:

dub remove-override myGameEngine ~master path/to/local/repo

Значок приложения

Компиляторы обычно не предоставляют встроенных функций для установки значка приложения (и строк версии) в Windows. Обычный способ — скомпилировать файл ресурсов (*.res) и связать его с проектом:

”sourceFiles-windows” : [“application.res”]

Но для этого требуется компилятор ресурсов, проприетарный инструмент из Microsoft SDK. Я думаю, что лучший способ — напрямую модифицировать EXE с помощью Electron’s rcedit. Предполагая, что у вас есть исполняемые файлы rcedit (rcedit-x86.exe, rcedit-x64.exe) в каталоге вашего проекта, вы можете вызывать их после каждой сборки с помощью postBuildCommands:

"postBuildCommands-windows-x86": [
    "$PACKAGE_DIR\\rcedit-x86 \"app.exe\" --set-file-version \"1.0.0.0\" --set-product-version \"1.0.0\" --set-icon \"$PACKAGE_DIR\\icon.ico\""
],
    
"postBuildCommands-windows-x86_64": [
    "$PACKAGE_DIR\\rcedit-x64 \"app.exe\" --set-file-version \"1.0.0.0\" --set-product-version \"1.0.0\" --set-icon \"$PACKAGE_DIR\\icon.ico\""
]

Знаете еще какие-нибудь крутые лайфхаки? Если да, поделитесь ими, и я добавлю их в статью.