tech.guitarrapc.cóm

Technical updates

ラッパーコマンドをConsoleAppFramework v4からv5へ移行する

前回の記事でラッパーコマンドの場合、ConsoleAppFramework v5へ移行する悩みを書きました。その後ConsoleAppFramework 5.4.0がリリースされ、ラッパーコマンドへの引数を渡しがサポートされました。これによってConsoleAppFramework v5でもラッパーコマンドをかけるようになったので、さくっとみてみます。

引数エスケープのサポート

5.4.0で、前回の記事でいうところの--を使って引数を分ける方法がサポートされました。以下の例でいうと、メソッドのパラメーターである引数1・引数2と、メソッドのパラメーター外を--で区別できます。

$ バッチA --引数1--引数2-- コマンドAの引数

例えば次のようなメソッド定義を用意します。エスケープされた引数を受け取るには、ConsoleAppContextEscapedArgumentsを利用します。

using ConsoleAppFramework;

var app = ConsoleApp.Create();
app.Add("", (string msg, ConsoleAppContext context) => Console.WriteLine($"""
    Arguments: {string.Join(" ", context.Arguments)}
    CommandArguments: {string.Join(" ", context.CommandArguments)}
    EscapedArguments: {string.Join(" ", context.EscapedArguments)}
    """
));
app.Run(args);

実行してみましょう。

// Output:
// Arguments: --msg foo -- --hello bar --hoge moge
// CommandArguments: --msg foo
// EscapedArguments: --hello bar --hoge moge
args = ["--msg", "foo", "--", "--hello", "bar", "--hoge", "moge"];

// Output:
// Arguments: --msg foo
// CommandArguments: --msg foo
// EscapedArguments:
args = ["--msg", "foo"];

// Output:
// Arguments: --msg foo
// CommandArguments: --msg foo
// EscapedArguments:
args = ["--msg", "foo", "--"];

ラッパーコマンドに引数を渡す

エスケープされた引数がわかっているなら、ラッパーコマンドにわたすのも簡単です。System.Diagnostics.Processでラッパーコマンドを実行するなら、Argumentsプロパティに渡せばOKです。1

using ConsoleAppFramework;
using System.Diagnostics;

args = ["--msg", "foo", "--", "run", "--help"];

var app = ConsoleApp.Create();
app.Add("", async (string msg, ConsoleAppContext context, CancellationToken ct = default) =>
{
    Console.WriteLine($"Starting dotnet run. {msg}");
    var psi = new ProcessStartInfo()
    {
        FileName = "dotnet",
        Arguments = string.Join(" ", context.EscapedArguments),
        CreateNoWindow = true,
        RedirectStandardOutput = true,
        RedirectStandardError = true,
        UseShellExecute = false,
    };
    using var process = new Process()
    {
        StartInfo = psi,
    };
    process.OutputDataReceived += static (_, args) => Console.WriteLine(args.Data);
    process.ErrorDataReceived += static (_, args) => Console.WriteLine(args.Data);
    process.Exited += static (_, _) => Console.WriteLine("Exited");
    process.Start();
    process.BeginOutputReadLine();
    process.BeginErrorReadLine();
    await process.WaitForExitAsync(ct);
});
app.Run(args);

dotnet run --helpが実行されていることが出力からわかります。いい感じに何とかなることがわかりますね。

Starting dotnet run. foo
Description:
  .NET Run Command

Usage:
  dotnet run [<applicationArguments>...] [options]

Arguments:
  <applicationArguments>  Arguments passed to the application that is being run. []

Options:
  -c, --configuration <CONFIGURATION>     The configuration to run for. The default for most projects is 'Debug'.
  -f, --framework <FRAMEWORK>             The target framework to run for. The target framework must also be specified in the project file.
  -r, --runtime <RUNTIME_IDENTIFIER>      The target runtime to run for.
  --project <project>                     The path to the project file to run (defaults to the current directory if there is only one project).
  -lp, --launch-profile <launch-profile>  The name of the launch profile (if any) to use when launching the application.
  --no-launch-profile                     Do not attempt to use launchSettings.json to configure the application.
  --no-build                              Do not build the project before running. Implies --no-restore.
  --interactive                           Allows the command to stop and wait for user input or action (for example to complete authentication).
  --no-restore                            Do not restore the project before building.
  --sc, --self-contained                  Publish the .NET runtime with your application so the runtime doesn't need to be installed on the target machine.
                                          The default is 'false.' However, when targeting .NET 7 or lower, the default is 'true' if a runtime identifier is specified.
  --no-self-contained                     Publish your application as a framework dependent application. A compatible .NET runtime must be installed on the target machine to run your application.
  -v, --verbosity <LEVEL>                 Set the MSBuild verbosity level. Allowed values are q[uiet], m[inimal], n[ormal], d[etailed], and diag[nostic].
  -a, --arch <ARCH>                       The target architecture.
  --os <OS>                               The target operating system.
  --disable-build-servers                 Force the command to ignore any persistent build servers.
  --artifacts-path <ARTIFACTS_DIR>        The artifacts path. All output from the project, including build, publish, and pack output, will go in subfolders under the specified path.
  -?, -h, --help                          Show command line help.



Exited

5.4.0の破壊的変更

TargetFrameworkは変わってないものの、LangVersionが13まで上がっています。このため、TargetFrameworkをnet8.0にしている場合は<LangVersion>13</LangVersion>を追加しましょう。2 破壊的変更になるので注意です。

まとめ

ConsoleAppFramework v5にアップグレードできたのでうれしいですねぇ。エスケープされた引数を識別できるようにAPIが用意されているのも使い勝手がよく、v4よりも扱いやすくなりました。


  1. プロセスの実行は罠が多くこのコードも例外処理が甘いです。事情を加味するとプロセス実行にはCysharp/ProcessXを使っておくと楽です。
  2. net9.0ならLangVerisonの指定は不要です。