概要
Xcode プロジェクトをプログラムから自動生成したいと思ったことはありますか?
私はあります。
しかも Xcode がやるのとまったく同じように動き、Xcode バージョンによって微妙に異なるプロジェクトテンプレートを出し分けたりもしたいです。
ローカルのマシンだけでなく、CI上でも動けば完璧ですね。
ご存知の通り、手動で新規で Xcode プロジェクトを作るには
- Xcode.app を開く
- メニューバーの
File
→New
→Project...
と辿る、もしくは単にShift + ⌘ + n
を押す - プロジェクトテンプレートを選ぶ
- 色々聞かれるので入力する
などと結構手間がかかります。
また、何らかの理由でプログラムからプロジェクトを自動生成したいときにはこの方法ではうまくいきません。
にもかかわらず、Xcode そのものや、また Xcode に同梱されている xcodebuild
などのコマンドラインツールにはプロジェクト作成のインターフェースが用意されていません。
世の中には自分以外にも同じような悩みを持った人がいるようで、調べるうちに色々な方法があることがわかりました。
色々な方法たち
XcodeGen
Cookpad さんの XcodeGenによる新時代のiOSプロジェクト管理 で紹介されていたので調べる前から知ってはいました。
これは一見良さそうなのですが、 Xcode がやるのとまったく同じように動かすためには手動で YAML を頑張って書く必要があります。
しかも Xcode がつくるテンプレートファイルなどは作ってくれないので、これも自力で書かねばなりません。
したがって今回の目的には適いませんでした。
Xgen
ライブラリなので直接コマンドラインから実行はできませんが、以下のような Swift スクリプトから簡単に呼び出すことができます。
import Xgen
let workspace = Workspace(path: "~/MyWorkspace")
workspace.addProject(at: "~/MyProject.xcodeproj")
try workspace.generate()
ちなみに作者は Swift by Sundell という Podcast を運営している方です。
仕組みは単純で、ハードコードされた XML Property List を書き出しているだけです。
Workspace か Playground ファイルを書き出すだけなので、Xcode から操作したときのようにプロジェクトテンプレートができるわけではありません。
私は Xcode がやるのとまったく同じように動く ものを求めているので、これもやはり違いそうです。
Apple Events
既存ツールが使えないなら自分で書くしかありません。
幸い Xcode は OSA に準拠しているので、 AppleScript や JavaScript から操作することができます。
OSA を JavaScript で書くのはドキュメントの不足からそれなりに大変なので、手軽にかける AppleScript で試すことにしました。
Single View iOS App 向けのスクリプトを書いたのですが、長いのでリンクを貼ります。
https://github.com/manicmaniac/automate-xcode-project-creation/blob/ac30b3ee50149494c1b8d3e3dbd533bf7b4f7835/create-project.applescript
コードは見ての通り、
- Xcode.app を開く
- メニューバーの
File
→New
→Project...
と辿る、もしくは単にShift + ⌘ + n
を押す- プロジェクトテンプレートを選ぶ
- 色々聞かれるので入力する
のように手動でやるときの作業をそのままコードにして、適宜ウェイトを入れたりしているだけです。
AppleScript の標準コーディング規約がタブインデントなこともあって、めちゃくちゃ横に長くて読みにくいですね。
tell application "System Events"
tell application process "Xcode"
set frontmost to true
click menu item "Project…" of menu 1 of menu item "New" of menu 1 of menu bar item "File" of menu bar 1
これはきちんとXcode がやるのとまったく同じように動きます。
Xcode バージョンの変更にもしっかり追従するので、要件を満たせました。
一件落着かと思いきや、実はこのスクリプトは CI 上では動きません。
なぜかというと、
- Apple Events へのアクセスを有効にするには OS が出すダイアログに対して許可をする必要があり、これはプログラムからは操作できない
- OS が出すダイアログを非表示にするには、あらかじめシステム環境設定から許可しておく必要があり、これはプログラムからは操作できない
- システム環境設定を CLI から操作するには、システム環境設定が保存されているファイル(tcc.db)を直接操作する必要があり、これは SIP で保護されている
- SIP を無効にするには OS をセーフモードでリブートする必要があり、これは CI では通常許可されていない
というような感じで、散々ヤクの毛刈りをした後に不可能ということが判明します。辛いですね。
では、結局最初の要件 Xcode がやるのとまったく同じように動き、Xcode バージョンによって微妙に異なるプロジェクトテンプレートを出し分けたり、CI上でも動くような方法はないのでしょうか。
実は、あるんです。
Xcode のプライベートフレームワークを使う
ご存知の通り、Xcode は複数のプライベートフレームワークから構成されているアプリケーションで、主要な機能の多くがプライベートフレームワーク経由でアクセス可能です。
例えば、今回利用しているプロジェクトテンプレートを吐き出す機能は、以下のフレームワークの機能の一部として実装されています。
/Applications/Xcode.app/Contents/Frameworks/IDEFoundation.framework
/Applications/Xcode.app/Contents/SharedFrameworks/DVTFoundation.framework
したがって、このプライベートフレームワークを用いてプロジェクトを作成する CLI アプリケーションを作成し、その CLI アプリケーションを実行することで、Xcode がやるのとまったく同じように動かすことができそうです。
また、このやり方なら Apple Events を用いないので、CI でも動かすことができますね。
というわけで、xcnew という CLI アプリケーションを実装しました。
手動で Xcode プロジェクトを作成するときにいろいろと入力する部分はコマンドラインオプションとして渡すようにしています。
xcnew - A command line tool to create Xcode project.
Usage: xcnew [-h|-v] [-n ORG_NAME] [-i ORG_ID] [-tuco] <PRODUCT_NAME> [OUTPUT_DIR]
Options:
-h, --help Show this help and exit
-v, --version Show version and exit
-n, --organization-name Specify organization's name
-i, --organization-identifier Specify organization's identifier
-t, --has-unit-tests Enable unit tests
-u, --has-ui-tests Enable UI tests
-c, --use-core-data Enable Core Data template
-o, --objc Use Objective-C (default: Swift)
Arguments:
<PRODUCT_NAME> Required TARGET_NAME of project.pbxproj
[OUTPUT_DIR] Optional directory name of the project
作成にあたっては上述のプライベートフレームワークをリバースエンジニアリングする必要がありなかなか難航しましたが、無事 xcnew
コマンドを使って要件を満たすことができました。
プライベートフレームワークの使い方を調べて、それを利用する方法は別途記事にすることにして、この記事はいったん目的を達したところでおしまいです。