パブリックリポジトリでGitHub Actionsの実行時間を集計する

主にパブリックリポジトリでの使用を目的とした、リポジトリ単位で指定した期間のGitHub Actions実行時間を集計するツールを作成しました。

github.com

GitHub Actionsのアクションとしても公開しており、GitHub Actionsで実行した場合は下記のようにジョブのサマリーとして集計結果を出力します。

これがあると開発が便利になる類のツールではないですが、自分のパブリックリポジトリでどれだけGitHub Actionsを使っているか気になっている方がいらっしゃいましたら使って頂けると嬉しいです。

使い方

詳しくはREADMEに記載していますが、例えばGitHub Actions上で毎月の頭に前月の実行時間を集計する場合、以下のようになります。

name: Calculate usage

on:
  schedule:
    - cron:  '5 0 1 * *'
    
jobs:
  calc-usage:
    runs-on: ubuntu-latest
    steps:
      - name: Set Start Date
        run: echo "START_DATE=$(date -d "$(date +'%Y%m01') 1 month ago" +'%Y-%m-%d')" >> $GITHUB_ENV
      - name: Set End Date
        run: echo "END_DATE=$(date -d "$(date +'%Y%m01') 1 days ago" +'%Y-%m-%d')" >> $GITHUB_ENV
      - uses: muno92/gha-usage@v0.1.0
        with:
          repo: ${{ github.repository }}
          start-date: ${{ env.START_DATE }}
          end-date: ${{ env.END_DATE }}
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

またコマンドライン版もGitHubのリリースページに添付していますので、手元で動かしたい方はそちらをご利用下さい。

使用する際に注意頂きたい点として、指定した期間に含まれるGitHub Actionsの実行数が1000以下である必要があります。

GitHub REST APIの仕様として指定した検索条件の先頭1000件までしか取得できないようになっているようで、例えば1ページ当たり100件の条件でAPIを叩いても10件目までしか取得できません。
(公式ドキュメントではSearch APIにしかそういった記述が見当たりませんでしたが、他のAPIも同様なのではないかと思われます)

そのため、

curl 'https://api.github.com/repos/muno92/gha-usage/actions/runs?created=2022-11-01..2022-11-30' | jq '.total_count'

のように実行数を確認頂いた上で適切な集計期間を指定して頂く必要があります。

動機

これまで、自作ツールのテストでmatrixビルドを使用したり、Nature RemoのAPIから部屋の気温・湿度を取得するプログラムを定期的にGitHub Actions上で実行したりとGitHub Actionsをプライベートでも日常的に使ってきました。 ただ、パブリックリポジトリでは合計の使用量を確認出来ません。

GitHub Actionsではパブリックリポジトリに対して支払が発生しないので実行時間が分からなくとも困りはしないのですが、自分がどれだけGitHub Actionsを利用しているのか確認してみたかったので、今回のツールを作りました。

仕組み

gha-usageはGET /repos/{owner}/{repo}/actions/runsで指定したリポジトリ・期間に実行されたワークフローの結果を取得した後、各ワークフロー実行結果が保持しているjobs_urlにリクエストを送って実行時間を集計しています。

つまり、必然的にN+1のHTTPリクエストが発生することになります。

出来ればN+1は避けたかったのでまずGitHub GraphQL APIを使って集計できないか調べてみたのですが、2022年11月時点ではジョブの実行時間を取得できません。
(Workflow RunのcreatedAtとupdatedAtの差分を取れば近しい値が取れるかもしれませんが、ランナーのOS毎に実行時間を取得するためにはジョブ単位で実行時間を取得する必要があります。)

GitHub REST APIの場合GET /repos/{owner}/{repo}/actions/workflows/{workflow_id}/timingというそれらしいAPIはあるのですが、ドキュメントに記載されている通りこのAPIは支払対象となる実行時間しか返しません。

そのため、最初に記載した2種類のAPIを組み合わせて実行時間を集計することにしました。

GET /repos/{owner}/{repo}/actions/runsはレスポンスボディの形式が以下のようになります。

{
  // 指定された条件に合致する(ページ分割される前の)実行結果総数
  "total_count": 65,
  "workflow_runs": [
    {
      "id": 3553437581,
      "name": "Test",
      "node_id": "WFR_kwLOIYWxB87TzSeN",
      "head_branch": "master",
      "head_sha": "6803c43ec8b27c4d8d8a878ee6a18789e623a039",
      "path": ".github/workflows/test.yml",
      省略
      "jobs_url": "https://api.github.com/repos/muno92/gha-usage/actions/runs/3553437581/jobs",
      省略
    },
    2件目以降
  ]
}

そして、workflow_runs1件1件のjobs_urlにリクエストした際のレスポンスボディは以下のようになります。

{
  "total_count": 2,
  "jobs": [
    {
      "id": 9719953258,
      省略
      "status": "completed",
      "conclusion": "success",
      "started_at": "2022-11-26T11:06:03Z",
      "completed_at": "2022-11-26T11:06:46Z",
      "name": "e2e_test",
      "steps": [
        {各ステップのステータスや開始・終了時刻},
      ],
      "check_run_url": "https://api.github.com/repos/muno92/gha-usage/check-runs/9719953258",
      "labels": [
        "ubuntu-latest"
      ],
      "runner_id": 1,
      "runner_name": "Hosted Agent",
      "runner_group_id": 2,
      "runner_group_name": "GitHub Actions"
    },
    2件目以降
  ]
}

各jobのcompleted_atとstarted_atの差分を取ればジョブ毎の実行時間が分かります。

そして、「ubuntu-latest」と出力されているlabelsはドキュメントに

Labels for the workflow job. Specified by the "runs_on" attribute in the action's workflow file.

と説明があるため、(セルフホストランナーを使用していないという前提の上で)この値を見ればどのOSのランナーで実行されたか判別できます。

これらの情報から、

  1. 1ページ目のworkflow_runsを取得
    • 1ページ当たりの件数は最大値の100を指定
  2. total_countが100以上だった場合、2ページ目以降を取得
  3. 各workflowのjobs_urlにリクエストを送り、実行時間を計算
    • 1ページ当たりの件数はworkflow取得時と同じ100を指定
    • ワークフロー毎のジョブ数が100を超えることはほとんど無いだろうと、ここでは2ページ目以降の存在を考慮しない

という流れで集計を行っています。
(2番と3番で投げるHTTPリクエストは非同期)

実装

実装にあたっては

  • HTTPリクエストを大量に送る事になるため、並行処理が強いらしいGoに期待
  • A Tour of GoをやったきりだったGoを手に馴染ませたかった

といった理由でGoを使うことにしました。

実用 Go言語Go言語による並行処理を都度都度参照しながら実装しましたが、並行処理以外ではほとんど詰まることなく進められました。
(パッケージをどう構成したら良いのか悩んだりジェネリクスを使ってみようとしたらメソッドで使えず断念したりといったことはありましたが、どん詰まりする程では無かったかなと思います)

非同期処理を実装する際はいきなり非同期でロジックを組むのではなく、一度同期処理を組んだ後に非同期処理に書き換える形で進めました。
これは一度に多くのことをやろうとするのではなく一歩ずつ進めていくほうが頭が混乱しにくいだろうと考えてのことだったのですが、「同期処理実装時に書いたテストが通る状態を維持しながら速度が上がればOK」とゴールが分かりやすくなったので結果的に良い形で進められたと思います。

そして、ある程度出来上がった段階になって使い方に記載したGitHub REST APIの取得上限が1000件という問題にぶち当たりどうしようかと悩んだのですが、これについてはプログラム側で頑張って対処するのではなく使用者側で適切に期間を指定して貰おうと割り切りました。

まとめ

最終的に、400近いHTTPリクエストが走る場合でも7秒ほどで処理を終えられる実装になったので、Goの並行処理性能にはとても満足しています。
Goだと愚直に実装していくだけだと分かってしまえば実装に迷う事も少なく、言語仕様が少ないシンプルさが好まれるのも分かるような気がしました。
(あまり複雑な事をやっておらずまだGoの辛みに行き当たっていないだけなのかもしれませんが)

また、折角Goで実装したのだからとクロスコンパイルしてコマンドラインでも使えるようにしたのですが、バイナリサイズが5MB前後と小さく収まったのは驚きでした。

他の言語で実装したらどうなるのか気になっているので、(モチベーションが尽きなければ)他言語で書き直して速度やシングルバイナリのサイズ、書き心地を比較してみたいと思います。

PHPカンファレンス2022に参加しました

9月24日(土)、25日(金)に開催されたPHPカンファレンス 2022に参加しました。

phpcon.php.gr.jp

今年はオンライン・オフラインのハイブリッド開催となっており、久しぶりに会場であるPiOに行きたい気持ちもあり悩みましたが、オンラインでの参加にしました。

当日の視聴セッション

今年はPHP本体(php-src)に触れる発表が多かったのが印象に残っています。

とりわけ、実際に機能を提案・実装したりバグ報告したりした経緯を発表されていた

は粘り強く対応されていてただただすごいと思いましたし、PHPはコミュニティで開発してるんだなと改めて実感しました。

それ以外の発表だと、今年業務でパフォーマンスの調査・チューニングをしたこともあり、「Laravel を低速化する技術」は「やっぱそこ効くよね」「そこは効かないのか」「それやっちゃう?w」と楽しく聞けました。

また、「Psalmで"完全に理解した"静的解析」はPsalmの内部に関する情報がみっしりと詰まっていて良かったです。

オンライン参加の感想

今年は昨年までと違い、参加者がコミュニケーションを取る場がtwitter & Discordからtwitter一本に統一されていました。
Discordの方が参加者間でコンテキストを共有して会話しやすい気がしなくもないのですが、どこに投稿しようか迷わずに済むのは良かったです。
(自分がtwitter上での会話にハードルを感じているからなだけで、他の参加者の方はそうでもないのかもしれませんが)

また、質疑応答の時間では運営の方から登壇している方に対して質問の復唱をお願いする場面がありました。
(質問者用のマイクで拾っている声が配信に流れないため)

途中(2日目?)からは質問している方の声も配信に流れるようになっていましたが、オンライン参加者の事も配慮頂いていて大変有り難かったです。

発表自体はたまに音が聞こえにくくなるハプニングが起こりはしましたが、スタッフの方が迅速に対応して下さったおかげでほとんどストレス無く見れました。

ただ、休憩時間にスポンサーブースを回ったり、発表中の会場の空気感を感じたりといったオフラインカンファレンスならではの楽しみは味わえなかったので、現地勢楽しそう・・・とtwitterを眺めていました。

来年は現地参加したいです。

まとめ

今年はハイブリッドでの開催となりスタッフの方は大変だったかと思いますが、2日間技術の話にどっぷり浸かれてとても楽しかったです。
ありがとうございました。

来年は安心して現地参加出来る情勢になっていると良いですが、どうなることやら・・・

PHPerKaigi 2022に当日スタッフとして参加しました

4月9日(土)〜4月11日(月)に開催されたPHPのカンファレンス、PHPerKaigi 2022に当日スタッフとして参加したのでその感想記事です。

当日スタッフとして参加

今回は2020年以来となる当日スタッフとしての参加。

day0の朝、会場のCoconeriホールに着くと「帰ってきたな」と感慨深くなりました。

そして机・椅子を設置したり、バックボード(記者会見でよく見るアレ)を組み立てたりと会場準備を進め、

横倒しにしたバックボードの骨組みにスポンサーロゴが印刷された布を面ファスナーで貼り付けている図。外見から想像するより軽かったです

本編が始まってからの担当は受付&Track B。

2020年に当日スタッフとして参加した際は受付しか担当していなかったのでTrack担当は少し不安でしたが、自動化が進んでいたこともあり問題無く進められました。

具体的に言うと、今回のPHPerKaigiは

  • ノベルティは事前に郵送
  • セッションは基本的に事前録画
  • 質問もDiscordのテキストチャットを使う
  • 幕間の動画があり、次のセッションの案内は不要

となっていたため、受付ではQRコードを読み取って名札を手渡すだけとなり、Trackでは

  • 照明のON/OFF
  • 休憩時間中は扉を開け放ち、次のセッションが始まったら閉じる
  • 各セッション毎に1回、forteeのスタッフ用ページにあるボタンをポチッとする
    (動画切替のため)

をするだけで済みました。

そのため。基本はTrack Bのセッションをじっくり見つつ、たまにTrack Aのセッションを見に行ったりスポンサーブース/アンカンファレンスを覗いてみたりと、カンファレンスを満喫出来ました。

「なんだかアンカンファレンスが盛り上がってるぞ」と見に行ったり、久しぶりに会った人と話したり出来るのはオフラインならではですね。
改めて、今年オフラインで開催・参加出来て良かったなと思います。

Fitbitで計測した歩数 (こうやって可視化してみると、設営・撤収があったday0/day2の歩数が多くなっていて面白い)

PHPerチャレンジ

PHPチャレンジは23764ptで24位

あまりスコアは伸びませんでしたが、デジタルサーカルさんやトラーナさんが作成された問題を解けたのは達成感がありました。

www.dgcircus.com toranabox.com

トラーナさんの5問目には特に苦戦し、会場準備の空き時間にも解き進め、

twitter.com

day0が終わった後にようやく解けました。

twitter.com

問題を作るのは大変だったろうなと感じたので、作問された方には本当に頭が下がります。

ただ、PHPerチャレンジ全体だと、後になって「あ、ここにもトークンあったのか!」と気づいて悔しい思いを何度かしたので、次回はより力を入れてチャレンジしたいです。

まとめ

私は毎年クロージングで主催の長谷川さんが話されている「We are Community」という考え方が大好きです。

昨年はスタッフではなく一般参加と立場を変え、また今年は業務でPHPに触れる機会が減っていましたが、それでもPHPerKaigiという場を用意して頂いたおかげで色々な人とコミュニケーションを取れ、PHPStanにPull Requestを送る機会も得られました。

muno-92.hatenablog.com

「やっぱりPHPPHPコミュニティは好きだし、関わり続けたいな」と実感したので、1年先がどうなっているかは分かりませんが、来年も開催されたら何かしらの形で(今のところの考えでは当日スタッフとして)参加したいと思います。

PHPerKaigi 2022への参加をきっかけにPHPStan本体にPRを出した

PHPerKaigi 2022の翌日、4/12にPHPerKaigi参加者用のDiscordで発生したやり取りをきっかけとしてPHPStan本体にコンストラクタ直接実行を検知するためのPRを出しました。

github.com

PHPerKaigiは実は4日目もあった!?と感じる程の盛り上がりがPRを出すまでにあったので、その一連の流れをまとめます。


2022/4/26 追記
PHPStan 1.6.0がリリースされ、Bleeding EdgeとしてPRで追加したルールも利用可能になりました。

https://github.com/phpstan/phpstan/releases/tag/1.6.0

Bleeding Edgeを有効にし、Levelを2以上にするとコンストラクタを直接呼び出した場合にエラーと見なされるようになります


2022/5/5 追記

PHPStan1.6.6にて、phpstan-strict-rulesにルールが移動されました。

github.com

phpstan-strict-rulesをインストールした上で

includes:
    - vendor/phpstan/phpstan/conf/bleedingEdge.neon
    - vendor/phpstan/phpstan-strict-rules/rules.neon
parameters:
    level: 2

のように設定頂ければ、コンストラクタの直接実行をエラーとして検知するようになります。


大まかな流れ

  • 09:01 t_wadaさんから「静的解析ツールで__construct2回呼び出しを禁止出来ないか」とDiscordに問いかけ
  • 10:18 Psalmでは検知出来る、PHPStanでは検知出来ないと返答
  • 18:02 t_wadaさんから「__constructの明示的な呼び出しをターゲットにするルールが無いのであればコントリビュートのチャンスでは」とコメント頂く
  • 20:15頃 tadsanさん・mpywさんが関心を持つ
  • 20:25 焦り出す自分
  • mpywさんが仮実装を投稿されたり、tadsanさんが対処しなければいけないケースを整理されたり
    • ここでPHPStanの拡張を書くのでは無く本体へのルール追加として提案する方向になる
  • 21:33 phpstan/phpstan-srcをforkして作業に取り掛かり始める
  • テストクラス・ルールクラスの実装を進め、その間にもDiscordで実装方法が検討される
  • 22:37 下記2パターンを検知出来るようになる
    • 同クラスのコンストラクタ以外のメソッドから$this->__construct
    • $生成したインスタンス->__construct
  • 23:08 コンストラクタ内で$this->__constructを実行する再帰呼び出しのパターンはエラーにならないようにする
  • 23:38 __constructの静的呼び出しを検知出来るようになる
  • 23:52 tadsanさんがIssueを立てて下さる
  • 00:13 条件式の整理などリファクタリングを行い、PRを提出
  • 00:32 MethodCall(->で呼び出すパターン)とStaticCallをそれぞれ別のルールでチェックする形に変更
    (その方が良いとIssueにコメントがあったため)
  • CIでLint/静的解析/テストが落ちる
  • 01:15 Lint/静的解析/テストを通るように修正
  • 01:25 CIの全ジョブがグリーンになり、Discord上の面々は解散
    記念スクショ
  • 03:52 PHPStan作者のOndreさんにPRが承認され、後は引き継いで頂けるとコメント頂く

発端

PHPerKaigi day1(4/10)でt_wadaさんが発表された「予防に勝る防御なし - 堅牢なコードを導く様々な設計のヒント」にて、たとえDateTimeImmutableであっても生成したインスタンス__constructを実行する事で値を書き換えられてしまうとの話がありました。

この__constructが2回実行されるコードについて、

静的解析で禁止すれば良いと発表資料に入れたいが、そういったルールは無いか

とt_wadaさんがDiscordで質問されたのが発端となり今回のPRが生まれました。

4/12時点の静的解析ツールでの検知

t_wadaさんの質問を見て、最初は「PHPStanのphpstan-disallowed-calls拡張を使えば禁止出来るのでは?」と思いました。
しかし、phpstan-disallowed-callsで特定クラスのコンストラクタを指定した場合、new DateTimeImmutableもルールに抵触してしまいます。

To disallow naive object creation (new ClassName() or new $classname), disallow NameSpace\ClassName::__construct in disallowedMethodCalls. Works even when there's no constructor defined in that class.

PHPStan自体のレベルを9(MAX)に設定しても__construct直接実行は検出出来ませんでしたが、Psalmでレベル1(MAX)にした場合はエラー検知出来ると分かったためその旨をDiscordに投稿しました。

※Psalmも下記の理由でエラー検知出来るだけで、__construct直接実行を禁止するルールがあったり、__constructを直接実行してはいけないとエラーメッセージが出たりする訳ではありません

  1. $dt->construct('2022-04-10')の場合
    https://psalm.dev/docs/running_psalm/issues/UnusedMethodCall/
    に抵触する
  2. $hoge = $dt->construct('2022-04-10')の場合(1番を回避出来てしまわないかの確認)
    __constructの戻り値はvoidのため、
    https://psalm.dev/docs/running_psalm/issues/AssignmentToVoid/
    に抵触する

PRを出すまで

上記の回答に対してt_wadaさんからコントリビュートチャンス!とコメント頂きましたがこの時点では腰が引けていました。

自分用のツールを「誰も見てないだろ」位の気軽さでパブリックリポジトリとして公開してはいるものの、OSSへのPRといったらPHPマニュアルに出した事が1回あるだけで、まだまだハードルを感じてしまっていました。

そういった気持ちで「とはいえ現状該当コードを禁止するルールは無い気がするし、Packagistでダウンロード数が多いPHPStan拡張をざっと見てみてそれでも無かったら作るかなあ」と考えながらDiscordを見たらtadsanさん・mpywさんが実装に関心を持たれており、速く実装しなければ、と慌てて取り掛かり始めました。

そして名前やテストの書き方どうしようなどと考えている間にもDiscordでは実装方法の検討などが進み、話がまとまった後、実装は私に任せて頂ける事になりました。
(ここで「自分がやる」ではなく「実装任せた」と言って頂けて大変有り難かったなと感じています)

過去にPHPStanの拡張を作ろうとした事があり、なんとなく「ソースコードPHP Parserで解析した結果から良い感じにNodeを選び、__constructという文字列が含まれているかチェックすればいい」位のイメージはついていたのですが、具体的にどのように実装すれば今回のケースを満たせるかは調べてみないと分からないといった状態だったので、アドバイスのおかげでその辺り悩まず、実装に集中する事が出来ました。

また、PHPをがっつり書くのは久しぶりだったのですが、抽象クラス・インターフェースを指定してクラスを定義するだけでどのメソッドが必要か型情報から教えて貰え、PhpStormなら1クリックでそのメソッドを生やせるため「PHP/PhpStorm最高だな」と改めて感じました。

そして実装を始めてからも

  • __constructを呼び出すのはこういったケースもある
  • この静的解析のエラーはここが原因

など、色々と助けて頂きました。

焦ってローカルで全テストなどを走らずにpushしたことでPR出してからCIを通すまでに手間取ってしまったので、その点は反省点です。

(ちなみに、phpstan-srcのMakefileに定義されているタスクの内、tests-coverageを実行するとM1 ProのMacでもファンがぶん回り、M1でもファンが回る事あるんだとなりました。)

さいごに

このような流れで、PHPStanへのPRを出す事が出来ました。

自分1人ではそもそもPR出す踏ん切りがつかなかったり、実装にあたっての不明点解消に時間が掛かったりとここまでスムーズには進められなかったと思います。
背中を押して頂いたり、実装案を提示して下さったり、色々と助けて頂いたt_wadaさん、tadsan、mpywさん、yu-ichiroさん、ありがとうございました。
そして、他の人が今回の自分のような立場に立っていた時に、上手く助けになれたらと思います。

また、カンファレンス会場のアンカンファレンスでスクリーンを囲んでわいわい話しながら実装しているような熱を感じ、単純に作業していて楽しかったです。
こういった偶然の出会い・化学反応がカンファレンスの醍醐味であり、たまらない点なのかなと改めて感じました。

これをきっかけにPHPStanへの関わりを(1ユーザーとしても1開発者としても)深めていきたいです。

2021年の振り返り

今年は正直あまり書ける事はないのですが、書くネタが無かった事も記録に残しておこうと思うので2021年を振り返ります。

まず、今年大きな変化としては社会人になってから初めての転職を8月にしました。

  • コロナ禍でほぼ在宅勤務の状態で入社
  • 未経験ではなく経験者としての採用に対するプレッシャー

と不安はありましたが、なんとか今年を乗り切れました。

その一方、プライベートでは今年後半は技術系以外の趣味に費やす時間が多くなり、あまり勉強出来ていなかったのが反省点です。

例えば、今年読んだ本を挙げてみると

  • Web配信の技術―HTTPキャッシュ・リバースプロキシ・CDNを活用する
  • プロを目指す人のためのRuby入門

だけでした。(振り返ってみてびっくり)

別に義務感100%で勉強している訳では無く、楽しくて勉強しているので自分に強制するのもなと思いつつ、プライベートで自由にコードを書く楽しさを最近あまり感じれていない面もあるので、来年は趣味と勉強の折り合いをつけていきたいなと思っています。

現職に入ってからはLaravelを使う案件に関わり、それまで趣味で触る程度だったLaravelについて少しは習熟出来たとは思っています。
ただ、特定のフレームワークの使い方について以前より詳しくなっただけであって、自分の技術力としてはあまり変化が無く、2021年前半までに身につけてきた知識を切り売りしているような感覚がありました。

(技術力と書くと曖昧になりますが。課題の整理力とか解決力でしょうか・・・自分の中でもはっきりと言語化出来ていません)

※もちろん、Middlewareの考え方など他に応用が利く点はあると思います。

来年はRailsを使う案件に関わる予定です。

Railsにキャッチアップしていく事はもちろんですが、特定の言語・フレームワークに囚われない基礎の部分(API設計、DB設計、HTTP、etc...)を固めつつ、興味を持った事を積極的に試してみて自分の幅を広げていけたらと考えています。

最後に

去年の振り返りで記載していた目標で記載していた目標

来年はk8s、クラウド、DDDなどの学習を進め、アウトプットとしては

- 最低でも年6記事(2ヶ月に1記事)
- 読んだ本の感想も書く

を目標に取り組んでいこうと思います。

は全然達成出来ませんでした。

あれこれと目標を立てるとまた今年の二の舞になりそうなので、上に書いた

特定の言語・フレームワークに囚われない基礎の部分(API設計、DB設計、HTTP、etc...)を固めつつ、興味を持った事を積極的に試してみて自分の幅を広げていく

を基本軸として持ち、目標はシンプルに「AWS クラウドラクティショナーの取得」にします。
クラウドの勉強はしていきたいと今も思っており、業務でAWSを触る機会もありそうなので、色々なAWSのサービスを都度都度つまみ食いしながら勉強していくのではなく、一定範囲の勉強を進める目的で)

とりあえず目標は1つにしましたが、その時その時で興味も変わると思うので、後のことは取ってから考え、興味の向くままに手を動かしていけたらと思います。

引き続き、来年もよろしくお願い致します。

ReSharperでC#のコードを静的解析するアクションを作りました

muno-92.hatenablog.com

の記事で紹介したReSharper Command Line Toolsを使った静的解析について、再利用しやすいようにアクションを作成しました。

github.com

on: [push]

jobs:
  inspection:
    runs-on: ubuntu-latest # or macos-latest, windows-latest
    name: Inspection
    steps:
      - name: Checkout
        uses: actions/checkout@v2
      - name: Setup .NET
        uses: actions/setup-dotnet@v1
        with:
          dotnet-version: '5.0.x' # or 3.1.x
      - name: Restore
        run: dotnet restore
      - name: Inspect code
        uses: muno92/resharper_inspectcode@1.0.0
        with:
          solutionPath: ./解析対象のソリューション.sln

のように指定して頂ければ、解析結果がアノテーションとして差し込まれます。

f:id:muno_92:20210511222430p:plain

良かったら使ってみて下さい。

これより下はどのようにアクションを作ったかについての内容になります。

TypeScriptでのアクション作成

アクションは「Docker」「JavaScript」「複合実行ステップ」の3種類がありますが、今回アクションを作成するに当たって以下の点を満たしたいと考えました。

  • 解析結果のXML(サンプル)でメッセージとは別のタグに記載されている重要度を取得してアノテーションに反映させたい
  • 複数のOS・.NETのバージョンで動くようにしたい
    • 複数の.NETでの動作は最低限可能にする(LTSの.NET Core3.1 + 最新の.NET5)
    • 複数OSでの動作は出来たら良いかな、という位

それに加え、普段使っていないTypeScriptを触ってみたかったので、公式から提供されているTypeScriptでJavaScriptアクションを作成するテンプレートを使ってアクションを作成しました。

アクションの中身

処理としてはhtmlparser2でXMLをパースして取り出した指摘事項を公式ツールキットのIssueCommand関数に渡してアノテーションとして表示されるようにしています。

www.npmjs.com

XMLのパースにどのライブラリを使ったら良いのか分かりませんでしたが、ダウンロード数やTypeScriptでの使いやすさ、Issue/PRが溜まっていないかといった点をチェックしてhtmlparser2を選びました。

このライブラリの方が良いよ、というのがあったら教えて頂けると嬉しいです。

アノテーションの表示については最初はツールキットのcore.warning()`やcore.error()で出力したメッセージのパターンをProblem Matchersで指定しようとしたのですが、この方法ではアノテーションとして表示されませんでした。

blog.utgw.net

によるとProblem Matchersは標準出力/標準エラー出力を読み取っているようですが、この記事を参考にRunnerのコードを追ってみた所、

と違いがあったため、それが原因ではないかと思います。

上記記事やActionCommandManagerのコードを見て初めてwarningやerrorのメッセージを出力する際にファイル情報をプロパティとして指定出来ると分かったため、その方法を採りました。
(この記事を書いている今になってドキュメントに記載されていると気づきました・・・)

ツールキットを使うと以下のような形になります。

import {issueCommand} from '@actions/core/lib/command'

issueCommand('warning', {file: 'ファイルパス', line: 行番号, col: 列番号}, 'メッセージ')

Problem Matchersを使った方法では正規表現を読み解かないとどのようなメッセージ形式なのか分かりませんが、こちらは明示的ですね。

感想

今回TypeScriptでアクションを作成しましたが、公式でテンプレートやツールキットが用意されており、作成し易かったです。

TypeScript自体についても、不慣れなライブラリを使う際に使い方が合っているか型で確認出来るので、やっぱり型があるのは良いなと思いました。

GitHub Actionsに限定せず、機会を見つけてTypeScriptを使っていこうと思います。

PHPerKaigi2021に参加しました

3/26(金)〜28(日)に開催されたPHPのカンファレンス、PHPerKaigi 2021に一般参加したのでその感想記事です。

開催前

開催日の10日前にノベルティが届きました。

タイムテーブルなどに加えて技術記事も掲載されているパンフレットやマスク、ペーパーナイフ、入浴剤など盛りだくさん。
(GMOさんのカレーはクオリティが高いパッケージで笑いましたw)

更にノベルティが入ったボックスはPHPerKaigiやスポンサー企業さんのロゴが印刷されており、それ単体で取っておきたくなるデザイン。

これだけあってアーリーバードのチケット代2000円は安っ!と感じるボリュームでした。

また、オンラインのカンファレンスだと開催日が近づいている実感が沸きづらいような気がしていましたが、事前にノベルティを受け取るともう少しでカンファレンスが始まると実感出来て良かったと思います。

事前録画形式での配信

本編のセッションはLTを除き、スピーカーの方が事前に収録した動画をニコ生で見るスタイルでした。

スピーカーの方が自分のセッションについてリアルタイムで補足されている内容を見ながらセッションを視聴出来、理解が深まるだけでなく双方向にコミュニケーションを取れる感じがあって良かったです。

またAsk the SpeakerはDiscordのボイスチャンネル上で行われていたのですが、オフラインでスピーカーの方に質問しに行くのと比べ、「ちょっとボイスチャンネルに入ってみよう」位の気軽な気持ちでチャンネルに入れたので、個人的には参加しやすかったです。

一方、20人参加しているようなボイスチャンネルでミュートを解除して声を出す勇気を持てず、チャットにテキストで書き込むだけになってしまったのは反省かなと思います。

次の機会があったら勇気を出して声を出してみたいと思います。

アンカンファレンス

PHPerKaigiではzoomを使ったアンカンファレンスも行われました。

タイムテーブル上から枠を予約するとその時間に参加可能なzoomのミーティングルームが自動で作成される仕組みで(当日になってそれを用意出来るfortee凄い)、技術的な雑談やCI/CDの話、懇親会など様々なアンカンファレンスが開かれていました。

特に、day0のアンカンファレンスでは突発的にforteeのライブデバッグが始まり、画面共有したコードを見ながら話しているのが、スクリーンに映されたコードを見てわいわいするオフラインのアンカンファレンスみたいでとても楽しかったです。

(余談ですが、ニコ生のタイムシフトで見返せるセッションよりもアンカンファレンスに参加した方が楽しそう、とは思いつつも見たいセッションもあるのでどうしよう、と悩み、オフラインカンファレンスもこんな感じだったな〜と少し懐かしくなりました。)

感想

今年はオンライン開催になったPHPerKaigiでしたがスタッフの方が色々と工夫して下さったおかげで、一方的にセッションを聞くだけにならずとても楽しめました。

改めてPHPerKaigiはこの参加者全員で作り上げ、一緒に楽しむ感じが好きなんだな〜と感じました。
(と言いつつほとんどROMになってしまったので次はもっとコミュニケーションを取っていきたい)

そして、オンラインでも楽しかったですがオフラインはもっと楽しいので次回はオフライン開催出来る情勢になっていると良いな〜と思います。

また来年もPHPerKaigiに参加したいです!