C# 在不同环境下调用 shell 脚本

目标 & 背景

一般在开发一个项目时,往往会分出 N 多个 git 仓库,挨个手动更新会很麻烦,如果希望使用 C# 调用一些 git 相关的命令,通常会使用 Process 这类。由于某些仓库,如配表的导出,我们会写一个比较长的 shell 处理,此时就需要使用 Process 来调用 shell

这个时候问题就出现了,在 Windows/Mac 上,死活找不到 dotnet,两者的处理过程也不一致,之前被这个问题困扰了好久,今天开一篇博客来记录下

本篇博客会使用 CliWrap 库来代替 Process

CliWrap git 示例

CliWarp 是一个非常优秀的库,可以极大的减少 Process 的处理过程,接下来我们以 git 常见的几个命令封装一个 Git.cs 工具出来

public class Git
{
    public string working_dir { get; private set; }

    public Git(string working_dir = ".") { this.working_dir = working_dir; }

    public void ChangeWorkingDir(string working_dir) { this.working_dir = working_dir; }

    public async Task<CommandResult> Pull()
    {
        if(!Directory.Exists(working_dir))
        {
            return null;
        }

        var cli = Cli.Wrap("git").WithArguments("pull").WithWorkingDirectory(working_dir) |
                  (Debug.Log, Debug.LogError);
        return await cli.ExecuteAsync();
    }

    public async Task<CommandResult> Checkout(string branch)
    {
        if(!Directory.Exists(working_dir))
        {
            return null;
        }

        var cli = Cli.Wrap("git").WithArguments("checkout {branch}").WithWorkingDirectory(working_dir) |
                  (Debug.Log, Debug.LogError);

        return await cli.ExecuteAsync();
    }

    public async Task<string> CurrentBranch()
    {
        if(!Directory.Exists(working_dir))
        {
            return string.Empty;
        }

        string name = string.Empty;
        var cli = Cli.Wrap("git").WithArguments("rev-parse --abbrev-ref HEAD").WithWorkingDirectory(working_dir) |
                  (s => { name = s; }, Debug.LogError);
        await cli.ExecuteAsync();

        return name;
    }
}

整体非常简单,外部 new Git() 直接调用对应的 API 即可,更多的使用方式建议阅读相关文档,都非常简单

运行 shell 脚本

这里我们要解决如下两个问题

  • 环境变量无法正确初始化
  • Windows/Mac 的兼容问题

不管是在 Windows 下,还是 Mac 下,我们希望运行 shell 脚本时,都要提供一个载体,或者说是一个 exe 来运行 shell 脚本,在 Mac 下可运行的载体非常多,bashzsh 等,但是在 Mac 下 zsh 需要完整打开一个终端才能运行,思前想后最后还是使用了 bash

但是在 Windows 下,并没有提供一个默认的 bash.exe,但是通常安装了 git 相关的环境,在 git 的安装目录下,一定会有一个 git-bash.exe 的工具,那么 Windows 的载体也解决了,此时在 Cli.Wrap("bash path here") 的参数中,Mac 就可以填写 /bin/bash 而 Windows 就可以填 git-bash.exe

此时问题依然没有完全解决,在 Mac 环境下,使用 /bin/bash 运行 shell 脚本时,依然会报找不到 dotnet 的错误,这个时候我们需要维护 ~/.bashrc 文件,把 Mac dotnet 的路径填写正确,具体参考如下

export PATH=/usr/local/share/dotnet/:$PATH

这样 Mac 的问题就解决了,此时还需要解决 Windows 找不到 git-bash.exe 的问题,在我实际使用过程中,部分电脑在环境变量中,填写 git-bash.exe 的路径,就可以成功运行,但是又有部分电脑不行,具体为什么没搞明白,所以最后的解决方案是,在工具上提供一个 git-bash 的路径输入,不同电脑自己配置一下即可

Mac 填写 /bin/bash,Windows 填写 C:\xxx\git-bash.exe

此时运行脚本就本成了如下内容,这样我们解决了上述所有的问题

var cli = Cli.Wrap(_git_bash).
              WithArguments("export_dev.sh").
              WithWorkingDirectory("../Config") |
          (Debug.Log, Debug.LogError);

await cli.ExecuteAsync();

为什么不使用 bat

这里要考虑清楚当前项目所有参与人员的真实开发环境,有的使用 Windows,有的使用 Mac,但是自动化打包流程还需要跑在 CI 上,如果每个环境都维护一份运行脚本,后面维护的工作量会很大,这里以 Luban 配表导出流程为例

类型作用
test测试配表
dev开发配表
release正式配表

在此基础上,我们还使用到了 Luban 的 Watch 功能,如此又增加了 watch_test.shwatch_dev.sh 两个脚本,也就是一共 5 份导出脚本,如果每个环境各自维护一份,每多一个环境就多 5 份脚本,这样实在是太糟糕了

因此 shell 就从中胜出了,但在 Windows 上还需要解决一个双击运行的问题。此时只需要任意双击一个 .sh 文件,然后选择始终使用 git-bash.exe 打开即可

最后

按照文中描述的过程,最终你可以在 Unity Editor 上把几乎所有的自动化集成进来,这样策划平时的大部分日常工作都可以在 Unity 上完成,而不需要每天做很多重复的 pull 工作,而且项目仓库多了,经常会忘记更新部分仓库

你可以像这样使用一个 Flags 的 enum,将日常需要的内容做成勾选的形式,最终相关人员只需要点一下执行,即可完成常用的自动化