mirror of
https://github.com/Kitware/CMake.git
synced 2026-08-09 01:01:12 +00:00
Reply file names embed the configuration name verbatim. Reconfiguring a build tree with a build type that differs from the previous one only in case (e.g. Debug -> debug) makes CMake write a reply whose name also differs only in case from the existing file. On a case-insensitive filesystem the write is skipped because the name already exists, but RemoveOldReplyFiles compared on-disk names to the just-written names textually and deleted the surviving file, leaving the reply index citing a target or directory reply that no longer exists on disk. Prune reply files by file identity via cmSystemTools::GetFileId instead of by name, so an on-disk entry that aliases a reply we just wrote is kept. An entry whose identity cannot be obtained is retained rather than deleted. Fixes: #28022
103 lines
3.6 KiB
C++
103 lines
3.6 KiB
C++
/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
|
|
file LICENSE.rst or https://cmake.org/licensing for details. */
|
|
#include <functional>
|
|
#include <iostream>
|
|
#include <map>
|
|
#include <set>
|
|
#include <string>
|
|
#include <unordered_set>
|
|
#include <utility>
|
|
#include <vector>
|
|
|
|
#include "cmFileAPI.h"
|
|
|
|
namespace {
|
|
|
|
// Identity oracle over a name->id map. A name absent from the map models a
|
|
// file whose identity cannot be obtained (GetFileId failure).
|
|
std::function<bool(std::string const&, int&)> makeOracle(
|
|
std::map<std::string, int> const& ids)
|
|
{
|
|
return [ids](std::string const& name, int& id) -> bool {
|
|
auto const it = ids.find(name);
|
|
if (it == ids.end()) {
|
|
return false;
|
|
}
|
|
id = it->second;
|
|
return true;
|
|
};
|
|
}
|
|
|
|
bool checkCase(char const* label, std::vector<std::string> const& entries,
|
|
std::unordered_set<std::string> const& replyNames,
|
|
std::map<std::string, int> const& ids,
|
|
std::set<std::string> const& expected)
|
|
{
|
|
std::vector<std::string> const removed =
|
|
cmFileAPI::FilesToRemove<int>(entries, replyNames, makeOracle(ids));
|
|
std::set<std::string> const actual(removed.begin(), removed.end());
|
|
if (actual != expected) {
|
|
std::cout << "FAILED: " << label << "\n expected removals:";
|
|
for (std::string const& e : expected) {
|
|
std::cout << ' ' << e;
|
|
}
|
|
std::cout << "\n actual removals: ";
|
|
for (std::string const& a : actual) {
|
|
std::cout << ' ' << a;
|
|
}
|
|
std::cout << '\n';
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
int testCMFileAPI(int /*unused*/, char* /*unused*/[])
|
|
{
|
|
bool ok = true;
|
|
|
|
// An on-disk entry that resolves to the same identity as a just-written
|
|
// reply (a case-only-different name aliasing one inode) is kept.
|
|
ok &= checkCase(
|
|
"case-variant alias kept", { "target-foo-Debug-H.json" },
|
|
{ "target-foo-debug-H.json" },
|
|
{ { "target-foo-debug-H.json", 1 }, { "target-foo-Debug-H.json", 1 } },
|
|
{});
|
|
|
|
// A genuinely stale entry with a distinct identity is removed.
|
|
ok &= checkCase("distinct stale removed", { "target-old-H.json" },
|
|
{ "target-new-H.json" },
|
|
{ { "target-new-H.json", 1 }, { "target-old-H.json", 2 } },
|
|
{ "target-old-H.json" });
|
|
|
|
// An entry whose exact name was just written is kept without consulting the
|
|
// identity oracle (its name is intentionally absent from the id map).
|
|
ok &= checkCase("exact-name match kept", { "index-x.json" },
|
|
{ "index-x.json" }, {}, {});
|
|
|
|
// Fail-safe: an entry whose identity cannot be obtained is retained.
|
|
ok &= checkCase("candidate id failure retained", { "unreadable.json" },
|
|
{ "target-new-H.json" }, { { "target-new-H.json", 1 } }, {});
|
|
|
|
// No kept-side fail-safe is needed: an alias shares its inode, so when a
|
|
// kept reply's identity is unknown the candidate's is too and the
|
|
// candidate-side fail-safe retains it. Both names are absent to model it.
|
|
ok &= checkCase("unknown kept id: correlated alias retained",
|
|
{ "alias-of-unidentified.json" },
|
|
{ "target-new-H.json", "unidentified-kept.json" },
|
|
{ { "target-new-H.json", 1 } }, {});
|
|
|
|
// Mixed: an aliasing entry is kept while an unrelated stale entry is
|
|
// removed.
|
|
ok &= checkCase("mixed alias and stale", { "a-alias.json", "stale.json" },
|
|
{ "a.json", "b.json" },
|
|
{ { "a.json", 1 },
|
|
{ "b.json", 2 },
|
|
{ "a-alias.json", 1 },
|
|
{ "stale.json", 3 } },
|
|
{ "stale.json" });
|
|
|
|
return ok ? 0 : 1;
|
|
}
|