Build Native Backend
April has been almost entirely about SC::Build.
The biggest milestone is that the native generator is now the default on all supported host platforms, so the direct compile / run workflow is no longer some experimental side path.
This is where the project is becoming much more interesting to me, because the native backend is also where cross targets, foreign-binary execution and the nicer bootstrap story are all landing. Generated backends are still useful, but at this point the standalone builder is clearly becoming the main workflow.
There is also now an experimental Fil-C target in the mix.
It is still very early, but I really like seeing SC::Build start to expose weirder compiler / toolchain combinations inside the same workflow.
I also posted a short note about it here:
Detailed list of commits:
- Build: Enable native generator by default on all platforms
- Build: Change default action to compile
- Build: Allow C++ 17 in SC-build.cpp configuration files
- Build: Add new experimental Fil-C target
Cross-Compilation
The next big theme has been making cross-compilation feel like a real supported workflow instead of a bag of flags.
SC::Build now has friendly Linux and Windows target profiles, packaged sysroots, llvm-mingw support, and raw --triple / --sysroot escape hatches for when one wants to go off the beaten path.
This is not the flashiest work in the world, but it changes a lot in practice. Once target profiles, sysroots and packaged toolchains are wired together properly, building for another platform starts feeling boring in the good way, instead of like a custom one-off setup.
Detailed list of commits:
- Build: Add first-class Linux native target profiles
- Build: Package host LLVM for Linux target profiles
- Everywhere: Support building with musl on Linux
- Build: Add packaged Linux sysroots for macOS
- Build: Add Linux sysroots and qemu runner support
- Build: Add llvm-mingw cross-compile
- Tools: Add llvm-mingw package
- Build: Add windows-gnu-arm64 cross target
- Build: Add --triple and --sysroot
- Build: Add windows-msvc-x86_64 cross target
- Build: Add windows-msvc-arm64 cross target
Runners and Cross-Runs
Producing foreign binaries is only half the story. This month also makes them much easier to execute, with much better Wine and QEMU integration across different host and target combinations.
There has been a lot of fiddly work here around Linux ARM64, portable MSVC packaging, auto-resolved wrappers and managed runner registration.
The nice result on the user side is that build run is increasingly able to do something sensible automatically even when the executable does not match the host platform, which is exactly what I wanted from this tool.
Detailed list of commits:
- Build: Add Wine runner support for Windows GNU targets
- Build: Auto-resolve Linux box64 Wine wrappers
- Build: Auto-wrap Linux arm64 Wine runners
- Build: Add explicit portable MSVC package overrides
- Build: Validate portable MSVC import packaging
- Build: Repair legacy portable MSVC package layouts
- Build: Reuse stored Wine path for portable MSVC
- Build: Repair Linux ARM64 portable MSVC Wine runners
- Build: Prefer plain Wine for Linux ARM64 console runs
- Build: Support Linux ARM64 Wine runs for Windows arm64 targets
- Build: Validate Windows GNU ARM64 runs on Linux
- Build: Add managed QEMU runner registration for native cross-runs
External Builds
Another important change is that SC::Build is becoming easier to use outside of the main repository.
The new external launchers and bootstrap flow make it much simpler to point a standalone project at a Sane C++ checkout, or even use a shared cached checkout, without having to copy the repository workflow by hand.
This is especially useful for small external experiments and tests, because the build-definition file can stay tiny while still pulling in the Sane C++ libraries and the right public headers automatically.
The additions around shared helpers, defaults and SC_BUILD self-detection also make it easier for a single SC-build.cpp file to act both as build script and as project source when needed, which is a pretty nice trick for tiny examples.
For example, an external project can just curl the launcher, write a tiny SC-build.cpp, and compile immediately:
#include "SaneCppBuild.h"
SC::Result SC::Build::configure(Definition& definition, const Parameters& parameters)
{
Project project = {"MyProject"};
SC_TRY(Build::addSaneCppLibraries(project, parameters));
SC_TRY(project.addFile("main.cpp"));
return definition.addProject(move(project));
}
curl -L -o SC-build.sh https://raw.githubusercontent.com/Pagghiu/SaneCppLibraries/main/SC-build.sh
chmod +x SC-build.sh
./SC-build.sh run
I like this a lot because it starts making SC::Build feel usable as a general standalone tool, not only as the thing used to build this repository itself.
Detailed list of commits:
- Build: Add external SC-build launchers
- Build: Add shared addSaneCppLibraries helper
- Build: Add the SC_BUILD macro to self-detect being targeted by SC::Build
- Build: Add defaults to simplify commonly set options
Public Headers
The external-project story is also much better now because there are explicit public headers whose names match the single-file library variants.
This means external users can include headers such as SaneCppStrings.h or SaneCppHttp.h in a much more obvious way, while keeping the door open to switch between single-file and multi-file integration styles with much less friction.
I think this is a very nice change for usability. If the public header names match the single-file library names, users do not need to care as much about how things are packaged internally, and moving between the two styles becomes much less annoying.
Detailed list of commits:
Build Ergonomics and CI
There has also been a steady stream of work on polishing the day-to-day experience around the builder and its CI coverage. This includes better output handling, more flexible CLI parsing, self-tests for the build system itself, and a lot of cache work to keep workflows lighter and less repetitive.
These changes are less visible than target-profile support or cross-runners, but they are the sort of things that stop a build tool from feeling fragile. Also, a lot of this month has been about making the CI less wasteful, because waiting on giant caches all the time is just boring.
Detailed list of commits:
- Build: Add richer native build output handling
- Build: Support named options after target
- Build: Do not update timestamp if file content is not changed
- Build: Add SCBuildTest to the CI
- Build: Get rid of extra new lines in build test console output
- Build: Share fixture package caches across runs
- Build: Preserve custom driver Linux target fixtures
- Build: Split workflow package caches by package
- Build: Shrink docs tool package caches
- Build: Merge docs package installs
- Build: Shrink llvm-mingw workflow caches
- Build: Add Windows-host packaged Linux sysroots
- Build: Warm Windows LLVM and sysroot caches
- Build: Warm shared Windows caches only once
- Build: Skip Windows LLVM restores in debug jobs
- Build: Restrict Windows cache skips to build matrix
- Build: Restore Posix llvm-mingw cache only in release
- Build: Shrink Windows LLVM workflow cache
- Build: Fix make run target selection for single-project workspaces
- Build: Add repository root directory include only in tests
Others
Outside SC::Build, the month also includes a few useful portability and quality-of-life changes.
Async loop timeout tests got some stabilization work, runtime path resolution became more portable across build targets, MinGW compilation support improved, and Strings gained case-insensitive helpers.
This section is smaller than usual, but that is mostly because April has been totally dominated by build-system work.
Detailed list of commits:
- CI: Try fixing packages caching
- Skills: Unify all skills into one designed for progressive discovery
- Everywhere: Support compiling using MinGW
- Async: Stabilize loop timeout test
- Async: Handle EINTR on io_uring
- Everywhere: Make runtime path resolution portable across build targets
- Async: Stabilizing loop timeout test
- Strings: Add case insensitive {startsWith | contains | equals}
See you next month!