プライベートリポジトリのRelease AssetsにXCFrameworkをホストして、Swift Package Manager・CocoaPodsで配布する

:::note info
この記事は [ZOZO Advent Calendar 2023](https://qiita.com/advent-calendar/2023/zozo) Vol.8の16日目の記事です。
:::

こんにちは。株式会社ZOZO 計測プラットフォーム開発本部 計測アプリ部 iOSブロックの[@na9ain](https://twitter.com/na9ain)です。

本記事では、GitHubのプライベートリポジトリのRelease AssetsでXCFrameworkをホストして、Swift Package Manager・CocoaPodsで配布・利用する方法を紹介します。

# はじめに

XCFrameworkをサーバー上でホストする必要性が生じる典型的な例は、XCFrameworkに含まれるファイルのサイズが100MBを超えるときです。

分かりやすさのために、このリポジトリを例として挙げます。

https://github.com/thenagain/opencv-xcframework

このリポジトリではSwift Package Manager・CocoaPodsでOpenCVのXCFrameworkを利用できるようにしています。

OpenCVのXCFrameworkはビルド時に、不要なモジュールを除外するなどの工夫をしない限り、バイナリのサイズが100MBを超えてしまい、GitHubのリポジトリにそのまま置くことができません。
このような問題の解決策の1つが、XCFrameworkをリポジトリに置くのではなく、Release Assetsでホストする方法です。

ここからは、このリポジトリを具体例として、配布側と利用側で必要な手順を紹介していきます。

# 配布編

## XCFrameworkをzipにする
配布したいXCFrameworkをzipファイルにします。

```:例
zip -X -r -y opencv2.xcframework-4.8.1.zip opencv2.xcframework
```

## XCFrameworkのzipをRelease Assetsにアップロードする
「Releases > Create a new release」から新しいリリースを作成します。
![image 2023-12-16 0.03.01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2286493/68e5d533-26e2-0cab-3b07-ddafd4c1ff61.png)


「↓ Attach binaries by dropping them here or selecting them.」からXCFrameworkのzipを選択して、アップロードします。
![image 2023-12-16 0.03.42.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2286493/3e0e1467-41d7-8b34-010d-b3d92d3f88c1.png)

アップロードが完了したら、「Save draft」で作成したリリースをドラフトとして保存しておきます。

## GitHub API URLを取得する

パブリックリポジトリであれば、`https://github.com/thenagain/opencv-xcframework/releases/download/4.8.1/opencv2.xcframework-4.8.1.zip` という風に直接URLを指定してもよいのですが、プライベートリポジトリの場合、GitHub APIのURLを指定する必要があります。

Release AssetsにアップロードしたファイルのGitHub API URLはcurlコマンドで確認することができます。

```:curlコマンドの例
curl -L \
  -H "Accept: application/vnd.github+json" \
  -H "Authorization: Bearer ghp_XXXXXXXXXXXXXXXXXX"\
  https://api.github.com/repos/thenagain/opencv-xcframework/releases
```

```json:レスポンスの例
[
  {
    "url": "https://api.github.com/repos/thenagain/opencv-xcframework/releases/134107686",
    .
    .
    .
    "assets": [
      {
        "url": "https://api.github.com/repos/thenagain/opencv-xcframework/releases/assets/140864041",
        "id": 140864041,
        "node_id": "RA_kwDOK6Gkas4IZWop",
        "name": "opencv2.xcframework-4.8.1.zip",
        .
        .
        .
        },
    .
    .
    .
  }
]
```

## パッケージファイルの記述

アップロードしたファイルのGitHub API URLをパッケージファイルに記述します。

### Swift Package Managerの場合

```swift:Package.swift
import PackageDescription

let version = "4.8.1"
let url = "https://api.github.com/repos/thenagain/opencv-xcframework/releases/assets/140864041.zip"
let sha256 = "44582a5486454e05d349d295cee520608ee9efe603b3305741fdf86e5c8754ad"

let package = Package(
    name: "opencv2",
    platforms: [
        .iOS(.v13)
    ],
    products: [
        .library(
            name: "opencv2",
            targets: ["opencv2"]),
    ],
    targets: [
        .binaryTarget(name: "opencv2",
                      url: url,
                      checksum: sha256)
    ]
)
```

:::note warn
`Package.swift`で注意したいこと
- URLの末尾に`.zip`を加える必要がある
- checksumを記述する必要がある

```sh:checksumを出力するコマンドの例
swift package compute-checksum opencv2.xcframework-4.8.1.zip
```
:::

### CocoaPodsの場合

```ruby:opencv2.podspec
Pod::Spec.new do |spec|
    spec.name         = "opencv2"
    spec.version      = "4.8.1"
    spec.summary      = "OpenCV (Computer Vision) for iOS."
    spec.description  = "OpenCV: Open Source Computer Vision Library."
    spec.homepage     = "https://opencv.org"
    spec.license      = { :type => "Apache License, Version 2.0", :url => "https://www.apache.org/licenses/LICENSE-2.0" }
    spec.authors      = "https://github.com/opencv/opencv/graphs/contributors"
    spec.platform = :ios, "13.0"

    spec.source = {
      :http => "https://api.github.com/repos/thenagain/opencv-xcframework/releases/assets/140864041",
      :type => "zip",
      :headers => ["Accept: application/octet-stream"]
    }

    spec.vendored_frameworks = "opencv2.xcframework"
end
```

:::note warn
`.podspec`で注意したいこと
- `source`で`:type => "zip"`と指定する
- `source`で`:headers => ["Accept: application/octet-stream"]`と指定する
:::

## （Optional） ドラフトリリースをパブリッシュする
パッケージファイルの変更とタグを`git push`したら、ドラフトで保存していたリリースを編集して、パブリッシュするとよいでしょう。
Organizationのプライベートリポジトリの場合、ドラフトリリースはWrite以上の権限を持っていないとアクセスすることができないからです[^1]。
[^1]: https://docs.github.com/ja/organizations/managing-user-access-to-your-organizations-repositories/managing-repository-roles/repository-roles-for-an-organization

なお、ドラフトリリースをパブリッシュしてもRelease AssetsのGitHub API URLは変わらないので安心してください。

# 利用編

配布側に対して、利用側に必要な手順は簡潔です。

## `~/.netrc`の設定
プライベートリポジトリのRelease Assetsにアクセスするためには、[personal access token](https://docs.github.com/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens)による認証が必要です。
Swift Package Manager・CocoaPodsともに`~/.netrc`を参照してくれるため、`~/.netrc`にその記述があれば、アクセスすることができます。

```:~/.netrcの例
machine api.github.com
login thenagain
password ghp_XXXXXXXXXXXXXXXXXX
```

# おわりに

XCFrameworkをサーバー上でホストする場合の選択肢はいろいろあると思いますが、この記事で紹介した方法のメリットは、利用側で必要なアクセス権をGitHubのリポジトリのみに絞れることだと思っています。ぜひ試してみてください。