Closed suzuki-shunsuke closed 3 years ago
buildflow には Build, Phase, Task という概念がある。 CircleCI の Pipeline, Workflow, Job みたいなものと思ってもらえるとよい。
$ buildflow run
で 1 つの build が実行される。 build は複数の phase からなり、 phase が 1 つずつ順に実行される。 phase は複数の task からなり、 task が全て終了すると、その phase も終了となる。 task は並列に実行したり、依存関係を定義したりできる。 task では外部コマンドを実行したりできる。
task には幾つか type がある。
今後他にも type を追加するかもしれない。
buildflow run
を実行したカレントディレクトリからルートディレクトリへと .buildflow.y[a]ml
を最初にファイルが見つかるまで再帰的に探索します。
--config (-c)
オプションでパスを指定することも出来ます。
設定ファイルの雛形を生成します。
$ buildflow init
これで buildflow run
を実行すると Hello world が出来てしまいます。
$ buildflow run
==============
= Phase: main =
==============
10:47:54UTC | hello | + /bin/sh -c echo hello
10:47:54UTC | hello |
10:47:54UTC | hello | hello
10:47:54UTC | hello |
================
= Phase Result: main =
================
status: succeeded
task: hello
status: succeeded
exit code: 0
start time: 2020-10-14T10:47:54Z
end time: 2020-10-14T10:47:54Z
duration: 4.818877ms
+ /bin/sh -c echo hello
hello
設定ファイルをみてみましょう。以下ではコメントなどを無視して必要な箇所だけを抜粋することがあります。
---
phases:
- name: main
tasks:
- name: hello
command:
command: echo hello
ちょっと出力はわかりにくいかもしれません。 改善したいと思いつつ、どうあるべきなのかまだ見えてないのでこんな感じになっています。
task の標準出力、標準エラー出力はリアルタイムで出力されます。
また、複数のタスクを並列実行できます。
複数のタスクのログをリアルタイムで出力すると当然混じるので、区別がつくように各行の prefix に timestamp | task name |
をつけて出力します。
それでも混じるとわかりにくいので、全 task が完了後に、全 task のログを混ざらないようにそれぞれ標準エラー出力します。
==============
= Phase: フェーズ名 =
==============
10:47:54UTC | タスクA | + /bin/sh -c echo hello # 実行されるコマンド
10:47:54UTC | タスクB | + /bin/sh -c echo foo
10:47:54UTC | タスクA | hello # コマンドの標準(エラー)出力
10:47:54UTC | タスクA |
... # リアルタイムに出力されるので複数の task のログが混ざる場合がある
================
= Phase Result: フェーズ名 = # 該当 phase の全タスク完了後に全タスクの結果と標準(エラー)出力を出力する
================
status: succeeded
task: hello
status: succeeded
exit code: 0
start time: 2020-10-14T10:47:54Z
end time: 2020-10-14T10:47:54Z
duration: 4.818877ms
+ /bin/sh -c echo hello
hello
...
依存関係のない task は並列に実行されます。実行順序は不定です。
設定ファイルの parallelism
を指定することで最大の並列度を制限できます。デフォルトは無制限です。
例えば parallelism: 1
とすれば並列で実行されることはなくなります。
task の dependency を指定することで依存関係を定義できます。将来的に depends_on
とかにリネームしたりするかもしれません。
---
phases:
- name: main
tasks:
- name: foo
command:
command: |
sleep 3
echo foo
- name: bar
command:
command: |
echo bar
dependency: # task dependency
- foo # this task is run after the task foo is finished
dependency の指定の仕方は 2 つあります。
task name は unique である必要はないです。 この仕様は今でも迷っていると言うか、 unique 制約を入れたほうがいいかなぁとか思ったりしています。
unique じゃない理由は、 task の items を使って動的に task を生成する場合に unique 制約を引っかからないように注意しないといけないの面倒だなとか、同じ task を複数回実行したい場合もありえなくはないし、そのときに名前をなんか変えないといけないの嫌だなぁとかそんな理由です。
一方で、
と思ってたりします。
buildflow の思想として
というのがあります。これを実現するため、 buildflow は Tengo というスクリプト言語を採用しています。
Tengo は Go で実装されたスクリプト言語ですが、 buildflow で Tengo を使うのに Tengo の処理系を別にインストールする必要はありません。 Tengo は Go のライブラリとして提供されており、 buildflow でそれを使っている(buildflow が処理できる)からです。
Tengo を採用した理由は awesome-go で script 言語を探して一番良さそうだったからです。
https://github.com/avelino/awesome-go#embeddable-scripting-languages
Lua とかもあるのでそれでも良かったかもですが、自分は Lua を全然知りません。 あとちゃんとバージョンニングされていたのも理由の一つです。 Tengo より人気のある言語もありましたが、バージョニングされてないという理由で見送ったりしました。
実は Tengo の前に他の言語 antonmedv/expr を採用していたのですが、途中で表現力が足りてないので移行しました。 github-comment でも antonmedv/expr は使ってますし、便利ではあるのですが、 変数が宣言できず、基本ワンライナーで書くしかないので無理だなと判断しました。
https://github.com/suzuki-shunsuke/buildflow/issues/20
buildflow における Tengo の用途はあくまでロジックの記述、シェルスクリプトでは扱いにくい map 等の操作です。 Tengo で外部コマンドを呼び出したりとかファイルを読み書きしたりとかそういうことは考えていません(てっきりそういうことが出来ない言語なのかと当初思っていましたが、できるようですね)。 また、複雑なロジックといってもそれはあくまで「シェルスクリプトで実装するには」という話であり、 例えば class を定義したり継承したりといったようなことは考えていません。 また、 Tengo ではテキスト処理などに使える標準ライブラリが提供されています(これがないと辛かったけど、あるので十分)。 なので今の所 Tengo で十分だと考えています。 Tengo よりリッチな言語があったとしても、今の所あまり移行するモチベーションはありません。
Tengo に関する不満を挙げると
ですね。
テストに関しては簡単なツールを別に作りました https://github.com/suzuki-shunsuke/tengo-tester 従来シェルスクリプトでこういうロジックを実装しても「動けばいい」程度に考えていてテストは書かないことが多かったですが、 ロジックだけを Tengo のスクリプトとして切り出し、テストツールも用意することでちゃんとテストを書くようになることを期待しています。
PR の CI では
のように PR の情報に基づいて挙動を変えたくなったりします。
シェルスクリプトで GitHub API 叩いて情報とってきて jq でパースしてとか、頑張れば別にできるんですが、 毎回そういうコードを書きたくないなと感じていました。
その思いこそが buildflow を開発した原点といっても過言ではありません。 それだけだと物足りない(?)ので色々機能を足していますが、この機能のためだけにでも buildflow を使いたいと思います。
なお、 PR の情報をとってくる機能はデフォルトで無効化されています(GitHub Access Token 必要ですしね)。
設定で pr: true
を指定してください。
そして環境変数 GITHUB_TOKEN
, GITHUB_ACCESS_TOKEN
を指定してください。
PR の情報をとってくるには、当然リポジトリと PR 番号の情報が必要ですが、
buildflow は各種 CI サービスの組み込みの環境変数からそれらの情報を自動で取得してくれます。
内部的には go-ci-env を使っているので、 PR 情報の自動取得をサポートしている CI サービスは以下のとおりです。
https://github.com/suzuki-shunsuke/go-ci-env#supported-ci-services
https://github.com/suzuki-shunsuke/buildflow#configuration-variables
result
という変数を宣言してください。それの値が戻り値として扱われます。
task は他のタスクの実行結果などを元に挙動を変えたり出来ます。 task の実行結果は Template や Tengo スクリプトにパラメータとして渡されます。
パラメータについて説明すると
Task
: 現在の taskTasks
: 同じ phase の task の一覧Phases
: phase の一覧。今の phase より前の phase 及び phase のタスクの実行結果を参照できるtask の実行結果は task のタイプによっても異なります。
https://github.com/suzuki-shunsuke/buildflow#configuration-variables
buildflow を知ってもらうためにも、 buildflow のサンプルを用意して公開したいと思っています。 一方で、サンプルのメンテって難しいし、どうしたらいいのかなと思っています。 いつの間にか動かなくなっていたサンプルなんて、ないほうがマシですし、 やるとしたら同じリポジトリで example みたいなディレクトリ作ってそこにサンプル置いて CI でテストするのが良いのだと思いますが、テストするとなると、とりあえず実行できれば OK とするのか(よくない)、 bats とかを使ってちゃんとテストを書くのかということになるかなと思っていますが、 bats とか使ってテスト書くのめんどいなぁと思ったりしています。
とりあえず https://github.com/suzuki-shunsuke/buildflow/tree/master/examples に置くことにしました。
https://github.com/meirwah/awesome-workflow-engines
比較はあまりしていないです。 というか workflow engine といっても buildflow は CI サービス上で実行することを主眼とした CLI ツールなので server / client モデルではないし、 Web UI も別に提供しないし、 用途が違うのでそういうものと比較しても仕方ないとは思います。
buildflow と同じようなものも当然あるんでしょうけど、あんまりちゃんと探してないです。 GitHub の PR の情報を自動で取得してとか、自分が欲しいそういった機能は多分既存のツールにはないだろうなと思って 作ってしまいました。
幾つか blog を書いている https://techblog.szksh.cloud/tags/buildflow/
buildflow を導入するために、 buildflow が自分以外の人でも分かる、扱えるものにしないといけない README はそれなりに書いているが、まだまだ足りない。 一本の大きなブログを書こうと思うとまとまらないので、個々のトピックに関して issue の comment として書いていきたい。 ある程度材料が揃ったらブログにするとか、 README に反映させるのが望ましい。
なぜ buildflow が必要なのか: https://github.com/suzuki-shunsuke/issue/issues/60