Skip to content

Commit 0b8b81b

Browse files
committed
refactor: remove lib-root module name enforcement
The build tool no longer enforces that the lib root's `export module` name must match [package].namespace + name. Module naming is the library author's responsibility, not the build tool's. Retained checks (structural correctness): - lib root file must exist (error if [lib].path set, warning if convention) - lib root must export a primary module (no partition suffix) - forbidden top-level module names (std, core, util, etc.) still rejected Removed checks: - module name must be prefixed by package name - module name must equal namespace.name exactly
1 parent 1f6ad76 commit 0b8b81b

2 files changed

Lines changed: 35 additions & 65 deletions

File tree

src/modgraph/validate.cppm

Lines changed: 24 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -72,23 +72,18 @@ ValidateReport validate(const Graph& g,
7272
// Each SourceUnit carries its OWN package name (set by the scanner) so this
7373
// works transparently for multi-package builds (path deps): each unit is
7474
// validated against its own manifest's package name, not the primary's.
75+
// 0.0.10+: module naming is the library author's choice. The build tool
76+
// no longer enforces that module names must be prefixed by the package
77+
// name. Only the forbidden-top-level-name check is retained to avoid
78+
// collisions with well-known module names (std, core, etc.).
7579
for (auto& u : g.units) {
7680
if (!u.provides) continue;
7781
const auto& m = u.provides->logicalName;
7882
auto base = m.substr(0, m.find(':')); // strip partition suffix
79-
const auto& pkg_name = u.packageName; // ← unit's own package
80-
const bool is_public = is_public_package_name(pkg_name);
81-
82-
if (is_public) {
83-
if (base != pkg_name && !base.starts_with(pkg_name + ".")) {
84-
r.errors.push_back({u.path,
85-
std::format("public module '{}' must be prefixed by package name '{}'",
86-
m, pkg_name)});
87-
}
88-
if (is_forbidden_top_module(base)) {
89-
r.errors.push_back({u.path,
90-
std::format("module '{}' uses a forbidden top-level name (core/util/common/...)", m)});
91-
}
83+
84+
if (is_forbidden_top_module(base)) {
85+
r.errors.push_back({u.path,
86+
std::format("module '{}' uses a forbidden top-level name (core/util/common/...)", m)});
9287
}
9388
}
9489

@@ -124,12 +119,14 @@ ValidateReport validate(const Graph& g,
124119

125120
// 2.5 Lib-root convention (M5.x+).
126121
//
127-
// For projects that ship a `kind = "lib"` target, expect a primary
128-
// module-interface file at either `[lib].path` (explicit override) or
129-
// `src/<package-tail>.cppm` (default convention). The file must
130-
// declare `export module <full-package-name>;` (no partition suffix);
131-
// partitions go in sibling files and are aggregated by re-exporting
132-
// from the lib root, à la `lib.rs` in cargo.
122+
// For projects that ship a `kind = "lib"` target, check that the
123+
// lib-root file exists. The lib root is either `[lib].path` (explicit
124+
// override) or `src/<package-tail>.cppm` (default convention).
125+
//
126+
// 0.0.10+: the module name exported by the lib root is NOT required
127+
// to match [package].namespace + name. The library author decides
128+
// the module name; the build tool auto-detects it via the scanner.
129+
// Only structural correctness is enforced (file exists, no partition).
133130
//
134131
// Pure-binary projects (mcpp itself, scaffolded `mcpp new`) skip this
135132
// check — they have no lib-root concept.
@@ -147,13 +144,9 @@ ValidateReport validate(const Graph& g,
147144
const bool exists = std::filesystem::exists(lib_root_abs, ec);
148145
if (!exists) {
149146
if (was_explicit) {
150-
// Explicit `[lib].path` pointing at a missing file is
151-
// always an error.
152147
r.errors.push_back({lib_root_rel, std::format(
153148
"[lib].path '{}' does not exist", lib_root_rel.string())});
154149
} else {
155-
// Convention miss is a warning — gives existing projects
156-
// a soft on-ramp before they rename / move files.
157150
r.warnings.push_back({lib_root_rel, std::format(
158151
"lib target without conventional lib root '{}' "
159152
"(create the file or set [lib].path)",
@@ -162,13 +155,11 @@ ValidateReport validate(const Graph& g,
162155
}
163156
}
164157

165-
// Even without on-disk verification we can still cross-check the
166-
// graph: if a unit at the lib-root path is present, it must
167-
// export `<package-name>` exactly (no partition).
158+
// If the lib-root file is in the graph, verify it exports a
159+
// primary module (not a partition). The actual module name is
160+
// the library author's choice — no name-matching enforced.
168161
const mcpp::modgraph::SourceUnit* lib_unit = nullptr;
169162
for (auto& u : g.units) {
170-
// Match relative or absolute — projectRoot may be empty in
171-
// tests, so we just compare path tails.
172163
auto u_rel = u.path.is_absolute() && !projectRoot.empty()
173164
? std::filesystem::relative(u.path, projectRoot)
174165
: u.path;
@@ -180,27 +171,15 @@ ValidateReport validate(const Graph& g,
180171
if (lib_unit) {
181172
if (!lib_unit->provides) {
182173
r.errors.push_back({lib_unit->path, std::format(
183-
"lib root '{}' must declare `export module {};`",
184-
lib_root_rel.string(), manifest.package.name)});
174+
"lib root '{}' must declare `export module <name>;`",
175+
lib_root_rel.string())});
185176
} else {
186177
const auto& m = lib_unit->provides->logicalName;
187178
if (m.find(':') != std::string::npos) {
188179
r.errors.push_back({lib_unit->path, std::format(
189-
"lib root '{}' exports a partition '{}' — must be the "
190-
"primary module '{}' (no `:partition` suffix)",
191-
lib_root_rel.string(), m, manifest.package.name)});
192-
} else {
193-
// 0.0.6+: compare against qualified name when namespace is set.
194-
const std::string expected =
195-
manifest.package.namespace_.empty()
196-
? manifest.package.name
197-
: manifest.package.namespace_ + "." + manifest.package.name;
198-
if (m != expected) {
199-
r.errors.push_back({lib_unit->path, std::format(
200-
"lib root '{}' exports module '{}', expected '{}' "
201-
"(must match [package].namespace + name)",
202-
lib_root_rel.string(), m, expected)});
203-
}
180+
"lib root '{}' exports a partition '{}' — "
181+
"must be a primary module (no `:partition` suffix)",
182+
lib_root_rel.string(), m)});
204183
}
205184
}
206185
}

tests/unit/test_modgraph.cpp

Lines changed: 11 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -115,25 +115,22 @@ int main(){})");
115115
std::filesystem::remove_all(dir);
116116
}
117117

118-
TEST(Validate, NamingRequiresPackagePrefix) {
118+
TEST(Validate, ModuleNameNotRequiredToMatchPackageName) {
119+
// 0.0.10+: module name does NOT need to be prefixed by package name.
120+
// The library author decides the module naming convention.
119121
Graph g;
120122
SourceUnit u;
121123
u.path = "/x/foo.cppm";
122-
u.packageName = "myorg.foo"; // unit's own package (post-multi-pkg refactor)
123-
u.provides = ModuleId{"core"};
124+
u.packageName = "myorg.foo";
125+
u.provides = ModuleId{"completely.different.name"};
124126
g.units.push_back(u);
125-
g.producerOf["core"] = 0;
127+
g.producerOf["completely.different.name"] = 0;
126128

127129
mcpp::manifest::Manifest m;
128130
m.package.name = "myorg.foo";
129131

130132
auto rep = validate(g, m);
131-
EXPECT_FALSE(rep.ok());
132-
bool found = false;
133-
for (auto& e : rep.errors) {
134-
if (e.message.find("prefixed by package name") != std::string::npos) { found = true; break; }
135-
}
136-
EXPECT_TRUE(found) << "expected naming-violation error";
133+
EXPECT_TRUE(rep.ok()) << "module name mismatch should not be an error";
137134
}
138135

139136
TEST(Validate, ForbiddenTopName) {
@@ -211,8 +208,9 @@ TEST(Validate, LibRootExportsPartitionIsError) {
211208
EXPECT_TRUE(found) << "expected lib-root partition error";
212209
}
213210

214-
TEST(Validate, LibRootWrongModuleNameIsError) {
215-
// Lib root file exports a module that doesn't match [package].name.
211+
TEST(Validate, LibRootDifferentModuleNameIsAllowed) {
212+
// 0.0.10+: lib root module name does NOT need to match [package].name.
213+
// The library author decides the module name; the build tool auto-detects.
216214
Graph g;
217215
SourceUnit u;
218216
u.path = "src/tinyhttps.cppm";
@@ -229,14 +227,7 @@ TEST(Validate, LibRootWrongModuleNameIsError) {
229227
m.targets.push_back(t);
230228

231229
auto rep = validate(g, m);
232-
EXPECT_FALSE(rep.ok());
233-
bool found = false;
234-
for (auto& e : rep.errors) {
235-
if (e.message.find("expected 'mcpplibs.tinyhttps'") != std::string::npos) {
236-
found = true; break;
237-
}
238-
}
239-
EXPECT_TRUE(found) << "expected expected-module-name error";
230+
EXPECT_TRUE(rep.ok()) << "module name mismatch should not be an error";
240231
}
241232

242233
TEST(Validate, LibRootNotEnforcedForBinaryProject) {

0 commit comments

Comments
 (0)