From 3de88c28844771af77dd346f19929665b11c6022 Mon Sep 17 00:00:00 2001 From: David Federman Date: Fri, 25 Oct 2024 12:59:19 -0700 Subject: [PATCH] Add predictions for deps.json and runtimeconfig.json files --- .../GenerateBuildDependencyFilePredictor.cs | 31 ++++ .../GeneratePublishDependencyFilePredictor.cs | 104 +++++++++++++ ...erateRuntimeConfigurationFilesPredictor.cs | 40 +++++ ...opyToOutputDirectoryItemsGraphPredictor.cs | 24 +++ ...pyToPublishDirectoryItemsGraphPredictor.cs | 31 ++++ src/BuildPrediction/ProjectPredictors.cs | 6 + ...nerateBuildDependencyFilePredictorTests.cs | 69 +++++++++ ...ratePublishDependencyFilePredictorTests.cs | 146 ++++++++++++++++++ ...RuntimeConfigurationFilesPredictorTests.cs | 118 ++++++++++++++ ...OutputDirectoryItemsGraphPredictorTests.cs | 76 +++++++-- ...ublishDirectoryItemsGraphPredictorTests.cs | 64 ++++++-- 11 files changed, 691 insertions(+), 18 deletions(-) create mode 100644 src/BuildPrediction/Predictors/GenerateBuildDependencyFilePredictor.cs create mode 100644 src/BuildPrediction/Predictors/GeneratePublishDependencyFilePredictor.cs create mode 100644 src/BuildPrediction/Predictors/GenerateRuntimeConfigurationFilesPredictor.cs create mode 100644 src/BuildPredictionTests/Predictors/GenerateBuildDependencyFilePredictorTests.cs create mode 100644 src/BuildPredictionTests/Predictors/GeneratePublishDependencyFilePredictorTests.cs create mode 100644 src/BuildPredictionTests/Predictors/GenerateRuntimeConfigurationFilesPredictorTests.cs diff --git a/src/BuildPrediction/Predictors/GenerateBuildDependencyFilePredictor.cs b/src/BuildPrediction/Predictors/GenerateBuildDependencyFilePredictor.cs new file mode 100644 index 0000000..5974613 --- /dev/null +++ b/src/BuildPrediction/Predictors/GenerateBuildDependencyFilePredictor.cs @@ -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; + +/// +/// Makes predictions based on the GenerateBuildDependencyFile target. +/// +public sealed class GenerateBuildDependencyFilePredictor : IProjectPredictor +{ + internal const string GenerateDependencyFilePropertyName = "GenerateDependencyFile"; + internal const string ProjectAssetsFilePropertyName = "ProjectAssetsFile"; + internal const string ProjectDepsFilePathPropertyName = "ProjectDepsFilePath"; + + /// + 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)); + } +} \ No newline at end of file diff --git a/src/BuildPrediction/Predictors/GeneratePublishDependencyFilePredictor.cs b/src/BuildPrediction/Predictors/GeneratePublishDependencyFilePredictor.cs new file mode 100644 index 0000000..bed8b8a --- /dev/null +++ b/src/BuildPrediction/Predictors/GeneratePublishDependencyFilePredictor.cs @@ -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; + +/// +/// Makes predictions based on the GeneratePublishDependencyFile target. +/// +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"; + + /// + 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. + } + + /// + /// Determines the value of _UseBuildDependencyFile by emulating the behavior from the _ComputeUseBuildDependencyFile target (and the _ComputePackageReferencePublish target). + /// + 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; + } + + /// + /// Calculates the effective value of $(PublishDepsFilePath). In unspecified, the default value is calculated inside the GeneratePublishDependencyFile target. + /// + /// + /// This can return null in the case of PublishSingleFile since the deps.json file is embedded within the single-file bundle. + /// + 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; + } +} \ No newline at end of file diff --git a/src/BuildPrediction/Predictors/GenerateRuntimeConfigurationFilesPredictor.cs b/src/BuildPrediction/Predictors/GenerateRuntimeConfigurationFilesPredictor.cs new file mode 100644 index 0000000..a7180b7 --- /dev/null +++ b/src/BuildPrediction/Predictors/GenerateRuntimeConfigurationFilesPredictor.cs @@ -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; + +/// +/// Makes predictions based on the GenerateRuntimeConfigurationFiles target. +/// +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"; + + /// + 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)); + } +} \ No newline at end of file diff --git a/src/BuildPrediction/Predictors/GetCopyToOutputDirectoryItemsGraphPredictor.cs b/src/BuildPrediction/Predictors/GetCopyToOutputDirectoryItemsGraphPredictor.cs index 9c1a95d..002d8fa 100644 --- a/src/BuildPrediction/Predictors/GetCopyToOutputDirectoryItemsGraphPredictor.cs +++ b/src/BuildPrediction/Predictors/GetCopyToOutputDirectoryItemsGraphPredictor.cs @@ -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"; /// public void PredictInputsAndOutputs(ProjectGraphNode projectGraphNode, ProjectPredictionReporter predictionReporter) @@ -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))); + } + } } } } diff --git a/src/BuildPrediction/Predictors/GetCopyToPublishDirectoryItemsGraphPredictor.cs b/src/BuildPrediction/Predictors/GetCopyToPublishDirectoryItemsGraphPredictor.cs index ec01839..f9fd669 100644 --- a/src/BuildPrediction/Predictors/GetCopyToPublishDirectoryItemsGraphPredictor.cs +++ b/src/BuildPrediction/Predictors/GetCopyToPublishDirectoryItemsGraphPredictor.cs @@ -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( diff --git a/src/BuildPrediction/ProjectPredictors.cs b/src/BuildPrediction/ProjectPredictors.cs index e61f721..b4b91e5 100644 --- a/src/BuildPrediction/ProjectPredictors.cs +++ b/src/BuildPrediction/ProjectPredictors.cs @@ -61,6 +61,9 @@ public static class ProjectPredictors /// /// /// + /// + /// + /// /// /// /// A collection of . @@ -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. }; diff --git a/src/BuildPredictionTests/Predictors/GenerateBuildDependencyFilePredictorTests.cs b/src/BuildPredictionTests/Predictors/GenerateBuildDependencyFilePredictorTests.cs new file mode 100644 index 0000000..60ed3bd --- /dev/null +++ b/src/BuildPredictionTests/Predictors/GenerateBuildDependencyFilePredictorTests.cs @@ -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); + } +} \ No newline at end of file diff --git a/src/BuildPredictionTests/Predictors/GeneratePublishDependencyFilePredictorTests.cs b/src/BuildPredictionTests/Predictors/GeneratePublishDependencyFilePredictorTests.cs new file mode 100644 index 0000000..889cb5b --- /dev/null +++ b/src/BuildPredictionTests/Predictors/GeneratePublishDependencyFilePredictorTests.cs @@ -0,0 +1,146 @@ +// 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 GeneratePublishDependencyFilePredictorTests +{ + [Fact] + public void DoesNotGenerateDependencyFile() + { + ProjectRootElement projectRootElement = ProjectRootElement.Create("project.csproj"); + + projectRootElement.AddProperty(GeneratePublishDependencyFilePredictor.IntermediateOutputPathPropertyName, @"obj\"); + projectRootElement.AddProperty(GeneratePublishDependencyFilePredictor.PublishDirPropertyName, @"bin\x64\Debug\net8.0\publish\"); + projectRootElement.AddProperty(GeneratePublishDependencyFilePredictor.ProjectDepsFileNamePropertyName, "project.deps.json"); + + 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 GeneratePublishDependencyFilePredictor() + .GetProjectPredictions(projectInstance) + .AssertPredictions( + projectInstance, + null, + null, + null, + null); + } + + [Fact] + public void UseBuildDependencyFile() + { + ProjectRootElement projectRootElement = ProjectRootElement.Create("project.csproj"); + projectRootElement.AddProperty(GenerateBuildDependencyFilePredictor.GenerateDependencyFilePropertyName, "true"); + + projectRootElement.AddProperty(GeneratePublishDependencyFilePredictor.IntermediateOutputPathPropertyName, @"obj\"); + projectRootElement.AddProperty(GeneratePublishDependencyFilePredictor.PublishDirPropertyName, @"bin\x64\Debug\net8.0\publish\"); + projectRootElement.AddProperty(GeneratePublishDependencyFilePredictor.ProjectDepsFileNamePropertyName, "project.deps.json"); + + 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 GeneratePublishDependencyFilePredictor() + .GetProjectPredictions(projectInstance) + .AssertPredictions( + projectInstance, + null, + null, + null, + null); + } + + [Fact] + public void PublishTrimmed() + { + ProjectRootElement projectRootElement = ProjectRootElement.Create("project.csproj"); + projectRootElement.AddProperty(GenerateBuildDependencyFilePredictor.GenerateDependencyFilePropertyName, "true"); + projectRootElement.AddProperty(GeneratePublishDependencyFilePredictor.SelfContainedPropertyName, "true"); + projectRootElement.AddProperty(GeneratePublishDependencyFilePredictor.PublishTrimmedPropertyName, "true"); + + projectRootElement.AddProperty(GeneratePublishDependencyFilePredictor.IntermediateOutputPathPropertyName, @"obj\"); + projectRootElement.AddProperty(GeneratePublishDependencyFilePredictor.PublishDirPropertyName, @"bin\x64\Debug\net8.0\publish\"); + projectRootElement.AddProperty(GeneratePublishDependencyFilePredictor.ProjectDepsFileNamePropertyName, "project.deps.json"); + + 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(GeneratePublishDependencyFilePredictor)), + }; + var expectedOutputFiles = new[] + { + new PredictedItem(@"bin\x64\Debug\net8.0\publish\project.deps.json", nameof(GeneratePublishDependencyFilePredictor)), + }; + + new GeneratePublishDependencyFilePredictor() + .GetProjectPredictions(projectInstance) + .AssertPredictions( + projectInstance, + expectedInputFiles, + null, + expectedOutputFiles.MakeAbsolute(projectRootElement.DirectoryPath), + null); + } + + [Fact] + public void PublishSingleFile() + { + ProjectRootElement projectRootElement = ProjectRootElement.Create("project.csproj"); + projectRootElement.AddProperty(GenerateBuildDependencyFilePredictor.GenerateDependencyFilePropertyName, "true"); + projectRootElement.AddProperty(GeneratePublishDependencyFilePredictor.SelfContainedPropertyName, "true"); + projectRootElement.AddProperty(GeneratePublishDependencyFilePredictor.PublishSingleFilePropertyName, "true"); + + projectRootElement.AddProperty(GeneratePublishDependencyFilePredictor.IntermediateOutputPathPropertyName, @"obj\"); + projectRootElement.AddProperty(GeneratePublishDependencyFilePredictor.PublishDirPropertyName, @"bin\x64\Debug\net8.0\publish\"); + projectRootElement.AddProperty(GeneratePublishDependencyFilePredictor.ProjectDepsFileNamePropertyName, "project.deps.json"); + + 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(GeneratePublishDependencyFilePredictor)), + }; + var expectedOutputFiles = new[] + { + new PredictedItem(@"obj\project.deps.json", nameof(GeneratePublishDependencyFilePredictor)), + }; + + new GeneratePublishDependencyFilePredictor() + .GetProjectPredictions(projectInstance) + .AssertPredictions( + projectInstance, + expectedInputFiles, + null, + expectedOutputFiles.MakeAbsolute(projectRootElement.DirectoryPath), + null); + } +} \ No newline at end of file diff --git a/src/BuildPredictionTests/Predictors/GenerateRuntimeConfigurationFilesPredictorTests.cs b/src/BuildPredictionTests/Predictors/GenerateRuntimeConfigurationFilesPredictorTests.cs new file mode 100644 index 0000000..8c07764 --- /dev/null +++ b/src/BuildPredictionTests/Predictors/GenerateRuntimeConfigurationFilesPredictorTests.cs @@ -0,0 +1,118 @@ +// 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 GenerateRuntimeConfigurationFilesPredictorTests +{ + private readonly string _rootDir; + + public GenerateRuntimeConfigurationFilesPredictorTests() + { + // Isolate each test into its own folder + _rootDir = Path.Combine(Directory.GetCurrentDirectory(), nameof(GenerateRuntimeConfigurationFilesPredictorTests), Guid.NewGuid().ToString()); + Directory.CreateDirectory(_rootDir); + } + + [Fact] + public void DoesNotGenerateRuntimeConfigurationFiles() + { + ProjectRootElement projectRootElement = ProjectRootElement.Create(Path.Combine(_rootDir, @"src\project.csproj")); + + string userRuntimeConfig = Path.Combine(projectRootElement.DirectoryPath, @"runtimeconfig.template.json"); + projectRootElement.AddProperty(GenerateRuntimeConfigurationFilesPredictor.UserRuntimeConfigPropertyName, userRuntimeConfig); + + string projectRuntimeConfigFilePath = Path.Combine(projectRootElement.DirectoryPath, @"bin\x64\Debug\net8.0\project.runtimeconfig.json"); + projectRootElement.AddProperty(GenerateRuntimeConfigurationFilesPredictor.ProjectRuntimeConfigFilePathPropertyName, projectRuntimeConfigFilePath); + + ProjectInstance projectInstance = TestHelpers.CreateProjectInstanceFromRootElement(projectRootElement); + + new GenerateRuntimeConfigurationFilesPredictor() + .GetProjectPredictions(projectInstance) + .AssertPredictions( + projectInstance, + null, + null, + null, + null); + } + + [Fact] + public void GeneratesRuntimeConfigurationFiles() + { + ProjectRootElement projectRootElement = ProjectRootElement.Create(Path.Combine(_rootDir, @"src\project.csproj")); + projectRootElement.AddProperty(GenerateRuntimeConfigurationFilesPredictor.GenerateRuntimeConfigurationFilesPropertyName, "true"); + + string userRuntimeConfig = Path.Combine(projectRootElement.DirectoryPath, @"runtimeconfig.template.json"); + projectRootElement.AddProperty(GenerateRuntimeConfigurationFilesPredictor.UserRuntimeConfigPropertyName, userRuntimeConfig); + + string projectRuntimeConfigFilePath = Path.Combine(projectRootElement.DirectoryPath, @"bin\x64\Debug\net8.0\project.runtimeconfig.json"); + projectRootElement.AddProperty(GenerateRuntimeConfigurationFilesPredictor.ProjectRuntimeConfigFilePathPropertyName, projectRuntimeConfigFilePath); + + string projectRuntimeConfigDevFilePath = Path.Combine(projectRootElement.DirectoryPath, @"bin\x64\Debug\net8.0\project.runtimeconfig.dev.json"); + projectRootElement.AddProperty(GenerateRuntimeConfigurationFilesPredictor.ProjectRuntimeConfigDevFilePathPropertyName, projectRuntimeConfigDevFilePath); + + ProjectInstance projectInstance = TestHelpers.CreateProjectInstanceFromRootElement(projectRootElement); + + var expectedOutputFiles = new[] + { + new PredictedItem(projectRuntimeConfigFilePath, nameof(GenerateRuntimeConfigurationFilesPredictor)), + new PredictedItem(projectRuntimeConfigDevFilePath, nameof(GenerateRuntimeConfigurationFilesPredictor)), + }; + + new GenerateRuntimeConfigurationFilesPredictor() + .GetProjectPredictions(projectInstance) + .AssertPredictions( + projectInstance, + null, + null, + expectedOutputFiles, + null); + } + + [Fact] + public void UserRuntimeConfigExists() + { + ProjectRootElement projectRootElement = ProjectRootElement.Create(Path.Combine(_rootDir, @"src\project.csproj")); + projectRootElement.AddProperty(GenerateRuntimeConfigurationFilesPredictor.GenerateRuntimeConfigurationFilesPropertyName, "true"); + + string userRuntimeConfig = Path.Combine(projectRootElement.DirectoryPath, @"runtimeconfig.template.json"); + projectRootElement.AddProperty(GenerateRuntimeConfigurationFilesPredictor.UserRuntimeConfigPropertyName, userRuntimeConfig); + Directory.CreateDirectory(projectRootElement.DirectoryPath); + File.WriteAllText(userRuntimeConfig, "dummy"); + + string projectRuntimeConfigFilePath = Path.Combine(projectRootElement.DirectoryPath, @"bin\x64\Debug\net8.0\project.runtimeconfig.json"); + projectRootElement.AddProperty(GenerateRuntimeConfigurationFilesPredictor.ProjectRuntimeConfigFilePathPropertyName, projectRuntimeConfigFilePath); + + string projectRuntimeConfigDevFilePath = Path.Combine(projectRootElement.DirectoryPath, @"bin\x64\Debug\net8.0\project.runtimeconfig.dev.json"); + projectRootElement.AddProperty(GenerateRuntimeConfigurationFilesPredictor.ProjectRuntimeConfigDevFilePathPropertyName, projectRuntimeConfigDevFilePath); + + ProjectInstance projectInstance = TestHelpers.CreateProjectInstanceFromRootElement(projectRootElement); + + var expectedInputFiles = new[] + { + new PredictedItem(userRuntimeConfig, nameof(GenerateRuntimeConfigurationFilesPredictor)), + }; + var expectedOutputFiles = new[] + { + new PredictedItem(projectRuntimeConfigFilePath, nameof(GenerateRuntimeConfigurationFilesPredictor)), + new PredictedItem(projectRuntimeConfigDevFilePath, nameof(GenerateRuntimeConfigurationFilesPredictor)), + }; + + new GenerateRuntimeConfigurationFilesPredictor() + .GetProjectPredictions(projectInstance) + .AssertPredictions( + projectInstance, + expectedInputFiles, + null, + expectedOutputFiles, + null); + } +} \ No newline at end of file diff --git a/src/BuildPredictionTests/Predictors/GetCopyToOutputDirectoryItemsGraphPredictorTests.cs b/src/BuildPredictionTests/Predictors/GetCopyToOutputDirectoryItemsGraphPredictorTests.cs index 607690f..6f1ca15 100644 --- a/src/BuildPredictionTests/Predictors/GetCopyToOutputDirectoryItemsGraphPredictorTests.cs +++ b/src/BuildPredictionTests/Predictors/GetCopyToOutputDirectoryItemsGraphPredictorTests.cs @@ -33,7 +33,7 @@ public void NoCopy() 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 predictor should *not* be transitive + // 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"); @@ -62,7 +62,7 @@ public void UseCommonOutputDirectory() 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 predictor should *not* be transitive + // 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"); @@ -79,9 +79,11 @@ public void UseCommonOutputDirectory() } [Theory] - [InlineData(false)] - [InlineData(true)] - public void WithCopy(bool copyContentTransitively) + [InlineData(false, false)] + [InlineData(true, false)] + [InlineData(false, true)] + [InlineData(true, true)] + public void WithCopy(bool copyContentTransitively, bool hasRuntimeOutput) { string projectFile = Path.Combine(_rootDir, @"src\project.csproj"); ProjectRootElement projectRootElement = ProjectRootElement.Create(projectFile); @@ -92,10 +94,18 @@ public void WithCopy(bool copyContentTransitively) ProjectRootElement dep2 = CreateDependencyProject("dep2", shouldCopy); ProjectRootElement dep3 = CreateDependencyProject("dep3", shouldCopy); - projectRootElement.AddProperty(GetCopyToOutputDirectoryItemsGraphPredictor.MSBuildCopyContentTransitivelyPropertyName, copyContentTransitively.ToString()); - dep1.AddProperty(GetCopyToOutputDirectoryItemsGraphPredictor.MSBuildCopyContentTransitivelyPropertyName, copyContentTransitively.ToString()); - dep2.AddProperty(GetCopyToOutputDirectoryItemsGraphPredictor.MSBuildCopyContentTransitivelyPropertyName, copyContentTransitively.ToString()); - dep3.AddProperty(GetCopyToOutputDirectoryItemsGraphPredictor.MSBuildCopyContentTransitivelyPropertyName, copyContentTransitively.ToString()); + AddPropertyToAllProjects(GetCopyToOutputDirectoryItemsGraphPredictor.MSBuildCopyContentTransitivelyPropertyName, copyContentTransitively.ToString()); + + AddPropertyToAllProjects(GenerateBuildDependencyFilePredictor.ProjectDepsFilePathPropertyName, @"$(MSBuildProjectDirectory)\bin\$(MSBuildProjectName).deps.json"); + AddPropertyToAllProjects(GenerateRuntimeConfigurationFilesPredictor.ProjectRuntimeConfigFilePathPropertyName, @"$(MSBuildProjectDirectory)\bin\$(MSBuildProjectName).runtimeconfig.json"); + AddPropertyToAllProjects(GenerateRuntimeConfigurationFilesPredictor.ProjectRuntimeConfigDevFilePathPropertyName, @"$(MSBuildProjectDirectory)\bin\$(MSBuildProjectName).runtimeconfig.dev.json"); + + if (hasRuntimeOutput) + { + AddPropertyToAllProjects(GetCopyToOutputDirectoryItemsGraphPredictor.HasRuntimeOutputPropertyName, "true"); + AddPropertyToAllProjects(GenerateBuildDependencyFilePredictor.GenerateDependencyFilePropertyName, "true"); + AddPropertyToAllProjects(GenerateRuntimeConfigurationFilesPredictor.GenerateRuntimeConfigurationFilesPropertyName, "true"); + } // The main project depends on 1 and 2; 2 depends on 3; 3 depends on 1. projectRootElement.AddItem("ProjectReference", @"..\dep1\dep1.proj"); @@ -136,6 +146,29 @@ public void WithCopy(bool copyContentTransitively) new PredictedItem(@"src\bin\dep2.xaml", nameof(GetCopyToOutputDirectoryItemsGraphPredictor)), ]; + if (hasRuntimeOutput) + { + expectedInputFiles.AddRange( + [ + new PredictedItem(@"dep1\bin\dep1.deps.json", nameof(GetCopyToOutputDirectoryItemsGraphPredictor)), + new PredictedItem(@"dep1\bin\dep1.runtimeconfig.json", nameof(GetCopyToOutputDirectoryItemsGraphPredictor)), + new PredictedItem(@"dep1\bin\dep1.runtimeconfig.dev.json", nameof(GetCopyToOutputDirectoryItemsGraphPredictor)), + new PredictedItem(@"dep2\bin\dep2.deps.json", nameof(GetCopyToOutputDirectoryItemsGraphPredictor)), + new PredictedItem(@"dep2\bin\dep2.runtimeconfig.json", nameof(GetCopyToOutputDirectoryItemsGraphPredictor)), + new PredictedItem(@"dep2\bin\dep2.runtimeconfig.dev.json", nameof(GetCopyToOutputDirectoryItemsGraphPredictor)), + ]); + + expectedOutputFiles.AddRange( + [ + new PredictedItem(@"src\bin\dep1.deps.json", nameof(GetCopyToOutputDirectoryItemsGraphPredictor)), + new PredictedItem(@"src\bin\dep1.runtimeconfig.json", nameof(GetCopyToOutputDirectoryItemsGraphPredictor)), + new PredictedItem(@"src\bin\dep1.runtimeconfig.dev.json", nameof(GetCopyToOutputDirectoryItemsGraphPredictor)), + new PredictedItem(@"src\bin\dep2.deps.json", nameof(GetCopyToOutputDirectoryItemsGraphPredictor)), + new PredictedItem(@"src\bin\dep2.runtimeconfig.json", nameof(GetCopyToOutputDirectoryItemsGraphPredictor)), + new PredictedItem(@"src\bin\dep2.runtimeconfig.dev.json", nameof(GetCopyToOutputDirectoryItemsGraphPredictor)), + ]); + } + if (copyContentTransitively) { expectedInputFiles.AddRange( @@ -155,6 +188,23 @@ public void WithCopy(bool copyContentTransitively) new PredictedItem(@"src\bin\dep3.txt", nameof(GetCopyToOutputDirectoryItemsGraphPredictor)), new PredictedItem(@"src\bin\dep3.xaml", nameof(GetCopyToOutputDirectoryItemsGraphPredictor)), ]); + + if (hasRuntimeOutput) + { + expectedInputFiles.AddRange( + [ + new PredictedItem(@"dep3\bin\dep3.deps.json", nameof(GetCopyToOutputDirectoryItemsGraphPredictor)), + new PredictedItem(@"dep3\bin\dep3.runtimeconfig.json", nameof(GetCopyToOutputDirectoryItemsGraphPredictor)), + new PredictedItem(@"dep3\bin\dep3.runtimeconfig.dev.json", nameof(GetCopyToOutputDirectoryItemsGraphPredictor)), + ]); + + expectedOutputFiles.AddRange( + [ + new PredictedItem(@"src\bin\dep3.deps.json", nameof(GetCopyToOutputDirectoryItemsGraphPredictor)), + new PredictedItem(@"src\bin\dep3.runtimeconfig.json", nameof(GetCopyToOutputDirectoryItemsGraphPredictor)), + new PredictedItem(@"src\bin\dep3.runtimeconfig.dev.json", nameof(GetCopyToOutputDirectoryItemsGraphPredictor)), + ]); + } } new GetCopyToOutputDirectoryItemsGraphPredictor() @@ -165,6 +215,14 @@ public void WithCopy(bool copyContentTransitively) null, expectedOutputFiles, null); + + void AddPropertyToAllProjects(string propertyName, string propertyValue) + { + projectRootElement.AddProperty(propertyName, propertyValue); + dep1.AddProperty(propertyName, propertyValue); + dep2.AddProperty(propertyName, propertyValue); + dep3.AddProperty(propertyName, propertyValue); + } } private ProjectRootElement CreateDependencyProject(string projectName, bool shouldCopy) diff --git a/src/BuildPredictionTests/Predictors/GetCopyToPublishDirectoryItemsGraphPredictorTests.cs b/src/BuildPredictionTests/Predictors/GetCopyToPublishDirectoryItemsGraphPredictorTests.cs index 143cea3..de13944 100644 --- a/src/BuildPredictionTests/Predictors/GetCopyToPublishDirectoryItemsGraphPredictorTests.cs +++ b/src/BuildPredictionTests/Predictors/GetCopyToPublishDirectoryItemsGraphPredictorTests.cs @@ -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; @@ -49,19 +50,33 @@ public void PublishNoCopy() .AssertNoPredictions(); } - [Fact] - public void Publish() + [Theory] + [InlineData(false)] + [InlineData(true)] + public void Publish(bool hasRuntimeOutput) { string projectFile = Path.Combine(_rootDir, @"src\project.csproj"); ProjectRootElement projectRootElement = ProjectRootElement.Create(projectFile); projectRootElement.DefaultTargets = "Publish"; - projectRootElement.AddProperty(GetCopyToPublishDirectoryItemsGraphPredictor.PublishDirPropertyName, @"bin\Publish"); const bool shouldCopy = true; ProjectRootElement dep1 = CreateDependencyProject("dep1", shouldCopy); ProjectRootElement dep2 = CreateDependencyProject("dep2", shouldCopy); ProjectRootElement dep3 = CreateDependencyProject("dep3", shouldCopy); + AddPropertyToAllProjects(GetCopyToPublishDirectoryItemsGraphPredictor.PublishDirPropertyName, @"bin\Publish"); + AddPropertyToAllProjects(GeneratePublishDependencyFilePredictor.ProjectDepsFileNamePropertyName, "$(MSBuildProjectName).deps.json"); + AddPropertyToAllProjects(GenerateBuildDependencyFilePredictor.ProjectDepsFilePathPropertyName, @"$(MSBuildProjectDirectory)\bin\$(MSBuildProjectName).deps.json"); + AddPropertyToAllProjects(GenerateRuntimeConfigurationFilesPredictor.ProjectRuntimeConfigFilePathPropertyName, @"$(MSBuildProjectDirectory)\bin\$(MSBuildProjectName).runtimeconfig.json"); + AddPropertyToAllProjects(GenerateRuntimeConfigurationFilesPredictor.ProjectRuntimeConfigDevFilePathPropertyName, @"$(MSBuildProjectDirectory)\bin\$(MSBuildProjectName).runtimeconfig.dev.json"); + + if (hasRuntimeOutput) + { + AddPropertyToAllProjects(GetCopyToOutputDirectoryItemsGraphPredictor.HasRuntimeOutputPropertyName, "true"); + AddPropertyToAllProjects(GenerateBuildDependencyFilePredictor.GenerateDependencyFilePropertyName, "true"); + AddPropertyToAllProjects(GenerateRuntimeConfigurationFilesPredictor.GenerateRuntimeConfigurationFilesPropertyName, "true"); + } + // The main project depends on 1 and 2; 2 depends on 3; 3 depends on 1. Note that this should *not* be transitive projectRootElement.AddItem("ProjectReference", @"..\dep1\dep1.proj"); projectRootElement.AddItem("ProjectReference", @"..\dep2\dep2.proj"); @@ -73,8 +88,8 @@ public void Publish() dep2.Save(); dep3.Save(); - var expectedInputFiles = new[] - { + List expectedInputFiles = + [ new PredictedItem(@"dep1\dep1.xml", nameof(GetCopyToPublishDirectoryItemsGraphPredictor)), new PredictedItem(@"dep1\dep1.resx", nameof(GetCopyToPublishDirectoryItemsGraphPredictor)), new PredictedItem(@"dep1\dep1.cs", nameof(GetCopyToPublishDirectoryItemsGraphPredictor)), @@ -83,10 +98,10 @@ public void Publish() new PredictedItem(@"dep2\dep2.resx", nameof(GetCopyToPublishDirectoryItemsGraphPredictor)), new PredictedItem(@"dep2\dep2.cs", nameof(GetCopyToPublishDirectoryItemsGraphPredictor)), new PredictedItem(@"dep2\dep2.txt", nameof(GetCopyToPublishDirectoryItemsGraphPredictor)), - }; + ]; - var expectedOutputFiles = new[] - { + List expectedOutputFiles = + [ new PredictedItem(@"src\bin\Publish\dep1.xml", nameof(GetCopyToPublishDirectoryItemsGraphPredictor)), new PredictedItem(@"src\bin\Publish\dep1.resx", nameof(GetCopyToPublishDirectoryItemsGraphPredictor)), new PredictedItem(@"src\bin\Publish\dep1.cs", nameof(GetCopyToPublishDirectoryItemsGraphPredictor)), @@ -95,7 +110,30 @@ public void Publish() new PredictedItem(@"src\bin\Publish\dep2.resx", nameof(GetCopyToPublishDirectoryItemsGraphPredictor)), new PredictedItem(@"src\bin\Publish\dep2.cs", nameof(GetCopyToPublishDirectoryItemsGraphPredictor)), new PredictedItem(@"src\bin\Publish\dep2.txt", nameof(GetCopyToPublishDirectoryItemsGraphPredictor)), - }; + ]; + + if (hasRuntimeOutput) + { + expectedInputFiles.AddRange( + [ + new PredictedItem(@"src\bin\project.deps.json", nameof(GetCopyToPublishDirectoryItemsGraphPredictor)), + new PredictedItem(@"src\bin\project.runtimeconfig.json", nameof(GetCopyToPublishDirectoryItemsGraphPredictor)), + new PredictedItem(@"dep1\bin\dep1.deps.json", nameof(GetCopyToPublishDirectoryItemsGraphPredictor)), + new PredictedItem(@"dep1\bin\dep1.runtimeconfig.json", nameof(GetCopyToPublishDirectoryItemsGraphPredictor)), + new PredictedItem(@"dep2\bin\dep2.deps.json", nameof(GetCopyToPublishDirectoryItemsGraphPredictor)), + new PredictedItem(@"dep2\bin\dep2.runtimeconfig.json", nameof(GetCopyToPublishDirectoryItemsGraphPredictor)), + ]); + + expectedOutputFiles.AddRange( + [ + new PredictedItem(@"src\bin\Publish\project.deps.json", nameof(GetCopyToPublishDirectoryItemsGraphPredictor)), + new PredictedItem(@"src\bin\Publish\project.runtimeconfig.json", nameof(GetCopyToPublishDirectoryItemsGraphPredictor)), + new PredictedItem(@"src\bin\Publish\dep1.deps.json", nameof(GetCopyToPublishDirectoryItemsGraphPredictor)), + new PredictedItem(@"src\bin\Publish\dep1.runtimeconfig.json", nameof(GetCopyToPublishDirectoryItemsGraphPredictor)), + new PredictedItem(@"src\bin\Publish\dep2.deps.json", nameof(GetCopyToPublishDirectoryItemsGraphPredictor)), + new PredictedItem(@"src\bin\Publish\dep2.runtimeconfig.json", nameof(GetCopyToPublishDirectoryItemsGraphPredictor)), + ]); + } new GetCopyToPublishDirectoryItemsGraphPredictor() .GetProjectPredictions(projectFile) @@ -105,6 +143,14 @@ public void Publish() null, expectedOutputFiles, null); + + void AddPropertyToAllProjects(string propertyName, string propertyValue) + { + projectRootElement.AddProperty(propertyName, propertyValue); + dep1.AddProperty(propertyName, propertyValue); + dep2.AddProperty(propertyName, propertyValue); + dep3.AddProperty(propertyName, propertyValue); + } } [Fact]