更新した Xcode 14.0.1 で、Alchemusica のビルドをやり直しました。Xcode は、バージョンを新しくするたびに何かしらトラブルを引き起こします。これは、Xcode のせいというよりは、こちらが古いスタイルに固執している(または変なスタイルを我流で作ってしまっている)ことが原因になっていることが多いのです。ただ、私はフルタイムの開発者ではないので、Apple の開発スタイルの進化にその都度ついていくのはちょっと無理です。そのため、Xcode を更新した時に、毎回大騒ぎして新しいスタイルを何とか身につける、というやり方をとっています。というわけで、今回の大騒ぎの記録です。
(1) 古い SDK って使えないの??
これまで、Xcode に付属しなくなった SDKs を xcodelegacy を使ってインストールしていました。しかし、xcodelegacy は Xcode 12 までしか対応していないようです。このツールは、Xcode の内部情報を書き換えたりするので、いつか対応しない日が来るだろうな、とは予想していました。そこで、SDK 自体は Xcode デフォルトのものを使い、Build Settings で macOS Deployment Target を指定するようにします。
Xcode 14.0.1 では、指定できる Deployment Target は 10.9 が最小です。Alchemusica は「10.6 以降対応」としてきましたが、1つのバイナリで 10.6 から最新まで対応するのはそろそろ無理がありそうです。そこで、10.6-10.8 については、古い Xcode で別バイナリを作ることにしました。これについては後述。
(2) CodeSign で失敗する
ビルドは一応通ったのですが、CodeSign コマンドがエラーを出します。
Alchemusica.app: resource fork, Finder information, or similar detritus not allowed
Command CodeSign failed with a nonzero exit code
CodeSign って何をやっているのかイマイチよくわかってないのですが、プロジェクトのルートディレクトリで xattr -rc . を実行したあと、ビルドし直したら解決する、という情報を見ました(参考:「『resource fork, Finder information, or similar detritus not allowed』が出た時の対処方法」@mute さん、Qiita 2023/02/16)。別プロジェクトでは確かにこれで解決したのですが、今回はうまくいかない。よく見ると、エラーを起こしているファイルが Contents/PlugIns/Ruby_Scripts/200.commands.rb でした。素性不明のファイルを PlugIns フォルダに入れているのがいけないのかも?と思って、本体のコードを書き直して Contents/Resources/Ruby_Scripts から読み出すようにしました。これで、CodeSign のエラーは解消しました。
(3) Ruby 2.0 のビルドで警告が多数出る
そんな古いバージョンを使うな、と叱られそうですが、組み込み用に使っているので、うっかりバージョンを上げるといろいろ面倒なわけです。ということで、それぞれ対処します。
rb_protect()を使うコードで、VALUE型をintにキャストすると、cast to smaller integer type 'int' from 'void *'が出る。→intではなくintptr_tにキャストして、そのあとintに再キャストする。INT2NUM(-1)で"shifting a negative signed value is undefined"という警告が出る。→CFLAGSに-Wno-shift-negative-valueを加える。rb_intern()のマクロ展開で'(' and '{' tokens introducing statement expression appear in different macro expansion contextsという警告が出る。→CFLAGSに-Wno-compound-token-split-by-macroを加える。(参考:「clang 12 -Wcompound-token-split-by-macro warning in ruby.h」)ただし、このフラグは Xcode 10 でビルドする時にはつけてはいけない(clang がまだ対応していないため)。
(4) MacOS Deployment Target 関連の条件付きコンパイル
Xcode で MacOS Deployment Target を指定すると、MAC_OS_X_VERSION_MIN_REQUIRED マクロが設定されます。10.9 なら MAC_OS_X_VERSION_10_9, 10.6 なら MAC_OS_X_VERSION_10_6 です。「10.6 にはないけど 10.9 にはある」API を使う時、条件付きコンパイルをしないといけませんが、その時の書き方に注意。うっかり下のように書いて、10.6 SDK を使った時に #if 節の方をコンパイルしてしまう、という不具合に悩まされました。
#if MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_9
[[NSBundle mainBundle] loadNibNamed:@"EventKindContextMenu" owner:kindDataCell topLevelObjects:NULL];
#else
[NSBundle loadNibNamed:@"EventKindContextMenu" owner:kindDataCell];
#endif
10.6 SDK では MAC_OS_X_VERSION_10_9 が定義されていないため、ゼロと評価されてしまって不等式が常に成立してしまいます。正解はこうです。
#if defined(MAC_OS_X_VERSION_10_9) && (MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_9)
[[NSBundle mainBundle] loadNibNamed:@"EventKindContextMenu" owner:kindDataCell topLevelObjects:NULL];
#else
[NSBundle loadNibNamed:@"EventKindContextMenu" owner:kindDataCell];
#endif
(5) MacOS Deployment Target 関連:未定義のメソッドで警告を出したい
MacOS Deployment Target を 10.9, Base SDK を「最新のSDK」でビルドすると、「最新のSDK」に存在している API はすべてコンパイルが通ってしまいます。例えば、10.12 以降で実装されている -[NSWindow convertPointFromScreen:] を使っても、デフォルトでは警告は出ません。このアプリを 10.9〜10.11 で実行すると、API が実装されていないので、このメソッドを使った時点でランタイムエラーが出ます(参考:「Xcode New API Usage Warning: SDK vs Deployment Target」technically speaking, 2014/06/15)。こういう仕様になっているのは、「実装されているかどうかを実行時に判別して、処理を分ける」ためなのですが、意図せず「うっかり」新しい API を使ってしまうことがありますから、警告を出してもらわないと困ります。-Wunguarded-availability を指定すると、警告が出るようになります(参考:「API Availability and Target Conditionals」ePirat's Blog、2019/10/30)。
(6) VMWare 上の Xcode 10.3 で 10.6 用のアプリをビルドする
10.6 対応のアプリ "Alchemusica_Legacy" は、Alchemusica の動作が確認できた後に、VMWare Fusion 上の Mac OS 10.14 + Xcode 10.3 でビルドすることにしました。いちいち Xcode を立ち上げるのは面倒なので、下のスクリプトでコマンドラインからビルドします。
#!/bin/bash
rsync -au -v --exclude='.*' --exclude='_*' --exclude='latest_binaries/*' /Volumes/VMware\ Shared\ Folders/Alchemusica/ ~/Development/Alchemusica/
xcodebuild -scheme Alchemusica-Legacy archive 2>&1 | tee _maclegacy.log
status=${PIPESTATUS[0]}
if [ $status = "0" ]; then
(cd /Volumes/VMware\ Shared\ Folders/Alchemusica/latest_binaries && rm -rf Alchemusica-Legacy.app Alchemusica-Legacy.zip) || exit 1
(cd latest_binaries && cp -af Alchemusica-Legacy.zip /Volumes/VMware\ Shared\ Folders/Alchemusica/latest_binaries) || exit 1
fi
exit 0
大騒ぎは一段落しました。しばらくはこれで開発を続けます。1年に一度ぐらいの更新ですが。