STAND FOR PEACEONLY!
slaveOftime
CICDdotnetfsharp脚本

介绍一个简单的 CICD 脚本帮助库

2022-11-03

工作这么多年,接触过各种为了让 CICD 自动化的脚本或一些声明式文件:

  • 比如偏重于前端的,可能就是在package.json里写一些,然后用 npm 来跑,也有一些 npm 的各种命令行处理的包。

  • 还有先前公司用过 JENKINS 文件,来编写编译、打包和发布的 pipeline,(看起来非常简洁,我的想法也是始于此)。

  • github actions 的 yaml 文件也非常不错,而且能集成 github 生态里的各种开源 action (插件),来帮你完成很多流程中的任务。

但是这些对我来说,要么就是缺乏一些开发时的检验和报错,要么就是缺乏完整的语言支持,想自定义一些东西的时候又很麻烦。当然啦,都是我个人水平的限制,导致对事物的理解的局限。总之呢,为了追求简洁和灵活性,以及本地校验的特性,我写了一个开源小项目 Fun.Build.

如何使用

我是用 fsharp 写的,运行于 dotnet 上,所以要使用它,你需要安装 dotnet sdk 6.0 及以上,然后创建一个文件比如叫 build.fsx, 内容如下:

#r "nuget: Fun.Build, 0.2.4"

open Fun.Build

pipeline "Demo1" {
    stage "check env" {
        run "dotnet --list-sdks"
    }
    runIfOnlySpecified
}

使用命令行运行:

dotnet fsi build.fsx -p Demo1

结果如下:

demo1-result

具体的文档可以参看 Fun.Build 仓库,在此我就不在赘述,我主要是介绍一些特色,分享一点优缺点。

自动生成命令行帮助信息

很多时候你的项目比较大,需要构建的 pipeline 比较多,你就会想有一个统一的入口进去后,提醒你有哪些 pipeline,并且不同的 pipeline 可以使用的配置有哪些。比如下面是我的实验性练手博客的自动化脚本:

#r "nuget: Fun.Build, 0.2.4"

open Fun.Build

let serverPath = "...."
let publishDir = "..."

let checkEnv =
    stage "CheckEnv" {
        run "dotnet tool restore"
        run "dotnet build"
    }


pipeline "dev" {
    description "Start local dev and open related tools"
    checkEnv
    stage "open-tool" {
        paralle
        run "code ."
        run (fun ctx -> async {
            do! Async.Sleep 5000
            ctx.OpenBrowser "https://localhost:6001" |> ignore
        }) 
        stage "run apps" {
            paralle
            workingDir serverPath
            stage "blazor" {
                whenCmdArg "--blazor" "" "Develop in blazor mode"
                run "powershell dotnet run -p:DefineConstants=BLAZOR;DEBUG"
                run "powershell dotnet fun-blazor watch Slaveoftime.Site.fsproj"
            }
            stage "static" {
               whenCmdArg "--static" "" "Develop in static mode, blazor will server custom elements"
               run "powershell dotnet watch run -- -p:DefineConstants=DEBUG"
            }
            run (fun _ -> async {
                do! Async.Sleep 5000
                return "dotnet tailwindcss -i ./wwwroot/css/app.css -o ./wwwroot/css/app-generated.css --watch"
            })
        }
    }
    runIfOnlySpecified
}


pipeline "deploy" {
    description "Deploy to server"
    checkEnv
    stage "bundle" {
        workingDir serverPath
        run "dotnet tailwindcss -i ./wwwroot/css/app.css -o ./wwwroot/css/app-generated.css --minify"
        run $"dotnet publish -c Release -o {publishDir}"
    }
    stage "push to server" {
        whenEnvVar "GITHUB_ACTION"
        run (fun ctx -> ())
    }
    runIfOnlySpecified
}


tryPrintPipelineCommandHelp ()

当你运行:

dotnet fsi .\cmd-info.fsx -- -h

你会得到所有注册的 pipeline,并且显示相应的描述:

pipelines

当你运行:

dotnet fsi .\cmd-info.fsx -- -p dev -h

你会得到具体的 pipeline 对应的描述,以及一些命令行参数,以实现不同的执行条件:

pipelines-dev

为什么这样设计

  • 基于 dotnet 我可以访问所有可以运行在 dotnet 6 上的 nuget 包,甚至也可以直接加载 dotnet dll:

    #load "./some.dll"
    

    当然这不是我的功劳,这是 fsharp fsi 提供的能力。我只是写了一个我个人觉得更简洁的 DSL (领域语言), 也就是一堆构建 pipeline, stage 的函数。

  • 使用 VSCode, VisualStudio 等打开,则可以有智能提醒

  • 你可以本地直接跑起来一验证,如果你用 JENKINS 文件,或者 github action yaml 文件,则需要上线去测试,或者需要有相应的环境才可。

  • 能自动生产命令行帮助信息

  • 非常灵活,你可以构建复杂的类和函数,最终被 pipeline 所调用。当然,你也可以用 powershell,我只是觉得 powershell 语法太难看。

  • 为了简洁和后期的可维护性。我以前接触过很多 pipeline,写出来的功能很强,但是改起来也很痛苦,比如先前遇见 JENKINS 集成 groovy 脚本的,全靠字符串匹配,脚本加载的顺序和具体加载了哪些脚本,都完全不可知,需要去内部的 git 仓库全局搜索。

当然也有一些缺陷

  • 依赖 dotnet sdk,所以如果你是不 dotnet 生态的话,你可能会觉得很重很麻烦。
  • 使用 fsharp,确实偏小众了,虽然基本的语法很简单,但是毕竟有一定学习成本。
  • 虽然有编译时验证,但是我用的是 fsharp computation expression 所以对于有的语法出错的提示不一定明确,让人混淆。

最后

只是一个简单的库,因为喜欢 fsharp computation expression 定义 DSL 的致简的能力所以想用来实现 pipeline,我对最后的结果非常满意,这就足够了吧。