Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 49 additions & 3 deletions docs/05-mcpp-toml.md
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,46 @@ sources = [
]
```

**Per-glob flags** (mcpp 0.0.95+): `[build] flags` is an ordered array of
inline tables attaching extra compile flags to exactly the sources a glob
matches — the tool for SIMD dispatch TUs and vendored-code warning isolation:

```toml
[build]
flags = [
{ glob = "third_party/**", cflags = ["-w"], cxxflags = ["-w"] },
{ glob = "src/simd/**/*.avx2.cpp", cxxflags = ["-mavx2"], defines = ["HAVE_AVX2"] },
{ glob = "src/x86/**/*.asm", asmflags = ["-DPREFIX"] },
]
```

Keys per entry: `glob` (package-root-relative, required) plus `cflags` /
`cxxflags` / `asmflags` / `defines` (no `ldflags` — linking has no per-TU
scope). Declaration order is application order: a later entry's flags land
later on the command line, so with GNU "last flag wins" a narrower glob
placed after a broader one overrides it. All matching entries apply. These
are private build flags — they never propagate to consumers. A glob that
matches no source file prints a warning (a typo'd glob must not silently do
nothing).

**Generated files** (mcpp 0.0.95+): `[generated_files]` maps a relative path
to file contents (TOML multiline strings supported). Entries are written
into the project tree before source globs expand — the same mechanism index
descriptors use to synthesize module wrappers — and the content enters the
fingerprint, so editing it rebuilds:

```toml
[generated_files]
"src/gen/wrap.cppm" = """
module;
#include <vendored.h>
export module wrap;
"""
```

Paths must stay inside the project root (`..` / absolute paths are parse
errors).

**Assembly sources** (mcpp 0.0.95+): `.S`/`.s` (GAS — preprocessed by the C
driver, covers ARM and AT&T-syntax x86) and `.asm` (NASM — Intel-syntax x86)
are first-class sources: default-globbed, fingerprinted, built incrementally
Expand Down Expand Up @@ -319,7 +359,9 @@ there is no ambiguity. Use the bare form for a single OS/family; use `cfg(...)`
when you need arch/env conditions or combinators.

- **Keys**: `dependencies` / `dev-dependencies` / `build-dependencies`, and
`build` with `cflags` / `cxxflags` / `ldflags`.
`build` with `cflags` / `cxxflags` / `ldflags` / `sources` (mcpp 0.0.95+ —
conditional source globs, e.g. gating `src/x86/**/*.asm` behind
`cfg(arch = "x86_64")`; `!`-exclusion globs work here too).
- **Evaluated against the resolved target** — the `--target` triple for a cross
build, otherwise the host. So a native Linux build never even *downloads* a
`[target.windows]` dependency.
Expand Down Expand Up @@ -352,8 +394,12 @@ extra = []
#### Table form — a feature that contributes more than implied features

A `[features]` entry may be written as a **table** instead of an array, letting the
feature carry package-owned preprocessor `defines` and/or capability `requires` /
`provides` (see §2.8.1) alongside its implied features:
feature carry package-owned preprocessor `defines`, feature-gated source globs
(`sources`, mcpp 0.0.95+ — the globs leave the default build and compile only when
the feature is active, exactly like an index descriptor's `features.<f>.sources`;
the highest-frequency shape for vendored libraries: *feature = a source set + a
define*), and/or capability `requires` / `provides` (see §2.8.1) alongside its
implied features:

```toml
[features]
Expand Down
52 changes: 50 additions & 2 deletions docs/zh/05-mcpp-toml.md
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,49 @@ sources = [
]
```

**per-glob 旗标**(mcpp 0.0.95+):`[build] flags` 是**有序**的内联表数组,把额外
编译旗标只附加到 glob 命中的源文件——SIMD 多档 dispatch TU 与三方代码告警隔离的
正解:

```toml
[build]
flags = [
{ glob = "third_party/**", cflags = ["-w"], cxxflags = ["-w"] },
{ glob = "src/simd/**/*.avx2.cpp", cxxflags = ["-mavx2"], defines = ["HAVE_AVX2"] },
{ glob = "src/x86/**/*.asm", asmflags = ["-DPREFIX"] },
]
```

每条目键:`glob`(相对包根,必填)+ `cflags` / `cxxflags` / `asmflags` /
`defines`(没有 `ldflags`——链接没有 per-TU 作用域)。声明顺序即应用顺序:靠后
条目的旗标排在命令行更后,配合 GNU "后旗标胜",窄 glob 放在宽 glob 之后即可覆盖。
所有命中条目都生效;这些是私有构建旗标,不会传播给消费者。glob 零命中会打印
warning(打错的 glob 不允许静默无效)。

**生成文件**(mcpp 0.0.95+):`[generated_files]` 把相对路径映射到文件内容(支持
TOML 多行字符串)。条目在源 glob 展开之前写入工程树——与 index 描述符合成模块
包装文件是同一机制——内容进指纹,改内容即重建:

```toml
[generated_files]
"src/gen/wrap.cppm" = """
module;
#include <vendored.h>
export module wrap;
"""
```

路径必须留在工程根之内(`..` / 绝对路径是解析错误)。

**汇编源**(mcpp 0.0.95+):`.S`/`.s`(GAS——由 C 驱动器预处理,覆盖 ARM 与
AT&T 语法 x86)和 `.asm`(NASM——Intel 语法 x86)是一等源文件:默认 glob 收录、
进指纹、增量并行构建、像任何对象一样链接。NASM 的输出格式由目标三元组推导
(`elf64`/`win64`/`macho64`/...——交叉构建零特判);`nasm` 仅在存在 `.asm` 单元时
惰性解析:先 `PATH`,再 mcpp 沙箱,再 `xlings install nasm`;找不到 ≥2.16 的
nasm 则**硬失败**(汇编绝不静默跳过)。限制:`.asm` 仅限 x86 目标(其他目标硬
报错——用条件 sources 门控)、MSVC 工具链不支持 `.S`、`.asm` 即 NASM 语法
(MASM 源请用 `!` 排除)。

### 2.4 `[lib]` — 库根模块约定

```toml
Expand Down Expand Up @@ -290,7 +333,9 @@ cxxflags = ["-march=x86-64-v2"]
OS/家族用裸形式;需要 arch/env 条件或组合子时用 `cfg(...)`。

- **可放的键**:`dependencies` / `dev-dependencies` / `build-dependencies`,以及
`build` 下的 `cflags` / `cxxflags` / `ldflags`。
`build` 下的 `cflags` / `cxxflags` / `ldflags` / `sources`(mcpp 0.0.95+——
条件源 glob,如把 `src/x86/**/*.asm` 门控在 `cfg(arch = "x86_64")` 之后;
`!` 排除 glob 同样可用)。
- **按解析后的目标求值**——交叉构建时是 `--target` 三元组,否则是 host。所以原生
Linux 构建**根本不会下载** `[target.windows]` 的依赖。
- **优先级**:精确三元组表压过 `cfg`/别名表;多个命中的谓词表,其旗标会拼接。
Expand All @@ -317,7 +362,10 @@ extra = []
#### 表形式 —— 让 feature 贡献的不止是隐含 feature

`[features]` 的条目除了写成数组,还可写成**表**,从而让该 feature 在隐含 feature
之外,携带包自有的预处理 `defines`,以及 capability 的 `requires` / `provides`
之外,携带包自有的预处理 `defines`、feature 门控的源 glob(`sources`,mcpp
0.0.95+——列出的 glob 离开默认构建,仅当 feature 激活时才编译,与 index 描述符的
`features.<f>.sources` 完全对等;这正是 vendored 大库最高频的形态:*feature =
一组源文件 + 一个 define*),以及 capability 的 `requires` / `provides`
(见 §2.8.1):

```toml
Expand Down
4 changes: 3 additions & 1 deletion src/build/ninja_backend.cppm
Original file line number Diff line number Diff line change
Expand Up @@ -170,13 +170,15 @@ bool is_scan_exempt(const std::filesystem::path& src) {
// Per-unit flags an assembler can take: the -D/-U/-I subset of the unit's C
// flags (feature defines land there). NASM shares the GNU -D/-U/-I spelling
// (and ≥2.14 inserts a missing -I path separator itself), so one filter
// serves both asm rules.
// serves both asm rules. Explicit per-glob asmflags (G4) append after the
// filtered subset — author-directed flags win.
std::vector<std::string> asm_unit_flags(const CompileUnit& cu) {
std::vector<std::string> out;
for (auto& f : cu.packageCflags) {
if (f.starts_with("-D") || f.starts_with("-U") || f.starts_with("-I"))
out.push_back(f);
}
out.insert(out.end(), cu.packageAsmflags.begin(), cu.packageAsmflags.end());
return out;
}

Expand Down
6 changes: 6 additions & 0 deletions src/build/plan.cppm
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ struct CompileUnit {
std::vector<std::filesystem::path> localIncludeDirs;
std::vector<std::string> packageCflags;
std::vector<std::string> packageCxxflags;
std::vector<std::string> packageAsmflags; // per-glob asmflags (G4)
std::optional<std::string> providesModule; // logical name, if .cppm export
std::vector<std::string> imports; // logical names imported
// Unit came from a scan_overrides declaration — plan-vs-ddi
Expand Down Expand Up @@ -445,6 +446,7 @@ BuildPlan make_plan(const mcpp::manifest::Manifest& manifest,
cu.localIncludeDirs = u.localIncludeDirs;
cu.packageCflags = u.packageCflags;
cu.packageCxxflags = u.packageCxxflags;
cu.packageAsmflags = u.packageAsmflags;
const auto fname = object_filename_for(u.path, objExt);
if (basenameCount[fname] > 1) {
// Use <sanitized-pkg>/<parent-dir-name> as prefix to handle
Expand Down Expand Up @@ -700,6 +702,10 @@ BuildPlan make_plan(const mcpp::manifest::Manifest& manifest,
main_cu.packageCflags = manifest.buildConfig.cflags;
main_cu.packageCxxflags = manifest.buildConfig.cxxflags;
}
// Root-relative -I flags → absolute (G8b), mirroring the scanner's
// treatment of every scanned unit.
mcpp::modgraph::absolutize_include_flags(projectRoot, main_cu.packageCflags);
mcpp::modgraph::absolutize_include_flags(projectRoot, main_cu.packageCxxflags);

// We didn't scan main.cpp earlier (it's not in scanner output unless globbed in).
// Best-effort: scan its imports here.
Expand Down
78 changes: 70 additions & 8 deletions src/build/prepare.cppm
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,15 @@ std::string canonical_compile_flags(const mcpp::manifest::Manifest& m) {
s += " ldflag:";
s += flag;
}
// Per-glob flags (G4): full ordered serialization — glob + every list —
// so editing any entry (or reordering) re-fingerprints the output dir.
for (auto const& gf : m.buildConfig.globFlags) {
s += " globflags:"; s += gf.glob;
for (auto const& f : gf.cflags) { s += " gc:"; s += f; }
for (auto const& f : gf.cxxflags) { s += " gxx:"; s += f; }
for (auto const& f : gf.asmflags) { s += " gas:"; s += f; }
for (auto const& f : gf.defines) { s += " gd:"; s += f; }
}
return s;
}

Expand Down Expand Up @@ -327,6 +336,31 @@ materialize_generated_files(const std::filesystem::path& root,
return {};
}

// L1 cfg merge for ONE manifest (root or dependency): append the matching
// conditional cflags/cxxflags/ldflags and sources (G1b) to its buildConfig.
// Sources also update the legacy modules.sources mirror — the scanner walks
// that. Conditional dependency maps are root-only and handled at the root
// call site; a dependency's conditional configs otherwise evaluate the same
// way (descriptor `target_cfg` must not be silently inert).
void merge_conditional_build(mcpp::manifest::Manifest& m,
const cfgpred::Ctx& ctx,
std::string_view targetTriple)
{
for (auto const& cc : m.conditionalConfigs) {
if (!cfgpred::matches(cc.predicate, ctx, targetTriple)) continue;
m.buildConfig.cflags.insert(m.buildConfig.cflags.end(),
cc.cflags.begin(), cc.cflags.end());
m.buildConfig.cxxflags.insert(m.buildConfig.cxxflags.end(),
cc.cxxflags.begin(), cc.cxxflags.end());
m.buildConfig.ldflags.insert(m.buildConfig.ldflags.end(),
cc.ldflags.begin(), cc.ldflags.end());
for (auto const& s : cc.sources) {
m.buildConfig.sources.push_back(s);
m.modules.sources.push_back(s);
}
}
}

bool is_std_module(std::string_view name) {
return name == "std" || name == "std.compat";
}
Expand Down Expand Up @@ -717,18 +751,14 @@ prepare_build(bool print_fingerprint,
// flags append to buildConfig, mirroring the [profile] merge above.
if (!m->conditionalConfigs.empty()) {
auto cc_ctx = cfgpred::context_for(overrides.target_triple);
merge_conditional_build(*m, cc_ctx, overrides.target_triple);
for (auto const& cc : m->conditionalConfigs) {
if (!cfgpred::matches(cc.predicate, cc_ctx, overrides.target_triple))
continue;
m->buildConfig.cflags.insert(m->buildConfig.cflags.end(),
cc.cflags.begin(), cc.cflags.end());
m->buildConfig.cxxflags.insert(m->buildConfig.cxxflags.end(),
cc.cxxflags.begin(), cc.cxxflags.end());
m->buildConfig.ldflags.insert(m->buildConfig.ldflags.end(),
cc.ldflags.begin(), cc.ldflags.end());
// Conditional dependencies (Phase 1b): merge into the manifest maps
// before dependency resolution so they resolve like any dep. insert()
// keeps an existing unconditional entry (no silent override).
// Root-only — a dependency's own conditional deps are out of scope.
m->dependencies.insert(cc.dependencies.begin(), cc.dependencies.end());
m->devDependencies.insert(cc.devDependencies.begin(), cc.devDependencies.end());
m->buildDependencies.insert(cc.buildDependencies.begin(), cc.buildDependencies.end());
Expand Down Expand Up @@ -962,6 +992,16 @@ prepare_build(bool print_fingerprint,
// when its source/inputs/env change. Leaf-only: it cannot gate the top-level
// dependency graph. Skipped under a cross --target (host program, host run).
// See .agents/docs/2026-06-30-l3-build-mcpp-implementation-design.md.
// Root [generated_files]: materialize before build.mcpp and the modgraph
// scan so synthesized sources are globbed like any on-disk file. (The
// per-dependency call sits in the dep resolution loop below; the root
// manifest needs its own.)
if (!m->buildConfig.generatedFiles.empty()) {
if (auto r = materialize_generated_files(*root, *m); !r) {
return std::unexpected(r.error());
}
}

if (auto bp = mcpp::build::run_build_program(
*m, *root, explicit_compiler, *tc, m->cppStandard.canonical,
/*isCross=*/!overrides.target_triple.empty());
Expand Down Expand Up @@ -1563,6 +1603,15 @@ prepare_build(bool print_fingerprint,
"dependency '{}': {}", depName, r.error()));
}

// Dependency-side L1 cfg merge (flags + sources): a descriptor's
// `target_cfg` / a dep mcpp.toml's [target.'cfg(...)'.build] must
// evaluate here too — before its globs expand.
if (!manifest->conditionalConfigs.empty()) {
merge_conditional_build(*manifest,
cfgpred::context_for(overrides.target_triple),
overrides.target_triple);
}

return std::pair{effRoot, std::move(*manifest)};
};

Expand Down Expand Up @@ -2502,14 +2551,27 @@ prepare_build(bool print_fingerprint,
auto& bc = pkg.manifest.buildConfig;
if (!bc.featureSources.empty()) {
if (!includeDevDeps) {
std::set<std::string> gated;
// glob → owned by at least one ACTIVE feature?
std::set<std::string> activeNow(active.begin(), active.end());
std::map<std::string, bool> gated;
for (auto& [f, globs] : bc.featureSources)
for (auto& g : globs) gated.insert(g);
for (auto& g : globs)
gated[g] = gated[g] || activeNow.contains(f);
auto drop = [&](std::vector<std::string>& v) {
std::erase_if(v, [&](const std::string& s) { return gated.contains(s); });
};
drop(bc.sources);
drop(pkg.manifest.modules.sources);
// Dropping the glob STRING is not enough: files it matches
// may still be covered by a broader base glob (the default
// src/** — the mcpp.toml G5 case). An inactive gate becomes
// a `!` exclusion so the gate actually gates; active gates
// are re-added below.
for (auto& [g, isActive] : gated) {
if (isActive || g.starts_with("!")) continue;
bc.sources.push_back("!" + g);
pkg.manifest.modules.sources.push_back("!" + g);
}
}
std::set<std::string> activeSet(active.begin(), active.end());
auto add = [](std::vector<std::string>& v, const std::string& g) {
Expand Down
Loading
Loading