Sane C++ Libraries
C++ Platform Abstraction Libraries
Loading...
Searching...
No Matches
SC::Build

SC::Build is a Tool and public API for describing builds in C++ and then either generating project files or building directly through a standalone native backend.

Features

  • Describe builds in SC-build.cpp using SC::Build::Definition, Workspace, Project, and Configuration
  • Generate XCode 14+ projects, Visual Studio 2019 / 2022 projects, and Makefiles
  • Build directly through SC::Build::Generator::Native on macOS, Linux, and Windows hosts
  • Build console executables, GUI applications, shared libraries, and static libraries
  • Attach compile/link settings at project, configuration, and per-file granularity
  • Build workspace-local static-library dependencies in dependency order on the native backend
  • Track header dependencies through compiler-generated dependency information
  • Emit compile_commands.json on the native backend and from the Make / XCode generated flows
  • Integrate with SC-package from Tools to download repository dependencies
  • Export Sane C++ libraries for Plugin hosts through addExportLibraries and addExportAllLibraries

Status

🟨 MVP

SC::Build is used by this repository to generate projects and/or build the test suites, tools, examples, and the SC shared library. The generated-project backends remain part of the daily workflow, while the standalone native backend now supports direct host builds on macOS, Linux, and Windows.

Description

Build descriptions are regular C++ files, conventionally named SC-build.cpp, that are compiled on the fly through the bootstrap scripts SC.sh and SC.bat.

The same public API serves two workflows:

  • Generated backends: emit XCode, Visual Studio, or Make projects into _Build/_Projects
  • Native backend: invoke compiler, linker, and archiver child processes directly without generating project files first

The repository tool surface is the SC-build tool described in Tools. Its command-line shape is:

./SC.sh build configure [workspace:project | project]
./SC.sh build compile [workspace:project | project] [options]
./SC.sh build run [workspace:project | project] [options] [-- extra args...]
./SC.sh build coverage [workspace:project | project] [options]

Named options accepted by compile, run, and coverage are:

  • -c, --config <NAME>
  • -g, --generator <NAME>
  • -a, --arch <NAME>
  • --target <PROFILE> (compile and run only)
  • --toolchain <NAME>
  • --triple <VALUE>
  • --sysroot <PATH>
  • --runner <MODE> (run only)
  • --runner-path <PATH> (run only)
  • -o, --output <MODE> (compile and run only)
  • -q, --quiet (compile and run only)
  • --normal (compile and run only)
  • -v, --verbose (compile and run only)

Generator values accepted by the tool are:

  • default
  • native
  • make
  • xcode
  • vs2022
  • vs2019

Architecture values accepted by the tool are:

  • arm64
  • intel64
  • intel32
  • wasm
  • any

Target profiles accepted by the tool today are:

  • host
  • native
  • linux-glibc-x86_64
  • linux-glibc-arm64
  • linux-musl-x86_64
  • linux-musl-arm64
  • windows-gnu-x86_64
  • windows-msvc-x86_64
  • windows-msvc-arm64
  • windows-gnu-arm64

Runner values accepted by build run today are:

  • auto
  • none
  • wine
  • qemu
  • custom

Toolchain values accepted by compile and run today are:

  • default
  • host-default
  • clang
  • filc
  • gcc
  • msvc
  • clang-cl
  • llvm-mingw

Current CLI behavior:

  • If no project is specified, compile builds the whole default workspace
  • run requires a single executable target and forwards arguments placed after --
  • If no configuration is specified, Debug is used
  • Host-default generator selection is vs2022 on Windows and make on macOS / Linux
  • configure is primarily for generated backends; the native backend builds directly into _Build/_Outputs and _Build/_Intermediates
  • compile / run accept quiet, normal, or verbose output control through --output or the --quiet / --normal / --verbose shortcuts
  • --target selects a friendly host or cross target profile without requiring the caller to spell the toolchain triple manually
  • --toolchain selects the compiler family orthogonally to --target
  • --triple and --sysroot are raw escape hatches for advanced toolchain overrides
  • Raw overrides are applied after --target, so they win over the friendly profile defaults
  • build run can use --runner / --runner-path to control how foreign executables are launched
  • Contradictory explicit combinations such as mismatched --generator, --arch, --runner, or --triple values now fail early with concrete CLI errors
  • Legacy positional compatibility is still supported after target as [configuration] [generator] [architecture] [output-mode]

This is the repository Tools/SC-build.cpp file used to configure the default workspace:

// Copyright (c) Stefano Cristiano
// SPDX-License-Identifier: MIT
#include "SC-build.h"
#include "../Libraries/FileSystemIterator/FileSystemIterator.h"
#include "SC-build/Build.inl"
namespace SC
{
namespace Tools
{
Result installSokol(const Build::Directories& directories, Package& package)
{
Download download;
download.packagesCacheDirectory = directories.packagesCacheDirectory;
download.packagesInstallDirectory = directories.packagesInstallDirectory;
download.packageName = "sokol";
download.packageVersion = "d5863cb";
download.shallowClone = "d5863cb78ea1552558c81d6db780dfcec49557ce";
download.url = "https://github.com/floooh/sokol.git";
download.isGitClone = true;
download.createLink = false;
package.packageBaseName = "sokol";
CustomFunctions functions;
functions.testFunction = &verifyGitCommitHashCache;
SC_TRY(packageInstall(download, package, functions));
return Result(true);
}
Result installDearImGui(const Build::Directories& directories, Package& package)
{
Download download;
download.packagesCacheDirectory = directories.packagesCacheDirectory;
download.packagesInstallDirectory = directories.packagesInstallDirectory;
download.packageName = "dear-imgui";
download.packageVersion = "af987eb";
download.url = "https://github.com/ocornut/imgui.git";
download.shallowClone = "af987eb1176fb4c11a6f0a4f2550d9907d113df5";
download.isGitClone = true;
download.createLink = false;
package.packageBaseName = "dear-imgui";
CustomFunctions functions;
functions.testFunction = &verifyGitCommitHashCache;
SC_TRY(packageInstall(download, package, functions));
return Result(true);
}
} // namespace Tools
namespace Build
{
SC_COMPILER_WARNING_PUSH_UNUSED_RESULT; // Doing some optimistic coding here, ignoring all failures
void addSaneCppLibraries(Project& project, const Parameters& parameters)
{
// Files
project.addFiles("Libraries", "**.cpp"); // recursively add all cpp files
project.addFiles("Libraries", "**.h"); // recursively add all header files
project.addFiles("Libraries", "**.inl"); // recursively add all inline files
// Libraries to link
if (parameters.platform == Platform::Apple)
{
project.addLinkFrameworks({"CoreFoundation", "CoreServices", "CFNetwork", "Foundation"});
}
if (parameters.platform == Platform::Windows)
{
project.addLinkLibraries({"Advapi32", "Dbghelp", "Mswsock", "ntdll", "Rstrtmgr", "Winhttp", "Ws2_32"});
}
else
{
project.addLinkLibraries({"dl", "pthread"});
}
// Debug visualization helpers
if (parameters.generator == Generator::VisualStudio2022)
{
project.addFiles("Support/DebugVisualizers/MSVC", "*.natvis");
}
else
{
project.addFiles("Support/DebugVisualizers/LLDB", "*");
}
}
static constexpr StringView buildPlatformName(Platform::Type platform)
{
switch (platform)
{
case Platform::Apple: return "macOS";
case Platform::Linux: return "linux";
case Platform::Windows: return "windows";
case Platform::Wasm: return "wasm";
case Platform::Unknown: return "unknown";
}
Assert::unreachable();
}
static constexpr Architecture::Type hostArchitecture()
{
switch (HostInstructionSet)
{
case InstructionSet::Intel32: return Architecture::Intel32;
case InstructionSet::Intel64: return Architecture::Intel64;
case InstructionSet::ARM64: return Architecture::Arm64;
}
Assert::unreachable();
}
static constexpr StringView buildArchitectureName(const Parameters& parameters, const Configuration& configuration)
{
Architecture::Type architecture = configuration.architecture;
if (architecture == Architecture::Any)
{
architecture = parameters.architecture;
}
if (parameters.generator == Generator::XCode)
{
switch (architecture)
{
case Architecture::Intel64: return "x86_64";
case Architecture::Arm64: return "arm64";
case Architecture::Any: return "arm64 x86_64";
case Architecture::Intel32:
case Architecture::Wasm: return "unsupported";
}
}
else if (parameters.generator == Generator::VisualStudio2019 or parameters.generator == Generator::VisualStudio2022)
{
if (architecture == Architecture::Any)
{
architecture = hostArchitecture();
}
switch (architecture)
{
case Architecture::Intel32: return "x86";
case Architecture::Intel64: return "x64";
case Architecture::Arm64: return "ARM64";
case Architecture::Any:
case Architecture::Wasm: return "unsupported";
}
}
else
{
if (architecture == Architecture::Any)
{
architecture = hostArchitecture();
}
switch (architecture)
{
case Architecture::Intel32: return "x86";
case Architecture::Intel64: return "x86_64";
case Architecture::Arm64: return "arm64";
case Architecture::Any:
case Architecture::Wasm: return "unsupported";
}
}
Assert::unreachable();
}
static constexpr StringView buildSystemName(Generator::Type generator)
{
switch (generator)
{
case Generator::Native: return "Native";
case Generator::Make: return "make";
case Generator::XCode: return "xcode";
case Generator::VisualStudio2019:
case Generator::VisualStudio2022: return "msbuild";
}
Assert::unreachable();
}
static constexpr StringView compilerName(const Parameters& parameters)
{
switch (parameters.toolchain.family)
{
case Toolchain::Clang: return "clang";
case Toolchain::FilC: return "filc";
case Toolchain::GCC: return "gcc";
case Toolchain::MSVC: return "msvc";
case Toolchain::ClangCL: return "clang-cl";
case Toolchain::LLVMMingw: return "llvm-mingw";
case Toolchain::CustomDriver: return "custom-driver";
case Toolchain::HostDefault:
if (parameters.platform == Platform::Windows)
{
return "msvc";
}
if (parameters.generator == Generator::XCode or parameters.platform == Platform::Apple)
{
return "clang";
}
return "gcc";
}
Assert::unreachable();
}
static Result expandBuildDirectoryVariables(StringView source, const Parameters& parameters,
const Configuration& configuration, String& output)
{
const ProjectWriter::ReplacePair substitutions[] = {
{"$(TARGET_OS)", buildPlatformName(parameters.platform)},
{"$(TARGET_ARCHITECTURES)", buildArchitectureName(parameters, configuration)},
{"$(BUILD_SYSTEM)", buildSystemName(parameters.generator)},
{"$(COMPILER)", compilerName(parameters)},
{"$(CONFIGURATION)", configuration.name.view()},
};
auto builder = StringBuilder::create(output);
SC_TRY(ProjectWriter::appendReplaceMultiple(builder, source, substitutions));
builder.finalize();
return Result(true);
}
static Result computeExecutableDirectory(const Project& project, const Parameters& parameters,
const Configuration& configuration, String& executableDirectory)
{
String outputDirectory = StringEncoding::Utf8;
SC_TRY(expandBuildDirectoryVariables(configuration.outputPath.view(), parameters, configuration, outputDirectory));
if (Path::isAbsolute(outputDirectory.view(), Path::AsNative))
{
SC_TRY(executableDirectory.assign(outputDirectory.view()));
}
else
{
Path::join(executableDirectory, {parameters.directories.outputsDirectory.view(), outputDirectory.view()}));
}
if (project.targetType == TargetType::GUIApplication and parameters.generator == Generator::XCode)
{
String bundleDirectory = StringEncoding::Utf8;
SC_TRY(StringBuilder::format(bundleDirectory, "{}.app", project.targetName.view()));
String fullDirectory = StringEncoding::Utf8;
SC_TRY(Path::join(fullDirectory, {executableDirectory.view(), bundleDirectory.view(), "Contents", "MacOS"}));
executableDirectory = move(fullDirectory);
}
return Result(true);
}
static Result appendEscapedCString(StringBuilder& builder, StringView text)
{
for (size_t idx = 0; idx < text.sizeInBytes(); ++idx)
{
const char ch = text.bytesWithoutTerminator()[idx];
if (ch == '\\' or ch == '"')
{
SC_TRY(builder.append("\\"));
}
const char character[] = {ch, 0};
SC_TRY(builder.append(StringView::fromNullTerminated(character, StringEncoding::Utf8)));
}
return Result(true);
}
static Result normalizeRelativePathForCompileDefine(String& path)
{
const StringView pathView = path.view();
String normalized = StringEncoding::Utf8;
auto builder = StringBuilder::create(normalized);
for (size_t idx = 0; idx < pathView.sizeInBytes(); ++idx)
{
char ch = pathView.bytesWithoutTerminator()[idx];
if (ch == '\\')
{
ch = '/';
}
const char character[] = {ch, 0};
SC_TRY(builder.append(StringView::fromNullTerminated(character, StringEncoding::Utf8)));
}
builder.finalize();
path = move(normalized);
return Result(true);
}
static Result addCompiledLibraryRootDefine(Project& project, const Parameters& parameters)
{
for (Configuration& configuration : project.configurations)
{
String executableDirectory = StringEncoding::Utf8;
SC_TRY(computeExecutableDirectory(project, parameters, configuration, executableDirectory));
String relativeRoot = StringEncoding::Utf8;
SC_TRY(Path::relativeFromTo(relativeRoot, executableDirectory.view(), project.rootDirectory.view(),
Path::AsNative, Path::AsNative));
SC_TRY(normalizeRelativePathForCompileDefine(relativeRoot));
String define = StringEncoding::Utf8;
SC_TRY(StringBuilder::format(define, "SC_LIBRARY_ROOT={}", relativeRoot.view()));
SC_TRY(configuration.compile.defines.push_back(move(define)));
}
return Result(true);
}
static Result addHotReloadIncludePathsDefine(Project& project, const Parameters& parameters, StringView imguiDirectory)
{
for (Configuration& configuration : project.configurations)
{
String executableDirectory = StringEncoding::Utf8;
SC_TRY(computeExecutableDirectory(project, parameters, configuration, executableDirectory));
String relativeImgui = StringEncoding::Utf8;
SC_TRY(Path::relativeFromTo(relativeImgui, executableDirectory.view(), imguiDirectory, Path::AsNative,
Path::AsNative));
SC_TRY(normalizeRelativePathForCompileDefine(relativeImgui));
String define = StringEncoding::Utf8;
auto builder = StringBuilder::create(define);
SC_TRY(builder.append("SC_HOT_RELOAD_INCLUDE_PATHS=\""));
SC_TRY(appendEscapedCString(builder, relativeImgui.view()));
SC_TRY(builder.append("\""));
builder.finalize();
SC_TRY(configuration.compile.defines.push_back(move(define)));
}
return Result(true);
}
static constexpr StringView TEST_PROJECT_NAME = "SCTest";
static constexpr StringView BUILD_TEST_PROJECT_NAME = "SCBuildTest";
Result configureTests(const Parameters& parameters, Workspace& workspace)
{
Project project = {TargetType::ConsoleExecutable, TEST_PROJECT_NAME};
// All relative paths are evaluated from this project root directory.
project.setRootDirectory(parameters.directories.libraryDirectory.view());
// Project Configurations
project.addPresetConfiguration(Configuration::Preset::Debug, parameters);
project.addPresetConfiguration(Configuration::Preset::Release, parameters);
project.addPresetConfiguration(Configuration::Preset::DebugCoverage, parameters);
project.configurations.back().coverage.excludeRegex =
".*\\/Tools.*|"
".*\\Test.(cpp|h|c)|"
".*\\test.(c|h)|"
".*\\/Tests/.*\\.*|"
".*\\/LibC\\+\\+.inl|" // new / delete overloads
".*\\/Assert.h|" // Can't test Assert::unreachable
".*\\/PluginMacros.h|" // macros for client plugins
".*\\/ProcessPosixFork.inl|" // Can't compute coverage for fork
".*\\/EnvironmentTable.h|" // Can't compute coverage for fork
".*\\/InitializerList.h|" // C++ Language Support
".*\\/Reflection/.*\\.*|" // constexpr and templates
".*\\/ContainersReflection/.*\\.*|" // constexpr and templates
".*\\/SerializationBinary/.*\\.*|" // constexpr and templates
".*\\/Extra/Deprecated/.*\\.*";
if (parameters.platform == Platform::Linux)
{
project.addPresetConfiguration(Configuration::Preset::Debug, parameters, "DebugValgrind");
project.configurations.back().compile.enableASAN = false; // ASAN and Valgrind don't mix
project.configurations.back().link.enableASAN = false; // ASAN and Valgrind don't mix
}
// Defines
// $(PROJECT_ROOT) expands to Project::setRootDirectory expressed relative to $(PROJECT_DIR)
project.addDefines({"SC_COMPILER_ENABLE_CONFIG=1", "SC_TOOLS_COMPILED_SEPARATELY=1"});
SC_TRY(addCompiledLibraryRootDefine(project, parameters));
// Includes
project.addIncludePaths({
".", // Libraries path (for PluginTest)
"Tests/SCTest", // SCConfig.h path (enabled by SC_COMPILER_ENABLE_CONFIG == 1)
});
addSaneCppLibraries(project, parameters);
project.addFiles("Tests/SCTest", "*.cpp"); // add all .cpp from SCTest directory
project.addFiles("Tests/SCTest", "*.h"); // add all .h from SCTest directory
project.addFiles("Tests/Libraries", "**.c*"); // add all tests from Libraries directory
project.addFiles("Tests/Libraries", "**.inl"); // add all tests from Libraries directory
project.removeFiles("Tests/Libraries/Build", "BuildTest.cpp");
project.addFiles("Tests/Support", "**.cpp"); // add all tests from Support directory
project.addFiles("Tests/Tools", "**.cpp"); // add all tests from Tools directory
project.addFiles("Tools", "SC-*.cpp"); // add all tools
project.addFiles("Tools", "*.h"); // add tools headers
if (not project.addExportLibraries({"Foundation", "Memory", "Strings", "Containers"}))
{
return Result::Error("Failed to configure exported SCTest libraries");
}
project.link.preserveExportedSymbols = true;
// Deprecated code tests and libraries (to be removed when deprecated code will be removed)
project.addFiles("Extra/Deprecated/Tests", "**.cpp"); // add all deprecated tests
project.addFiles("Extra/Deprecated/Libraries", "**.h"); // add all deprecated libraries header files
project.addFiles("Extra/Deprecated/Libraries", "**.cpp"); // add all deprecated libraries cpp files
// This is a totally useless per-file define to test "per-file" flags SC::Build feature.
SourceFiles specificFiles;
// For testing purposes let's create a needlessly complex selection filter for "SC Spaces.cpp"
specificFiles.addSelection("Tests/SCTest", "*.cpp");
specificFiles.removeSelection("Tests/SCTest", "SCTest.cpp");
// Add an useless define to be checked inside "SC Spaces.cpp" and "SCTest.cpp"
specificFiles.compile.addDefines({"SC_SPACES_SPECIFIC_DEFINE=1"});
specificFiles.compile.addIncludePaths({"../Directory With Spaces"});
// For testing purposes disable some warnings caused in "SC Spaces.cpp"
specificFiles.compile.disableWarnings({4100}); // MSVC only
specificFiles.compile.disableWarnings({"unused-parameter"}); // GCC and Clang
specificFiles.compile.disableClangWarnings({"reserved-user-defined-literal"}); // Clang Only
project.addSpecificFileFlags(specificFiles);
SC_TRY(workspace.projects.push_back(move(project)));
return Result(true);
}
Result configureSCBuildTest(const Parameters& parameters, Workspace& workspace)
{
Project project = {TargetType::ConsoleExecutable, BUILD_TEST_PROJECT_NAME};
project.setRootDirectory(parameters.directories.libraryDirectory.view());
project.addPresetConfiguration(Configuration::Preset::Debug, parameters);
project.addPresetConfiguration(Configuration::Preset::Release, parameters);
project.addDefines({"SC_COMPILER_ENABLE_CONFIG=1", "SC_TOOLS_COMPILED_SEPARATELY=1"});
SC_TRY(addCompiledLibraryRootDefine(project, parameters));
project.addIncludePaths({
".",
"Tests/SCBuildTest",
});
addSaneCppLibraries(project, parameters);
project.addFiles("Tests/SCBuildTest", "*.cpp");
project.addFiles("Tests/SCBuildTest", "*.h");
project.addFiles("Tests/Libraries/Build", "BuildTest.cpp");
project.addFiles("Tools", "SC-*.cpp");
project.addFiles("Tools", "*.h");
SC_TRY(workspace.projects.push_back(move(project)));
return Result(true);
}
Result configureSCSharedLibrary(const Parameters& parameters, Workspace& workspace)
{
Project project = {TargetType::SharedLibrary, "SC"};
project.setRootDirectory(parameters.directories.libraryDirectory.view());
project.addPresetConfiguration(Configuration::Preset::Debug, parameters);
project.addPresetConfiguration(Configuration::Preset::Release, parameters);
project.addIncludePaths({"."});
addSaneCppLibraries(project, parameters);
SC_TRY_MSG(project.addExportAllLibraries(), "Failed to configure exported Sane C++ libraries");
SC_TRY(workspace.projects.push_back(move(project)));
return Result(true);
}
Result configureTestSTLInterop(const Parameters& parameters, Workspace& workspace)
{
Project project = {TargetType::ConsoleExecutable, "InteropSTL"};
// All relative paths are evaluated from this project root directory.
project.setRootDirectory(parameters.directories.libraryDirectory.view());
// Project Configurations
project.addPresetConfiguration(Configuration::Preset::Debug, parameters);
project.addPresetConfiguration(Configuration::Preset::Release, parameters);
// Enable C++ STL, exceptions and RTTI
project.files.compile.enableStdCpp = true;
project.files.compile.enableExceptions = true;
project.files.compile.enableRTTI = true;
project.files.compile.cppStandard = CppStandard::CPP17; // string_view requires C++17
// $(PROJECT_ROOT) expands to Project::setRootDirectory expressed relative to $(PROJECT_DIR)
project.addDefines({"SC_COMPILER_ENABLE_STD_CPP=1"});
SC_TRY(addCompiledLibraryRootDefine(project, parameters));
project.addIncludePaths({"."}); // Libraries path
addSaneCppLibraries(project, parameters);
project.addFiles("Tests/InteropSTL", "*.cpp");
project.addFiles("Tests/InteropSTL", "*.h");
workspace.projects.push_back(move(project));
return Result(true);
}
static constexpr StringView EXAMPLE_PROJECT_NAME = "SCExample";
Result configureExamplesGUI(const Parameters& parameters, Workspace& workspace)
{
Project project = {TargetType::GUIApplication, EXAMPLE_PROJECT_NAME};
const bool isWindowsGNUTarget = parameters.platform == Platform::Windows and
(parameters.targetMachine.environment == TargetEnvironment::WindowsGNU or
parameters.toolchain.family == Toolchain::LLVMMingw);
// All relative paths are evaluated from this project root directory.
project.setRootDirectory(parameters.directories.libraryDirectory.view());
// Project icon (currently used only by Xcode backend)
project.iconPath = "Documentation/Doxygen/SC.svg";
// Install dependencies
Tools::Package sokol;
SC_TRY(Tools::installSokol(parameters.directories, sokol));
Tools::Package imgui;
SC_TRY(Tools::installDearImGui(parameters.directories, imgui));
// Add includes
project.addIncludePaths({".", sokol.packageLocalDirectory.view(), imgui.packageLocalDirectory.view()});
// Project Configurations
project.addPresetConfiguration(Configuration::Preset::Debug, parameters);
project.addPresetConfiguration(Configuration::Preset::Release, parameters);
project.addPresetConfiguration(Configuration::Preset::DebugCoverage, parameters);
addSaneCppLibraries(project, parameters); // add all SC Libraries
project.addFiles(imgui.packageLocalDirectory.view(), "*.cpp");
project.addFiles(sokol.packageLocalDirectory.view(), "*.h");
SC_TRY(addCompiledLibraryRootDefine(project, parameters));
SC_TRY(addHotReloadIncludePathsDefine(project, parameters, imgui.packageLocalDirectory.view()));
project.addExportAllLibraries(); // Export all SC libraries for plugins
SC_TRY(project.addExportDirectories({imgui.packageLocalDirectory.view()}));
project.link.preserveExportedSymbols = true;
if (parameters.platform == Platform::Apple)
{
project.addFiles("Examples/SCExample", "*.m"); // add all .m from SCExample directory
project.addLinkFrameworks({"Metal", "MetalKit", "QuartzCore"});
project.addLinkFrameworksMacOS({"Cocoa"});
project.addLinkFrameworksIOS({"UIKit", "Foundation"});
}
else
{
project.addFiles("Examples/SCExample", "*.c"); // add all .c from SCExample directory
if (parameters.platform == Platform::Linux)
{
project.addLinkLibraries({"GL", "EGL", "X11", "Xi", "Xcursor"});
}
}
if (parameters.platform == Platform::Windows)
{
project.addDefines({"IMGUI_API=__declspec( dllexport )"});
project.addLinkLibraries({"d3d11", "dxgi", "gdi32", "kernel32", "shell32", "user32"});
}
else
{
project.addDefines({"IMGUI_API=__attribute__((visibility(\"default\")))"});
}
project.addFiles("Examples/SCExample", "**.h"); // add all .h from SCExample directory recursively
project.addFiles("Examples/SCExample", "**.cpp"); // add all .cpp from SCExample directory recursively
if (not project.addExportLibraries({"Async", "Containers", "ContainersReflection", "File", "FileSystem",
"Foundation", "Http", "Memory", "Plugin", "Process", "Reflection",
"SerializationBinary", "SerializationText", "Socket", "Strings", "Threading"}))
{
return Result::Error("Failed to configure exported SCExample libraries");
}
if (isWindowsGNUTarget)
{
SourceFiles sokolWarnings;
sokolWarnings.addSelection("Examples/SCExample", "SCExampleSokol.c");
sokolWarnings.compile.disableClangWarnings({"unknown-pragmas"});
project.addSpecificFileFlags(sokolWarnings);
SourceFiles imguiWarnings;
imguiWarnings.addSelection(imgui.packageLocalDirectory.view(), "*.cpp");
imguiWarnings.compile.disableClangWarnings({"uninitialized-const-pointer"});
project.addSpecificFileFlags(imguiWarnings);
}
SC_TRY(workspace.projects.push_back(move(project)));
return Result(true);
}
Result configureExamplesConsole(const Parameters& parameters, Workspace& workspace)
{
// Read all projects from Examples directory
FileSystemIterator::FolderState entries[2];
FileSystemIterator fsi;
String path;
SC_TRY(Path::join(path, {parameters.directories.libraryDirectory.view(), "Examples"}));
fsi.init(path.view(), entries);
// Create a project for folder containing a .cpp file
while (fsi.enumerateNext())
{
FileSystemIterator::Entry entry = fsi.get();
if (not entry.isDirectory() or entry.name == EXAMPLE_PROJECT_NAME)
continue;
StringView name, extension;
SC_TRY(Path::parseNameExtension(entry.name, name, extension));
Project project;
project.targetType = TargetType::ConsoleExecutable;
project.name = name;
project.targetName = name;
// All relative paths are evaluated from this project root directory.
project.setRootDirectory(parameters.directories.libraryDirectory.view());
project.addPresetConfiguration(Configuration::Preset::Debug, parameters);
project.addPresetConfiguration(Configuration::Preset::Release, parameters);
#if 0 // Flip this ifdef to add all Sane C++ Libraries instead of using the SC.cpp unity build
addSaneCppLibraries(project, parameters);
#else
project.addFile("SC.cpp"); // Unity build file including all Sane C++ Libraries
if (parameters.platform == Platform::Apple)
{
project.addLinkFrameworks({"CoreFoundation", "CoreServices"});
}
if (parameters.platform != Platform::Windows)
{
project.addLinkLibraries({"dl", "pthread"});
}
#endif
project.addFiles(entry.path, "**.cpp");
workspace.projects.push_back(move(project));
}
return Result(true);
}
Result configureSingleFileLibs(Definition& definition, const Parameters& parameters)
{
Workspace workspace = {"SCSingleFileLibs"};
// Read all single file libraries from the _Build/_SingleFileLibrariesTest directory
FileSystemIterator::FolderState entries[1];
FileSystemIterator fsi;
String path;
SC_TRY(Path::join(path, {parameters.directories.libraryDirectory.view(), "_Build", "_SingleFileLibrariesTest"}));
SC_TRY_MSG(fsi.init(path.view(), entries), "Cannot access _Build/_SingleFileLibrariesTest");
// Create a project for each single file library
while (fsi.enumerateNext())
{
StringView name, extension;
SC_TRY(Path::parseNameExtension(fsi.get().name, name, extension));
if (extension != "cpp" or not name.startsWith("Test_"))
continue; // Only process .cpp files
Project project;
project.targetType = TargetType::ConsoleExecutable;
project.name = name;
project.targetName = project.name;
// All relative paths are evaluated from this project root directory.
project.setRootDirectory(parameters.directories.libraryDirectory.view());
project.addPresetConfiguration(Configuration::Preset::Debug, parameters);
project.addPresetConfiguration(Configuration::Preset::Release, parameters);
// Link C++ stdlib to avoid needing to link Memory library to define __cxa_guard_acquire etc.
project.addDefines({"SC_COMPILER_ENABLE_STD_CPP=1"});
project.configurations[0].compile.enableStdCpp = true;
project.configurations[1].compile.enableStdCpp = true;
project.addIncludePaths({"_Build/_SingleFileLibraries"});
project.addFile(fsi.get().path);
// Libraries to link
if (parameters.platform == Platform::Apple)
{
project.addLinkFrameworks({"CoreFoundation", "CoreServices"});
}
if (parameters.platform != Platform::Windows)
{
project.addLinkLibraries({"dl", "pthread"});
}
workspace.projects.push_back(move(project));
}
definition.workspaces.push_back(move(workspace));
return Result(true);
}
static constexpr StringView DEFAULT_WORKSPACE = "SCWorkspace";
Result configure(Definition& definition, const Parameters& parameters)
{
Workspace defaultWorkspace = {DEFAULT_WORKSPACE};
SC_TRY(configureTests(parameters, defaultWorkspace));
SC_TRY(configureSCBuildTest(parameters, defaultWorkspace));
SC_TRY(configureSCSharedLibrary(parameters, defaultWorkspace));
SC_TRY(configureTestSTLInterop(parameters, defaultWorkspace));
SC_TRY(configureExamplesConsole(parameters, defaultWorkspace));
SC_TRY(configureExamplesGUI(parameters, defaultWorkspace));
definition.workspaces.push_back(move(defaultWorkspace));
// Ignore errors from configuring single file libraries
(void)configureSingleFileLibs(definition, parameters);
return Result(true);
}
Result executeAction(const Action& action) { return Build::Action::execute(action, configure, DEFAULT_WORKSPACE); }
} // namespace Build
#if !defined(SC_TOOLS_COMPILED_SEPARATELY) && !defined(SC_TOOLS_IMPORT)
StringView Tools::Tool::getToolName() { return "SC-build"; }
StringView Tools::Tool::getDefaultAction() { return "configure"; }
Result Tools::Tool::runTool(Tools::Tool::Arguments& arguments) { return Tools::runBuildTool(arguments); }
#endif
} // namespace SC
#define SC_COMPILER_WARNING_POP
Pops warning from inside a macro.
Definition Compiler.h:123
#define SC_COMPILER_WARNING_PUSH_UNUSED_RESULT
Disables unused-result warning (due to ignoring a return value marked as [[nodiscard]])
Definition Compiler.h:159
constexpr T && move(T &value)
Converts an lvalue to an rvalue reference.
Definition Compiler.h:279
#define SC_TRY_MSG(expression, failedMessage)
Checks the value of the given expression and if failed, returns a result with failedMessage to caller...
Definition Result.h:60
#define SC_TRY(expression)
Checks the value of the given expression and if failed, returns this value to caller.
Definition Result.h:49

Public API

The public API in Tools/SC-build/Build.h currently exposes:

  • Definition, Workspace, Project, and Configuration for the build graph
  • Machine and TargetEnvironment for explicit host / target modeling
  • TargetType::{ConsoleExecutable, GUIApplication, SharedLibrary, StaticLibrary}
  • Generator::{Native, XCode, VisualStudio2022, VisualStudio2019, Make}
  • Toolchain::{HostDefault, Clang, FilC, GCC, MSVC, ClangCL, LLVMMingw, CustomDriver}
  • RunnerSpec::{Auto, None, Wine, QEMU, Custom}
  • ExecutionOptions for native-backend parallelism / verbosity knobs
  • OutputMode::{Quiet, Normal, Verbose} and ExecutionOptions::outputMode for native-backend presentation control
  • Project::addSpecificFileFlags for per-file flag groups
  • Project::addExportLibraries, addExportAllLibraries, and addExportDirectories for plugin host exports

Backend Matrix

Backend Platforms Target kinds compile_commands.json Notes
Native macOS, Linux, Windows hosts Console executable, GUI application, shared library, static library Yes Direct compile / run / coverage flow; no project files are generated
XCode Apple Console executable, GUI application, shared library, static library Yes GUI apps also emit storyboard / assets; Intel32 and Wasm architectures are unsupported
VisualStudio2022 / VisualStudio2019 Windows Console executable, GUI application, shared library, static library No The repository defaults to VS2022, but VS2019 generation is still available
Make Apple, Linux Console executable, GUI application, shared library, static library Yes on clang-style flows GCC builds still compile, but Makefile-side compile database generation is unavailable there

Native Backend

The standalone backend is selected through SC::Build::Generator::Native.

Current implemented scope:

  • Host platforms: macOS, Linux and Windows
  • Toolchain families exposed by the API: HostDefault, Clang, FilC, GCC, MSVC, ClangCL, LLVMMingw, and CustomDriver
  • Experimental compiler-first track: FilC is now exposed by the API and CLI through --toolchain filc, but it is Linux-only, compiler-first, and not a public target-profile row yet
  • Target kinds: console executables, GUI applications, shared libraries, and static libraries
  • Dependency tracking: compiler-generated dependency files / dependency output
  • Incrementality: skips up-to-date compile and link steps
  • Workspace dependency ordering: native executable targets can link workspace-local static libraries by name
  • Parallelism: compile fan-out is controlled by ExecutionOptions::maxParallelJobs
  • Output modes: quiet hides progress lines and successful child output, normal shows progress plus grouped failures and summaries, and verbose also shows rebuild traces, skip lines, and successful compile output
  • Output layout: _Build/_Outputs, _Build/_Intermediates, and _Build/_BuildCache

Current cross-compilation scope:

  • macOS and Linux hosts can compile windows-gnu-x86_64 and windows-gnu-arm64 through packaged llvm-mingw
  • Linux hosts can now experiment with Fil-C through SC-package install filc plus build ... --toolchain filc; this is currently limited to native Linux x86_64 output and is still outside the public support matrix while compile/start validation hardens
  • Linux glibc and musl target profiles now exist as first-class native-backend profiles; they shape canonical target triples and sysroot flags, and macOS hosts now auto-select both a packaged LLVM toolchain and packaged Linux sysroots for them when the caller has not provided explicit compiler paths
  • macOS now has real native-backend SCTest compile validation for linux-glibc-x86_64, linux-glibc-arm64, linux-musl-x86_64, and linux-musl-arm64
  • build run can now wrap foreign Linux targets through qemu-user; on macOS this can auto-resolve host qemu-* executables from PATH, and the runner passes -L <sysroot> so dynamically linked Linux targets can find their loader and libraries
  • macOS hosts can acquire a portable MSVC + Windows SDK package with ./SC.sh package install msvc and compile windows-msvc-x86_64 and windows-msvc-arm64 through the native backend
  • Linux arm64 hosts can now validate the same portable MSVC path end-to-end for windows-msvc-x86_64 and windows-msvc-arm64; the package tool auto-prefers a generated box64 + wine64 wrapper when those host tools are installed, can fall back to an auto-installed packaged Linux Wine runner that resolves a maintained generic-arm box64 build when system box64 is absent, and native build run can now auto-install a separate ARM64 Wine runtime for windows-*-arm64 execution while still accepting plain wine64 / wine or an explicit --wine / SC_MSVC_WINE override
  • SC-package install msvc also accepts explicit --import-directory <path> and --wine <path> overrides so imported layouts and custom Wine wrappers no longer have to be driven only through environment variables
  • Once a portable MSVC package is installed, later native-backend Windows MSVC builds can reuse the recorded wrapper path from sc-msvc-package.json instead of requiring SC_MSVC_WINE or host Wine discovery again
  • Existing portable MSVC layouts can now repair missing sc-msvc-package.json metadata and wrapper scripts in place, and SDK version detection now falls back from Windows Kits/10/bin to Include or Lib when the SDK tools directory is absent
  • Existing packaged Linux Wine runners now also repair their launcher scripts in place, so later portable MSVC wrapper updates do not require deleting the cached runner package first
  • Portable MSVC caches are now host-specific (macOS vs Linux) so shared workspaces do not reuse the wrong recorded Wine runner path across hosts
  • On Linux arm64, the packaged MSVC validation story is now real for both target architectures: windows-msvc-x86_64 has a clean native-backend SCTest compile validation plus a targeted BaseTest/new-delete run through the maintained packaged Box64 runner, and windows-msvc-arm64 now also has a clean native-backend SCTest compile plus a targeted BaseTest/new-delete run through an auto-installed native ARM64 Wine runner
  • On Linux arm64, build run now keeps Windows console targets on plain packaged wine instead of auto-switching to wineconsole, because the current Box64 Wine console path is less reliable than plain wine on this host
  • build run can auto-route windows-gnu-x86_64 executables through Wine on macOS and Linux, and Linux arm64 now also smoke-runs windows-gnu-arm64 through the packaged native ARM64 Wine runner
  • On Linux arm64, that same native build run path now auto-prefers generated box64 + wine64 wrappers when the host provides those commands, and console targets still switch to a sibling wineconsole --backend=curses wrapper when it is available on Linux x64 hosts
  • The current MSVC-via-Wine validation target is still narrower than the Windows GNU path: macOS has a real compile, link, and tiny-start smoke for windows-msvc-x86_64, while Linux arm64 now has targeted SCTest BaseTest/new-delete smokes for both windows-msvc-x86_64 and windows-msvc-arm64
  • build run now routes Wine launches through cmd /c with Windows-style paths, which fixes real macOS Wine startup for windows-gnu-x86_64
  • windows-gnu-arm64 and windows-msvc-arm64 are no longer blocked by a hardcoded CLI rule; Linux arm64 now has a real ARM64-capable Wine runtime for targeted smokes, while the packaged macOS Wine runner still does not ship an ARM64 Windows loader
  • Cross-target plugin tests remain out of scope for now because the current plugin test flow assumes MSVC-oriented Windows behavior

Typical native commands:

./SC.sh build compile SCBuildTest --config Debug --generator native
./SC.sh build compile SCBuildTest -c d -g native -a arm64 --verbose
./SC.sh package install llvm
./SC.sh build compile SCTest --target linux-glibc-arm64 --output quiet
./SC.sh build compile SCTest --target linux-musl-x86_64 --output quiet
./SC.sh build compile SCTest --target windows-gnu-x86_64 --output quiet
./SC.sh build compile SCTest --target windows-gnu-arm64 --output quiet
./SC.sh package install filc --import-directory /home/user/filc-0.678-linux-x86_64
./SC.sh build compile SaneHttpGet --generator native --toolchain filc --output quiet
./SC.sh package install msvc
./SC.sh package install msvc --import-directory /opt/msvc-portable --wine /opt/bin/wine-wrapper
./SC.sh build compile SCTest --target windows-msvc-x86_64 --output quiet
./SC.sh build compile SCTest --target windows-msvc-arm64 --output quiet
./SC.sh build compile SCTest --target windows-gnu-x86_64 --triple x86_64-custom-windows-gnu --sysroot /opt/sysroots/windows
./SC.sh build run SCTest --target windows-gnu-x86_64 --runner auto -- --test BaseTest --test-section new/delete
./SC.sh build run SCBuildTest --config Debug --generator native -- --test "BuildTest"
SC.bat build compile SCTest Debug native

Important current limits:

  • macOS is the only non-Linux host with a validated packaged Linux sysroot path today
  • The QEMU runner path is implemented, but real repository-side QEMU execution is not yet validated in CI against a host-installed qemu-user package
  • Windows-host Linux-target packaging and validation are still pending
  • Fil-C is still an experimental compiler-first Linux track: no public linux-filc-* target profile exists yet, Linux x86_64 is the only intended output slice for the first milestone, and Linux arm64 hosts may still require imported local installs plus host-specific translation/linker helpers during validation
  • Windows native sysroot selection is not implemented yet
  • run is valid only for executable targets and only when a single project is selected
  • The repository build configure command does not rely on a Windows-native generation pass because native builds do not need generated project files

Per-file Flags

Project::addSpecificFileFlags is implemented and used by this repository.

The current backend behavior is:

Backend Per-file behavior
Native Full compile-flag merge for the selected files
Make Full compile-flag merge, emitted as grouped per-file variables in the generated Makefile
XCode Per-file include paths, defines, and non-MSVC warning disables are emitted; other per-file compile knobs are not yet emitted, and conflicting higher-level flags cannot be removed
VisualStudio2019 / VisualStudio2022 Per-file include paths, defines, and MSVC warning disables are emitted; other per-file compile knobs are not yet emitted

The repository example lives in Tools/SC-build.cpp, where the SCTest target applies a dedicated define, include path, and warning disables to a selected subset of files.

Plugin Host Exports

For host executables using the Plugin library:

  • Project::addExportLibraries adds the needed SC_EXPORT_LIBRARY_<LIBRARY>=1 defines to the host executable
  • Project::addExportAllLibraries does the same for every Sane C++ library
  • On Linux both helpers also add -rdynamic

LinkFlags::preserveExportedSymbols currently performs fine-grained exported-symbol preservation only on the standalone native backend and on generated Makefiles. The XCode backend currently falls back to disabling dead-code stripping when preservation is requested.

Generated Layout

Repository-generated paths are currently:

  • _Build/_Projects/XCode/SCWorkspace/...
  • _Build/_Projects/VisualStudio2022/SCWorkspace/...
  • _Build/_Projects/VisualStudio2019/SCWorkspace/...
  • _Build/_Projects/Make/SCWorkspace/apple/...
  • _Build/_Projects/Make/SCWorkspace/linux/...
  • _Build/_Outputs/<expanded-configuration>/...
  • _Build/_Intermediates/<project>/<expanded-configuration>/...
  • _Build/_Intermediates/<workspace>/compile_commands.json for native workspace databases
  • _Build/_BuildCache/... for native-backend state

How To Debug

  1. Build SC-build itself through one of the repository build tasks or through build compile
  2. Launch the generated SC-build executable under your debugger with the same arguments passed by the bootstrap

With any debugger or IDE other than VSCode, debug _Build/_Tools/<Platform>/SC-build (or _Build/_Tools/Windows/SC-build.exe) and pass arguments similar to:

"name": "Debug SC-build [linux] (lldb)",
"type": "lldb",
"request": "launch",
"program": "${workspaceFolder}/_Build/_Tools/Linux/SC-build",
"args": [
"${workspaceFolder}",
"${workspaceFolder}/Tools",
"${workspaceFolder}/_Build",
"build",
"configure",
"SCTest",
"Debug"
],

Videos

This is the list of videos that have been recorded showing some of the internal thoughts that have been going into this library:

Blog

Some relevant blog posts are: