EBuild is a powerful, modular C++ build system with a .NET-based CLI designed to handle complex module-based projects. While inspired by the modularity and flexibility of systems like Unreal Build Tool, EBuild is an independent project with no connection to Unreal Build Tool or Epic Games. It provides a flexible and extensible framework for compiling, linking, and managing dependencies across various platforms and compilers.
- Modular Design: EBuild's core revolves around the concept of modules defined in
.ebuild.csfiles, which encapsulate source files, dependencies, and build configurations. - Multi-Compiler Support:
- MSVC Toolchain: Full support for Microsoft Visual C++ compiler (cl.exe), link (link.exe), lib (lib.exe), and resource compiler (rc.exe)
- GCC Toolchain: Complete GCC/G++ support with AR for static libraries
- Extensible compiler abstraction allowing custom compiler implementations
- Cross-Platform Support:
- Windows (Win32) platform with MSVC as default toolchain
- Unix platform with GCC as default toolchain
- Extensible platform system for custom targets
- Advanced Dependency Management:
- Automatic dependency resolution with circular dependency detection
- Access control system (Public/Private) for transitive dependency propagation
- Module variants based on build options
- Flexible Build Graph: Creates and resolves complex dependency graphs with caching for efficient builds
- Module Options: Define configurable module parameters that can affect binary output and create build variants
- Parallel Builds: Configurable worker count for concurrent compilation
- IDE Integration: Generate
compile_commands.jsonfor IDE language server support - Logging: Integrated logging system with verbose mode for debugging build processes
- .NET 8.0 SDK or later
- A C++ compiler toolchain:
- Windows: Visual Studio 2019 or later (for MSVC toolchain), or MinGW/Cygwin (for GCC toolchain)
- Linux/Unix: GCC/G++ toolchain
Clone the repository and build the solution using the .NET CLI or your preferred IDE:
git clone https://github.com/yldrefruz/ebuild.git
cd ebuildBuild the project:
dotnet buildThe compiled ebuild executable will be available in the build output directory (typically ebuild/bin/Debug/net8.0/ or ebuild/bin/Release/net8.0/).
EBuild provides a command-line interface for managing builds. The main entry point is the ebuild executable.
For the most up-to-date command usage, use:
ebuild --helpCompile and link the specified module:
ebuild build <module-file> [options]Options:
-c, --configuration <configuration>: Build configuration (default:debug)--toolchain <name>: Specify the toolchain to use (e.g.,msvc,gcc)-t, --target-architecture <arch>: Target architecture (e.g.,X64,X86,Arm64)-p, --build-worker-count <number>: Number of parallel build workers (default: 1)-n, --dry-run: Perform a trial run without actual compilation--clean: Perform a clean build-C, --additional-compiler-option <option>: Additional compiler options (can be specified multiple times)-L, --additional-linker-option <option>: Additional linker options (can be specified multiple times)-P, --additional-dependency-path <path>: Additional paths to search for dependency modules-D, --option <key=value>: Module options to pass (format:key=value)-v, --verbose: Enable verbose logging
Example:
ebuild build examples/zlib/zlib.ebuild.cs -c release -p 4 --verboseGenerate compile_commands.json:
ebuild generate compile_commands.json <module-file> [options]Options:
-o, --outfile <path>: Output file path (default:compile_commands.json)-d, --dependencies: Also generate for dependencies
This generates a compilation database for IDE integration and language servers (clangd, ccls, etc.).
Create a scaffolded module file or update the C# solution to include the module's dependencies:
ebuild generate module <module-file> [options]Options:
-o, --outfile <path>: (when generating) the module file to create (default:index.ebuild.cs)-f, --force: Overwrite an existing module file-u, --update: Update the solution (.sln) to include dependencies of the module-t, --template <name>: Module template to use (default:default)-O, --template-options <key=value;...>: Semicolon-separated key=value pairs passed to the template generator
This command is useful when bootstrapping new example modules or when you want to keep your solution in sync with generated modules.
Generate Build Graph:
ebuild generate buildgraph <module-file> [options]Options:
--format <format>: Output format -StringorHtml(default:String)
Outputs a visual representation of the build dependency graph.
Check for Circular Dependencies:
ebuild check circular-dependencies <module-file>Detects and reports circular dependency chains in the module graph.
Print Dependencies:
ebuild check print-dependencies <module-file>Displays the complete dependency tree for the module.
Get Properties:
ebuild property get <property-name>Available properties:
ebuild.api.dll: Returns the path to the ebuild.api.dll assembly
Modules are the core building blocks of EBuild. Each module represents a unit of code with its own source files, dependencies, and build configurations. Modules are defined in .ebuild.cs files and inherit from ModuleBase.
A basic module definition:
using ebuild.api;
public class MyModule : ModuleBase
{
public MyModule(ModuleContext context) : base(context)
{
Name = "MyModule";
Type = ModuleType.StaticLibrary; // or SharedLibrary, Executable, ExecutableWin32
// Public includes are propagated to dependent modules
Includes.Public.Add("include");
// Private includes are only used by this module
Includes.Private.Add("src");
// Add source files
SourceFiles.AddRange(ModuleUtilities.GetAllSourceFiles(this, "src", "cpp", "h"));
// Public libraries are passed to dependent modules
Libraries.Public.Add("SomeLibrary.lib");
// Public dependencies become transitive dependencies
Dependencies.Public.Add(new ModuleReference("PublicDependency"));
// Private dependencies are only linked to this module
Dependencies.Private.Add(new ModuleReference("PrivateDependency"));
}
}EBuild uses an AccessLimitList<T> pattern for dependency propagation:
- Public: Items are propagated to all dependent modules (transitive)
- Private: Items are only used within the current module (non-transitive)
Collections with access control:
Includes- Include directoriesDefinitions- Preprocessor definitionsDependencies- Module dependenciesLibraries- Link librariesLibrarySearchPaths- Library search directoriesForceIncludes- Forced includes
Use .Joined() to get combined public and private items.
Define configurable parameters using the [ModuleOption] attribute:
public class ConfigurableModule : ModuleBase
{
[ModuleOption(Description = "Enable debug logging", ChangesResultBinary = true)]
public bool EnableDebug = false;
[ModuleOption(Description = "Optimization level", ChangesResultBinary = true)]
public int OptimizationLevel = 2;
[ModuleOption(Description = "Custom define", ChangesResultBinary = false)]
public string CustomDefine = "DEFAULT";
public ConfigurableModule(ModuleContext context) : base(context)
{
Name = "ConfigurableModule";
Type = ModuleType.StaticLibrary;
// Use options in configuration
if (EnableDebug)
{
Definitions.Public.Add("ENABLE_DEBUG=1");
}
CompilerOptions.Add($"/O{OptimizationLevel}");
}
}Pass options via command line:
ebuild build module.ebuild.cs -D EnableDebug=true -D OptimizationLevel=3When ChangesResultBinary = true, different option values create separate build variants with unique output paths.
ModuleType.StaticLibrary- Static library (.lib/.a)ModuleType.SharedLibrary- Shared/dynamic library (.dll/.so)ModuleType.Executable- Console executableModuleType.ExecutableWin32- Windows GUI executable (Win32 subsystem)
Source Files:
SourceFiles.AddRange(ModuleUtilities.GetAllSourceFiles(this, "src", "cpp", "h"));Definitions:
Definitions.Public.Add("MY_DEFINE=1");
Definitions.Private.Add("INTERNAL_USE");
GlobalDefinitions.Add("GLOBAL_DEFINE"); // Applied to all modulesCompiler and Linker Options:
CompilerOptions.Add("/W4"); // MSVC warning level
CompilerOptions.Add("-Wall"); // GCC warnings
LinkerOptions.Add("/SUBSYSTEM:WINDOWS");Advanced Compiler Settings:
CStandard = CStandards.C17; // C standard
CppStandard = CppStandards.Cpp20; // C++ standard
OptimizationLevel = OptimizationLevel.O2; // Optimization
EnableExceptions = true;
EnableRTTI = true;
EnableFastFloatingPointOperations = true;Additional Dependencies:
AdditionalDependencies.Public.Add(new AdditionalDependency
{
SourcePath = "path/to/file",
DestinationPath = "relative/dest",
IsDirectory = false
});using ebuild.api;
public class AdvancedModule : ModuleBase
{
[ModuleOption(Description = "Enable extra features", ChangesResultBinary = true)]
public bool EnableExtraFeatures = false;
public AdvancedModule(ModuleContext context) : base(context)
{
Name = "AdvancedModule";
Type = ModuleType.Executable;
// Public includes for dependent modules
Includes.Public.AddRange(new[] { "include", "third_party/include" });
// Private implementation includes
Includes.Private.Add("src/internal");
// Source files
SourceFiles.AddRange(ModuleUtilities.GetAllSourceFiles(this, "src", "cpp", "h"));
// Public dependencies (transitive)
Dependencies.Public.Add(new ModuleReference("CoreLibrary"));
// Private dependencies (non-transitive)
Dependencies.Private.Add(new ModuleReference("ThirdPartyLib"));
// Libraries
Libraries.Public.AddRange(new[] { "SomeLibrary.lib", "AnotherLibrary.lib" });
LibrarySearchPaths.Public.Add("third_party/lib");
// Definitions
Definitions.Public.Add("API_VERSION=2");
if (EnableExtraFeatures)
{
Definitions.Public.Add("EXTRA_FEATURES=1");
}
// Compiler settings
CppStandard = CppStandards.Cpp20;
OptimizationLevel = context.Configuration == "release"
? OptimizationLevel.Speed
: OptimizationLevel.None;
// Platform-specific configuration
if (context.Platform.Name == "windows")
{
Libraries.Public.Add("user32.lib");
CompilerOptions.Add("/W4");
}
else if (context.Platform.Name == "unix")
{
Libraries.Public.Add("pthread");
CompilerOptions.Add("-Wall");
}
}
}The examples/zlib/ directory contains a complete example that:
- Downloads and extracts zlib source from GitHub
- Verifies SHA256 checksum
- Configures build with module options
- Builds as a static library
See examples/zlib/zlib.ebuild.cs for the full implementation.
EBuild consists of three main components:
-
ebuild - CLI application built with CliFx framework
- Commands:
build,generate,check,property - Compiler implementations: MSVC (cl.exe, rc.exe), GCC
- Linker implementations: MSVC (link.exe, lib.exe), GCC, AR
- Platform implementations: Win32, Unix
- Toolchain implementations: MSVC, GCC
- Commands:
-
ebuild.api - Core framework library
- Base classes:
ModuleBase,CompilerBase,LinkerBase,PlatformBase - Interfaces:
IToolchain,ICompilerFactory,ILinkerFactory,IModuleFile - Core types:
AccessLimitList,ModuleReference,ModuleContext - Attributes:
[ModuleOption],[OutputTransformer],[Platform],[Compiler]
- Base classes:
-
ebuild.Tests - NUnit test suite
- Integration tests for real build scenarios
- Unit tests for core functionality
EBuild uses a factory pattern for compiler and linker creation:
Platform → Default Toolchain → Factories → Compiler/Linker Instances
Built-in Toolchains:
-
MSVC Toolchain (
msvc)- Compiler:
MsvcClCompiler(cl.exe) - Resource Compiler:
MsvcRcCompiler(rc.exe) - Linker:
MsvcLinkLinker(link.exe) for executables/DLLs - Archiver:
MsvcLibLinker(lib.exe) for static libraries
- Compiler:
-
GCC Toolchain (
gcc)- Compiler:
GccCompiler(gcc/g++) - Linker:
GccLinker(gcc/g++) for executables/shared libraries - Archiver:
ArLinker(ar) for static libraries
- Compiler:
Built-in Platforms:
-
Win32 Platform (
windows)- Default toolchain: MSVC
- File extensions:
.obj,.lib,.dll,.exe - Resource files:
.rc→.res
-
Unix Platform (
unix)- Default toolchain: GCC
- File extensions:
.o,.a,.so, (no extension for executables)
The build system constructs a dependency graph:
- Module Loading:
.ebuild.csfiles are compiled and loaded dynamically - Dependency Resolution: Recursively resolves module dependencies with caching
- Circular Detection: Detects and reports circular dependency chains
- Build Ordering: Topologically sorts modules for correct build order
- Parallel Execution: Executes build steps with configurable worker count
Module A (Public dep) → Module B (depends on A) → Module C (depends on B)
- Module B gets Module A's public includes, libraries, and definitions
- Module C gets Module A's public items transitively through Module B
- Module A's private items stay within Module A
The API for extending EBuild is complete. However, the system currently does not load external extension assemblies. This feature is planned for future development.
Create a class inheriting from CompilerBase:
using ebuild.api;
using ebuild.api.Compiler;
[Compiler("MyCompiler")]
public class MyCompiler : CompilerBase
{
public override async Task<bool> Compile(CompilerSettings settings, CancellationToken cancellationToken)
{
// Implement compilation logic
// Access settings.SourceFiles, settings.OutputFile, etc.
// Return true on success
}
}Create a corresponding factory:
public class MyCompilerFactory : ICompilerFactory
{
public string Name => "my.compiler";
public Type CompilerType => typeof(MyCompiler);
public bool CanCreate(ModuleBase module, IModuleInstancingParams instancingParams)
{
// Return true if this compiler can handle the module
return true;
}
public CompilerBase CreateCompiler(ModuleBase module, IModuleInstancingParams instancingParams)
{
return new MyCompiler();
}
public string GetExecutablePath(ModuleBase module, IModuleInstancingParams instancingParams)
{
return "/path/to/compiler";
}
}Create a class inheriting from LinkerBase:
using ebuild.api.Linker;
public class MyLinker : LinkerBase
{
public override async Task<bool> Link(LinkerSettings settings, CancellationToken cancellationToken)
{
// Implement linking logic
// Access settings.ObjectFiles, settings.OutputFile, etc.
// Return true on success
}
}Create a corresponding factory:
public class MyLinkerFactory : ILinkerFactory
{
public string Name => "my.linker";
public Type LinkerType => typeof(MyLinker);
public bool CanCreate(ModuleBase module, IModuleInstancingParams instancingParams)
{
return module.Type != ModuleType.StaticLibrary;
}
public LinkerBase CreateLinker(ModuleBase module, IModuleInstancingParams instancingParams)
{
return new MyLinker();
}
}Create a class inheriting from PlatformBase:
using ebuild.api;
[Platform("MyPlatform")]
public class MyPlatform : PlatformBase
{
public MyPlatform() : base("myplatform")
{
}
public override string? GetDefaultToolchainName() => "gcc";
public override string ExtensionForStaticLibrary => ".a";
public override string ExtensionForSharedLibrary => ".so";
public override string ExtensionForExecutable => "";
public override IEnumerable<string> GetPlatformDefinitions(ModuleBase module)
{
yield return new Definition("MY_PLATFORM", "1");
}
public override IEnumerable<string> GetPlatformCompilerFlags(ModuleBase module)
{
yield return "-fPIC";
}
public override IEnumerable<string> GetPlatformLibraries(ModuleBase module)
{
yield return "pthread";
yield return "dl";
}
}Create a class implementing IToolchain:
using ebuild.api.Toolchain;
public class MyToolchain : IToolchain
{
public string Name => "mytoolchain";
public ICompilerFactory? GetCompilerFactory(ModuleBase module, IModuleInstancingParams instancingParams)
{
return new MyCompilerFactory();
}
public ILinkerFactory? GetLinkerFactory(ModuleBase module, IModuleInstancingParams instancingParams)
{
if (module.Type == ModuleType.StaticLibrary)
return new MyArchiverFactory();
return new MyLinkerFactory();
}
public ICompilerFactory? GetResourceCompilerFactory(ModuleBase module, IModuleInstancingParams instancingParams)
{
return null; // Optional resource compiler
}
}The examples/ directory contains working examples:
Located in examples/zlib/, this demonstrates:
- Automatic source downloading and verification
- SHA256 checksum validation
- Cross-platform static library building
Build it:
ebuild build examples/zlib/zlib.ebuild.csLocated in examples/circular-dependency/, demonstrates circular dependency detection.
Located in examples/icu/, this is a more involved, real-world example that demonstrates:
- Downloading and preparing upstream ICU sources and binary data
- Building several helper tools (icupkg, pkgdata) used to package ICU data
- Producing ICU libraries (static/shared) and different data package modes (static/shared/common)
Files in examples/icu/ and their roles:
icu.ebuild.cs— Top-level meta-module that wires the pieces together. It has a module optionDataMode(defaultstatic) which controls how ICU data is provided (static,shared, orcommon). Building this module triggers the source/data preparation steps.icu-source.ebuild.cs— Downloads the ICU sources and the prebuilt binary data (little/big-endian), prepares include files and places data into the module tree. This runs automatically as a prebuild step when required.icu-common.ebuild.cs— Builds the core ICU common implementation (icuuc). Can transform to static or shared output.icu-i18n.ebuild.cs— Builds ICU i18n components used by the library/tools.icu-toolutil.ebuild.cs— Utility code used by the ICU packaging tools.icu-icupkg.ebuild.cs— Builds theicupkgexecutable used to unpack and inspect ICU data packages.icu-pkgdata.ebuild.cs— Buildspkgdata, the tool that assembles ICU data libraries from unpacked data.icu-data.ebuild.cs— Coordinates buildingicupkgandpkgdata, runs them to assemble data libraries, and places resulting data libraries underBinaries/icudt.icu-stubdata.ebuild.cs— Builds stub data variants used during tool building where full data isn't required.
Quick usage examples:
- Build the ICU library (default behavior downloads sources and data as needed):
ebuild build examples/icu/icu.ebuild.cs- Build ICU and request the shared library output for ICU core and i18n (use the
output:modulesyntax):
ebuild build shared:examples/icu/icu.ebuild.cs- Build ICU but instruct the top-level module to use the
commondata mode (no per-binary data linked):
ebuild build examples/icu/icu.ebuild.cs -D DataMode=commonNotes and caveats:
- Building the ICU example may download upstream ICU archives (sources and prebuilt data). The
icu-source.ebuild.csfile contains SHA256 checks for the downloads. - The
icu.ebuild.csheader includes license notes — ICU and Unicode data have their own licenses; review them before building. - Building ICU fully (pkgdata/icupkg steps) may require a working toolchain and sufficient native tools available on PATH; the example sets PATH for subprocesses where necessary.
# Clone the repository
git clone https://github.com/yldrefruz/ebuild.git
cd ebuild
# Build the solution
dotnet build
# Build in release mode
dotnet build -c Release
# Run tests
dotnet testThe test suite includes:
- Integration Tests: Real build scenarios using example modules
- Unit Tests: Core functionality tests
Run specific test categories:
# Run all tests
dotnet test
# Run with verbose output
dotnet test --logger "console;verbosity=detailed"ebuild/
├── ebuild/ # CLI application
│ ├── Commands/ # Command implementations
│ ├── Compilers/ # Compiler implementations
│ ├── Linkers/ # Linker implementations
│ ├── Platforms/ # Platform implementations
│ ├── Toolchains/ # Toolchain implementations
│ └── Modules/ # Module loading and build graph
├── ebuild.api/ # Core API framework
│ ├── Compiler/ # Compiler abstractions
│ ├── Linker/ # Linker abstractions
│ └── Toolchain/ # Toolchain interfaces
├── ebuild.Tests/ # Test suite
│ ├── Integration/ # Integration tests
│ └── Unit/ # Unit tests
└── examples/ # Example modules
├── zlib/ # Zlib build example
└── circular-dependency/
Contributions are welcome! Here's how you can help:
- Report Issues: Open an issue for bugs or feature requests
- Submit Pull Requests:
- Fork the repository
- Create a feature branch
- Make your changes
- Add tests if applicable
- Submit a PR with a clear description
- Improve Documentation: Help improve this README or add inline documentation
- Add Examples: Create example modules demonstrating EBuild features
- Follow existing code style and conventions
- Add XML documentation comments for public APIs
- Write tests for new features
- Keep commits focused and atomic
- Update README if adding new features
This project is licensed under the MIT License. See the LICENSE file for details.
While inspired by Unreal Build Tool's modularity, EBuild is an independent project with no affiliation to Epic Games or Unreal Engine.