mirror of
https://github.com/NixOS/nix.git
synced 2025-11-20 09:19:36 +01:00
Merge pull request #14360 from lovesegfault/scan-for-references-detailed
feat(libstore): add scanForReferencesDeep and use it for why-depends
This commit is contained in:
commit
7f1d92793e
4 changed files with 326 additions and 43 deletions
|
|
@ -3,6 +3,10 @@
|
|||
|
||||
#include "nix/store/references.hh"
|
||||
#include "nix/store/path.hh"
|
||||
#include "nix/util/source-accessor.hh"
|
||||
|
||||
#include <functional>
|
||||
#include <vector>
|
||||
|
||||
namespace nix {
|
||||
|
||||
|
|
@ -21,4 +25,57 @@ public:
|
|||
StorePathSet getResultPaths();
|
||||
};
|
||||
|
||||
/**
|
||||
* Result of scanning a single file for references.
|
||||
*/
|
||||
struct FileRefScanResult
|
||||
{
|
||||
CanonPath filePath; ///< The file that was scanned
|
||||
StorePathSet foundRefs; ///< Which store paths were found in this file
|
||||
};
|
||||
|
||||
/**
|
||||
* Scan a store path tree and report which references appear in which files.
|
||||
*
|
||||
* This is like scanForReferences() but provides per-file granularity.
|
||||
* Useful for cycle detection and detailed dependency analysis like `nix why-depends --precise`.
|
||||
*
|
||||
* The function walks the tree using the provided accessor and streams each file's
|
||||
* contents through a RefScanSink to detect hash references. For each file that
|
||||
* contains at least one reference, a callback is invoked with the file path and
|
||||
* the set of references found.
|
||||
*
|
||||
* Note: This function only searches for the hash part of store paths (e.g.,
|
||||
* "dc04vv14dak1c1r48qa0m23vr9jy8sm0"), not the name part. A store path like
|
||||
* "/nix/store/dc04vv14dak1c1r48qa0m23vr9jy8sm0-foo" will be detected if the
|
||||
* hash appears anywhere in the scanned content, regardless of the "-foo" suffix.
|
||||
*
|
||||
* @param accessor Source accessor to read the tree
|
||||
* @param rootPath Root path to scan
|
||||
* @param refs Set of store paths to search for
|
||||
* @param callback Called for each file that contains at least one reference
|
||||
*/
|
||||
void scanForReferencesDeep(
|
||||
SourceAccessor & accessor,
|
||||
const CanonPath & rootPath,
|
||||
const StorePathSet & refs,
|
||||
std::function<void(FileRefScanResult)> callback);
|
||||
|
||||
/**
|
||||
* Scan a store path tree and return which references appear in which files.
|
||||
*
|
||||
* This is a convenience wrapper around the callback-based scanForReferencesDeep()
|
||||
* that collects all results into a map for efficient lookups.
|
||||
*
|
||||
* Note: This function only searches for the hash part of store paths, not the name part.
|
||||
* See the callback-based overload for details.
|
||||
*
|
||||
* @param accessor Source accessor to read the tree
|
||||
* @param rootPath Root path to scan
|
||||
* @param refs Set of store paths to search for
|
||||
* @return Map from file paths to the set of references found in each file
|
||||
*/
|
||||
std::map<CanonPath, StorePathSet>
|
||||
scanForReferencesDeep(SourceAccessor & accessor, const CanonPath & rootPath, const StorePathSet & refs);
|
||||
|
||||
} // namespace nix
|
||||
|
|
|
|||
|
|
@ -1,11 +1,15 @@
|
|||
#include "nix/store/path-references.hh"
|
||||
#include "nix/util/hash.hh"
|
||||
#include "nix/util/archive.hh"
|
||||
#include "nix/util/source-accessor.hh"
|
||||
#include "nix/util/canon-path.hh"
|
||||
#include "nix/util/logging.hh"
|
||||
|
||||
#include <map>
|
||||
#include <cstdlib>
|
||||
#include <mutex>
|
||||
#include <algorithm>
|
||||
#include <functional>
|
||||
|
||||
namespace nix {
|
||||
|
||||
|
|
@ -54,4 +58,90 @@ StorePathSet scanForReferences(Sink & toTee, const Path & path, const StorePathS
|
|||
return refsSink.getResultPaths();
|
||||
}
|
||||
|
||||
void scanForReferencesDeep(
|
||||
SourceAccessor & accessor,
|
||||
const CanonPath & rootPath,
|
||||
const StorePathSet & refs,
|
||||
std::function<void(FileRefScanResult)> callback)
|
||||
{
|
||||
// Recursive tree walker
|
||||
auto walk = [&](this auto & self, const CanonPath & path) -> void {
|
||||
auto stat = accessor.lstat(path);
|
||||
|
||||
switch (stat.type) {
|
||||
case SourceAccessor::tRegular: {
|
||||
// Create a fresh sink for each file to independently detect references.
|
||||
// RefScanSink accumulates found hashes globally - once a hash is found,
|
||||
// it remains in the result set. If we reused the same sink across files,
|
||||
// we couldn't distinguish which files contain which references, as a hash
|
||||
// found in an earlier file wouldn't be reported when found in later files.
|
||||
PathRefScanSink sink = PathRefScanSink::fromPaths(refs);
|
||||
|
||||
// Scan this file by streaming its contents through the sink
|
||||
accessor.readFile(path, sink);
|
||||
|
||||
// Get the references found in this file
|
||||
auto foundRefs = sink.getResultPaths();
|
||||
|
||||
// Report if we found anything in this file
|
||||
if (!foundRefs.empty()) {
|
||||
debug("scanForReferencesDeep: found %d references in %s", foundRefs.size(), path.abs());
|
||||
callback(FileRefScanResult{.filePath = path, .foundRefs = std::move(foundRefs)});
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case SourceAccessor::tDirectory: {
|
||||
// Recursively scan directory contents
|
||||
auto entries = accessor.readDirectory(path);
|
||||
for (const auto & [name, entryType] : entries) {
|
||||
self(path / name);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case SourceAccessor::tSymlink: {
|
||||
// Create a fresh sink for the symlink target (same reason as regular files)
|
||||
PathRefScanSink sink = PathRefScanSink::fromPaths(refs);
|
||||
|
||||
// Scan symlink target for references
|
||||
auto target = accessor.readLink(path);
|
||||
sink(std::string_view(target));
|
||||
|
||||
// Get the references found in this symlink target
|
||||
auto foundRefs = sink.getResultPaths();
|
||||
|
||||
if (!foundRefs.empty()) {
|
||||
debug("scanForReferencesDeep: found %d references in symlink %s", foundRefs.size(), path.abs());
|
||||
callback(FileRefScanResult{.filePath = path, .foundRefs = std::move(foundRefs)});
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case SourceAccessor::tChar:
|
||||
case SourceAccessor::tBlock:
|
||||
case SourceAccessor::tSocket:
|
||||
case SourceAccessor::tFifo:
|
||||
case SourceAccessor::tUnknown:
|
||||
default:
|
||||
throw Error("file '%s' has an unsupported type", path.abs());
|
||||
}
|
||||
};
|
||||
|
||||
// Start the recursive walk from the root
|
||||
walk(rootPath);
|
||||
}
|
||||
|
||||
std::map<CanonPath, StorePathSet>
|
||||
scanForReferencesDeep(SourceAccessor & accessor, const CanonPath & rootPath, const StorePathSet & refs)
|
||||
{
|
||||
std::map<CanonPath, StorePathSet> results;
|
||||
|
||||
scanForReferencesDeep(accessor, rootPath, refs, [&](FileRefScanResult result) {
|
||||
results[std::move(result.filePath)] = std::move(result.foundRefs);
|
||||
});
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
} // namespace nix
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue