ソースコードを開示せずにユーザーにXCFrameworkを配布する一番の方法はXCFrameworkをSwift Package Managerで配布することです。
この記事は、配布する側の立場で陥りやすい罠とその回避方法をまとめたものです。
やってみたけどハマってしまった場合に読んでみたり、罠にハマる前に勉強しておきたい方は読んでみてください。
また、CocoaPodsからSwift Package Managerに乗り換えようとしてハマっている方は前作をまず読むのがおすすめです。
https://zenn.dev/d_date/articles/cd4ce3b2b5c29d
今回の記事は、Private RepositoryにXCFrameworkをホストする必然性が出てきた方に向けた内容です。
XCFrameworkを配布する方法
XCFrameworkを作成する手順は省略しますが、Swift Package ManagerがPrivate Repositoryにアクセスするためには、ユーザーがそのリポジトリにアクセスできるようにしておく必要があります。
XCFrameworkを配布するためには、以下の選択肢が思い浮かびます。
a. △ 直接Commitする
b. × Git LFS( Large File Storage )を使う
c. ○ Releaseにアップロードする
d. × Package Registryを使う
まずaについては、容量の大きいバイナリを直接Commitするのは避けるべきです。
Gitの履歴が肥大化してしまい、クローンやフェッチに時間がかかるようになってしまいます。それだけではなく、GitHubのリポジトリサイズ制限に引っかかる可能性もあります。
次にbについて、これも推奨しません。
Git LFSはGitの拡張機能であり、GitHubのFreeプランでは月間10GBまでしか使えません。また通信量にも制限や課金が発生します。
dについては、Swift Package ManagerのPackage Registryにはまだ対応していません。
したがって、cのReleaseにアップロードする方法を採用します。
Releaseにアップロードする
Releaseにアップロードするには、GitHubのリポジトリのReleaseタブから行います。
事前にコミットにtagを付けておき、そのtagに対応するReleaseを作成し、AssetsにXCFrameworkをドラッグ&ドロップするだけです。
Releaseにアップロードした後、Releaseのページにアクセスすると、アップロードしたXCFrameworkのURLを取得できます。
これをPackage.swiftのbinaryTarget
に指定します。
import PackageDescription
let package = Package(
name: "MySDK",
platforms: [.iOS(.v14)],
products: [
.library(
name: "MySDK",
targets: ["MySDK"])
],
dependencies: [],
targets: [
.binaryTarget(
name: "MySDK",
url: "https://api.github.com/repos///releases/assets/.zip" ,
checksum: ""
)
]
)
は組織名、
はリポジトリ名、
はReleaseにアップロードしたXCFrameworkのAsset IDです。
はXCFrameworkのSHA256チェックサムです。
チェックサムは以下のコマンドで計算できます。
swift package compute-checksum SDK.xcframework.zip
のはずですが、実際にPackage.swiftに指定してみるとchecksumが間違っていると怒られます。その時正しいチェックサムを教えてくれるので、それを指定すると確実です。
さて、ここで404 Not Foundエラーが発生する場合があります。
ReleaseのAssetは認証が必要なため、Swift Package Managerがアクセスできない場合があります。
この場合はnetrcを使って認証情報を提供する必要があります。
.netrcを使って認証情報を提供する
netrcファイルは、ユーザーのホームディレクトリに配置されるテキストファイルで、FTPやHTTPクライアントが使用する認証情報を保存します。
Swift Package Managerもこのファイルを参照して認証情報を取得します。
netrcファイルの例は以下の通りです。
machine github.com
login
password
はGitHubのユーザー名です。
はPersonal Access Tokenを利用できますが、今回はGitHub Appを使う方法を紹介します。Personal Access Tokenは、GitHubのSettings > Developer settings > Personal access tokensから作成できますが、今回はGitHub Appを使う方法を紹介します。
[!WARNING] PATは簡単に作成できますが、漏洩するとリポジトリに不正アクセスされる可能性があるのと、有効期限が切れる前にローテーションする必要があり面倒です。
netrcファイルを作成したら、~/.netrc
に配置します。
GitHub Appを使って認証情報を提供する
GitHub Appは、GitHubのAPIにアクセスするためのアプリケーションです。
GitHub Appを作成し、必要な権限を設定します。
Settings
> Developer settings
> GitHub Apps
から新しいGitHub Appを作成します。
GitHub Appを作成したら、Client ID
とPrivate Key
を取得します。App IDは今回は利用しません。
スクロールしていくと、Private Keyを生成するボタンがあります。
Private KeyはPEM形式でローカルに保存されます。
次に、GitHub Appをインストールします。
左側のメニューからInstall Appを選択し、インストールしたいリポジトリを選択します。
このリポジトリはSDKを利用する側と、SDKを配布する側の両方にインストールする必要があります。
Xcode Cloudで利用する
Xcode CloudでSwift Package Managerを利用する場合、netrcファイルをクローンしたタイミングで配置する必要があります。そのために、 ci_post_clone.sh
スクリプトを利用します。
環境変数の設定
スクリプトでは、環境変数からGitHub AppのPrivate Key、Client ID、Installation IDを取得し、JWTを生成してGitHub APIにアクセスし、インストールトークンを取得します。
本来は、Installation IDはAPIを都度叩いて取得しますが、実はこれは固定値なので、環境変数として直接設定しています。
Xcode Cloudの(Shared) Environment VariablesにPEMを入れる場合はBase64エンコードして改行コードを削除したものを設定します。そのまま入れるとPrivate Keyが見つからないという罠に陥ります。
base64 -i private_key.pem | pbcopy
JWTを生成してインストールトークンを取得する
以下のようにJWTを生成します。JWTの有効期限は10分以内に設定します。
b64url() { openssl base64 -A | tr '+/' '-_' | tr -d '='; }
pem_file="$(mktemp)"
trap 'rm -f "$pem_file"' EXIT
echo "$PRIVATE_KEY" | base64 -d > "$pem_file" 2>/dev/nul
tr -d '\r' "$pem_file" > "${pem_file}.clean" && mv "${pem_file}.clean" "$pem_file"
grep -q "BEGIN .*PRIVATE KEY" "$pem_file"
openssl pkey -in "$pem_file" -noout >/dev/null 2>&1
now=$(date -u +%s)
iat=$((now - 60))
exp=$((now + 540))
header='{"alg":"RS256","typ":"JWT"}'
payload=$(cat JSON
{"iat":${iat},"exp":${exp},"iss":"${CLIENT_ID}"}
JSON
)
h_b64=$(printf %s "$header" | b64url)
p_b64=$(printf %s "$payload" | b64url)
sig_b64=$(printf '%s.%s' "$h_b64" "$p_b64" | openssl dgst -binary -sha256 -sign "$pem_file" | b64url)
JWT="${h_b64}.${p_b64}.${sig_b64}"
Access Tokenを取得する
APIを叩いてAccess Tokenを取得します。
resp_with_code=$(
curl -sS -X POST \
-w "\n%{http_code}" \
-H "Authorization: Bearer ${JWT}" \
-H "Accept: application/vnd.github+json" \
"https://api.github.com/app/installations/${INSTALLATION_ID}/access_tokens"
)
resp_body="${resp_with_code%$'\n'*}"
GH_TOKEN=$(printf %s "$resp_body" | jq -r '.token // empty')
EXPIRES_AT=$(printf %s "$resp_body" | jq -r '.expires_at // empty')
netrcファイルを作成する
取得したAccess Tokenを使ってnetrcファイルを作成します。この時ユーザー名はx-access-token
を指定します。
{
echo "machine github.com login x-access-token password ${GH_TOKEN}"
echo "machine api.github.com login x-access-token password ${GH_TOKEN}"
} > "$HOME/.netrc"
chmod 600 "$HOME/.netrc"
Gitの設定を変更する
Remote URLにAccess Tokenを含めるようにGitの設定を変更します。
git config --global url."https://x-access-token:${GH_TOKEN}@github.com/".insteadOf "https://github.com/"
おまけ
post_clone.shスクリプトの最後に以下を追加しておくと、CI上でSwift Package Managerを利用する場合にPackage PluginやMacroのFingerprint Validationをスキップすることができます。
defaults write com.apple.dt.Xcode IDESkipPackagePluginFingerprintValidatation -bool YES
defaults write com.apple.dt.Xcode IDESkipMacroFingerprintValidation -bool YES
[!NOTE] IDESkipPackagePluginFingerprintValidatationは IDESkipPackagePluginFingerprintValidation ではありません。タイプミスに注意してください。
Views: 0