Skip to content

Commit

Permalink
Merge pull request #478 from natidea/transitiveProjectRef
Browse files Browse the repository at this point in the history
Raise Transitive Project References to MSBuild elements
  • Loading branch information
natidea authored Dec 21, 2016
2 parents dceed2b + b8228ad commit a700d03
Show file tree
Hide file tree
Showing 11 changed files with 359 additions and 12 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Library</OutputType>
<TargetFramework>netstandard1.4</TargetFramework>
</PropertyGroup>

<ItemGroup>
<Compile Include="**\*.cs" />
<EmbeddedResource Include="**\*.resx" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="NETStandard.Library" Version="1.6" />
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// Copyright (c) .NET Foundation and contributors. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

namespace AuxLibrary
{
public static class Helper
{
public static string GetMessage()
{
return "This string came from AuxLibrary!";
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// Copyright (c) .NET Foundation and contributors. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

namespace MainLibrary
{
public static class Helper
{
public static string GetMessage()
{
return "This string came from MainLibrary!";
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Library</OutputType>
<TargetFramework>netstandard1.4</TargetFramework>
</PropertyGroup>
<ItemGroup>
<Compile Include="**\*.cs" />
<EmbeddedResource Include="**\*.resx" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="NETStandard.Library" Version="1.6" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\AuxLibrary\AuxLibrary.csproj" />
</ItemGroup>
</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// Copyright (c) .NET Foundation and contributors. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

using System;

namespace TestApp
{
public class Program
{
public static void Main(string[] args)
{
Console.WriteLine("TestApp --depends on--> MainLibrary --depends on--> AuxLibrary");
Console.WriteLine(MainLibrary.Helper.GetMessage());
Console.WriteLine(AuxLibrary.Helper.GetMessage());
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp1.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<Compile Include="**\*.cs" />
<EmbeddedResource Include="**\*.resx" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NETCore.App" Version="1.0.1" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\MainLibrary\MainLibrary.csproj" />
</ItemGroup>
</Project>
Original file line number Diff line number Diff line change
Expand Up @@ -693,6 +693,76 @@ public void ItUsesResolvedPackageVersionFromSameTarget()
.First().Should().Be("Dep.Lib.Chi/4.1.0");
}

[Fact]
public void ItMarksTransitiveProjectReferences()
{
// --------------------------------------------------------------------------
// Given the following layout, only ProjC and ProjE are transitive references
// (ProjB and ProjD are direct references, and ProjF is declared private in ProjC):
//
// TestProject (i.e. current project assets file)
// -> ProjB
// -> ProjC
// -> ProjD
// -> ProjE
// -> ProjF (PrivateAssets=Compile)
// -> ProjD
// --------------------------------------------------------------------------

var target = CreateTarget(".NETCoreApp,Version=v1.0",

CreateTargetLibrary("ProjB/1.0.0", "project",
dependencies: new string[] { "\"ProjC\": \"1.0.0\"" }),

CreateTargetLibrary("ProjC/1.0.0", "project",
dependencies: new string[] {
"\"ProjD\": \"1.0.0\"", "\"ProjE\": \"1.0.0\"", "\"ProjF\": \"1.0.0\""
}),

CreateTargetLibrary("ProjD/1.0.0", "project"),

CreateTargetLibrary("ProjE/1.0.0", "project"),

CreateTargetLibrary("ProjF/1.0.0", "project",
compile: new string[] { CreateFileItem("bin/Debug/_._") })
);

var libraries = new string[]
{
"ProjB", "ProjC", "ProjD", "ProjE", "ProjF"
}
.Select(
proj => CreateProjectLibrary($"{proj}/1.0.0",
path: $"../{proj}/{proj}.csproj",
msbuildProject: $"../{proj}/{proj}.csproj"))
.ToArray();

string lockFileContent = CreateLockFileSnippet(
targets: new string[] { target },
libraries: libraries,
projectFileDependencyGroups: new string[]
{
CreateProjectFileDependencyGroup(".NETCoreApp,Version=v1.0", "ProjB", "ProjD")
}
);

var task = GetExecutedTaskFromContents(lockFileContent, out var lockFile);

task.PackageDependencies.Count().Should().Be(6);

var transitivePkgs = task.PackageDependencies
.Where(t => t.GetMetadata(MetadataKeys.TransitiveProjectReference) == "true");
transitivePkgs.Count().Should().Be(2);
transitivePkgs.Select(t => t.ItemSpec)
.Should().Contain(new string[] { "ProjC/1.0.0", "ProjE/1.0.0" });

var others = task.PackageDependencies.Except(transitivePkgs);
others.Count().Should().Be(4);
others.Where(t => t.ItemSpec == "ProjB/1.0.0").Count().Should().Be(1);
others.Where(t => t.ItemSpec == "ProjD/1.0.0").Count().Should().Be(2);
others.Where(t => t.ItemSpec == "ProjF/1.0.0").Count().Should().Be(1);
}

private ResolvePackageDependencies GetExecutedTaskFromPrefix(string lockFilePrefix)
{
LockFile lockFile;
Expand Down
1 change: 1 addition & 0 deletions src/Tasks/Microsoft.NET.Build.Tasks/MetadataKeys.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,5 +27,6 @@ public static class MetadataKeys
// Tags
public const string Analyzer = "Analyzer";
public const string AnalyzerLanguage = "AnalyzerLanguage";
public const string TransitiveProjectReference = "TransitiveProjectReference";
}
}
29 changes: 24 additions & 5 deletions src/Tasks/Microsoft.NET.Build.Tasks/ResolvePackageDependencies.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ namespace Microsoft.NET.Build.Tasks
/// </summary>
public sealed class ResolvePackageDependencies : TaskBase
{
private const string ProjectTypeKey = "project";
private readonly Dictionary<string, string> _fileTypes = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
private readonly HashSet<string> _projectFileDependencies = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
private IPackageResolver _packageResolver;
Expand Down Expand Up @@ -288,6 +289,12 @@ private void GetPackageAndFileDependencies(LockFileTarget target)
var resolvedPackageVersions = target.Libraries
.ToDictionary(pkg => pkg.Name, pkg => pkg.Version.ToNormalizedString(), StringComparer.OrdinalIgnoreCase);

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

TaskItem item;
foreach (var package in target.Libraries)
{
Expand All @@ -303,17 +310,25 @@ private void GetPackageAndFileDependencies(LockFileTarget target)
}

// get sub package dependencies
GetPackageDependencies(package, target.Name, resolvedPackageVersions);
GetPackageDependencies(package, target.Name, resolvedPackageVersions, transitiveProjectRefs);

// get file dependencies on this package
GetFileDependencies(package, target.Name);
}
}

// A package is a TransitiveProjectReference if it is a project, is not directly referenced,
// and does not contain a placeholder compile time assembly
private bool IsTransitiveProjectReference(LockFileTargetLibrary package) =>
string.Equals(package.Type, ProjectTypeKey, StringComparison.OrdinalIgnoreCase) &&
!_projectFileDependencies.Contains(package.Name) &&
package.CompileTimeAssemblies.FirstOrDefault(f => NuGetUtils.IsPlaceholderFile(f.Path)) == null;

private void GetPackageDependencies(
LockFileTargetLibrary package,
string targetName,
Dictionary<string, string> resolvedPackageVersions)
Dictionary<string, string> resolvedPackageVersions,
HashSet<string> transitiveProjectRefs)
{
string packageId = $"{package.Name}/{package.Version.ToNormalizedString()}";
TaskItem item;
Expand All @@ -332,14 +347,18 @@ private void GetPackageDependencies(
item.SetMetadata(MetadataKeys.ParentTarget, targetName); // Foreign Key
item.SetMetadata(MetadataKeys.ParentPackage, packageId); // Foreign Key

if (transitiveProjectRefs.Contains(deps.Id))
{
item.SetMetadata(MetadataKeys.TransitiveProjectReference, "true");
}

_packageDependencies.Add(item);
}
}

private void GetFileDependencies(LockFileTargetLibrary package, string targetName)
{
string packageId = $"{package.Name}/{package.Version.ToNormalizedString()}";
TaskItem item;

// for each type of file group
foreach (var fileGroup in (FileGroup[])Enum.GetValues(typeof(FileGroup)))
Expand All @@ -356,7 +375,7 @@ private void GetFileDependencies(LockFileTargetLibrary package, string targetNam
}

var fileKey = $"{packageId}/{filePath}";
item = new TaskItem(fileKey);
var item = new TaskItem(fileKey);
item.SetMetadata(MetadataKeys.FileGroup, fileGroup.ToString());
item.SetMetadata(MetadataKeys.ParentTarget, targetName); // Foreign Key
item.SetMetadata(MetadataKeys.ParentPackage, packageId); // Foreign Key
Expand Down Expand Up @@ -401,7 +420,7 @@ private void SaveFileKeyType(string fileKey, FileGroup fileGroup)

private string ResolvePackagePath(LockFileLibrary package)
{
if (package.Type == "project")
if (string.Equals(package.Type, ProjectTypeKey, StringComparison.OrdinalIgnoreCase))
{
var relativeMSBuildProjectPath = package.MSBuildProject;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,6 @@ Copyright (c) .NET Foundation. All rights reserved.
<!-- General Properties -->
<PropertyGroup>
<MSBuildAllProjects>$(MSBuildAllProjects);$(MSBuildThisFileFullPath)</MSBuildAllProjects>

<!-- Flag PackageDependency Tasks -->
<EnableResolvePackageDependencies
Condition="'$(EnableResolvePackageDependencies)' == ''">true</EnableResolvePackageDependencies>
</PropertyGroup>

<!-- Project Assets File -->
Expand Down Expand Up @@ -104,18 +100,22 @@ Copyright (c) .NET Foundation. All rights reserved.
============================================================
ResolvePackageDependenciesForBuild
Populate items for build...
Populate items for build. This is triggered before target
"AssignProjectConfiguration" to ensure ProjectReference items
are populated before ResolveProjectReferences is run.
============================================================
-->
<PropertyGroup>
<ResolvePackageDependenciesForBuildDependsOn>
ResolveLockFileReferences;
ResolveLockFileAnalyzers;
ResolveLockFileCopyLocalProjectDeps;
IncludeTransitiveProjectReferences;
</ResolvePackageDependenciesForBuildDependsOn>
</PropertyGroup>
<Target Name="ResolvePackageDependenciesForBuild"
Condition="'$(ResolvePackageDependenciesForBuild)' == 'true' and '$(EnableResolvePackageDependencies)' == 'true' and Exists('$(ProjectAssetsFile)')"
Condition="'$(ResolvePackageDependenciesForBuild)' == 'true' and Exists('$(ProjectAssetsFile)')"
BeforeTargets="AssignProjectConfiguration"
DependsOnTargets="$(ResolvePackageDependenciesForBuildDependsOn)" />

<!--
Expand Down Expand Up @@ -225,10 +225,18 @@ Copyright (c) .NET Foundation. All rights reserved.

<!--
============================================================
HELPER: Get File Dependencies matching active TFM and RID
HELPERS: Get Package and File Dependencies matching active TFM and RID
============================================================
-->

<Target Name="_ComputeActiveTFMPackageDependencies"
DependsOnTargets="RunResolvePackageDependencies"
Returns="_ActiveTFMPackageDependencies">
<ItemGroup>
<_ActiveTFMPackageDependencies Include="@(PackageDependencies->WithMetadataValue('ParentTarget', '$(_NugetTargetMonikerAndRID)'))" />
</ItemGroup>
</Target>

<Target Name="_ComputeActiveTFMFileDependencies"
DependsOnTargets="RunResolvePackageDependencies"
Returns="_ActiveTFMFileDependencies">
Expand Down Expand Up @@ -290,6 +298,39 @@ Copyright (c) .NET Foundation. All rights reserved.
</ItemGroup>
</Target>

<!--
============================================================
ProjectReference Targets: Include transitive project references before
ResolveProjectReferences is called
- _ComputeTransitiveProjectReferences
- IncludeTransitiveProjectReferences
============================================================
-->

<Target Name="_ComputeTransitiveProjectReferences"
DependsOnTargets="_ComputeActiveTFMPackageDependencies"
Returns="_TransitiveProjectReferences">
<ItemGroup>
<_TransitiveProjectDependencies Include="@(_ActiveTFMPackageDependencies->WithMetadataValue('TransitiveProjectReference', 'true'))" />

<!-- Get corresponding package definitions -->
<__TransitiveProjectDefinitions Include="@(PackageDefinitions)" Exclude="@(_TransitiveProjectDependencies)" />
<_TransitiveProjectDefinitions Include="@(PackageDefinitions)" Exclude="@(__TransitiveProjectDefinitions)" />

<_TransitiveProjectReferences Include="%(_TransitiveProjectDefinitions.Path)">
<ResolvedPath>%(_TransitiveProjectDefinitions.ResolvedPath)</ResolvedPath>
</_TransitiveProjectReferences>
</ItemGroup>
</Target>

<Target Name="IncludeTransitiveProjectReferences"
DependsOnTargets="_ComputeTransitiveProjectReferences" >
<ItemGroup>
<!-- Add the references we computed -->
<ProjectReference Include="@(_TransitiveProjectReferences)" />
</ItemGroup>
</Target>

<!--
============================================================
Analyzer Targets: For populating Analyzers based on lock file
Expand Down
Loading

0 comments on commit a700d03

Please sign in to comment.