2022年9月21日水曜日

Homebrew と git fsmonitor の相性が悪かったので対策した

Git 2.37.0 で FSMonitor daemon が追加されたことで status が早くなったそうです
この記事で fsmonitor の事書いてしばらく使っていたのですが、brew upgrade したときにたまに以下のエラーで失敗するようになってしまいました。

Error: Another active Homebrew update process is already in progress.
Please wait for it to finish or terminate it to continue.

これは Homebrew 3.5.7 で対応されたので、現在は発生しなくなっていると思います。

当時はあんまり調べる時間なかったので、一旦 fsmonitor を無効にしてましたが、修正 PRIssue をみた感じ fsmonitor が homebrew の lock ファイルを掴んでしまうからみたいです。

Homebrew 3.5.7 は fsmonitor を stop させる対応となっていますが、そもそも監視しておくメリットってあるのかな?ということで、 brew 配下だけ fsmonitor を無効にする設定を紹介します。

ちなみに brew に追加する tap も git なのでそれらも無効にします。

includeIf を使って任意のディレクトリ以下の git config をカスタムする

グローバル設定は false で使いたいディレクトリだけ true にするとか、brew 配下のワーキングディレクトリすべてに git config core.fsmonitor false していけばいい話ではあるのですが、デフォルト true で使いたい&一括 false 設定をしたいです。

そこで、includeIf を使います。

includeIf はその名の通り、条件を満たした場合のみ config を include する機能です。
リンク先のドキュメントに Example があるのでどういうことができるのか想像できると思います。

今回は brew --prefix のパス以下の場合に fsmonitor = false にした config ファイルを読み込ませます。
(brew --prefix に合わせて適宜変更してください)


これだけです。簡単ですね。
includeIf 使うと特定のディレクトリ以下で user 切り替えるとかもできるので覚えておくと良いテクニックだったと思います。

おまけ

https://github.com/jgavris/rs-git-fsmonitor

これはたまたま見つけたデフォルトの fsmonitor の hook スクリプトを Rust rewrite したやつ。
fsmonitor って true/false ではなくて hook スクリプトを指定することもできるんですね。

git status がもっと速くなるみたいですよ。
(筆者環境では差は感じられなかった)



2022年9月13日火曜日

Dagger 使ってみた

Dagger とは?
Dagger は CI/CD パイプライン用のポータブルな開発キットです。
Dagger は他の一般的な CI/CD が YAML でパイプラインを記述するのと異なり、CUE で記述します。CUE については後述しますが、YAML よりプログラミング言語に近くて柔軟性が高いって感じですかね。

CUE によって YAML ではやりづらかったパイプライン再利用が容易になっています。
また、Dagger はローカルでも CI でも、どこでもパイプラインが実行できるのが特徴です。

Dagger はすべてコンテナ上で実行されます。コンテナが動く環境上であればどこでも「同じ」パイプラインを実行できるのが Dagger であり、CI/CD ツールではなく、パイプラインを共通化するレイヤーとして機能するツールです。

CUE
CUE は Configure, Unify, Execute の頭文字のようです。
CUE は JSON のスーパーセットで、CUE から JSON/YAML にエクスポートも可能です。

CUE については Dagger のページにも説明が詳しくあるのでそちらを参照してください。
https://docs.dagger.io/1215/what-is-cue/

CUE は YAML のマージ機能も備えているので↓みたいな使い方もできるみたいです。これはこれで便利そうですね。
CircleCI の設定ファイルを分割して CUE で合成してみたら割と簡単で便利そう - Mitsuyuki.Shiiba

今回は Dagger の話なので CUE そのものもそのうち触ってみたいです。

使ってみる
ものは試しで、使ってみました。
Dagger のサンプルとして todoapp があるのでそれでまずは動作を試してみるのがオススメです。このへんはありきたりな内容になってしまうので、省略します。
iutest に適用
iutest は筆者が書いてる C++ テスティングフレームワークです。
(まぁ最近はテスティングフレームワークの機能開発はほとんどしてないんですが・・いろんな CI サービスを使っている・使っていたのでそういう意味では参考になることもあるかもしれないです。)

今回 alpine イメージ上でのテストを今までしてなかったので、それを Dagger でやるようにしました。

ベースとなる cue ファイルはサンプルの todoapp を使いました。
1から書くよりもサンプルなどをベースに書いていくと楽だと思います。

なんですが、todoapp が参考にしたときと大分違う設定になっていました。
ちょうどそのころの Dagger 使った系の記事があったのでそちらを参考にしてください。
ベンダーロックインしないCI/CDパイプライン、Daggerを使ってCodeCheckを自動化してみた。

どういうふうに書いたかは百聞は一見に如かずということで、レポジトリを見てください。
この記事向けにコメントも追加してます。



とはいえ、見てくださいだけではこの記事の意味がないのでこの設定に至るまでの紆余曲折を記述しておきます。
変数展開は () でする
同じ設定はまとめたいので変数にしてたのですが、使うときに単純に変数を書くだけじゃだめでハマりました。
       // 必要な環境変数をセット
        env: {
            // string は () で囲んで展開する
            IUTEST_ROOT_DIR: (iutest_root_dir)
            // 文字列の中で変数展開する場合は ( をエスケープする
            IUTEST_OUTPUT_DIR: "\(test_result_dir)"
        }
exit: 0 は今の所意味がない
CTest が失敗しても後続の処理を継続したかったので終了コードを 0 に上書きしたいなーと思ったのですが、今はできないっぽい。
なので、しかたがないので script: contents の処理で失敗を無視してます。
always は condition の always ではない
↑と同じ理由で常に実行される処理を書きたかったので、always を使ってみたのですが、どうやらこれは実行条件の always とはちょっと違って、cache 利用するかしないかの always だったようです。
always を true にするとキャッシュを無視して常に実行されるようになりますが、依存処理が失敗していても実行するようなことはできませんでした。

https://docs.dagger.io/1231/always-execute/

常に実行したいってのは cache invalidate のことではないんだよなぁ・・

GitHub Actions で動かすには
dagger の cue ファイルを置いておいても、そのままでは CI サービスで実行されないので、各サービスでワークフローを書く必要があります。
GitHub Actions の場合は、dagger 用の action があるのでそれを使えばすぐに動かせます。
  dagger:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - uses: dagger/dagger-for-github@v3
        with:
          cmds: |
            project init
            project update
            do ctest
    
CUE/Dagger を新たに覚えるのはつらい
ここから使ってみた感想なのですが、CUE が使いやすいかというとまだわからないのですが、少なくとも覚えることは多い印象。
YAML でのパイプライン記述における問題の1つである再利用性を CUE は解決していますが、最初に紹介した CUE を YAML のマージツールとして利用することで十分なような気もしますし、各 CI ツールで再利用性できる機能があったりしますし、Dagger である必要性があまり感じられませんでした。

より理解をするには CUE と Dagger の境界を認識して、それぞれで調べる力、検索力が必要になるかなと思いました。
ポータビリティについて
環境のポータビリティ
Dagger は「環境」のポータビリティを実現しています。
CI でもローカルでも同じ「環境」で処理を実行できます。

ローカルと CI で同じ「処理」をすることは Makefile でもなんでもいいですが、可能です。
なので CI と同じ「処理」をすることは Dagger を使わなくても可能です。
ただ、それだと実行環境は CI やローカルで異なるため、実行結果は変わってきます。
Dagger は実行環境もセットになっているので、同じ「環境」で同じ「処理」ができ、CI とローカルで同じ実行結果が期待できます。

これは大きなメリットです。仕事や個人開発でも CI でしか起きない問題の調査はめんどうです。ローカルで同じことができればこれも楽になります。
また、問題調査としてではなく開発者がローカルで動作確認をしたい場合に、ローカルで手軽に実行できるのも利点ですね。
私はゲーム開発に携わっているので、ターゲットプラットフォームでのビルドをローカルでしたいという需要はすごくわかります。(Dagger でゲーム開発のターゲットプラットフォームビルドができるかというと難しい気がしますが・・)

ただ、Dagger の実行環境はコンテナに制限されるため、コンテナでできないことはできません。
また、iutest はテスティングフレームワークなので、1つの同じ環境でテストするよりも、多様な環境でテストできたほうが嬉しいです。(※Dagger で複数環境用意するのもありですが、管理下の想定された環境よりも、管理外の環境のほうが意味があると思っている)
今回は alpine コンテナでのテストが今までなかったので Dagger を採用して GitHub Actions で動かしています(Dagger も環境の1つなので何かしらのテストを回しておきたいという意思)。
ワークフローのポータビリティ
ワークフロー?パイプライン?
言葉の定義があいまいなんですが、ここでは PR トリガーから結果の通知など CI の最初から最後までという意味合いでワークフローを使ってます。

「A PORTABLE DEVKIT FOR CI/CD PIPELINES」
Dagger のトップページにはこう書かれています。
パイプラインという言葉を使ってるので Dagger がカバーしている範囲はパイプラインと呼びます。

図にするとこんな感じかな。

Jenkins でジョブに直接処理を書かずに .sh ファイルとかにしようね。とか。ビルドツール使おうねとか。そららはパイプラインの話。
それのもう1つ上のレイヤーで環境とセットでまとめてくれるのが Dagger 。
さらに上のレイヤーであるワークフローはそれぞれ対応する必要がある。

Dagger はベンダー依存しない CI ツールという立ち位置ですが GitHub Actions での実行例を見ていただければ分かる通り、特定の CI サービス上で Dagger を実行するには Dagger だけでは完結しません。というか、GitHub Actions でいうとトリガー部分(on: pull_request)は Dagger では設定できませんし、動かす CI サービス固有の機能を使いたい場合は Dagger 外でやる必要があります。(Dagger はそういう CI の差を埋めてくれるものではないので)
そして CI サービスを引っ越しする場合には、トリガー周りなどそういった設定は書き直しになります。

CI/CD ワークフローのポータビリティという観点では、Dagger は直接的に貢献するわけではないです。(パイプライン部分だけでもポータブルになるので楽にはなる)
ただ、どのサービスを使うのか決まっているのであれば、そのサービスの書き方をしたほうが良いとも思えます。
逆に社内で使ってる CI サービスが多種多様であるとか、OSS だから手軽に乗り換えできると嬉しいとかであれば、Dagger を使う利点はあると思います。
ローカル実行について
実はローカル実行できる CI サービスもあります。
なので目的や事情によってはそのサービスのローカル実行機能を利用したほうがいい場合もあるかもしれません。
例えば CircleCI では cli からローカル実行できます。(制約はあります
GitHub Actions の場合、nektos/act というツールを使えばエミュレートできます。(act は大分できないことや、Hosted runner との差異がありますが・・)

これらで十分なのであれば Dagger に置き換える必要はないと思います。
例えば、act は GitHub Actions のイベントをエミュレートしてくれるのでワークフローのローカル実行が可能ですが、Dagger は step レベルのローカル実行しかできないです。
このへんは使い分けでしょうね。

まとめ
個人的には最近使ってみた Earthly の方が Dagger より使いやすいなと思いました。
Dagger がちょっと話題になりましたが、類似のツールと比較したり、今置かれている状況を踏まえて選択するのが良いと思いました。
以上、久しぶりに長めに書いた。ではでは。


2022年9月7日水曜日

CEDEC でやってた補助動詞の漢字・ら抜き言葉の検出を実装してみた

CEDEC 2022 の「AIによる自然言語処理を活用したゲームシナリオの誤字検出への取り組み」で誤字検出方法が解説されてました。
セッションタイトルにある AI の部分は BERT 使った誤字検出の話で、サクッと実装は難しそうなので、比較的簡単そうな「補助動詞の漢字」「ら抜き言葉」の検出を実装しました。

実はこのセッションは昨年の CEDEC 2021 の続編で、昨年の内容も一部検証実装してました。
そのときの記事はこちらです。
CEDEC でやってた表記ゆれ検出をお試し実装してみた

今回の実装は昨年書いた tails-of-words に機能追加として実装してます。
なんですが、昨年のアップデートかつ実装難度としても高くないのであまりここに書くことがありません。。
(どういうロジックなのかは割とまんまな記事が出るのでそちらか、おそらく CEDiL に資料公開されると思うのでそちらを見て下さい。)

ら抜き言葉検出の改良

強いて書くとすると「ら抜き言葉」の検出がセッションのとは少し異なるくらいですね。
セッションでは「ら抜き言葉」の誤検出の例として以下のテキストがでてました。

  • 写真撮ってきたから見れ!
  • あれ?あれれれ?
  • 今こそ来たれ!
まず資料の通りに実装した場合の結果がこちら
なぜか1つ目と2つ目は「ら抜き言葉」とは判定されない解析結果でした(つまり誤検出しない)。
ツイートでは前処理の違いかもと言ってますが、 jumanpp に渡す前にテキストの正規化をしているのでもしかしてその違いがあるのかも?と思ったのですが、前処理で変化するような入力テキストではないので違いました。
使っている jumanpp or 辞書の違いでしょうか。(確認できないので、深追いはしない)

では、「今こそ来たれ!」ですが「れ」の直後の品詞が「特殊」となってます。
セッションで誤検出の例として上がったものすべて「特殊」品詞が直後にあるので、このパターンを「ら抜き言葉」から除外すれば良さそうです。

というわけで改良後はこうなります。

最後に
BERT とかそのへんもいつか触ってみたいです。
では。