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
3 changes: 3 additions & 0 deletions documentation/general/dotnet-run-file.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,9 @@ Additionally, the implicit project file has the following customizations:
in case there is a project or solution in the same directory as the file-based app.
This ensures that items from nested projects and artifacts are not included by the app.

- `EnableDefaultEmbeddedResourceItems` and `EnableDefaultNoneItems` properties are set to `false` if the default SDK (`Microsoft.NET.Sdk`) is being used.
This avoids including files like `./**/*.resx` in simple file-based apps where users usually don't expect that.

## Grow up

When file-based programs reach an inflection point where build customizations in a project file are needed,
Expand Down
21 changes: 17 additions & 4 deletions src/Cli/dotnet/Commands/Run/VirtualProjectBuildingCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1156,6 +1156,7 @@ public static void WriteProjectFile(
var packageDirectives = directives.OfType<CSharpDirective.Package>();
var projectDirectives = directives.OfType<CSharpDirective.Project>();

const string defaultSdkName = "Microsoft.NET.Sdk";
string firstSdkName;
string? firstSdkVersion;

Expand All @@ -1167,7 +1168,7 @@ public static void WriteProjectFile(
}
else
{
firstSdkName = "Microsoft.NET.Sdk";
firstSdkName = defaultSdkName;
firstSdkVersion = null;
}

Expand All @@ -1191,6 +1192,18 @@ public static void WriteProjectFile(
<DisableDefaultItemsInProjectFolder>true</DisableDefaultItemsInProjectFolder>
""");

// Only set these to false when using the default SDK with no additional SDKs
// to avoid including .resx and other files that are typically not expected in simple file-based apps.
// When other SDKs are used (e.g., Microsoft.NET.Sdk.Web), keep the default behavior.
bool usingOnlyDefaultSdk = firstSdkName == defaultSdkName && sdkDirectives.Count() <= 1;
if (usingOnlyDefaultSdk)
{
writer.WriteLine($"""
<EnableDefaultEmbeddedResourceItems>false</EnableDefaultEmbeddedResourceItems>
<EnableDefaultNoneItems>false</EnableDefaultNoneItems>
""");
}

// 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)
Expand Down Expand Up @@ -1399,9 +1412,9 @@ public static void WriteProjectFile(

if (!sdkDirectives.Any())
{
Debug.Assert(firstSdkName == "Microsoft.NET.Sdk" && firstSdkVersion == null);
writer.WriteLine("""
<Import Project="Sdk.targets" Sdk="Microsoft.NET.Sdk" />
Debug.Assert(firstSdkName == defaultSdkName && firstSdkVersion == null);
writer.WriteLine($"""
<Import Project="Sdk.targets" Sdk="{defaultSdkName}" />
""");
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -394,13 +394,14 @@ public void NestedDirectory()
}

/// <summary>
/// Default items like <c>None</c> or <c>Content</c> are copied over.
/// Default items like <c>None</c> or <c>Content</c> are copied over if non-default SDK is used.
/// </summary>
[Fact]
public void DefaultItems()
public void DefaultItems_NonDefaultSdk()
{
var testInstance = _testAssetsManager.CreateTestDirectory();
File.WriteAllText(Path.Join(testInstance.Path, "Program.cs"), """
#:sdk Microsoft.NET.Sdk.Web
Console.WriteLine();
""");
File.WriteAllText(Path.Join(testInstance.Path, "my.json"), "");
Expand Down Expand Up @@ -433,6 +434,8 @@ public void DefaultItems_MoreIncluded()
var testInstance = _testAssetsManager.CreateTestDirectory();
File.WriteAllText(Path.Join(testInstance.Path, "Program.cs"), """
#:property EnableDefaultCompileItems=true
#:property EnableDefaultEmbeddedResourceItems=true
#:property EnableDefaultNoneItems=true
Console.WriteLine();
""");
File.WriteAllText(Path.Join(testInstance.Path, "my.json"), "");
Expand Down Expand Up @@ -487,6 +490,8 @@ public void DefaultItems_ExcludedViaMetadata()
{
var testInstance = _testAssetsManager.CreateTestDirectory();
File.WriteAllText(Path.Join(testInstance.Path, "Program.cs"), """
#:property EnableDefaultEmbeddedResourceItems=true
#:property EnableDefaultNoneItems=true
Console.WriteLine();
""");
File.WriteAllText(Path.Join(testInstance.Path, "my.json"), "");
Expand Down Expand Up @@ -522,6 +527,7 @@ public void DefaultItems_ImplicitBuildFileInDirectory()
{
var testInstance = _testAssetsManager.CreateTestDirectory();
File.WriteAllText(Path.Join(testInstance.Path, "Program.cs"), """
#:sdk Microsoft.NET.Sdk.Web
Console.WriteLine(Util.GetText());
""");
File.WriteAllText(Path.Join(testInstance.Path, "Util.cs"), """
Expand Down Expand Up @@ -677,6 +683,8 @@ public void DefaultItems_AlongsideProj([CombinatorialValues("sln", "slnx", "cspr

var testInstance = _testAssetsManager.CreateTestDirectory();
File.WriteAllText(Path.Join(testInstance.Path, "Program.cs"), """
#:property EnableDefaultEmbeddedResourceItems=true
#:property EnableDefaultNoneItems=true
Console.WriteLine();
""");
File.WriteAllText(Path.Join(testInstance.Path, "my.json"), "");
Expand Down
83 changes: 45 additions & 38 deletions test/dotnet.Tests/CommandTests/Run/RunFileTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1363,7 +1363,7 @@ public void Verbosity_CompilationDiagnostics()
}

/// <summary>
/// Default projects include embedded resources by default.
/// File-based projects using the default SDK do not include embedded resources by default.
/// </summary>
[Fact]
public void EmbeddedResource()
Expand All @@ -1372,6 +1372,21 @@ public void EmbeddedResource()
File.WriteAllText(Path.Join(testInstance.Path, "Program.cs"), s_programReadingEmbeddedResource);
File.WriteAllText(Path.Join(testInstance.Path, "Resources.resx"), s_resx);

// By default, with the default SDK, embedded resources are not included.
new DotnetCommand(Log, "run", "Program.cs")
.WithWorkingDirectory(testInstance.Path)
.Execute()
.Should().Pass()
.And.HaveStdOut("""
Resource not found
""");

// This behavior can be overridden to enable embedded resources.
File.WriteAllText(Path.Join(testInstance.Path, "Program.cs"), $"""
#:property EnableDefaultEmbeddedResourceItems=true
{s_programReadingEmbeddedResource}
""");

new DotnetCommand(Log, "run", "Program.cs")
.WithWorkingDirectory(testInstance.Path)
.Execute()
Expand All @@ -1380,9 +1395,23 @@ public void EmbeddedResource()
[MyString, TestValue]
""");

// This behavior can be overridden.
// When using a non-default SDK, embedded resources are included by default.
File.WriteAllText(Path.Join(testInstance.Path, "Program.cs"), $"""
#:property EnableDefaultEmbeddedResourceItems=false
#:sdk Microsoft.NET.Sdk.Web
{s_programReadingEmbeddedResource}
""");

new DotnetCommand(Log, "run", "Program.cs")
.WithWorkingDirectory(testInstance.Path)
.Execute()
.Should().Pass()
.And.HaveStdOut("""
[MyString, TestValue]
""");

// When using the default SDK explicitly, embedded resources are not included.
File.WriteAllText(Path.Join(testInstance.Path, "Program.cs"), $"""
#:sdk Microsoft.NET.Sdk
{s_programReadingEmbeddedResource}
""");

Expand All @@ -1405,7 +1434,10 @@ public void EmbeddedResource_AlongsideProj([CombinatorialValues("sln", "slnx", "
bool considered = ext is "sln" or "slnx" or "csproj";

var testInstance = _testAssetsManager.CreateTestDirectory();
File.WriteAllText(Path.Join(testInstance.Path, "Program.cs"), s_programReadingEmbeddedResource);
File.WriteAllText(Path.Join(testInstance.Path, "Program.cs"), $"""
#:property EnableDefaultEmbeddedResourceItems=true
{s_programReadingEmbeddedResource}
""");
File.WriteAllText(Path.Join(testInstance.Path, "Resources.resx"), s_resx);
File.WriteAllText(Path.Join(testInstance.Path, $"repo.{ext}"), "");

Expand Down Expand Up @@ -3147,6 +3179,7 @@ public void UpToDate_DefaultItems(bool optOut)
var testInstance = _testAssetsManager.CreateTestDirectory();
var code = $"""
{(optOut ? "#:property FileBasedProgramCanSkipMSBuild=false" : "")}
#:property EnableDefaultEmbeddedResourceItems=true
{s_programReadingEmbeddedResource}
""";
File.WriteAllText(Path.Join(testInstance.Path, "Program.cs"), code);
Expand All @@ -3167,50 +3200,20 @@ public void UpToDate_DefaultItems(bool optOut)
Build(testInstance, BuildLevel.All, ["--no-cache"], expectedOutput: "[MyString, UpdatedValue]");
}

/// <summary>
/// Combination of <see cref="UpToDate_DefaultItems"/> with <see cref="CscOnly"/> optimization.
/// </summary>
[Theory, CombinatorialData] // https://github.com/dotnet/sdk/issues/50912
public void UpToDate_DefaultItems_CscOnly(bool optOut)
{
var testInstance = _testAssetsManager.CreateTestDirectory(baseDirectory: OutOfTreeBaseDirectory);
var code = $"""
{(optOut ? "#:property FileBasedProgramCanSkipMSBuild=false" : "")}
{s_programReadingEmbeddedResource}
""";
File.WriteAllText(Path.Join(testInstance.Path, "Program.cs"), code);
File.WriteAllText(Path.Join(testInstance.Path, "Resources.resx"), s_resx);

Build(testInstance, optOut ? BuildLevel.All : BuildLevel.Csc, expectedOutput: optOut ? "[MyString, TestValue]" : "Resource not found");

// Update the RESX file.
File.WriteAllText(Path.Join(testInstance.Path, "Resources.resx"), s_resx.Replace("TestValue", "UpdatedValue"));

Build(testInstance, optOut ? BuildLevel.All : BuildLevel.None, expectedOutput: optOut ? "[MyString, UpdatedValue]" : "Resource not found");

// Update the C# file.
File.WriteAllText(Path.Join(testInstance.Path, "Program.cs"), "//v2\n" + code);

Build(testInstance, optOut ? BuildLevel.All : BuildLevel.Csc, expectedOutput: optOut ? "[MyString, UpdatedValue]" : "Resource not found");

Build(testInstance, BuildLevel.All, ["--no-cache"], expectedOutput: "[MyString, UpdatedValue]");

// Update the C# file.
File.WriteAllText(Path.Join(testInstance.Path, "Program.cs"), "//v3\n" + code);

Build(testInstance, optOut ? BuildLevel.All : BuildLevel.Csc, expectedOutput: "[MyString, UpdatedValue]");
}

/// <summary>
/// Combination of <see cref="UpToDate_DefaultItems"/> with <see cref="CscOnly_AfterMSBuild"/> optimization.
/// </summary>
/// <remarks>
/// Note: we cannot test <see cref="CscOnly"/> because that optimization doesn't support neither <c>#:property</c> nor <c>#:sdk</c> which we need to enable default items.
/// </remarks>
[Theory, CombinatorialData] // https://github.com/dotnet/sdk/issues/50912
public void UpToDate_DefaultItems_CscOnly_AfterMSBuild(bool optOut)
{
var testInstance = _testAssetsManager.CreateTestDirectory(baseDirectory: OutOfTreeBaseDirectory);
var code = $"""
#:property Configuration=Release
{(optOut ? "#:property FileBasedProgramCanSkipMSBuild=false" : "")}
#:property EnableDefaultEmbeddedResourceItems=true
{s_programReadingEmbeddedResource}
""";
File.WriteAllText(Path.Join(testInstance.Path, "Program.cs"), code);
Expand Down Expand Up @@ -3861,6 +3864,8 @@ public void Api_Diagnostic_01()
<FileBasedProgram>true</FileBasedProgram>
<EnableDefaultCompileItems>false</EnableDefaultCompileItems>
<DisableDefaultItemsInProjectFolder>true</DisableDefaultItemsInProjectFolder>
<EnableDefaultEmbeddedResourceItems>false</EnableDefaultEmbeddedResourceItems>
<EnableDefaultNoneItems>false</EnableDefaultNoneItems>
<OutputType>Exe</OutputType>
<TargetFramework>{ToolsetInfo.CurrentTargetFramework}</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
Expand Down Expand Up @@ -3929,6 +3934,8 @@ public void Api_Diagnostic_02()
<FileBasedProgram>true</FileBasedProgram>
<EnableDefaultCompileItems>false</EnableDefaultCompileItems>
<DisableDefaultItemsInProjectFolder>true</DisableDefaultItemsInProjectFolder>
<EnableDefaultEmbeddedResourceItems>false</EnableDefaultEmbeddedResourceItems>
<EnableDefaultNoneItems>false</EnableDefaultNoneItems>
<OutputType>Exe</OutputType>
<TargetFramework>{ToolsetInfo.CurrentTargetFramework}</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
Expand Down
Loading