diff --git a/src/BuildPrediction/FakesPredictor.cs b/src/BuildPrediction/FakesPredictor.cs
new file mode 100644
index 0000000..cfe8015
--- /dev/null
+++ b/src/BuildPrediction/FakesPredictor.cs
@@ -0,0 +1,44 @@
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System;
+using System.IO;
+using Microsoft.Build.Execution;
+
+namespace Microsoft.Build.Prediction.Predictors
+{
+ ///
+ /// Predicts inputs and outputs related to Fakes.
+ ///
+ public sealed class FakesPredictor : IProjectPredictor
+ {
+ internal const string FakesImportedPropertyName = "FakesImported";
+
+ internal const string FakesUseV2GenerationPropertyName = "FakesUseV2Generation";
+
+ internal const string FakesOutputPathPropertyName = "FakesOutputPath";
+
+ internal const string FakesItemName = "Fakes";
+
+ ///
+ public void PredictInputsAndOutputs(ProjectInstance projectInstance, ProjectPredictionReporter predictionReporter)
+ {
+ if (!projectInstance.GetPropertyValue(FakesImportedPropertyName).Equals("true", StringComparison.OrdinalIgnoreCase))
+ {
+ return;
+ }
+
+ string fakesOutputPath = projectInstance.GetPropertyValue(FakesOutputPathPropertyName);
+ foreach (ProjectItemInstance item in projectInstance.GetItems(FakesItemName))
+ {
+ predictionReporter.ReportInputFile(item.EvaluatedInclude);
+
+ if (!string.IsNullOrWhiteSpace(fakesOutputPath))
+ {
+ string fakesAssembly = Path.Combine(fakesOutputPath, $"{Path.GetFileNameWithoutExtension(item.EvaluatedInclude)}.Fakes.dll");
+ predictionReporter.ReportOutputFile(fakesAssembly);
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/BuildPrediction/Predictors/FakesOutputPathPredictor.cs b/src/BuildPrediction/Predictors/FakesOutputPathPredictor.cs
deleted file mode 100644
index a5638ef..0000000
--- a/src/BuildPrediction/Predictors/FakesOutputPathPredictor.cs
+++ /dev/null
@@ -1,27 +0,0 @@
-// Copyright (c) Microsoft. All rights reserved.
-// Licensed under the MIT license. See LICENSE file in the project root for full license information.
-
-using Microsoft.Build.Execution;
-
-namespace Microsoft.Build.Prediction.Predictors
-{
- ///
- /// Predicts the output directory for Microsoft Fakes assemblies based on the FakesOutputPath property.
- ///
- public sealed class FakesOutputPathPredictor : IProjectPredictor
- {
- internal const string FakesOutputPathPropertyName = "FakesOutputPath";
-
- ///
- public void PredictInputsAndOutputs(
- ProjectInstance projectInstance,
- ProjectPredictionReporter predictionReporter)
- {
- string fakesOutputPath = projectInstance.GetPropertyValue(FakesOutputPathPropertyName);
- if (!string.IsNullOrWhiteSpace(fakesOutputPath))
- {
- predictionReporter.ReportOutputDirectory(fakesOutputPath);
- }
- }
- }
-}
\ No newline at end of file
diff --git a/src/BuildPrediction/Predictors/GetCopyToOutputDirectoryItemsGraphPredictor.cs b/src/BuildPrediction/Predictors/GetCopyToOutputDirectoryItemsGraphPredictor.cs
index a2a7b27..ada810c 100644
--- a/src/BuildPrediction/Predictors/GetCopyToOutputDirectoryItemsGraphPredictor.cs
+++ b/src/BuildPrediction/Predictors/GetCopyToOutputDirectoryItemsGraphPredictor.cs
@@ -96,6 +96,29 @@ private static void PredictInputsAndOutputs(
}
}
}
+
+ // FakesV2 projects add Fakes assemblies as content which are transitively copied to referencing projects. See CopyFakesAssembliesToOutputDir target.
+ if (dependency.ProjectInstance.GetPropertyValue(FakesPredictor.FakesImportedPropertyName).Equals("true", StringComparison.OrdinalIgnoreCase)
+ && dependency.ProjectInstance.GetPropertyValue(FakesPredictor.FakesUseV2GenerationPropertyName).Equals("true", StringComparison.OrdinalIgnoreCase))
+ {
+ string fakesOutputPath = dependency.ProjectInstance.GetPropertyValue(FakesPredictor.FakesOutputPathPropertyName);
+ if (!string.IsNullOrWhiteSpace(fakesOutputPath))
+ {
+ // Make it absolute since it may be relative to the dependency project
+ fakesOutputPath = Path.Combine(dependency.ProjectInstance.Directory, fakesOutputPath);
+
+ foreach (ProjectItemInstance item in dependency.ProjectInstance.GetItems(FakesPredictor.FakesItemName))
+ {
+ string fakesAssemblyFileName = $"{Path.GetFileNameWithoutExtension(item.EvaluatedInclude)}.Fakes.dll";
+ predictionReporter.ReportInputFile(Path.Combine(fakesOutputPath, fakesAssemblyFileName));
+
+ if (!string.IsNullOrEmpty(outDir))
+ {
+ predictionReporter.ReportOutputFile(Path.Combine(outDir, fakesAssemblyFileName));
+ }
+ }
+ }
+ }
}
}
}
diff --git a/src/BuildPrediction/ProjectPredictors.cs b/src/BuildPrediction/ProjectPredictors.cs
index 83e846b..5892d12 100644
--- a/src/BuildPrediction/ProjectPredictors.cs
+++ b/src/BuildPrediction/ProjectPredictors.cs
@@ -64,7 +64,7 @@ public static class ProjectPredictors
///
///
///
- ///
+ ///
///
///
/// A collection of .
@@ -115,7 +115,7 @@ public static class ProjectPredictors
new GenerateBuildDependencyFilePredictor(),
new GeneratePublishDependencyFilePredictor(),
new GenerateRuntimeConfigurationFilesPredictor(),
- new FakesOutputPathPredictor(),
+ new FakesPredictor(),
//// NOTE! When adding a new predictor here, be sure to update the doc comment above.
};
diff --git a/src/BuildPredictionTests/Predictors/FakesOutputPathPredictorTests.cs b/src/BuildPredictionTests/Predictors/FakesOutputPathPredictorTests.cs
deleted file mode 100644
index 821c877..0000000
--- a/src/BuildPredictionTests/Predictors/FakesOutputPathPredictorTests.cs
+++ /dev/null
@@ -1,96 +0,0 @@
-// Copyright (c) Microsoft. All rights reserved.
-// Licensed under the MIT license. See LICENSE file in the project root for full license information.
-
-using Microsoft.Build.Construction;
-using Microsoft.Build.Execution;
-using Microsoft.Build.Prediction.Predictors;
-using Xunit;
-
-namespace Microsoft.Build.Prediction.Tests.Predictors
-{
- public class FakesOutputPathPredictorTests
- {
- [Fact]
- public void FakesOutputPathFoundAsOutputDir()
- {
- const string FakesOutputPath = @"C:\repo\FakesAssemblies";
- ProjectInstance projectInstance = CreateTestProjectInstance(FakesOutputPath);
- new FakesOutputPathPredictor()
- .GetProjectPredictions(projectInstance)
- .AssertPredictions(
- projectInstance,
- null,
- null,
- null,
- new[] { new PredictedItem(FakesOutputPath, nameof(FakesOutputPathPredictor)) });
- }
-
- [Fact]
- public void RelativeFakesOutputPathFoundAsOutputDir()
- {
- const string FakesOutputPath = @"bin\FakesAssemblies";
- ProjectInstance projectInstance = CreateTestProjectInstance(FakesOutputPath);
- new FakesOutputPathPredictor()
- .GetProjectPredictions(projectInstance)
- .AssertPredictions(
- projectInstance,
- null,
- null,
- null,
- new[] { new PredictedItem(FakesOutputPath, nameof(FakesOutputPathPredictor)) });
- }
-
- [Fact]
- public void DefaultFakesAssembliesDirectoryFoundAsOutputDir()
- {
- const string FakesOutputPath = @"FakesAssemblies";
- ProjectInstance projectInstance = CreateTestProjectInstance(FakesOutputPath);
- new FakesOutputPathPredictor()
- .GetProjectPredictions(projectInstance)
- .AssertPredictions(
- projectInstance,
- null,
- null,
- null,
- new[] { new PredictedItem(FakesOutputPath, nameof(FakesOutputPathPredictor)) });
- }
-
- [Fact]
- public void NoOutputsReportedIfNoFakesOutputPath()
- {
- ProjectInstance projectInstance = CreateTestProjectInstance(null);
- new FakesOutputPathPredictor()
- .GetProjectPredictions(projectInstance)
- .AssertNoPredictions();
- }
-
- [Fact]
- public void NoOutputsReportedIfEmptyFakesOutputPath()
- {
- ProjectInstance projectInstance = CreateTestProjectInstance(string.Empty);
- new FakesOutputPathPredictor()
- .GetProjectPredictions(projectInstance)
- .AssertNoPredictions();
- }
-
- [Fact]
- public void NoOutputsReportedIfWhitespaceFakesOutputPath()
- {
- ProjectInstance projectInstance = CreateTestProjectInstance(" ");
- new FakesOutputPathPredictor()
- .GetProjectPredictions(projectInstance)
- .AssertNoPredictions();
- }
-
- private static ProjectInstance CreateTestProjectInstance(string fakesOutputPath)
- {
- ProjectRootElement projectRootElement = ProjectRootElement.Create();
- if (fakesOutputPath != null)
- {
- projectRootElement.AddProperty(FakesOutputPathPredictor.FakesOutputPathPropertyName, fakesOutputPath);
- }
-
- return TestHelpers.CreateProjectInstanceFromRootElement(projectRootElement);
- }
- }
-}
\ No newline at end of file
diff --git a/src/BuildPredictionTests/Predictors/FakesPredictorTests.cs b/src/BuildPredictionTests/Predictors/FakesPredictorTests.cs
new file mode 100644
index 0000000..660e55b
--- /dev/null
+++ b/src/BuildPredictionTests/Predictors/FakesPredictorTests.cs
@@ -0,0 +1,103 @@
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System;
+using System.IO;
+using Microsoft.Build.Construction;
+using Microsoft.Build.Execution;
+using Microsoft.Build.Prediction.Predictors;
+using Xunit;
+
+namespace Microsoft.Build.Prediction.Tests.Predictors
+{
+ public class FakesPredictorTests
+ {
+ [Fact]
+ public void FindItems()
+ {
+ const string FakesOutputPath = @"bin\FakesAssemblies";
+ ProjectInstance projectInstance = CreateTestProjectInstance(FakesOutputPath, ["A.fakes", "B.fakes", "C.fakes"]);
+
+ var expectedInputFiles = new[]
+ {
+ new PredictedItem("A.fakes", nameof(FakesPredictor)),
+ new PredictedItem("B.fakes", nameof(FakesPredictor)),
+ new PredictedItem("C.fakes", nameof(FakesPredictor)),
+ };
+ var expectedOutputFiles = new[]
+ {
+ new PredictedItem(Path.Combine(FakesOutputPath, "A.Fakes.dll"), nameof(FakesPredictor)),
+ new PredictedItem(Path.Combine(FakesOutputPath, "B.Fakes.dll"), nameof(FakesPredictor)),
+ new PredictedItem(Path.Combine(FakesOutputPath, "C.Fakes.dll"), nameof(FakesPredictor)),
+ };
+ new FakesPredictor()
+ .GetProjectPredictions(projectInstance)
+ .AssertPredictions(
+ projectInstance,
+ expectedInputFiles,
+ null,
+ expectedOutputFiles,
+ null);
+ }
+
+ [Theory]
+ [InlineData(null)]
+ [InlineData("")]
+ [InlineData(" ")]
+ public void NoOutputsReportedIfInvalidFakesOutputPath(string fakesOutputPath)
+ {
+ ProjectInstance projectInstance = CreateTestProjectInstance(fakesOutputPath, ["A.fakes", "B.fakes", "C.fakes"]);
+ var expectedInputFiles = new[]
+ {
+ new PredictedItem("A.fakes", nameof(FakesPredictor)),
+ new PredictedItem("B.fakes", nameof(FakesPredictor)),
+ new PredictedItem("C.fakes", nameof(FakesPredictor)),
+ };
+
+ new FakesPredictor()
+ .GetProjectPredictions(projectInstance)
+ .AssertPredictions(
+ projectInstance,
+ expectedInputFiles,
+ null,
+ null,
+ null);
+ }
+
+ [Fact]
+ public void NoPredictionsReportedNoFakesItems()
+ {
+ ProjectInstance projectInstance = CreateTestProjectInstance(@"bin\FakesAssemblies", []);
+ var expectedInputFiles = new[]
+ {
+ new PredictedItem("A.fakes", nameof(FakesPredictor)),
+ new PredictedItem("B.fakes", nameof(FakesPredictor)),
+ new PredictedItem("C.fakes", nameof(FakesPredictor)),
+ };
+
+ new FakesPredictor()
+ .GetProjectPredictions(projectInstance)
+ .AssertNoPredictions();
+ }
+
+ private static ProjectInstance CreateTestProjectInstance(
+ string fakesOutputPath,
+ ReadOnlySpan fakesItems)
+ {
+ ProjectRootElement projectRootElement = ProjectRootElement.Create();
+ projectRootElement.AddProperty(FakesPredictor.FakesImportedPropertyName, "true");
+
+ if (fakesOutputPath != null)
+ {
+ projectRootElement.AddProperty(FakesPredictor.FakesOutputPathPropertyName, fakesOutputPath);
+ }
+
+ foreach (string fakesItem in fakesItems)
+ {
+ projectRootElement.AddItem(FakesPredictor.FakesItemName, fakesItem);
+ }
+
+ return TestHelpers.CreateProjectInstanceFromRootElement(projectRootElement);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/BuildPredictionTests/Predictors/GetCopyToOutputDirectoryItemsGraphPredictorTests.cs b/src/BuildPredictionTests/Predictors/GetCopyToOutputDirectoryItemsGraphPredictorTests.cs
index 6f1ca15..d4e273b 100644
--- a/src/BuildPredictionTests/Predictors/GetCopyToOutputDirectoryItemsGraphPredictorTests.cs
+++ b/src/BuildPredictionTests/Predictors/GetCopyToOutputDirectoryItemsGraphPredictorTests.cs
@@ -225,6 +225,51 @@ void AddPropertyToAllProjects(string propertyName, string propertyValue)
}
}
+ [Fact]
+ public void DependencyWithFakesAssemblies()
+ {
+ string projectFile = Path.Combine(_rootDir, @"src\project.csproj");
+ ProjectRootElement projectRootElement = ProjectRootElement.Create(projectFile);
+ projectRootElement.AddProperty(GetCopyToOutputDirectoryItemsGraphPredictor.OutDirPropertyName, @"bin\");
+
+ string dependencyProjectFile = Path.Combine(_rootDir, @"dep\dep.csproj");
+ ProjectRootElement dependencyProjectRootElement = ProjectRootElement.Create(dependencyProjectFile);
+ dependencyProjectRootElement.AddProperty(FakesPredictor.FakesImportedPropertyName, "true");
+ dependencyProjectRootElement.AddProperty(FakesPredictor.FakesUseV2GenerationPropertyName, "true");
+ dependencyProjectRootElement.AddProperty(FakesPredictor.FakesOutputPathPropertyName, @"bin\FakesAssemblies");
+ dependencyProjectRootElement.AddItem(FakesPredictor.FakesItemName, "A.fakes");
+ dependencyProjectRootElement.AddItem(FakesPredictor.FakesItemName, "B.fakes");
+ dependencyProjectRootElement.AddItem(FakesPredictor.FakesItemName, "C.fakes");
+
+ projectRootElement.AddItem("ProjectReference", @"..\dep\dep.csproj");
+
+ projectRootElement.Save();
+ dependencyProjectRootElement.Save();
+
+ var expectedInputFiles = new[]
+ {
+ new PredictedItem(@"dep\bin\FakesAssemblies\A.Fakes.dll", nameof(GetCopyToOutputDirectoryItemsGraphPredictor)),
+ new PredictedItem(@"dep\bin\FakesAssemblies\B.Fakes.dll", nameof(GetCopyToOutputDirectoryItemsGraphPredictor)),
+ new PredictedItem(@"dep\bin\FakesAssemblies\C.Fakes.dll", nameof(GetCopyToOutputDirectoryItemsGraphPredictor)),
+ };
+
+ var expectedOutputFiles = new[]
+ {
+ new PredictedItem(@"src\bin\A.Fakes.dll", nameof(GetCopyToOutputDirectoryItemsGraphPredictor)),
+ new PredictedItem(@"src\bin\B.Fakes.dll", nameof(GetCopyToOutputDirectoryItemsGraphPredictor)),
+ new PredictedItem(@"src\bin\C.Fakes.dll", nameof(GetCopyToOutputDirectoryItemsGraphPredictor)),
+ };
+
+ new GetCopyToOutputDirectoryItemsGraphPredictor()
+ .GetProjectPredictions(projectFile)
+ .AssertPredictions(
+ _rootDir,
+ expectedInputFiles,
+ null,
+ expectedOutputFiles,
+ null);
+ }
+
private ProjectRootElement CreateDependencyProject(string projectName, bool shouldCopy)
{
string projectDir = Path.Combine(_rootDir, projectName);