Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 14 additions & 1 deletion src/Cli/dotnet/Commands/Project/Convert/ProjectConvertCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,8 @@ 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,
userSecretsId: DetermineUserSecretsId());
userSecretsId: DetermineUserSecretsId(),
excludeDefaultProperties: FindDefaultPropertiesToExclude());
}

// Copy or move over included items.
Expand Down Expand Up @@ -184,6 +185,18 @@ ImmutableArray<CSharpDirective> UpdateDirectives(ImmutableArray<CSharpDirective>

return result.DrainToImmutable();
}

IEnumerable<string> FindDefaultPropertiesToExclude()
{
foreach (var (name, defaultValue) in VirtualProjectBuildingCommand.DefaultProperties)
{
string projectValue = projectInstance.GetPropertyValue(name);
if (!string.Equals(projectValue, defaultValue, StringComparison.OrdinalIgnoreCase))
{
yield return name;
}
}
}
}

private string DetermineOutputDirectory(string file)
Expand Down
61 changes: 38 additions & 23 deletions src/Cli/dotnet/Commands/Run/VirtualProjectBuildingCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ internal sealed class VirtualProjectBuildingCommand : CommandBase
/// <remarks>
/// Kept in sync with the default <c>dotnet new console</c> project file (enforced by <c>DotnetProjectAddTests.SameAsTemplate</c>).
/// </remarks>
private static readonly FrozenDictionary<string, string> s_defaultProperties = FrozenDictionary.Create<string, string>(StringComparer.OrdinalIgnoreCase,
public static readonly FrozenDictionary<string, string> DefaultProperties = FrozenDictionary.Create<string, string>(StringComparer.OrdinalIgnoreCase,
[
new("OutputType", "Exe"),
new("TargetFramework", $"net{TargetFrameworkVersion}"),
Expand Down Expand Up @@ -1141,8 +1141,12 @@ public static void WriteProjectFile(
string? targetFilePath = null,
string? artifactsPath = null,
bool includeRuntimeConfigInformation = true,
string? userSecretsId = null)
string? userSecretsId = null,
IEnumerable<string>? excludeDefaultProperties = null)
{
Debug.Assert(userSecretsId == null || !isVirtualProject);
Debug.Assert(excludeDefaultProperties == null || !isVirtualProject);

int processedDirectives = 0;

var sdkDirectives = directives.OfType<CSharpDirective.Sdk>();
Expand Down Expand Up @@ -1181,6 +1185,20 @@ public static void WriteProjectFile(
<PublishDir>artifacts/$(MSBuildProjectName)</PublishDir>
<PackageOutputPath>artifacts/$(MSBuildProjectName)</PackageOutputPath>
<FileBasedProgram>true</FileBasedProgram>
<EnableDefaultCompileItems>false</EnableDefaultCompileItems>
<DisableDefaultItemsInProjectFolder>true</DisableDefaultItemsInProjectFolder>
""");

// 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)}</{name}>
""");
}

writer.WriteLine($"""
</PropertyGroup>

<ItemGroup>
Expand Down Expand Up @@ -1247,34 +1265,30 @@ public static void WriteProjectFile(
""");

// First write the default properties except those specified by the user.
var customPropertyNames = propertyDirectives.Select(d => d.Name).ToHashSet(StringComparer.OrdinalIgnoreCase);
foreach (var (name, value) in s_defaultProperties)
if (!isVirtualProject)
{
if (!customPropertyNames.Contains(name))
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)}</{name}>
""");
}
}

if (userSecretsId != null && !customPropertyNames.Contains("UserSecretsId"))
{
writer.WriteLine($"""
<{name}>{EscapeValue(value)}</{name}>
<UserSecretsId>{EscapeValue(userSecretsId)}</UserSecretsId>
""");
}
}

if (userSecretsId != null && !customPropertyNames.Contains("UserSecretsId"))
{
writer.WriteLine($"""
<UserSecretsId>{EscapeValue(userSecretsId)}</UserSecretsId>
""");
}

// Write virtual-only properties.
if (isVirtualProject)
{
writer.WriteLine("""
<EnableDefaultCompileItems>false</EnableDefaultCompileItems>
<DisableDefaultItemsInProjectFolder>true</DisableDefaultItemsInProjectFolder>
<RestoreUseStaticGraphEvaluation>false</RestoreUseStaticGraphEvaluation>
""");
}

// Write custom properties.
foreach (var property in propertyDirectives)
{
Expand All @@ -1289,6 +1303,7 @@ public static void WriteProjectFile(
if (isVirtualProject)
{
writer.WriteLine("""
<RestoreUseStaticGraphEvaluation>false</RestoreUseStaticGraphEvaluation>
<Features>$(Features);FileBasedProgram</Features>
""");
}
Expand Down
171 changes: 147 additions & 24 deletions test/dotnet.Tests/CommandTests/Run/RunFileTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -878,6 +878,128 @@ public void DirectoryBuildProps()
.And.HaveStdOut("Hello from TestName");
}

/// <summary>
/// Overriding default (implicit) properties of file-based apps via implicit build files.
/// </summary>
[Fact]
public void DefaultProps_DirectoryBuildProps()
{
var testInstance = _testAssetsManager.CreateTestDirectory();
File.WriteAllText(Path.Join(testInstance.Path, "Program.cs"), """
Console.WriteLine("Hi");
""");
File.WriteAllText(Path.Join(testInstance.Path, "Directory.Build.props"), """
<Project>
<PropertyGroup>
<ImplicitUsings>disable</ImplicitUsings>
</PropertyGroup>
</Project>
""");

new DotnetCommand(Log, "run", "Program.cs")
.WithWorkingDirectory(testInstance.Path)
.Execute()
.Should().Fail()
// error CS0103: The name 'Console' does not exist in the current context
.And.HaveStdOutContaining("error CS0103");

// Converting to a project should not change the behavior.

new DotnetCommand(Log, "project", "convert", "Program.cs")
.WithWorkingDirectory(testInstance.Path)
.Execute()
.Should().Pass();

new DotnetCommand(Log, "run")
.WithWorkingDirectory(Path.Join(testInstance.Path, "Program"))
.Execute()
.Should().Fail()
// error CS0103: The name 'Console' does not exist in the current context
.And.HaveStdOutContaining("error CS0103");
}

/// <summary>
/// Overriding default (implicit) properties of file-based apps from custom SDKs.
/// </summary>
[Fact]
public void DefaultProps_CustomSdk()
{
var testInstance = _testAssetsManager.CreateTestDirectory();

var sdkDir = Path.Join(testInstance.Path, "MySdk");
Directory.CreateDirectory(sdkDir);
File.WriteAllText(Path.Join(sdkDir, "Sdk.props"), """
<Project>
<PropertyGroup>
<ImplicitUsings>disable</ImplicitUsings>
</PropertyGroup>
</Project>
""");
File.WriteAllText(Path.Join(sdkDir, "Sdk.targets"), """
<Project />
""");
File.WriteAllText(Path.Join(sdkDir, "MySdk.csproj"), $"""
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>{ToolsetInfo.CurrentTargetFramework}</TargetFramework>
<PackageType>MSBuildSdk</PackageType>
<IncludeBuildOutput>false</IncludeBuildOutput>
</PropertyGroup>
<ItemGroup>
<None Include="Sdk.*" Pack="true" PackagePath="Sdk" />
</ItemGroup>
</Project>
""");

new DotnetCommand(Log, "pack")
.WithWorkingDirectory(sdkDir)
.Execute()
.Should().Pass();

var appDir = Path.Join(testInstance.Path, "app");
Directory.CreateDirectory(appDir);
File.WriteAllText(Path.Join(appDir, "NuGet.config"), $"""
<configuration>
<packageSources>
<add key="local" value="{Path.Join(sdkDir, "bin", "Release")}" />
<add key="nuget.org" value="https://api.nuget.org/v3/index.json" />
</packageSources>
</configuration>
""");
File.WriteAllText(Path.Join(appDir, "Program.cs"), """
#:sdk Microsoft.NET.Sdk
#:sdk MySdk@1.0.0
Console.WriteLine("Hi");
""");

// Use custom package cache to avoid reuse of the custom SDK packed by previous test runs.
var packagesDir = Path.Join(testInstance.Path, ".packages");

new DotnetCommand(Log, "run", "Program.cs")
.WithEnvironmentVariable("NUGET_PACKAGES", packagesDir)
.WithWorkingDirectory(appDir)
.Execute()
.Should().Fail()
// error CS0103: The name 'Console' does not exist in the current context
.And.HaveStdOutContaining("error CS0103");

// Converting to a project should not change the behavior.

new DotnetCommand(Log, "project", "convert", "Program.cs")
.WithEnvironmentVariable("NUGET_PACKAGES", packagesDir)
.WithWorkingDirectory(appDir)
.Execute()
.Should().Pass();

new DotnetCommand(Log, "run")
.WithEnvironmentVariable("NUGET_PACKAGES", packagesDir)
.WithWorkingDirectory(Path.Join(appDir, "Program"))
.Execute()
.Should().Fail()
// error CS0103: The name 'Console' does not exist in the current context
.And.HaveStdOutContaining("error CS0103");
}

[Fact]
public void ComputeRunArguments_Success()
{
Expand Down Expand Up @@ -3441,6 +3563,14 @@ public void Api()
<PublishDir>artifacts/$(MSBuildProjectName)</PublishDir>
<PackageOutputPath>artifacts/$(MSBuildProjectName)</PackageOutputPath>
<FileBasedProgram>true</FileBasedProgram>
<EnableDefaultCompileItems>false</EnableDefaultCompileItems>
<DisableDefaultItemsInProjectFolder>true</DisableDefaultItemsInProjectFolder>
<OutputType>Exe</OutputType>
<TargetFramework>{ToolsetInfo.CurrentTargetFramework}</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<PublishAot>true</PublishAot>
<PackAsTool>true</PackAsTool>
</PropertyGroup>

<ItemGroup>
Expand All @@ -3451,16 +3581,9 @@ public void Api()
<Import Project="Sdk.props" Sdk="Aspire.Hosting.Sdk" Version="9.1.0" />

<PropertyGroup>
<OutputType>Exe</OutputType>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<PublishAot>true</PublishAot>
<PackAsTool>true</PackAsTool>
<EnableDefaultCompileItems>false</EnableDefaultCompileItems>
<DisableDefaultItemsInProjectFolder>true</DisableDefaultItemsInProjectFolder>
<RestoreUseStaticGraphEvaluation>false</RestoreUseStaticGraphEvaluation>
<TargetFramework>net11.0</TargetFramework>
<LangVersion>preview</LangVersion>
<RestoreUseStaticGraphEvaluation>false</RestoreUseStaticGraphEvaluation>
<Features>$(Features);FileBasedProgram</Features>
</PropertyGroup>

Expand Down Expand Up @@ -3512,6 +3635,14 @@ public void Api_Diagnostic_01()
<PublishDir>artifacts/$(MSBuildProjectName)</PublishDir>
<PackageOutputPath>artifacts/$(MSBuildProjectName)</PackageOutputPath>
<FileBasedProgram>true</FileBasedProgram>
<EnableDefaultCompileItems>false</EnableDefaultCompileItems>
<DisableDefaultItemsInProjectFolder>true</DisableDefaultItemsInProjectFolder>
<OutputType>Exe</OutputType>
<TargetFramework>{ToolsetInfo.CurrentTargetFramework}</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<PublishAot>true</PublishAot>
<PackAsTool>true</PackAsTool>
</PropertyGroup>

<ItemGroup>
Expand All @@ -3521,14 +3652,6 @@ public void Api_Diagnostic_01()
<Import Project="Sdk.props" Sdk="Microsoft.NET.Sdk" />

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>{ToolsetInfo.CurrentTargetFramework}</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<PublishAot>true</PublishAot>
<PackAsTool>true</PackAsTool>
<EnableDefaultCompileItems>false</EnableDefaultCompileItems>
<DisableDefaultItemsInProjectFolder>true</DisableDefaultItemsInProjectFolder>
<RestoreUseStaticGraphEvaluation>false</RestoreUseStaticGraphEvaluation>
<Features>$(Features);FileBasedProgram</Features>
</PropertyGroup>
Expand Down Expand Up @@ -3580,6 +3703,14 @@ public void Api_Diagnostic_02()
<PublishDir>artifacts/$(MSBuildProjectName)</PublishDir>
<PackageOutputPath>artifacts/$(MSBuildProjectName)</PackageOutputPath>
<FileBasedProgram>true</FileBasedProgram>
<EnableDefaultCompileItems>false</EnableDefaultCompileItems>
<DisableDefaultItemsInProjectFolder>true</DisableDefaultItemsInProjectFolder>
<OutputType>Exe</OutputType>
<TargetFramework>{ToolsetInfo.CurrentTargetFramework}</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<PublishAot>true</PublishAot>
<PackAsTool>true</PackAsTool>
</PropertyGroup>

<ItemGroup>
Expand All @@ -3589,14 +3720,6 @@ public void Api_Diagnostic_02()
<Import Project="Sdk.props" Sdk="Microsoft.NET.Sdk" />

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>{ToolsetInfo.CurrentTargetFramework}</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<PublishAot>true</PublishAot>
<PackAsTool>true</PackAsTool>
<EnableDefaultCompileItems>false</EnableDefaultCompileItems>
<DisableDefaultItemsInProjectFolder>true</DisableDefaultItemsInProjectFolder>
<RestoreUseStaticGraphEvaluation>false</RestoreUseStaticGraphEvaluation>
<Features>$(Features);FileBasedProgram</Features>
</PropertyGroup>
Expand Down
Loading