diff --git a/.forgejo/workflows/ci.yaml b/.forgejo/workflows/ci.yaml index 2feeb1d..6d9d973 100644 --- a/.forgejo/workflows/ci.yaml +++ b/.forgejo/workflows/ci.yaml @@ -51,9 +51,8 @@ jobs: persist-credentials: true - name: Cache glslang clone+build - # Cache covers crafter-build's per-(target, march) external dep cache. - # First run builds glslang up to 6 times (3 marches × 2 targets); the - # cache makes subsequent runs reuse those builds. + # Cache covers crafter-build's external dep cache. The first run builds + # glslang; the cache makes subsequent runs reuse that build. uses: actions/cache@v4 with: path: | @@ -89,27 +88,14 @@ jobs: if: (github.event_name == 'push' || github.event_name == 'workflow_dispatch') && github.ref == 'refs/heads/master' run: CRAFTER_BUILD_HOME=$PWD/share/crafter-build CRAFTER_BUILD_MARCH=x86-64-v4 ./bin/crafter-build - # - name: Build Windows x86-64-v2 (mingw) - # if: (github.event_name == 'push' || github.event_name == 'workflow_dispatch') && github.ref == 'refs/heads/master' - # run: CRAFTER_BUILD_HOME=$PWD/share/crafter-build ./bin/crafter-build --target=x86_64-w64-mingw32 - - # - name: Build Windows x86-64-v3 (mingw) - # if: (github.event_name == 'push' || github.event_name == 'workflow_dispatch') && github.ref == 'refs/heads/master' - # run: CRAFTER_BUILD_HOME=$PWD/share/crafter-build CRAFTER_BUILD_MARCH=x86-64-v3 ./bin/crafter-build --target=x86_64-w64-mingw32 - - # - name: Build Windows x86-64-v4 (mingw) - # if: (github.event_name == 'push' || github.event_name == 'workflow_dispatch') && github.ref == 'refs/heads/master' - # run: CRAFTER_BUILD_HOME=$PWD/share/crafter-build CRAFTER_BUILD_MARCH=x86-64-v4 ./bin/crafter-build --target=x86_64-w64-mingw32 - - name: Package artifacts if: (github.event_name == 'push' || github.event_name == 'workflow_dispatch') && github.ref == 'refs/heads/master' run: | set -eux mkdir -p dist - # 6 archives — 3 marches × 2 OSes. Each is self-contained: the - # Linux launcher is statically linked; the Windows launcher has - # crafter-build.dll + libcrafter-build.dll.a alongside the exe. + # 3 archives, one per march level. Each carries the executable, the + # static library and share/; libc++ comes from the host. for march in x86-64-v2 x86-64-v3 x86-64-v4; do # Short suffix (v2/v3/v4) for archive filenames; the upload-artifact # steps below reference these short names. @@ -124,18 +110,12 @@ jobs: cp bin/crafter.build-lib-x86_64-pc-linux-gnu-$march-*/libcrafter-build.a "$stage_lin/lib/" cp -r share "$stage_lin/" tar czf "dist/crafter-build-linux-x86_64-$short.tar.gz" -C "$stage_lin" . - - # stage_win=$(mktemp -d) - # mkdir -p "$stage_win/bin" - # cp bin/crafter.build-exe-x86_64-w64-mingw32-$march-*/* "$stage_win/bin/" - # cp -r share "$stage_win/" - # (cd "$stage_win" && zip -r "$GITHUB_WORKSPACE/dist/crafter-build-windows-x86_64-$short.zip" .) done ls -la dist/ # Upload each archive as its own workflow artifact (one upload per file) - # so users browsing the PR / run page get six small, individually-named + # so users browsing the PR / run page get small, individually-named # downloads instead of one wrapper zip containing the lot. - name: Upload Linux v2 artifact if: (github.event_name == 'push' || github.event_name == 'workflow_dispatch') && github.ref == 'refs/heads/master' @@ -149,18 +129,6 @@ jobs: if: (github.event_name == 'push' || github.event_name == 'workflow_dispatch') && github.ref == 'refs/heads/master' uses: actions/upload-artifact@v3 with: { name: crafter-build-linux-x86_64-v4, path: dist/crafter-build-linux-x86_64-v4.tar.gz, if-no-files-found: error } - # - name: Upload Windows v2 artifact - # if: (github.event_name == 'push' || github.event_name == 'workflow_dispatch') && github.ref == 'refs/heads/master' - # uses: actions/upload-artifact@v3 - # with: { name: crafter-build-windows-x86_64-v2, path: dist/crafter-build-windows-x86_64-v2.zip, if-no-files-found: error } - # - name: Upload Windows v3 artifact - # if: (github.event_name == 'push' || github.event_name == 'workflow_dispatch') && github.ref == 'refs/heads/master' - # uses: actions/upload-artifact@v3 - # with: { name: crafter-build-windows-x86_64-v3, path: dist/crafter-build-windows-x86_64-v3.zip, if-no-files-found: error } - # - name: Upload Windows v4 artifact - # if: (github.event_name == 'push' || github.event_name == 'workflow_dispatch') && github.ref == 'refs/heads/master' - # uses: actions/upload-artifact@v3 - # with: { name: crafter-build-windows-x86_64-v4, path: dist/crafter-build-windows-x86_64-v4.zip, if-no-files-found: error } - name: Update rolling 'latest' tag if: (github.event_name == 'push' || github.event_name == 'workflow_dispatch') && github.ref == 'refs/heads/master' @@ -172,7 +140,7 @@ jobs: - name: Publish rolling 'latest' release # forgejo-release uploads each file in release-dir as a separate - # release asset, so the Releases page shows six individually + # release asset, so the Releases page shows individually # downloadable archives — pick the v-level matching your CPU. if: (github.event_name == 'push' || github.event_name == 'workflow_dispatch') && github.ref == 'refs/heads/master' uses: https://code.forgejo.org/actions/forgejo-release@v2 diff --git a/AGENTS.md b/AGENTS.md index f0eff75..33f5018 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -8,12 +8,12 @@ crafter-build is a C++26 modules build system. A project is described by a `proj | Path | What lives there | |---|---| -| `interfaces/Crafter.Build-.cppm` | Module interface partitions. `Crafter.Build.cppm` is the umbrella that re-exports them. `Crafter.Build-Api.h` defines `CRAFTER_API`. | +| `interfaces/Crafter.Build-.cppm` | Module interface partitions. `Crafter.Build.cppm` is the umbrella that re-exports them. | | `implementations/Crafter.Build-.cpp` | Implementation units, one per partition, named `Crafter.Build:_impl`. `main.cpp` is a thin wrapper around `Crafter::Run`. | | `project.cpp` | The self-host build description: a `crafter.build-lib` library, a `crafter.build-exe` executable, the test declarations and the lint rules. | | `tests//main.cpp` | One self-test per directory; `tests//fixture/` holds the sample project it builds. | | `examples/` | Standalone user-facing projects, MIT licensed. | -| `build.sh`, `build.cmd` | Bootstrap scripts for Linux and Windows. They compile everything by hand, without crafter-build. | +| `build.sh` | Bootstrap script. It compiles everything by hand, without crafter-build. | | `wasi-runtime/` | `runtime.js` and `index.html.in`, shipped for browser targets. | | `lib/` | Vendored third-party headers (nlohmann/json, glslang's DirStackFileIncluder). Do not edit. | | `share/crafter-build/`, `bin/`, `build/`, `lib/*.a` | Generated and gitignored. | @@ -42,12 +42,10 @@ Three things to know about this loop: - `bin/crafter-build` is the bootstrap binary and the harness for every command below. The self-host build writes to `bin/crafter.build-exe-/crafter-build` and does not replace it. - Changes under `implementations/` are picked up by the self-host build and by the tests, which link the local library. Changes under `interfaces/` are not: the harness compiles against the copies in `share/crafter-build/` and the PCM cache built from them, so a new or changed declaration is invisible until you run `./build.sh` again. The symptom is `use of undeclared identifier` for the symbol you just added. -- A new `CRAFTER_API` symbol that a `project.cpp` reaches on its default path also needs `./build.sh`: the old binary does not export it, and `LoadProject` fails with an undefined symbol. +- A new exported symbol that a `project.cpp` reaches on its default path also needs `./build.sh`: the old binary does not export it, and `LoadProject` fails with an undefined symbol. `./build.sh` clones and builds glslang on first run and takes several minutes; later runs are faster. `./bin/crafter-build clean` deletes `bin/` and `build/` without loading `project.cpp`. Set `CRAFTER_BUILD_HOME` only when running a binary from outside this tree; `CRAFTER_BUILD_MARCH` and `CRAFTER_BUILD_MTUNE` default to `native`. -Windows builds use `build.cmd` with `LIBCXX_DIR` pointing at a libc++ install; the Linux-to-mingw cross-compile is a separate artifact with a different ABI. See the README. Windows branches of recent features are wired but often unverified: say so in the PR when you touch one and cannot test it. - ## Build, test and style commands ```bash @@ -104,10 +102,9 @@ Suppress one finding with `// lint-disable-next-line ` or a whole file wit Beyond the rules: -- `import std;` everywhere; standard headers are never `#include`d. C headers (`clang-c/Index.h`, `dlfcn.h`, `windows.h`) go in the global module fragment after `module;`. -- Mark every function or method that `project.cpp` or a downstream consumer calls with `CRAFTER_API` in the interface. It is the Windows DLL boundary. +- `import std;` everywhere; standard headers are never `#include`d. C headers (`clang-c/Index.h`, `dlfcn.h`) go in the global module fragment after `module;`. - Comments explain why: the failure mode, the constraint, the rejected alternative. The codebase carries long comments on purpose. Do not strip them, and write them for new non-obvious code. Commit bodies follow the same rule. -- Platform splits live in `:Platform`, guarded by `CRAFTER_BUILD_CONFIGURATION_TARGET_x86_64_pc_windows_msvc` and `_x86_64_w64_mingw32`. In `project.cpp` and tests, branch on `cfg.target` strings instead of preprocessor macros. +- There is no host-platform preprocessor split: the host is Linux. Target differences (mingw, wasm) branch on `cfg.target` strings at runtime, in the library as well as in `project.cpp` and tests. - A build step fails when its command exits nonzero, never because it printed. Use `RunBuildCommand` or `RunCommandChecked`; warnings go to `Progress::Diagnostic`. - Every output path goes through `Configuration::BinDir()`, `BuildDir()` and `PcmDir()`. Hand-rolled `path / "bin" / ...` clobbered variants before; do not add new ones. - Anything that changes what gets compiled or bundled belongs in `VariantId()`. It already hashes type, debug, sysroot, defines, compile flags, wasm variant flags and uninterpreted project args. @@ -119,7 +116,7 @@ These were decided deliberately. Do not undo them without the maintainer's say-s 1. **Configuration is C++.** No JSON, YAML, TOML or DSL for project, runner or lint configuration. Lint rules are C++ callbacks; there is no clang-format or clang-tidy integration and no separate "format rule" type. A rule that calls `ctx.SetContent` is a transform, and that is the whole mechanism. 2. **Per-project artifacts go in `/build` and `/bin`. Only cross-project things go in `~/.cache/crafter.build/`:** the std PCM and Crafter.Build PCMs per target and march, and external clones keyed by their spec hash. 3. **Sources ship, PCMs build locally.** Packages install `*.cppm` under `share/crafter-build/`. Nothing target-baked ships except the executable itself, and that defaults to a portable `-march` in `PKGBUILD` and CI. -4. **Linux is a monolithic executable with `--export-dynamic`; Windows is a DLL plus a launcher.** The asymmetry is intentional. +4. **crafter-build is a monolithic executable with `--export-dynamic`.** `project.so` resolves `Crafter::` symbols from the running binary at dlopen time. There is no Windows host; `x86_64-w64-mingw32` is a cross-compile target for user projects only. 5. **Convention over configuration.** Lead with the zero-config path. Env var, then CLI flag, then full `project.cpp` are the escalation, not the entry point. 6. **One skip mechanism** (runner probe plus exit 77) and **one `TestRunner` shape** (template fields with `{bin}`, `{bundle}` and similar placeholders; `Cmd(...)` covers any wrapper). Do not add a special-case runner class or a hardcoded "skip on this OS" branch. 7. **The house-style rules stay in their own repo.** They are not library API of crafter-build and are not forced on downstream projects. @@ -154,4 +151,3 @@ These were decided deliberately. Do not undo them without the maintainer's say-s - The bootstrap binary is compiled without `CRAFTER_BUILD_HAS_ASSET`, so `cfg.assets` only works in the self-hosted binary. Changing asset extensions means updating two maps in `Crafter.Build-Clang.cpp`: `assetOutput` and the transitive copy loop. - A warning is printed only when its unit actually recompiles. A build with nothing to do is silent, which is not evidence that a warning is gone. - `VariantId()` names the output directory, so CI globs `bin/crafter.build-exe---*/` rather than predicting the hash. -- When driving `build.cmd` from a Windows `cmd` shell over SSH: write `set VAR=value&&next` without a space before `&&`, and `if exist X rmdir /S /Q X` instead of chaining `rmdir` with `&&`. diff --git a/README.md b/README.md index 301f355..a5ebf0a 100644 --- a/README.md +++ b/README.md @@ -154,18 +154,15 @@ Outcomes: ✅ pass, ❌ fail, 💥 crash (with signal name), ⏱ timeout, ⏭ sk ```cpp TestRunner::Local() // run on this machine (default) TestRunner::Cmd("wine") // local exec via prefix command -TestRunner::Ssh("hostname") // remote linux via scp + ssh -TestRunner::SshWin("winhost") // remote windows via scp + ssh-cmd.exe -TestRunner::Wsl() // Linux binaries via WSL2 (Windows host) ``` -`Cmd(cmd)` is a generic ` ` prefix runner — covers wine, qemu-user, valgrind, gdbserver, anything with that shape. Probes via `which ` (or `where` on Windows hosts). The `Ssh` variants `scp -r` the test's whole bin dir, run the binary remotely, then clean up; each invocation uses a unique remote subdir so parallel runs don't collide. `Wsl()` mirrors the SSH model but copies the bundle into WSL's native filesystem (faster than executing in-place from `/mnt/c`) — useful when a Windows host wants to exercise Linux test binaries. +`Cmd(cmd)` is a generic ` ` prefix runner — covers wine, qemu-user, valgrind, gdbserver, anything with that shape. Probes via `which `. For elaborate setups (rsync, ssh ControlMaster, env vars on the spawn, `QEMU_LD_PREFIX` for sysroots), construct `TestRunner` directly — `copy`/`exec`/`cleanup`/`probe` are public string fields with `{bundle}`, `{remote_bundle}`, `{bin}`, `{bin_name}`, `{args}` placeholders. -For cross-arch builds, set `cfg.sysroot` on the `Configuration` to point at a sysroot for the target. `BuildStdPcm` and the compile/link commands both consume it: `--sysroot=` is passed to clang, and `/usr/share/libc++/v1/std.cppm` is precompiled instead of the host's. The cheapest aarch64 sysroot is an extracted [Arch Linux ARM rootfs](http://os.archlinuxarm.org/) plus `libc++` + `libc++abi` + `gcc` installed into it via `pacman --root --config --dbpath /var/lib/pacman -Sy …` (point pacman at ALARM's mirrors via explicit config to avoid pulling host-arch packages). See `tests/CrossArchAarch64/inner/` for a working sysroot+qemu example. +For cross-arch builds, set `cfg.sysroot` on the `Configuration` to point at a sysroot for the target. `BuildStdPcm` and the compile/link commands both consume it: `--sysroot=` is passed to clang, and `/usr/share/libc++/v1/std.cppm` is precompiled instead of the host's. The cheapest aarch64 sysroot is an extracted [Arch Linux ARM rootfs](http://os.archlinuxarm.org/) plus `libc++` + `libc++abi` + `gcc` installed into it via `pacman --root --config --dbpath /var/lib/pacman -Sy …` (point pacman at ALARM's mirrors via explicit config to avoid pulling host-arch packages). -For `--target=x86_64-w64-mingw32` (Windows from Linux), V2's pipeline cross-compiles as long as `mingw-w64` is installed. Pair it with `--runner=cmd:wine` (no VM needed) or `--runner=sshwin:winhost` (real Windows). Full MSVC ABI (`x86_64-pc-windows-msvc`) cross-compile from Linux is not yet wired; use MinGW for now. `-fuse-ld=lld` is auto-injected on every link, and `-lstdc++exp` is auto-linked for the mingw target so user projects don't need to declare those flags. +For `--target=x86_64-w64-mingw32` (Windows from Linux), the pipeline cross-compiles as long as `mingw-w64` is installed. Pair it with `--runner=cmd:wine` to run the result. Only the MinGW ABI is supported; there is no MSVC target and no Windows host. `-fuse-ld=lld` is auto-injected on every link, and `-lstdc++exp` is auto-linked for the mingw target so user projects don't need to declare those flags. ### Configuring runners @@ -192,12 +189,9 @@ Spec grammar: ``` local cmd: # cmd:wine, cmd:qemu-aarch64, cmd:valgrind, ... -ssh:[:] -sshwin:[:] # cmd.exe-shell variant for Windows remotes -wsl[:] # Windows host -> Linux binaries via WSL2 ``` -Each runner carries a `probe` command (e.g. `which wine`, `ssh -BatchMode=yes true`); if it fails, every test using that runner is reported as ⏭ skipped and the rest of the suite continues. Skipped tests count toward the summary but don't fail the suite (`AllPassed()` returns true on skip-only runs). +Each runner carries a `probe` command (e.g. `which wine`); if it fails, every test using that runner is reported as ⏭ skipped and the rest of the suite continues. Skipped tests count toward the summary but don't fail the suite (`AllPassed()` returns true on skip-only runs). ### Linking the parent project's library