Skip to content

Commit 8e70150

Browse files
authored
Override default properties with custom file-level directives (#49767)
1 parent 1ec32f4 commit 8e70150

File tree

3 files changed

+99
-92
lines changed

3 files changed

+99
-92
lines changed

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

Lines changed: 58 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -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.
@@ -698,34 +710,33 @@ public static void WriteProjectFile(
698710
writer.WriteLine();
699711
}
700712

701-
// Kept in sync with the default `dotnet new console` project file (enforced by `DotnetProjectAddTests.SameAsTemplate`).
702-
writer.WriteLine($"""
703-
<PropertyGroup>
704-
<OutputType>Exe</OutputType>
705-
<TargetFramework>net10.0</TargetFramework>
706-
<ImplicitUsings>enable</ImplicitUsings>
707-
<Nullable>enable</Nullable>
708-
<PublishAot>true</PublishAot>
709-
</PropertyGroup>
710-
""");
711-
712-
if (isVirtualProject)
713+
// Write default and custom properties.
713714
{
714715
writer.WriteLine("""
715-
716716
<PropertyGroup>
717-
<EnableDefaultCompileItems>false</EnableDefaultCompileItems>
718-
</PropertyGroup>
719717
""");
720-
}
721718

722-
if (propertyDirectives.Any())
723-
{
724-
writer.WriteLine("""
719+
// First write the default properties except those specified by the user.
720+
var customPropertyNames = propertyDirectives.Select(d => d.Name).ToHashSet(StringComparer.OrdinalIgnoreCase);
721+
foreach (var (name, value) in s_defaultProperties)
722+
{
723+
if (!customPropertyNames.Contains(name))
724+
{
725+
writer.WriteLine($"""
726+
<{name}>{EscapeValue(value)}</{name}>
727+
""");
728+
}
729+
}
725730

726-
<PropertyGroup>
727-
""");
731+
// Write virtual-only properties.
732+
if (isVirtualProject)
733+
{
734+
writer.WriteLine("""
735+
<EnableDefaultCompileItems>false</EnableDefaultCompileItems>
736+
""");
737+
}
728738

739+
// Write custom properties.
729740
foreach (var property in propertyDirectives)
730741
{
731742
writer.WriteLine($"""
@@ -735,24 +746,23 @@ public static void WriteProjectFile(
735746
processedDirectives++;
736747
}
737748

738-
writer.WriteLine(" </PropertyGroup>");
739-
}
749+
// Write virtual-only properties which cannot be overridden.
750+
if (isVirtualProject)
751+
{
752+
writer.WriteLine("""
753+
<Features>$(Features);FileBasedProgram</Features>
754+
""");
755+
}
740756

741-
if (isVirtualProject)
742-
{
743-
// After `#:property` directives so they don't override this.
744757
writer.WriteLine("""
745-
746-
<PropertyGroup>
747-
<Features>$(Features);FileBasedProgram</Features>
748758
</PropertyGroup>
759+
749760
""");
750761
}
751762

752763
if (packageDirectives.Any())
753764
{
754765
writer.WriteLine("""
755-
756766
<ItemGroup>
757767
""");
758768

@@ -774,13 +784,15 @@ public static void WriteProjectFile(
774784
processedDirectives++;
775785
}
776786

777-
writer.WriteLine(" </ItemGroup>");
787+
writer.WriteLine("""
788+
</ItemGroup>
789+
790+
""");
778791
}
779792

780793
if (projectDirectives.Any())
781794
{
782795
writer.WriteLine("""
783-
784796
<ItemGroup>
785797
""");
786798

@@ -793,7 +805,10 @@ public static void WriteProjectFile(
793805
processedDirectives++;
794806
}
795807

796-
writer.WriteLine(" </ItemGroup>");
808+
writer.WriteLine("""
809+
</ItemGroup>
810+
811+
""");
797812
}
798813

799814
Debug.Assert(processedDirectives + directives.OfType<CSharpDirective.Shebang>().Count() == directives.Length);
@@ -803,7 +818,6 @@ public static void WriteProjectFile(
803818
Debug.Assert(targetFilePath is not null);
804819

805820
writer.WriteLine($"""
806-
807821
<ItemGroup>
808822
<Compile Include="{EscapeValue(targetFilePath)}" />
809823
</ItemGroup>
@@ -814,12 +828,12 @@ public static void WriteProjectFile(
814828
{
815829
var targetDirectory = Path.GetDirectoryName(targetFilePath) ?? "";
816830
writer.WriteLine($"""
817-
<ItemGroup>
818-
<RuntimeHostConfigurationOption Include="EntryPointFilePath" Value="{EscapeValue(targetFilePath)}" />
819-
<RuntimeHostConfigurationOption Include="EntryPointFileDirectoryPath" Value="{EscapeValue(targetDirectory)}" />
820-
</ItemGroup>
831+
<ItemGroup>
832+
<RuntimeHostConfigurationOption Include="EntryPointFilePath" Value="{EscapeValue(targetFilePath)}" />
833+
<RuntimeHostConfigurationOption Include="EntryPointFileDirectoryPath" Value="{EscapeValue(targetDirectory)}" />
834+
</ItemGroup>
821835
822-
""");
836+
""");
823837
}
824838

825839
foreach (var sdk in sdkDirectives)
@@ -835,12 +849,14 @@ public static void WriteProjectFile(
835849
""");
836850
}
837851

838-
writer.WriteLine();
839-
writer.WriteLine(TargetOverrides);
852+
writer.WriteLine($"""
853+
854+
{TargetOverrides}
855+
856+
""");
840857
}
841858

842859
writer.WriteLine("""
843-
844860
</Project>
845861
""");
846862

test/dotnet.Tests/CommandTests/Project/Convert/DotnetProjectConvertTests.cs

Lines changed: 41 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -642,26 +642,22 @@ public void Directives()
642642
#!/program
643643
#:sdk Microsoft.NET.Sdk
644644
645-
#:property TargetFramework=net11.0
645+
#:property TargetFramework=net472
646646
647647
#:property LangVersion=preview
648648
Console.WriteLine();
649649
""",
650-
expectedProject: $"""
650+
expectedProject: """
651651
<Project Sdk="Microsoft.NET.Sdk">
652652
653653
<Sdk Name="Aspire.Hosting.Sdk" Version="9.1.0" />
654654
655655
<PropertyGroup>
656656
<OutputType>Exe</OutputType>
657-
<TargetFramework>{ToolsetInfo.CurrentTargetFramework}</TargetFramework>
658657
<ImplicitUsings>enable</ImplicitUsings>
659658
<Nullable>enable</Nullable>
660659
<PublishAot>true</PublishAot>
661-
</PropertyGroup>
662-
663-
<PropertyGroup>
664-
<TargetFramework>net11.0</TargetFramework>
660+
<TargetFramework>net472</TargetFramework>
665661
<LangVersion>preview</LangVersion>
666662
</PropertyGroup>
667663
@@ -677,6 +673,44 @@ public void Directives()
677673
""");
678674
}
679675

676+
/// <summary>
677+
/// There should be only one <c>PropertyGroup</c> element when the default properties are overridden.
678+
/// </summary>
679+
[Fact]
680+
public void Directives_AllDefaultOverridden()
681+
{
682+
VerifyConversion(
683+
inputCSharp: """
684+
#!/program
685+
#:sdk Microsoft.NET.Web.Sdk
686+
#:property OutputType=Exe
687+
#:property TargetFramework=net472
688+
#:property Nullable=disable
689+
#:property PublishAot=false
690+
#:property Custom=1
691+
#:property ImplicitUsings=disable
692+
Console.WriteLine();
693+
""",
694+
expectedProject: """
695+
<Project Sdk="Microsoft.NET.Web.Sdk">
696+
697+
<PropertyGroup>
698+
<OutputType>Exe</OutputType>
699+
<TargetFramework>net472</TargetFramework>
700+
<Nullable>disable</Nullable>
701+
<PublishAot>false</PublishAot>
702+
<Custom>1</Custom>
703+
<ImplicitUsings>disable</ImplicitUsings>
704+
</PropertyGroup>
705+
706+
</Project>
707+
708+
""",
709+
expectedCSharp: """
710+
Console.WriteLine();
711+
""");
712+
}
713+
680714
[Fact]
681715
public void Directives_Variable()
682716
{
@@ -694,9 +728,6 @@ public void Directives_Variable()
694728
<ImplicitUsings>enable</ImplicitUsings>
695729
<Nullable>enable</Nullable>
696730
<PublishAot>true</PublishAot>
697-
</PropertyGroup>
698-
699-
<PropertyGroup>
700731
<MyProp>MyValue</MyProp>
701732
</PropertyGroup>
702733
@@ -772,9 +803,6 @@ public void Directives_Separators()
772803
<ImplicitUsings>enable</ImplicitUsings>
773804
<Nullable>enable</Nullable>
774805
<PublishAot>true</PublishAot>
775-
</PropertyGroup>
776-
777-
<PropertyGroup>
778806
<Prop1>One=a/b</Prop1>
779807
<Prop2>Two/a=b</Prop2>
780808
</PropertyGroup>
@@ -884,9 +912,6 @@ public void Directives_Escaping()
884912
<ImplicitUsings>enable</ImplicitUsings>
885913
<Nullable>enable</Nullable>
886914
<PublishAot>true</PublishAot>
887-
</PropertyGroup>
888-
889-
<PropertyGroup>
890915
<Prop>&lt;test&quot;&gt;</Prop>
891916
</PropertyGroup>
892917
@@ -921,9 +946,6 @@ public void Directives_Whitespace()
921946
<ImplicitUsings>enable</ImplicitUsings>
922947
<Nullable>enable</Nullable>
923948
<PublishAot>true</PublishAot>
924-
</PropertyGroup>
925-
926-
<PropertyGroup>
927949
<Name>Value</Name>
928950
<NugetPackageDescription>&quot;My package with spaces&quot;</NugetPackageDescription>
929951
</PropertyGroup>
@@ -968,9 +990,6 @@ public void Directives_AfterToken()
968990
<ImplicitUsings>enable</ImplicitUsings>
969991
<Nullable>enable</Nullable>
970992
<PublishAot>true</PublishAot>
971-
</PropertyGroup>
972-
973-
<PropertyGroup>
974993
<Prop1>1</Prop1>
975994
<Prop2>2</Prop2>
976995
</PropertyGroup>
@@ -1017,9 +1036,6 @@ public void Directives_AfterIf()
10171036
<ImplicitUsings>enable</ImplicitUsings>
10181037
<Nullable>enable</Nullable>
10191038
<PublishAot>true</PublishAot>
1020-
</PropertyGroup>
1021-
1022-
<PropertyGroup>
10231039
<Prop1>1</Prop1>
10241040
<Prop2>2</Prop2>
10251041
</PropertyGroup>
@@ -1063,9 +1079,6 @@ public void Directives_Comments()
10631079
<ImplicitUsings>enable</ImplicitUsings>
10641080
<Nullable>enable</Nullable>
10651081
<PublishAot>true</PublishAot>
1066-
</PropertyGroup>
1067-
1068-
<PropertyGroup>
10691082
<Prop1>1</Prop1>
10701083
<Prop2>2</Prop2>
10711084
</PropertyGroup>

test/dotnet.Tests/CommandTests/Run/RunFileTests.cs

Lines changed: 0 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1869,22 +1869,12 @@ public void Api()
18691869
18701870
<PropertyGroup>
18711871
<OutputType>Exe</OutputType>
1872-
<TargetFramework>{ToolsetInfo.CurrentTargetFramework}</TargetFramework>
18731872
<ImplicitUsings>enable</ImplicitUsings>
18741873
<Nullable>enable</Nullable>
18751874
<PublishAot>true</PublishAot>
1876-
</PropertyGroup>
1877-
1878-
<PropertyGroup>
18791875
<EnableDefaultCompileItems>false</EnableDefaultCompileItems>
1880-
</PropertyGroup>
1881-
1882-
<PropertyGroup>
18831876
<TargetFramework>net11.0</TargetFramework>
18841877
<LangVersion>preview</LangVersion>
1885-
</PropertyGroup>
1886-
1887-
<PropertyGroup>
18881878
<Features>$(Features);FileBasedProgram</Features>
18891879
</PropertyGroup>
18901880
@@ -1952,13 +1942,7 @@ public void Api_Diagnostic_01()
19521942
<ImplicitUsings>enable</ImplicitUsings>
19531943
<Nullable>enable</Nullable>
19541944
<PublishAot>true</PublishAot>
1955-
</PropertyGroup>
1956-
1957-
<PropertyGroup>
19581945
<EnableDefaultCompileItems>false</EnableDefaultCompileItems>
1959-
</PropertyGroup>
1960-
1961-
<PropertyGroup>
19621946
<Features>$(Features);FileBasedProgram</Features>
19631947
</PropertyGroup>
19641948
@@ -2025,13 +2009,7 @@ public void Api_Diagnostic_02()
20252009
<ImplicitUsings>enable</ImplicitUsings>
20262010
<Nullable>enable</Nullable>
20272011
<PublishAot>true</PublishAot>
2028-
</PropertyGroup>
2029-
2030-
<PropertyGroup>
20312012
<EnableDefaultCompileItems>false</EnableDefaultCompileItems>
2032-
</PropertyGroup>
2033-
2034-
<PropertyGroup>
20352013
<Features>$(Features);FileBasedProgram</Features>
20362014
</PropertyGroup>
20372015

0 commit comments

Comments
 (0)