Skip to content

Tags: mcpp-community/mcpp

Tags

v2026.9.17.1

Toggle v2026.9.17.1's commit message

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
One reading of a compile-flag element, the databases list what the co…

…mpiler receives (#655); macOS 27 (#657)

* One reading of a compile-flag element; the databases list what the compiler receives (#655)

An element of `cflags`, `cxxflags` and `asmflags` now has one meaning on every
host: the POSIX shell's word syntax without expansions, where a backslash
escapes only a blank, a quote or a backslash (mcpp.manifest.flag_words,
SPEC-004 §8). The ninja writer quotes each word for its host, and
compile_commands.json and `mcpp emit build-database` list the words, so the
databases equal the build by construction instead of re-reading command lines.

Before, an element meant whatever the host's command-line reader made of it
(measured on Linux and Windows runners, #656: five of fourteen spellings gave
different macros), and the databases listed the text with a third reading,
which is how libarchive's `-DPLATFORM_CONFIG_H=\"...\"` reached consumers with
its backslashes.

- A `defines` entry is one value: `N="x"` reaches the compiler as `-DN="x"`
  (it arrived as `-DN=x`). Features, globs, targets and `mcpp:cfg=` alike.
- Link units keep their rendered text; GAS units' database entries list their
  edge's list; the `-std` guard, dialect promotion and include normalisation
  read words.
- Windows `shell_quote_arg` doubles backslashes before a quote and the closing
  quote.
- The first plan after an upgrade names an element whose words changed
  (`build/flag-words`); a repeated plan says nothing. No flag-list element of
  the published index changes.

Tests: FlagWords.* (syntax table and round trip), CompileCommandsArgs.*
(edge equals database, host quoting round trip, /bin/sh as the reference),
e2e 736 (same assertions on every host; fails on 2026.9.16.2).
Records: .agents/docs/2026-09-17-655-*.
Version 2026.9.17.1.

* 655: the ecosystem verification script

* CI on macOS 27: ci-macos, ci-macos-e2e and both fresh-install channels

GitHub serves macOS 27 under the preview label xcode-27 (macOS 27.0 with Xcode
27, actions/runner-images#14404); there is no macos-27 label. Measured on #658:
the label delivers macOS 27.0 (26A5406e), arm64, ImageOS macos27.

- ci-macos and ci-macos-e2e run on macos-15 and xcode-27.
- ci-fresh-install runs the xlings and Homebrew channels on macos-14 (the
  floor) and xcode-27 (the newest release).
- Every macOS 27 leg asserts sw_vers major 27, because the label names an
  Xcode and its base OS has changed once.
- setup-macos-llvm takes the image as an input and puts it in the xlings cache
  key, which named macos15 for every image.

* macOS 27: the std module builds; the -D compatibility rule; host-shaped test fixes

macOS 27 (measured on the xcode-27 image, #659): the 27.0 SDK's math.h leaves
INFINITY and NAN to <float.h> once modules are on, and clang 22's header does
not supply them under -std=c++23, so libc++'s std module stopped building.
apple_float_macro_words (hostflags.cppm) states both with the SDK's own GNU-mode
spellings for clang on an Apple target; its readers quote the words their own
way (ninja text, the std module command, graph std flags, build program argv).
gnu++23, -U__STRICT_ANSI__ and -D__need_infinity_nan were rejected for their
effect beyond the defect. The raw clang steps of ci-macos state the same words.

Compatibility: an element beginning with -D or /D that contains a space is one
word, verbatim, as every release since #234 passed it (SPEC-004 §8 rule 8).

Windows: flag_element no longer quotes a backslash the syntax does not read as
an escape, normalize_include_flags keeps the spelling of an unchanged element,
and the split_flags quoting tests follow the host's reader.

* CI macOS 27: wrap a bootstrap mcpp that predates the float macro fix

The raw clang steps now build import std on macOS 27 (run 35142520288). The
self-host step failed because the bootstrap, 2026.9.16.1, predates the fix.
setup-macos-llvm wraps a bootstrap older than 2026.9.17.1 on macOS 27 or later
and passes the same two words through CCC_OVERRIDE_OPTIONS; the freshly built
binary runs unwrapped, and the wrapper retires with the bootstrap pin.

* Review: the diagnostic block sits above the defines comment; correct the 252 claim; index reading with the -D exception

- report_flag_words_changes was inserted between fold_build_defines_into_flags
  and its comment; it now precedes the comment.
- The record claimed e2e 252 keeps its real old-client leg on macOS through the
  wrapper. It does not, on any macOS leg including main: MCPP_BOOT is the xlings
  shim. Corrected and listed as a residual, with dialect promotion reading words.
- The index reading script applies the -D exception: 285 literals, 0 differ.

---------

Co-authored-by: speak-agent <248744407+speak-agent@users.noreply.github.com>

v2026.9.16.2

Toggle v2026.9.16.2's commit message

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
chore(xlings): pin 2026.9.16.1; version 2026.9.16.2 (#654)

kXlingsVersion and every pin check_version_pins.sh names move to xlings
2026.9.16.1 (openxlings/xlings#601). The region object mcpp writes for an
index artifact is now a preference chain (CN, then GLOBAL, then git) rather
than a single base, and one index refresh is bounded. The constant's comment
records it as the fourth floor. No mcpp code changes: list_repos gains an
additive artifact_bases field that mcpp does not read.

mcpp.toml and MCPP_VERSION move to 2026.9.16.2; the bootstrap pin stays at
2026.9.16.1.

Co-authored-by: speak-agent <248744407+speak-agent@users.noreply.github.com>

v2026.9.16.1

Toggle v2026.9.16.1's commit message

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
Two spellings of one version constraint are one source (#647 E4.2 fol…

…low-up) (#651)

Self-review of #650 before releasing 2026.9.16.1 found that the gate E4.2 added
refuses a manifest the previous release builds.

`[feature-deps.<f>]` must restate its dependency's source, and a restatement
naming another source is refused rather than silently dropped. The comparison
was made on the bytes of the declaration. A path is normalised first, so a path
dependency is judged on what it means; a version constraint was not, so two
spellings of one constraint were read as two sources.

Measured, with the fixture in the leg below:

  [dependencies]
  fmtlib.fmt = ">=11.0.0"
  [feature-deps.extra]
  fmtlib.fmt = { version = ">= 11.0.0", reexport = true }

  2026.9.15.2 (released):  builds, no error and no warning
  2026.9.16.1 (f4529b2):  error: ... restates the dependency 'fmtlib.fmt' with
                           version = ">= 11.0.0", while the declaration in
                           effect on this row names version = ">=11.0.0".

One space turns a build that worked yesterday into a refusal. The judgement is
now made on what the declarations mean: the constraint is compared with its
whitespace removed, while the message still shows each declaration as it was
written, so a genuine disagreement reads as the manifest spells it. A gate
refuses a restatement that names another source, and nothing else.

The same review asked why the first custom-index sync does not go through
`decide_for_miss`/`apply` when the record says the refresh sites do. It should
not: that pair's debounce and one-sync-per-process guard are about the index
that resolves a dependency, while this sync creates a local copy of a different
set of repositories that nothing else will create, so taking the guard would let
an earlier refresh of the builtin index suppress a clone the build cannot
proceed without. Only the opt-outs are shared. The comment said "the policy's
answer is taken" and now states which half is taken, and why the other is not.

Criterion: e2e 711 gains a leg. A restatement differing from the declaration
only in whitespace is not refused, and one naming `>=9.9.8` against `>=9.9.9`
still is. On f4529b2 the first half fails with the refusal quoted above.

Readings: unit 121 passed, 0 failed; e2e 187, 710 to 714 pass.

Co-authored-by: speak-agent <248744407+speak-agent@users.noreply.github.com>

v2026.9.15.2

Toggle v2026.9.15.2's commit message

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
Link forms, standard levels and path lengths: #641 and #642 (2026.9.1…

…5.2) (#644)

* A C++-layer provider compiles its implementation units at the standard it states (#641 item 2)

A package that provides the C++ layer and states [package] standard now
compiles each C++ unit that neither provides nor imports a module at exactly
that level; module units, the std module included, stay at the graph's level.
The level is appended to the unit's own flag vector in make_plan, so the
compile edge, the scan edge, compile_commands.json and the build database read
one value. The provider predicate is factored into the manifest module and used
by the std module adoption. The dependency cache key and the fingerprint
metadata carry the stated level only when it exists; the higher-standard
warning no longer reports a provider whose level is applied.

Tests: unit CxxLayerStandard.* and CacheKey.ACxxLayerProvidersStatedStandardChangesTheKey;
e2e 696 (requires llvm). Docs: 07-workspace 4.2 and 22-target-side with mirrors.

* docs(design): #641 and #642 triage record, probes, and implementation plan

* mcpp pack --features reaches every build pass of the pack, and run --format passes its features to the pack (#641 item 4)

* A required compiler family takes the pins of its own payload (#641 item 1); a dependency's C++ shared library over a graph C++ runtime is refused or carries a stated private copy (#641 item 5)

* The refusal offers the static edge only where a request or a default made the library shared; e2e 690 reads four legs

* docs: M1-M3 in 20-toolchains, 10-pack-and-release and 50-machine-output with mirrors; CI runs the graph C++ runtime e2e scripts on the hermetic llvm job

* Bounded tool-build paths: owner-relative object addresses, a short sub-build scratch, extended-length opens on Windows (#641 item 3; M7, M8, M9)

M7. A source outside the package that declares it (a build program's
mcpp::source under a dependency's root) is addressed as
obj/<declaring>/__pkg/<owning package>/<path inside it>, or
obj/<declaring>/__ext/<8-hex hash of its directory>/ when no package contains
it. The mirror of the declaring-package-relative path, with one __up per
level, is gone for these sources; sources inside their declaring package,
root-owned flat addresses, the #233 census and backstop, and the cache address
(packageRel) are unchanged. In the root census branch the escaping form
applies only where the mirrored prefix was used (a basename collision), so the
flat obj/<name> of a generated OUT_DIR source (#239) is unchanged.

M8. The host-tool sub-build scratch moves from <entry>/build-<hash> to
<cache>/tool/.build/<hash of entry and consuming work root>
(tool_store::scratch_dir), keeping per-consumer isolation, re-run reuse and
removal after publish; the entry layout is unchanged.

M9. mcpp::platform::fs::extended_length (identity outside Windows; absolute,
normal, backslash, \\?\ or \\?\UNC\ form on Windows, idempotent) with the pure
spelling rule windows_extended_length_spelling. Call sites, enumerated from the
$mcpp rules of ninja_backend.cppm and the __action-stamp wrapper:
  src/cli/cmd_build.cppm  cmd_dyndep: --output; the --expect-* ddi read;
                          emit_dyndep_single input; emit_dyndep_from_files inputs
  src/cli/cmd_build.cppm  cmd_stage: source and --output (stage_file)
  src/cli/cmd_build.cppm  cmd_bmi_equal: both paths (bmi_equivalent)
  src/cli/cmd_build.cppm  cmd_coff_def: each object read; the .def write
  src/cli/cmd_build.cppm  cmd_bmi_compile: --bmi --slot --sem --command-file
                          --dep-from --dep-to (not --self, an executable)
  src/cli/cmd_build.cppm  cmd_bmi_supervise: --slot --token --command-file
  src/cli/cmd_build.cppm  cmd_bmi_await: --slot --object
  src/cli.cppm            __action-stamp: each stamp
Downstream opens receive the converted paths. Two follow-ons: the detached
compile's log opens with CreateFileW (CreateFileA does not honour the prefix
past MAX_PATH), and CompileRequest::bmiTarget keeps ninja's spelling of the
BMI for the copied depfile's target, which must not change.

Tests: unit ObjectAddress.ASourceUnderAnotherPackageIsAddressedByThatPackage,
ObjectAddress.AnUnownedEscapingSourceIsAddressedByAHashOfItsDirectory (both
fail on the previous object_for), ToolStoreScratch.IsShortPerConsumerAndStable,
PlatformFs.ExtendedLength* (four); e2e 698 (leg A fails on 2026.9.15.1 with
obj/installer/__up/__up/dep/platform/impl/helper.o). Docs: 30-build-mcpp and
its Chinese mirror.

* feat: a library states its default link form, and the root build program reads each dependency's form (#642 E1, E2)

- [targets.<n>] linkage and its row form state a library's default form; kind =
  "shared" stays the constraint. Precedence: the root edge, a written
  dependency_linkage, the package default, static. An explicit override is
  honoured with an information line and no degraded record.
- The link-form resolution is computed once before the root build program
  (hasSources from the scanner's own package_source_files) and applied after
  the scan as before.
- The root build program receives MCPP_DEP_<NAME>_LINKAGE under the names of
  MCPP_DEP_<NAME>_DIR; mcpp::dep_linkage() reads it. Dependency programs do not.
- Form B descriptors read linkage the same way.
- Unit tests (linkage_form, manifest, xpkg), e2e 692 and 693, docs 04, 22, 30
  with their Chinese mirrors, SPEC-004 1.4.

* The M3 refusal reads the resolution's declaredShared fact for the package that constrains its form

* Version 2026.9.15.2; CHANGELOG; the plan's ledger and refinements

* dep_dir and dep_linkage answer under the qualified namespace.name a consumer writes; e2e 693 leg F

* e2e 696 and 698 write fixture paths through host_path

* docs: a shared library over a graph C++ runtime on Mach-O exports what its sources mark with default visibility

* docs(design): refinements measured on macos-15 and for qualified dependency names

* e2e 698: the consumer's tool command survives cmd.exe's quote stripping on Windows

* e2e 698 sources the host path helper before it changes directory

---------

Co-authored-by: speak-agent <248744407+speak-agent@users.noreply.github.com>
Co-authored-by: speak-agent <x.d2learn.org@gmail.com>

v2026.9.15.1

Toggle v2026.9.15.1's commit message

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
mcpp emit build-database, program probes as argument vectors, and xli…

…ngs 2026.9.14.1 (2026.9.15.1, #636) (#639)

* ci: workspace mcpp bootstrap pin -> 2026.9.14.3 (released + indexed); #634 ledger closed

Version group 2 moves to 2026.9.14.3, released from 9b6a118 and named
`latest` by the published xim index artifact `xim-index-ed04041`.

The #634 implementation plan and the triage record are marked landed. The
ledger closes every row with a reading (E1-E24 in mcpp#637, E25 in mcpp#638,
P1-P9 in mcpp-plugins#24, I1 in mcpplibs/mcpp-index#426, R1-R4, V1-V4), and
§9 records what landed, the sandbox readings on 2026.9.14.2, 2026.9.14.3 and
the 2026.9.14.1 control, the ecosystem review, and two observations this work
did not change.

* docs(design): the build database of #636 and the two defects on the way to the latest xlings

The record of the analysis, the plan and its self-review: xlings stages a
hookless package from the shared download directory; a POSIX redirect in the
vendored-xlings probe reaches cmd.exe on Windows; and mcpp emits an S1 build
database from the plan without writing into the project.

* fix(platform,xlings): a program probe is an argument vector, not a /bin/sh string

The vendored-xlings version probe ran `<xlings> --version 2>/dev/null` as a
command string. On Windows every command string reaches cmd.exe, which cannot
open /dev/null: it printed "The system cannot find the path specified." in
every command after the first, did not run xlings, and returned an empty
version, so a Windows home never replaced a vendored xlings older than the pin.

mcpp.platform.process gains capture_stdout: the program runs directly, its
standard output is captured, its standard error is discarded and its standard
input is empty; on Windows the redirect names cmd.exe's own null device. The
xlings probe and the four other program probes that carried POSIX grammar on
a path Windows reaches use it: the clean link specs, the libstdc++ fallback
probe, the freestanding size report and the publish digest.

Tests: capture_stdout unit tests (stdout only, exit code, empty stdin,
environment, empty output for a missing program, the Windows command line);
a version-probe unit test that runs a .bat through the real launcher on
Windows; e2e 687 (no path error on the second command; an older vendored
binary is replaced).

* feat(emit): mcpp emit build-database, the plan as a build database written into nothing (#636)

`mcpp emit build-database` plans as `mcpp build --configure-only` plans,
with the same selectors, and prints the plan as an S1 build database (C++
Build Database: IDE Profile 0.2.0, a profile of WG21 P2977R2) without writing
into the project. `--spec compile-commands` prints compile_commands.json
entries instead; `--format json` wraps the document in the
mcpp.build-database envelope; `-o <file>` writes it atomically.
docs/specs/build-database.md (SPEC-005) states the rules.

The document is S1 level 2: one set per package plus <package>:test and
mcpp:std; visible-sets lists every other set, because the engine resolves
imports over one module graph; each unit's arguments come from the record the
compile database renders; ide.role is the declaration form the scanner read.

Five engine changes make the no-write planning exact:
- BuildOverrides::work_dir had two writes that ignored it (the multi-version
  mangling stage, and the root package's generated_files, which are now
  compared rather than written under plan_only);
- the std module is described without being compiled (describe_std_module,
  the derivation ensure_built builds from);
- the std units' commands are recovered from the commands mcpp runs, so the
  std cache identity does not change;
- compile_commands and the build database render one UnitInvocation record;
- the scanner records the module declaration form (ModuleDeclaration).
mcpp.lock is read from the project and never written back; a difference is
the warning MCPP_LOCK_WOULD_CHANGE. Planning narration goes to stderr through
a descriptor-level redirect. A failure envelope omits data.

Tests: BuildDatabase.* unit tests drive the real gcc and clang std command
builders; Scanner.DeclarationFormIsRecordedAsRead;
CompileCommandsEmit.UnitInvocationsAreTheCompileDatabaseArguments;
WireGolden.NullDataIsOmitted; e2e 688 validates the output against the
vendored S1 schema and uses --configure-only as the control leg for the
no-write criterion.

* fix(emit): a store package's build program inputs are not watched

A store package's build program may declare inputs, and they were listed in
`watch`; its files are fixed by the version its manifest and lock name, so
only an editable package's declared inputs are inputs that can change.
Found by running the command over lsp-mcpp, whose openkal-musl dependency
declares its own mcpp.toml.

* test(emit): the MSVC std units are recovered from their builder

Drives msvc::std_module_build_commands and std_compat_build_commands, the
command shape a Windows host with msvc@system runs (cd /d, a quoted cl.exe,
/ifcOutput, /Fo:, 2>&1), through recover_invocation.

* fix(emit): the selector digest separates values with newlines

A \x1f separator followed by `f`, `c` or `a` (`\x1ffeatures`, `\x1fcap`,
`\x1faccel`) is one longer hex escape; clang refuses it as out of range and
every clang host failed to build the branch. GCC accepted it, which is why
the Linux build did not see it. The sandbox verification script for the
release is added alongside.

* fix(scan): an entry source outside the globs is read by the scanner

A target's entry source that no `sources` glob matched (a discovered test,
a `main` outside the globs) had its imports read from line-leading `import`
alone, in make_plan and again in the standard library check before planning.
An import inside a comment or a raw string was therefore planned as one.
Validating the lsp-mcpp repository's own build database against S1 found
it: its scanner test was planned as importing three modules no source
provides.

scan_entry_file reads such a file with scan_file. The scanner's refusals (an
import inside `#if`, a header unit, an extension without a role) were never
applied on this path and are not applied now: a file it refuses yields the
line-leading imports of its code, comments and raw strings removed, with the
declaration form Unknown. Both readers now call it, so they cannot disagree
about `import std`.

Refs #636

* feat(emit): baseline and local arguments, private, and config-files

The S1 SHOULD fields the lsp-mcpp validator reads, with values the plan
determines:

- `baseline-arguments` and `local-arguments`. A unit's arguments are its
  driver, the set's baseline, its local arguments and its own trailing
  `-c <source> -o <object>`, whose operands name the unit's source and object
  from its work directory. The baseline is the longest prefix every unit of
  the set shares; a prefix keeps argument order, which decides include search
  and macro definitions.
- `private` is false: every module is visible to every set.
- `config-files` lists what the driver reads without being named: the clang
  `<driver>.cfg` of resolve_clang_driver unless the units pass
  `--no-default-config`, and GCC's `lib/gcc/<triple>/<version>/specs`
  (or the major version) beside the driver.

e2e 688 gains J (the decomposition, private, config-files) and K (the
discovered test's imports inside a comment and a raw string are not
`requires`); its F mapping compares paths with one separator, and the
fixture's main.cpp is well-formed. SPEC-005 R3.2a, R3.6, R3.8 and R3.9a state
the rules.

Refs #636

* test(e2e): 687 plans in a home with its own configuration

The macOS runner's inherited configuration names its ~/.xlings shim as the
xlings binary, so nothing was vendored and the precondition failed before
either criterion ran. The home now uses its own configuration, whose binary
is the vendored one.

Refs #636

* chore: bump version to 2026.9.15.1

mcpp.toml and MCPP_VERSION move together; the bootstrap pin stays at the
released 2026.9.14.3. The changelog names the release on the three entries
that land in it, and records the S1 fields and the entry-source scan.

Refs #636

* chore(xlings): pin 2026.9.14.1

kXlingsVersion and every pin check_version_pins.sh names move to the xlings
release in which a package without an install hook receives its own archive
(openxlings/xlings#596). The constant's comment records it as the third
reason for the floor.

Refs #636

* docs(design): the execution record of #636

What landed in each repository, the five findings made while implementing,
and the measurements taken so far, including lsp-mcpp's validator and its
conformance runner against this mcpp.

Refs #636

* docs(verify): a doctor control, the lock file named, and a dependency project

Section D reads a clean store first, then requires the finding's label and
the seeded lock file's name, and after --fix requires the reinstalled
payload: no seeded file and */mcpp.toml present. Section C2 emits the build
database of section B's project: the dependency's set, no watched store
path, the S1 SHOULD fields, and an unchanged tree with target/ present.
Sections that need python3 say so when it is missing.

Refs #636

* docs(specs): SPEC-005's index row carries its last modification date

Refs #636

* fix(emit): a plan that builds nothing writes no link specs

prepare_build ran `g++ -dumpspecs` to write mcpp-clean-link.specs under
plan_only. The file is read only by the link line, so the planning pass
neither needs it nor should run the driver for it, and the compile arguments
are the same without it (e2e 688 F still compares them with configure-only's).
Found by an independent review of #639.

e2e 688 criterion L measures what SPEC-005 R2.2 states: the fresh work
directory holds the resolution record and no object, BMI or link specs, and
a home whose build cache is empty stays without objects or BMIs while its
document lists the std unit. Its control leg runs configure-only on a copy
in the same home and sees the std module compiled. R2.2 now says what is
true: nothing is compiled, and the toolchain is still queried as for
`mcpp build`.

Refs #636

---------

Co-authored-by: speak-agent <248744407+speak-agent@users.noreply.github.com>

v2026.9.14.3

Toggle v2026.9.14.3's commit message
mcpp 2026.9.14.3

v2026.9.14.2

Toggle v2026.9.14.2's commit message
mcpp 2026.9.14.2

v2026.9.14.1

Toggle v2026.9.14.1's commit message

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
The package std module takes the prebuilt C library's tokens, and an …

…unset iOS floor is the located SDK's version (2026.9.14.1) (#633)

Co-authored-by: speak-agent <x.d2learn.org@gmail.com>

v2026.9.13.2

Toggle v2026.9.13.2's commit message

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
What a framework and its ecosystem library still hit in the engine: the 

#630 batch (#631)

* The C++ layer answers for its own headers, and the iOS rows take libc++ and the builtins from the graph (#630, item 4)

* manifest: min_api_level is a known [target.<triple>] scalar key (#610, #630 item 5)

The unknown-key sweep's kKnownTargetScalars list drifted from the parser: it
read min_api_level (toml.cppm) but never listed it, so a correctly spelled
key was reported as unsupported and --strict turned that report into a build
failure. Adds the key to the list and to the message, extends 641 with the
manifest-only regression (a manifest that declares min_api_level on one row
must not trip --strict when building a different row), and adds a unit test
whose denominator is read from toml.cppm's own body.find(...) parse sites
rather than a second hand-written list, in both directions, so a sixth key
added without a matching list entry fails the test instead of shipping.

* pm: an OS-only selector is a platform for `mcpp emit xpkg` (#630 item 7)

A [target.<selector>] tool declaration whose predicate names only an
operating system (cfg(linux), cfg(os = "linux"), the windows/macos/unix
equivalents, and cfg(unix) naming both linux and macosx) answers the exact
question a descriptor's per-platform block already asks, so emit_xpkg now
folds it into that block instead of only raising the publish/target-axis-tools
advisory. Any predicate mentioning an architecture, an environment, a layer,
or wrapped in any combinator (any/all/not, even one built entirely from OS
terms) keeps the warning, whose text now says so.

cfgpred::os_only_platforms is built on the existing cfg() Parser (two more
optional taps: seenKV and combinator) rather than a second reading of the
predicate text, per the "one grammar, two readers" rule this repository
already follows elsewhere. Unit tests cover both directions for the
classifier and for emit_xpkg's rendered output.

* e2e 663: the baseline is read from the link line's archives, not from a report line that is absent by design

* One identity, two declarations, said aloud (T3, #630)

A second `git`/`path` declaration of an already-resolved dependency
identity carried no comparable reference: `ResolvedRecord` held no path
and no `gitRev`, so a `git`/`git` or `path`/`path` disagreement fell
through silently and the winner was whichever request the FIFO worklist
happened to dequeue first. A `path`/`git` KIND clash refused outright even
when the root itself was a party.

`ResolvedRecord` gains `sourceRef` (the declared git ref, the canonical
path, or the SemVer constraint) and `fromRoot`, so the resolve hit can
compare a second declaration's REFERENCE rather than only its kind, and
can tell whether the root is one of the two requesters. The root's
declaration now wins a kind clash or a same-kind reference conflict it is
a party to, reported through `mcpp::diag::warning`
("dependency/source-override") naming both requesters and both
references; a losing `version` requirement is checked against the
winning checkout's own `[package] version` and refused if violated, in
the same Holds/Violated shape `addrset::unify` already uses for a tool
pin. A kind clash between two non-root requesters keeps the existing
refusal, with one added hint sentence. The "root arrives after a
dependency" ordering is provably unreachable under FIFO worklist seeding
(root deps are seeded before the loop starts); rather than depend on that
silently, it is refused by name so the invariant cannot degrade into
today's accident of queue order.

docs/05 gains the decision table under a heading that finally answers
the "what happens when two of them disagree" its own preamble promises,
mirrored in docs/zh/05.

tests/e2e/661 builds a local git repository (three commits, then an
uncommitted edit) with no network, and covers all six rows of the
decision table plus the negative direction (the same reference declared
twice must stay silent, and `--strict` must stay green).

* The tool store's key holds the source: a git tool by its commit, a path tool by a stamp of its tree (#630, item 6)

* docs: the iOS rows take libc++ and the builtins from the graph; the #630 design record

* An Apple cross target without a graph libc++: the SDK's headers when std is not imported, and a reported degradation when it is

* pack: stage what is declared before walking what is discovered (#630 §3)

`pack::run` used to refuse a Mach-O program, and a non-PE artifact on a
Windows host, before any staging ran at all -- so a dispatched format that
names one program and never reads the dependency closure (an `.app`/`.msi`
bundler) had no tree to work from either, on a host where the built-in
closure walk cannot run.

The tree is now staged unconditionally -- the program, then the runtime
files `mcpp::deploy`/`[runtime] deploy` declared -- and the closure walk is
one step within it whose outcome is recorded rather than a precondition for
reaching that step. `pack::run` returns a `ClosureResult` (`walked` plus a
`reason` when not) instead of `void`; `closure_unavailable_outcome(Format)`
is the pure decision of what an unavailable closure means per format:
`--format tar`/`--format dir` still fail the command (the archive IS the
closure, unchanged), a dispatched format receives the tree regardless. The
stage manifest gains a `closure = walked | not-walked` header line, with
`reason = <why>` when not walked, so a provider can read it instead of
inferring a gap from an empty `lib/`.

The ELF product of `--format dir` is unaffected: the reorder only moves
where the Mach-O/Windows-host refusals are decided, not what the ELF path
does once it is reached.

Tests: unit coverage for `closure_unavailable_outcome` and for the stage
manifest writer/reader with both `ClosureStatus` values (the "not-walked"
case cannot be produced on Linux through `pack::run` itself -- it needs a
Mach-O program or a Windows host packing a non-PE artifact, covered
end-to-end by 266_pack_refuses_a_macho_program.sh on macOS CI); a new e2e,
662_pack_stages_declared_files_before_the_closure.sh, asserting the staged
tree's exact structure for `--format dir` and for a dispatched format's
action reading `${mcpp.stage_dir}`.

Docs: docs/30-build-mcpp.md and the zh mirror gain the `closure` manifest
field in the `${mcpp.stage_dir}` section.

* pack.binfmt: a Mach-O reader for needed_names (#630 §4)

`needed_names` returned "not implemented" for Mach-O; `elf_needed` and
`pe_needed` already read a dependency list without executing anything.
Mach-O gets the same treatment:

- `detail::macho_thin_needed` walks a thin object's load commands:
  `LC_LOAD_DYLIB`, `LC_LOAD_WEAK_DYLIB`, `LC_REEXPORT_DYLIB` and
  `LC_LOAD_UPWARD_DYLIB` contribute names, `LC_RPATH` contributes search
  entries, in load-command order. Both widths (`mach_header`/
  `mach_header_64`) and both endiannesses (the CIGAM magics) are handled.
- `detail::macho_needed` / the exported `macho_needed(path, arch)` add the
  FAT (universal) case: a slice is selected by `cputype` against the
  caller's `arch` (mcpp's canonical spelling), defaulting to the first
  slice when none is given -- which is what `needed_names` does, since it
  has no triple to pass. `FAT_MAGIC` and `FAT_MAGIC_64` are both handled;
  FAT headers are always big-endian on disk regardless of a slice's own
  endianness.
- `needed_names` completes for Mach-O by delegating to `macho_needed`, and
  `is_system_lib` gains a Mach-O row: `/usr/lib/` and `/System/Library/`
  are the operating system's, case-sensitively (`@rpath/...` is a search,
  not a root, so it is never system).
- `resolve_macho_names` is a pure resolver: `@executable_path`,
  `@loader_path` and `@rpath/<x>` (tried against every rpath entry, in
  order, with the same two substitutions) resolve to the first path that
  exists; a name that resolves nowhere is reported as `unresolved`, not
  dropped.

NOT wired into `pack::run`'s Mach-O closure step. Bundling a resolved
dylib beside the program and rewriting its `LC_RPATH` needs a load-command
editor -- a load command has no free space to grow a longer path into --
and the record (§4.2) designs that editor after resolution is measured on
a real macOS build, not before. `pack::run`'s Mach-O branch still reports
"not-walked" (see the sibling #630 §3 commit); this reader and resolver
are exposed for that measurement and for whoever wires them in next.

Tests: unit fixtures built in-process (a tiny Mach-O/fat-Mach-O byte
writer, no external tools, same shape the ELF/PE fixtures already use) --
a thin arm64 object with two `LC_LOAD_DYLIB`, one `LC_LOAD_WEAK_DYLIB` and
two `LC_RPATH`; a fat x86_64+arm64 object whose slices carry different
names, asserting the wrong slice is never read; a big-endian-magic thin
object; the wrong-arch request on a thin file (ignored, as documented);
`is_system_lib`'s Mach-O row; and the resolver's rpath-order and
unresolved-reporting behavior.

Docs: docs/10-pack-and-release.md and the zh mirror note that the closure
is read now, and narrow the "planned" gap to bundling + the `LC_RPATH`
rewrite.

* docs: the no-package iOS path is reported, not refused

* e2e 666: on macOS a Mach-O program is staged without its closure and handed to a dispatched format

* record: what landed for the Mach-O reader

* The package std module on an Apple cross target keeps the SDK on its command

* record: progress of the batch

* T9 (#630 A9): the universal APK is the library route applied to an app

The route mcpp pack takes is a function of the artifact's FORM, not of the
target's kind. A `kind = "app"` target whose resolved link form is a shared
object on every requested row (every Android row) now accepts more than one
`--target`, the way a library target already does: the other legs are built
first (`build_extra_android_legs`, the same leg-loop shape
`build_and_pack_library` uses) and staged as `lib/<abi>/lib<name>.so` beside
the primary leg's own `lib/<abi>/`, into one tree, behind one dispatch pass.
A `bin` target, or any row whose form is an executable, keeps today's
refusal for a second `--target`.

`toolchain::triple::android_abi` derives the ABI name from the triple's
architecture (aarch64 -> arm64-v8a, x86_64 -> x86_64, per the 2026-09-12
design record). `PackRoute::isApplication` and the pure predicate
`accepts_several_targets` let `cmd_pack` decide the route before building
anything. `Plan::extraSharedLegs`, empty for every pre-existing caller,
keeps a single-`--target` pack's staged tree byte-identical.

Tests: unit coverage for `android_abi` and `accepts_several_targets`
(8 cases), and e2e 664 exercising the staged tree for one and two
`--target` flags and both refusal directions on the sandbox's android-ndk
capability. docs/10 and its zh mirror gain the shape and layout.

* The tool store's rule in docs/30, the examples/12 README and the CI probe: the key holds the source

* 2026.9.13.2: the #630 batch

* e2e 304, 661, 666 on macOS; the builtins package is 22.1.8.4

* ci-macos-ios: the report names 22.1.8.4

* record: what landed for #630 and where each claim was measured

* ci-macos-ios: a degradation is read by its sentence, not its domain

* the builtins package is 22.1.8.5

---------

Co-authored-by: speak-agent <x.d2learn.org@gmail.com>

v2026.9.13.1

Toggle v2026.9.13.1's commit message

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
The action lists have no size limit, and ${mcpp.self} names the engin…

…e (2026.9.13.1) (#629)

* build.mcpp: an action's lists have no declared size limit

The bundled mcpp module held an action's six list fields in fixed arrays
(8192 bytes of serialised JSON for inputs and outputs, 16384 for command)
and refused a declaration that did not fit. The bound was in bytes, so a
consumer's checkout depth decided whether a list of 44 resource files was
accepted (HuxerUI#130 measured the margin at 45 bytes), and outputs is the
one list an author cannot shorten: an output the program does not name
cannot be built, and there is no depfile for outputs.

The arrays were fixed because one constraint was read as two. The module
must not import std, and its exported interface must name no std type;
neither forbids the heap, and <cstdlib> was already in the global module
fragment. The six arrays become one growable std-free buffer over realloc.
The exported surface and the protocol version are unchanged, and the
payload of every action that fit before is byte-identical, so the cache key
is untouched. The overflow marker keeps its wire form and now means
allocation failure; the engine's message says so.

e2e 659 declares 200 inputs and 200 outputs and a 19200-byte command and
reads the action's edge out of build.ninja; under 2026.9.12.4 the same
fixture is refused.

* build.mcpp: ${mcpp.self} names the engine, and mcpp stage is the portable copy

An action's command is an argv with no shell, so a build program had no
portable way to copy a file: cp is absent on Windows, cmd /c copy is a
shell and an 8191-character limit, and a copier carried by a package is a
host-tool sub-build for one copy. The engine is the one program present
wherever a build runs, and mcpp stage --verify content --output <dst> <src>
is the copy every stage_file edge already performs. ${mcpp.self} joins the
argv substitution family and is replaced by the engine's absolute path, as
the check wrapper already bakes it in.

mcpp stage's argument shape is a contract from here on; its help text now
states the real default (content). e2e 660 copies the linked program
through the token on every shard; under 2026.9.12.4 the token stays
literal and the edge fails.

* docs: the action lists' limit, the ${mcpp.self} row, and the design record for the four upstream asks

* chore: bump version to 2026.9.13.1

* ninja: the command-length guard measures a literal command, not its edge line

check_inline_command_lengths read each build line as a proxy for the
command. That is right for a rule that expands $in and $out and wrong for
an action rule, whose command is a literal argv and whose inputs and
outputs exist only so that ninja can order and re-run it. e2e 659's first
run on the Windows shard refused an action with 200 outputs and 200 inputs
with the whole edge line counted as argv.

The guard now measures a literal command's own text. The refusal names the
edge by its first output and the count of the rest, instead of printing
every output. e2e 659 declares 600 inputs and 600 outputs, so the edge
line crosses the POSIX 128 KiB limit as well as Windows's 32767 and the
guard is measured on every shard; its negative leg declares a 140800-byte
literal command and expects the refusal to name the edge.

* ci(openkal-cross): say what the Windows host provides for the mingw target

The Windows job of this workflow was green with one sandbox lineage and
red with the next on the same sources and the same runner image (PR #629:
lld could not find -lntdll and its neighbours). On a Windows host
openkal-windows's build program generates no import libraries, so where
lld finds them is a property of the host, the sandbox or the payload, and
nothing in the job said which. This step prints it.

---------

Co-authored-by: speak-agent <248744407+speak-agent@users.noreply.github.com>