Hybrid#

Real world tests combining multiple compilers

Note

In the Hybrid examples we use the Args and Register module to abstract away Taskflow and CLI11 APIs. To see the lowlevel usage please see the lowlevel [compiler] tests.

Single Boilerplate#

We are only using a single Toolchain - Target pair

 1int main(int argc, char ** argv) {
 2    // Register your [toolchain.{name}]
 3    // In this case it will be [toolchain.gcc]
 4    ArgToolchain arg_gcc;
 5
 6    // Args module to get data from the command line or .toml file passed in through --config
 7    Args::Init()
 8        .AddToolchain("gcc", "Generic gcc toolchain", arg_gcc)
 9        .Parse(argc, argv);
10
11    // Register module
12    Reg::Init();
13    Reg::Call(Args::Clean()).Func(clean_cb);
14
15    // TODO, Write your target builds here
16    // See examples below
17}
18
19static void clean_cb() {
20    env::log_info(EXE, fmt::format("Cleaning {}", env::get_project_build_dir()));
21    fs::remove_all(env::get_project_build_dir());
22}
 1# Required parameters
 2root_dir = "" # build executable meant to be invoked from the current directory
 3build_dir = "_build" # Creates this directory relative to where you invoke your build executable
 4
 5# Optional parameters
 6loglevel = "trace" # trace, debug, info, warning, critical
 7clean = true # calls clean_cb if true, user specifies how their project must be cleaned
 8
 9# Toolchain
10# Valid configurations are
11# build = false, test = false
12# build = true, test = false
13# build = true, test = true
14[toolchain.gcc]
15build = true
16test = true

Single#

Compile a single source with a single GCC compiler.

 1int main(int argc, char ** argv) {
 2    // See Single Boilerplate
 3
 4    Toolchain_gcc gcc;
 5    ExecutableTarget_gcc hello_world("hello_world", gcc, "");
 6
 7    // Select your builds and tests using the .toml files
 8    Reg::Toolchain(arg_gcc.state)
 9        .Func([&]() { gcc.Verify(); })
10        .Build(arg_gcc.state, hello_world_build_cb, hello_world)
11        .Test(arg_gcc.state, "{executable}", hello_world);
12
13    // Build and Test Target
14    Reg::Run();
15}
16
17static void hello_world_build_cb(BaseTarget &target) {
18    target.AddSource("main.cpp", "src");
19    target.Build();
20}

Multiple Boilerplate#

We are using multiple Toolchain - Target pairs

 1int main(int argc, char ** argv) {
 2    // Register your [toolchain.{name}]
 3    // In this case it will be [toolchain.gcc] and [toolchain.msvc]
 4    ArgToolchain arg_gcc;
 5    ArgToolchain arg_msvc;
 6
 7    // Args module to get data from the command line or .toml file passed in through --config
 8    Args::Init()
 9        .AddToolchain("gcc", "Generic gcc toolchain", arg_gcc)
10        .AddToolchain("msvc", "Generic msvc toolchain", arg_msvc)
11        .Parse(argc, argv);
12    // NOTE, You can add more toolchains here as per your project requirement
13
14    // Register module
15    Reg::Init();
16    Reg::Call(Args::Clean()).Func(clean_cb);
17
18    // TODO, Write your target builds here
19    // See examples below
20}
21
22static void clean_cb() {
23    env::log_info(EXE, fmt::format("Cleaning {}", env::get_project_build_dir()));
24    fs::remove_all(env::get_project_build_dir());
25}
 1# Required parameters
 2root_dir = "" # build executable meant to be invoked from the current directory
 3build_dir = "_build" # Creates this directory relative to where you invoke your build executable
 4
 5# Optional parameters
 6loglevel = "trace" # trace, debug, info, warning, critical
 7clean = true # calls clean_cb if true, user specifies how their project must be cleaned
 8
 9# Toolchain
10# Valid configurations are
11# build = false, test = false
12# build = true, test = false
13# build = true, test = true
14[toolchain.gcc]
15build = true
16test = true
17
18# If we are building on Windows make these true
19[toolchain.msvc]
20build = false
21test = false

Note

On Windows, make sure you install the Build Tools properly and invoke vcvarsall.bat amd64 or equivalent from the command line to activate your toolchain.

Simple#

Similar to lowlevel GCC Flags example for both the GCC and MSVC compiler

 1int main(int argc, char ** argv) {
 2    // See Multiple Boilerplate
 3
 4    Toolchain_gcc gcc;
 5    ExecutableTarget_gcc g_cppflags("cppflags", gcc, "files");
 6    ExecutableTarget_gcc g_cflags("cflags", gcc, "files");
 7    // Select your builds and tests using the .toml files
 8    Reg::Toolchain(arg_gcc.state)
 9        .Func([&](){ gcc.Verify(); })
10        .Build(cppflags_build_cb, g_cppflags)
11        .Build(cflags_build_cb, g_cflags)
12        .Test("{executable}", g_cppflags)
13        .Test("{executable}", g_cflags);
14
15    Toolchain_msvc msvc;
16    ExecutableTarget_msvc m_cppflags("cppflags", msvc, "files");
17    ExecutableTarget_msvc m_cflags("cflags", msvc, "files");
18    Reg::Toolchain(arg_msvc.state)
19        .Func([&](){ msvc.Verify(); })
20        .Build(cppflags_build_cb, m_cppflags)
21        .Build(cflags_build_cb, m_cflags)
22        .Test("{executable}", m_cppflags)
23        .Test("{executable}", m_cflags);
24
25    // Build and Test target
26    Reg::Run();
27
28    return 0;
29}
30
31static void cppflags_build_cb(BaseTarget &cppflags) {
32    cppflags.AddSource("main.cpp", "src");
33    cppflags.AddSource("src/random.cpp");
34    cppflags.AddIncludeDir("include", true);
35
36    // Toolchain specific code goes here
37    switch (cppflags.GetToolchain().GetId()) {
38    case ToolchainId::Gcc: {
39        cppflags.AddPreprocessorFlag("-DRANDOM=1");
40        cppflags.AddCppCompileFlag("-Wall");
41        cppflags.AddCppCompileFlag("-Werror");
42        cppflags.AddLinkFlag("-lm");
43        break;
44    }
45    case ToolchainId::Msvc: {
46        cppflags.AddPreprocessorFlag("/DRANDOM=1");
47        cppflags.AddCppCompileFlag("/W4");
48        break;
49    }
50    default:
51        break;
52    }
53
54    cppflags.Build();
55}
56
57static void cflags_build_cb(BaseTarget &cflags) {
58    cflags.AddSource("main.c", "src");
59
60    // Toolchain specific code goes here
61    switch (cflags.GetToolchain().GetId()) {
62    case ToolchainId::Gcc: {
63        cflags.AddPreprocessorFlag("-DRANDOM=1");
64        cflags.AddCCompileFlag("-Wall");
65        cflags.AddCCompileFlag("-Werror");
66        cflags.AddLinkFlag("-lm");
67        break;
68    }
69    case ToolchainId::Msvc: {
70        cflags.AddPreprocessorFlag("/DRANDOM=1");
71        cflags.AddCCompileFlag("/W4");
72        break;
73    }
74    default:
75        break;
76    }
77
78    cflags.Build();
79}

Foolib#

For library developers

Scenario

Say suppose you are a library developer who has created an amazing Foo library. How would you easily specifiy your project build to be used by yourself and end users?

Solution: Create Header / Source segregations. For example. build.foo.h and build.foo.cpp End users can now create their own build.[project].cpp file and compile build.foo.cpp along with their source and use appropriate APIs are provided by your files.

Depending on the complexity of your project the library developer can provide multiple APIs with different options that need to be selected at run time / compile time.

Header

1#pragma once
2
3#include "buildcc.h"
4
5void fooTarget(buildcc::BaseTarget &target, const fs::path &relative_path);

Source

1#include "build.foo.h"
2
3void fooTarget(buildcc::BaseTarget &target, const fs::path &relative_path) {
4    target.AddSource(relative_path / "src/foo.cpp");
5    target.AddIncludeDir(relative_path / "src", true);
6}

External Lib#

For end users consuming third party libraries

Scenario

User would like to use the third party library Foo in their codebase. The Foo library resides in a different directory as visualized below.

@startmindmap
* [folder]
** external_lib
*** [project_root]
** foolib
@endmindmap

 1#include "build.foo.h"
 2
 3int main(int argc, char ** argv) {
 4    // See Multiple Boilerplate
 5
 6    // Build steps
 7    Toolchain_gcc gcc;
 8    Toolchain_msvc msvc;
 9
10    ExecutableTarget_gcc g_foolib("foolib", gcc, TargetEnv("..."));
11    ExecutableTarget_msvc m_foolib("foolib", msvc, TargetEnv("...");
12
13    Reg::Toolchain(arg_gcc.state)
14        .Build(foolib_build_cb, g_foolib);
15
16    Reg::Toolchain(arg_msvc.state)
17        .Build(foolib_build_cb, m_foolib);
18
19    Reg::Run();
20}
21
22static void foolib_build_cb(BaseTarget &target) {
23    fooTarget(target, "../foolib");
24    target.AddSource("main.cpp");
25    target.Build();
26}

Custom Target#

For super customized targets and toolchains

 1int main(int argc, char ** argv) {
 2
 3    ArgToolchain arg_gcc;
 4    ArgToolchain arg_msvc;
 5    ArgToolchain toolchain_clang_gnu;
 6    ArgTarget target_clang_gnu;
 7
 8    Args::Init()
 9        .AddToolchain("gcc", "Generic gcc toolchain", arg_gcc)
10        .AddToolchain("msvc", "Generic msvc toolchain", arg_msvc)
11        .AddToolchain("clang_gnu", "Clang GNU toolchain", toolchain_clang_gnu)
12        .AddTarget("clang_gnu", "Clang GNU target", target_clang_gnu)
13        .Parse(argc, argv);
14
15    // Additional boilerplate
16
17    // Supplied at compile time
18    Toolchain_gcc gcc;
19    Toolchain_msvc msvc;
20    // Get custom toolchain from the command line, supplied at run time
21    auto &clang = toolchain_clang_gnu;
22    clang.SetToolchainInfoFunc(GlobalToolchainInfo::Get(clang.id));
23
24    ExecutableTarget_gcc g_foolib("foolib", gcc, "");
25    ExecutableTarget_msvc m_foolib("foolib", msvc, "");
26    // Get compile_command and link_command from the command line
27    Target_custom c_foolib("CFoolib.exe", TargetType::Executable, clang, "", target_clang_gnu.GetTargetConfig());
28
29    Reg::Toolchain(arg_gcc.state)
30        .Build(foolib_build_cb, g_foolib);
31    Reg::Toolchain(arg_msvc.state)
32        .Build(foolib_build_cb, m_foolib);
33    Reg::Toolchain(toolchain_clang_gnu.state)
34        .Build( foolib_build_cb, c_foolib);
35
36    // Build targets
37    Reg::Run();
38}
39
40static void foolib_build_cb(BaseTarget &target) {
41    target.AddSource("src/foo.cpp");
42    target.AddIncludeDir("src", true);
43    target.AddSource("main.cpp");
44    target.Build();
45}
 1# See Multiple boilerplate .toml file
 2
 3# Custom toolchain added here
 4[toolchain.clang_gnu]
 5build = true
 6test = true
 7
 8# Custom toolchain added here, supplied during runtime
 9id = "Clang"
10name = "clang_gnu"
11asm_compiler = "llvm-as"
12c_compiler = "clang"
13cpp_compiler = "clang++"
14archiver = "llvm-ar"
15linker = "ld"
16
17# Custom target added here
18[target.clang_gnu]
19compile_command = "{compiler} {preprocessor_flags} {include_dirs} {compile_flags} -o {output} -c {input}"
20link_command = "{cpp_compiler} {link_flags} {compiled_sources} -o {output} {lib_dirs} {lib_deps}"

PrecompileHeader#

Precompile header usage with GCC and MSVC compilers

 1// Modified Lowlevel GCC Flags example for PCH
 2
 3static void cppflags_build_cb(BaseTarget &cppflags) {
 4cppflags.AddSource("main.cpp", "src");
 5cppflags.AddSource("random.cpp", "src");
 6cppflags.AddIncludeDir("include", true);
 7
 8cppflags.AddPch("pch/pch_cpp.h");
 9cppflags.AddPch("pch/pch_c.h");
10cppflags.AddIncludeDir("pch", true);
11
12// Toolchain specific code goes here
13switch (cppflags.GetToolchain().GetId()) {
14case ToolchainId::Gcc: {
15    cppflags.AddPreprocessorFlag("-DRANDOM=1");
16    cppflags.AddCppCompileFlag("-Wall");
17    cppflags.AddCppCompileFlag("-Werror");
18    cppflags.AddLinkFlag("-lm");
19    break;
20}
21case ToolchainId::Msvc: {
22    cppflags.AddPreprocessorFlag("/DRANDOM=1");
23    cppflags.AddCppCompileFlag("/W4");
24    break;
25}
26default:
27    break;
28}
29
30cppflags.Build();
31}
32
33static void cflags_build_cb(BaseTarget &cflags) {
34cflags.AddSource("main.c", "src");
35
36cflags.AddPch("pch/pch_c.h");
37cflags.AddIncludeDir("pch", false);
38cflags.AddHeader("pch/pch_c.h");
39
40// Toolchain specific code goes here
41switch (cflags.GetToolchain().GetId()) {
42case ToolchainId::Gcc: {
43    cflags.AddPreprocessorFlag("-DRANDOM=1");
44    cflags.AddCCompileFlag("-Wall");
45    cflags.AddCCompileFlag("-Werror");
46    cflags.AddLinkFlag("-lm");
47    break;
48}
49case ToolchainId::Msvc: {
50    cflags.AddPreprocessorFlag("/DRANDOM=1");
51    cflags.AddCCompileFlag("/W4");
52    break;
53}
54default:
55    break;
56}
57
58cflags.Build();
59}

Dependency Chaining#

Chain Generators and Targets using the Register module

 1int main(int argc, char ** argv) {
 2    // See Multiple Boilerplate
 3
 4    Toolchain_gcc gcc;
 5    Toolchain_msvc msvc;
 6
 7    BaseGenerator cpp_generator("cpp_generator", "");
 8    Reg::Call().Build(cpp_generator_cb, cpp_generator);
 9
10    ExecutableTarget_gcc g_cpptarget("cpptarget", gcc, "");
11    Reg::Toolchain(arg_gcc.state)
12        .Func([&](){ gcc.Verify(); })
13        .Build(cpp_target_cb, g_cpptarget, cpp_generator)
14        .Dep(g_cpptarget, cpp_generator);
15
16    ExecutableTarget_msvc m_cpptarget("cpptarget", msvc, "");
17    Reg::Toolchain(arg_msvc.state)
18        .Func([&](){ msvc.Verify(); })
19        .Build(cpp_target_cb, m_cpptarget, cpp_generator)
20        .Dep(m_cpptarget, cpp_generator);
21}
22
23// Use a python generator to create main.cpp
24static void cpp_generator_cb(BaseGenerator &generator) {
25    generator.AddOutput("{gen_build_dir}/main.cpp", "main_cpp");
26    generator.AddCommand("python3 {gen_root_dir}/python/gen.py --source_type cpp "
27                        "--destination {main_cpp}");
28    generator.Build();
29}
30
31// Use main.cpp generated by the python script to compile your target
32static void cpp_target_cb(BaseTarget &cpptarget,
33                      const BaseGenerator &cpp_generator) {
34    const fs::path main_cpp =
35        fs::path(cpp_generator.GetValueByIdentifier("main_cpp"))
36            .lexically_relative(env::get_project_root_dir());
37    cpptarget.AddSource(main_cpp);
38    cpptarget.Build();
39}

Target Info#

  • Target Info usage to store Target specific information

  • Example usage for Header Only targets, however it can store information for all Target inputs

  • Common information used between multiple targets can be stored into a TargetInfo instance

 1int main(int argc, char ** argv) {
 2    // See Multiple boilerplate
 3
 4    Toolchain_gcc gcc;
 5    Toolchain_msvc msvc;
 6
 7    // TargetInfo
 8    TargetInfo g_genericadd_ho(gcc, "files");
 9    ExecutableTarget_gcc g_genericadd1("generic_add_1", gcc, "files");
10    Reg::Toolchain(arg_gcc.state)
11        .Func(genericadd_ho_cb, g_genericadd_ho)
12        .Build(genericadd1_build_cb, g_genericadd1, g_genericadd_ho);
13
14    TargetInfo m_genericadd_ho(msvc, "files");
15    ExecutableTarget_msvc m_genericadd1("generic_add_1", msvc, "files");
16    Reg::Toolchain(arg_msvc.state)
17        .Func(genericadd_ho_cb, m_genericadd_ho)
18        .Build(genericadd1_build_cb, m_genericadd1, m_genericadd_ho);
19}
20
21// HO library contains include dirs and header files which are copied into executable target
22static void genericadd1_build_cb(BaseTarget &genericadd,
23                                const TargetInfo &genericadd_ho) {
24    genericadd.AddSource("src/main1.cpp");
25    genericadd.Copy(genericadd_ho, {
26                                        SyncOption::IncludeDirs,
27                                        SyncOption::HeaderFiles,
28                                    });
29    genericadd.Build();
30}