-
Notifications
You must be signed in to change notification settings - Fork 127
add uv.lock support #1425
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
Merged
Merged
add uv.lock support #1425
Changes from 41 commits
Commits
Show all changes
48 commits
Select commit
Hold shift + click to select a range
5d7ccf0
add component type
cataggar 5be9389
metadata
cataggar 413d3e3
style
cataggar 298b1d2
start md
cataggar b7f550a
Merge remote-tracking branch 'origin/main' into uv
cataggar 392e970
fix style
cataggar 8cc618d
fix import style
cataggar 772baf1
add uv test project
cataggar 316324d
add UvLockComponentDetector deps
cataggar 9cf0e81
just use PipComponent
cataggar b2e686a
add unit tests
cataggar cd3b461
add explicit component references
cataggar d9984eb
simplify name warning
cataggar fff4dbd
one test works
cataggar 5fdb6ba
add UvLock
cataggar 0b840e0
update detector to use UvLock parsing
cataggar 329d936
add metadata
cataggar 50d397f
metadata is for each package
cataggar 80e8c80
punt on explicit
cataggar b5a9cb0
not yet
cataggar 707cd11
more detector coverage
cataggar e2398ff
add UvLock coverage
cataggar ed06cff
undo change
cataggar 60717dc
ComponentType Uv not needed
cataggar 3b4db0a
improve test coverage of UvLock
cataggar 74b9ecd
enable nullable
cataggar e1a3248
100% line coverage
cataggar 46af1a0
100% branch coverage
cataggar 611cea6
100%
cataggar 98d6def
add IDefaultOffComponentDetector
cataggar 3c63df9
Merge remote-tracking branch 'origin/main' into uv
cataggar 835f3b4
add links
cataggar 424e4df
add UvLockDetectorExperiment
cataggar 6a17664
fix DetectorRestrictionService
cataggar dc6aa11
add experiment tests
cataggar 2d18463
not the behavior we want
cataggar 5e7fb35
parse package source
cataggar c7f1dc7
add explicitreference
cataggar 7215227
add test for explicit dep
cataggar 288c567
fix dependency and devDependency
cataggar e540df8
fix dev and non dev lists
cataggar d03ee6c
null checks
cataggar f5097b7
add #nullable enable
cataggar 066e1d8
fix tests
cataggar 58e453f
undo DetectorRestrictionService
cataggar dc218a5
undo DetectorRestritionServiceTests changes
cataggar 26a21c9
PipReportComponentDetector
cataggar c2d9c07
update UvLockDetectorExperimentTests
cataggar File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| # 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. | ||
10 changes: 10 additions & 0 deletions
10
src/Microsoft.ComponentDetection.Detectors/uv/UvDependency.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| #nullable enable | ||
| namespace Microsoft.ComponentDetection.Detectors.Uv | ||
| { | ||
| public class UvDependency | ||
| { | ||
| public required string Name { get; init; } | ||
|
|
||
| public string? Specifier { get; set; } | ||
| } | ||
| } |
144 changes: 144 additions & 0 deletions
144
src/Microsoft.ComponentDetection.Detectors/uv/UvLock.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,144 @@ | ||
| #nullable enable | ||
| namespace Microsoft.ComponentDetection.Detectors.Uv | ||
| { | ||
| using System; | ||
| using System.Collections.Generic; | ||
| using System.IO; | ||
| using Tomlyn; | ||
| using Tomlyn.Model; | ||
|
|
||
| public class UvLock | ||
| { | ||
| // a list of packages with their dependencies | ||
| public List<UvPackage> Packages { get; set; } = []; | ||
|
|
||
| // static method to parse the TOML stream into a UvLock model | ||
| public static UvLock Parse(Stream tomlStream) | ||
| { | ||
| using var reader = new StreamReader(tomlStream); | ||
| var tomlContent = reader.ReadToEnd(); | ||
| var model = Toml.ToModel(tomlContent); | ||
| return new UvLock | ||
| { | ||
| Packages = ParsePackagesFromModel(model), | ||
| }; | ||
| } | ||
|
|
||
| internal static List<UvPackage> ParsePackagesFromModel(object? model) | ||
| { | ||
| if (model is not TomlTable table) | ||
| { | ||
| throw new InvalidOperationException("TOML root is not a table"); | ||
| } | ||
|
|
||
| if (!table.TryGetValue("package", out var packagesObj) || packagesObj is not TomlTableArray packages) | ||
| { | ||
| return []; | ||
| } | ||
|
|
||
| var result = new List<UvPackage>(); | ||
| foreach (var pkg in packages) | ||
| { | ||
| var parsed = ParsePackage(pkg); | ||
| if (parsed is not null) | ||
| { | ||
| result.Add(parsed); | ||
| } | ||
| } | ||
|
|
||
| return result; | ||
| } | ||
|
|
||
| internal static UvPackage? ParsePackage(object? pkg) | ||
| { | ||
| if (pkg is not TomlTable pkgTable) | ||
| { | ||
| return null; | ||
| } | ||
|
|
||
| if (pkgTable.TryGetValue("name", out var nameObj) && nameObj is string name && | ||
| pkgTable.TryGetValue("version", out var versionObj) && versionObj is string version) | ||
| { | ||
| var uvPackage = new UvPackage | ||
| { | ||
| Name = name, | ||
| Version = version, | ||
| Dependencies = [], | ||
| MetadataRequiresDist = [], | ||
| MetadataRequiresDev = [], | ||
| }; | ||
|
|
||
| if (pkgTable.TryGetValue("dependencies", out var depsObj) && depsObj is TomlArray depsArray) | ||
| { | ||
| uvPackage.Dependencies = ParseDependenciesArray(depsArray); | ||
| } | ||
|
|
||
| if (pkgTable.TryGetValue("metadata", out var metadataObj) && metadataObj is TomlTable metadataTable) | ||
| { | ||
| ParseMetadata(metadataTable, uvPackage); | ||
| } | ||
|
|
||
| // Parse source | ||
| if (pkgTable.TryGetValue("source", out var sourceObj) && sourceObj is TomlTable sourceTable) | ||
| { | ||
| var source = new UvSource | ||
| { | ||
| Registry = sourceTable.TryGetValue("registry", out var regObj) && regObj is string reg ? reg : null, | ||
| Virtual = sourceTable.TryGetValue("virtual", out var virtObj) && virtObj is string virt ? virt : null, | ||
| }; | ||
| uvPackage.Source = source; | ||
| } | ||
|
|
||
| return uvPackage; | ||
| } | ||
|
|
||
| return null; | ||
| } | ||
|
|
||
| internal static List<UvDependency> ParseDependenciesArray(TomlArray? depsArray) | ||
| { | ||
| var deps = new List<UvDependency>(); | ||
| if (depsArray is null) | ||
| { | ||
| return deps; | ||
| } | ||
|
|
||
| foreach (var dep in depsArray) | ||
| { | ||
| if (dep is TomlTable depTable && | ||
| depTable.TryGetValue("name", out var depNameObj) && depNameObj is string depName) | ||
| { | ||
| var depSpec = depTable.TryGetValue("specifier", out var specObj) && specObj is string s ? s : null; | ||
| deps.Add(new UvDependency | ||
| { | ||
| Name = depName, | ||
| Specifier = depSpec, | ||
| }); | ||
| } | ||
| } | ||
|
|
||
| return deps; | ||
| } | ||
|
|
||
| internal static void ParseMetadata(TomlTable? metadataTable, UvPackage uvPackage) | ||
| { | ||
| if (metadataTable is null) | ||
| { | ||
| return; | ||
| } | ||
|
|
||
| if (metadataTable.TryGetValue("requires-dist", out var requiresDistObj) && requiresDistObj is TomlArray requiresDistArr) | ||
| { | ||
| uvPackage.MetadataRequiresDist = ParseDependenciesArray(requiresDistArr); | ||
| } | ||
|
|
||
| if (metadataTable.TryGetValue("requires-dev", out var requiresDevObj) && requiresDevObj is TomlTable requiresDevTable) | ||
| { | ||
| if (requiresDevTable.TryGetValue("dev", out var devObj) && devObj is TomlArray devArr) | ||
| { | ||
| uvPackage.MetadataRequiresDev = ParseDependenciesArray(devArr); | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } |
101 changes: 101 additions & 0 deletions
101
src/Microsoft.ComponentDetection.Detectors/uv/UvLockComponentDetector.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,101 @@ | ||
| namespace Microsoft.ComponentDetection.Detectors.Uv | ||
| { | ||
| using System; | ||
| using System.Collections.Generic; | ||
| using System.Linq; | ||
| using System.Threading; | ||
| using System.Threading.Tasks; | ||
| using Microsoft.ComponentDetection.Contracts; | ||
| using Microsoft.ComponentDetection.Contracts.Internal; | ||
| using Microsoft.ComponentDetection.Contracts.TypedComponent; | ||
| using Microsoft.Extensions.Logging; | ||
|
|
||
| public class UvLockComponentDetector : FileComponentDetector, IDefaultOffComponentDetector | ||
| { | ||
| public UvLockComponentDetector( | ||
| IComponentStreamEnumerableFactory componentStreamEnumerableFactory, | ||
| IObservableDirectoryWalkerFactory walkerFactory, | ||
| ILogger<UvLockComponentDetector> logger) | ||
| { | ||
| this.ComponentStreamEnumerableFactory = componentStreamEnumerableFactory; | ||
| this.Scanner = walkerFactory; | ||
| this.Logger = logger; | ||
| } | ||
|
|
||
| public override string Id => "UvLock"; | ||
|
|
||
| public override IList<string> SearchPatterns { get; } = ["uv.lock"]; | ||
|
|
||
| public override IEnumerable<ComponentType> SupportedComponentTypes => [ComponentType.Pip]; | ||
|
|
||
| public override int Version => 1; | ||
|
|
||
| public override IEnumerable<string> Categories => ["Python"]; | ||
|
|
||
| internal static bool IsRootPackage(UvPackage pck) | ||
| { | ||
| return pck.Source?.Virtual != null; | ||
| } | ||
|
|
||
| protected override Task OnFileFoundAsync(ProcessRequest processRequest, IDictionary<string, string> detectorArgs, CancellationToken cancellationToken = default) | ||
| { | ||
| var singleFileComponentRecorder = processRequest.SingleFileComponentRecorder; | ||
| var file = processRequest.ComponentStream; | ||
|
|
||
| try | ||
| { | ||
| // Parse the file stream into a UvLock model | ||
| file.Stream.Position = 0; // Ensure stream is at the beginning | ||
| var uvLock = UvLock.Parse(file.Stream); | ||
|
|
||
| var rootPackage = uvLock.Packages.FirstOrDefault(IsRootPackage); | ||
|
|
||
| var explicitPackages = new HashSet<string>(); | ||
| foreach (var dep in rootPackage.MetadataRequiresDist) | ||
| { | ||
| explicitPackages.Add(dep.Name); | ||
| } | ||
|
|
||
| var devPackages = new HashSet<string>(); | ||
| foreach (var devDep in rootPackage.MetadataRequiresDev) | ||
| { | ||
| devPackages.Add(devDep.Name); | ||
| } | ||
|
|
||
| foreach (var pkg in uvLock.Packages) | ||
| { | ||
| if (IsRootPackage(pkg)) | ||
| { | ||
| continue; | ||
| } | ||
|
|
||
| var pipComponent = new PipComponent(pkg.Name, pkg.Version); | ||
| var isExplicit = explicitPackages.Contains(pkg.Name); | ||
| var isDev = devPackages.Contains(pkg.Name); | ||
| var detectedComponent = new DetectedComponent(pipComponent); | ||
| singleFileComponentRecorder.RegisterUsage(detectedComponent, isDevelopmentDependency: isDev, isExplicitReferencedDependency: isExplicit); | ||
|
|
||
| foreach (var dep in pkg.Dependencies) | ||
| { | ||
| var depPkg = uvLock.Packages.FirstOrDefault(p => p.Name.Equals(dep.Name, StringComparison.OrdinalIgnoreCase)); | ||
| if (depPkg != null) | ||
| { | ||
| var depComponentWithVersion = new PipComponent(depPkg.Name, depPkg.Version); | ||
| singleFileComponentRecorder.RegisterUsage(new DetectedComponent(depComponentWithVersion), parentComponentId: pipComponent.Id); | ||
| } | ||
| else | ||
| { | ||
| this.Logger.LogWarning("Dependency {DependencyName} not found in uv.lock packages", dep.Name); | ||
| } | ||
| } | ||
| } | ||
| } | ||
| catch (Exception ex) | ||
| { | ||
| this.Logger.LogError(ex, "Failed to parse uv.lock file {File}", file.Location); | ||
| } | ||
|
|
||
| return Task.CompletedTask; | ||
| } | ||
| } | ||
| } |
23 changes: 23 additions & 0 deletions
23
src/Microsoft.ComponentDetection.Detectors/uv/UvPackage.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,23 @@ | ||
| #nullable enable | ||
| namespace Microsoft.ComponentDetection.Detectors.Uv | ||
| { | ||
| using System.Collections.Generic; | ||
|
|
||
| public class UvPackage | ||
| { | ||
| public required string Name { get; init; } | ||
|
|
||
| public required string Version { get; init; } | ||
|
|
||
| public List<UvDependency> Dependencies { get; set; } = []; | ||
|
|
||
| // Metadata dependencies (requires-dist) | ||
| public List<UvDependency> MetadataRequiresDist { get; set; } = []; | ||
|
|
||
| // Metadata dev dependencies (requires-dev) | ||
| public List<UvDependency> MetadataRequiresDev { get; set; } = []; | ||
|
|
||
| // Source property for uv.lock | ||
| public UvSource? Source { get; set; } | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| #nullable enable | ||
|
|
||
| namespace Microsoft.ComponentDetection.Detectors.Uv | ||
| { | ||
| public class UvSource | ||
| { | ||
| public string? Registry { get; set; } | ||
|
|
||
| public string? Virtual { get; set; } | ||
| } | ||
| } |
23 changes: 23 additions & 0 deletions
23
...Microsoft.ComponentDetection.Orchestrator/Experiments/Configs/UvLockDetectorExperiment.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,23 @@ | ||
| namespace Microsoft.ComponentDetection.Orchestrator.Experiments.Configs; | ||
|
|
||
| using Microsoft.ComponentDetection.Contracts; | ||
| using Microsoft.ComponentDetection.Detectors.Pip; | ||
| using Microsoft.ComponentDetection.Detectors.Uv; | ||
|
|
||
| /// <summary> | ||
| /// Experiment to validate UvLockComponentDetector against PipComponentDetector. | ||
| /// </summary> | ||
| public class UvLockDetectorExperiment : IExperimentConfiguration | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Looks like this should have been |
||
| { | ||
| /// <inheritdoc /> | ||
| public string Name => "UvLockDetectorExperiment"; | ||
|
|
||
| /// <inheritdoc /> | ||
| public bool IsInControlGroup(IComponentDetector componentDetector) => componentDetector is PipComponentDetector; | ||
|
cataggar marked this conversation as resolved.
Outdated
|
||
|
|
||
| /// <inheritdoc /> | ||
| public bool IsInExperimentGroup(IComponentDetector componentDetector) => componentDetector is UvLockComponentDetector; | ||
|
|
||
| /// <inheritdoc /> | ||
| public bool ShouldRecord(IComponentDetector componentDetector, int numComponents) => true; | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.