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

[5.0.2xx] Adding transitive project references based on target framework #15525

Closed
wants to merge 3 commits into from
Closed
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
48 changes: 17 additions & 31 deletions src/Tasks/Microsoft.NET.Build.Tasks/LockFileExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
using System.Collections.Generic;
using System.Linq;
using Microsoft.Build.Framework;
using NuGet.Frameworks;
using NuGet.Packaging.Core;
using NuGet.ProjectModel;

Expand Down Expand Up @@ -111,30 +110,7 @@ public static LockFileTargetLibrary GetLibrary(this LockFileTarget lockFileTarge
.FirstOrDefault(e => e.Name.Equals(libraryName, StringComparison.OrdinalIgnoreCase));
}

private static readonly char[] DependencySeparators = new char[] { '<', '=', '>' };

public static Dictionary<string, string> GetProjectFileDependencies(this LockFile lockFile)
{
var projectDeps = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);

foreach (var group in lockFile.ProjectFileDependencyGroups)
{
foreach (var dep in group.Dependencies)
{
var parts = dep.Split(DependencySeparators, StringSplitOptions.RemoveEmptyEntries);
var packageName = parts[0].Trim();

if (!projectDeps.ContainsKey(packageName))
{
projectDeps.Add(packageName, parts.Length == 2 ? parts[1].Trim() : null);
}
}
}

return projectDeps;
}

public static HashSet<string> GetProjectFileDependencySet(this LockFile lockFile)
public static HashSet<string> GetProjectFileDependencySet(this LockFile lockFile, string frameworkAlias)
{
// Get package name from e.g. Microsoft.VSSDK.BuildTools >= 15.0.25604-Preview4
static string GetPackageNameFromDependency(string dependency)
Expand Down Expand Up @@ -165,16 +141,26 @@ static int IndexOfWhiteSpace(string s)

foreach (var group in lockFile.ProjectFileDependencyGroups)
{
foreach (string dependency in group.Dependencies)
var groupFrameworkAlias = GetFrameworkAliasForDependencyGroup(group);
if (string.IsNullOrEmpty(groupFrameworkAlias) || string.IsNullOrEmpty(frameworkAlias) || groupFrameworkAlias.Equals(frameworkAlias) ||
NuGetUtils.ParseFrameworkName(groupFrameworkAlias.Split('/').First()).DotNetFrameworkName.Equals(NuGetUtils.ParseFrameworkName(frameworkAlias).DotNetFrameworkName))
{
string packageName = GetPackageNameFromDependency(dependency);
set.Add(packageName);
foreach (string dependency in group.Dependencies)
{
string packageName = GetPackageNameFromDependency(dependency);
set.Add(packageName);
}
}
}

return set;
}

private static string GetFrameworkAliasForDependencyGroup(ProjectFileDependencyGroup group)
{
return group.FrameworkName;
}

public static HashSet<string> GetPlatformExclusionList(
this LockFileTarget lockFileTarget,
LockFileTargetLibrary platformLibrary,
Expand Down Expand Up @@ -254,7 +240,7 @@ public static IEnumerable<IGrouping<string, LockFileRuntimeTarget>> GetRuntimeTa

// A package is a TransitiveProjectReference if it is a project, is not directly referenced,
// and does not contain a placeholder compile time assembly
public static bool IsTransitiveProjectReference(this LockFileTargetLibrary library, LockFile lockFile, ref HashSet<string> directProjectDependencies)
public static bool IsTransitiveProjectReference(this LockFileTargetLibrary library, LockFile lockFile, ref HashSet<string> directProjectDependencies, string frameworkAlias)
{
if (!library.IsProject())
{
Expand All @@ -263,10 +249,10 @@ public static bool IsTransitiveProjectReference(this LockFileTargetLibrary libra

if (directProjectDependencies == null)
{
directProjectDependencies = lockFile.GetProjectFileDependencySet();
directProjectDependencies = lockFile.GetProjectFileDependencySet(frameworkAlias);
}

return !directProjectDependencies.Contains(library.Name)
return !directProjectDependencies.Contains(library.Name)
&& !library.CompileTimeAssemblies.Any(f => f.IsPlaceholderFile());
}

Expand Down
3 changes: 2 additions & 1 deletion src/Tasks/Microsoft.NET.Build.Tasks/ResolvePackageAssets.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1198,7 +1198,8 @@ private void WriteTransitiveProjectReferences()

foreach (var library in _runtimeTarget.Libraries)
{
if (!library.IsTransitiveProjectReference(_lockFile, ref directProjectDependencies))
if (!library.IsTransitiveProjectReference(_lockFile, ref directProjectDependencies,
_lockFile.GetLockFileTargetAlias(_lockFile.GetTargetAndReturnNullIfNotFound(_targetFramework, null))))
{
continue;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -163,14 +163,14 @@ protected override void ExecuteCore()
}
});

ReadProjectFileDependencies();
ReadProjectFileDependencies(string.IsNullOrEmpty(TargetFramework) || !_targetNameToAliasMap.ContainsKey(TargetFramework) ? null : _targetNameToAliasMap[TargetFramework]);
RaiseLockFileTargets();
GetPackageAndFileDefinitions();
}

private void ReadProjectFileDependencies()
private void ReadProjectFileDependencies(string frameworkAlias)
{
_projectFileDependencies = LockFile.GetProjectFileDependencySet();
_projectFileDependencies = LockFile.GetProjectFileDependencySet(frameworkAlias);
}

// get library and file definitions
Expand Down Expand Up @@ -299,9 +299,11 @@ private void GetPackageAndFileDependencies(LockFileTarget target)
var resolvedPackageVersions = target.Libraries
.ToDictionary(pkg => pkg.Name, pkg => pkg.Version.ToNormalizedString(), StringComparer.OrdinalIgnoreCase);

string frameworkAlias = _targetNameToAliasMap[target.Name];

var transitiveProjectRefs = new HashSet<string>(
target.Libraries
.Where(lib => lib.IsTransitiveProjectReference(LockFile, ref _projectFileDependencies))
.Where(lib => lib.IsTransitiveProjectReference(LockFile, ref _projectFileDependencies, frameworkAlias))
.Select(pkg => pkg.Name),
StringComparer.OrdinalIgnoreCase);

Expand All @@ -311,8 +313,6 @@ private void GetPackageAndFileDependencies(LockFileTarget target)

if (_projectFileDependencies.Contains(package.Name))
{
string frameworkAlias = _targetNameToAliasMap[target.Name];

TaskItem item = new TaskItem(packageId);
item.SetMetadata(MetadataKeys.ParentTarget, frameworkAlias); // Foreign Key
item.SetMetadata(MetadataKeys.ParentPackage, string.Empty); // Foreign Key
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -297,5 +297,59 @@ public void It_copies_content_transitively()

File.Exists(contentPath).Should().BeTrue();
}

[Fact]
public void It_conditionally_references_project_based_on_tfm()
{
var testProjectA = new TestProject()
{
Name = "ProjectA",
TargetFrameworks = "netstandard2.1"
};

var testProjectB = new TestProject()
{
Name = "ProjectB",
TargetFrameworks = "netstandard2.1"
};
testProjectB.ReferencedProjects.Add(testProjectA);

string source = @"using System;
class Program
{
static void Main(string[] args)
{
Console.WriteLine(ProjectA.ProjectAClass.Name);
}
}";
var testProjectC = new TestProject()
{
Name = "ProjectC",
IsExe = true,
TargetFrameworks = "netstandard2.1;netcoreapp3.1"
};
testProjectC.ReferencedProjects.Add(testProjectB);
testProjectC.SourceFiles.Add("Program.cs", source);

var testAsset = _testAssetsManager.CreateTestProject(testProjectC).WithProjectChanges((path, p) =>
{
if (Path.GetFileName(path).Equals("ProjectC.csproj"))
{
var ns = p.Root.Name.Namespace;
var itemGroup = new XElement(ns + "ItemGroup",
new XAttribute("Condition", @"'$(TargetFramework)' == 'netcoreapp3.1'"));
var projRef = new XElement(ns + "ProjectReference",
new XAttribute("Include", Path.Combine(path, "..", "..", testProjectA.Name, $"{testProjectA.Name}.csproj")));
itemGroup.Add(projRef);
p.Root.Add(itemGroup);
}
});

var buildCommand = new BuildCommand(testAsset);
buildCommand
.Execute()
.Should()
.Pass();
}
}
}