2021年12月23日木曜日

GitHub で草生やさないようにする

 GitHub で「草を生やす」方法はすぐ出てくるのですが、「草を生やさない」方法がすぐに見つからなくて困っていたので備忘録。

というのも、GitHub Actions で定期的に commit をするワークフローがあるのですが、そのコミットが contribution に含まれてしまってました。 
問題の workflow => https://github.com/srz-zumix/wandbox-status/actions(今は修正済み)
このワークフローでは、修正前は自分の GitHub アカウントの noreply メールアドレスを使ってました。

git config --local user.name ${{ github.actor }}
git config --local user.email ${{ github.actor }}@users.noreply.github.com
筆者は noreply ではなく個人のメールアドレスを登録してるのでカウントされないと思ってたのですが、そうではなかったみたいです。
コントリビューションがプロフィールに表示されないのはなぜですか? - GitHub Docs
※「草が生えない」で困ってる人は↑の公式ドキュメントを読むのをおすすめします。

というわけで、user.email をなんかテキトーなメールアドレスにしたら良いのですが、ちょうど良さげな記事があったのでこれにしました。

GitHub Actionsのボットがコミットしたようにアイコンをつけるにはメールに「github-actions[bot]@users.noreply.github.com」を指定すれば良い - nwtgck / Ryo Ota

git config --local user.name github-actions[bot]
git config --local user.email github-actions[bot]@users.noreply.github.com
これで無事、定期ジョブの自動コミットで草を生やさないようにできました。
(濃い草が自動コミットも含まれていた時期)


めでたしめでたし。

2021年12月16日木曜日

Visual Studio でライブラリなど外部コードの警告を出さないようにする方法

これは「C++のカレンダー | Advent Calendar 2021 - Qiita」の16日目の記事です。

はじめに

警告はときにプログラムの問題を教えてくれます。
警告は基本的にないのが望ましいです。なぜなら、野原から四葉のクローバーを探し出すのは大変だからです。(見つけた四葉は幸運をもたらすか、不幸をもたらすか。。見つけるなら早いほうがいいですよ)

CI/CD 担当をやっていると警告を直してもらうための対応とかしたりします。
警告出てたらステータスを変えて通知とか、ちょっとずつ警告をエラーにしていくとか。
ここで問題になるのは、自分たちが改変することができない SDK やライブラリの警告です。
通知で頑張ってる場合はフィルターでなんとかなるかもしれません。
as Error にしている場合は困ります。

そんな場合は自分たちのコード以外をシステムインクルード(#include <...>)し別途警告レベルを制御することで解決できます。

Visual Studio で外部コードの警告を制御する

Visual Studio では 2017 (15.6) からシステムインクルードの警告レベルを別途設定できるようになりました。「/external (外部ヘッダー診断) | Microsoft Docs」こちらの公式ドキュメントにもありますが 2017 では試験機能となっています。/external オプションに加えて /experimental:external オプションも必要になるので注意してください。2019 からは正式機能となっており、設定も専用のページができました。

さて、これを利用すれば外部コードはシステムインクルードとすることで警告がでないようにできます。試しに以下のコードで試してみました。


/external の設定

プロジェクトプロパティの「C/C++」→「外部インクルード」を開き、 /external:W0 を設定します。コード分析も外部インクルードに対して別途制御可能なので、有効にしている場合は設定しましょう。

外部インクルードディレクトリにパスを追加している場合は、システムインクルードすれば外部インクルードとして認識されます。もしただのインクルードディレクトリにパスを追加している場合は、/external:anglebracket オプションを有効にすることで外部インクルードとみなされます。(システムインクルードすれば警告でなくなると逃げ口として意図しない対応をされる可能性があるので、インクルードディレクトリの設定を変更するのをオススメします)

システムインクルードではない場合

C4390 と C4456 が出ます。

システムインクルードの場合

システムインクルードにすると警告が消えました!

消えない警告もある?

たまたま見つけたのですが、システムインクルードしたファイルの警告でも /external:W0 が効かずに警告されるケースがありました。(こういう書き方をすることはない気もしますが)
コードはこちらです。gcc/clang では警告されませんでした。(https://wandbox.org/permlink/kPn2z1MLCAvbOBny

↑は C4390/C4702 の2つ。↓は C4390 は消えるが、C4702 は消えない。

Visual Studio でシステムインクルードヘッダーの警告を厳しくする

逆にシステムインクルードの警告レベルを上げるとどうなるのでしょうか?
標準ライブラリはシステムインクルードしていると思いますが、それらのコードに警告はあるのか・・? /external:W4 にして試してみました。


結果としては警告は出ない、が警告があるのかないのかはわからない。でした。
というのも VS2017 より前から標準ライブラリの警告って見た記憶がないため(たぶん)、別途制御されているのでは?と思ったからです。
ちなみに後述してますが gcc/clang では(とあるコンパイルオプションをつけると)標準ライブラリからも警告が出ていたこともあります。
MSVC の STL は公開されているので、暇なときにビルドしてみて確認してみようかと思います。
https://github.com/microsoft/STL

gcc/clang の場合

gcc/clang の場合は古くからシステムインクルードの警告は除外されていました。
(Visual Studio が今までできてなくて 2017 でようやくという感じでした)
Wandbox で試してみたのでリンクを貼っておきます。
コードは Gist にも置いてあります。

システムインクルードではない場合

警告でます。
gcc: https://wandbox.org/permlink/MLrLASaM5o5A3UWm
clang: https://wandbox.org/permlink/x6TT2NFROSmyLhV4


システムインクルードの場合

警告でません。
gcc: https://wandbox.org/permlink/XVHXnWIMyOfDMjl3
clang: https://wandbox.org/permlink/strTYwduT4ZLH5NK

gcc/clang でシステムインクルードヘッダーの警告を有効にする

gcc/clang でもシステムインクルードの警告を有効にできます。-Wsystem-headers オプションを使います。
試しにしてみると・・

新しいコンパイラーと標準ライブラリの組み合わせだと警告でませんでした。
古いとそこそこ出ます。

gcc 9.1.0: https://wandbox.org/permlink/lhXWuNvWEJdKwJNh
gcc 8.3.0: https://wandbox.org/permlink/4dHKZKtdqmDGmXNU 
clang 6.0.0: https://wandbox.org/permlink/MaVYzrGDeT79eMZa
clang 5.0.0: https://wandbox.org/permlink/qjbmV9U2c8MxHe6A

まとめ

システムインクルードを使って外部コードの警告を除外、自身のコードの警告を取り除く/取り除いてもらうように自動化/自働化しましょう。

2021年12月9日木曜日

Facebook 改め Meta 社の静的解析ツール Infer を GitHub Actions でかける

この記事は「C++のカレンダー | Advent Calendar 2021 - Qiita」の9日目です。

Infer とは

Infer は Facebook 社改め Meta 社が作った静的解析ツールです。
今回は C++ Advent Calendar ということで C++ の静的解析ツールとして紹介しますが、Infer 自体は OCaml で書かれています。
また、C++ の他に Java/C/Objective-C の解析も可能です。

C++ の静的解析ツールというと Cppcheck や Coverity 、コンパイラ付属のものがあったりします。筆者はこれまでこれらのツールを使ってきました。
Infer は 2015 年にOSS化して公開されたのですが、ずっと使ってみたいと思いつつ機会がありませんでした。

今回こうして記事にしているのは、仕事で必要になったからとか趣味でやる気になったからではなく、社名が変わったからネタとして触ることにしました。
理由はともあれ、以下で紹介していきます!

ローカル環境で使う場合

本記事は表題どおり「GitHub Actions」で Infer を使う手順を紹介しますが、ローカル環境で実行する場合も軽く触れておきます。
まず大事なこととして、Windows 非対応です!
Mac の場合は brew install infer でインストールできます。Linux の場合はビルド済みバイナリをダウンロードするか、ソースコードビルドで使うことができます。Docker という手もあります。
詳しいことは公式の「Getting Started with Infer」を見てください。

セットアップできたら「infer run -- ビルドコマンド」のようにコマンド実行すると以下のように結果が出ます。

ビルドコマンドは対象によって変わると思いますが、コンパイラー以外にも cmake や make 、mvn などのビルドツールにも対応しています。

ビルドコマンドが bash script になっているような場合はキャプチャーするコマンドを認識できないので、--force-integration オプションで教えてあげてください。
対応している integration は --force-integration オプションのヘルプで確認できます。
e.g. infer run --force-integration clang -- build.sh

GitHub Actions で使う

では、ここから本題です。
GitHub Actions で Infer を使うために、Setup Infer action (srz-zumix/setup-infer) と Infer reviewdog action (srz-zumix/reviewdog-action-infer) を作成しました。

setup-infer

Infer を現在の環境にセットアップするアクションです。
当然ですが Windows は非対応です。また Mac の場合はバージョン指定はできず、 brew install で取得できるバージョンがインストールされます。
(要望あれば対応しようかと思いますので、issues へどうぞ)

reviewdog-action-infer

Infer の結果を reviewdog を使って PR にコメントするアクションです。
Infer の実行結果が入っている infer-out から result.txt を参照し、reviewdog を使って PR に問題をコメントします。


なぜ action を分けたのか

最初は Infer インストールや解析込みの action を書こうと思ったのですが、ビルドツールや環境はユーザーによってバラバラで、action 側ですべてに対応するのは難しいと考えてこのようになりました。

Infer の実行

上記2つの action は infer での解析処理をしませんので、ワークフローで適宜呼び出してください。
個人開発してるテストフレームワーク(iutest)の例を記載しておきます。

  infer:
    runs-on: ubuntu-latest
    needs: prepare
    steps:
      - uses: actions/checkout@v2
      - uses: srz-zumix/setup-infer@v1
      - name: infer
        run: |
          infer -- make -C test IUTEST_USE_PYTHON=0
      - name: Check Infer report
        uses: srz-zumix/reviewdog-action-infer@v1
        with:
          reporter: github-pr-review
   
結果

iutest のワークフローの結果はこちらです。
静的解析なしだと 10 分くらいのテストが、Infer ありだと 25 分弱と少し時間がかかってますが、まぁ趣味開発なので許容範囲です。

速度向上

Recommended flow for CI | Infer」こちらで CI 向けのおすすめ実行方法が書いてあります。うまくキャッシュできるのであれば --reactive オプションでキャプチャーした情報を再利用すれば早くなると思います。
reportdiff は reviewdog で同等のことができるのでどちらでも。

また --changed-files-index オプションで差分ファイルのリストを指定すれば、そのファイルの解析だけが行われるため、速度向上可能です。
ただし、C++ の場合はヘッダーファイルを指定しても解析されません。.cpp ファイルを指定しないとダメです。(※ --changed-files-index は analyze 時に有効なので capture 時間の短縮にはならない)

Infer capture/analyze TIPS
capture/analyze 中にクラッシュする場合

--skip-analysis-in-path で除外指定できるのでそれをおすすめします。
-g オプションでデバッグモードになるので原因調査ができるかもしれませんが、ストレージを圧迫するかもしれません。
(3,000 弱のソースコードファイル数からなるプロジェクトでやったら 600GB 越えたあたりでディスクフルになって PC 自体が落ちました。。)

解析途中でエラーが出る場合

--keep-going オプションを付けると続行可能です。

compile_commands.json の場合 --clang-blacklisted-flags/--clang-blacklisted-flags-with-arg は効かない

そのまんまです。特に xcodebuild の場合 infer が使う clang が知らないオプションを使ってることがあり、「error: unknown argument: '-index-store-path'」のように失敗していまいます。 infer はデフォルトで  --clang-blacklisted-flags-with-arg に -index-store-path が入ってますが、 compile_commands.json から capture をする場合は --clang-blacklisted-flags(-with-arg) オプションは考慮されないので、json ファイルの command から削除してください。

--continue-analyz

仕事のプロジェクトに Infer 掛けてみたら analyze でクラッシュするわ、エラー起こるわ、なんか応答なくなるわ、そもそも解析時間がクソ長いわ、で辛かったんですが、--continue-analyz オプションつけると続きから解析してくれるので作業が無駄になりません。
(最初に知りたかった)

.inferconfig にオプションを書ける

infer のコマンドラインオプションは .inferconfig ファイルに書いておくことができます。
ファイルの中身は JSON 形式でオプションから -- を除いた名前をキーに設定をします。

例えば、上記で紹介した --skip-analysis-in-path や、 infer の clang にインクルードパスを追加する場合は以下のように書けます。

{
    "skip-analysis-in-path": [
        "path/to/lib/xxx/src",
    ],
    "Xclang": [
        "-I /path/to/dir/include",
    ]
}
まとめ

静的解析ツールは1つかければ十分というものではないので、ぜひ試してみてください。

以上。

2021年12月6日月曜日

CircleCI の Concurrency と Parallelism

この記事は「CircleCI Advent Calendar 2021のカレンダー | Advent Calendar 2021 - Qiita」の6日目です。

CircleCI が無料プランで 30 同時実行可能になったので、Parallelism との関係を調べてみました。

Parallelism とは?

CircleCI ではワークフローのジョブとジョブはそれぞれ並行実行されます。
参考:ワークフローを使用したジョブのスケジュール - CircleCI#ワークフローの構成例 

一方、Parallelism はジョブの中の処理を並列実行する仕組みです。
参考:テストの並列実行 - CircleCI

Parallelism はジョブの steps をそれぞれのワーカーで同じように実行されますが、各ワーカーに付与される CIRCLE_NODE_INDEX 環境変数の値(0,1,2...)でテストを分割したり条件分岐できます。
この Parallelism はプランによって使用できる上限が決まっています。
筆者の場合は今月頭の時点で 16 でした。

さて、ここで少し疑問がわきました。
16 parallelism のジョブを 2 つ使ったら 30 同時実行されるのでしょうか?

Parallelism を含むジョブを 30 以上実行したらどうなるか?

筆者が CI サービスのまとめをしてるリポジトリで検証してみました。

まとめルートリポジトリ:https://github.com/srz-zumix/ci-specs
同時実行の関連まとめ:https://github.com/srz-zumix/ci-parallel

ジョブの開始・終了時刻を記録、その情報から json を生成して chrome://tracing で可視化しています。
この方法は以前「ブログズミ: chrome://tracing で並列処理の可視化をしてみたらすごく便利だった話」で詳細に書いてますので、そちらを参照してください。
ジョブ設定はこちらを参照してください。
https://github.com/srz-zumix/ci-parallel/blob/master/.circleci/config.yml

16 parallelism x 2 job

16 -> 16 という感じでジョブは同時実行されませんでした。
続いて Parallelism を減らし、ジョブ数を増やしてみます。

4 parallelism x 8 job

今度は 28 -> 4 という感じで実行されました。
組み合わせたらダメとうわけではないようです。

(1 parallelism x ) 31 job

最後に Parallelism なし(1)を 31 job です。

当然ですが、30 -> 1 のように実行されました。

まとめ

Parallelism の数分だけ空きがないとそのジョブはキュー待ちする
同時実行数に収まるように Parallelism 数を決めると良さそうですね。
※この挙動に関しては 2021/12/05 時点での結果であり将来的に変わる可能性はあります。

以上。