🚚

モバイルアプリ開発のためのプライベート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では、プライベートリポジトリへのアクセス権限を設定する必要があります。

  1. SSH公開鍵設定
  • ライブラリリポジトリのDeploy keysにSSH公開鍵を登録
    • https://github.com/<org>/<repo>/settings/keys
  1. SSH秘密鍵の登録
  • アプリ側リポジトリのGitHub ActionsのRepository secretsにSSH秘密鍵を登録
    • https://github.com/<org>/<repo>/settings/secrets/actions に任意の名前 (例: PRIVATE_REPO_SSH_KEY) で保存
  1. 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 ...
  1. 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) の作成
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)環境の設定

  1. Repository secretsの登録
  • アプリ側リポジトリのGitHub ActionsのRepository secretsにPATを登録
    • https://github.com/<org>/<repo>/settings/secrets/actions に任意の名前 (例: GRADLE_GITHUB_PAT) で保存
  1. GitHub Actionsワークフロー
    - name: Test
      run: ./gradlew test
      env:
        GITHUB_TOKEN: ${{ secrets.GRADLE_GITHUB_PAT }}
        # GITHUB_ACTOR は自動で設定される
    

プライベートリポジトリ特有の考慮点

  • 認証方式: GitHub Personal Access Token (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と同様の仕組みを使用します。

  1. SSH公開鍵設定
  • ライブラリリポジトリのDeploy keysにSSH公開鍵を登録
    • https://github.com/<org>/<repo>/settings/keys
  1. SSH秘密鍵の登録
  • アプリ側リポジトリのGitHub ActionsのRepository secretsにSSH秘密鍵を登録
    • https://github.com/<org>/<repo>/settings/secrets/actions に任意の名前 (例: PRIVATE_REPO_SSH_KEY) で保存
  1. 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を利用して、シンプルかつ実用的な構成に落ち着きました。

まとめ

プライベートリポジトリでのライブラリ公開方法と利用方法について解説しました。
各開発環境の標準的なパッケージ管理方法に沿った比較的シンプルな方法でライブラリを社内に限定して公開することができました。

開発時の情報収集では、同じことをしているが古い情報だったり、前提条件が違うため使えない手法だったりと言うことが多発しました。
本記事が同じことをしようとしている人の解決の糸口になることを望んでいます。

GENDA

Discussion