BuildExe as a Package Manager#

When we are compiling our “script” to an executable we can also add additional library build files which define how the library is built.

The procedure is similar to git cloning the library to the ENV{BUILDCC_HOME}/libs folder using the libs options in buildexe.

Please see BuildExe Setup to setup your libs folder appropriately.

Basic Procedure#

usecase "build.user_project.cpp" as build_cpp
usecase "libs/library/build.library.h" as build_lib_header
usecase "libs/library/build.library.cpp" as build_lib_source

usecase "compile.toml" as compile_toml
usecase "host_toolchain.toml" as host_toolchain_toml
rectangle "./build.user_project" as build_project_exe
usecase "build.toml" as build_toml

rectangle "./buildexe" as buildexe_exe

artifact "library" as library
artifact "./hello_world" as hello_world_exe

build_cpp -right-> buildexe_exe
build_lib_header -right-> buildexe_exe
build_lib_source -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

library -up-> hello_world_exe

Helloworld “fmtlib” example#

  • Git clone the fmt library into your ENV{BUILDCC_HOME}/libs folder

  • Run buildexe libs --help-all.
    • You should automatically see the library folder name pop up under the libs submodule.

    • In this case it will be the fmt option.

1script
2    Options:
3        --configs TEXT ...          Config files for script mode
4
5libs
6    Libraries
7    Options:
8        --fmt TEXT ...              fmt library
  • Since we want to use the fmt library in our project we can now write our compile.toml file as given below. (See highlighted lines)

  • We then write our “script”, include the fmt build header file and define our targets and dependencies.

  • Lastly we invoke buildexe to build our project

buildexe --config compile.toml --config $BUILDCC_HOME/host/host_toolchain.toml

Directory structure#

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

Write your fmtlib build files#

Note

This process might seem like a hassle. But please note that fmtlib does not currently have support for BuildCC like build files and it must be provided by the user.

build.fmt.h#
 1#pragma once
 2
 3#include "buildcc.h"
 4
 5using namespace buildcc;
 6
 7/**
 8* @brief User configurable options
 9* default_flags: Adds default preprocessor, compile and link flags to the fmt
10* library if true. If false these would need to be provided by the user.
11*/
12struct FmtConfig {
13    bool default_flags{true};
14    // NOTE, Add more options here as required to customize your fmtlib build
15};
16
17/**
18* @brief Build the libfmt static or dynamic library
19*
20* @param target Initialized specialized library target
21* @param config See FmtConfig above
22*/
23void build_fmt_cb(BaseTarget& target, const FmtConfig& config = FmtConfig());
24
25/**
26* @brief Information for fmt header only library
27*
28* @param target_info Holds the include dirs, headers and preprocessor flag
29* information
30*/
31void build_fmt_ho_cb(TargetInfo& target_info);
build.fmt.cpp#
 1#include "build.fmt.h"
 2
 3void build_fmt_cb(BaseTarget& target, const FmtConfig& config) {
 4    target.AddSource("src/os.cc");
 5    target.AddSource("src/format.cc");
 6    target.AddIncludeDir("include", false);
 7    target.GlobHeaders("include/fmt");
 8
 9    // Toolchain specific flags added
10    // if default_flags == true
11    if (config.default_flags) {
12        switch (target.GetToolchain().GetId()) {
13        case ToolchainId::Gcc:
14            target.AddCppCompileFlag("-std=c++11");
15            break;
16        case ToolchainId::MinGW:
17            target.AddCppCompileFlag("-std=c++11");
18            break;
19        case ToolchainId::Msvc:
20            target.AddCppCompileFlag("/std:c++11");
21            break;
22        default:
23            break;
24        }
25    }
26
27    // Register your fmt lib tasks
28    target.Build();
29}
30
31void build_fmt_ho_cb(TargetInfo& target_info) {
32    target_info.AddIncludeDir("include", false);
33    target_info.GlobHeaders("include/fmt");
34    target_info.AddPreprocessorFlag("-DFMT_HEADER_ONLY=1");
35}

Write your C++ “script”#

Core build setup is highlighted below

  • On line 4 we include our build.fmt.h include file. See compile.toml libs submodule to correlate

  • On line 8 we include the buildexe_lib_dirs.h include file. This is a generated include file which contains the absolute paths of the library folders.
    • Access is through BuildExeLibDir::[lib_folder_name]

    • This is the reason why we need to make sure that our git cloned library folder name is also a valid C++ variable name.

  • On line 40 we point to the absolute fmt libs folder path for the sources and redirect the output to our env::get_project_build_dir() / "fmt" folder.
    • In this way we can safely use out of root projects and redirect the output files to our build location

    • There are other input source -> output object redirect options through additional APIs.

  • On line 43 and 44 we directly use our fmtlib build APIs to define how fmtlib should be built

  • On line 47 and 48 we define our Hello World executable target
    • See main.cpp below for fmtlib hello world example

    • See hello_world_build_cb for build steps

  • On line 79 hello_world_build_cb in additional to compiling our main.cpp file
    • We need to link our compiled fmt_lib using the AddLibDep API

    • We also insert the fmt_lib include dirs to the hello world target since we need to #include "fmt/format.h" in main.cpp

  • On line 52 we register a dependency of fmt_lib on hello_world.
    • This guarantees that the fmt library will be built before the hello world executable.

    • This is essential because we need to link fmtlib with our hello world executable.

 1#include "buildcc.h"
 2
 3// Included through libs
 4#include "build.fmt.h"
 5
 6// Generated by BuildCC
 7// See the `_build_internal` directory
 8#include "buildexe_lib_dirs.h"
 9
10using namespace buildcc;
11
12// Function Prototypes
13static void clean_cb();
14static void hello_world_build_cb(BaseTarget &target, BaseTarget &fmt_lib);
15
16int main(int argc, char **argv) {
17    // Get arguments
18    ArgToolchain arg_gcc;
19
20    Args::Init()
21        .AddToolchain("gcc", "Generic gcc toolchain", arg_gcc)
22        .Parse(argc, argv);
23
24    // Initialize your environment
25    Reg::Init();
26
27    // Pre-build steps
28    Reg::Call(Args::Clean().Func(clean_cb);
29
30    // Build steps
31    // Explicit toolchain - target pairs
32    Toolchain_gcc gcc;
33
34    // Setup your [Library]Target_[toolchain] fmtlib instance
35    // Update your TargetEnv to point to `BuildExeLibDir::fmt` folder
36    // The generated build files will go into your current `project_build_dir / fmt` folder
37    StaticTarget_gcc fmt_lib(
38        "libfmt", gcc,
39        TargetEnv(BuildExeLibDir::fmt, env::get_project_build_dir() / "fmt"));
40
41    // We use the build.fmt.h and build.fmt.cpp APIs to define how we build our fmtlib
42    FmtConfig fmt_config;
43
44    // Define our hello world executable
45    ExecutableTarget_gcc hello_world("hello_world", gcc, "");
46
47    // Fmt lib is a dependency to the Hello world executable
48    // This means that fmt lib is guaranteed to be built before the hello world executable
49    Reg::Toolchain(arg_gcc.state)
50        .Build(arg_gcc.state, build_fmt_cb, fmt_lib, fmt_config)
51        .Build(hello_world_build_cb, hello_world, fmt_lib)
52        .Dep(hello_world, fmt_lib)
53        .Test("{executable}", hello_world);;
54
55
56    // Build and Test Target
57    // Builds libfmt.a and ./hello_world
58    Reg::Run();
59
60    // Post Build steps
61    // - Clang Compile Commands
62    plugin::ClangCompileCommands({&hello_world}).Generate();
63    // - Graphviz dump
64    std::cout << reg.GetTaskflow().dump() << std::endl;
65
66    return 0;
67}
68
69static void clean_cb() {
70    fs::remove_all(env::get_project_build_dir());
71}
72
73static void hello_world_build_cb(BaseTarget &target, BaseTarget &fmt_lib) {
74    target.AddSource("main.cpp", "src");
75
76    // Add fmt_lib as a library dependency
77    target.AddLibDep(fmt_lib);
78    // We need to insert the fmt lib include dirs and header files into our hello_world executable target (naturally)
79    target.Insert(fmt_lib, {
80                                SyncOption::IncludeDirs,
81                                SyncOption::HeaderFiles,
82                            });
83
84    // Register your tasks
85    target.Build();
86}

Write your compile.toml file#

  • The only difference from the compile.toml in BuildExe “Script” example is the additional of the libs submodule

  • We use the fmt option since we git cloned the library into the libs folder

  • We add the various fmt build files that need to be compiled with our “script”

  • See highlighed lines 19 and 20

 1# Settings
 2root_dir = ""
 3build_dir = "_build_internal"
 4loglevel = "debug"
 5clean = false
 6
 7# BuildExe run mode
 8mode = "script"
 9
10# Target information
11name = "single"
12type = "executable"
13relative_to_root = ""
14srcs = ["build.main.cpp"]
15
16[script]
17configs = ["build.toml"]
18
19[libs]
20fmt = ["build.fmt.cpp", "build.fmt.h"]

Write your build.toml file#

 1# Root
 2root_dir = ""
 3build_dir = "_build"
 4loglevel = "debug"
 5clean = true
 6
 7# Toolchain
 8[toolchain.gcc]
 9build = true
10test = true

Write your main.cpp helloworld example in fmtlib#

#include "fmt/format.h"
int main() {
    fmt::print("{} {}", "Hello", "World");
    return 0;
}