MacOSの配布用Qtアプリケーション作りが難解過ぎた

黒基調のウィンドウは格好良いです。

1週間以上掛けて、ようやく配布用アプリケーションが作成できました。
MacOS版チケットぴあ自動購入の処理は、1年前のソースコードをそのまま持ってきました。(プラスnewspプラグインの処理を入れています)
MacOS版の方がWindows版よりも購入できるという報告をちらほら聞くため、敢えてそのようにしています。Success PostでMac版を使った時にはそれが分かるようにしているので、そこで判断できます。

今のバージョンである、macOS Sequoiaは、AppleシリコンのM1以降でのみ対応されており、そのCPUでビルドしたアプリケーションを作成する必要がありました。

別件でMac Miniを購入したことと、ユーザからの要望があったので作成してみようと思った訳ですが、情報が殆どないためAIサービスに頼るしかありませんでした。

最初は、ChatGPTに聞いていたのですが、開発者IDを取得するところで躓いてしまい、先に進めなくなってしまったため、ChatGPTよりも賢いと評判のClaudeに相談することにしました。

Apple M1以降のCPUを搭載しているMacOS用に配布するためのQtアプリケーションを作成するには、アプリケーションに開発者IDを署名する必要があります。

公式には以下に作成方法が書かれているのですが、中途半端な開発者ID証明書を作成するところまでは簡単にできます。
https://developer.apple.com/jp/help/account/create-certificates/create-developer-id-certificates/

xcodeから作成するのが手っ取り早い。

結論から言うと、開発者ID証明書には、中間証明書とCA証明書を付加する必要があります。

Apple Root CAは以下からダウンロードして、キーチェーンアクセス.appにダブルクリックして取り込みます。
https://www.apple.com/certificateauthority/

中間証明書のありかは、Claudeに聞いて取得してきました。

1.G2の中間証明書を直接ダウンロード:
curl http://certs.apple.com/devidg2.der -o devidg2.der
2.ダウンロードした証明書をインストール:
sudo security add-trusted-cert -d -r trustRoot -k /Library/Keychains/System.keychain devidg2.der

xcode→Settings→Accounts→Manage Certificatesで以下の画面を開き、+ボタンからDeveloper Application IDを指定すると、CAルート証明書までリンクしている自己証明書が作成されます。

コマンドで「security find-identity -v -p codesigning」を実行して、Developer ID Applicationがvalid状態であり且つ、ルートCA証明書まで正しく登録できていれば証明書の作成は完成です。

次に、アプリケーションに署名を付与していきます。

プロジェクト内に「entitlements.plist」を作成し、内容を以下にします。

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.cs.allow-jit</key>
<true/>
<key>com.apple.security.cs.allow-unsigned-executable-memory</key>
<true/>
<key>com.apple.security.cs.disable-library-validation</key>
<true/>
<key>com.apple.security.network.client</key>
<true/>
</dict>
</plist>

proファイルに以下の項目を追加します。

macx {
ICON = res/ticketpia.icns
QMAKE_INFO_PLIST = Info.plist
ENTITLEMENTS.files = entitlements.plist
ENTITLEMENTS.path = Contents
QMAKE_BUNDLE_DATA += ENTITLEMENTS
}

ビルドを行い、macdeployqtにて必要なライブラリをapp内に取り込みます。QtWebEngine.appも同様に行います。

% macdeployqt ticketpia.app 
% macdeployqt ticketpia.app/Contents/Frameworks/QtWebEngineCore.framework/Helpers/QtWebEngineProcess.app

# パスを変更
install_name_tool -change /opt/homebrew/opt/brotli/lib/libbrotlidec.1.dylib @executable_path/../Frameworks/libbrotlidec.1.dylib ticketpia.app/Contents/MacOS/ticketpia

install_name_tool -change /opt/homebrew/opt/brotli/lib/libbrotlienc.1.dylib @executable_path/../Frameworks/libbrotlienc.1.dylib ticketpia.app/Contents/MacOS/ticketpia

install_name_tool -change /opt/homebrew/opt/brotli/lib/libbrotlicommon.1.dylib @executable_path/../Frameworks/libbrotlicommon.1.dylib ticketpia.app/Contents/MacOS/ticketpia

以下にてアプリケーションに署名していきます。

# 拡張属性をクリア
xattr -cr ticketpia.app

# メインアプリの署名
codesign --deep --force --verify --verbose \
--sign "Developer ID Application: KAZUHIRO OZAWA (VSAYCF9RQD)" \
--options runtime ticketpia.app

# WebEngineProcessの署名(entitlementsを指定)
codesign --deep --force --verify --verbose \
--sign "Developer ID Application: KAZUHIRO OZAWA (VSAYCF9RQD)" \
--options runtime \
--entitlements ticketpia.app/Contents/entitlements.plist \ ticketpia.app/Contents/Frameworks/QtWebEngineCore.framework/Helpers/QtWebEngineProcess.app

この2つの署名を行わないと、例えばWebEngineProcessの署名を行わない場合、ticketpia.appのアプリは起動するがWebEngineが表示されずに落ちてしまう現象が発生します。

この情報がどこかに刺さったら嬉しいです。

I2C通信に触れてみた

最近仕事でI2C通信に触れる機会があり、触ったことが無かったため、体験する良い機会だと思い、自分で機材を買って動作確認を行った。

OSはUbuntu 22.04を使った。最近Ubuntuに嵌ってしまい、Windowsに戻りたくない病を患っている。Ubuntuを使ってつくづく思うのは、Windowsはとても恵まれた環境だということだ。使っている人数がUbuntuとは桁違いに多いため、企業が積極的にサポートしているおかげで、ドライバは必ず対応されている。それに比べてLinux環境は、最新のものを使おうとするとドライバが自動でインストールされないことがあるため、自分で探してきてインストールする必要があったり、ドライバのアクセス権を宣言しないとドライバ自体が使えなかったりと色々な知識やググりが必要になる。

今回I2C通信を体験するにあたり買った機材は、以下の2つ。

サンハヤト USB・I2C(SMBus)変換モジュール MM-CP2112
https://www.sunhayato.co.jp/material2/ett09/item_1052
→USBポートからI2Cへ変換するCP2112というICが搭載されたもの。I2Cのマスター側として動作する。

I2C通信のスレーブ側として動作させるのが以下のモジュール。

Grove-I2C高精度温度および湿度センサー(SHT35)

https://jp.seeedstudio.com/Grove-I2C-High-Accuracy-Temp-Humi-Sensor-SHT35.html

I2C通信は、WriteとReadを同時に行うことはできない。Writeしたい場合は、I2Cアドレスに何バイトのデータを書き込むことを伝えて、ACKを受信後、書き込むデータをWriteする。Readしたい場合も同様に、I2Cアドレスに何バイトのデータを読み込むことを伝えて、ACKを受信後、読み込むデータをReadする。主導権は常にマスター側にある。この0x45(default)とox44(optional)の使い方がサンプルソースを読んでも理解できず、2〜3時間悩んだ。SHT35のサンプルソースは以下だが、Arduinoという評価ボードから実行するためのコードであるため、CP2112のAPIとは異なるArduino専用のAPIを使っている点が分かり辛さを増幅していた。

https://github.com/Seeed-Studio/Seeed_SHT35

SHT35のデータシート(以下を参照)には確かに「The ADDR pin must not be left floating. Please note that only the 7 MSBs of the I2C Read/Write header constitute the I2C Address.(I2C Read/Write ヘ ッ ダ の 7 MSB だ け が I2C Address を構成することに注意してください。)」と書いてあるし、
https://github.com/SeeedDocument/Grove-I2C_High_Accuracy_Temp-Humi_Sensor-SHT35/raw/master/res/Datasheet%20SHT3x-DIS.pdf

CP2112のAPI仕様にも「slaveAddress is the address of the slave device to write to. This value must be between 0x02 – 0xFE. The least significant bit is the read/write bit for the SMBus transaction and must be 0.(アドレスはデバイスのスレーブアドレス(0x02-0xFE)です。デバイスはこのアドレスのみを認識します。デフォルトは 0x02 です。最下位ビットはSMBusトランザクションのリード/ライト・ビットで、0でなければなりません。)」と何故か1バイトのうち、1ビット目だけ指定できないようになっている点に疑問を持たなかったのが敗因だが、よくよく考えてみれば不自然な範囲である。

上の図の8ビット目のところは、I2C通信プロトコル固有のビットでWirteなら0、Readなら1に使われる。CP2112のAPI仕様のI2Cアドレスは0x02〜0xFEまでしか指定できないのは、1ビット目が固有ビットになっているから空けておけという意味なのである。
上手く行かなくて悩んでいるとき、「空ける必要があるのであれば、0x45は空いてないから0x44にしないと行けないのかな?」と訳の分からない解釈をしてわざわざジャンパーを0x44に切り替えるために、カッターと半田作業をして余計なことをしていた。。。


以下に今回試したソースコードをアップしました。0x44 << 0x1としている点が味噌です。
https://github.com/zattu1/i2c

上記を動かすには、Qtフレームワークが必要なのと、CP2112のサンプルコードにあるSiliconLabs.rulesを/etc/udev/rules.d/に放り込むのと、CP2112のライブラリ群を/usr/local/lib/に放り込む必要がある。

以下はCP2112のSDK
https://jp.silabs.com/documents/public/software/USBXpressHostSDK-Linux.tar