工作这么多年,接触过各种为了让 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
结果如下:
具体的文档可以参看 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,并且显示相应的描述:
当你运行:
dotnet fsi .\cmd-info.fsx -- -p dev -h
你会得到具体的 pipeline 对应的描述,以及一些命令行参数,以实现不同的执行条件:
为什么这样设计
基于 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,我对最后的结果非常满意,这就足够了吧。