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 ;
54using System . Collections . Immutable ;
65using System . Collections . ObjectModel ;
76using 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
0 commit comments