From add30b9a4f9dac4c2f49b5c47570df5be8421a50 Mon Sep 17 00:00:00 2001 From: Jorijn van der Graaf Date: Fri, 2 Oct 2026 22:34:57 +0200 Subject: [PATCH] feat(packaging): ship crafter-build as a Flatpak with its own toolchain packaging/flatpak/ holds the manifest for net.catcrafts.CrafterBuild and its side files. The runtime is the Freedesktop Sdk, not the Platform: a build tool needs glibc's headers and crt objects, gcc's libgcc_s, cmake, ninja, git and python at run time. Everything compiler-shaped is ours under /app, built from one LLVM 22.1.8 source tree: clang, lld, libclang, libc++ with its std module, compiler-rt for the host and for wasm32, the WASI sysroot (wasi-sdk 34 in sysroot-only mode, driven by the clang just built) and wasmtime for running the result. One tree means the modules crafter-build builds and the compiler that consumes them cannot drift apart, and the toolchain resolver finds all of it beside the executable with no configuration. The crafter-build module runs the bootstrap, the self-host build and the test suite inside the sandbox, so a Flatpak that installs has passed the suite with the toolchain it ships. CRAFTER_BUILD_MARCH is x86-64-v2 for the shipped binary; user projects keep -march=native. WasmHelloRuns compiles a fixture for wasm32-wasip1 and runs it under wasmtime. The existing wasm tests only inspect generated files, so this is the one that proves the sysroot, the std module for wasm and wasm-ld work together; it skips where the sysroot or wasmtime is missing. Co-Authored-By: Claude Fable 5.1 --- .gitignore | 4 +- packaging/flatpak/catcrafts.flatpakrepo | 8 + .../net.catcrafts.CrafterBuild.flatpakref | 13 ++ .../net.catcrafts.CrafterBuild.metainfo.xml | 34 ++++ .../flatpak/net.catcrafts.CrafterBuild.yaml | 182 ++++++++++++++++++ packaging/flatpak/wasi-host-toolchain.cmake | 23 +++ project.cpp | 1 + tests/WasmHelloRuns/fixture/main.cpp | 9 + tests/WasmHelloRuns/main.cpp | 57 ++++++ 9 files changed, 330 insertions(+), 1 deletion(-) create mode 100644 packaging/flatpak/catcrafts.flatpakrepo create mode 100644 packaging/flatpak/net.catcrafts.CrafterBuild.flatpakref create mode 100644 packaging/flatpak/net.catcrafts.CrafterBuild.metainfo.xml create mode 100644 packaging/flatpak/net.catcrafts.CrafterBuild.yaml create mode 100644 packaging/flatpak/wasi-host-toolchain.cmake create mode 100644 tests/WasmHelloRuns/fixture/main.cpp create mode 100644 tests/WasmHelloRuns/main.cpp diff --git a/.gitignore b/.gitignore index a47aaf6..67d2a68 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,6 @@ build/ bin/ lib/*.a share/crafter-build/ -.claude \ No newline at end of file +.claude +# written by CI next to the manifest so relative source paths still resolve +packaging/flatpak/ci-manifest.yaml diff --git a/packaging/flatpak/catcrafts.flatpakrepo b/packaging/flatpak/catcrafts.flatpakrepo new file mode 100644 index 0000000..8792ccd --- /dev/null +++ b/packaging/flatpak/catcrafts.flatpakrepo @@ -0,0 +1,8 @@ +[Flatpak Repo] +Title=Catcrafts +Url=https://flatpak.catcrafts.net/repo/ +Homepage=https://forgejo.catcrafts.net/Catcrafts +Comment=Catcrafts tools and applications +Description=crafter-build and other Catcrafts software, built and signed by Catcrafts CI +DefaultBranch=stable +GPGKey=GPG_KEY_BASE64_PLACEHOLDER diff --git a/packaging/flatpak/net.catcrafts.CrafterBuild.flatpakref b/packaging/flatpak/net.catcrafts.CrafterBuild.flatpakref new file mode 100644 index 0000000..16359ae --- /dev/null +++ b/packaging/flatpak/net.catcrafts.CrafterBuild.flatpakref @@ -0,0 +1,13 @@ +[Flatpak Ref] +Title=Crafter Build +Name=net.catcrafts.CrafterBuild +Branch=stable +Url=https://flatpak.catcrafts.net/repo/ +SuggestRemoteName=catcrafts +Homepage=https://forgejo.catcrafts.net/Catcrafts/Crafter.Build +Comment=C++26 modules build system +Description=crafter-build with its own LLVM toolchain and WASI sysroot +Icon= +RuntimeRepo=https://dl.flathub.org/repo/flathub.flatpakrepo +IsRuntime=false +GPGKey=GPG_KEY_BASE64_PLACEHOLDER diff --git a/packaging/flatpak/net.catcrafts.CrafterBuild.metainfo.xml b/packaging/flatpak/net.catcrafts.CrafterBuild.metainfo.xml new file mode 100644 index 0000000..7108966 --- /dev/null +++ b/packaging/flatpak/net.catcrafts.CrafterBuild.metainfo.xml @@ -0,0 +1,34 @@ + + + + net.catcrafts.CrafterBuild + Crafter Build + C++26 modules build system + CC0-1.0 + LGPL-3.0-only + + Catcrafts + + +

+ A build system for C++26 modules. Project descriptions are C++: a + project.cpp constructs a Configuration and returns it, and crafter-build + compiles, loads and executes it. The Flatpak carries its own LLVM, so + every machine builds with the same clang, lld, libc++ and WASI sysroot. +

+
+ https://forgejo.catcrafts.net/Catcrafts/Crafter.Build + https://forgejo.catcrafts.net/Catcrafts/Crafter.Build/issues + https://forgejo.catcrafts.net/Catcrafts/Crafter.Build + + crafter-build + + + + + +

First release distributed as a Flatpak, with a bundled LLVM 22 toolchain and WASI sysroot. Windows host support is removed; crafter-build gains --version and resolves its compiler from one place.

+
+
+
+
diff --git a/packaging/flatpak/net.catcrafts.CrafterBuild.yaml b/packaging/flatpak/net.catcrafts.CrafterBuild.yaml new file mode 100644 index 0000000..01e286e --- /dev/null +++ b/packaging/flatpak/net.catcrafts.CrafterBuild.yaml @@ -0,0 +1,182 @@ +# SPDX-License-Identifier: LGPL-3.0-only +# SPDX-FileCopyrightText: Copyright (C) 2026 Catcrafts® +# +# crafter-build as a Flatpak. The runtime is the Freedesktop *Sdk*, not the +# Platform: a build tool needs glibc's headers and crt objects, gcc's +# libgcc_s, cmake, ninja, make, git and python at run time. Everything +# compiler-shaped is ours and lives under /app: clang, lld, libclang, libc++, +# compiler-rt (native and wasm32) and the WASI sysroot, all built from one +# LLVM source tree so the modules crafter-build builds and the compiler that +# consumes them can never drift apart. +app-id: net.catcrafts.CrafterBuild +runtime: org.freedesktop.Sdk +runtime-version: '26.08' +sdk: org.freedesktop.Sdk +command: crafter-build +finish-args: + # git dependencies, and the local web server `-r` starts for browser builds + - --share=network + - --socket=ssh-auth + # projects live anywhere; user sysroots too + - --filesystem=host + # so `-r` can launch what it built, games included + - --share=ipc + - --socket=wayland + - --socket=fallback-x11 + - --socket=pulseaudio + - --device=all + # gdb and friends from the Sdk + - --allow=devel + - --env=PATH=/app/bin:/usr/bin +build-options: + env: + CRAFTER_BUILD_LLVM_DIR: /app + # The shipped binary runs on any x86_64 from the last decade; user + # projects still build with -march=native by default. + CRAFTER_BUILD_MARCH: x86-64-v2 + CRAFTER_BUILD_MTUNE: generic + # CI replaces this with the tag; a local build is a development build. + CRAFTER_BUILD_VERSION: 0.0.0-dev +cleanup: + - /share/man + - /share/doc +modules: + - name: llvm + buildsystem: cmake-ninja + builddir: true + subdir: llvm + build-options: + # Debug info for all of LLVM would be a multi-gigabyte .Debug extension. + no-debuginfo: true + config-opts: + - -DCMAKE_BUILD_TYPE=Release + - -DLLVM_ENABLE_ASSERTIONS=OFF + - -DLLVM_ENABLE_PROJECTS=clang;lld + - -DLLVM_ENABLE_RUNTIMES=compiler-rt;libcxx;libcxxabi;libunwind + - -DLLVM_TARGETS_TO_BUILD=X86;AArch64;WebAssembly + # One shared LLVM and one shared clang library instead of a statically + # linked copy per tool: a fraction of the install size. + - -DLLVM_BUILD_LLVM_DYLIB=ON + - -DLLVM_LINK_LLVM_DYLIB=ON + - -DCLANG_LINK_CLANG_DYLIB=ON + - -DCLANG_DEFAULT_CXX_STDLIB=libc++ + - -DCLANG_DEFAULT_LINKER=lld + - -DLIBCXX_INSTALL_MODULES=ON + - -DLLVM_INCLUDE_TESTS=OFF + - -DLLVM_INCLUDE_EXAMPLES=OFF + - -DLLVM_INCLUDE_DOCS=OFF + - -DLLVM_INCLUDE_BENCHMARKS=OFF + - -DLLVM_INSTALL_UTILS=OFF + - -DCLANG_INCLUDE_TESTS=OFF + - -DCOMPILER_RT_INCLUDE_TESTS=OFF + # The Sdk links with binutils' ld; two libLLVM-sized links at a time + # fit the CX53's memory with room to spare. + - -DLLVM_PARALLEL_LINK_JOBS=2 + cleanup: + - /include/llvm + - /include/llvm-c + - /include/clang + - /include/clang-tidy + - /include/lld + - /lib/cmake + - /lib/*.a + - /share/opt-viewer + - /share/scan-build + - /share/scan-view + - /bin/clang-check + - /bin/clang-extdef-mapping + - /bin/clang-import-test + - /bin/clang-linker-wrapper + - /bin/clang-nvlink-wrapper + - /bin/clang-offload-bundler + - /bin/clang-offload-packager + - /bin/clang-refactor + - /bin/clang-rename + - /bin/clang-repl + - /bin/clang-sycl-linker + - /bin/diagtool + - /bin/git-clang-format + - /bin/hmaptool + - /bin/llvm-exegesis + - /bin/scan-build* + - /bin/scan-view + sources: + - type: archive + url: https://github.com/llvm/llvm-project/releases/download/llvmorg-22.1.8/llvm-project-22.1.8.src.tar.xz + sha256: 922f1817a0df7b1489272d18134ee0087a8b068828f87ac63b9861b1a9965888 + + - name: wasi-sysroot + # wasi-sdk's sysroot-only mode, driven by the clang built above: + # wasi-libc, compiler-rt builtins (installed into our clang's resource + # dir, where the wasm32 driver looks for them) and libc++ for wasip1 and + # wasip1-threads. The llvm-project tree is the same tarball as above so + # the wasm libc++ matches the native one. + buildsystem: cmake-ninja + builddir: true + build-options: + no-debuginfo: true + config-opts: + - -DCMAKE_TOOLCHAIN_FILE=/run/build/wasi-sysroot/wasi-host-toolchain.cmake + - -DWASI_SDK_BUILD_TOOLCHAIN=OFF + - -DWASI_SDK_INSTALL_TO_CLANG_RESOURCE_DIR=ON + - -DWASI_SDK_TARGETS=wasm32-wasip1;wasm32-wasip1-threads + - -DWASI_SDK_INCLUDE_TESTS=OFF + - -DCMAKE_INSTALL_PREFIX=/app + post-install: + # wasi-sdk installs into /share/wasi-sysroot; crafter-build + # looks there (Crafter.Build:Toolchain), so nothing to move. + - test -f /app/share/wasi-sysroot/share/libc++/v1/std.cppm + sources: + - type: git + url: https://github.com/WebAssembly/wasi-sdk.git + tag: wasi-sdk-34 + disable-submodules: true + - type: git + url: https://github.com/WebAssembly/wasi-libc.git + commit: 2e6fb9d8ee0cdf9e431fbcabe8af3115de000a13 + dest: src/wasi-libc + - type: archive + url: https://github.com/llvm/llvm-project/releases/download/llvmorg-22.1.8/llvm-project-22.1.8.src.tar.xz + sha256: 922f1817a0df7b1489272d18134ee0087a8b068828f87ac63b9861b1a9965888 + dest: src/llvm-project + - type: file + path: wasi-host-toolchain.cmake + + - name: wasmtime + # The runner for wasm32-wasi tests and `-r` on a non-browser wasm build. + buildsystem: simple + build-commands: + - install -Dm755 wasmtime /app/bin/wasmtime + sources: + - type: archive + url: https://github.com/bytecodealliance/wasmtime/releases/download/v49.0.2/wasmtime-v49.0.2-x86_64-linux.tar.xz + sha256: a4d6e9e3a5a60f527cf7793d674c48930c80c2e8977995b8a275cad3254b9322 + + - name: crafter-build + buildsystem: simple + build-options: + # The self-host build fetches Crafter.Math, Crafter.Asset, + # Crafter.LintRules and glslang from git; they are unpinned on purpose + # (sibling repos tracking their default branch, see AGENTS.md). + build-args: + - --share=network + build-commands: + # Bootstrap by hand, then let the result build itself: only the + # self-hosted binary has asset support and ThinLTO. + - ./build.sh + - ./bin/crafter-build + - CRAFTER_BUILD_HOME=$PWD/share/crafter-build ./bin/crafter-build test + - install -Dm755 bin/crafter.build-exe-*/crafter-build /app/bin/crafter-build + - install -Dm644 bin/crafter.build-lib-*/libcrafter-build.a /app/lib/libcrafter-build.a + - mkdir -p /app/share && cp -r share/crafter-build /app/share/ + - install -Dm644 LICENSE LICENSE.GPL -t /app/share/licenses/net.catcrafts.CrafterBuild/ + - install -Dm644 packaging/flatpak/net.catcrafts.CrafterBuild.metainfo.xml -t /app/share/metainfo/ + sources: + - type: dir + path: ../.. + skip: + - .git + - bin + - build + - lib/libcrafter-build.a + - share/crafter-build diff --git a/packaging/flatpak/wasi-host-toolchain.cmake b/packaging/flatpak/wasi-host-toolchain.cmake new file mode 100644 index 0000000..662d822 --- /dev/null +++ b/packaging/flatpak/wasi-host-toolchain.cmake @@ -0,0 +1,23 @@ +# SPDX-License-Identifier: LGPL-3.0-only +# SPDX-FileCopyrightText: Copyright (C) 2026 Catcrafts® +# +# Points wasi-sdk's sysroot build at the clang the Flatpak built, instead of +# the one wasi-sdk would build itself. Mirrors the wasi-sdk.cmake toolchain +# file an installed wasi-sdk ships, with our prefix. +set(WASI_SDK_PREFIX /app) +set(CMAKE_SYSTEM_NAME WASI) +set(CMAKE_SYSTEM_VERSION 1) +set(CMAKE_SYSTEM_PROCESSOR wasm32) +set(triple wasm32-wasip1) +set(CMAKE_C_COMPILER ${WASI_SDK_PREFIX}/bin/clang) +set(CMAKE_CXX_COMPILER ${WASI_SDK_PREFIX}/bin/clang++) +set(CMAKE_ASM_COMPILER ${WASI_SDK_PREFIX}/bin/clang) +set(CMAKE_AR ${WASI_SDK_PREFIX}/bin/llvm-ar) +set(CMAKE_RANLIB ${WASI_SDK_PREFIX}/bin/llvm-ranlib) +set(CMAKE_NM ${WASI_SDK_PREFIX}/bin/llvm-nm) +set(CMAKE_C_COMPILER_TARGET ${triple}) +set(CMAKE_CXX_COMPILER_TARGET ${triple}) +set(CMAKE_ASM_COMPILER_TARGET ${triple}) +# The sysroot does not exist yet when cmake probes the compiler. +set(CMAKE_C_COMPILER_WORKS ON) +set(CMAKE_CXX_COMPILER_WORKS ON) diff --git a/project.cpp b/project.cpp index f2a982b..d3364d3 100644 --- a/project.cpp +++ b/project.cpp @@ -132,6 +132,7 @@ extern "C" Configuration CrafterBuildProject(std::span a cfg.AddTest("VersionFlag").Dependencies({ CrafterBuildLib.get() }); cfg.AddTest("WasiBrowserRuntime").Dependencies({ CrafterBuildLib.get() }); cfg.AddTest("WasmVariants").Dependencies({ CrafterBuildLib.get() }); + cfg.AddTest("WasmHelloRuns").Dependencies({ CrafterBuildLib.get() }); cfg.AddTest("RunSingleTestExit").Dependencies({ CrafterBuildLib.get() }); // LoadProject dlopens the synthesized project.so, which references // Crafter:: symbols (HostTarget, Configuration ctors) that have to be diff --git a/tests/WasmHelloRuns/fixture/main.cpp b/tests/WasmHelloRuns/fixture/main.cpp new file mode 100644 index 0000000..59fb23d --- /dev/null +++ b/tests/WasmHelloRuns/fixture/main.cpp @@ -0,0 +1,9 @@ +// SPDX-License-Identifier: LGPL-3.0-only +// SPDX-FileCopyrightText: Copyright (C) 2026 Catcrafts® + +import std; + +int main() { + std::println("hello from wasm"); + return 0; +} diff --git a/tests/WasmHelloRuns/main.cpp b/tests/WasmHelloRuns/main.cpp new file mode 100644 index 0000000..58a910f --- /dev/null +++ b/tests/WasmHelloRuns/main.cpp @@ -0,0 +1,57 @@ +// SPDX-License-Identifier: LGPL-3.0-only +// SPDX-FileCopyrightText: Copyright (C) 2026 Catcrafts® + +import std; +import Crafter.Build; +namespace fs = std::filesystem; +using namespace Crafter; + +// A wasm build end to end: the fixture is compiled for wasm32-wasip1 against +// the resolved toolchain's WASI sysroot and the result is run under +// wasmtime. The other wasm tests only inspect generated files; this is the +// one that proves the sysroot, the std module for wasm and wasm-ld all work +// together. It skips when the sysroot or wasmtime is missing, so a +// development box without them is not a failure. Inside the Flatpak both are +// bundled, and there the skip would be the failure to look for. +int main() { + const Toolchain& tc = GetToolchain(); + if (!fs::exists(tc.wasiSysroot / "include")) { + std::println(std::cerr, "SKIP: no WASI sysroot at {}", tc.wasiSysroot.string()); + return 77; + } + if (std::system("which wasmtime > /dev/null 2>&1") != 0) { + std::println(std::cerr, "SKIP: wasmtime not on PATH"); + return 77; + } + + Configuration cfg; + cfg.path = fs::current_path() / "tests" / "WasmHelloRuns" / "fixture"; + cfg.name = "wasm-hello"; + cfg.outputName = "wasm-hello"; + cfg.target = "wasm32-wasip1"; + cfg.type = ConfigurationType::Executable; + std::array ifaces = {}; + std::array impls = { "main" }; + cfg.GetInterfacesAndImplementations(ifaces, impls); + + std::unordered_map> depResults; + std::mutex depMutex; + BuildResult r = Build(cfg, depResults, depMutex); + if (!r.result.empty()) { + std::println(std::cerr, "build failed: {}", r.result); + return 1; + } + + const fs::path wasm = ExpectedOutput(cfg); + if (!fs::exists(wasm)) { + std::println(std::cerr, "no .wasm produced at {}", wasm.string()); + return 1; + } + + const auto run = RunCommandWithTimeout(std::format("wasmtime {}", wasm.string()), std::chrono::seconds(60)); + if (run.exitCode != 0 || !run.output.contains("hello from wasm")) { + std::println(std::cerr, "wasmtime run failed (exit {}): {}", run.exitCode, run.output); + return 1; + } + return 0; +}