Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
259 changes: 182 additions & 77 deletions .agents/docs/2026-08-06-provisions-and-build-inputs.md

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion .github/actions/bootstrap-mcpp/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ inputs:
# `package.name`, so one of the two was simply unreachable — and which one
# depended on the machine, which is why CI failed on `compat:lua` on
# Windows and `mcpplibs.capi:lua` on Linux. Never pin below that.
default: '2026.8.5.2'
default: '2026.8.6.2'
cache-target:
description: also restore/save target/ (build artifacts + BMIs)
required: false
Expand Down
2 changes: 1 addition & 1 deletion .github/actions/setup-macos-llvm/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ inputs:
# Floor imposed by the index, not a routine bump — see
# .github/actions/bootstrap-mcpp/action.yml for why 0.4.69 is required
# (two packages named `lua` in one repo need openxlings/xlings#381).
default: '2026.8.5.2'
default: '2026.8.6.2'

runs:
using: composite
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/bootstrap-macos.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ jobs:
# Dormant (workflow_dispatch only), but kept in step with the rest —
# check_version_pins.sh holds it there. Floor: 0.4.69, below which the
# index cannot resolve two packages that share a short name.
XLINGS_VERSION: '2026.8.5.2'
XLINGS_VERSION: '2026.8.6.2'
steps:
- uses: actions/checkout@v4

Expand Down
19 changes: 17 additions & 2 deletions .github/workflows/ci-aarch64-fresh-install.yml
Original file line number Diff line number Diff line change
Expand Up @@ -120,12 +120,27 @@ jobs:
echo "self-hosting $repo @ $ref"
mcpp self config --mirror GLOBAL 2>/dev/null || true
mcpp build --target aarch64-linux-musl
m=$(find target/aarch64-linux-musl -type f -path '*/bin/mcpp' | head -1)
# Absolute: it is used again after `cd /tmp/xlings-src` below.
m=$(find "$PWD/target/aarch64-linux-musl" -type f -path '*/bin/mcpp' | head -1)
file "$m" | grep -q "ARM aarch64" || { echo "expected aarch64 mcpp"; exit 1; }
"$m" --version
git clone --depth 1 https://github.com/openxlings/xlings /tmp/xlings-src
cd /tmp/xlings-src
mcpp build --target aarch64-linux-musl
# "$m", not `mcpp`: the just-built binary is the code under review,
# and building xlings with the INSTALLED one meant this half of the
# gate never saw the PR — the same defect the clone-ref comment above
# records, one line further down. It surfaced the same way: a fix for
# an aarch64-only failure in exactly this build could not be
# validated here, because the binary running it predated the fix.
#
# MCPP_HOME must be carried over explicitly: mcpp derives it from the
# BINARY's location, so a binary sitting in /tmp/mcpp-src/target would
# otherwise adopt an empty home and re-bootstrap the whole ecosystem
# instead of reusing what the fresh-install steps above provisioned.
export MCPP_HOME=$(mcpp self env | awk -F'= *' '/^MCPP_HOME/{print $2; exit}')
echo "reusing MCPP_HOME=$MCPP_HOME"
test -d "$MCPP_HOME" || { echo "could not determine MCPP_HOME"; exit 1; }
"$m" build --target aarch64-linux-musl
x=$(find target/aarch64-linux-musl -type f -path '*/bin/xlings' | head -1)
file "$x" | grep -q "ARM aarch64" || { echo "expected aarch64 xlings"; exit 1; }
"$x" --version
Expand Down
6 changes: 3 additions & 3 deletions .github/workflows/ci-fresh-install.yml
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@ jobs:
env:
XLINGS_NON_INTERACTIVE: '1'
run: |
curl -fsSL https://raw.githubusercontent.com/openxlings/xlings/main/tools/other/quick_install.sh | bash -s v2026.8.5.2
curl -fsSL https://raw.githubusercontent.com/openxlings/xlings/main/tools/other/quick_install.sh | bash -s v2026.8.6.2
echo "$HOME/.xlings/subos/current/bin" >> "$GITHUB_PATH"

- name: Install mcpp and config mirror
Expand Down Expand Up @@ -292,7 +292,7 @@ jobs:

- name: Install xlings + mcpp
run: |
curl -fsSL https://raw.githubusercontent.com/openxlings/xlings/main/tools/other/quick_install.sh | bash -s v2026.8.5.2
curl -fsSL https://raw.githubusercontent.com/openxlings/xlings/main/tools/other/quick_install.sh | bash -s v2026.8.6.2
# Deliberately NOT writing to $GITHUB_PATH here. On container
# images that declare no PATH in their config (opensuse/
# tumbleweed), appending a single dir to GITHUB_PATH makes the
Expand Down Expand Up @@ -363,7 +363,7 @@ jobs:
# (older ones carry minos=15 and refuse to start).
# v0.4.51+: in-process sha256 — this image has no sha256sum
# binary, so pinned fetches failed before it.
curl -fsSL https://raw.githubusercontent.com/openxlings/xlings/main/tools/other/quick_install.sh | bash -s v2026.8.5.2
curl -fsSL https://raw.githubusercontent.com/openxlings/xlings/main/tools/other/quick_install.sh | bash -s v2026.8.6.2
echo "$HOME/.xlings/subos/current/bin" >> "$GITHUB_PATH"

- name: Install mcpp and config mirror
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/ci-linux-e2e.yml
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ jobs:

- name: Bootstrap xlings + released mcpp
run: |
curl -fsSL https://raw.githubusercontent.com/openxlings/xlings/main/tools/other/quick_install.sh | bash -s v2026.8.5.2
curl -fsSL https://raw.githubusercontent.com/openxlings/xlings/main/tools/other/quick_install.sh | bash -s v2026.8.6.2
export PATH="$HOME/.xlings/subos/current/bin:$PATH"
xlings update
xlings install mcpp -y -g
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/cross-build-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ jobs:
# release assets were uploaded in a broken state (records present,
# blobs missing → 404 on GET); re-uploaded clean. The stale-INDEX
# half is handled by the marker-clear below.
XLINGS_VERSION: '2026.8.5.2'
XLINGS_VERSION: '2026.8.6.2'
run: |
tarball="xlings-${XLINGS_VERSION}-linux-x86_64.tar.gz"
curl -fsSL -o "/tmp/${tarball}" \
Expand Down Expand Up @@ -255,7 +255,7 @@ jobs:
- name: Bootstrap mcpp via xlings
env:
XLINGS_NON_INTERACTIVE: '1'
XLINGS_VERSION: '2026.8.5.2'
XLINGS_VERSION: '2026.8.6.2'
run: |
tarball="xlings-${XLINGS_VERSION}-linux-x86_64.tar.gz"
curl -fsSL -o "/tmp/${tarball}" \
Expand Down
14 changes: 7 additions & 7 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ jobs:
# Pin xlings to a known-good version. The upstream install
# script always grabs `latest` (no version override), so we
# download + self-install manually to avoid broken releases.
XLINGS_VERSION: '2026.8.5.2'
XLINGS_VERSION: '2026.8.6.2'
run: |
if [ ! -x "$HOME/.xlings/subos/default/bin/xlings" ]; then
tarball="xlings-${XLINGS_VERSION}-linux-x86_64.tar.gz"
Expand Down Expand Up @@ -288,7 +288,7 @@ jobs:
- name: Bootstrap mcpp via xlings
env:
XLINGS_NON_INTERACTIVE: '1'
XLINGS_VERSION: '2026.8.5.2'
XLINGS_VERSION: '2026.8.6.2'
run: |
tarball="xlings-${XLINGS_VERSION}-linux-x86_64.tar.gz"
curl -fsSL -o "/tmp/${tarball}" \
Expand Down Expand Up @@ -358,11 +358,11 @@ jobs:
# below are pinned to the same version as XLINGS_VERSION; they are
# NOT interpolated from it, so check_version_pins.sh scans for them
# explicitly (they were absent from the old lock-step comment).
XLA="xlings-2026.8.5.2-linux-aarch64.tar.gz"
XLA="xlings-2026.8.6.2-linux-aarch64.tar.gz"
if curl -fsSL -o "/tmp/$XLA" \
"https://github.com/openxlings/xlings/releases/download/v2026.8.5.2/$XLA"; then
"https://github.com/openxlings/xlings/releases/download/v2026.8.6.2/$XLA"; then
tar -xzf "/tmp/$XLA" -C /tmp
XLBIN=$(find /tmp/xlings-2026.8.5.2-linux-aarch64 -path '*/bin/xlings' -type f | head -1)
XLBIN=$(find /tmp/xlings-2026.8.6.2-linux-aarch64 -path '*/bin/xlings' -type f | head -1)
if [ -n "$XLBIN" ]; then
mkdir -p "$STAGING/$WRAPPER/registry/bin"
cp "$XLBIN" "$STAGING/$WRAPPER/registry/bin/xlings"
Expand Down Expand Up @@ -440,7 +440,7 @@ jobs:
- name: Bootstrap mcpp via xlings
env:
XLINGS_NON_INTERACTIVE: '1'
XLINGS_VERSION: '2026.8.5.2'
XLINGS_VERSION: '2026.8.6.2'
run: |
if [ ! -x "$HOME/.xlings/subos/default/bin/xlings" ]; then
WORK=$(mktemp -d)
Expand Down Expand Up @@ -622,7 +622,7 @@ jobs:
shell: bash
env:
XLINGS_NON_INTERACTIVE: '1'
XLINGS_VERSION: '2026.8.5.2'
XLINGS_VERSION: '2026.8.6.2'
run: |
# Captured before the `cd` below, in POSIX form: this step never
# returns to the workspace, and GITHUB_WORKSPACE is a backslash
Expand Down
2 changes: 1 addition & 1 deletion .xlings.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"workspace": {
"mcpp": "2026.8.5.3"
"mcpp": "2026.8.6.1"
}
}
89 changes: 88 additions & 1 deletion docs/05-mcpp-toml.md
Original file line number Diff line number Diff line change
Expand Up @@ -596,7 +596,9 @@ aliases `windows` / `linux` / `macos` / `unix` are never valid target triples, s
there is no ambiguity. Use the bare form for a single OS/family; use `cfg(...)`
when you need arch/env conditions or combinators.

- **Keys**: `dependencies` / `dev-dependencies` / `build-dependencies`, and
- **Keys**: `dependencies` / `dev-dependencies` / `build-dependencies` /
`feature-deps.<feature>` (mcpp 2026.8.6.2+ — see §2.14; the feature is
registered unconditionally, only its dependency set is scoped), and
`build` with `cflags` / `cxxflags` / `ldflags` / `sources` (mcpp 0.0.95+ —
conditional source globs, e.g. gating `src/x86/**/*.asm` behind
`cfg(arch = "x86_64")`; `!`-exclusion globs work here too), plus `flags` and
Expand Down Expand Up @@ -1064,6 +1066,91 @@ else — the same separation Cargo draws with `[build-dependencies]`. (Before
2026.8.5.2 it was also built as an ordinary library, which made `import mcpp;`
inside a rule fail: the bundled module does not exist in that second compile.)

#### `reexport = true` — a library standing up a toolchain for its user (2026.8.6.2+)

Everything above is declared by whoever *uses* the tool. That is the wrong
place when the knowledge belongs to a library: gRPC's code generation needs
protobuf's `protoc`, and no user of a gRPC package should have to know that.

`reexport = true` hands an edge's build-time provisions — its `tools`, its
`host-module`, and the dependency's directory — to **this package's own
consumers**:

```toml
# inside the grpc package's manifest
[feature-deps.codegen]
"compat.protobuf" = { version = "35.1", tools = ["protoc"], reexport = true }
grpc-plugin = { version = "1.83.0", tools = ["grpc_cpp_plugin"], reexport = true }
grpcgen = { version = "1.83.0", host-module = true, reexport = true }
```

Its user then writes one line, and imports the rule:

```toml
[dependencies]
grpc = { version = "1.83.0", features = ["codegen"] }
```

```cpp
// build.mcpp
import mcpp;
import grpcgen;
int main() { return grpcgen::generate_all() ? 0 : 1; }
```

- **Off by default, and deliberately not the edge's `visibility`.** `visibility`
already defaults to `"public"`, so riding it would let any dependency at any
depth put entries into your build program's tool namespace without saying so.
Handing something to your consumers is a supply-chain statement; it has to be
written down.
- **One hop per declaration.** A re-exported provision reaches the consumers of
the package that declared it. For it to travel further, the next package must
re-export in turn — each package decides only what *it* hands on.
- **A feature may add a request to a dependency you already declare.** gRPC
depends on protobuf unconditionally and its `codegen` feature adds
`tools = ["protoc"], reexport = true` to that same edge. `tools` and
`features` union, `host-module` and `reexport` OR together; `version` /
`path` / `git` do not merge, so a feature still cannot silently override the
unconditional entry's identity.
- **Visibility, not execution.** `dep_bin()` returns a path; whether anything
runs is still the consumer's `build.mcpp`'s decision. Nothing changes about
who builds the tool or how the tool store is keyed.
- **Unqualified names are resolved by a ladder, not by luck.** Once two
libraries can re-export, both may offer the tail `protobuf`. The
fully-qualified `MCPP_DEP_<NS>_<NAME>_BIN_<TOOL>` is always published; the
bare spelling is bound to `mcpplibs.<x>`, else `compat.<x>`, else an
unnamespaced `<x>`, else the single remaining candidate — and when it is
contested mcpp says so instead of picking silently.

##### Older mcpp reading a manifest that uses this

An unrecognized dependency key is reported as a **degradation** and ignored
(mcpp 2026.8.6.2+), so a package written for a newer mcpp still loads and the
parts this reader understands still apply. Before that release it was a hard
load failure with a misleading message, which is why a published package could
not adopt a new key at all — the same property the index floor establishes:
data must not decide whether the program works.

Consequently a package that *relies* on `reexport` for its ergonomics still
needs a client new enough to implement it; what changed is that everything else
about that package keeps working on an older one.

##### Scoping a provision per platform

A package may declare a `bin` target on some platforms only. Because the
*library* now decides what is requested, an unconditional request turns an
unsupported platform into an error its user cannot edit away. Scope it:

```toml
[target.'cfg(not(windows))'.feature-deps.codegen]
"compat.protobuf" = { version = "35.1", tools = ["protoc"], reexport = true }
```

`[target.<sel>.feature-deps.<feature>]` (2026.8.6.2+) follows the same rules as
the other conditional dependency tables (§2.7.1). The **feature itself is
registered on every platform** — only what it pulls in is conditional — so
requesting it where no predicate matches is not an unknown-feature error.

## Appendix A. Schema Ownership Principle (admission criteria for new fields)

> **Closed syntax, open vocabulary**: whoever owns the parsing semantics defines the keys; whoever owns the domain knowledge defines the values.
Expand Down
33 changes: 32 additions & 1 deletion docs/07-build-mcpp.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ int main() {
| `mcpp::source(p)` | `mcpp:source=` |
| `mcpp::include_dir(d)` / `mcpp::include_dir_after(d)` | `mcpp:include-dir=` / `mcpp:include-dir-after=` |
| `mcpp::rerun_if_changed(p)` / `mcpp::rerun_if_env_changed(v)` | the matching `rerun-*` directives |
| `mcpp::rerun_if_changed_glob(pat)` *(2026.8.6.2+)* | `mcpp:rerun-if-changed-glob=` — re-run when the **set** of files matching `pat` changes (see below) |
| `mcpp::dep_bin(pkg, tool)` *(2026.8.5.1+)* | reads `MCPP_DEP_<PKG>_BIN_<TOOL>` — the absolute path of a **host tool** built by a dependency (see below) |
| `mcpp::action{…}.submit()` *(2026.8.5.1+)* | `mcpp:action=` — declares a **build-graph node** instead of doing the work here (see below) |

Expand All @@ -123,7 +124,37 @@ mcpp builds that `kind = "bin"` target **for the build machine** (even under
`mcpp.toml` rather than here for the same reason a dependency does: asking the
graph for an extra artifact is a graph-level request, and the graph stays
statically analysable. See [05 §2.14](05-mcpp-toml.md) for the full contract,
including `[tools.overrides]`.
including `[tools.overrides]` and `reexport = true` (which is how a library
hands you the whole toolchain so you declare **one** dependency instead of
four).

### Globbing your inputs: `rerun_if_changed_glob` (2026.8.6.2+)

The re-run key is built from *declared* inputs. Declare files and it works;
glob a directory and it does not — adding a `.proto` changes no declared file's
hash, so the program never re-runs and the new file is silently never
generated. `rerun_if_changed_glob` is how a program says "my output depends on
which files are here":

```cpp
import mcpp;
int main() {
mcpp::rerun_if_changed_glob("proto/**/*.proto");
// … scan the directory, declare one action per file …
}
```

The pattern is relative to the manifest directory and uses the same `*` / `**`
grammar as `sources = [...]`. Its fingerprint is the **sorted set of matching
paths** and nothing else:

- **not contents** — a file whose bytes matter is an ordinary
`rerun_if_changed` input, which already hashes them;
- **not mtime or size** — mtime is unstable across `git checkout`, container
builds and `rsync`, and size is a weaker signal than the hash above.

The build output tree and `.git` are never part of the set, so a wide pattern
cannot make the program re-run forever against its own outputs.

### Declaring work instead of doing it: `mcpp::action` (2026.8.5.1+)

Expand Down
14 changes: 14 additions & 0 deletions docs/09-release.md
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,20 @@ would again let the index guard and the installed version drift apart.
> §3, seen from the other side. Nothing about a release removes older versions,
> and no reasoning should be built on the idea that it does.

> **The same misdiagnosis, again (2026-08-06).** `ci-aarch64-fresh-install`
> failed with `xlings: version '2026.8.5.3' not found for 'mcpp' — available:
> 2026.8.6.1`, and the fix commit again claimed the index had dropped the
> version. It had not: both `openxlings/xim-pkgindex` and `d2learn/xim-pkgindex`
> list 63 mcpp versions including `2026.8.5.3`. Note what `available:` actually
> enumerated — a single version, the one that job had just installed — which is
> the shape of an *installed-versions* view, not an index listing. That step
> resolves a `.xlings.json` **workspace** pin, and a workspace-scoped resolve is
> the documented scope trap.
>
> The bump itself was fine (§4 endorses it, and it did unblock the job). The
> lesson is narrower and keeps being relearned: **before concluding "the index
> dropped it", read the index.** One `curl` of `pkgs/m/mcpp.lua` settles it.

## 6. Checklist

```
Expand Down
Loading
Loading