@@ -63,6 +63,18 @@ internal sealed class VirtualProjectBuildingCommand : CommandBase
6363 "MSBuild.rsp" ,
6464 ] ;
6565
66+ /// <remarks>
67+ /// Kept in sync with the default <c>dotnet new console</c> project file (enforced by <c>DotnetProjectAddTests.SameAsTemplate</c>).
68+ /// </remarks>
69+ private static readonly FrozenDictionary < string , string > s_defaultProperties = FrozenDictionary . Create < string , string > ( StringComparer . OrdinalIgnoreCase ,
70+ [
71+ new ( "OutputType" , "Exe" ) ,
72+ new ( "TargetFramework" , "net10.0" ) ,
73+ new ( "ImplicitUsings" , "enable" ) ,
74+ new ( "Nullable" , "enable" ) ,
75+ new ( "PublishAot" , "true" ) ,
76+ ] ) ;
77+
6678 internal static readonly string TargetOverrides = """
6779 <!--
6880 Override targets which don't work with project files that are not present on disk.
@@ -201,7 +213,6 @@ public override int Execute()
201213 }
202214
203215 Dictionary < string , string ? > savedEnvironmentVariables = [ ] ;
204- ProjectCollection ? projectCollection = null ;
205216 try
206217 {
207218 // Set environment variables.
@@ -212,17 +223,20 @@ public override int Execute()
212223 }
213224
214225 // Set up MSBuild.
215- ReadOnlySpan < ILogger > binaryLoggers = binaryLogger is null ? [ ] : [ binaryLogger ] ;
216- projectCollection = new ProjectCollection (
226+ ReadOnlySpan < ILogger > binaryLoggers = binaryLogger is null ? [ ] : [ binaryLogger . Value ] ;
227+ IEnumerable < ILogger > loggers = [ .. binaryLoggers , consoleLogger ] ;
228+ var projectCollection = new ProjectCollection (
217229 MSBuildArgs . GlobalProperties ,
218- [ .. binaryLoggers , consoleLogger ] ,
230+ loggers ,
219231 ToolsetDefinitionLocations . Default ) ;
220232 var parameters = new BuildParameters ( projectCollection )
221233 {
222- Loggers = projectCollection . Loggers ,
234+ Loggers = loggers ,
223235 LogTaskInputs = binaryLoggers . Length != 0 ,
224236 } ;
225237
238+ BuildManager . DefaultBuildManager . BeginBuild ( parameters ) ;
239+
226240 // Do a restore first (equivalent to MSBuild's "implicit restore", i.e., `/restore`).
227241 // See https://github.com/dotnet/msbuild/blob/a1c2e7402ef0abe36bf493e395b04dd2cb1b3540/src/MSBuild/XMake.cs#L1838
228242 // and https://github.com/dotnet/msbuild/issues/11519.
@@ -234,8 +248,6 @@ public override int Execute()
234248 hostServices : null ,
235249 BuildRequestDataFlags . ClearCachesAfterBuild | BuildRequestDataFlags . SkipNonexistentTargets | BuildRequestDataFlags . IgnoreMissingEmptyAndInvalidImports | BuildRequestDataFlags . FailOnUnresolvedSdk ) ;
236250
237- BuildManager . DefaultBuildManager . BeginBuild ( parameters ) ;
238-
239251 var restoreResult = BuildManager . DefaultBuildManager . BuildRequest ( restoreRequest ) ;
240252 if ( restoreResult . OverallResult != BuildResultCode . Success )
241253 {
@@ -250,12 +262,6 @@ public override int Execute()
250262 CreateProjectInstance ( projectCollection ) ,
251263 targetsToBuild : MSBuildArgs . RequestedTargets ?? [ "Build" ] ) ;
252264
253- // For some reason we need to BeginBuild after creating BuildRequestData otherwise the binlog doesn't contain Evaluation.
254- if ( NoRestore )
255- {
256- BuildManager . DefaultBuildManager . BeginBuild ( parameters ) ;
257- }
258-
259265 var buildResult = BuildManager . DefaultBuildManager . BuildRequest ( buildRequest ) ;
260266 if ( buildResult . OverallResult != BuildResultCode . Success )
261267 {
@@ -282,7 +288,7 @@ public override int Execute()
282288 Environment . SetEnvironmentVariable ( key , value ) ;
283289 }
284290
285- binaryLogger ? . Shutdown ( ) ;
291+ binaryLogger ? . Value . ReallyShutdown ( ) ;
286292 consoleLogger . Shutdown ( ) ;
287293 }
288294
@@ -310,7 +316,7 @@ static Action<IDictionary<string, string>> AddRestoreGlobalProperties(ReadOnlyDi
310316 } ;
311317 }
312318
313- static ILogger ? GetBinaryLogger ( IReadOnlyList < string > ? args )
319+ static Lazy < FacadeLogger > ? GetBinaryLogger ( IReadOnlyList < string > ? args )
314320 {
315321 if ( args is null ) return null ;
316322 // Like in MSBuild, only the last binary logger is used.
@@ -319,12 +325,17 @@ static Action<IDictionary<string, string>> AddRestoreGlobalProperties(ReadOnlyDi
319325 var arg = args [ i ] ;
320326 if ( LoggerUtility . IsBinLogArgument ( arg ) )
321327 {
322- return new BinaryLogger
328+ // We don't want to create the binlog file until actually needed, hence we wrap this in a Lazy.
329+ return new ( ( ) =>
323330 {
324- Parameters = arg . IndexOf ( ':' ) is >= 0 and var index
325- ? arg [ ( index + 1 ) ..]
326- : "msbuild.binlog" ,
327- } ;
331+ var logger = new BinaryLogger
332+ {
333+ Parameters = arg . IndexOf ( ':' ) is >= 0 and var index
334+ ? arg [ ( index + 1 ) ..]
335+ : "msbuild.binlog" ,
336+ } ;
337+ return LoggerUtility . CreateFacadeLogger ( [ logger ] ) ;
338+ } ) ;
328339 }
329340 }
330341
@@ -720,34 +731,33 @@ public static void WriteProjectFile(
720731 writer . WriteLine ( ) ;
721732 }
722733
723- // Kept in sync with the default `dotnet new console` project file (enforced by `DotnetProjectAddTests.SameAsTemplate`).
724- writer . WriteLine ( $ """
725- <PropertyGroup>
726- <OutputType>Exe</OutputType>
727- <TargetFramework>net10.0</TargetFramework>
728- <ImplicitUsings>enable</ImplicitUsings>
729- <Nullable>enable</Nullable>
730- <PublishAot>true</PublishAot>
731- </PropertyGroup>
732- """ ) ;
733-
734- if ( isVirtualProject )
734+ // Write default and custom properties.
735735 {
736736 writer . WriteLine ( """
737-
738737 <PropertyGroup>
739- <EnableDefaultCompileItems>false</EnableDefaultCompileItems>
740- </PropertyGroup>
741738 """ ) ;
742- }
743739
744- if ( propertyDirectives . Any ( ) )
745- {
746- writer . WriteLine ( """
740+ // First write the default properties except those specified by the user.
741+ var customPropertyNames = propertyDirectives . Select ( d => d . Name ) . ToHashSet ( StringComparer . OrdinalIgnoreCase ) ;
742+ foreach ( var ( name , value ) in s_defaultProperties )
743+ {
744+ if ( ! customPropertyNames . Contains ( name ) )
745+ {
746+ writer . WriteLine ( $ """
747+ <{ name } >{ EscapeValue ( value ) } </{ name } >
748+ """ ) ;
749+ }
750+ }
747751
748- <PropertyGroup>
749- """ ) ;
752+ // Write virtual-only properties.
753+ if ( isVirtualProject )
754+ {
755+ writer . WriteLine ( """
756+ <EnableDefaultCompileItems>false</EnableDefaultCompileItems>
757+ """ ) ;
758+ }
750759
760+ // Write custom properties.
751761 foreach ( var property in propertyDirectives )
752762 {
753763 writer . WriteLine ( $ """
@@ -757,24 +767,23 @@ public static void WriteProjectFile(
757767 processedDirectives ++ ;
758768 }
759769
760- writer . WriteLine ( " </PropertyGroup>" ) ;
761- }
770+ // Write virtual-only properties which cannot be overridden.
771+ if ( isVirtualProject )
772+ {
773+ writer . WriteLine ( """
774+ <Features>$(Features);FileBasedProgram</Features>
775+ """ ) ;
776+ }
762777
763- if ( isVirtualProject )
764- {
765- // After `#:property` directives so they don't override this.
766778 writer . WriteLine ( """
767-
768- <PropertyGroup>
769- <Features>$(Features);FileBasedProgram</Features>
770779 </PropertyGroup>
780+
771781 """ ) ;
772782 }
773783
774784 if ( packageDirectives . Any ( ) )
775785 {
776786 writer . WriteLine ( """
777-
778787 <ItemGroup>
779788 """ ) ;
780789
@@ -796,13 +805,15 @@ public static void WriteProjectFile(
796805 processedDirectives ++ ;
797806 }
798807
799- writer . WriteLine ( " </ItemGroup>" ) ;
808+ writer . WriteLine ( """
809+ </ItemGroup>
810+
811+ """ ) ;
800812 }
801813
802814 if ( projectDirectives . Any ( ) )
803815 {
804816 writer . WriteLine ( """
805-
806817 <ItemGroup>
807818 """ ) ;
808819
@@ -815,7 +826,10 @@ public static void WriteProjectFile(
815826 processedDirectives ++ ;
816827 }
817828
818- writer . WriteLine ( " </ItemGroup>" ) ;
829+ writer . WriteLine ( """
830+ </ItemGroup>
831+
832+ """ ) ;
819833 }
820834
821835 Debug . Assert ( processedDirectives + directives . OfType < CSharpDirective . Shebang > ( ) . Count ( ) == directives . Length ) ;
@@ -825,7 +839,6 @@ public static void WriteProjectFile(
825839 Debug . Assert ( targetFilePath is not null ) ;
826840
827841 writer . WriteLine ( $ """
828-
829842 <ItemGroup>
830843 <Compile Include="{ EscapeValue ( targetFilePath ) } " />
831844 </ItemGroup>
@@ -836,12 +849,12 @@ public static void WriteProjectFile(
836849 {
837850 var targetDirectory = Path . GetDirectoryName ( targetFilePath ) ?? "" ;
838851 writer . WriteLine ( $ """
839- <ItemGroup>
840- <RuntimeHostConfigurationOption Include="EntryPointFilePath" Value="{ EscapeValue ( targetFilePath ) } " />
841- <RuntimeHostConfigurationOption Include="EntryPointFileDirectoryPath" Value="{ EscapeValue ( targetDirectory ) } " />
842- </ItemGroup>
852+ <ItemGroup>
853+ <RuntimeHostConfigurationOption Include="EntryPointFilePath" Value="{ EscapeValue ( targetFilePath ) } " />
854+ <RuntimeHostConfigurationOption Include="EntryPointFileDirectoryPath" Value="{ EscapeValue ( targetDirectory ) } " />
855+ </ItemGroup>
843856
844- """ ) ;
857+ """ ) ;
845858 }
846859
847860 foreach ( var sdk in sdkDirectives )
@@ -857,12 +870,14 @@ public static void WriteProjectFile(
857870 """ ) ;
858871 }
859872
860- writer . WriteLine ( ) ;
861- writer . WriteLine ( TargetOverrides ) ;
873+ writer . WriteLine ( $ """
874+
875+ { TargetOverrides }
876+
877+ """ ) ;
862878 }
863879
864880 writer . WriteLine ( """
865-
866881 </Project>
867882 """ ) ;
868883
0 commit comments