RenovateのGemfileやPodfileの更新でリリースノートを取得する

Renovateは様々なデータソース・パッケージマネージャーに対応していて、もちろんRubyBundlerのGemfileにも対応しているのだが、APIリクエストのレートリミットを回避する関係で、https://rubygems.org をデータソースとする場合はリリースノートの取得ができなくなっている。

これが不便で、自分達のチームではGemfileの更新にはDependabotを併用するという面倒な運用をしていた。最近になってこれを部分的に解消できる設定がRenovateに誕生したのでご紹介。

customChangelogUrlという設定を使うと、あるパッケージのリリースノートを探しにいくURLを指定することができる。これを使うとGitHubやGitLabの該当リポジトリのReleasesや CHANGELOG.md などからリリースノートをリストアップしてくれる。

例えばfastlaneを指定する場合はこうなる:

{
  "packageRules": [
    {
      "matchManagers": ["bundler"],
      "matchPackageNames": ["fastlane"],
      "customChangelogUrl": "https://github.com/fastlane/fastlane",
    },
  ]
}

iOS開発文脈ではCocoaPodsのPodfileでも同様の状態にあり、そちらにも活用することができる。

{
  "packageRules": [
    {
      "matchManagers": ["cocoapods"],
      "matchPackageNames": ["SwiftLint"],
      "customChangelogUrl": "https://github.com/realm/SwiftLint",
    },
  ]
}

これでDependabotに別れを告げてRenovateに統一することができた。めでたしめでたし。

GraphQLスキーマをサーバーサイドからクライアントサイドに自動同期し、変更履歴も確認したい

似たような話が先日ありましたね。

こちらはGraphQLではなくてOpenAPI、かつサーバーサイド側のリポジトリからクライアントサイド側のリポジトリにプッシュする方式でした。

今関わっているプロダクトでは、1つのGraphQLスキーマが複数のリポジトリから必要とされていたり、それぞれで同期タイミングを制御できたらいいかなということもあったりで、クライアントサイドからGitHub Actionsの定期実行でプルする方式で更新をしています。

イメージは次のとおり:

name: Update GraphQL Schema

on:
  schedule:
    - cron: "0 0 * * 1-5" # JSTで月曜〜金曜の9時、これは適当です
  workflow_dispatch:

jobs:
  UpdateGraphQLSchema:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - run: make update-graphql-schema
      - uses: peter-evans/create-pull-request@v4
        with:
          commit-message: Update schema.graphqls
          title: Update schema.graphqls
          body: ""
          branch: update-graphql-schema
          token: ${{ secrets.GITHUB_PAT }}

トリガーとしては、scheduleでの定期実行 OR workflow_dispatchでの手動実行になっています。

実際の更新部分は例として make update-graphql-schema としていますが、中身としてはApollo Clientのコマンドを実行してスキーマをダウンロードする(実態としてはIntrospectionを実行してスキーマを得る)ようにしています。ここについては、サーバーサイドのリポジトリをチェックアウトしてきて、元のスキーマファイルをコピーするようにしてきてもよいでしょう。

更新したスキーマをゲットできたら、あとは peter-evans/create-pull-request actionを使ってプルリクエスト(以下、PR)を作成しています。

ここで扱っているのはApollo Clientを使ったネイティブアプリで、Apollo iOSApollo Kotlinもアプリのビルド時にオンデマンドでコード生成を行うので、スキーマ更新と同時にコード生成をしてPRに含める、ということはありません。

さて、これはこれで単純でいいのですが、スキーマの変更内容がPRで確認できたとしても、その詳細(変更の意図や経緯)がこれだけでは分かりません。ということで、サーバーサイドのリポジトリの関連するPRを拾い上げて、スキーマ更新のPRに変更履歴という体で貼り付けていました。

しかしさすがに人力で関連PRを探しに行くのは面倒なので自動化したい。ということで、チームメンバーであるところの id:mangano-ito さんがやってくれました。

name: Update GraphQL Schema

on:
  schedule:
    - cron: "0 0 * * 1-5" # JSTで月曜〜金曜の9時、これは適当です
  workflow_dispatch:

jobs:
  UpdateGraphQLSchema:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
        with:
          fetch-depth: 0
      - id: generate-associated-pr-list
        uses: ./.github/actions/generate-associated-pr-list
        with:
          our-path: path/to/client/schema.graphqls
          their-path: path/to/server/schema.graphql
          their-owner: ...
          their-repo: ...
          github-token: ${{ secrets.GITHUB_PAT }}
      - run: make update-graphql-schema
      - uses: peter-evans/create-pull-request@v4
        with:
          commit-message: Update schema.graphqls
          title: Update schema.graphqls
          body:  |
            ## Associated Pull Requests
            ${{ steps.generate-associated-pr-list.outputs.result }}
          branch: update-graphql-schema
          token: ${{ secrets.GITHUB_PAT }}

generate-associated-pr-list というのがお手製のComposite Actionです。自分方のリポジトリのファイルと、相手方のリポジトリのファイルの変更日時から、相手方のファイルのその間のコミット・それに紐付くPRを拾い上げて、Markdownのリストとして出力してくれるものです。パラメーター名も、gitの衝突解消時のours, theirsという用語を基にしていてオシャレですね。

その実装の大事な部分だけ抜粋してみます:

runs:
  using: composite
  steps:
    - id: get-last-updated-date
      shell: bash
      env:
        TZ: UTC0
        PATH_TO_FILE: ${{ inputs.our-path }}
      run: |
        value=$(\
          git log -1 \
            --date="format-local:%Y-%m-%dT%H:%M:%SZ" \
            --pretty="format:%cd" \
            "${PATH_TO_FILE}" \
        )
        echo "::set-output name=value::$value"
    - id: find-prs
      uses: octokit/graphql-action@v2.x
      env:
        GITHUB_TOKEN: ${{ inputs.github-token }}
      with:
        query: |
          query FindAssociatedPullRequests(
            $since: GitTimestamp!,
            $path: String!,
            $owner: String!,
            $repo: String!,
          ) {
            repository(owner: $owner, name: $repo) {
              defaultBranchRef {
                target {
                  ... on Commit {
                    history(since: $since, path: $path) {
                      nodes {
                        associatedPullRequests(first: 1) {
                          nodes {
                            number
                            url
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          }
        since: ${{ steps.get-last-updated-date.outputs.value }}
        path: ${{ inputs.their-path }}
        owner: ${{ inputs.their-owner }}
        repo: ${{ inputs.their-repo }}

GitHubのGraphQL API (v4) を上手く使って、特定のファイルの特定日時以降のコミット・それに紐付くPRをリストアップしています。これで人力で関連PRを探しに行かなくてよくなりました。めでたしめでたし。

このようなワークフロー・改善により、自分達のGraphQL APIとこれからも仲良く付き合っていきたい。

期間の扱い方とその名前

とあるAPIスキーマの叩き台をクライアントサイドとして検討している際に、コンテンツの公開期間やイベントの開催期間のような期間について議論が少し盛り上がった。

要件としては、期間の開始と終了の日時をそれぞれ取得できたい。

期間を考える時、開始と終了がそれぞれinclusiveなのかexclusiveなのかをまず考慮すべきであるが、開始日時の重複や終了日時に隙間を発生させないためには、開始はinclusive、終了はexclusive、つまり半開区間(左閉右開)にするのが望ましいだろう。

終了をexclusiveにすると、例えば8月の1ヶ月間、つまり8月1日0時0分〜9月1日0時0分という期間の場合、ユーザー向けの表示としては終了日時は「8月31日23時59分まで」と表示したくはなるが、これはプレゼンテーションロジックとしてクライアントサイドの責務としてやる。基本的には-1秒してからフォーマットすればよい。

次にその名前を簡単に考えてみるとsince / until のような名前が思い付く。ここであるメンバーから「untilにはinclusiveな意味合いを感じてしまうがどうだろうか」という意見が出た。

untilはinclusiveかexclusiveなのかを少し調べてみると、どちらの意味にも取り得て、文脈依存であるようだ。

ここではuntilはexclusiveとして扱いますよ、ということでもいいのだが、『リーダブルコード』にもそういう話がありましたね、ということをまた別のメンバーが提示してくれた。該当箇所は「3.5 包含/排他的範囲にはbeginとendを使う」で、

 ここに使う仮引数の名前は何がいいだろうか? プログラミングの命名規約では、包含/排他的範囲に begin と end を使うことが多い。

 でも、end は少しあいまいだ。例えば、「本の終盤(the end of the book)を読んでいる」の「end」は包含的だ。残念ながら英語には「ちょうど最後の値を超えたところ」を意味する簡潔な言葉がない。

 begin と end の対はイディオムになっている(少なくとも C++ の標準ライブラリではこれが使われている。また、配列がこのように「スライス」されることも多い)ので、これが最善の選択と言える。

begin / end というイディオムが示されていた。我々の扱うAPIでは開始日時としてstartAtという命名例もあったので、これを考慮すると startAt / endAt ということになるだろうか(DBの日時系のカラムで created_atupdated_at とするように、日時系のデータに At サフィックスを付けている)。

また別の例として、半開区間の話にも絡むが、java.timePeriodDurationAPIはこのようになっていた。

APIの仕様として左閉右開の半開区間であることを明にして、引数名にもInclusiveとExclusiveを入れていて冗長だが分かりやすい。今回のケースではそこまで冗長にはせず startAt / endAt に落ち着きそうになったのであった。

学びある議論で楽しかった。

Mockolo 1.7.0のビルド済みバイナリがarm64にしか対応していない

https://github.com/uber/mockolo/releases/download/1.7.0/mockolo.tar.gz に含まれる mockolo バイナリがarm64アーキテクチャーにしか対応していなかった。これだとAppleシリコンMacでは実行できるが、Intel Macでは実行できない。

$ file mockolo
mockolo: Mach-O 64-bit executable arm64

その前はどうかと思って1.6.3を確認してみたら、そちらはx86_64だけだった。これは本当はx86_64でもarm64でも実行できるユニバーサルバイナリになっていてほしい。

ということで修正PRを出してみている。SwiftPMでは swift build の引数に --arch arm64 --arch x86_64 を足すだけでユニバーサルバイナリにできるので、それをやっただけです。

github.com

追記

このPRを含む1.7.1がリリースされていました。Intel Macの方もAppleシリコンMacの方もどうぞご利用ください。

SwiftUIのTextは+演算子で結合できる(fontやforegroundColorなどを変えていてもできる)

  • SwiftUI.Text には + 演算子が用意されているので Text("Foo") + Text("Bar") のように結合ができる
  • Text("Foo").font(.title) + Text("Bar").foregroundColor(.secondary) のように一部のmodifierを付けていても結合できる
    • これらのmodifierは、多くのViewでは some View が戻り値だが、Textでは型が変わらずにTextのままなので、そのまま + 演算子が使える

基本的なテクニックではあるけれど、modifierを呼び出した結果が some View じゃないのは意外と気にせずに使っている部分でもありそうなのでメモ。

CircleCI公式のDockerイメージにはyqがプリインストールされているので、自分で入れる必要はない

CircleCIのダイナミックコンフィグとsetupワークフロー*1で、path-filtering Orbcontinuation Orbを使う時の話題ですが、タイトルがすべてです。

github.com

設定ファイルをマージする用途でyqを使うために、自分でインストールするような記述を色んなブログで見かけたりもしたのですが、path-filtering/filter job, continuation/continue jobのデフォルトexecutorとなるDockerイメージにはyqがプリインストール済みです。

Open Test Reporting: platform-agnosticなテストレポートのフォーマット

たまたまJUnit 5.9.0のリリースノートを読んでいたら面白いものが目に着きました。

  • XML reports in new Open Test Reporting format

テストレポートのフォーマットとしてはJUnitデファクトスタンダードのようになっていて、様々なプログラミング言語やテストフレームワーク、またCIサービスがテスト結果のサマリー表示のためにサポートしています。しかしJUnitは元々Java世界のものであり、他言語のテスト結果をJUnitフォーマットに出力しようとすると構造を無理矢理合わせることになったり、拡張性もなかったり(もしくは独自拡張になったり)します。言語・テストフレームワーク非依存な新しい仕様を定義することでこうした状況を解消しようという取り組みで面白いですね。

Design goalsとして

  • human-readable
  • streamable
  • machine-readable
  • schema-aware
  • extensible

の5つが挙げられています。ファイル形式としてはJSONとかではなく、JUnitと同じくXMLではあるんですね 👀

JSON is less verbose than XML, but the latter provides more expressive ways to define schemas. Moreover, XML has typed extensions built-in via the use of multiple schemas. Thus, the new formats use XML with accompanying XML schemas.

世の中のエコシステムがどれだけここに乗っかっていくか気になるところですが、まずは本家JUnit自体がサポートしたということで、着目していきたいですね。