Skip to content

Commit 6769668

Browse files
authored
Avoid unnecessary evaluations during dotnet run file.cs (#49991)
1 parent daf90b6 commit 6769668

File tree

11 files changed

+236
-104
lines changed

11 files changed

+236
-104
lines changed

src/Cli/Microsoft.DotNet.Cli.Utils/MSBuildArgs.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -167,11 +167,11 @@ public MSBuildArgs CloneWithAdditionalProperties(ReadOnlyDictionary<string, stri
167167
return new MSBuildArgs(new(newProperties), RestoreGlobalProperties, RequestedTargets, Verbosity, OtherMSBuildArgs.ToArray());
168168
}
169169

170-
public MSBuildArgs CloneWithAdditionalTarget(string additionalTarget)
170+
public MSBuildArgs CloneWithAdditionalTargets(params ReadOnlySpan<string> additionalTargets)
171171
{
172172
string[] newTargets = RequestedTargets is not null
173-
? [.. RequestedTargets, additionalTarget]
174-
: [ additionalTarget ];
173+
? [.. RequestedTargets, .. additionalTargets]
174+
: [.. additionalTargets];
175175
return new MSBuildArgs(GlobalProperties, RestoreGlobalProperties, newTargets, Verbosity, OtherMSBuildArgs.ToArray());
176176
}
177177

src/Cli/dotnet/Commands/Restore/RestoringCommand.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,7 @@ private static MSBuildArgs GetCommandArguments(
125125
.ToDictionary(kvp => kvp.Key, kvp => kvp.Value, StringComparer.OrdinalIgnoreCase) is { } filteredList ? new(filteredList): ReadOnlyDictionary<string, string>.Empty;
126126
var restoreMSBuildArgs =
127127
MSBuildArgs.FromProperties(RestoreOptimizationProperties)
128-
.CloneWithAdditionalTarget("Restore")
128+
.CloneWithAdditionalTargets("Restore")
129129
.CloneWithExplicitArgs([.. newArgumentsToAdd, .. existingArgumentsToForward])
130130
.CloneWithAdditionalProperties(restoreProperties);
131131
if (msbuildArgs.Verbosity is {} verbosity)

src/Cli/dotnet/Commands/Run/Api/RunApiCommand.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ public override RunApiOutput Execute()
111111
msbuildRestoreProperties: ReadOnlyDictionary<string, string>.Empty);
112112

113113
runCommand.TryGetLaunchProfileSettingsIfNeeded(out var launchSettings);
114-
var targetCommand = (Utils.Command)runCommand.GetTargetCommand(buildCommand.CreateProjectInstance);
114+
var targetCommand = (Utils.Command)runCommand.GetTargetCommand(buildCommand.CreateProjectInstance, cachedRunProperties: null);
115115
runCommand.ApplyLaunchSettingsProfileToCommand(targetCommand, launchSettings);
116116

117117
return new RunApiOutput.RunCommand

src/Cli/dotnet/Commands/Run/CSharpCompilerCommand.cs

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -129,10 +129,6 @@ static int ProcessBuildResponse(BuildResponse response, out bool fallbackToNorma
129129

130130
private void PrepareAuxiliaryFiles(out string rspPath)
131131
{
132-
Reporter.Verbose.WriteLine(CanReuseAuxiliaryFiles
133-
? "CSC auxiliary files can be reused."
134-
: "CSC auxiliary files can NOT be reused.");
135-
136132
string fileDirectory = Path.GetDirectoryName(EntryPointFileFullPath) ?? string.Empty;
137133
string fileNameWithoutExtension = Path.GetFileNameWithoutExtension(EntryPointFileFullPath);
138134

src/Cli/dotnet/Commands/Run/RunCommand.cs

Lines changed: 44 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
// Licensed to the .NET Foundation under one or more agreements.
22
// The .NET Foundation licenses this file to you under the MIT license.
33

4-
using System.Collections.Frozen;
54
using System.Collections.Immutable;
65
using System.Collections.ObjectModel;
76
using System.CommandLine;
@@ -124,14 +123,15 @@ public int Execute()
124123
}
125124

126125
Func<ProjectCollection, ProjectInstance>? projectFactory = null;
126+
RunProperties? cachedRunProperties = null;
127127
if (ShouldBuild)
128128
{
129129
if (string.Equals("true", launchSettings?.DotNetRunMessages, StringComparison.OrdinalIgnoreCase))
130130
{
131131
Reporter.Output.WriteLine(CliCommandStrings.RunCommandBuilding);
132132
}
133133

134-
EnsureProjectIsBuilt(out projectFactory);
134+
EnsureProjectIsBuilt(out projectFactory, out cachedRunProperties);
135135
}
136136
else
137137
{
@@ -145,13 +145,16 @@ public int Execute()
145145
Debug.Assert(!ReadCodeFromStdin);
146146
var command = CreateVirtualCommand();
147147
command.MarkArtifactsFolderUsed();
148-
projectFactory = command.CreateProjectInstance;
148+
149+
var cacheEntry = command.GetPreviousCacheEntry();
150+
projectFactory = CanUseRunPropertiesForCscBuiltProgram(BuildLevel.None, cacheEntry) ? null : command.CreateProjectInstance;
151+
cachedRunProperties = cacheEntry?.Run;
149152
}
150153
}
151154

152155
try
153156
{
154-
ICommand targetCommand = GetTargetCommand(projectFactory);
157+
ICommand targetCommand = GetTargetCommand(projectFactory, cachedRunProperties);
155158
ApplyLaunchSettingsProfileToCommand(targetCommand, launchSettings);
156159

157160
// Env variables specified on command line override those specified in launch profile:
@@ -294,22 +297,24 @@ internal bool TryGetLaunchProfileSettingsIfNeeded(out ProjectLaunchSettingsModel
294297
}
295298
}
296299

297-
private void EnsureProjectIsBuilt(out Func<ProjectCollection, ProjectInstance>? projectFactory)
300+
private void EnsureProjectIsBuilt(out Func<ProjectCollection, ProjectInstance>? projectFactory, out RunProperties? cachedRunProperties)
298301
{
299302
int buildResult;
300303
if (EntryPointFileFullPath is not null)
301304
{
302305
var command = CreateVirtualCommand();
303306
buildResult = command.Execute();
304-
projectFactory = command.LastBuildLevel is BuildLevel.Csc ? null : command.CreateProjectInstance;
307+
projectFactory = CanUseRunPropertiesForCscBuiltProgram(command.LastBuild.Level, command.LastBuild.Cache?.PreviousEntry) ? null : command.CreateProjectInstance;
308+
cachedRunProperties = command.LastBuild.Cache?.CurrentEntry.Run;
305309
}
306310
else
307311
{
308312
Debug.Assert(ProjectFileFullPath is not null);
309313

310314
projectFactory = null;
315+
cachedRunProperties = null;
311316
buildResult = new RestoringCommand(
312-
MSBuildArgs.CloneWithExplicitArgs([ProjectFileFullPath, ..MSBuildArgs.OtherMSBuildArgs]),
317+
MSBuildArgs.CloneWithExplicitArgs([ProjectFileFullPath, .. MSBuildArgs.OtherMSBuildArgs]),
313318
NoRestore,
314319
advertiseWorkloadUpdates: false
315320
).Execute();
@@ -322,13 +327,23 @@ private void EnsureProjectIsBuilt(out Func<ProjectCollection, ProjectInstance>?
322327
}
323328
}
324329

330+
private static bool CanUseRunPropertiesForCscBuiltProgram(BuildLevel level, RunFileBuildCacheEntry? previousCache)
331+
{
332+
return level == BuildLevel.Csc ||
333+
(level == BuildLevel.None && previousCache?.BuildLevel == BuildLevel.Csc);
334+
}
335+
325336
private VirtualProjectBuildingCommand CreateVirtualCommand()
326337
{
327338
Debug.Assert(EntryPointFileFullPath != null);
328339

340+
var args = MSBuildArgs.RequestedTargets is null or []
341+
? MSBuildArgs.CloneWithAdditionalTargets("Build", ComputeRunArgumentsTarget)
342+
: MSBuildArgs.CloneWithAdditionalTargets(ComputeRunArgumentsTarget);
343+
329344
return new(
330345
entryPointFileFullPath: EntryPointFileFullPath,
331-
msbuildArgs: MSBuildArgs)
346+
msbuildArgs: args)
332347
{
333348
NoRestore = NoRestore,
334349
NoCache = NoCache,
@@ -361,23 +376,33 @@ private MSBuildArgs SetupSilentBuildArgs(MSBuildArgs msbuildArgs)
361376
}
362377
}
363378

364-
internal ICommand GetTargetCommand(Func<ProjectCollection, ProjectInstance>? projectFactory)
379+
internal ICommand GetTargetCommand(Func<ProjectCollection, ProjectInstance>? projectFactory, RunProperties? cachedRunProperties)
365380
{
366381
if (projectFactory is null && ProjectFileFullPath is null)
367382
{
368383
// If we are running a file-based app and projectFactory is null, it means csc was used instead of full msbuild.
369384
// So we can skip project evaluation to continue the optimized path.
370385
Debug.Assert(EntryPointFileFullPath is not null);
386+
Reporter.Verbose.WriteLine("Getting target command: for csc-built program.");
371387
return CreateCommandForCscBuiltProgram(EntryPointFileFullPath);
372388
}
373389

390+
if (cachedRunProperties != null)
391+
{
392+
// We can also skip project evaluation if we already evaluated the project during virtual build
393+
// or we have cached run properties in previous run (and this is a --no-build run).
394+
Reporter.Verbose.WriteLine("Getting target command: from cache.");
395+
return CreateCommandFromRunProperties(cachedRunProperties.WithApplicationArguments(ApplicationArgs));
396+
}
397+
398+
Reporter.Verbose.WriteLine("Getting target command: evaluating project.");
374399
FacadeLogger? logger = LoggerUtility.DetermineBinlogger([.. MSBuildArgs.OtherMSBuildArgs], "dotnet-run");
375400
var project = EvaluateProject(ProjectFileFullPath, projectFactory, MSBuildArgs, logger);
376401
ValidatePreconditions(project);
377402
InvokeRunArgumentsTarget(project, NoBuild, logger, MSBuildArgs);
378403
logger?.ReallyShutdown();
379-
var runProperties = ReadRunPropertiesFromProject(project, ApplicationArgs);
380-
var command = CreateCommandFromRunProperties(project, runProperties);
404+
var runProperties = RunProperties.FromProject(project).WithApplicationArguments(ApplicationArgs);
405+
var command = CreateCommandFromRunProperties(runProperties);
381406
return command;
382407

383408
static ProjectInstance EvaluateProject(string? projectFilePath, Func<ProjectCollection, ProjectInstance>? projectFactory, MSBuildArgs msbuildArgs, ILogger? binaryLogger)
@@ -407,29 +432,18 @@ static void ValidatePreconditions(ProjectInstance project)
407432
}
408433
}
409434

410-
static RunProperties ReadRunPropertiesFromProject(ProjectInstance project, string[] applicationArgs)
411-
{
412-
var runProperties = RunProperties.FromProjectAndApplicationArguments(project, applicationArgs);
413-
if (string.IsNullOrEmpty(runProperties.RunCommand))
414-
{
415-
ThrowUnableToRunError(project);
416-
}
417-
418-
return runProperties;
419-
}
420-
421-
static ICommand CreateCommandFromRunProperties(ProjectInstance project, RunProperties runProperties)
435+
static ICommand CreateCommandFromRunProperties(RunProperties runProperties)
422436
{
423-
CommandSpec commandSpec = new(runProperties.RunCommand, runProperties.RunArguments);
437+
CommandSpec commandSpec = new(runProperties.Command, runProperties.Arguments);
424438

425439
var command = CommandFactoryUsingResolver.Create(commandSpec)
426-
.WorkingDirectory(runProperties.RunWorkingDirectory);
440+
.WorkingDirectory(runProperties.WorkingDirectory);
427441

428442
SetRootVariableName(
429443
command,
430-
project.GetPropertyValue("RuntimeIdentifier"),
431-
project.GetPropertyValue("DefaultAppHostRuntimeIdentifier"),
432-
project.GetPropertyValue("TargetFrameworkVersion"));
444+
runtimeIdentifier: runProperties.RuntimeIdentifier,
445+
defaultAppHostRuntimeIdentifier: runProperties.DefaultAppHostRuntimeIdentifier,
446+
targetFrameworkVersion: runProperties.TargetFrameworkVersion);
433447

434448
return command;
435449
}
@@ -482,7 +496,7 @@ static void InvokeRunArgumentsTarget(ProjectInstance project, bool noBuild, Faca
482496

483497
static readonly string ComputeRunArgumentsTarget = "ComputeRunArguments";
484498

485-
private static void ThrowUnableToRunError(ProjectInstance project)
499+
internal static void ThrowUnableToRunError(ProjectInstance project)
486500
{
487501
string targetFrameworks = project.GetPropertyValue("TargetFrameworks");
488502
if (!string.IsNullOrEmpty(targetFrameworks))
@@ -585,7 +599,7 @@ private static void ThrowUnableToRunError(ProjectInstance project)
585599

586600
public static RunCommand FromArgs(string[] args)
587601
{
588-
var parseResult = Parser.Parse(["dotnet", "run", ..args]);
602+
var parseResult = Parser.Parse(["dotnet", "run", .. args]);
589603
return FromParseResult(parseResult);
590604
}
591605

src/Cli/dotnet/Commands/Run/RunProperties.cs

Lines changed: 32 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,19 +6,44 @@
66

77
namespace Microsoft.DotNet.Cli.Commands.Run;
88

9-
internal record RunProperties(string RunCommand, string? RunArguments, string? RunWorkingDirectory)
9+
internal sealed record RunProperties(
10+
string Command,
11+
string? Arguments,
12+
string? WorkingDirectory,
13+
string RuntimeIdentifier,
14+
string DefaultAppHostRuntimeIdentifier,
15+
string TargetFrameworkVersion)
1016
{
11-
internal static RunProperties FromProjectAndApplicationArguments(ProjectInstance project, string[] applicationArgs)
17+
internal RunProperties(string command, string? arguments, string? workingDirectory)
18+
: this(command, arguments, workingDirectory, string.Empty, string.Empty, string.Empty)
1219
{
13-
string runProgram = project.GetPropertyValue("RunCommand");
14-
string runArguments = project.GetPropertyValue("RunArguments");
15-
string runWorkingDirectory = project.GetPropertyValue("RunWorkingDirectory");
20+
}
21+
22+
internal static RunProperties FromProject(ProjectInstance project)
23+
{
24+
var result = new RunProperties(
25+
Command: project.GetPropertyValue("RunCommand"),
26+
Arguments: project.GetPropertyValue("RunArguments"),
27+
WorkingDirectory: project.GetPropertyValue("RunWorkingDirectory"),
28+
RuntimeIdentifier: project.GetPropertyValue("RuntimeIdentifier"),
29+
DefaultAppHostRuntimeIdentifier: project.GetPropertyValue("DefaultAppHostRuntimeIdentifier"),
30+
TargetFrameworkVersion: project.GetPropertyValue("TargetFrameworkVersion"));
1631

32+
if (string.IsNullOrEmpty(result.Command))
33+
{
34+
RunCommand.ThrowUnableToRunError(project);
35+
}
36+
37+
return result;
38+
}
39+
40+
internal RunProperties WithApplicationArguments(string[] applicationArgs)
41+
{
1742
if (applicationArgs.Length != 0)
1843
{
19-
runArguments += " " + ArgumentEscaper.EscapeAndConcatenateArgArrayForProcessStart(applicationArgs);
44+
return this with { Arguments = Arguments + " " + ArgumentEscaper.EscapeAndConcatenateArgArrayForProcessStart(applicationArgs) };
2045
}
2146

22-
return new(runProgram, runArguments, runWorkingDirectory);
47+
return this;
2348
}
2449
}

0 commit comments

Comments
 (0)