实现一个更新所有 dotnet tool 的 dotnet tool

Intro

dotnet tool 是从 .NET Core 2.1 开始支持的命令行工具,在使用 dotnet tool 比较多了的时候,想要更新所有的 dotnet tool 就比较麻烦,而目前 .NET SDK 还不支持,也有一些人希望能够支持一次更新所有的 dotnet tool 这样我们就不需要一个一个地去更新了

Implement

实现思路比较简单,获取所有的 dotnet tool 并逐个进行更新.

我们可以通过 dotnet tool list -g command 来获取我们安装的所有的 dotnet global tool,输出结果如下:

实现一个更新所有 dotnet tool 的 dotnet tool

dotnet tool list sample

第一行是一个标题,第二行是分隔线,从第三行开始是 dotnet tool 数据

第一列是 NuGet package id, 第二列是 package version, 第三列是 dotnet tool 对应的命令,我们可以获取 dotnet tool list -g  输出并从第三行开始读取数据,只需要读取第一列数据的 package id 即可

获取所有 dotnet tool list 的代码如下:

// get dotnet path
var dotnetPath = ApplicationHelper.GetDotnetPath();
// execute `dotnet tool list -g` to get all dotnet tool
var dotnetToolListOutput = await CommandExecutor.ExecuteAndCaptureAsync(dotnetPath, "tool list -g");
// output
Console.WriteLine("`dotnet tool list -g` output:");
Console.WriteLine(dotnetToolListOutput.StandardOut);

拿到输出结果之后,我们就可以得到 dotnet tool list 并开始逐个更新了,实现如下:

var dotnetToolList = dotnetToolListOutput.StandardOut.Split(Environment.NewLine, StringSplitOptions.RemoveEmptyEntries);
if (dotnetToolList.Length > 2)
{
    foreach (var tool in dotnetToolList[2..])
    {
        var toolId = tool.Split(' ', StringSplitOptions.RemoveEmptyEntries)[0].Trim();
        if (fullToolName.Equals(toolId))
        {
            continue;
        }
        Console.WriteLine($"update tool {toolId}...");
        try
        {
            await CommandExecutor.ExecuteAsync(dotnetPath, $"tool update -g {toolId}");
            Console.WriteLine($"update tool {toolId} completed");
        }
        catch (Exception ex)
        {
            Console.WriteLine($"update tool {toolId} failed: {ex}");
        }
    }
}

至此我们的核心逻辑就基本完成了,实现效果如下:

实现一个更新所有 dotnet tool 的 dotnet tool

dotnet-update-all-tools

如果要发布还要准备一下 nuget package 的一些信息

<PropertyGroup>
    <IsPackable>true</IsPackable>
    <PackAsTool>true</PackAsTool>
    <ToolCommandName>dotnet-update-all-tools</ToolCommandName>
    <PackageId>dotnet-update-all-tools</PackageId>
    <Version>1.0.0</Version>
</PropertyGroup>

PackAsTool 意味着我们要打包一个 dotnet tool

ToolCommandName是我们 tool 安装后对应的执行命令

More

上面的 ApplicationHelper.GetDotnetPath() 实现是通过扫描 PATH 找到可执行的dotne path,有些 platform 上需要 full path 才能工作,实现可以参考:https://github.com/WeihanLi/WeihanLi.Common/blob/86b6acc10f50df5867c590ca10039c1e7a8bb740/src/WeihanLi.Common/Helpers/ApplicationHelper.cs#L61

CommandExecutor 实现是起了一个进程并捕获进程输出,可以参考:https://github.com/WeihanLi/WeihanLi.Common/blob/86b6acc10f50df5867c590ca10039c1e7a8bb740/src/WeihanLi.Common/Helpers/CommandExecutor.cs#L45

最终的 dotnet tool  在 System.CommandLine的基础上包装了一下,以为了更好的体验和扩展的方便

var command = new Command(toolName);
command.SetHandler(async () =>
{
    // 前面的逻辑在这里
});
await command.InvokeAsync(args);

自此我们的 dotnet tool 就完成了

可以通过下面的命令来进行安装

dotnet tool install --global dotnet-update-all-tools

安装好之后执行 dotnet-update-all-tools 即可更新所有的 dotnet tool 了。