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
46 changes: 46 additions & 0 deletions src/BuildPrediction/Predictors/DotnetSdkPredictor.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
// 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.Collections.Concurrent;
using System.IO;
using Microsoft.Build.Execution;

namespace Microsoft.Build.Prediction.Predictors;

/// <summary>
/// Makes predictions based on using Microsoft.NET.Sdk.
/// </summary>
public sealed class DotnetSdkPredictor : IProjectPredictor
{
internal const string UsingMicrosoftNETSdkPropertyName = "UsingMicrosoftNETSdk";

private readonly ConcurrentDictionary<string, bool> _globalJsonExistenceCache = new(PathComparer.Instance);

/// <inheritdoc/>
public void PredictInputsAndOutputs(
ProjectInstance projectInstance,
ProjectPredictionReporter predictionReporter)
{
var usingMicrosoftNETSdk = projectInstance.GetPropertyValue(UsingMicrosoftNETSdkPropertyName);
if (!usingMicrosoftNETSdk.Equals("true", StringComparison.OrdinalIgnoreCase))
{
return;
}

// Microsoft.NET.Sdk reads global.json.
string currentProbeDirectory = projectInstance.Directory;
while (currentProbeDirectory != null)
{
string globalJsonPath = Path.Combine(currentProbeDirectory, "global.json");
bool globalJsonPathExists = _globalJsonExistenceCache.GetOrAdd(globalJsonPath, File.Exists);
if (globalJsonPathExists)
{
predictionReporter.ReportInputFile(globalJsonPath);
break;
}

currentProbeDirectory = Path.GetDirectoryName(currentProbeDirectory);
}
}
}
2 changes: 2 additions & 0 deletions src/BuildPrediction/ProjectPredictors.cs
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ public static class ProjectPredictors
/// <item><see cref="ModuleDefinitionFilePredictor"/></item>
/// <item><see cref="CppContentFilesProjectOutputGroupPredictor"/></item>
/// <item><see cref="LinkItemsPredictor"/></item>
/// <item><see cref="DotnetSdkPredictor"/></item>
/// </list>
/// </remarks>
/// <returns>A collection of <see cref="IProjectPredictor"/>.</returns>
Expand Down Expand Up @@ -106,6 +107,7 @@ public static class ProjectPredictors
new ModuleDefinitionFilePredictor(),
new CppContentFilesProjectOutputGroupPredictor(),
new LinkItemsPredictor(),
new DotnetSdkPredictor(),
//// NOTE! When adding a new predictor here, be sure to update the doc comment above.
};

Expand Down
115 changes: 115 additions & 0 deletions src/BuildPredictionTests/Predictors/DotnetSdkPredictorTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
// 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 DotnetSdkPredictorTests
{
private readonly string _rootDir;

public DotnetSdkPredictorTests()
{
// Isolate each test into its own folder
_rootDir = Path.Combine(Directory.GetCurrentDirectory(), nameof(DotnetSdkPredictorTests), Guid.NewGuid().ToString());
Directory.CreateDirectory(_rootDir);
}

[Fact]
public void GlobalJsonExistsAdjacent()
{
ProjectRootElement projectRootElement = ProjectRootElement.Create(Path.Combine(_rootDir, @"src\project.csproj"));
projectRootElement.AddProperty(DotnetSdkPredictor.UsingMicrosoftNETSdkPropertyName, "true");

Directory.CreateDirectory(Path.Combine(_rootDir, "src"));
File.WriteAllText(Path.Combine(_rootDir, @"src\global.json"), "{}");

// Extraneous one above, which is not predicted as an input.
File.WriteAllText(Path.Combine(_rootDir, "global.json"), "{}");

ProjectInstance projectInstance = TestHelpers.CreateProjectInstanceFromRootElement(projectRootElement);

var expectedInputFiles = new[]
{
new PredictedItem(@"src\global.json", nameof(DotnetSdkPredictor)),
};

new DotnetSdkPredictor()
.GetProjectPredictions(projectInstance)
.AssertPredictions(
projectInstance,
expectedInputFiles.MakeAbsolute(_rootDir),
null,
null,
null);
}

[Fact]
public void GlobalJsonExistsAbove()
{
ProjectRootElement projectRootElement = ProjectRootElement.Create(Path.Combine(_rootDir, @"src\project.csproj"));
projectRootElement.AddProperty(DotnetSdkPredictor.UsingMicrosoftNETSdkPropertyName, "true");

File.WriteAllText(Path.Combine(_rootDir, "global.json"), "{}");

ProjectInstance projectInstance = TestHelpers.CreateProjectInstanceFromRootElement(projectRootElement);

var expectedInputFiles = new[]
{
new PredictedItem(@"global.json", nameof(DotnetSdkPredictor)),
};

new DotnetSdkPredictor()
.GetProjectPredictions(projectInstance)
.AssertPredictions(
projectInstance,
expectedInputFiles.MakeAbsolute(_rootDir),
null,
null,
null);
}

[Fact]
public void NoGlobalJsonExists()
{
ProjectRootElement projectRootElement = ProjectRootElement.Create(Path.Combine(_rootDir, @"src\project.csproj"));
projectRootElement.AddProperty(DotnetSdkPredictor.UsingMicrosoftNETSdkPropertyName, "true");

ProjectInstance projectInstance = TestHelpers.CreateProjectInstanceFromRootElement(projectRootElement);

new DotnetSdkPredictor()
.GetProjectPredictions(projectInstance)
.AssertPredictions(
projectInstance,
null,
null,
null,
null);
}

[Fact]
public void NotUsingDotnetSdk()
{
ProjectRootElement projectRootElement = ProjectRootElement.Create(Path.Combine(_rootDir, @"src\project.csproj"));

Directory.CreateDirectory(Path.Combine(_rootDir, "src"));
File.WriteAllText(Path.Combine(_rootDir, "global.json"), "{}");

ProjectInstance projectInstance = TestHelpers.CreateProjectInstanceFromRootElement(projectRootElement);

new DotnetSdkPredictor()
.GetProjectPredictions(projectInstance)
.AssertPredictions(
projectInstance,
null,
null,
null,
null);
}
}