ローカル環境を可能な限り汚染しないXcodeの開発環境構築

公開:2020/09/30
更新:2020/09/30
6 min読了の目安(約5500字TECH技術記事
Likes14

解決したい課題

  • チーム開発時に各メンバーのローカル環境にインストールしてあるツールのバージョンの差異が気になる
    • Podfile.lockでversionの差分が出るのがすごく気になる。気にしない人はそのままcommitするし…
  • READMEに環境構築の手順を書く手間を減らしたい
  • 環境構築に関する問い合わせも減らしたい

結論

  • ローカル環境に事前にインストールするツールはHomebrewbundlerのみ
    • Swift系のツールはMint経由でインストール、Ruby系のツールはbundlerでプロジェクト直下にインストール
    • 各ツールの依存関係は以下になる
Homebrew
└─ Mint
   ├─ Carthage
   ├─ xcodegen
   ├─ swiftgen
   └─ swiftlint

bundler
└─ CocoaPods

解決方法

ツールのインストール

Homebrew

  • Homebrewをローカル環境にインストール
  • Homebrew経由でインストールしたいツールを記述したBrewfileを用意
brew "mint"
  • brew bundle --no-upgrade --no-lock でBrewfileに記載のツールをローカル環境にインストール
    • --no-lock をつけると Brewfile.lock.json が生成されなくなる
    • lockファイルの有無は迷ったが、brewやXcodeのバージョンの差分も出てしまうのが嫌なので生成しないようにした
  • これで各ツールの依存関係は以下になる
Homebrew
└─ Mint

Mint

  • Swift製のツールはMint経由でインストールする
  • mint経由でインストールしたいツールを記述したMintfileを用意
carthage/carthage@0.35.0
yonaskolb/xcodegen@2.17.0
swiftgen/swiftgen@6.3.0
realm/swiftlint@0.40.0
  • Mintはlockファイルはなく、Mintfileに直接バージョンを記述する形式
    • Mintは異なるバージョンのツールをローカル環境にインストールできる
  • mint bootstrap でローカル環境にlinkなしでインストール
    • mint install でもいいと思いますが、linkしたくないのでbootstrapにした
  • これで各ツールの依存関係は以下になる
Homebrew
└─ Mint
   ├─ Carthage
   ├─ xcodegen
   ├─ swiftgen
   └─ swiftlint
  • mintでインストールしたツールは mint run carthage bootstrap のように使用することになる
    • XcodeプロジェクトのBuild Phasesで実行するCarthageのスクリプトも mint run carthage copy-frameworks になることに注意

bundler

  • bundlerをローカル環境にインストール
    • バージョン指定でインストールしたい場合は sudo gem install bundler:2.0.2 な感じ
  • bundler経由でインストールしたいツールを記述したGemfileを用意
source 'https://rubygems.org'

gem 'cocoapods'
  • bundle install --path vendor/bundle または .bundle/configを用意して bundle install し、プロジェクトのディレクトリ直下にツールをインストール
  • これでツールの依存関係は以下になる
Homebrew
└─ Mint
   ├─ Carthage
   ├─ xcodegen
   ├─ swiftgen
   └─ swiftlint

bundler
└─ CocoaPods
  • bundleでインストールしたツールは bundle exec pod install のように使用することになる

環境構築方法

  • 各種環境構築を記述したMakefileを用意
    • makeの本来の用途であるファイルを作るというよりかは、単純なタスクランナーとして使う

APP_NAME := App

DIR := $(shell dirname $(realpath $(firstword $(MAKEFILE_LIST))))
MAKEFILE_PATH := ${DIR}/Makefile
MINTFILE_PATH := ${DIR}/Mintfile
GEMFILE_PATH := ${DIR}/../Gemfile

make := make -f ${MAKEFILE_PATH}
mint-run := mint run -m ${MINTFILE_PATH}

USES_CACHE := false

.PHONY: setup setup-uses-cache 
.PHONY: setup-tools setup-carthage setup-cocoapods 
.PHONY: generate-asset-files generate-xcodeproj open 
.PHONY: clean

default: setup

setup:
	${make} setup-tools
	${make} generate-asset-files
	${make} setup-carthage
	${make} generate-xcodeproj
	${make} open

setup-uses-cache:
	${make} setup USES_CACHE=true

setup-tools:
	brew bundle --no-upgrade --no-lock --file ${DIR}/Brewfile
	mint bootstrap -m ${MINTFILE_PATH}
	bundle install --gemfile ${GEMFILE_PATH}

setup-carthage:
ifeq ($(USES_CACHE),true)
	${mint-run} carthage bootstrap --platform iOS --no-use-binaries --cache-builds
else
	${mint-run} carthage bootstrap --platform iOS --no-use-binaries
endif

setup-cocoapods:
	bundle exec --gemfile ${GEMFILE_PATH} \
      pod install --project-directory=${DIR}

generate-asset-files:
	${mint-run} swiftgen config run --config ${DIR}/swiftgen.yml

generate-xcodeproj:
	${mint-run} xcodegen -s ${DIR}/project.yml
	${make} setup-cocoapods # Xcodeプロジェクトが生成された後に実行する必要がある

open:
	open ${DIR}/${APP_NAME}.xcworkspace

clean:
	rm -rf ${DIR}/../vendor
	rm -rf ${DIR}/${APP_NAME}.xcodeproj	
	rm -rf ${DIR}/${APP_NAME}.xcworkspace
	rm -rf ${DIR}/Carthage
	rm -rf ${DIR}/Pods
  • make を実行すれば各種ツールのインストールからXcodeの環境構築まですべて終わる
    • CarthageやCocoaPodsのライブラリ更新はMakefileではサポートしないで、わかる人が適切にCartfile.resolvedやPodfile.lockを更新する形にしている
  • サンプルはyusuga/xcode-setup

Makefileについて

  • もうちょっとシンプルに書くのは可能ですが、makeはCIから呼んだり別のMakefileからも呼ぶこともあるので、都度パスの指定を入れている
  • GemfileがAppディレクトリの一つ上の階層にありますが、CIを使うときは以下のディレクトリ構成でやってて、CIでもGemfileを使うのでこういう構成にしている
.
├── App
│   ├── App
│   ├── App.xcodeproj
│   ├── App.xcworkspace
│   ├── Brewfile
│   ├── Cartfile
│   ├── Cartfile.private
│   ├── Cartfile.resolved
│   ├── Carthage
│   ├── Makefile
│   ├── Mintfile
│   ├── Podfile
│   ├── Podfile.lock
│   ├── Pods
│   ├── project.yml
│   └── swiftgen.yml
├── Gemfile
├── Gemfile.lock
├── LICENSE
├── README.md
├── fastlane
│   ├── Appfile
│   ├── Fastfile
│   ├── Matchfile
│   └── README.md
└── vendor
    └── bundle
  • Makefileは賛否両論みたいだけど(参考: また make の話してる(2020年9月14日))、個人的には複雑なことをせず用法容量をちゃんと守ったMakefile + 複雑なことは別途スクリプトに書いてそれをMakefileから呼び出す、という形に落ちついた

今後解決したい課題

  • brew bundle --no-upgrademint bootstrap で、2回目以降の処理時間をもっと減らしたい
  • Homebrew、Mint、bundlerもローカル環境にインストールしたくない
    • ローカル環境にインストールしているとHomebrewやMintのバージョン違いで問題が起きるケースをカバーできない(ほぼなさそうだけど)
    • Dockerで解決できるかな?