こんにちは!チームの信頼性を支えるCI/CDパイプライン、日々構築していますか?
開発を進める中で、「自分の手元のMacでは完璧に動いたのに、CI(GitHub Actions)のWindows環境でなぜかテストが落ちる……」「改行コードのせいでシェルスクリプトが謎のエラーを吐いた」といった、OSの差異に悩まされた経験はありませんか?
世界中のエンジニアが一度は通るこの「クロスプラットフォームの罠」。今回は、GitHub ActionsでWindows、macOS、Linuxという主要3大OSの挙動の違いを華麗に吸収し、どこで動かしてもビクともしない堅牢なパイプラインを作るための極意を、優しく丁寧にお伝えします。
これをマスターすれば、OSごとの細かい挙動の差異に頭を悩ませる日々から解放され、毎日の開発作業が劇的に楽になりますよ!
—
1. なぜクロスプラットフォーム開発は難しいのか?
私たちが普段何気なく書いているスクリプトやコマンドは、実はOSごとに異なる「常識」の上で動いています。GitHub ActionsでマルチOS(`matrix`戦略など)を使うとき、以下の3大落とし穴にハマりがちです。
1. デフォルトシェルの違い
- Linux / macOS: `bash` が標準
- Windows: `pwsh` (PowerShell Core) または `cmd.exe` が標準
2. パス区切り文字と環境変数の違い
- Linux / macOS: `/` (スラッシュ)、`${VAR}`
- Windows: `\` (バックスラッシュ)、`$env:VAR` または `%VAR%`
3. 改行コード(CRLF vs LF)の悪夢
- Windowsで保存されたスクリプト(`CRLF`)をLinuxのBashで実行すると、行末の `\r` を解釈できずに `command not found` などの不可解なエラーが発生します。
これらをすべて個別のOSごとにワークフローファイルへ書き下ろしていると、コードが重複して保守性最悪のスパゲッティ状態になってしまいます。
—
2. 基礎のセットアップ:GitHub Actionsの基本を知る
まずは、GitHub Actionsが何をするものなのか、そしてどうやって動かすのかをサクッと確認しておきましょう。
GitHub Actionsは、GitHubのサーバー上でコードのビルド、テスト、デプロイなどの作業を自動化(CI/CD)してくれるツールです。リポジトリの `.github/workflows/` ディレクトリの中にYAMLファイルを置くだけで動き出します。
最初の第一歩:HelloWorldワークフロー
まずは、3つのOSすべてで「Hello, World!」を表示する、最もシンプルなクロスプラットフォーム・ワークフローを見てみましょう。
.github/workflows/hello.yml
name: Hello World Matrix
on: [push]
jobs:
build:
name: Run on ${{ matrix.os }}
runs-on: ${{ matrix.os }}
# 3つのOSをマトリックス(網羅的)に実行する
strategy:
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Say Hello
run: echo “こんにちは!現在のOSは ${{ matrix.os }} です。”
これをリポジトリに配置してプッシュするだけで、GitHubが裏側でLinux、macOS、Windowsの仮想マシンを立ち上げ、それぞれの環境で挨拶を実行してくれます。これがすべての自動化の基礎となります!
—
3. 現場で使える!OS差異を吸収する「共通化シェルスクリプト術」
ここからが本題です。OSごとに違うシェルを使うのではなく、「すべてのOSで同じスクリプトを動かす」ための実践的なテクニックを解説します。
秘訣①:GitHub Actionsのデフォルトシェルを統一する
実は、GitHub Actionsの `run` ステップは、指定しないとOSごとのデフォルトシェル(WindowsならPowerShell)で実行されてしまいます。これを、Windows環境であってもGit for Windowsに同梱されている `bash` を強制的に使わせることで、スクリプトの共通化が一気に進みます。
ワークフローファイル全体、またはジョブ単位で `defaults.run.shell` を指定するのがプロの技です。
jobs:
test:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
# ここでWindowsを含めてデフォルトシェルをbashに統一!
defaults:
run:
shell: bash
steps:
- uses: actions/checkout@v4
- name: Run unified script
run: ./scripts/ci-task.sh
これによって、Windows上であってもBashの強力な構文(環境変数展開や条件分岐など)をそのまま利用できるようになります。
—
秘訣②:改行コード(CRLF)問題の完全封じ込め
Windows環境で開発していると、知らず知らずのうちにファイルの改行コードが `CRLF` になってしまいます。これを防ぐために、Gitの機能とGitHub Actionsの設定を組み合わせます。
1. リポジトリのルートに `.gitattributes` を配置し、シェルスクリプトは必ず `LF` でチェックアウトするように強制します。
.gitattributes
.sh text eol=lf
2. さらに、`actions/checkout` アクションの段階でも明示的に挙動を制御できますが、`.gitattributes` を正しく設定しておけば、どのOSでチェックアウトしてもLinux標準の `LF` を維持できます。これで「`\r`: command not found」のエラーとは永遠にお別れです。
—
秘訣③:OS固有の分岐が必要な場合のスマートな書き方
とはいえ、ファイルパスの構造や拡張子(例: Linuxのビルド成果物はバイナリ、Windowsは `.exe`)など、どうしてもOSごとに処理を分けたい瞬間は訪れます。
その場合、シェルスクリプト(Bash)の中で環境変数 `RUNNER_OS` を参照してスマートに分岐させましょう。GitHub Actionsは、実行中のOS名を自動的に環境変数として渡してくれます。
以下は、全OS対応の共通スクリプト(`scripts/build.sh`)のサンプルです。
!/usr/bin/env bash
set -euo pipefail
echo “=== ビルドプロセス開始 (OS: ${RUNNER_OS}) ===”
OSごとの拡張子や挙動の吸収
if [ “${RUNNER_OS}” = “Windows” ]; then
BINARY_NAME=”myapp.exe”
echo “Windows向けの特別な前処理を実行中…”
elif [ “${RUNNER_OS}” = “macOS” ]; then
BINARY_NAME=”myapp”
echo “macOS向けの特別な前処理を実行中…”
else
BINARY_NAME=”myapp”
echo “Linux向けの特別な前処理を実行中…”
fi
共通のビルド処理
echo “Building ${BINARY_NAME}…”
実際のコンパイルコマンドなどをここに記述
このように、ワークフロー側で複雑な分岐を書くのではなく、スクリプト側の `RUNNER_OS` 判定に閉じ込めることで、ローカルの手元(Linux/Mac/WSL)でも同じスクリプトをテストしやすくなります。
—
4. まとめ:クロスプラットフォームを制する者はCI/CDを制する
今回は、GitHub Actionsにおけるクロスプラットフォーム開発の勘所と、OSの差異を美しく吸収する共通化シェルスクリプト術について解説しました。
- デフォルトシェルを `bash` に統一することで、OSごとの文法の違いを最小限にする。
- `.gitattributes` で改行コード(CRLF/LF)のトラブルを根本から断つ。
- 環境変数 `RUNNER_OS` を活用し、OSごとの差異はスクリプト内でスマートに吸収する。
この設計を取り入れるだけで、あなたのパイプラインは驚くほど頑健になり、「手元では動くのにCIで落ちる」というストレスから解放されます。
これをマスターすれば、毎日の開発作業が劇的に楽になりますよ!ぜひ、次のプロジェクトのワークフローに取り入れてみてください。あなたのCI/CDライフがより快適になることを応援しています!