diff --git a/docs/07-workspace.md b/docs/07-workspace.md index 89e3d7016..44d7c1804 100644 --- a/docs/07-workspace.md +++ b/docs/07-workspace.md @@ -290,6 +290,14 @@ share it are planned and compiled together. Before 2026.10.5.2 a virtual root's profiles reached no member and were ignored without a diagnostic, and a rooted workspace's applied only when its own package was the first one selected. +A selected member's `[target..build] dialect_cxxflags` remains a +graph-wide setting under that condition, including in a workspace plan. Matching +rows reach the standard-library module, scanning and every C++ translation unit; +nonmatching rows contribute nothing. Workspace grouping includes the predicates +and flag lists, so members with different declarations use separate plans. +Conditional sources and ordinary compile flags remain attributes of the member, +and an ordinary dependency's dialect flags do not change its consumer's graph. + **The root package is a member.** In a workspace whose root carries `[package]`, the root package receives `[workspace.package]` and `[workspace.build]` once, as every other member does (2026.10.5.2+), so its diff --git a/docs/zh/07-workspace.md b/docs/zh/07-workspace.md index a9e3e05fb..dfdca12d5 100644 --- a/docs/zh/07-workspace.md +++ b/docs/zh/07-workspace.md @@ -230,6 +230,12 @@ c++23;什么都不写的成员得到 c++26。这两种情况值相同而意图 显式选择加入,因为依赖是解析图上的一条边:隐式继承一条边,会在成员自己的 manifest 只字未提的情况下改变它解析到什么。 +成员被选中构建时,`[target..build] dialect_cxxflags` 在工作空间计划中 +仍然是带条件的全图设置。匹配的条件会作用于标准库模块、扫描和所有 C++ 编译单元, +不匹配的条件不贡献参数。工作空间分组包含条件及参数列表,因此声明不同的成员使用 +不同计划。条件源文件和普通编译参数仍属于成员自身;普通依赖的方言参数不会改变 +消费者的模块图。 + **追加的向量能覆盖什么。** 成员的词在命令行上排在工作空间的词之后。编译器按 "后者胜出"处理的标志因此可以通过重写来覆盖:`-fno-exceptions` 之后的 `-fexceptions`、`-Wx` 之后的 `-Wno-x`、`-O0` 之后的 `-O2`。头文件目录按顺序搜索, diff --git a/src/project.cppm b/src/project.cppm index 0906b2d12..583d15b21 100644 --- a/src/project.cppm +++ b/src/project.cppm @@ -765,6 +765,14 @@ export std::string root_position_key(const mcpp::manifest::Manifest& m) { field("standard", m.package.standard); auto const& b = m.buildConfig; list("dialect_cxxflags", b.dialectCxxflags); + // Conditional dialect flags are root-position values too. Grouping runs + // before the target is resolved, so retain the predicate and declaration + // order rather than evaluating a member's rows against the host. + for (auto const& cc : m.conditionalConfigs) { + if (cc.dialectCxxflags.empty()) continue; + field("dialect_predicate", cc.predicate); + list("conditional_dialect_cxxflags", cc.dialectCxxflags); + } field("cxx_runtime", b.cxxRuntime); field("cxx_runtime_tests", b.cxxRuntimeTests); field("cxx_runtime_shared", b.cxxRuntimeShared); @@ -846,6 +854,16 @@ virtual_workspace_root(const mcpp::manifest::Manifest& workspace, b.cxxRuntimeShared = f.cxxRuntimeShared; b.target = f.target; b.dialectCxxflags = f.dialectCxxflags; + // The virtual root is created before conditional merging. Carry only the + // member's graph-wide dialect declarations: its sources, flags, runtime + // files and dependencies still belong to the member's own node (#786). + for (auto const& cc : first.conditionalConfigs) { + if (cc.dialectCxxflags.empty()) continue; + mcpp::manifest::ConditionalConfig row; + row.predicate = cc.predicate; + row.dialectCxxflags = cc.dialectCxxflags; + v.conditionalConfigs.push_back(std::move(row)); + } b.abiThreads = f.abiThreads; b.abiThreadsDeclared = f.abiThreadsDeclared; b.abiExceptions = f.abiExceptions; diff --git a/tests/e2e/890_workspace_conditional_dialect.sh b/tests/e2e/890_workspace_conditional_dialect.sh new file mode 100644 index 000000000..c4fe2120e --- /dev/null +++ b/tests/e2e/890_workspace_conditional_dialect.sh @@ -0,0 +1,100 @@ +#!/usr/bin/env bash +# requires: unix-shell +# #786: a selected member's architecture-specific dialect reaches the virtual +# workspace root, std precompilation and every TU. Different declarations must +# separate workspace plans; an ordinary dependency cannot change the dialect. +set -e +source "$(dirname "$0")/_host_path.sh" + +TMP=$(mktemp -d) +trap 'rm -rf "$TMP"' EXIT +fail() { echo "FAIL: $1"; shift; for f in "$@"; do echo "--- $f ---"; cat "$f" 2>/dev/null; done; exit 1; } +MCPP="${MCPP:-mcpp}" +REGISTRY_HOST=$(host_path "${MCPP_HOME:-$HOME/.mcpp}/registry") +export MCPP_HOME="$TMP/mcpp-home" +mkdir -p "$MCPP_HOME" +cat > "$MCPP_HOME/config.toml" < mcpp.toml <<'TOML' +[workspace] +members = ["app", "other"] +[workspace.build] +dialect_cxxflags = ["-DBASE786"] +TOML +cat > dep/mcpp.toml < dep/dep.cppm +write_member() { + local name="$1" value="$2" + cat > "$name/mcpp.toml" < "$name/main.cpp" < selected.log 2>&1 || fail "selected-member build lost its dialect" selected.log +grep -q -- '-DARCH786=1' compile_commands.json || fail "CDB lost the conditional dialect" compile_commands.json +grep -rq -- '-DARCH786=1' "$MCPP_HOME/build-cache/v1/std" || fail "std precompile lost the conditional dialect" selected.log +if grep -q -- '-DNONMATCH786\|-DDEP_ONLY786' compile_commands.json; then + fail "nonmatching/dependency dialect reached compiler commands" compile_commands.json +fi + +cd app +"$MCPP" build > inside.log 2>&1 || fail "building inside a member lost its dialect" inside.log +cd .. +"$MCPP" build --workspace > workspace.log 2>&1 || fail "workspace grouping conflated distinct dialects" workspace.log +graphs=$(find target -name build.ninja | wc -l | tr -d ' ') +[ "$graphs" = 2 ] || fail "expected two configurations for different member dialects, got $graphs" workspace.log +grep -rq -- '-DARCH786=2' "$MCPP_HOME/build-cache/v1/std" || fail "second member reused the first member's std configuration" workspace.log + +echo "PASS: 890 workspace conditional dialect" diff --git a/tests/unit/test_workspace_plan.cpp b/tests/unit/test_workspace_plan.cpp index 297e93698..0a08c1c96 100644 --- a/tests/unit/test_workspace_plan.cpp +++ b/tests/unit/test_workspace_plan.cpp @@ -91,6 +91,50 @@ TEST(WorkspacePlan, TheVirtualRootHoldsNoPackageContent) { EXPECT_EQ(mcpp::project::root_position_key(v), mcpp::project::root_position_key(first)); } +TEST(WorkspacePlan, ConditionalDialectFlagsSeparateConfigurations) { + auto a = member("a"); + auto b = member("b"); + mcpp::manifest::ConditionalConfig row; + row.predicate = "i686-windows-msvc"; + row.dialectCxxflags = {"-DARCH_FLAG=1"}; + a.conditionalConfigs.push_back(row); + EXPECT_NE(mcpp::project::root_position_key(a), mcpp::project::root_position_key(b)); + b.conditionalConfigs.push_back(row); + EXPECT_EQ(mcpp::project::root_position_key(a), mcpp::project::root_position_key(b)); + b.conditionalConfigs.front().inputs.cxxflags = {"-DMEMBER_ONLY"}; + b.conditionalConfigs.front().inputs.sources = {"arch.cpp"}; + EXPECT_EQ(mcpp::project::root_position_key(a), mcpp::project::root_position_key(b)); + b.conditionalConfigs.front().dialectCxxflags = {"-DARCH_FLAG=2"}; + EXPECT_NE(mcpp::project::root_position_key(a), mcpp::project::root_position_key(b)); + b.conditionalConfigs.front().dialectCxxflags = row.dialectCxxflags; + b.conditionalConfigs.front().predicate = "x86_64-windows-msvc"; + EXPECT_NE(mcpp::project::root_position_key(a), mcpp::project::root_position_key(b)); +} + +TEST(WorkspacePlan, VirtualRootPreservesOnlyConditionalDialectDeclarations) { + mcpp::manifest::Manifest ws; + auto first = member("a"); + mcpp::manifest::ConditionalConfig row; + row.predicate = "i686-windows-msvc"; + row.dialectCxxflags = {"-DARCH_FLAG"}; + row.inputs.cxxflags = {"-DMEMBER_ONLY"}; + row.inputs.sources = {"arch.cpp"}; + row.libraries = {"member_runtime"}; + first.conditionalConfigs.push_back(row); + mcpp::manifest::ConditionalConfig packageOnly; + packageOnly.predicate = "windows"; + packageOnly.inputs.cxxflags = {"-DOTHER_MEMBER_FLAG"}; + first.conditionalConfigs.push_back(packageOnly); + const auto v = mcpp::project::virtual_workspace_root(ws, first, "/ws"); + ASSERT_EQ(v.conditionalConfigs.size(), 1u); + EXPECT_EQ(v.conditionalConfigs.front().predicate, row.predicate); + EXPECT_EQ(v.conditionalConfigs.front().dialectCxxflags, row.dialectCxxflags); + EXPECT_TRUE(v.conditionalConfigs.front().inputs.cxxflags.empty()); + EXPECT_TRUE(v.conditionalConfigs.front().inputs.sources.empty()); + EXPECT_TRUE(v.conditionalConfigs.front().libraries.empty()); + EXPECT_EQ(mcpp::project::root_position_key(v), mcpp::project::root_position_key(first)); +} + // A member's products are in `bin//`, qualified when another // member of the workspace has the same name; the workspace's own package // keeps `bin/`.