Skip to content

Commit

Permalink
use MSBuild-evaluated property values for TFM
Browse files Browse the repository at this point in the history
  • Loading branch information
brettfo authored Apr 12, 2024
1 parent 37d86e4 commit 1842419
Show file tree
Hide file tree
Showing 9 changed files with 184 additions and 167 deletions.
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

0 comments on commit 1842419

Please sign in to comment.