6
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

できるだけ広範囲のmacOS環境でネイティブ実行できるjqバイナリをビルドする

Last updated at Posted at 2023-11-16

jq とは

jq コマンドは,便利なJSONパーサーとして(そして単なるパーサーを超えてプログラミング言語としても)知られています。jq で何ができるかは,次のページが参考になるでしょう。

jq のインストール法3選

さて,macOS に jq をインストールする方法としては,次のような方法が考えられます。

  1. Homebrew や MacPorts といったパッケージ管理システムを利用する
  2. 公式配布バイナリを利用する
  3. 自分でソースコードからビルドする

既に Homebrew や MacPorts を使っている方であれば,1. が簡単でしょう。

Homebrewの場合
$ brew install jq
MacPortsの場合
$ sudo port install jq

パッケージ管理システムによるインストールの欠点

自分一人で使う分には Homebrew や MacPorts で困らないのですが,この jq のバイナリを自作アプリに同梱して配布するなど,他のマシンへと移植する場合を考えると,バイナリのポータビリティが気になります。

ライブラリの依存性

MacPortsの場合

例えば,MacPorts でインストールされる /opt/local/bin/jq が何に依存しているか,その依存ライブラリを調べてみましょう。otool コマンドが役立ちます。

MacPorts でインストールされる jq バイナリの依存ライブラリの調査
$ otool -L /opt/local/bin/jq
/opt/local/bin/jq:
	/usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1336.0.0)
	/opt/local/lib/libonig.5.dylib (compatibility version 10.0.0, current version 10.0.0)

/opt/local/lib/ から始まるパスが現れていることから,この jq バイナリは,MacPorts でインストールされる oniguruma ライブラリ(これは正規表現ライブラリ鬼車です)に依存してしまっていることが分かります。これでは,この jq バイナリを単に他のマシンにコピーするだけでは動きません。

Homebrewの場合

Homebrew でインストールされる /usr/local/bin/jq についても同様に,Homebrew の依存関係解決に基づきインストールされる oniguruma ライブラリの存在に依拠しています。

Homebrew でインストールされる jq バイナリの依存ライブラリの調査
$ otool -L /usr/local/bin/jq
/usr/local/bin/jq:
	/usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1319.100.3)
	/usr/local/opt/oniguruma/lib/libonig.5.dylib (compatibility version 9.0.0, current version 9.0.0)

バイナリの対象アーキテクチャ

また,バイナリの対象アーキテクチャを調べてみましょう。file コマンドが使えます。

MacPorts でインストールされる jq バイナリの対象CPUアーキテクチャの確認
$ file /opt/local/bin/jq
/opt/local/bin/jq: Mach-O 64-bit executable arm64

この MacPorts は Apple Silicon 上で実行しているので,arm64バイナリが生成されています。これでは,Intel Mac 上にこのバイナリを持っていったときに実行できません。

逆に,x86_64 バイナリを Apple Silicon 上で実行するのは,Rosetta 2 をインストールしてあれば可能ではありますが,Rosetta 2 がインストールされているマシンでなければ動きませんし,動作パフォーマンスも下がるので,好ましくありません。

起動OSの最低バージョン

このバイナリが起動できる最低OSバージョンを調べてみます。otool -l が役立ちます。

MacPorts でインストールされる jq バイナリの最低動作要件の確認
$ otool -l /opt/local/bin/jq | grep minos
    minos 14.0

この MacPorts は,macOS 14 Sonoma 上で実行しているため,その上でデフォルトで生成されたバイナリは,起動できるOSバージョンの最低値が macOS 14 と設定されてしまっています。これでは,macOS 13 以前の環境に持っていったときに使えません。Homebrew の場合も同様に,実行しているホストOSを最低動作環境とするバイナリがインストールされます。

公式配布バイナリ

次に,jq の公式配布バイナリの状況を調べてみます。公式サイトでは,AMD64 (Intel CPU) 向けバイナリと ARM64 (Apple Silicon) 向けバイナリがそれぞれ配布されています。

image.png

最新の jq Ver.1.7 の公式バイナリについて,それぞれの状況を調べてみましょう。

Intel CPU 向け公式バイナリ

Intel CPU 向け公式バイナリの依存ライブラリの調査
$ file jq-macos-amd64
jq-macos-amd64: Mach-O 64-bit executable x86_64
$ otool -L jq-macos-amd64
jq-macos-amd64:
	/usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1319.100.3)
$ otool -l jq-macos-amd64 | grep minos
    minos 13.0

Apple Silicon 向け公式バイナリ

Apple Silicon 向け公式バイナリの依存ライブラリの調査
$ file jq-macos-arm64
jq-macos-arm64: Mach-O 64-bit executable arm64
$ otool -L jq-macos-arm64
jq-macos-arm64:
	/usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1319.100.3)
$ otool -l jq-macos-arm64 | grep minos
    minos 13.0

このように,Intel CPU 向けバイナリ,Apple Silicon 向けバイナリのどちらも,

  • 単一アーキテクチャ用バイナリ
  • 依存性はOS標準のライブラリのみ
  • 要求OSバージョンは macOS 13 Ventura 以降

となっていることが分かります。macOS 13 Ventura は,本記事執筆時点では最新OSの1つ前のバージョンです。ちょっと要求水準が高めですね。

もっと広範囲の環境で動くようにしたい

他のマシンへのポータビリティを高めるべく,次のような条件を見たす jq バイナリが欲しいです。

  • Intel CPU / Apple Silicon のどちらでもネイティブ起動できる Universal Binary である
  • OS標準のライブラリにのみしか依存しない
  • できるだけ広い範囲のOS環境で起動できる

このようなバイナリを得るためには,ソースコードから自前で jq をビルドする必要があるでしょう。

ソースコードからの jq バイナリのビルド

公式サイトの説明によると,バイナリのビルド&インストール方法は次のように説明されています。

git clone --recursive https://github.com/jqlang/jq.git
cd jq
autoreconf -i
./configure
make
sudo make install

この案内に沿ってバイナリのビルドを進めましょう。

autoreconf できるようにする

このためには,autoreconf コマンドが必要です。そのためには,autoconf をインストールしておきましょう。ここは Homebrew や MacPorts を使うのが簡単です。

Homebrewの場合
$ brew install autoconf 
MacPortsの場合
$ sudo port install autoconf 

続きをやってみます。

$ git clone --recursive https://github.com/jqlang/jq.git
$ cd jq
$ autoreconf -i
Can't exec "aclocal": No such file or directory at /usr/local/Cellar/autoconf/2.71/share/autoconf/Autom4te/FileUtils.pm line 274.
autoreconf: error: aclocal failed with exit status: 2

何やらエラーが出ました。これは autoreconfaclocal も必要とするからです。このためには,automake をインストールしておきましょう。aclocalautomake に付随してインストールされます。

Homebrewの場合
$ brew install automake 
MacPortsの場合
$ sudo port install automake 

改めて autoreconf にチャレンジします。

$ autoreconf -i
src/Makefile.am:24: error: Libtool library used but 'LIBTOOL' is undefined

またエラーが出ました。今度は libtoool をインストールします。

Homebrewの場合
$ brew install libtool 
MacPortsの場合
$ sudo port install libtool 

これで,autoreconf -i が通るようになります。

Homebrew を使っている場合の注意:oniguruma のアンインストール

Homebrew を使っている場合1で,既に Homebrew によって oniguruma がインストールされていると,以下の方法でビルドされる jq バイナリが Homebrew の oniguruma を参照してしまいます2。よって,事前に Homebrew の oniguruma をアンインストールしておきます。

$ brew uninstall oniguruma

まずはビルドしてみる

では,この状態でビルドしてみましょう。configure & make をしてみます。

$ ./configure
$ make

すると,同ディレクトリ内に jq バイナリが生成されます。

まずは依存ライブラリを確認します。

$ otool -L jq
jq:
	/usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1336.0.0)

システム標準のライブラリのみを参照しており,期待通りです。

ただし,この方法でビルドするだけでは,

  • ホストマシンと同じCPUアーキテクチャのみを動作対象とする
  • ホストマシンのOSバージョンを最低動作要件とする

バイナリが生成されてしまいます。これでは困るので,対象アーキテクチャと最低動作要件を明示指定したバイナリを作りましょう。
そのためには,CFLAGS, CXXFLAGS, LDFLAGS にオプションを付け加えます。

Apple Silicon 向けバイナリのビルド

Apple Silicon 搭載 Mac は,最低でも macOS 11 Big Sur です。よって,動作要件としては macOS 11 以上を指定すれば十分です。

clangの -arch オプションによるクロスコンパイル機能を使えば,コンパイルを実行しているホストマシンのCPUが Intel CPU / Apple Silicon のいずれであっても,x86_64 / arm64 の双方のバイナリを生成できます。

次のように,CFLAGS, CXXFLAGS, LDFLAGS のオプションを指定して,autoreconf -i を実行します。

$ export CFLAGS="-arch arm64 -mmacosx-version-min=11.0"
$ export CXXFLAGS="-arch arm64 -mmacosx-version-min=11.0"
$ export LDFLAGS="-arch arm64 -mmacosx-version-min=11.0"
$ autoreconf -i

次に configure スクリプトを実行します。ただし,クロスコンパイルになる場合(ホストマシンのアーキテクチャと生成バイナリのアーキテクチャが異なる場合)は,configure スクリプトの実行に,--host オプションでホストマシンのアーキテクチャを明示指定しておく必要があります。

ホストが Apple Silicon の場合
$ ./configure
ホストが Intel CPU の場合
$ ./configure --host=x86_64 

そしていよいよ make します。既に make を実行していたのであれば,一旦 make clean しておきましょう。

$ make clean
$ make

そして,生成された jq バイナリの状況をチェックします。

依存ライブラリのチェック

$ otool -L jq
jq:
	/usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1336.0.0)

対象アーキテクチャのチェック

$ file jq
jq: Mach-O 64-bit executable arm64

最低動作OS要件のチェック

$ otool -l jq | grep minos
    minos 11.0

いずれも期待通りの結果となりました。このバイナリを別名で退避しておきます。

$ cp jq jq-arm64

Intel CPU 向けバイナリのビルド

Intel CPU の Mac は昔からあります。最初期のものは MacOS X 10.5 Leopard のあたりです。よって,動作要件を MacOS X 10.5 Leopard 以上とするバイナリを生成してみましょう。

$ export CFLAGS="-arch x86_64 -mmacosx-version-min=10.5"
$ export CXXFLAGS="-arch x86_64 -mmacosx-version-min=10.5"
$ export LDFLAGS="-arch x86_64 -mmacosx-version-min=10.5"
$ autoreconf -i

そして,configure スクリプトを実行します。ホストが Apple Silicon の場合はクロスコンパイルを指定します。

ホストが Apple Silicon の場合
$ ./configure --host=arm64
ホストが Intel CPU の場合
$ ./configure

そして make します。

$ make clean
$ make

そして,生成された jq バイナリの状況をチェックします。

依存ライブラリのチェック

$ otool -L jq
jq:
	/usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1336.0.0)

対象アーキテクチャのチェック

$ file jq
jq: Mach-O 64-bit executable x86_64

最低動作OS要件のチェック

最低動作OS要件のチェックは otool -l で行えますが,少し昔のOS用のバイナリの場合,出力の形式が異なり,grep minos で探せません。otool -l の出力の中の LC_VERSION_MIN_MACOSX という行の2行下の version でチェックします。

$ otool -l jq
(中略)
      cmd LC_VERSION_MIN_MACOSX
  cmdsize 16
  version 10.5
      sdk 14.0
(後略)

すると,正しく MacOS X 10.5 Leopard を最低動作要件とするバイナリが生成されていることが分かります。

このバイナリを別名で退避しておきます。

$ cp jq jq-x86_64

Universal Binary として結合する

最後に,こうして得られた

  • macOS 11.0 Big Sur 以上で動作する Apple Silicon 向けバイナリ jq-arm64
  • MacOS X 10.5 Leopard 以上で動作する Intel CPU 向けバイナリ jq-x86_64

を Universal Binary として結合しましょう。バイナリの結合や分解には lipo コマンドを使います。

$ lipo -create -output jq jq-arm64 jq-x86_64

こうして得られた jq バイナリを確認します。

$ file jq
jq: Mach-O universal binary with 2 architectures: [x86_64:Mach-O 64-bit executable x86_64] [arm64]
jq (for architecture x86_64):	Mach-O 64-bit executable x86_64
jq (for architecture arm64):	Mach-O 64-bit executable arm64

確かに Universal Binary となっています!

これで,めでたくできるだけ広範囲の macOS 環境でネイティブ実行できる jq バイナリが得られました!これならば,どこの環境に持っていっても確実に動作するので,安心して頒布可能です。以上のような,クロスコンパイルも用いた Universal Binary のビルド方法は,jq に限らず,広く活用できることでしょう。

  1. MacPorts の場合は,/opt 以下に分離されているため,MacPorts の oniguruma がインストールされていても,そちらを参照しません。よって oniguruma のアンインストールは不要です。

  2. configure スクリプトが,ホストマシンにインストールされた oniguruma の有無をチェックしています。インストールされている場合,そちらを動的リンクするようにビルドされてしまいます。インストールされていない場合は,jq のソースコードレポジトリに同梱された oniguruma が静的リンクされます。

6
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?