diff --git a/src/Cli/dotnet/Commands/Run/RunCommand.cs b/src/Cli/dotnet/Commands/Run/RunCommand.cs
index 33ec7b5e3219..4235c5b0e431 100644
--- a/src/Cli/dotnet/Commands/Run/RunCommand.cs
+++ b/src/Cli/dotnet/Commands/Run/RunCommand.cs
@@ -6,6 +6,7 @@
using System.CommandLine;
using System.CommandLine.Parsing;
using System.Diagnostics;
+using System.Diagnostics.CodeAnalysis;
using Microsoft.Build.Evaluation;
using Microsoft.Build.Exceptions;
using Microsoft.Build.Execution;
@@ -497,6 +498,7 @@ static void InvokeRunArgumentsTarget(ProjectInstance project, bool noBuild, Faca
}
}
+ [DoesNotReturn]
internal static void ThrowUnableToRunError(ProjectInstance project)
{
string targetFrameworks = project.GetPropertyValue("TargetFrameworks");
diff --git a/src/Cli/dotnet/Commands/Run/RunProperties.cs b/src/Cli/dotnet/Commands/Run/RunProperties.cs
index 81e92cb88315..6972d140ac56 100644
--- a/src/Cli/dotnet/Commands/Run/RunProperties.cs
+++ b/src/Cli/dotnet/Commands/Run/RunProperties.cs
@@ -1,6 +1,7 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
+using System.Diagnostics.CodeAnalysis;
using Microsoft.Build.Execution;
using Microsoft.DotNet.Cli.Utils;
@@ -19,9 +20,9 @@ internal RunProperties(string command, string? arguments, string? workingDirecto
{
}
- internal static RunProperties FromProject(ProjectInstance project)
+ internal static bool TryFromProject(ProjectInstance project, [NotNullWhen(returnValue: true)] out RunProperties? result)
{
- var result = new RunProperties(
+ result = new RunProperties(
Command: project.GetPropertyValue("RunCommand"),
Arguments: project.GetPropertyValue("RunArguments"),
WorkingDirectory: project.GetPropertyValue("RunWorkingDirectory"),
@@ -30,6 +31,17 @@ internal static RunProperties FromProject(ProjectInstance project)
TargetFrameworkVersion: project.GetPropertyValue("TargetFrameworkVersion"));
if (string.IsNullOrEmpty(result.Command))
+ {
+ result = null;
+ return false;
+ }
+
+ return true;
+ }
+
+ internal static RunProperties FromProject(ProjectInstance project)
+ {
+ if (!TryFromProject(project, out var result))
{
RunCommand.ThrowUnableToRunError(project);
}
diff --git a/src/Cli/dotnet/Commands/Run/VirtualProjectBuildingCommand.cs b/src/Cli/dotnet/Commands/Run/VirtualProjectBuildingCommand.cs
index 6997b8052a0c..216f717c81df 100644
--- a/src/Cli/dotnet/Commands/Run/VirtualProjectBuildingCommand.cs
+++ b/src/Cli/dotnet/Commands/Run/VirtualProjectBuildingCommand.cs
@@ -353,7 +353,9 @@ public override int Execute()
Debug.Assert(buildRequest.ProjectInstance != null);
// Cache run info (to avoid re-evaluating the project instance).
- cache.CurrentEntry.Run = RunProperties.FromProject(buildRequest.ProjectInstance);
+ cache.CurrentEntry.Run = RunProperties.TryFromProject(buildRequest.ProjectInstance, out var runProperties)
+ ? runProperties
+ : null;
TryCacheCscArguments(cache, buildResult, buildRequest.ProjectInstance);
@@ -897,7 +899,7 @@ private BuildLevel GetBuildLevel(out CacheInfo cache)
{
if (!NeedsToBuild(out cache))
{
- Reporter.Verbose.WriteLine("No need to build, the output is up to date.");
+ Reporter.Verbose.WriteLine("No need to build, the output is up to date. Cache: " + ArtifactsPath);
return BuildLevel.None;
}
diff --git a/test/dotnet.Tests/CommandTests/Run/RunFileTests.cs b/test/dotnet.Tests/CommandTests/Run/RunFileTests.cs
index 2c87e8cc7469..ecc6a6731057 100644
--- a/test/dotnet.Tests/CommandTests/Run/RunFileTests.cs
+++ b/test/dotnet.Tests/CommandTests/Run/RunFileTests.cs
@@ -1483,6 +1483,230 @@ public void NoBuild_02()
.And.HaveStdOut("Changed");
}
+ [Fact]
+ public void Build_Library()
+ {
+ var testInstance = _testAssetsManager.CreateTestDirectory();
+ var programFile = Path.Join(testInstance.Path, "lib.cs");
+ File.WriteAllText(programFile, """
+ #:property OutputType=Library
+ class C;
+ """);
+
+ var artifactsDir = VirtualProjectBuildingCommand.GetArtifactsPath(programFile);
+ if (Directory.Exists(artifactsDir)) Directory.Delete(artifactsDir, recursive: true);
+
+ new DotnetCommand(Log, "build", "lib.cs")
+ .WithWorkingDirectory(testInstance.Path)
+ .Execute()
+ .Should().Pass();
+
+ new DotnetCommand(Log, "run", "lib.cs")
+ .WithWorkingDirectory(testInstance.Path)
+ .Execute()
+ .Should().Fail()
+ .And.HaveStdErr(string.Format(CliCommandStrings.RunCommandExceptionUnableToRun,
+ Path.ChangeExtension(programFile, ".csproj"),
+ ToolsetInfo.CurrentTargetFrameworkVersion,
+ "Library"));
+ }
+
+ [Fact]
+ public void Build_Library_MultiTarget()
+ {
+ var testInstance = _testAssetsManager.CreateTestDirectory();
+ var programFile = Path.Join(testInstance.Path, "lib.cs");
+ File.WriteAllText(programFile, $"""
+ #:property OutputType=Library
+ #:property PublishAot=false
+ #:property LangVersion=preview
+ #:property TargetFrameworks=netstandard2.0;{ToolsetInfo.CurrentTargetFramework}
+ class C;
+ """);
+
+ // https://github.com/dotnet/sdk/issues/51077: cannot set this via `#:property` directive.
+ File.WriteAllText(Path.Join(testInstance.Path, "Directory.Build.props"), """
+
+
+
+
+
+ """);
+
+ var artifactsDir = VirtualProjectBuildingCommand.GetArtifactsPath(programFile);
+ if (Directory.Exists(artifactsDir)) Directory.Delete(artifactsDir, recursive: true);
+
+ new DotnetCommand(Log, "build", "lib.cs")
+ .WithWorkingDirectory(testInstance.Path)
+ .Execute()
+ .Should().Pass();
+
+ new DotnetCommand(Log, "run", "lib.cs")
+ .WithWorkingDirectory(testInstance.Path)
+ .Execute()
+ .Should().Fail()
+ .And.HaveStdErr(string.Format(CliCommandStrings.RunCommandExceptionUnableToRunSpecifyFramework, "--framework"));
+
+ new DotnetCommand(Log, "run", "lib.cs", "--framework", ToolsetInfo.CurrentTargetFramework)
+ .WithWorkingDirectory(testInstance.Path)
+ .Execute()
+ .Should().Fail()
+ .And.HaveStdErr(string.Format(CliCommandStrings.RunCommandExceptionUnableToRun,
+ Path.ChangeExtension(programFile, ".csproj"),
+ ToolsetInfo.CurrentTargetFrameworkVersion,
+ "Library"));
+ }
+
+ [Fact]
+ public void Build_Module()
+ {
+ var testInstance = _testAssetsManager.CreateTestDirectory();
+ var programFile = Path.Join(testInstance.Path, "module.cs");
+ File.WriteAllText(programFile, """
+ #:property OutputType=Module
+ #:property ProduceReferenceAssembly=false
+ class C;
+ """);
+
+ var artifactsDir = VirtualProjectBuildingCommand.GetArtifactsPath(programFile);
+ if (Directory.Exists(artifactsDir)) Directory.Delete(artifactsDir, recursive: true);
+
+ new DotnetCommand(Log, "build", "module.cs")
+ .WithWorkingDirectory(testInstance.Path)
+ .Execute()
+ .Should().Pass();
+
+ new DotnetCommand(Log, "run", "module.cs")
+ .WithWorkingDirectory(testInstance.Path)
+ .Execute()
+ .Should().Fail()
+ .And.HaveStdErr(string.Format(CliCommandStrings.RunCommandExceptionUnableToRun,
+ Path.ChangeExtension(programFile, ".csproj"),
+ ToolsetInfo.CurrentTargetFrameworkVersion,
+ "Module"));
+ }
+
+ [Fact]
+ public void Build_WinExe()
+ {
+ var testInstance = _testAssetsManager.CreateTestDirectory();
+ var programFile = Path.Join(testInstance.Path, "winexe.cs");
+ File.WriteAllText(programFile, """
+ #:property OutputType=WinExe
+ Console.WriteLine("Hello WinExe");
+ """);
+
+ var artifactsDir = VirtualProjectBuildingCommand.GetArtifactsPath(programFile);
+ if (Directory.Exists(artifactsDir)) Directory.Delete(artifactsDir, recursive: true);
+
+ new DotnetCommand(Log, "build", "winexe.cs")
+ .WithWorkingDirectory(testInstance.Path)
+ .Execute()
+ .Should().Pass();
+
+ new DotnetCommand(Log, "run", "winexe.cs")
+ .WithWorkingDirectory(testInstance.Path)
+ .Execute()
+ .Should().Pass()
+ .And.HaveStdOut("Hello WinExe");
+ }
+
+ [Fact]
+ public void Build_Exe()
+ {
+ var testInstance = _testAssetsManager.CreateTestDirectory();
+ var programFile = Path.Join(testInstance.Path, "exe.cs");
+ File.WriteAllText(programFile, """
+ #:property OutputType=Exe
+ Console.WriteLine("Hello Exe");
+ """);
+
+ var artifactsDir = VirtualProjectBuildingCommand.GetArtifactsPath(programFile);
+ if (Directory.Exists(artifactsDir)) Directory.Delete(artifactsDir, recursive: true);
+
+ new DotnetCommand(Log, "build", "exe.cs")
+ .WithWorkingDirectory(testInstance.Path)
+ .Execute()
+ .Should().Pass();
+
+ new DotnetCommand(Log, "run", "exe.cs")
+ .WithWorkingDirectory(testInstance.Path)
+ .Execute()
+ .Should().Pass()
+ .And.HaveStdOut("Hello Exe");
+ }
+
+ [Fact]
+ public void Build_Exe_MultiTarget()
+ {
+ var testInstance = _testAssetsManager.CreateTestDirectory();
+ var programFile = Path.Join(testInstance.Path, "exe.cs");
+ File.WriteAllText(programFile, $"""
+ #:property OutputType=Exe
+ #:property PublishAot=false
+ #:property LangVersion=preview
+ #:property TargetFrameworks=netstandard2.0;{ToolsetInfo.CurrentTargetFramework}
+ Console.WriteLine("Hello Exe");
+ """);
+
+ // https://github.com/dotnet/sdk/issues/51077: cannot set this via `#:property` directive.
+ File.WriteAllText(Path.Join(testInstance.Path, "Directory.Build.props"), """
+
+
+
+
+
+ """);
+
+ var artifactsDir = VirtualProjectBuildingCommand.GetArtifactsPath(programFile);
+ if (Directory.Exists(artifactsDir)) Directory.Delete(artifactsDir, recursive: true);
+
+ new DotnetCommand(Log, "build", "exe.cs")
+ .WithWorkingDirectory(testInstance.Path)
+ .Execute()
+ .Should().Pass();
+
+ new DotnetCommand(Log, "run", "exe.cs")
+ .WithWorkingDirectory(testInstance.Path)
+ .Execute()
+ .Should().Fail()
+ .And.HaveStdErr(string.Format(CliCommandStrings.RunCommandExceptionUnableToRunSpecifyFramework, "--framework"));
+
+ new DotnetCommand(Log, "run", "exe.cs", "--framework", ToolsetInfo.CurrentTargetFramework)
+ .WithWorkingDirectory(testInstance.Path)
+ .Execute()
+ .Should().Pass()
+ .And.HaveStdOut("Hello Exe");
+ }
+
+ [Fact]
+ public void Build_AppContainerExe()
+ {
+ var testInstance = _testAssetsManager.CreateTestDirectory();
+ var programFile = Path.Join(testInstance.Path, "appcontainerexe.cs");
+ File.WriteAllText(programFile, """
+ #:property OutputType=AppContainerExe
+ Console.WriteLine("Hello AppContainerExe");
+ """);
+
+ var artifactsDir = VirtualProjectBuildingCommand.GetArtifactsPath(programFile);
+ if (Directory.Exists(artifactsDir)) Directory.Delete(artifactsDir, recursive: true);
+
+ new DotnetCommand(Log, "build", "appcontainerexe.cs")
+ .WithWorkingDirectory(testInstance.Path)
+ .Execute()
+ .Should().Pass();
+
+ new DotnetCommand(Log, "run", "appcontainerexe.cs")
+ .WithWorkingDirectory(testInstance.Path)
+ .Execute()
+ .Should().Fail()
+ .And.HaveStdErr(string.Format(CliCommandStrings.RunCommandExceptionUnableToRun,
+ Path.ChangeExtension(programFile, ".csproj"),
+ ToolsetInfo.CurrentTargetFrameworkVersion,
+ "AppContainerExe"));
+ }
+
[Fact]
public void Publish()
{