BuildExe “Script” example#

Basic Procedure#

Since we are writing our scripts in C++ we first need to compile our “script” to an executable, and then execute it to build targets.

usecase "build.helloworld.cpp" as build_cpp
usecase "compile.toml" as compile_toml
usecase "host_toolchain.toml" as host_toolchain_toml
usecase "./build.helloworld" as build_project_exe
usecase "build.toml" as build_toml

rectangle "./buildexe" as buildexe_exe
artifact "./hello_world" as hello_world_exe

build_cpp -right-> buildexe_exe
compile_toml -up-> buildexe_exe
host_toolchain_toml -up-> buildexe_exe
buildexe_exe -right-> build_project_exe
build_toml -up-> build_project_exe
build_project_exe -right-> hello_world_exe

Attention

Limitation of script mode

We need to compile our build “script” using a HOST toolchain. We cannot use a cross compiler here.

Helloworld “script” example#

  • Write your C++ “script”

  • Write your compile.toml file

  • Write your build.toml file

  • Invoke buildexe from the command line from the [workspace] folder
    • Pay attention to the root_dir and build_dir parameters set in your compile.toml and build.toml file.

    • These directories are relative to the directory from which you invoke buildexe

./buildexe --config compile.toml --config $BUILDCC_HOME/host/host_toolchain.toml
  • Your target will now be present in [build_dir]/[toolchain_name]/[target_name] (taken from build.toml and build.helloworld.cpp)

Directory structure#

@startmindmap
* [workspace]
** [src]
*** main.cpp
** build.helloworld.cpp
** compile.toml
** build.toml
@endmindmap

Write your C++ “script”#

From the “script” below we can see that we have a few lines of boilerplate

  • Setup args

  • Setup register (pre and post callback requirements)

We then setup our main toolchain-target pairs. Highlighted below

  • Specify your toolchain
    • Verify the toolchain existance on your machine by using the .Verify API

    • If multiple similar toolchains are detected (due to multiple installations), the first found toolchain is picked

    • You can pass in the VerifyToolchainConfig to narrow down your search and verification.

  • Specify your compatible target
    • Every specific target is meant to use a specific target.

    • For example: ExecutableTarget_gcc specialized target can use the Toolchain_gcc specialized toolchain but not Toolchain_msvc.

  • Use the Register .Build API. We use callbacks here to avoid cluttering our int main function.
    • arg_gcc.state contains our build and test values passed in from build.toml (see below). The .Build API conditionally selects the target at run time.

    • IMPORTANT Please do not forget to invoke the Target .Build API. This API registers the various CompileCommandTasks and LinkCommandTasks.

    • IMPORTANT In line with the above statement, Once the Target .Build API has been executed (tasks have been registered), do not attempt to add more information to the Targets. Internally the .Build API locks the target from accepting further input and any attempt to do so will std::terminate your program (this is by design).

build.helloworld.cpp#
 1#include "buildcc.h"
 2
 3using namespace buildcc;
 4
 5void clean_cb();
 6// All specialized targets derive from BaseTarget
 7void hello_world_build_cb(BaseTarget & target);
 8
 9int main(int argc, char ** argv) {
10    // Setup your args
11    ArgToolchain arg_gcc;
12
13    Args::Init()
14        .AddToolchain("gcc", "GCC toolchain", arg_gcc)
15        .Parse(argc, argv);
16
17    // Register
18    Reg::Init();
19
20    // Pre build steps
21    // for example. clean your environment
22    Reg::Call(Args::Clean()).Func(clean_cb);
23
24    // Build steps
25    // Main setup
26    Toolchain_gcc gcc;
27    ExecutableTarget_gcc hello_world("hello_world", gcc, "");
28
29    Reg::Toolchain(arg_gcc.state)
30        .Func([&](){ gcc.Verify() })
31        .Build(hello_world_build_cb, hello_world);
32
33    // Build your targets
34    Reg::Run();
35
36    // Post build steps
37    // for example. clang compile commands database
38    plugin::ClangCompileCommands({&hello_world}).Generate();
39
40    return 0;
41}
42
43void clean_cb() {
44    fs::remove_all(env::get_project_build_dir());
45}
46
47void hello_world_build_cb(BaseTarget & target) {
48    // Add your source
49    target.AddSource("src/main.cpp");
50
51    // Initializes the target build tasks
52    target.Build();
53}

Write your compile.toml file#

compile.toml#
 1# Settings
 2root_dir = ""
 3build_dir = "_build_internal"
 4loglevel = "info"
 5clean = false
 6
 7# BuildExe run mode
 8mode = "script"
 9
10# Target information
11name = "build.helloworld"
12type = "executable"
13relative_to_root = ""
14srcs = ["build.helloworld.cpp"]
15
16[script]
17configs = ["build.toml"]
  • root_dir tells BuildExe your project root directory relative from where it is invoked and build_dir tells BuildExe that the built artifacts should be inserted in this directory relative from where it is invoked.

  • clean deletes your build_dir completely for a fresh setup.

  • mode consists of script and immediate mode. See the Basic Procedure uml diagrams for a better understanding of the differences and purpose.
  • Setup your target information
    • name of your compiled “script” executable

    • type MUST always be executable in script mode

    • relative_to_root is a QOL feature to point to a path inside your root where the build “scripts” reside.

    • srcs and equivalent are files that you want to compile. Please see Compile Options for BuildExe for a full list of target options and inputs for script mode

  • [script] submodule
    • configs are .toml files passed to our compiled “script” executable. Please see Build Options for “scripts” for a full list of default build options.

    • The values inside configs are converted to --config [file].toml --config [file2].toml and so on and passed with the generated executable.

    • In this example: ./build.helloworld --config build.toml is run which generates your targets.

Write your build.toml file#

build.toml#
 1# Root
 2root_dir = ""
 3build_dir = "_build"
 4loglevel = "debug"
 5
 6# Project
 7clean = false
 8
 9# Toolchain
10[toolchain.gcc]
11build = true
12test = false
  • Please see the .cpp example above and correlate with these options.

  • root_dir tells BuildExe your project root directory relative from where it is invoked and build_dir tells BuildExe that the built artifacts should be inserted in this directory relative from where it is invoked.

  • clean invokes your clean_cb which determines how your build must be cleaned. In this example we delete the build_dir for a fresh setup.

  • [toolchain.gcc] submodule
    • This is a nested submodule of toolchain -> gcc -> --build, --test options and so on.

    • The naming convention follows toolchain.[name] provided when using the .AddToolchain API.

    • In our example: args.AddToolchain("gcc", "GCC toolchain", arg_gcc);

    • The build and test values are used by the Register module.

    • In our example arg_gcc.state.build and arg_gcc.state.test

    • REASONING The reason why this has been done is because Buildcc allows your to mix multiple toolchains in a single script. We can now conditionally (at run time) choose the toolchains with which we would want to compile our targets.