musl host support: bootstrap and run on Alpine (x86_64-alpine-linux-musl)
The Linux host code paths were gated on the x86_64_pc_linux_gnu target define alone, and LoadProject hardcoded --target=x86_64-pc-linux-gnu when compiling project.cpp and the module PCMs. Both break on musl hosts: the glibc release launcher cannot run there at all (no glibc loader; gcompat lacks libgcc_s and the __isoc23_* symbols), and a musl-built launcher would still compile project.cpp for the wrong libc. - The host gates test clang's __linux__ instead of one triple's define: they mean "Linux host", whatever the libc (or, later, the CPU). - LoadProject derives the host triple from HostTarget() (clang's -print-target-triple), which is also more correct on glibc distros whose triple isn't x86_64-pc-linux-gnu. - project.cpp / the cmake libc++ flags treat any *-linux-gnu or *-linux-musl target as Linux. - build.sh targets the host toolchain's triple (CRAFTER_BUILD_TARGET overrides) and derives the per-target define from it. On Arch this is byte-for-byte the previous behaviour. - CI: a release-musl job bootstraps on alpine:edge, runs the tests, and attaches crafter-build-linux-x86_64-musl-v2.tar.gz to the rolling latest release (via the API, so the glibc assets survive). Dynamic against musl libc++: the launcher dlopens project.so, so it cannot be fully static. First consumer: imsd's package CI, which cross-compiles for aarch64 from an Alpine container. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
802caac1c2
commit
dc239f4257
7 changed files with 131 additions and 22 deletions
|
|
@ -307,7 +307,7 @@ CompileCommand Crafter::GetCompileCommand(const Configuration& config) {
|
|||
}
|
||||
|
||||
if(config.type == ConfigurationType::LibraryDynamic) {
|
||||
#ifdef CRAFTER_BUILD_CONFIGURATION_TARGET_x86_64_pc_linux_gnu
|
||||
#ifdef __linux__
|
||||
out.command += " -fPIC -D CRAFTER_BUILD_CONFIGURATION_TYPE_SHARED_LIBRARY";
|
||||
#endif
|
||||
#if defined(CRAFTER_BUILD_CONFIGURATION_TARGET_x86_64_pc_windows_msvc) || defined(CRAFTER_BUILD_CONFIGURATION_TARGET_x86_64_w64_mingw32)
|
||||
|
|
@ -453,7 +453,7 @@ BuildResult Crafter::Build(Configuration& config, std::unordered_map<fs::path, s
|
|||
// users supply cfg.sysroot pointing at their wasi-sdk install. Covers all
|
||||
// wasm32-* triples (wasi, wasip1, wasip2, ...); the sysroot's per-triple
|
||||
// subdirs handle the differences.
|
||||
#ifdef CRAFTER_BUILD_CONFIGURATION_TARGET_x86_64_pc_linux_gnu
|
||||
#ifdef __linux__
|
||||
if (config.sysroot.empty() && config.target.starts_with("wasm32")) {
|
||||
config.sysroot = "/usr/share/wasi-sysroot";
|
||||
}
|
||||
|
|
@ -1131,7 +1131,7 @@ BuildResult Crafter::Build(Configuration& config, std::unordered_map<fs::path, s
|
|||
if(buildResult.repack) {
|
||||
Progress::Task task(std::format("Linking {}", config.outputName));
|
||||
if(config.type == ConfigurationType::Executable) {
|
||||
#ifdef CRAFTER_BUILD_CONFIGURATION_TARGET_x86_64_pc_linux_gnu
|
||||
#ifdef __linux__
|
||||
if(config.target == "x86_64-w64-mingw32") {
|
||||
try {
|
||||
// Copy any LibraryDynamic dependency DLLs alongside
|
||||
|
|
@ -1204,7 +1204,7 @@ BuildResult Crafter::Build(Configuration& config, std::unordered_map<fs::path, s
|
|||
}
|
||||
#endif
|
||||
} else if(config.type == ConfigurationType::LibraryStatic) {
|
||||
#ifdef CRAFTER_BUILD_CONFIGURATION_TARGET_x86_64_pc_linux_gnu
|
||||
#ifdef __linux__
|
||||
// ThinLTO emits LLVM bitcode objects; plain `ar` writes an archive
|
||||
// index that omits their symbols, so a consumer's lld link can't
|
||||
// pull the needed members. llvm-ar writes a bitcode-aware index.
|
||||
|
|
@ -1550,7 +1550,7 @@ ArgQuery Crafter::ApplyStandardArgs(Configuration& cfg, std::span<const std::str
|
|||
// includes it. (Build() also runs this once more for callers that bypassed
|
||||
// ApplyStandardArgs, but doing it here makes dep PcmDirs consistent
|
||||
// between the consumer's command-construction and the dep's own build.)
|
||||
#ifdef CRAFTER_BUILD_CONFIGURATION_TARGET_x86_64_pc_linux_gnu
|
||||
#ifdef __linux__
|
||||
if (cfg.sysroot.empty() && cfg.target.starts_with("wasm32")) {
|
||||
cfg.sysroot = "/usr/share/wasi-sysroot";
|
||||
}
|
||||
|
|
|
|||
|
|
@ -137,8 +137,8 @@ std::string BuildInjectedCMakeFlags(const fs::path& cmakeBuildDir, std::string_v
|
|||
// itself is built with -stdlib=libc++); mingw cross-compile uses libstdc++
|
||||
// shipped by the mingw-w64 toolchain so we leave the default; Windows
|
||||
// native (msvc target) similarly uses libc++ via LIBCXX_DIR.
|
||||
#ifdef CRAFTER_BUILD_CONFIGURATION_TARGET_x86_64_pc_linux_gnu
|
||||
if (target == "x86_64-pc-linux-gnu") {
|
||||
#ifdef __linux__
|
||||
if (target.ends_with("-linux-gnu") || target.ends_with("-linux-musl")) {
|
||||
out += " -DCMAKE_CXX_FLAGS=-stdlib=libc++";
|
||||
out += " -DCMAKE_EXE_LINKER_FLAGS=-stdlib=libc++";
|
||||
out += " -DCMAKE_SHARED_LINKER_FLAGS=-stdlib=libc++";
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ module;
|
|||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <limits.h>
|
||||
#ifdef CRAFTER_BUILD_CONFIGURATION_TARGET_x86_64_pc_linux_gnu
|
||||
#ifdef __linux__
|
||||
#include <unistd.h>
|
||||
#include <dlfcn.h>
|
||||
#include <sys/wait.h>
|
||||
|
|
@ -41,7 +41,7 @@ namespace {
|
|||
// descriptor is close-on-exec / non-inheritable so spawned clang processes
|
||||
// don't keep the lock alive past our own release.
|
||||
class CacheLock {
|
||||
#ifdef CRAFTER_BUILD_CONFIGURATION_TARGET_x86_64_pc_linux_gnu
|
||||
#ifdef __linux__
|
||||
std::int32_t fd_ = -1;
|
||||
public:
|
||||
explicit CacheLock(const fs::path& cacheDir) {
|
||||
|
|
@ -765,7 +765,7 @@ Configuration Crafter::LoadProject(const fs::path& projectFile, std::span<const
|
|||
}
|
||||
#endif
|
||||
|
||||
#ifdef CRAFTER_BUILD_CONFIGURATION_TARGET_x86_64_pc_linux_gnu
|
||||
#ifdef __linux__
|
||||
|
||||
CommandResult Crafter::RunCommandChecked(std::string_view cmd) {
|
||||
std::array<char, 128> buffer;
|
||||
|
|
@ -796,7 +796,9 @@ CommandResult Crafter::RunCommandWithTimeout(std::string_view cmd, std::chrono::
|
|||
std::array<char, 128> buffer;
|
||||
CommandResult result{};
|
||||
|
||||
std::string wrapped = std::format("timeout --kill-after=2 {} {} 2>&1", timeout.count(), cmd);
|
||||
// -k (not --kill-after=): the short form is what GNU coreutils and busybox
|
||||
// (Alpine) have in common.
|
||||
std::string wrapped = std::format("timeout -k 2 {} {} 2>&1", timeout.count(), cmd);
|
||||
|
||||
FILE* pipe = popen(wrapped.c_str(), "r");
|
||||
if (!pipe) throw std::runtime_error("popen() failed!");
|
||||
|
|
@ -968,12 +970,12 @@ namespace {
|
|||
continue;
|
||||
}
|
||||
std::string cmd = std::format(
|
||||
"clang++ --target=x86_64-pc-linux-gnu -march=native -mtune=native "
|
||||
"clang++ --target={} -march=native -mtune=native "
|
||||
"-std=c++26 -stdlib=libc++ -O3 "
|
||||
"-Wno-reserved-identifier -Wno-reserved-module-identifier "
|
||||
"-fprebuilt-module-path={} "
|
||||
"--precompile {} -o {}",
|
||||
cacheDir.string(), cppmPath.string(), pcmPath.string());
|
||||
HostTarget(), cacheDir.string(), cppmPath.string(), pcmPath.string());
|
||||
CommandResult r = Crafter::RunCommandChecked(cmd);
|
||||
if (r.exitCode != 0) {
|
||||
throw std::runtime_error(std::format("Failed to precompile {} (exit {}): {}", name, r.exitCode, r.output));
|
||||
|
|
@ -998,8 +1000,11 @@ Configuration Crafter::LoadProject(const fs::path& projectFile, std::span<const
|
|||
|
||||
fs::path sourceDir = Crafter::GetCrafterBuildHome();
|
||||
|
||||
// The host toolchain's own triple (x86_64-pc-linux-gnu on glibc distros,
|
||||
// x86_64-alpine-linux-musl on Alpine): project.cpp and the module PCMs
|
||||
// it imports must be built for the libc crafter-build itself runs on.
|
||||
Configuration hostConfig;
|
||||
hostConfig.target = "x86_64-pc-linux-gnu";
|
||||
hostConfig.target = HostTarget();
|
||||
hostConfig.march = "native";
|
||||
hostConfig.mtune = "native";
|
||||
fs::path cacheDir = GetCacheDir() / std::format("{}-{}", hostConfig.target, hostConfig.march);
|
||||
|
|
@ -1015,13 +1020,15 @@ Configuration Crafter::LoadProject(const fs::path& projectFile, std::span<const
|
|||
const bool stale = !fs::exists(soPath) || fs::last_write_time(soPath) < fs::last_write_time(absProject) || fs::last_write_time(soPath) < fs::last_write_time(hostExe);
|
||||
|
||||
if (stale) {
|
||||
// -fuse-ld=lld like every other link the tool performs: lld is a hard
|
||||
// dependency, binutils' ld is not (Alpine's clang would default to it).
|
||||
std::string compileCmd = std::format(
|
||||
"clang++ --target=x86_64-pc-linux-gnu -march=native -mtune=native "
|
||||
"-std=c++26 -stdlib=libc++ -shared -fPIC -O3 "
|
||||
"clang++ --target={} -march=native -mtune=native "
|
||||
"-std=c++26 -stdlib=libc++ -shared -fPIC -O3 -fuse-ld=lld "
|
||||
"-Wno-return-type-c-linkage "
|
||||
"-fprebuilt-module-path={} "
|
||||
"{} -o {}",
|
||||
cacheDir.string(),
|
||||
hostConfig.target, cacheDir.string(),
|
||||
absProject.string(), soPath.string());
|
||||
|
||||
std::string result = RunBuildCommand(compileCmd);
|
||||
|
|
|
|||
Loading…
Reference in a new issue