DUB Tricks

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

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

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

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

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

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

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

”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.

--

--

Computer graphics developer

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store