Skip to content

Add support for dotnet file.cs (without explicit run subcommand) #48387

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 9 commits into from
Jun 13, 2025
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
16 changes: 7 additions & 9 deletions documentation/general/dotnet-run-file.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,8 @@ For example, the remaining command-line arguments after the first argument (the
(except for the arguments recognized by `dotnet run` unless they are after the `--` separator)
and working directory is not changed (e.g., `cd /x/ && dotnet run /y/file.cs` runs the program in directory `/x/`).

`dotnet path.cs` is a shortcut for `dotnet run path.cs` provided that `path.cs` is a valid [target path](#target-path).

### Other commands

Commands `dotnet restore file.cs` and `dotnet build file.cs` are needed for IDE support and hence work for file-based programs.
Expand Down Expand Up @@ -294,28 +296,24 @@ Also, `InternalsVisibleTo` needs to be added into a C# file as an attribute, or

### Shebang support

It might be beneficial to also ship `dotnet-run` binary
(or `dotnet-run-file` that would only work with file-based programs, not project-based ones, perhaps simply named `cs`)
because some shells do not support multiple command-line arguments in the shebang
Some shells do not support multiple command-line arguments in the shebang
which is needed if one wants to use `/usr/bin/env` to find the `dotnet` executable
(although `-S` argument can be sometimes used to enable multiple argument support):
(although `-S` argument can be sometimes used to enable multiple argument support),
so `dotnet file.cs` instead of `dotnet run file.cs` should be used in shebangs:

```cs
#!/usr/bin/env dotnet run
// ^ Might not work in all shells. "dotnet run" might be passed as a single argument to "env".
```
```cs
#!/usr/bin/env dotnet-run
#!/usr/bin/env dotnet
// ^ Should work in all shells.
```
```cs
#!/usr/bin/env -S dotnet run
// ^ Workaround in some shells.
// ^ Works in some shells.
```

We could also consider making `dotnet file.cs` work because `dotnet file.dll` also works today
but that would require changes to the native dotnet host.

### Other possible commands

We can consider supporting other commands like `dotnet pack`, `dotnet watch`,
Expand Down
6 changes: 5 additions & 1 deletion src/Cli/dotnet/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using System.CommandLine;
using System.Diagnostics;
using Microsoft.DotNet.Cli.CommandFactory;
using Microsoft.DotNet.Cli.Commands.Run;
using Microsoft.DotNet.Cli.Commands.Workload;
using Microsoft.DotNet.Cli.Extensions;
using Microsoft.DotNet.Cli.ShellShim;
Expand Down Expand Up @@ -125,7 +126,10 @@ internal static int ProcessArgs(string[] args, TimeSpan startupTime, ITelemetry
ParseResult parseResult;
using (new PerformanceMeasurement(performanceData, "Parse Time"))
{
parseResult = Parser.Instance.Parse(args);
// If we get C# file path as the first argument, parse as `dotnet run file.cs`.
parseResult = args is [{ } filePath, ..] && VirtualProjectBuildingCommand.IsValidEntryPointPath(filePath)
? Parser.Instance.Parse(["run", .. args])
: Parser.Instance.Parse(args);

// Avoid create temp directory with root permission and later prevent access in non sudo
// This method need to be run very early before temp folder get created
Expand Down
40 changes: 40 additions & 0 deletions test/dotnet.Tests/CommandTests/Run/RunFileTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,46 @@ public void FilePath(string? path, bool differentCasing)
}
}

/// <summary>
/// <c>dotnet file.cs</c> is equivalent to <c>dotnet run file.cs</c>.
/// </summary>
[Fact]
public void FilePath_WithoutRun()
{
var testInstance = _testAssetsManager.CreateTestDirectory();
File.WriteAllText(Path.Join(testInstance.Path, "Program.cs"), s_program);

new DotnetCommand(Log, "Program.cs")
.WithWorkingDirectory(testInstance.Path)
.Execute()
.Should().Pass()
.And.HaveStdOut("""
Hello from Program
""");

File.WriteAllText(Path.Join(testInstance.Path, "Program.cs"), $"""
#:property Configuration=Release
{s_program}
""");

new DotnetCommand(Log, "Program.cs")
.WithWorkingDirectory(testInstance.Path)
.Execute()
.Should().Pass()
.And.HaveStdOut("""
Hello from Program
Release config
""");

new DotnetCommand(Log, "Program.cs", "-c", "Debug")
.WithWorkingDirectory(testInstance.Path)
.Execute()
.Should().Pass()
.And.HaveStdOut("""
Hello from Program
""");
}

/// <summary>
/// Casing of the argument is used for the output binary name.
/// </summary>
Expand Down
Loading