Skip to content
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
Expand Up @@ -2,6 +2,7 @@
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

using System;
using System.Collections.Generic;
using System.IO;
using Microsoft.Build.Execution;
using Microsoft.Build.Graph;
Expand All @@ -15,23 +16,44 @@ public sealed class GetCopyToOutputDirectoryItemsGraphPredictor : IProjectGraphP
{
internal const string UseCommonOutputDirectoryPropertyName = "UseCommonOutputDirectory";
internal const string OutDirPropertyName = "OutDir";
internal const string MSBuildCopyContentTransitivelyPropertyName = "MSBuildCopyContentTransitively";

/// <inheritdoc/>
public void PredictInputsAndOutputs(ProjectGraphNode projectGraphNode, ProjectPredictionReporter predictionReporter)
{
string outDir = projectGraphNode.ProjectInstance.GetPropertyValue(OutDirPropertyName);
HashSet<ProjectGraphNode> visitedNodes = new();
PredictInputsAndOutputs(projectGraphNode, outDir, predictionReporter, visitedNodes);
}

private static void PredictInputsAndOutputs(
ProjectGraphNode projectGraphNode,
string outDir,
ProjectPredictionReporter predictionReporter,
HashSet<ProjectGraphNode> visitedNodes)
{
ProjectInstance projectInstance = projectGraphNode.ProjectInstance;

// The GetCopyToOutputDirectoryItems target gets called on all dependencies, unless UseCommonOutputDirectory is set to true.
var useCommonOutputDirectory = projectInstance.GetPropertyValue(UseCommonOutputDirectoryPropertyName);
if (!useCommonOutputDirectory.Equals("true", StringComparison.OrdinalIgnoreCase))
{
string outDir = projectInstance.GetPropertyValue(OutDirPropertyName);
bool copyContentTransitively = projectInstance.GetPropertyValue(MSBuildCopyContentTransitivelyPropertyName).Equals("true", StringComparison.OrdinalIgnoreCase);

// Note that GetCopyToOutputDirectoryItems effectively only is able to go one project reference deep despite being recursive as
// it uses @(_MSBuildProjectReferenceExistent) to recurse, which is not set in the recursive calls.
// See: https://github.com/microsoft/msbuild/blob/master/src/Tasks/Microsoft.Common.CurrentVersion.targets
foreach (ProjectGraphNode dependency in projectGraphNode.ProjectReferences)
{
if (!visitedNodes.Add(dependency))
{
// Avoid duplicate predictions
continue;
}

// If transitive, recurse
if (copyContentTransitively)
{
PredictInputsAndOutputs(dependency, outDir, predictionReporter, visitedNodes);
}

// Process each item type considered in GetCopyToOutputDirectoryItems. Yes, Compile is considered.
ReportCopyToOutputDirectoryItemsAsInputs(dependency.ProjectInstance, ContentItemsPredictor.ContentItemName, outDir, predictionReporter);
ReportCopyToOutputDirectoryItemsAsInputs(dependency.ProjectInstance, EmbeddedResourceItemsPredictor.EmbeddedResourceItemName, outDir, predictionReporter);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

using System;
using System.Collections.Generic;
using System.IO;
using Microsoft.Build.Construction;
using Microsoft.Build.Prediction.Predictors;
Expand Down Expand Up @@ -77,8 +78,10 @@ public void UseCommonOutputDirectory()
.AssertNoPredictions();
}

[Fact]
public void WithCopy()
[Theory]
[InlineData(false)]
[InlineData(true)]
public void WithCopy(bool copyContentTransitively)
{
string projectFile = Path.Combine(_rootDir, @"src\project.csproj");
ProjectRootElement projectRootElement = ProjectRootElement.Create(projectFile);
Expand All @@ -89,7 +92,12 @@ public void WithCopy()
ProjectRootElement dep2 = CreateDependencyProject("dep2", shouldCopy);
ProjectRootElement dep3 = CreateDependencyProject("dep3", shouldCopy);

// The main project depends on 1 and 2; 2 depends on 3; 3 depends on 1. Note that this should *not* be transitive
projectRootElement.AddProperty(GetCopyToOutputDirectoryItemsGraphPredictor.MSBuildCopyContentTransitivelyPropertyName, copyContentTransitively.ToString());
dep1.AddProperty(GetCopyToOutputDirectoryItemsGraphPredictor.MSBuildCopyContentTransitivelyPropertyName, copyContentTransitively.ToString());
dep2.AddProperty(GetCopyToOutputDirectoryItemsGraphPredictor.MSBuildCopyContentTransitivelyPropertyName, copyContentTransitively.ToString());
dep3.AddProperty(GetCopyToOutputDirectoryItemsGraphPredictor.MSBuildCopyContentTransitivelyPropertyName, copyContentTransitively.ToString());

// The main project depends on 1 and 2; 2 depends on 3; 3 depends on 1.
projectRootElement.AddItem("ProjectReference", @"..\dep1\dep1.proj");
projectRootElement.AddItem("ProjectReference", @"..\dep2\dep2.proj");
dep2.AddItem("ProjectReference", @"..\dep3\dep3.proj");
Expand All @@ -100,8 +108,8 @@ public void WithCopy()
dep2.Save();
dep3.Save();

var expectedInputFiles = new[]
{
List<PredictedItem> expectedInputFiles =
[
new PredictedItem(@"dep1\dep1.xml", nameof(GetCopyToOutputDirectoryItemsGraphPredictor)),
new PredictedItem(@"dep1\dep1.resx", nameof(GetCopyToOutputDirectoryItemsGraphPredictor)),
new PredictedItem(@"dep1\dep1.cs", nameof(GetCopyToOutputDirectoryItemsGraphPredictor)),
Expand All @@ -112,10 +120,10 @@ public void WithCopy()
new PredictedItem(@"dep2\dep2.cs", nameof(GetCopyToOutputDirectoryItemsGraphPredictor)),
new PredictedItem(@"dep2\dep2.txt", nameof(GetCopyToOutputDirectoryItemsGraphPredictor)),
new PredictedItem(@"dep2\dep2.xaml", nameof(GetCopyToOutputDirectoryItemsGraphPredictor)),
};
];

var expectedOutputFiles = new[]
{
List<PredictedItem> expectedOutputFiles =
[
new PredictedItem(@"src\bin\dep1.xml", nameof(GetCopyToOutputDirectoryItemsGraphPredictor)),
new PredictedItem(@"src\bin\dep1.resx", nameof(GetCopyToOutputDirectoryItemsGraphPredictor)),
new PredictedItem(@"src\bin\dep1.cs", nameof(GetCopyToOutputDirectoryItemsGraphPredictor)),
Expand All @@ -126,7 +134,28 @@ public void WithCopy()
new PredictedItem(@"src\bin\dep2.cs", nameof(GetCopyToOutputDirectoryItemsGraphPredictor)),
new PredictedItem(@"src\bin\dep2.txt", nameof(GetCopyToOutputDirectoryItemsGraphPredictor)),
new PredictedItem(@"src\bin\dep2.xaml", nameof(GetCopyToOutputDirectoryItemsGraphPredictor)),
};
];

if (copyContentTransitively)
{
expectedInputFiles.AddRange(
[
new PredictedItem(@"dep3\dep3.xml", nameof(GetCopyToOutputDirectoryItemsGraphPredictor)),
new PredictedItem(@"dep3\dep3.resx", nameof(GetCopyToOutputDirectoryItemsGraphPredictor)),
new PredictedItem(@"dep3\dep3.cs", nameof(GetCopyToOutputDirectoryItemsGraphPredictor)),
new PredictedItem(@"dep3\dep3.txt", nameof(GetCopyToOutputDirectoryItemsGraphPredictor)),
new PredictedItem(@"dep3\dep3.xaml", nameof(GetCopyToOutputDirectoryItemsGraphPredictor)),
]);

expectedOutputFiles.AddRange(
[
new PredictedItem(@"src\bin\dep3.xml", nameof(GetCopyToOutputDirectoryItemsGraphPredictor)),
new PredictedItem(@"src\bin\dep3.resx", nameof(GetCopyToOutputDirectoryItemsGraphPredictor)),
new PredictedItem(@"src\bin\dep3.cs", nameof(GetCopyToOutputDirectoryItemsGraphPredictor)),
new PredictedItem(@"src\bin\dep3.txt", nameof(GetCopyToOutputDirectoryItemsGraphPredictor)),
new PredictedItem(@"src\bin\dep3.xaml", nameof(GetCopyToOutputDirectoryItemsGraphPredictor)),
]);
}

new GetCopyToOutputDirectoryItemsGraphPredictor()
.GetProjectPredictions(projectFile)
Expand Down