Skip to content

Commit 9bca826

Browse files
authored
0.7.1: nothing in the package instantiates std::filesystem::path's iterator (#18)
* rules-slang: normalise a per_file key without std::filesystem::path MSVC 14.52 (both 36629 and 36725, measured on xrgui's CI) refuses to instantiate _Path_iterator's hidden-friend operator== inside a module interface unit that imports std: include\filesystem(1572): error C2801: '..._Path_iterator<...>::operator ==' must be a non-static member lexically_normal() was the one call 0.7.0 added that reaches it. The key needs two normalisations, separators and a leading ./, and both are string operations. * a member does not instantiate the path iterator; relative_to() is the string form, in the lib root The first fix removed lexically_normal() and the error stayed. The lib root compiles lexically_relative and passes; a member that imports the lib root and reaches _Path_iterator's hidden-friend operator== again fails under MSVC 14.52 (36629 and 36725, xrgui CI). So members do not: the Slang rule's sidecar name and the AppImage member's launcher path go through one string helper, mcpp::plugins::names::relative_to. * the lib root reads paths apart as strings; its BMI no longer carries _Path_iterator Removing the iterator-instantiating calls from the members changed nothing: the rule failed with the same C2801 with nothing but create_directories and path construction left, while declare.cppm -- same import set, no std::filesystem use at all -- compiled. What every failure shared was importing both std and mcpp.plugins, whose BMI carried _Path_iterator instantiated by common_base_dir / namespace_of, and then touching path. components() splits on both separators, which is the component comparison 0.5.2 introduced lexically_relative for; nothing in the lib root instantiates the iterator now. create_directories stays: the STL walks wchar_t pointers there, not the iterator. * 0.7.1: nothing in the package instantiates std::filesystem::path's iterator MSVC 14.52 (36629 and 36725, xrgui CI) refuses the STL's own hidden-friend _Path_iterator::operator== in any module that imports both std and a module whose BMI already carries that instantiation. The three commits before this one found the shape by elimination; this one is the version that carries it.
1 parent 95a7553 commit 9bca826

5 files changed

Lines changed: 87 additions & 26 deletions

File tree

README.md

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ imports each one from `build.mcpp` under the module name the member declares.
66

77
```toml
88
[build-dependencies.mcpp]
9-
plugins = { version = "0.7.0", features = ["rules-spirv"], host-module = true }
9+
plugins = { version = "0.7.1", features = ["rules-spirv"], host-module = true }
1010
```
1111

1212
`[build-dependencies]`, not `[dependencies]`. The two keys answer separate
@@ -81,7 +81,7 @@ A project names the rule and nothing else:
8181

8282
```toml
8383
[build-dependencies.mcpp]
84-
plugins = { version = "0.7.0", features = ["rules-cuda"], host-module = true }
84+
plugins = { version = "0.7.1", features = ["rules-cuda"], host-module = true }
8585
```
8686

8787
The payloads each rule drives are declared **here**, under the feature that
@@ -165,6 +165,15 @@ looks harmless.
165165
0.5.0, 0.5.1 and 0.5.2 do not move it. Naming an island's entry points is a
166166
change to what this package generates, not to what it asks the engine for.
167167

168+
0.7.1 does not move it either, and records a compiler rather than an engine:
169+
under MSVC 14.52 (36629 and 36725, measured on xrgui's CI) a module that has
170+
instantiated `std::filesystem::path`'s iterator poisons every importer that
171+
touches `path` again -- `filesystem(1572): error C2801: '_Path_iterator<...>::operator =='
172+
must be a non-static member`. Nothing in this package instantiates that
173+
iterator now: the lib root reads paths apart as strings
174+
(`mcpp::plugins::names::components`), and the members' relative-path
175+
arithmetic is `mcpp::plugins::names::relative_to`.
176+
168177
The previous shared floor was 2026.9.7.1, the release that reads
169178
`device_extensions` and `rule_module`, reports `[language] modules` and the
170179
package's own name to a build program, writes the build program a declared rule

dist/appimage.cppm

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -361,8 +361,8 @@ inline plan plan_for(options opt = {}) {
361361
p.reason = "no launcher in the staged tree";
362362
return p;
363363
}
364-
const auto launcher_rel =
365-
std::filesystem::path(launcher).lexically_relative(stage).generic_string();
364+
// Strings, not `lexically_relative`: see `mcpp::plugins::names::relative_to`.
365+
const auto launcher_rel = mcpp::plugins::names::relative_to(launcher, stage);
366366

367367
// ── The three files AppImage requires, written into the staged tree ────
368368
const std::string name = app_name_for(opt);

mcpp.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
[package]
22
name = "plugins"
33
namespace = "mcpp"
4-
version = "0.7.0"
4+
version = "0.7.1"
55
description = "Official mcpp build plugins: rule packages under mcpp.rules.*, build-time utilities under mcpp.tools.*, each member selected by a feature"
66
license = "Apache-2.0"
77
authors = ["mcpp-community"]

rules/slang.cppm

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -325,9 +325,17 @@ inline bool write_header(const std::string& header, const std::string& inc) {
325325

326326
// The path as `per_file` keys it and as `mcpp::device_sources()` lists it are
327327
// both package-relative, but one may have been typed on Windows and the other
328-
// derived there. Components are compared, not characters.
328+
// derived there: separators are unified and a leading `./` dropped before the
329+
// two are compared.
330+
//
331+
// Strings, not `std::filesystem::path`: this member must not instantiate the
332+
// path iterator -- see `mcpp::plugins::names::relative_to` for the compiler
333+
// that refuses it. The two normalisations this key needs are string operations.
329334
inline std::string key_of(std::string_view path) {
330-
return std::filesystem::path(path).lexically_normal().generic_string();
335+
std::string s(path);
336+
for (auto& c : s) if (c == '\\') c = '/';
337+
while (s.starts_with("./")) s.erase(0, 2);
338+
return s;
331339
}
332340

333341
inline bool compile(std::span<const std::string> shaders, options opt = {}) {
@@ -458,8 +466,7 @@ inline bool compile(std::span<const std::string> shaders, options opt = {}) {
458466
// Where a sidecar is found at run time: relative to the package root,
459467
// which is where `mcpp run` starts the program. The cost of that is
460468
// stated on `mcpp::plugins::surface::storage::sidecar`.
461-
const auto sidecarName =
462-
std::filesystem::path(spv).lexically_relative(root).generic_string();
469+
const auto sidecarName = mcpp::plugins::names::relative_to(spv, root);
463470
items.push_back({ .identifier = p.stem().string(),
464471
.name_space = ns,
465472
.data_header = headerRel,

src/plugins.cppm

Lines changed: 62 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ export namespace mcpp::plugins {
4949
//
5050
// One package, one version: the number lives in mcpp.toml, and the CI step
5151
// `the collection states its own version` compares the two.
52-
inline constexpr std::string_view version = "0.7.0";
52+
inline constexpr std::string_view version = "0.7.1";
5353

5454
} // namespace mcpp::plugins
5555

@@ -63,6 +63,20 @@ inline constexpr std::string_view version = "0.7.0";
6363
// is guaranteed rather than two files that happen to say the same thing.
6464
export namespace mcpp::plugins::names {
6565

66+
// `a` made relative to the directory `b`, as strings: separators unified, and
67+
// the prefix stripped when `a` lies under `b`; `a` unchanged otherwise.
68+
//
69+
// NOTHING IN THIS PACKAGE INSTANTIATES `std::filesystem::path`'s ITERATOR --
70+
// see `components()` below for the compiler that refuses it. The members'
71+
// relative-path arithmetic is this string function, defined once here.
72+
inline std::string relative_to(std::string a, std::string b) {
73+
for (auto& c : a) if (c == '\\') c = '/';
74+
for (auto& c : b) if (c == '\\') c = '/';
75+
while (!b.empty() && b.back() == '/') b.pop_back();
76+
if (!b.empty() && a.starts_with(b + "/")) return a.substr(b.size() + 1);
77+
return a;
78+
}
79+
6680
// A GENERATED NAME THE C++ COMPILER WILL ACCEPT.
6781
//
6882
// Three transformations, and the third is the one every hand-rolled copy of
@@ -154,13 +168,44 @@ inline std::vector<std::string> split_module_name(std::string_view name) {
154168
// A single path has no common prefix with anything, so its own directory is the
155169
// base and its namespace is empty -- which is the same answer the general case
156170
// gives once a second file appears beside it.
171+
// A path's components as strings: both separators split, empty and `.`
172+
// components dropped. This is the one place the lib root reads a path apart,
173+
// and it does so WITHOUT `std::filesystem::path`'s iterator on purpose:
174+
//
175+
// A MODULE THAT INSTANTIATES `_Path_iterator` POISONS ITS IMPORTERS UNDER MSVC
176+
// 14.52. Measured on xrgui's CI (14.52.36629 and .36725): this unit compiled
177+
// while it iterated paths, and every member importing it that then touched
178+
// `std::filesystem` at all failed inside the STL --
179+
//
180+
// include\filesystem(1572): error C2801: '..._Path_iterator<...>::operator =='
181+
// must be a non-static member
182+
//
183+
// -- the iterator's hidden-friend comparison, refused when the importer meets
184+
// it both through `import std` and through this module's BMI. Removing the
185+
// calls from the members changed nothing; the instantiation had to leave the
186+
// lib root. Component comparison is what `lexically_relative` bought in 0.5.2
187+
// (a Windows separator bug), and splitting on both separators keeps that.
188+
inline std::vector<std::string> components(std::string_view path) {
189+
std::vector<std::string> out;
190+
std::string cur;
191+
auto flush = [&] { if (!cur.empty() && cur != ".") out.push_back(cur); cur.clear(); };
192+
for (char c : path) { if (c == '/' || c == '\\') flush(); else cur += c; }
193+
flush();
194+
return out;
195+
}
196+
197+
// The directory part of a path, as written: everything before the last
198+
// separator, or empty when there is none.
199+
inline std::string_view parent_of(std::string_view path) {
200+
const auto slash = path.find_last_of("/\\");
201+
return slash == std::string_view::npos ? std::string_view{} : path.substr(0, slash);
202+
}
203+
157204
inline std::string common_base_dir(std::span<const std::string> paths) {
158205
std::vector<std::string> prefix;
159206
bool first = true;
160207
for (auto const& src : paths) {
161-
std::vector<std::string> segs;
162-
for (auto const& part : std::filesystem::path(src).parent_path())
163-
if (auto s = part.string(); !s.empty() && s != ".") segs.push_back(s);
208+
auto segs = components(parent_of(src));
164209
if (first) { prefix = std::move(segs); first = false; continue; }
165210
std::size_t keep = 0;
166211
while (keep < prefix.size() && keep < segs.size() && prefix[keep] == segs[keep]) ++keep;
@@ -186,29 +231,29 @@ inline std::string common_base_dir(std::span<const std::string> paths) {
186231
// 'image' in namespace 'island_interface::kernels'`, while the same fixture
187232
// passed on Linux and macOS.
188233
//
189-
// `lexically_relative` compares COMPONENTS, so the separator a caller happened
190-
// to write is not part of the question. A base that is not a prefix yields a
191-
// path starting `..`, which is a caller error rather than a namespace; it
192-
// answers with no segments rather than with the whole absolute path, which is
193-
// what the string form produced.
234+
// COMPONENTS are compared, so the separator a caller happened to write is not
235+
// part of the question -- `components()` splits on both. A base that is not a
236+
// prefix is a caller error rather than a namespace; it answers with no
237+
// segments rather than with the whole absolute path, which is what the first
238+
// string form produced. (Through `components()` rather than
239+
// `lexically_relative` for the reason stated on it.)
194240
inline std::vector<std::string> namespace_of(std::string_view src, std::string_view base) {
195241
std::vector<std::string> out;
196-
const auto dir = std::filesystem::path(src).parent_path();
197-
auto rel = dir;
242+
auto dir = components(parent_of(src));
198243
if (!base.empty()) {
199-
rel = dir.lexically_relative(std::filesystem::path(base));
200-
if (rel.empty() || rel.begin()->string() == "..") return out;
244+
const auto b = components(base);
245+
if (b.size() > dir.size()) return out;
246+
for (std::size_t i = 0; i < b.size(); ++i) if (b[i] != dir[i]) return out;
247+
dir.erase(dir.begin(), dir.begin() + static_cast<std::ptrdiff_t>(b.size()));
201248
}
202-
for (auto const& part : rel) {
203-
auto s = part.string();
204-
if (s.empty() || s == "." || s == ".." || s == "/" || s == "\\") continue;
249+
for (auto const& s : dir) {
250+
if (s == "..") continue;
205251
// `shaders/default/` is an ordinary directory name and
206252
// `namespace default {` is not a namespace.
207253
out.push_back(identifier(s, "dir"));
208254
}
209255
return out;
210256
}
211-
212257
} // namespace mcpp::plugins::names
213258

214259

0 commit comments

Comments
 (0)