Skip to content

Commit c8bc6af

Browse files
committed
feat(build): MCPP_TARGET_OS/ARCH/ENV contract env splits
Cargo CARGO_CFG_TARGET_* parity (design §3.1 item 3): contract_env now parses the resolved target ONCE through the canonical triple parser (mcpp.toolchain.triple — already imported for MCPP_HOST) and injects MCPP_TARGET_OS linux|macos|windows MCPP_TARGET_ARCH GNU spelling (x86_64, aarch64, ...) MCPP_TARGET_ENV gnu|musl|msvc, "" when the triple has no env segment so every build.mcpp (ffmpeg/opencv per-OS selection) stops hand-splitting MCPP_TARGET. All three are always SET; an escape-hatch triple outside the canonical vocabulary yields "" for all three (parse failure is not an error here — MCPP_TARGET still carries the verbatim spelling). Re-run key: verified — contract_hash hashes the WHOLE (name,value) env vector, and the new vars ride that same vector, so they fold into ctxHash with no extra plumbing (a target change already re-ran the program via MCPP_TARGET; the splits add no new invalidation axis). Typed lib: mcpp::target_os()/target_arch()/target_env() readers. e2e 110 extended: asserts the three vars are set, OS/ARCH agree with the full MCPP_TARGET triple's leading segments, and ENV is set (content may be empty on macOS). Docs env tables updated (en/zh). Verified: unit 35/35; e2e 110/143/144/111/125 green.
1 parent e8b10a8 commit c8bc6af

4 files changed

Lines changed: 42 additions & 2 deletions

File tree

docs/07-build-mcpp.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,9 @@ The running program receives the build context as `MCPP_*` variables
111111
| Variable | Typed reader | Value |
112112
|---|---|---|
113113
| `MCPP_TARGET` | `mcpp::target()` | resolved canonical triple (the `--target` triple under cross; the host triple natively) |
114+
| `MCPP_TARGET_OS` | `mcpp::target_os()` | the target's OS segment (`linux`/`macos`/`windows`) — no need to hand-split `MCPP_TARGET` |
115+
| `MCPP_TARGET_ARCH` | `mcpp::target_arch()` | the target's arch segment (GNU spelling: `x86_64`, `aarch64`, …) |
116+
| `MCPP_TARGET_ENV` | `mcpp::target_env()` | the target's env segment (`gnu`/`musl`/`msvc`); empty string when the triple has none (macOS) |
114117
| `MCPP_HOST` | `mcpp::host()` | the host triple |
115118
| `MCPP_PROFILE` | `mcpp::profile()` | effective profile name (`dev`/`release`/…) |
116119
| `MCPP_OUT_DIR` | `mcpp::out_dir()` | a writable scratch/output dir owned by mcpp |

docs/zh/07-build-mcpp.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,9 @@ int main() {
103103
| 变量 | 类型化读取 ||
104104
|---|---|---|
105105
| `MCPP_TARGET` | `mcpp::target()` | 解析后的 canonical 三元组(交叉构建下是 `--target` 三元组,原生构建是宿主) |
106+
| `MCPP_TARGET_OS` | `mcpp::target_os()` | 目标的 OS 段(`linux`/`macos`/`windows`)——不必再手撕 `MCPP_TARGET` |
107+
| `MCPP_TARGET_ARCH` | `mcpp::target_arch()` | 目标的 arch 段(GNU 拼写:`x86_64``aarch64`…) |
108+
| `MCPP_TARGET_ENV` | `mcpp::target_env()` | 目标的 env 段(`gnu`/`musl`/`msvc`);三元组无 env 段(macOS)时为空串 |
106109
| `MCPP_HOST` | `mcpp::host()` | 宿主三元组 |
107110
| `MCPP_PROFILE` | `mcpp::profile()` | 生效 profile 名(`dev`/`release`/…) |
108111
| `MCPP_OUT_DIR` | `mcpp::out_dir()` | mcpp 提供的可写输出/暂存目录 |

src/build/build_program.cppm

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -263,6 +263,9 @@ inline void rerun_if_env_changed(const char* var) { std::printf("mcpp:rerun-if-e
263263
// ── environment contract (read side; values injected by the engine) ─────
264264
inline const char* env_or(const char* n) { const char* v = std::getenv(n); return v ? v : ""; }
265265
inline const char* target() { return env_or("MCPP_TARGET"); }
266+
inline const char* target_os() { return env_or("MCPP_TARGET_OS"); }
267+
inline const char* target_arch() { return env_or("MCPP_TARGET_ARCH"); }
268+
inline const char* target_env() { return env_or("MCPP_TARGET_ENV"); }
266269
inline const char* host() { return env_or("MCPP_HOST"); }
267270
inline const char* profile() { return env_or("MCPP_PROFILE"); }
268271
inline const char* out_dir() { return env_or("MCPP_OUT_DIR"); }
@@ -383,6 +386,20 @@ contract_env(const fs::path& root, const fs::path& outDir, const BuildProgramEnv
383386
std::vector<std::pair<std::string, std::string>> e;
384387
auto hostT = mcpp::toolchain::triple::host_triple().str();
385388
e.emplace_back("MCPP_TARGET", env.targetTriple.empty() ? hostT : env.targetTriple);
389+
// Convenience splits of the resolved target (Cargo CARGO_CFG_TARGET_*
390+
// parity): parsed ONCE here through the canonical triple parser so every
391+
// build.mcpp stops hand-splitting MCPP_TARGET. MCPP_TARGET_ENV is "" when
392+
// the triple has no env segment (macOS); all three are "" for an
393+
// escape-hatch triple outside the canonical vocabulary. They ride the
394+
// same env vector, so contract_hash folds them into the re-run key.
395+
{
396+
mcpp::toolchain::triple::Triple t{};
397+
if (env.targetTriple.empty()) t = mcpp::toolchain::triple::host_triple();
398+
else if (auto p = mcpp::toolchain::triple::parse(env.targetTriple)) t = *p;
399+
e.emplace_back("MCPP_TARGET_OS", t.os);
400+
e.emplace_back("MCPP_TARGET_ARCH", t.arch);
401+
e.emplace_back("MCPP_TARGET_ENV", t.env);
402+
}
386403
e.emplace_back("MCPP_HOST", hostT);
387404
e.emplace_back("MCPP_PROFILE", env.profile);
388405
e.emplace_back("MCPP_OUT_DIR", outDir.string());

tests/e2e/110_build_mcpp_env_contract.sh

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,11 @@ int main() {
2424
f << "extern \"C\" const char* bp_target() { return \"" << env_or("MCPP_TARGET") << "\"; }\n";
2525
f << "extern \"C\" const char* bp_host() { return \"" << env_or("MCPP_HOST") << "\"; }\n";
2626
f << "extern \"C\" const char* bp_profile() { return \"" << env_or("MCPP_PROFILE") << "\"; }\n";
27+
f << "extern \"C\" const char* bp_tos() { return \"" << env_or("MCPP_TARGET_OS") << "\"; }\n";
28+
f << "extern \"C\" const char* bp_tarch() { return \"" << env_or("MCPP_TARGET_ARCH") << "\"; }\n";
29+
// ENV may legitimately be empty (macOS) — assert set-ness, not content.
30+
f << "extern \"C\" int bp_tenv_set() { return "
31+
<< (std::getenv("MCPP_TARGET_ENV") ? 1 : 0) << "; }\n";
2732
f << "extern \"C\" const char* bp_features() { return \"" << env_or("MCPP_FEATURES") << "\"; }\n";
2833
f << "extern \"C\" int bp_has_extra() { return "
2934
<< (std::getenv("MCPP_FEATURE_EXTRA") ? 1 : 0) << "; }\n";
@@ -45,9 +50,13 @@ extern "C" const char* bp_host();
4550
extern "C" const char* bp_profile();
4651
extern "C" const char* bp_features();
4752
extern "C" int bp_has_extra();
53+
extern "C" const char* bp_tos();
54+
extern "C" const char* bp_tarch();
55+
extern "C" int bp_tenv_set();
4856
int main() {
49-
std::println("target={} host={} profile={} features=[{}] extra={}",
50-
bp_target(), bp_host(), bp_profile(), bp_features(), bp_has_extra());
57+
std::println("target={} host={} profile={} features=[{}] extra={} tos={} tarch={} tenvset={}",
58+
bp_target(), bp_host(), bp_profile(), bp_features(), bp_has_extra(),
59+
bp_tos(), bp_tarch(), bp_tenv_set());
5160
return 0;
5261
}
5362
EOF
@@ -67,6 +76,14 @@ out="$("$MCPP" run 2>&1 | tail -1)"
6776
host_triple_re='[a-z0-9_]+-(linux|macos|windows)(-[a-z]+)?'
6877
[[ "$out" =~ target=$host_triple_re\ host=$host_triple_re\ profile=dev\ features=\[\]\ extra=0 ]] || {
6978
echo "unexpected contract values: $out"; exit 1; }
79+
# MCPP_TARGET_OS/ARCH/ENV splits: OS is one of the canonical spellings, ARCH is
80+
# non-empty, ENV is always SET (may be "" — no env segment on macOS) and must
81+
# agree with the full triple's segments.
82+
[[ "$out" =~ tos=(linux|macos|windows)\ tarch=([a-z0-9_]+)\ tenvset=1 ]] || {
83+
echo "target split vars missing/odd: $out"; exit 1; }
84+
tos="${BASH_REMATCH[1]}"; tarch="${BASH_REMATCH[2]}"
85+
[[ "$out" == *"target=${tarch}-${tos}"* ]] || {
86+
echo "target split disagrees with MCPP_TARGET: $out"; exit 1; }
7087

7188
# Identical build → no re-run (either the whole-build fast path short-circuits
7289
# before build.mcpp, or the build.mcpp cache hits — both are fine; a "running"

0 commit comments

Comments
 (0)