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\"" ]
Знаете еще какие-нибудь крутые лайфхаки? Если да, поделитесь ими, и я добавлю их в статью.