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
  • --abi is reserved for a future public ABI selector; use --target for glibc, musl, GNU, or MSVC selection today
  • --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
  • --runner auto prefers direct execution for runnable native Linux outputs before falling back to Wine or QEMU
  • 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
{
SC_TRY(
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";
static constexpr StringView AWAIT_COROUTINE_SHIM_TEST_PROJECT_NAME = "SCAwaitCoroutineShimTest";
static constexpr StringView STD_CPP_HEADER_NO_LINK_TEST_PROJECT_NAME = "SCStdCppHeaderNoLinkTest";
static constexpr StringView ZLIB_FILC_PROJECT_NAME = "ZLibFilC";
static void configureSaneStrictNoStdCpp(Project& project)
{
// These targets intentionally preserve the old pressure-test mode: SC code cannot include standard C++ headers
// and SC provides the tiny runtime shims instead of linking the C++ standard-library runtime.
project.files.compile.includeStdCpp = false;
project.link.linkStdCpp = false;
project.saneCpp.enabled = true;
project.saneCpp.provideCppRuntimeShims = true;
}
static void configureSaneStdHeadersNoRuntime(Project& project)
{
// Used by coroutine/Await coverage: standard C++ headers are allowed, but linking libstdc++/libc++ is still
// forbidden so we keep proving the explicit-runtime-shim path.
project.files.compile.includeStdCpp = true;
project.link.linkStdCpp = false;
project.saneCpp.enabled = true;
project.saneCpp.provideCppRuntimeShims = true;
}
static Result configureZLibFilC(const Parameters& parameters, Workspace& workspace)
{
ProcessEnvironment environment;
StringSpan sourceDirectory;
StringSpan outputDirectory;
StringSpan intermediateDirectory;
if (not environment.get("SC_ZLIB_FILC_SOURCE_DIR", sourceDirectory) or sourceDirectory.isEmpty())
{
return Result(true);
}
SC_TRY_MSG(environment.get("SC_ZLIB_FILC_OUTPUT_DIR", outputDirectory) and not outputDirectory.isEmpty(),
"SC_ZLIB_FILC_OUTPUT_DIR is required when SC_ZLIB_FILC_SOURCE_DIR is set");
SC_TRY_MSG(environment.get("SC_ZLIB_FILC_INTERMEDIATE_DIR", intermediateDirectory) and
not intermediateDirectory.isEmpty(),
"SC_ZLIB_FILC_INTERMEDIATE_DIR is required when SC_ZLIB_FILC_SOURCE_DIR is set");
Project project = {ZLIB_FILC_PROJECT_NAME, TargetType::SharedLibrary};
project.setRootDirectory(sourceDirectory);
SC_TRY(project.targetName.assign("libz"));
project.addPresetConfiguration(Configuration::Preset::Debug, parameters);
project.addPresetConfiguration(Configuration::Preset::Release, parameters);
for (Configuration& configuration : project.configurations)
{
SC_TRY(configuration.outputPath.assign(outputDirectory));
SC_TRY(configuration.intermediatesPath.assign(intermediateDirectory));
SC_TRY(configuration.compile.defines.push_back("ZEXPORT=__attribute__((visibility(\"default\")))"));
SC_TRY(configuration.compile.defines.push_back("ZEXPORTVA=__attribute__((visibility(\"default\")))"));
SC_TRY(configuration.compile.defines.push_back("HAVE_UNISTD_H"));
SC_TRY(configuration.compile.defines.push_back("HAVE_HIDDEN"));
SC_TRY(configuration.compile.disableWarnings({"implicit-fallthrough"}));
configuration.link.enableDeadCodeStripping = false;
}
static constexpr StringView sources[] = {
"adler32.c", "compress.c", "crc32.c", "deflate.c", "gzclose.c", "gzlib.c", "gzread.c", "gzwrite.c",
"infback.c", "inffast.c", "inflate.c", "inftrees.c", "trees.c", "uncompr.c", "zutil.c",
};
for (const StringView source : sources)
{
SC_TRY(project.addFile(source));
}
SC_TRY(project.addIncludePaths({"."}));
SC_TRY(workspace.projects.push_back(move(project)));
return Result(true);
}
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;
}
configureSaneStrictNoStdCpp(project);
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);
configureSaneStrictNoStdCpp(project);
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.cppStandard = CppStandard::CPP20;
configureSaneStdHeadersNoRuntime(project);
project.addDefines({"SC_COMPILER_ENABLE_CONFIG=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 configureStdCppHeaderNoLinkTest(const Parameters& parameters, Workspace& workspace)
{
Project project = {STD_CPP_HEADER_NO_LINK_TEST_PROJECT_NAME};
project.setRootDirectory(parameters.directories.projectDirectory.view());
project.addPresetConfiguration(Configuration::Preset::Debug, parameters);
project.addPresetConfiguration(Configuration::Preset::Release, parameters);
project.files.compile.cppStandard = CppStandard::CPP20;
project.link.linkStdCpp = false; // This probe explicitly verifies standard headers without libstdc++.
project.addFile("Support/CompileTests/StdCppHeaderNoLinkProbe.cpp");
SC_TRY(workspace.projects.push_back(move(project)));
return Result(true);
}
Result configureSCAwaitCoroutineShimTest(const Parameters& parameters, Workspace& workspace)
{
Project project = {AWAIT_COROUTINE_SHIM_TEST_PROJECT_NAME};
project.setRootDirectory(parameters.directories.projectDirectory.view());
project.addPresetConfiguration(Configuration::Preset::Debug, parameters);
project.addPresetConfiguration(Configuration::Preset::Release, parameters);
configureSaneStrictNoStdCpp(project);
project.files.compile.cppStandard = CppStandard::CPP20;
project.addIncludePaths({"."});
project.addDefines({"SC_AWAIT_ENABLE_NO_STDLIB_COROUTINE=1"});
project.addFile("Support/CompileTests/AwaitCoroutineShimProbe.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);
configureSaneStrictNoStdCpp(project);
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.enableExceptions = true;
project.files.compile.enableRTTI = true;
project.files.compile.cppStandard = CppStandard::CPP17;
// InteropSTL is the explicit full-standard-C++ coverage target: headers and runtime link are both enabled.
project.files.compile.includeStdCpp = true;
project.link.linkStdCpp = true;
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);
configureSaneStrictNoStdCpp(project);
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.startsWith("Await"))
{
project.files.compile.cppStandard = CppStandard::CPP20;
configureSaneStdHeadersNoRuntime(project);
SC_TRY(addSaneCppLibraries(project, parameters));
project.addFiles("Libraries/Await", "**.cpp");
project.addFiles("Libraries/Await", "**.h");
}
else
{
configureSaneStrictNoStdCpp(project);
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);
if (name == "Test_SaneCppAwait" or name == "Test_SaneCppAwaitStandalone")
{
project.files.compile.cppStandard = CppStandard::CPP20;
configureSaneStdHeadersNoRuntime(project);
}
else
{
configureSaneStrictNoStdCpp(project);
}
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(configureStdCppHeaderNoLinkTest(parameters, defaultWorkspace));
SC_TRY(configureSCAwaitCoroutineShimTest(parameters, defaultWorkspace));
SC_TRY(configureSCSharedLibrary(parameters, defaultWorkspace));
SC_TRY(configureTestSTLInterop(parameters, defaultWorkspace));
SC_TRY(configureExamplesConsole(parameters, defaultWorkspace));
SC_TRY(configureExamplesGUI(parameters, defaultWorkspace));
SC_TRY(configureZLibFilC(parameters, defaultWorkspace));
definition.workspaces.push_back(move(defaultWorkspace));
(void)configureSingleFileLibs(definition, parameters);
return Result(true);
}
SC_COMPILER_WARNING_POP_UNUSED_RESULT;
} // namespace Build
} // namespace SC

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}
  • SupportMatrixEntry, SupportStatus, SupportTier, and getNativeBackendSupportMatrix() for the native-backend cross-compilation support matrix
  • 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
  • Project::saneCpp and Configuration::saneCpp for Sane C++ Libraries specific policy flags such as strict no-standard-library mode

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()).

SC-build is a general-purpose build system, so generic C++ standard-library policy lives in generic compile/link flags. Normal mode is the default: standard C/C++ headers are available and the C++ standard-library runtime may be linked. Sane C++ Libraries specific options are grouped under saneCpp and are enabled automatically by addSaneCppLibraries().

To request the stricter historical no-stdlib pressure mode for an entire Sane C++ target:

project.files.compile.includeStdCpp = false;
project.link.linkStdCpp = false;
project.saneCpp.provideCppRuntimeShims = true;

For a single configuration:

configuration.compile.includeStdCpp = false;
configuration.link.linkStdCpp = false;
configuration.saneCpp.provideCppRuntimeShims = true;

The generic includeStdCpp flag controls whether the backend passes best-effort no-standard-C++-include flags such as -nostdinc++. When saneCpp.enabled is true, SC-build also emits SC_INCLUDE_STD_CPP from that generic compile flag.

Targets that intentionally use STL runtime features can opt into C++ standard-library linkage:

project.link.linkStdCpp = true;

If a Sane C++ target avoids linking the C++ runtime, it must either provide the runtime ABI shims itself or obtain them from another object/library:

project.link.linkStdCpp = false;
project.saneCpp.provideCppRuntimeShims = true;

SC-build rejects project.link.linkStdCpp = true together with project.saneCpp.provideCppRuntimeShims = true because both sides would be trying to provide the same C++ runtime ABI symbols.

The repository bootstrap keeps broad compiler compatibility for headers by default while still avoiding C++ runtime linkage. Set SC_BOOTSTRAP_INCLUDE_STD_CPP=0 only when you intentionally want to test or benchmark strict-mode bootstrap compilation. Set SC_BOOTSTRAP_LINK_STD_CPP=1 if you explicitly want the bootstrap tool to link the C++ standard-library runtime.

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 Cross Support Matrix

The native backend exposes its current cross-compilation support claims through Build::getNativeBackendSupportMatrix(). Documentation and CI should treat that API as the source of truth for host / target rows, build support, run support, support tier, runner family, and validation notes.

Host Target Architecture Build Run Runner Tier
macOS windows-gnu x86_64 supported supported wine tier1
macOS windows-gnu arm64 supported not-yet wine tier1
macOS windows-msvc x86_64 supported smoke-supported wine tier2
macOS windows-msvc arm64 supported not-yet wine tier2
macOS linux-glibc x86_64 supported not-yet qemu tier1
macOS linux-glibc arm64 supported not-yet qemu tier1
macOS linux-musl x86_64 supported not-yet qemu tier1
macOS linux-musl arm64 supported not-yet qemu tier1
Windows linux-glibc arm64 supported not-yet qemu tier2
Windows linux-musl x86_64 supported not-yet qemu tier2
Linux windows-gnu x86_64 supported supported wine tier1
Linux windows-gnu arm64 supported smoke-supported wine tier1
Linux windows-msvc x86_64 supported smoke-supported wine tier2
Linux windows-msvc arm64 supported smoke-supported wine tier2

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 toolchain-only for now; no public linux-filc-* target profile exists
  • 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 remains toolchain-only outside the public target-profile matrix
  • 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 glibc and musl Linux target runs are still not-yet in the support matrix; real host-QEMU validation remains opportunistic until CI can acquire qemu-x86_64 and qemu-aarch64 deterministically
  • Windows hosts have packaged LLVM + Linux sysroot compile validation for representative linux-glibc-arm64 and linux-musl-x86_64 target profiles; Windows-host Linux runs remain pending
  • 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

Cross-Compilation Recipes

The friendly --target profiles select the target platform, architecture, environment and default toolchain family. Use --toolchain, --triple or --sysroot only when you need to override that resolved profile. build run --runner auto first chooses direct execution for runnable native Linux outputs, then falls back to Wine for Windows targets or QEMU for foreign Linux targets when that row is supported by the host.

Prepare package-managed inputs before relying on a cross row:

./SC.sh package install llvm
./SC.sh package install llvm-mingw
./SC.sh package install linux-sysroot-glibc-arm64
./SC.sh package install linux-sysroot-musl-x86_64
./SC.sh package verify
./SC.sh package lock

Compile Linux targets from macOS with the packaged LLVM and sysroot receipts:

./SC.sh build compile SCTest --target linux-glibc-arm64 --output quiet
./SC.sh build compile SCTest --target linux-musl-x86_64 --output quiet

Register QEMU when a host has a known qemu-user layout and you want foreign Linux build run smoke tests:

./SC.sh package install qemu --import-directory /opt/qemu-user
./SC.sh package verify qemu
./SC.sh build run SCTest --target linux-glibc-arm64 --runner auto -- --test "BaseTest" --test-section "new/delete"

Compile and smoke-run Windows GNU targets through the shared Wine runner model:

./SC.sh build compile SCTest --target windows-gnu-x86_64 --output quiet
./SC.sh build run SCTest --target windows-gnu-x86_64 --runner auto -- --test "BaseTest" --test-section "new/delete"

Compile portable MSVC targets after acquiring the packaged toolchain:

./SC.sh package install msvc
./SC.sh package verify msvc
./SC.sh build compile SCTest --target windows-msvc-x86_64 --output quiet
./SC.sh build compile SCTest --target windows-msvc-arm64 --output quiet

On Windows hosts, the Linux rows are compile-only today:

SC.bat package install llvm
SC.bat package install linux-sysroot-glibc-arm64
SC.bat package install linux-sysroot-musl-x86_64
SC.bat build compile SCTest --target linux-glibc-arm64 --output quiet
SC.bat build compile SCTest --target linux-musl-x86_64 --output quiet

Future preset hooks should compose the same public vocabulary instead of introducing parallel names: target=<profile>, toolchain=<name>, runner=<auto|none|wine|qemu|custom>, runner-path=<path>, triple=<value> and sysroot=<path>. A preset for a cross row should therefore expand to the same options shown above, plus package checks such as SC-package verify or SC-package lock when it depends on managed inputs.

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 package verify 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 run SCTest --arch intel64 --runner auto -- --test BaseTest
./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 zlib-filc
./SC.sh build run SCTest Debug native --toolchain filc -- --test ZLibStreamTest
./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
SC.bat build compile SCTest --target linux-glibc-arm64 --output quiet
SC.bat build compile SCTest --target linux-musl-x86_64 --output quiet

Important current limits:

  • The QEMU runner path can reuse a managed imported qemu-user layout or host qemu-* executables; real host-QEMU smoke validation remains opportunistic on macOS until QEMU-user acquisition is deterministic in CI, so macOS Linux run support stays not-yet
  • Windows-host Linux-target support is compile-only today and validated for representative glibc arm64 and musl x86_64 slices; Windows-host Linux run validation is still pending
  • Fil-C is still an experimental compiler-first Linux track: the public shape is --toolchain filc, no public linux-filc-* target profile exists, 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
  • Fil-C build run installs/rebuilds zlib-filc on demand and prepends that package's lib directory to LD_LIBRARY_PATH so zlib-backed tests load a Fil-C-built libz.so.1
  • 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

Windows runtime targets are long-path-aware by default. Override this with project.windows.longPathAware or config.windows.longPathAware when generating Windows targets. The option is valid for console executables, GUI applications, and shared libraries; static libraries reject it during validation.

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: