はじめに
本記事は全5部構成シリーズの最終回です。
- #1:概要・構成編
- #2:ビルド・単体テスト編
- #3:静的解析・カバレッジ編
- #4:ハマりどころ編
- #5:GitLab経験者向けワークフロー構文解説 ← いまここ
筆者はGitLab CIを業務で使っており、stages / artifacts / needs / extends / rules などを活用した経験があります。今回GitHub Actionsを初めて使うにあたり、「GitLab CIのあの概念はGitHub Actionsではどう書くのか」という視点で理解を整理しました。同じような背景を持つ方の参考になれば幸いです。
サンプルコードはこちらです。
https://github.com/YusukeHarada/c-sample
全体構造の対応関係
まずファイルと全体構造の対応から整理します。
| GitLab CI | GitHub Actions | 備考 |
|---|---|---|
.gitlab-ci.yml |
.github/workflows/ci.yml |
ファイルの置き場所が異なる |
stages |
jobs(暗黙的な順序) |
GitHub Actionsはstagesという概念がない |
job |
job 内の steps
|
粒度が異なる(後述) |
script |
run |
コマンドの記述キー |
image |
runs-on + container
|
ランナーの指定方法が異なる |
artifacts |
actions/upload-artifact |
アクションとして実装されている |
needs |
needs |
ほぼ同じ概念 |
rules |
if |
条件分岐の書き方が異なる |
extends |
再利用ワークフロー / with
|
GitHub Actionsには直接の対応物がない |
allow_failure: false |
continue-on-error: false(デフォルト) |
デフォルト動作が異なる |
before_script |
run の複数行 / 別ステップ |
GitHub Actionsにbefore_scriptはない |
CI_COMMIT_TAG などの変数 |
github.ref などのコンテキスト |
変数体系が異なる |
最大の違い:「ジョブとステップ」の概念
GitLab CIではひとつの job にひとまとまりの script を書きますが、GitHub Actionsでは job の中に複数の steps を持つ構造になっています。
GitLab CI:
lint-cppcheck:
stage: analyze
before_script:
- apt-get update && apt-get install -y cppcheck
script:
- cppcheck --enable=all main.c
- cat cppcheck_result.txt
artifacts:
paths:
- cppcheck_result.txt
GitHub Actions(同等の処理):
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Install cppcheck # before_scriptに相当
run: sudo apt-get install -y cppcheck
- name: Run cppcheck # scriptに相当
run: cppcheck --enable=all main.c
- name: Upload artifact # artifactsに相当
uses: actions/upload-artifact@v4
with:
name: cppcheck-result
path: cppcheck_result.txt
GitLab CIの job ひとつが、GitHub Actionsでは steps の集合体として表現されるイメージです。
ci.yml 各キーワードの解説
今回作成したci.ymlを例に、各キーワードの意味を解説します。
on:トリガー条件
GitLab CIでは rules や only / except でトリガーを制御しますが、GitHub Actionsでは on キーでワークフロー全体のトリガーを定義します。
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
GitLab CIの以下に相当します。
rules:
- if: '$CI_COMMIT_BRANCH == "main"'
- if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
runs-on:実行環境の指定
GitLab CIの image に近い概念ですが、Dockerイメージではなくランナーの種類を指定します。
runs-on: ubuntu-latest
GitLab CIのように特定のDockerイメージを使いたい場合は container キーを別途指定します。今回はGitHub提供のubuntuランナーをそのまま使っているため container は不要です。
uses:アクションの呼び出し
GitHub Actionsには「アクション」という再利用可能なステップが豊富に公開されています。GitLab CIに直接対応する概念はありませんが、extends でテンプレートを再利用する感覚に近いです。
- uses: actions/checkout@v4
actions/checkout@v4 はリポジトリのコードをチェックアウトする公式アクションです。GitLab CIではランナーが自動でcloneしてくれますが、GitHub Actionsでは明示的に呼ぶ必要があります。
- uses: actions/upload-artifact@v4
with:
name: coverage-report
path: out/coverage/
actions/upload-artifact はGitLab CIの artifacts に相当します。with キーでアクションへのパラメータを渡します。
id と steps.<id>.outcome:ステップ結果の参照
id はステップに名前をつける仕組みです。後続のステップからその名前を使って実行結果を参照するために使います。
- name: Run unit tests
id: test # このステップに "test" という名前をつける
run: make test
- name: Final status check
if: always()
run: |
echo "${{ steps.test.outcome }}" # "test" という名前で結果を参照
steps.<id>.outcome には実行結果として以下のいずれかが入ります。
| 値 | 意味 |
|---|---|
success |
ステップが正常に完了した |
failure |
ステップが失敗した |
skipped |
条件により実行されなかった |
cancelled |
キャンセルされた |
GitLab CIとの違い
GitLab CIの needs はジョブ単位の依存関係を表しますが、GitHub Actionsの id は同一ジョブ内のステップ間での結果参照に使います。スコープが異なる点に注意が必要です。
# GitLab CI:ジョブ単位での依存
report:
needs:
- test
- lint-cppcheck
# GitHub Actions:同一ジョブ内のステップ単位での結果参照
- name: Final status check
run: |
if [[ "${{ steps.test.outcome }}" == "success" &&
"${{ steps.cppcheck.outcome }}" == "success" ]]; then
echo "🎉 All checks passed!"
fi
今回のci.ymlでの使い方
Final status check ステップで「テスト・静的解析・警告チェックがすべて成功したか」をまとめて判定するために使っています。
- name: Run unit tests
id: test
run: make test
- name: Static code analysis with cppcheck
id: cppcheck
run: cppcheck ...
- name: Build check with strict warnings
id: build_check
run: gcc -Wall -Wextra -Werror ...
- name: Run coverage measurement
id: coverage
run: make coverage
continue-on-error: true # 失敗してもワークフローを止めない
- name: Final status check
if: always()
run: |
if [[ "${{ steps.test.outcome }}" == "success" &&
"${{ steps.cppcheck.outcome }}" == "success" &&
"${{ steps.build_check.outcome }}" == "success" ]]; then
echo "🎉 All CI checks passed successfully!"
else
echo "❌ Some CI checks failed!"
exit 1
fi
カバレッジは continue-on-error: true を指定しているため、仮に失敗しても outcome が failure になるだけでワークフロー全体は止まりません。Final status check の判定対象からも外しているため、カバレッジが取れなくてもCIとしては成功扱いになる設計です。
$GITHUB_OUTPUT:ステップ間での値の受け渡し
同一ジョブ内のステップ間で値を渡すには $GITHUB_OUTPUT を使います。GitLab CIの dotenv アーティファクトや変数の受け渡しに近い概念です。
- name: Run unit tests
id: test
run: |
make test
echo "test_status=passed" >> $GITHUB_OUTPUT # 値を書き出す
- name: Check result
run: |
echo "${{ steps.test.outputs.test_status }}" # 別ステップで参照
if: always():失敗時でも実行
GitLab CIの when: always に相当します。前のステップが失敗してもそのステップを必ず実行したい場合に使います。
- name: Clean up
if: always()
run: make clean
continue-on-error:失敗を許容する
GitLab CIの allow_failure: true に相当します。ステップが失敗してもワークフロー全体を失敗扱いにしないようにします。
- name: Run coverage measurement
run: make coverage
continue-on-error: true # カバレッジが取れなくてもCIを止めない
hashFiles():ファイル存在チェック
特定のファイルやディレクトリが存在する場合にのみステップを実行したい場合に使います。
- uses: actions/upload-artifact@v4
if: always() && hashFiles('out/coverage/**') != ''
hashFiles('out/coverage/**') はマッチするファイルが存在すればハッシュ値を、存在しなければ空文字を返します。GitLab CIには直接対応する構文はなく、script 内でシェルスクリプトで判定するケースが多いです。
stages がない?ジョブの実行順序
GitLab CIではステージを定義することでジョブの実行順序を制御しますが、GitHub Actionsには stages という概念がありません。
GitHub Actionsでは needs キーで依存関係を明示することで順序を制御します。
jobs:
build:
runs-on: ubuntu-latest
steps: ...
test:
needs: build # buildジョブが完了してから実行
runs-on: ubuntu-latest
steps: ...
deploy:
needs: [build, test] # 両方完了してから実行
runs-on: ubuntu-latest
steps: ...
今回のci.ymlはすべてのステップを1つの build ジョブにまとめているため needs は使っていませんが、処理を複数ジョブに分割する場合はこの形になります。
extends に相当するものは?
GitLab CIでは .default_rules のようなテンプレートジョブを定義して extends で再利用できますが、GitHub Actionsには直接対応する機能がありません。
代替手段として以下があります。
再利用可能ワークフロー(Reusable Workflows): 別のワークフローファイルを呼び出す仕組みです。GitLab CIの extends よりも粒度が大きく、ワークフロー単位での再利用になります。
Composite Actions: 複数のステップをまとめてアクションとして定義できます。extends に近い使い方ができますが、別ファイルに切り出す必要があります。
今回のサンプルは単一ワークフローのシンプルな構成のため、これらは使用していません。
タグ条件の書き方
GitLab CIでは以下のようにタグ時のみ実行する条件を書けます。
rules:
- if: '$CI_COMMIT_TAG'
when: always
- when: never
GitHub Actionsでの同等の書き方はこうなります。
on:
push:
tags:
- 'v*' # v始まりのタグ時のみ実行
または特定のジョブだけタグ時に実行したい場合は if で条件を指定します。
jobs:
deploy:
if: startsWith(github.ref, 'refs/tags/')
runs-on: ubuntu-latest
steps: ...
まとめ:GitLab CI → GitHub Actions 対応表(再掲)
| やりたいこと | GitLab CI | GitHub Actions |
|---|---|---|
| 実行環境の指定 | image |
runs-on |
| コマンド実行 | script |
run |
| 事前処理 | before_script |
別 step として定義 |
| 実行順序の制御 | stages |
needs |
| 成果物の保存 | artifacts |
actions/upload-artifact |
| 成果物の取得 | 自動 | actions/download-artifact |
| 失敗を許容 | allow_failure: true |
continue-on-error: true |
| 常に実行 | when: always |
if: always() |
| 条件分岐 |
rules / only
|
if / on
|
| テンプレート再利用 | extends |
Reusable Workflows / Composite Actions |
| ジョブ間の依存 | needs |
needs |
| CI定義済み変数 |
$CI_COMMIT_BRANCH など |
${{ github.ref }} など |
| 変数の定義 | variables |
env |
| 複数処理の結果をまとめて判定 | ジョブを分けて needs でつなぐ |
id + steps.<id>.outcome
|
id はGitLab CIの何に相当するか
id に厳密に対応するGitLab CIの機能はありません。発想として一番近いのは、needs で複数ジョブの結果をまとめて後続ジョブで処理するパターンです。
GitLab CIでは処理をジョブに分けて needs でつなぐことで、複数の処理結果をまとめて扱います。
# GitLab CI:ジョブを分けてneedsでつなぎ、reportジョブでまとめる
test:
stage: test
script:
- ./mybinary > runlog.log
lint-cppcheck:
stage: analyze
script:
- cppcheck --enable=all main.c
report:
stage: post_stage
needs:
- test
- lint-cppcheck
script:
- cat runlog.log
- cat cppcheck_result.txt
一方GitHub Actionsでは、同じことを同一ジョブ内のステップとして書き、id で各ステップに名前をつけて結果を参照します。
# GitHub Actions:同一ジョブ内でidを使って結果をまとめる
- name: Run unit tests
id: test
run: make test
- name: Run cppcheck
id: cppcheck
run: cppcheck --enable=all main.c
- name: Final status check # reportジョブに相当
if: always()
run: |
if [[ "${{ steps.test.outcome }}" == "success" &&
"${{ steps.cppcheck.outcome }}" == "success" ]]; then
echo "🎉 All checks passed!"
else
echo "❌ Some checks failed!"
exit 1
fi
つまり「GitLab CIではジョブを分けてneedsでつなぐ」ところを、「GitHub Actionsではステップをidで識別してoutcomeで参照する」という設計の違いがあります。どちらも「複数の処理結果をまとめて判定する」という目的は同じです。
GitHub ActionsはGitLab CIと考え方は似ていますが、「ジョブの中にステップがある」という構造と、artifactsがアクションとして実装されている点が大きな違いです。また id + outcome のようにステップ単位で結果を扱える点はGitLab CIにはない特徴です。GitLab CIの経験があれば、この対応関係を押さえるだけで比較的スムーズに読み解けると思います。
本シリーズはこれで完結です。#1〜#5を通して、C言語プロジェクトへのCI導入から
GitLab CIとの比較まで一通りカバーしました。