Skip to content

Commit 53a199a

Browse files
committed
feat(cli): --toolchain SPEC — pick the compiler for one build (L1, measured 2.51x)
在 mcpp 自身上实测:`gcc@16.1.0` **81.83s**,`--toolchain llvm@22.1.8` **32.61s** —— **2.51×,且已在 50s 目标以内**。 **为什么是「按次选择」而不是「换默认」。** 换默认工具链会让**所有已发布包的指纹失效** (全生态一次性重编),三平台的 llvm 载荷版本目前还不统一(Windows 20.1.7 vs Linux/macOS 22.1.8),还牵涉 `-static-libstdc++` 与 libc++/libstdc++ 的 ABI 选择。 那是生态决策,需要协调;**按次选择不需要任何人配合,而收益是同一个 2.51×**。 ⚠️ 但它**不改变形状**:clang 下 makespan 32.20s / 关键路径 32.15s = 仍然 100%, 平均并行度 3.90×——和 gcc 一模一样。clang 只是每个模块便宜 2.5 倍。 工程再长大一倍,它同样顶到墙。这就是 L2 仍然必要的原因。 实现走 `MCPP_TOOLCHAIN` 侧信道(与 `--offline` / `--jobs` 同一条,理由相同: 消费者在 prepare 的解析深处,穿参数要改沿途每一个调用者), 并计为 user-explicit —— mcpp 不会再悄悄改写它。 e2e 231 补两条,**两侧都钉**:`--toolchain gcc@16.1.0` 必须真的走到工具链解析 (`Resolved gcc@16.1.0`),以及 `--toolchain llvm@22.1.8` 必须**压过 manifest 里的 pin** —— 只钉前一条的话,一个什么都不做的实现也能通过。断言看的是**解析结果**而不是耗时: 在 CI 上断言时间等于在测 runner 的心情。
1 parent ccb3b86 commit 53a199a

3 files changed

Lines changed: 48 additions & 0 deletions

File tree

src/build/prepare.cppm

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1363,6 +1363,21 @@ prepare_build(bool print_fingerprint,
13631363
// silently overrule one the user wrote down.
13641364
auto tcOrigin = tcSpec.has_value() ? TcOrigin::ManifestToolchain
13651365
: TcOrigin::None;
1366+
// `--toolchain` (arriving as MCPP_TOOLCHAIN, the same side channel
1367+
// `--offline` and `--jobs` use) beats everything, including the manifest.
1368+
//
1369+
// This is the usable form of "which compiler". On this repository the
1370+
// choice is worth 2.48x — gcc@16.1.0 builds mcpp in 79.9s, llvm@22.1.8 in
1371+
// 32.2s — but CHANGING THE DEFAULT is an ecosystem decision, not a
1372+
// performance one: it invalidates every published package's fingerprint and
1373+
// the three platforms do not yet ship the same llvm. Selecting per build
1374+
// costs nobody anything and needs no coordination.
1375+
//
1376+
// It counts as user-explicit, so mcpp will not quietly revise it.
1377+
if (const char* tcEnv = std::getenv("MCPP_TOOLCHAIN"); tcEnv && *tcEnv) {
1378+
tcSpec = std::string(tcEnv);
1379+
tcOrigin = TcOrigin::ManifestToolchain;
1380+
}
13661381
if (!tcSpec.has_value()) {
13671382
auto cfg = get_cfg();
13681383
if (cfg && !(*cfg)->defaultToolchain.empty()) {

src/cli.cppm

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@ void print_usage() {
9090
std::println(" --no-color Disable colored output");
9191
std::println(" --offline Never touch the network (also: MCPP_OFFLINE=1)");
9292
std::println(" --jobs N|auto, -j Concurrent compiles ('auto' = cores + free RAM)");
93+
std::println(" --toolchain SPEC Use this toolchain for one build (e.g. llvm@22.1.8)");
9394
std::println("");
9495
std::println("Docs: https://github.com/mcpp-community/mcpp/tree/main/docs");
9596
}
@@ -128,6 +129,13 @@ int run(int argc, char** argv) {
128129
if (i + 1 < argc) mcpp::platform::env::set("MCPP_JOBS", argv[++i]);
129130
}
130131
else if (a.starts_with("--jobs=")) mcpp::platform::env::set("MCPP_JOBS", std::string(a.substr(7)));
132+
// --toolchain rides the same channel, for the same reason: its consumer
133+
// is deep inside prepare's resolution and threading a parameter down
134+
// would touch every caller in between.
135+
else if (a == "--toolchain") {
136+
if (i + 1 < argc) mcpp::platform::env::set("MCPP_TOOLCHAIN", argv[++i]);
137+
}
138+
else if (a.starts_with("--toolchain=")) mcpp::platform::env::set("MCPP_TOOLCHAIN", std::string(a.substr(12)));
131139
else if (a.starts_with("-j") && a.size() > 2)
132140
mcpp::platform::env::set("MCPP_JOBS", std::string(a.substr(2)));
133141
}
@@ -283,6 +291,8 @@ int run(int argc, char** argv) {
283291
.help("Global dependency cache: global (default) | local | off"))
284292
.option(cl::Option("jobs").short_name('j').takes_value().value_name("N")
285293
.help("Concurrent compiles: a number, or 'auto' to size from cores + free RAM"))
294+
.option(cl::Option("toolchain").takes_value().value_name("SPEC")
295+
.help("Build with this toolchain for one build, e.g. llvm@22.1.8"))
286296
.option(cl::Option("no-cache")
287297
.help("Deprecated alias for --cache=off (also clears the build dir)"))
288298
.option(cl::Option("target").takes_value().help(

tests/e2e/231_jobs_option.sh

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,3 +62,26 @@ if grep -qi 'invalid job count' "$TMP/sep.txt"; then
6262
fi
6363

6464
echo "jobs option OK"
65+
66+
# 10. `--toolchain` selects a toolchain for ONE build, without touching the
67+
# manifest. This is the usable form of "which compiler": on mcpp itself the
68+
# choice is worth 2.5x (gcc 81.8s vs llvm 32.6s), but changing the DEFAULT
69+
# would invalidate every published package's fingerprint, so per-build
70+
# selection is the part that costs nobody anything.
71+
#
72+
# Asserted by OBSERVING THE RESOLUTION, not by timing: a timing assertion on
73+
# CI measures the runner's mood.
74+
out=$("$MCPP" build --release --toolchain gcc@16.1.0 2>&1) \
75+
|| { echo "--toolchain gcc failed:"; echo "$out"; exit 1; }
76+
echo "$out" | grep -q 'Resolved gcc@16.1.0' \
77+
|| { echo "--toolchain did not reach toolchain resolution:"; echo "$out"; exit 1; }
78+
79+
# ...and it must BEAT the manifest, or it is not an override. The fixture pins
80+
# gcc on Linux, so asking for something else has to change what gets resolved.
81+
if [ "$(uname -s)" = "Linux" ]; then
82+
out=$("$MCPP" build --release --toolchain llvm@22.1.8 2>&1) || true
83+
echo "$out" | grep -q 'Resolved llvm@22.1.8' \
84+
|| { echo "--toolchain lost to the manifest pin:"; echo "$out"; exit 1; }
85+
fi
86+
87+
echo "toolchain override OK"

0 commit comments

Comments
 (0)