Skip to content

Commit 0435c17

Browse files
committed
fix(manifest): Profile's dependency_linkage becomes two plain members
Windows CI could not compile `tests/unit/test_modgraph.cpp`: optional:262: error: no matching constructor for initialization of '_SMF_control<_Optional_construct_base<basic_string<char,...>>, ...>' reached through `Manifest` -> `std::map<std::string, Profile>` -> `Profile::dependencyLinkage`, an `std::optional<std::string>` DATA MEMBER of an exported struct. `TargetEntry::sysroot`'s comment in the same file already forbids that shape and prescribes the remedy -- two plain members -- after the same error on an earlier occasion. `Profile` was the last member in the module still shaped that way. Semantics are unchanged: not declared still means "whatever [build] said", which is what the optional carried and what `dependencyLinkageDeclared` carries now. WHICH EDIT IN THIS BRANCH TIPS IT IS NOT ESTABLISHED. Two diagnoses were refuted: that the new emptiness predicate's being an inline member was the cause (made it a free function; CI failed identically), and that the runner image had changed (re-ran main's own Windows job unchanged on today's image; it passed). `test_modgraph.cpp` copies a Manifest by value on main too, so the landmine was already armed; this removes the type that cannot be copied rather than the perturbation that reached it.
1 parent 9aa80d7 commit 0435c17

6 files changed

Lines changed: 74 additions & 14 deletions

File tree

.agents/docs/2026-09-09-two-answers-and-two-silences.md

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -457,8 +457,33 @@ existed. That is #258's medicine applied one level further out, and it closed a
457457
fourth instance found while writing it — `xpkg.cppm`'s gate omitted
458458
`privateIncludeDirs`, which its own loop fills.
459459

460-
**They are free functions, and that was forced by a measurement rather than
461-
chosen.** The first version made them members, which is the obvious shape and
460+
**A latent defect in this module had to be removed before any of it could
461+
land, and it is worth recording because the diagnosis went wrong twice.**
462+
Windows CI failed to compile `tests/unit/test_modgraph.cpp` with
463+
464+
optional:262: error: no matching constructor for initialization of
465+
'_SMF_control<_Optional_construct_base<basic_string<char,...>>, ...>'
466+
467+
reached through `Manifest` -> `std::map<std::string, Profile>` ->
468+
`Profile::dependencyLinkage`, an `std::optional<std::string>` DATA MEMBER of an
469+
exported struct. `TargetEntry::sysroot`'s comment, a few hundred lines above in
470+
the same file, already forbids that shape and gives the remedy — two plain
471+
members — after the same error on an earlier occasion. `Profile` was the last
472+
member in the module still shaped that way.
473+
474+
The first attempt blamed the new emptiness predicate for being an inline member
475+
of an exported struct, and made it a free function. CI failed identically, which
476+
refuted that. The second attempt blamed the runner image, and was refuted by
477+
re-running main's own Windows job unchanged on today's image: it passed. So the
478+
trigger is somewhere in this change and the cause is the member, and those are
479+
different questions. **Which edit tips it is not established here** — the honest
480+
statement is that any perturbation of this module's interface can, and that
481+
removing the type which cannot be copied removes the class rather than the
482+
instance. `test_modgraph.cpp` copies a `Manifest` by value on main too
483+
(`scan_packages({PackageRoot{dir, m}})`), so the landmine was always armed.
484+
485+
**The emptiness predicates are free functions, which was the first repair and is
486+
kept on its own merits.** The first version made them members, which is the obvious shape and
462487
the wrong one: adding an inline member to a struct this module exports changes
463488
what importers materialise from its BMI, and `Profile` carries a
464489
`std::optional<std::string>` that is already known to break under clang with the

CHANGELOG.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,22 @@ GCC 的接口拼法本就是纯语言,因此同一个工程在 GCC 上能构建
7171
第四处随之关闭:xpkg 的闸门漏掉了它自己会填的 `privateIncludeDirs`。索引里 228 份
7272
descriptor 用新旧两个二进制解析,结果逐字节相同。
7373

74+
### `Profile.dependency_linkage` 不再是 `std::optional<std::string>` 成员
75+
76+
导出结构体的 `std::optional<std::string>` **数据成员**会迫使本模块的接口实例化该特化的
77+
特殊成员机制,在 clang + MSVC 标准库下无法编译:
78+
79+
```
80+
optional:262: error: no matching constructor for initialization of
81+
'_SMF_control<_Optional_construct_base<basic_string<char,...>>, ...>'
82+
```
83+
84+
路径是 `Manifest` -> `std::map<std::string, Profile>` -> `Profile`。同一文件里
85+
`TargetEntry::sysroot` 的注释早已禁止这个形状并给出修法(两个普通成员),`Profile`
86+
模块里最后一个仍是该形状的成员。现按同一修法改为
87+
`dependencyLinkage` + `dependencyLinkageDeclared`,语义不变:未声明仍表示「沿用
88+
`[build]` 的值」。
89+
7490
### `[runtime]``[target.<谓词>.runtime]` 的未知键会被报出
7591

7692
不受支持的键被报出而不是丢弃,是 `[build]``[target.<triple>]`

modules/manifest/src/toml.cppm

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -705,8 +705,10 @@ std::expected<Manifest, ManifestError> parse_string(std::string_view content,
705705
if (v.is_string()) out.push_back(v.as_string());
706706
};
707707
if (auto it = tt.find("dependency_linkage");
708-
it != tt.end() && it->second.is_string())
708+
it != tt.end() && it->second.is_string()) {
709709
pr.dependencyLinkage = it->second.as_string();
710+
pr.dependencyLinkageDeclared = true;
711+
}
710712
read_list("cflags", pr.cflags);
711713
read_list("cxxflags", pr.cxxflags);
712714
read_list("ldflags", pr.ldflags);

modules/manifest/src/types.cppm

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1401,12 +1401,29 @@ struct Profile {
14011401
bool strip = false;
14021402
// `dependency_linkage`, per profile (#519).
14031403
//
1404-
// OPTIONAL, and that is load-bearing rather than stylistic: resolving a
1405-
// profile REPLACES the whole struct with the declared one, so a plain
1406-
// value would make `[profile.dev] opt = 0` silently reset a
1407-
// `[build] dependency_linkage = "shared"` back to the field default.
1408-
// Absent means "whatever [build] said".
1409-
std::optional<std::string> dependencyLinkage;
1404+
// DECLARED-OR-NOT IS LOAD-BEARING, and that is why there are two members
1405+
// rather than one: resolving a profile REPLACES the whole struct with the
1406+
// declared one, so a plain value alone would make `[profile.dev] opt = 0`
1407+
// silently reset a `[build] dependency_linkage = "shared"` back to the
1408+
// field default. Not declared means "whatever [build] said".
1409+
//
1410+
// TWO MEMBERS AND NOT AN `std::optional<std::string>`, for exactly the
1411+
// reason `TargetEntry::sysroot` gives above, and this was the last member
1412+
// in the module still shaped the way that note forbids. An
1413+
// `std::optional<std::string>` DATA MEMBER of an exported struct forces
1414+
// this module's interface to materialise that specialisation's
1415+
// special-member machinery, and under clang with the MSVC standard library
1416+
// it does not compile:
1417+
//
1418+
// optional:262: error: no matching constructor for initialization of
1419+
// '_SMF_control<_Optional_construct_base<basic_string<char,...>>, ...>'
1420+
//
1421+
// reached through `Manifest` -> `std::map<std::string, Profile>` ->
1422+
// `Profile`. The error names whichever translation unit happens to copy a
1423+
// Manifest -- `tests/unit/test_modgraph.cpp` is one -- and says nothing
1424+
// about the member that caused it.
1425+
std::string dependencyLinkage;
1426+
bool dependencyLinkageDeclared = false;
14101427
// Passthrough escape hatch (fixed keys, open values — I6 completeness):
14111428
std::vector<std::string> cflags;
14121429
std::vector<std::string> cxxflags;

src/build/prepare.cppm

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2071,8 +2071,8 @@ prepare_build(bool print_fingerprint,
20712071
// profile that does not mention it leaves `[build]` standing; a plain
20722072
// value would reset it, because the block above REPLACES `pr` wholesale
20732073
// with the declared profile.
2074-
if (pr.dependencyLinkage)
2075-
m->buildConfig.dependencyLinkage = *pr.dependencyLinkage;
2074+
if (pr.dependencyLinkageDeclared)
2075+
m->buildConfig.dependencyLinkage = pr.dependencyLinkage;
20762076
m->buildConfig.optLevel = pr.optLevel;
20772077
m->buildConfig.debug = pr.debug;
20782078
m->buildConfig.lto = pr.lto;

tests/unit/test_manifest.cpp

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3639,10 +3639,10 @@ dependency_linkage = "static"
36393639
)");
36403640
ASSERT_TRUE(m);
36413641
ASSERT_TRUE(m->profiles.contains("dev"));
3642-
EXPECT_FALSE(m->profiles.at("dev").dependencyLinkage.has_value());
3642+
EXPECT_FALSE(m->profiles.at("dev").dependencyLinkageDeclared);
36433643
ASSERT_TRUE(m->profiles.contains("fast"));
3644-
ASSERT_TRUE(m->profiles.at("fast").dependencyLinkage.has_value());
3645-
EXPECT_EQ(*m->profiles.at("fast").dependencyLinkage, "static");
3644+
ASSERT_TRUE(m->profiles.at("fast").dependencyLinkageDeclared);
3645+
EXPECT_EQ(m->profiles.at("fast").dependencyLinkage, "static");
36463646
}
36473647

36483648
TEST(Manifest, ADependencyEdgeLinkageIsAClosedVocabularyToo) {

0 commit comments

Comments
 (0)