diff --git a/src/Cli/Microsoft.DotNet.Cli.Utils/MSBuildArgs.cs b/src/Cli/Microsoft.DotNet.Cli.Utils/MSBuildArgs.cs index 7fedc17f3878..cb8befe5fab2 100644 --- a/src/Cli/Microsoft.DotNet.Cli.Utils/MSBuildArgs.cs +++ b/src/Cli/Microsoft.DotNet.Cli.Utils/MSBuildArgs.cs @@ -382,4 +382,12 @@ public void ApplyPropertiesToRestore() RestoreGlobalProperties = new(newdict); } } + + internal string[]? GetResolvedTargets() + => this switch + { + { RequestedTargets: null or { Length: 0 } } => GetTargetResult, + { GetTargetResult: null or { Length: 0 } } => RequestedTargets, + _ => [.. RequestedTargets.Union(GetTargetResult, StringComparer.OrdinalIgnoreCase)] + }; } diff --git a/src/Cli/Microsoft.TemplateEngine.Cli/Microsoft.TemplateEngine.Cli.csproj b/src/Cli/Microsoft.TemplateEngine.Cli/Microsoft.TemplateEngine.Cli.csproj index 6504a731f36f..cdfa92ae8ce1 100644 --- a/src/Cli/Microsoft.TemplateEngine.Cli/Microsoft.TemplateEngine.Cli.csproj +++ b/src/Cli/Microsoft.TemplateEngine.Cli/Microsoft.TemplateEngine.Cli.csproj @@ -20,9 +20,10 @@ - - - + + + + diff --git a/src/Cli/Microsoft.TemplateEngine.Cli/TelemetryHelper.cs b/src/Cli/Microsoft.TemplateEngine.Cli/TelemetryHelper.cs index f52d1fc17e14..edf67f5b4802 100644 --- a/src/Cli/Microsoft.TemplateEngine.Cli/TelemetryHelper.cs +++ b/src/Cli/Microsoft.TemplateEngine.Cli/TelemetryHelper.cs @@ -1,7 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using Microsoft.DotNet.Cli.Utils; +using Microsoft.DotNet.Utilities; using Microsoft.TemplateEngine.Abstractions; using Microsoft.TemplateEngine.Utils; diff --git a/src/Cli/Microsoft.TemplateEngine.Cli/TemplateInvoker.cs b/src/Cli/Microsoft.TemplateEngine.Cli/TemplateInvoker.cs index a34f201e095c..f3e29f93befe 100644 --- a/src/Cli/Microsoft.TemplateEngine.Cli/TemplateInvoker.cs +++ b/src/Cli/Microsoft.TemplateEngine.Cli/TemplateInvoker.cs @@ -4,6 +4,7 @@ using System.Text.RegularExpressions; using Microsoft.DotNet.Cli.Utils; using Microsoft.DotNet.Cli.Utils.Extensions; +using Microsoft.DotNet.Utilities; using Microsoft.TemplateEngine.Abstractions; using Microsoft.TemplateEngine.Abstractions.TemplatePackage; using Microsoft.TemplateEngine.Cli.Commands; diff --git a/src/Cli/dotnet/CommandFactory/CommandResolution/CompositeCommandResolver.cs b/src/Cli/dotnet/CommandFactory/CommandResolution/CompositeCommandResolver.cs index 0ab407749a36..a0e54bcb7c80 100644 --- a/src/Cli/dotnet/CommandFactory/CommandResolution/CompositeCommandResolver.cs +++ b/src/Cli/dotnet/CommandFactory/CommandResolution/CompositeCommandResolver.cs @@ -4,6 +4,7 @@ #nullable disable using Microsoft.DotNet.Cli.Utils; +using Microsoft.DotNet.Utilities; namespace Microsoft.DotNet.Cli.CommandFactory.CommandResolution; diff --git a/src/Cli/dotnet/Commands/Build/BuildCommand.cs b/src/Cli/dotnet/Commands/Build/BuildCommand.cs index 9b6dabdb020a..8c67ed030f36 100644 --- a/src/Cli/dotnet/Commands/Build/BuildCommand.cs +++ b/src/Cli/dotnet/Commands/Build/BuildCommand.cs @@ -30,15 +30,14 @@ public static CommandBase FromParseResult(ParseResult parseResult, string? msbui return CommandFactory.CreateVirtualOrPhysicalCommand( BuildCommandParser.GetCommand(), BuildCommandDefinition.SlnOrProjectOrFileArgument, - (msbuildArgs, appFilePath) => new VirtualProjectBuildingCommand( + configureVirtualCommand: (msbuildArgs, appFilePath) => new VirtualProjectBuildingCommand( entryPointFileFullPath: Path.GetFullPath(appFilePath), - msbuildArgs: msbuildArgs - ) + msbuildArgs: msbuildArgs) { NoRestore = noRestore, NoCache = true, }, - (msbuildArgs, msbuildPath) => new RestoringCommand( + createPhysicalCommand: (msbuildArgs, msbuildPath) => new RestoringCommand( msbuildArgs: msbuildArgs.CloneWithAdditionalArgs("-consoleloggerparameters:Summary"), noRestore: noRestore, msbuildPath: msbuildPath diff --git a/src/Cli/dotnet/Commands/Clean/CleanCommand.cs b/src/Cli/dotnet/Commands/Clean/CleanCommand.cs index 15a183a4da6d..e2074b7a7d58 100644 --- a/src/Cli/dotnet/Commands/Clean/CleanCommand.cs +++ b/src/Cli/dotnet/Commands/Clean/CleanCommand.cs @@ -24,8 +24,8 @@ public static CommandBase FromParseResult(ParseResult result, string? msbuildPat CleanCommandParser.GetCommand(), CleanCommandDefinition.SlnOrProjectOrFileArgument, static (msbuildArgs, appFilePath) => new VirtualProjectBuildingCommand( - entryPointFileFullPath: appFilePath, - msbuildArgs: msbuildArgs) + entryPointFileFullPath: appFilePath, + msbuildArgs: msbuildArgs) { NoBuild = false, NoRestore = true, diff --git a/src/Cli/dotnet/Commands/Clean/FileBasedAppArtifacts/CleanFileBasedAppArtifactsCommand.cs b/src/Cli/dotnet/Commands/Clean/FileBasedAppArtifacts/CleanFileBasedAppArtifactsCommand.cs index 6654ab3697b9..f449417b0340 100644 --- a/src/Cli/dotnet/Commands/Clean/FileBasedAppArtifacts/CleanFileBasedAppArtifactsCommand.cs +++ b/src/Cli/dotnet/Commands/Clean/FileBasedAppArtifacts/CleanFileBasedAppArtifactsCommand.cs @@ -7,6 +7,7 @@ using Microsoft.DotNet.Cli.Commands.Run; using Microsoft.DotNet.Cli.Utils; using Microsoft.DotNet.Cli.Utils.Extensions; +using Microsoft.DotNet.ProjectTools; namespace Microsoft.DotNet.Cli.Commands.Clean.FileBasedAppArtifacts; @@ -60,7 +61,7 @@ public override int Execute() private IEnumerable GetFoldersToRemove() { - var directory = new DirectoryInfo(VirtualProjectBuildingCommand.GetTempSubdirectory()); + var directory = new DirectoryInfo(VirtualProjectBuilder.GetTempSubdirectory()); if (!directory.Exists) { @@ -84,7 +85,7 @@ private IEnumerable GetFoldersToRemove() private static FileInfo GetMetadataFile() { - return new FileInfo(VirtualProjectBuildingCommand.GetTempSubpath(RunFileArtifactsMetadata.FilePath)); + return new FileInfo(VirtualProjectBuilder.GetTempSubpath(RunFileArtifactsMetadata.FilePath)); } private FileStream? OpenMetadataFile() diff --git a/src/Cli/dotnet/Commands/CommandFactory.cs b/src/Cli/dotnet/Commands/CommandFactory.cs index 40bc500082b3..8d857d8383e8 100644 --- a/src/Cli/dotnet/Commands/CommandFactory.cs +++ b/src/Cli/dotnet/Commands/CommandFactory.cs @@ -5,6 +5,7 @@ using Microsoft.DotNet.Cli.CommandLine; using Microsoft.DotNet.Cli.Commands.Run; using Microsoft.DotNet.Cli.Utils; +using Microsoft.DotNet.ProjectTools; namespace Microsoft.DotNet.Cli.Commands; @@ -23,7 +24,7 @@ internal static CommandBase CreateVirtualOrPhysicalCommand( var args = parseResult.GetValue(catchAllUserInputArgument) ?? []; LoggerUtility.SeparateBinLogArguments(args, out var binLogArgs, out var nonBinLogArgs); var forwardedArgs = parseResult.OptionValuesToBeForwarded(command); - if (nonBinLogArgs is [{ } arg] && VirtualProjectBuildingCommand.IsValidEntryPointPath(arg)) + if (nonBinLogArgs is [{ } arg] && VirtualProjectBuilder.IsValidEntryPointPath(arg)) { var msbuildArgs = MSBuildArgs.AnalyzeMSBuildArguments([.. forwardedArgs, .. binLogArgs], [ diff --git a/src/Cli/dotnet/Commands/Hidden/InternalReportInstallSuccess/InternalReportInstallSuccessCommand.cs b/src/Cli/dotnet/Commands/Hidden/InternalReportInstallSuccess/InternalReportInstallSuccessCommand.cs index af7e3df0c98e..057e00d4aa2d 100644 --- a/src/Cli/dotnet/Commands/Hidden/InternalReportInstallSuccess/InternalReportInstallSuccessCommand.cs +++ b/src/Cli/dotnet/Commands/Hidden/InternalReportInstallSuccess/InternalReportInstallSuccessCommand.cs @@ -8,6 +8,7 @@ using Microsoft.DotNet.Cli.Telemetry; using Microsoft.DotNet.Cli.Utils; using Microsoft.DotNet.Configurer; +using Microsoft.DotNet.Utilities; namespace Microsoft.DotNet.Cli.Commands.Hidden.InternalReportInstallSuccess; diff --git a/src/Cli/dotnet/Commands/MSBuild/MSBuildLogger.cs b/src/Cli/dotnet/Commands/MSBuild/MSBuildLogger.cs index 265b1eafbb76..d50dfd57a071 100644 --- a/src/Cli/dotnet/Commands/MSBuild/MSBuildLogger.cs +++ b/src/Cli/dotnet/Commands/MSBuild/MSBuildLogger.cs @@ -6,6 +6,7 @@ using Microsoft.DotNet.Cli.Telemetry; using Microsoft.DotNet.Cli.Utils; using Microsoft.DotNet.Configurer; +using Microsoft.DotNet.Utilities; namespace Microsoft.DotNet.Cli.Commands.MSBuild; diff --git a/src/Cli/dotnet/Commands/New/MSBuildEvaluation/MSBuildEvaluator.cs b/src/Cli/dotnet/Commands/New/MSBuildEvaluation/MSBuildEvaluator.cs index a79c2ec2af7c..318f1ec64418 100644 --- a/src/Cli/dotnet/Commands/New/MSBuildEvaluation/MSBuildEvaluator.cs +++ b/src/Cli/dotnet/Commands/New/MSBuildEvaluation/MSBuildEvaluator.cs @@ -4,6 +4,7 @@ using System.Diagnostics; using Microsoft.Build.Evaluation; using Microsoft.DotNet.Cli.Utils; +using Microsoft.DotNet.Utilities; using Microsoft.Extensions.Logging; using Microsoft.TemplateEngine.Abstractions; using Microsoft.TemplateEngine.Utils; diff --git a/src/Cli/dotnet/Commands/Package/Add/PackageAddCommand.cs b/src/Cli/dotnet/Commands/Package/Add/PackageAddCommand.cs index c88d6cba80e7..97d1df80e369 100644 --- a/src/Cli/dotnet/Commands/Package/Add/PackageAddCommand.cs +++ b/src/Cli/dotnet/Commands/Package/Add/PackageAddCommand.cs @@ -6,13 +6,13 @@ using Microsoft.Build.Construction; using Microsoft.Build.Evaluation; using Microsoft.CodeAnalysis; +using Microsoft.DotNet.Cli.CommandLine; using Microsoft.DotNet.Cli.Commands.MSBuild; using Microsoft.DotNet.Cli.Commands.NuGet; using Microsoft.DotNet.Cli.Commands.Run; -using Microsoft.DotNet.Cli.CommandLine; -using Microsoft.DotNet.Cli.Extensions; using Microsoft.DotNet.Cli.Utils; using Microsoft.DotNet.FileBasedPrograms; +using Microsoft.DotNet.ProjectTools; using NuGet.ProjectModel; namespace Microsoft.DotNet.Cli.Commands.Package.Add; @@ -25,7 +25,7 @@ public override int Execute() { var (fileOrDirectory, allowedAppKinds) = PackageCommandDefinition.ProcessPathOptions(_parseResult); - if (allowedAppKinds.HasFlag(AppKinds.FileBased) && VirtualProjectBuildingCommand.IsValidEntryPointPath(fileOrDirectory)) + if (allowedAppKinds.HasFlag(AppKinds.FileBased) && VirtualProjectBuilder.IsValidEntryPointPath(fileOrDirectory)) { return ExecuteForFileBasedApp(fileOrDirectory); } @@ -192,6 +192,7 @@ private int ExecuteForFileBasedApp(string path) NoCache = true, NoBuild = true, }; + var projectCollection = new ProjectCollection(); var projectInstance = command.CreateProjectInstance(projectCollection); diff --git a/src/Cli/dotnet/Commands/Package/Remove/PackageRemoveCommand.cs b/src/Cli/dotnet/Commands/Package/Remove/PackageRemoveCommand.cs index c38cdfc91a11..8abbb1cc8f4b 100644 --- a/src/Cli/dotnet/Commands/Package/Remove/PackageRemoveCommand.cs +++ b/src/Cli/dotnet/Commands/Package/Remove/PackageRemoveCommand.cs @@ -8,6 +8,7 @@ using Microsoft.DotNet.Cli.CommandLine; using Microsoft.DotNet.Cli.Utils; using Microsoft.DotNet.FileBasedPrograms; +using Microsoft.DotNet.ProjectTools; namespace Microsoft.DotNet.Cli.Commands.Package.Remove; @@ -24,7 +25,7 @@ public override int Execute() var (fileOrDirectory, allowedAppKinds) = PackageCommandDefinition.ProcessPathOptions(_parseResult); - if (allowedAppKinds.HasFlag(AppKinds.FileBased) && VirtualProjectBuildingCommand.IsValidEntryPointPath(fileOrDirectory)) + if (allowedAppKinds.HasFlag(AppKinds.FileBased) && VirtualProjectBuilder.IsValidEntryPointPath(fileOrDirectory)) { return ExecuteForFileBasedApp(path: fileOrDirectory, packageId: packageToRemove); } diff --git a/src/Cli/dotnet/Commands/Project/Convert/ProjectConvertCommand.cs b/src/Cli/dotnet/Commands/Project/Convert/ProjectConvertCommand.cs index 46c52d650bd7..ee9a5e1d77a5 100644 --- a/src/Cli/dotnet/Commands/Project/Convert/ProjectConvertCommand.cs +++ b/src/Cli/dotnet/Commands/Project/Convert/ProjectConvertCommand.cs @@ -9,6 +9,7 @@ using Microsoft.DotNet.Cli.Commands.Run; using Microsoft.DotNet.Cli.Utils; using Microsoft.DotNet.FileBasedPrograms; +using Microsoft.DotNet.ProjectTools; using Microsoft.TemplateEngine.Cli.Commands; namespace Microsoft.DotNet.Cli.Commands.Project.Convert; @@ -23,31 +24,24 @@ public override int Execute() { // Check the entry point file path. string file = Path.GetFullPath(_file); - if (!VirtualProjectBuildingCommand.IsValidEntryPointPath(file)) + if (!VirtualProjectBuilder.IsValidEntryPointPath(file)) { throw new GracefulException(CliCommandStrings.InvalidFilePath, file); } string targetDirectory = DetermineOutputDirectory(file); - // Find directives (this can fail, so do this before creating the target directory). - var sourceFile = SourceFile.Load(file); - var directives = FileLevelDirectiveHelpers.FindDirectives(sourceFile, reportAllErrors: !_force, VirtualProjectBuildingCommand.ThrowingReporter); - // Create a project instance for evaluation. var projectCollection = new ProjectCollection(); - var command = new VirtualProjectBuildingCommand( - entryPointFileFullPath: file, - msbuildArgs: MSBuildArgs.FromOtherArgs([])) - { - Directives = directives, - }; - var projectInstance = command.CreateProjectInstance(projectCollection); - // Evaluate directives. - directives = VirtualProjectBuildingCommand.EvaluateDirectives(projectInstance, directives, sourceFile, VirtualProjectBuildingCommand.ThrowingReporter); - command.Directives = directives; - projectInstance = command.CreateProjectInstance(projectCollection); + var builder = new VirtualProjectBuilder(file, VirtualProjectBuildingCommand.TargetFrameworkVersion); + + builder.CreateProjectInstance( + projectCollection, + VirtualProjectBuildingCommand.ThrowingReporter, + out var projectInstance, + out var evaluatedDirectives, + validateAllDirectives: !_force); // Find other items to copy over, e.g., default Content items like JSON files in Web apps. var includeItems = FindIncludedItems().ToList(); @@ -66,7 +60,7 @@ public override int Execute() } else { - VirtualProjectBuildingCommand.RemoveDirectivesFromFile(directives, sourceFile.Text, targetFile); + VirtualProjectBuilder.RemoveDirectivesFromFile(evaluatedDirectives, builder.EntryPointSourceFile.Text, targetFile); } // Create project file. @@ -79,9 +73,9 @@ public override int Execute() { using var stream = File.Open(projectFile, FileMode.Create, FileAccess.Write); using var writer = new StreamWriter(stream, Encoding.UTF8); - VirtualProjectBuildingCommand.WriteProjectFile(writer, UpdateDirectives(directives), isVirtualProject: false, + VirtualProjectBuilder.WriteProjectFile(writer, UpdateDirectives(evaluatedDirectives), isVirtualProject: false, userSecretsId: DetermineUserSecretsId(), - excludeDefaultProperties: FindDefaultPropertiesToExclude()); + defaultProperties: GetDefaultProperties()); } // Copy or move over included items. @@ -230,14 +224,14 @@ ImmutableArray UpdateDirectives(ImmutableArray return result.DrainToImmutable(); } - IEnumerable FindDefaultPropertiesToExclude() + IEnumerable<(string name, string value)> GetDefaultProperties() { - foreach (var (name, defaultValue) in VirtualProjectBuildingCommand.DefaultProperties) + foreach (var (name, defaultValue) in VirtualProjectBuilder.GetDefaultProperties(VirtualProjectBuildingCommand.TargetFrameworkVersion)) { string projectValue = projectInstance.GetPropertyValue(name); - if (!string.Equals(projectValue, defaultValue, StringComparison.OrdinalIgnoreCase)) + if (string.Equals(projectValue, defaultValue, StringComparison.OrdinalIgnoreCase)) { - yield return name; + yield return (name, defaultValue); } } } diff --git a/src/Cli/dotnet/Commands/Run/Api/RunApiCommand.cs b/src/Cli/dotnet/Commands/Run/Api/RunApiCommand.cs index 68396f2951b6..1a3cb3645bfa 100644 --- a/src/Cli/dotnet/Commands/Run/Api/RunApiCommand.cs +++ b/src/Cli/dotnet/Commands/Run/Api/RunApiCommand.cs @@ -9,6 +9,7 @@ using System.Text.Json.Serialization; using Microsoft.DotNet.Cli.Utils; using Microsoft.DotNet.FileBasedPrograms; +using Microsoft.DotNet.ProjectTools; namespace Microsoft.DotNet.Cli.Commands.Run.Api; @@ -66,10 +67,16 @@ public override RunApiOutput Execute() { var sourceFile = SourceFile.Load(EntryPointFileFullPath); var directives = FileLevelDirectiveHelpers.FindDirectives(sourceFile, reportAllErrors: true, ErrorReporters.CreateCollectingReporter(out var diagnostics)); - string artifactsPath = ArtifactsPath ?? VirtualProjectBuildingCommand.GetArtifactsPath(EntryPointFileFullPath); + string artifactsPath = ArtifactsPath ?? VirtualProjectBuilder.GetArtifactsPath(EntryPointFileFullPath); var csprojWriter = new StringWriter(); - VirtualProjectBuildingCommand.WriteProjectFile(csprojWriter, directives, isVirtualProject: true, targetFilePath: EntryPointFileFullPath, artifactsPath: artifactsPath); + VirtualProjectBuilder.WriteProjectFile( + csprojWriter, + directives, + VirtualProjectBuilder.GetDefaultProperties(VirtualProjectBuildingCommand.TargetFrameworkVersion), + isVirtualProject: true, + targetFilePath: EntryPointFileFullPath, + artifactsPath: artifactsPath); return new RunApiOutput.Project { @@ -88,11 +95,8 @@ public override RunApiOutput Execute() { var msbuildArgs = MSBuildArgs.FromVerbosity(VerbosityOptions.quiet); var buildCommand = new VirtualProjectBuildingCommand( - entryPointFileFullPath: EntryPointFileFullPath, - msbuildArgs: msbuildArgs) - { - CustomArtifactsPath = ArtifactsPath, - }; + EntryPointFileFullPath, msbuildArgs, artifactsPath: ArtifactsPath); + buildCommand.MarkArtifactsFolderUsed(); var runCommand = new RunCommand( diff --git a/src/Cli/dotnet/Commands/Run/RunCommand.cs b/src/Cli/dotnet/Commands/Run/RunCommand.cs index 9351e2e7538c..4a0b48ddb7df 100644 --- a/src/Cli/dotnet/Commands/Run/RunCommand.cs +++ b/src/Cli/dotnet/Commands/Run/RunCommand.cs @@ -19,6 +19,7 @@ using Microsoft.DotNet.Cli.Utils.Extensions; using Microsoft.DotNet.FileBasedPrograms; using Microsoft.DotNet.ProjectTools; +using Microsoft.DotNet.Utilities; namespace Microsoft.DotNet.Cli.Commands.Run; @@ -545,7 +546,7 @@ static void SetRootVariableName(ICommand command, string runtimeIdentifier, stri static ICommand CreateCommandForCscBuiltProgram(string entryPointFileFullPath, string[] args) { - var artifactsPath = VirtualProjectBuildingCommand.GetArtifactsPath(entryPointFileFullPath); + var artifactsPath = VirtualProjectBuilder.GetArtifactsPath(entryPointFileFullPath); var exePath = Path.Join(artifactsPath, "bin", "debug", Path.GetFileNameWithoutExtension(entryPointFileFullPath) + FileNameSuffixes.CurrentPlatform.Exe); var commandSpec = new CommandSpec(path: exePath, args: ArgumentEscaper.EscapeAndConcatenateArgArrayForProcessStart(args)); var command = CommandFactoryUsingResolver.Create(commandSpec); @@ -664,7 +665,7 @@ internal static void ThrowUnableToRunError(ProjectInstance project) if (!readCodeFromStdin) { - if (VirtualProjectBuildingCommand.IsValidEntryPointPath(arg)) + if (VirtualProjectBuilder.IsValidEntryPointPath(arg)) { arg = Path.GetFullPath(arg); } @@ -749,7 +750,7 @@ public static RunCommand FromParseResult(ParseResult parseResult) // If '-' is specified as the input file, read all text from stdin into a temporary file and use that as the entry point. // We create a new directory for each file so other files are not included in the compilation. // We fail if the file already exists to avoid reusing the same file for multiple stdin runs (in case the random name is duplicate). - string directory = VirtualProjectBuildingCommand.GetTempSubpath(Path.GetRandomFileName()); + string directory = VirtualProjectBuilder.GetTempSubpath(Path.GetRandomFileName()); VirtualProjectBuildingCommand.CreateTempSubdirectory(directory); entryPointFilePath = Path.Join(directory, "app.cs"); using (var stdinStream = Console.OpenStandardInput()) diff --git a/src/Cli/dotnet/Commands/Run/RunTelemetry.cs b/src/Cli/dotnet/Commands/Run/RunTelemetry.cs index fd45de6a6a63..0d3ab62507b8 100644 --- a/src/Cli/dotnet/Commands/Run/RunTelemetry.cs +++ b/src/Cli/dotnet/Commands/Run/RunTelemetry.cs @@ -6,6 +6,7 @@ using Microsoft.DotNet.Cli.Utils; using Microsoft.DotNet.FileBasedPrograms; using Microsoft.DotNet.ProjectTools; +using Microsoft.DotNet.Utilities; namespace Microsoft.DotNet.Cli.Commands.Run; diff --git a/src/Cli/dotnet/Commands/Run/VirtualProjectBuildingCommand.cs b/src/Cli/dotnet/Commands/Run/VirtualProjectBuildingCommand.cs index 68a2665b070e..a2e434a6927a 100644 --- a/src/Cli/dotnet/Commands/Run/VirtualProjectBuildingCommand.cs +++ b/src/Cli/dotnet/Commands/Run/VirtualProjectBuildingCommand.cs @@ -1,28 +1,23 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System.Collections.Frozen; using System.Collections.Immutable; using System.Collections.ObjectModel; using System.Diagnostics; -using System.Security; using System.Text.Json; using System.Text.Json.Serialization; -using System.Xml; -using Microsoft.Build.Construction; -using Microsoft.Build.Definition; using Microsoft.Build.Evaluation; using Microsoft.Build.Execution; using Microsoft.Build.Framework; using Microsoft.Build.Logging; using Microsoft.Build.Logging.SimpleErrorLogger; using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.Text; using Microsoft.DotNet.Cli.Commands.Clean.FileBasedAppArtifacts; using Microsoft.DotNet.Cli.Commands.Restore; using Microsoft.DotNet.Cli.Utils; using Microsoft.DotNet.Cli.Utils.Extensions; using Microsoft.DotNet.FileBasedPrograms; +using Microsoft.DotNet.ProjectTools; namespace Microsoft.DotNet.Cli.Commands.Run; @@ -69,19 +64,6 @@ internal sealed class VirtualProjectBuildingCommand : CommandBase ("MSBuild.rsp", true), ]; - /// - /// Kept in sync with the default dotnet new console project file (enforced by DotnetProjectAddTests.SameAsTemplate). - /// - public static readonly FrozenDictionary DefaultProperties = FrozenDictionary.Create(StringComparer.OrdinalIgnoreCase, - [ - new("OutputType", "Exe"), - new("TargetFramework", $"net{TargetFrameworkVersion}"), - new("ImplicitUsings", "enable"), - new("Nullable", "enable"), - new("PublishAot", "true"), - new("PackAsTool", "true"), - ]); - /// /// For purposes of determining whether CSC is enough to build as opposed to full MSBuild, /// we can ignore properties that do not affect the build on their own. @@ -101,43 +83,6 @@ internal sealed class VirtualProjectBuildingCommand : CommandBase public static string TargetFrameworkVersion => Product.TargetFrameworkVersion; - public VirtualProjectBuildingCommand( - string entryPointFileFullPath, - MSBuildArgs msbuildArgs) - { - Debug.Assert(Path.IsPathFullyQualified(entryPointFileFullPath)); - - EntryPointFileFullPath = entryPointFileFullPath; - MSBuildArgs = msbuildArgs.CloneWithAdditionalProperties(new Dictionary(StringComparer.OrdinalIgnoreCase) - { - // See https://github.com/dotnet/msbuild/blob/main/documentation/specs/build-nonexistent-projects-by-default.md. - { "_BuildNonexistentProjectsByDefault", bool.TrueString }, - { "RestoreUseSkipNonexistentTargets", bool.FalseString }, - { "ProvideCommandLineArgs", bool.TrueString }, - } - .AsReadOnly()); - - if (MSBuildArgs.RequestedTargets is null or []) - { - RequestedTargets = MSBuildArgs.GetTargetResult; - } - else if (MSBuildArgs.GetTargetResult is null or []) - { - RequestedTargets = MSBuildArgs.RequestedTargets; - } - else - { - RequestedTargets = MSBuildArgs.RequestedTargets - .Union(MSBuildArgs.GetTargetResult, StringComparer.OrdinalIgnoreCase) - .ToArray(); - } - } - - public string EntryPointFileFullPath { get; } - public MSBuildArgs MSBuildArgs { get; } - private string[]? RequestedTargets { get; } - public string? CustomArtifactsPath { get; init; } - public string ArtifactsPath => field ??= CustomArtifactsPath ?? GetArtifactsPath(EntryPointFileFullPath); public bool NoRestore { get; init; } /// @@ -161,18 +106,8 @@ public VirtualProjectBuildingCommand( /// public bool NoWriteBuildMarkers { get; init; } - private SourceFile EntryPointSourceFile - { - get - { - if (field == default) - { - field = SourceFile.Load(EntryPointFileFullPath); - } - - return field; - } - } + public VirtualProjectBuilder Builder { get; } + public MSBuildArgs MSBuildArgs { get; } public ImmutableArray Directives { @@ -180,7 +115,7 @@ public ImmutableArray Directives { if (field.IsDefault) { - field = FileLevelDirectiveHelpers.FindDirectives(EntryPointSourceFile, reportAllErrors: false, VirtualProjectBuildingCommand.ThrowingReporter); + field = FileLevelDirectiveHelpers.FindDirectives(Builder.EntryPointSourceFile, reportAllErrors: false, ThrowingReporter); Debug.Assert(!field.IsDefault); } @@ -190,10 +125,27 @@ public ImmutableArray Directives set; } + public VirtualProjectBuildingCommand( + string entryPointFileFullPath, + MSBuildArgs msbuildArgs, + string? artifactsPath = null) + { + MSBuildArgs = msbuildArgs.CloneWithAdditionalProperties(new Dictionary(StringComparer.OrdinalIgnoreCase) + { + // See https://github.com/dotnet/msbuild/blob/main/documentation/specs/build-nonexistent-projects-by-default.md. + { "_BuildNonexistentProjectsByDefault", bool.TrueString }, + { "RestoreUseSkipNonexistentTargets", bool.FalseString }, + { "ProvideCommandLineArgs", bool.TrueString }, + } + .AsReadOnly()); + + Builder = new VirtualProjectBuilder(entryPointFileFullPath, TargetFrameworkVersion, MSBuildArgs.GetResolvedTargets(), artifactsPath); + } + public override int Execute() { bool msbuildGet = MSBuildArgs.GetProperty is [_, ..] || MSBuildArgs.GetItem is [_, ..] || MSBuildArgs.GetTargetResult is [_, ..]; - bool evalOnly = msbuildGet && RequestedTargets is null or []; + bool evalOnly = msbuildGet && Builder.RequestedTargets is null or []; bool minimizeStdOut = msbuildGet && MSBuildArgs.GetResultOutputFile is null or []; var verbosity = MSBuildArgs.Verbosity ?? MSBuildForwardingAppWithoutLogging.DefaultVerbosity; @@ -218,7 +170,7 @@ public override int Execute() if (!NoWriteBuildMarkers) { - CreateTempSubdirectory(ArtifactsPath); + CreateTempSubdirectory(Builder.ArtifactsPath); MarkArtifactsFolderUsed(); } } @@ -262,8 +214,8 @@ public override int Execute() // Execute CSC. int result = new CSharpCompilerCommand { - EntryPointFileFullPath = EntryPointFileFullPath, - ArtifactsPath = ArtifactsPath, + EntryPointFileFullPath = Builder.EntryPointFileFullPath, + ArtifactsPath = Builder.ArtifactsPath, CanReuseAuxiliaryFiles = cache.DetermineFinalCanReuseAuxiliaryFiles(), CscArguments = cache.PreviousEntry?.CscArguments ?? [], BuildResultFile = cache.PreviousEntry?.BuildResultFile, @@ -349,7 +301,7 @@ public override int Execute() { var buildRequest = new BuildRequestData( CreateProjectInstance(projectCollection), - targetsToBuild: RequestedTargets ?? [Constants.Build, Constants.CoreCompile]); + targetsToBuild: Builder.RequestedTargets ?? [Constants.Build, Constants.CoreCompile]); var buildResult = BuildManager.DefaultBuildManager.BuildRequest(buildRequest); if (buildResult.OverallResult != BuildResultCode.Success) @@ -716,7 +668,7 @@ private CacheInfo ComputeCacheEntry() RuntimeVersion = CSharpCompilerCommand.RuntimeVersion, }; - var entryPointFile = new FileInfo(EntryPointFileFullPath); + var entryPointFile = new FileInfo(Builder.EntryPointFileFullPath); // Collect current implicit build files. CollectImplicitBuildFiles(entryPointFile.Directory, cacheEntry.ImplicitBuildFiles, out var exampleMSBuildFile); @@ -764,7 +716,7 @@ private bool NeedsToBuild(out CacheInfo cache) // Check cache files. - string artifactsDirectory = ArtifactsPath; + string artifactsDirectory = Builder.ArtifactsPath; var successCacheFile = new FileInfo(Path.Join(artifactsDirectory, BuildSuccessCacheFileName)); if (!successCacheFile.Exists) @@ -911,7 +863,7 @@ static FileSystemInfo ResolveLinkTargetOrSelf(FileSystemInfo fileSystemInfo) public RunFileBuildCacheEntry? GetPreviousCacheEntry() { - return DeserializeCacheEntry(Path.Join(ArtifactsPath, BuildSuccessCacheFileName)); + return DeserializeCacheEntry(Path.Join(Builder.ArtifactsPath, BuildSuccessCacheFileName)); } private void EnsurePreviousCacheEntry(CacheInfo cache) @@ -927,7 +879,7 @@ private BuildLevel GetBuildLevel(out CacheInfo cache) { if (!NeedsToBuild(out cache)) { - Reporter.Verbose.WriteLine("No need to build, the output is up to date. Cache: " + ArtifactsPath); + Reporter.Verbose.WriteLine("No need to build, the output is up to date. Cache: " + Builder.ArtifactsPath); return BuildLevel.None; } @@ -1027,7 +979,7 @@ public void MarkArtifactsFolderUsed() return; } - string directory = ArtifactsPath; + string directory = Builder.ArtifactsPath; try { @@ -1046,13 +998,13 @@ private void MarkBuildStart() return; } - string directory = ArtifactsPath; + string directory = Builder.ArtifactsPath; CreateTempSubdirectory(directory); MarkArtifactsFolderUsed(); - File.WriteAllText(Path.Join(directory, BuildStartCacheFileName), EntryPointFileFullPath); + File.WriteAllText(Path.Join(directory, BuildStartCacheFileName), Builder.EntryPointFileFullPath); } private void MarkBuildSuccess(CacheInfo cache) @@ -1062,133 +1014,31 @@ private void MarkBuildSuccess(CacheInfo cache) return; } - string successCacheFile = Path.Join(ArtifactsPath, BuildSuccessCacheFileName); + string successCacheFile = Path.Join(Builder.ArtifactsPath, BuildSuccessCacheFileName); using var stream = File.Open(successCacheFile, FileMode.Create, FileAccess.Write, FileShare.None); JsonSerializer.Serialize(stream, cache.CurrentEntry, RunFileJsonSerializerContext.Default.RunFileBuildCacheEntry); } - /// - /// If there are any #:project , expands $() in them and ensures they point to project files (not directories). - /// - public static ImmutableArray EvaluateDirectives( - ProjectInstance? project, - ImmutableArray directives, - SourceFile sourceFile, - ErrorReporter errorReporter) - { - if (directives.OfType().Any()) - { - return directives - .Select(d => d is CSharpDirective.Project p - ? (project is null - ? p - : p.WithName(project.ExpandString(p.Name), CSharpDirective.Project.NameKind.Expanded)) - .EnsureProjectFilePath(sourceFile, errorReporter) - : d) - .ToImmutableArray(); - } - - return directives; - } - public ProjectInstance CreateProjectInstance(ProjectCollection projectCollection) { return CreateProjectInstance(projectCollection, addGlobalProperties: null); } - private ProjectInstance CreateProjectInstance( - ProjectCollection projectCollection, - Action>? addGlobalProperties) + public ProjectInstance CreateProjectInstance(ProjectCollection projectCollection, Action>? addGlobalProperties) { - var project = CreateProjectInstance(projectCollection, Directives, addGlobalProperties); + Builder.CreateProjectInstance( + projectCollection, + ThrowingReporter, + out var project, + out var evaluatedDirectives, + Directives, + addGlobalProperties); - var directives = EvaluateDirectives(project, Directives, EntryPointSourceFile, VirtualProjectBuildingCommand.ThrowingReporter); - if (directives != Directives) - { - Directives = directives; - project = CreateProjectInstance(projectCollection, directives, addGlobalProperties); - } + Directives = evaluatedDirectives; return project; } - private ProjectInstance CreateProjectInstance( - ProjectCollection projectCollection, - ImmutableArray directives, - Action>? addGlobalProperties) - { - var projectRoot = CreateProjectRootElement(projectCollection); - - var globalProperties = projectCollection.GlobalProperties; - if (addGlobalProperties is not null) - { - globalProperties = new Dictionary(projectCollection.GlobalProperties, StringComparer.OrdinalIgnoreCase); - addGlobalProperties(globalProperties); - } - - return ProjectInstance.FromProjectRootElement(projectRoot, new ProjectOptions - { - ProjectCollection = projectCollection, - GlobalProperties = globalProperties, - }); - - ProjectRootElement CreateProjectRootElement(ProjectCollection projectCollection) - { - var projectFileFullPath = Path.ChangeExtension(EntryPointFileFullPath, ".csproj"); - var projectFileWriter = new StringWriter(); - WriteProjectFile( - projectFileWriter, - directives, - isVirtualProject: true, - targetFilePath: EntryPointFileFullPath, - artifactsPath: ArtifactsPath, - includeRuntimeConfigInformation: RequestedTargets?.ContainsAny("Publish", "Pack") != true); - var projectFileText = projectFileWriter.ToString(); - - using var reader = new StringReader(projectFileText); - using var xmlReader = XmlReader.Create(reader); - var projectRoot = ProjectRootElement.Create(xmlReader, projectCollection); - projectRoot.FullPath = projectFileFullPath; - return projectRoot; - } - } - - public static string GetArtifactsPath(string entryPointFileFullPath) - { - // Include entry point file name so the directory name is not completely opaque. - string fileName = Path.GetFileNameWithoutExtension(entryPointFileFullPath); - string hash = Sha256Hasher.HashWithNormalizedCasing(entryPointFileFullPath); - string directoryName = $"{fileName}-{hash}"; - - return GetTempSubpath(directoryName); - } - - /// - /// Obtains a temporary subdirectory for file-based app artifacts, e.g., /tmp/dotnet/runfile/. - /// - public static string GetTempSubdirectory() - { - // We want a location where permissions are expected to be restricted to the current user. - string directory = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) - ? Path.GetTempPath() - : Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData); - - if (string.IsNullOrEmpty(directory)) - { - throw new InvalidOperationException(FileBasedProgramsResources.EmptyTempPath); - } - - return Path.Join(directory, "dotnet", "runfile"); - } - - /// - /// Obtains a specific temporary path in a subdirectory for file-based app artifacts, e.g., /tmp/dotnet/runfile/{name}. - /// - public static string GetTempSubpath(string name) - { - return Path.Join(GetTempSubdirectory(), name); - } - /// /// Creates a temporary subdirectory for file-based apps. /// Use to obtain the path. @@ -1208,369 +1058,6 @@ public static void CreateTempSubdirectory(string path) } } - public static void WriteProjectFile( - TextWriter writer, - ImmutableArray directives, - bool isVirtualProject, - string? targetFilePath = null, - string? artifactsPath = null, - bool includeRuntimeConfigInformation = true, - string? userSecretsId = null, - IEnumerable? excludeDefaultProperties = null) - { - Debug.Assert(userSecretsId == null || !isVirtualProject); - Debug.Assert(excludeDefaultProperties == null || !isVirtualProject); - - int processedDirectives = 0; - - var sdkDirectives = directives.OfType(); - var propertyDirectives = directives.OfType(); - var packageDirectives = directives.OfType(); - var projectDirectives = directives.OfType(); - - const string defaultSdkName = "Microsoft.NET.Sdk"; - string firstSdkName; - string? firstSdkVersion; - - if (sdkDirectives.FirstOrDefault() is { } firstSdk) - { - firstSdkName = firstSdk.Name; - firstSdkVersion = firstSdk.Version; - processedDirectives++; - } - else - { - firstSdkName = defaultSdkName; - firstSdkVersion = null; - } - - if (isVirtualProject) - { - Debug.Assert(!string.IsNullOrWhiteSpace(artifactsPath)); - - // Note that ArtifactsPath needs to be specified before Sdk.props - // (usually it's recommended to specify it in Directory.Build.props - // but importing Sdk.props manually afterwards also works). - writer.WriteLine($""" - - - - false - {EscapeValue(artifactsPath)} - artifacts/$(MSBuildProjectName) - artifacts/$(MSBuildProjectName) - true - false - true - """); - - // Only set these to false when using the default SDK with no additional SDKs - // to avoid including .resx and other files that are typically not expected in simple file-based apps. - // When other SDKs are used (e.g., Microsoft.NET.Sdk.Web), keep the default behavior. - bool usingOnlyDefaultSdk = firstSdkName == defaultSdkName && sdkDirectives.Count() <= 1; - if (usingOnlyDefaultSdk) - { - writer.WriteLine($""" - false - false - """); - } - - // Write default properties before importing SDKs so they can be overridden by SDKs - // (and implicit build files which are imported by the default .NET SDK). - foreach (var (name, value) in DefaultProperties) - { - writer.WriteLine($""" - <{name}>{EscapeValue(value)} - """); - } - - writer.WriteLine($""" - - - - - - - """); - - if (firstSdkVersion is null) - { - writer.WriteLine($""" - - """); - } - else - { - writer.WriteLine($""" - - """); - } - } - else - { - string slashDelimited = firstSdkVersion is null - ? firstSdkName - : $"{firstSdkName}/{firstSdkVersion}"; - writer.WriteLine($""" - - - """); - } - - foreach (var sdk in sdkDirectives.Skip(1)) - { - if (isVirtualProject) - { - WriteImport(writer, "Sdk.props", sdk); - } - else if (sdk.Version is null) - { - writer.WriteLine($""" - - """); - } - else - { - writer.WriteLine($""" - - """); - } - - processedDirectives++; - } - - if (isVirtualProject || processedDirectives > 1) - { - writer.WriteLine(); - } - - // Write default and custom properties. - { - writer.WriteLine(""" - - """); - - // First write the default properties except those specified by the user. - if (!isVirtualProject) - { - var customPropertyNames = propertyDirectives - .Select(static d => d.Name) - .Concat(excludeDefaultProperties ?? []) - .ToHashSet(StringComparer.OrdinalIgnoreCase); - foreach (var (name, value) in DefaultProperties) - { - if (!customPropertyNames.Contains(name)) - { - writer.WriteLine($""" - <{name}>{EscapeValue(value)} - """); - } - } - - if (userSecretsId != null && !customPropertyNames.Contains("UserSecretsId")) - { - writer.WriteLine($""" - {EscapeValue(userSecretsId)} - """); - } - } - - // Write custom properties. - foreach (var property in propertyDirectives) - { - writer.WriteLine($""" - <{property.Name}>{EscapeValue(property.Value)} - """); - - processedDirectives++; - } - - // Write virtual-only properties which cannot be overridden. - if (isVirtualProject) - { - writer.WriteLine(""" - false - $(Features);FileBasedProgram - """); - } - - writer.WriteLine(""" - - - """); - } - - if (packageDirectives.Any()) - { - writer.WriteLine(""" - - """); - - foreach (var package in packageDirectives) - { - if (package.Version is null) - { - writer.WriteLine($""" - - """); - } - else - { - writer.WriteLine($""" - - """); - } - - processedDirectives++; - } - - writer.WriteLine(""" - - - """); - } - - if (projectDirectives.Any()) - { - writer.WriteLine(""" - - """); - - foreach (var projectReference in projectDirectives) - { - writer.WriteLine($""" - - """); - - processedDirectives++; - } - - writer.WriteLine(""" - - - """); - } - - Debug.Assert(processedDirectives + directives.OfType().Count() == directives.Length); - - if (isVirtualProject) - { - Debug.Assert(targetFilePath is not null); - - // Only add explicit Compile item when EnableDefaultCompileItems is not true. - // When EnableDefaultCompileItems=true, the file is included via default MSBuild globbing. - // See https://github.com/dotnet/sdk/issues/51785 - writer.WriteLine($""" - - - - - """); - - if (includeRuntimeConfigInformation) - { - var targetDirectory = Path.GetDirectoryName(targetFilePath) ?? ""; - writer.WriteLine($""" - - - - - - """); - } - - foreach (var sdk in sdkDirectives) - { - WriteImport(writer, "Sdk.targets", sdk); - } - - if (!sdkDirectives.Any()) - { - Debug.Assert(firstSdkName == defaultSdkName && firstSdkVersion == null); - writer.WriteLine($""" - - """); - } - - writer.WriteLine(); - } - - writer.WriteLine(""" - - """); - - static string EscapeValue(string value) => SecurityElement.Escape(value); - - static void WriteImport(TextWriter writer, string project, CSharpDirective.Sdk sdk) - { - if (sdk.Version is null) - { - writer.WriteLine($""" - - """); - } - else - { - writer.WriteLine($""" - - """); - } - } - } - - public static SourceText? RemoveDirectivesFromFile(ImmutableArray directives, SourceText text) - { - if (directives.Length == 0) - { - return null; - } - - Debug.Assert(directives.OrderBy(d => d.Info.Span.Start).SequenceEqual(directives), "Directives should be ordered by source location."); - - for (int i = directives.Length - 1; i >= 0; i--) - { - var directive = directives[i]; - text = text.Replace(directive.Info.Span, string.Empty); - } - - return text; - } - - public static void RemoveDirectivesFromFile(ImmutableArray directives, SourceText text, string filePath) - { - if (RemoveDirectivesFromFile(directives, text) is { } modifiedText) - { - new SourceFile(filePath, modifiedText).Save(); - } - } - - public static bool IsValidEntryPointPath(string entryPointFilePath) - { - if (!File.Exists(entryPointFilePath)) - { - return false; - } - - if (entryPointFilePath.EndsWith(".cs", StringComparison.OrdinalIgnoreCase)) - { - return true; - } - - // Check if the first two characters are #! - try - { - using var stream = File.OpenRead(entryPointFilePath); - int first = stream.ReadByte(); - int second = stream.ReadByte(); - return first == '#' && second == '!'; - } - catch - { - return false; - } - } - public static readonly ErrorReporter ThrowingReporter = static (sourceFile, textSpan, message) => throw new GracefulException($"{sourceFile.GetLocationString(textSpan)}: {FileBasedProgramsResources.DirectiveError}: {message}"); } diff --git a/src/Cli/dotnet/Installer/Windows/WindowsUtils.cs b/src/Cli/dotnet/Installer/Windows/WindowsUtils.cs index 906305d373cf..5a471cc8b1ef 100644 --- a/src/Cli/dotnet/Installer/Windows/WindowsUtils.cs +++ b/src/Cli/dotnet/Installer/Windows/WindowsUtils.cs @@ -7,6 +7,7 @@ using System.Security.Principal; using Microsoft.DotNet.Cli.Telemetry; using Microsoft.DotNet.Cli.Utils; +using Microsoft.DotNet.Utilities; using Microsoft.Win32; namespace Microsoft.DotNet.Cli.Installer.Windows; diff --git a/src/Cli/dotnet/Program.cs b/src/Cli/dotnet/Program.cs index 077d46aa615a..4f5e8e663e2f 100644 --- a/src/Cli/dotnet/Program.cs +++ b/src/Cli/dotnet/Program.cs @@ -18,6 +18,8 @@ using Microsoft.DotNet.Cli.Utils; using Microsoft.DotNet.Cli.Utils.Extensions; using Microsoft.DotNet.Configurer; +using Microsoft.DotNet.ProjectTools; +using Microsoft.DotNet.Utilities; using Microsoft.Extensions.EnvironmentAbstractions; using NuGet.Frameworks; using CommandResult = System.CommandLine.Parsing.CommandResult; @@ -307,7 +309,7 @@ internal static int ProcessArgs(string[] args, TimeSpan startupTime) // If we didn't match any built-in commands, and a C# file path is the first argument, // parse as `dotnet run --file file.cs ..rest_of_args` instead. if (parseResult.GetResult(Parser.DotnetSubCommand) is { Tokens: [{ Type: TokenType.Argument, Value: { } } unmatchedCommandOrFile] } - && VirtualProjectBuildingCommand.IsValidEntryPointPath(unmatchedCommandOrFile.Value)) + && VirtualProjectBuilder.IsValidEntryPointPath(unmatchedCommandOrFile.Value)) { List otherTokens = new(parseResult.Tokens.Count - 1); foreach (var token in parseResult.Tokens) diff --git a/src/Cli/dotnet/ReleasePropertyProjectLocator.cs b/src/Cli/dotnet/ReleasePropertyProjectLocator.cs index 2f39135d0cf2..fba8ee42a7c3 100644 --- a/src/Cli/dotnet/ReleasePropertyProjectLocator.cs +++ b/src/Cli/dotnet/ReleasePropertyProjectLocator.cs @@ -8,6 +8,7 @@ using Microsoft.Build.Execution; using Microsoft.DotNet.Cli.Commands.Run; using Microsoft.DotNet.Cli.Utils; +using Microsoft.DotNet.ProjectTools; using Microsoft.NET.Build.Tasks; using Microsoft.VisualStudio.SolutionPersistence.Model; @@ -110,7 +111,7 @@ DependentCommandOptions commandOptions { foreach (string arg in _slnOrProjectArgs.Append(Directory.GetCurrentDirectory())) { - if (VirtualProjectBuildingCommand.IsValidEntryPointPath(arg)) + if (VirtualProjectBuilder.IsValidEntryPointPath(arg)) { return new VirtualProjectBuildingCommand(Path.GetFullPath(arg), MSBuildArgs.FromProperties(globalProps)) .CreateProjectInstance(ProjectCollection.GlobalProjectCollection); diff --git a/src/Cli/dotnet/Telemetry/TelemetryCommonProperties.cs b/src/Cli/dotnet/Telemetry/TelemetryCommonProperties.cs index 625e343d7589..8c71bff14e7d 100644 --- a/src/Cli/dotnet/Telemetry/TelemetryCommonProperties.cs +++ b/src/Cli/dotnet/Telemetry/TelemetryCommonProperties.cs @@ -6,6 +6,7 @@ using System.Collections.Frozen; using Microsoft.DotNet.Cli.Utils; using Microsoft.DotNet.Configurer; +using Microsoft.DotNet.Utilities; using RuntimeEnvironment = Microsoft.DotNet.Cli.Utils.RuntimeEnvironment; using RuntimeInformation = System.Runtime.InteropServices.RuntimeInformation; diff --git a/src/Cli/dotnet/dotnet.csproj b/src/Cli/dotnet/dotnet.csproj index 390d2c7a1e7b..a6be5fb2a5e6 100644 --- a/src/Cli/dotnet/dotnet.csproj +++ b/src/Cli/dotnet/dotnet.csproj @@ -45,7 +45,6 @@ - diff --git a/src/Microsoft.DotNet.ProjectTools/Microsoft.DotNet.ProjectTools.csproj b/src/Microsoft.DotNet.ProjectTools/Microsoft.DotNet.ProjectTools.csproj index 398ea7eecdab..9de97043c331 100644 --- a/src/Microsoft.DotNet.ProjectTools/Microsoft.DotNet.ProjectTools.csproj +++ b/src/Microsoft.DotNet.ProjectTools/Microsoft.DotNet.ProjectTools.csproj @@ -12,6 +12,7 @@ + diff --git a/src/Cli/Microsoft.DotNet.Cli.Utils/Sha256Hasher.cs b/src/Microsoft.DotNet.ProjectTools/Utilities/Sha256Hasher.cs similarity index 57% rename from src/Cli/Microsoft.DotNet.Cli.Utils/Sha256Hasher.cs rename to src/Microsoft.DotNet.ProjectTools/Utilities/Sha256Hasher.cs index 33fe02237329..176173e776c5 100644 --- a/src/Cli/Microsoft.DotNet.Cli.Utils/Sha256Hasher.cs +++ b/src/Microsoft.DotNet.ProjectTools/Utilities/Sha256Hasher.cs @@ -1,11 +1,9 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -#if NET - using System.Security.Cryptography; -namespace Microsoft.DotNet.Cli.Utils; +namespace Microsoft.DotNet.Utilities; public static class Sha256Hasher { @@ -13,20 +11,8 @@ public static class Sha256Hasher /// The hashed mac address needs to be the same hashed value as produced by the other distinct sources given the same input. (e.g. VsCode) /// public static string Hash(string text) - { - byte[] bytes = Encoding.UTF8.GetBytes(text); - byte[] hash = SHA256.HashData(bytes); -#if NET9_0_OR_GREATER - return Convert.ToHexStringLower(hash); -#else - return Convert.ToHexString(hash).ToLowerInvariant(); -#endif - } + => Convert.ToHexStringLower(SHA256.HashData(Encoding.UTF8.GetBytes(text))); public static string HashWithNormalizedCasing(string text) - { - return Hash(text.ToUpperInvariant()); - } + => Hash(text.ToUpperInvariant()); } - -#endif diff --git a/src/Microsoft.DotNet.ProjectTools/VirtualProjectBuilder.cs b/src/Microsoft.DotNet.ProjectTools/VirtualProjectBuilder.cs new file mode 100644 index 000000000000..ace65c8c4726 --- /dev/null +++ b/src/Microsoft.DotNet.ProjectTools/VirtualProjectBuilder.cs @@ -0,0 +1,559 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Collections.Immutable; +using System.Diagnostics; +using System.Security; +using System.Xml; +using Microsoft.Build.Construction; +using Microsoft.Build.Definition; +using Microsoft.Build.Evaluation; +using Microsoft.Build.Execution; +using Microsoft.CodeAnalysis.Text; +using Microsoft.DotNet.FileBasedPrograms; +using Microsoft.DotNet.Utilities; + +namespace Microsoft.DotNet.ProjectTools; + +internal sealed class VirtualProjectBuilder +{ + private readonly IEnumerable<(string name, string value)> _defaultProperties; + + public string EntryPointFileFullPath { get; } + + public SourceFile EntryPointSourceFile + { + get + { + if (field == default) + { + field = SourceFile.Load(EntryPointFileFullPath); + } + + return field; + } + } + + public string ArtifactsPath + => field ??= GetArtifactsPath(EntryPointFileFullPath); + + public string[]? RequestedTargets { get; } + + public VirtualProjectBuilder( + string entryPointFileFullPath, + string targetFrameworkVersion, + string[]? requestedTargets = null, + string? artifactsPath = null) + { + Debug.Assert(Path.IsPathFullyQualified(entryPointFileFullPath)); + + EntryPointFileFullPath = entryPointFileFullPath; + RequestedTargets = requestedTargets; + ArtifactsPath = artifactsPath; + _defaultProperties = GetDefaultProperties(targetFrameworkVersion); + } + + /// + /// Kept in sync with the default dotnet new console project file (enforced by DotnetProjectConvertTests.SameAsTemplate). + /// + public static IEnumerable<(string name, string value)> GetDefaultProperties(string targetFrameworkVersion) => + [ + ("OutputType", "Exe"), + ("TargetFramework", $"net{targetFrameworkVersion}"), + ("ImplicitUsings", "enable"), + ("Nullable", "enable"), + ("PublishAot", "true"), + ("PackAsTool", "true"), + ]; + + public static string GetArtifactsPath(string entryPointFileFullPath) + { + // Include entry point file name so the directory name is not completely opaque. + string fileName = Path.GetFileNameWithoutExtension(entryPointFileFullPath); + string hash = Sha256Hasher.HashWithNormalizedCasing(entryPointFileFullPath); + string directoryName = $"{fileName}-{hash}"; + + return GetTempSubpath(directoryName); + } + + /// + /// Obtains a temporary subdirectory for file-based app artifacts, e.g., /tmp/dotnet/runfile/. + /// + public static string GetTempSubdirectory() + { + // We want a location where permissions are expected to be restricted to the current user. + string directory = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) + ? Path.GetTempPath() + : Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData); + + if (string.IsNullOrEmpty(directory)) + { + throw new InvalidOperationException(FileBasedProgramsResources.EmptyTempPath); + } + + return Path.Join(directory, "dotnet", "runfile"); + } + + /// + /// Obtains a specific temporary path in a subdirectory for file-based app artifacts, e.g., /tmp/dotnet/runfile/{name}. + /// + public static string GetTempSubpath(string name) + { + return Path.Join(GetTempSubdirectory(), name); + } + + public static bool IsValidEntryPointPath(string entryPointFilePath) + { + if (!File.Exists(entryPointFilePath)) + { + return false; + } + + if (entryPointFilePath.EndsWith(".cs", StringComparison.OrdinalIgnoreCase)) + { + return true; + } + + // Check if the first two characters are #! + try + { + using var stream = File.OpenRead(entryPointFilePath); + int first = stream.ReadByte(); + int second = stream.ReadByte(); + return first == '#' && second == '!'; + } + catch + { + return false; + } + } + + /// + /// If there are any #:project , + /// evaluates their values as MSBuild expressions (i.e. substitutes $() and @() with property and item values, etc.) and + /// resolves the evaluated values to full project file paths (e.g. if the evaluted value is a directory finds a project in that directory). + /// + internal static ImmutableArray EvaluateDirectives( + ProjectInstance? project, + ImmutableArray directives, + SourceFile sourceFile, + ErrorReporter errorReporter) + { + if (directives.OfType().Any()) + { + return directives + .Select(d => d is CSharpDirective.Project p + ? (project is null + ? p + : p.WithName(project.ExpandString(p.Name), CSharpDirective.Project.NameKind.Expanded)) + .EnsureProjectFilePath(sourceFile, errorReporter) + : d) + .ToImmutableArray(); + } + + return directives; + } + + public void CreateProjectInstance( + ProjectCollection projectCollection, + ErrorReporter errorReporter, + out ProjectInstance project, + out ImmutableArray evaluatedDirectives, + ImmutableArray directives = default, + Action>? addGlobalProperties = null, + bool validateAllDirectives = false) + { + if (directives.IsDefault) + { + directives = FileLevelDirectiveHelpers.FindDirectives(EntryPointSourceFile, validateAllDirectives, errorReporter); + } + + project = CreateProjectInstance(projectCollection, directives, addGlobalProperties); + + evaluatedDirectives = EvaluateDirectives(project, directives, EntryPointSourceFile, errorReporter); + if (evaluatedDirectives != directives) + { + project = CreateProjectInstance(projectCollection, evaluatedDirectives, addGlobalProperties); + } + } + + private ProjectInstance CreateProjectInstance( + ProjectCollection projectCollection, + ImmutableArray directives, + Action>? addGlobalProperties = null) + { + var projectRoot = CreateProjectRootElement(projectCollection); + + var globalProperties = projectCollection.GlobalProperties; + if (addGlobalProperties is not null) + { + globalProperties = new Dictionary(projectCollection.GlobalProperties, StringComparer.OrdinalIgnoreCase); + addGlobalProperties(globalProperties); + } + + return ProjectInstance.FromProjectRootElement(projectRoot, new ProjectOptions + { + ProjectCollection = projectCollection, + GlobalProperties = globalProperties, + }); + + ProjectRootElement CreateProjectRootElement(ProjectCollection projectCollection) + { + var projectFileFullPath = Path.ChangeExtension(EntryPointFileFullPath, ".csproj"); + var projectFileWriter = new StringWriter(); + + WriteProjectFile( + projectFileWriter, + directives, + _defaultProperties, + isVirtualProject: true, + targetFilePath: EntryPointFileFullPath, + artifactsPath: ArtifactsPath, + includeRuntimeConfigInformation: RequestedTargets?.ContainsAny("Publish", "Pack") != true); + + var projectFileText = projectFileWriter.ToString(); + + using var reader = new StringReader(projectFileText); + using var xmlReader = XmlReader.Create(reader); + var projectRoot = ProjectRootElement.Create(xmlReader, projectCollection); + projectRoot.FullPath = projectFileFullPath; + return projectRoot; + } + } + + public static void WriteProjectFile( + TextWriter writer, + ImmutableArray directives, + IEnumerable<(string name, string value)> defaultProperties, + bool isVirtualProject, + string? targetFilePath = null, + string? artifactsPath = null, + bool includeRuntimeConfigInformation = true, + string? userSecretsId = null) + { + Debug.Assert(userSecretsId == null || !isVirtualProject); + + int processedDirectives = 0; + + var sdkDirectives = directives.OfType(); + var propertyDirectives = directives.OfType(); + var packageDirectives = directives.OfType(); + var projectDirectives = directives.OfType(); + + const string defaultSdkName = "Microsoft.NET.Sdk"; + string firstSdkName; + string? firstSdkVersion; + + if (sdkDirectives.FirstOrDefault() is { } firstSdk) + { + firstSdkName = firstSdk.Name; + firstSdkVersion = firstSdk.Version; + processedDirectives++; + } + else + { + firstSdkName = defaultSdkName; + firstSdkVersion = null; + } + + if (isVirtualProject) + { + Debug.Assert(!string.IsNullOrWhiteSpace(artifactsPath)); + + // Note that ArtifactsPath needs to be specified before Sdk.props + // (usually it's recommended to specify it in Directory.Build.props + // but importing Sdk.props manually afterwards also works). + writer.WriteLine($""" + + + + false + {EscapeValue(artifactsPath)} + artifacts/$(MSBuildProjectName) + artifacts/$(MSBuildProjectName) + true + false + true + """); + + // Only set these to false when using the default SDK with no additional SDKs + // to avoid including .resx and other files that are typically not expected in simple file-based apps. + // When other SDKs are used (e.g., Microsoft.NET.Sdk.Web), keep the default behavior. + bool usingOnlyDefaultSdk = firstSdkName == defaultSdkName && sdkDirectives.Count() <= 1; + if (usingOnlyDefaultSdk) + { + writer.WriteLine($""" + false + false + """); + } + + // Write default properties before importing SDKs so they can be overridden by SDKs + // (and implicit build files which are imported by the default .NET SDK). + foreach (var (name, value) in defaultProperties) + { + writer.WriteLine($""" + <{name}>{EscapeValue(value)} + """); + } + + writer.WriteLine($""" + + + + + + + """); + + if (firstSdkVersion is null) + { + writer.WriteLine($""" + + """); + } + else + { + writer.WriteLine($""" + + """); + } + } + else + { + string slashDelimited = firstSdkVersion is null + ? firstSdkName + : $"{firstSdkName}/{firstSdkVersion}"; + writer.WriteLine($""" + + + """); + } + + foreach (var sdk in sdkDirectives.Skip(1)) + { + if (isVirtualProject) + { + WriteImport(writer, "Sdk.props", sdk); + } + else if (sdk.Version is null) + { + writer.WriteLine($""" + + """); + } + else + { + writer.WriteLine($""" + + """); + } + + processedDirectives++; + } + + if (isVirtualProject || processedDirectives > 1) + { + writer.WriteLine(); + } + + // Write default and custom properties. + { + writer.WriteLine(""" + + """); + + // First write the default properties except those specified by the user. + if (!isVirtualProject) + { + var customPropertyNames = propertyDirectives + .Select(static d => d.Name) + .ToHashSet(StringComparer.OrdinalIgnoreCase); + + foreach (var (name, value) in defaultProperties) + { + if (!customPropertyNames.Contains(name)) + { + writer.WriteLine($""" + <{name}>{EscapeValue(value)} + """); + } + } + + if (userSecretsId != null && !customPropertyNames.Contains("UserSecretsId")) + { + writer.WriteLine($""" + {EscapeValue(userSecretsId)} + """); + } + } + + // Write custom properties. + foreach (var property in propertyDirectives) + { + writer.WriteLine($""" + <{property.Name}>{EscapeValue(property.Value)} + """); + + processedDirectives++; + } + + // Write virtual-only properties which cannot be overridden. + if (isVirtualProject) + { + writer.WriteLine(""" + false + $(Features);FileBasedProgram + """); + } + + writer.WriteLine(""" + + + """); + } + + if (packageDirectives.Any()) + { + writer.WriteLine(""" + + """); + + foreach (var package in packageDirectives) + { + if (package.Version is null) + { + writer.WriteLine($""" + + """); + } + else + { + writer.WriteLine($""" + + """); + } + + processedDirectives++; + } + + writer.WriteLine(""" + + + """); + } + + if (projectDirectives.Any()) + { + writer.WriteLine(""" + + """); + + foreach (var projectReference in projectDirectives) + { + writer.WriteLine($""" + + """); + + processedDirectives++; + } + + writer.WriteLine(""" + + + """); + } + + Debug.Assert(processedDirectives + directives.OfType().Count() == directives.Length); + + if (isVirtualProject) + { + Debug.Assert(targetFilePath is not null); + + // Only add explicit Compile item when EnableDefaultCompileItems is not true. + // When EnableDefaultCompileItems=true, the file is included via default MSBuild globbing. + // See https://github.com/dotnet/sdk/issues/51785 + writer.WriteLine($""" + + + + + """); + + if (includeRuntimeConfigInformation) + { + var targetDirectory = Path.GetDirectoryName(targetFilePath) ?? ""; + writer.WriteLine($""" + + + + + + """); + } + + foreach (var sdk in sdkDirectives) + { + WriteImport(writer, "Sdk.targets", sdk); + } + + if (!sdkDirectives.Any()) + { + Debug.Assert(firstSdkName == defaultSdkName && firstSdkVersion == null); + writer.WriteLine($""" + + """); + } + + writer.WriteLine(); + } + + writer.WriteLine(""" + + """); + + static string EscapeValue(string value) => SecurityElement.Escape(value); + + static void WriteImport(TextWriter writer, string project, CSharpDirective.Sdk sdk) + { + if (sdk.Version is null) + { + writer.WriteLine($""" + + """); + } + else + { + writer.WriteLine($""" + + """); + } + } + } + + public static SourceText? RemoveDirectivesFromFile(ImmutableArray directives, SourceText text) + { + if (directives.Length == 0) + { + return null; + } + + Debug.Assert(directives.OrderBy(d => d.Info.Span.Start).SequenceEqual(directives), "Directives should be ordered by source location."); + + for (int i = directives.Length - 1; i >= 0; i--) + { + var directive = directives[i]; + text = text.Replace(directive.Info.Span, string.Empty); + } + + return text; + } + + public static void RemoveDirectivesFromFile(ImmutableArray directives, SourceText text, string filePath) + { + if (RemoveDirectivesFromFile(directives, text) is { } modifiedText) + { + new SourceFile(filePath, modifiedText).Save(); + } + } +} diff --git a/test/Microsoft.NET.Build.Tests/GivenThatWeWantToBuildANetCoreAppForTelemetry.cs b/test/Microsoft.NET.Build.Tests/GivenThatWeWantToBuildANetCoreAppForTelemetry.cs index 2dfa4c6fda50..7e1b1e6f1896 100644 --- a/test/Microsoft.NET.Build.Tests/GivenThatWeWantToBuildANetCoreAppForTelemetry.cs +++ b/test/Microsoft.NET.Build.Tests/GivenThatWeWantToBuildANetCoreAppForTelemetry.cs @@ -2,7 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Reflection; -using Microsoft.DotNet.Cli.Utils; +using Microsoft.DotNet.Utilities; namespace Microsoft.NET.Build.Tests { diff --git a/test/Microsoft.NET.Build.Tests/Microsoft.NET.Build.Tests.csproj b/test/Microsoft.NET.Build.Tests/Microsoft.NET.Build.Tests.csproj index cda2e95e19e4..1bc85b007091 100644 --- a/test/Microsoft.NET.Build.Tests/Microsoft.NET.Build.Tests.csproj +++ b/test/Microsoft.NET.Build.Tests/Microsoft.NET.Build.Tests.csproj @@ -37,6 +37,7 @@ + diff --git a/test/Microsoft.TemplateEngine.Cli.UnitTests/TelemetryHelperTests.cs b/test/Microsoft.TemplateEngine.Cli.UnitTests/TelemetryHelperTests.cs index 842143be0acc..f7f61ea14770 100644 --- a/test/Microsoft.TemplateEngine.Cli.UnitTests/TelemetryHelperTests.cs +++ b/test/Microsoft.TemplateEngine.Cli.UnitTests/TelemetryHelperTests.cs @@ -2,7 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using FakeItEasy; -using Microsoft.DotNet.Cli.Utils; +using Microsoft.DotNet.Utilities; using Microsoft.TemplateEngine.Abstractions; using Microsoft.TemplateEngine.Abstractions.Parameters; using Microsoft.TemplateEngine.Utils; diff --git a/test/dotnet.Tests/CommandTests/Project/Convert/DotnetProjectConvertTests.cs b/test/dotnet.Tests/CommandTests/Project/Convert/DotnetProjectConvertTests.cs index 3dd157806387..1b800a103c8b 100644 --- a/test/dotnet.Tests/CommandTests/Project/Convert/DotnetProjectConvertTests.cs +++ b/test/dotnet.Tests/CommandTests/Project/Convert/DotnetProjectConvertTests.cs @@ -10,6 +10,7 @@ using Microsoft.DotNet.Cli.Run.Tests; using Microsoft.DotNet.Cli.Utils; using Microsoft.DotNet.FileBasedPrograms; +using Microsoft.DotNet.ProjectTools; namespace Microsoft.DotNet.Cli.Project.Convert.Tests; @@ -1003,6 +1004,51 @@ public void UserSecretsId_Overridden_SameAsImplicit(bool hasDirective, bool hasD """); } + [Fact] + public void ForceOption_Off() + { + var testInstance = _testAssetsManager.CreateTestDirectory(); + var filePath = Path.Join(testInstance.Path, "Program.cs"); + File.WriteAllText(filePath, """ + #:property Prop1=1 + #define X + #:property Prop2=2 + Console.WriteLine(); + #:property Prop1=3 + """); + + new DotnetCommand(Log, "project", "convert", "Program.cs") + .WithWorkingDirectory(testInstance.Path) + .Execute() + .Should().Fail() + .And.HaveStdErrContaining(FileBasedProgramsResources.CannotConvertDirective); + + new DirectoryInfo(Path.Join(testInstance.Path)) + .EnumerateDirectories().Should().BeEmpty(); + } + + [Fact] + public void ForceOption_On() + { + var testInstance = _testAssetsManager.CreateTestDirectory(); + var filePath = Path.Join(testInstance.Path, "Program.cs"); + File.WriteAllText(filePath, """ + #:property Prop1=1 + #define X + #:property Prop2=2 + Console.WriteLine(); + #:property Prop1=3 + """); + + new DotnetCommand(Log, "project", "convert", "--force", "Program.cs") + .WithWorkingDirectory(testInstance.Path) + .Execute() + .Should().Pass(); + + new DirectoryInfo(Path.Join(testInstance.Path)) + .EnumerateDirectories().Should().NotBeEmpty(); + } + [Fact] public void Directives() { @@ -1698,11 +1744,11 @@ private static void Convert(string inputCSharp, out string actualProject, out st actualDiagnostics = null; var diagnosticBag = collectDiagnostics ? ErrorReporters.CreateCollectingReporter(out actualDiagnostics) : VirtualProjectBuildingCommand.ThrowingReporter; var directives = FileLevelDirectiveHelpers.FindDirectives(sourceFile, reportAllErrors: !force, diagnosticBag); - directives = VirtualProjectBuildingCommand.EvaluateDirectives(project: null, directives, sourceFile, diagnosticBag); + directives = VirtualProjectBuilder.EvaluateDirectives(project: null, directives, sourceFile, diagnosticBag); var projectWriter = new StringWriter(); - VirtualProjectBuildingCommand.WriteProjectFile(projectWriter, directives, isVirtualProject: false); + VirtualProjectBuilder.WriteProjectFile(projectWriter, directives, VirtualProjectBuilder.GetDefaultProperties(VirtualProjectBuildingCommand.TargetFrameworkVersion), isVirtualProject: false); actualProject = projectWriter.ToString(); - actualCSharp = VirtualProjectBuildingCommand.RemoveDirectivesFromFile(directives, sourceFile.Text)?.ToString(); + actualCSharp = VirtualProjectBuilder.RemoveDirectivesFromFile(directives, sourceFile.Text)?.ToString(); } /// diff --git a/test/dotnet.Tests/CommandTests/Run/RunFileTests.cs b/test/dotnet.Tests/CommandTests/Run/RunFileTests.cs index ac3e989cfa21..42a012d1a03d 100644 --- a/test/dotnet.Tests/CommandTests/Run/RunFileTests.cs +++ b/test/dotnet.Tests/CommandTests/Run/RunFileTests.cs @@ -11,6 +11,7 @@ using Microsoft.DotNet.Cli.Commands.Run; using Microsoft.DotNet.Cli.Utils; using Microsoft.DotNet.FileBasedPrograms; +using Microsoft.DotNet.ProjectTools; namespace Microsoft.DotNet.Cli.Run.Tests; @@ -912,7 +913,7 @@ public void WorkingDirectory(bool cscOnly) var workDir = TestPathUtility.ResolveTempPrefixLink(Path.GetTempPath()).TrimEnd(Path.DirectorySeparatorChar); - var artifactsDir = VirtualProjectBuildingCommand.GetArtifactsPath(programPath); + var artifactsDir = VirtualProjectBuilder.GetArtifactsPath(programPath); if (Directory.Exists(artifactsDir)) Directory.Delete(artifactsDir, recursive: true); Build(testInstance, @@ -961,7 +962,7 @@ public void WorkingDirectory_CscOnly_AfterMSBuild() var workDir = TestPathUtility.ResolveTempPrefixLink(Path.GetTempPath()).TrimEnd(Path.DirectorySeparatorChar); - var artifactsDir = VirtualProjectBuildingCommand.GetArtifactsPath(programPath); + var artifactsDir = VirtualProjectBuilder.GetArtifactsPath(programPath); if (Directory.Exists(artifactsDir)) Directory.Delete(artifactsDir, recursive: true); Build(testInstance, @@ -1668,7 +1669,7 @@ public void NoRestore_01() File.WriteAllText(programFile, s_program); // Remove artifacts from possible previous runs of this test. - var artifactsDir = VirtualProjectBuildingCommand.GetArtifactsPath(programFile); + var artifactsDir = VirtualProjectBuilder.GetArtifactsPath(programFile); if (Directory.Exists(artifactsDir)) Directory.Delete(artifactsDir, recursive: true); // It is an error when never restored before. @@ -1700,7 +1701,7 @@ public void NoRestore_02() File.WriteAllText(programFile, s_program); // Remove artifacts from possible previous runs of this test. - var artifactsDir = VirtualProjectBuildingCommand.GetArtifactsPath(programFile); + var artifactsDir = VirtualProjectBuilder.GetArtifactsPath(programFile); if (Directory.Exists(artifactsDir)) Directory.Delete(artifactsDir, recursive: true); // It is an error when never restored before. @@ -1744,7 +1745,7 @@ public void Restore_StaticGraph_Implicit() File.WriteAllText(programFile, "Console.WriteLine();"); // Remove artifacts from possible previous runs of this test. - var artifactsDir = VirtualProjectBuildingCommand.GetArtifactsPath(programFile); + var artifactsDir = VirtualProjectBuilder.GetArtifactsPath(programFile); if (Directory.Exists(artifactsDir)) Directory.Delete(artifactsDir, recursive: true); new DotnetCommand(Log, "restore", "Program.cs") @@ -1764,7 +1765,7 @@ public void Restore_StaticGraph_Explicit() """); // Remove artifacts from possible previous runs of this test. - var artifactsDir = VirtualProjectBuildingCommand.GetArtifactsPath(programFile); + var artifactsDir = VirtualProjectBuilder.GetArtifactsPath(programFile); if (Directory.Exists(artifactsDir)) Directory.Delete(artifactsDir, recursive: true); new DotnetCommand(Log, "restore", "Program.cs") @@ -1782,7 +1783,7 @@ public void NoBuild_01() File.WriteAllText(programFile, s_program); // Remove artifacts from possible previous runs of this test. - var artifactsDir = VirtualProjectBuildingCommand.GetArtifactsPath(programFile); + var artifactsDir = VirtualProjectBuilder.GetArtifactsPath(programFile); if (Directory.Exists(artifactsDir)) Directory.Delete(artifactsDir, recursive: true); // It is an error when never built before. @@ -1822,7 +1823,7 @@ public void NoBuild_02() File.WriteAllText(programFile, s_program); // Remove artifacts from possible previous runs of this test. - var artifactsDir = VirtualProjectBuildingCommand.GetArtifactsPath(programFile); + var artifactsDir = VirtualProjectBuilder.GetArtifactsPath(programFile); if (Directory.Exists(artifactsDir)) Directory.Delete(artifactsDir, recursive: true); // It is an error when never built before. @@ -1865,7 +1866,7 @@ public void Build_Library() class C; """); - var artifactsDir = VirtualProjectBuildingCommand.GetArtifactsPath(programFile); + var artifactsDir = VirtualProjectBuilder.GetArtifactsPath(programFile); if (Directory.Exists(artifactsDir)) Directory.Delete(artifactsDir, recursive: true); new DotnetCommand(Log, "build", "lib.cs") @@ -1905,7 +1906,7 @@ class C; """); - var artifactsDir = VirtualProjectBuildingCommand.GetArtifactsPath(programFile); + var artifactsDir = VirtualProjectBuilder.GetArtifactsPath(programFile); if (Directory.Exists(artifactsDir)) Directory.Delete(artifactsDir, recursive: true); new DotnetCommand(Log, "build", "lib.cs") @@ -1940,7 +1941,7 @@ public void Build_Module() class C; """); - var artifactsDir = VirtualProjectBuildingCommand.GetArtifactsPath(programFile); + var artifactsDir = VirtualProjectBuilder.GetArtifactsPath(programFile); if (Directory.Exists(artifactsDir)) Directory.Delete(artifactsDir, recursive: true); new DotnetCommand(Log, "build", "module.cs") @@ -1968,7 +1969,7 @@ public void Build_WinExe() Console.WriteLine("Hello WinExe"); """); - var artifactsDir = VirtualProjectBuildingCommand.GetArtifactsPath(programFile); + var artifactsDir = VirtualProjectBuilder.GetArtifactsPath(programFile); if (Directory.Exists(artifactsDir)) Directory.Delete(artifactsDir, recursive: true); new DotnetCommand(Log, "build", "winexe.cs") @@ -1993,7 +1994,7 @@ public void Build_Exe() Console.WriteLine("Hello Exe"); """); - var artifactsDir = VirtualProjectBuildingCommand.GetArtifactsPath(programFile); + var artifactsDir = VirtualProjectBuilder.GetArtifactsPath(programFile); if (Directory.Exists(artifactsDir)) Directory.Delete(artifactsDir, recursive: true); new DotnetCommand(Log, "build", "exe.cs") @@ -2030,7 +2031,7 @@ public void Build_Exe_MultiTarget() """); - var artifactsDir = VirtualProjectBuildingCommand.GetArtifactsPath(programFile); + var artifactsDir = VirtualProjectBuilder.GetArtifactsPath(programFile); if (Directory.Exists(artifactsDir)) Directory.Delete(artifactsDir, recursive: true); new DotnetCommand(Log, "build", "exe.cs") @@ -2061,7 +2062,7 @@ public void Build_AppContainerExe() Console.WriteLine("Hello AppContainerExe"); """); - var artifactsDir = VirtualProjectBuildingCommand.GetArtifactsPath(programFile); + var artifactsDir = VirtualProjectBuilder.GetArtifactsPath(programFile); if (Directory.Exists(artifactsDir)) Directory.Delete(artifactsDir, recursive: true); new DotnetCommand(Log, "build", "appcontainerexe.cs") @@ -2086,7 +2087,7 @@ public void Publish() var programFile = Path.Join(testInstance.Path, "Program.cs"); File.WriteAllText(programFile, s_program); - var artifactsDir = VirtualProjectBuildingCommand.GetArtifactsPath(programFile); + var artifactsDir = VirtualProjectBuilder.GetArtifactsPath(programFile); if (Directory.Exists(artifactsDir)) Directory.Delete(artifactsDir, recursive: true); var publishDir = Path.Join(testInstance.Path, "artifacts"); @@ -2117,7 +2118,7 @@ public void PublishWithCustomTarget() var programFile = Path.Join(testInstance.Path, "Program.cs"); File.WriteAllText(programFile, s_program); - var artifactsDir = VirtualProjectBuildingCommand.GetArtifactsPath(programFile); + var artifactsDir = VirtualProjectBuilder.GetArtifactsPath(programFile); if (Directory.Exists(artifactsDir)) Directory.Delete(artifactsDir, recursive: true); var publishDir = Path.Join(testInstance.Path, "artifacts"); @@ -2152,7 +2153,7 @@ public void Publish_WithJson() { "MyKey": "MyValue" } """); - var artifactsDir = VirtualProjectBuildingCommand.GetArtifactsPath(programFile); + var artifactsDir = VirtualProjectBuilder.GetArtifactsPath(programFile); if (Directory.Exists(artifactsDir)) Directory.Delete(artifactsDir, recursive: true); var publishDir = Path.Join(testInstance.Path, "artifacts"); @@ -2176,7 +2177,7 @@ public void Publish_Options() var programFile = Path.Join(testInstance.Path, "Program.cs"); File.WriteAllText(programFile, s_program); - var artifactsDir = VirtualProjectBuildingCommand.GetArtifactsPath(programFile); + var artifactsDir = VirtualProjectBuilder.GetArtifactsPath(programFile); if (Directory.Exists(artifactsDir)) Directory.Delete(artifactsDir, recursive: true); var publishDir = Path.Join(testInstance.Path, "artifacts"); @@ -2201,7 +2202,7 @@ public void Publish_PublishDir_IncludesFileName() var programFile = Path.Join(testInstance.Path, "MyCustomProgram.cs"); File.WriteAllText(programFile, s_program); - var artifactsDir = VirtualProjectBuildingCommand.GetArtifactsPath(programFile); + var artifactsDir = VirtualProjectBuilder.GetArtifactsPath(programFile); if (Directory.Exists(artifactsDir)) Directory.Delete(artifactsDir, recursive: true); var publishDir = Path.Join(testInstance.Path, "artifacts"); @@ -2269,7 +2270,7 @@ public void Publish_In_SubDir() var programFile = Path.Join(subDir.FullName, "Program.cs"); File.WriteAllText(programFile, s_program); - var artifactsDir = VirtualProjectBuildingCommand.GetArtifactsPath(programFile); + var artifactsDir = VirtualProjectBuilder.GetArtifactsPath(programFile); if (Directory.Exists(artifactsDir)) Directory.Delete(artifactsDir, recursive: true); var publishDir = Path.Join(subDir.FullName, "artifacts"); @@ -2304,7 +2305,7 @@ public void Pack() .Should().Pass() .And.HaveStdOut("Hello; EntryPointFilePath set? True"); - var artifactsDir = VirtualProjectBuildingCommand.GetArtifactsPath(programFile); + var artifactsDir = VirtualProjectBuilder.GetArtifactsPath(programFile); if (Directory.Exists(artifactsDir)) Directory.Delete(artifactsDir, recursive: true); var outputDir = Path.Join(testInstance.Path, "artifacts"); @@ -2348,7 +2349,7 @@ public void Pack_CustomPath() .Should().Pass() .And.HaveStdOut("Hello; EntryPointFilePath set? True"); - var artifactsDir = VirtualProjectBuildingCommand.GetArtifactsPath(programFile); + var artifactsDir = VirtualProjectBuilder.GetArtifactsPath(programFile); if (Directory.Exists(artifactsDir)) Directory.Delete(artifactsDir, recursive: true); var outputDir = Path.Join(testInstance.Path, "custom"); @@ -2384,7 +2385,7 @@ public void Clean() .Should().Pass() .And.HaveStdOut("Hello from Program"); - var artifactsDir = new DirectoryInfo(VirtualProjectBuildingCommand.GetArtifactsPath(programFile)); + var artifactsDir = new DirectoryInfo(VirtualProjectBuilder.GetArtifactsPath(programFile)); artifactsDir.Should().HaveFiles(["build-start.cache", "build-success.cache"]); var dllFile = artifactsDir.File("bin/debug/Program.dll"); @@ -2409,7 +2410,7 @@ public void ArtifactsDirectory_Permissions() File.WriteAllText(programFile, s_program); // Remove artifacts from possible previous runs of this test. - var artifactsDir = VirtualProjectBuildingCommand.GetArtifactsPath(programFile); + var artifactsDir = VirtualProjectBuilder.GetArtifactsPath(programFile); if (Directory.Exists(artifactsDir)) Directory.Delete(artifactsDir, recursive: true); new DotnetCommand(Log, "build", "Program.cs") @@ -2898,7 +2899,7 @@ public void UserSecrets(bool useIdArg, string? userSecretsId) File.WriteAllText(programPath, code); // Remove artifacts from possible previous runs of this test. - var artifactsDir = VirtualProjectBuildingCommand.GetArtifactsPath(programPath); + var artifactsDir = VirtualProjectBuilder.GetArtifactsPath(programPath); if (Directory.Exists(artifactsDir)) Directory.Delete(artifactsDir, recursive: true); if (useIdArg) @@ -2953,7 +2954,7 @@ public void CscArguments() File.WriteAllText(entryPointPath, s_program); // Remove artifacts from possible previous runs of this test. - var artifactsDir = VirtualProjectBuildingCommand.GetArtifactsPath(entryPointPath); + var artifactsDir = VirtualProjectBuilder.GetArtifactsPath(entryPointPath); if (Directory.Exists(artifactsDir)) Directory.Delete(artifactsDir, recursive: true); // Build using MSBuild. @@ -3235,7 +3236,7 @@ public void CscVsMSBuild(string fileName) string programName = Path.GetFileNameWithoutExtension(fileName); // Remove artifacts from possible previous runs of this test. - var artifactsDir = VirtualProjectBuildingCommand.GetArtifactsPath(entryPointPath); + var artifactsDir = VirtualProjectBuilder.GetArtifactsPath(entryPointPath); if (Directory.Exists(artifactsDir)) Directory.Delete(artifactsDir, recursive: true); var artifactsBackupDir = Path.ChangeExtension(artifactsDir, ".bak"); if (Directory.Exists(artifactsBackupDir)) Directory.Delete(artifactsBackupDir, recursive: true); @@ -3310,7 +3311,7 @@ public void UpToDate() """); // Remove artifacts from possible previous runs of this test. - var artifactsDir = VirtualProjectBuildingCommand.GetArtifactsPath(Path.Join(testInstance.Path, "Program.cs")); + var artifactsDir = VirtualProjectBuilder.GetArtifactsPath(Path.Join(testInstance.Path, "Program.cs")); if (Directory.Exists(artifactsDir)) Directory.Delete(artifactsDir, recursive: true); Build(testInstance, BuildLevel.Csc, expectedOutput: "Hello v1"); @@ -3493,7 +3494,7 @@ public void UpToDate_SymbolicLink() File.CreateSymbolicLink(path: programPath, pathToTarget: originalPath); // Remove artifacts from possible previous runs of this test. - var artifactsDir = VirtualProjectBuildingCommand.GetArtifactsPath(programPath); + var artifactsDir = VirtualProjectBuilder.GetArtifactsPath(programPath); if (Directory.Exists(artifactsDir)) Directory.Delete(artifactsDir, recursive: true); Build(testInstance, BuildLevel.All, expectedOutput: "v1", programFileName: programFileName); @@ -3533,7 +3534,7 @@ public void UpToDate_SymbolicLink2() File.CreateSymbolicLink(path: programPath, pathToTarget: intermediatePath); // Remove artifacts from possible previous runs of this test. - var artifactsDir = VirtualProjectBuildingCommand.GetArtifactsPath(programPath); + var artifactsDir = VirtualProjectBuilder.GetArtifactsPath(programPath); if (Directory.Exists(artifactsDir)) Directory.Delete(artifactsDir, recursive: true); Build(testInstance, BuildLevel.All, expectedOutput: "v1", programFileName: programFileName); @@ -3588,7 +3589,7 @@ public class LibClass File.WriteAllText(programPath, code); // Remove artifacts from possible previous runs of this test. - var artifactsDir = VirtualProjectBuildingCommand.GetArtifactsPath(programPath); + var artifactsDir = VirtualProjectBuilder.GetArtifactsPath(programPath); if (Directory.Exists(artifactsDir)) Directory.Delete(artifactsDir, recursive: true); var programFileName = "App/Program.cs"; @@ -3683,7 +3684,7 @@ public void CscOnly() """); // Remove artifacts from possible previous runs of this test. - var artifactsDir = VirtualProjectBuildingCommand.GetArtifactsPath(Path.Join(testInstance.Path, "Program.cs")); + var artifactsDir = VirtualProjectBuilder.GetArtifactsPath(Path.Join(testInstance.Path, "Program.cs")); if (Directory.Exists(artifactsDir)) Directory.Delete(artifactsDir, recursive: true); Build(testInstance, BuildLevel.Csc, expectedOutput: "v1"); @@ -3801,7 +3802,7 @@ public void CscOnly_IntermediateFiles() """); // Remove artifacts from possible previous runs of this test. - var artifactsDir = VirtualProjectBuildingCommand.GetArtifactsPath(Path.Join(testInstance.Path, "Program.cs")); + var artifactsDir = VirtualProjectBuilder.GetArtifactsPath(Path.Join(testInstance.Path, "Program.cs")); if (Directory.Exists(artifactsDir)) Directory.Delete(artifactsDir, recursive: true); File.WriteAllText(Path.Join(testInstance.Path, "Directory.Build.props"), ""); @@ -3844,7 +3845,7 @@ public void CscOnly_NotRestored() File.WriteAllText(Path.Join(testInstance.Path, "Program.cs"), s_program); // Remove artifacts from possible previous runs of this test. - var artifactsDir = VirtualProjectBuildingCommand.GetArtifactsPath(Path.Join(testInstance.Path, "Program.cs")); + var artifactsDir = VirtualProjectBuilder.GetArtifactsPath(Path.Join(testInstance.Path, "Program.cs")); if (Directory.Exists(artifactsDir)) Directory.Delete(artifactsDir, recursive: true); new DotnetCommand(Log, "run", "Program.cs", "-bl", "--no-restore") @@ -3890,7 +3891,7 @@ public void CscOnly_SpacesInPath() """); // Remove artifacts from possible previous runs of this test. - var artifactsDir = VirtualProjectBuildingCommand.GetArtifactsPath(programPath); + var artifactsDir = VirtualProjectBuilder.GetArtifactsPath(programPath); if (Directory.Exists(artifactsDir)) Directory.Delete(artifactsDir, recursive: true); Build(testInstance, BuildLevel.Csc, expectedOutput: "v1", programFileName: programFileName); @@ -3904,7 +3905,7 @@ public void CscOnly_Args() File.WriteAllText(programPath, s_program); // Remove artifacts from possible previous runs of this test. - var artifactsDir = VirtualProjectBuildingCommand.GetArtifactsPath(programPath); + var artifactsDir = VirtualProjectBuilder.GetArtifactsPath(programPath); if (Directory.Exists(artifactsDir)) Directory.Delete(artifactsDir, recursive: true); Build(testInstance, BuildLevel.Csc, args: ["test", "args"], expectedOutput: """ @@ -3935,7 +3936,7 @@ public void CscOnly_SymbolicLink() File.CreateSymbolicLink(path: programPath, pathToTarget: originalPath); // Remove artifacts from possible previous runs of this test. - var artifactsDir = VirtualProjectBuildingCommand.GetArtifactsPath(programPath); + var artifactsDir = VirtualProjectBuilder.GetArtifactsPath(programPath); if (Directory.Exists(artifactsDir)) Directory.Delete(artifactsDir, recursive: true); Build(testInstance, BuildLevel.Csc, expectedOutput: "v1", programFileName: programFileName); @@ -3970,7 +3971,7 @@ public void CscOnly_AfterMSBuild() File.WriteAllText(programPath, code); // Remove artifacts from possible previous runs of this test. - var artifactsDir = VirtualProjectBuildingCommand.GetArtifactsPath(programPath); + var artifactsDir = VirtualProjectBuilder.GetArtifactsPath(programPath); if (Directory.Exists(artifactsDir)) Directory.Delete(artifactsDir, recursive: true); Build(testInstance, BuildLevel.All, expectedOutput: "v1 Release"); @@ -4029,7 +4030,7 @@ public void CscOnly_AfterMSBuild_SpacesInPath() File.WriteAllText(programPath, code); // Remove artifacts from possible previous runs of this test. - var artifactsDir = VirtualProjectBuildingCommand.GetArtifactsPath(programPath); + var artifactsDir = VirtualProjectBuilder.GetArtifactsPath(programPath); if (Directory.Exists(artifactsDir)) Directory.Delete(artifactsDir, recursive: true); Build(testInstance, BuildLevel.All, expectedOutput: "v1 Release", programFileName: programFileName); @@ -4057,7 +4058,7 @@ public void CscOnly_AfterMSBuild_Args() File.WriteAllText(programPath, code); // Remove artifacts from possible previous runs of this test. - var artifactsDir = VirtualProjectBuildingCommand.GetArtifactsPath(programPath); + var artifactsDir = VirtualProjectBuilder.GetArtifactsPath(programPath); if (Directory.Exists(artifactsDir)) Directory.Delete(artifactsDir, recursive: true); Build(testInstance, BuildLevel.All, args: ["test", "args"], expectedOutput: """ @@ -4096,7 +4097,7 @@ public void CscOnly_AfterMSBuild_HardLinks() File.WriteAllText(programPath, code); // Remove artifacts from possible previous runs of this test. - var artifactsDir = VirtualProjectBuildingCommand.GetArtifactsPath(programPath); + var artifactsDir = VirtualProjectBuilder.GetArtifactsPath(programPath); if (Directory.Exists(artifactsDir)) Directory.Delete(artifactsDir, recursive: true); Build(testInstance, BuildLevel.All); @@ -4130,7 +4131,7 @@ public void CscOnly_AfterMSBuild_SymbolicLink() File.CreateSymbolicLink(path: programPath, pathToTarget: originalPath); // Remove artifacts from possible previous runs of this test. - var artifactsDir = VirtualProjectBuildingCommand.GetArtifactsPath(programPath); + var artifactsDir = VirtualProjectBuilder.GetArtifactsPath(programPath); if (Directory.Exists(artifactsDir)) Directory.Delete(artifactsDir, recursive: true); Build(testInstance, BuildLevel.All, expectedOutput: "v1", programFileName: programFileName); @@ -4183,7 +4184,7 @@ public class LibClass File.WriteAllText(programPath, code); // Remove artifacts from possible previous runs of this test. - var artifactsDir = VirtualProjectBuildingCommand.GetArtifactsPath(programPath); + var artifactsDir = VirtualProjectBuilder.GetArtifactsPath(programPath); if (Directory.Exists(artifactsDir)) Directory.Delete(artifactsDir, recursive: true); var programFileName = "App/Program.cs"; @@ -4235,7 +4236,7 @@ public void CscOnly_AfterMSBuild_OptOut(bool canSkipMSBuild, bool inDirectoryBui File.WriteAllText(programPath, code); // Remove artifacts from possible previous runs of this test. - var artifactsDir = VirtualProjectBuildingCommand.GetArtifactsPath(programPath); + var artifactsDir = VirtualProjectBuilder.GetArtifactsPath(programPath); if (Directory.Exists(artifactsDir)) Directory.Delete(artifactsDir, recursive: true); Build(testInstance, BuildLevel.All, expectedOutput: "v1 Release"); @@ -4266,7 +4267,7 @@ public void CscOnly_AfterMSBuild_AuxiliaryFilesNotReused() File.WriteAllText(programPath, code); // Remove artifacts from possible previous runs of this test. - var artifactsDir = VirtualProjectBuildingCommand.GetArtifactsPath(programPath); + var artifactsDir = VirtualProjectBuilder.GetArtifactsPath(programPath); if (Directory.Exists(artifactsDir)) Directory.Delete(artifactsDir, recursive: true); Build(testInstance, BuildLevel.All, expectedOutput: "v1 Release"); @@ -4565,7 +4566,7 @@ public void EntryPointFilePath(bool cscOnly) """"); // Remove artifacts from possible previous runs of this test. - var artifactsDir = VirtualProjectBuildingCommand.GetArtifactsPath(filePath); + var artifactsDir = VirtualProjectBuilder.GetArtifactsPath(filePath); if (Directory.Exists(artifactsDir)) Directory.Delete(artifactsDir, recursive: true); var prefix = cscOnly diff --git a/test/dotnet.Tests/TelemetryCommandTest.cs b/test/dotnet.Tests/TelemetryCommandTest.cs index 8d31a917db13..aa180fe14930 100644 --- a/test/dotnet.Tests/TelemetryCommandTest.cs +++ b/test/dotnet.Tests/TelemetryCommandTest.cs @@ -6,6 +6,7 @@ using Microsoft.DotNet.Cli.Commands.Hidden.InternalReportInstallSuccess; using Microsoft.DotNet.Cli.Telemetry; using Microsoft.DotNet.Cli.Utils; +using Microsoft.DotNet.Utilities; namespace Microsoft.DotNet.Tests { diff --git a/test/dotnet.Tests/TelemetryFilterTest.cs b/test/dotnet.Tests/TelemetryFilterTest.cs index 5b067a3f5cca..db5f835e791d 100644 --- a/test/dotnet.Tests/TelemetryFilterTest.cs +++ b/test/dotnet.Tests/TelemetryFilterTest.cs @@ -5,6 +5,7 @@ using Microsoft.DotNet.Cli.Telemetry; using Microsoft.DotNet.Cli.Utils; +using Microsoft.DotNet.Utilities; using Parser = Microsoft.DotNet.Cli.Parser; namespace Microsoft.DotNet.Tests