diff --git a/src/Microsoft.ComponentDetection.Contracts/TypedComponent/NugetComponent.cs b/src/Microsoft.ComponentDetection.Contracts/TypedComponent/NugetComponent.cs index dad787527..c335d7fd4 100644 --- a/src/Microsoft.ComponentDetection.Contracts/TypedComponent/NugetComponent.cs +++ b/src/Microsoft.ComponentDetection.Contracts/TypedComponent/NugetComponent.cs @@ -1,5 +1,6 @@ namespace Microsoft.ComponentDetection.Contracts.TypedComponent; +using System.Collections.Generic; using PackageUrl; public class NuGetComponent : TypedComponent @@ -22,6 +23,8 @@ public NuGetComponent(string name, string version, string[] authors = null) public string[] Authors { get; set; } + public ISet TargetFrameworks { get; set; } = new HashSet(); + public override ComponentType Type => ComponentType.NuGet; public override string Id => $"{this.Name} {this.Version} - {this.Type}"; diff --git a/src/Microsoft.ComponentDetection.Detectors/nuget/FrameworkPackages/FrameworkPackages.cs b/src/Microsoft.ComponentDetection.Detectors/nuget/FrameworkPackages/FrameworkPackages.cs new file mode 100644 index 000000000..b6a9c5da1 --- /dev/null +++ b/src/Microsoft.ComponentDetection.Detectors/nuget/FrameworkPackages/FrameworkPackages.cs @@ -0,0 +1,122 @@ +namespace Microsoft.ComponentDetection.Detectors.NuGet; + +using System; +using System.Collections; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using global::NuGet.Frameworks; +using global::NuGet.Versioning; + +/// +/// Represents a set of packages that are provided by a specific framework. +/// At the moment this only represents the packages that are provided by the Microsoft.NETCore.App framework. +/// We could extend this to represent the packages provided by other frameworks like Microsoft.AspNetCore.App and Microsoft.WindowsDesktop.App. +/// +internal sealed partial class FrameworkPackages : IEnumerable>, IEnumerable +{ + private static readonly Dictionary FrameworkPackagesByFramework = []; + + static FrameworkPackages() + { + AddPackages(NETStandard20.Instance); + AddPackages(NETStandard21.Instance); + AddPackages(NETCoreApp20.Instance); + AddPackages(NETCoreApp21.Instance); + AddPackages(NETCoreApp22.Instance); + AddPackages(NETCoreApp30.Instance); + AddPackages(NETCoreApp31.Instance); + AddPackages(NETCoreApp50.Instance); + AddPackages(NETCoreApp60.Instance); + AddPackages(NETCoreApp70.Instance); + AddPackages(NETCoreApp80.Instance); + AddPackages(NETCoreApp90.Instance); + + static void AddPackages(FrameworkPackages packages) => FrameworkPackagesByFramework[packages.Framework] = packages; + } + + public FrameworkPackages(NuGetFramework framework) => this.Framework = framework; + + public FrameworkPackages(NuGetFramework framework, FrameworkPackages frameworkPackages) + : this(framework) => this.Packages = new(frameworkPackages.Packages); + + public NuGetFramework Framework { get; } + + public Dictionary Packages { get; } = new Dictionary(StringComparer.OrdinalIgnoreCase); + + public static FrameworkPackages GetFrameworkPackages(NuGetFramework framework) + { + if (FrameworkPackagesByFramework.TryGetValue(framework, out var frameworkPackages)) + { + return frameworkPackages; + } + + // if we didn't predefine the package overrides, load them from the targeting pack + // we might just leave this out since in future frameworks we'll have this functionality built into NuGet. + var frameworkPackagesFromPack = LoadFrameworkPackagesFromPack(framework); + + return FrameworkPackagesByFramework[framework] = frameworkPackagesFromPack ?? new FrameworkPackages(framework); + } + + private static FrameworkPackages LoadFrameworkPackagesFromPack(NuGetFramework framework) + { + if (framework is null || framework.Framework != FrameworkConstants.FrameworkIdentifiers.NetCoreApp) + { + return null; + } + + // packs location : %ProgramFiles%\dotnet\packs + var packsFolder = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles), "dotnet", "packs", "Microsoft.NETCore.App.Ref"); + if (!Directory.Exists(packsFolder)) + { + return null; + } + + var packVersionPattern = $"{framework.Version.Major}.{framework.Version.Minor}.*"; + var packDirectories = Directory.GetDirectories(packsFolder, packVersionPattern); + var packageOverridesFile = packDirectories + .Select(d => (Overrides: Path.Combine(d, "data", "PackageOverrides.txt"), Version: ParseVersion(Path.GetFileName(d)))) + .Where(d => File.Exists(d.Overrides)) + .OrderByDescending(d => d.Version) + .FirstOrDefault().Overrides; + + if (packageOverridesFile == null) + { + // we should also try to grab them from the user's package folder - they'll be in one location or the other. + return null; + } + + // Adapted from https://github.com/dotnet/sdk/blob/c3a8f72c3a5491c693ff8e49e7406136a12c3040/src/Tasks/Common/ConflictResolution/PackageOverride.cs#L52-L68 + var frameworkPackages = new FrameworkPackages(framework); + var packageOverrides = File.ReadAllLines(packageOverridesFile); + + foreach (var packageOverride in packageOverrides) + { + var packageOverrideParts = packageOverride.Trim().Split('|'); + + if (packageOverrideParts.Length == 2) + { + var packageId = packageOverrideParts[0]; + var packageVersion = ParseVersion(packageOverrideParts[1]); + + frameworkPackages.Packages[packageId] = packageVersion; + } + } + + return frameworkPackages; + + static NuGetVersion ParseVersion(string versionString) => NuGetVersion.TryParse(versionString, out var version) ? version : null; + } + + private void Add(string id, string version) + { + // intentionally redirect to indexer to allow for overwrite + this.Packages[id] = NuGetVersion.Parse(version); + } + + public bool IsAFrameworkComponent(string id, NuGetVersion version) => this.Packages.TryGetValue(id, out var frameworkPackageVersion) && frameworkPackageVersion >= version; + + IEnumerator> IEnumerable>.GetEnumerator() => this.Packages.GetEnumerator(); + + IEnumerator IEnumerable.GetEnumerator() => throw new NotImplementedException(); +} diff --git a/src/Microsoft.ComponentDetection.Detectors/nuget/FrameworkPackages/FrameworkPackages.net5.0.cs b/src/Microsoft.ComponentDetection.Detectors/nuget/FrameworkPackages/FrameworkPackages.net5.0.cs new file mode 100644 index 000000000..0326c7dfb --- /dev/null +++ b/src/Microsoft.ComponentDetection.Detectors/nuget/FrameworkPackages/FrameworkPackages.net5.0.cs @@ -0,0 +1,81 @@ +namespace Microsoft.ComponentDetection.Detectors.NuGet; + +using global::NuGet.Frameworks; + +/// +/// Framework packages for net5.0. +/// +internal partial class FrameworkPackages +{ + internal static class NETCoreApp50 + { + internal static FrameworkPackages Instance { get; } = new(NuGetFramework.Parse("net5.0"), NETCoreApp31.Instance) + { + { "Microsoft.CSharp", "4.7.0" }, + { "runtime.debian.8-x64.runtime.native.System", "4.3.0" }, + { "runtime.debian.8-x64.runtime.native.System.IO.Compression", "4.3.0" }, + { "runtime.debian.8-x64.runtime.native.System.Net.Http", "4.3.0" }, + { "runtime.debian.8-x64.runtime.native.System.Net.Security", "4.3.0" }, + { "runtime.debian.8-x64.runtime.native.System.Security.Cryptography", "4.3.0" }, + { "runtime.fedora.23-x64.runtime.native.System", "4.3.0" }, + { "runtime.fedora.23-x64.runtime.native.System.IO.Compression", "4.3.0" }, + { "runtime.fedora.23-x64.runtime.native.System.Net.Http", "4.3.0" }, + { "runtime.fedora.23-x64.runtime.native.System.Net.Security", "4.3.0" }, + { "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography", "4.3.0" }, + { "runtime.fedora.24-x64.runtime.native.System", "4.3.0" }, + { "runtime.fedora.24-x64.runtime.native.System.IO.Compression", "4.3.0" }, + { "runtime.fedora.24-x64.runtime.native.System.Net.Http", "4.3.0" }, + { "runtime.fedora.24-x64.runtime.native.System.Net.Security", "4.3.0" }, + { "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography", "4.3.0" }, + { "runtime.opensuse.13.2-x64.runtime.native.System", "4.3.0" }, + { "runtime.opensuse.13.2-x64.runtime.native.System.IO.Compression", "4.3.0" }, + { "runtime.opensuse.13.2-x64.runtime.native.System.Net.Http", "4.3.0" }, + { "runtime.opensuse.13.2-x64.runtime.native.System.Net.Security", "4.3.0" }, + { "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography", "4.3.0" }, + { "runtime.opensuse.42.1-x64.runtime.native.System", "4.3.0" }, + { "runtime.opensuse.42.1-x64.runtime.native.System.IO.Compression", "4.3.0" }, + { "runtime.opensuse.42.1-x64.runtime.native.System.Net.Http", "4.3.0" }, + { "runtime.opensuse.42.1-x64.runtime.native.System.Net.Security", "4.3.0" }, + { "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography", "4.3.0" }, + { "runtime.osx.10.10-x64.runtime.native.System", "4.3.0" }, + { "runtime.osx.10.10-x64.runtime.native.System.IO.Compression", "4.3.0" }, + { "runtime.osx.10.10-x64.runtime.native.System.Net.Http", "4.3.0" }, + { "runtime.osx.10.10-x64.runtime.native.System.Net.Security", "4.3.0" }, + { "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography", "4.3.0" }, + { "runtime.rhel.7-x64.runtime.native.System", "4.3.0" }, + { "runtime.rhel.7-x64.runtime.native.System.IO.Compression", "4.3.0" }, + { "runtime.rhel.7-x64.runtime.native.System.Net.Http", "4.3.0" }, + { "runtime.rhel.7-x64.runtime.native.System.Net.Security", "4.3.0" }, + { "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography", "4.3.0" }, + { "runtime.ubuntu.14.04-x64.runtime.native.System", "4.3.0" }, + { "runtime.ubuntu.14.04-x64.runtime.native.System.IO.Compression", "4.3.0" }, + { "runtime.ubuntu.14.04-x64.runtime.native.System.Net.Http", "4.3.0" }, + { "runtime.ubuntu.14.04-x64.runtime.native.System.Net.Security", "4.3.0" }, + { "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography", "4.3.0" }, + { "runtime.ubuntu.16.04-x64.runtime.native.System", "4.3.0" }, + { "runtime.ubuntu.16.04-x64.runtime.native.System.IO.Compression", "4.3.0" }, + { "runtime.ubuntu.16.04-x64.runtime.native.System.Net.Http", "4.3.0" }, + { "runtime.ubuntu.16.04-x64.runtime.native.System.Net.Security", "4.3.0" }, + { "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography", "4.3.0" }, + { "runtime.ubuntu.16.10-x64.runtime.native.System", "4.3.0" }, + { "runtime.ubuntu.16.10-x64.runtime.native.System.IO.Compression", "4.3.0" }, + { "runtime.ubuntu.16.10-x64.runtime.native.System.Net.Http", "4.3.0" }, + { "runtime.ubuntu.16.10-x64.runtime.native.System.Net.Security", "4.3.0" }, + { "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography", "4.3.0" }, + { "System.Buffers", "4.5.1" }, + { "System.Collections.Immutable", "5.0.0" }, + { "System.ComponentModel.Annotations", "5.0.0" }, + { "System.Diagnostics.DiagnosticSource", "5.0.0" }, + { "System.Formats.Asn1", "5.0.0" }, + { "System.Net.Http.Json", "5.0.0" }, + { "System.Reflection.DispatchProxy", "4.7.1" }, + { "System.Reflection.Metadata", "5.0.0" }, + { "System.Runtime.CompilerServices.Unsafe", "5.0.0" }, + { "System.Text.Encoding.CodePages", "5.0.0" }, + { "System.Text.Encodings.Web", "5.0.0" }, + { "System.Text.Json", "5.0.0" }, + { "System.Threading.Channels", "5.0.0" }, + { "System.Threading.Tasks.Dataflow", "5.0.0" }, + }; + } +} diff --git a/src/Microsoft.ComponentDetection.Detectors/nuget/FrameworkPackages/FrameworkPackages.net6.0.cs b/src/Microsoft.ComponentDetection.Detectors/nuget/FrameworkPackages/FrameworkPackages.net6.0.cs new file mode 100644 index 000000000..ca5b2d92b --- /dev/null +++ b/src/Microsoft.ComponentDetection.Detectors/nuget/FrameworkPackages/FrameworkPackages.net6.0.cs @@ -0,0 +1,34 @@ +namespace Microsoft.ComponentDetection.Detectors.NuGet; + +using global::NuGet.Frameworks; + +/// +/// Framework packages for net6.0. +/// +internal partial class FrameworkPackages +{ + internal static class NETCoreApp60 + { + internal static FrameworkPackages Instance { get; } = new(NuGetFramework.Parse("net6.0"), NETCoreApp50.Instance) + { + { "Microsoft.Win32.Registry", "5.0.0" }, + { "System.Collections.Immutable", "6.0.0" }, + { "System.Diagnostics.DiagnosticSource", "6.0.1" }, + { "System.Formats.Asn1", "6.0.1" }, + { "System.IO.FileSystem.AccessControl", "5.0.0" }, + { "System.IO.Pipes.AccessControl", "5.0.0" }, + { "System.Net.Http.Json", "6.0.1" }, + { "System.Reflection.Metadata", "6.0.1" }, + { "System.Runtime.CompilerServices.Unsafe", "6.0.0" }, + { "System.Security.AccessControl", "6.0.1" }, + { "System.Security.Cryptography.Cng", "5.0.0" }, + { "System.Security.Cryptography.OpenSsl", "5.0.0" }, + { "System.Security.Principal.Windows", "5.0.0" }, + { "System.Text.Encoding.CodePages", "6.0.0" }, + { "System.Text.Encodings.Web", "6.0.0" }, + { "System.Text.Json", "6.0.9" }, + { "System.Threading.Channels", "6.0.0" }, + { "System.Threading.Tasks.Dataflow", "6.0.0" }, + }; + } +} diff --git a/src/Microsoft.ComponentDetection.Detectors/nuget/FrameworkPackages/FrameworkPackages.net7.0.cs b/src/Microsoft.ComponentDetection.Detectors/nuget/FrameworkPackages/FrameworkPackages.net7.0.cs new file mode 100644 index 000000000..206d1a194 --- /dev/null +++ b/src/Microsoft.ComponentDetection.Detectors/nuget/FrameworkPackages/FrameworkPackages.net7.0.cs @@ -0,0 +1,26 @@ +namespace Microsoft.ComponentDetection.Detectors.NuGet; + +using global::NuGet.Frameworks; + +/// +/// Framework packages for net7.0. +/// +internal partial class FrameworkPackages +{ + internal static class NETCoreApp70 + { + internal static FrameworkPackages Instance { get; } = new(NuGetFramework.Parse("net7.0"), NETCoreApp60.Instance) + { + { "System.Collections.Immutable", "7.0.0" }, + { "System.Diagnostics.DiagnosticSource", "7.0.2" }, + { "System.Formats.Asn1", "7.0.0" }, + { "System.Net.Http.Json", "7.0.1" }, + { "System.Reflection.Metadata", "7.0.2" }, + { "System.Text.Encoding.CodePages", "7.0.0" }, + { "System.Text.Encodings.Web", "7.0.0" }, + { "System.Text.Json", "7.0.4" }, + { "System.Threading.Channels", "7.0.0" }, + { "System.Threading.Tasks.Dataflow", "7.0.0" }, + }; + } +} diff --git a/src/Microsoft.ComponentDetection.Detectors/nuget/FrameworkPackages/FrameworkPackages.net8.0.cs b/src/Microsoft.ComponentDetection.Detectors/nuget/FrameworkPackages/FrameworkPackages.net8.0.cs new file mode 100644 index 000000000..86a273591 --- /dev/null +++ b/src/Microsoft.ComponentDetection.Detectors/nuget/FrameworkPackages/FrameworkPackages.net8.0.cs @@ -0,0 +1,26 @@ +namespace Microsoft.ComponentDetection.Detectors.NuGet; + +using global::NuGet.Frameworks; + +/// +/// Framework packages for net8.0. +/// +internal partial class FrameworkPackages +{ + internal static class NETCoreApp80 + { + internal static FrameworkPackages Instance { get; } = new(NuGetFramework.Parse("net8.0"), NETCoreApp70.Instance) + { + { "System.Collections.Immutable", "8.0.0" }, + { "System.Diagnostics.DiagnosticSource", "8.0.1" }, + { "System.Formats.Asn1", "8.0.1" }, + { "System.Net.Http.Json", "8.0.0" }, + { "System.Reflection.Metadata", "8.0.0" }, + { "System.Text.Encoding.CodePages", "8.0.0" }, + { "System.Text.Encodings.Web", "8.0.0" }, + { "System.Text.Json", "8.0.4" }, + { "System.Threading.Channels", "8.0.0" }, + { "System.Threading.Tasks.Dataflow", "8.0.1" }, + }; + } +} diff --git a/src/Microsoft.ComponentDetection.Detectors/nuget/FrameworkPackages/FrameworkPackages.net9.0.cs b/src/Microsoft.ComponentDetection.Detectors/nuget/FrameworkPackages/FrameworkPackages.net9.0.cs new file mode 100644 index 000000000..484c7e8a3 --- /dev/null +++ b/src/Microsoft.ComponentDetection.Detectors/nuget/FrameworkPackages/FrameworkPackages.net9.0.cs @@ -0,0 +1,98 @@ +namespace Microsoft.ComponentDetection.Detectors.NuGet; + +using global::NuGet.Frameworks; + +/// +/// Framework packages for net9.0. +/// +internal partial class FrameworkPackages +{ + internal static class NETCoreApp90 + { + internal static FrameworkPackages Instance { get; } = new(NuGetFramework.Parse("net9.0"), NETCoreApp80.Instance) + { + { "Microsoft.VisualBasic", "10.4.0" }, + { "runtime.debian.8-x64.runtime.native.System", "4.3.1" }, + { "runtime.debian.8-x64.runtime.native.System.IO.Compression", "4.3.2" }, + { "runtime.debian.8-x64.runtime.native.System.Net.Http", "4.3.1" }, + { "runtime.debian.8-x64.runtime.native.System.Net.Security", "4.3.1" }, + { "runtime.debian.8-x64.runtime.native.System.Security.Cryptography", "4.3.4" }, + { "runtime.debian.8-x64.runtime.native.System.Security.Cryptography.OpenSsl", "4.3.3" }, + { "runtime.fedora.23-x64.runtime.native.System", "4.3.1" }, + { "runtime.fedora.23-x64.runtime.native.System.IO.Compression", "4.3.2" }, + { "runtime.fedora.23-x64.runtime.native.System.Net.Http", "4.3.1" }, + { "runtime.fedora.23-x64.runtime.native.System.Net.Security", "4.3.1" }, + { "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography", "4.3.4" }, + { "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography.OpenSsl", "4.3.3" }, + { "runtime.fedora.24-x64.runtime.native.System", "4.3.1" }, + { "runtime.fedora.24-x64.runtime.native.System.IO.Compression", "4.3.2" }, + { "runtime.fedora.24-x64.runtime.native.System.Net.Http", "4.3.1" }, + { "runtime.fedora.24-x64.runtime.native.System.Net.Security", "4.3.1" }, + { "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography", "4.3.4" }, + { "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography.OpenSsl", "4.3.3" }, + { "runtime.opensuse.13.2-x64.runtime.native.System", "4.3.1" }, + { "runtime.opensuse.13.2-x64.runtime.native.System.IO.Compression", "4.3.2" }, + { "runtime.opensuse.13.2-x64.runtime.native.System.Net.Http", "4.3.1" }, + { "runtime.opensuse.13.2-x64.runtime.native.System.Net.Security", "4.3.1" }, + { "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography", "4.3.4" }, + { "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography.OpenSsl", "4.3.3" }, + { "runtime.opensuse.42.1-x64.runtime.native.System", "4.3.1" }, + { "runtime.opensuse.42.1-x64.runtime.native.System.IO.Compression", "4.3.2" }, + { "runtime.opensuse.42.1-x64.runtime.native.System.Net.Http", "4.3.1" }, + { "runtime.opensuse.42.1-x64.runtime.native.System.Net.Security", "4.3.1" }, + { "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography", "4.3.4" }, + { "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography.OpenSsl", "4.3.3" }, + { "runtime.osx.10.10-x64.runtime.native.System", "4.3.1" }, + { "runtime.osx.10.10-x64.runtime.native.System.IO.Compression", "4.3.2" }, + { "runtime.osx.10.10-x64.runtime.native.System.Net.Http", "4.3.1" }, + { "runtime.osx.10.10-x64.runtime.native.System.Net.Security", "4.3.1" }, + { "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography", "4.3.4" }, + { "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.Apple", "4.3.1" }, + { "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.OpenSsl", "4.3.3" }, + { "runtime.rhel.7-x64.runtime.native.System", "4.3.1" }, + { "runtime.rhel.7-x64.runtime.native.System.IO.Compression", "4.3.2" }, + { "runtime.rhel.7-x64.runtime.native.System.Net.Http", "4.3.1" }, + { "runtime.rhel.7-x64.runtime.native.System.Net.Security", "4.3.1" }, + { "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography", "4.3.4" }, + { "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography.OpenSsl", "4.3.3" }, + { "runtime.ubuntu.14.04-x64.runtime.native.System", "4.3.1" }, + { "runtime.ubuntu.14.04-x64.runtime.native.System.IO.Compression", "4.3.2" }, + { "runtime.ubuntu.14.04-x64.runtime.native.System.Net.Http", "4.3.1" }, + { "runtime.ubuntu.14.04-x64.runtime.native.System.Net.Security", "4.3.1" }, + { "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography", "4.3.4" }, + { "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography.OpenSsl", "4.3.3" }, + { "runtime.ubuntu.16.04-x64.runtime.native.System", "4.3.1" }, + { "runtime.ubuntu.16.04-x64.runtime.native.System.IO.Compression", "4.3.2" }, + { "runtime.ubuntu.16.04-x64.runtime.native.System.Net.Http", "4.3.1" }, + { "runtime.ubuntu.16.04-x64.runtime.native.System.Net.Security", "4.3.1" }, + { "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography", "4.3.4" }, + { "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography.OpenSsl", "4.3.3" }, + { "runtime.ubuntu.16.10-x64.runtime.native.System", "4.3.1" }, + { "runtime.ubuntu.16.10-x64.runtime.native.System.IO.Compression", "4.3.2" }, + { "runtime.ubuntu.16.10-x64.runtime.native.System.Net.Http", "4.3.1" }, + { "runtime.ubuntu.16.10-x64.runtime.native.System.Net.Security", "4.3.1" }, + { "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography", "4.3.4" }, + { "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography.OpenSsl", "4.3.3" }, + { "System.Buffers", "5.0.0" }, + { "System.Collections.Immutable", "9.0.0" }, + { "System.Diagnostics.DiagnosticSource", "9.0.0" }, + { "System.Formats.Asn1", "9.0.0" }, + { "System.Formats.Tar", "9.0.0" }, + { "System.IO.Pipelines", "9.0.0" }, + { "System.Memory", "5.0.0" }, + { "System.Net.Http.Json", "9.0.0" }, + { "System.Numerics.Vectors", "5.0.0" }, + { "System.Private.Uri", "4.3.2" }, + { "System.Reflection.DispatchProxy", "6.0.0" }, + { "System.Reflection.Metadata", "9.0.0" }, + { "System.Runtime.CompilerServices.Unsafe", "7.0.0" }, + { "System.Text.Encoding.CodePages", "9.0.0" }, + { "System.Text.Encodings.Web", "9.0.0" }, + { "System.Text.Json", "9.0.0" }, + { "System.Threading.Channels", "9.0.0" }, + { "System.Threading.Tasks.Dataflow", "9.0.0" }, + { "System.Threading.Tasks.Extensions", "5.0.0" }, + { "System.Xml.XPath.XDocument", "5.0.0" }, + }; + } +} diff --git a/src/Microsoft.ComponentDetection.Detectors/nuget/FrameworkPackages/FrameworkPackages.netcoreapp2.0.cs b/src/Microsoft.ComponentDetection.Detectors/nuget/FrameworkPackages/FrameworkPackages.netcoreapp2.0.cs new file mode 100644 index 000000000..dc934febf --- /dev/null +++ b/src/Microsoft.ComponentDetection.Detectors/nuget/FrameworkPackages/FrameworkPackages.netcoreapp2.0.cs @@ -0,0 +1,49 @@ +namespace Microsoft.ComponentDetection.Detectors.NuGet; + +using global::NuGet.Frameworks; + +/// +/// Framework packages for .NETCoreApp,Version=v2.0. +/// +internal partial class FrameworkPackages +{ + internal static class NETCoreApp20 + { + internal static FrameworkPackages Instance { get; } = new(NuGetFramework.Parse("netcoreapp2.0"), NETStandard20.Instance) + { + { "System.Buffers", "4.4.0" }, + { "System.Collections.Concurrent", "4.3.0" }, + { "System.Collections.Immutable", "1.4.0" }, + { "System.ComponentModel", "4.3.0" }, + { "System.ComponentModel.Annotations", "4.4.0" }, + { "System.ComponentModel.EventBasedAsync", "4.3.0" }, + { "System.Diagnostics.Contracts", "4.3.0" }, + { "System.Diagnostics.DiagnosticSource", "4.4.1" }, + { "System.Dynamic.Runtime", "4.3.0" }, + { "System.Linq.Parallel", "4.3.0" }, + { "System.Linq.Queryable", "4.3.0" }, + { "System.Net.Requests", "4.3.0" }, + { "System.Net.WebHeaderCollection", "4.3.0" }, + { "System.Numerics.Vectors", "4.4.0" }, + { "System.ObjectModel", "4.3.0" }, + { "System.Reflection.DispatchProxy", "4.4.0" }, + { "System.Reflection.Emit", "4.7.0" }, + { "System.Reflection.Emit.ILGeneration", "4.7.0" }, + { "System.Reflection.Emit.Lightweight", "4.7.0" }, + { "System.Reflection.Metadata", "1.5.0" }, + { "System.Reflection.TypeExtensions", "4.7.0" }, + { "System.Runtime.InteropServices.WindowsRuntime", "4.3.0" }, + { "System.Runtime.Loader", "4.3.0" }, + { "System.Runtime.Numerics", "4.3.0" }, + { "System.Runtime.Serialization.Json", "4.3.0" }, + { "System.Security.Principal", "4.3.0" }, + { "System.Text.RegularExpressions", "4.3.1" }, + { "System.Threading", "4.3.0" }, + { "System.Threading.Tasks.Dataflow", "4.8.0" }, + { "System.Threading.Tasks.Extensions", "4.4.0" }, + { "System.Threading.Tasks.Parallel", "4.3.0" }, + { "System.Xml.XDocument", "4.3.0" }, + { "System.Xml.XmlSerializer", "4.3.0" }, + }; + } +} diff --git a/src/Microsoft.ComponentDetection.Detectors/nuget/FrameworkPackages/FrameworkPackages.netcoreapp2.1.cs b/src/Microsoft.ComponentDetection.Detectors/nuget/FrameworkPackages/FrameworkPackages.netcoreapp2.1.cs new file mode 100644 index 000000000..ef5489f50 --- /dev/null +++ b/src/Microsoft.ComponentDetection.Detectors/nuget/FrameworkPackages/FrameworkPackages.netcoreapp2.1.cs @@ -0,0 +1,25 @@ +namespace Microsoft.ComponentDetection.Detectors.NuGet; + +using global::NuGet.Frameworks; + +/// +/// Framework packages for .NETCoreApp,Version=v2.1. +/// +internal partial class FrameworkPackages +{ + internal static class NETCoreApp21 + { + internal static FrameworkPackages Instance { get; } = new(NuGetFramework.Parse("netcoreapp2.1"), NETCoreApp20.Instance) + { + { "System.Collections.Immutable", "1.5.0" }, + { "System.ComponentModel.Annotations", "4.4.1" }, + { "System.Diagnostics.DiagnosticSource", "4.5.0" }, + { "System.Memory", "4.5.5" }, + { "System.Reflection.DispatchProxy", "4.5.0" }, + { "System.Reflection.Metadata", "1.6.0" }, + { "System.Threading.Tasks.Dataflow", "4.9.0" }, + { "System.Threading.Tasks.Extensions", "4.5.4" }, + { "System.ValueTuple", "4.5.0" }, + }; + } +} diff --git a/src/Microsoft.ComponentDetection.Detectors/nuget/FrameworkPackages/FrameworkPackages.netcoreapp2.2.cs b/src/Microsoft.ComponentDetection.Detectors/nuget/FrameworkPackages/FrameworkPackages.netcoreapp2.2.cs new file mode 100644 index 000000000..d9fa440fa --- /dev/null +++ b/src/Microsoft.ComponentDetection.Detectors/nuget/FrameworkPackages/FrameworkPackages.netcoreapp2.2.cs @@ -0,0 +1,15 @@ +namespace Microsoft.ComponentDetection.Detectors.NuGet; + +using global::NuGet.Frameworks; + +/// +/// Framework packages for .NETCoreApp,Version=v2.2. +/// +internal partial class FrameworkPackages +{ + internal static class NETCoreApp22 + { + // .NETCore 2.2 was the same as .NETCore 2.1 + internal static FrameworkPackages Instance { get; } = new(NuGetFramework.Parse("netcoreapp2.2"), NETCoreApp21.Instance); + } +} diff --git a/src/Microsoft.ComponentDetection.Detectors/nuget/FrameworkPackages/FrameworkPackages.netcoreapp3.0.cs b/src/Microsoft.ComponentDetection.Detectors/nuget/FrameworkPackages/FrameworkPackages.netcoreapp3.0.cs new file mode 100644 index 000000000..84f3c854d --- /dev/null +++ b/src/Microsoft.ComponentDetection.Detectors/nuget/FrameworkPackages/FrameworkPackages.netcoreapp3.0.cs @@ -0,0 +1,49 @@ +namespace Microsoft.ComponentDetection.Detectors.NuGet; + +using global::NuGet.Frameworks; + +/// +/// Framework packages for .NETCoreApp,Version=v3.0. +/// +internal partial class FrameworkPackages +{ + internal static class NETCoreApp30 + { + internal static FrameworkPackages Instance { get; } = new(NuGetFramework.Parse("netcoreapp3.0"), NETCoreApp21.Instance) + { + { "Microsoft.CSharp", "4.4.0" }, + { "Microsoft.Win32.Registry", "4.4.0" }, + { "runtime.debian.8-x64.runtime.native.System.Security.Cryptography.OpenSsl", "4.3.0" }, + { "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography.OpenSsl", "4.3.0" }, + { "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography.OpenSsl", "4.3.0" }, + { "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography.OpenSsl", "4.3.0" }, + { "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography.OpenSsl", "4.3.0" }, + { "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.Apple", "4.3.0" }, + { "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.OpenSsl", "4.3.0" }, + { "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography.OpenSsl", "4.3.0" }, + { "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography.OpenSsl", "4.3.0" }, + { "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography.OpenSsl", "4.3.0" }, + { "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography.OpenSsl", "4.3.0" }, + { "System.Collections.Immutable", "1.6.0" }, + { "System.ComponentModel.Annotations", "4.6.0" }, + { "System.Data.DataSetExtensions", "4.5.0" }, + { "System.Diagnostics.DiagnosticSource", "4.6.0" }, + { "System.IO.FileSystem.AccessControl", "4.4.0" }, + { "System.Numerics.Vectors", "4.5.0" }, + { "System.Private.DataContractSerialization", "4.3.0" }, + { "System.Reflection.DispatchProxy", "4.6.0" }, + { "System.Reflection.Metadata", "1.7.0" }, + { "System.Runtime.CompilerServices.Unsafe", "4.6.0" }, + { "System.Security.AccessControl", "4.4.0" }, + { "System.Security.Cryptography.Cng", "4.4.0" }, + { "System.Security.Cryptography.OpenSsl", "4.4.0" }, + { "System.Security.Cryptography.Xml", "4.4.0" }, + { "System.Security.Principal.Windows", "4.4.0" }, + { "System.Text.Encoding.CodePages", "4.6.0" }, + { "System.Text.Encodings.Web", "4.6.0" }, + { "System.Text.Json", "4.6.0" }, + { "System.Threading.Channels", "4.6.0" }, + { "System.Threading.Tasks.Dataflow", "4.10.0" }, + }; + } +} diff --git a/src/Microsoft.ComponentDetection.Detectors/nuget/FrameworkPackages/FrameworkPackages.netcoreapp3.1.cs b/src/Microsoft.ComponentDetection.Detectors/nuget/FrameworkPackages/FrameworkPackages.netcoreapp3.1.cs new file mode 100644 index 000000000..f7f879843 --- /dev/null +++ b/src/Microsoft.ComponentDetection.Detectors/nuget/FrameworkPackages/FrameworkPackages.netcoreapp3.1.cs @@ -0,0 +1,27 @@ +namespace Microsoft.ComponentDetection.Detectors.NuGet; + +using global::NuGet.Frameworks; + +/// +/// Framework packages for .NETCoreApp,Version=v3.1. +/// +internal partial class FrameworkPackages +{ + internal static class NETCoreApp31 + { + internal static FrameworkPackages Instance { get; } = new(NuGetFramework.Parse("netcoreapp3.1"), NETCoreApp30.Instance) + { + { "System.Collections.Immutable", "1.7.0" }, + { "System.ComponentModel.Annotations", "4.7.0" }, + { "System.Diagnostics.DiagnosticSource", "4.7.0" }, + { "System.Reflection.DispatchProxy", "4.7.0" }, + { "System.Reflection.Metadata", "1.8.0" }, + { "System.Runtime.CompilerServices.Unsafe", "4.7.1" }, + { "System.Text.Encoding.CodePages", "4.7.0" }, + { "System.Text.Encodings.Web", "4.7.0" }, + { "System.Text.Json", "4.7.0" }, + { "System.Threading.Channels", "4.7.0" }, + { "System.Threading.Tasks.Dataflow", "4.11.0" }, + }; + } +} diff --git a/src/Microsoft.ComponentDetection.Detectors/nuget/FrameworkPackages/FrameworkPackages.netstandard2.0.cs b/src/Microsoft.ComponentDetection.Detectors/nuget/FrameworkPackages/FrameworkPackages.netstandard2.0.cs new file mode 100644 index 000000000..8095fb527 --- /dev/null +++ b/src/Microsoft.ComponentDetection.Detectors/nuget/FrameworkPackages/FrameworkPackages.netstandard2.0.cs @@ -0,0 +1,108 @@ +namespace Microsoft.ComponentDetection.Detectors.NuGet; + +using global::NuGet.Frameworks; + +/// +/// Framework packages for .NETStandard,Version=v2.0. +/// +internal partial class FrameworkPackages +{ + internal static class NETStandard20 + { + internal static FrameworkPackages Instance { get; } = new(NuGetFramework.Parse("netstandard2.0")) + { + { "Microsoft.Win32.Primitives", "4.3.0" }, + { "System.AppContext", "4.3.0" }, + { "System.Collections", "4.3.0" }, + { "System.Collections.NonGeneric", "4.3.0" }, + { "System.Collections.Specialized", "4.3.0" }, + { "System.ComponentModel", "4.0.1" }, + { "System.ComponentModel.EventBasedAsync", "4.0.11" }, + { "System.ComponentModel.Primitives", "4.3.0" }, + { "System.ComponentModel.TypeConverter", "4.3.0" }, + { "System.Console", "4.3.1" }, + { "System.Data.Common", "4.3.0" }, + { "System.Diagnostics.Contracts", "4.0.1" }, + { "System.Diagnostics.Debug", "4.3.0" }, + { "System.Diagnostics.FileVersionInfo", "4.3.0" }, + { "System.Diagnostics.Process", "4.3.0" }, + { "System.Diagnostics.StackTrace", "4.3.0" }, + { "System.Diagnostics.TextWriterTraceListener", "4.3.0" }, + { "System.Diagnostics.Tools", "4.3.0" }, + { "System.Diagnostics.TraceSource", "4.3.0" }, + { "System.Diagnostics.Tracing", "4.3.0" }, + { "System.Drawing.Primitives", "4.3.0" }, + { "System.Dynamic.Runtime", "4.0.11" }, + { "System.Globalization", "4.3.0" }, + { "System.Globalization.Calendars", "4.3.0" }, + { "System.Globalization.Extensions", "4.3.0" }, + { "System.IO", "4.3.0" }, + { "System.IO.Compression", "4.3.0" }, + { "System.IO.Compression.ZipFile", "4.3.0" }, + { "System.IO.FileSystem", "4.3.0" }, + { "System.IO.FileSystem.DriveInfo", "4.3.1" }, + { "System.IO.FileSystem.Primitives", "4.3.0" }, + { "System.IO.FileSystem.Watcher", "4.3.0" }, + { "System.IO.IsolatedStorage", "4.3.0" }, + { "System.IO.MemoryMappedFiles", "4.3.0" }, + { "System.IO.Pipes", "4.3.0" }, + { "System.IO.UnmanagedMemoryStream", "4.3.0" }, + { "System.Linq", "4.3.0" }, + { "System.Linq.Expressions", "4.3.0" }, + { "System.Linq.Parallel", "4.0.1" }, + { "System.Linq.Queryable", "4.0.1" }, + { "System.Net.Http", "4.3.4" }, + { "System.Net.NameResolution", "4.3.0" }, + { "System.Net.NetworkInformation", "4.3.0" }, + { "System.Net.Ping", "4.3.0" }, + { "System.Net.Primitives", "4.3.1" }, + { "System.Net.Requests", "4.0.11" }, + { "System.Net.Security", "4.3.2" }, + { "System.Net.Sockets", "4.3.0" }, + { "System.Net.WebHeaderCollection", "4.0.1" }, + { "System.Net.WebSockets", "4.3.0" }, + { "System.Net.WebSockets.Client", "4.3.2" }, + { "System.Reflection", "4.3.0" }, + { "System.Reflection.Extensions", "4.3.0" }, + { "System.Reflection.Primitives", "4.3.0" }, + { "System.Resources.Reader", "4.3.0" }, + { "System.Resources.ResourceManager", "4.3.0" }, + { "System.Resources.Writer", "4.3.0" }, + { "System.Runtime", "4.3.1" }, + { "System.Runtime.CompilerServices.VisualC", "4.3.0" }, + { "System.Runtime.Extensions", "4.3.1" }, + { "System.Runtime.Handles", "4.3.0" }, + { "System.Runtime.InteropServices", "4.3.0" }, + { "System.Runtime.InteropServices.RuntimeInformation", "4.3.0" }, + { "System.Runtime.Numerics", "4.0.1" }, + { "System.Runtime.Serialization.Formatters", "4.3.0" }, + { "System.Runtime.Serialization.Primitives", "4.3.0" }, + { "System.Runtime.Serialization.Xml", "4.3.0" }, + { "System.Security.Claims", "4.3.0" }, + { "System.Security.Cryptography.Algorithms", "4.3.1" }, + { "System.Security.Cryptography.Csp", "4.3.0" }, + { "System.Security.Cryptography.Encoding", "4.3.0" }, + { "System.Security.Cryptography.Primitives", "4.3.0" }, + { "System.Security.Cryptography.X509Certificates", "4.3.2" }, + { "System.Security.Principal", "4.0.1" }, + { "System.Security.SecureString", "4.3.0" }, + { "System.Text.Encoding", "4.3.0" }, + { "System.Text.Encoding.Extensions", "4.3.0" }, + { "System.Text.RegularExpressions", "4.3.0" }, + { "System.Threading", "4.0.11" }, + { "System.Threading.Overlapped", "4.3.0" }, + { "System.Threading.Tasks", "4.3.0" }, + { "System.Threading.Tasks.Parallel", "4.0.1" }, + { "System.Threading.Thread", "4.3.0" }, + { "System.Threading.ThreadPool", "4.3.0" }, + { "System.Threading.Timer", "4.3.0" }, + { "System.ValueTuple", "4.4.0" }, + { "System.Xml.ReaderWriter", "4.3.1" }, + { "System.Xml.XDocument", "4.0.11" }, + { "System.Xml.XmlDocument", "4.3.0" }, + { "System.Xml.XmlSerializer", "4.0.11" }, + { "System.Xml.XPath", "4.3.0" }, + { "System.Xml.XPath.XDocument", "4.3.0" }, + }; + } +} diff --git a/src/Microsoft.ComponentDetection.Detectors/nuget/FrameworkPackages/FrameworkPackages.netstandard2.1.cs b/src/Microsoft.ComponentDetection.Detectors/nuget/FrameworkPackages/FrameworkPackages.netstandard2.1.cs new file mode 100644 index 000000000..9d5b2bc00 --- /dev/null +++ b/src/Microsoft.ComponentDetection.Detectors/nuget/FrameworkPackages/FrameworkPackages.netstandard2.1.cs @@ -0,0 +1,48 @@ +namespace Microsoft.ComponentDetection.Detectors.NuGet; + +using global::NuGet.Frameworks; + +/// +/// Framework packages for .NETStandard,Version=v2.1. +/// +internal partial class FrameworkPackages +{ + internal static class NETStandard21 + { + internal static FrameworkPackages Instance { get; } = new(NuGetFramework.Parse("netstandard2.1"), NETStandard20.Instance) + { + { "System.Buffers", "4.5.1" }, + { "System.Collections.Concurrent", "4.3.0" }, + { "System.Collections.Immutable", "1.4.0" }, + { "System.ComponentModel", "4.3.0" }, + { "System.ComponentModel.Composition", "4.5.0" }, + { "System.ComponentModel.EventBasedAsync", "4.3.0" }, + { "System.Diagnostics.Contracts", "4.3.0" }, + { "System.Dynamic.Runtime", "4.3.0" }, + { "System.Linq.Queryable", "4.3.0" }, + { "System.Memory", "4.5.5" }, + { "System.Net.Requests", "4.3.0" }, + { "System.Net.WebHeaderCollection", "4.3.0" }, + { "System.Numerics.Vectors", "4.5.0" }, + { "System.ObjectModel", "4.3.0" }, + { "System.Private.DataContractSerialization", "4.3.0" }, + { "System.Reflection.DispatchProxy", "4.5.1" }, + { "System.Reflection.Emit", "4.7.0" }, + { "System.Reflection.Emit.ILGeneration", "4.7.0" }, + { "System.Reflection.Emit.Lightweight", "4.7.0" }, + { "System.Reflection.TypeExtensions", "4.3.0" }, + { "System.Runtime.Loader", "4.3.0" }, + { "System.Runtime.Numerics", "4.3.0" }, + { "System.Runtime.Serialization.Json", "4.3.0" }, + { "System.Security.AccessControl", "4.4.0" }, + { "System.Security.Cryptography.Xml", "4.4.0" }, + { "System.Security.Principal", "4.3.0" }, + { "System.Security.Principal.Windows", "4.4.0" }, + { "System.Threading", "4.3.0" }, + { "System.Threading.Tasks.Extensions", "4.5.4" }, + { "System.Threading.Tasks.Parallel", "4.3.0" }, + { "System.Xml.XDocument", "4.3.0" }, + { "System.Xml.XmlSerializer", "4.3.0" }, + }; + } +} diff --git a/src/Microsoft.ComponentDetection.Detectors/nuget/FrameworkPackages/Readme.md b/src/Microsoft.ComponentDetection.Detectors/nuget/FrameworkPackages/Readme.md new file mode 100644 index 000000000..7b7b0982b --- /dev/null +++ b/src/Microsoft.ComponentDetection.Detectors/nuget/FrameworkPackages/Readme.md @@ -0,0 +1,7 @@ +The .NET SDK does conflict resolution with NuGet packages to decide when to ignore the content of packages in favor of that from other sources (like other packages or the Framework itself). + +Since packages are immutable - we can precompute what packages will "lose" to framework assets. These lists were calculated with a tool that downloads packages, resolve what assets apply for a given frameowrk, then compares those to the framework's reference assemblies. + +The framework targeting packs have a file to ship these precomputed package versions, [PackageOverrides.txt](https://github.com/dotnet/sdk/blob/7deb36232b9c0ccd5084fced1df07920c10a5b72/src/Tasks/Microsoft.NET.Build.Tasks/ResolveTargetingPackAssets.cs#L199) -- but that file wasn't kept up to date of the course framework versions. It was meant as a "fast path" not a source of truth. In future framework versions this will need to be the source of truth since it will feed into NuGet's supplied by platform feature. The latest version of this file for net9.0 can be seen [here](https://github.com/dotnet/runtime/blob/release/9.0/src/installer/pkg/sfx/Microsoft.NETCore.App/PackageOverrides.txt) and is included in the [targeting pack package](https://www.nuget.org/packages/Microsoft.NETCore.App.Ref). + +Once caclculating these we reduce them to a minimum set by allowing compatible frameworks to build upon the previous framework's data - thus reducing the total code size and memory usage of the set of framework packages. diff --git a/src/Microsoft.ComponentDetection.Detectors/nuget/NuGetPackagesConfigDetector.cs b/src/Microsoft.ComponentDetection.Detectors/nuget/NuGetPackagesConfigDetector.cs index 44a899c27..a2d0d1e6f 100644 --- a/src/Microsoft.ComponentDetection.Detectors/nuget/NuGetPackagesConfigDetector.cs +++ b/src/Microsoft.ComponentDetection.Detectors/nuget/NuGetPackagesConfigDetector.cs @@ -53,17 +53,34 @@ protected override Task OnFileFoundAsync(ProcessRequest processRequest, IDiction { try { + var singleFileComponentRecorder = processRequest.SingleFileComponentRecorder; var packagesConfig = new PackagesConfigReader(processRequest.ComponentStream.Stream); foreach (var package in packagesConfig.GetPackages(allowDuplicatePackageIds: true)) { - processRequest.SingleFileComponentRecorder.RegisterUsage( - new DetectedComponent( + var detectedComponent = new DetectedComponent( new NuGetComponent( package.PackageIdentity.Id, - package.PackageIdentity.Version.ToNormalizedString())), + package.PackageIdentity.Version.ToNormalizedString())); + + singleFileComponentRecorder.RegisterUsage( + detectedComponent, true, null, + /* TODO: Is this really the same concept? + Docs for NuGet say packages.config development dependencies are just not persisted as dependencies in the package. + That is not same as excluding from the output directory / runtime. */ package.IsDevelopmentDependency); + + // get the actual component in case it already exists + var libraryComponent = singleFileComponentRecorder.GetComponent(detectedComponent.Component.Id); + + // Add framework information to the actual component + var targetFramework = package.TargetFramework?.GetShortFolderName(); + + if (targetFramework is not null) + { + ((NuGetComponent)libraryComponent.Component).TargetFrameworks.Add(targetFramework); + } } } catch (Exception e) when (e is PackagesConfigReaderException or XmlException) diff --git a/src/Microsoft.ComponentDetection.Detectors/nuget/NuGetProjectModelProjectCentricComponentDetector.cs b/src/Microsoft.ComponentDetection.Detectors/nuget/NuGetProjectModelProjectCentricComponentDetector.cs index 162f1b3d6..034f7a167 100644 --- a/src/Microsoft.ComponentDetection.Detectors/nuget/NuGetProjectModelProjectCentricComponentDetector.cs +++ b/src/Microsoft.ComponentDetection.Detectors/nuget/NuGetProjectModelProjectCentricComponentDetector.cs @@ -24,167 +24,6 @@ public class NuGetProjectModelProjectCentricComponentDetector : FileComponentDet private readonly ConcurrentDictionary frameworkComponentsThatWereOmmittedWithCount = new ConcurrentDictionary(); - private readonly List netCoreFrameworkNames = ["Microsoft.AspNetCore.App", "Microsoft.AspNetCore.Razor.Design", "Microsoft.NETCore.App"]; - - // This list is meant to encompass all net standard dependencies, but likely contains some net core app 1.x ones, too. - // The specific guidance we got around populating this list is to do so based on creating a dotnet core 1.x app to make sure we had the complete - // set of netstandard.library files that could show up in later sdk versions. - private readonly string[] netStandardDependencies = - [ - "Libuv", - "Microsoft.CodeAnalysis.Analyzers", - "Microsoft.CodeAnalysis.Common", - "Microsoft.CodeAnalysis.CSharp", - "Microsoft.CodeAnalysis.VisualBasic", - "Microsoft.CSharp", - "Microsoft.DiaSymReader.Native", - "Microsoft.NETCore.DotNetHost", - "Microsoft.NETCore.DotNetHostPolicy", - "Microsoft.NETCore.DotNetHostResolver", - "Microsoft.NETCore.Jit", - "Microsoft.NETCore.Platforms", - "Microsoft.NETCore.Runtime.CoreCLR", - "Microsoft.NETCore.Targets", - "Microsoft.NETCore.Windows.ApiSets", - "Microsoft.VisualBasic", - "Microsoft.Win32.Primitives", - "Microsoft.Win32.Registry", - "NETStandard.Library", - "runtime.debian.8-x64.runtime.native.System.Security.Cryptography.OpenSsl", - "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography.OpenSsl", - "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography.OpenSsl", - "runtime.native.System", - "runtime.native.System.IO.Compression", - "runtime.native.System.Net.Http", - "runtime.native.System.Net.Security", - "runtime.native.System.Security.Cryptography.Apple", - "runtime.native.System.Security.Cryptography.OpenSsl", - "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography.OpenSsl", - "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography.OpenSsl", - "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.Apple", - "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.OpenSsl", - "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography.OpenSsl", - "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography.OpenSsl", - "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography.OpenSsl", - "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography.OpenSsl", - "System.AppContext", - "System.Buffers", - "System.Collections", - "System.Collections.Concurrent", - "System.Collections.Immutable", - "System.Collections.NonGeneric", - "System.Collections.Specialized", - "System.ComponentModel", - "System.ComponentModel.Annotations", - "System.ComponentModel.EventBasedAsync", - "System.ComponentModel.Primitives", - "System.ComponentModel.TypeConverter", - "System.Console", - "System.Data.Common", - "System.Diagnostics.Contracts", - "System.Diagnostics.Debug", - "System.Diagnostics.DiagnosticSource", - "System.Diagnostics.FileVersionInfo", - "System.Diagnostics.Process", - "System.Diagnostics.StackTrace", - "System.Diagnostics.TextWriterTraceListener", - "System.Diagnostics.Tools", - "System.Diagnostics.TraceSource", - "System.Diagnostics.Tracing", - "System.Drawing.Primitives", - "System.Dynamic.Runtime", - "System.Globalization", - "System.Globalization.Calendars", - "System.Globalization.Extensions", - "System.IO", - "System.IO.Compression", - "System.IO.Compression.ZipFile", - "System.IO.FileSystem", - "System.IO.FileSystem.DriveInfo", - "System.IO.FileSystem.Primitives", - "System.IO.FileSystem.Watcher", - "System.IO.IsolatedStorage", - "System.IO.MemoryMappedFiles", - "System.IO.Pipes", - "System.IO.UnmanagedMemoryStream", - "System.Linq", - "System.Linq.Expressions", - "System.Linq.Parallel", - "System.Linq.Queryable", - "System.Net.Http", - "System.Net.HttpListener", - "System.Net.Mail", - "System.Net.NameResolution", - "System.Net.NetworkInformation", - "System.Net.Ping", - "System.Net.Primitives", - "System.Net.Requests", - "System.Net.Security", - "System.Net.ServicePoint", - "System.Net.Sockets", - "System.Net.WebClient", - "System.Net.WebHeaderCollection", - "System.Net.WebProxy", - "System.Net.WebSockets", - "System.Net.WebSockets.Client", - "System.Numerics.Vectors", - "System.ObjectModel", - "System.Reflection", - "System.Reflection.DispatchProxy", - "System.Reflection.Emit", - "System.Reflection.Emit.ILGeneration", - "System.Reflection.Emit.Lightweight", - "System.Reflection.Extensions", - "System.Reflection.Metadata", - "System.Reflection.Primitives", - "System.Reflection.TypeExtensions", - "System.Resources.Reader", - "System.Resources.ResourceManager", - "System.Resources.Writer", - "System.Runtime", - "System.Runtime.CompilerServices.VisualC", - "System.Runtime.Extensions", - "System.Runtime.Handles", - "System.Runtime.InteropServices", - "System.Runtime.InteropServices.RuntimeInformation", - "System.Runtime.Loader", - "System.Runtime.Numerics", - "System.Runtime.Serialization.Formatters", - "System.Runtime.Serialization.Json", - "System.Runtime.Serialization.Primitives", - "System.Runtime.Serialization.Xml", - "System.Security.Claims", - "System.Security.Cryptography.Algorithms", - "System.Security.Cryptography.Cng", - "System.Security.Cryptography.Csp", - "System.Security.Cryptography.Encoding", - "System.Security.Cryptography.OpenSsl", - "System.Security.Cryptography.Primitives", - "System.Security.Cryptography.X509Certificates", - "System.Security.Principal", - "System.Security.Principal.Windows", - "System.Text.Encoding", - "System.Text.Encoding.CodePages", - "System.Text.Encoding.Extensions", - "System.Text.RegularExpressions", - "System.Threading", - "System.Threading.Overlapped", - "System.Threading.Tasks", - "System.Threading.Tasks.Dataflow", - "System.Threading.Tasks.Extensions", - "System.Threading.Tasks.Parallel", - "System.Threading.Thread", - "System.Threading.ThreadPool", - "System.Threading.Timer", - "System.Web.HttpUtility", - "System.Xml.ReaderWriter", - "System.Xml.XDocument", - "System.Xml.XmlDocument", - "System.Xml.XmlSerializer", - "System.Xml.XPath", - "System.Xml.XPath.XDocument", - ]; - private readonly IFileUtilityService fileUtilityService; public NuGetProjectModelProjectCentricComponentDetector( @@ -222,7 +61,6 @@ protected override Task OnFileFoundAsync(ProcessRequest processRequest, IDiction throw new FormatException("Lockfile did not contain a PackageSpec"); } - var frameworkComponents = this.GetFrameworkComponents(lockFile); var explicitReferencedDependencies = this.GetTopLevelLibraries(lockFile) .Select(x => this.GetLibraryComponentWithDependencyLookup(lockFile.Libraries, x.Name, x.Version, x.VersionRange)) .ToList(); @@ -235,10 +73,12 @@ protected override Task OnFileFoundAsync(ProcessRequest processRequest, IDiction var singleFileComponentRecorder = this.ComponentRecorder.CreateSingleFileComponentRecorder(lockFile.PackageSpec.RestoreMetadata.ProjectPath); foreach (var target in lockFile.Targets) { + var frameworkPackages = FrameworkPackages.GetFrameworkPackages(target.TargetFramework); + // This call to GetTargetLibrary is not guarded, because if this can't be resolved then something is fundamentally broken (e.g. an explicit dependency reference not being in the list of libraries) foreach (var library in explicitReferencedDependencies.Select(x => target.GetTargetLibrary(x.Name)).Where(x => x != null)) { - this.NavigateAndRegister(target, explicitlyReferencedComponentIds, singleFileComponentRecorder, library, null, frameworkComponents); + this.NavigateAndRegister(target, explicitlyReferencedComponentIds, singleFileComponentRecorder, library, null, frameworkPackages); } } } @@ -264,19 +104,28 @@ private void NavigateAndRegister( ISingleFileComponentRecorder singleFileComponentRecorder, LockFileTargetLibrary library, string parentComponentId, - HashSet dotnetRuntimePackageNames, + FrameworkPackages frameworkPackages, HashSet visited = null) { - if (this.IsAFrameworkComponent(dotnetRuntimePackageNames, library.Name, library.Dependencies) - || library.Type == ProjectDependencyType) + if (library.Type == ProjectDependencyType) { return; } + var isFrameworkComponent = frameworkPackages?.IsAFrameworkComponent(library.Name, library.Version) ?? false; + var isDevelopmentDependency = IsADevelopmentDependency(library); + visited ??= []; var libraryComponent = new DetectedComponent(new NuGetComponent(library.Name, library.Version.ToNormalizedString())); - singleFileComponentRecorder.RegisterUsage(libraryComponent, explicitlyReferencedComponentIds.Contains(libraryComponent.Component.Id), parentComponentId); + singleFileComponentRecorder.RegisterUsage(libraryComponent, explicitlyReferencedComponentIds.Contains(libraryComponent.Component.Id), parentComponentId, isDevelopmentDependency: isFrameworkComponent || isDevelopmentDependency); + + // get the actual component in case it already exists + libraryComponent = singleFileComponentRecorder.GetComponent(libraryComponent.Component.Id); + + // Add framework information to the actual component + var targetFramework = target.TargetFramework?.GetShortFolderName() ?? "unknown"; + ((NuGetComponent)libraryComponent.Component).TargetFrameworks.Add(targetFramework); foreach (var dependency in library.Dependencies) { @@ -293,30 +142,15 @@ private void NavigateAndRegister( else { visited.Add(dependency.Id); - this.NavigateAndRegister(target, explicitlyReferencedComponentIds, singleFileComponentRecorder, targetLibrary, libraryComponent.Component.Id, dotnetRuntimePackageNames, visited); + this.NavigateAndRegister(target, explicitlyReferencedComponentIds, singleFileComponentRecorder, targetLibrary, libraryComponent.Component.Id, frameworkPackages, visited); } } - } - private bool IsAFrameworkComponent(HashSet frameworkComponents, string libraryName, IList dependencies = null) - { - var isAFrameworkComponent = frameworkComponents.Contains(libraryName); - - if (isAFrameworkComponent) - { - this.frameworkComponentsThatWereOmmittedWithCount.AddOrUpdate(libraryName, 1, (name, existing) => existing + 1); - - if (dependencies != null) - { - // Also track shallow children if this is a top level library so we have a rough count of how many things have been ommitted + root relationships - foreach (var item in dependencies) - { - this.frameworkComponentsThatWereOmmittedWithCount.AddOrUpdate(item.Id, 1, (name, existing) => existing + 1); - } - } - } + // a placeholder item is an empty file that doesn't exist with name _._ meant to indicate an empty folder in a nuget package, but also used by NuGet when a package's assets are excluded. + bool IsAPlaceholderItem(LockFileItem item) => Path.GetFileName(item.Path).Equals(PackagingCoreConstants.EmptyFolder, StringComparison.OrdinalIgnoreCase); - return isAFrameworkComponent; + // A library is development dependency if all of the runtime assemblies and runtime targets are placeholders or empty (All returns true for empty). + bool IsADevelopmentDependency(LockFileTargetLibrary library) => library.RuntimeAssemblies.Concat(library.RuntimeTargets).All(IsAPlaceholderItem); } private List<(string Name, Version Version, VersionRange VersionRange)> GetTopLevelLibraries(LockFile lockFile) @@ -394,61 +228,4 @@ private LockFileLibrary GetLibraryComponentWithDependencyLookup(IList GetFrameworkComponents(LockFile lockFile) - { - var frameworkDependencies = new HashSet(); - foreach (var projectFileDependencyGroup in lockFile.ProjectFileDependencyGroups) - { - var topLevelLibraries = this.GetTopLevelLibraries(lockFile); - foreach (var (name, version, versionRange) in topLevelLibraries) - { - if (this.netCoreFrameworkNames.Contains(name)) - { - frameworkDependencies.Add(name); - - foreach (var target in lockFile.Targets) - { - var matchingLibrary = target.Libraries.FirstOrDefault(x => x.Name == name); - var dependencyComponents = this.GetDependencyComponentIds(lockFile, target, matchingLibrary.Dependencies); - frameworkDependencies.UnionWith(dependencyComponents); - } - } - } - } - - foreach (var netstandardDep in this.netStandardDependencies) - { - frameworkDependencies.Add(netstandardDep); - } - - return frameworkDependencies; - } - - private HashSet GetDependencyComponentIds(LockFile lockFile, LockFileTarget target, IList dependencies, HashSet visited = null) - { - visited ??= []; - var currentComponents = new HashSet(); - foreach (var dependency in dependencies) - { - if (visited.Contains(dependency.Id)) - { - continue; - } - - currentComponents.Add(dependency.Id); - var libraryToExpand = target.GetTargetLibrary(dependency.Id); - if (libraryToExpand == null) - { - // We have to exclude this case -- it looks like a bug in project.assets.json, but there are project.assets.json files that don't have a dependency library in the libraries set. - } - else - { - visited.Add(dependency.Id); - currentComponents.UnionWith(this.GetDependencyComponentIds(lockFile, target, libraryToExpand.Dependencies, visited)); - } - } - - return currentComponents; - } } diff --git a/test/Microsoft.ComponentDetection.Detectors.Tests/NuGetProjectModelProjectCentricComponentDetectorTests.cs b/test/Microsoft.ComponentDetection.Detectors.Tests/NuGetProjectModelProjectCentricComponentDetectorTests.cs index 04ce211f2..8d614c88f 100644 --- a/test/Microsoft.ComponentDetection.Detectors.Tests/NuGetProjectModelProjectCentricComponentDetectorTests.cs +++ b/test/Microsoft.ComponentDetection.Detectors.Tests/NuGetProjectModelProjectCentricComponentDetectorTests.cs @@ -1,7 +1,6 @@ namespace Microsoft.ComponentDetection.Detectors.Tests; using System; -using System.Collections.Generic; using System.Linq; using System.Runtime.InteropServices; using System.Threading.Tasks; @@ -14,7 +13,6 @@ namespace Microsoft.ComponentDetection.Detectors.Tests; using Microsoft.ComponentDetection.TestsUtilities; using Microsoft.VisualStudio.TestTools.UnitTesting; using Moq; -using Newtonsoft.Json; [TestClass] [TestCategory("Governance/All")] @@ -44,13 +42,12 @@ public async Task ScanDirectoryAsync_Base_2_2_VerificationAsync() // Number of unique nodes in ProjectAssetsJson Console.WriteLine(string.Join(",", detectedComponents.Select(x => x.Component.Id))); - detectedComponents.Should().HaveCount(3); - detectedComponents.Select(x => x.Component).Cast().FirstOrDefault(x => x.Name.Contains("coverlet.msbuild")).Should().NotBeNull(); + detectedComponents.Should().HaveCount(22); + + var nonDevComponents = detectedComponents.Where(c => !componentRecorder.GetEffectiveDevDependencyValue(c.Component.Id).GetValueOrDefault()); + nonDevComponents.Should().HaveCount(3); - detectedComponents.Should().OnlyContain(x => - componentRecorder.IsDependencyOfExplicitlyReferencedComponents( - x.Component.Id, - y => y.Id == x.Component.Id)); + detectedComponents.Select(x => x.Component).Cast().FirstOrDefault(x => x.Name.Contains("coverlet.msbuild")).Should().NotBeNull(); componentRecorder.ForAllComponents(grouping => grouping.AllFileLocations.Should().Contain(location => location.Contains("Loader.csproj"))); } @@ -67,11 +64,14 @@ public async Task ScanDirectoryAsync_Base_2_2_additional_VerificationAsync() // Number of unique nodes in ProjectAssetsJson Console.WriteLine(string.Join(",", detectedComponents.Select(x => x.Component.Id))); - detectedComponents.Should().HaveCount(26); - detectedComponents.Select(x => x.Component).Cast().FirstOrDefault(x => x.Name.Contains("Polly")).Should().NotBeNull(); - detectedComponents.Select(x => x.Component).Cast().Count(x => x.Name.Contains("System.Composition")).Should().Be(5); + detectedComponents.Should().HaveCount(68); + + var nonDevComponents = detectedComponents.Where(c => !componentRecorder.GetEffectiveDevDependencyValue(c.Component.Id).GetValueOrDefault()); + nonDevComponents.Should().HaveCount(23); + nonDevComponents.Select(x => x.Component).Cast().FirstOrDefault(x => x.Name.Contains("Polly")).Should().NotBeNull(); + nonDevComponents.Select(x => x.Component).Cast().Count(x => x.Name.Contains("System.Composition")).Should().Be(5); - var nugetVersioning = detectedComponents.FirstOrDefault(x => (x.Component as NuGetComponent).Name.Contains("NuGet.DependencyResolver.Core")); + var nugetVersioning = nonDevComponents.FirstOrDefault(x => (x.Component as NuGetComponent).Name.Contains("NuGet.DependencyResolver.Core")); nugetVersioning.Should().NotBeNull(); componentRecorder.IsDependencyOfExplicitlyReferencedComponents( @@ -89,11 +89,9 @@ public async Task ScanDirectoryAsync_ExcludedFrameworkComponent_2_2_Verification .WithFile(this.projectAssetsJsonFileName, osAgnostic) .ExecuteDetectorAsync(); - var ommittedComponentInformationJson = scanResult.AdditionalTelemetryDetails[NuGetProjectModelProjectCentricComponentDetector.OmittedFrameworkComponentsTelemetryKey]; - var omittedComponentsWithCount = JsonConvert.DeserializeObject>(ommittedComponentInformationJson); - - (omittedComponentsWithCount.Keys.Count > 5).Should().BeTrue("Ommitted framework assemblies are missing. There should be more than ten, but this is a gut check to make sure we have data."); - omittedComponentsWithCount.Should().Contain("Microsoft.NETCore.App", 4, "There should be four cases of the NETCore.App library being omitted in the test data."); + var developmentDependencies = componentRecorder.GetDetectedComponents().Where(c => componentRecorder.GetEffectiveDevDependencyValue(c.Component.Id).GetValueOrDefault()); + developmentDependencies.Should().HaveCountGreaterThan(5, "Ommitted framework assemblies are missing. There should be more than ten, but this is a gut check to make sure we have data."); + developmentDependencies.Should().Contain(c => c.Component.Id.StartsWith("Microsoft.NETCore.App "), "Microsoft.NETCore.App should be treated as a development dependency."); } [TestMethod] @@ -150,6 +148,7 @@ public async Task ScanDirectoryAsync_DependencyGraph_2_2_additional_Verification var expectedExplicitRefs = new[] { "DotNet.Glob", + "Microsoft.NETCore.App", "MinVer", "Nett", "Newtonsoft.Json", @@ -164,6 +163,7 @@ public async Task ScanDirectoryAsync_DependencyGraph_2_2_additional_Verification "System.Composition.Runtime", "System.Composition.TypedParts", "System.Reactive", + "System.Threading.Tasks.Dataflow", "coverlet.msbuild", "YamlDotNet", }; @@ -172,7 +172,7 @@ public async Task ScanDirectoryAsync_DependencyGraph_2_2_additional_Verification { var component = detectedComponents.First(x => x.Component.Id == componentId); var expectedExplicitRefValue = expectedExplicitRefs.Contains(((NuGetComponent)component.Component).Name); - graph.IsComponentExplicitlyReferenced(componentId).Should().Be(expectedExplicitRefValue); + graph.IsComponentExplicitlyReferenced(componentId).Should().Be(expectedExplicitRefValue, "{0} should{1} be explicitly referenced.", componentId, expectedExplicitRefValue ? string.Empty : "n't"); } } @@ -186,8 +186,11 @@ public async Task ScanDirectoryAsync_Base_3_1_VerificationAsync() // Number of unique nodes in ProjectAssetsJson var detectedComponents = componentRecorder.GetDetectedComponents(); - detectedComponents.Should().HaveCount(2); - detectedComponents.Select(x => x.Component).Cast().FirstOrDefault(x => x.Name.Contains("Microsoft.Extensions.DependencyModel")).Should().NotBeNull(); + detectedComponents.Should().HaveCount(11); + + var nonDevComponents = detectedComponents.Where(c => !componentRecorder.GetEffectiveDevDependencyValue(c.Component.Id).GetValueOrDefault()); + nonDevComponents.Should().ContainSingle(); + nonDevComponents.Select(x => x.Component).Cast().Single().Name.Should().StartWith("Microsoft.Extensions.DependencyModel"); var systemTextJson = detectedComponents.FirstOrDefault(x => (x.Component as NuGetComponent).Name.Contains("System.Text.Json")); @@ -206,12 +209,9 @@ public async Task ScanDirectoryAsync_ExcludedFrameworkComponent_3_1_Verification .WithFile(this.projectAssetsJsonFileName, osAgnostic) .ExecuteDetectorAsync(); - var ommittedComponentInformationJson = scanResult.AdditionalTelemetryDetails[NuGetProjectModelProjectCentricComponentDetector.OmittedFrameworkComponentsTelemetryKey]; - var omittedComponentsWithCount = JsonConvert.DeserializeObject>(ommittedComponentInformationJson); - - // With 3.X, we don't expect there to be a lot of these, but there are still netstandard libraries present which can bring things into the graph - omittedComponentsWithCount.Keys.Should().HaveCount(4, "Ommitted framework assemblies are missing. There should be more than ten, but this is a gut check to make sure we have data."); - omittedComponentsWithCount.Should().Contain("System.Reflection", 1, "There should be one case of the System.Reflection library being omitted in the test data."); + var developmentDependencies = componentRecorder.GetDetectedComponents().Where(c => componentRecorder.GetEffectiveDevDependencyValue(c.Component.Id).GetValueOrDefault()); + developmentDependencies.Should().HaveCount(10, "Omitted framework assemblies are missing."); + developmentDependencies.Should().Contain(c => c.Component.Id.StartsWith("System.Reflection "), "System.Reflection should be treated as a development dependency."); } [TestMethod] @@ -244,19 +244,19 @@ public async Task ScanDirectoryAsync_DependencyGraph_3_1_VerificationAsync() // Top level dependencies look like this: // (we expect all non-proj and non-framework to show up as explicit refs, so those will be absent from the check) // - // "ExtCore.Infrastructure >= 5.1.0", // "Microsoft.Extensions.DependencyModel >= 3.0.0", // "System.Runtime.Loader >= 4.3.0" var expectedExplicitRefs = new[] { "Microsoft.Extensions.DependencyModel", + "System.Runtime.Loader", }; foreach (var componentId in graph.GetComponents()) { var component = detectedComponents.First(x => x.Component.Id == componentId); var expectedExplicitRefValue = expectedExplicitRefs.Contains(((NuGetComponent)component.Component).Name); - graph.IsComponentExplicitlyReferenced(componentId).Should().Be(expectedExplicitRefValue); + graph.IsComponentExplicitlyReferenced(componentId).Should().Be(expectedExplicitRefValue, "{0} should{1} be explicitly referenced.", componentId, expectedExplicitRefValue ? string.Empty : "n't"); } } diff --git a/test/findLastPackage/AssemblyUtilities.cs b/test/findLastPackage/AssemblyUtilities.cs new file mode 100644 index 000000000..337b654de --- /dev/null +++ b/test/findLastPackage/AssemblyUtilities.cs @@ -0,0 +1,125 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Data; +using System.Linq; +using System.Reflection.Metadata; +using System.Reflection.PortableExecutable; +using System.Text; +using System.Threading.Tasks; + +namespace findLastPackage +{ + internal static class AssemblyUtilities + { + + internal static (Version assemblyVersion, Version fileVersion) GetVersions(Stream assemblyStream) + { + using (var peReader = new PEReader(assemblyStream)) + { + var metadataReader = peReader.GetMetadataReader(); + var assemblyDefinition = metadataReader.GetAssemblyDefinition(); + var name = metadataReader.GetString(assemblyDefinition.Name); + var version = assemblyDefinition.Version; + + string? fileVersion = null; + MetadataStringComparer comparer = metadataReader.StringComparer; + foreach (CustomAttributeHandle attrHandle in assemblyDefinition.GetCustomAttributes()) + { + CustomAttribute attr = metadataReader.GetCustomAttribute(attrHandle); + StringHandle typeNamespaceHandle = default(StringHandle), typeNameHandle = default(StringHandle); + if (TryGetAttributeName(metadataReader, attr, out typeNamespaceHandle, out typeNameHandle) && + comparer.Equals(typeNamespaceHandle, "System.Reflection")) + { + if (comparer.Equals(typeNameHandle, "AssemblyFileVersionAttribute")) + { + GetStringAttributeArgumentValue(metadataReader, attr, ref fileVersion); + } + } + } + + return (assemblyVersion: version, fileVersion: Version.Parse(fileVersion)); + } + } + + + + /// Gets the name of an attribute. + /// The metadata reader. + /// The attribute. + /// The namespace of the attribute. + /// The name of the attribute. + /// true if the name could be retrieved; otherwise, false. + private static bool TryGetAttributeName(MetadataReader reader, CustomAttribute attr, out StringHandle typeNamespaceHandle, out StringHandle typeNameHandle) + { + EntityHandle ctorHandle = attr.Constructor; + switch (ctorHandle.Kind) + { + case HandleKind.MemberReference: + EntityHandle container = reader.GetMemberReference((MemberReferenceHandle)ctorHandle).Parent; + if (container.Kind == HandleKind.TypeReference) + { + TypeReference tr = reader.GetTypeReference((TypeReferenceHandle)container); + typeNamespaceHandle = tr.Namespace; + typeNameHandle = tr.Name; + return true; + } + break; + + case HandleKind.MethodDefinition: + MethodDefinition md = reader.GetMethodDefinition((MethodDefinitionHandle)ctorHandle); + TypeDefinition td = reader.GetTypeDefinition(md.GetDeclaringType()); + typeNamespaceHandle = td.Namespace; + typeNameHandle = td.Name; + return true; + } + + // Unusual case, potentially invalid IL + typeNamespaceHandle = default(StringHandle); + typeNameHandle = default(StringHandle); + return false; + } + + /// Gets the string argument value of an attribute with a single fixed string argument. + /// The metadata reader. + /// The attribute. + /// The value parsed from the attribute, if it could be retrieved; otherwise, the value is left unmodified. + private static void GetStringAttributeArgumentValue(MetadataReader reader, CustomAttribute attr, ref string? value) + { + EntityHandle ctorHandle = attr.Constructor; + BlobHandle signature; + switch (ctorHandle.Kind) + { + case HandleKind.MemberReference: + signature = reader.GetMemberReference((MemberReferenceHandle)ctorHandle).Signature; + break; + case HandleKind.MethodDefinition: + signature = reader.GetMethodDefinition((MethodDefinitionHandle)ctorHandle).Signature; + break; + default: + // Unusual case, potentially invalid IL + return; + } + + BlobReader signatureReader = reader.GetBlobReader(signature); + BlobReader valueReader = reader.GetBlobReader(attr.Value); + + const ushort Prolog = 1; // two-byte "prolog" defined by ECMA-335 (II.23.3) to be at the beginning of attribute value blobs + if (valueReader.ReadUInt16() == Prolog) + { + SignatureHeader header = signatureReader.ReadSignatureHeader(); + int parameterCount; + if (header.Kind == SignatureKind.Method && // attr ctor must be a method + !header.IsGeneric && // attr ctor must be non-generic + signatureReader.TryReadCompressedInteger(out parameterCount) && // read parameter count + parameterCount == 1 && // attr ctor must have 1 parameter + signatureReader.ReadSignatureTypeCode() == SignatureTypeCode.Void && // attr ctor return type must be void + signatureReader.ReadSignatureTypeCode() == SignatureTypeCode.String) // attr ctor first parameter must be string + { + value = valueReader.ReadSerializedString(); + } + } + } + + } +} diff --git a/test/findLastPackage/NuGetUtilities.cs b/test/findLastPackage/NuGetUtilities.cs new file mode 100644 index 000000000..34309ba14 --- /dev/null +++ b/test/findLastPackage/NuGetUtilities.cs @@ -0,0 +1,157 @@ +using Microsoft.DotNet.PackageValidation; +using NuGet.Common; +using NuGet.Frameworks; +using NuGet.Packaging; +using NuGet.Protocol; +using NuGet.Protocol.Core.Types; +using NuGet.Versioning; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Net; +using System.Net.Http.Json; +using System.Reflection; +using System.Reflection.Metadata; +using System.Text; +using System.Text.Json; +using System.Text.Json.Serialization; +using System.Threading.Tasks; + +namespace findLastPackage +{ + internal static class NuGetUtilities + { + static NuGetUtilities() + { + Package.InitializeRuntimeGraph(Path.Combine(AppContext.BaseDirectory, "RuntimeIdentifierGraph.json")); + } + + public static Version[] GetStableVersions(string packageId) + { + string allPackageVersionsUrl = $"https://api.nuget.org/v3-flatcontainer/{packageId.ToLowerInvariant()}/index.json"; + string versionsJson = string.Empty; + try + { + using (HttpClient httpClient = new HttpClient()) + { + var packageVersions = httpClient.GetFromJsonAsync(allPackageVersionsUrl).Result; + + return packageVersions!.Versions.Where(s => !s.Contains('-')).Select(s => Version.Parse(s)).OrderDescending().ToArray(); + } + } + catch (Exception) + { + return []; + } + } + + public static Version[] GetStableVersions2(string packageId) + { + ILogger logger = NullLogger.Instance; + CancellationToken cancellationToken = CancellationToken.None; + + SourceCacheContext cache = new SourceCacheContext(); + SourceRepository repository = Repository.Factory.GetCoreV3("https://api.nuget.org/v3/index.json"); + PackageMetadataResource resource = repository.GetResourceAsync().Result; + + IEnumerable packages = resource.GetMetadataAsync( + packageId, + includePrerelease: false, + includeUnlisted: false, + cache, + logger, + cancellationToken).Result; + + return packages.Select(p => p.Identity.Version.As3PartVersion()).OrderDescending().ToArray(); + } + + private static Version As3PartVersion(this NuGetVersion nugetVersion) => new Version(nugetVersion.Major, nugetVersion.Minor, nugetVersion.Patch); + + public static IEnumerable<(string path, Version assemblyVersion, Version fileVersion)> ResolvePackageAssetVersions(string packageId, Version version, NuGetFramework framework) + { + string packageDownloadUrl = $"https://www.nuget.org/api/v2/package/{packageId}/{version}"; + + using var packageStream = DownloadPackage(packageId, version); + + Package package; + + try + { + package = Package.Create(packageStream); + } + catch (InvalidDataException) + { + Console.WriteLine($"Error loading package {packageId}, {version}"); + yield break; + } + + using (package) + { + // we will compare against runtime asset since this may be higher version + // than compile - it's the one that's serviced when compile may be pinned + var assets = package.FindBestRuntimeAssetForFramework(framework); + + // if we couldn't find RID-agnostic assets, try for a single RID. + if (assets == null || assets.Count == 0) + { + assets = package.FindBestRuntimeAssetForFrameworkAndRuntime(framework, "win"); + } + + if (assets == null || assets.Count == 0) + { + assets = package.FindBestCompileAssetForFramework(framework); + } + + if (assets != null) + { + var matchingAssets = assets.Where(ca => Path.GetFileNameWithoutExtension(ca.Path).Equals(packageId, StringComparison.OrdinalIgnoreCase)); + + foreach (var asset in matchingAssets) + { + var entry = package.PackageReader.GetEntry(asset.Path); + using var assemblyStream = entry.Open(); + using var seekableStream = new MemoryStream((int)entry.Length); + assemblyStream.CopyTo(seekableStream); + seekableStream.Position = 0; + + var versions = AssemblyUtilities.GetVersions(seekableStream); + + yield return (path: asset.Path, assemblyVersion: versions.assemblyVersion, fileVersion: versions.fileVersion); + } + } + } + } + + private static Stream DownloadPackage(string packageId, Version version) + { + ILogger logger = NullLogger.Instance; + CancellationToken cancellationToken = CancellationToken.None; + + SourceCacheContext cache = new SourceCacheContext(); + SourceRepository repository = Repository.Factory.GetCoreV3("https://api.nuget.org/v3/index.json"); + FindPackageByIdResource resource = repository.GetResourceAsync().Result; + + NuGetVersion packageVersion = new NuGetVersion(version); + MemoryStream packageStream = new MemoryStream(); + + resource.CopyNupkgToStreamAsync( + packageId, + packageVersion, + packageStream, + cache, + logger, + cancellationToken).Wait(); + + return packageStream; + } + + private class PackageVersions + { + [JsonPropertyName("versions")] + public string[] Versions { get; set; } + } + + + } +} diff --git a/test/findLastPackage/Package.cs b/test/findLastPackage/Package.cs new file mode 100644 index 000000000..4e918a3e6 --- /dev/null +++ b/test/findLastPackage/Package.cs @@ -0,0 +1,235 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +// adapted from https://github.com/dotnet/sdk/blob/6ef345b1ff76eec2de6a0deeda321cae45da655b/src/Compatibility/ApiCompat/Microsoft.DotNet.PackageValidation/Package.cs + +using System.Collections.ObjectModel; +using NuGet.Client; +using NuGet.ContentModel; +using NuGet.Frameworks; +using NuGet.Packaging; +using NuGet.Packaging.Core; +using NuGet.RuntimeModel; + +namespace Microsoft.DotNet.PackageValidation +{ + /// + /// This class represents a NuGet package. + /// + public class Package : IDisposable + { + private readonly ContentItemCollection _contentItemCollection; + private readonly ManagedCodeConventions _conventions; + private static RuntimeGraph? s_runtimeGraph; + + /// + /// The name of the package + /// + public string PackageId { get; } + + /// + /// The version of the package. + /// + public string Version { get; } + + /// + /// Package reader + /// + public PackageArchiveReader PackageReader { get; } + + /// + /// List of assets in the package. + /// + public IEnumerable PackageAssets { get; } + + /// + /// List of compile assets in the package. + /// + public IEnumerable CompileAssets { get; } + + /// + /// List of assets under ref in the package. + /// + public IEnumerable RefAssets { get; } + + /// + /// List of assets under lib in the package. + /// + public IEnumerable LibAssets { get; } + + /// + /// List of all the runtime specific assets in the package. + /// + public IEnumerable RuntimeSpecificAssets { get; } + + /// + /// List of all the runtime assets in the package. + /// + public IEnumerable RuntimeAssets { get; } + + /// + /// List of rids in the package. + /// + public IEnumerable Rids { get; } + + /// + /// List of assembly references grouped by target framework. + /// + public IReadOnlyDictionary>? AssemblyReferences { get; } + + /// + /// List of the frameworks in the package. + /// + public IReadOnlyList FrameworksInPackage { get; } + + public Package(PackageArchiveReader packageReader, + string packageId, + string version, + IEnumerable packageAssets, + IReadOnlyDictionary>? assemblyReferences = null) + { + PackageReader = packageReader; + PackageId = packageId; + Version = version; + + _conventions = new ManagedCodeConventions(s_runtimeGraph); + _contentItemCollection = new ContentItemCollection(); + _contentItemCollection.Load(packageAssets); + + PackageAssets = _contentItemCollection.FindItems(_conventions.Patterns.AnyTargettedFile); + RefAssets = _contentItemCollection.FindItems(_conventions.Patterns.CompileRefAssemblies); + LibAssets = _contentItemCollection.FindItems(_conventions.Patterns.CompileLibAssemblies); + CompileAssets = RefAssets.Any() ? RefAssets : LibAssets; + RuntimeAssets = _contentItemCollection.FindItems(_conventions.Patterns.RuntimeAssemblies); + RuntimeSpecificAssets = RuntimeAssets.Where(t => t.Path.StartsWith("runtimes")).ToArray(); + Rids = RuntimeSpecificAssets.Select(t => (string)t.Properties["rid"]) + .Distinct() + .ToArray(); + FrameworksInPackage = CompileAssets.Select(t => (NuGetFramework)t.Properties["tfm"]) + .Concat(RuntimeAssets.Select(t => (NuGetFramework)t.Properties["tfm"])) + .Distinct() + .ToArray(); + AssemblyReferences = assemblyReferences; + } + + public static void InitializeRuntimeGraph(string runtimeGraph) + { + s_runtimeGraph = JsonRuntimeFormat.ReadRuntimeGraph(runtimeGraph); + } + + public static Package Create(Stream packageStream) + { + PackageArchiveReader packageReader = new(packageStream); + NuspecReader nuspecReader = packageReader.NuspecReader; + string packageId = nuspecReader.GetId(); + string version = nuspecReader.GetVersion().ToString(); + IEnumerable packageAssets = packageReader.GetFiles().Where(t => t.EndsWith(".dll")).ToArray(); + + return new Package(packageReader, packageId, version, packageAssets); + } + + /// + /// Finds the best runtime asset for for a specific framework. + /// + /// The framework where the package needs to be installed. + /// A ContentItem representing the best runtime asset + public IReadOnlyList? FindBestRuntimeAssetForFramework(NuGetFramework framework) + { + SelectionCriteria managedCriteria = _conventions.Criteria.ForFramework(framework); + IList? items = _contentItemCollection.FindBestItemGroup(managedCriteria, + _conventions.Patterns.RuntimeAssemblies)?.Items; + + return items != null ? + new ReadOnlyCollection(items.Where(t => !t.Path.StartsWith("runtimes")).ToArray()) : + null; + } + + /// + /// Finds the best runtime specific asset for for a specific framework. + /// + /// The framework where the package needs to be installed. + /// A ContentItem representing the best runtime specific asset. + public IReadOnlyList? FindBestRuntimeSpecificAssetForFramework(NuGetFramework framework) + { + SelectionCriteria managedCriteria = _conventions.Criteria.ForFramework(framework); + IList? items = _contentItemCollection.FindBestItemGroup(managedCriteria, + _conventions.Patterns.RuntimeAssemblies)?.Items; + + return items != null ? + new ReadOnlyCollection(items.Where(t => t.Path.StartsWith("runtimes")).ToArray()) : + null; + } + + /// + /// Finds the best runtime asset for a framework-rid pair. + /// + /// The framework where the package needs to be installed. + /// The rid where the package needs to be installed. + /// A ContentItem representing the best runtime asset + public IReadOnlyList? FindBestRuntimeAssetForFrameworkAndRuntime(NuGetFramework framework, string rid) + { + SelectionCriteria managedCriteria = _conventions.Criteria.ForFrameworkAndRuntime(framework, rid); + IList? items = _contentItemCollection.FindBestItemGroup(managedCriteria, + _conventions.Patterns.RuntimeAssemblies)?.Items; + + return items != null ? + new ReadOnlyCollection(items) : + null; + } + + /// + /// Finds the best compile time asset for a specific framework. + /// + /// The framework where the package needs to be installed. + /// A ContentItem representing the best compile time asset. + public IReadOnlyList? FindBestCompileAssetForFramework(NuGetFramework framework) + { + SelectionCriteria managedCriteria = _conventions.Criteria.ForFramework(framework); + PatternSet patternSet = RefAssets.Any() ? + _conventions.Patterns.CompileRefAssemblies : + _conventions.Patterns.CompileLibAssemblies; + IList? items = _contentItemCollection.FindBestItemGroup(managedCriteria, patternSet)?.Items; + + return items != null ? + new ReadOnlyCollection(items) : + null; + } + + /// + /// Finds the best assembly references for a specific framework. + /// + /// The framework where the package needs to be installed. + /// The assembly references for the specified framework. + public IEnumerable? FindBestAssemblyReferencesForFramework(NuGetFramework framework) + { + if (AssemblyReferences is null) + return null; + + // Fast path: return for direct matches + if (AssemblyReferences.TryGetValue(framework, out IEnumerable? references)) + { + return references; + } + + // Search for the nearest newer assembly references framework. + Queue tfmQueue = new(AssemblyReferences.Keys); + while (tfmQueue.Count > 0) + { + NuGetFramework assemblyReferencesFramework = tfmQueue.Dequeue(); + + NuGetFramework? bestAssemblyReferencesFramework = NuGetFrameworkUtility.GetNearest(tfmQueue.Concat(new NuGetFramework[] { framework }), assemblyReferencesFramework, (key) => key); + if (bestAssemblyReferencesFramework == framework) + { + return AssemblyReferences[assemblyReferencesFramework]; + } + } + + return null; + } + + public void Dispose() + { + PackageReader?.Dispose(); + } + } +} diff --git a/test/findLastPackage/Program.cs b/test/findLastPackage/Program.cs new file mode 100644 index 000000000..8e5c04482 --- /dev/null +++ b/test/findLastPackage/Program.cs @@ -0,0 +1,228 @@ +using findLastPackage; +using Microsoft.DotNet.PackageValidation; +using NuGet.Frameworks; +using NuGet.Versioning; +using System.Diagnostics; +using System.Reflection; + +(string path, string tfm)[] frameworkPacks = [ + ("C:\\Users\\ericstj\\.nuget\\packages\\netstandard.library\\2.0.3\\build\\netstandard2.0\\ref", "netstandard2.0"), + ("C:\\Users\\ericstj\\.nuget\\packages\\netstandard.library.ref\\2.1.0", "netstandard2.1"), + ("C:\\Users\\ericstj\\.nuget\\packages\\microsoft.netcore.app\\2.0.0", "netcoreapp2.0"), + ("C:\\Users\\ericstj\\.nuget\\packages\\microsoft.netcore.app\\2.1.0", "netcoreapp2.1"), + ("C:\\Users\\ericstj\\.nuget\\packages\\microsoft.netcore.app.ref\\3.0.0", "netcoreapp3.0"), + ("C:\\Users\\ericstj\\.nuget\\packages\\microsoft.netcore.app.ref\\3.1.0", "netcoreapp3.1"), + ("C:\\Users\\ericstj\\.nuget\\packages\\microsoft.netcore.app.ref\\5.0.0", "net5.0"), + ("C:\\Users\\ericstj\\.nuget\\packages\\microsoft.netcore.app.ref\\6.0.32", "net6.0"), + ("C:\\Users\\ericstj\\.nuget\\packages\\microsoft.netcore.app.ref\\7.0.19", "net7.0"), + ("C:\\Users\\ericstj\\.nuget\\packages\\microsoft.netcore.app.ref\\8.0.7", "net8.0"), + ("C:\\Users\\ericstj\\.nuget\\packages\\microsoft.netcore.app.ref\\9.0.0-rc.2.24473.4", "net9.0") + ]; + +// framework compat facades that shouldn't be compared against packages +HashSet filesToIgnore = new(StringComparer.OrdinalIgnoreCase) +{ + "mscorlib", + "Microsoft.VisualBasic", + "System", + "System.ComponentModel.DataAnnotations", + "System.Configuration", + "System.Core", + "System.Data", + "System.Drawing", + "System.IO.Compression.FileSystem", + "System.Net", + "System.Numerics", + "System.Runtime.Serialization", + "System.Security", + "System.ServiceProcess", + "System.ServiceModel.Web", + "System.Transactions", + "System.Web", + "System.Windows", + "System.Xml", + "System.Xml.Serialization", + "System.Xml.Linq", + "WindowsBase" +}; + +NuGetVersion? ParseVersion(string versionString) => NuGetVersion.TryParse(versionString, out var version) ? version : null; + +var inboxPackages = new Dictionary>(); +foreach (var frameworkPack in frameworkPacks) +{ + // calculate from assembly metadata + var tfm = NuGetFramework.Parse(frameworkPack.tfm); + var packages = ProcessFiles(frameworkPack.path, tfm).ToDictionary(); + + // merge in overrides data + var overridesFile = Path.Combine(frameworkPack.path, "data", "PackageOverrides.txt"); + if (File.Exists(overridesFile)) + { + var packageOverrides = File.ReadAllLines(overridesFile); + + foreach (var packageOverride in packageOverrides) + { + var packageOverrideParts = packageOverride.Trim().Split('|'); + + if (packageOverrideParts.Length == 2) + { + var packageId = packageOverrideParts[0]; + var packageVersion = Version.Parse(packageOverrideParts[1]); + + if (packages.TryGetValue(packageId, out var existingVersion)) + { + if (existingVersion < packageVersion) + { + Console.WriteLine($"{packageId} {tfm.GetShortFolderName()} -- Caclulated {existingVersion} < PackageOverrides {packageVersion}"); + } + else if (existingVersion > packageVersion) + { + Console.WriteLine($"{packageId} {tfm.GetShortFolderName()} -- Caclulated {existingVersion} > PackageOverrides {packageVersion}"); + continue; + } + } + + packages[packageId] = packageVersion; + } + + } + } + + inboxPackages[tfm] = packages; +} + +// reduce packages and emit +FrameworkReducer reducer = new(); +foreach (var framework in inboxPackages.Keys) +{ + // create a copy to reduce, so we don't change the data used for reduction. + var reducedPackages = new Dictionary(inboxPackages[framework]); + + // find the nearest framework not ourself, and remove any packages that it defines + var nearest = reducer.GetNearest(framework, inboxPackages.Keys.Where(f => f != framework)); + + if (nearest != null) + { + foreach (var package in inboxPackages[nearest]) + { + if (reducedPackages.TryGetValue(package.Key, out var existingVersion)) + { + if (existingVersion < package.Value) + { + Console.WriteLine($"{package.Key} - Compatible framework {nearest} has higher version {package.Value} than {framework} - {existingVersion}"); + } + else if (existingVersion == package.Value) + { + // compatible framework has the same version referenced + reducedPackages.Remove(package.Key); + } + // else compatible framework has a lower version, keep it + + } + else + { + Console.WriteLine($"{package.Key} - Compatible framework {nearest} has package but {framework} does not"); + } + } + } + + // write out our source file + using StreamWriter fileWriter = new($"FrameworkPackages.{framework.GetShortFolderName()}.cs"); + + // DotNetFrameworkName is something like ".NETStandard,Version=v2.0", convert to an identifier + var tfmToken = framework.DotNetFrameworkName.Replace(",Version=v", "").Replace(".", ""); + var nearestTfmToken = nearest?.DotNetFrameworkName.Replace(",Version=v", "").Replace(".", ""); + + fileWriter.WriteLine("namespace Microsoft.ComponentDetection.Detectors.NuGet;"); + fileWriter.WriteLine(); + fileWriter.WriteLine("using global::NuGet.Frameworks;"); + fileWriter.WriteLine(); + fileWriter.WriteLine("/// "); + fileWriter.WriteLine($"/// Framework packages for {framework.ToString()}."); + fileWriter.WriteLine("/// "); + fileWriter.WriteLine("internal partial class FrameworkPackages"); + fileWriter.WriteLine("{"); + fileWriter.WriteLine($" internal static class {tfmToken}"); + fileWriter.WriteLine(" {"); + fileWriter.WriteLine($" internal static FrameworkPackages Instance {{ get; }} = new(NuGetFramework.Parse(\"{framework.GetShortFolderName()}\"){(nearestTfmToken != null ? $", {nearestTfmToken}.Instance" : "")})"); + fileWriter.WriteLine(" {"); + foreach(var package in reducedPackages.OrderBy(p => p.Key)) + { + fileWriter.WriteLine($" {{ \"{package.Key}\", \"{package.Value}\" }},"); + } + fileWriter.WriteLine(" };"); + fileWriter.WriteLine(" }"); + fileWriter.WriteLine("}"); +} + +IEnumerable<(string packageId, Version version)> ProcessFiles(string referencePath, NuGetFramework tfm) +{ + if (!Directory.Exists(referencePath)) + { + throw new Exception($"Directory doesn't exist {referencePath}"); + } + + if (!File.Exists(Path.Combine(referencePath, "System.Runtime.dll"))) + { + referencePath = Path.Combine(referencePath, "ref", tfm.GetShortFolderName()); + + if (!File.Exists(Path.Combine(referencePath, "System.Runtime.dll"))) + throw new Exception($"Couldn't find references in {referencePath}"); + } + + foreach (var libraryPath in Directory.EnumerateFiles(referencePath, "*.dll").Where(f => !filesToIgnore.Contains(Path.GetFileNameWithoutExtension(f)))) + { + var assemblyName = AssemblyName.GetAssemblyName(libraryPath); + var packageId = assemblyName.Name; + var assemblyVersion = assemblyName.Version; + var assemblyFileVersion = Version.Parse(FileVersionInfo.GetVersionInfo(libraryPath).FileVersion); + + // For a library in a ref pack, look at all stable packages. + var stableVersions = NuGetUtilities.GetStableVersions2(packageId); + // Starting with the latest download each. + foreach (var stableVersion in stableVersions) + { + // Evaluate the package for the current framework. + var packageContentVersions = NuGetUtilities.ResolvePackageAssetVersions(packageId, stableVersion, tfm); + + if (!packageContentVersions.Any()) + { + continue; + } + + bool packageWins = false; + foreach (var packageContentVersion in packageContentVersions) + { + if (packageContentVersion.assemblyVersion > assemblyVersion) + { + packageWins = true; + break; + } + + if (packageContentVersion.assemblyVersion < assemblyVersion) + { + break; + } + + // equal assembly version + + if (packageContentVersion.fileVersion > assemblyFileVersion) + { + packageWins = true; + break; + } + + // package file version is equal to or less than -- package loses + } + + // If the library wins, stop. If it loses, then continue with the next newest package + if (!packageWins) + { + yield return (packageId, stableVersion); + break; + } + } + } +} + diff --git a/test/findLastPackage/Readme.md b/test/findLastPackage/Readme.md new file mode 100644 index 000000000..2e8c06bbf --- /dev/null +++ b/test/findLastPackage/Readme.md @@ -0,0 +1,11 @@ +A tool for calculating overlaping library packages on past frameworks. + +The .NET SDK does conflict resolution with NuGet packages to decide when to ignore the content of packages in favor of that from other sources (like other packages or the Framework itself). + +Since packages are immutable - we can precompute what packages will "lose" to framework assets. To do that we download the package, resolve what assets apply, then compare those to the framework assets. We use the reference assets only, since that's the minimum version of the framework assets and what conflict resolution itself would use. + +The framework targeting packs have a file to ship these precomputed package versions, [PackageOverrides.txt](https://github.com/dotnet/sdk/blob/7deb36232b9c0ccd5084fced1df07920c10a5b72/src/Tasks/Microsoft.NET.Build.Tasks/ResolveTargetingPackAssets.cs#L199) -- but that file wasn't kept up to date of the course framework versions. It was meant as a "fast path" not a source of truth. In future framework versions this will need to be the source of truth since it will feed into NuGet's supplied by platform feature. + +Once caclculating these we reduce them to a minimum set by allowing compatible frameworks to build upon the previous framework's data - thus reducing the total code size and memory usage of the set of framework packages. + +This tool is very special purpose and one-off for this scenario (even generating the exact source format expected by the component-detection types). Its a means to an end and shared for reference. diff --git a/test/findLastPackage/findLastPackage.csproj b/test/findLastPackage/findLastPackage.csproj new file mode 100644 index 000000000..cdcf66966 --- /dev/null +++ b/test/findLastPackage/findLastPackage.csproj @@ -0,0 +1,18 @@ + + + + Exe + net8.0 + enable + enable + + + + + + + + + + +