🤩

Private RepositoryのRelease Assetsにアップロードした、XCFrameworkをより簡単に利用する

2023/12/21に公開

※この記事は Luup Developers Advent Calendar の21日目の記事です。

こんにちは。はじめまして。iOSエンジニアの山手です。本業では、公共交通系のiOSアプリの開発に携わりつつ、Luupでは、業務委託としてiOSアプリの機能改修や品質改善等のお手伝いをさせていただいています。

今回の記事は、Private RepositoryのRelease Assetsに対してXCFrameworkのzipファイルをアップロードし、PrivateなSwift PackageのBinary targetとして利用するテクニックの紹介になります。

Swift PMでBinary frameworkを配布する

Swift Packageでは、XCFramework形式のBinary frameworkを配布する機能が、Xcode11からサポートされています。
これにより、ソースコードを隠匿化した状態で、Frameworkを配布できるようになりました。
Objective-CとSwiftが混在した状態のFrameworkやC++のコードを含むFrameworkもXCFrameworkを介すことで、Swift Package化できるようになっています。

LUUP iOSアプリでは、Swift Package対応が行われていないサードパーティーのFrameworkをSwift Packageで管理するために、Swift PMのBinary Targetを活用しています。

let package = Package(
    name: "SampleFramework",
    products: [
        .library(
            name: "SampleFramework",
            targets: ["SampleFramework", "SampleFrameworkLocal"]
        )
    ],
    targets: [
        .binaryTarget(
            name: "SampleFrameworkRemoteBinary",
            url: "https://hogehoge/fugafuga/SampleFrameworkRemote.xcframework.zip",
            checksum: "9b588da829930de437275973d5e627c8b85ec068ae524758c6f12b2b4412a3ea"
        ),
         .binaryTarget(
            name: "SampleFrameworkLocalBinary",
            path: "path/to/SampleFrameworkLocal.xcframework"
        )
    ]
)

上記は、パッケージのターゲットにBinary Targetを宣言した例です。
Binary Targetの宣言には2つの方法があります。

  1. XCFrameworkをRepositoryに含めず、リモートのストレージなどに置き、URLを指定する
  2. Repositoryのバンドルとして含め、XCFrameworkをパス指定する

XCFrameworkをSwift Packageで配布することは、どちらの方法でも実現できますが、デメリットも存在します。

1つ目のリモートのストレージなどに置く場合は、Swift Package Managerを介した認証周りへの対応、アップロードしたzipファイルのChecksumの取得、Package.swiftの更新が必要になります。

2つ目のRepositoryのバンドルとして含める場合は、Checksumの記載が不要になりPackage.swiftの更新は不要になります。しかし、RepositoryにBinaryを含めるため内包するFrameworkによっては、Repositoryサイズの肥大化に繋がります。

手間を少なく実現するのであれば、2番のローカル指定です。
しかし、Repository自体の容量が増えるため、将来的にgitの動作速度にも影響する可能性があります。
また、RealmなどBinary化後のファイルサイズが100MBを超えるようなFrameworkの場合、GitHubへのPushが出来ません。

できる限り、プロジェクトのRepositoryには外部FrameworkのBinaryを含めず、Binaryはリモートに置いたものを取得するべきと考えています。

GitHubのRelease Assetsを活用する

リモートにFrameworkのBinaryを置くとなった時、どこにアップロードするかという問題が生まれます。
GitHubを用いて開発する場合は、Release Assetsに対してBinaryをアップロードすることを考える方が多いのではと思います。
実際、Release Assetsは、Git LFSと異なりストレージ容量や帯域の消費をしないため、コスト的にもメリットが大きいです。

Public Repositoryであれば、認証なくRelease AssetsにアップロードしたBinaryをBinary TargetのURLとして指定可能ですが、社外からも利用・参照できるため、PrivateなFrameworkを含める場合やPublic Repositoryへの機密情報を誤Pushするリスクなどを考えるとPrivate Repositoryで運用することが望ましいです。

しかし、Private RepositoryのRelease AssetsをBinary TargetのURLとして利用するには、GitHubの認証周りの対応コストやRelease Assets周りの更新手順が手動運用では、複雑になるという問題も抱えています。

Private RepositoryでXCFrameworkを活用する際の課題、解決策については、以前に下記の記事にまとめています。

https://qiita.com/MYamate_jp/items/3a45db130498545d2b98

Private RepositoryのRelease Assetsを利用したSwift Packageを実現するFastlane Pluginを作った

Private RepositoryのRelease Assetsを利用したSwift Packageを実現するために必要な作業を自動化するFastlaneのPluginを開発、公開しました。
XCFrameworkの生成コストなどを考え、現在はローカル環境下での利用を想定し、実装しています。
(Private Repositoryでの運用を考え、開発していますが、Public RepositoryのRelease Assetsを活用する場合でも、利用できます)

https://github.com/MasamiYamate/fastlane-plugin-privatexcframeworkpackaging

Plugin開発に至った背景

LUUP iOSアプリでは、Swift Package Manager未対応のFrameworkをXCFramework化して、Swift Package Managerで管理しています。
PublicなSwift Package未対応のFrameworkは、Public Repositoryで管理、PrivateなXCFramework形式で提供されているFrameworkは、アプリ本体のRepositoryに含める形で利用しています。
現状でも問題なく運用できていますが、ヒューマンエラーによる機密情報の漏洩リスクやアプリ本体のRepository容量の肥大化のリスクを抱えています。

リスクを最小限にするため、Private Repository上で、Swift Package未対応のFrameworkをSwift Package化すべきと考えました。
Private Repositoryで運用するためのひと工夫必要なため、手動で更新作業を行う場合、保守運用コストが高くなってしまいます。
そこで、必要な作業を自動化するFastlaneのPluginを開発に至りました。

Private RepositoryのRelease Assetsを利用したSwift Packageを利用する手順

Public Repositoryでは、認証不要なので簡単にRelease AssetsにアップロードしたXCFrameworkを利用することができますが、Private Repositoryでは、ひと工夫必要です。
必要な手順は、下記の通りです。

  1. Swift Package未対応のFrameworkのビルド、XCFrameworkの生成
  2. 生成した、XCFrameworkをzipファイルに圧縮する
  3. zipファイルのChecksumを取得する
  4. XCFrameworkをアップロードするためのReleaseを発行する
  5. 発行したReleaseのAssetsに対して、zip圧縮したXCFrameworkをアップロードする
  6. GitHub APIまたは、GitHub CLIを介して、AssetのURLを取得する
  7. AssetsのURL、zipファイルのChecksumを用いて、Package.swiftのBinary Targetを更新する
  8. Package.swift更新後のReleaseを発行する

GitHubの各リリースに表示されるAssetsへのリンクは、zipファイルそのもののホスティング先のURLではないため、GitHub APIまたは、GitHub CLIを介して、Asset URLを取得する必要があります。
そのため、Private Repositoryで運用する場合、Release AssetsにアップロードしたAsset URLをPackage.swiftを更新する前に取得する必要があります。

今回開発したPluginでは、Step2からStep7までの手順を自動化しています。
XCFrameworkの生成は、導入するプロジェクトによって事情が異なると考えたため、Pluginでは行っていません。

Pluginを利用するための事前準備

GitHub CLIのセットアップ

ReleaseやPull Requestの発行、Release Assetsの取得、更新にGitHub CLIを利用します。
事前に、GitHub CLIの事前セットアップが必要です。

https://cli.github.com/

必要なファイルの配置

プロジェクトのディレクトリー直下に./XCFrameworksディレクトリーを作成し、生成したXCFrameworkを配置します。
また、下記のPrivatePackageConfig.ymlをディレクトリー直下に配置します。

# デフォルトブランチ名
default_branch_name: "main"
# パッケージ名
package_name: "PrivateXCFrameworkPackagingExampleFramework" # パッケージ名
# Package.swiftのProductの配列に含まれるLibraryの項目値
libraries:
 # Library名
 - name: "SampleFramework"
   # Libraryが内包するターゲット名の配列
   targets:
    - "SampleFramework"
# Package.swiftのTargetの配列に含まれるBinary targetの項目値
binary_targets:
 - "SampleFramework"

Pluginが実行する処理内容

  1. PrivatePackageConfig.yml記載のデフォルトブランチからBinary配布用のReleaseを発行する
  2. ./XCFrameworksディレクトリー配下に配置されたXCFrameworkをzip圧縮する
  3. XCFrameworkのzipファイルのChecksumを取得する
  4. XCFrameworkのzipファイルをStep1で、発行したReleaseのAssetsとしてアップロードする
  5. GitHub CLIを介して、Step4でアップロードしたzipファイルのAsset URLを取得する
  6. Package.swift更新作業のWorkブランチをデフォルブランチからチェックアウトする
  7. Step3、Step5で取得したURL、ChecksumをPackage.swiftに反映する
  8. Package.swift更新後、Pull Requestを自動発行する

利用手順のStep2〜7の項目を自動的に行い、Pull Requestの発行までを実行します。
発行されたPull Requestをマージ後、手動でReleaseを発行することで、任意のXCFrameworkを含むSwift PackageをPrivate Repositoryで利用可能です。

LUUP iOSアプリへの導入

現時点では、Advent Calendar向けにpluginの開発までとなっており、LUUP iOSアプリには導入できていない状況です。
GitHub API経由でRelease Assetsにアクセスするため、HTTP接続時の認証にGitHub Personal Access Tokenを用いる関係で、
Organizationのトークンポリシーの確認が必要であったり、Xcode Cloudでの動作確認が残っています。
まだまだ、導入課題は残っていますが、実現に向けて着実と進めていきたいと思います。

まとめ

今回、Public Repositoryで運用せざるを得ないRepositoryのPrivate化を狙い、簡単なFastlane Pluginを作成してみました。
今回開発したPluginによって、Xcode 13.3以後で利用できるようになったPrivate RepositoryのRelease Assetsを用いたSwift Packageをより実現しやすくなったと思います。

まだ、導入課題は残っていますが、同様の課題を抱えているiOS開発者の方の参考になれば幸いです。

Luup Developers Blog

Discussion