2024年を振り返って

サイボウズに入社して三年が経ちました。 夏ごろまではインフラ移行やリリースの自動化・高速化などに携わり、秋からは新しいサブチームを立ち上げてEMとしてチームをリードする立場になりました。 プロダクトの根幹となる領域を担当し、他のサブチームの認知負荷を下げるという立場で、苦労しながらもなんとかチームを軌道に乗せることができたと思っています。

今年の初めにはS3にファイルをキャッシュするGitHub Actionを作って公開しました。 こちらは業務で関わっているリポジトリで必要になったために自作したのですが、今では様々なビルド成果物や依存パッケージのキャッシュに使っています。 弊社はこういう感じで手を動かすタイプの人があまりいないので、こういう動きをしてもいいのかなと迷うことも多いです (こういうオープンネスもあまり評価されない)。 でも結果オーライということでこのアクションに関しては作って良かったなと思っています。 itchyny.hatenablog.com

他のOSS活動としては、去年に引き続きgojqとjqのメンテをやっていました。 jqへの貢献としては、日々のissueやPRの対応に加えて、新しいフィルターの実装やバグ修正などを行いました。

メンテナがみんな忙しくて次のリリースはいつになるやという感じですが、引き続きやっていくので応援よろしくお願いします。

今年の初めには台湾に旅行に行きました。 久しぶりの海外旅行で初めは緊張していましたが、なんとかなりました。 有名な夜市は外国人観光客が多く英語が通じましたし、九份にはツアーで行ったので困りませんでした。 ただ、台北でも夜市から少しハズレにある個人店でご飯を食べようとすると、コミュニケーションに困る場面がありました。 台湾茶にはかなりハマり、今でも普段の食事で台湾茶を飲む日があるくらいです。 今年は他には広島や岡山、名古屋に旅行に行きました。 来年は夏に北海道とか行きたいですね。

最近息子が産まれて、今は妻と共に育児に専念しています。 息子がじっと自分を見つめてくるのを見ていると、何があってもこの子を大事に育て上げなければという気持ちが湧いてきます。 カラフルな色が描かれている絵本を見せたり、ジブリの曲を歌ってあげたりしているのですが、あまり反応もなく良い刺激になっているのかはよく分かりません。 もう少し大きくなれば笑ってくれるようになるのでしょうか。これからの成長がとても楽しみです。

今年は株式投資に目覚めた年でした。 以前から個別株の売買は多少していたものの全く何も分からないまま適当にトレードしており、八月の暴落が起きた時にかなりの損失を出してしまいました。 それをきっかけに真面目に勉強を始め、今はテクニカル分析を中心とした短期トレードにハマっています。 株価は需給によって上下すること、あらゆる事実や予測が織り込まれること、様々な時間軸と資金力で取引する人がいることなどが分かってきました。 来年は良い成績が納められるよう(相場に焼かれないよう)、ぼちぼち頑張っていきたいと思います。

今年もよくドラマを見ていました。 特に『アンメット-ある脳外科医の日記』『海のはじまり』『ライオンの隠れ家』『海に眠るダイヤモンド』は印象に残っています。

来年は子の成長を見守りながら、趣味の幅を広げたり新しいことに挑戦していきたいです。

百瀬弥生「分かる、好きな人の子どもって、こんなにかわいいんだ!って。やっぱ似てるとこ、ちょいちょいあるしね。」

海のはじまり 第6話

dependabotのPRに自動でビルド成果物をコミットする

ビルド成果物をリポジトリに含める必要がある場合、どのタイミングでコミットするかが問題になります。 例えばGitHub Actionsのように、JavaScriptの成果物をリポジトリに含める必要があるようなケースです。 リリースを打つ時にビルド成果物をコミットするという方法もありますが、この記事ではメインブランチへのマージ時にはビルドしないといけないという前提があることにします。

人間がPRを出す場合は手元からビルドして成果物も一緒にコミットできますが、dependabotのようなbotにはビルドをさせることができません。 しかし、GitHub Actionsを使えば、dependabotのPRに対してもビルド成果物をコミットすることができます。

name: CI

on:
  push:
    branches:
      - main
  pull_request:

jobs:
  test:
    name: Test
    runs-on: ubuntu-latest
    permissions:
      contents: read
    steps:
      - name: Checkout code
        uses: actions/checkout@v4
        with:
          ref: ${{ github.event.pull_request.head.sha || github.sha }}
          token: ${{ secrets.DEPENDABOT_TOKEN || secrets.GITHUB_TOKEN }}
          fetch-depth: ${{ github.actor == 'dependabot[bot]' && 2 || 1 }}
      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: npm
      - name: Install Dependencies
        run: npm ci
      - name: Run tests
        run: npm run test
      - name: Build
        run: npm run build
      - name: Check for dist changes
        id: dist-changes
        run: git diff --exit-code
      - name: Push dist changes for dependabot
        if: github.actor == 'dependabot[bot]' && failure() &&
          steps.dist-changes.outcome == 'failure'
        run: |
          git config user.name "$(git show -s --format=%an)"
          git config user.email "$(git show -s --format=%ae)"
          git commit --all --amend --no-edit
          git push --force origin "HEAD:${GITHUB_HEAD_REF}"
  • GITHUB_TOKENの権限を強めたくないので、permissionsは読み取り権限のみにしています。Dependabot secretを使うことで、dependabotのPRでしか利用できないシークレットを設定できます。このシークレットには該当リポジトリへのwrite権限を付与したアクセストークンを設定しておきます。
  • pull_requestイベントでactions/checkoutを使うと、マージコミットをチェックアウトしてしまいます。今回はPRにコミットを積みたいので、refgithub.event.pull_request.head.shapushイベント時のフォールバックを指定します。
  • あとはテストとビルドを行います。そしてビルド成果物が変更されているかを確認し、もし変更があればdependabotのPRにコミットします。今回はパッケージの更新コミットにamendしたかったので、push --forceで更新しています。また、fetch-depthも2にしています。もしamendしない場合はpush --forceも不要でfetch-depthを指定しないでも問題ありません。

ポイントはチェックアウトするときのrefと、Dependabot secretを使うことでしょうか。 GitHub Actionsがそうなのですが、ビルド成果物をコミットしなければならないというシチュエーションはそもそも珍しいかもしれません。 しかし、Dependabot secretの存在を知っておくと、dependabotのPRにのみ利用できるシークレットを設定できるのでとても便利ですね。

それではまた!

バイナリエディタ bed のコマンドラインの機能を強化しました

先日、自分の作っているバイナリエディタbedhomebrew/coreに入ったことをご報告しました。

その後、コードを眺めていると色々と直したいところが出てきたり、欲しい機能の実装イメージが沸いたりして、また活発に開発するようになりました。 今でもそれなりに使われていることを意識すると、急にメンテする気力が湧いてくるんですよね。不思議です。 個人OSSの継続にはモチベーションが大事です。フィードバックは大歓迎です。

最近、コマンドライン周りの機能強化をやっています。

  • コマンドの実行履歴を上下キー (と<C-n>, <C-p>) で辿れるようにしました。実装としては、実行したコマンドをスライスに追加していく (のと重複を削除する) だけです。少し面倒なのが、コマンド実行 (:) と検索 (/, ?) で別の履歴として保存しないといけないことです。
  • コマンドライン環境変数の補完に対応しました。例えば、$GOPATHのように環境変数ディレクトリを指しているとき、 :e $GOP<TAB>:e $GOPATH/ まで補完して、さらにその中のファイルを候補として表示してくれます。
  • 新しいコマンドとして :cd:pwd を実装しました。:cdは作業中のディレクトリを変更するコマンドで、:pwdはそれを表示するコマンドです。:cd - で前にいた場所に戻るといった細かい (けど使う人には便利な) 機能も実装しています。

全く別の文脈 (とあるVimプラグイン) で作業ディレクトリのことを考えていて、bedでもVimみたいにディレクトリを移動できればファイルを開くのが楽になるなと思い:cdの実装を考え始めたのがきっかけでした。 初めはos.Chdirを呼ぶだけだと思っていたのですが、これまでコマンド実行中にディレクトリが移動することがなかったので、既存の機能に影響して苦労しました。 例えば :w test で保存したウィンドウは、その後 :w で同じファイルを上書きします。 しかし、このtestが作業中のディレクトリからの相対パスになっていたので、:cdを作ったことで挙動が変わってしまいました。 各ウィンドウで絶対パスを管理することで、この問題は解決しました。

コードを読み返していると、当時勢いで書いた部分が多く、色々な箇所をリファクタリングしたくなってきました。 今回は、コマンドラインのパーサーを大きく書き直しました。 昔は[]runeを引き回して次のオフセットを返すようなパーサーをよく書いていたのですが、Goのプラクティス的には文字列を渡してパースしたものと残りの文字列を返す方が良いということがわかってきました。 strings.CutPrefixはとても便利ですね。 他にもやたら状態遷移するコードを好んで書いていたのですが、よほど複雑なものでない限りはコードが読みにくくなるだけなのでやめました。 ここ数年でもだいぶコーディングの好みが変わってきているのを感じます。

テストもだいぶ書き直して、読みやすく安定したものになりました。 bedのテストを見返すと、UIが送ったイベントをウィンドウが処理するのを待つためにsleepするみたいなテストが存在していました。 例えば、UIが:w:qのイベントを送った時に、ファイル保存が完了するよりも前に:qを処理してしまうと、保存がうまくいきません。 でもテストの中でsleepするのは良くないですよね。何を待っているのかわからなくなりますし、その時間で処理が完了する保証もなく、不安定になってしまいます。 UIが送ったイベントに対しては再描画のリクエストがUIに返ってくるので、そのイベントの受信を待つよう修正することでsleepを撲滅することができました。 テストで何かを待ちたくなったら、sleepするより良い待ち方はないのか、本当にsleepする時間内に完了する保証があるのか考える必要があります。

Vimの操作に慣れていて、バイナリファイルを軽く確認したい、軽く編集もしたいという人に、bedはとてもオススメできるツールです。 ぜひ使ってみてください。

バイナリエディタ bed が homebrew/core に入りました

数年前に、趣味でbedというバイナリエディタを作ったことがありました。 当時はメモリーに乗らないような大きなファイルを描画したり、編集できるようにするロジックを考えるのが楽しく、とても熱中していました。 itchyny.hatenablog.com 基本的な機能をある程度作ってからは急速にやる気がなくなってしまい、細々とリファクタリングやパフォーマンス改善などをしてメンテナンスをしていました。

bedは元々itchyny/homebrew-tapでHomebrew formulaを配信していたのですが、最近homebrew/coreに入りました。 github.com これで、次のコマンドでbedをインストールできるようになりました。

brew install bed

私の作ったものでhomebrew/coreに入ったプロダクトが二つになりました。 一つ目は、もちろんgojqです。 誰かが自分のプロダクトを良いものだと思ってくれて、より多くの人に使われるように動いてくれるのは嬉しいことですね。

VimのYAMLのシンタックスハイライトを改善してGitHub Actionsのワークフローファイルでハイライトが壊れにくくしました

GitHub ActionsのワークフローにはYAMLファイルを使いますが、Vimシンタックスハイライトがうまく効かなくて困ることがよくありました。 Actionsでは複数行にわたる文字列に複雑なシェルスクリプトを書くことが多いのですが、 その中の一部がYAMLのフロースカラースタイルの文字列として認識されてしまい、ハイライトが壊れることがあるのです。

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - run: |
          echo "Hello world!" | cut -f1 -d" "
          echo "This line is broken!"
      - env:
          test: ${{ inputs.* | join(' ') }}
        run: |
          echo 'This line is broken!'

はてなブログも全く同じ問題があることから、中ではVimシンタックスハイライトが使われているようです。 私の修正がブログのシステムに取り込まれると、このエントリーも更新できなくなりますが…

参考: YAMLの用語

参考: YAMLの用語
ブロックスカラースタイル: |
  これはブロックスカラースタイルの
  文字列です (改行を維持しない > もあるよ)
フロースカラースタイル:
  "これはダブルクォートされた
  フロースカラースタイルの
  文字列です (シングルクォートもあるよ)"
プレインスタイル: これは、
  クォートのないフロースカラースタイル、
  つまりプレインスタイルの文字列です

ブロックスカラースタイルのコードの中のシェルスクリプトとしての文字列に色がつくのは便利な気もしますが、YAMLのフロースカラースタイルの文字列は改行も含めるなど自由度が高いので、ブロックスタイルの文字列の中でフロースタイルの解釈をするのはおかしいですね。 また、プレインスタイル文字列の中のシングルクォートの解釈もおかしいです (上のjoinの例)。

GitHub Actionsをよく触るようになってからYAMLシンタックスハイライトが壊れることが多くなったので、重い腰を上げてVimYAMLシンタックスハイライトを改善しました (色々とあって取り込まれてからブログを書くまで時間がかかってしまいました…)。

github.com

このパッチの中でも、特に重要なのは以下の部分です。

syn match yamlBlockScalarHeader '[|>]\%([1-9][+-]\|[+-]\?[1-9]\?\)\%(\s\+#.*\)\?$' contained
            \ contains=yamlComment nextgroup=yamlBlockString skipnl
syn region yamlBlockString start=/^\z(\s\+\)/ skip=/^$/ end=/^\%(\z1\)\@!/ contained

|->1-といったブロックスカラースタイルのヘッダーを正しく認識し、その後に続くブロックスカラースタイルの文字列を認識するようになりました。 これによって、文字列の中に複雑なシェルスクリプトを書いてもYAMLとしてのハイライトが壊れにくくなりました。

Vimシンタックス定義の詳しいドキュメントはこちらです。 ここでは軽くシンタックスを作る時の考え方を書いておこうと思います。 Vimシンタックス定義の二つの大事な考え方は、nextgroupによる状態遷移と、containsによる構文アイテムの入れ子構造です。

シンタックスハイライトの構文アイテムには、トップレベルのアイテムとそうではないものがあります。 まずはトップレベルの構文アイテムでもって、そのパターンがあればファイルのどこでも認識したいものを定義し、そこからnextgroupを使って次に続くアイテムを指定していくというのが基本的な考え方です。 例えば、YAMLだと行が /^\s*\zs-\ze\%(\s\|$\)/ にマッチした場合は基本的にファイルのどこでもリストのマーカーとして認識させたいので、トップレベルのアイテムとして定義します。 トップレベルのアイテムがあることで、ファイルの途中からでも構文を認識できるようになっています。

多くのプログラミング言語(PietやBefungeといった例外を除く)の文法は、木構造で表現できます。 つまり構文要素の隣接関係と包含関係によって表現できるということです。 これらに対応するのが、nextgroupcontainsです。

隣接関係とは、functionというキーワードの後には識別子が続き、さらに開き括弧、引数、閉じ括弧、ブロックの開始が続くといった感じです。 Vimシンタックス定義では、nextgroupというオプションで次に続く構文アイテムを指定することができます。 ここにはトップレベルではないアイテム、containedというオプションを指定したアイテムを指定します。 実際に書いていくと同じアイテムたちを何度も指定したくなりますが、そのような場合はsyntax clusterを使うと便利です。

トップレベルのアイテムとnextgroupを使えばある程度の文法は表現できるのですが、これだけでは表現できない文法もあります。 例えば、"1 ${2 + 3} 4"みたいな文字列補間は、隣接関係だけでは表現できません。 他にも、文字列の中の\uXXXXのようなエスケープシーケンスとか、コメントの中のTODOとか、そういうものに色をつけたいことがあります。 こういった包含関係を表現するために、containsというオプションで構文アイテムの入れ子構造を表現します。 状態遷移と入れ子構造を組み合わせることで、Vimシンタックス定義は複雑な構文に対応しているのです。

今回、YAMLシンタックスハイライトを改善したことで、Vimシンタックス定義の仕組みを改めて自分の中で整理できました。 Vimシンタックスを改善したい人に、この記事が参考になれば幸いです。 個人的には、よく編集するGitHub Actionsのワークフローファイルでのハイライトを改善できましたし、積年のいくつかの問題 (#8234#10730#11517) を一気に解決できたので、とても満足しています。 それでは、また。