dependabot-nuget 0.378.0 → 0.380.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 (40) hide show
  1. checksums.yaml +4 -4
  2. data/helpers/lib/NuGetUpdater/Directory.Packages.props +1 -0
  3. data/helpers/lib/NuGetUpdater/NuGetUpdater.Cli/Commands/CloneCommand.cs +8 -17
  4. data/helpers/lib/NuGetUpdater/NuGetUpdater.Cli/Commands/GraphCommand.cs +47 -0
  5. data/helpers/lib/NuGetUpdater/NuGetUpdater.Cli/Commands/RunCommand.cs +12 -23
  6. data/helpers/lib/NuGetUpdater/NuGetUpdater.Cli/Commands/SharedOptions.cs +17 -0
  7. data/helpers/lib/NuGetUpdater/NuGetUpdater.Cli/Program.cs +1 -0
  8. data/helpers/lib/NuGetUpdater/NuGetUpdater.Cli.Test/EntryPointTestHelper.cs +84 -0
  9. data/helpers/lib/NuGetUpdater/NuGetUpdater.Cli.Test/EntryPointTests.Graph.cs +65 -0
  10. data/helpers/lib/NuGetUpdater/NuGetUpdater.Cli.Test/EntryPointTests.Run.cs +2 -60
  11. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Analyze/CompatabilityChecker.cs +5 -7
  12. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Discover/DiscoveryWorker.cs +115 -4
  13. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Discover/ProjectDiscoveryResult.cs +4 -0
  14. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Discover/SdkProjectDiscovery.cs +76 -2
  15. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Graph/GraphWorker.cs +256 -0
  16. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/IGraphWorker.cs +6 -0
  17. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/NuGetUpdater.Core.csproj +1 -0
  18. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/ApiModel/CreateDependencySubmission.cs +92 -0
  19. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/IApiHandler.cs +1 -0
  20. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/RunWorker.cs +74 -0
  21. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/BindingRedirectManager.cs +8 -3
  22. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/FileWriters/FileWriterWorker.cs +2 -4
  23. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/PackageReferenceUpdater.cs +29 -49
  24. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Utilities/GitSubmoduleParser.cs +62 -0
  25. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Utilities/MSBuildHelper.cs +34 -1
  26. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Discover/DiscoveryWorkerTestBase.cs +10 -0
  27. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Discover/DiscoveryWorkerTests.cs +334 -1
  28. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Discover/ExpectedDiscoveryResults.cs +1 -0
  29. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Discover/SdkProjectDiscoveryTests.cs +56 -0
  30. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Graph/GraphWorkerTests.cs +465 -0
  31. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Run/EndToEndTests.InsecureHttpFeed.cs +292 -0
  32. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Run/MessageReportTests.cs +35 -0
  33. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Run/RunWorkerTests.cs +305 -0
  34. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Run/SerializationTests.cs +100 -0
  35. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/TestHttpServer.cs +82 -0
  36. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/BindingRedirectsTests.cs +52 -0
  37. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/PackageReferenceUpdaterTests.cs +73 -39
  38. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Utilities/GitSubmoduleParserTests.cs +135 -0
  39. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Utilities/MSBuildHelperTests.cs +8 -0
  40. metadata +16 -4
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5dc9026764d19eb60cee009792a8649f12625f3e5d9691653cab374dd54475f2
4
- data.tar.gz: 7f7122a374ba245a1ed4a12ceb2688f26d2f0d5fab48f9419f352d37daf5ff61
3
+ metadata.gz: 7d1e2d0fc0dbdb9ca5edd820796d80e93ff3508a43ec0a21e25be1ca9a296dcd
4
+ data.tar.gz: 77f9feddc3e1f6a42a72770aef37989b081b19c4d721b4131b0c575a1cc27ae6
5
5
  SHA512:
6
- metadata.gz: ec48dd6709dec2c379ea172d0cbf11c90e0be5055af38ce46da976b5adb834224e6973c80a7dd989235826d8d8aaf1505f24ccd86c4ee628e6007c63ee80188c
7
- data.tar.gz: 2e7473ea553408e050f3686d380537c279859408b2db4fc5aa15cf9c5e81e2404551498d1f1547bf2e6713aac8548f3bb1c09c6c7bb3ee5f82ae4a8556742635
6
+ metadata.gz: 2ac9681cf7e3f0e093c8bda609e7706b37bc5d8c2299c8dd8f0be92d16c9c1e8960b9fa1b82f293e4b0ba0a25c40709614fa5cff5c3e1d354aa2b079f611e88a
7
+ data.tar.gz: bbcf9c27baa289052da2eb6fcfeab8cd2610cfc855eb466a3f3857476a23726969cca288157462174a9bdc616444a061db54813ecbefe5c0d8343dbfcdbcea3e
@@ -27,6 +27,7 @@
27
27
  <PackageVersion Include="OpenTelemetry" Version="1.15.3" />
28
28
  <PackageVersion Include="OpenTelemetry.Exporter.Console" Version="1.15.3" />
29
29
  <PackageVersion Include="OpenTelemetry.Exporter.OpenTelemetryProtocol" Version="1.15.3" />
30
+ <PackageVersion Include="packageurl-dotnet" Version="2.0.0" />
30
31
  <PackageVersion Include="Semver" Version="3.0.0" />
31
32
  <PackageVersion Include="System.CommandLine" Version="2.0.3" />
32
33
  <PackageVersion Include="System.ComponentModel.Composition" Version="10.0.3" />
@@ -8,33 +8,24 @@ namespace NuGetUpdater.Cli.Commands;
8
8
 
9
9
  internal static class CloneCommand
10
10
  {
11
- internal static readonly Option<FileInfo> JobPathOption = new("--job-path") { Required = true };
12
- internal static readonly Option<DirectoryInfo> RepoContentsPathOption = new("--repo-contents-path") { Required = true };
13
- internal static readonly Option<Uri> ApiUrlOption = new("--api-url")
14
- {
15
- Required = true,
16
- CustomParser = (argumentResult) => Uri.TryCreate(argumentResult.Tokens.Single().Value, UriKind.Absolute, out var uri) ? uri : throw new ArgumentException("Invalid API URL format.")
17
- };
18
- internal static readonly Option<string> JobIdOption = new("--job-id") { Required = true };
19
-
20
11
  internal static Command GetCommand(Action<int> setExitCode)
21
12
  {
22
13
  var command = new Command("clone", "Clones a repository in preparation for a dependabot job.")
23
14
  {
24
- JobPathOption,
25
- RepoContentsPathOption,
26
- ApiUrlOption,
27
- JobIdOption,
15
+ SharedOptions.JobPathOption,
16
+ SharedOptions.RepoContentsPathOption,
17
+ SharedOptions.ApiUrlOption,
18
+ SharedOptions.JobIdOption,
28
19
  };
29
20
 
30
21
  command.TreatUnmatchedTokensAsErrors = true;
31
22
 
32
23
  command.SetAction(async (parseResult, cancellationToken) =>
33
24
  {
34
- var jobPath = parseResult.GetValue(JobPathOption);
35
- var repoContentsPath = parseResult.GetValue(RepoContentsPathOption);
36
- var apiUrl = parseResult.GetValue(ApiUrlOption);
37
- var jobId = parseResult.GetValue(JobIdOption);
25
+ var jobPath = parseResult.GetValue(SharedOptions.JobPathOption);
26
+ var repoContentsPath = parseResult.GetValue(SharedOptions.RepoContentsPathOption);
27
+ var apiUrl = parseResult.GetValue(SharedOptions.ApiUrlOption);
28
+ var jobId = parseResult.GetValue(SharedOptions.JobIdOption);
38
29
 
39
30
  var apiHandler = new HttpApiHandler(apiUrl!.ToString(), jobId!);
40
31
  var logger = new OpenTelemetryLogger();
@@ -0,0 +1,47 @@
1
+ using System.CommandLine;
2
+
3
+ using NuGetUpdater.Core;
4
+ using NuGetUpdater.Core.Discover;
5
+ using NuGetUpdater.Core.Graph;
6
+ using NuGetUpdater.Core.Run;
7
+
8
+ namespace NuGetUpdater.Cli.Commands;
9
+
10
+ internal static class GraphCommand
11
+ {
12
+ internal static Command GetCommand(Action<int> setExitCode)
13
+ {
14
+ Command command = new("graph", "Generates a dependency graph for a repository.")
15
+ {
16
+ SharedOptions.JobPathOption,
17
+ SharedOptions.RepoContentsPathOption,
18
+ SharedOptions.CaseInsensitiveRepoContentsPathOption,
19
+ SharedOptions.ApiUrlOption,
20
+ SharedOptions.JobIdOption,
21
+ SharedOptions.BaseCommitShaOption
22
+ };
23
+
24
+ command.TreatUnmatchedTokensAsErrors = true;
25
+
26
+ command.SetAction(async (parseResult, cancellationToken) =>
27
+ {
28
+ var jobPath = parseResult.GetValue(SharedOptions.JobPathOption);
29
+ var repoContentsPath = parseResult.GetValue(SharedOptions.RepoContentsPathOption);
30
+ var caseInsensitiveRepoContentsPath = parseResult.GetValue(SharedOptions.CaseInsensitiveRepoContentsPathOption);
31
+ var apiUrl = parseResult.GetValue(SharedOptions.ApiUrlOption);
32
+ var jobId = parseResult.GetValue(SharedOptions.JobIdOption);
33
+ var baseCommitSha = parseResult.GetValue(SharedOptions.BaseCommitShaOption);
34
+
35
+ var apiHandler = new HttpApiHandler(apiUrl!.ToString(), jobId!);
36
+ var (experimentsManager, _errorResult) = await ExperimentsManager.FromJobFileAsync(jobId!, jobPath!.FullName);
37
+ var logger = new OpenTelemetryLogger();
38
+ var discoverWorker = new DiscoveryWorker(jobId!, experimentsManager, logger);
39
+ var worker = new GraphWorker(jobId!, apiHandler, discoverWorker, logger);
40
+ var result = await worker.RunAsync(jobPath!, repoContentsPath!, caseInsensitiveRepoContentsPath, baseCommitSha!);
41
+ setExitCode(result);
42
+ return 0;
43
+ });
44
+
45
+ return command;
46
+ }
47
+ }
@@ -9,39 +9,28 @@ namespace NuGetUpdater.Cli.Commands;
9
9
 
10
10
  internal static class RunCommand
11
11
  {
12
- internal static readonly Option<FileInfo> JobPathOption = new("--job-path") { Required = true };
13
- internal static readonly Option<DirectoryInfo> RepoContentsPathOption = new("--repo-contents-path") { Required = true };
14
- internal static readonly Option<DirectoryInfo?> CaseInsensitiveRepoContentsPathOption = new("--case-insensitive-repo-contents-path") { Required = false };
15
- internal static readonly Option<Uri> ApiUrlOption = new("--api-url")
16
- {
17
- Required = true,
18
- CustomParser = (argumentResult) => Uri.TryCreate(argumentResult.Tokens.Single().Value, UriKind.Absolute, out var uri) ? uri : throw new ArgumentException("Invalid API URL format.")
19
- };
20
- internal static readonly Option<string> JobIdOption = new("--job-id") { Required = true };
21
- internal static readonly Option<string> BaseCommitShaOption = new("--base-commit-sha") { Required = true };
22
-
23
12
  internal static Command GetCommand(Action<int> setExitCode)
24
13
  {
25
14
  Command command = new("run", "Runs a full dependabot job.")
26
15
  {
27
- JobPathOption,
28
- RepoContentsPathOption,
29
- CaseInsensitiveRepoContentsPathOption,
30
- ApiUrlOption,
31
- JobIdOption,
32
- BaseCommitShaOption
16
+ SharedOptions.JobPathOption,
17
+ SharedOptions.RepoContentsPathOption,
18
+ SharedOptions.CaseInsensitiveRepoContentsPathOption,
19
+ SharedOptions.ApiUrlOption,
20
+ SharedOptions.JobIdOption,
21
+ SharedOptions.BaseCommitShaOption
33
22
  };
34
23
 
35
24
  command.TreatUnmatchedTokensAsErrors = true;
36
25
 
37
26
  command.SetAction(async (parseResult, cancellationToken) =>
38
27
  {
39
- var jobPath = parseResult.GetValue(JobPathOption);
40
- var repoContentsPath = parseResult.GetValue(RepoContentsPathOption);
41
- var caseInsensitiveRepoContentsPath = parseResult.GetValue(CaseInsensitiveRepoContentsPathOption);
42
- var apiUrl = parseResult.GetValue(ApiUrlOption);
43
- var jobId = parseResult.GetValue(JobIdOption);
44
- var baseCommitSha = parseResult.GetValue(BaseCommitShaOption);
28
+ var jobPath = parseResult.GetValue(SharedOptions.JobPathOption);
29
+ var repoContentsPath = parseResult.GetValue(SharedOptions.RepoContentsPathOption);
30
+ var caseInsensitiveRepoContentsPath = parseResult.GetValue(SharedOptions.CaseInsensitiveRepoContentsPathOption);
31
+ var apiUrl = parseResult.GetValue(SharedOptions.ApiUrlOption);
32
+ var jobId = parseResult.GetValue(SharedOptions.JobIdOption);
33
+ var baseCommitSha = parseResult.GetValue(SharedOptions.BaseCommitShaOption);
45
34
 
46
35
  var apiHandler = new HttpApiHandler(apiUrl!.ToString(), jobId!);
47
36
  var (experimentsManager, _errorResult) = await ExperimentsManager.FromJobFileAsync(jobId!, jobPath!.FullName);
@@ -0,0 +1,17 @@
1
+ using System.CommandLine;
2
+
3
+ namespace NuGetUpdater.Cli.Commands;
4
+
5
+ internal static class SharedOptions
6
+ {
7
+ internal static readonly Option<FileInfo> JobPathOption = new("--job-path") { Required = true };
8
+ internal static readonly Option<DirectoryInfo> RepoContentsPathOption = new("--repo-contents-path") { Required = true };
9
+ internal static readonly Option<DirectoryInfo?> CaseInsensitiveRepoContentsPathOption = new("--case-insensitive-repo-contents-path") { Required = false };
10
+ internal static readonly Option<Uri> ApiUrlOption = new("--api-url")
11
+ {
12
+ Required = true,
13
+ CustomParser = (argumentResult) => Uri.TryCreate(argumentResult.Tokens.Single().Value, UriKind.Absolute, out var uri) ? uri : throw new ArgumentException("Invalid API URL format.")
14
+ };
15
+ internal static readonly Option<string> JobIdOption = new("--job-id") { Required = true };
16
+ internal static readonly Option<string> BaseCommitShaOption = new("--base-commit-sha") { Required = true };
17
+ }
@@ -20,6 +20,7 @@ internal sealed class Program
20
20
  {
21
21
  CloneCommand.GetCommand(setExitCode),
22
22
  RunCommand.GetCommand(setExitCode),
23
+ GraphCommand.GetCommand(setExitCode),
23
24
  };
24
25
  command.TreatUnmatchedTokensAsErrors = true;
25
26
 
@@ -0,0 +1,84 @@
1
+ using System.Text;
2
+ using System.Text.Json;
3
+
4
+ using NuGetUpdater.Core.Run;
5
+ using NuGetUpdater.Core.Run.ApiModel;
6
+ using NuGetUpdater.Core.Test;
7
+ using NuGetUpdater.Core.Test.Update;
8
+
9
+ using Xunit;
10
+
11
+ namespace NuGetUpdater.Cli.Test;
12
+
13
+ using TestFile = (string Path, string Content);
14
+
15
+ internal static class EntryPointTestHelper
16
+ {
17
+ internal static async Task RunAsync(
18
+ string commandName,
19
+ TestFile[] files,
20
+ Job job,
21
+ string[] expectedUrls,
22
+ MockNuGetPackage[]? packages = null,
23
+ string? repoContentsPath = null,
24
+ int expectedExitCode = 0)
25
+ {
26
+ using var tempDirectory = new TemporaryDirectory();
27
+
28
+ // write test files
29
+ foreach (var testFile in files)
30
+ {
31
+ var fullPath = Path.Join(tempDirectory.DirectoryPath, testFile.Path);
32
+ var directory = Path.GetDirectoryName(fullPath)!;
33
+ Directory.CreateDirectory(directory);
34
+ await File.WriteAllTextAsync(fullPath, testFile.Content);
35
+ }
36
+
37
+ // write job file
38
+ var jobPath = Path.Combine(tempDirectory.DirectoryPath, "job.json");
39
+ await File.WriteAllTextAsync(jobPath, JsonSerializer.Serialize(new { Job = job }, RunWorker.SerializerOptions));
40
+
41
+ // save packages
42
+ await UpdateWorkerTestBase.MockNuGetPackagesInDirectory(packages, tempDirectory.DirectoryPath);
43
+
44
+ var actualUrls = new List<string>();
45
+ using var http = TestHttpServer.CreateTestStringServer((method, url) =>
46
+ {
47
+ actualUrls.Add($"{method} {new Uri(url).PathAndQuery}");
48
+ return (200, "ok");
49
+ });
50
+ var args = new List<string>()
51
+ {
52
+ commandName,
53
+ "--job-path",
54
+ jobPath,
55
+ "--repo-contents-path",
56
+ repoContentsPath ?? tempDirectory.DirectoryPath,
57
+ "--api-url",
58
+ http.BaseUrl,
59
+ "--job-id",
60
+ "TEST-ID",
61
+ "--base-commit-sha",
62
+ "BASE-COMMIT-SHA"
63
+ };
64
+
65
+ var output = new StringBuilder();
66
+ // redirect stdout
67
+ var originalOut = Console.Out;
68
+ Console.SetOut(new StringWriter(output));
69
+ int result = -1;
70
+ try
71
+ {
72
+ result = await Program.Main(args.ToArray());
73
+ }
74
+ catch
75
+ {
76
+ // restore stdout
77
+ Console.SetOut(originalOut);
78
+ throw;
79
+ }
80
+
81
+ Assert.True(result == expectedExitCode, $"Expected exit code {expectedExitCode} but got {result}.\nSTDOUT:\n" + output.ToString());
82
+ Assert.Equal(expectedUrls, actualUrls);
83
+ }
84
+ }
@@ -0,0 +1,65 @@
1
+ using NuGetUpdater.Core.Run.ApiModel;
2
+ using NuGetUpdater.Core.Test;
3
+
4
+ using Xunit;
5
+
6
+ namespace NuGetUpdater.Cli.Test;
7
+
8
+ using TestFile = (string Path, string Content);
9
+
10
+ public partial class EntryPointTests
11
+ {
12
+ public class Graph
13
+ {
14
+ [Fact]
15
+ public async Task Graph_Simple()
16
+ {
17
+ // verify we can pass command line arguments for graph command
18
+ await RunAsync(
19
+ packages:
20
+ [
21
+ MockNuGetPackage.CreateSimplePackage("Some.Package", "1.0.0", "net8.0"),
22
+ ],
23
+ files:
24
+ [
25
+ ("Directory.Build.props", "<Project />"),
26
+ ("Directory.Build.targets", "<Project />"),
27
+ ("Directory.Packages.props", """
28
+ <Project>
29
+ <PropertyGroup>
30
+ <ManagePackageVersionsCentrally>false</ManagePackageVersionsCentrally>
31
+ </PropertyGroup>
32
+ </Project>
33
+ """),
34
+ ("src/project.csproj", """
35
+ <Project Sdk="Microsoft.NET.Sdk">
36
+ <PropertyGroup>
37
+ <TargetFramework>net8.0</TargetFramework>
38
+ </PropertyGroup>
39
+ <ItemGroup>
40
+ <PackageReference Include="Some.Package" Version="1.0.0" />
41
+ </ItemGroup>
42
+ </Project>
43
+ """)
44
+ ],
45
+ job: new Job()
46
+ {
47
+ Source = new()
48
+ {
49
+ Provider = "github",
50
+ Repo = "test",
51
+ Directory = "src",
52
+ }
53
+ },
54
+ expectedUrls:
55
+ [
56
+ "POST /update_jobs/TEST-ID/create_dependency_submission",
57
+ "PATCH /update_jobs/TEST-ID/mark_as_processed",
58
+ ]
59
+ );
60
+ }
61
+
62
+ private static Task RunAsync(TestFile[] files, Job job, string[] expectedUrls, MockNuGetPackage[]? packages = null, string? repoContentsPath = null, int expectedExitCode = 0)
63
+ => EntryPointTestHelper.RunAsync("graph", files, job, expectedUrls, packages, repoContentsPath, expectedExitCode);
64
+ }
65
+ }
@@ -143,65 +143,7 @@ public partial class EntryPointTests
143
143
  );
144
144
  }
145
145
 
146
- private static async Task RunAsync(TestFile[] files, Job job, string[] expectedUrls, MockNuGetPackage[]? packages = null, string? repoContentsPath = null, int expectedExitCode = 0)
147
- {
148
- using var tempDirectory = new TemporaryDirectory();
149
-
150
- // write test files
151
- foreach (var testFile in files)
152
- {
153
- var fullPath = Path.Join(tempDirectory.DirectoryPath, testFile.Path);
154
- var directory = Path.GetDirectoryName(fullPath)!;
155
- Directory.CreateDirectory(directory);
156
- await File.WriteAllTextAsync(fullPath, testFile.Content);
157
- }
158
-
159
- // write job file
160
- var jobPath = Path.Combine(tempDirectory.DirectoryPath, "job.json");
161
- await File.WriteAllTextAsync(jobPath, JsonSerializer.Serialize(new { Job = job }, RunWorker.SerializerOptions));
162
-
163
- // save packages
164
- await UpdateWorkerTestBase.MockNuGetPackagesInDirectory(packages, tempDirectory.DirectoryPath);
165
-
166
- var actualUrls = new List<string>();
167
- using var http = TestHttpServer.CreateTestStringServer((method, url) =>
168
- {
169
- actualUrls.Add($"{method} {new Uri(url).PathAndQuery}");
170
- return (200, "ok");
171
- });
172
- var args = new List<string>()
173
- {
174
- "run",
175
- "--job-path",
176
- jobPath,
177
- "--repo-contents-path",
178
- repoContentsPath ?? tempDirectory.DirectoryPath,
179
- "--api-url",
180
- http.BaseUrl,
181
- "--job-id",
182
- "TEST-ID",
183
- "--base-commit-sha",
184
- "BASE-COMMIT-SHA"
185
- };
186
-
187
- var output = new StringBuilder();
188
- // redirect stdout
189
- var originalOut = Console.Out;
190
- Console.SetOut(new StringWriter(output));
191
- int result = -1;
192
- try
193
- {
194
- result = await Program.Main(args.ToArray());
195
- }
196
- catch
197
- {
198
- // restore stdout
199
- Console.SetOut(originalOut);
200
- throw;
201
- }
202
-
203
- Assert.True(result == expectedExitCode, $"Expected exit code {expectedExitCode} but got {result}.\nSTDOUT:\n" + output.ToString());
204
- Assert.Equal(expectedUrls, actualUrls);
205
- }
146
+ private static Task RunAsync(TestFile[] files, Job job, string[] expectedUrls, MockNuGetPackage[]? packages = null, string? repoContentsPath = null, int expectedExitCode = 0)
147
+ => EntryPointTestHelper.RunAsync("run", files, job, expectedUrls, packages, repoContentsPath, expectedExitCode);
206
148
  }
207
149
  }
@@ -172,15 +172,13 @@ internal static class CompatibilityChecker
172
172
 
173
173
  foreach (var source in sources)
174
174
  {
175
- var sourceRepository = Repository.Factory.GetCoreV3(source);
176
- var feed = await sourceRepository.GetResourceAsync<FindPackageByIdResource>();
177
- if (feed is null)
178
- {
179
- throw new NotSupportedException($"Failed to get FindPackageByIdResource for {source.SourceUri}");
180
- }
181
-
175
+ FindPackageByIdResource feed;
182
176
  try
183
177
  {
178
+ var sourceRepository = Repository.Factory.GetCoreV3(source);
179
+ feed = await sourceRepository.GetResourceAsync<FindPackageByIdResource>()
180
+ ?? throw new NotSupportedException($"Failed to get FindPackageByIdResource for {source.SourceUri}");
181
+
184
182
  // a non-compliant v2 API returning 404 can cause this to throw
185
183
  var exists = await feed.DoesPackageExistAsync(
186
184
  package.Id,
@@ -16,6 +16,8 @@ using NuGetUpdater.Core.Run.ApiModel;
16
16
  using NuGetUpdater.Core.Updater;
17
17
  using NuGetUpdater.Core.Utilities;
18
18
 
19
+ using static NuGetUpdater.Core.Utilities.GitSubmoduleParser;
20
+
19
21
  namespace NuGetUpdater.Core.Discover;
20
22
 
21
23
  public partial class DiscoveryWorker : IDiscoveryWorker
@@ -110,6 +112,33 @@ public partial class DiscoveryWorker : IDiscoveryWorker
110
112
  var repoRoot = new DirectoryInfo(repoRootPath);
111
113
  projectResults = [.. projectResults.Where(p => PathHelper.IsFileUnderDirectory(repoRoot, new FileInfo(Path.Join(workspacePath, p.FilePath))))];
112
114
 
115
+ // filter out projects that are in submodules
116
+ var submodulePaths = GetSubmodulePaths(repoRootPath);
117
+ if (submodulePaths.Length > 0)
118
+ {
119
+ projectResults = FilterProjectsInSubmodules(projectResults, initialWorkspacePath, submodulePaths);
120
+
121
+ if (dotNetToolsJsonDiscovery is not null)
122
+ {
123
+ var fullRelativePath = PathHelper.JoinPath(initialWorkspacePath, dotNetToolsJsonDiscovery.FilePath).NormalizePathToUnix();
124
+ if (IsPathInSubmodule(fullRelativePath, submodulePaths))
125
+ {
126
+ _logger.Info($" Excluding file [{dotNetToolsJsonDiscovery.FilePath}] because it is in a submodule.");
127
+ dotNetToolsJsonDiscovery = null;
128
+ }
129
+ }
130
+
131
+ if (globalJsonDiscovery is not null)
132
+ {
133
+ var fullRelativePath = PathHelper.JoinPath(initialWorkspacePath, globalJsonDiscovery.FilePath).NormalizePathToUnix();
134
+ if (IsPathInSubmodule(fullRelativePath, submodulePaths))
135
+ {
136
+ _logger.Info($" Excluding file [{globalJsonDiscovery.FilePath}] because it is in a submodule.");
137
+ globalJsonDiscovery = null;
138
+ }
139
+ }
140
+ }
141
+
113
142
  // if any projectResults are not successful, return a failed result
114
143
  if (projectResults.Any(p => p.IsSuccess == false))
115
144
  {
@@ -175,7 +204,7 @@ public partial class DiscoveryWorker : IDiscoveryWorker
175
204
  ImmutableArray<string> projects;
176
205
  try
177
206
  {
178
- projects = await ExpandEntryPointsIntoProjectsAsync(entryPoints, _experimentsManager, _logger);
207
+ projects = await ExpandEntryPointsIntoProjectsAsync(entryPoints, _experimentsManager, _logger, repoRootPath);
179
208
  }
180
209
  catch (InvalidProjectFileException e)
181
210
  {
@@ -223,7 +252,7 @@ public partial class DiscoveryWorker : IDiscoveryWorker
223
252
  .ToImmutableArray();
224
253
  }
225
254
 
226
- internal async static Task<ImmutableArray<string>> ExpandEntryPointsIntoProjectsAsync(IEnumerable<string> entryPoints, ExperimentsManager experimentsManager, ILogger logger)
255
+ internal async static Task<ImmutableArray<string>> ExpandEntryPointsIntoProjectsAsync(IEnumerable<string> entryPoints, ExperimentsManager experimentsManager, ILogger logger, string repoRootPath)
227
256
  {
228
257
  HashSet<string> expandedProjects = new(PathComparer.Instance);
229
258
  HashSet<string> seenProjects = new(PathComparer.Instance);
@@ -247,7 +276,16 @@ public partial class DiscoveryWorker : IDiscoveryWorker
247
276
  else if (extension == ".slnx")
248
277
  {
249
278
  logger.Info($" Expanding solution: {candidateEntryPoint}:");
250
- SolutionModel solution = await SolutionSerializers.SlnXml.OpenAsync(candidateEntryPoint, CancellationToken.None);
279
+ SolutionModel solution;
280
+ try
281
+ {
282
+ solution = await SolutionSerializers.SlnXml.OpenAsync(candidateEntryPoint, CancellationToken.None);
283
+ }
284
+ catch (SolutionException ex)
285
+ {
286
+ throw new UnparseableFileException(ex.Message, candidateEntryPoint);
287
+ }
288
+
251
289
  string solutionPath = Path.GetDirectoryName(candidateEntryPoint) ?? string.Empty;
252
290
 
253
291
  foreach (SolutionProjectModel project in solution.SolutionProjects)
@@ -284,6 +322,24 @@ public partial class DiscoveryWorker : IDiscoveryWorker
284
322
  }
285
323
 
286
324
  var result = expandedProjects.OrderBy(p => p).ToImmutableArray();
325
+
326
+ // pre-filter projects that are in submodules to avoid unnecessary restore operations
327
+ var submodulePaths = GetSubmodulePaths(repoRootPath);
328
+ if (submodulePaths.Length > 0)
329
+ {
330
+ result = [.. result.Where(p =>
331
+ {
332
+ var relativePath = Path.GetRelativePath(repoRootPath, p).NormalizePathToUnix();
333
+ if (IsPathInSubmodule(relativePath, submodulePaths))
334
+ {
335
+ logger.Info($" Excluding project [{relativePath}] because it is in a submodule.");
336
+ return false;
337
+ }
338
+
339
+ return true;
340
+ })];
341
+ }
342
+
287
343
  return result;
288
344
  }
289
345
 
@@ -344,13 +400,20 @@ public partial class DiscoveryWorker : IDiscoveryWorker
344
400
  try
345
401
  {
346
402
  // get all packages.config results first
347
- var expandedProjects = await ExpandEntryPointsIntoProjectsAsync(normalizedProjectPaths, _experimentsManager, _logger);
403
+ var expandedProjects = await ExpandEntryPointsIntoProjectsAsync(normalizedProjectPaths, _experimentsManager, _logger, repoRootPath);
348
404
  foreach (var expandedProject in expandedProjects)
349
405
  {
350
406
  var packagesConfigResult = await PackagesConfigDiscovery.Discover(repoRootPath, workspacePath, expandedProject, _logger);
351
407
  if (packagesConfigResult is not null)
352
408
  {
353
409
  var relativeProjectPath = Path.GetRelativePath(workspacePath, expandedProject).NormalizePathToUnix();
410
+ var dependencyGraph = packagesConfigResult.Dependencies
411
+ .Where(d => !string.IsNullOrEmpty(d.Version))
412
+ .OrderBy(d => d.Name, StringComparer.OrdinalIgnoreCase)
413
+ .ToImmutableDictionary(
414
+ d => $"{d.Name}/{d.Version}",
415
+ _ => ImmutableArray<string>.Empty,
416
+ StringComparer.OrdinalIgnoreCase);
354
417
  results[relativeProjectPath] = new ProjectDiscoveryResult()
355
418
  {
356
419
  FilePath = relativeProjectPath,
@@ -358,6 +421,7 @@ public partial class DiscoveryWorker : IDiscoveryWorker
358
421
  TargetFrameworks = packagesConfigResult.TargetFrameworks,
359
422
  ImportedFiles = [], // no imported files resolved for packages.config scenarios
360
423
  AdditionalFiles = packagesConfigResult.AdditionalFiles,
424
+ DependencyGraph = dependencyGraph,
361
425
  };
362
426
  }
363
427
  }
@@ -472,10 +536,57 @@ public partial class DiscoveryWorker : IDiscoveryWorker
472
536
  AdditionalFiles = mergedAdditionalFiles,
473
537
  PackageManagementKind = (PackageManagementKind)Math.Max((int)result1.PackageManagementKind, (int)result2.PackageManagementKind),
474
538
  PackageManagementSpecialFileRelativePath = result1.PackageManagementSpecialFileRelativePath ?? result2.PackageManagementSpecialFileRelativePath,
539
+ HasNoWarnNU1701 = result1.HasNoWarnNU1701 || result2.HasNoWarnNU1701,
540
+ DependencyGraph = MergeDependencyGraphs(result1.DependencyGraph, result2.DependencyGraph),
475
541
  };
476
542
  return mergedResult;
477
543
  }
478
544
 
545
+ internal ImmutableArray<ProjectDiscoveryResult> FilterProjectsInSubmodules(
546
+ ImmutableArray<ProjectDiscoveryResult> projectResults,
547
+ string workspacePath,
548
+ ImmutableArray<string> submodulePaths)
549
+ {
550
+ var filtered = new List<ProjectDiscoveryResult>();
551
+ foreach (var project in projectResults)
552
+ {
553
+ var fullRelativePath = PathHelper.JoinPath(workspacePath, project.FilePath).NormalizePathToUnix();
554
+ if (IsPathInSubmodule(fullRelativePath, submodulePaths))
555
+ {
556
+ _logger.Info($" Excluding project [{project.FilePath}] because it is in a submodule.");
557
+ }
558
+ else
559
+ {
560
+ filtered.Add(project);
561
+ }
562
+ }
563
+
564
+ return [.. filtered];
565
+ }
566
+
567
+ private static ImmutableDictionary<string, ImmutableArray<string>> MergeDependencyGraphs(
568
+ ImmutableDictionary<string, ImmutableArray<string>> graph1,
569
+ ImmutableDictionary<string, ImmutableArray<string>> graph2)
570
+ {
571
+ var merged = graph1.ToDictionary(kvp => kvp.Key, kvp => kvp.Value, StringComparer.OrdinalIgnoreCase);
572
+ foreach (var kvp in graph2)
573
+ {
574
+ if (merged.TryGetValue(kvp.Key, out var existing))
575
+ {
576
+ merged[kvp.Key] = existing
577
+ .Union(kvp.Value, StringComparer.OrdinalIgnoreCase)
578
+ .OrderBy(n => n, StringComparer.OrdinalIgnoreCase)
579
+ .ToImmutableArray();
580
+ }
581
+ else
582
+ {
583
+ merged[kvp.Key] = kvp.Value;
584
+ }
585
+ }
586
+
587
+ return merged.ToImmutableDictionary(StringComparer.OrdinalIgnoreCase);
588
+ }
589
+
479
590
  internal static async Task WriteResultsAsync(string repoRootPath, string outputPath, WorkspaceDiscoveryResult result)
480
591
  {
481
592
  var resultPath = Path.IsPathRooted(outputPath)
@@ -17,4 +17,8 @@ public record ProjectDiscoveryResult : IDiscoveryResultWithDependencies
17
17
  public required ImmutableArray<string> AdditionalFiles { get; init; }
18
18
  public required ImmutableArray<Dependency> Dependencies { get; init; }
19
19
  public bool HasNoWarnNU1701 { get; init; } = false;
20
+ /// <summary>
21
+ /// Maps each package (keyed as "Name/Version") to its direct dependency package names, as extracted from project.assets.json.
22
+ /// </summary>
23
+ public ImmutableDictionary<string, ImmutableArray<string>> DependencyGraph { get; init; } = ImmutableDictionary<string, ImmutableArray<string>>.Empty;
20
24
  }