Skip to content

Commit 631c7fd

Browse files
committed
refactor(build): L4 — split prepare_inputs out of the 6521-line prepare.cppm
`prepare.cppm` 6521 行、编译 16.4s,是本仓库关键路径上唯一的真离群点。 抽出 `mcpp.build.prepare_inputs`(341 行):cfg() 谓词求值 + 指纹的两个规范化函数 —— 它们对 prepare 的其余部分零依赖,是这个文件里唯一自足的部分。 prepare 6521 → 6186 行;两个函数 **re-export**,所以没有任何调用方需要改 (一次拆分如果唯一可见的效果是别的文件编不过,那不叫改进)。 ⚠️ **实测:构建时间没有变化**(off 79.23s / on 34.54s,与拆分前一致)。 这与拆分前的分析一致,值得写下来而不是含糊过去: **抽出来的东西成了 prepare 的依赖,链只会变长不会变短。** `… → prepare_inputs → prepare → …` 仍然串行,prepare 少掉的成本正好由新模块付掉。 要缩短关键路径,抽出的部分必须是 prepare 的**兄弟** —— 被 prepare 的**导入者** 直接使用。调查过:configure 只用 `BuildContext`,execute 用 `BuildContext` + `prepare_build`,doctor/pack/cli.cmd_build 只用 `prepare_build`; 而链上是 prepare → execute → configure,execute 需要 `prepare_build`, 所以把类型抽走也不会让谁离开这条链。真正有效的是拆 `prepare_build` 本身。 L2 落地之后这件事的收益又小了一截:一个接口现在只阻塞导入者约 22% 的编译时间, 而不是全部。所以这次拆分按**架构**理由留下(6500 行的模块本就该拆), 不按性能理由 —— 性能上它是零。
1 parent 34cd72c commit 631c7fd

2 files changed

Lines changed: 381 additions & 341 deletions

File tree

src/build/prepare.cppm

Lines changed: 6 additions & 341 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,12 @@ module;
99

1010
export module mcpp.build.prepare;
1111

12+
// The cfg() predicate evaluator and the fingerprint canonicalisers moved out —
13+
// see mcpp.build.prepare_inputs. Re-exported so every existing caller of
14+
// `target_dir` / `canonical_compile_flags` keeps working: a split whose only
15+
// visible effect is that other files stop compiling is not an improvement.
16+
export import mcpp.build.prepare_inputs;
17+
1218
import std;
1319
import mcpp.diag;
1420
import mcpp.home;
@@ -98,347 +104,6 @@ inline void warn_unknown_xpkg_keys(const mcpp::manifest::Manifest& dm,
98104
}
99105
}
100106

101-
// ── L1 platform-conditional config: cfg() predicate evaluation ──────────────
102-
// Context = the RESOLVED target's coordinates. A `[target.'cfg(...)'.build]`
103-
// predicate is evaluated against this (target triple for a cross build, host
104-
// for a native build), so conditional flags follow what the binary will run on
105-
// — not the build host. See the manifest design doc.
106-
namespace cfgpred {
107-
108-
struct Ctx { std::string os, arch, family, env; };
109-
110-
// Derive the cfg context from the resolved --target triple, falling back to
111-
// the host for a native build. Parsing goes through triple.cppm — the single
112-
// triple parser — so the cfg vocabulary IS the canonical triple vocabulary
113-
// (os: linux|macos|windows, arch: GNU spellings, env: gnu|musl|msvc), and
114-
// alias spellings ("x86_64-w64-mingw32") evaluate identically to canonical.
115-
inline Ctx context_for(std::string_view targetTriple) {
116-
namespace triple = mcpp::toolchain::triple;
117-
Ctx c;
118-
auto t = targetTriple.empty()
119-
? std::optional<triple::Triple>(triple::host_triple())
120-
: triple::parse(targetTriple);
121-
if (t) {
122-
c.os = t->os;
123-
c.arch = t->arch;
124-
c.env = t->env;
125-
c.family = t->family();
126-
} else {
127-
// Escape-hatch triple outside the language: only the leading arch
128-
// segment is derivable; other dimensions stay empty (never match).
129-
auto dash = targetTriple.find('-');
130-
c.arch = std::string(dash == std::string_view::npos ? targetTriple
131-
: targetTriple.substr(0, dash));
132-
}
133-
return c;
134-
}
135-
136-
// Recursive-descent evaluator over the inside of `cfg(...)`:
137-
// expr := all(list) | any(list) | not(expr) | key="value" | bareword
138-
// key ∈ {os, arch, family, env} bareword ∈ {windows, unix, linux, macos}
139-
struct Parser {
140-
std::string_view s; std::size_t i = 0; const Ctx& c;
141-
void ws() { while (i < s.size() && std::isspace((unsigned char)s[i])) ++i; }
142-
bool eat(char ch) { ws(); if (i < s.size() && s[i] == ch) { ++i; return true; } return false; }
143-
std::string ident() {
144-
ws(); std::size_t b = i;
145-
while (i < s.size() && (std::isalnum((unsigned char)s[i]) || s[i] == '_')) ++i;
146-
return std::string(s.substr(b, i - b));
147-
}
148-
std::string str() {
149-
ws(); if (i >= s.size() || s[i] != '"') return {};
150-
++i; std::size_t b = i; while (i < s.size() && s[i] != '"') ++i;
151-
auto v = std::string(s.substr(b, i - b)); if (i < s.size()) ++i; return v;
152-
}
153-
bool match_alias(const std::string& a) {
154-
if (a == "windows") return c.os == "windows";
155-
if (a == "linux") return c.os == "linux";
156-
if (a == "macos") return c.os == "macos";
157-
if (a == "unix") return c.family == "unix";
158-
return false; // unknown bareword → no match
159-
}
160-
bool match_kv(const std::string& k, const std::string& v) {
161-
if (k == "os") return c.os == v;
162-
if (k == "arch") return c.arch == v;
163-
if (k == "family") return c.family == v;
164-
if (k == "env") return c.env == v;
165-
return false;
166-
}
167-
bool expr() {
168-
std::string id = ident();
169-
if (id == "all" || id == "any") {
170-
eat('(');
171-
bool acc = (id == "all");
172-
ws();
173-
if (!(i < s.size() && s[i] == ')')) {
174-
do { bool r = expr(); acc = (id == "all") ? (acc && r) : (acc || r); }
175-
while (eat(','));
176-
}
177-
eat(')');
178-
return acc;
179-
}
180-
if (id == "not") { eat('('); bool r = expr(); eat(')'); return !r; }
181-
ws();
182-
if (i < s.size() && s[i] == '=') { ++i; return match_kv(id, str()); }
183-
return match_alias(id);
184-
}
185-
};
186-
187-
// Evaluate a `[target.<predicate>]` key. Returns the cfg() result, or — for a
188-
// non-cfg key (a bare triple) — an exact match against the resolved triple.
189-
inline bool matches(const std::string& predicate, const Ctx& c, std::string_view triple) {
190-
std::string_view k = predicate;
191-
if (k.starts_with("cfg(") && k.ends_with(")")) {
192-
Parser p{ k.substr(4, k.size() - 5), 0, c };
193-
return p.expr();
194-
}
195-
// Bare OS/family alias sugar: `[target.linux]` ≡ `[target.'cfg(linux)']`.
196-
// These aliases are never valid triples (no dash), so there is no ambiguity
197-
// with the exact-triple namespace. Evaluated as the cfg bareword.
198-
if (predicate == "windows" || predicate == "linux" ||
199-
predicate == "macos" || predicate == "unix") {
200-
Parser p{ predicate, 0, c };
201-
return p.expr();
202-
}
203-
// Bare-triple match, spelling-independent: a `[target.x86_64-w64-mingw32]`
204-
// key matches a resolved `x86_64-windows-gnu` build (and vice versa) —
205-
// both normalize through triple::parse. Unparseable keys (the explicit-
206-
// section escape hatch) fall back to exact string comparison.
207-
if (triple.empty()) return false;
208-
if (auto p = mcpp::toolchain::triple::parse(predicate)) {
209-
if (auto rt = mcpp::toolchain::triple::parse(triple))
210-
return p->str() == rt->str();
211-
}
212-
return predicate == triple;
213-
}
214-
215-
} // namespace cfgpred
216-
217-
export std::filesystem::path target_dir(const mcpp::toolchain::Toolchain& tc,
218-
const mcpp::toolchain::Fingerprint& fp,
219-
const std::filesystem::path& root)
220-
{
221-
// Canonical triple names the output directory (D1: `target/
222-
// x86_64-windows-gnu/`, not the GNU spelling the compiler reports via
223-
// -dumpmachine) — alias inputs land in the same directory. Triples
224-
// outside the language keep their raw spelling.
225-
auto triple = tc.targetTriple.empty() ? std::string{"unknown"} : tc.targetTriple;
226-
if (auto t = mcpp::toolchain::triple::parse(triple)) triple = t->str();
227-
return root / "target" / triple / fp.hex;
228-
}
229-
230-
231-
// Compose a stable canonical compile-flags string for fingerprinting.
232-
// Exported so the "every build-variant knob is in here" invariant is machine-
233-
// checkable: the profile knobs were absent for a long time precisely because
234-
// nothing could assert on this string.
235-
export std::string canonical_compile_flags(const mcpp::manifest::Manifest& m) {
236-
std::string s;
237-
s += "-std="; s += m.package.standard;
238-
s += " -fmodules";
239-
// macOS deployment target changes the effective compile triple
240-
// (arm64-apple-macosxNN) — a std.pcm built for one target cannot be
241-
// loaded by a TU compiled for another. Fold the resolved value
242-
// (env override > [build] macos_deployment_target manifest default)
243-
// into the fingerprint so switching targets rebuilds the BMI cache
244-
// instead of dying with a module config mismatch.
245-
//
246-
// The built-in default floor (rustc-style) lives in the single
247-
// resolver (platform::macos::deployment_target), so this rule, the
248-
// flags and the std-module prebuild always agree — the 0.0.50-era
249-
// attempt to inject a default here alone left the test build's
250-
// std.pcm unstaged (import std failed wholesale on macos CI).
251-
if constexpr (mcpp::platform::is_macos) {
252-
auto dtv = mcpp::platform::macos::deployment_target(
253-
m.buildConfig.macosDeploymentTarget);
254-
if (!dtv.empty()) {
255-
s += " macos_deployment_target=";
256-
s += dtv;
257-
}
258-
}
259-
if (!m.buildConfig.cStandard.empty()) {
260-
s += " c_standard=";
261-
s += m.buildConfig.cStandard;
262-
}
263-
for (auto const& flag : m.buildConfig.cflags) {
264-
s += " cflag:";
265-
s += flag;
266-
}
267-
for (auto const& flag : m.buildConfig.cxxflags) {
268-
s += " cxxflag:";
269-
s += flag;
270-
}
271-
// Explicit [build] dialect_cxxflags (auto-promoted ones are already in
272-
// cxxflags above) — they change every BMI in the graph.
273-
for (auto const& flag : m.buildConfig.dialectCxxflags) {
274-
s += " dialect:";
275-
s += flag;
276-
}
277-
for (auto const& flag : m.buildConfig.ldflags) {
278-
s += " ldflag:";
279-
s += flag;
280-
}
281-
// Per-glob flags (G4): full ordered serialization — glob + every list —
282-
// so editing any entry (or reordering) re-fingerprints the output dir.
283-
for (auto const& gf : m.buildConfig.globFlags) {
284-
s += " globflags:"; s += gf.glob;
285-
for (auto const& f : gf.cflags) { s += " gc:"; s += f; }
286-
for (auto const& f : gf.cxxflags) { s += " gxx:"; s += f; }
287-
for (auto const& f : gf.asmflags) { s += " gas:"; s += f; }
288-
for (auto const& f : gf.defines) { s += " gd:"; s += f; }
289-
}
290-
// [build] module_extensions changes WHICH FILES ARE MODULE INTERFACES,
291-
// i.e. the shape of the graph: which units emit a BMI, which objects link
292-
// unconditionally, which ninja rule each unit gets. That is a build
293-
// variant, so it belongs in the fingerprint — mcpp.toml's mtime alone only
294-
// protects the fast path within one output dir, not the BMI cache.
295-
//
296-
// Contrast [build] build_program_timeout, which is deliberately absent:
297-
// it changes no edge. See BuildConfig::buildProgramTimeoutSecs.
298-
for (auto const& e : m.buildConfig.moduleExtensions) {
299-
s += " modext:";
300-
s += e;
301-
}
302-
// The resolved [profile] knobs. These are NOT in cflags/cxxflags: the
303-
// profile block (see the profile resolution below) lands them in
304-
// buildConfig.optLevel/debug/lto/strip and flags.cppm turns them into
305-
// -O<n>/-g/-flto at command-construction time. Leaving them out made
306-
// `--dev`, `--release` and `--profile dist` share ONE fingerprint, hence
307-
// one target/<triple>/<fp>/ directory AND one global cache entry — so a
308-
// release build could be served -O0 -g dependency objects. They are
309-
// build-variant by definition; they belong here.
310-
s += " opt="; s += m.buildConfig.optLevel;
311-
s += " debug="; s += m.buildConfig.debug ? "1" : "0";
312-
s += " lto="; s += m.buildConfig.lto ? "1" : "0";
313-
s += " strip="; s += m.buildConfig.strip ? "1" : "0";
314-
return s;
315-
}
316-
317-
std::string canonical_package_build_metadata(
318-
const std::vector<mcpp::modgraph::PackageRoot>& packages)
319-
{
320-
std::string s;
321-
for (auto const& pkg : packages) {
322-
s += "\npackage:";
323-
s += pkg.manifest.package.namespace_;
324-
s += "/";
325-
s += pkg.manifest.package.name;
326-
s += "@";
327-
s += pkg.manifest.package.version;
328-
s += " source=";
329-
s += pkg.manifest.package.sourceProvenance;
330-
auto const& runtime = pkg.manifest.runtimeConfig;
331-
for (auto const& requirement : runtime.requirements) {
332-
s += " runtime-need:";
333-
s += requirement.kind;
334-
s += ':';
335-
s += requirement.value;
336-
s += ':';
337-
s += requirement.phase;
338-
s += requirement.required ? ":required" : ":optional";
339-
}
340-
for (auto const& artifact : runtime.artifacts) {
341-
s += " runtime-artifact:";
342-
s += artifact.role;
343-
s += ':';
344-
s += artifact.path.generic_string();
345-
s += ':';
346-
s += artifact.provenance;
347-
s += ':';
348-
s += artifact.abi;
349-
s += ':';
350-
s += artifact.digest;
351-
s += ':';
352-
s += artifact.hostFingerprint;
353-
}
354-
for (auto const& value : runtime.linkIntent.libraries)
355-
s += " link-library:" + value;
356-
for (auto const& value : runtime.linkIntent.linkLibraryDirs)
357-
s += " link-dir:" + value.generic_string();
358-
for (auto const& value : runtime.linkIntent.transitiveNeededDirs)
359-
s += " needed-dir:" + value.generic_string();
360-
for (auto const& value : runtime.linkIntent.runtimeSearchDirs)
361-
s += " runtime-dir:" + value.generic_string();
362-
for (auto const& value : runtime.linkIntent.frameworks)
363-
s += " framework:" + value;
364-
for (auto const& value : runtime.linkIntent.deployFiles)
365-
s += " deploy:" + value.generic_string();
366-
// Legacy fields remain fingerprinted while they are readable.
367-
for (auto const& value : runtime.libraryDirs)
368-
s += " legacy-runtime-dir:" + value.generic_string();
369-
for (auto const& value : runtime.dlopenLibs)
370-
s += " legacy-soname:" + value;
371-
for (auto const& value : runtime.capabilities)
372-
s += " legacy-capability:" + value;
373-
for (auto const& value : runtime.provides)
374-
s += " legacy-provides:" + value;
375-
for (auto const& [capability, provider] : runtime.providerOverrides)
376-
s += " provider-override:" + capability + '=' + provider;
377-
if (!pkg.manifest.buildConfig.cStandard.empty()) {
378-
s += " c_standard=";
379-
s += pkg.manifest.buildConfig.cStandard;
380-
}
381-
for (auto const& flag : pkg.manifest.buildConfig.cflags) {
382-
s += " cflag:";
383-
s += flag;
384-
}
385-
for (auto const& flag : pkg.manifest.buildConfig.cxxflags) {
386-
s += " cxxflag:";
387-
s += flag;
388-
}
389-
for (auto const& flag : pkg.manifest.buildConfig.ldflags) {
390-
s += " ldflag:";
391-
s += flag;
392-
}
393-
// Per-glob flags — same full ordered serialization as the root-side
394-
// block above. Until #253 dependency globFlags were unfingerprinted
395-
// (held only by "descriptor frozen per version" + "feature toggles
396-
// always change cflags via -DMCPP_FEATURE_*"); feature-folded entries
397-
// make the vector build-variant, so fingerprint it directly.
398-
// featureOrigin is diagnostic-only and deliberately NOT serialized
399-
// (the active feature set is already in cflags above).
400-
for (auto const& gf : pkg.manifest.buildConfig.globFlags) {
401-
s += " globflags:"; s += gf.glob;
402-
for (auto const& f : gf.cflags) { s += " gc:"; s += f; }
403-
for (auto const& f : gf.cxxflags) { s += " gxx:"; s += f; }
404-
for (auto const& f : gf.asmflags) { s += " gas:"; s += f; }
405-
for (auto const& f : gf.defines) { s += " gd:"; s += f; }
406-
}
407-
// Same reason as the root block, and it cannot be skipped on the
408-
// grounds that "a descriptor is frozen per version": path and git
409-
// dependencies are not frozen, and this key changes their products.
410-
for (auto const& e : pkg.manifest.buildConfig.moduleExtensions) {
411-
s += " modext:";
412-
s += e;
413-
}
414-
if (pkg.usageResolved) {
415-
for (auto const& dir : pkg.privateBuild.includeDirs) {
416-
s += " private_include:";
417-
s += dir.generic_string();
418-
}
419-
for (auto const& dir : pkg.publicUsage.includeDirs) {
420-
s += " public_include:";
421-
s += dir.generic_string();
422-
}
423-
for (auto const& dir : pkg.privateBuild.includeDirsAfter) {
424-
s += " private_include_after:";
425-
s += dir.generic_string();
426-
}
427-
for (auto const& dir : pkg.publicUsage.includeDirsAfter) {
428-
s += " public_include_after:";
429-
s += dir.generic_string();
430-
}
431-
}
432-
for (auto const& [path, content] : pkg.manifest.buildConfig.generatedFiles) {
433-
s += " genfile:";
434-
s += path.generic_string();
435-
s += "=";
436-
s += content;
437-
}
438-
}
439-
return s;
440-
}
441-
442107
std::expected<void, std::string>
443108
materialize_generated_files(const std::filesystem::path& root,
444109
const mcpp::manifest::Manifest& manifest)

0 commit comments

Comments
 (0)