-
Notifications
You must be signed in to change notification settings - Fork 119
uv detector improvements - multi group dev dependencies, git source detection, transitive dev propagation #1631
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,6 +1,20 @@ | ||
| # uv Detection | ||
|
|
||
| ## Requirements | ||
|
|
||
| [uv](https://docs.astral.sh/uv/) detection relies on a [uv.lock](https://docs.astral.sh/uv/concepts/projects/layout/#the-lockfile) file being present. | ||
|
|
||
| ## Detection strategy | ||
|
|
||
| uv detection is performed by parsing a <em>uv.lock</em> found under the scan directory. | ||
|
|
||
| Full dependency graph generation is supported. | ||
|
|
||
| Dev dependencies across all dependency groups (e.g., `dev`, `lint`, `test`) are identified via transitive reachability analysis. A package reachable from both production and dev roots is classified as non-dev. | ||
|
|
||
| Git-sourced packages are registered as `GitComponent` with the repository URL and commit hash extracted from the lockfile. | ||
|
|
||
| ## Known limitations | ||
|
|
||
| 1. Editable (`source = { editable = "..." }`) and non-root workspace member packages are registered as regular components rather than being filtered out. | ||
| 2. Lockfile version validation is not performed; only lockfile version 1 has been tested. |
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -26,7 +26,7 @@ | |||||
|
|
||||||
| public override IList<string> SearchPatterns { get; } = ["uv.lock"]; | ||||||
|
|
||||||
| public override IEnumerable<ComponentType> SupportedComponentTypes => [ComponentType.Pip]; | ||||||
| public override IEnumerable<ComponentType> SupportedComponentTypes => [ComponentType.Pip, ComponentType.Git]; | ||||||
|
|
||||||
| public override int Version => 1; | ||||||
|
|
||||||
|
|
@@ -37,6 +37,40 @@ | |||||
| return pck.Source?.Virtual != null; | ||||||
| } | ||||||
|
|
||||||
| internal static (Uri RepositoryUrl, string CommitHash) ParseGitUrl(string gitUrl) | ||||||
| { | ||||||
| var uri = new Uri(gitUrl); | ||||||
| var repoUrl = new Uri(uri.GetLeftPart(UriPartial.Path)); | ||||||
| var commitHash = uri.Fragment.TrimStart('#'); | ||||||
| return (repoUrl, commitHash); | ||||||
| } | ||||||
|
|
||||||
| internal static HashSet<string> GetTransitivePackages(IEnumerable<string> roots, List<UvPackage> packages) | ||||||
| { | ||||||
| var lookup = packages.ToDictionary(p => p.Name, p => p, StringComparer.OrdinalIgnoreCase); | ||||||
| var visited = new HashSet<string>(StringComparer.OrdinalIgnoreCase); | ||||||
| var queue = new Queue<string>(roots); | ||||||
|
|
||||||
| while (queue.Count > 0) | ||||||
| { | ||||||
| var name = queue.Dequeue(); | ||||||
| if (!visited.Add(name)) | ||||||
| { | ||||||
| continue; | ||||||
| } | ||||||
|
|
||||||
| if (lookup.TryGetValue(name, out var pkg)) | ||||||
| { | ||||||
| foreach (var dep in pkg.Dependencies) | ||||||
| { | ||||||
| queue.Enqueue(dep.Name); | ||||||
| } | ||||||
| } | ||||||
| } | ||||||
|
|
||||||
| return visited; | ||||||
| } | ||||||
|
|
||||||
| protected override Task OnFileFoundAsync(ProcessRequest processRequest, IDictionary<string, string> detectorArgs, CancellationToken cancellationToken = default) | ||||||
| { | ||||||
| var singleFileComponentRecorder = processRequest.SingleFileComponentRecorder; | ||||||
|
|
@@ -49,8 +83,8 @@ | |||||
| var uvLock = UvLock.Parse(file.Stream); | ||||||
|
|
||||||
| var rootPackage = uvLock.Packages.FirstOrDefault(IsRootPackage); | ||||||
| var explicitPackages = new HashSet<string>(); | ||||||
| var devPackages = new HashSet<string>(); | ||||||
| var explicitPackages = new HashSet<string>(StringComparer.OrdinalIgnoreCase); | ||||||
| var devRootNames = new HashSet<string>(StringComparer.OrdinalIgnoreCase); | ||||||
|
|
||||||
| if (rootPackage != null) | ||||||
| { | ||||||
|
|
@@ -61,30 +95,57 @@ | |||||
|
|
||||||
| foreach (var devDep in rootPackage.MetadataRequiresDev) | ||||||
| { | ||||||
| devPackages.Add(devDep.Name); | ||||||
| devRootNames.Add(devDep.Name); | ||||||
| } | ||||||
| } | ||||||
|
|
||||||
| // Compute dev-only packages via transitive reachability analysis. | ||||||
| // A package is dev-only if it is reachable from dev roots but NOT from production roots. | ||||||
| var prodRoots = rootPackage?.Dependencies.Select(d => d.Name) ?? []; | ||||||
|
||||||
| var prodRoots = rootPackage?.Dependencies.Select(d => d.Name) ?? []; | |
| var prodRoots = explicitPackages; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@theztefan - This is a valid recommendation to investigate, just because a package is a root it doesn't mean it is a runtime dependency, if the manifest explicitly says is it a dev dependency it should be classified as such, so will its children dependencies.
Copilot
AI
Feb 17, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ParseGitUrl/GitComponent creation can throw (e.g., invalid/relative URL, or missing #<commit> fragment). Because this happens inside the single try/catch around the whole file, one bad git URL will abort processing of the entire uv.lock and result in no components being recorded. Consider using Uri.TryCreate + validating a non-empty (and ideally hex/length-checked) commit hash, and if parsing fails log a warning and either skip just that package or fall back to a PipComponent instead of failing the whole detector run.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My comment from above is valid here as well. Don't think we should be handling this here.
Check failure on line 147 in src/Microsoft.ComponentDetection.Detectors/uv/UvLockComponentDetector.cs
GitHub Actions / build (ubuntu-latest)
Closing brace should be followed by blank line (https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1513.md)
Check failure on line 147 in src/Microsoft.ComponentDetection.Detectors/uv/UvLockComponentDetector.cs
GitHub Actions / build (ubuntu-latest)
Closing brace should be followed by blank line (https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1513.md)
Check failure on line 147 in src/Microsoft.ComponentDetection.Detectors/uv/UvLockComponentDetector.cs
GitHub Actions / verify (macos-latest)
Closing brace should be followed by blank line (https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1513.md)
Check failure on line 147 in src/Microsoft.ComponentDetection.Detectors/uv/UvLockComponentDetector.cs
GitHub Actions / verify (ubuntu-latest)
Closing brace should be followed by blank line (https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1513.md)
Check failure on line 147 in src/Microsoft.ComponentDetection.Detectors/uv/UvLockComponentDetector.cs
GitHub Actions / Pip
Closing brace should be followed by blank line (https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1513.md)
Check failure on line 147 in src/Microsoft.ComponentDetection.Detectors/uv/UvLockComponentDetector.cs
GitHub Actions / Maven
Closing brace should be followed by blank line (https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1513.md)
Check failure on line 147 in src/Microsoft.ComponentDetection.Detectors/uv/UvLockComponentDetector.cs
GitHub Actions / Rust
Closing brace should be followed by blank line (https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1513.md)
Check failure on line 147 in src/Microsoft.ComponentDetection.Detectors/uv/UvLockComponentDetector.cs
GitHub Actions / verify (windows-latest)
Closing brace should be followed by blank line (https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1513.md)
Check failure on line 147 in src/Microsoft.ComponentDetection.Detectors/uv/UvLockComponentDetector.cs
GitHub Actions / CocoaPods
Closing brace should be followed by blank line (https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1513.md)
Check failure on line 147 in src/Microsoft.ComponentDetection.Detectors/uv/UvLockComponentDetector.cs
GitHub Actions / Poetry
Closing brace should be followed by blank line (https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1513.md)
Check failure on line 147 in src/Microsoft.ComponentDetection.Detectors/uv/UvLockComponentDetector.cs
GitHub Actions / Yarn
Closing brace should be followed by blank line (https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1513.md)
Check failure on line 147 in src/Microsoft.ComponentDetection.Detectors/uv/UvLockComponentDetector.cs
GitHub Actions / Gradle
Closing brace should be followed by blank line (https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1513.md)
Check failure on line 147 in src/Microsoft.ComponentDetection.Detectors/uv/UvLockComponentDetector.cs
GitHub Actions / NuGet
Closing brace should be followed by blank line (https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1513.md)
Check failure on line 147 in src/Microsoft.ComponentDetection.Detectors/uv/UvLockComponentDetector.cs
GitHub Actions / NPM
Closing brace should be followed by blank line (https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1513.md)
Check failure on line 147 in src/Microsoft.ComponentDetection.Detectors/uv/UvLockComponentDetector.cs
GitHub Actions / Go
Closing brace should be followed by blank line (https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1513.md)
Check failure on line 147 in src/Microsoft.ComponentDetection.Detectors/uv/UvLockComponentDetector.cs
GitHub Actions / Pnpm
Closing brace should be followed by blank line (https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1513.md)
Check failure on line 147 in src/Microsoft.ComponentDetection.Detectors/uv/UvLockComponentDetector.cs
GitHub Actions / Ruby
Closing brace should be followed by blank line (https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1513.md)
Copilot
AI
Feb 17, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Dependency edge registration does a linear search for every dependency (uvLock.Packages.FirstOrDefault(...)). For larger uv.lock files this becomes O(packages * deps) and you already build a name→package lookup for the transitive-closure calculation. Consider building a single case-insensitive lookup dictionary once (e.g., at the start of OnFileFoundAsync) and reusing it both for transitive reachability and for resolving depPkg when adding edges.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ParseGitUrl assumes the git URL is always a valid absolute URI and always contains a non-empty fragment. If the URL is malformed or doesn’t include a commit hash fragment, new Uri(...) or GitComponent construction will throw, causing the detector to bail out for the entire uv.lock. Consider using Uri.TryCreate plus an explicit check for a non-empty commit hash (and falling back to logging + treating it as a non-git package or skipping just that package) to avoid a single bad entry breaking detection.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I believe this is false and not a concern we should take care of at this level. The
uv.lockfiles are machine generated and follow the strict specs which for git sources afaik always includes the commit hash. This means that this could potentially take effect in cases where we have malformeduv.lockbut then it should fail because it's malformed 😄