Commit graph Crafter.Build/implementations/Crafter.Build-Test.cpp
Author SHA1 Message Date
561398013c
feat(script): scripts in project.cpp, run with crafter-build run
A project.cpp could describe how a project is built, tested and linted,
but not the chores around it: packaging, installing, publishing. Those
stayed shell lines in a README or in someone's history.

Configuration gains `scripts`, a plain list like `defines`. A Script is
a name and a C++ callback, as a lint rule is; there is no script
language and no config file. The callback gets a ScriptContext (the
project, the project file's directory, the arguments) and returns the
exit status. ctx.Run runs a shell command with the terminal attached
and returns its status. It is only stored when project.cpp loads, which
happens for every verb and for every project that depends on this one.

`crafter-build run <name> [args...]` runs one, a bare `run` lists them.
Only the scripts of the project crafter-build was pointed at are
runnable, never a dependency's.

Everything after the script's name is handed to the script untouched.
The two branches that do this sit ahead of every other branch of the
parse loop, --version and --help included. Below the verbs,
`run package clean` would delete build/, and any other word would land
in the project's args and so in the variant hash, moving the build to
another directory. Options before the name keep their meaning, so
project args go there.

The driver is its own partition, :Script. It refuses a project with no
scripts, a name that is empty or starts with '-' (it could never be
typed), a name declared twice (running either would be a guess) and an
unknown name, each with exit 1 and without running anything. A script
that throws exits 1. A return value outside 0..255 becomes 1: an exit
status is eight bits, and a raw wait status such as 256 would otherwise
wrap to success.

Two helpers in :Platform:

- RunCommandAttached runs a command with inherited stdio. Every helper
  so far captured through popen, which is right for a compile step and
  useless for a command that runs for half an hour or asks a question.
  It reads the wait status through DecodeWaitStatus, which
  RunCommandChecked and RunCommandWithTimeout now share.
- ShellQuote is exported. It existed twice, as ShellQuote in :External
  and ShellQuoteSh in :Test, with the same behaviour; both are gone. A
  script that puts a path into a command needs it too.

Not included: a string shorthand for one-command scripts (a one-line
lambda covers it) and a ctx.Build() helper (Build() is already
exported).

A new partition and new exported symbols: run ./build.sh.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-20 19:08:26 +02:00
046609ff6f
feat(toolchain): resolve the compiler in one place
Every clang++, clang and llvm-ar the tool spawned was a bare name on
PATH, the std module source was /usr/share/libc++/v1/std.cppm, the WASI
sysroot /usr/share/wasi-sysroot and the mingw headers
/usr/x86_64-w64-mingw32, spelled out at eighteen call sites. A
toolchain installed anywhere else, which is what the Flatpak ships
under /app, had no way in.

Crafter.Build:Toolchain resolves all of it once per process:
CRAFTER_BUILD_LLVM_DIR, else a clang++ installed beside crafter-build
(<prefix>/bin, found through the same walk GetCrafterBuildHome does),
else PATH. What sits beside the compiler is used when present: its
libclang.so, its clang-c headers, its std module source, its WASI
sysroot. An explicit prefix without a clang++ is an error rather than a
fallback, for the same reason CRAFTER_BUILD_LIBCLANG was already
exclusive. CRAFTER_BUILD_LIBCXX_DIR points at a libc++ install other
than the compiler's own, and CRAFTER_MINGW_DIR now does what its help
line always claimed: it is passed as --sysroot for the mingw target.
Binaries linked by a toolchain outside /usr get an rpath to its lib
dir, since its libc++.so.1 is not on the loader's default path.

HostTarget() is the resolved clang's -print-target-triple. A clang built
without a vendor reports x86_64-unknown-linux-gnu where Arch's reports
x86_64-pc-linux-gnu, so target equality that decides whether a test runs
locally and which targets a bare `test` sweeps goes through SameTarget,
which ignores the vendor field. The strings themselves are never
rewritten: clang rejects a PCM whose triple differs from the importing
unit's. The examples and README stop hardcoding the host triple; the
default is already the host.

`crafter-build --version` prints the version baked in at build time
(CRAFTER_BUILD_VERSION, else `git describe`, else 0.0.0-dev), the
compiler and the host triple, and is answered before any argument is
forwarded to project.cpp. The version is a define on the library, so
each version is its own library variant and relinks the executable and
tests once per commit.

build.sh gains a shebang and `set -eu`, honours CRAFTER_BUILD_LLVM_DIR
and CRAFTER_BUILD_VERSION, skips the glslang clone when it is already
there, and writes a fresh archive instead of appending to one that may
hold members from an older layout.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-02 22:26:31 +02:00
46082a3117
refactor: drop Windows host support
crafter-build is moving to a single Flatpak distribution, and there is
no Windows host in that picture: no build.cmd, no MSVC or mingw host
build, no DLL plus launcher. Delete every host-Windows path instead of
leaving it to rot unverified: the cmd.exe command runner and quoting,
the LOCALAPPDATA cache, the MSVC and mingw-host LoadProject variants
with their LIBCXX_DIR and CRAFTER_MINGW_DIR plumbing, the winsock port
probe, the LoadLibrary libclang loader, the console-width and isatty
shims, and the preprocessor guards around the Linux bodies.

The x86_64-w64-mingw32 cross-compile target stays. It is target logic
(output names, -femulated-tls, the libstdc++ link extras, copying a
dependency DLL next to a mingw exe, wine as the runner) and did not
depend on any of the removed code. The msvc target goes with the host
that was the only thing able to use it.

Three target-path warts fixed on the way: ExpectedOutput named a mingw
static library <name>.lib while the archive step wrote lib<name>.a, so
those libraries relinked on every run; the import lib is no longer
copied next to a mingw exe, since the dependency's own bin dir is
already on the link line; and llvm-ar now writes every archive, not
only the LTO ones, so Debug and Release archives are made the same way.

CRAFTER_API and Crafter.Build-Api.h existed only for the DLL boundary.
Nothing downstream used them, so they are removed rather than kept as
an empty macro nobody can verify.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-02 22:03:34 +02:00
catbot
e8fde57582 fix: key the host PCM cache on source content, add clean, hash project args
Three follow-ons to the stale-build report, all cases of an identity not
capturing something that changes the output.

The host PCM cache under <cache>/crafter.build/<target>-<march>/ is shared by
every crafter-build on the machine, and freshness was a per-file mtime
comparison. That cannot tell "this PCM is newer than my source" from "this PCM
was built from different sources that happen to be newer", so a package install
and a working checkout — or two checkouts of different versions — silently
compiled their project.cpp against each other's declarations. Invalidation now
keys on a stamp over the bytes of every module source, which also covers the
case one file's mtime never could: the cached PCMs import each other, so a
change to :Interface invalidates :Clang's PCM with Crafter.Build-Clang.cppm
untouched.

Project args ApplyStandardArgs does not itself interpret are now folded into
VariantId. Such a flag typically decides what gets compiled or bundled — the
report's example is --no-webgpu dropping entries from cfg.files — and without it
both settings shared one bin dir and interleaved their outputs there, leaving a
bundle matching neither. Sorted and deduplicated so flag order doesn't split the
cache, and inherited by test Configurations.

`crafter-build clean` removes the project's bin/ and build/ trees. It
deliberately does not load project.cpp: cleaning is most often reached when
something is already wrong, and a clean that first needs the project to compile
is useless exactly then.
2026-07-30 17:43:09 +00:00
catbot
13697cd026 fix: re-resolve module imports before checking staleness
Adding a data member to a class in a module interface did not rebuild every
object compiled against the old layout. The build succeeded with no error or
warning and the resulting binary mixed both layouts, surfacing later as a
SIGSEGV in a destructor.

GetInterfacesAndImplementations scans a TU's `import X;` statements when the
source is declared. An import that matches neither a module in the
Configuration nor one reachable through `dependencies` was dropped on the
floor, leaving that TU with no staleness edge to the interface it consumes.
`dependencies` is frequently assigned *after* the scan — AddTest does exactly
that, resolving tests/<name>/main.cpp and only then returning a builder whose
.Dependencies() supplies the library — so consumers of a dependency's modules
routinely carried no edge at all. A layout change then rebuilt the library,
relinked the consumer, and kept the consumer's object as it was.

Unresolved names are now remembered on the partition/implementation as
pendingImports, and Configuration::ResolvePendingImports retries them against
the dependency DAG as it stands. Build() calls it immediately before comparing
mtimes, which closes the window for every caller rather than only the ones that
declare in the right order; TestBuilder::Dependencies also calls it so the
Configuration is coherent for anyone inspecting it before the build.

Resolves #27
2026-07-30 17:12:24 +00:00
8892154b28 linting
Some checks failed
CI / build-test-release (push) Failing after 7m15s
2026-07-23 01:24:42 +02:00
a25b0a1ded wasm flags update
Some checks failed
CI / build-test-release (push) Failing after 5m34s
2026-07-22 18:25:39 +02:00
603840879d new tests
All checks were successful
CI / build-test-release (push) Successful in 1h4m52s
2026-05-27 19:45:05 +02:00
8de93aaf06 test: drop transport runners (ssh/sshwin/wsl) and the Shell-quoting enum
All checks were successful
CI / build-test-release (pull_request) Successful in 9m56s
With test.toml + ForTarget covering the cross-arch + Windows-on-Linux
cases, the env-var-driven transport runners are dead weight. This commit
removes them and the retired tests that exercised the env-var plumbing:

  - TestRunner::Ssh / SshWin / Wsl factories and their copy/exec/cleanup
    template machinery.
  - TestRunner::Shell enum (Host/Sh/Cmd) and the ShellQuoteSh helper —
    only Host shell quoting is needed once the remote shells are gone.
  - TestRunner::copy / cleanup / remoteDir / argsShell fields.
  - WindowsPathToWsl and the {remote_bundle}/{bin_win}/{bundle_wsl}
    placeholder substitution in RunSingleTest's transport branch.
  - ParseRunnerSpec narrowed from {local, cmd, ssh, sshwin, wsl} to
    {local, cmd} — the override hatch is preserved, just simpler.
  - tests/SshRunner, tests/WindowsViaSsh, tests/QemuUser: these tested
    the CRAFTER_BUILD_RUNNER_<target> → runner plumbing that has been
    replaced by ForTarget. The runner derivation is exercised every
    time CrossArchAarch64 / Wasi / WindowsViaWine runs.
  - tests/UnitLib: ssh/sshwin spec assertions become "throws on bogus
    spec" assertions.

Refs issue #8.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-27 18:07:33 +02:00
dc27c5c204 test: introduce test.toml + target-derived runners alongside existing machinery
Vendors toml++ v3.4.0 as lib/toml.hpp and wires it into Crafter.Build-Test
to parse a declarative test.toml manifest (target/march/mtune/sysroot/
requires/timeout/args/defines). Test discovery now treats project.cpp and
test.toml as mutually exclusive: project.cpp stays the escape hatch for
outer-driver tests, test.toml gives downstream test authors a no-boilerplate
path.

Adds:
- TestRunner::Wine() and TestRunner::ForTarget(cfg) — runner is now derived
  from cfg.target (Local for host, Wine for Windows-on-Linux, wasmtime for
  WASI, qemu-<arch> with QEMU_LD_PREFIX for non-host Linux). The env-var
  override CRAFTER_BUILD_RUNNER_<target> still wins as a power-user escape
  hatch via FromEnv.
- Declarative preconditions: tool:<name>, file:<path>, env:<VAR> are
  evaluated before the build; missing preconditions Skip without paying
  the compile cost.
- Hard-fail-unless-declared: when a derived runner's tool is missing AND
  the test didn't declare 'tool:<that>' in requires, the missing runner
  is a Fail instead of a silent Skip. Surfaces broken cross-arch CI
  config that previously hid as "skipped".
- Multi-target sweep: bare `crafter-build test` (no --target=) now
  iterates every distinct test.toml-declared target plus the host, so
  cross-arch tests run by default without the user needing to know which
  targets exist. `--target=X` bypasses the sweep.

Test struct gains a `requires_` vector so project.cpp users can declare
preconditions too (matching what test.toml writes there).

Existing tests, factories (Ssh/SshWin/Wsl/Cmd), and CRAFTER_BUILD_RUNNER_*
machinery remain intact — this commit only adds; migration and deletion
follow in subsequent commits.

Refs issue #8.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-27 17:57:47 +02:00
0ab30a1d81 fixes
Some checks failed
CI / build-test-release (push) Failing after 8m31s
2026-04-30 02:20:19 +02:00
4d09eaac2a loading bar
Some checks failed
CI / build-test-release (push) Failing after 4m41s
2026-04-29 03:27:11 +02:00
eaee502e8c V2: WASI, -r flag, CI pipeline, examples & tests cleanup
Some checks failed
CI / build-test-release (pull_request) Failing after 44s
WASI / wasm32 target support
- Auto-detect /usr/share/wasi-sysroot on Linux when target starts_with("wasm32")
- Skip -march/-mtune for wasm (clang rejects them)
- Apply -fno-exceptions -fno-c++-static-destructors -mllvm -wasm-enable-sjlj
  -D_WASI_EMULATED_SIGNAL to wasm builds (compile + std PCM, kept in sync)
- .wasm output extension in expectedOutputFor and link command
- EnableWasiBrowserRuntime(cfg): opt-in helper that drops index.html +
  runtime.js next to the .wasm; runtime.js reads window.CRAFTER_WASM_URL
  set in the templated index.html so a single shim handles any output name

-r run flag in the CLI: build then exec the artifact (host targets only;
  rejects libraries; auto .exe/.wasm extension handling)

CI pipeline (.forgejo/workflows/ci.yaml)
- Triggers: PR/push to master + manual dispatch
- Single arch-latest container job: install deps, bootstrap, self-rebuild,
  run tests, cross-compile mingw, package both archives, upload artifacts
- Rolling 'latest' release published only on push/dispatch to master

mingw cross-compile from Linux now works end-to-end:
- ExternalDependency cache key includes target so per-target glslang builds
  don't collide; CMAKE_BUILD_TYPE=Release pinned (otherwise glslang appends
  'd' to lib names and breaks linking); cross-compile cmake flags
  (CMAKE_SYSTEM_NAME=Windows, CMAKE_*_COMPILER_TARGET=...)
- project.cpp accepts --target=<triple>; Linux-only -Wl,--export-dynamic
  and -ldl are gated; mingw glslang skips the standalone exe (its libgcc_eh
  link pulls pthread which mingw doesn't link by default)
- mingw compile uses -femulated-tls so std::__once_callable etc reference
  the same emutls symbols libstdc++ provides
- mingw link auto-adds -lstdc++exp -lpthread

GetCrafterBuildHome() exposed from the Platform module; LoadProject (Linux
+ Windows) now both use it instead of duplicating the resolution.

Examples reorg: hello-world, library, with-module, wasi, tests — each with
its own README. Tests reorg: per-test directory with inner/ fixture, no
shared tests/fixtures/ tree. New Wasi test verifies .wasm magic bytes.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-28 23:24:46 +02:00
cdfdb976c8 test runner, cross-target runners, lib/exe split
- subprocess-isolated test runner (replaces V1 dlopen-RunTest);
  Pass/Fail/Crash/Timeout/Skipped outcomes via :Test partition
- TestRunner abstraction with command templates: Local, Ssh,
  SshWin (cmd.exe-shell), QemuUser, FromEnv; probe-based skip
  when runner unreachable
- transitive PCM-path propagation in Build(); resolveImport
  walks deps recursively; depResults cache keyed by PcmDir()
  so per-target builds don't collide
- cfg.sysroot threaded through BuildStdPcm + base compile/link
  command (enables aarch64 cross via Arch Linux ARM rootfs)
- lib + exe split: project.cpp defines crafterBuildLib
  (LibraryStatic) + crafterBuildExe (Executable depending on
  it); build.sh produces lib/libcrafter-build.a alongside
  bin/crafter-build for downstream static-link consumers
- Windows DLL+launcher: CRAFTER_API macro, /EXPORT flag for
  project.dll's CrafterBuildProject; Crafter::Run as the real
  entry point with main.cpp as a thin wrapper
- 18 tests: HelloWorld/WithModule/Defines/CrossProjectModule/
  Diamond × (Linux + sshwin:winvm), plus Incremental,
  BuildError, Libraries, RunnerClassification, QemuUser,
  SshRunner, WindowsViaSsh, CrossArchAarch64
- single ./bin/crafter-build test runs everything; Windows
  variants skip gracefully if winvm SSH alias unreachable

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-27 22:32:19 +02:00
5e1fcd8590 V2 progress 2026-04-23 01:57:25 +02:00
7a64859d24 windows exception handler
Some checks failed
demo.yaml / windows exception handler (push) Failing after 0s
2026-03-09 20:14:44 +01:00
0819baf6d3 fixed recursive libs
Some checks failed
demo.yaml / fixed recursive libs (push) Failing after 0s
2025-11-16 18:44:41 +01:00
0eed272765 rewrite 2025-10-31 16:50:47 +01:00