Skip to content

Commit 4e2b476

Browse files
committed
build.mcpp: a build program is told which package it is building
Found by self-review of the module surface. Every name a rule package generates is derived from the package's name -- the module a consumer imports, the namespace the accessors sit in, the symbols in a generated header -- and nothing in the build-program contract answered that question. The closest available answer was the leaf of MCPP_MANIFEST_DIR, which is a directory name. The two differ whenever a project lays a package out under a generic folder, and `mcpp.rules.spirv` was measurably wrong there: `examples/09-heterogeneous/vulkan/app/` declares `name = "vulkan-saxpy"` and generated `app.shaders`, so every `<something>/app/` in a workspace claimed one module. A user-facing name derived from a folder is also a name that changes when a folder is renamed. MCPP_PKG_NAME and MCPP_PKG_NAMESPACE, with `mcpp::package_name()` and `mcpp::package_namespace()` reading them. Set at both sites that build a BuildProgramEnv -- the dependency loop and the root; those two are the whole enumeration. The e2e distinguishes the two derivations by construction: the fixture's directory is `app` and its package is `vulkan-saxpy`, so an implementation that still read the directory writes `app` where the assertion expects `vulkan-saxpy`. A fixture whose name matched its folder would pass either way, which is what every existing one did.
1 parent 77d4918 commit 4e2b476

6 files changed

Lines changed: 113 additions & 0 deletions

File tree

docs/07-build-mcpp.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -659,6 +659,8 @@ The running program receives the build context as `MCPP_*` variables
659659
| `MCPP_CXX_STDLIB` *(2026.9.6.3+)* | `mcpp::cxx_stdlib()` | the C++ standard library the resolved toolchain uses — `libstdc++`, `libc++`, `msvc-stl`; empty when no toolchain resolved. A different question from `MCPP_TARGET_LIBC`, which is the C library |
660660
| `MCPP_ACCEL` *(2026.9.5.2+)* | `mcpp::accel()` | the device axis of this build, resolved — `--accel` / `--no-accel` over `[build] accel` — in the wire form `cuda12.9+{sm_89} ptx>=89`; empty when the build asks for no accelerator. A rule package derives its own flags (`-gencode`, `--offload-arch`) from it, so the architecture set is written once, in the manifest. The same value feeds the `cfg(accelerator = "…")` layer key |
661661
| `MCPP_LANGUAGE_MODULES` *(2026.9.7.1+)* | -- | `1` when the declaring package sets `[language] modules`, `0` otherwise. A rule that GENERATES a consumer-facing declaration reads it to choose between a module interface and a header, so a project states that once and never again. An older engine leaves it absent, which a rule reads as `0` -- the behaviour every consumer had before the variable existed |
662+
| `MCPP_PKG_NAME` *(2026.9.7.1+)* | -- | The `[package] name` of the package this program builds. Every name a rule generates is derived from it: the module a consumer imports, the namespace the accessors sit in, the symbols in a generated header. Before it existed the closest available answer was the leaf of `MCPP_MANIFEST_DIR`, which is a directory name -- so a package named `vulkan-saxpy` in a directory named `app` generated `app.shaders`, and every `<something>/app/` in a workspace claimed the same module. Absent under an older engine, which a rule reads as "use whatever you used before" |
663+
| `MCPP_PKG_NAMESPACE` *(2026.9.7.1+)* | -- | The `[package] namespace`. Empty when the package declares none. A rule that must produce a name unique across an index uses the pair rather than the name alone, because package identity is `(namespace, name)` |
662664
| `MCPP_DEVICE_SOURCES` *(2026.9.5.2+)* | `mcpp::device_sources()` | the device-kind sources (`.cu`, `.hip`, …) the package's effective `sources` match, package-root-relative, one per line; empty when there are none. The engine compiles none of them — the rule package this program imports turns each into an `mcpp::action`. Already narrowed: a `{ glob, accel }` entry the build does not cover contributes nothing, so `--no-accel` yields an empty list |
663665
| `MCPP_OUT_DIR` | `mcpp::out_dir()` | a writable scratch/output dir owned by mcpp |
664666
| `MCPP_MANIFEST_DIR` | `mcpp::manifest_dir()` | the package root (= CWD) |

docs/zh/07-build-mcpp.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -563,6 +563,8 @@ mcpp 会把它自己构建时用的**同一份** std 模块暂存过来,缓存
563563
| `MCPP_CXX_STDLIB` *(2026.9.6.3+)* | `mcpp::cxx_stdlib()` | 解析出的工具链使用的 C++ 标准库 —— `libstdc++``libc++``msvc-stl`;没有工具链解析时为空串。与 `MCPP_TARGET_LIBC` 不是同一个问题,后者是 C 库 |
564564
| `MCPP_ACCEL` *(2026.9.5.2+)* | `mcpp::accel()` | 本次构建的设备轴,已解析 —— `--accel` / `--no-accel` 优先于 `[build] accel` —— 线上形态 `cuda12.9+{sm_89} ptx>=89`;不要加速器时为空串。规则包从它推导自己的开关(`-gencode``--offload-arch`),架构集合因此只在 manifest 写一次。同一个值也喂给 `cfg(accelerator = "…")` 这个 layer 键 |
565565
| `MCPP_LANGUAGE_MODULES` *(2026.9.7.1+)* | -- | 声明它的那个包设了 `[language] modules` 时为 `1`,否则 `0`**生成**面向消费者声明的规则读它来在模块接口与头文件之间选择,项目因此只需说一次。旧引擎不设这个变量,规则把缺席读作 `0` —— 也就是这个变量存在之前每个消费者的行为 |
566+
| `MCPP_PKG_NAME` *(2026.9.7.1+)* | -- | 这个程序所构建的包的 `[package] name`。规则生成的每个名字都由它推导:消费者导入的模块、访问器所在的命名空间、生成头里的符号。在它存在之前,可用的最接近的答案是 `MCPP_MANIFEST_DIR` 的末段,那是目录名 —— 于是一个叫 `vulkan-saxpy` 的包放在名为 `app` 的目录下会生成 `app.shaders`,而工作区里每一个 `<something>/app/` 都声称拥有同一个模块。旧引擎下缺席,规则把缺席读作「沿用你之前用的那个」 |
567+
| `MCPP_PKG_NAMESPACE` *(2026.9.7.1+)* | -- | `[package] namespace`。包未声明命名空间时为空。需要产出在索引范围内唯一的名字的规则用这一对而不是单用名字,因为包身份是 `(namespace, name)` |
566568
| `MCPP_DEVICE_SOURCES` *(2026.9.5.2+)* | `mcpp::device_sources()` | 本包有效 `sources` 匹配到的设备类源文件(`.cu``.hip`…),相对包根,一行一个;没有时为空串。引擎一个都不编译 —— 由本程序引入的规则包把每一个变成一条 `mcpp::action`。已经过收窄:构建未覆盖的 `{ glob, accel }` 条目贡献为空,因此 `--no-accel` 得到空列表 |
567569
| `MCPP_OUT_DIR` | `mcpp::out_dir()` | mcpp 提供的可写输出/暂存目录 |
568570
| `MCPP_MANIFEST_DIR` | `mcpp::manifest_dir()` | 包根(= CWD) |

src/build/build_program.cppm

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,12 @@ struct BuildProgramEnv {
124124
// (`-gencode`, `--offload-arch`) from here and the architecture set is
125125
// written once, in the manifest, and never again in a build program.
126126
std::string accel;
127+
// The package this program is building, from `[package]`. Reported because
128+
// every name a rule generates is derived from it -- the module a consumer
129+
// imports, the namespace its accessors sit in -- and a build program had no
130+
// way to ask. See hostprogram::package_name for what it replaced.
131+
std::string packageName;
132+
std::string packageNamespace;
127133
// Whether this package builds C++ modules (`[language] modules`).
128134
//
129135
// Reported because a rule package that GENERATES a consumer-facing
@@ -522,6 +528,8 @@ contract_env(const fs::path& root, const fs::path& outDir, const BuildProgramEnv
522528
}
523529
e.emplace_back("MCPP_OUT_DIR", outDir.string());
524530
e.emplace_back("MCPP_MANIFEST_DIR", root.string());
531+
e.emplace_back("MCPP_PKG_NAME", env.packageName);
532+
e.emplace_back("MCPP_PKG_NAMESPACE", env.packageNamespace);
525533
std::string csv;
526534
for (auto const& f : env.features) {
527535
if (!csv.empty()) csv += ',';

src/build/hostprogram.cppm

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -375,6 +375,21 @@ inline const char* target_libc_profile() { return env_or("MCPP_TARGET_L
375375
inline const char* target_libc() { return env_or("MCPP_TARGET_LIBC"); }
376376
377377
inline const char* manifest_dir() { return env_or("MCPP_MANIFEST_DIR"); }
378+
// THE PACKAGE THIS PROGRAM IS BUILDING, BY NAME.
379+
//
380+
// A rule package that generates a consumer-facing declaration has to name it,
381+
// and every name it produces is derived from this one: the module a project
382+
// imports, the namespace the accessors sit in, the symbols in a generated
383+
// header. Before these existed the closest thing available was the leaf of
384+
// `manifest_dir()`, which is a directory name rather than a package name --
385+
// so a package called `vulkan-saxpy` in a directory called `app` generated
386+
// `app.shaders`, and every `<something>/app/` in a workspace claimed it.
387+
//
388+
// Empty under an engine older than 2026.9.7.1, which a rule reads as "fall
389+
// back to whatever you did before". That is what keeps an already-published
390+
// rule package working unchanged.
391+
inline const char* package_name() { return env_or("MCPP_PKG_NAME"); }
392+
inline const char* package_namespace() { return env_or("MCPP_PKG_NAMESPACE"); }
378393
inline bool has_feature(const char* name) {
379394
char buf[256] = "MCPP_FEATURE_";
380395
unsigned long o = 13;

src/build/prepare.cppm

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8133,6 +8133,8 @@ prepare_build(bool print_fingerprint,
81338133
// The DECLARING package's setting, not the root project's: a rule
81348134
// generating a declaration for this package must match how this
81358135
// package is compiled.
8136+
bpEnv.packageName = pkg.manifest.package.name;
8137+
bpEnv.packageNamespace = pkg.manifest.package.namespace_;
81368138
bpEnv.languageModules = pkg.manifest.language.modules;
81378139
bpEnv.ruleModules = pkg.manifest.buildConfig.ruleModules;
81388140
if (auto dit = deviceSourcesByPackage.find(pkg.root.string()); dit != deviceSourcesByPackage.end())
@@ -9059,6 +9061,8 @@ prepare_build(bool print_fingerprint,
90599061
bpEnv.toolsBin = projectSubosBin;
90609062
bpEnv.profile = effectiveProfile;
90619063
bpEnv.accel = resolvedAccel();
9064+
bpEnv.packageName = m->package.name;
9065+
bpEnv.packageNamespace = m->package.namespace_;
90629066
bpEnv.languageModules = m->language.modules;
90639067
bpEnv.ruleModules = m->buildConfig.ruleModules;
90649068
if (auto dit = deviceSourcesByPackage.find(root->string()); dit != deviceSourcesByPackage.end())
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
#!/usr/bin/env bash
2+
# requires: gcc
3+
# A BUILD PROGRAM IS TOLD WHICH PACKAGE IT IS BUILDING.
4+
#
5+
# Every name a rule package generates is derived from this one: the module a
6+
# consumer imports, the namespace the accessors sit in, the symbols in a
7+
# generated header. Until 2026.9.7.1 nothing answered it, and the closest thing
8+
# available was the leaf of `MCPP_MANIFEST_DIR` -- a DIRECTORY name.
9+
#
10+
# THE DIRECTORY AND THE PACKAGE ARE DELIBERATELY DIFFERENT HERE, and that is the
11+
# whole test. A fixture whose package name happens to equal its directory leaf
12+
# passes against both the old derivation and the new one, so it would assert
13+
# nothing. `mcpp.rules.spirv` shipped with exactly that defect: an example laid
14+
# out as `vulkan/app/` with `name = "vulkan-saxpy"` generated `app.shaders`, and
15+
# every `<something>/app/` in a workspace claimed the same module.
16+
#
17+
# The answer is written to a FILE rather than printed, because mcpp shows a
18+
# build program's stdout only when it exits non-zero -- a criterion reading the
19+
# build log would be measuring the failure path.
20+
set -e
21+
22+
TMP=$(mktemp -d)
23+
trap "rm -rf $TMP" EXIT
24+
cd "$TMP"
25+
26+
# The directory is `app`. The package is not.
27+
mkdir -p app/src
28+
cat > app/src/main.cpp <<'EOF'
29+
int main() { return 0; }
30+
EOF
31+
32+
cat > app/build.mcpp <<'EOF'
33+
#include <cstdio>
34+
#include <string>
35+
import mcpp;
36+
int main() {
37+
std::string out = std::string(mcpp::manifest_dir()) + "/answered.txt";
38+
std::FILE* f = std::fopen(out.c_str(), "w");
39+
if (f == nullptr) return 3;
40+
std::fprintf(f, "name=%s\n", mcpp::package_name());
41+
std::fprintf(f, "namespace=%s\n", mcpp::package_namespace());
42+
std::fclose(f);
43+
return 0;
44+
}
45+
EOF
46+
47+
cat > app/mcpp.toml <<'EOF'
48+
[package]
49+
name = "vulkan-saxpy"
50+
namespace = "example"
51+
version = "0.1.0"
52+
53+
[build]
54+
sources = ["src/*.cpp"]
55+
56+
[targets.vulkan-saxpy]
57+
kind = "bin"
58+
main = "src/main.cpp"
59+
EOF
60+
61+
cd app
62+
"${MCPP:-mcpp}" build > build.log 2>&1 || { echo "FAIL: build"; cat build.log; exit 1; }
63+
64+
[ -f answered.txt ] || { echo "FAIL: the build program wrote no answer"; exit 1; }
65+
cat answered.txt
66+
67+
grep -qx 'name=vulkan-saxpy' answered.txt || {
68+
echo "FAIL: package_name() did not answer the [package] name"
69+
echo " (a directory-derived answer would read 'app')"
70+
exit 1; }
71+
grep -qx 'namespace=example' answered.txt || {
72+
echo "FAIL: package_namespace() did not answer the [package] namespace"
73+
exit 1; }
74+
75+
# THE REVERSE LEG: the directory leaf is `app`, so an implementation that still
76+
# derived from the directory would have written `app` above. Assert the two are
77+
# actually different in this fixture, or the check above proves nothing.
78+
[ "$(basename "$PWD")" = "app" ] || {
79+
echo "FAIL: this fixture no longer distinguishes the two derivations"
80+
exit 1; }
81+
82+
echo "PASS: a build program reads its package identity, not its directory name"

0 commit comments

Comments
 (0)