モバイルアプリ開発のためのプライベートSDKの社内展開手法
モバイル開発部の庄司です。
本記事では、GENDAが直面した、iOS、Android、Flutterといった多様な開発環境に対応するモバイルアプリ向けプライベートSDKの社内配布における課題と、その解決策について詳しく解説します。
グループ内のサービスで共通IDプラットフォーム「GENDA ID」を利用するためのSDKを、プライベートリポジトリで管理しつつ、各プラットフォームの標準パッケージマネージャーで導入できるようにした具体的な手法を紹介します。これにより、社内限定のライブラリ配布における特有の課題解決のヒントになれば幸いです。
背景
なぜプライベートSDKが必要になったか
GENDAはM&Aを主軸に成長を続ける企業として、iOS・Android・Flutterといった様々な開発環境のモバイルアプリを運営しています。2024年には GENDA ID という共通IDプラットフォームを立ち上げ、グループ内のサービスからこの共通のログイン基盤を利用できるようにしました。
ここで課題となったのが、複数のプラットフォーム向けに社内限定でSDKを配布する方法です。
GENDA ID SDKとは
GENDA ID SDKは、グループ内のモバイルアプリから OpenID Connect (OIDC) でGENDA IDにログインするための機能を提供するライブラリです。2024年末時点ではFlutter SDKのみでしたが、現在は以下の3プラットフォームに対応しています。
- iOS SDK: Swift + ASWebAuthenticationSession
- Android SDK: Kotlin + Android Custom Tabs
- Flutter SDK: Dart + OIDCサードパーティライブラリ
社内公開・利用の要件
ライブラリを社内に公開する方法を検討する際、以下の要件を重視しました。
ユーザー視点での要件
- 低コストな導入: 各プラットフォームの標準的なパッケージマネージャーで導入できること
- 開発者体験の維持: 既存の開発フローを変更せず、普段通りの開発ができること
技術的要件
- 組織横断的な利用: GENDAではGitHub Enterpriseを活用し、グループにおける複数のGitHub Organizationを運用しているため、SDKのリポジトリとは別のOrganizationからも利用可能であること
- CI/CD対応: GitHub Actionsからシームレスに参照できること
- セキュリティ: プライベートリポジトリとして適切にアクセス制御されていること
選定した方法
上記の要件から、各プラットフォームの展開方法は下記の通りに選定しました。
- iOS: GitHubリポジトリのライブラリプロジェクトをアプリのPackage.swiftからSSHで参照
- Android: GitHub PackagesにMavenリポジトリとして公開し、アプリ側のbuild.gradleから参照
- Flutter: GitHubリポジトリのライブラリプロジェクトをアプリのpubspec.yamlからSSHで参照
ここから各プラットフォームでの詳細を説明していきます。
iOS
イメージ
プライベートリポジトリに置いたソースコードをSSH接続で取得します。
ライブラリの公開方法
iOSアプリ用ライブラリは、Swift Package Manager (SPM)経由で配布しています。
リリース方法はGitのtagをpushするだけで特別なサーバーは不要です。
Package.swiftの設定
サードパーティライブラリに依存していないのでシンプルなパッケージ定義で、特別な設定は不要です。
// Package.swift
let package = Package(
name: "GendaIDClient",
platforms: [.iOS(.v16)],
products: [
.library(
name: "GendaIDClient",
targets: ["GendaIDClient"]
)
],
// ... 省略
)
ライブラリを利用するアプリ側
Package.swiftでの依存関係追加
プライベートリポジトリのライブラリを参照する際、SSH形式のURLを指定します。
開発者のローカル環境では、GitHubの該当リポジトリへのSSHアクセスが設定されていれば追加設定は不要です。
dependencies: [
// Git tagのセマンティックバージョン
.package(url: "git@github.com:example_org/ios-sdk.git", from: "x.y.z"),
],
targets: [
.target(
name: "<your-target-name>",
dependencies: [
.product(name: "GendaIDClient", package: "ios-sdk"),
]
),
]
import GendaIDClient
let client: GendaIDClient = .init(
id: "<Client ID>",
redirectURL: URL(string: "example://callback")!
)
Task {
let response = try await client.authorize(
...
)
}
CI (GitHub Actions)環境での設定
GitHub Actionsでは、プライベートリポジトリへのアクセス権限を設定する必要があります。
- SSH公開鍵設定
- ライブラリリポジトリのDeploy keysにSSH公開鍵を登録
https://github.com/<org>/<repo>/settings/keys
- SSH秘密鍵の登録
- アプリ側リポジトリのGitHub ActionsのRepository secretsにSSH秘密鍵を登録
-
https://github.com/<org>/<repo>/settings/secrets/actions
に任意の名前 (例:PRIVATE_REPO_SSH_KEY
) で保存
-
- GitHub Actionsワークフローの設定
steps:
- name: Setup for Private Repo
uses: ./.github/actions/setup-for-private-repo # SSH秘密鍵設定 (後述)
with:
ssh_key: ${{ secrets.PRIVATE_REPO_SSH_KEY }}
- run: |
xcodebuild test ...
- setup-for-private-repo アクションの配置
GitHubのrepository secretのSSH秘密鍵を受け取って、SSH接続するcomposite actionを置いておきます。
# 一部抜粋
env:
SSH_KEY: ${{ inputs.ssh_key }}
run: |
mkdir -p ~/.ssh
echo "${SSH_KEY}" | tr -d "\r" > ~/.ssh/secret_key
chmod 600 ~/.ssh/secret_key
eval $(ssh-agent -s)
ssh-add ~/.ssh/secret_key
ssh-keyscan -H github.com >> ~/.ssh/known_hosts
echo "GIT_SSH_COMMAND=ssh -i ~/.ssh/secret_key" >> "${GITHUB_ENV}"
これらの設定によりiOSアプリ開発でプライベートリポジトリに置いたライブラリを参照できるようになりました。
Android
イメージ
GitHub PackagesにMavenリポジトリとして公開し、アプリ側のsettings.gradleから参照します。
プライベートリポジトリに置いたpackageを参照するにはGitHub Personal Access Token (PAT)を使います。
ライブラリの公開方法
GitHub Actionsのリリース用workflowでGitHub Packagesにアップロードするため、Mavenリポジトリのcredentialsを設定します。
build.gradle.ktsでの公開設定
publishing {
repositories {
maven {
name = "GitHubPackages"
url = uri("https://maven.pkg.github.com/example_org/android-sdk")
credentials {
username = System.getenv("GITHUB_ACTOR")
password = System.getenv("GITHUB_TOKEN") # workflowから渡される環境変数
}
}
}
publications {
create<MavenPublication>("release") {
groupId = "org.example"
artifactId = "android-sdk"
version = "x.y.z"
afterEvaluate {
from(components["release"])
}
}
}
}
release.yml
GitHub Actionsのリリース用workflowで ./gradlew publish
を実行します。
workflowは自分自身のリポジトリへアクセスするため、credentialsとして ${{ github.token }}
を利用できます。
- name: Publish
run: ./gradlew publish
env:
GITHUB_TOKEN: ${{ github.token }} # 認証トークンを環境変数に設定する
# GITHUB_ACTOR は自動で設定される
ライブラリを利用するアプリ側
GitHub Packagesの利用設定
1. GitHub Personal Access Token (PAT) の作成
- GitHub PAT (classic) を作成
- 必要な権限:
read:packages
のみ - 有効期限は適切に設定
2. ローカル開発環境の設定
local.properties (Gitで管理しない):
github.username=<GitHub Username>
github.pat=<GitHub PAT classic>
settings.gradle.kts:
- ローカル開発環境: local.properties (Git管理されていないファイル)からPATを読み込みます。
- CI (GitHub Actions): repository secret GitHub Actionsのリリース用workflow内の
${{ github.token }}
を環境変数から読み込みます。
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories {
google()
mavenCentral()
// GitHub Packages の設定
maven {
url = uri("https://maven.pkg.github.com/example_org/android-sdk")
credentials {
// .gitignore された local.properties からGitHubのusernameとPATを読み込む
val localProperties = Properties().apply {
File("local.properties").let {
if (it.exists()) {
this.load(FileInputStream(it))
}
}
}
// CI環境では環境変数から取得
username = localProperties.getProperty("github.username")
?: System.getenv("GITHUB_ACTOR")
password = localProperties.getProperty("github.pat")
?: System.getenv("GITHUB_TOKEN")
}
}
}
}
3. プロジェクトへの依存関係追加
libs.versions.toml:
[versions]
gendaidclient = "x.y.z" # Gitタグのセマンティックバージョン
[libraries]
gendaid-client = { group = "org.example", name = "android-sdk", version.ref = "gendaidclient" }
build.gradle:
dependencies {
implementation(libs.gendaid.client)
}
CI (GitHub Actions)環境の設定
- Repository secretsの登録
- アプリ側リポジトリのGitHub ActionsのRepository secretsにPATを登録
-
https://github.com/<org>/<repo>/settings/secrets/actions
に任意の名前 (例:GRADLE_GITHUB_PAT
) で保存
-
-
GitHub Actionsワークフロー
- name: Test run: ./gradlew test env: GITHUB_TOKEN: ${{ secrets.GRADLE_GITHUB_PAT }} # GITHUB_ACTOR は自動で設定される
プライベートリポジトリ特有の考慮点
-
認証方式: GitHub Personal Access Token (PAT)を使用
- GitHub Packagesへのアクセスには PAT (classic)での認証しか対応していない のが歯がゆいところです。
- ライブラリを参照する開発者は
read:packages
権限のみを設定したPATを作成します。
ここまでの設定でAndroidアプリでプライベートリポジトリのライブラリを扱うことができるようになりました。
Flutter
イメージ
FlutterアプリのライブラリはiOSと同じくGitリポジトリから直接参照する方式を採用しています。
特別なパッケージレジストリは使用せず、Gitのタグでバージョン管理を行います。
ライブラリの公開方法
pubspec.yamlの設定
iOS同様シンプルなパッケージ定義で、特別な設定は不要です。
name: genda_id_client
description: "A new Flutter package project."
version: x.y.z
homepage: "https://github.com/example_org/flutter-sdk"
publish_to: "none" # pub.devへの公開を無効化
dependencies:
flutter:
sdk: flutter
# その他の依存関係...
ライブラリを利用するアプリ側
プライベートリポジトリのライブラリを参照する際、SSH形式のURLを指定します。
pubspec.yamlでの依存関係追加
dependencies:
genda_id_client:
git:
url: git@github.com:example_org/flutter-sdk.git
ref: x.y.z # Gitタグのセマンティックバージョン
ローカル開発環境での設定
開発者のローカル環境では、GitHubの該当リポジトリへのSSHアクセスが設定されていれば追加設定は不要です。
CI (GitHub Actions)環境での設定
FlutterのプライベートリポジトリアクセスもiOSと同様の仕組みを使用します。
- SSH公開鍵設定
- ライブラリリポジトリのDeploy keysにSSH公開鍵を登録
https://github.com/<org>/<repo>/settings/keys
- SSH秘密鍵の登録
- アプリ側リポジトリのGitHub ActionsのRepository secretsにSSH秘密鍵を登録
-
https://github.com/<org>/<repo>/settings/secrets/actions
に任意の名前 (例:PRIVATE_REPO_SSH_KEY
) で保存
-
- GitHub Actionsワークフローの設定
steps:
- name: Setup for Private Repo
uses: ./.github/actions/setup-for-private-repo # iOSの方で利用したものと同じ
with:
ssh_key: ${{ secrets.PRIVATE_REPO_SSH_KEY }}
- run: flutter pub get
プライベートリポジトリ利用のポイント
- 統一された認証方式: iOS/Flutter共にSSH認証で統一
- シンプルな設定: 標準的なGit参照で完結
- メンテナンス性: パッケージレジストリの管理が不要
pub (Dartのパッケージマネージャー)はSSH形式のリポジトリ参照が標準でサポートされているため、プライベートリポジトリの扱いも比較的シンプルです。
SSH鍵さえ適切に設定すれば、pub.devの公開パッケージと同様に利用できます。
試行錯誤してやめたこと
プライベートリポジトリのライブラリ配布方法を検討する中で、いくつかの方法を試しましたが、最終的に採用しなかったものがあります。
GitHub Appトークンの利用
GitHub Appを使った認証を検討しました。
GitHub Appトークンの有効期限が1時間と短く、CIで動作するたびに生成されるので安全だということで調査をしたのですが、弊社のようにGitHub Enterprise配下に複数のOrganizationがあり、それらのOrganizationにあるリポジトリを跨って利用するのには向いていないようで採用に至りませんでした。
プライベートパッケージレジストリの構築
- 独自のパッケージレジストリサーバーの構築も検討
- 運用コストと複雑性から見送り
Git Submoduleの利用
- バージョン管理の柔軟性に欠ける
- 各プラットフォームのパッケージマネージャーとの相性が悪い
最終的に、各開発環境の標準的な方法 (SPM、Gradle、pub)を使いつつ、認証はSSHとPATを利用して、シンプルかつ実用的な構成に落ち着きました。
まとめ
プライベートリポジトリでのライブラリ公開方法と利用方法について解説しました。
各開発環境の標準的なパッケージ管理方法に沿った比較的シンプルな方法でライブラリを社内に限定して公開することができました。
開発時の情報収集では、同じことをしているが古い情報だったり、前提条件が違うため使えない手法だったりと言うことが多発しました。
本記事が同じことをしようとしている人の解決の糸口になることを望んでいます。
Discussion