@@ -7634,17 +7634,82 @@ prepare_build(bool print_fingerprint,
76347634 }
76357635 }
76367636 const auto root = iface.lexically_normal();
7637+ // ORDERED BY WHAT THEY IMPORT, NOT BY WHERE THEY SIT.
7638+ //
7639+ // The compile loop accumulates BMIs in list order, so each
7640+ // entry sees only what precedes it. Path order was the previous
7641+ // rule and it is not a valid one: `rules/spirv.cppm` sorts
7642+ // before `src/surface.cppm`, so a member importing a unit its
7643+ // package shares was compiled first and failed with "failed to
7644+ // read compiled module ... imports must be built before being
7645+ // imported". Reproduced, and reproduced in both directions --
7646+ // renaming the shared unit so its path sorted first made the
7647+ // same package build, which is what says the cause is the sort
7648+ // and nothing else.
7649+ //
7650+ // A package that works today is ordered IDENTICALLY: the sort
7651+ // below keeps path order wherever no import constrains it, so
7652+ // it differs only where the old order was already broken.
7653+ struct Unit {
7654+ std::filesystem::path path;
7655+ std::string name;
7656+ std::vector<std::string> imports;
7657+ };
7658+ std::vector<Unit> pending;
76377659 for (auto const& f : matched) { // std::set: sorted
76387660 if (dropped.contains(f)) continue;
76397661 if (std::filesystem::equivalent(f, root, ec)) continue;
76407662 std::ifstream is(f);
76417663 if (!is) continue;
76427664 std::stringstream buf;
76437665 buf << is.rdbuf();
7644- auto name = prov::declared_interface_name(buf.str());
7666+ auto text = buf.str();
7667+ auto name = prov::declared_interface_name(text);
76457668 if (name.empty()) continue;
7646- push( f, std::move(name));
7669+ pending.push_back({ f, std::move(name), prov::declared_imports(text)} );
76477670 }
7671+
7672+ // Only names this package itself declares constrain anything.
7673+ // `import std;` and the lib root are already ahead of every
7674+ // entry here, and a name from another package is ordered by the
7675+ // cross-package DFS below rather than by this sort.
7676+ std::map<std::string, std::size_t> byName;
7677+ for (std::size_t i = 0; i < pending.size(); ++i)
7678+ byName.emplace(pending[i].name, i);
7679+
7680+ std::vector<char> state(pending.size(), 0); // 0 new, 1 open, 2 done
7681+ std::vector<std::size_t> order;
7682+ order.reserve(pending.size());
7683+ // Iterative post-order DFS over the path-sorted list: the first
7684+ // unit that can be emitted is emitted, which is what preserves
7685+ // path order in the unconstrained case.
7686+ const auto visit = [&](std::size_t start) {
7687+ std::vector<std::pair<std::size_t, std::size_t>> stack{{start, 0}};
7688+ while (!stack.empty()) {
7689+ auto& [u, k] = stack.back();
7690+ if (state[u] == 2) { stack.pop_back(); continue; }
7691+ state[u] = 1;
7692+ if (k < pending[u].imports.size()) {
7693+ auto const& want = pending[u].imports[k++];
7694+ auto it = byName.find(want);
7695+ // A CYCLE IS LEFT TO THE COMPILER, ON PURPOSE. It
7696+ // is ill-formed C++ and the compiler says so with
7697+ // the two units named; refusing here would report
7698+ // the same fact in a worse place, and getting the
7699+ // ordering wrong is no longer possible either way.
7700+ if (it != byName.end() && state[it->second] == 0)
7701+ stack.push_back({it->second, 0});
7702+ continue;
7703+ }
7704+ state[u] = 2;
7705+ order.push_back(u);
7706+ stack.pop_back();
7707+ }
7708+ };
7709+ for (std::size_t i = 0; i < pending.size(); ++i)
7710+ if (state[i] == 0) visit(i);
7711+
7712+ for (auto i : order) push(pending[i].path, std::move(pending[i].name));
76487713 return out;
76497714 };
76507715 for (std::size_t c = 0; c < provisionGraph.visible.size(); ++c) {
0 commit comments