diff --git a/documentation/general/dotnet-run-file.md b/documentation/general/dotnet-run-file.md
index 0c75aa0910d7..c8e8d5600067 100644
--- a/documentation/general/dotnet-run-file.md
+++ b/documentation/general/dotnet-run-file.md
@@ -178,7 +178,9 @@ have the shared `.cs` files source-included via `
+ Condition="'$(UseArtifactsOutput)' == 'true' Or '$(ArtifactsPath)' != '' Or '$(FileBasedAppArtifactsPath)' != ''"/>
true
diff --git a/src/Tasks/Microsoft.NET.Build.Tasks/targets/Microsoft.NET.DefaultArtifactsPath.props b/src/Tasks/Microsoft.NET.Build.Tasks/targets/Microsoft.NET.DefaultArtifactsPath.props
index b5dd5f111c1b..d08a2de949b0 100644
--- a/src/Tasks/Microsoft.NET.Build.Tasks/targets/Microsoft.NET.DefaultArtifactsPath.props
+++ b/src/Tasks/Microsoft.NET.Build.Tasks/targets/Microsoft.NET.DefaultArtifactsPath.props
@@ -40,4 +40,12 @@ Copyright (c) .NET Foundation. All rights reserved.
$(MSBuildProjectDirectory)\artifacts
<_ArtifactsPathLocationType>ProjectFolder
+
+
+
+ true
+ $(FileBasedAppArtifactsPath)
+ false
+ <_ArtifactsPathLocationType>FileBasedApp
+
diff --git a/test/dotnet.Tests/CommandTests/Run/RunFileTests.cs b/test/dotnet.Tests/CommandTests/Run/RunFileTests.cs
index 98b62c75dd06..214898ebb4bf 100644
--- a/test/dotnet.Tests/CommandTests/Run/RunFileTests.cs
+++ b/test/dotnet.Tests/CommandTests/Run/RunFileTests.cs
@@ -13,7 +13,7 @@ namespace Microsoft.DotNet.Cli.Run.Tests;
public sealed class RunFileTests(ITestOutputHelper log) : SdkTest(log)
{
- private static readonly string s_program = """
+ private static readonly string s_program = /* lang=C#-Test */ """
if (args.Length > 0)
{
Console.WriteLine("echo args:" + string.Join(";", args));
@@ -27,7 +27,7 @@ public sealed class RunFileTests(ITestOutputHelper log) : SdkTest(log)
#endif
""";
- private static readonly string s_programDependingOnUtil = """
+ private static readonly string s_programDependingOnUtil = /* lang=C#-Test */ """
if (args.Length > 0)
{
Console.WriteLine("echo args:" + string.Join(";", args));
@@ -35,7 +35,7 @@ public sealed class RunFileTests(ITestOutputHelper log) : SdkTest(log)
Console.WriteLine("Hello, " + Util.GetMessage());
""";
- private static readonly string s_util = """
+ private static readonly string s_util = /* lang=C#-Test */ """
static class Util
{
public static string GetMessage()
@@ -45,6 +45,29 @@ public static string GetMessage()
}
""";
+ private static readonly string s_programReadingEmbeddedResource = /* lang=C#-Test */ """
+ var assembly = System.Reflection.Assembly.GetExecutingAssembly();
+ var resourceName = assembly.GetManifestResourceNames().SingleOrDefault();
+
+ if (resourceName is null)
+ {
+ Console.WriteLine("Resource not found");
+ return;
+ }
+
+ using var stream = assembly.GetManifestResourceStream(resourceName)!;
+ using var reader = new System.Resources.ResourceReader(stream);
+ Console.WriteLine(reader.Cast().Single());
+ """;
+
+ private static readonly string s_resx = """
+
+
+ TestValue
+
+
+ """;
+
private static readonly string s_consoleProject = $"""
@@ -950,26 +973,8 @@ public void BinaryLog_EvaluationData()
public void EmbeddedResource()
{
var testInstance = _testAssetsManager.CreateTestDirectory();
- string code = """
- using var stream = System.Reflection.Assembly.GetExecutingAssembly().GetManifestResourceStream("Program.Resources.resources");
-
- if (stream is null)
- {
- Console.WriteLine("Resource not found");
- return;
- }
-
- using var reader = new System.Resources.ResourceReader(stream);
- Console.WriteLine(reader.Cast().Single());
- """;
- File.WriteAllText(Path.Join(testInstance.Path, "Program.cs"), code);
- File.WriteAllText(Path.Join(testInstance.Path, "Resources.resx"), """
-
-
- TestValue
-
-
- """);
+ File.WriteAllText(Path.Join(testInstance.Path, "Program.cs"), s_programReadingEmbeddedResource);
+ File.WriteAllText(Path.Join(testInstance.Path, "Resources.resx"), s_resx);
new DotnetCommand(Log, "run", "Program.cs")
.WithWorkingDirectory(testInstance.Path)
@@ -982,7 +987,7 @@ public void EmbeddedResource()
// This behavior can be overridden.
File.WriteAllText(Path.Join(testInstance.Path, "Program.cs"), $"""
#:property EnableDefaultEmbeddedResourceItems=false
- {code}
+ {s_programReadingEmbeddedResource}
""");
new DotnetCommand(Log, "run", "Program.cs")
@@ -994,6 +999,35 @@ Resource not found
""");
}
+ ///
+ /// .resx files in ./artifacts/ should not be included.
+ /// Part of .
+ ///
+ [Fact]
+ public void EmbeddedResource_InRepoArtifacts()
+ {
+ var testInstance = _testAssetsManager.CreateTestDirectory();
+ File.WriteAllText(Path.Join(testInstance.Path, "Program.cs"), s_programReadingEmbeddedResource);
+ File.WriteAllText(Path.Join(testInstance.Path, "Directory.Build.props"), """
+
+
+ true
+
+
+ """);
+ var dir = Path.Join(testInstance.Path, "artifacts", "obj", "AnotherApp");
+ Directory.CreateDirectory(dir);
+ File.WriteAllText(Path.Join(dir, "Resources.resx"), s_resx);
+
+ new DotnetCommand(Log, "run", "Program.cs", "-bl")
+ .WithWorkingDirectory(testInstance.Path)
+ .Execute()
+ .Should().Pass()
+ .And.HaveStdOut("""
+ Resource not found
+ """);
+ }
+
[Fact]
public void NoRestore_01()
{
@@ -1408,6 +1442,58 @@ public void ArtifactsDirectory_Permissions()
.Should().Be(actualMode, artifactsDir);
}
+ [Fact]
+ public void ArtifactsPath()
+ {
+ var testInstance = _testAssetsManager.CreateTestDirectory();
+ var programPath = Path.Join(testInstance.Path, "Program.cs");
+ File.WriteAllText(programPath, s_program);
+
+ var globalArtifactsDir = VirtualProjectBuildingCommand.GetArtifactsPath(programPath);
+ if (Directory.Exists(globalArtifactsDir)) Directory.Delete(globalArtifactsDir, recursive: true);
+
+ new DirectoryInfo(Path.Join(testInstance.Path, "artifacts")).Should().NotExist();
+ new DirectoryInfo(Path.Join(testInstance.Path, "bin")).Should().NotExist();
+
+ new DotnetCommand(Log, "build", "Program.cs")
+ .WithWorkingDirectory(testInstance.Path)
+ .Execute()
+ .Should().Pass();
+
+ new DirectoryInfo(globalArtifactsDir).EnumerateDirectories().Should().NotBeEmpty();
+ new DirectoryInfo(Path.Join(testInstance.Path, "artifacts")).Should().NotExist();
+ new DirectoryInfo(Path.Join(testInstance.Path, "bin")).Should().NotExist();
+ }
+
+ ///
+ /// When the surrounding repo uses artifacts layout, file-based apps place their artifacts there.
+ ///
+ [Fact]
+ public void ArtifactsPath_ReusedFromRepo()
+ {
+ var testInstance = _testAssetsManager.CreateTestDirectory();
+ var programPath = Path.Join(testInstance.Path, "Program.cs");
+ File.WriteAllText(programPath, s_program);
+ File.WriteAllText(Path.Join(testInstance.Path, "Directory.Build.props"), """
+
+
+ true
+
+
+ """);
+
+ new DirectoryInfo(Path.Join(testInstance.Path, "artifacts")).Should().NotExist();
+
+ new DotnetCommand(Log, "build", "Program.cs")
+ .WithWorkingDirectory(testInstance.Path)
+ .Execute()
+ .Should().Pass();
+
+ // We still put our marker files into the global artifacts directory, but it should not contain any subdirectories.
+ new DirectoryInfo(VirtualProjectBuildingCommand.GetArtifactsPath(programPath)).EnumerateDirectories().Should().BeEmpty();
+ new FileInfo(Path.Join(testInstance.Path, "artifacts", "bin", "Program", "debug", "Program.dll")).Should().Exist();
+ }
+
[Fact]
public void LaunchProfile()
{
@@ -1843,8 +1929,7 @@ public void Api()
- false
- /artifacts
+ /artifacts
artifacts/$(MSBuildProjectName)
@@ -1922,8 +2007,7 @@ public void Api_Diagnostic_01()
- false
- /artifacts
+ /artifacts
artifacts/$(MSBuildProjectName)
@@ -1994,8 +2078,7 @@ public void Api_Diagnostic_02()
- false
- /artifacts
+ /artifacts
artifacts/$(MSBuildProjectName)