diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 472622e..cfd0e0c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -169,6 +169,77 @@ jobs: grep -q "trap: back, witness=1" run.log grep -q "cpu: percpu round-trips" run.log + # ⚠️ THE TEMPLATE IS RENDERED BY HAND HERE, AND IT HAS TO BE. + # + # `mcpp new --template` resolves the package from the INDEX and takes no + # path, so asking it for a template this commit ADDS would resolve the + # previously published version and fail on a template that version does + # not carry. The CI for a new template could never pass before the + # template was published, which is the wrong way round. + # + # What belongs to this repository is the template's CONTENT: that the + # files it ships generate a project which builds for all three machines. + # That the scaffolder can fetch it is mcpp's own concern and mcpp's own + # tests. + # + # ⚠️ The dependency is rewritten to a path. The rendered manifest names + # `openarch = ""`, which is correct for a user and + # unresolvable here — this version is not published yet, and a check that + # silently fell back to the previous one would be testing the wrong tree. + # + # ⚠️ IN THE `gate` JOB'S riscv64 ROW, AND THE PLACEMENT IS THE FIX FOR A + # MISTAKE. 0.4.0 put this in `host-encoders`, which installs no emulator — + # so the check could only ever assert `mcpp build`, and a generated project + # that ran on nothing passed it. The emulator lives here. + # + # One row rather than all three: the template's CONTENT is + # host-independent, and the rewrite below puts `$PWD` into a manifest, + # which on a Windows runner is `/d/a/openarch/openarch` under Git Bash + # while mcpp wants a native path — a mismatch this repository has been + # bitten by before. + - name: The template generates a project that builds for all three machines and runs + if: matrix.arch == 'riscv64' + run: | + set -euo pipefail + T=templates/three-machines + V=$(grep -m1 '^version' mcpp.toml | cut -d'"' -f2) + D=$(mktemp -d); mkdir -p "$D/src" + for f in mcpp.toml README.md build.mcpp; do + sed -e "s/{{project\.name}}/k/g" -e "s/{{self\.version}}/$V/g" \ + "$T/$f.in" > "$D/$f" + done + cp "$T"/*.ld "$D/" + cp "$T"/src/* "$D/src/" + sed -i.bak "s|openarch = \"$V\"|openarch = { path = \"$PWD\" }|" "$D/mcpp.toml" + rm -f "$D/mcpp.toml.bak" + grep -q "path = " "$D/mcpp.toml" || { cat "$D/mcpp.toml"; echo "the dependency rewrite did not apply"; exit 1; } + for t in riscv64-none-elf aarch64-none-elf x86_64-none-elf; do + ( cd "$D" && mcpp build --target "$t" > /dev/null 2>&1 ) || true + ( cd "$D" && mcpp build --target "$t" ) \ + || { echo "the template does not build for $t"; exit 1; } + done + echo "the template builds for riscv64, aarch64 and x86_64" + + # ⚠️ AND IT MUST RUN, NOT ONLY BUILD — THIS ASSERTION IS HERE BECAUSE + # ITS ABSENCE SHIPPED A BROKEN QUICK START. + # + # 0.4.0's check stopped at `mcpp build`, so a generated project that + # compiled for all three machines and ran on none of them passed. What + # was missing is not in the sources: `mcpp.toml` declares the emulator + # packages under `[xlings] deps`, which is not an install trigger, so + # on a machine that has not installed them `build.mcpp` finds nothing + # and configures no runner. Measured against a freshly unpacked mcpp. + # + # This job installs the emulator in an earlier step, so the run below + # exercises the path a user gets AFTER the README's one-time install. + # What it cannot cover is the before — see the note in the README, and + # the comment in build.mcpp on why a build program cannot warn. + ( cd "$D" && mcpp run --target riscv64-none-elf 2>&1 | tee run.log ) \ + || { cat "$D/run.log"; echo "the generated project does not run"; exit 1; } + grep -q "switch ok" "$D/run.log" \ + || { cat "$D/run.log"; echo "the generated project ran without reaching the end"; exit 1; } + echo "the template's project runs, not only builds" + # The same source produced that output. Asserted rather than trusted: a # probe that had quietly grown a per-architecture branch would still pass # every line above, and the gate would be measuring two programs. @@ -309,55 +380,6 @@ jobs: # header now: a target build instantiates only what it calls — measured, # zero foreign symbols in either image — and a host build that calls both # gets both, with nothing to activate. - # ⚠️ THE TEMPLATE IS RENDERED BY HAND HERE, AND IT HAS TO BE. - # - # `mcpp new --template` resolves the package from the INDEX and takes no - # path, so asking it for a template this commit ADDS would resolve the - # previously published version and fail on a template that version does - # not carry. The CI for a new template could never pass before the - # template was published, which is the wrong way round. - # - # What belongs to this repository is the template's CONTENT: that the - # files it ships generate a project which builds for all three machines. - # That the scaffolder can fetch it is mcpp's own concern and mcpp's own - # tests. - # - # ⚠️ The dependency is rewritten to a path. The rendered manifest names - # `openarch = ""`, which is correct for a user and - # unresolvable here — this version is not published yet, and a check that - # silently fell back to the previous one would be testing the wrong tree. - # - # ⚠️ LINUX ONLY, AND THAT IS A CHOICE ABOUT PATHS RATHER THAN ABOUT - # COVERAGE. The rewrite below puts `$PWD` into a manifest, and on a - # Windows runner `$PWD` under Git Bash is `/d/a/openarch/openarch` while - # mcpp wants a native path — a mismatch this repository has already been - # bitten by once, in a C++ string literal that came out as - # `"D:\a\openkal\openkal/include"`. The template's CONTENT is - # host-independent; what varies per host is the toolchain payload, and - # that is what the `portability` job covers. - - name: The template generates a project that builds for all three machines - if: runner.os == 'Linux' - run: | - set -euo pipefail - T=templates/three-machines - V=$(grep -m1 '^version' mcpp.toml | cut -d'"' -f2) - D=$(mktemp -d); mkdir -p "$D/src" - for f in mcpp.toml README.md build.mcpp; do - sed -e "s/{{project\.name}}/k/g" -e "s/{{self\.version}}/$V/g" \ - "$T/$f.in" > "$D/$f" - done - cp "$T"/*.ld "$D/" - cp "$T"/src/* "$D/src/" - sed -i.bak "s|openarch = \"$V\"|openarch = { path = \"$PWD\" }|" "$D/mcpp.toml" - rm -f "$D/mcpp.toml.bak" - grep -q "path = " "$D/mcpp.toml" || { cat "$D/mcpp.toml"; echo "the dependency rewrite did not apply"; exit 1; } - for t in riscv64-none-elf aarch64-none-elf x86_64-none-elf; do - ( cd "$D" && mcpp build --target "$t" > /dev/null 2>&1 ) || true - ( cd "$D" && mcpp build --target "$t" ) \ - || { echo "the template does not build for $t"; exit 1; } - done - echo "the template builds for riscv64, aarch64 and x86_64" - # ⚠️ THREE ENCODERS SINCE 0.4.0, AND THE THIRD SETTLED A QUESTION THE # FIRST TWO LEFT OPEN. `openarch.pte` owns `MAIR_EL1` because aarch64's # entry holds an INDEX into it rather than a memory type, while riscv's diff --git a/abi/mcpp.toml b/abi/mcpp.toml index 34dc67c..e35ade7 100644 --- a/abi/mcpp.toml +++ b/abi/mcpp.toml @@ -24,7 +24,7 @@ [package] namespace = "mcpplibs" name = "openarch-abi" -version = "0.4.0" +version = "0.4.1" description = "openarch's C ABI: the contract between the interface and an instruction set's backend" license = "Apache-2.0" authors = ["mcpplibs"] diff --git a/backends/aarch64/mcpp.toml b/backends/aarch64/mcpp.toml index a01d1a6..bc251dd 100644 --- a/backends/aarch64/mcpp.toml +++ b/backends/aarch64/mcpp.toml @@ -14,7 +14,7 @@ [package] namespace = "mcpplibs" name = "openarch-aarch64" -version = "0.4.0" +version = "0.4.1" description = "openarch's aarch64 backend: the instructions behind the ABI" license = "Apache-2.0" authors = ["mcpplibs"] diff --git a/backends/riscv64/mcpp.toml b/backends/riscv64/mcpp.toml index ea6ec61..4b33603 100644 --- a/backends/riscv64/mcpp.toml +++ b/backends/riscv64/mcpp.toml @@ -14,7 +14,7 @@ [package] namespace = "mcpplibs" name = "openarch-riscv64" -version = "0.4.0" +version = "0.4.1" description = "openarch's riscv64 backend: the instructions behind the ABI" license = "Apache-2.0" authors = ["mcpplibs"] diff --git a/backends/x86_64/mcpp.toml b/backends/x86_64/mcpp.toml index e801bdd..500ff2e 100644 --- a/backends/x86_64/mcpp.toml +++ b/backends/x86_64/mcpp.toml @@ -22,7 +22,7 @@ [package] namespace = "mcpplibs" name = "openarch-x86-64" -version = "0.4.0" +version = "0.4.1" description = "openarch's x86_64 backend: the instructions behind the ABI" license = "Apache-2.0" authors = ["mcpplibs"] diff --git a/examples/switch/build.mcpp b/examples/switch/build.mcpp index 5eb50b8..4ef7c0d 100644 --- a/examples/switch/build.mcpp +++ b/examples/switch/build.mcpp @@ -49,6 +49,34 @@ int main() { const char* pkg = (arch == "riscv64") ? "qemu-riscv" : "qemu-arm"; const char* sys = (arch == "riscv64") ? "riscv64" : "aarch64"; + // ⚠️ DECLARED IS NOT INSTALLED, AND THE DIFFERENCE IS INVISIBLE UNTIL + // SOMEBODY BUILDS THIS ON A MACHINE THAT HAS NOT ALREADY GOT THE EMULATOR. + // + // The manifest's `[xlings] deps` names the emulator packages, and + // `mcpp::xpkg_dir` answers "where did that package land" — it installs + // nothing, and a manifest's `[xlings] deps` is not an install trigger. On a + // clean registry it returns empty, the branch below configures no runner, + // and `mcpp run` reports + // + // error: no runner is configured for 'riscv64-none-elf' + // + // with advice about writing a `runner` key — true in general, and not the + // cause here. Measured against a freshly unpacked mcpp: the project builds + // for all three machines and runs on none of them. + // + // An ordinary bare-metal project never meets this, because a BOARD package + // declares its emulator in the index descriptor's platform `deps`, and + // those ARE installed with the package. This project has no board package — + // none serves three machines — so it declares them itself. + // + // ⚠️ AND IT CANNOT WARN. A build program's output reaches the user only + // when it FAILS: mcpp captures the process and prints what it captured + // solely on a non-zero exit. A `std::cerr` note here was written, measured, + // and removed — it printed nothing on the very builds that needed it, which + // is worse than no note at all because it looks like a fix. Failing instead + // would be wrong too: `mcpp build` has no need of an emulator. + // + // So the README carries it, and this comment records why it has to. if (const char* dir = mcpp::xpkg_dir("xim", pkg); dir && *dir) { mcpp::runner(std::format("{}/bin/qemu-system-{}", dir, sys).c_str()); mcpp::runner("-machine"); diff --git a/mcpp.toml b/mcpp.toml index 4a85a00..e8539f6 100644 --- a/mcpp.toml +++ b/mcpp.toml @@ -33,7 +33,7 @@ [package] namespace = "mcpplibs" name = "openarch" -version = "0.4.0" +version = "0.4.1" description = "openarch: the architecture-mechanism layer — execution contexts, traps, per-CPU state and address spaces, as one interface over several instruction sets" license = "Apache-2.0" authors = ["mcpplibs"] diff --git a/templates/three-machines/README.md.in b/templates/three-machines/README.md.in index 2ad4c71..3a56916 100644 --- a/templates/three-machines/README.md.in +++ b/templates/three-machines/README.md.in @@ -1,11 +1,32 @@ # {{project.name}} ``` +xlings install qemu-riscv qemu-arm -y # once, per machine mcpp run --target riscv64-none-elf mcpp run --target aarch64-none-elf mcpp build --target x86_64-none-elf ``` +⚠️ **The first line is not optional, and leaving it out fails in a way that +does not name it.** `mcpp.toml` DECLARES those two packages under `[xlings] +deps`, and a declaration there is not an install trigger — it is what lets +`build.mcpp` ask where they landed. Without them installed, every target still +builds and `mcpp run` reports + +``` +error: no runner is configured for 'riscv64-none-elf' +``` + +which suggests writing a `runner` key. That advice is right in general and +wrong here: this project has one, and it is conditional on finding the +emulator. + +⭐ An ordinary bare-metal project never meets this. A board-support package +declares its emulator in the package index, and those ARE installed with the +package — so `mcpp new --template ` gives a project that runs +immediately. This one has no board package, because no board serves three +different instruction sets, which is the whole point of it. + ⭐ **The same `src/main.cpp` on all three.** That is the claim this template exists to hand over, and it is worth checking rather than believing: build it for two targets and compare the output. diff --git a/templates/three-machines/build.mcpp.in b/templates/three-machines/build.mcpp.in index 5eb50b8..4ef7c0d 100644 --- a/templates/three-machines/build.mcpp.in +++ b/templates/three-machines/build.mcpp.in @@ -49,6 +49,34 @@ int main() { const char* pkg = (arch == "riscv64") ? "qemu-riscv" : "qemu-arm"; const char* sys = (arch == "riscv64") ? "riscv64" : "aarch64"; + // ⚠️ DECLARED IS NOT INSTALLED, AND THE DIFFERENCE IS INVISIBLE UNTIL + // SOMEBODY BUILDS THIS ON A MACHINE THAT HAS NOT ALREADY GOT THE EMULATOR. + // + // The manifest's `[xlings] deps` names the emulator packages, and + // `mcpp::xpkg_dir` answers "where did that package land" — it installs + // nothing, and a manifest's `[xlings] deps` is not an install trigger. On a + // clean registry it returns empty, the branch below configures no runner, + // and `mcpp run` reports + // + // error: no runner is configured for 'riscv64-none-elf' + // + // with advice about writing a `runner` key — true in general, and not the + // cause here. Measured against a freshly unpacked mcpp: the project builds + // for all three machines and runs on none of them. + // + // An ordinary bare-metal project never meets this, because a BOARD package + // declares its emulator in the index descriptor's platform `deps`, and + // those ARE installed with the package. This project has no board package — + // none serves three machines — so it declares them itself. + // + // ⚠️ AND IT CANNOT WARN. A build program's output reaches the user only + // when it FAILS: mcpp captures the process and prints what it captured + // solely on a non-zero exit. A `std::cerr` note here was written, measured, + // and removed — it printed nothing on the very builds that needed it, which + // is worse than no note at all because it looks like a fix. Failing instead + // would be wrong too: `mcpp build` has no need of an emulator. + // + // So the README carries it, and this comment records why it has to. if (const char* dir = mcpp::xpkg_dir("xim", pkg); dir && *dir) { mcpp::runner(std::format("{}/bin/qemu-system-{}", dir, sys).c_str()); mcpp::runner("-machine");