dependabot-nuget 0.287.0 → 0.288.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/helpers/lib/NuGetUpdater/.gitignore +1 -0
- data/helpers/lib/NuGetUpdater/Directory.Build.targets +17 -0
- data/helpers/lib/NuGetUpdater/Directory.Packages.props +10 -3
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Cli/Commands/DiscoverCommand.cs +7 -3
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Cli/Commands/RunCommand.cs +1 -1
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Cli.Test/EntryPointTests.Discover.cs +72 -51
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Analyze/AnalyzeWorker.cs +1 -1
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/DependencyDiscovery.props +7 -0
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/DependencyDiscovery.targets +10 -0
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Discover/DiscoveryWorker.cs +36 -19
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Discover/PackagesConfigDiscovery.cs +6 -2
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Discover/PackagesConfigDiscoveryResult.cs +2 -1
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Discover/ProjectDiscoveryResult.cs +5 -0
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Discover/SdkProjectDiscovery.cs +386 -12
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Discover/WorkspaceDiscoveryResult.cs +1 -1
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/ExperimentsManager.cs +11 -0
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/NuGetUpdater.Core.csproj +7 -2
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/ApiModel/Job.cs +23 -0
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/RunWorker.cs +0 -4
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/TargetFrameworkReporter.targets +13 -0
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/LockFileUpdater.cs +1 -0
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/PackageReferenceUpdater.cs +2 -0
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Utilities/CollectionExtensions.cs +17 -0
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Utilities/MSBuildHelper.cs +57 -4
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Utilities/PathComparer.cs +31 -0
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Utilities/PathHelper.cs +30 -2
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Analyze/AnalyzeWorkerTests.cs +50 -0
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Discover/DiscoveryWorkerTestBase.cs +74 -38
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Discover/DiscoveryWorkerTests.PackagesConfig.cs +50 -4
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Discover/DiscoveryWorkerTests.Proj.cs +5 -5
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Discover/DiscoveryWorkerTests.Project.cs +728 -253
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Discover/DiscoveryWorkerTests.cs +322 -78
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Discover/ExpectedDiscoveryResults.cs +2 -6
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Discover/SdkProjectDiscoveryTests.cs +472 -0
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/MockNuGetPackage.cs +46 -1
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/NuGetUpdater.Core.Test.csproj +0 -1
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Run/RunWorkerTests.cs +1 -1
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Run/SerializationTests.cs +33 -0
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/TemporaryDirectory.cs +3 -2
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTestBase.cs +4 -2
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Utilities/PathHelperTests.cs +3 -2
- data/lib/dependabot/nuget/file_parser.rb +7 -1
- data/lib/dependabot/nuget/native_discovery/native_discovery_json_reader.rb +0 -3
- data/lib/dependabot/nuget/native_discovery/native_workspace_discovery.rb +7 -10
- data/lib/dependabot/nuget/native_helpers.rb +13 -4
- metadata +12 -8
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Discover/DirectoryPackagesPropsDiscovery.cs +0 -69
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Discover/DirectoryPackagesPropsDiscoveryResult.cs +0 -11
- data/lib/dependabot/nuget/native_discovery/native_directory_packages_props_discovery.rb +0 -44
@@ -1,12 +1,376 @@
|
|
1
1
|
using System.Collections.Immutable;
|
2
|
+
using System.Reflection;
|
3
|
+
using System.Xml.Linq;
|
4
|
+
using System.Xml.XPath;
|
5
|
+
|
6
|
+
using Microsoft.Build.Logging.StructuredLogger;
|
2
7
|
|
3
8
|
using NuGet.Versioning;
|
4
9
|
|
10
|
+
using NuGetUpdater.Core.Utilities;
|
11
|
+
|
12
|
+
using LoggerProperty = Microsoft.Build.Logging.StructuredLogger.Property;
|
13
|
+
|
5
14
|
namespace NuGetUpdater.Core.Discover;
|
6
15
|
|
7
16
|
internal static class SdkProjectDiscovery
|
8
17
|
{
|
9
|
-
|
18
|
+
private static readonly HashSet<string> TopLevelPackageItemNames = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
|
19
|
+
{
|
20
|
+
"PackageReference"
|
21
|
+
};
|
22
|
+
|
23
|
+
// the items listed below represent collection names that NuGet will resolve a package into, along with the metadata value names to get the package name and version
|
24
|
+
private static readonly Dictionary<string, (string NameMetadata, string VersionMetadata)> ResolvedPackageItemNames = new Dictionary<string, (string, string)>(StringComparer.OrdinalIgnoreCase)
|
25
|
+
{
|
26
|
+
["NativeCopyLocalItems"] = ("NuGetPackageId", "NuGetPackageVersion"),
|
27
|
+
["ResourceCopyLocalItems"] = ("NuGetPackageId", "NuGetPackageVersion"),
|
28
|
+
["RuntimeCopyLocalItems"] = ("NuGetPackageId", "NuGetPackageVersion"),
|
29
|
+
["ResolvedAnalyzers"] = ("NuGetPackageId", "NuGetPackageVersion"),
|
30
|
+
["_PackageDependenciesDesignTime"] = ("Name", "Version"),
|
31
|
+
};
|
32
|
+
|
33
|
+
// these packages are resolved during restore, but aren't really updatable and shouldn't be reported as dependencies
|
34
|
+
private static readonly HashSet<string> NonReportedPackgeNames = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
|
35
|
+
{
|
36
|
+
"NETStandard.Library"
|
37
|
+
};
|
38
|
+
|
39
|
+
public static async Task<ImmutableArray<ProjectDiscoveryResult>> DiscoverAsync(string repoRootPath, string workspacePath, string startingProjectPath, ExperimentsManager experimentsManager, ILogger logger)
|
40
|
+
{
|
41
|
+
if (experimentsManager.UseDirectDiscovery)
|
42
|
+
{
|
43
|
+
return await DiscoverWithBinLogAsync(repoRootPath, workspacePath, startingProjectPath, logger);
|
44
|
+
}
|
45
|
+
else
|
46
|
+
{
|
47
|
+
return await DiscoverWithTempProjectAsync(repoRootPath, workspacePath, startingProjectPath, logger);
|
48
|
+
}
|
49
|
+
}
|
50
|
+
|
51
|
+
public static async Task<ImmutableArray<ProjectDiscoveryResult>> DiscoverWithBinLogAsync(string repoRootPath, string workspacePath, string startingProjectPath, ILogger logger)
|
52
|
+
{
|
53
|
+
// N.b., there are many paths used in this function. The MSBuild binary log always reports fully qualified paths, so that's what will be used
|
54
|
+
// throughout until the very end when the appropriate kind of relative path is returned.
|
55
|
+
|
56
|
+
// step through the binlog one item at a time
|
57
|
+
var startingProjectDirectory = Path.GetDirectoryName(startingProjectPath)!;
|
58
|
+
|
59
|
+
// the following collection feature heavily; the shape is described as follows
|
60
|
+
|
61
|
+
Dictionary<string, Dictionary<string, Dictionary<string, string>>> packagesPerProject = new(PathComparer.Instance);
|
62
|
+
// projectPath tfm packageName, packageVersion
|
63
|
+
|
64
|
+
Dictionary<string, HashSet<string>> topLevelPackagesPerProject = new(PathComparer.Instance);
|
65
|
+
// projectPath, packageNames
|
66
|
+
|
67
|
+
Dictionary<string, Dictionary<string, string>> resolvedProperties = new(PathComparer.Instance);
|
68
|
+
// projectPath propertyName, propertyValue
|
69
|
+
|
70
|
+
Dictionary<string, HashSet<string>> importedFiles = new(PathComparer.Instance);
|
71
|
+
// projectPath, importedFiles
|
72
|
+
|
73
|
+
Dictionary<string, HashSet<string>> referencedProjects = new(PathComparer.Instance);
|
74
|
+
// projectPath, referencedProjects
|
75
|
+
|
76
|
+
var tfms = await MSBuildHelper.GetTargetFrameworkValuesFromProject(repoRootPath, startingProjectPath, logger);
|
77
|
+
foreach (var tfm in tfms)
|
78
|
+
{
|
79
|
+
// create a binlog
|
80
|
+
var binLogPath = Path.Combine(Path.GetTempPath(), $"msbuild_{Guid.NewGuid():d}.binlog");
|
81
|
+
try
|
82
|
+
{
|
83
|
+
// TODO: once the updater image has all relevant SDKs installed, we won't have to sideline global.json anymore
|
84
|
+
var (exitCode, stdOut, stdErr) = await MSBuildHelper.SidelineGlobalJsonAsync(startingProjectDirectory, repoRootPath, async () =>
|
85
|
+
{
|
86
|
+
// the built-in target `GenerateBuildDependencyFile` forces resolution of all NuGet packages, but doesn't invoke a full build
|
87
|
+
var dependencyDiscoveryTargetsPath = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location)!, "DependencyDiscovery.targets");
|
88
|
+
var args = new string[]
|
89
|
+
{
|
90
|
+
"build",
|
91
|
+
startingProjectPath,
|
92
|
+
"/t:_DiscoverDependencies",
|
93
|
+
$"/p:TargetFramework={tfm}",
|
94
|
+
$"/p:CustomAfterMicrosoftCommonCrossTargetingTargets={dependencyDiscoveryTargetsPath};CustomAfterMicrosoftCommonTargets={dependencyDiscoveryTargetsPath}",
|
95
|
+
$"/bl:{binLogPath}"
|
96
|
+
};
|
97
|
+
var (exitCode, stdOut, stdErr) = await ProcessEx.RunAsync("dotnet", args, workingDirectory: startingProjectDirectory);
|
98
|
+
return (exitCode, stdOut, stdErr);
|
99
|
+
}, logger, retainMSBuildSdks: true);
|
100
|
+
MSBuildHelper.ThrowOnUnauthenticatedFeed(stdOut);
|
101
|
+
if (stdOut.Contains("""error MSB4057: The target "GenerateBuildDependencyFile" does not exist in the project."""))
|
102
|
+
{
|
103
|
+
// this can happen if it's a non-SDK-style project; totally normal, not worth examining the binlog
|
104
|
+
return [];
|
105
|
+
}
|
106
|
+
if (exitCode != 0)
|
107
|
+
{
|
108
|
+
// log error, but still try to resolve what we can
|
109
|
+
logger.Log($" Error determining dependencies from `{startingProjectPath}`:\nSTDOUT:\n{stdOut}\nSTDERR:\n{stdErr}");
|
110
|
+
}
|
111
|
+
|
112
|
+
var buildRoot = BinaryLog.ReadBuild(binLogPath);
|
113
|
+
buildRoot.VisitAllChildren<BaseNode>(node =>
|
114
|
+
{
|
115
|
+
switch (node)
|
116
|
+
{
|
117
|
+
case LoggerProperty property:
|
118
|
+
{
|
119
|
+
var projectEvaluation = property.GetNearestParent<ProjectEvaluation>();
|
120
|
+
if (projectEvaluation is not null)
|
121
|
+
{
|
122
|
+
var properties = resolvedProperties.GetOrAdd(projectEvaluation.ProjectFile, () => new(StringComparer.OrdinalIgnoreCase));
|
123
|
+
properties[property.Name] = property.Value;
|
124
|
+
}
|
125
|
+
}
|
126
|
+
break;
|
127
|
+
case Import import:
|
128
|
+
{
|
129
|
+
var projectEvaluation = GetNearestProjectEvaluation(import);
|
130
|
+
if (projectEvaluation is not null)
|
131
|
+
{
|
132
|
+
// props and targets files might have been imported from these, but they're not to be considered as dependency files
|
133
|
+
var forbiddenDirectories = new[]
|
134
|
+
{
|
135
|
+
GetPropertyValueFromProjectEvaluation(projectEvaluation, "BaseIntermediateOutputPath"), // e.g., "obj/"
|
136
|
+
GetPropertyValueFromProjectEvaluation(projectEvaluation, "BaseOutputPath"), // e.g., "bin/"
|
137
|
+
}
|
138
|
+
.Where(p => !string.IsNullOrEmpty(p))
|
139
|
+
.Select(p => Path.Combine(Path.GetDirectoryName(projectEvaluation.ProjectFile)!, p!))
|
140
|
+
.Select(p => p.NormalizePathToUnix())
|
141
|
+
.Select(p => new DirectoryInfo(p))
|
142
|
+
.ToArray();
|
143
|
+
if (PathHelper.IsFileUnderDirectory(new DirectoryInfo(repoRootPath), new FileInfo(import.ImportedProjectFilePath)))
|
144
|
+
{
|
145
|
+
if (!forbiddenDirectories.Any(f => PathHelper.IsFileUnderDirectory(f, new FileInfo(import.ImportedProjectFilePath))))
|
146
|
+
{
|
147
|
+
var imports = importedFiles.GetOrAdd(projectEvaluation.ProjectFile, () => new(PathComparer.Instance));
|
148
|
+
imports.Add(import.ImportedProjectFilePath);
|
149
|
+
}
|
150
|
+
}
|
151
|
+
}
|
152
|
+
}
|
153
|
+
break;
|
154
|
+
case NamedNode namedNode when namedNode is AddItem or RemoveItem:
|
155
|
+
ProcessResolvedPackageReference(namedNode, packagesPerProject, topLevelPackagesPerProject);
|
156
|
+
|
157
|
+
// maintain list of project references
|
158
|
+
if (namedNode is AddItem addItem && addItem.Name == "ProjectReference")
|
159
|
+
{
|
160
|
+
var projectEvaluation = GetNearestProjectEvaluation(addItem);
|
161
|
+
if (projectEvaluation is not null)
|
162
|
+
{
|
163
|
+
foreach (var referencedProject in addItem.Children.OfType<Item>())
|
164
|
+
{
|
165
|
+
var referencedProjectPaths = referencedProjects.GetOrAdd(projectEvaluation.ProjectFile, () => new(PathComparer.Instance));
|
166
|
+
var referencedProjectPath = new FileInfo(Path.Combine(Path.GetDirectoryName(projectEvaluation.ProjectFile)!, referencedProject.Name)).FullName;
|
167
|
+
referencedProjectPaths.Add(referencedProjectPath);
|
168
|
+
}
|
169
|
+
}
|
170
|
+
}
|
171
|
+
break;
|
172
|
+
}
|
173
|
+
}, takeChildrenSnapshot: true);
|
174
|
+
}
|
175
|
+
catch (HttpRequestException)
|
176
|
+
{
|
177
|
+
// likely an unauthenticated feed; this needs to be sent further up the chain
|
178
|
+
throw;
|
179
|
+
}
|
180
|
+
finally
|
181
|
+
{
|
182
|
+
try
|
183
|
+
{
|
184
|
+
File.Delete(binLogPath);
|
185
|
+
}
|
186
|
+
catch
|
187
|
+
{
|
188
|
+
}
|
189
|
+
}
|
190
|
+
}
|
191
|
+
|
192
|
+
// and done
|
193
|
+
var projectDiscoveryResults = packagesPerProject.Keys.OrderBy(p => p).Select(projectPath =>
|
194
|
+
{
|
195
|
+
// gather some project-level information
|
196
|
+
var packagesByTfm = packagesPerProject[projectPath];
|
197
|
+
var projectFullDirectory = Path.GetDirectoryName(projectPath)!;
|
198
|
+
var doc = XDocument.Load(projectPath);
|
199
|
+
var localPropertyDefinitionElements = doc.Root!.XPathSelectElements("/Project/PropertyGroup/*");
|
200
|
+
var projectPropertyNames = localPropertyDefinitionElements.Select(e => e.Name.LocalName).ToHashSet(StringComparer.OrdinalIgnoreCase);
|
201
|
+
var projectRelativePath = Path.GetRelativePath(workspacePath, projectPath);
|
202
|
+
var topLevelPackageNames = topLevelPackagesPerProject.GetOrAdd(projectPath, () => new(StringComparer.OrdinalIgnoreCase));
|
203
|
+
|
204
|
+
// create dependencies
|
205
|
+
var tfms = packagesByTfm.Keys.OrderBy(tfm => tfm).ToImmutableArray();
|
206
|
+
var dependencies = tfms.SelectMany(tfm =>
|
207
|
+
{
|
208
|
+
return packagesByTfm[tfm].Keys.OrderBy(p => p).Select(packageName =>
|
209
|
+
{
|
210
|
+
var packageVersion = packagesByTfm[tfm][packageName]!;
|
211
|
+
var isTopLevel = topLevelPackageNames.Contains(packageName);
|
212
|
+
var dependencyType = isTopLevel ? DependencyType.PackageReference : DependencyType.Unknown;
|
213
|
+
return new Dependency(packageName, packageVersion, dependencyType, TargetFrameworks: [tfm], IsDirect: isTopLevel, IsTransitive: !isTopLevel);
|
214
|
+
});
|
215
|
+
}).ToImmutableArray();
|
216
|
+
|
217
|
+
// others
|
218
|
+
var properties = resolvedProperties[projectPath]
|
219
|
+
.Where(pkvp => projectPropertyNames.Contains(pkvp.Key))
|
220
|
+
.Select(pkvp => new Property(pkvp.Key, pkvp.Value, Path.GetRelativePath(repoRootPath, projectPath).NormalizePathToUnix()))
|
221
|
+
.OrderBy(p => p.Name)
|
222
|
+
.ToImmutableArray();
|
223
|
+
var referenced = referencedProjects.GetOrAdd(projectPath, () => new(StringComparer.OrdinalIgnoreCase))
|
224
|
+
.Select(p => Path.GetRelativePath(projectFullDirectory, p).NormalizePathToUnix())
|
225
|
+
.OrderBy(p => p)
|
226
|
+
.ToImmutableArray();
|
227
|
+
var imported = importedFiles.GetOrAdd(projectPath, () => new(StringComparer.OrdinalIgnoreCase))
|
228
|
+
.Select(p => Path.GetRelativePath(projectFullDirectory, p))
|
229
|
+
.Select(p => p.NormalizePathToUnix())
|
230
|
+
.OrderBy(p => p)
|
231
|
+
.ToImmutableArray();
|
232
|
+
|
233
|
+
return new ProjectDiscoveryResult()
|
234
|
+
{
|
235
|
+
FilePath = projectRelativePath,
|
236
|
+
Dependencies = dependencies,
|
237
|
+
TargetFrameworks = tfms,
|
238
|
+
Properties = properties,
|
239
|
+
ReferencedProjectPaths = referenced,
|
240
|
+
ImportedFiles = imported,
|
241
|
+
};
|
242
|
+
}).ToImmutableArray();
|
243
|
+
return projectDiscoveryResults;
|
244
|
+
}
|
245
|
+
|
246
|
+
private static void ProcessResolvedPackageReference(
|
247
|
+
NamedNode node,
|
248
|
+
Dictionary<string, Dictionary<string, Dictionary<string, string>>> packagesPerProject, // projectPath -> tfm -> (packageName, packageVersion)
|
249
|
+
Dictionary<string, HashSet<string>> topLevelPackagesPerProject
|
250
|
+
)
|
251
|
+
{
|
252
|
+
var doRemoveOperation = node is RemoveItem;
|
253
|
+
var doAddOperation = node is AddItem;
|
254
|
+
|
255
|
+
if (TopLevelPackageItemNames.Contains(node.Name))
|
256
|
+
{
|
257
|
+
foreach (var child in node.Children.OfType<Item>())
|
258
|
+
{
|
259
|
+
var projectEvaluation = GetNearestProjectEvaluation(node);
|
260
|
+
if (projectEvaluation is not null)
|
261
|
+
{
|
262
|
+
var packageName = child.Name;
|
263
|
+
if (NonReportedPackgeNames.Contains(packageName))
|
264
|
+
{
|
265
|
+
continue;
|
266
|
+
}
|
267
|
+
|
268
|
+
var topLevelPackages = topLevelPackagesPerProject.GetOrAdd(projectEvaluation.ProjectFile, () => new(StringComparer.OrdinalIgnoreCase));
|
269
|
+
|
270
|
+
if (doRemoveOperation)
|
271
|
+
{
|
272
|
+
topLevelPackages.Remove(packageName);
|
273
|
+
}
|
274
|
+
|
275
|
+
if (doAddOperation)
|
276
|
+
{
|
277
|
+
topLevelPackages.Add(packageName);
|
278
|
+
}
|
279
|
+
}
|
280
|
+
}
|
281
|
+
}
|
282
|
+
else if (ResolvedPackageItemNames.TryGetValue(node.Name, out var metadataNames))
|
283
|
+
{
|
284
|
+
var nameMetadata = metadataNames.NameMetadata;
|
285
|
+
var versionMetadata = metadataNames.VersionMetadata;
|
286
|
+
var projectEvaluation = GetNearestProjectEvaluation(node);
|
287
|
+
if (projectEvaluation is not null)
|
288
|
+
{
|
289
|
+
// without a tfm we can't do anything meaningful with the package reference
|
290
|
+
var tfm = GetPropertyValueFromProjectEvaluation(projectEvaluation, "TargetFramework");
|
291
|
+
if (tfm is not null)
|
292
|
+
{
|
293
|
+
foreach (var child in node.Children.OfType<Item>())
|
294
|
+
{
|
295
|
+
var packageName = GetChildMetadataValue(child, nameMetadata);
|
296
|
+
var packageVersion = GetChildMetadataValue(child, versionMetadata);
|
297
|
+
if (packageName is not null && packageVersion is not null)
|
298
|
+
{
|
299
|
+
if (NonReportedPackgeNames.Contains(packageName))
|
300
|
+
{
|
301
|
+
continue;
|
302
|
+
}
|
303
|
+
|
304
|
+
var tfmsPerProject = packagesPerProject.GetOrAdd(projectEvaluation.ProjectFile, () => new(StringComparer.OrdinalIgnoreCase));
|
305
|
+
var packagesPerTfm = tfmsPerProject.GetOrAdd(tfm, () => new(StringComparer.OrdinalIgnoreCase));
|
306
|
+
|
307
|
+
if (doRemoveOperation)
|
308
|
+
{
|
309
|
+
packagesPerTfm.Remove(packageName);
|
310
|
+
}
|
311
|
+
|
312
|
+
if (doAddOperation)
|
313
|
+
{
|
314
|
+
packagesPerTfm[packageName] = packageVersion;
|
315
|
+
}
|
316
|
+
}
|
317
|
+
}
|
318
|
+
}
|
319
|
+
}
|
320
|
+
}
|
321
|
+
}
|
322
|
+
|
323
|
+
private static string? GetChildMetadataValue(TreeNode node, string metadataItemName)
|
324
|
+
{
|
325
|
+
var metadata = node.Children.OfType<Metadata>();
|
326
|
+
var metadataValue = metadata.FirstOrDefault(m => m.Name.Equals(metadataItemName, StringComparison.OrdinalIgnoreCase))?.Value;
|
327
|
+
return metadataValue;
|
328
|
+
}
|
329
|
+
|
330
|
+
private static ProjectEvaluation? GetNearestProjectEvaluation(BaseNode node)
|
331
|
+
{
|
332
|
+
// we need to find the containing project evaluation
|
333
|
+
// if this is a <PackageReference>, one of the parents is it
|
334
|
+
// otherwise, we need to find the parent `Project` and the corresponding evaluation from the build
|
335
|
+
var projectEvaluation = node.GetNearestParent<ProjectEvaluation>();
|
336
|
+
if (projectEvaluation is null)
|
337
|
+
{
|
338
|
+
var project = node.GetNearestParent<Project>();
|
339
|
+
if (project is null)
|
340
|
+
{
|
341
|
+
return null;
|
342
|
+
}
|
343
|
+
|
344
|
+
var build = project.GetNearestParent<Build>();
|
345
|
+
if (build is null)
|
346
|
+
{
|
347
|
+
return null;
|
348
|
+
}
|
349
|
+
|
350
|
+
projectEvaluation = build.FindEvaluation(project.EvaluationId);
|
351
|
+
}
|
352
|
+
|
353
|
+
return projectEvaluation;
|
354
|
+
}
|
355
|
+
|
356
|
+
private static string? GetPropertyValueFromProjectEvaluation(ProjectEvaluation projectEvaluation, string propertyName)
|
357
|
+
{
|
358
|
+
var propertiesFolder = projectEvaluation.Children.OfType<Folder>().FirstOrDefault(f => f.Name == "Properties");
|
359
|
+
if (propertiesFolder is null)
|
360
|
+
{
|
361
|
+
return null;
|
362
|
+
}
|
363
|
+
|
364
|
+
var property = propertiesFolder.Children.OfType<LoggerProperty>().FirstOrDefault(p => p.Name.Equals(propertyName, StringComparison.OrdinalIgnoreCase));
|
365
|
+
if (property is null)
|
366
|
+
{
|
367
|
+
return null;
|
368
|
+
}
|
369
|
+
|
370
|
+
return property.Value;
|
371
|
+
}
|
372
|
+
|
373
|
+
public static async Task<ImmutableArray<ProjectDiscoveryResult>> DiscoverWithTempProjectAsync(string repoRootPath, string workspacePath, string projectPath, ILogger logger)
|
10
374
|
{
|
11
375
|
// Determine which targets and props files contribute to the build.
|
12
376
|
var (buildFiles, projectTargetFrameworks) = await MSBuildHelper.LoadBuildFilesAndTargetFrameworksAsync(repoRootPath, projectPath);
|
@@ -30,6 +394,10 @@ internal static class SdkProjectDiscovery
|
|
30
394
|
// The build file dependencies have the correct DependencyType and the TopLevelDependencies have the evaluated version.
|
31
395
|
// Combine them to have the set of dependencies that are directly referenced from the build file.
|
32
396
|
var fileDependencies = BuildFile.GetDependencies(buildFile).ToImmutableArray();
|
397
|
+
|
398
|
+
// this is new-ish behavior; don't ever report this dependency because there's no meaningful way to update it
|
399
|
+
fileDependencies = fileDependencies.Where(d => !d.Name.Equals("Microsoft.NET.Sdk", StringComparison.OrdinalIgnoreCase)).ToImmutableArray();
|
400
|
+
|
33
401
|
var fileDependencyLookup = fileDependencies
|
34
402
|
.ToLookup(d => d.Name, StringComparer.OrdinalIgnoreCase);
|
35
403
|
var sdkDependencies = fileDependencies
|
@@ -58,7 +426,7 @@ internal static class SdkProjectDiscovery
|
|
58
426
|
.OrderBy(p => p.Name)
|
59
427
|
.ToImmutableArray();
|
60
428
|
var referencedProjectPaths = MSBuildHelper.GetProjectPathsFromProject(projectPath)
|
61
|
-
.Select(path => Path.GetRelativePath(workspacePath, path))
|
429
|
+
.Select(path => Path.GetRelativePath(workspacePath, path).NormalizePathToUnix())
|
62
430
|
.OrderBy(p => p)
|
63
431
|
.ToImmutableArray();
|
64
432
|
|
@@ -72,22 +440,28 @@ internal static class SdkProjectDiscovery
|
|
72
440
|
.OrderBy(d => d.Name)
|
73
441
|
.ToImmutableArray();
|
74
442
|
|
443
|
+
// for the temporary project, these directories correspond to $(OutputPath) and $(IntermediateOutputPath) and files from
|
444
|
+
// these directories should not be reported
|
445
|
+
var intermediateDirectories = new string[]
|
446
|
+
{
|
447
|
+
Path.Join(Path.GetDirectoryName(buildFile.Path), "bin"),
|
448
|
+
Path.Join(Path.GetDirectoryName(buildFile.Path), "obj"),
|
449
|
+
};
|
450
|
+
var projectDirectory = Path.GetDirectoryName(buildFile.Path)!;
|
75
451
|
results.Add(new()
|
76
452
|
{
|
77
|
-
FilePath = Path.GetRelativePath(workspacePath, buildFile.Path),
|
453
|
+
FilePath = Path.GetRelativePath(workspacePath, buildFile.Path).NormalizePathToUnix(),
|
78
454
|
Properties = properties,
|
79
455
|
TargetFrameworks = tfms,
|
80
456
|
ReferencedProjectPaths = referencedProjectPaths,
|
81
457
|
Dependencies = allDependencies,
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
Dependencies = directDependencies.Concat(sdkDependencies)
|
90
|
-
.OrderBy(d => d.Name)
|
458
|
+
ImportedFiles = buildFiles.Where(b =>
|
459
|
+
{
|
460
|
+
var fileType = b.GetFileType();
|
461
|
+
return fileType == ProjectBuildFileType.Props || fileType == ProjectBuildFileType.Targets;
|
462
|
+
})
|
463
|
+
.Where(b => !intermediateDirectories.Any(i => PathHelper.IsFileUnderDirectory(new DirectoryInfo(i), new FileInfo(b.Path))))
|
464
|
+
.Select(b => Path.GetRelativePath(projectDirectory, b.Path).NormalizePathToUnix())
|
91
465
|
.ToImmutableArray(),
|
92
466
|
});
|
93
467
|
}
|
@@ -7,7 +7,7 @@ public sealed record WorkspaceDiscoveryResult : NativeResult
|
|
7
7
|
public required string Path { get; init; }
|
8
8
|
public bool IsSuccess { get; init; } = true;
|
9
9
|
public ImmutableArray<ProjectDiscoveryResult> Projects { get; init; }
|
10
|
-
public
|
10
|
+
public ImmutableArray<string> ImportedFiles { get; init; } = [];
|
11
11
|
public GlobalJsonDiscoveryResult? GlobalJson { get; init; }
|
12
12
|
public DotNetToolsJsonDiscoveryResult? DotNetToolsJson { get; init; }
|
13
13
|
}
|
@@ -7,12 +7,23 @@ namespace NuGetUpdater.Core;
|
|
7
7
|
public record ExperimentsManager
|
8
8
|
{
|
9
9
|
public bool UseLegacyDependencySolver { get; init; } = false;
|
10
|
+
public bool UseDirectDiscovery { get; init; } = false;
|
11
|
+
|
12
|
+
public Dictionary<string, object> ToDictionary()
|
13
|
+
{
|
14
|
+
return new()
|
15
|
+
{
|
16
|
+
["nuget_legacy_dependency_solver"] = UseLegacyDependencySolver,
|
17
|
+
["nuget_use_direct_discovery"] = UseDirectDiscovery,
|
18
|
+
};
|
19
|
+
}
|
10
20
|
|
11
21
|
public static ExperimentsManager GetExperimentsManager(Dictionary<string, object>? experiments)
|
12
22
|
{
|
13
23
|
return new ExperimentsManager()
|
14
24
|
{
|
15
25
|
UseLegacyDependencySolver = IsEnabled(experiments, "nuget_legacy_dependency_solver"),
|
26
|
+
UseDirectDiscovery = IsEnabled(experiments, "nuget_use_direct_discovery"),
|
16
27
|
};
|
17
28
|
}
|
18
29
|
|
@@ -7,6 +7,12 @@
|
|
7
7
|
<GeneratePathProperty>true</GeneratePathProperty>
|
8
8
|
</PropertyGroup>
|
9
9
|
|
10
|
+
<ItemGroup>
|
11
|
+
<None Include="DependencyDiscovery.props" CopyToOutputDirectory="PreserveNewest" />
|
12
|
+
<None Include="DependencyDiscovery.targets" CopyToOutputDirectory="PreserveNewest" />
|
13
|
+
<None Include="TargetFrameworkReporter.targets" CopyToOutputDirectory="PreserveNewest" />
|
14
|
+
</ItemGroup>
|
15
|
+
|
10
16
|
<ItemGroup>
|
11
17
|
<ProjectReference Include="..\NuGetProjects\NuGet.CommandLine\NuGet.CommandLine.csproj" />
|
12
18
|
</ItemGroup>
|
@@ -15,8 +21,7 @@
|
|
15
21
|
<PackageReference Include="GuiLabs.Language.Xml" />
|
16
22
|
<PackageReference Include="DiffPlex" />
|
17
23
|
<PackageReference Include="Microsoft.Build.Locator" />
|
18
|
-
<PackageReference Include="
|
19
|
-
<PackageReference Include="Microsoft.Build.Utilities.Core" ExcludeAssets="Runtime" PrivateAssets="All" />
|
24
|
+
<PackageReference Include="MSBuild.StructuredLogger" />
|
20
25
|
<PackageReference Include="NuGet.Core" Aliases="CoreV2" />
|
21
26
|
</ItemGroup>
|
22
27
|
|
@@ -1,9 +1,14 @@
|
|
1
|
+
using System.Text.Json;
|
2
|
+
using System.Text.Json.Serialization;
|
3
|
+
|
1
4
|
namespace NuGetUpdater.Core.Run.ApiModel;
|
2
5
|
|
3
6
|
public sealed record Job
|
4
7
|
{
|
5
8
|
public string PackageManager { get; init; } = "nuget";
|
6
9
|
public AllowedUpdate[]? AllowedUpdates { get; init; } = null;
|
10
|
+
|
11
|
+
[JsonConverter(typeof(NullAsBoolConverter))]
|
7
12
|
public bool Debug { get; init; } = false;
|
8
13
|
public object[]? DependencyGroups { get; init; } = null;
|
9
14
|
public object[]? Dependencies { get; init; } = null;
|
@@ -47,3 +52,21 @@ public sealed record Job
|
|
47
52
|
}
|
48
53
|
}
|
49
54
|
}
|
55
|
+
|
56
|
+
public class NullAsBoolConverter : JsonConverter<bool>
|
57
|
+
{
|
58
|
+
public override bool Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
59
|
+
{
|
60
|
+
if (reader.TokenType == JsonTokenType.Null)
|
61
|
+
{
|
62
|
+
return false;
|
63
|
+
}
|
64
|
+
|
65
|
+
return reader.GetBoolean();
|
66
|
+
}
|
67
|
+
|
68
|
+
public override void Write(Utf8JsonWriter writer, bool value, JsonSerializerOptions options)
|
69
|
+
{
|
70
|
+
writer.WriteBooleanValue(value);
|
71
|
+
}
|
72
|
+
}
|
@@ -335,10 +335,6 @@ public class RunWorker
|
|
335
335
|
{
|
336
336
|
auxiliaryFiles.Add(GetFullRepoPath(discoveryResult.DotNetToolsJson.FilePath));
|
337
337
|
}
|
338
|
-
if (discoveryResult.DirectoryPackagesProps is not null)
|
339
|
-
{
|
340
|
-
auxiliaryFiles.Add(GetFullRepoPath(discoveryResult.DirectoryPackagesProps.FilePath));
|
341
|
-
}
|
342
338
|
|
343
339
|
foreach (var project in discoveryResult.Projects)
|
344
340
|
{
|
@@ -0,0 +1,13 @@
|
|
1
|
+
<Project>
|
2
|
+
<Import Project="DependencyDiscovery.props" />
|
3
|
+
|
4
|
+
<Target Name="ReportTargetFramework">
|
5
|
+
<!-- this property is for non-SDK projects, commonly with `packages.config -->
|
6
|
+
<!-- e.g., returns ".NETFramework,Version=v4.5" -->
|
7
|
+
<Message Text="ProjectData::TargetFrameworkVersion=$(TargetFrameworkIdentifier),Version=$(TargetFrameworkVersion)" Importance="High" Condition="'$(TargetFrameworkIdentifier)' != '' AND '' != '$(TargetFrameworkVersion)'" />
|
8
|
+
|
9
|
+
<!-- these properties are for SDK projects -->
|
10
|
+
<Message Text="ProjectData::TargetFramework=$(TargetFramework)" Importance="High" Condition="'$(TargetFramework)' != ''" />
|
11
|
+
<Message Text="ProjectData::TargetFrameworks=$(TargetFrameworks)" Importance="High" Condition="'$(TargetFrameworks)' != ''" />
|
12
|
+
</Target>
|
13
|
+
</Project>
|
@@ -311,6 +311,8 @@ internal static class PackageReferenceUpdater
|
|
311
311
|
{
|
312
312
|
logger.Log($" Transitive dependency [{dependencyName}/{newDependencyVersion}] was not added.\nSTDOUT:\n{stdout}\nSTDERR:\n{stderr}");
|
313
313
|
}
|
314
|
+
|
315
|
+
return exitCode;
|
314
316
|
}, logger, retainMSBuildSdks: true);
|
315
317
|
}
|
316
318
|
|
@@ -0,0 +1,17 @@
|
|
1
|
+
using Newtonsoft.Json.Linq;
|
2
|
+
|
3
|
+
namespace NuGetUpdater.Core.Utilities;
|
4
|
+
|
5
|
+
public static class CollectionExtensions
|
6
|
+
{
|
7
|
+
public static TValue GetOrAdd<TKey, TValue>(this Dictionary<TKey, TValue> dictionary, TKey key, Func<TValue> valueFactory) where TKey : notnull
|
8
|
+
{
|
9
|
+
if (!dictionary.TryGetValue(key, out var value))
|
10
|
+
{
|
11
|
+
value = valueFactory();
|
12
|
+
dictionary[key] = value;
|
13
|
+
}
|
14
|
+
|
15
|
+
return value;
|
16
|
+
}
|
17
|
+
}
|