dependabot-nuget 0.378.0 → 0.379.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 +105 -3
  13. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Discover/ProjectDiscoveryResult.cs +4 -0
  14. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Discover/SdkProjectDiscovery.cs +71 -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: 6a30c97332d1fca64d5fc48d192b55802ef9000a40aa3eb283b165c347ebe64d
4
+ data.tar.gz: 63eaedf049ff81f75b1e4c0cdc2ea42601d0a01a3f68fab8b8577ecc8f2d4b8e
5
5
  SHA512:
6
- metadata.gz: ec48dd6709dec2c379ea172d0cbf11c90e0be5055af38ce46da976b5adb834224e6973c80a7dd989235826d8d8aaf1505f24ccd86c4ee628e6007c63ee80188c
7
- data.tar.gz: 2e7473ea553408e050f3686d380537c279859408b2db4fc5aa15cf9c5e81e2404551498d1f1547bf2e6713aac8548f3bb1c09c6c7bb3ee5f82ae4a8556742635
6
+ metadata.gz: 5522815d4c5abbe6cd19a15a62eddd9d81b25e15b4facbe4ca6ffd01246ece451ce6887f3289bbd30139877a565c5edc56eb20c187ae69e69d2970911ef0a614
7
+ data.tar.gz: 052c7aa235636a4eab5db8b7149ee81ee7cc576e8dff81049e3345ca4774ac68ad04f30b23c8d0bbbf29d5bdc9cd27b8b1f50bcdd3a27e59f8778f387bec0060
@@ -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);
@@ -284,6 +313,24 @@ public partial class DiscoveryWorker : IDiscoveryWorker
284
313
  }
285
314
 
286
315
  var result = expandedProjects.OrderBy(p => p).ToImmutableArray();
316
+
317
+ // pre-filter projects that are in submodules to avoid unnecessary restore operations
318
+ var submodulePaths = GetSubmodulePaths(repoRootPath);
319
+ if (submodulePaths.Length > 0)
320
+ {
321
+ result = [.. result.Where(p =>
322
+ {
323
+ var relativePath = Path.GetRelativePath(repoRootPath, p).NormalizePathToUnix();
324
+ if (IsPathInSubmodule(relativePath, submodulePaths))
325
+ {
326
+ logger.Info($" Excluding project [{relativePath}] because it is in a submodule.");
327
+ return false;
328
+ }
329
+
330
+ return true;
331
+ })];
332
+ }
333
+
287
334
  return result;
288
335
  }
289
336
 
@@ -344,13 +391,20 @@ public partial class DiscoveryWorker : IDiscoveryWorker
344
391
  try
345
392
  {
346
393
  // get all packages.config results first
347
- var expandedProjects = await ExpandEntryPointsIntoProjectsAsync(normalizedProjectPaths, _experimentsManager, _logger);
394
+ var expandedProjects = await ExpandEntryPointsIntoProjectsAsync(normalizedProjectPaths, _experimentsManager, _logger, repoRootPath);
348
395
  foreach (var expandedProject in expandedProjects)
349
396
  {
350
397
  var packagesConfigResult = await PackagesConfigDiscovery.Discover(repoRootPath, workspacePath, expandedProject, _logger);
351
398
  if (packagesConfigResult is not null)
352
399
  {
353
400
  var relativeProjectPath = Path.GetRelativePath(workspacePath, expandedProject).NormalizePathToUnix();
401
+ var dependencyGraph = packagesConfigResult.Dependencies
402
+ .Where(d => !string.IsNullOrEmpty(d.Version))
403
+ .OrderBy(d => d.Name, StringComparer.OrdinalIgnoreCase)
404
+ .ToImmutableDictionary(
405
+ d => $"{d.Name}/{d.Version}",
406
+ _ => ImmutableArray<string>.Empty,
407
+ StringComparer.OrdinalIgnoreCase);
354
408
  results[relativeProjectPath] = new ProjectDiscoveryResult()
355
409
  {
356
410
  FilePath = relativeProjectPath,
@@ -358,6 +412,7 @@ public partial class DiscoveryWorker : IDiscoveryWorker
358
412
  TargetFrameworks = packagesConfigResult.TargetFrameworks,
359
413
  ImportedFiles = [], // no imported files resolved for packages.config scenarios
360
414
  AdditionalFiles = packagesConfigResult.AdditionalFiles,
415
+ DependencyGraph = dependencyGraph,
361
416
  };
362
417
  }
363
418
  }
@@ -472,10 +527,57 @@ public partial class DiscoveryWorker : IDiscoveryWorker
472
527
  AdditionalFiles = mergedAdditionalFiles,
473
528
  PackageManagementKind = (PackageManagementKind)Math.Max((int)result1.PackageManagementKind, (int)result2.PackageManagementKind),
474
529
  PackageManagementSpecialFileRelativePath = result1.PackageManagementSpecialFileRelativePath ?? result2.PackageManagementSpecialFileRelativePath,
530
+ HasNoWarnNU1701 = result1.HasNoWarnNU1701 || result2.HasNoWarnNU1701,
531
+ DependencyGraph = MergeDependencyGraphs(result1.DependencyGraph, result2.DependencyGraph),
475
532
  };
476
533
  return mergedResult;
477
534
  }
478
535
 
536
+ internal ImmutableArray<ProjectDiscoveryResult> FilterProjectsInSubmodules(
537
+ ImmutableArray<ProjectDiscoveryResult> projectResults,
538
+ string workspacePath,
539
+ ImmutableArray<string> submodulePaths)
540
+ {
541
+ var filtered = new List<ProjectDiscoveryResult>();
542
+ foreach (var project in projectResults)
543
+ {
544
+ var fullRelativePath = PathHelper.JoinPath(workspacePath, project.FilePath).NormalizePathToUnix();
545
+ if (IsPathInSubmodule(fullRelativePath, submodulePaths))
546
+ {
547
+ _logger.Info($" Excluding project [{project.FilePath}] because it is in a submodule.");
548
+ }
549
+ else
550
+ {
551
+ filtered.Add(project);
552
+ }
553
+ }
554
+
555
+ return [.. filtered];
556
+ }
557
+
558
+ private static ImmutableDictionary<string, ImmutableArray<string>> MergeDependencyGraphs(
559
+ ImmutableDictionary<string, ImmutableArray<string>> graph1,
560
+ ImmutableDictionary<string, ImmutableArray<string>> graph2)
561
+ {
562
+ var merged = graph1.ToDictionary(kvp => kvp.Key, kvp => kvp.Value, StringComparer.OrdinalIgnoreCase);
563
+ foreach (var kvp in graph2)
564
+ {
565
+ if (merged.TryGetValue(kvp.Key, out var existing))
566
+ {
567
+ merged[kvp.Key] = existing
568
+ .Union(kvp.Value, StringComparer.OrdinalIgnoreCase)
569
+ .OrderBy(n => n, StringComparer.OrdinalIgnoreCase)
570
+ .ToImmutableArray();
571
+ }
572
+ else
573
+ {
574
+ merged[kvp.Key] = kvp.Value;
575
+ }
576
+ }
577
+
578
+ return merged.ToImmutableDictionary(StringComparer.OrdinalIgnoreCase);
579
+ }
580
+
479
581
  internal static async Task WriteResultsAsync(string repoRootPath, string outputPath, WorkspaceDiscoveryResult result)
480
582
  {
481
583
  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
  }