Skip to content

Gilqamesh/Builder

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

90 Commits
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Builder

Builder is a C++ build framework organized around modules.

The usual workflow is:

  1. Write a C++ module.
  2. Write the module's builder.cpp.
  3. Run the module with ./cli <module> [args...].

Prerequisites

Builder currently targets a Linux or Linux-compatible POSIX environment. The default bootstrap makefile expects clang++ (C++23), clang, ar, ln, mkdir, mv, and rm under /usr/bin, and libdl.

Quick Start

Bootstrap the local Builder CLI:

make -f ws0/m03gagbhst621faiop1rztfkqp_builder_cli/bootstrap.mk bootstrap

This creates the initial Builder CLI and builder plugin used to rebuild the modules that implement Builder itself.

Run existing modules from the current workspace graph, couple of examples:

./cli m03gagbht2l61mj6qitacwbmea_byte_stream
./cli m03gagbhtft23yhjwpp881tfmc_uuid

Module that generates a new module with minimal boilerplate:

./cli m03gagbht5685jfnokvj7crv2c_create_module <workspace> hello_module

Module Command

The normal command is:

./cli <module> [args...]

<module> is the globally unique module name. Builder discovers that module from the workspace graph, builds its default CLI if needed, then execs that CLI with [args...].

Environment Variables

Builder uses two environment variables to find source and output locations:

  • BUILDER_WORKSPACE_ROOT: directory containing the workspace directories; defaults to the current directory.
  • BUILDER_ARTIFACT_ROOT: directory where Builder writes generated files; defaults to <BUILDER_WORKSPACE_ROOT>/artifacts.

Builder sets these variables before running a module CLI, so the CLI and its child processes see the same workspace and artifact locations.

What is a module?

A module is the unit Builder builds and runs. It owns:

  • source files;
  • builder.cpp;
  • build output;
  • public headers;
  • linkable library capability;
  • default CLI capability.

A module directory is:

<workspace>/<module_name>/

Module names must be globally unique because includes and ./cli <module> refer to modules by name.

Module Naming

This repository uses generated module names with a durable identity prefix and a human-readable semantic slug:

m<zero-padded-base36-uuidv7>_<semantic_slug>

For example:

m03gagbht2l61mj6qitacwbmea_base36

The whole string is the module identity. The generated prefix keeps module names globally unique across workspaces and repositories. The semantic slug makes the module easier to recognize in includes, dependency lists, commands, and diagnostics, but it is still part of the identity.

What are workspaces?

Workspaces define which modules may depend on which other modules. Workspaces are directories named wsN under the workspace root:

ws0/
ws1/
ws2/
...

The numeric suffix is the workspace order from lower-level to higher-level.

The rules are:

  • module source may depend on modules in the same workspace or a lower workspace;
  • builder.cpp may depend only on modules in lower workspaces.

Workspace ws0 is bootstrap-specific. It contains the modules needed to seed and rebuild Builder itself. For that active Builder bootstrap group, modules that implement the running Builder can use same-workspace builder dependencies so the local bootstrap seed can build Builder without a lower bootstrap workspace.

That exception is not yet a general module authoring semantic. Higher-level modules should normally live in later workspaces, and builder dependency cycles are not legal outside the active Builder bootstrap group.

How are dependencies found?

Builder derives dependencies from module-qualified includes:

#include <module_name/header.h>

Includes in source files and public headers become module dependencies. Includes in builder.cpp become builder dependencies. Builder exposes builder dependencies to builder.cpp as normal C++ include and link inputs.

Include the public header for each module API you reference directly. Do not rely on transitive includes or forward declarations of another module's API to make your code compile.

Dependencies outside these rules fail before build output is produced.

Same-workspace module dependency cycles are handled as strongly connected component groups. The group graph is ordered topologically from dependency to dependent, and every module in a group can publish interfaces before library compilation. Builder dependency cycles are not modeled as module groups.

What are build phases?

A build configuration selects library type and phase order. The default phase order is:

source -> interface -> library -> binary

The phases are:

  • source: make source-side inputs available;
  • interface: publish public headers;
  • library: publish linkable library output;
  • binary: publish executable output, including the default CLI.

builder.cpp includes the phase API and defines one C-callable function per phase.

#include <m03gagbhsujjf63n0w3r2w4q6h_build_phases/build_phases.h>

extern "C" void phase__source(const m03gagbhsujjf63n0w3r2w4q6h_build_phases::source_phase_t* phase) {
    phase->install_source_tree();
}

extern "C" void phase__interface(const m03gagbhsujjf63n0w3r2w4q6h_build_phases::interface_phase_t* phase) {
    phase->install_headers_from_source();
}

extern "C" void phase__library(const m03gagbhsujjf63n0w3r2w4q6h_build_phases::library_phase_t* phase) {
}

extern "C" void phase__binary(const m03gagbhsujjf63n0w3r2w4q6h_build_phases::binary_phase_t* phase) {
    phase->install_cli({});
}

Builder owns graph order, group order, and phase order. A builder only describes local work for the phase it is given.

Each phase has two roots:

  • build_dir(): private scratch for the current phase;
  • install_dir(): public completed output for later phases.

Generated files and compiler outputs belong under build_dir(). Published paths are copied into install_dir().

Important phase APIs:

  • build(path): selects a path from an earlier phase install_dir() for compile/link helpers;
  • install(path): publishes a path into the current phase install_dir() under the same relative path. The path must come from the current build_dir() or an earlier phase install_dir(). Installing two paths to the same relative path is an error.
  • install<T>(): ensures phase T is installed and returns its result.

build_library(...) and install_cli(...) accept compile defines for the sources being compiled. install_cli(...) always builds the module's cli.cpp and publishes it as the default cli artifact. Compile define keys are validated by m03gagbhsmhr0naw0zpccv4gaq_cxx_toolchain::define_t and should use a macro-safe module identity prefix when they are module-owned. Compile defines are private compile inputs; public module headers should not expose private macros unless the macro is deliberately part of the public API. External host tools should be validated when the code that uses them runs, not while installing unrelated phases.

Public Includes

Public includes are module-qualified:

#include <module_name/header.h>

For a module named m03gagbht2l61mj6qitacwbmea_base36 with base36.h, install_interface(...) publishes this include:

#include <m03gagbht2l61mj6qitacwbmea_base36/base36.h>

Imported upstream packages sometimes include their own headers without Builder's module prefix. install_interface_compatibility(...) publishes the extra include root needed for those upstream self-includes. Module users should still include through the module name.

Public headers that use include guards should derive the guard from the module name and the module-relative installed header path.

What is builder.cpp?

builder.cpp is the module's C++ build file. Its job is to build that module's own outputs: published headers, libraries, generated files, imported files, and binaries.

It is regular C++ code. If builder.cpp needs helper code, tools, or libraries, it includes their module-qualified public headers. Builder then makes those dependencies available to builder.cpp through normal C++ includes and linking.

Each phase function in builder.cpp describes only the local work for that phase. For example, it can install headers, compile source files, generate a file under build_dir(), run an imported tool, or publish the module's default CLI.

To build a library, replace the empty phase__library function above with one that compiles a source file from the source phase install_dir() and publishes the library:

#include <m03gagbhsujjf63n0w3r2w4q6h_build_phases/build_phases.h>
#include <m03gagbhsnusi43zogoacgj2ez_filesystem/filesystem.h>

extern "C" void phase__library(const m03gagbhsujjf63n0w3r2w4q6h_build_phases::library_phase_t* phase) {
    const auto sources = phase->install<m03gagbhsujjf63n0w3r2w4q6h_build_phases::source_phase_t>();
    const auto library = phase->build_library({
        phase->build(sources.root() / m03gagbhsnusi43zogoacgj2ez_filesystem::relative_path_t("base36.cpp") )
    }, {});
    phase->install_library(library);
}

Artifacts

Builder writes generated files under BUILDER_ARTIFACT_ROOT.

Artifacts are grouped by module name, but the exact directory layout is an implementation detail:

<BUILDER_ARTIFACT_ROOT>/<module_name>/

Builder exposes a stable latest view for tooling and debugging:

<BUILDER_ARTIFACT_ROOT>/<module_name>/latest/

latest is a stable path for external tooling, but it is not dependency versioning or a module authoring API. Module builders should use phase APIs such as build_dir(), build(path), install(path), and install<T>() instead of hard-coding artifact paths or inspecting phase install roots directly.

Some Modules from Workspace ws0

  • m03gagbhsp2drqq3gkop8pzfrm_workspace_graph: discovers modules, derives dependency edges from includes, validates workspace order, forms module dependency strongly connected component groups, and propagates versions.
  • m03gagbhsujjf63n0w3r2w4q6h_build_phases: defines phase APIs, lazy installation, phase roots, interface publication, library builds, and binary builds.
  • m03gagbhst621faiop1rztfkqp_builder_cli: implements ./cli <module> [args...], CLI self-update, target binary installation, and process execution.
  • m03gagbhsmhr0naw0zpccv4gaq_cxx_toolchain: compiles objects, archives static libraries, links shared libraries, links binaries, and injects include/link inputs.
  • m03gagbhsnusi43zogoacgj2ez_filesystem: filesystem path and file operations.
  • m03gagbhsvr0m5w15urj0o291m_process: process execution.
  • m03gagbhsyhlx2pk5sdabbr1sx_signal_handler: signal-aware cleanup guards.
  • m03gagbhsx4j5z28bqkac3dhhh_shared_library: shared library loading.

Long-term goals

  • Support versioned module dependencies.
  • Support composing modules by name through a small LISP-like expression language.
  • Support module tests as first-class graph nodes with declared dependencies.

License

See LICENSE.

About

C++ build tool for modular workspaces where build logic is written directly in C++ instead of a build DSL.

Topics

Resources

License

Stars

5 stars

Watchers

0 watching

Forks

Packages

 
 
 

Contributors