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
@@ -0,0 +1,31 @@
// 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 Microsoft.Build.Execution;

namespace Microsoft.Build.Prediction.Predictors;

/// <summary>
/// Makes predictions based on the GenerateBuildDependencyFile target.
/// </summary>
public sealed class GenerateBuildDependencyFilePredictor : IProjectPredictor
{
internal const string GenerateDependencyFilePropertyName = "GenerateDependencyFile";
internal const string ProjectAssetsFilePropertyName = "ProjectAssetsFile";
internal const string ProjectDepsFilePathPropertyName = "ProjectDepsFilePath";

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

predictionReporter.ReportInputFile(projectInstance.GetPropertyValue(ProjectAssetsFilePropertyName));
predictionReporter.ReportOutputFile(projectInstance.GetPropertyValue(ProjectDepsFilePathPropertyName));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
// 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 Microsoft.Build.Execution;

namespace Microsoft.Build.Prediction.Predictors;

/// <summary>
/// Makes predictions based on the GeneratePublishDependencyFile target.
/// </summary>
public sealed class GeneratePublishDependencyFilePredictor : IProjectPredictor
{
internal const string PublishAotPropertyName = "PublishAot";
internal const string PublishDirPropertyName = "PublishDir";
internal const string PublishSingleFilePropertyName = "PublishSingleFile";
internal const string SelfContainedPropertyName = "SelfContained";
internal const string PreserveStoreLayoutPropertyName = "PreserveStoreLayout";
internal const string PublishTrimmedPropertyName = "PublishTrimmed";
internal const string RuntimeStorePackagesItemName = "RuntimeStorePackages";
internal const string PackageReferenceItemName = "PackageReference";
internal const string PrivateAssetsMetadataName = "PrivateAssets";
internal const string PublishMetadataName = "Publish";
internal const string PublishDepsFilePathPropertyName = "PublishDepsFilePath";
internal const string ProjectDepsFileNamePropertyName = "ProjectDepsFileName";
internal const string IntermediateOutputPathPropertyName = "IntermediateOutputPath";

/// <inheritdoc/>
public void PredictInputsAndOutputs(
ProjectInstance projectInstance,
ProjectPredictionReporter predictionReporter)
{
if (!projectInstance.GetPropertyValue(GenerateBuildDependencyFilePredictor.GenerateDependencyFilePropertyName).Equals("true", StringComparison.OrdinalIgnoreCase)
|| ShouldUseBuildDependencyFile(projectInstance)
|| projectInstance.GetPropertyValue(PublishAotPropertyName).Equals("true", StringComparison.OrdinalIgnoreCase))
{
return;
}

predictionReporter.ReportInputFile(projectInstance.GetPropertyValue(GenerateBuildDependencyFilePredictor.ProjectAssetsFilePropertyName));

string publishDepsFilePath = GetEffectivePublishDepsFilePath(projectInstance);
string intermediateDepsFilePath = publishDepsFilePath is not null
? publishDepsFilePath
: projectInstance.GetPropertyValue(IntermediateOutputPathPropertyName) + projectInstance.GetPropertyValue(ProjectDepsFileNamePropertyName);
predictionReporter.ReportOutputFile(intermediateDepsFilePath);

// Note: GetCopyToPublishDirectoryItemsGraphPredictor will predict the final (published) location for the publish deps file since that's the target which does that copy.
}

/// <summary>
/// Determines the value of _UseBuildDependencyFile by emulating the behavior from the _ComputeUseBuildDependencyFile target (and the _ComputePackageReferencePublish target).
/// </remarks>
internal static bool ShouldUseBuildDependencyFile(ProjectInstance projectInstance)
{
bool hasExcludeFromPublishPackageReference = false;
foreach (ProjectItemInstance packageReference in projectInstance.GetItems(PackageReferenceItemName))
{
string packageReferencePublishMetadata = packageReference.GetMetadataValue(PublishMetadataName);
if (packageReferencePublishMetadata.Equals("false", StringComparison.OrdinalIgnoreCase))
{
hasExcludeFromPublishPackageReference = true;
break;
}

if (string.IsNullOrEmpty(packageReferencePublishMetadata)
&& packageReference.GetMetadataValue(PrivateAssetsMetadataName).Equals("All", StringComparison.OrdinalIgnoreCase))
{
hasExcludeFromPublishPackageReference = true;
break;
}
}

bool trimRuntimeAssets = projectInstance.GetPropertyValue(PublishSingleFilePropertyName).Equals("true", StringComparison.OrdinalIgnoreCase)
&& projectInstance.GetPropertyValue(SelfContainedPropertyName).Equals("true", StringComparison.OrdinalIgnoreCase);
return !hasExcludeFromPublishPackageReference
&& projectInstance.GetItems(RuntimeStorePackagesItemName).Count == 0
&& !projectInstance.GetPropertyValue(PreserveStoreLayoutPropertyName).Equals("true", StringComparison.OrdinalIgnoreCase)
&& !projectInstance.GetPropertyValue(PublishTrimmedPropertyName).Equals("true", StringComparison.OrdinalIgnoreCase)
&& !trimRuntimeAssets;
}

/// <summary>
/// Calculates the effective value of $(PublishDepsFilePath). In unspecified, the default value is calculated inside the GeneratePublishDependencyFile target.
/// </summary>
/// <remarks>
/// This can return null in the case of PublishSingleFile since the deps.json file is embedded within the single-file bundle.
/// </remarks>
internal static string GetEffectivePublishDepsFilePath(ProjectInstance projectInstance)
{
string publishDepsFilePath = projectInstance.GetPropertyValue(PublishDepsFilePathPropertyName);
if (!string.IsNullOrEmpty(publishDepsFilePath))
{
return publishDepsFilePath;
}

if (!projectInstance.GetPropertyValue(PublishSingleFilePropertyName).Equals("true", StringComparison.OrdinalIgnoreCase))
{
return projectInstance.GetPropertyValue(PublishDirPropertyName) + projectInstance.GetPropertyValue(ProjectDepsFileNamePropertyName);
}

return null;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
// 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;

/// <summary>
/// Makes predictions based on the GenerateRuntimeConfigurationFiles target.
/// </summary>
public sealed class GenerateRuntimeConfigurationFilesPredictor : IProjectPredictor
{
internal const string GenerateRuntimeConfigurationFilesPropertyName = "GenerateRuntimeConfigurationFiles";
internal const string UserRuntimeConfigPropertyName = "UserRuntimeConfig";
internal const string ProjectRuntimeConfigFilePathPropertyName = "ProjectRuntimeConfigFilePath";
internal const string ProjectRuntimeConfigDevFilePathPropertyName = "ProjectRuntimeConfigDevFilePath";

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

string userRuntimeConfig = projectInstance.GetPropertyValue(UserRuntimeConfigPropertyName);
string userRuntimeConfigFullPath = Path.Combine(projectInstance.Directory, userRuntimeConfig);
if (File.Exists(userRuntimeConfigFullPath))
{
predictionReporter.ReportInputFile(userRuntimeConfigFullPath);
}

predictionReporter.ReportOutputFile(projectInstance.GetPropertyValue(ProjectRuntimeConfigFilePathPropertyName));
predictionReporter.ReportOutputFile(projectInstance.GetPropertyValue(ProjectRuntimeConfigDevFilePathPropertyName));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ public sealed class GetCopyToOutputDirectoryItemsGraphPredictor : IProjectGraphP
internal const string UseCommonOutputDirectoryPropertyName = "UseCommonOutputDirectory";
internal const string OutDirPropertyName = "OutDir";
internal const string MSBuildCopyContentTransitivelyPropertyName = "MSBuildCopyContentTransitively";
internal const string HasRuntimeOutputPropertyName = "HasRuntimeOutput";

/// <inheritdoc/>
public void PredictInputsAndOutputs(ProjectGraphNode projectGraphNode, ProjectPredictionReporter predictionReporter)
Expand Down Expand Up @@ -62,6 +63,29 @@ private static void PredictInputsAndOutputs(

// Process each item type considered in GetCopyToOutputDirectoryXamlAppDefs
ReportCopyToOutputDirectoryItemsAsInputs(dependency.ProjectInstance, XamlAppDefPredictor.XamlAppDefItemName, outDir, predictionReporter);

// Process items added by AddDepsJsonAndRuntimeConfigToCopyItemsForReferencingProjects
bool hasRuntimeOutput = dependency.ProjectInstance.GetPropertyValue(HasRuntimeOutputPropertyName).Equals("true", StringComparison.OrdinalIgnoreCase);
if (hasRuntimeOutput)
{
if (dependency.ProjectInstance.GetPropertyValue(GenerateBuildDependencyFilePredictor.GenerateDependencyFilePropertyName).Equals("true", StringComparison.OrdinalIgnoreCase))
{
string projectDepsFilePath = dependency.ProjectInstance.GetPropertyValue(GenerateBuildDependencyFilePredictor.ProjectDepsFilePathPropertyName);
predictionReporter.ReportInputFile(projectDepsFilePath);
predictionReporter.ReportOutputFile(Path.Combine(outDir, Path.GetFileName(projectDepsFilePath)));
}

if (dependency.ProjectInstance.GetPropertyValue(GenerateRuntimeConfigurationFilesPredictor.GenerateRuntimeConfigurationFilesPropertyName).Equals("true", StringComparison.OrdinalIgnoreCase))
{
string projectRuntimeConfigFilePath = dependency.ProjectInstance.GetPropertyValue(GenerateRuntimeConfigurationFilesPredictor.ProjectRuntimeConfigFilePathPropertyName);
predictionReporter.ReportInputFile(projectRuntimeConfigFilePath);
predictionReporter.ReportOutputFile(Path.Combine(outDir, Path.GetFileName(projectRuntimeConfigFilePath)));

string projectRuntimeConfigDevFilePath = dependency.ProjectInstance.GetPropertyValue(GenerateRuntimeConfigurationFilesPredictor.ProjectRuntimeConfigDevFilePathPropertyName);
predictionReporter.ReportInputFile(projectRuntimeConfigDevFilePath);
predictionReporter.ReportOutputFile(Path.Combine(outDir, Path.GetFileName(projectRuntimeConfigDevFilePath)));
}
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,37 @@ private static void ReportCopyToPublishDirectoryItems(
ReportCopyToPublishDirectoryItems(projectInstance, EmbeddedResourceItemsPredictor.EmbeddedResourceItemName, publishDir, predictionReporter);
ReportCopyToPublishDirectoryItems(projectInstance, CompileItemsPredictor.CompileItemName, publishDir, predictionReporter);
ReportCopyToPublishDirectoryItems(projectInstance, NoneItemsPredictor.NoneItemName, publishDir, predictionReporter);

// Process items added by AddDepsJsonAndRuntimeConfigToPublishItemsForReferencingProjects
bool hasRuntimeOutput = projectInstance.GetPropertyValue(GetCopyToOutputDirectoryItemsGraphPredictor.HasRuntimeOutputPropertyName).Equals("true", StringComparison.OrdinalIgnoreCase);
if (hasRuntimeOutput)
{
if (projectInstance.GetPropertyValue(GenerateBuildDependencyFilePredictor.GenerateDependencyFilePropertyName).Equals("true", StringComparison.OrdinalIgnoreCase))
{
if (GeneratePublishDependencyFilePredictor.ShouldUseBuildDependencyFile(projectInstance))
{
string projectDepsFilePath = projectInstance.GetPropertyValue(GenerateBuildDependencyFilePredictor.ProjectDepsFilePathPropertyName);
predictionReporter.ReportInputFile(projectDepsFilePath);
predictionReporter.ReportOutputFile(Path.Combine(publishDir, Path.GetFileName(projectDepsFilePath)));
}
else
{
string publishDepsFilePath = GeneratePublishDependencyFilePredictor.GetEffectivePublishDepsFilePath(projectInstance);
if (publishDepsFilePath is not null)
{
predictionReporter.ReportInputFile(publishDepsFilePath);
predictionReporter.ReportOutputFile(Path.Combine(publishDir, Path.GetFileName(publishDepsFilePath)));
}
}
}

if (projectInstance.GetPropertyValue(GenerateRuntimeConfigurationFilesPredictor.GenerateRuntimeConfigurationFilesPropertyName).Equals("true", StringComparison.OrdinalIgnoreCase))
{
string projectRuntimeConfigFilePath = projectInstance.GetPropertyValue(GenerateRuntimeConfigurationFilesPredictor.ProjectRuntimeConfigFilePathPropertyName);
predictionReporter.ReportInputFile(projectRuntimeConfigFilePath);
predictionReporter.ReportOutputFile(Path.Combine(publishDir, Path.GetFileName(projectRuntimeConfigFilePath)));
}
}
}

private static void ReportCopyToPublishDirectoryItems(
Expand Down
6 changes: 6 additions & 0 deletions src/BuildPrediction/ProjectPredictors.cs
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,9 @@ public static class ProjectPredictors
/// <item><see cref="CppContentFilesProjectOutputGroupPredictor"/></item>
/// <item><see cref="LinkItemsPredictor"/></item>
/// <item><see cref="DotnetSdkPredictor"/></item>
/// <item><see cref="GenerateBuildDependencyFilePredictor"/></item>
/// <item><see cref="GeneratePublishDependencyFilePredictor"/></item>
/// <item><see cref="GenerateRuntimeConfigurationFilesPredictor"/></item>
/// </list>
/// </remarks>
/// <returns>A collection of <see cref="IProjectPredictor"/>.</returns>
Expand Down Expand Up @@ -108,6 +111,9 @@ public static class ProjectPredictors
new CppContentFilesProjectOutputGroupPredictor(),
new LinkItemsPredictor(),
new DotnetSdkPredictor(),
new GenerateBuildDependencyFilePredictor(),
new GeneratePublishDependencyFilePredictor(),
new GenerateRuntimeConfigurationFilesPredictor(),
//// NOTE! When adding a new predictor here, be sure to update the doc comment above.
};

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

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 GenerateBuildDependencyFilePredictorTests
{
[Fact]
public void DoesNotGenerateDependencyFile()
{
ProjectRootElement projectRootElement = ProjectRootElement.Create("project.csproj");

string projectAssetsFile = Path.Combine(projectRootElement.DirectoryPath, @"obj\project.assets.json");
projectRootElement.AddProperty(GenerateBuildDependencyFilePredictor.ProjectAssetsFilePropertyName, projectAssetsFile);

string projectDepsFilePath = Path.Combine(projectRootElement.DirectoryPath, @"bin\x64\Debug\net8.0\project.deps.json");
projectRootElement.AddProperty(GenerateBuildDependencyFilePredictor.ProjectDepsFilePathPropertyName, projectDepsFilePath);

ProjectInstance projectInstance = TestHelpers.CreateProjectInstanceFromRootElement(projectRootElement);

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

[Fact]
public void GeneratesDependencyFile()
{
ProjectRootElement projectRootElement = ProjectRootElement.Create("project.csproj");
projectRootElement.AddProperty(GenerateBuildDependencyFilePredictor.GenerateDependencyFilePropertyName, "true");

string projectAssetsFile = Path.Combine(projectRootElement.DirectoryPath, @"obj\project.assets.json");
projectRootElement.AddProperty(GenerateBuildDependencyFilePredictor.ProjectAssetsFilePropertyName, projectAssetsFile);

string projectDepsFilePath = Path.Combine(projectRootElement.DirectoryPath, @"bin\x64\Debug\net8.0\project.deps.json");
projectRootElement.AddProperty(GenerateBuildDependencyFilePredictor.ProjectDepsFilePathPropertyName, projectDepsFilePath);

ProjectInstance projectInstance = TestHelpers.CreateProjectInstanceFromRootElement(projectRootElement);

var expectedInputFiles = new[]
{
new PredictedItem(projectAssetsFile, nameof(GenerateBuildDependencyFilePredictor)),
};
var expectedOutputFiles = new[]
{
new PredictedItem(projectDepsFilePath, nameof(GenerateBuildDependencyFilePredictor)),
};

new GenerateBuildDependencyFilePredictor()
.GetProjectPredictions(projectInstance)
.AssertPredictions(
projectInstance,
expectedInputFiles,
null,
expectedOutputFiles,
null);
}
}
Loading