dependabot-nuget 0.292.0 → 0.293.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (70) hide show
  1. checksums.yaml +4 -4
  2. data/helpers/lib/NuGetUpdater/.gitignore +1 -0
  3. data/helpers/lib/NuGetUpdater/Directory.Packages.props +1 -0
  4. data/helpers/lib/NuGetUpdater/DotNetPackageCorrelation/Correlator.cs +197 -0
  5. data/helpers/lib/NuGetUpdater/DotNetPackageCorrelation/DotNetPackageCorrelation.csproj +12 -0
  6. data/helpers/lib/NuGetUpdater/DotNetPackageCorrelation/Model/PackageMapper.cs +68 -0
  7. data/helpers/lib/NuGetUpdater/DotNetPackageCorrelation/Model/PackageSet.cs +11 -0
  8. data/helpers/lib/NuGetUpdater/DotNetPackageCorrelation/Model/Release.cs +25 -0
  9. data/helpers/lib/NuGetUpdater/DotNetPackageCorrelation/Model/ReleasesFile.cs +9 -0
  10. data/helpers/lib/NuGetUpdater/DotNetPackageCorrelation/Model/RuntimePackages.cs +11 -0
  11. data/helpers/lib/NuGetUpdater/DotNetPackageCorrelation/Model/Sdk.cs +13 -0
  12. data/helpers/lib/NuGetUpdater/DotNetPackageCorrelation/Model/SemVerComparer.cs +16 -0
  13. data/helpers/lib/NuGetUpdater/DotNetPackageCorrelation/Model/SemVersionConverter.cs +42 -0
  14. data/helpers/lib/NuGetUpdater/DotNetPackageCorrelation.Cli/DotNetPackageCorrelation.Cli.csproj +16 -0
  15. data/helpers/lib/NuGetUpdater/DotNetPackageCorrelation.Cli/Program.cs +32 -0
  16. data/helpers/lib/NuGetUpdater/DotNetPackageCorrelation.Test/CorrelatorTests.cs +99 -0
  17. data/helpers/lib/NuGetUpdater/DotNetPackageCorrelation.Test/DotNetPackageCorrelation.Test.csproj +18 -0
  18. data/helpers/lib/NuGetUpdater/DotNetPackageCorrelation.Test/EndToEndTests.cs +30 -0
  19. data/helpers/lib/NuGetUpdater/DotNetPackageCorrelation.Test/RuntimePackagesTests.cs +206 -0
  20. data/helpers/lib/NuGetUpdater/NuGetUpdater.Cli/Commands/AnalyzeCommand.cs +6 -4
  21. data/helpers/lib/NuGetUpdater/NuGetUpdater.Cli/Commands/DiscoverCommand.cs +8 -7
  22. data/helpers/lib/NuGetUpdater/NuGetUpdater.Cli/Commands/RunCommand.cs +4 -4
  23. data/helpers/lib/NuGetUpdater/NuGetUpdater.Cli/Commands/UpdateCommand.cs +17 -5
  24. data/helpers/lib/NuGetUpdater/NuGetUpdater.Cli.Test/EntryPointTests.Analyze.cs +7 -1
  25. data/helpers/lib/NuGetUpdater/NuGetUpdater.Cli.Test/EntryPointTests.Discover.cs +46 -6
  26. data/helpers/lib/NuGetUpdater/NuGetUpdater.Cli.Test/EntryPointTests.Update.cs +8 -0
  27. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Analyze/AnalyzeWorker.cs +8 -17
  28. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Analyze/DependencyFinder.cs +4 -4
  29. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Analyze/Extensions.cs +1 -1
  30. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Clone/CloneWorker.cs +2 -1
  31. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/DependencyDiscovery.targets +2 -2
  32. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Discover/DiscoveryWorker.cs +7 -20
  33. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Discover/ProjectDiscoveryResult.cs +3 -3
  34. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Discover/SdkProjectDiscovery.cs +99 -2
  35. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/EnsureDotNetPackageCorrelation.targets +25 -0
  36. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/ExperimentsManager.cs +9 -22
  37. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Files/PackagesConfigBuildFile.cs +5 -1
  38. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/MissingFileException.cs +2 -1
  39. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/NativeResult.cs +3 -3
  40. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/NuGetUpdater.Core.csproj +3 -0
  41. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/ApiModel/DependencyFileNotFound.cs +7 -3
  42. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/ApiModel/DependencyFileNotParseable.cs +15 -0
  43. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/ApiModel/JobErrorBase.cs +24 -0
  44. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/RunWorker.cs +6 -21
  45. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/UnparseableFileException.cs +12 -0
  46. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/PackageReferenceUpdater.cs +21 -0
  47. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/UpdaterWorker.cs +6 -30
  48. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Utilities/DotNetPackageCorrelationManager.cs +46 -0
  49. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Utilities/MSBuildHelper.cs +51 -27
  50. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Utilities/NuGetHelper.cs +1 -1
  51. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Analyze/AnalyzeWorkerTestBase.cs +15 -4
  52. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Analyze/AnalyzeWorkerTests.cs +15 -9
  53. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Discover/DiscoveryWorkerTestBase.cs +20 -12
  54. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Discover/DiscoveryWorkerTests.Project.cs +108 -0
  55. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Discover/DiscoveryWorkerTests.cs +16 -12
  56. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Discover/ExpectedDiscoveryResults.cs +1 -1
  57. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/MockNuGetPackage.cs +15 -28
  58. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Run/RunWorkerTests.cs +5 -4
  59. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Run/SerializationTests.cs +9 -1
  60. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/TestBase.cs +24 -0
  61. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/ExpectedUpdateOperationResult.cs +1 -1
  62. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTestBase.cs +11 -15
  63. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTests.DirsProj.cs +1 -1
  64. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTests.Mixed.cs +14 -8
  65. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTests.PackageReference.cs +148 -3
  66. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTests.PackagesConfig.cs +12 -14
  67. data/helpers/lib/NuGetUpdater/NuGetUpdater.sln +18 -1
  68. data/lib/dependabot/nuget/native_helpers.rb +41 -16
  69. metadata +25 -6
  70. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/ErrorType.cs +0 -12
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: fc0cf2e8a7f769337787f983af710cd8a4c30c464b1118f22589c522b3ed107a
4
- data.tar.gz: b1e2fb7957b3cd3daf6331e199ebce5b0f94d84f42033de6e6f0e39bef4295b8
3
+ metadata.gz: 833b170b34a1cef53346970cdfc208b39f49899aaeeb1d2da285463eabf2aea3
4
+ data.tar.gz: aea74663e1ec787d2496c6d63f97c531d6ccd5415cdd810ce7d8518080b027cc
5
5
  SHA512:
6
- metadata.gz: 2c66ad9bb9ce3d932d0965f18fc4a6dde14ae022413343689a7c92f1e9439cca523447c20719f579bbc6c5fe6caeadab1dde0056f0a99e4a1afd09c7a5a79266
7
- data.tar.gz: eb4601daef366ea15b011fb20a3efce1b976da73a4c4ba14c991a1aa9910a01d220b2cc46a086e47e7336432597b01f431d65ad4d40b431b1545996e1af613dd
6
+ metadata.gz: 889ab157302c7dbb0be345ca4e5204fe5518d6aafbd61d7992008404fa84200240e6f88655027ddb4a77611964cb97e2ea7857ad6453d33e498e4d6416b79eb8
7
+ data.tar.gz: 17f3003ef471825cf980d00da41c528bb051dc64285d6ca808ca84bea09a89b91a197a388d8f62c4e089427a52e77ba24d13b452ec9bc7231b46d18c9c84d697
@@ -4,4 +4,5 @@ bin/
4
4
  obj/
5
5
  Properties/launchSettings.json
6
6
  NuGetUpdater.sln.DotSettings.user
7
+ NuGetUpdater.Core/dotnet-package-correlation.json
7
8
  *.binlog
@@ -26,6 +26,7 @@
26
26
  <PackageVersion Include="MSBuild.StructuredLogger" Version="2.2.386" />
27
27
  <PackageVersion Include="Newtonsoft.Json" Version="13.0.3" />
28
28
  <PackageVersion Include="NuGet.Core" Version="2.14.0" Aliases="CoreV2" />
29
+ <PackageVersion Include="Semver" Version="3.0.0" />
29
30
  <PackageVersion Include="System.CommandLine" Version="2.0.0-beta4.22272.1" />
30
31
  <PackageVersion Include="System.ComponentModel.Composition" Version="9.0.0" />
31
32
  <PackageVersion Include="System.Net.Http" Version="4.3.4" />
@@ -0,0 +1,197 @@
1
+ using System.Collections.Immutable;
2
+ using System.Text.Json;
3
+ using System.Text.RegularExpressions;
4
+
5
+ using Semver;
6
+
7
+ namespace DotNetPackageCorrelation;
8
+
9
+ public partial class Correlator
10
+ {
11
+ public static readonly JsonSerializerOptions SerializerOptions = new()
12
+ {
13
+ WriteIndented = true,
14
+ Converters = { new SemVersionConverter() },
15
+ };
16
+
17
+ private readonly DirectoryInfo _releaseNotesDirectory;
18
+
19
+ public Correlator(DirectoryInfo releaseNotesDirectory)
20
+ {
21
+ _releaseNotesDirectory = releaseNotesDirectory;
22
+ }
23
+
24
+ public async Task<(RuntimePackages RuntimePackages, IEnumerable<string> Warnings)> RunAsync()
25
+ {
26
+ var runtimeVersions = new List<Version>();
27
+ foreach (var directory in Directory.EnumerateDirectories(_releaseNotesDirectory.FullName))
28
+ {
29
+ var directoryName = Path.GetFileName(directory);
30
+ if (Version.TryParse(directoryName, out var version))
31
+ {
32
+ runtimeVersions.Add(version);
33
+ }
34
+ }
35
+
36
+ var runtimePackages = new RuntimePackages();
37
+ var warnings = new List<string>();
38
+ foreach (var majorVersion in runtimeVersions)
39
+ {
40
+ var releasesJsonPath = Path.Combine(_releaseNotesDirectory.FullName, majorVersion.ToString(), "releases.json");
41
+ if (!File.Exists(releasesJsonPath))
42
+ {
43
+ warnings.Add($"Unable to find releases.json file for version {majorVersion}");
44
+ continue;
45
+ }
46
+
47
+ var releasesJson = await File.ReadAllTextAsync(releasesJsonPath);
48
+ var releasesFile = JsonSerializer.Deserialize<ReleasesFile>(releasesJson, SerializerOptions)!;
49
+
50
+ foreach (var release in releasesFile.Releases)
51
+ {
52
+ foreach (var sdk in release.GetSdks())
53
+ {
54
+ if (sdk.Version is null)
55
+ {
56
+ warnings.Add($"Skipping release with missing version information from {releasesJson}");
57
+ continue;
58
+ }
59
+
60
+ if (sdk.RuntimeVersion is null)
61
+ {
62
+ warnings.Add($"Skipping release with missing runtime version information from {releasesJson}");
63
+ continue;
64
+ }
65
+
66
+ if (runtimePackages.Runtimes.ContainsKey(sdk.RuntimeVersion))
67
+ {
68
+ // already processed this runtime
69
+ continue;
70
+ }
71
+
72
+ var packagesAndVersions = new PackageSet();
73
+ runtimePackages.Runtimes.Add(sdk.RuntimeVersion, packagesAndVersions);
74
+ var runtimeDirectory = new DirectoryInfo(Path.Combine(_releaseNotesDirectory.FullName, majorVersion.ToString(), sdk.RuntimeVersion.ToString()));
75
+ var runtimeMarkdownPath = Path.Combine(runtimeDirectory.FullName, $"{sdk.RuntimeVersion}.md");
76
+ if (!File.Exists(runtimeMarkdownPath))
77
+ {
78
+ warnings.Add($"Unable to find expected markdown file {runtimeMarkdownPath}");
79
+ continue;
80
+ }
81
+
82
+ var markdownContent = await File.ReadAllTextAsync(runtimeMarkdownPath);
83
+ var packages = GetPackagesFromMarkdown(runtimeMarkdownPath, markdownContent, warnings);
84
+ foreach (var (packageName, packageVersion) in packages)
85
+ {
86
+ packagesAndVersions.Packages[packageName] = packageVersion;
87
+ }
88
+ }
89
+ }
90
+ }
91
+
92
+ return (runtimePackages, warnings);
93
+ }
94
+
95
+ public static ImmutableArray<(string Name, SemVersion Version)> GetPackagesFromMarkdown(string markdownPath, string markdownContent, List<string> warnings)
96
+ {
97
+ var lines = markdownContent.Split("\n").Select(l => l.Trim()).ToArray();
98
+
99
+ // the markdown file contains a table that looks like this:
100
+ // Package name | Version
101
+ // :----------- | :------------------
102
+ // Some.Package | 1.2.3
103
+ // ...
104
+ // however there are some formatting issues with some elements that prevent markdown parsers from
105
+ // discovering it, so we fall back to manual parsing
106
+
107
+ var tableStartLine = -1;
108
+ for (int i = 0; i < lines.Length; i++)
109
+ {
110
+ if (Regex.IsMatch(lines[i], "Package name.*Version"))
111
+ {
112
+ tableStartLine = i;
113
+ break;
114
+ }
115
+ }
116
+
117
+ if (tableStartLine == -1)
118
+ {
119
+ warnings.Add($"Unable to find table start in file {markdownPath}");
120
+ return [];
121
+ }
122
+
123
+ // skip the column names and separator line
124
+ tableStartLine += 2;
125
+
126
+ var tableEndLine = lines.Length; // assume the end of the file unless we find a blank line
127
+ for (int i = tableStartLine; i < lines.Length; i++)
128
+ {
129
+ if (string.IsNullOrEmpty(lines[i]))
130
+ {
131
+ tableEndLine = i;
132
+ break;
133
+ }
134
+ }
135
+
136
+ var packages = new List<(string Name, SemVersion Version)>();
137
+ for (int i = tableStartLine; i < tableEndLine; i++)
138
+ {
139
+ var line = lines[i].Trim();
140
+ var foundMatch = false;
141
+ foreach (var pattern in SpecialCasePatterns)
142
+ {
143
+ var match = pattern.Match(line);
144
+ if (match.Success)
145
+ {
146
+ var packageName = match.Groups["PackageName"].Value;
147
+ var packageVersionString = match.Groups["PackageVersion"].Value;
148
+ if (SemVersion.TryParse(packageVersionString, out var packageVersion))
149
+ {
150
+ packages.Add((packageName, packageVersion));
151
+ foundMatch = true;
152
+ break; ;
153
+ }
154
+ }
155
+ }
156
+
157
+ if (!foundMatch)
158
+ {
159
+ warnings.Add($"Unable to parse package and version from string [{line}] in file [{markdownPath}]:{i}");
160
+ }
161
+ }
162
+
163
+ return packages.ToImmutableArray();
164
+ }
165
+
166
+ // The different patterns the lines in the markdown might take. Due to issues with regular expressions, this list
167
+ // is in a very specific order.
168
+ private static ImmutableArray<Regex> SpecialCasePatterns { get; } = [
169
+ StandardLineWithFileExtensions(),
170
+ StandardLine(),
171
+ PackageNameDotVersion(),
172
+ PackageFileNameWithOptionalTrailingPipe(),
173
+ MultiColumnWithOptionalFileSuffix(),
174
+ ];
175
+
176
+ [GeneratedRegex(@"^(?<PackageName>[^|\s]+)\s*\|\s*(?<PackageVersion>[^|\s]+?)(\.symbols)?\.nupkg$", RegexOptions.Compiled)]
177
+ // Some.Package | 1.2.3.nupkg
178
+ // Some.Package | 1.2.3.symbols.nupkg
179
+ private static partial Regex StandardLineWithFileExtensions();
180
+
181
+ [GeneratedRegex(@"^(?<PackageName>[^|\s]+)\s*\|\s*(?<PackageVersion>[^|\s]+)$", RegexOptions.Compiled)]
182
+ // Some.Package | 1.2.3
183
+ private static partial Regex StandardLine();
184
+
185
+ [GeneratedRegex(@"^(?<PackageName>[^\d]+)\.(?<PackageVersion>[\d].+)$", RegexOptions.Compiled)]
186
+ // Some.Package.1.2.3
187
+ private static partial Regex PackageNameDotVersion();
188
+
189
+ [GeneratedRegex(@"^(?<PackageName>[^\d]+)\.(?<PackageVersion>\d.+?)\.nupkg(\s+\|)?$", RegexOptions.Compiled)]
190
+ // some.package.1.2.3.nupkg
191
+ // some.package.1.2.3.nupkg |
192
+ private static partial Regex PackageFileNameWithOptionalTrailingPipe();
193
+
194
+ [GeneratedRegex(@"^(?<PackageName>[^|\s]+)\s*\|[^|]*\|\s*(?<PackageVersion>.*?)(\.nupkg)?$", RegexOptions.Compiled)]
195
+ // Some.Package | 1.2 | 1.2.3.nupkg
196
+ private static partial Regex MultiColumnWithOptionalFileSuffix();
197
+ }
@@ -0,0 +1,12 @@
1
+ <Project Sdk="Microsoft.NET.Sdk">
2
+
3
+ <PropertyGroup>
4
+ <TargetFramework>$(CommonTargetFramework)</TargetFramework>
5
+ </PropertyGroup>
6
+
7
+ <ItemGroup>
8
+ <PackageReference Include="Semver" />
9
+ <PackageReference Include="System.Text.Json" />
10
+ </ItemGroup>
11
+
12
+ </Project>
@@ -0,0 +1,68 @@
1
+ using Semver;
2
+
3
+ namespace DotNetPackageCorrelation;
4
+
5
+ public class PackageMapper
6
+ {
7
+ public RuntimePackages RuntimePackages { get; }
8
+
9
+ private PackageMapper(RuntimePackages runtimePackages)
10
+ {
11
+ RuntimePackages = runtimePackages;
12
+ }
13
+
14
+ /// <summary>
15
+ /// Find the version of <paramref name="candidatePackageName"/> that shipped at the same time as
16
+ /// &quot;<paramref name="packageName"/>/<paramref name="packageVersion"/>&quot;.
17
+ /// </summary>
18
+ public SemVersion? GetPackageVersionThatShippedWithOtherPackage(string packageName, SemVersion packageVersion, string candidatePackageName)
19
+ {
20
+ var runtimeVersion = GetRuntimeVersionFromPackage(packageName, packageVersion);
21
+ if (runtimeVersion is null)
22
+ {
23
+ // no runtime found that contains the package
24
+ return null;
25
+ }
26
+
27
+ var candidateRuntimeVersions = RuntimePackages.Runtimes.Keys
28
+ .Where(v => v.Major == runtimeVersion.Major)
29
+ .Where(v => v.ComparePrecedenceTo(runtimeVersion) <= 0)
30
+ .OrderBy(v => v, SemVerComparer.Instance)
31
+ .Reverse()
32
+ .ToArray();
33
+ foreach (var candidateRuntimeVersion in candidateRuntimeVersions)
34
+ {
35
+ if (!RuntimePackages.Runtimes.TryGetValue(candidateRuntimeVersion, out var packageSet))
36
+ {
37
+ continue;
38
+ }
39
+
40
+ if (packageSet.Packages.TryGetValue(candidatePackageName, out var foundPackageVersion))
41
+ {
42
+ return foundPackageVersion;
43
+ }
44
+ }
45
+
46
+ return null;
47
+ }
48
+
49
+ private SemVersion? GetRuntimeVersionFromPackage(string packageName, SemVersion packageVersion)
50
+ {
51
+ // TODO: linear search is slow
52
+ foreach (var runtime in RuntimePackages.Runtimes)
53
+ {
54
+ if (runtime.Value.Packages.TryGetValue(packageName, out var foundPackageVersion) &&
55
+ foundPackageVersion == packageVersion)
56
+ {
57
+ return runtime.Key;
58
+ }
59
+ }
60
+
61
+ return null;
62
+ }
63
+
64
+ public static PackageMapper Load(RuntimePackages runtimePackages)
65
+ {
66
+ return new PackageMapper(runtimePackages);
67
+ }
68
+ }
@@ -0,0 +1,11 @@
1
+ using System.Text.Json.Serialization;
2
+
3
+ using Semver;
4
+
5
+ namespace DotNetPackageCorrelation;
6
+
7
+ public record PackageSet
8
+ {
9
+ [JsonObjectCreationHandling(JsonObjectCreationHandling.Populate)]
10
+ public SortedDictionary<string, SemVersion> Packages { get; init; } = new(StringComparer.OrdinalIgnoreCase);
11
+ }
@@ -0,0 +1,25 @@
1
+ using System.Collections.Immutable;
2
+ using System.Text.Json.Serialization;
3
+
4
+ namespace DotNetPackageCorrelation;
5
+
6
+ public record Release
7
+ {
8
+ [JsonPropertyName("sdk")]
9
+ public required Sdk Sdk { get; init; }
10
+
11
+ [JsonPropertyName("sdks")]
12
+ public ImmutableArray<Sdk>? Sdks { get; init; } = [];
13
+ }
14
+
15
+ public static class ReleaseExtensions
16
+ {
17
+ public static IEnumerable<Sdk> GetSdks(this Release release)
18
+ {
19
+ yield return release.Sdk;
20
+ foreach (var sdk in release.Sdks ?? [])
21
+ {
22
+ yield return sdk;
23
+ }
24
+ }
25
+ }
@@ -0,0 +1,9 @@
1
+ using System.Text.Json.Serialization;
2
+
3
+ namespace DotNetPackageCorrelation;
4
+
5
+ public record ReleasesFile
6
+ {
7
+ [JsonPropertyName("releases")]
8
+ public required Release[] Releases { get; init; }
9
+ }
@@ -0,0 +1,11 @@
1
+ using System.Text.Json.Serialization;
2
+
3
+ using Semver;
4
+
5
+ namespace DotNetPackageCorrelation;
6
+
7
+ public record RuntimePackages
8
+ {
9
+ [JsonObjectCreationHandling(JsonObjectCreationHandling.Populate)]
10
+ public SortedDictionary<SemVersion, PackageSet> Runtimes { get; init; } = new(SemVerComparer.Instance);
11
+ }
@@ -0,0 +1,13 @@
1
+ using System.Text.Json.Serialization;
2
+
3
+ using Semver;
4
+
5
+ namespace DotNetPackageCorrelation;
6
+
7
+ public record Sdk
8
+ {
9
+ [JsonPropertyName("version")]
10
+ public required SemVersion? Version { get; init; }
11
+ [JsonPropertyName("runtime-version")]
12
+ public SemVersion? RuntimeVersion { get; init; }
13
+ }
@@ -0,0 +1,16 @@
1
+ using Semver;
2
+
3
+ namespace DotNetPackageCorrelation;
4
+
5
+ public class SemVerComparer : IComparer<SemVersion>
6
+ {
7
+ public static SemVerComparer Instance = new();
8
+
9
+ public int Compare(SemVersion? x, SemVersion? y)
10
+ {
11
+ ArgumentNullException.ThrowIfNull(x);
12
+ ArgumentNullException.ThrowIfNull(y);
13
+
14
+ return x.ComparePrecedenceTo(y);
15
+ }
16
+ }
@@ -0,0 +1,42 @@
1
+ using System.Diagnostics.CodeAnalysis;
2
+ using System.Text.Json;
3
+ using System.Text.Json.Serialization;
4
+
5
+ using Semver;
6
+
7
+ namespace DotNetPackageCorrelation;
8
+
9
+ public class SemVersionConverter : JsonConverter<SemVersion?>
10
+ {
11
+ public override SemVersion? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
12
+ {
13
+ var value = reader.GetString();
14
+ if (SemVersion.TryParse(value, out var result))
15
+ {
16
+ return result;
17
+ }
18
+
19
+ return null;
20
+ }
21
+
22
+ public override SemVersion ReadAsPropertyName(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
23
+ {
24
+ var value = Read(ref reader, typeToConvert, options);
25
+ if (value is null)
26
+ {
27
+ throw new JsonException($"Unable to read {nameof(SemVersion)} as property name.");
28
+ }
29
+
30
+ return value;
31
+ }
32
+
33
+ public override void Write(Utf8JsonWriter writer, SemVersion? value, JsonSerializerOptions options)
34
+ {
35
+ writer.WriteStringValue(value?.ToString());
36
+ }
37
+
38
+ public override void WriteAsPropertyName(Utf8JsonWriter writer, [DisallowNull] SemVersion value, JsonSerializerOptions options)
39
+ {
40
+ writer.WritePropertyName(value.ToString());
41
+ }
42
+ }
@@ -0,0 +1,16 @@
1
+ <Project Sdk="Microsoft.NET.Sdk">
2
+
3
+ <PropertyGroup>
4
+ <TargetFramework>$(CommonTargetFramework)</TargetFramework>
5
+ <OutputType>Exe</OutputType>
6
+ </PropertyGroup>
7
+
8
+ <ItemGroup>
9
+ <PackageReference Include="System.CommandLine" />
10
+ </ItemGroup>
11
+
12
+ <ItemGroup>
13
+ <ProjectReference Include="..\DotNetPackageCorrelation\DotNetPackageCorrelation.csproj" />
14
+ </ItemGroup>
15
+
16
+ </Project>
@@ -0,0 +1,32 @@
1
+ using System.CommandLine;
2
+ using System.Text.Json;
3
+
4
+ using DotNetPackageCorrelation;
5
+
6
+ namespace DotNetPackageCorrelation.Cli;
7
+
8
+ public class Program
9
+ {
10
+ public static async Task<int> Main(string[] args)
11
+ {
12
+ var coreLocationOption = new Option<DirectoryInfo>("--core-location", "The location of the .NET Core source code.") { IsRequired = true };
13
+ var outputOption = new Option<FileInfo>("--output", "The location to write the result.") { IsRequired = true };
14
+ var command = new Command("build")
15
+ {
16
+ coreLocationOption,
17
+ outputOption,
18
+ };
19
+ command.TreatUnmatchedTokensAsErrors = true;
20
+ command.SetHandler(async (coreLocationDirectory, output) =>
21
+ {
22
+ // the tool is expected to be given the path to the .NET Core repository, but the correlator only needs a specific subdirectory
23
+ var releaseNotesDirectory = new DirectoryInfo(Path.Combine(coreLocationDirectory.FullName, "release-notes"));
24
+ var correlator = new Correlator(releaseNotesDirectory);
25
+ var (sdkPackages, _warnings) = await correlator.RunAsync();
26
+ var json = JsonSerializer.Serialize(sdkPackages, Correlator.SerializerOptions);
27
+ await File.WriteAllTextAsync(output.FullName, json);
28
+ }, coreLocationOption, outputOption);
29
+ var exitCode = await command.InvokeAsync(args);
30
+ return exitCode;
31
+ }
32
+ }
@@ -0,0 +1,99 @@
1
+ using Semver;
2
+
3
+ using Xunit;
4
+
5
+ namespace DotNetPackageCorrelation.Tests;
6
+
7
+ public class CorrelatorTests
8
+ {
9
+ [Fact]
10
+ public async Task FileHandling_AllFilesShapedAppropriately()
11
+ {
12
+ // the JSON and markdown are shaped as expected
13
+ // we're able to determine from `Runtime.Package/8.0.0` that the corresponding version of `Some.Package` is `1.2.3`
14
+ var (packageMapper, warnings) = await PackageMapperFromFilesAsync(
15
+ ("8.0/releases.json", """
16
+ {
17
+ "releases": [
18
+ {
19
+ "sdk": {
20
+ "version": "8.0.100",
21
+ "runtime-version": "8.0.0"
22
+ }
23
+ }
24
+ ]
25
+ }
26
+ """),
27
+ ("8.0/8.0.0/8.0.0.md", """
28
+ Package name | Version
29
+ :-- | :--
30
+ Runtime.Package | 8.0.0
31
+ Some.Package | 1.2.3
32
+ """)
33
+ );
34
+ Assert.Empty(warnings);
35
+ AssertPackageVersion(packageMapper, "Runtime.Package", "8.0.0", "Some.Package", "1.2.3");
36
+ }
37
+
38
+ [Theory]
39
+ [InlineData("Some.Package | 1.2.3", "Some.Package", "1.2.3")] // happy path
40
+ [InlineData("Some.Package.1.2.3", "Some.Package", "1.2.3")] // looks like a restore directory
41
+ [InlineData("Some.Package | 1.2 | 1.2.3.nupkg", "Some.Package", "1.2.3")] // extra columns from a bad filename split
42
+ [InlineData("Some.Package | 1.2.3.nupkg", "Some.Package", "1.2.3")] // version contains package extension
43
+ [InlineData("Some.Package | 1.2.3.symbols.nupkg", "Some.Package", "1.2.3")] // version contains symbols package extension
44
+ [InlineData("some.package.1.2.3.nupkg", "some.package", "1.2.3")] // first column is a filename, second column is missing
45
+ [InlineData("some.package.1.2.3.nupkg |", "some.package", "1.2.3")] // first column is a filename, second column is empty
46
+ public void PackagesParsedFromMarkdown(string markdownLine, string expectedPackageName, string expectedPackageVersion)
47
+ {
48
+ var markdownContent = $"""
49
+ Package name | Version
50
+ :-- | :--
51
+ {markdownLine}
52
+ """;
53
+ var warnings = new List<string>();
54
+ var packages = Correlator.GetPackagesFromMarkdown("test.md", markdownContent, warnings);
55
+ Assert.Empty(warnings);
56
+ var actualpackage = Assert.Single(packages);
57
+ Assert.Equal(expectedPackageName, actualpackage.Name);
58
+ Assert.Equal(expectedPackageVersion, actualpackage.Version.ToString());
59
+ }
60
+
61
+ private static void AssertPackageVersion(PackageMapper packageMapper, string runtimePackageName, string runtimePackageVersion, string candidatePackageName, string? expectedPackageVersion)
62
+ {
63
+ var actualPackageVersion = packageMapper.GetPackageVersionThatShippedWithOtherPackage(runtimePackageName, SemVersion.Parse(runtimePackageVersion), candidatePackageName);
64
+ if (expectedPackageVersion is null)
65
+ {
66
+ Assert.Null(actualPackageVersion);
67
+ }
68
+ else
69
+ {
70
+ Assert.NotNull(actualPackageVersion);
71
+ Assert.Equal(expectedPackageVersion, actualPackageVersion.ToString());
72
+ }
73
+ }
74
+
75
+ private static async Task<(PackageMapper PackageMapper, IEnumerable<string> Warnings)> PackageMapperFromFilesAsync(params (string Path, string Content)[] files)
76
+ {
77
+ var testDirectory = Path.Combine(Path.GetDirectoryName(typeof(CorrelatorTests).Assembly.Location)!, "test-data", Guid.NewGuid().ToString("D"));
78
+ Directory.CreateDirectory(testDirectory);
79
+
80
+ try
81
+ {
82
+ foreach (var (path, content) in files)
83
+ {
84
+ var fullPath = Path.Combine(testDirectory, path);
85
+ Directory.CreateDirectory(Path.GetDirectoryName(fullPath)!);
86
+ await File.WriteAllTextAsync(fullPath, content);
87
+ }
88
+
89
+ var correlator = new Correlator(new DirectoryInfo(testDirectory));
90
+ var (runtimePackages, warnings) = await correlator.RunAsync();
91
+ var packageMapper = PackageMapper.Load(runtimePackages);
92
+ return (packageMapper, warnings);
93
+ }
94
+ finally
95
+ {
96
+ Directory.Delete(testDirectory, recursive: true);
97
+ }
98
+ }
99
+ }
@@ -0,0 +1,18 @@
1
+ <Project Sdk="Microsoft.NET.Sdk">
2
+
3
+ <PropertyGroup>
4
+ <TargetFramework>$(CommonTargetFramework)</TargetFramework>
5
+ <OutputType>Exe</OutputType>
6
+ </PropertyGroup>
7
+
8
+ <ItemGroup>
9
+ <PackageReference Include="Microsoft.NET.Test.Sdk" />
10
+ <PackageReference Include="xunit" />
11
+ <PackageReference Include="xunit.runner.visualstudio" />
12
+ </ItemGroup>
13
+
14
+ <ItemGroup>
15
+ <ProjectReference Include="..\DotNetPackageCorrelation\DotNetPackageCorrelation.csproj" />
16
+ </ItemGroup>
17
+
18
+ </Project>
@@ -0,0 +1,30 @@
1
+ using System.Runtime.CompilerServices;
2
+
3
+ using Semver;
4
+
5
+ using Xunit;
6
+
7
+ namespace DotNetPackageCorrelation.Tests;
8
+
9
+ public class EndToEndTests
10
+ {
11
+ [Fact]
12
+ public async Task IntegrationTest()
13
+ {
14
+ // arrange
15
+ var thisFileDirectory = Path.GetDirectoryName(GetThisFilePath())!;
16
+ var dotnetCoreDirectory = Path.Combine(thisFileDirectory, "..", "..", "dotnet-core");
17
+ var correlator = new Correlator(new DirectoryInfo(Path.Combine(dotnetCoreDirectory, "release-notes")));
18
+
19
+ // act
20
+ var (runtimePackages, _warnings) = await correlator.RunAsync();
21
+ var packageMapper = PackageMapper.Load(runtimePackages);
22
+
23
+ // assert
24
+ // Microsoft.NETCore.App.Ref/8.0.8 didn't ship with System.Text.Json, but the previous version 8.0.7 shipped at the same time as System.Text.Json/8.0.4
25
+ var systemTextJsonVersion = packageMapper.GetPackageVersionThatShippedWithOtherPackage("Microsoft.NETCore.App.Ref", SemVersion.Parse("8.0.8"), "System.Text.Json");
26
+ Assert.Equal("8.0.4", systemTextJsonVersion?.ToString());
27
+ }
28
+
29
+ private static string GetThisFilePath([CallerFilePath] string? path = null) => path ?? throw new ArgumentNullException(nameof(path));
30
+ }