Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

use MSBuild-evaluated property values for TFM #9417

Merged
merged 1 commit into from
Apr 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Collections.Immutable;
using System.Diagnostics.CodeAnalysis;
using System.Text.Json;

using NuGetUpdater.Core.Discover;
Expand Down Expand Up @@ -29,7 +30,7 @@ protected static async Task TestDiscoveryAsync(
protected static void ValidateWorkspaceResult(ExpectedWorkspaceDiscoveryResult expectedResult, WorkspaceDiscoveryResult actualResult)
{
Assert.NotNull(actualResult);
Assert.Equal(expectedResult.FilePath, actualResult.FilePath);
Assert.Equal(expectedResult.FilePath.NormalizePathToUnix(), actualResult.FilePath.NormalizePathToUnix());
ValidateDirectoryPackagesProps(expectedResult.DirectoryPackagesProps, actualResult.DirectoryPackagesProps);
ValidateResultWithDependencies(expectedResult.GlobalJson, actualResult.GlobalJson);
ValidateResultWithDependencies(expectedResult.DotNetToolsJson, actualResult.DotNetToolsJson);
Expand All @@ -50,7 +51,7 @@ void ValidateResultWithDependencies(ExpectedDependencyDiscoveryResult? expectedR
Assert.NotNull(actualResult);
}

Assert.Equal(expectedResult.FilePath, actualResult.FilePath);
Assert.Equal(expectedResult.FilePath.NormalizePathToUnix(), actualResult.FilePath.NormalizePathToUnix());
ValidateDependencies(expectedResult.Dependencies, actualResult.Dependencies);
Assert.Equal(expectedResult.ExpectedDependencyCount ?? expectedResult.Dependencies.Length, actualResult.Dependencies.Length);
}
Expand All @@ -64,12 +65,12 @@ void ValidateProjectResults(ImmutableArray<ExpectedSdkProjectDiscoveryResult> ex

foreach (var expectedProject in expectedProjects)
{
var actualProject = actualProjects.Single(p => p.FilePath == expectedProject.FilePath);
var actualProject = actualProjects.Single(p => p.FilePath.NormalizePathToUnix() == expectedProject.FilePath.NormalizePathToUnix());

Assert.Equal(expectedProject.FilePath, actualProject.FilePath);
AssertEx.Equal(expectedProject.Properties, actualProject.Properties);
Assert.Equal(expectedProject.FilePath.NormalizePathToUnix(), actualProject.FilePath.NormalizePathToUnix());
AssertEx.Equal(expectedProject.Properties, actualProject.Properties, PropertyComparer.Instance);
AssertEx.Equal(expectedProject.TargetFrameworks, actualProject.TargetFrameworks);
AssertEx.Equal(expectedProject.ReferencedProjectPaths, actualProject.ReferencedProjectPaths);
AssertEx.Equal(expectedProject.ReferencedProjectPaths.Select(PathHelper.NormalizePathToUnix), actualProject.ReferencedProjectPaths.Select(PathHelper.NormalizePathToUnix));
ValidateDependencies(expectedProject.Dependencies, actualProject.Dependencies);
Assert.Equal(expectedProject.ExpectedDependencyCount ?? expectedProject.Dependencies.Length, actualProject.Dependencies.Length);
}
Expand Down Expand Up @@ -114,4 +115,21 @@ protected static async Task<WorkspaceDiscoveryResult> RunDiscoveryAsync(TestFile
var resultJson = await File.ReadAllTextAsync(resultPath);
return JsonSerializer.Deserialize<WorkspaceDiscoveryResult>(resultJson, DiscoveryWorker.SerializerOptions)!;
}

internal class PropertyComparer : IEqualityComparer<Property>
{
public static PropertyComparer Instance { get; } = new();

public bool Equals(Property? x, Property? y)
{
return x?.Name == y?.Name &&
x?.Value == y?.Value &&
x?.SourceFilePath.NormalizePathToUnix() == y?.SourceFilePath.NormalizePathToUnix();
}

public int GetHashCode([DisallowNull] Property obj)
{
throw new NotImplementedException();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,14 @@ await TestDiscoveryAsync(
<package id="NuGet.Core" version="2.11.1" targetFramework="net46" />
<package id="NuGet.Server" version="2.11.2" targetFramework="net46" />
<package id="RouteMagic" version="1.3" targetFramework="net46" />
<package id="WebActivatorEx" version="2.1.0" targetFramework="net46"></package>
<package id="WebActivatorEx" version="2.1.0" targetFramework="net46" />
</packages>
"""),
("myproj.csproj", """
<Project>
<PropertyGroup>
<TargetFramework>net46</TargetFramework>
</PropertyGroup>
</Project>
""")
],
Expand All @@ -40,16 +43,21 @@ await TestDiscoveryAsync(
new()
{
FilePath = "myproj.csproj",
Properties = [
new("TargetFramework", "net46", "myproj.csproj"),
],
TargetFrameworks = ["net46"],
Dependencies = [
new("Microsoft.CodeDom.Providers.DotNetCompilerPlatform", "1.0.0", DependencyType.PackagesConfig, TargetFrameworks: []),
new("Microsoft.Net.Compilers", "1.0.1", DependencyType.PackagesConfig, TargetFrameworks: []),
new("Microsoft.Web.Infrastructure", "1.0.0.0", DependencyType.PackagesConfig, TargetFrameworks: []),
new("Microsoft.Web.Xdt", "2.1.1", DependencyType.PackagesConfig, TargetFrameworks: []),
new("Newtonsoft.Json", "8.0.3", DependencyType.PackagesConfig, TargetFrameworks: []),
new("NuGet.Core", "2.11.1", DependencyType.PackagesConfig, TargetFrameworks: []),
new("NuGet.Server", "2.11.2", DependencyType.PackagesConfig, TargetFrameworks: []),
new("RouteMagic", "1.3", DependencyType.PackagesConfig, TargetFrameworks: []),
new("WebActivatorEx", "2.1.0", DependencyType.PackagesConfig, TargetFrameworks: []),
new("Microsoft.NETFramework.ReferenceAssemblies", "1.0.3", DependencyType.Unknown, TargetFrameworks: ["net46"], IsTransitive: true),
new("Microsoft.CodeDom.Providers.DotNetCompilerPlatform", "1.0.0", DependencyType.PackagesConfig, TargetFrameworks: ["net46"]),
new("Microsoft.Net.Compilers", "1.0.1", DependencyType.PackagesConfig, TargetFrameworks: ["net46"]),
new("Microsoft.Web.Infrastructure", "1.0.0.0", DependencyType.PackagesConfig, TargetFrameworks: ["net46"]),
new("Microsoft.Web.Xdt", "2.1.1", DependencyType.PackagesConfig, TargetFrameworks: ["net46"]),
new("Newtonsoft.Json", "8.0.3", DependencyType.PackagesConfig, TargetFrameworks: ["net46"]),
new("NuGet.Core", "2.11.1", DependencyType.PackagesConfig, TargetFrameworks: ["net46"]),
new("NuGet.Server", "2.11.2", DependencyType.PackagesConfig, TargetFrameworks: ["net46"]),
new("RouteMagic", "1.3", DependencyType.PackagesConfig, TargetFrameworks: ["net46"]),
new("WebActivatorEx", "2.1.0", DependencyType.PackagesConfig, TargetFrameworks: ["net46"]),
],
}
],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -376,5 +376,30 @@ await TestDiscoveryAsync(
],
});
}

[Fact]

public async Task NoDependenciesReturnedIfNoTargetFrameworkCanBeResolved()
{
await TestDiscoveryAsync(
workspacePath: "",
files: [
("myproj.csproj", """
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>$(SomeCommonTfmThatCannotBeResolved)</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Some.Package" Version="1.2.3" />
</ItemGroup>
</Project>
""")
],
expectedResult: new()
{
FilePath = "",
Projects = []
});
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -110,15 +110,16 @@ public void ProjectPathsCanBeParsedFromSolutionFiles(string solutionContent, str
[InlineData("<Project><PropertyGroup><TargetFrameworks>netstandard2.0</TargetFrameworks></PropertyGroup></Project>", "netstandard2.0", null)]
[InlineData("<Project><PropertyGroup><TargetFrameworks> ; netstandard2.0 ; </TargetFrameworks></PropertyGroup></Project>", "netstandard2.0", null)]
[InlineData("<Project><PropertyGroup><TargetFrameworks>netstandard2.0 ; netstandard2.1 ; </TargetFrameworks></PropertyGroup></Project>", "netstandard2.0", "netstandard2.1")]
public void TfmsCanBeDeterminedFromProjectContents(string projectContents, string? expectedTfm1, string? expectedTfm2)
[InlineData("<Project><PropertyGroup><TargetFramework>netstandard2.0</TargetFramework><TargetFrameworkVersion Condition='False'>v4.7.2</TargetFrameworkVersion></PropertyGroup></Project>", "netstandard2.0", null)]
[InlineData("<Project><PropertyGroup><TargetFramework>$(PropertyThatCannotBeResolved)</TargetFramework></PropertyGroup></Project>", null, null)]
public async Task TfmsCanBeDeterminedFromProjectContents(string projectContents, string? expectedTfm1, string? expectedTfm2)
{
var projectPath = Path.GetTempFileName();
try
{
File.WriteAllText(projectPath, projectContents);
var expectedTfms = new[] { expectedTfm1, expectedTfm2 }.Where(tfm => tfm is not null).ToArray();
var buildFile = ProjectBuildFile.Open(Path.GetDirectoryName(projectPath)!, projectPath);
var actualTfms = MSBuildHelper.GetTargetFrameworkMonikers(ImmutableArray.Create(buildFile));
var (_buildFiles, actualTfms) = await MSBuildHelper.LoadBuildFilesAndTargetFrameworksAsync(Path.GetDirectoryName(projectPath)!, projectPath);
AssertEx.Equal(expectedTfms, actualTfms);
}
finally
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,7 @@ public async Task BuildFileEnumerationWithGlobalJsonWithComments()

private static async Task<string[]> LoadBuildFilesFromTemp(TemporaryDirectory temporaryDirectory, string relativeProjectPath)
{
var buildFiles = await MSBuildHelper.LoadBuildFilesAsync(temporaryDirectory.DirectoryPath, $"{temporaryDirectory.DirectoryPath}/{relativeProjectPath}");
var (buildFiles, _tfms) = await MSBuildHelper.LoadBuildFilesAndTargetFrameworksAsync(temporaryDirectory.DirectoryPath, $"{temporaryDirectory.DirectoryPath}/{relativeProjectPath}");
var buildFilePaths = buildFiles.Select(f => f.RelativePath.NormalizePathToUnix()).ToArray();
return buildFilePaths;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,86 +9,87 @@ internal static class SdkProjectDiscovery
public static async Task<ImmutableArray<ProjectDiscoveryResult>> DiscoverAsync(string repoRootPath, string workspacePath, string projectPath, Logger logger)
{
// Determine which targets and props files contribute to the build.
var buildFiles = await MSBuildHelper.LoadBuildFilesAsync(repoRootPath, projectPath, includeSdkPropsAndTargets: true);
var (buildFiles, projectTargetFrameworks) = await MSBuildHelper.LoadBuildFilesAndTargetFrameworksAsync(repoRootPath, projectPath);
var tfms = projectTargetFrameworks.Order().ToImmutableArray();

// Get all the dependencies which are directly referenced from the project file or indirectly referenced from
// targets and props files.
var topLevelDependencies = MSBuildHelper.GetTopLevelPackageDependencyInfos(buildFiles);

var results = ImmutableArray.CreateBuilder<ProjectDiscoveryResult>();
foreach (var buildFile in buildFiles)
if (tfms.Length > 0)
{
// Only include build files that exist beneath the RepoRootPath.
if (buildFile.IsOutsideBasePath)
foreach (var buildFile in buildFiles)
{
continue;
}

// The build file dependencies have the correct DependencyType and the TopLevelDependencies have the evaluated version.
// Combine them to have the set of dependencies that are directly referenced from the build file.
var fileDependencies = BuildFile.GetDependencies(buildFile)
.ToDictionary(d => d.Name, StringComparer.OrdinalIgnoreCase);
var sdkDependencies = fileDependencies.Values
.Where(d => d.Type == DependencyType.MSBuildSdk)
.ToImmutableArray();
var indirectDependencies = topLevelDependencies
.Where(d => !fileDependencies.ContainsKey(d.Name))
.ToImmutableArray();
var directDependencies = topLevelDependencies
.Where(d => fileDependencies.ContainsKey(d.Name))
.Select(d =>
// Only include build files that exist beneath the RepoRootPath.
if (buildFile.IsOutsideBasePath)
{
var dependency = fileDependencies[d.Name];
return d with
{
Type = dependency.Type,
IsDirect = true
};
}).ToImmutableArray();

if (buildFile.GetFileType() == ProjectBuildFileType.Project)
{
// Collect information that is specific to the project file.
var tfms = MSBuildHelper.GetTargetFrameworkMonikers(buildFiles)
.OrderBy(tfm => tfm)
.ToImmutableArray();
var properties = MSBuildHelper.GetProperties(buildFiles).Values
.Where(p => !p.SourceFilePath.StartsWith(".."))
.OrderBy(p => p.Name)
.ToImmutableArray();
var referencedProjectPaths = MSBuildHelper.GetProjectPathsFromProject(projectPath)
.Select(path => Path.GetRelativePath(workspacePath, path))
.OrderBy(p => p)
.ToImmutableArray();
continue;
}

// Get the complete set of dependencies including transitive dependencies.
var dependencies = indirectDependencies.Concat(directDependencies).ToImmutableArray();
dependencies = dependencies
.Select(d => d with { TargetFrameworks = tfms })
// The build file dependencies have the correct DependencyType and the TopLevelDependencies have the evaluated version.
// Combine them to have the set of dependencies that are directly referenced from the build file.
var fileDependencies = BuildFile.GetDependencies(buildFile)
.ToDictionary(d => d.Name, StringComparer.OrdinalIgnoreCase);
var sdkDependencies = fileDependencies.Values
.Where(d => d.Type == DependencyType.MSBuildSdk)
.ToImmutableArray();
var transitiveDependencies = await GetTransitiveDependencies(repoRootPath, projectPath, tfms, dependencies, logger);
ImmutableArray<Dependency> allDependencies = dependencies.Concat(transitiveDependencies).Concat(sdkDependencies)
.OrderBy(d => d.Name)
var indirectDependencies = topLevelDependencies
.Where(d => !fileDependencies.ContainsKey(d.Name))
.ToImmutableArray();
var directDependencies = topLevelDependencies
.Where(d => fileDependencies.ContainsKey(d.Name))
.Select(d =>
{
var dependency = fileDependencies[d.Name];
return d with
{
Type = dependency.Type,
IsDirect = true
};
}).ToImmutableArray();

results.Add(new()
if (buildFile.GetFileType() == ProjectBuildFileType.Project)
{
FilePath = Path.GetRelativePath(workspacePath, buildFile.Path),
Properties = properties,
TargetFrameworks = tfms,
ReferencedProjectPaths = referencedProjectPaths,
Dependencies = allDependencies,
});
}
else
{
results.Add(new()
{
FilePath = Path.GetRelativePath(workspacePath, buildFile.Path),
Dependencies = directDependencies.Concat(sdkDependencies)
// Collect information that is specific to the project file.
var properties = MSBuildHelper.GetProperties(buildFiles).Values
.Where(p => !p.SourceFilePath.StartsWith(".."))
.OrderBy(p => p.Name)
.ToImmutableArray();
var referencedProjectPaths = MSBuildHelper.GetProjectPathsFromProject(projectPath)
.Select(path => Path.GetRelativePath(workspacePath, path))
.OrderBy(p => p)
.ToImmutableArray();

// Get the complete set of dependencies including transitive dependencies.
var dependencies = indirectDependencies.Concat(directDependencies).ToImmutableArray();
dependencies = dependencies
.Select(d => d with { TargetFrameworks = tfms })
.ToImmutableArray();
var transitiveDependencies = await GetTransitiveDependencies(repoRootPath, projectPath, tfms, dependencies, logger);
ImmutableArray<Dependency> allDependencies = dependencies.Concat(transitiveDependencies).Concat(sdkDependencies)
.OrderBy(d => d.Name)
.ToImmutableArray(),
});
.ToImmutableArray();

results.Add(new()
{
FilePath = Path.GetRelativePath(workspacePath, buildFile.Path),
Properties = properties,
TargetFrameworks = tfms,
ReferencedProjectPaths = referencedProjectPaths,
Dependencies = allDependencies,
});
}
else
{
results.Add(new()
{
FilePath = Path.GetRelativePath(workspacePath, buildFile.Path),
Dependencies = directDependencies.Concat(sdkDependencies)
.OrderBy(d => d.Name)
.ToImmutableArray(),
});
}
}
}

Expand Down
Loading
Loading