Skip to content
Open
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
2 changes: 2 additions & 0 deletions src/Cli/dotnet/Commands/Run/RunCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -497,6 +498,7 @@ static void InvokeRunArgumentsTarget(ProjectInstance project, bool noBuild, Faca
}
}

[DoesNotReturn]
internal static void ThrowUnableToRunError(ProjectInstance project)
{
string targetFrameworks = project.GetPropertyValue("TargetFrameworks");
Expand Down
16 changes: 14 additions & 2 deletions src/Cli/dotnet/Commands/Run/RunProperties.cs
Original file line number Diff line number Diff line change
@@ -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;

Expand All @@ -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"),
Expand All @@ -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)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like a call site of FromProject in src/Cli/dotnet/Commands/Test/MTP/SolutionAndProjectUtility.cs:308 is reimplementing some aspects of FromProject. It doesn't seem like it would lead to incorrect behavior, but, thought I would call it out anyway.

{
if (!TryFromProject(project, out var result))
{
RunCommand.ThrowUnableToRunError(project);
}
Expand Down
6 changes: 4 additions & 2 deletions src/Cli/dotnet/Commands/Run/VirtualProjectBuildingCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down Expand Up @@ -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;
}

Expand Down
224 changes: 224 additions & 0 deletions test/dotnet.Tests/CommandTests/Run/RunFileTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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"), """
<Project>
<PropertyGroup>
<TargetFramework></TargetFramework>
</PropertyGroup>
</Project>
""");

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")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This question isn't really relevant to the fix, but..
What wizardry are you doing to get console output to actually flow out when running a winexe? If I make a new console app from template, change OutputType to WinExe, and dotnet run the project, I don't get any output.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Output from WinExe is visible when redirected, e.g., dotnet run winexe.cs > out.txt works. And the test utility redirects the output under the hood.

.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"), """
<Project>
<PropertyGroup>
<TargetFramework></TargetFramework>
</PropertyGroup>
</Project>
""");

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()
{
Expand Down
Loading