アプリケーションビルド(Mac編)
Apple Silicon/Intelネイティブのハイブリッドアーキテクチャ(Universal Binary 2)アプリをビルドし,ネットワーク経由で配付できるようにコード署名と公証に対応するための手順を説明します。アプリケーションフォルダーにインストールすることができ,ファイルシステムやネットワークにアクセスしてもクラッシュしないようなアプリをビルドするためには,こうした手順を踏む必要があります。
ポイント: ARM (Apple Silicon) ターゲットのコンパイルをするためには,プロジェクトモードでアプリケーションを開発する必要があります。バイナリモード(いわゆるストラクチャファイル)はインタープリターモード専用なので,ビルドすることができません。
- 設定>ストラクチャ設定
コンパイラーページを開き,コンパイル対象CPUを全てのプロセッサに設定します。
ARM (Apple Silicon) ターゲットのコンパイルをするためには,Xcodeがインストールされていなければなりません。
インストール後,最初の起動時にコマンドラインツール(追加コンポーネント)のインストールが始まるはずです。コマンドラインツールがインストールされてない場合,xcode-select --install
でダウンロードしてください。
ARM (Apple Silicon) ターゲットのコンパイルをするためにXcodeのアップデートが必要な場合,下記のメッセージが表示されます。
HelperToolのインストールに失敗した,というメッセージが表示されることがあります。
コンパイルが途中まで進んでいた場合,その回は失敗に終わるかもしれません。
コンパイルコードを削除して再コンパイルすれば成功するはずです。
コンポーネントは,プロジェクトフォルダーのComponents
フォルダーにインストールします。下記いずれかのファイル形式がサポートされています。
- .4DB
- .4DC
- .4DZ
- .4dbase
- プロジェクトフォルダー
.4DZはProject
フォルダーをzip圧縮したファイル形式です。コンパイルされているとは限りません。
Resources
などの関連フォルダーを含めるのであれば,.4dbaseまたはプロジェクトフォルダーをインストールする必要があります。
通常,コンポーネントには複数のプロジェクトに共通の汎用的なモジュールとして開発します。それぞれのプロジェクトのコンポーネントのソースコードをインストールするのでは管理が大変です。それでComponents
フォルダーにエイリアスをインストールすることが推奨されています。下記のように整理できるかもしれません。
- Components: エイリアス
- Components - Releases: コンパイル版のコンポーネント
- Components - Sources: コンポーネントのソースコード
サンプルプロジェクトのOn Startupには,依存コンポーネントのエイリアスを作成するコードが記述されています。
cs.Startup.new().linkComponents().restartIfNecessary()
サンプルプロジェクトのコンポーネントは,下記の要領でコンパイル→ビルド→インストールができるようになっています。
BuildApp.buildComponent()
ダイアログに現在時刻を表示するだけの簡単なプログラムです。
ちなみにフォームはアプリケーションプロセスで表示し,バックグラウンド処理はプリエンプティブモードのワーカーで実行しています。On Timerは使用していません。
アプリケーションをビルドすると,データベースフォルダーのSettings
フォルダーは読み書きができなくなります。設定ファイルを外部ファイルで管理するため,ユーザー設定を有効にする必要があります。
- データベース設定>セキュリティ>外部ファイルのユーザー設定を有効にする
- デザイン>設定>ユーザー設定
が管理できるようになります。
データファイルがストラクチャファイルとは別の場所にあれば,
- デザイン>設定>データファイル用のユーザー設定
も管理できるようになります。
- 設定>ストラクチャ設定
一般ページを開き,起動時モードをアプリケーションに設定します。この項目はストラクチャレベルで設定する必要があります。
アプリケーションモードのスプラッシュ画面が不要であれば,インターフェースページを開き,ウィンドウの表示のスプラッシュスクリーンを解除します。
- ツールボックス>メニュー
モードメニューを削除します。
注記: デザインモードにはoption+command+右クリックで移動できます。
SET ABOUT
を使用し,「4Dについて」画面をカスタマイズします。
プロジェクトフォルダーにDefault Data/default.4DD
というフォルダーが存在する場合,内容がビルドアプリケーション内にコピーされ,デフォルトのデータファイルとしてリードオンリーモードで使用されるようになっています。
-
Default Data
フォルダーを作成し,下記の項目をコピーします。 -
データファイル
default.4DD
default.Match
-
インデックスファイル
default.4DIndx
-
設定フォルダー
Settings
データファイルを切り替える条件が満たされているかチェックするコードをスタートアップで実行します。
(Version type ?? Merged application)
(Application type=4D Volume desktop) | (Application type=4D Server)
(Data file=Folder(Get 4D folder(Database folder);fk platform path).folder("Default Data").file("default.4DD").platformPath)
(Is data file locked)
上記の条件が満たされたなら,OPEN DATA FILE
またはCREATE DATA FILE
で運用データファイルを作成または使用します。これらのコマンドはアプリケーション再起動前に実行される最後のコマンドであるべきです。既存のスタートアップコードとは相互に排他的な処理になるようにプログラムする必要があります。
- ユーザー設定ファイルもデフォルトデータフォルダーから運用データファイルにコピーします。
- デザイン>設定>データファイル用のユーザー設定
バックアップページを開き,ログを使用を解除します。
デフォルトデータファイルを使用している間は,データベースが書き込み保護されており,まだログファイルを有効にすることはできません。運用データファイルに切り替わった後,ログファイルを使用する条件が満たされているかチェックするコードをスタートアップで実行します。
(Version type ?? Merged application)
(Application type=4D Volume desktop) | (Application type=4D Server)
(Log file="")
(Not(Is data file locked))
SELECT LOG FILE
でログファイルを作成する場所を決定します。ログファイルは次回のバックアップ完了後から使用されるようになります。
アプリのアイコンをカスタマイズするには,Finderの「情報を見る」ダイアログではなく,ビルドプロジェクトのServerIconMacPath
RuntimeVLIconMacPath
ClientMacIconForMacPath
を使用し,icns
ファイルのパスを指定します。icns
ファイルを作成するには,必要なサイズの.png
画像を.iconset
という拡張子のフォルダーに入れ,下記のコマンドラインを実行します。
iconutil -c <path>
単一フレームのicns
画像では,カスタムアイコンが表示されません。.png
画像は,sips
でリサイズすることができます。
sips -z <pixelsH> <pixelsW> <path>
c.f. Macアプリの.icnsを作るときのメモ
初期のMac OS Xでは,作成したアプリを.zip
.pkg
.dmg
などのファイル形式に圧縮し,ユーザーのマシンにダウンロードしてインストールすることができました。Mac OS X 10.7 Lion以降,ユーザーがインターネットからダウンロードしたアプリは,システムに統合された防御機構(GateKeeper)が初回の起動時にセキュリティのチェックを実施します。GateKeeperの設定は,システム環境設定の「セキュリティとプライバシー(一般)」で確認および変更することができます。
10.8 Mountain Lion以降,デフォルトの設定は「App Storeと確認済みの開発元からのアプリケーションを許可」です。App Storeではない場所からダウンロードしたアプリは,Apple Developer Program に登録された開発元の有効なコード署名が確認できない場合,GateKeeperは起動をブロックします。
10.15 Catalina以降,コード署名に加え,公証(ノータリゼーション)が確認できないアプリは,デフォルトで起動を許可しない設定になりました。公証とは,プログラムがマルウェアのように自己を改竄したりしないことを保証するために,Appleが運営している申告・判定・登録システムのことです。GateKeeperは,初回の起動でアプリが公証にパスしたことを証明する電子的なチケット(ステープル)の存在をチェックします。チケットが確認できない場合,Appleのサーバーにアクセスし,そのアプリが公証にパスしたものかどうか,公証データベースに問い合わせます。
4Dおよび4D Serverは署名されていますが,ビルドしたアプリは,4Dから派生した「別アプリ」に該当するため,デベロッパー各自が署名や公証を実施しなければなりません。具体的には,下記のリソースに署名を実施し,公証のチケットを発行してもらうことが必要です。
- ビルドアプリ(本体)
- 4Dに標準で付属しているアプリ(
Updater
InstallTool
[17r5以降]) - 4Dに標準で付属している実行ファイル(
php-fcgi-4d
HelperTool
InstallTool
[17r4以前]) - その他の実行バンドル(フレームワーク・プラグイン)
- その他の実行ファイル(
.js
.html
.json
LAUNCH EXTERNAL PROCESS
で起動する外部プログラム)
コード署名が確認できた場合,下記のようなメッセージが初回の起動時にだけ表示されます。
…はインターネットからダウンロードされたアプリケーションです。開いてもよろしいですか?
このファイルは"Safari"により…ダウンロードされました。
コード署名が確認できない場合,下記のような警告メッセージが表示されます。
…は,開発元を検証できないため開けません。
このアプリケーションにマルウェアが含まれていないことを検証できません。
ユーザーは,開発元が信頼できると判断できる場合,control
キーを押しながらアプリをクリックし,「Mac のセキュリティ設定を一時的に無視して」アプリを起動することができます。また,インターネット経由ではなく,接続した外部ドライブ等からコピーしたファイルであれば,そのまま開くことができます。ですから,4Dで開発したアプリを配付するために,署名と公証が絶対に必要というわけではありません。それでも,利便性とユーザーエクスペリンスの観点から,実施することが勧められています。
コード署名に問題がある場合,下記のような警告メッセージが表示されます。このようなアプリを開くことはできません。
…は,壊れているため開けません。ゴミ箱に入れる必要があります。
このファイルは"Safari"により…ダウンロードされました。
下記のようなコマンドをターミナルに入力し検疫フラグ(アプリがネットワーク経由でダウンロードされたという印)や問題のあるコード署名を取り除くことができます。
xattr -cr {application_path}
codesign --remove-signature {application_path}
署名と公証を実施するために必要なものは下記のとおりです。
- macOS 10.14.5以降がインストールされたMac
- Apple ID の2ファクタ認証
- App用パスワード
- Apple Developer Programの有効なメンバーシップ(無料メンバーはNG)
- Xcode 10以降
- Developer ID 証明書
Apple Developer Programには,無料のプログラムも用意されていますが,「App StoreでのApp配信」および「Mac App Store以外でのソフトウェア配信」が特典に含まれていません。無料のメンバーシップでは,Developer ID 証明書の発行ができないためです。署名と公証には,Developer ID 証明書が必要です。
Apple IDは,Mac,iOSデバイス,アプリ,オンラインいずれかの方法で作成することができます。Macユーザーであれば,すでに保有しているかもしれません。
アプリ開発用に新しいアカウントを作成することもできますが,2ファクタ認証を有効にする必要があり,原則的に2台以上のApple機器で同じApple IDを使用していることが想定されているので,個人のApple IDをそのまま使用したほうが簡単かもしれません。
注記: Apple機器が1台だけの場合,電話で2ファクタ認証を完了することができます。
2ファクタ認証は,古典的なユーザー名とパスワードの組み合わせを知っていること(第1ファクター:知識)に加え,当該ユーザーの個人的な電子機器を持っていること(第2ファクター:デバイス)を本人確認の根拠にするという仕組みです。具体的には,6桁の確認コードを電話などに送信し,入力させることにより,ユーザーを認証します。Apple IDの場合,2ファクターは任意ですが,Apple Developer IDの場合,2ファクターは必須となっています。
Apple IDでログインして,Apple Developer Programに加入の手続きを開始します。2ファクタ認証が有効にされていなければ,このとき設定を済ませるよう案内されます。支払いが完了すると,数時間後に手続き完了の通知メールが送られるはずです。
最新版のmacOSおよびXcodeは,App Storeから入手することができます。古いバージョンのXcodeがアプリケーションフォルダーにインストールされている場合,アプリケーションは上書きされます。以前のXcodeを残しておきたいのであれば,サブフォルダーなど,あらかじめ別の場所に退避してください。
注記: Apple Developerにログインすると,開発中(ベータ版)および旧バージョンのソフトウェアがダウンロードできます。
4Dアプリの署名と公証は,Xcodeアプリではなく,コマンドラインツール経由で実施します。Xcodeのコマンドラインツールは,xcrun
コマンドを介して実行します。xcrun
はデフォルトの場所にインストールされているXcodeの内部にあるプログラムを実行するようになっています。デフォルトのパスは下記のコマンドラインで確認することができます。
xcode-select -p
複数のXcodeがインストールされている場合,あるいは標準的ではない場所にXcodeがインストールされている場合,
xcode-select --switch <path>
ただし,他の開発ツールに影響を与える恐れがあるので,環境変数のDEVELOPER_DIR
で一時的にパスを変更したほうが無難かもしれません。
Xcodeを起動し,アプリケーションメニューの「Preferences」から「Accounts」のページを開きます。Developer IDを入力し,ログインします。
「Manage Certificates」をクリックし,画面の左下にある「+」アイコンをクリックして,「Developer ID Application」および「Developer ID Installer」証明書を作成します。
注記: 無料のApple Developer IDでログインしている場合,Developer ID系の証明書は作成できません。「Mac Developer」証明書は作成できますが,これは個人のデバイスでアプリをテストするための証明書であり,アプリを配付する目的では使用できないものです。
配付用アプリの公証は,ビルド毎に実行する必要があるので,自動化しておくと便利です。その場合,パスワードの入力を自動化するために,App用パスワードを作成しておきます。パスワードは,Apple IDのアカウントページにログインし,「セキュリティ」セクションの「App 用パスワード」の下の「パスワードを生成」をクリックすれば自動的に作成されます。「このパスワードのラベルを入力」には,後で参照できる簡単な文字列(例:abcde
)を入力します。
appleid.apple.comにログインします。
2ファクタ認証のサインインを許可します。
セキュリティに移動します。
**パスワードを生成…**をクリックします。
パスワードのラベルには任意の英数字(空白はOK,記号はNG)を入力します。
App用パスワードは25
まで作成することができます。
セキュリティの「編集」ボタンをクリックすると,App用パスワードの履歴を表示することができ,削除することもできます。ラベルは,この画面でパスワードを管理するためのものです。同じラベルを使用しても,毎回,違うパスワードが発行されます。
コード署名には下記いずれかのツールを使用します。
altool
(旧)notarytool
(新)
署名には,前述の手順で作成したpp用パスワードが必要です。そのままパラメーターに渡すこともできますが,できればパスワードをキーチェーンに保存し,登録名で参照することが勧められています。
- altool
xcrun altool --store-password-in-keychain-item <item_name> -u <apple_developer_id> -p <secret_password>
これにより,キーチェーンにアプリ用のパスワードが保存され,下記のようなコマンドラインでパスワードが参照できるようになります。
xcrun altool --notarize-app -u <apple_developer_id> -p "@keychain:<item_name>"
- notarytool
xcrun notarytool store-credentials <item_name> --apple-id <apple_developer_id> --team-id <apple_developer_team_id> --password <secret_password>
これにより,キーチェーンにアプリ用のパスワードが保存され,下記のようなコマンドラインでパスワードが参照できるようになります。
xcrun notarytool --keychain-profile <item_name>
アプリのコード署名には,アプリの実行に必要なエンタイトルメントが組み込まれることになっています。エンタイトルメントは,XML形式のプロパティリスト(.plist
)で編集します。XML形式の.plist
をそのままコード署名に使用することはできません。バイナリ形式の.plist
に変換する必要があります。ファイル形式を変換するには,下記のようなコマンドラインを実行します。
plutil -convert xml1 <path>
エンタイトルメントを必要とするようなアプリは,マルウェアではないことを保証するため,書き込みが禁止された実行環境(Hardened Runtime)で動作することが求められています。
4Dの場合,アプリ本体に加え,php-fcgi-4d
HelperTool
Updater
InstallTool
といった実行ファイルが組み込まれているため,それぞれに対し,Hardened Runtimeオプションを付けてコード署名を実施する必要があります。
codesign --options=runtime --entitlements <path>
プラグイン・フレームワーク・ライブラリなど,実行ファイルがロードする個別のファイルにHardened Runtimeオプションを付ける必要はありません。
公証アプリは,セキュアなタイムスタンプを付けて署名されていなければなりません。そのためには,インターネットに接続された状態でcodesign
を実行し,下記のオプションを指定する必要があります。
codesign --timestamp
公証アプリは,macOS 10.9 SDK以降で開発されていなければなりません。4D本体は,この条件をクリアしていますが,個別のプラグイン・フレームワーク・ライブラリはそうではないかもしれません。そのような場合,macOS 10.9 SDK以降で再コンパイルする必要があります。
アプリ・プラグイン・フレームワークなどの「バンドル」フォルダーは,再帰的にコード署名を実施することができます。
codesign --deep
--deep
オプションは,すでに署名されているリソースの署名は上書きしません。コード署名は,原則的にフォルダー構造の中から外に向かって実施します。4Dの場合,プリインストールされたアプリや実行ファイルにはエンタイトルメントとHardened Runtimeオプションを指定して署名します。プラグイン・フレームワーク・ライブラリなどのリソースは,エンタイトルメントを指定せずにコード署名を実施します。最後に外殻のアプリ本体を署名しますが,このとき--force
(強制的に署名)オプションを指定してしまうと,内部リソースの署名が無効になってしまうので,すでに署名が済んでいるリソースの外部では--force
オプションを指定はしないでください。
4Dプラグインには,歴史的な経緯により,4DCB
というバンドル識別子(CFBundlePackageType
)が設定されていました。Appleの公証は,BNDL
APPL
FMWK
のような標準バンドル識別子でなければ,包括的なチェックを実施しないようです。プラグインのバンドル識別子は,BNDL
に設定する必要があります。
アプリ・プラグイン・フレームワークなどの「バンドル」の内部には,Info.plist
という名称のカタログファイルが存在します。このファイルには,バンドルの基本的な情報が辞書形式(キー/値ペア)で書き込まれていますが,CFBundleName
CFBundleExecutable
等のキー値が実際のファイル名と完全に一致しない場合,たとえば小文字の代わりに大文字が使用されている場合,コード署名エラーになります。
invalid Info.plist (plist or signature have been modified)
コード署名に使用する証明書が署名のアイデンティーとなります。アプリに署名するのであれば,Developer ID Application:…
証明書,インストーラーに署名するのであれば,Developer ID Installer:…
証明書をアイデンティーとして使用します。
キーチェーンに登録されている証明書は,下記のコマンドラインでリストアップすることができます。
security find-identity -p basic -v
ユーザーがアプリをダウンロードした後,初回の起動でアプリが公証にパスしたことを証明する電子的なチケット(ステープル)の存在をチェックします。チケットが確認できない場合,Appleのサーバーにアクセスし,そのアプリが公証にパスしたものかどうか,公証データベースに問い合わせます。
下記のコマンドラインでチケットをアプリに紐付ける(ステープルする)ことができます。アプリが公証にパスした後でなければ,ステープルはできません。紐付けは,ディスクイメージやインストーラーではなく,アプリ自体に対して実行します。コマンドは,インターネットに接続された状態で実行する必要があります。
xcrun stapler staple <path>
サンプルプログラムにインストールされているbuilderコンポーネントのSignApp
クラスには,コード署名や公証に必要なコマンドラインツールが実装されています。
- altool - 公証
- notarytool - 公証
- stapler - ステープル
- codesign - コード署名
- ditto - .zipを作成
- hdiutil - .dmgを作成
- install_name_tool - ダイナミックリンクライブラリの参照パスを書き換え
- pkgbuild - .pkgの作成
- productsign - .pkgの署名
- xcode-select - Xcodeの管理
- security - キーチェーンの検索
- plutil - プロパティリストの編集
コンポーネントを使用すれば,オブジェクト指向のシンプルなAPIでコード署名と公証を済ませることができます。→例題
デザインモードのアプリケーションビルド画面を使用すれば,基本的な項目が設定できます。より細かくビルド過程を制御するためには,作成したビルド設定XMLファイルとBUILD APPLICATION
コマンドを使用し,ログファイルを解析することが求められます。
サンプルプログラムにインストールされているbuilderコンポーネントのBuildApp
クラスには,BUILD APPLICATION
コマンドの全オプションが実装されています。
コンポーネントを使用すれば,オブジェクト指向のシンプルなAPIでビルドを済ませることができます。→例題
buildコンポーネントtest_build
メソッド
/*
* ビルド
*/
$buildApp:=cs.BuildApp.new()
$buildApp.findLicenses(New collection("4DOE"; "4UOE"; "4DDP"; "4UUD"))
$isOEM:=($buildApp.settings.Licenses.ArrayLicenseMac.Item.indexOf("@:4DOE@")#-1)
$buildApp.settings.BuildApplicationName:=Folder(fk database folder).name
$buildApp.settings.BuildApplicationSerialized:=True
$buildApp.settings.BuildMacDestFolder:=Temporary folder+Generate UUID
$buildApp.settings.SourcesFiles.RuntimeVL.RuntimeVLIncludeIt:=True
$buildApp.settings.SourcesFiles.RuntimeVL.RuntimeVLMacFolder:=$buildApp.getAppFolderForVersion().folder("4D Volume Desktop.app").platformPath
$buildApp.settings.SourcesFiles.RuntimeVL.IsOEM:=$isOEM
$buildApp.settings.SignApplication.MacSignature:=False
$buildApp.settings.SignApplication.AdHocSign:=False
$status:=$buildApp.build()
/*
署名
*/
$credentials:=New object
$credentials.username:="[email protected]"
$credentials.password:="@keychain:altool"
$credentials.keychainProfile:="notarytool"
$credentials.certificateName:="Developer ID Application: keisuke miyako (Y69CWUC25B)"
$signApp:=cs.SignApp.new($credentials)
$app:=Folder($buildApp.settings.BuildMacDestFolder; fk platform path).folder("Final Application").folder($buildApp.settings.BuildApplicationName+".app")
$status.sign:=$signApp.sign($app)
/*
公証
*/
$status.archive:=$signApp.archive($app; ".pkg")
$status.notarize:=$signApp.notarize($status.archive.file)
test_build_app
メソッドを実行して.pkg
または.dmg
形式の署名/公証アーカイブを作成しました。
$build:=cs.Build.new()
$build.versionString:=cs.Version.new().updatePatch().getString()
$status:=$build.buildDesktop(".dmg")
If ($status.build.success)
If ($status.archive.success)
If ($status.notarize.success)
SHOW ON DISK($status.app.platformPath)
End if
End if
End if
updatePatch()
: パッチ番号をインクリメントします。このバージョンコードがInfo.plist
に書き込まれます。buildDesktop()
: ビルド/署名/アーカイブ/公証まで一連の処理を実行します。
$app:=cs.App.new()
$appName:=$app.getName()
$build:=cs.Build.new()
$build.versionString:=cs.Version.new().updatePatch().getString()
$build.certificateName:="Apple Distribution: keisuke miyako (Y69CWUC25B)"
$build.desktopAppIdentifier:="org.fourd."+$appName
$build.entitlements:=New object
$build.entitlements["com.apple.security.app-sandbox"]:=False
$build.entitlements["com.apple.security.application-groups"]:=New collection("Y69CWUC25B.org.fourd")
$build.entitlements["com.apple.security.inherit"]:=True
$build.entitlements["com.apple.security.network.client"]:=True
$build.entitlements["com.apple.security.network.server"]:=True
$build.entitlements["com.apple.security.files.user-selected.read-write"]:=True
$build.entitlements["com.apple.security.files.user-selected.executable"]:=True
$status:=$build.buildDesktop(".pkg")
If ($status.build.success)
If ($status.archive.success)
If ($status.notarize.success)
SHOW ON DISK($status.app.platformPath)
End if
End if
End if