diff --git a/src/Tasks/Microsoft.NET.Build.Tasks/LockFileExtensions.cs b/src/Tasks/Microsoft.NET.Build.Tasks/LockFileExtensions.cs index 3452689a8c77..c31ec8904bb7 100644 --- a/src/Tasks/Microsoft.NET.Build.Tasks/LockFileExtensions.cs +++ b/src/Tasks/Microsoft.NET.Build.Tasks/LockFileExtensions.cs @@ -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; @@ -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 GetProjectFileDependencies(this LockFile lockFile) - { - var projectDeps = new Dictionary(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 GetProjectFileDependencySet(this LockFile lockFile) + public static HashSet GetProjectFileDependencySet(this LockFile lockFile, string frameworkAlias) { // Get package name from e.g. Microsoft.VSSDK.BuildTools >= 15.0.25604-Preview4 static string GetPackageNameFromDependency(string dependency) @@ -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 GetPlatformExclusionList( this LockFileTarget lockFileTarget, LockFileTargetLibrary platformLibrary, @@ -254,7 +240,7 @@ public static IEnumerable> 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 directProjectDependencies) + public static bool IsTransitiveProjectReference(this LockFileTargetLibrary library, LockFile lockFile, ref HashSet directProjectDependencies, string frameworkAlias) { if (!library.IsProject()) { @@ -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()); } diff --git a/src/Tasks/Microsoft.NET.Build.Tasks/ResolvePackageAssets.cs b/src/Tasks/Microsoft.NET.Build.Tasks/ResolvePackageAssets.cs index 840897e4bb54..e3a33a8fbc15 100644 --- a/src/Tasks/Microsoft.NET.Build.Tasks/ResolvePackageAssets.cs +++ b/src/Tasks/Microsoft.NET.Build.Tasks/ResolvePackageAssets.cs @@ -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; } diff --git a/src/Tasks/Microsoft.NET.Build.Tasks/ResolvePackageDependencies.cs b/src/Tasks/Microsoft.NET.Build.Tasks/ResolvePackageDependencies.cs index a3b6ebf1d32d..be6e7e20b3da 100644 --- a/src/Tasks/Microsoft.NET.Build.Tasks/ResolvePackageDependencies.cs +++ b/src/Tasks/Microsoft.NET.Build.Tasks/ResolvePackageDependencies.cs @@ -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 @@ -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( target.Libraries - .Where(lib => lib.IsTransitiveProjectReference(LockFile, ref _projectFileDependencies)) + .Where(lib => lib.IsTransitiveProjectReference(LockFile, ref _projectFileDependencies, frameworkAlias)) .Select(pkg => pkg.Name), StringComparer.OrdinalIgnoreCase); @@ -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 diff --git a/src/Tests/Microsoft.NET.Build.Tests/GivenThatWeWantToReferenceAProject.cs b/src/Tests/Microsoft.NET.Build.Tests/GivenThatWeWantToReferenceAProject.cs index 680274fa2372..df2ad535cd14 100644 --- a/src/Tests/Microsoft.NET.Build.Tests/GivenThatWeWantToReferenceAProject.cs +++ b/src/Tests/Microsoft.NET.Build.Tests/GivenThatWeWantToReferenceAProject.cs @@ -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(); + } } }