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 standalone native backend is now the default day-to-day workflow on macOS, Linux, and Windows, while the generated-project backends remain available for IDE and project-file flows.

Description

Build descriptions are regular C++ files, conventionally named SC-build.cpp, that are compiled on the fly through the repository bootstraps SC.sh / SC.bat or the external-project launchers SC-build.sh / SC-build.bat / SC-build.ps1.

For the external bootstrap workflow, including vendored, shared-checkout, and standalone-cache usage, see External SC::Build Bootstrap.

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 normal command-line shape is:

./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]

Generated-project workflows also use:

./SC.sh build configure [workspace:project | project]

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 native on Windows, macOS, and Linux
  • configure is primarily for generated backends; normal native compile / run flows build 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
#define SC_BUILD_SOURCE 1
#include "SC-build.h"
#include "../Libraries/FileSystemIterator/FileSystemIterator.h"
#include "../Libraries/Process/Process.h"
#include "SC-build/Build.inl"
#include "SC-build/BuildCLI.h"
#if !defined(SC_TOOLS_COMPILED_SEPARATELY)
#define SC_TOOLS_IMPORT
#include "SC-package.cpp"
#undef SC_TOOLS_IMPORT
#endif
#include "SC-build/BuildCLI.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 addSaneCppDebugVisualizers(Project& project, const Parameters& parameters)
{
if (parameters.generator == Generator::VisualStudio2022)
{
String debugVisualizersRoot = StringEncoding::Utf8;
(void)Path::join(debugVisualizersRoot,
{parameters.directories.libraryDirectory.view(), "Support/DebugVisualizers/MSVC"});
project.addFiles(debugVisualizersRoot.view(), "*.natvis");
}
else
{
String debugVisualizersRoot = StringEncoding::Utf8;
(void)Path::join(debugVisualizersRoot,
{parameters.directories.libraryDirectory.view(), "Support/DebugVisualizers/LLDB"});
project.addFiles(debugVisualizersRoot.view(), "*");
}
}
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 AWAIT_TEST_PROJECT_NAME = "SCAwaitTest";
static constexpr StringView BUILD_TEST_PROJECT_NAME = "SCBuildTest";
Result configureTests(const Parameters& parameters, Workspace& workspace)
{
Project project = {TEST_PROJECT_NAME};
project.setRootDirectory(parameters.directories.projectDirectory.view());
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|"
".*\\/Assert.h|"
".*\\/PluginMacros.h|"
".*\\/ProcessPosixFork.inl|"
".*\\/EnvironmentTable.h|"
".*\\/InitializerList.h|"
".*\\/Reflection/.*\\.*|"
".*\\/ContainersReflection/.*\\.*|"
".*\\/SerializationBinary/.*\\.*|"
".*\\/Extra/Deprecated/.*\\.*";
if (parameters.platform == Platform::Linux)
{
project.addPresetConfiguration(Configuration::Preset::Debug, parameters, "DebugValgrind");
project.configurations.back().compile.enableASAN = false;
project.configurations.back().link.enableASAN = false;
}
project.addDefines({"SC_COMPILER_ENABLE_CONFIG=1", "SC_TOOLS_COMPILED_SEPARATELY=1"});
SC_TRY(addCompiledLibraryRootDefine(project, parameters));
project.addIncludePaths({
".",
"Tests/SCTest",
});
SC_TRY(addSaneCppLibraries(project, parameters, Libraries::Multiple));
SC_TRY(project.addIncludePaths({parameters.directories.libraryDirectory.view()}));
addSaneCppDebugVisualizers(project, parameters);
project.addFiles("Tests/SCTest", "*.cpp");
project.addFiles("Tests/SCTest", "*.h");
project.addFiles("Tests/Libraries", "**.c*");
project.addFiles("Tests/Libraries", "**.inl");
project.removeFiles("Tests/Libraries/Await", "AwaitTest.cpp");
project.removeFiles("Tests/Libraries/Build", "BuildTest.cpp");
project.addFiles("Tests/Support", "**.cpp");
project.addFiles("Tests/Tools", "**.cpp");
project.addFiles("Tools", "SC-*.cpp");
project.addFiles("Tools", "*.h");
if (not project.addExportLibraries({"Foundation", "Memory", "Strings", "Containers"}))
{
return Result::Error("Failed to configure exported SCTest libraries");
}
project.link.preserveExportedSymbols = true;
project.addFiles("Extra/Deprecated/Tests", "**.cpp");
project.addFiles("Extra/Deprecated/Libraries", "**.h");
project.addFiles("Extra/Deprecated/Libraries", "**.cpp");
SourceFiles specificFiles;
specificFiles.addSelection("Tests/SCTest", "*.cpp");
specificFiles.removeSelection("Tests/SCTest", "SCTest.cpp");
specificFiles.compile.addDefines({"SC_SPACES_SPECIFIC_DEFINE=1"});
specificFiles.compile.addIncludePaths({"../Directory With Spaces"});
specificFiles.compile.disableWarnings({4100});
specificFiles.compile.disableWarnings({"unused-parameter"});
specificFiles.compile.disableClangWarnings({"reserved-user-defined-literal"});
project.addSpecificFileFlags(specificFiles);
SC_TRY(workspace.projects.push_back(move(project)));
return Result(true);
}
Result configureSCBuildTest(const Parameters& parameters, Workspace& workspace)
{
Project project = {BUILD_TEST_PROJECT_NAME};
project.setRootDirectory(parameters.directories.projectDirectory.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",
});
SC_TRY(addSaneCppLibraries(project, parameters, Libraries::Multiple));
SC_TRY(project.addIncludePaths({parameters.directories.libraryDirectory.view()}));
addSaneCppDebugVisualizers(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 configureSCAwaitTest(const Parameters& parameters, Workspace& workspace)
{
Project project = {AWAIT_TEST_PROJECT_NAME};
project.setRootDirectory(parameters.directories.projectDirectory.view());
project.addPresetConfiguration(Configuration::Preset::Debug, parameters);
project.addPresetConfiguration(Configuration::Preset::Release, parameters);
project.files.compile.enableStdCpp = true;
project.files.compile.cppStandard = CppStandard::CPP20;
project.addDefines(
{"SC_COMPILER_ENABLE_CONFIG=1", "SC_COMPILER_ENABLE_STD_CPP=1", "SC_TOOLS_COMPILED_SEPARATELY=1"});
SC_TRY(addCompiledLibraryRootDefine(project, parameters));
project.addIncludePaths({
".",
"Tests/SCAwaitTest",
});
SC_TRY(addSaneCppLibraries(project, parameters, Libraries::Multiple));
SC_TRY(project.addIncludePaths({parameters.directories.libraryDirectory.view()}));
addSaneCppDebugVisualizers(project, parameters);
project.addFiles("Tests/SCAwaitTest", "*.cpp");
project.addFiles("Tests/SCAwaitTest", "*.h");
project.addFiles("Tests/Libraries/Await", "**.cpp");
SC_TRY(workspace.projects.push_back(move(project)));
return Result(true);
}
Result configureSCSharedLibrary(const Parameters& parameters, Workspace& workspace)
{
Project project = {"SC", TargetType::SharedLibrary};
project.setRootDirectory(parameters.directories.projectDirectory.view());
project.addPresetConfiguration(Configuration::Preset::Debug, parameters);
project.addPresetConfiguration(Configuration::Preset::Release, parameters);
project.addIncludePaths({"."});
SC_TRY(addSaneCppLibraries(project, parameters, Libraries::Multiple));
SC_TRY(project.addIncludePaths({parameters.directories.libraryDirectory.view()}));
addSaneCppDebugVisualizers(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 = {"InteropSTL"};
project.setRootDirectory(parameters.directories.projectDirectory.view());
project.addPresetConfiguration(Configuration::Preset::Debug, parameters);
project.addPresetConfiguration(Configuration::Preset::Release, parameters);
project.files.compile.enableStdCpp = true;
project.files.compile.enableExceptions = true;
project.files.compile.enableRTTI = true;
project.files.compile.cppStandard = CppStandard::CPP17;
project.addDefines({"SC_COMPILER_ENABLE_STD_CPP=1"});
SC_TRY(addCompiledLibraryRootDefine(project, parameters));
project.addIncludePaths({"."});
SC_TRY(addSaneCppLibraries(project, parameters, Libraries::Multiple));
SC_TRY(project.addIncludePaths({parameters.directories.libraryDirectory.view()}));
addSaneCppDebugVisualizers(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 = {EXAMPLE_PROJECT_NAME, TargetType::GUIApplication};
const bool isWindowsGNUTarget = parameters.platform == Platform::Windows and
(parameters.targetMachine.environment == TargetEnvironment::WindowsGNU or
parameters.toolchain.family == Toolchain::LLVMMingw);
project.setRootDirectory(parameters.directories.projectDirectory.view());
project.iconPath = "Documentation/Doxygen/SC.svg";
Tools::Package sokol;
SC_TRY(Tools::installSokol(parameters.directories, sokol));
Tools::Package imgui;
SC_TRY(Tools::installDearImGui(parameters.directories, imgui));
project.addIncludePaths({".", sokol.packageLocalDirectory.view(), imgui.packageLocalDirectory.view()});
project.addPresetConfiguration(Configuration::Preset::Debug, parameters);
project.addPresetConfiguration(Configuration::Preset::Release, parameters);
project.addPresetConfiguration(Configuration::Preset::DebugCoverage, parameters);
SC_TRY(addSaneCppLibraries(project, parameters, Libraries::Multiple));
SC_TRY(project.addIncludePaths({parameters.directories.libraryDirectory.view()}));
addSaneCppDebugVisualizers(project, parameters);
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();
SC_TRY(project.addExportDirectories({imgui.packageLocalDirectory.view()}));
project.link.preserveExportedSymbols = true;
if (parameters.platform == Platform::Apple)
{
project.addFiles("Examples/SCExample", "*.m");
project.addLinkFrameworks({"Metal", "MetalKit", "QuartzCore"});
project.addLinkFrameworksMacOS({"Cocoa"});
project.addLinkFrameworksIOS({"UIKit", "Foundation"});
}
else
{
project.addFiles("Examples/SCExample", "*.c");
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");
project.addFiles("Examples/SCExample", "**.cpp");
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)
{
FileSystemIterator::FolderState entries[2];
FileSystemIterator fsi;
String path;
SC_TRY(Path::join(path, {parameters.directories.projectDirectory.view(), "Examples"}));
fsi.init(path.view(), entries);
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;
project.setRootDirectory(parameters.directories.projectDirectory.view());
project.addPresetConfiguration(Configuration::Preset::Debug, parameters);
project.addPresetConfiguration(Configuration::Preset::Release, parameters);
if (name == "AwaitEcho")
{
project.files.compile.enableStdCpp = true;
project.files.compile.cppStandard = CppStandard::CPP20;
project.addDefines({"SC_COMPILER_ENABLE_STD_CPP=1"});
SC_TRY(addSaneCppLibraries(project, parameters));
project.addFiles("Libraries/Await", "**.cpp");
project.addFiles("Libraries/Await", "**.h");
}
else
{
SC_TRY(addSaneCppLibraries(project, parameters));
}
SC_TRY(project.addIncludePaths({parameters.directories.libraryDirectory.view()}));
project.addFiles(entry.path, "**.cpp");
workspace.projects.push_back(move(project));
}
return Result(true);
}
Result configureSingleFileLibs(Definition& definition, const Parameters& parameters)
{
Workspace workspace = {"SCSingleFileLibs"};
FileSystemIterator::FolderState entries[1];
FileSystemIterator fsi;
String path;
SC_TRY(Path::join(path, {parameters.directories.projectDirectory.view(), "_Build", "_SingleFileLibrariesTest"}));
SC_TRY_MSG(fsi.init(path.view(), entries), "Cannot access _Build/_SingleFileLibrariesTest");
while (fsi.enumerateNext())
{
StringView name, extension;
SC_TRY(Path::parseNameExtension(fsi.get().name, name, extension));
if (extension != "cpp" or not name.startsWith("Test_"))
continue;
Project project;
project.targetType = TargetType::ConsoleExecutable;
project.name = name;
project.targetName = project.name;
project.setRootDirectory(parameters.directories.projectDirectory.view());
project.addPresetConfiguration(Configuration::Preset::Debug, parameters);
project.addPresetConfiguration(Configuration::Preset::Release, parameters);
project.addDefines({"SC_COMPILER_ENABLE_STD_CPP=1"});
project.configurations[0].compile.enableStdCpp = true;
project.configurations[1].compile.enableStdCpp = true;
if (name == "Test_SaneCppAwait")
{
project.files.compile.cppStandard = CppStandard::CPP20;
}
project.addIncludePaths({"_Build/_SingleFileLibraries"});
project.addFile(fsi.get().path);
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);
}
Result configure(Definition& definition, const Parameters& parameters)
{
Workspace defaultWorkspace = {"SCWorkspace"};
SC_TRY(configureTests(parameters, defaultWorkspace));
SC_TRY(configureSCBuildTest(parameters, defaultWorkspace));
SC_TRY(configureSCAwaitTest(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));
(void)configureSingleFileLibs(definition, parameters);
return Result(true);
}
} // namespace Build
} // 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
  • Libraries::{SingleFile, Multiple} plus addSaneCppLibraries(...) for adding Sane C++ Libraries to a target
  • 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
  • Directories::projectDirectory for the root of the project being configured, distinct from the SaneCppLibraries checkout
  • Project::addSpecificFileFlags for per-file flag groups
  • Project::addExportLibraries, addExportAllLibraries, and addExportDirectories for plugin host exports

Consuming Sane C++ Libraries

External SC-build.cpp files can add Sane C++ Libraries to a project with:

SC_TRY(addSaneCppLibraries(project, parameters));

This defaults to Libraries::SingleFile, which adds SC.cpp.

To compile the individual library sources instead, use:

SC_TRY(addSaneCppLibraries(project, parameters, Libraries::Multiple));

Call the helper after setting the project root directory, typically with project.setRootDirectory(parameters.directories.projectDirectory.view()).

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 Explicit generated-project workflow for Visual Studio
Make Apple, Linux Console executable, GUI application, shared library, static library Yes on clang-style flows Explicit generated-project workflow for Make; 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; the native runner can reuse a managed SC-package install qemu registration or fall back to host qemu-* executables from PATH, and it 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
./SC.sh build compile SCBuildTest -c d -a arm64 --verbose
./SC.sh package install llvm
./SC.sh package install qemu --import-directory /opt/qemu-user
./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 --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 -- --test "BuildTest"
SC.bat build compile SCTest --config Debug

Important current limits:

  • macOS is the only non-Linux host with a validated packaged Linux sysroot path today
  • The QEMU runner path can now reuse a managed imported qemu-user layout, but real repository-side QEMU execution is not yet validated in CI against a real host QEMU install
  • 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 remains available for generated-project workflows; native builds do not need generated project files first

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.

SC_BUILD Define

When SC-build.cpp is compiled as the build-definition tool, SC::Build defines SC_BUILD=1 for that compilation.

This allows one SC-build.cpp file to provide SC::Build::configure(...) in the SC_BUILD branch and a normal main() in the #else branch if the same file is also added to the built target.

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

Generated Project Workflows

Use build configure when you explicitly want generated projects for IDEs or Make:

./SC.sh build configure
SC.bat build configure

Typical examples:

  • XCode: open _Build/_Projects/XCode/SCWorkspace/SCWorkspace.xcworkspace
  • Visual Studio: open _Build/_Projects/VisualStudio2022/SCWorkspace/SCWorkspace.sln
  • Make: use _Build/_Projects/Make/SCWorkspace/apple or _Build/_Projects/Make/SCWorkspace/linux

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: