Makd is a GNU Make library/framework based on Makeit, adapted to D. It combines the power of Make and rdmd to provide a lot of free functionality, like implicit rules to compile binaries (only when necessary), tracking if any of the source files changed, it improves considerably Make's output, it provides a default test target that runs unittests and arbitrary integration tests, it detects if you change the compilation flags and recompile if necessary, etc.
MakD complies with Neptune for versioning.
- Major branch development period: 3 months
- Maintained minor versions: most recent
| Major | Initial release date | Supported until |
|---|---|---|
| v2.x.x | v2.0.0: 2017-09-08 | 2020-12-31 |
| v3.x.x | v3.0.0: 2020-11-02 | TBD |
Contents
First of all, is important to clarify that Makd do some assumptions on your
project's layout. All sources files should be located in src/ on the root
of the project (you can override this by overriding the $(SRC) variable
though). This is the bare minimum you have to know, but there are a few more
conventions (for example, integration tests should go to
$(INTEGRATIONTEST)/, integrationtest/ by default), they will be
explained when explaining the features that rely on them.
To get started you need to have makd as a submodule (or copy it to your project) and create a top-level makefile for your project (or convert the old one).
A typical Top-level Makefile should look like this:
# Include the top-level makefile
include submodules/makd/Makd.makAssuming your makd installation is in submodules/makd. By default, the
default target when typing just make is all, and you can add targets to
it, which will be explained later.
You can change this default target by explicitly overriding the
.DEFAULT_GOAL variable, which tells GNU Make which target should be built
when you just run make without arguments. If you set it, make sure you
define it after including Makd.mak, order is important in this case:
# Default goal for building this directory
.DEFAULT_GOAL := some-targetThis Makefile file should be written only once and never touched again (most
likely). But in your project you might have more than one Makefile, for example
you could have one in your src directory and another one in your test
directory, so you can do make in src without specifying -C ... Also,
probably your .DEFAULT_GOAL in the src/Makefile will be all while
the one in test/Makefile can be test instead.
This is the file where you define what your Makefile will actually do. Makd
does a lot for you, so this file is usually very terse. To define a binary to
compile, all you need to write in your Build.mak is this:
$B/someapp: $C/src/main/someapp.dThat's it, this is the bare minimum you need. With this you can now write
make $PWD/build/devel/bin/someapp and you should get your binary there (why
build/devel/bin will be explained later in the next section). $B is
a special variable holding the path where your binaries will be stored, and
$C is a special variable storing the current path (the path where the
current Build.mak is, not the directory where make was invoked). Both
are absolute paths, to enable Makd to support building the project from
different locations (to make this work you should refer to all the project
files using this $C/ prefix when you refer to the current directory of
your Build.mak).
Usually you want a shortcut to type less, so you might want to add:
.PHONY: someapp
someapp: $B/someappNow you can simply write make someapp to build it. Simple.
But maybe you want to type just make. Since the .DEFAULT_GOAL defined in
your Makefile is all, you can use the special all variable to add
targets to build when is called:
all += someappNow you can simply write make and you'll get your program built.
Putting it all together, your file should look like:
.PHONY: someapp
someapp: $B/someapp
$B/someapp: $C/src/main/someapp.d
all += someappMakd has a lot of configuration variables available. This file lives in the
top-level directory of the project and serves as a global configuration point.
There is only one Config.mak per project, so the configuration defined here
should make sense for all the Makefiles defined across the project. For
example you could redefine the colors used here, or the default DMD binary to
use. This is why this file, when present, should be always added to the version
control system. But normally you shouldn't need to create this file.
This file (and Config.local.mak) should only define variables, as it's parsed
before any other variables or functions are defined. All the predefined variable
and functions available in Build.mak are not available here, except for
$F, $T and $R, so use with care (see Predefined variables for
details).
This is a local (personal) version of the Config.mak, so users can customize the build system to their taste. Here is where you usually should define which Flavors to compile by default, or which colors to use, or the path to a non-conventional compiler location. This file should never be added to the version control system.
This file is loaded after Config.mak so it overrides its values.
Everything built by Makd is left in the build directory (or the directory
specified in BUILD_DIR_NAME variable if you defined it). In the build
directory you can find these other directories and files:
<flavor>- Makd support Flavors (also called variants), by default flags are
provided for the devel and the prod flavors. All the symbols
produced by the devel variant (the default) for example, will live in
the
develsubdirectory in the build directory. last- This is a symbolic link to the latest flavor that has been built. Is
useful to use by script, where you do
makebut you don't know the name of the default flavor. Then you can just access tobuild/last. doc- Generated documentation is put in this directory. Flavors shouldn't
affect how the documentation is built, so there is only one
docdirectory.
Each flavor directory have a set of files and directories of its own:
bin- This is where the generated binaries are left.
tmp- This is where object files, dependencies files and any other temporary
file is left. Usually after a build all the contents of this directory
is trash and only works as a cache. If you remove this directory a new
build will be triggered next time you run make though, even if nothing
changed. The project directory structure is replicated inside this
directory, except for the directories specified by the
BUILD_DIR_EXCLUDEvariable (by default the build directory itself, the.gitdirectory and the submodule directories). pkg- Generated packages are built in this directory. You can change this via
the
Pvariable. build-d-flags- A signature file to keep track of building flags changes.
Once you have the basic setup done, you can already enjoy a lot of small cool features. For example you get a nice, terse and colorful output, for example:
mkversion src/Version.d rdmd build/devel/bin/someapp
If there are any errors, messages will appear in red so they are easier to spot.
If you like the good old make verbose output, just use make V=1 and you'll
get everything. If you don't like colors, just use make COLOR=. Makd also
honours Make options --silent, --quiet and -s. So if you want to
avoid all output, just use make -s as usual.
All these variables can be configured in your Config.local.mak if you want to always have it verbose or whatever.
If you want to force a build there is also the not-so-known make -B, there
is no need to use the built-in make clean target and destroy all your cache
(with all the other Flavors you compiled in the past).
By default the devel flavor is compiled, but you can compile the
prod flavor by using make F=prod.
Also, if you have several cores, use make -j2 and enjoy of Make's
parallelism for free! (this will use 2 cores, you can use -j3 for 3 and so
on).
If you want to build as much as possible without stopping, you can also use
make -k (for --keep-going) so Make doesn't stop on the first error.
This is particularly useful for Testing, if you want to find out how many tests
are broken without fixing everything first.
Finally, if you want to speed things up a little bit, you can use make -r,
which suppress the many Make predefined rules, which we don't use and sometime
makes Make evaluate more options than needed.
Of course you can combine many Makd and Make options, and specify more than one target, for example:
make -Brj4 F=prod V=1 COLOR= all test
So, we already shown you can use a couple of built-in predefined targets. The whole set of predefined targets are:
allcleantestfasttestunittestallunittestfastunittestintegrationtestexampleexample-rundocpkggraph-deps
Not all of them will be useful out of the box, you need to assign other targets
to them to be useful. In this category are: all and doc. For all we
already saw how to feed it, just add targets to the predefined variable with
the same name (all += sometarget). All those special target behaves the
same.
The built-in *unittest target will compile and run the unittests in every
.d file found in the $(SRC) directory. The integrationtest target
will compile and run every test program in $(INTEGRATIONTEST)/. The
test target includes the allunittest and integrationtest targets by
default, but you can add more by using the test special variable (test +=
mytest). The fasttest target will only run the fastunittest target by
default, but you can add more too by using the fasttest special variable.
See the Testing section for more details.
The example target will compile example programs found in
$(EXAMPLE)/*/*.d (example/ by default) in a similar fashion to what
integrationtest does. An example can be skipped by adding the file to the
$(EXAMPLE_FILTER_OUT) variable. Check Skipping tests section for
a similar example of filtering out files. Examples are built as part of the
test target by default, but they are not ran, as they could expect user
input or have other limitations that might not be suitable for general testing.
An example-run target is provided, though, in case you want to run all
examples manually. You can use the EXAMPLEFLAGS variable to pass custom
arguments to the example programs when running them, look at the Adding
specific flags section for details on how to use EXAMPLEFLAGS as you
would do with UTFLAGS.
The pkg target builds all packages defined in $P, see Packaging
section for more details.
The clean target simply removes The build directory recursively. Just
remember to put all your generated files there and the clean target will always
work ;). If you can't do that (because you generated a source file for example),
you can use the special variable clean too (clean += src/trash.d
src/garbage.d for example).
The doc target will, by default, call harbored-mod tool to generate the documentation
for the project from DDOC comments inside source files. Harbored-mod is
choosen because it also allows Markdown syntax which makes the documentation
easier to read in the source files, as it doesn't require as much DDOC macros
as the dmd.
The graph-deps target is used to generate a dependencies graph. To generate
this graph the dot tool from the graphviz
visualization software is used (the location of the tool can be specified via
the DOT variable). By default only cyclic dependencies are generated in the
graph, but other kind of dependencies graphs can be generated (please take
a look at the ./graph-deps --help ouput for details, you can override the
options to pass to graph-deps using the GRAPH_DEPS_FLAGS variables).
There are a lot of predefined variables provided by Makd, we've already seen
quite a few important ones (F, COLOR, V for example).
Some of these variables are meant to be overridden and some are mean to be just
used (read-only), otherwise the library could break. Here we list a lot of them,
but always check the source Makd.mak if you want to know them all!
The standard Make variable LDFLAGS have a special treatment when used with
dmd/rdmd: the -L is automatically prepended, so if you need to
specify libraries to link to, just use -lname, not -L-lname (same with
any other linker flag).
- The special target variables
all,test,doc. - Color handling variables (
COLOR* variables, please look at the Makd.mak source for details). Fto change the default Flavor to build.Vto change the default verboseness.BUILD_DIR_NAMEandBUILD_DIR_EXCLUDE, but usually you shouldn't.Pis where built packages will be created. Defaults to$G/pkg.- Program location variables:
DCis the D compiler to use, you can build your project with a different DMD by usingmake DC=/usr/bin/experimental-dmdfor example. Same forRDMD, andFPM. - Less likely you might want to override the
DFLAGS,RDMDFLAGSorFPMFLAGS, but usually there are better methods to do that instead. TEST_FILTER_OUTto exclude some files from the unit tests or integration tests.EXAMPLE_FILTER_OUTto exclude some examples from being built with theexampleandtesttargets.TEST_RUNNER_MODULEandTEST_RUNNER_STRINGare used to override the module or string to inject in the unittest file that runs all the unit tests. See Testing for details.INTEGRATIONTESTto change the default location of integration tests (integrationtestby default).EXAMPLEto change the default location of the example programs (example/by default).SRCis where all the source files of your project is expected to be. By default issrcbut you can override it with.if you keep the source file in the top-level. The path must be relative to the project's top-level directory. It's using mainly to search for unittests.PKGis where package definitions are searched. When building packages, each*.pkgfile in that directory will be built. By default$T/pkg.PKG_DEFAULTScontains the default options passed tomkpkg.PKG_FILEScontains the list of packages definitions.PKG_PREBUILDhold commands to run previous to build packages.PROJECT_NAMEcontains the name of the project, used in documentation generatation. It defaults to the name of the top directory.VERSION_FILEis the location where to write a D module storing detailed information on the Git version and build information (like person who did the build, date, etc.). If this file shouldn't be generated at all, you can set this variable to be empty. By default it$(GS)/Version.d.VERSIONis the version to be used when creating documentation. It's obtained via themkversion.shby default.PKGVERSIONis the version to be used when creating packages. It's obtained via theVERSIONvariable by default.PKGITERATIONoptionally allows setting the package iteration (forwarded to fpm as --iteration, known as the debian_revision [in Debian](https://www.debian.org/doc/debian-policy/#version)).PRE_BUILD_DandPOST_BUILD_Dhold scripts executed before and after running the command to build D targets (when using thebuild_dfunction). By default they are used to generate theVersion.dfile, but users can override it not to generate the file or do something else on top of that.COVwill compile and run tests with coverage support if is set to1. Please see Coverage for details.COVDIRspecifies the directory where to store coverage reports (by default$O/cov. Please see Coverage for details.COVMERGEindicates if coverage reports should be merged (1will merge,0will not). Please see Coverage for details.
Some of this variables are typically overridden in the Config.mak file, others in the Build.mak file, others in the Config.local.mak or directly in the command line (like the style stuff).
Probably the most important read-only variables are the ones related to generated objects locations:
Tis the project's top-level directory (retrieved from git).Ris the current directory relatively to$T.Cis the directory where the current Build.mak is (which might not be the same as the Make predefined variableCURDIR). You should always use this variable to refer to local project files.Gis the base generated files directory, taking into account the flavor (for examplebuild/devel).Ois the objects/temporary directory (for examplebuild/devel/tmp).Bis the generated binaries directory (for examplebuild/devel/bin).Dis the generated documentation directory (for examplebuild/doc).GSis the temporary where generated sources are stored, so that-I$(GC)is added to the compiler (for examplebuild/devel/include).
All these variables except for R are absolute paths. This is to work
properly when run in different directories. You should take that into account.
Sometimes is good to be able to have some information about the environment provided by Makd. For this purpose, the following variables are exported:
MAKD_TOPDIR: project's top directory as seen by Makd.MAKD_PATH: directory where theMakd.makfile lives.MAKD_TMPDIR: temporary directory inside the build directory that can be used for temporary stuff.MAKD_BINDIR: directory where build binaries are stored.MAKD_FLAVOR: flavor currently being built (usually eitherdevelorprod).MAKD_DVER: D version used (usually either1or2).MAKD_VERBOSE: indicates if Makd is running in verbose mode (V=1). This is only considered false when empty, any other value means true.MAKD_COLOR: indicates if Makd is running in color mode (COLOR=1). This is only considered false when empty, any other value means true.
There are a few useful predefined functions you might want to know about. Only the most important (the ones you are most likely to use) are mentioned here, once again, please refer to the Makd.mak source if you want to see them all.
Probably the most important is exec. This function takes care of the pretty
output and verboseness. Each time you write a custom rule (hopefully you won't
need to do this often), you should probably use it. Here is the function
signature:
$(call exec,command[,pretty_target[,pretty_command]])command is the command to execute, pretty_target is the name that will
be printed as the target that's being build (by default is $@, i.e. the
actual target being built), and pretty_command is the string that will be
print as the command (by default the first word in command).
Here is an example rule:
touch-file:
$(call exec,touch -m $@)This will print:
touch touch-file
When built. And will print touch -m touch-file if V=1 is used, as
expected.
When COLOR is enabled, exec will colorize the output based on the
COLOR_OUT variable. For commands that use colors in the output themselves,
having MakD coloring the output will just make the output weird. Because of
this, there is this flavour of exec that will not colorize the output (but
will still use colors, when enabled, for the fancy progress indication).
This is a convenient shortcut to write rules to build D programs. It will run
the PRE_BUILD_D and POST_BUILD_D and rdmd for the actual build.
It takes 3 optional arguments:
- arguments to be passed to
BUILD.d(usuallyrdmd) - arguments to be passed to the
PRE_BUILD_Dscript - arguments to be passed to the
POST_BUILD_Dscript
This is a very simple function that just checks a certain Debian package is installed. The signature is:
$(call check_deb,package_name,required_version[,compare_op])
package_name is, of course, the name of the package to check.
required_version is the version number we require to build the project and
compare_op is the comparison operator it should be used by the check (by
default is >=, but it can be any of <,<=,=,>=,>).
You can use this as the first command to run for a target action, for example:
myprogram: some-source.d
$(call check_deb,dstep,0.0.1)
rdmd --build --whatever.If you need to share it for multiple targets you can just make a simple alias with a lazy variable:
check_dstep = $(call check_deb,dstep,0.0.1)
myprogram: some-source.d
$(check_dstep)
rdmd --build --whatever.Wrapper around the find command to avoid errors when the directory to
search doesn't exist at all. Use this to avoid spurious find errors.
It takes the directory/ies where to search as first arguments and conditions and other find options as the second argument.
Example:
files := $(call find $C/$(SRC),-name '*.di')Find files and get the their file names relative to another directory.
Arguments are:
- The files suffix (
.hor.cppfor example). - A directory rewrite, the matched files will be rewriten to be in the
directory specified in this argument (it defaults to
$3if omitted). - Where to search for the files (
$Cif omitted). - A
filter-outpattern applied over the original file list (previous to the rewrite). It can be empty, which has no effect (nothing is filtered).
Example:
UNITTEST_FILES := $(call find_files,.d,,$C/$(SRC),$(TEST_FILTER_OUT))This function converts a file path to a D module. It takes as first argument
a file path to convert and as optional second argument the base path of the
sources (path that is not part of the fully qualified module name), by defaul
$C/$(SRC). This function takes into account the special pkg/package.d
module name, converting it to just pkg.
For example:
$(call file2module,project/x.d,project/) # -> x
$(call file2module,project/y/package.d,project/) # -> y
$(call file2module,./some/longer/pkg/package.d,./) # -> some.longer.pkg
$(call file2module,./some/longer/pkg/mod.d,./) # -> some.longer.pkg.modOK, this is not really a function, but you might use it in a way that can be
closer to a function than a variable. When we are in verbose mode, V is
empty and when we are not in verbose mode is set to @. The effect is you
only get some Make output if we are not in verbose mode.
For example, this:
test:
$Vecho testIf called via make test will produce:
test
While if called via make V=1 test, it will produce:
echo test test
This is only useful for commands you normally don't want to print, but you want
to be friendly to the user and show the command if verbose mode is used.
Normally you should always use $V instead of @.
Yes, is a bit confusing that $V internally becomes empty when you use
V=1, but when you use it is very natural :)
Flavors are just different ways to compile one project using different flags. By
default the devel and prod flavors are defined. The The build
directory stores one subdirectory for each flavor so you can compile one after
the other without mixing objects compiled for one with the other and your cache
doesn't get destroyed by a make clean.
To change variables based on the flavor (or define new flavors), usually the Config.mak is the place, and you can use normal Make constructs, for example:
ifeq ($F,devel)
override DFLAGS += -debug=ProjectDebug
endif
ifeq ($F,prod)
override DFLAGS += -version=SuperOptimized
endifUsually the override option is needed, if you want to still add these
special flags even if the user passes a DFLAGS=-flag to Make.
To compile the project using a particular flavor, just pass the F variable
to make, for example:
make F=prod
If you need to define more flavors, you can do so by defining the
$(VALID_FLAVORS) variable in your Config.mak, for example:
VALID_FLAVORS := devel prod profilingThere is a not-so-known Make feature that makes it very easy to override variables for a particular target, and usually that's the best way to pass specific variables to a particular target.
For example, you need to link one binary to a particular library but not the others, then just do:
$B/prog-with-lib: override LDFLAGS += -lthelib
$B/prog-with-lib: $C/src/progwithlibs.d
$B/prog: $C/src/prog.dThen LDFLAGS will only include -lthelib when the target
$B/prog-with-lib is made, but not others. One catch about this is this
variable override is propagated, so if your target needs to build a prerequisite
first, the building of the prerequisite will also see the modified variable. If
you want to avoid this, Makd also expands the special variable
$([email protected]_FLAGS). That is $(<name of the target>.EXTRA_FLAGS) (yes,
Make support recursive expansion of variables :D), for example:
$B/prog-with-lib.EXTRA_FLAGS := -lthelib
$B/prog: $C/src/prog.dWill have a similar effect, but the variable expansion will only work for this particular target. This is a corner case and hopefully you won't need to use it.
Makd supports a simple facility to make packages based on fpm. A simple
wrapper program mkpkg is provided to ease the creation of scripts that use
fpm to create packages.
The predefined pkg target depends on the special variable $(pkg),
where you can add any extra target that must be built for pkg.
By default every package file specified in $(PKG_FILES) will be
added to $(pkg), and by default $(PKG_FILES) holds all
*.pkg files in the $(PKG) directory (by default $T/pkg).
mkpkg is invoked for every file specified in $(PKG_FILES).
These files are expected to be Python scripts. They have some pre-defined built-in variables, some of which the user is expected to fill and some of which are tools for the user to define packages.
These are the built-in variables that the user should fill:
OPTS- a
dict()(associative array) where each item will be mapped to a fpm command-line option. If the key is only one character (for examplec), it will be passed as-<key><value>and if it's more, it will be passed as--<key>=<value>(_characters in the key will be replaced by-for convenience). The<value>can beTrue, a string or an array of strings. If it'sTrue, just-<key>or--<key>is passed (without an actual value), if it's an array of strings, the key is used as fpm flag for each item in<value>. No validation is performed over the keys or values, they are just passed blindly to fpm. ARGS- a
list()(array) to pass to fpm as positional arguments (usually the list of files to include in the package).
These variables should never be rebound (never assign to them like OPTS
= dict(...)), you always need to update them instead (normally using
OPTS.update(...) and ARGS.extend([...])).
An extra built-in variable will be available, VAR, containing variables
passed to the mkpkg util. By default Makd passes the following variables:
shortname- name of the package as calculated from the
.pkgfile. suffix- a suffix to add to the package name to support installing multiple versions simultaneously (see Package suffix for details).
fullnameshortnamewith thesuffixappended to it for convenience.version- package version number as defined by
PKGVERSION. iteration- package iteration as defined by
PKGITERATION. builddir- base build directory (
$G). bindir- directory where the built binaries are stored.
lsb_release- Debian
lsb_release -uccontent (distribution name).
mkpkg also defines the following built-in functions in the special built-in
variable FUN:
autodeps(bin[, ...][, path=''])- returns a sorted
list()of packagesbindepends on based on the outcome of running thelddutility and searching to which packages the libraries is linked belong to usingdpkg. You can specify multiple binaries to get a list of dependencies for all of them. This function is tightly coupled to Debian packages for now. If apathis given, then all thebinpassed will be prepended with thispath.bins can be passed as multiple arguments or as one list. mapfiles(src, dst, file[, ...][, append_suffix=True])- A very simple function that just returns a list with
{src}/{file}={dst}/{file}{VAR.suffix}for eachfilepassed.files can be passed as multiple arguments or as one list. A named argumentappend_suffixcan be passed at the end to control whetherVAR.suffixis appended to each destination file.append_suffixdefaults toTrueif not given. desc([type[, prolog[, epilog]]])A simple function to customize
OPTS['description']. It can add an optionaltypeof package (will append `` (<type>)`` to the first line (short description),prolog(inserted before the long description) and anepilog(appended at the end of the long description. To use only one of them, you can use Python's keyword arguments syntax. Examples:FUN.desc('common files', 'These are just config files', 'Part of whatever') # All specified FUN.desc(epilog='Just an epilog') FUN.desc('a type', epilog='And an epilog') FUN.desc(prolog='A prolog', epilog='And an epilog, but no type')
Note that
OPTS['description']must be defined and hold a non-empty string.
One can exclude packages from being built under certain conditions
(e.g. based on compilation flags) by filtering PKG_FILES:
ifeq ($(IS_RELEASE_MODE),1)
PKG_FILES := $(filter-out $(PKG)/NonReleaseApp.pkg,$(PKG_FILES))
endifGenerated packages will be stored in the $P directory (by default
$G/pkg. Since each package usually have a different name, as the version
usually changes with each change, all old packages are removed before making
new ones with the pkg target and also generates a Debian changelog from
the git history (you can override this by re-defining the PKG_PREBUILD
variable).
The options to pass by default to mkpkg are defined by the variable
PKG_DEFAULTS, you can override it if the defaults are not suitable for you
projects. By default it builds Debian packages from files, a Debian changelog
is provided, and a version and iteration (using the Debian version).
Bear in mind that you should use lazy variables when overriding
PKG_DEFAULTS and PKG_PREBUILD if you want to use variables defined in
the pkg target.
Please run mkpkg --help if you want to know more about that utility.
For more details on how to create packages using fpm (thus, to know which
options you can define in OPTS and what to pass as ARGS) please refer
to the fpm wiki.
Since the package version is included in the file, is very complicated to have
the target really based on the package file name, because of this Makd uses
a stamp approach. The building of the package will be tracked via the special
file $O/pkg-%.stamp file.
So when specifying dependencies (this target should depends on all files used to build the package), you should use this special file instead.
To make it easy to build test packages that can be installed in parallel with
the current packages, the variable PKG_SUFFIX can be passed to make
when building the package (for example make pkg PKG_SUFFIX=-test). This
will produce a package with name name-test. Bear in mind the files will
conflict if the regular name package and a suffixed package have the
same files. To avoid this problem, the {SUFFIX} variable will be replaced
by the contents of the PKG_SUFFIX variable. So the most common pattern is
to add the suffix to any non-configuration file in the package.
For convenience, here is a simple example:
$P/defaults.py
# This is a normal python module defining some defaults
OPTS.update(
description = '''\
Test package packing some daemon
This is an extended package description with multiple lines
This is a longer paragraph in the package description that
can span multiple lines.''',
url = 'https://github.com/sociomantic/makd',
maintainer = 'dunnhumby Germany GmbH <[email protected]>',
vendor = 'dunnhumby Germany GmbH',
)$P/daemon.pkg:
import defaults
bins = 'daemon admtool util1'
OPTS.update(
name = VAR.fullname,
category = 'net',
depends = FUN.autodeps(bins, path=VAR.bindir) + [
'bash',
'libnew' if VAR.lsb_release == 'trusty' else 'libold',
],
)
ARGS.extend(FUN.mapfiles(VAR.bindir, '/usr/sbin', bins) + [
'README.rst=/usr/share/doc/' + VAR.fullname '/',
])$P/client.pkg:
import defaults
bins = 'client clitool'
OPTS.update(
name = VAR.fullname,
description = FUN.desc('tools', epilog='These are just ' +
'utilities for the daemon package'),
category = 'net',
depends = FUN.autodeps(bins, path=VAR.bindir),
)
ARGS.extend(FUN.mapfiles(VAR.bindir, '/usr/bin', bins))
ARGS.extend(FUN.mapfiles('.', '/etc', 'util.conf', append_suffix=False))Suppose that the targets daemon and client build the binaries
daemon, admtool, util1 and client, clitool respectively,
then you probably want to make sure you build those before making the package,
so in the Build.mak file you should put something like:
$O/pkg-daemon.stamp: daemon
$O/pkg-client.stamp: utilWith this configuration, a call to make pkg will leave the built packages
in the $P directory.
Makd supports testing generally by the special variables $(test) and
$(fasttest). You can add any custom target to this variables to be executed
when you use the corresponding test and fasttest targets.
Automatic unittest and integration tests support is added on top of that.
If you have a test script, you can easily add the target to run that script to
$(test) too (or $(fasttest)) and $(test) if it's really fast).
For example:
.PHONY: supertest
supertest:
./super-test.sh
test += supertestThen when you run make test all the unittests, integration tests and your
test will run.
Only unittest that live in the directory specified by the $(SRC) variable
and the $(INTEGRATIONTEST) directory (see Integration tests) are built
and run automatically, the unittest target will scan for all the files with
the .d suffix there.
All the unit tests are built using these extra options:
-unittest -debug=UnitTest -version=UnitTest
There are two different categories of unittest though: fast and slow. Tests
are assumed to be fast unless they are separated to a different file, with the
suffix _slowtest.d. Usually all the slow tests for module m should be
moved to m_slowtest.d, but this is just a convention.
The general unittest target is just an alias for the more specific target
allunittest and it will run all the unit tests (fast and slow). This target
is automatically added to the $(test) special variable, so they will be run
when using the test target too. On the other hand, the fastunittest
target will only run the fast unit tests, leaving the slow out, and is added to
the fasttest target.
Unit tests are compiled in a separate binary that imports all modules in the
project. By default, this binary will just have an empty main() function
and will let the D runtime to execute the tests by passing -unittest.
If Ocean is present as
a submodule, then ocean.core.UnitTestRunner will be imported instead.
If you want to import a custom module to run the unit tests, you can do so by
specifying the module via the TEST_RUNNER_MODULE variable. If you do this,
no main() function will be generated, so the module you are importing
should define it.
If you want to define a custom main() function, or put any other content
into the file generated to run the unit tests (importing all modules), you can
define TEST_RUNNER_MODULE as an empty variable and then put the contents
you want to add to the file in the TEST_RUNNER_STRING variable.
Bear in mind that unless you exclude files with a main() function (see
Skipping tests), you'll get link errors about having multiple definitions
for main(). To avoid this issue you should version out all the
main() functions in your project (both to produce project binaries or to
produce Integration tests):
version (UnitTest) {} else
void main()
{
// stuff
}Integration tests are expected to live in the $(INTEGRATIONTEST) directory
(integrationtest by default), and it is expected that each subdirectory
stores a separate test program with a main.d file as the entry point. So
the typical layout for the $(INTEGRATIONTEST)/ directory is:
$(INTEGRATIONTEST)/
test_1/
main.d
onemodule.d
test_2/
main.d
othermodule.d
The integrationtest target scan for those individual programs (specifically
for files with the pattern: $(INTEGRATIONTEST)/*/main.d) and builds them
and runs them.
It is also expected that the integration tests are slow, so by default they are
only added to the test target, but you can manually add them (all or just
a few) to the fasttest target too (fasttest += integrationtest should be
enough to add them all).
The $(TEST_FILTER_OUT) variable is used to exclude some tests. The contents
of this variable will always be applied to the list of files to use in the tests
through the Make $(filter-out) function. This means you can use a single
% as a wildcard. You should always use absolute paths (which can be easily
done by applying the prefix $C/ to files). Adding files to the
$(TEST_FILTER_OUT) variable should be done in the Build.mak file. Always
use +=, there might be other predefined modules to skip.
For Unit tests, you just have to add the individual files you want to exclude
from the tests. You can use a single % as a wildcard to exclude a whole
package for example:
TEST_FILTER_OUT += \
$C/src/brokenmodule.d \
$C/src/brokenpackage/%For Integration tests, you can only skip a full test program, to do that just
exclude the main.d for that program. For example:
TEST_FILTER_OUT += $C/integrationtest/brokenprog/main.dSome tests might need special flags for the unittest to compile, like when you need to link to external libraries.
For Unit tests you can add unittest specific flags by using the following syntax:
$O/%unittests: override LDFLAGS += -lglib-2.0This will link all the unittests to the glib-2.0 library, both fastunittest
and allunittest. To apply flags to an individual test use a more specific
target, for example:
$O/allunittests: override LDFLAGS += -lextraThis will link the extra library only to the full unit tests, but not to the fast ones.
If you want to run the tests using some special options of the unit test runner
(see build/last/*unittests -h for a list of supported options), you can use
the special variable UTFLAGS, for example:
make allunittest UTFLAGS="-v -s"
This will print all the executed tests and a summary at the end with the number of passed tests, failed tests, etc.
Some special options are passed automatically, for example if make -k is
used, the -k option will be passed to the unit test runner too, and if
make V=1 is used, the options -v -s will be passed to the unit test
runner.
For Integration tests the way to pass special flags is similar, but not the same. Use the following syntax:
$O/test-feature: override LDFLAGS += -lglib-2.0The targets for individual integration test programs are defined following this
pattern: $O/test-%. The previous example will link the program at
$(INTEGRATIONTEST)/feature/main.d against glib-2.0 as expected.
To pass flags to the test program execution, you can use the special variable
$(ITFLAGS). Unfortunately, unless you are running a specific integration
test, the only way to do this for individual suites is to write it in the makefile,
otherwise the same flags will be used to run all the integration tests.
To run the feature integration test with the flag --verbose, for example,
you can do this (pay attention to the .stamp suffix, it is necessary):
$O/test-feature.stamp: override ITFLAGS += --verboseIf you want to run all the integration test programs with the same flags, you can still use:
make integrationtest ITFLAGS=--verbose
Once you built and ran the unittests once, if you want, for some reason, repeat
the tests, you can just run the generated *unittests and test-*
programs. All the programs are built in the build/last/tmp directory ($O
more specifically).
A reason to run it again could be to use different command-line options (the
unit tests runner accepts a few, try build/last/tmp/allunittests -h for
help). For example, if you want to re-run the tests, but without stopping on the
first failure, use:
build/last/tmp/allunittests -k
This option is used automatically if you run make -k.
Remember to re-run make if you change any sources, the test programs need to
be re-compiled in that case!
Compiling using code coverage can be done by passing COV=1 to make. If
the D runtime supports the DRT_COV* environment variables (see list of
version below), the coverage reports will be put in the $(COVDIR) directory
($O/cov by default), otherwise they will be written in the top-level
directory.
Also by default the the coverage reports will be merged. To change this you can
override the $(COVMERGE) environment variable (1 to merge,
0 to overwrite). This also only works for the versions specified
below, previous versions will always overwrite the reports on each
run.
You should be careful about report merging, as unless you clean the reports
manually, they will be accumulated ad infinitum as there is no obvious point
where reports can be cleaned automatically (except for make clean of
course). There is a convenience target to just clean coverage reports:
clean-cov.
Merging and overrideable directory versions:
dmd: v2.078.0+