プログラミング

環境ごとに異なるXcodeプロジェクトを生成するためのXcodeGenとMakefileの設定

mackey

こんにちは。エンジニアのmackeyです。以前のプロジェクトで、私自身初めてXcodeを使用してiOSアプリケーションを開発していました。開発にあたって、一からプロジェクトの構築する必要があったのですが、複数の環境で開発を進めていくことに関してある程度満足する設定ができたので、実践したことをまとめていきます。

各種バージョン

  • Xcode: 14.2
  • XcodeGen: 2.33.0

XcodeGenについて

Xcodeプロジェクトの生成にXcodeGenを使用しました。

XcodeGenは、project.ymlというYAMLファイルにXcodeプロジェクトの各種設定をあらかじめ書いておき、xcodegen generate コマンドを実行することで、設定ファイルに基づいた .xcodeproj が生成されるツールです。

XcodeGenを使用するメリットとして、公式のGitHubでは以下のように書かれています。

✅ Generate projects on demand and remove your .xcodeproj from git, which means no more merge conflicts!

✅ Groups and files in Xcode are always synced to your directories on disk

✅ Easy configuration of projects which is human readable and git friendly

✅ Easily copy and paste files and directories without having to edit anything in Xcode

✅ Share build settings across multiple targets with build setting groups

✅ Automatically generate Schemes for different environments like test and production

✅ Easily create new projects with complicated setups on demand without messing around with Xcode

✅ Generate from anywhere including on CI

✅ Distribute your spec amongst multiple files for easy sharing and overriding

✅ Easily create multi-platform frameworks

✅ Integrate Carthage frameworks without any work

✅ Export Dependency Diagrams to view in Graphviz

https://github.com/yonaskolb/XcodeGen

これらのメリットのうち、.xcodeprojに対するコンフリクトを避けることができることが大きなメリットとして挙げられることが多いようです。しかし、私の場合は大人数で開発をしたわけではなかったので、それほどのメリットは感じませんでした。

それよりも、CI/CD時にも異なるプロジェクトファイルを生成できるという点が役に立ったと感じています。今回の場合、開発環境・検証環境・本番環境と、3つの異なる環境が必要でした。設定には以下の記事が参考になりました。

iOSアプリで環境ごとに設定を変えるベストプラクティス(Swift)

こちらの記事にもあるように、XcodeのBuild ConfigurationsにDEBUG、RELEASE以外の環境を追加することはあまり好ましくないようです。なので、XcodeGenを用いて環境別のプロジェクトファイルを生成できることは好都合でした。

Makefileで環境ごとに実行するスクリプトを設定する

環境変数は .env.* ファイルにまとめて定義するようにしました。以下は開発環境の場合の例です。

ENVIRONMENT=dev
BUNDLE_ID=jp.xxx.yyy
API_ENDPOINT=https://dev.xxx.yyy

そして、環境ごとにXcodeGenを実行するスクリプトを以下のようにMakefileで書いています。

ifeq ($(MAKECMDGOALS),generate-xcodeproj-dev)
ENV_FILE=.env.dev
else ifeq ($(MAKECMDGOALS),generate-xcodeproj-stg)
ENV_FILE=.env.stg
else ifeq ($(MAKECMDGOALS),generate-xcodeproj-prd)
ENV_FILE=.env.prd
endif

include $(ENV_FILE)
.EXPORT_ALL_VARIABLES:

.PHONY: generate-xcodeproj-dev
generate-xcodeproj-dev:
  $(MAKE) generate-xcodeproj

.PHONY: generate-xcodeproj-stg
generate-xcodeproj-stg:
  $(MAKE) generate-xcodeproj

.PHONY: generate-xcodeproj-prd
generate-xcodeproj-prd:
  $(MAKE) generate-xcodeproj

.PHONY: generate-xcodeproj
generate-xcodeproj:
  touch ./MyProject/Env.generated.swift
  mint run xcodegen generate

※ XcodeGenの管理はパッケージマネージャのMintを使用しています

Makefileの最初の部分では、コマンドごとに異なる .env ファイルを読み込み、環境変数としてエクスポートしています。これによって、例えば開発環境向けに make generate-xcodeproj-dev を実行すると、.env.dev に書いた環境変数が読み込まれた後にXcodeGenが実行されます。

環境変数の使用方法

プログラムで使用したい環境変数

.envファイルに書いた環境変数をプログラム内で使用するために、Xcodeのビルド時に、あらかじめ定義しておいたテンプレートファイル(Env.swift)内に環境変数を流し込み、Env.generated.swiftファイルを作成するようにします。

import Foundation

enum Env {
  // プログラム内で使用したい環境変数のみ書く
  static let Environment: String = "${ENVIRONMENT}"
  static let ApiEndpoint: String = "${API_ENDPOINT}"
}

こうすることで、プログラム内で Env.ApiEndpoint のように環境変数を使用することができます。

具体的な実装方法は以下の記事を参考にしましたので、そちらをご覧ください。

Xcode の Run Script Phase で .env を元に Swift コードを自動生成する

注意点として、Env.generated.swiftを.gitignoreに含める場合、clone後の初回時やCIの時にXcodeGenを実行するとEnv.generated.swiftがXcodeのCompile Sourcesに含まれなくなってしまいます。これを防ぐために、MakeFile内で

touch ./MyProject/Env.generated.swift

をして、XcodeGenを実行する前にファイルを作成するようにしています。

XcodeGenで使用したい環境変数

Project.yml上で ${ENVIRONMENT} のように書くことで、環境変数をXcodeGenで使用することができます。以下に例を示します。

name: MyProject
options:
  bundleIdPrefix: ${BUNDLE_ID}

(おまけ)XcodeGenの各種設定

XcodeGenは便利ではあるのですが、特定の設定を変更したい場合、それを実現するために必要なproject.ymlの書き方を調べる必要があります。先にXcode上で手動で設定して、後からそれをproject.ymlに反映する方法を調べるといったこともしばしばありました。

以下で、具体的な設定を紹介します(いくつかの部分だけ抜き出しています)。注意点として、infoとsettingsでそれぞれ書くべき項目が分かれているので、書く場所を間違えないようにする必要があります。

targets:
  MyProject:
    type: application
    platform: iOS
    info:
      path: MyProject/Info.plist
      properties:
        CFBundleDisplayName: ${APP_DISPLAY_NAME} // アプリの表示名
        UIUserInterfaceStyle: Light // ダークモードを無効
        UISupportedInterfaceOrientations: [UIInterfaceOrientationPortrait] // アプリを回転させないようにする
        UIBackgroundModes:
          - remote-notification // プッシュ通知を有効にするために必要な設定
    settings:
      base:
        TARGETED_DEVICE_FAMILY: 1 // 対応デバイスをiPhoneのみにする
        EXCLUDED_ARCHS[sdk=iphonesimulator*]: arm64 // 下記参照

対応デバイスをiPhoneのみにする設定

アプリをiPad非対応(iPhoneのみ)にしたい場合、Xcode上ではGeneral > Supported Destinationから設定できます。これはproject.yml上ではTARGETED_DEVICE_FAMILYという項目で設定します。

1という数字はiPhoneに対応しています。その他のデバイスや複数デバイスに対応したい場合の書き方は、以下の記事等を参照してください。

Xcode の TARGETED_DEVICE_FAMILY 一覧

EXCLUDED_ARCHSについて

以下の記事にもあるように、arm64 アーキテクチャに対応していないライブラリをM1 macのシミュレータ向けにビルドしようとしてエラーになっていたので、シミュレータ向けにはarm64ビルドをしないように設定するための項目です。

M1 Mac & Xcodeで「building for iOS Simulator, but linking in object file ... for architecture arm64」エラーの対処法

まとめ

今回は、XcodeGenを用いて環境ごとに異なるXcodeプロジェクトを生成するための方法を紹介しました。XcodeGenはproject.ymlを書くのに多少のコストはかかりますが、それ以上のメリットもあるので、採用する価値があると感じています。

AUTHOR
mackey
mackey
記事URLをコピーしました