fastlane を使っている場合、ビルドマシンに同じ Bundle Identifier の Provisioning Profile が複数インストールされていると、 本来使いたいのとは別の Profile が使われてしまうことがある。

ビルド後に使い終わった Profile をアンイストールすれば、この問題を回避できる。

概要

  • fastlane における Provisioning Profile 重複問題
  • Provisioning Profile のアンインストール
  • security を使って Provisioning Profile を XML にして UUID を取得する
  • Profile をアンインストールする手順を fastlane の action 化する

環境

  • macOS 10.11.6
  • ruby 2.3.1
  • fastlane 1.94.0

fastlane における Provisioning Profile 重複問題

fastlane を使っている場合、ビルドの度に sigh アクションで Profile をダウンロード & インストールしていることが多いと思う。 通常これで問題ないのだが、何かの手違いで Profile が重複してしまうと、そのときダウンロードしてきた Profile が使われなくて困ることがある。 例えば、以下のような場合に、この問題に遭遇する。

  • 同じ Bundle Identifier のプロファイルを作りなおした場合
  • AdHoc 用と App Store 用のプロファイルを両方インストールした場合

そこで、ビルドが終わった後には、そのときインストールした Profile をアンインストールしたい。

Provisioning Profile のアンインストール

インストールした Profile は ~/Library/MobileDevice/Provisioning Profiles ディレクトリに保存されている。 Profile をアンインストールするには、この中にあるファイルを削除すれば良い。

ファイル名は <UUID>.mobileprovision となっており、特定の Profile だけアンインストールしたい場合は、 その UUID を知る必要がある。

Provisioning Profile から UUID を取得する

Profile はバイナリファイルなので、そのままでは中身を読み難い。そこで security コマンドを使ってテキストに変換する。

security cms -D -i <Path to mobileprovision>

とすれば、Profile の中身が XML で出力される。

<?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>UUID</key>
        <string>XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX</string>

        <!-- 中略 -->
</dict>
</plist>

あとはいつもの plist なので、好きに parse すれば良い。

fastlane の action 化する

lane でさくっと使いたいので、action 化してみる。 sh アクションで security コマンドを実行して XML を取得したら、REXML で parse して UUID を取得する。

fastlane/actions/uninstall_profile.rb

module Fastlane
  module Actions
    class UninstallProfileAction < Action
      def self.run(params)

        UI.message ""
        UI.message Terminal::Table.new(
          title: "Uninstall Profile".green,
          headings: ["Option", "Value"],
          rows: params.values
        )
        UI.message ""

        # see http://qiita.com/mattak@github/items/dcb25ad7e12501d1525d
        xml = sh("security cms -D -i #{params[:file]}")

        require 'rexml/document'
        doc = REXML::Document.new xml

        uuid = doc.elements.to_a('plist/dict/key')
          .find { |e| e.text == 'UUID' }
          .next_element
          .text

        profile_name = "#{uuid}.mobileprovision"
        profile_path = File.join(Dir.home, 'Library/MobileDevice/Provisioning Profiles', profile_name)

        unless File.exists?(profile_path)
          UI.message "Provisioning Profile is not installed at #{profile_path}"
          return
        end

        File.delete(profile_path)
      end

      #####################################################
      # @!group Documentation
      #####################################################

      def self.description
        "Uninstall provisioning profile from Xcode"
      end

      def self.available_options
        [
          FastlaneCore::ConfigItem.new(key: :file,
                                       env_name: "FL_UNINSTALL_PROFILE_FILE",
                                       description: "Provisioning profile",
                                       verify_block: proc do |value|
                                          UI.user_error!("Couldn't find file at path '#{value}'") unless File.exist?(value)
                                       end)
        ]
      end

      def self.authors
        ["dddnuts"]
      end

      def self.is_supported?(platform)
        platform == :ios
      end
    end
  end
end

これで、lane では uninstall_profile(file: <Path to file>) で使えるようになる。

ちなみに、sigh でダウンロードしてきたファイルは lane_context[SharedValues::SIGH_PROFILE_PATH] にある(sigh.rb のソースを参照)。

platform :ios do
  lane :beta do
    sigh
    gym
    uninstall_profile(
      file: lane_context[SharedValues::SIGH_PROFILE_PATH]
    )
  end
end

まとめ・感想

  • fastlane における Provisioning Profile 重複問題を Profile のアンインストールで回避した
  • 特定の Profile のみアンインストールするためには UUID が必要
  • Profile はバイナリなので security コマンドでテキスト(XML)にして読みやすくした
  • Profile をアンインストールする手順を action 化して fastlane で簡単に使えるようにした

CI で自動化する場合、そもそも Provisioning Profile は全てアンインストールした状態でビルドするとか、 環境依存を出来る限り無くす方が筋が良いことは分かっているんだけど、 他の人とひとつの Jenkins Slave を相乗りしてるとか、プロジェクトによっては自動化が全然進んでないとか、 諸事情でうまくいかないことはあるので、そんなときに役にたてば :angel:

参考