Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 11 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ imports each one from `build.mcpp` under the module name the member declares.

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

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

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

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

0.7.1 does not move it either, and records a compiler rather than an engine:
under MSVC 14.52 (36629 and 36725, measured on xrgui's CI) a module that has
instantiated `std::filesystem::path`'s iterator poisons every importer that
touches `path` again -- `filesystem(1572): error C2801: '_Path_iterator<...>::operator =='
must be a non-static member`. Nothing in this package instantiates that
iterator now: the lib root reads paths apart as strings
(`mcpp::plugins::names::components`), and the members' relative-path
arithmetic is `mcpp::plugins::names::relative_to`.

The previous shared floor was 2026.9.7.1, the release that reads
`device_extensions` and `rule_module`, reports `[language] modules` and the
package's own name to a build program, writes the build program a declared rule
Expand Down
4 changes: 2 additions & 2 deletions dist/appimage.cppm
Original file line number Diff line number Diff line change
Expand Up @@ -361,8 +361,8 @@ inline plan plan_for(options opt = {}) {
p.reason = "no launcher in the staged tree";
return p;
}
const auto launcher_rel =
std::filesystem::path(launcher).lexically_relative(stage).generic_string();
// Strings, not `lexically_relative`: see `mcpp::plugins::names::relative_to`.
const auto launcher_rel = mcpp::plugins::names::relative_to(launcher, stage);

// ── The three files AppImage requires, written into the staged tree ────
const std::string name = app_name_for(opt);
Expand Down
2 changes: 1 addition & 1 deletion mcpp.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[package]
name = "plugins"
namespace = "mcpp"
version = "0.7.0"
version = "0.7.1"
description = "Official mcpp build plugins: rule packages under mcpp.rules.*, build-time utilities under mcpp.tools.*, each member selected by a feature"
license = "Apache-2.0"
authors = ["mcpp-community"]
Expand Down
15 changes: 11 additions & 4 deletions rules/slang.cppm
Original file line number Diff line number Diff line change
Expand Up @@ -325,9 +325,17 @@ inline bool write_header(const std::string& header, const std::string& inc) {

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

inline bool compile(std::span<const std::string> shaders, options opt = {}) {
Expand Down Expand Up @@ -458,8 +466,7 @@ inline bool compile(std::span<const std::string> shaders, options opt = {}) {
// Where a sidecar is found at run time: relative to the package root,
// which is where `mcpp run` starts the program. The cost of that is
// stated on `mcpp::plugins::surface::storage::sidecar`.
const auto sidecarName =
std::filesystem::path(spv).lexically_relative(root).generic_string();
const auto sidecarName = mcpp::plugins::names::relative_to(spv, root);
items.push_back({ .identifier = p.stem().string(),
.name_space = ns,
.data_header = headerRel,
Expand Down
79 changes: 62 additions & 17 deletions src/plugins.cppm
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ export namespace mcpp::plugins {
//
// One package, one version: the number lives in mcpp.toml, and the CI step
// `the collection states its own version` compares the two.
inline constexpr std::string_view version = "0.7.0";
inline constexpr std::string_view version = "0.7.1";

} // namespace mcpp::plugins

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

// `a` made relative to the directory `b`, as strings: separators unified, and
// the prefix stripped when `a` lies under `b`; `a` unchanged otherwise.
//
// NOTHING IN THIS PACKAGE INSTANTIATES `std::filesystem::path`'s ITERATOR --
// see `components()` below for the compiler that refuses it. The members'
// relative-path arithmetic is this string function, defined once here.
inline std::string relative_to(std::string a, std::string b) {
for (auto& c : a) if (c == '\\') c = '/';
for (auto& c : b) if (c == '\\') c = '/';
while (!b.empty() && b.back() == '/') b.pop_back();
if (!b.empty() && a.starts_with(b + "/")) return a.substr(b.size() + 1);
return a;
}

// A GENERATED NAME THE C++ COMPILER WILL ACCEPT.
//
// Three transformations, and the third is the one every hand-rolled copy of
Expand Down Expand Up @@ -154,13 +168,44 @@ inline std::vector<std::string> split_module_name(std::string_view name) {
// A single path has no common prefix with anything, so its own directory is the
// base and its namespace is empty -- which is the same answer the general case
// gives once a second file appears beside it.
// A path's components as strings: both separators split, empty and `.`
// components dropped. This is the one place the lib root reads a path apart,
// and it does so WITHOUT `std::filesystem::path`'s iterator on purpose:
//
// A MODULE THAT INSTANTIATES `_Path_iterator` POISONS ITS IMPORTERS UNDER MSVC
// 14.52. Measured on xrgui's CI (14.52.36629 and .36725): this unit compiled
// while it iterated paths, and every member importing it that then touched
// `std::filesystem` at all failed inside the STL --
//
// include\filesystem(1572): error C2801: '..._Path_iterator<...>::operator =='
// must be a non-static member
//
// -- the iterator's hidden-friend comparison, refused when the importer meets
// it both through `import std` and through this module's BMI. Removing the
// calls from the members changed nothing; the instantiation had to leave the
// lib root. Component comparison is what `lexically_relative` bought in 0.5.2
// (a Windows separator bug), and splitting on both separators keeps that.
inline std::vector<std::string> components(std::string_view path) {
std::vector<std::string> out;
std::string cur;
auto flush = [&] { if (!cur.empty() && cur != ".") out.push_back(cur); cur.clear(); };
for (char c : path) { if (c == '/' || c == '\\') flush(); else cur += c; }
flush();
return out;
}

// The directory part of a path, as written: everything before the last
// separator, or empty when there is none.
inline std::string_view parent_of(std::string_view path) {
const auto slash = path.find_last_of("/\\");
return slash == std::string_view::npos ? std::string_view{} : path.substr(0, slash);
}

inline std::string common_base_dir(std::span<const std::string> paths) {
std::vector<std::string> prefix;
bool first = true;
for (auto const& src : paths) {
std::vector<std::string> segs;
for (auto const& part : std::filesystem::path(src).parent_path())
if (auto s = part.string(); !s.empty() && s != ".") segs.push_back(s);
auto segs = components(parent_of(src));
if (first) { prefix = std::move(segs); first = false; continue; }
std::size_t keep = 0;
while (keep < prefix.size() && keep < segs.size() && prefix[keep] == segs[keep]) ++keep;
Expand All @@ -186,29 +231,29 @@ inline std::string common_base_dir(std::span<const std::string> paths) {
// 'image' in namespace 'island_interface::kernels'`, while the same fixture
// passed on Linux and macOS.
//
// `lexically_relative` compares COMPONENTS, so the separator a caller happened
// to write is not part of the question. A base that is not a prefix yields a
// path starting `..`, which is a caller error rather than a namespace; it
// answers with no segments rather than with the whole absolute path, which is
// what the string form produced.
// COMPONENTS are compared, so the separator a caller happened to write is not
// part of the question -- `components()` splits on both. A base that is not a
// prefix is a caller error rather than a namespace; it answers with no
// segments rather than with the whole absolute path, which is what the first
// string form produced. (Through `components()` rather than
// `lexically_relative` for the reason stated on it.)
inline std::vector<std::string> namespace_of(std::string_view src, std::string_view base) {
std::vector<std::string> out;
const auto dir = std::filesystem::path(src).parent_path();
auto rel = dir;
auto dir = components(parent_of(src));
if (!base.empty()) {
rel = dir.lexically_relative(std::filesystem::path(base));
if (rel.empty() || rel.begin()->string() == "..") return out;
const auto b = components(base);
if (b.size() > dir.size()) return out;
for (std::size_t i = 0; i < b.size(); ++i) if (b[i] != dir[i]) return out;
dir.erase(dir.begin(), dir.begin() + static_cast<std::ptrdiff_t>(b.size()));
}
for (auto const& part : rel) {
auto s = part.string();
if (s.empty() || s == "." || s == ".." || s == "/" || s == "\\") continue;
for (auto const& s : dir) {
if (s == "..") continue;
// `shaders/default/` is an ordinary directory name and
// `namespace default {` is not a namespace.
out.push_back(identifier(s, "dir"));
}
return out;
}

} // namespace mcpp::plugins::names


Expand Down
Loading