@@ -19,8 +19,15 @@ tests/examples/<member>/ one test project per library (a workspace member; <
1919tests/check_mirror_urls.lua lint: GLOBAL+CN table completeness, and that CN points at mcpp-res
2020tests/check_package_name.lua lint: identity shape (name is a single atomic segment, hierarchy belongs to namespace)
2121tests/list_cn_urls.lua extracts the CN urls for mirror-cn-reachable
22+ tests/run_members.sh runs workspace members one at a time and times each. The entry point used both by CI
23+ and locally; see "Running workspace members locally" below
24+ tests/plan_shards.lua assigns members to shards from measured times. Called once by the `select` job, and by
25+ run_members.sh --shard, so both produce the same split
26+ tests/member-timings.tsv the measured per-member wall-clock plan_shards.lua reads. Refreshed deliberately from
27+ the member-timings artifact rather than written back on every run
2228README.md index overview and contribution entry point (README.zh-CN.md is the Chinese version)
23- .github/workflows/validate.yml CI: lint / mirror-cn-reachable / workspace (a 3-platform matrix)
29+ .github/workflows/validate.yml CI: lint / mirror-cn-reachable / select / workspace (sharded per platform on a full
30+ run) / timings
2431.agents/docs/<date>-*.md the design-document convention
2532docs/ contributor reference documentation (this directory; docs/zh/ holds the Chinese version)
2633tools/gtc the gitcode CLI, see cn-mirror.md
@@ -150,28 +157,65 @@ locally with `mcpp xpkg parse pkgs/<x>/<name>.lua`.
150157## CI behavior (validate.yml)
151158
152159- Triggers: a PR (touching ` pkgs/**/*.lua ` , ` tests/** ` , either README, ` mcpp.toml ` , ` index.toml ` or this workflow),
153- a push to main, the nightly cron, and manual dispatch.
160+ a push to main, the nightly cron, and manual dispatch. Dispatch takes a ` cache ` input — ` global ` , the default, or
161+ ` local ` . Under ` local ` every member rebuilds every dependency from scratch, which isolates a member's own cost from
162+ what it inherited from the members that ran before it; that is the condition per-member times should be compared
163+ under, and it is also far slower.
154164- ` env.MCPP_VERSION ` is the mcpp version every job uses; local verification should match it.
155165- ` lint ` (always runs): lua syntax via ` loadfile(f,'t') ` ; ` spec= ` /` name= ` /` xpm= ` must be present; leading-v versions
156166 are rejected; runs ` check_mirror_urls.lua ` ; runs ` check_package_name.lua ` (identity shape, see "Package identity"
157167 above); then runs ` mcpp xpkg parse ` over every descriptor with the mcpp version CI pins (strict — an unknown key
158168 fails). ` xpkg parse ` in mcpp >= 0.0.106 enforces the identity shape itself, which makes the lua lint an earlier and
159169 cheaper redundant gate.
160170- ` mirror-cn-reachable ` (always runs): ` curl ` s each CN url; all must return 200.
161- - ` workspace (linux|macos|windows) ` : the whole test surface is one mcpp workspace and the ** only build/run channel** —
162- there is no shell-driven exception (the public module packages imgui/ffmpeg/opencv/tinyhttps are ordinary members
163- too, resolving from the checkout through a member-level ` [indices] default = { path = "../../.." } ` ,
164- mcpp >= 0.0.97; members consuming ` compat ` inherit the root-level declaration, see "Index redirection" above).
171+ - ` select ` : decides the entire plan once and emits it to the runners as data. Three questions are answered here
172+ rather than on each runner — which members run, how many shards each platform gets, and which members land on
173+ which shard.
165174 - Selective member testing: on a PR, ` git diff ` maps changed files to the affected members
166- (` pkgs/<x>/<lib>.lua ` → members whose mcpp.toml references ` <lib> ` ; ` tests/examples/<m>/** ` → member ` <m> ` ), and
167- only those run through ` mcpp test -p <member> ` ; global changes — the workflow itself, the non-member part of the
168- workspace manifest, ` tools/ ` and so on — go to a full ` mcpp test --workspace ` . push/nightly/dispatch are always
169- full runs.
175+ (` pkgs/<x>/<lib>.lua ` → members whose mcpp.toml references ` <lib> ` ; ` tests/examples/<m>/** ` → member ` <m> ` ).
176+ A change that can affect everything selects the full workspace instead: a non-PR event, this workflow file, a
177+ non-member edit to the workspace manifest, or a shared test script. Documentation-only and ` tools/ ` -only changes
178+ select nothing.
179+ - Sharding applies to full runs only — a selective run is one job per platform. The shard count per platform is
180+ that platform's ** measured runner concurrency** (linux 3, macos 1, windows 2) rather than a round number.
181+ Wall-clock is ` ceil(shards / concurrency) × slowest-shard ` , so shards beyond the concurrency remove no work and
182+ each still pays its own checkout, mcpp download and cache restore. At concurrency 1, splitting macOS is strictly
183+ slower than not splitting it. Re-measure with:
184+ ` gh api repos/<owner>/<repo>/actions/runs/<id>/jobs --paginate --jq '[.jobs[]|select(.status=="in_progress")]|length' `
185+ - The assignment itself comes from ` tests/plan_shards.lua ` . It reads ` tests/member-timings.tsv ` and packs
186+ longest-first onto the least-loaded shard; ties break toward the shard already holding members with overlapping
187+ dependencies, because shards share no build cache and a dependency landing on two shards is built twice. A
188+ member with no recorded time is charged the median, so a newly added member is assumed neither free nor huge.
189+ Against round-robin on the real workspace the slowest linux shard falls from 4158s to 3706s and the spread from
190+ 47% to 15%. One floor no split can beat remains: the single slowest member, ` grpc-module ` at 1701s.
191+ - Planning runs here, on linux, because it needs lua: windows has no apt or brew, and Homebrew installs ` lua `
192+ rather than ` lua5.4 ` .
193+ - ` workspace (<platform> <shard>/<count>) ` : the whole test surface is one mcpp workspace and the ** only build/run
194+ channel** — there is no shell-driven exception (the public module packages imgui/ffmpeg/opencv/tinyhttps are
195+ ordinary members too, resolving from the checkout through a member-level ` [indices] default = { path = "../../.." } ` ,
196+ mcpp >= 0.0.97; members consuming ` compat ` inherit the root-level declaration, see "Index redirection" above). The
197+ shard suffix appears only where the platform is actually split.
198+ - Members run through ` tests/run_members.sh ` , the same script used locally. A timing table that exists only in CI
199+ cannot be consulted while deciding what to optimise, and a local harness that differs from CI measures something
200+ else.
201+ - The package build cache is global, which is mcpp's default. The step formerly set ` MCPP_BUILD_CACHE: local ` to
202+ work around mcpp #344 , in which one cache entry could hold two object layouts; mcpp 2026.8.3.4 keyed the cache per
203+ package with the consumer-dependent layout included, so the reason no longer holds. Keeping the bypass was
204+ expensive: under ` local ` , 59 members that largely share abseil, protobuf and opencv rebuilt each of them from
205+ scratch, and a full linux run reached 2h30m — past the timeout, so it produced no result at all.
170206 - The ` ~/.mcpp/registry ` cache carries the toolchains and the already-built compat packages, so a repeat run is
171207 incremental and fast. Its key is computed once, in a step of its own, from ` git ls-files -s ` over the tracked
172208 inputs — never with ` hashFiles() ` , which globs the working tree and would re-hash the multi-GB build output under
173209 ` tests/examples/*/target ` when actions/cache re-evaluates the key in its post (save) step (that blew past the
174210 runner's 120s template-evaluation cap on windows).
211+ - The published index is refreshed before testing. Most members resolve everything from the checkout, but a member
212+ redirecting a namespace other than ` compat ` takes the rest from the published index, whose snapshot is whatever
213+ the pinned mcpp release vendored — older than main by construction, and never moved by anything else in the run.
214+ - ` timings ` : merges the per-shard timing artifacts into one ranking per platform in the run summary, and publishes
215+ the combined table as the ` member-timings ` artifact. Sharding otherwise hides where the time goes, since each
216+ runner reports only its own slice. The table is not committed automatically: a number that rewrites itself on
217+ every run makes every diff noisy and silently absorbs a one-off slow runner. Refresh ` tests/member-timings.tsv `
218+ from that artifact when the numbers have actually moved.
175219
176220## Reproducing lint locally (equivalent to the CI lint job)
177221
@@ -187,6 +231,26 @@ done
187231[ $fail -eq 0 ] && echo " ALL LINT PASS"
188232```
189233
234+ ## Running workspace members locally
235+
236+ ` tests/run_members.sh ` is the entry point CI uses, so a local run measures the same thing under the same split:
237+
238+ ``` bash
239+ bash tests/run_members.sh --all # every member
240+ bash tests/run_members.sh opencv-module protobuf # named members
241+ bash tests/run_members.sh --all --shard 1/3 # exactly what CI's linux shard 1 runs
242+ bash tests/run_members.sh --all --shard 0/2 --platform windows
243+ bash tests/run_members.sh --all --cache local # bypass the package build cache
244+ ```
245+
246+ Shard indices are 0-based, and ` --shard ` delegates to ` tests/plan_shards.lua ` — the script the ` select ` job calls —
247+ so shard N locally holds the members shard N holds in CI. Without lua on ` PATH ` it falls back to round-robin and
248+ says so. ` --platform ` chooses which column of ` tests/member-timings.tsv ` to read and defaults to the host.
249+
250+ ` MCPP ` selects the binary (default: ` mcpp ` on ` PATH ` ); ` MCPP_TIMINGS ` names a file to append
251+ ` <seconds>\t<member>\t<ok|FAIL> ` rows to. The exit status is non-zero if any member failed, and the timing table
252+ prints either way — a run worth diagnosing is exactly the one where the times matter.
253+
190254## After the merge
191255
192256` publish-artifact.yml ` republishes the mcpp-index artifact and moves the pointer automatically once the change lands
0 commit comments