Skip to content

Commit 2b356a7

Browse files
committed
feat(manifest): bare OS-alias sugar for [target.*] conditional tables (v0.0.80)
[target.linux.dependencies] / [target.windows.build] now work as concise sugar for [target.'cfg(linux)'...] — dropping the cfg() wrapper and its mandatory TOML quotes for the common single-OS case. The bare aliases windows/linux/macos/unix are never valid triples, so there's no ambiguity with the exact-triple namespace. One evaluator branch in cfgpred::matches() (the parser already routes deps/build for any [target.<key>] into conditionalConfigs); evaluation stays target-resolved, identical to cfg(). Backward-compatible: cfg(...) and exact triples unchanged. toolchain/linkage remain exact-triple only (documented). - src/build/prepare.cppm: cfgpred::matches() bare-alias branch - tests/e2e/91_target_bare_alias.sh: bare alias works + parity with cfg(), host-aware - docs/05-mcpp-toml.md (+zh): new §2.7.1 documenting all three [target.*] forms (the L1 conditional config was previously undocumented in the user guide) - design doc; version -> 0.0.80
1 parent f0a0e20 commit 2b356a7

7 files changed

Lines changed: 256 additions & 2 deletions

File tree

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
# Bare OS-alias sugar for `[target.*]` conditional tables (Design)
2+
3+
Addresses the ergonomic complaint that `[target.'cfg(linux)'.dependencies.compat]`
4+
is visually noisy. Lets the common single-OS case drop the `cfg(...)` wrapper (and
5+
its mandatory TOML quotes) while keeping the full `cfg(...)` grammar for compound
6+
predicates. Ships in mcpp 0.0.80.
7+
8+
## Before / after
9+
10+
```toml
11+
# before — quotes (TOML-mandated around cfg(...)) + ceremony
12+
[target.'cfg(windows)'.dependencies.compat]
13+
openblas = "0.3.33"
14+
[target.'cfg(windows)'.build]
15+
ldflags = ["-Llib", "-llibopenblas"]
16+
17+
# after — bare alias for the 90% case, no quotes
18+
[target.windows.dependencies.compat]
19+
openblas = "0.3.33"
20+
[target.windows.build]
21+
ldflags = ["-Llib", "-llibopenblas"]
22+
23+
# compound predicates STILL use cfg(...) (arch / env / all / any / not)
24+
[target.'cfg(all(linux, not(arch = "aarch64")))'.build]
25+
cxxflags = ["-march=x86-64-v2"]
26+
```
27+
28+
## Why this is unambiguous
29+
30+
A mcpp target triple is always `<arch>-<os>[-<env>]` (`x86_64-linux-musl`,
31+
`x86_64-pc-windows-msvc`). The bare OS/family aliases **`windows` / `linux` /
32+
`macos` / `unix` are never valid triples** (no dash), so `[target.linux]` can only
33+
mean the `cfg(linux)` predicate — there is no collision with the exact-triple
34+
namespace (`[target.x86_64-linux-musl]`). This is the same alias set already
35+
accepted *inside* `cfg(...)` (`cfgpred::match_alias`), now also accepted as a bare
36+
section key.
37+
38+
This is a deliberate, small divergence from Cargo (which always requires
39+
`cfg()`/triple) — justified because mcpp's alias set is unambiguous, and the win is
40+
removing the quote-noise from the overwhelmingly common per-OS case.
41+
42+
## Implementation (one evaluator branch)
43+
44+
The parser already does the right thing: `manifest.cppm`'s `[target]` loop reads
45+
`build` / `dependencies` / … for **every** `[target.<key>]` and stores a
46+
`ConditionalConfig{ predicate = <key> }` (manifest.cppm:1242-1275). So
47+
`[target.linux.dependencies.compat]` is *already* parsed into a conditional config
48+
with `predicate = "linux"`. The only gap is evaluation.
49+
50+
`cfgpred::matches()` (`prepare.cppm:139-146`) currently:
51+
```cpp
52+
inline bool matches(const std::string& predicate, const Ctx& c, std::string_view triple) {
53+
std::string_view k = predicate;
54+
if (k.starts_with("cfg(") && k.ends_with(")")) { Parser p{...}; return p.expr(); }
55+
return !triple.empty() && predicate == triple; // bare-triple exact match
56+
}
57+
```
58+
A bare `"linux"` falls through to the triple branch and never matches (host build →
59+
`triple` empty; cross build → `"linux" != "x86_64-linux-gnu"`). Fix — add the
60+
alias branch **before** the triple fallback:
61+
```cpp
62+
if (predicate == "windows" || predicate == "linux" ||
63+
predicate == "macos" || predicate == "unix") {
64+
Parser p{ predicate, 0, c }; // evaluate as the cfg bareword
65+
return p.expr();
66+
}
67+
```
68+
That's the whole behavioral change. Evaluation stays **target-resolved** (the
69+
`Ctx` is built from the resolved `--target`, else host) — identical semantics to
70+
`cfg(linux)`. No parser change, no schema change, fully backward-compatible
71+
(`cfg(...)` and exact triples unchanged).
72+
73+
## Scope boundary (documented)
74+
75+
The sugar covers the **L1 conditional config**: `.dependencies` /
76+
`.dev-dependencies` / `.build-dependencies` / `.build` (cflags/cxxflags/ldflags).
77+
78+
`[target.<triple>].toolchain` and `.linkage` remain **exact-triple only** — they
79+
describe a *specific* cross target, not an OS family, and are looked up by exact
80+
triple in `prepare_build`. Writing `toolchain`/`linkage` under a bare alias (or a
81+
`cfg(...)` key) has no effect today; a follow-up may add a schema warning to flag
82+
that footgun. Out of scope here.
83+
84+
## Tests
85+
86+
`tests/e2e/91_target_bare_alias.sh`: a project with `[target.linux.build] cxxflags`
87+
+ `[target.windows.dependencies]`; assert on Linux the cxxflag define reaches the
88+
TU and the windows-only dep is NOT pulled; assert `[target.'cfg(linux)']` and the
89+
bare `[target.linux]` produce identical results (parity).
90+
91+
## Docs
92+
93+
`docs/05-mcpp-toml.md` (+ zh): the `[target.*]` section gains the bare-alias form
94+
as the recommended spelling for single-OS, with `cfg(...)` shown for compound
95+
predicates and the triple form for exact targets.

docs/05-mcpp-toml.md

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -272,6 +272,48 @@ toolchain = "gcc@15.1.0-musl"
272272
linkage = "static"
273273
```
274274

275+
### 2.7.1 `[target.*]` — Platform-Conditional Dependencies & Flags
276+
277+
Scope dependencies and build flags to a platform with a `[target.<sel>]` table.
278+
The selector `<sel>` has three forms:
279+
280+
| Selector | Meaning | Example |
281+
|---|---|---|
282+
| **bare OS alias** | a single OS / family — the concise, common form | `[target.windows]`, `[target.unix]` |
283+
| **`cfg(...)` predicate** | a compound condition (arch / env / combinators) | `[target.'cfg(all(linux, not(arch = "aarch64")))']` |
284+
| **exact triple** | one specific target (also carries `toolchain` / `linkage`) | `[target.x86_64-linux-musl]` |
285+
286+
Under a selector you may put platform-conditional **dependencies** and **build flags**:
287+
288+
```toml
289+
# Concise bare-alias form — pull OpenBLAS and link it only on Windows.
290+
[target.windows.dependencies.compat]
291+
openblas = "0.3.33"
292+
[target.windows.build]
293+
ldflags = ["-Llib", "-llibopenblas"]
294+
295+
# cfg(...) for compound predicates (grammar: all/any/not over os/arch/family/env,
296+
# plus the bare aliases windows/unix/linux/macos).
297+
[target.'cfg(all(linux, not(arch = "aarch64")))'.build]
298+
cxxflags = ["-march=x86-64-v2"]
299+
```
300+
301+
`[target.windows]` is exactly equivalent to `[target.'cfg(windows)']` — the bare
302+
aliases `windows` / `linux` / `macos` / `unix` are never valid target triples, so
303+
there is no ambiguity. Use the bare form for a single OS/family; use `cfg(...)`
304+
when you need arch/env conditions or combinators.
305+
306+
- **Keys**: `dependencies` / `dev-dependencies` / `build-dependencies`, and
307+
`build` with `cflags` / `cxxflags` / `ldflags`.
308+
- **Evaluated against the resolved target** — the `--target` triple for a cross
309+
build, otherwise the host. So a native Linux build never even *downloads* a
310+
`[target.windows]` dependency.
311+
- **Precedence**: an exact-triple table wins over a `cfg`/alias table; multiple
312+
matching predicate tables have their flags concatenated.
313+
- **`toolchain` / `linkage` are exact-triple only** — they describe one specific
314+
cross target, so put them under `[target.<triple>]` (above), not under a bare
315+
alias or `cfg(...)`.
316+
275317
### 2.8 `[features]` — Features (Cargo-style, additive)
276318

277319
```toml

docs/zh/05-mcpp-toml.md

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -260,6 +260,43 @@ toolchain = "gcc@15.1.0-musl"
260260
linkage = "static"
261261
```
262262

263+
### 2.7.1 `[target.*]` — 平台条件依赖与编译旗标
264+
265+
`[target.<sel>]` 表把依赖和编译旗标限定到某平台。选择子 `<sel>` 有三种形式:
266+
267+
| 选择子 | 含义 | 例子 |
268+
|---|---|---|
269+
| **裸 OS 别名** | 单个 OS / 家族——简洁、最常用 | `[target.windows]``[target.unix]` |
270+
| **`cfg(...)` 谓词** | 复合条件(arch / env / 组合子) | `[target.'cfg(all(linux, not(arch = "aarch64")))']` |
271+
| **精确三元组** | 某个具体目标(还承载 `toolchain` / `linkage`) | `[target.x86_64-linux-musl]` |
272+
273+
任一选择子下都可放平台条件的**依赖****编译旗标**:
274+
275+
```toml
276+
# 简洁的裸别名形式——仅在 Windows 上拉取并链接 OpenBLAS。
277+
[target.windows.dependencies.compat]
278+
openblas = "0.3.33"
279+
[target.windows.build]
280+
ldflags = ["-Llib", "-llibopenblas"]
281+
282+
# 复合谓词用 cfg(...)(语法:all/any/not 作用在 os/arch/family/env 上,
283+
# 外加裸别名 windows/unix/linux/macos)。
284+
[target.'cfg(all(linux, not(arch = "aarch64")))'.build]
285+
cxxflags = ["-march=x86-64-v2"]
286+
```
287+
288+
`[target.windows]``[target.'cfg(windows)']` 完全等价——裸别名
289+
`windows` / `linux` / `macos` / `unix` 永远不是合法的目标三元组,故无歧义。单个
290+
OS/家族用裸形式;需要 arch/env 条件或组合子时用 `cfg(...)`
291+
292+
- **可放的键**:`dependencies` / `dev-dependencies` / `build-dependencies`,以及
293+
`build` 下的 `cflags` / `cxxflags` / `ldflags`
294+
- **按解析后的目标求值**——交叉构建时是 `--target` 三元组,否则是 host。所以原生
295+
Linux 构建**根本不会下载** `[target.windows]` 的依赖。
296+
- **优先级**:精确三元组表压过 `cfg`/别名表;多个命中的谓词表,其旗标会拼接。
297+
- **`toolchain` / `linkage` 仅限精确三元组**——它们描述某个具体交叉目标,故应放在
298+
`[target.<triple>]`(见上)下,而非裸别名或 `cfg(...)` 下。
299+
263300
### 2.8 `[features]` — 特性(Cargo 式,加性)
264301

265302
```toml

mcpp.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "mcpp"
3-
version = "0.0.79"
3+
version = "0.0.80"
44
description = "Modern C++ build & package management tool"
55
license = "Apache-2.0"
66
authors = ["mcpp-community"]

src/build/prepare.cppm

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,14 @@ inline bool matches(const std::string& predicate, const Ctx& c, std::string_view
142142
Parser p{ k.substr(4, k.size() - 5), 0, c };
143143
return p.expr();
144144
}
145+
// Bare OS/family alias sugar: `[target.linux]` ≡ `[target.'cfg(linux)']`.
146+
// These aliases are never valid triples (no dash), so there is no ambiguity
147+
// with the exact-triple namespace. Evaluated as the cfg bareword.
148+
if (predicate == "windows" || predicate == "linux" ||
149+
predicate == "macos" || predicate == "unix") {
150+
Parser p{ predicate, 0, c };
151+
return p.expr();
152+
}
145153
return !triple.empty() && predicate == triple; // bare-triple exact match
146154
}
147155

src/toolchain/fingerprint.cppm

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import mcpp.toolchain.detect;
1818

1919
export namespace mcpp::toolchain {
2020

21-
inline constexpr std::string_view MCPP_VERSION = "0.0.79";
21+
inline constexpr std::string_view MCPP_VERSION = "0.0.80";
2222

2323
struct FingerprintInputs {
2424
Toolchain toolchain;

tests/e2e/91_target_bare_alias.sh

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
#!/usr/bin/env bash
2+
# 91_target_bare_alias.sh — bare OS-alias sugar for [target.*] conditional tables:
3+
# `[target.linux.build]` ≡ `[target.'cfg(linux)'.build]`. The bare aliases
4+
# windows/linux/macos/unix are never valid triples, so they unambiguously mean the
5+
# cfg predicate. HOST-AWARE: asserts (a) exactly one OS alias applies (the host's)
6+
# and (b) the bare alias agrees with the cfg() form per-OS — so it validates the
7+
# parity on whichever of linux/macos/windows the runner is.
8+
# See .agents/docs/2026-06-30-target-bare-alias-sugar-design.md.
9+
set -e
10+
11+
TMP=$(mktemp -d)
12+
trap "rm -rf $TMP" EXIT
13+
cd "$TMP"
14+
15+
mkdir -p app/src
16+
cat > app/mcpp.toml <<'EOF'
17+
[package]
18+
name = "app"
19+
version = "0.1.0"
20+
21+
# Bare-alias forms (the sugar under test).
22+
[target.linux.build]
23+
cxxflags = ["-DBARE_LINUX=1"]
24+
[target.macos.build]
25+
cxxflags = ["-DBARE_MACOS=1"]
26+
[target.windows.build]
27+
cxxflags = ["-DBARE_WIN=1"]
28+
[target.unix.build]
29+
cxxflags = ["-DBARE_UNIX=1"]
30+
31+
# cfg(...) forms — must produce identical results (parity).
32+
[target.'cfg(linux)'.build]
33+
cxxflags = ["-DCFG_LINUX=1"]
34+
[target.'cfg(macos)'.build]
35+
cxxflags = ["-DCFG_MACOS=1"]
36+
[target.'cfg(windows)'.build]
37+
cxxflags = ["-DCFG_WIN=1"]
38+
[target.'cfg(unix)'.build]
39+
cxxflags = ["-DCFG_UNIX=1"]
40+
EOF
41+
cat > app/src/main.cpp <<'EOF'
42+
// Exactly one OS bare-alias must apply — the host's — on any platform.
43+
#if (defined(BARE_LINUX) + defined(BARE_MACOS) + defined(BARE_WIN)) != 1
44+
#error "exactly one bare OS alias (linux/macos/windows) must apply on any host"
45+
#endif
46+
// Bare alias and cfg() must agree per OS (parity).
47+
#if defined(BARE_LINUX) != defined(CFG_LINUX)
48+
#error "[target.linux] disagrees with [target.'cfg(linux)']"
49+
#endif
50+
#if defined(BARE_MACOS) != defined(CFG_MACOS)
51+
#error "[target.macos] disagrees with [target.'cfg(macos)']"
52+
#endif
53+
#if defined(BARE_WIN) != defined(CFG_WIN)
54+
#error "[target.windows] disagrees with [target.'cfg(windows)']"
55+
#endif
56+
#if defined(BARE_UNIX) != defined(CFG_UNIX)
57+
#error "[target.unix] disagrees with [target.'cfg(unix)']"
58+
#endif
59+
// unix family applies iff not windows.
60+
#if defined(BARE_WIN) && defined(BARE_UNIX)
61+
#error "unix wrongly applied on a windows host"
62+
#endif
63+
#if !defined(BARE_WIN) && !defined(BARE_UNIX)
64+
#error "unix should apply on a non-windows host"
65+
#endif
66+
int main() { return 0; }
67+
EOF
68+
69+
cd app
70+
"$MCPP" build > b.log 2>&1 || { cat b.log; echo "FAIL: build errored (bare alias not honored?)"; exit 1; }
71+
72+
echo "OK"

0 commit comments

Comments
 (0)