DUB Tricks

Timur Gafarov
4 min readMay 3, 2019

--

DUB is a standard package manager and build automation system for D. Before DUB appeared in 2012, everyone in D community used different build tools — such as classic Make, DSSS, CDC, xfBuild and custom build scripts, including those written in D itself. I used my own, called Cook, which was my very first public project in D and started as a single-file build script that could be added to any project and run with RDMD. Eventually all these became obsolete, and now D world can’t be imagined without DUB. Today I’m going to show some of its ‘hidden’ features that I use in complex projects.

Platform specific settings

It’s very common to use different build options under different environments. For example, you may want to link against custom *.lib files under Windows, or specify some unusual linker parameters. The same is true for x86/x86_64. DUB allows to do some conditional compilation using optional platform specific settings.

From the dub.json spec:
“Platform specific settings are supported through the use of field name suffixes. Suffixes are dash separated list of operating system/architecture/compiler identifiers, as defined in the D language reference, but converted to lower case. The order of these suffixes is os-architecture-compiler, where any of these parts can be left off.”

Example:

"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"]

subPackages

Have you ever maintained a project that consisted of multiple separate programs? If so, there’s no need to make each of them a separate package — DUB supports so-called subpackages.

From the dub.json spec:
“A package may contain an arbitrary number of additional publicly visible packages. These packages can be defined in the “subPackages” section of the main dub.json file. They can be referenced by concatenating their name with the name of the main package using a colon as the delimiter (i.e. “main-package-name:sub-package-name”). The typical use for this feature is to split up a library into a number of parts without breaking it up into different code repositories.”

Example:

{
"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": {
...
}
}
]
}

subConfigurations

Similarly, DUB allows multiple configurations of the same package. This is useful to allow a customized build of a dependency.

Example:

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

Now any package that uses myGameEngine may choose a specific configuration:

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

stringImportPaths

One of the lesser known features of D is reading string literals from files at compile time (string import), which is very handy for storing large pieces of text:

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

However, the drawback is that you should explicitly define a list of directories where compiler must look for these files, using -J key, or the compilation will fail. DUB supports a special configuration property for that:

"stringImportPaths": ["myTextDir"]

copyFiles

Another underused great feature of DUB is an ability to copy files from a package to the project.

From the dub.json spec:
A list of globs matching files or directories to be copied to targetPath. Matching directories are copied recursively, i.e. “copyFiles”: [“path/to/dir”]” recursively copies dir, while “copyFiles”: [“path/to/dir/*”]” only copies files within dir.

Together with postBuildCommands this opens a door for an automatic deploy: you can copy shared libraries, configuration files, generate documentation, etc. For dealing with shared libraries you can combine copyFiles with platform specifiers:

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

Package Overrides

This is extremely useful for working on several interdependent projects. Imagine that you are writing a game and an engine for it. The engine is a DUB package and lives in a separate repository. How should you develop it locally without pushing changes to remote every time when you want to test unstable features using your game/sandbox? Seems the only solution is to include the entire engine into the game. But with DUB you have a nicer option — package override:

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

Now all your local projects that use myGameEngine ~master will fetch the local copy, not the “official” one. Be sure to have the following dependency in your game:

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

When you’re done testing the engine and happy with your changes, you can immediately commit them from the local copy as usual, and then remove the override:

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

Application Icon

Compilers usually don’t provide a built-in functionality to set application icon (and version strings) under Windows. The usual way is to compile a resource file (*.res) and link it with the project:

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

But this requires resource compiler, a proprietary tool from Microsoft SDK. I think the better way is to directly modify the EXE using Electron’s rcedit. Assuming you have rcedit executables (rcedit-x86.exe, rcedit-x64.exe) in your project’s directory, you can call them after each build using 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\""
]

Do you know any more cool hacks? If so, share them, and I’ll add them to the article.

--

--