diff --git a/implementations/Crafter.Build-Clang.cpp b/implementations/Crafter.Build-Clang.cpp index f93451a..2d1b47c 100644 --- a/implementations/Crafter.Build-Clang.cpp +++ b/implementations/Crafter.Build-Clang.cpp @@ -443,6 +443,42 @@ namespace { } return outcome; } + + // Two configurations among `config` and its transitive dependencies that + // export a module of the same name: the same project loaded along two + // paths with different args, say, or from a checkout on one path and a + // clone on the other. A program can hold one module of each name. Built + // anyway, both PCM dirs went onto the consumer's -fprebuilt-module-path, + // clang resolved the module by name to the first for every import, the + // ones inside the other build's consumers included, and nothing checked + // that it was the build they had been compiled against. The program then + // linked code compiled against two layouts of the same types, and ran + // (issue #41). Empty when the dependencies agree, otherwise the error, + // naming the path to each of the two builds. + std::string DuplicateModuleError(const Configuration& config) { + struct Owner { + const Configuration* cfg; + std::string chain; + }; + const auto describe = [](const Owner& owner) { + return std::format("{} ({}, {}, project args {})", owner.chain, owner.cfg->path.string(), owner.cfg->VariantId(), owner.cfg->projectArgs); + }; + std::unordered_map owners; + std::unordered_set visited; + std::string error; + std::function walk = [&](const Configuration& c, const std::string& chain) { + if (!error.empty() || !visited.insert(&c).second) return; + for (const std::unique_ptr& iface : c.interfaces) { + const auto [it, inserted] = owners.emplace(iface->name, Owner{ &c, chain }); + if (inserted || it->second.cfg == &c) continue; + error = std::format("module {} is built twice for {}:\n {}\n {}\nA program can hold one build of each module: load {} with the same args, and from the same place, along every path.", iface->name, config.name, describe(it->second), describe(Owner{ &c, chain }), c.name); + return; + } + for (const Configuration* dep : c.dependencies) walk(*dep, std::format("{} -> {}", chain, dep->name)); + }; + walk(config, config.name); + return error; + } } BuildResult Crafter::Build(Configuration& config, std::unordered_map>& depResults, std::mutex& depMutex) { @@ -451,6 +487,11 @@ BuildResult Crafter::Build(Configuration& config, std::unordered_map