diff --git a/src/BuildPrediction/Predictors/LinkItemsPredictor.cs b/src/BuildPrediction/Predictors/LinkItemsPredictor.cs
index 9991568..60803ab 100644
--- a/src/BuildPrediction/Predictors/LinkItemsPredictor.cs
+++ b/src/BuildPrediction/Predictors/LinkItemsPredictor.cs
@@ -2,31 +2,81 @@
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
using System;
+using System.Collections.Generic;
using Microsoft.Build.Execution;
-namespace Microsoft.Build.Prediction.Predictors
+namespace Microsoft.Build.Prediction.Predictors;
+
+///
+/// Predicts inputs based on Link, Lib, and ImpLib items.
+///
+public sealed class LinkItemsPredictor : IProjectPredictor
{
- ///
- /// Finds ClInclude items, typically header files used during compilation.
- ///
- public sealed class LinkItemsPredictor : IProjectPredictor
+ // See Microsoft.CppCommon.targets for items which use AdditionalDependencies.
+ internal const string LinkItemName = "Link";
+ internal const string LibItemName = "Lib";
+ internal const string ImpLibItemName = "ImpLib";
+
+ internal const string AdditionalDependenciesMetadata = "AdditionalDependencies";
+ internal const string AdditionalLibraryDirectoriesMetadata = "AdditionalLibraryDirectories";
+
+ private static readonly char[] IncludePathsSeparator = [';'];
+
+ ///
+ public void PredictInputsAndOutputs(ProjectInstance projectInstance, ProjectPredictionReporter reporter)
+ {
+ // This predictor only applies to vcxproj files
+ if (!projectInstance.FullPath.EndsWith(".vcxproj", StringComparison.OrdinalIgnoreCase))
+ {
+ return;
+ }
+
+ // These are commonly added via an ItemDefinitionGroup, so use a HashSet to dedupe the repeats.
+ HashSet reportedFiles = new(StringComparer.OrdinalIgnoreCase);
+ HashSet reportedDirectories = new(StringComparer.OrdinalIgnoreCase);
+
+ ReportInputsForItemType(reporter, projectInstance, LinkItemName, reportedFiles, reportedDirectories);
+ ReportInputsForItemType(reporter, projectInstance, LibItemName, reportedFiles, reportedDirectories);
+ ReportInputsForItemType(reporter, projectInstance, ImpLibItemName, reportedFiles, reportedDirectories);
+ }
+
+ private void ReportInputsForItemType(
+ ProjectPredictionReporter reporter,
+ ProjectInstance projectInstance,
+ string itemType,
+ HashSet reportedFiles,
+ HashSet reportedDirectories)
{
- internal const string LinkItemName = "Link";
+ ICollection items = projectInstance.GetItems(itemType);
+ if (items.Count == 0)
+ {
+ return;
+ }
- ///
- public void PredictInputsAndOutputs(
- ProjectInstance projectInstance,
- ProjectPredictionReporter predictionReporter)
+ foreach (ProjectItemInstance item in items)
{
- // This predictor only applies to vcxproj files
- if (!projectInstance.FullPath.EndsWith(".vcxproj", StringComparison.OrdinalIgnoreCase))
+ reporter.ReportInputFile(item.EvaluatedInclude);
+
+ string[] additionalDependencies = item.GetMetadataValue(AdditionalDependenciesMetadata)
+ .Split(IncludePathsSeparator, StringSplitOptions.RemoveEmptyEntries);
+ foreach (string dependency in additionalDependencies)
{
- return;
+ string trimmedDependency = dependency.Trim();
+ if (!string.IsNullOrEmpty(trimmedDependency) && reportedFiles.Add(trimmedDependency))
+ {
+ reporter.ReportInputFile(trimmedDependency);
+ }
}
- foreach (ProjectItemInstance item in projectInstance.GetItems(LinkItemName))
+ string[] additionalLibraryDirectories = item.GetMetadataValue(AdditionalLibraryDirectoriesMetadata)
+ .Split(IncludePathsSeparator, StringSplitOptions.RemoveEmptyEntries);
+ foreach (string directory in additionalLibraryDirectories)
{
- predictionReporter.ReportInputFile(item.EvaluatedInclude);
+ string trimmedDirectory = directory.Trim();
+ if (!string.IsNullOrEmpty(trimmedDirectory) && reportedDirectories.Add(trimmedDirectory))
+ {
+ reporter.ReportInputDirectory(trimmedDirectory);
+ }
}
}
}
diff --git a/src/BuildPredictionTests/Predictors/LinkItemsPredictorTests.cs b/src/BuildPredictionTests/Predictors/LinkItemsPredictorTests.cs
index f49285c..141e922 100644
--- a/src/BuildPredictionTests/Predictors/LinkItemsPredictorTests.cs
+++ b/src/BuildPredictionTests/Predictors/LinkItemsPredictorTests.cs
@@ -8,58 +8,178 @@
using Microsoft.Build.Prediction.Predictors;
using Xunit;
-namespace Microsoft.Build.Prediction.Tests.Predictors
+namespace Microsoft.Build.Prediction.Tests.Predictors;
+
+public sealed class LinkItemsPredictorTests
{
- public class LinkItemsPredictorTests
+ [Theory]
+ [InlineData(LinkItemsPredictor.LinkItemName)]
+ [InlineData(LinkItemsPredictor.LibItemName)]
+ [InlineData(LinkItemsPredictor.ImpLibItemName)]
+ public void ItemDefinitionGroup(string itemType)
{
- private readonly string _rootDir;
+ ProjectRootElement projectRootElement = ProjectRootElement.Create(@"src\project.vcxproj");
+
+ ProjectItemDefinitionElement itemDefinition = projectRootElement.AddItemDefinitionGroup().AddItemDefinition(itemType);
+ itemDefinition.AddMetadata(LinkItemsPredictor.AdditionalDependenciesMetadata, @"..\AdditionalDependency.lib;%(AdditionalDependencies)");
+ itemDefinition.AddMetadata(LinkItemsPredictor.AdditionalLibraryDirectoriesMetadata, @"..\AdditionalLibraryDirectory;%(AdditionalLibraryDirectories)");
+
+ projectRootElement.AddItem(itemType, @"..\someLib.lib");
- public LinkItemsPredictorTests()
+ ProjectInstance projectInstance = TestHelpers.CreateProjectInstanceFromRootElement(projectRootElement);
+
+ var expectedInputFiles = new[]
+ {
+ new PredictedItem("someLib.lib", nameof(LinkItemsPredictor)),
+ new PredictedItem("AdditionalDependency.lib", nameof(LinkItemsPredictor)),
+ };
+ var expectedInputDirectories = new[]
{
- // Isolate each test into its own folder
- _rootDir = Path.Combine(Directory.GetCurrentDirectory(), nameof(LinkItemsPredictorTests), Guid.NewGuid().ToString());
- Directory.CreateDirectory(_rootDir);
- }
+ new PredictedItem("AdditionalLibraryDirectory", nameof(LinkItemsPredictor)),
+ };
+ new LinkItemsPredictor()
+ .GetProjectPredictions(projectInstance)
+ .AssertPredictions(
+ projectInstance,
+ expectedInputFiles.MakeAbsolute(Directory.GetCurrentDirectory()),
+ expectedInputDirectories.MakeAbsolute(Directory.GetCurrentDirectory()),
+ null,
+ null);
+ }
+
+ [Theory]
+ [InlineData(LinkItemsPredictor.LinkItemName)]
+ [InlineData(LinkItemsPredictor.LibItemName)]
+ [InlineData(LinkItemsPredictor.ImpLibItemName)]
+ public void OverrideMetadata(string itemType)
+ {
+ ProjectRootElement projectRootElement = ProjectRootElement.Create(@"src\project.vcxproj");
+
+ ProjectItemDefinitionElement itemDefinition = projectRootElement.AddItemDefinitionGroup().AddItemDefinition(itemType);
+ itemDefinition.AddMetadata(LinkItemsPredictor.AdditionalDependenciesMetadata, @"..\AdditionalDependency.lib;%(AdditionalDependencies)");
+ itemDefinition.AddMetadata(LinkItemsPredictor.AdditionalLibraryDirectoriesMetadata, @"..\AdditionalLibraryDirectory;%(AdditionalLibraryDirectories)");
- [Fact]
- public void FindsItems()
+ ProjectItemElement item = projectRootElement.AddItem(itemType, @"..\someLib.lib");
+ item.AddMetadata(LinkItemsPredictor.AdditionalDependenciesMetadata, @"..\ReplacedAdditionalDependency.lib");
+ item.AddMetadata(LinkItemsPredictor.AdditionalLibraryDirectoriesMetadata, @"..\ReplacedAdditionalLibraryDirectory");
+
+ ProjectInstance projectInstance = TestHelpers.CreateProjectInstanceFromRootElement(projectRootElement);
+
+ var expectedInputFiles = new[]
{
- ProjectRootElement projectRootElement = ProjectRootElement.Create(Path.Combine(_rootDir, @"project.vcxproj"));
- projectRootElement.AddItem(LinkItemsPredictor.LinkItemName, "foo.lib");
- projectRootElement.AddItem(LinkItemsPredictor.LinkItemName, "bar.lib");
- projectRootElement.AddItem(LinkItemsPredictor.LinkItemName, "baz.lib");
-
- ProjectInstance projectInstance = TestHelpers.CreateProjectInstanceFromRootElement(projectRootElement);
-
- var expectedInputFiles = new[]
- {
- new PredictedItem("foo.lib", nameof(LinkItemsPredictor)),
- new PredictedItem("bar.lib", nameof(LinkItemsPredictor)),
- new PredictedItem("baz.lib", nameof(LinkItemsPredictor)),
- };
- new LinkItemsPredictor()
- .GetProjectPredictions(projectInstance)
- .AssertPredictions(
- projectInstance,
- expectedInputFiles,
- null,
- null,
- null);
- }
-
- [Fact]
- public void SkipOtherProjectTypes()
+ new PredictedItem("someLib.lib", nameof(LinkItemsPredictor)),
+ new PredictedItem("ReplacedAdditionalDependency.lib", nameof(LinkItemsPredictor)),
+ };
+ var expectedInputDirectories = new[]
{
- ProjectRootElement projectRootElement = ProjectRootElement.Create(Path.Combine(_rootDir, @"project.csproj"));
- projectRootElement.AddItem(LinkItemsPredictor.LinkItemName, "foo.lib");
- projectRootElement.AddItem(LinkItemsPredictor.LinkItemName, "bar.lib");
- projectRootElement.AddItem(LinkItemsPredictor.LinkItemName, "baz.lib");
+ new PredictedItem("ReplacedAdditionalLibraryDirectory", nameof(LinkItemsPredictor)),
+ };
+ new LinkItemsPredictor()
+ .GetProjectPredictions(projectInstance)
+ .AssertPredictions(
+ projectInstance,
+ expectedInputFiles.MakeAbsolute(Directory.GetCurrentDirectory()),
+ expectedInputDirectories.MakeAbsolute(Directory.GetCurrentDirectory()),
+ null,
+ null);
+ }
+
+ [Theory]
+ [InlineData(LinkItemsPredictor.LinkItemName)]
+ [InlineData(LinkItemsPredictor.LibItemName)]
+ [InlineData(LinkItemsPredictor.ImpLibItemName)]
+ public void AppendMetadata(string itemType)
+ {
+ ProjectRootElement projectRootElement = ProjectRootElement.Create(@"src\project.vcxproj");
+
+ ProjectItemDefinitionElement itemDefinition = projectRootElement.AddItemDefinitionGroup().AddItemDefinition(itemType);
+ itemDefinition.AddMetadata(LinkItemsPredictor.AdditionalDependenciesMetadata, @"..\AdditionalDependency.lib;%(AdditionalDependencies)");
+ itemDefinition.AddMetadata(LinkItemsPredictor.AdditionalLibraryDirectoriesMetadata, @"..\AdditionalLibraryDirectory;%(AdditionalLibraryDirectories)");
+
+ ProjectItemElement item = projectRootElement.AddItem(itemType, @"..\someLib.lib");
+ item.AddMetadata(LinkItemsPredictor.AdditionalDependenciesMetadata, @"..\AnotherAdditionalDependency.lib;%(AdditionalDependencies)");
+ item.AddMetadata(LinkItemsPredictor.AdditionalLibraryDirectoriesMetadata, @"..\AnotherAdditionalLibraryDirectory;%(AdditionalLibraryDirectories)");
+
+ ProjectInstance projectInstance = TestHelpers.CreateProjectInstanceFromRootElement(projectRootElement);
+
+ var expectedInputFiles = new[]
+ {
+ new PredictedItem("someLib.lib", nameof(LinkItemsPredictor)),
+ new PredictedItem("AdditionalDependency.lib", nameof(LinkItemsPredictor)),
+ new PredictedItem("AnotherAdditionalDependency.lib", nameof(LinkItemsPredictor)),
+ };
+ var expectedInputDirectories = new[]
+ {
+ new PredictedItem("AdditionalLibraryDirectory", nameof(LinkItemsPredictor)),
+ new PredictedItem("AnotherAdditionalLibraryDirectory", nameof(LinkItemsPredictor)),
+ };
+ new LinkItemsPredictor()
+ .GetProjectPredictions(projectInstance)
+ .AssertPredictions(
+ projectInstance,
+ expectedInputFiles.MakeAbsolute(Directory.GetCurrentDirectory()),
+ expectedInputDirectories.MakeAbsolute(Directory.GetCurrentDirectory()),
+ null,
+ null);
+ }
+
+ [Theory]
+ [InlineData(LinkItemsPredictor.LinkItemName)]
+ [InlineData(LinkItemsPredictor.LibItemName)]
+ [InlineData(LinkItemsPredictor.ImpLibItemName)]
+ public void DuplicatesAndSpaces(string itemType)
+ {
+ ProjectRootElement projectRootElement = ProjectRootElement.Create(@"src\project.vcxproj");
+
+ ProjectItemDefinitionElement itemDefinition = projectRootElement.AddItemDefinitionGroup().AddItemDefinition(itemType);
+ itemDefinition.AddMetadata(LinkItemsPredictor.AdditionalDependenciesMetadata, @"..\AdditionalDependency.lib;%(AdditionalDependencies)");
+ itemDefinition.AddMetadata(LinkItemsPredictor.AdditionalLibraryDirectoriesMetadata, @"..\AdditionalLibraryDirectory;%(AdditionalLibraryDirectories)");
+
+ ProjectItemElement item = projectRootElement.AddItem(itemType, @"..\someLib.lib");
+ item.AddMetadata(
+ LinkItemsPredictor.AdditionalDependenciesMetadata,
+ $@"%(AdditionalDependencies); ;;%(AdditionalDependencies);..\AnotherAdditionalDependency.lib;{Environment.NewLine}..\AnotherAdditionalDependency.lib;%(AdditionalDependencies)");
+ item.AddMetadata(
+ LinkItemsPredictor.AdditionalLibraryDirectoriesMetadata,
+ $@"%(AdditionalLibraryDirectories); ;;%(AdditionalLibraryDirectories);..\AnotherAdditionalLibraryDirectories;{Environment.NewLine}..\AnotherAdditionalLibraryDirectories;%(AdditionalLibraryDirectories)");
+
+ ProjectInstance projectInstance = TestHelpers.CreateProjectInstanceFromRootElement(projectRootElement);
+
+ var expectedInputFiles = new[]
+ {
+ new PredictedItem("someLib.lib", nameof(LinkItemsPredictor)),
+ new PredictedItem("AdditionalDependency.lib", nameof(LinkItemsPredictor)),
+ new PredictedItem("AnotherAdditionalDependency.lib", nameof(LinkItemsPredictor)),
+ };
+ var expectedInputDirectories = new[]
+ {
+ new PredictedItem("AdditionalLibraryDirectory", nameof(LinkItemsPredictor)),
+ new PredictedItem("AnotherAdditionalLibraryDirectories", nameof(LinkItemsPredictor)),
+ };
+ new LinkItemsPredictor()
+ .GetProjectPredictions(projectInstance)
+ .AssertPredictions(
+ projectInstance,
+ expectedInputFiles.MakeAbsolute(Directory.GetCurrentDirectory()),
+ expectedInputDirectories.MakeAbsolute(Directory.GetCurrentDirectory()),
+ null,
+ null);
+ }
+
+ [Fact]
+ public void SkipOtherProjectTypes()
+ {
+ ProjectRootElement projectRootElement = ProjectRootElement.Create(@"src\project.csproj");
+
+ ProjectItemDefinitionElement itemDefinition = projectRootElement.AddItemDefinitionGroup().AddItemDefinition(LinkItemsPredictor.LinkItemName);
+ itemDefinition.AddMetadata(LinkItemsPredictor.AdditionalDependenciesMetadata, @"..\AdditionalDependency.lib;%(AdditionalDependencies)");
+ itemDefinition.AddMetadata(LinkItemsPredictor.AdditionalLibraryDirectoriesMetadata, @"..\AdditionalLibraryDirectory;%(AdditionalLibraryDirectories)");
- ProjectInstance projectInstance = TestHelpers.CreateProjectInstanceFromRootElement(projectRootElement);
+ projectRootElement.AddItem(LinkItemsPredictor.LinkItemName, @"..\someLib.lib");
- new AdditionalIncludeDirectoriesPredictor()
- .GetProjectPredictions(projectInstance)
- .AssertNoPredictions();
- }
+ ProjectInstance projectInstance = TestHelpers.CreateProjectInstanceFromRootElement(projectRootElement);
+ new LinkItemsPredictor()
+ .GetProjectPredictions(projectInstance)
+ .AssertNoPredictions();
}
}
\ No newline at end of file