command-line-api icon indicating copy to clipboard operation
command-line-api copied to clipboard

Getting started tutorial does not work with `dotnet run` using .NET 8.0

Open frankbuckley opened this issue 1 year ago • 4 comments

Following the getting started tutorial:

using System.CommandLine;

namespace CommandLineTest;

internal class Program
{
    private static async Task<int> Main(string[] args)
    {
        var fileOption = new Option<FileInfo?>(
            name: "--file",
            description: "The file to read and display on the console.");

        var rootCommand = new RootCommand("Sample app for System.CommandLine");
        rootCommand.AddOption(fileOption);

        rootCommand.SetHandler((file) => ReadFile(file!),
            fileOption);

        return await rootCommand.InvokeAsync(args);
    }

    private static void ReadFile(FileInfo file)
    {
        File.ReadLines(file.FullName)
            .ToList()
            .ForEach(Console.WriteLine);
    }
}

and with a launchSettings.json:

{
  "profiles": {
    "CommandLineTest": {
      "commandName": "Project",
      "commandLineArgs": "--file CommandLineTest.runtimeconfig.json"
    }
  }
}

If you start the project from Visual Studio, then it works as expected and writes the contents of the file to the console.

If you start the built project from the bin\Debug\net8.0 directory*, then it works as expected and writes the contents of the file to the console.

If you start the project using dotnet run -- --file CommandLineTest.runtimeconfig.json, then you get an exception:

Unhandled exception: System.IO.FileNotFoundException: Could not find file 'V:\dev\testing\CommandLineTest\CommandLineTest\CommandLineTest.runtimeconfig.json'.
File name: 'V:\dev\testing\CommandLineTest\CommandLineTest\CommandLineTest.runtimeconfig.json'
   at Microsoft.Win32.SafeHandles.SafeFileHandle.CreateFile(String fullPath, FileMode mode, FileAccess access, FileShare share, FileOptions options)
   at Microsoft.Win32.SafeHandles.SafeFileHandle.Open(String fullPath, FileMode mode, FileAccess access, FileShare share, FileOptions options, Int64 preallocationSize, Nullable`1 unixCreateMode)
   at System.IO.Strategies.OSFileStreamStrategy..ctor(String path, FileMode mode, FileAccess access, FileShare share, FileOptions options, Int64 preallocationSize, Nullable`1 unixCreateMode)
   at System.IO.Strategies.FileStreamHelpers.ChooseStrategyCore(String path, FileMode mode, FileAccess access, FileShare share, FileOptions options, Int64 preallocationSize, Nullable`1 unixCreateMode)
   at System.IO.StreamReader.ValidateArgsAndOpenPath(String path, Encoding encoding, Int32 bufferSize)
   at System.IO.File.ReadLines(String path)
   at CommandLineTest.Program.ReadFile(FileInfo file) in V:\dev\testing\CommandLineTest\CommandLineTest\Program.cs:line 24
   at CommandLineTest.Program.<>c.<Main>b__0_0(FileInfo file) in V:\dev\testing\CommandLineTest\CommandLineTest\Program.cs:line 16
   at System.CommandLine.Handler.<>c__DisplayClass2_0`1.<SetHandler>b__0(InvocationContext context)
   at System.CommandLine.Invocation.AnonymousCommandHandler.Invoke(InvocationContext context)
   at System.CommandLine.Invocation.AnonymousCommandHandler.InvokeAsync(InvocationContext context)
   at System.CommandLine.Invocation.InvocationPipeline.<>c__DisplayClass4_0.<<BuildInvocationChain>b__0>d.MoveNext()

This is due to difference in how current directory is set when running from Visual Studio vs. CLI.

FileInfo (and DirectoryInfo) end up using current directory to normalize relative paths.

If you add the following lines to Main() and run from CLI and Visual Studio you can observe the differences:

        Console.WriteLine($"BaseDirectory: {AppContext.BaseDirectory}");
        Console.WriteLine($"CurrentDirectory: {Environment.CurrentDirectory}");
        Console.WriteLine($"Path.GetFullPath(\"./\"): {Path.GetFullPath("./")}");

You might consider updating the getting started docs to note the problem and/or update the path used for the dotnet run example - for example: dotnet run -- --file .\bin\Debug\net8.0\CommandLineTest.runtimeconfig.json

Related:

  • https://github.com/dotnet/project-system/issues/3619
  • https://github.com/dotnet/sdk/issues/30724
  • https://github.com/dotnet/sdk/issues/9949

frankbuckley avatar May 13 '24 16:05 frankbuckley

Workaround to get Visual Studio to behave the same as dotnet run from the project root is to add "workingDirectory": "./" to launchSettings.json:

{
  "profiles": {
    "CommandLineTest": {
      "commandName": "Project",
      "commandLineArgs": "--file ./bin/Debug/net8.0/CommandLineTest.runtimeconfig.json",
      "workingDirectory": "./"
    }
  }
}

This works the same in Visual Studio as:

dotnet run -- --file ./bin/Debug/net8.0/CommandLineTest.runtimeconfig.json

frankbuckley avatar May 13 '24 16:05 frankbuckley

https://github.com/dotnet/project-system/issues/5053 is now closed, any chance we can get this solved?

Trolldemorted avatar Jul 25 '24 11:07 Trolldemorted

@Trolldemorted, this issue explains the problem, but don't resolve it. The PR dotnet/docs#41823 will fix it.

vernou avatar Jul 25 '24 12:07 vernou

The PR was merged, so this ticket can be closed.

vernou avatar Jul 26 '24 22:07 vernou