1
1
Fork 0
mirror of https://github.com/NixOS/nix.git synced 2025-12-15 21:41:04 +01:00
nix/src/libstore/build/find-cycles.cc
Bernardo Meurer Costa c70df0a2da
refactor(libstore/find-cycles): use std::filesystem::path operator/
Replace string concatenation for path joining with type-safe
`std::filesystem::path operator/`.
2025-10-25 20:54:21 +00:00

210 lines
7.3 KiB
C++

#include "find-cycles.hh"
#include "nix/store/store-api.hh"
#include "nix/util/file-system.hh"
#include "nix/util/archive.hh" // For caseHackSuffix
#include <filesystem>
namespace nix {
CycleEdgeScanSink::CycleEdgeScanSink(StringSet && hashes, std::string storeDir)
: RefScanSink(std::move(hashes))
, storeDir(std::move(storeDir))
{
}
void CycleEdgeScanSink::setCurrentPath(const std::string & path)
{
currentFilePath = path;
// Clear tracking for new file
recordedForCurrentFile.clear();
}
void CycleEdgeScanSink::operator()(std::string_view data)
{
// Call parent's operator() to do the actual hash searching
// This reuses all the proven buffer boundary handling logic
RefScanSink::operator()(data);
// Check which hashes have been found and not yet recorded for this file
// getResult() returns the set of ALL hashes found so far
for (const auto & hash : getResult()) {
if (recordedForCurrentFile.insert(hash).second) {
// This hash was just found and not yet recorded for current file
// Create an edge from current file to the target
auto targetPath = storeDir + hash;
edges.push_back({currentFilePath, targetPath});
debug("found cycle edge: %s → %s (hash: %s)", currentFilePath, targetPath, hash);
}
}
}
StoreCycleEdgeVec && CycleEdgeScanSink::getEdges()
{
return std::move(edges);
}
void scanForCycleEdges(const Path & path, const StorePathSet & refs, StoreCycleEdgeVec & edges)
{
StringSet hashes;
// Extract the store directory from the path
// Example: /run/user/1000/nix-test/store/abc-foo -> /run/user/1000/nix-test/store/
auto storePrefixPath = std::filesystem::path(path);
storePrefixPath.remove_filename();
std::string storePrefix = storePrefixPath.string();
debug("scanForCycleEdges: storePrefixPath = %s", storePrefixPath.string());
debug("scanForCycleEdges: storePrefix = %s", storePrefix);
// Collect hashes to search for
for (auto & i : refs) {
hashes.insert(std::string(i.hashPart()));
}
// Create sink that reuses RefScanSink's hash-finding logic
CycleEdgeScanSink sink(std::move(hashes), storePrefix);
// Walk the filesystem and scan files using the sink
scanForCycleEdges2(path, sink);
// Extract the found edges
edges = sink.getEdges();
}
/**
* Recursively walk filesystem and stream files into the sink.
* This reuses RefScanSink's hash-finding logic instead of reimplementing it.
*/
void scanForCycleEdges2(const std::string & path, CycleEdgeScanSink & sink)
{
auto fsPath = std::filesystem::path(path);
auto status = std::filesystem::symlink_status(fsPath);
debug("scanForCycleEdges2: scanning path = %s", path);
if (std::filesystem::is_regular_file(status)) {
// Handle regular files - stream contents into sink
// The sink (RefScanSink) handles all hash detection and buffer management
sink.setCurrentPath(path);
// Use Nix's portable readFile that streams into a sink
// This handles all file I/O portably across platforms
readFile(path, sink);
} else if (std::filesystem::is_directory(status)) {
// Handle directories - recursively scan contents
std::map<std::string, std::string> unhacked;
for (DirectoryIterator i(path); i != DirectoryIterator(); ++i) {
std::string entryName = i->path().filename().string();
#ifdef __APPLE__
// Handle case-insensitive filesystems on macOS
std::string name(entryName);
size_t pos = entryName.find(caseHackSuffix);
if (pos != std::string::npos) {
debug("removing case hack suffix from '%s'", (fsPath / entryName).string());
name.erase(pos);
}
if (unhacked.find(name) != unhacked.end()) {
throw Error(
"file name collision between '%1%' and '%2%'",
(fsPath / unhacked[name]).string(),
(fsPath / entryName).string());
}
unhacked[name] = entryName;
#else
unhacked[entryName] = entryName;
#endif
}
for (auto & [name, actualName] : unhacked) {
debug("scanForCycleEdges2: recursing into %s/%s", path, actualName);
scanForCycleEdges2((fsPath / actualName).string(), sink);
}
} else if (std::filesystem::is_symlink(status)) {
// Handle symlinks - stream link target into sink
auto linkTarget = std::filesystem::read_symlink(fsPath).string();
debug("scanForCycleEdges2: scanning symlink %s -> %s", path, linkTarget);
sink.setCurrentPath(path);
sink(std::string_view(linkTarget));
} else {
throw Error("file '%1%' has an unsupported type", path);
}
}
void transformEdgesToMultiedges(StoreCycleEdgeVec & edges, StoreCycleEdgeVec & multiedges)
{
debug("transformEdgesToMultiedges: processing %lu edges", edges.size());
// First pass: join edges to multiedges
for (auto & edge2 : edges) {
bool edge2Joined = false;
for (auto & edge1 : multiedges) {
// Check if edge1.back() == edge2.front()
// This means we can extend edge1 by appending edge2
if (edge1.back() == edge2.front()) {
// Append all but the first element of edge2 (to avoid duplication)
for (size_t i = 1; i < edge2.size(); i++) {
edge1.push_back(edge2[i]);
}
edge2Joined = true;
break;
}
// Check if edge2.back() == edge1.front()
// This means we can extend edge1 by prepending edge2
if (edge2.back() == edge1.front()) {
// Prepend all but the last element of edge2 (to avoid duplication)
for (int i = edge2.size() - 2; i >= 0; i--) {
edge1.push_front(edge2[i]);
}
edge2Joined = true;
break;
}
}
if (!edge2Joined) {
multiedges.push_back(edge2);
}
}
// Second pass: merge multiedges that can now be connected
// After joining edges, some multiedges might now be connectable
bool merged = true;
while (merged) {
merged = false;
for (size_t i = 0; i < multiedges.size() && !merged; i++) {
for (size_t j = i + 1; j < multiedges.size() && !merged; j++) {
auto & path1 = multiedges[i];
auto & path2 = multiedges[j];
if (path1.back() == path2.front()) {
// Append path2 to path1
for (size_t k = 1; k < path2.size(); k++) {
path1.push_back(path2[k]);
}
multiedges.erase(multiedges.begin() + j);
merged = true;
} else if (path2.back() == path1.front()) {
// Prepend path2 to path1
for (int k = path2.size() - 2; k >= 0; k--) {
path1.push_front(path2[k]);
}
multiedges.erase(multiedges.begin() + j);
merged = true;
}
}
}
}
debug("transformEdgesToMultiedges: result has %lu multiedges", multiedges.size());
}
} // namespace nix