Important: Make sure that you have read and understood the previous example Advanced C++ Project first!
This example demonstrates how to import and build external libraries for using them in the advanced C++ project.
Key takeaways from this example:
- Externals can be imported via the multi-repository configuration
- Externals can be built as native targets
- Externals can be built as foreign targets, using foreign build tools
The repository structure remains almost identical to the previous advanced C++
project, with one exception: a new target root etc/extern
was added, which
contains the targets for building external libraries.
Looking at the multi-repository configuration
etc/repos.json
, you can see that the additional root
(root/extern
) was used to create the repositories
fmtlib
: the modern formatter librarygtest
: the Google testing and mocking framework
The main repository example
imports both of them via the open names fmtlib
and gtest
, which themselves import rules/toolchain
(rules that include the
toolchain definition) via the open name rules
.
┌─────────────────┐ rules ┌─────────────────┐
│ example |<─────────┤ rules/flags |<──── root/flags
└─────────────────┘ └─────────────────┘
▲ {fmtlib,gtest} ▲ toolchain
| |
┌────────┴────────┐ rules ┌────────┴────────┐
│ {fmtlib,gtest} |<─────────┤ rules/toolchain |<──── root/toolchain
└─────────────────┘ └─────────────────┘
▲
|
root/extern
The build descriptions for external libraries are located in the targets root
etc/extern
. This targets root is an overlay to the source directory of the
repository. Therefore, the containing *.TARGETS
files can reference source
files as if they are located in the same directory, mimicking the same directory
structure.
A native target uses Mustbuild for building. Although the repository fmtlib
comes with its own build system, the library is small enough to provide a
native build description for it by using the native rule
["CC","library"]
.
The native target fmt
is described in
etc/extern/fmtlib.TARGETS
.
// Native target "fmt"
fmt: {
// Use rule ["CC", "library"] from binding "rules"
type: ref_ext('rules', 'CC', 'library'),
// We want to read variable 'DEBUG'.
arguments_config: 'DEBUG',
// Library name: libfmt.a
name: 'fmt',
// Library type: static
shared: false,
// Public headers
hdrs: [ref('include', 'public_headers')],
// Source files
srcs: ['src/os.cc', 'src/format.cc'],
// Private compile flags (depends on 'DEBUG')
'private-cflags': select(var('DEBUG'),
['-O0', '-g'],
['-O2', '-DNDEBUG']),
},
The target directly references the external source files src/os.cc
and
src/format.cc
. Additional local compile flags are set depending on the
variable DEBUG
.
To build the target, you have to set the main repository to fmtlib
, because
this target is technically not part of the example
repository.
$ must --main fmtlib build fmt
INFO: Found 5 repositories to set up
INFO: Requested target is [["@","fmtlib","","fmt"],{}]
INFO: Discovered 3 actions, 1 trees, 0 blobs
INFO: Processed 3 actions, 0 cache hits.
INFO: Artifacts built, logical paths are:
libfmt.a [123b5f9d93f7deaa9cb444009b6ef98325cb4bc7:266088:f]
(1 runfiles omitted.)
In this example project, the target greet
(defined in
libs/greet/TARGETS
) uses the external library fmt
if
variable USE_FMTLIB
is true. This becomes evident when building the top-level
target LIBS
, which installs libgreet
including its dependency libfmt.a
.
$ must build LIBS -D'{"USE_FMTLIB":true}'
INFO: Found 9 repositories to set up
INFO: Requested target is [["@","example","","LIBS"],{"USE_FMTLIB":true}]
INFO: Discovered 5 actions, 2 trees, 1 blobs
INFO: Processed 5 actions, 3 cache hits.
INFO: Artifacts built, logical paths are:
include/greet/greet.hpp [cff6acf4df51e55e9283e956840a772616ce05d8:133:f]
lib/greet/libgreet.a [abf39695fcab74ba19b3684d5a52716277055033:2104:f]
lib/libfmt.a [123b5f9d93f7deaa9cb444009b6ef98325cb4bc7:266088:f]
share/pkgconfig/greet.pc [a431bb34beebef273bac537c2e18a3377488842a:234:f]
Note that when building with
{"BUILD_SHARED":true}
, the librarylibfmt.a
disappears, because all oflibgreet
's private dependencies are eaten up by the shared object.
Foreign targets are not built directly by Mustbuild. Instead, Mustbuild calls a foreign build tool and collects the produced artifacts. Currently supported foreign build rules are:
["CC/foreign/make","library"]
for building with GNU Make["CC/foreign/cmake","library"]
for building with CMake["CC/foreign/shell","library"]
for building using custom shell commands
The complexity of the gtest
repository justifies using CMake. The foreign
target gtest_main
is described in
etc/extern/gtest.TARGETS
.
// Foreign target "gtest_main"
gtest_main: {
// Use rule ["CC/foreign/cmake", "library"] from binding "rules"
type: ref_ext('rules', 'CC/foreign/cmake', 'library'),
// We want to read variable 'DEBUG'.
arguments_config: 'DEBUG',
// Library name
name: 'gtest_main',
// Project directory (includes CMakeLists.txt)
project: [tree('.')],
// CMake defines (-Dxxx)
defines: _gtest_defines('DEBUG'),
// Produced library files
out_libs: 'libgtest_main.a',
// Dependencies
deps: ['gtest'],
},
The target directly references the entire source tree tree('.')
, including all
CMakeLists.txt
files. Depending on variable DEBUG
, the build option
CMAKE_BUILD_TYPE
is set to either Debug
or Release
.
To build the target, you have to set the main repository to gtest
, because
this target is technically not part of the example
repository.
Note that for building this target,
cmake
must be available inPATH
. The settings can be adjusted inetc/toolchain/CC/foreign/TARGETS
.
$ must --main gtest build gtest_main
INFO: Found 5 repositories to set up
INFO: Requested target is [["@","gtest","","gtest_main"],{}]
INFO: Discovered 5 actions, 0 trees, 1 blobs
INFO: Processed 2 actions, 0 cache hits, 3 omitted.
INFO: Artifacts built, logical paths are:
libgtest_main.a [3ab2c3a25eb17f3fab2ad0960357a0ac4fe41dc7:3086:f]
In this example project, the target test_libgreet
(defined in
test/TARGETS
) uses the external library gtest_main
. This
is clearly visible when looking at the produced test report.
$ must build TESTS -P test_libgreet/stdout
INFO: Found 9 repositories to set up
INFO: Requested target is [["@","example","","TESTS"],{}]
INFO: Target tainted ["test"].
INFO: Discovered 13 actions, 6 trees, 3 blobs
INFO: Processed 13 actions, 11 cache hits.
INFO: Artifacts built, logical paths are:
test_helloworld [4f0b6c7d0f4069834f98ad6522432921fa15c80e:177:t]
test_libgreet [1021990d001bb43b3db8c032df4943160b8d2a7a:177:t]
Running main() from /home/user/.cache/just/protocol-dependent/generation-0/exec_root/a5484da2d0ea457cdfc01ea0b45ee4464cdc20fe.6404-127520892503616/build_root/source/googletest/src/gtest_main.cc
[==========] Running 1 test from 1 test suite.
[----------] Global test environment set-up.
[----------] 1 test from GreetTest
[ RUN ] GreetTest.output
[ OK ] GreetTest.output (0 ms)
[----------] 1 test from GreetTest (0 ms total)
[----------] Global test environment tear-down
[==========] 1 test from 1 test suite ran. (0 ms total)
[ PASSED ] 1 test.
For code analysis (intellisense, linters, etc.), all external headers that are included by the main project must be accessible. As those are only visible to the build (within build actions), a target needs to be defined that installs those headers for you.
In the top-level TARGETS
file, the target DEV
uses rule
["CC","install-with-deps"]
to install the public headers (hdrs-only
) of all externals.
// Installed headers of external libraries (for development/intellisense)
DEV: {
type: ref_ext('rules', 'CC', 'install-with-deps'),
'hdrs-only': true,
targets: [
ref_ext('fmtlib', '', 'fmt'),
ref_ext('gtest', '', 'gtest_main'),
],
},
This target builds all externals and installs their public headers to a location of your choice. This location must be added to the search paths of your IDE or linter.
$ must install DEV -o .ext/include
INFO: Found 9 repositories to set up
INFO: Requested target is [["@","example","","DEV"],{}]
INFO: Discovered 8 actions, 1 trees, 1 blobs
INFO: Processed 2 actions, 2 cache hits, 6 omitted.
INFO: Artifacts can be found in:
.ext/include/fmt [a9d20ba6a8bbb01b278b596fe5e8157b03082a97:461:t]
.ext/include/gtest [5225df8651dadc1506b58cdda5df06be7375b986:560:t]
# add .ext/include to your IDE's search paths
External libraries are fully supported for installing debug binaries. The rule
["CC","install-with-deps"]
will also collect all related source and header files from external libraries
when installing with {"DEBUG":true}
. For debugging your code, just step into
the install directory and run your binary with gdb
.
$ must install APPS -D'{"USE_FMTLIB":true,"DEBUG":true}' -o out
$ cd out; gdb ./bin/helloworld