Skip to content
Draft
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
29 changes: 29 additions & 0 deletions documentation/general/dotnet-run-file.md
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,35 @@ and it is not a DLL path, built-in command, or a NuGet tool (e.g., `dotnet watch
even if a valid `watch` file-based app exists in the current directory;
one can use `dotnet ./watch` to run the file-based app).

### `dnx`

The `dnx` launcher also supports running file-based apps whose target path is valid according to the same rules as `dotnet run`:
the file must exist and either have a `.cs` extension or start with `#!`.
The target path must also be explicit: it must be fully qualified or contain a directory separator,
such as `./app`, `../app`, or `some/directory/app`.
This requirement avoids ambiguity with NuGet tool package IDs, so a bare `dnx app` always retains the existing tool execution behavior
even if a file named `app` exists in the current directory.

```ps1
dnx ./some/path.cs arg0 arg1
```

This is equivalent to `dotnet run --file ./some/path.cs -- arg0 arg1`.
All arguments after the target path are passed to the app verbatim, including a literal `--`
and arguments that have the same names as `dnx` tool options.
Comment on lines +132 to +138

For file-based apps, `dnx` starts the `dotnet` host with the target directory as its working directory so SDK resolution,
including the search for `global.json`, starts from the target directory rather than the directory from which `dnx` was invoked.
After the SDK CLI has started, `dnx` restores the original working directory before building and running the app.
Consequently, implicit build files such as `Directory.Build.props` are still discovered relative to the file-based app,
while the running app observes the directory from which the user invoked `dnx` as its current working directory.
Comment on lines +140 to +144

For example, `cd /x/ && dnx /y/file.cs` searches for `global.json` and implicit build files from `/y/`,
but runs the app with `/x/` as its current working directory.

If the first argument is not a valid file-based app target path, `dnx` retains its existing NuGet tool execution behavior:
it uses the newest installed SDK regardless of `global.json`.

### 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
Original file line number Diff line number Diff line change
Expand Up @@ -1057,9 +1057,15 @@ The default is to publish a framework-dependent application.</value>
<data name="RunFrameworkOptionDescription" xml:space="preserve">
<value>The target framework to run for. The target framework must also be specified in the project file.</value>
</data>
<data name="RunFileModeOptionDescription" xml:space="preserve">
<value>Treat the first application argument as the file-based app to run. A relative file path is resolved against the directory specified by --working-directory.</value>
</data>
<data name="RunRuntimeOptionDescription" xml:space="preserve">
<value>The target runtime to run for.</value>
</data>
<data name="RunWorkingDirectoryOptionDescription" xml:space="preserve">
<value>The working directory to use when running the application. This option overrides the working directory from a launch profile.</value>
</data>
<data name="SdkAppFullName" xml:space="preserve">
<value>.NET SDK Command</value>
</data>
Expand Down Expand Up @@ -1551,4 +1557,16 @@ If command is specified without the argument, it lists all the template packages
<data name="SDKSchemaCommandDefinition" xml:space="preserve">
<value>Display the command schema as JSON.</value>
</data>
<data name="DnxCommandDescription" xml:space="preserve">
<value>Run a file-based app or execute a tool package without permanently installing it.</value>
</data>
<data name="DnxPackageOrFileArgumentDescription" xml:space="preserve">
<value>A qualified path to a file-based app, or a package reference in the form of a package identifier like 'dotnetsay' or package identifier and version separated by '@' like 'dotnetsay@2.1.7'.</value>
</data>
<data name="DnxPackageOrFileArgumentName" xml:space="preserve">
<value>FILE_OR_PACKAGE</value>
</data>
<data name="DnxArgumentsDescription" xml:space="preserve">
<value>Arguments forwarded to the file-based app or tool.</value>
</data>
</root>
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,12 @@ namespace Microsoft.DotNet.Cli.Commands.Dnx;
internal sealed class DnxCommandDefinition : ToolExecuteCommandDefinitionBase
{
public DnxCommandDefinition()
: base("dnx")
: base("dnx", CommandDefinitionStrings.DnxPackageOrFileArgumentName)
{
Description = CommandDefinitionStrings.DnxCommandDescription;
PackageIdentityArgument.Description = CommandDefinitionStrings.DnxPackageOrFileArgumentDescription;
PackageIdentityArgument.HelpName = CommandDefinitionStrings.DnxPackageOrFileArgumentName;
CommandArgument.Description = CommandDefinitionStrings.DnxArgumentsDescription;
Hidden = true;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,17 @@ internal sealed class RunCommandDefinition : Command
HelpName = CommandDefinitionStrings.CommandOptionFileHelpName,
};

public readonly Option<bool> FileModeOption = new("--file-mode")
{
Description = CommandDefinitionStrings.RunFileModeOptionDescription,
Hidden = true,
};

public readonly Option<string> WorkingDirectoryOption = new("--working-directory")
{
Description = CommandDefinitionStrings.RunWorkingDirectoryOptionDescription,
};

public readonly Option<ReadOnlyDictionary<string, string>?> PropertyOption = CommonOptions.CreatePropertyOption();

public readonly Option<string> LaunchProfileOption = new("--launch-profile", "-lp")
Expand Down Expand Up @@ -107,6 +118,8 @@ public RunCommandDefinition()
Options.Add(FrameworkOption);
Options.Add(ProjectOption);
Options.Add(FileOption);
Options.Add(FileModeOption);
Options.Add(WorkingDirectoryOption);
Options.Add(PropertyOption);
Options.Add(LaunchProfileOption);
Options.Add(NoLaunchProfileOption);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ public ToolExecuteCommandDefinition()

internal abstract class ToolExecuteCommandDefinitionBase : Command
{
public readonly Argument<PackageIdentityWithRange> PackageIdentityArgument = CommonArguments.CreateRequiredPackageIdentityArgument("dotnetsay", "2.1.7");
public readonly Argument<PackageIdentityWithRange> PackageIdentityArgument;

public readonly Argument<IEnumerable<string>> CommandArgument = new("commandArguments")
{
Expand All @@ -34,9 +34,13 @@ internal abstract class ToolExecuteCommandDefinitionBase : Command

public readonly NuGetRestoreOptions RestoreOptions = new(forward: true);

public ToolExecuteCommandDefinitionBase(string name)
public ToolExecuteCommandDefinitionBase(string name, string packageIdentityArgumentName = CommonArguments.PackageIdArgumentName)
: base(name, CommandDefinitionStrings.ToolExecuteCommandDescription)
{
PackageIdentityArgument = CommonArguments.CreateRequiredPackageIdentityArgument(
"dotnetsay",
"2.1.7",
packageIdentityArgumentName);
Arguments.Add(PackageIdentityArgument);
Arguments.Add(CommandArgument);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,11 @@ internal static class CommonArguments
IsDynamic = true
};

public static Argument<PackageIdentityWithRange> CreateRequiredPackageIdentityArgument(string examplePackage = "Newtonsoft.Json", string exampleVersion = "13.0.3") =>
new(PackageIdArgumentName)
public static Argument<PackageIdentityWithRange> CreateRequiredPackageIdentityArgument(
string examplePackage = "Newtonsoft.Json",
string exampleVersion = "13.0.3",
string argumentName = PackageIdArgumentName) =>
new(argumentName)
{
Description = string.Format(CommandDefinitionStrings.PackageIdentityArgumentDescription, examplePackage, exampleVersion),
CustomParser = argumentResult => ParsePackageIdentityWithVersionSeparator(argumentResult.Tokens[0]?.Value)!.Value,
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading