Plan 9とGo言語のブログ

主にPlan 9やGo言語の日々気づいたことを書きます。

GitHub ActionsでC言語のコードをクロスコンパイルする

GitHub ActionsではARM64ランナーも公開されつつありますが、ここでは gcc を使ったクロスコンパイルを説明します。この記事ではホスト*1アーキテクチャx86_64、ターゲット*2アーキテクチャarm64 としていますが、他のターゲットでも同様の手順となるでしょう。また、C言語を前提に書いていますが、他の言語でもライブラリをリンクする場合は参考になるんじゃないかなと思います。

aptリポジトリの準備

まずはターゲットとなるアーキテクチャをパッケージ管理システムに追加します。

sudo dpkg --add-architecture arm64

GitHub Actionsのubuntuランナーにはx86パッケージのリポジトリしか設定されていないので、ARMパッケージがあるaptリポジトリのURLを/etc/apt/sources.list.d/arm64.listに設定します。

deb [arch=arm64] http://ports.ubuntu.com/ubuntu-ports jammy main restricted
deb [arch=arm64] http://ports.ubuntu.com/ubuntu-ports jammy-security main restricted
deb [arch=arm64] http://ports.ubuntu.com/ubuntu-ports jammy-updates main restricted

また、GitHub Actionsランナーの /etc/apt/sources.listアーキテクチャを制限していないので、このままだと arm64 パッケージも探してしまって警告が出力されます。どのみちデフォルトのリポジトリarm64 パッケージは用意されていないので、探さないよう arch= オプションを設定しておきます。

sudo sed -i -E '/^deb(-src)? ([^[])/s/ / [arch=amd64,i386] /' /etc/apt/sources.list

sources.list の各項目がどんな意味なのかは以下の記事が分かりやすいと思います。

kujira16.hateblo.jp

コンパイラとライブラリのインストール

ここまで終われば、コンパイラと必要なライブラリをインストールしましょう。

sudo apt update
sudo apt install -y gcc-aarch64-linux-gnu
sudo apt install -y libsystemd-dev libsystemd-dev:arm64 # libsystemdをリンクしたい場合の例

このとき、 gcc では aarch64 の部分がターゲーットのアーキテクチャ名になります。また、リンクするライブラリはパッケージ名の後ろに :arm64 のようにアーキテクチャ名を追加します。

arm64とaarch64の関係

ここまでで、 arm64aarch64 といった名称を使いましたが、何が違うのでしょうか。

これらの名前は、aarch64 は命令セットの名前に、arm64 はARMプロセッサの64bitアーキテクチャに由来します。クロスコンパイルする状況においては結局どちらも64bit ARMを意味していますが、歴史的な事情によって使っている名称が異なります。

以下は各システムがどちらの表記を使っているかまとめた表です。せっかくなのでx86の表記も加えてみました。

システム x86 ARM
GCC x86_64 aarch64
Clang x86_64 aarch64 1
GNU x86_64 arm64
Debian/Ubuntu amd64 arm64
RHEL x86_64 aarch64
Plan 9 amd64 arm64
Go amd64 arm64
Windows x64 2 arm64

なので、gccパッケージのターゲット名は aarch64 となっているし、ライブラリのアーキテクチャ名はDebian/Ubuntu命名に沿うので libsystemd:arm64 表記が使われているわけですね*3

ソースコードをビルドする

ビルドするときはターゲット用の gcc を使えばいいだけです。 make を使っている場合は CC 変数にセットします。

make CC=aarch64-linux-gnu-gcc

ライブラリのリンク等は、ターゲット用の gcc がターゲット用のライブラリを探してくれるので、開発者が意識することはありません。

ワークフロー

ここまでのワークフローをまとめます。

steps:
  - name: Add an architecture to install packages
    run: |
      sudo dpkg --add-architecture arm64
      sudo sed -i -E '/^deb(-src)? ([^[])/s/ / [arch=amd64,i386] /' /etc/apt/sources.list

      source /etc/lsb-release
      o="$(mktemp)"
      url='http://ports.ubuntu.com/ubuntu-ports'
      echo "deb [arch=arm64] $url $DISTRIB_CODENAME main restricted" >>"$o"
      echo "deb [arch=arm64] $url $DISTRIB_CODENAME-security main restricted" >>"$o"
      echo "deb [arch=arm64] $url $DISTRIB_CODENAME-updates main restricted" >>"$o"
      sudo install -m 644 "$o" /etc/apt/sources.list.d/arm64.list
  - name: Build sources
    run: |
      sudo apt update
      sudo apt install -y gcc-aarch64-linux-gnu
      sudo apt install -y libsystemd-dev libsystemd-dev:arm64
      make CC=aarch64-linux-gnu-gcc

リポジトリの設定部分はlufia/workflows/.github.actions/setup-multiarchとして複合ワークフローにしておいたので、よければ使ってください。

steps:
    - uses: lufia/workflows/.github/actions/setup-multiarch@v0.5.0
      with:
        arch: arm64

  1. AppleバックエンドのことをARM64と呼んでいたがAArch64に統合された
  2. x86-64 表記もあるが、x64 の方が多いと思う

*1:ソースコードコンパイルする側

*2:ビルドされたバイナリを実行する側

*3:理屈は分かるけど紛らわしいので統一してほしい