🪶

AndroidのOSSライセンス表示を簡単にする『license-scribe-plugin』を開発した

に公開
2026/01/18

license-scribe-plugin

はじめに

こんにちは、Androidエンジニアの@syarihuです。

Androidアプリ開発では、Apache 2.0やMITなどのOSSライブラリを多数利用します。これらのライセンスにはattribution(帰属表示)の義務があり、アプリ内でライセンス情報を表示する必要があります。Googleも開発者の責任としてOSSライセンス表示を求めています。

本記事では、Gradle 9に対応し、Edge to Edgeやダークテーマにも対応したOSSライセンス管理プラグイン「license-scribe-plugin」を紹介します。

2種類のプラグインの紹介

license-scribe-pluginは、用途に応じて2種類のプラグインを提供しています。

プラグイン ユースケース
license-scribe-screen-plugin テーマカラー以外のカスタマイズ不要で、手軽にOSSライセンス画面を表示したい場合
license-scribe-gradle-plugin UIをカスタマイズしたい、ライセンス情報をGit管理して意図しないライセンス(GPLなど)の混入を検知したい場合

まずは、このプラグインで何ができるのか、実際の画面を見てみましょう。

実際の画面イメージ

license-scribe-screen-pluginを使うと、次のようなOSSライセンス画面を簡単に実装できます。

Light Mode Dark Mode
Light Mode Dark Mode

また、license-scribe-gradle-pluginを使えば、Jetpack Composeで自由にUIをカスタマイズすることもできます。

Compose Example

1行でOSSライセンス画面を起動

license-scribe-screen-pluginを使えば、たった1行でOSSライセンス画面を起動できます。

OpenSourceLicensesActivity.start(context)

テーマカラーをカスタマイズしたい場合も、引数を指定するだけで対応できます。

OpenSourceLicensesActivity.start(
  context = this,
  themeColor = "#6200EE".toColorInt(),
)

themeColorでツールバーの背景色とアクセントカラーを指定できます。ツールバーのタイトル文字色はthemeColorの明るさに基づいて自動的に黒か白が選択されます。

開発の背景

筆者は本業のAndroidプロジェクトでlicense-list-pluginを利用していました。このプラグインは依存関係のライセンス情報をYAML形式で出力し、JSONやHTMLに変換できる便利なツールです。

しかし、Gradle 9対応に伴いlicense-list-pluginがdeprecatedになったため、代替ツールを探す必要がありました。

筆者が必要としていた機能

代替ツールを検討する中で、筆者のユースケースでは次のような機能が必要だとわかりました。

必要だった機能 理由
Kotlinコードの自動生成 HTMLではなくネイティブUIでライセンス画面を実装したかった
Edge to Edge対応 Android 15以降で必須となるため
ダークテーマ対応 アプリ全体がダークテーマに対応しているため
ライセンスの可視化 意図しないライセンス(GPLなど)の混入を検知しやすくしたかった

既存のツールはそれぞれの設計思想に基づいて作られており、筆者のユースケースとは方向性が異なっていました。そこで、これらの機能を備えた新しいプラグインを開発することにしました。

license-scribe-pluginで実現したこと

前述の要件を満たすため、次のような特徴を持つlicense-scribe-pluginを開発しました。

  • ライセンスファーストのYAML構造で見通しが良い
  • Kotlinコード自動生成で変換ロジック不要
  • Edge to Edge対応とダークテーマ対応
  • 「カスタマイズしたい人」と「手軽に使いたい人」の両方に対応

license-scribe-screen-plugin(手軽派向け)

oss-licenses-pluginにインスパイアされて開発したプラグインです。YAML管理不要で、1行でOSSライセンス画面を起動できます。

主な特徴

  • YAML管理不要
  • 1行で画面起動可能
  • Edge to Edge対応
  • ダークテーマ対応(デフォルトでシステム設定に追従、常にオン/オフも設定可能)
  • テーマカラーのカスタマイズ可能
  • Composeを導入していないプロジェクトでも利用可能

設計上の工夫

生成されるOpenSourceLicensesActivityは、内部的にはComposeではなく従来のView(RecyclerView)で実装しています。

Composeで実装することも可能でしたが、Composeを導入していないプロジェクトでも手軽に使えることを優先し、意図的にViewベースで生成するようにしました。

必要なAndroidX依存関係(core-ktx, activity, appcompat, recyclerview)は、ほとんどのAndroidプロジェクトで既に含まれているため、実質的にruntime libraryを追加するだけで導入できます。

導入手順

license-scribe-pluginはMaven Centralに公開しているため、追加のリポジトリ設定は不要です。build.gradle.ktsにプラグインと依存関係を追加するだけで利用できます。

// build.gradle.kts
plugins {
  id("net.syarihu.license-scribe-screen") version "<latest-version>"
}

dependencies {
  // Runtime library
  implementation("net.syarihu.licensescribe:license-scribe-runtime:<latest-version>")

  // 以下のAndroidX依存関係は生成されるActivityに必要ですが、
  // 通常のAndroidプロジェクトでは既に含まれていることがほとんどです
  // implementation("androidx.core:core-ktx:...")
  // implementation("androidx.activity:activity:...")
  // implementation("androidx.appcompat:appcompat:...")
  // implementation("androidx.recyclerview:recyclerview:...")
}

licenseScribeScreen {
  generatedPackageName.set("com.example.app")
}

generatedPackageNameには、生成されるコードのパッケージ名を指定します。

使い方

設定画面のPreferenceからOSSライセンス画面を起動する例を示します。まず、Preference XMLでライセンス項目を定義します。

<!-- res/xml/preferences.xml -->
<?xml version="1.0" encoding="utf-8"?>
<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
  <androidx.preference.PreferenceCategory android:title="About">
    <androidx.preference.Preference
      android:key="open_source_licenses"
      android:title="Open Source Licenses"
      android:summary="View licenses for open source software" />
  </androidx.preference.PreferenceCategory>
</androidx.preference.PreferenceScreen>

次に、PreferenceFragmentCompatでクリックリスナーを設定し、OSSライセンス画面を起動します。

package com.example.app

import android.os.Bundle
import androidx.preference.Preference
import androidx.preference.PreferenceFragmentCompat

class SettingsFragment : PreferenceFragmentCompat() {
  override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
    setPreferencesFromResource(R.xml.preferences, rootKey)

    findPreference<Preference>("open_source_licenses")?.setOnPreferenceClickListener {
      // デフォルトスタイルで起動
      OpenSourceLicensesActivity.start(requireContext())
      true
    }
  }
}

OpenSourceLicensesActivity.start()メソッドを呼び出すだけでOSSライセンス画面が起動します。テーマカラーをカスタマイズしたい場合は、次のようにthemeColorを指定します。

OpenSourceLicensesActivity.start(
  context = requireContext(),
  themeColor = "#6200EE".toColorInt(),
)

toolbarTitleColorは、themeColorの明るさに基づいて自動的に黒か白が選択されます。自動判定ではなく自分で色を指定したい場合は、toolbarTitleColorを明示的に設定してください。

OpenSourceLicensesActivity.start(
  context = requireContext(),
  themeColor = "#6200EE".toColorInt(),
  toolbarTitleColor = Color.WHITE, // 明示的に白を指定
)

license-scribe-gradle-plugin(カスタマイズ派向け)

license-list-pluginにインスパイアされて開発したプラグインです。ライセンス情報をYAMLで管理し、Kotlinコードを自動生成します。

ただし、license-list-pluginとはYAMLの構造や仕組みが全く異なるため、互換性はありません。license-list-pluginから移行する場合は、新規でYAMLを生成する必要があります。

主な特徴

  • ライセンスファーストのYAML構造で、プロジェクトで使用しているライセンスが一目でわかる
  • Kotlinコード自動生成でUI実装が自由
  • マルチモジュール対応
    • appモジュールが依存するモジュールの依存関係も推移的に取得するため、必要なライセンスを正しく取得可能
    • 生成されるコードはLicenseProviderインターフェースを実装しているため、DIで扱いやすい

導入手順

build.gradle.ktsにプラグインと依存関係を追加します。

// build.gradle.kts
plugins {
  id("net.syarihu.license-scribe") version "<latest-version>"
}

dependencies {
  implementation("net.syarihu.licensescribe:license-scribe-runtime:<latest-version>")
}

licenseScribe {
  baseDir.set(layout.projectDirectory.dir("licenses"))
  generatedPackageName.set("com.example.app")
}

baseDirにはライセンス定義ファイルを配置するディレクトリを、generatedPackageNameには生成されるコードのパッケージ名を指定します。

初期設定

プラグインを追加したら、初期化タスクを実行してYAMLファイルを生成します。

./gradlew scribeLicensesDebugInit

このコマンドを実行すると、licenses/debug/scribe-licenses.ymlが生成されます。

タスク名のDebug部分はビルドバリアントに応じて変わります。Releaseバリアントの場合はscribeLicensesReleaseInit、Product Flavorsを使用している場合はscribeLicensesStagingDebugInitのようにフレーバー名とビルドタイプを組み合わせた名前になります。以降のタスク(Sync、Generate、Reportなど)も同様です。

YAMLファイルの出力先もバリアントごとに異なります(licenses/debug/、licenses/release/など)。複数のビルドバリアントを使用している場合は、それぞれのYAMLファイルをGitに含める必要があります。

YAMLの構造

生成されるYAMLはライセンスファーストの構造になっています。次に生成されるYAMLの例を示します。

licenses:
  apache-2.0:
    name: Apache License 2.0
    url: https://www.apache.org/licenses/LICENSE-2.0
    artifacts:
      com.squareup.okhttp3:
      - name: okhttp
        url: https://square.github.io/okhttp/
        copyrightHolders:
        - Square, Inc.
      com.squareup.retrofit2:
      - name: retrofit
        url: https://github.com/square/retrofit
        copyrightHolders:
        - Square, Inc.

  mit:
    name: MIT License
    url: https://opensource.org/licenses/MIT
    artifacts:
      org.checkerframework:
      - name: checker-qual
        url: https://checkerframework.org
        copyrightHolders:
        - Michael Ernst

トップレベルにライセンスの種類(apache-2.0、mitなど)があり、その下にそのライセンスを使用しているアーティファクトがグループ化されています。これにより、プロジェクトで使用しているライセンスの種類が一目でわかります。

YAMLの編集

POMにライセンス情報や著作権者情報が含まれていない場合、ライセンスはunknownとして、copyrightHoldersは空の状態で生成されます。これらの情報は手動で編集して正しい値を設定する必要があります。

licenses:
  unknown:
    name: Unknown License
    url: null
    artifacts:
      com.example:
      - name: some-library
        copyrightHolders: []  # 著作権者を手動で追加

上記のようなunknownライセンスのアーティファクトは、正しいライセンス(apache-2.0やmitなど)の下に移動し、copyrightHoldersを追記してください。

依存関係の更新

依存関係を追加・削除した場合は、Syncタスクを実行してYAMLを更新します。

./gradlew scribeLicensesDebugSync

Syncを実行すると、新しい依存関係が追加され、削除された依存関係が取り除かれます。手動で編集したライセンス情報やcopyrightHoldersなどの情報は引き継がれるため、一度編集した内容が失われることはありません。

KotlinコードはassembleDebug時に自動生成されるため、YAMLの更新だけで完了します。

Composeでの利用例

生成されたKotlinコードを使って、Jetpack ComposeでOSSライセンス画面を実装する例を示します。

package com.example.app.ui

import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material3.Card
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalUriHandler
import androidx.compose.ui.unit.dp
import com.example.app.AppLicenses
import net.syarihu.licensescribe.LicenseInfo

@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun OpenSourceLicensesScreen() {
  val uriHandler = LocalUriHandler.current

  Scaffold(
    topBar = {
      TopAppBar(title = { Text("Open Source Licenses") })
    },
  ) { innerPadding ->
    LazyColumn(
      modifier = Modifier
        .fillMaxSize()
        .padding(innerPadding),
      contentPadding = PaddingValues(vertical = 8.dp),
    ) {
      items(AppLicenses.all, key = { it.artifactId }) { license ->
        LicenseItem(
          license = license,
          onItemClick = {
            license.artifactUrl?.let { uriHandler.openUri(it) }
          },
          onLicenseClick = {
            license.licenseUrl?.let { uriHandler.openUri(it) }
          },
        )
      }
    }
  }
}

@Composable
private fun LicenseItem(
  license: LicenseInfo,
  onItemClick: () -> Unit,
  onLicenseClick: () -> Unit,
) {
  Card(
    modifier = Modifier
      .fillMaxWidth()
      .padding(horizontal = 16.dp, vertical = 8.dp)
      .clickable(onClick = onItemClick),
  ) {
    Column(modifier = Modifier.padding(16.dp)) {
      Text(
        text = license.artifactName,
        style = MaterialTheme.typography.titleMedium,
      )
      Text(
        text = license.artifactId,
        style = MaterialTheme.typography.bodySmall,
        color = MaterialTheme.colorScheme.onSurfaceVariant,
      )
      if (license.copyrightHolders.isNotEmpty()) {
        Text(
          text = license.copyrightHolders.joinToString(", "),
          style = MaterialTheme.typography.bodySmall,
          color = MaterialTheme.colorScheme.onSurfaceVariant,
        )
      }
      Text(
        text = license.licenseName,
        style = MaterialTheme.typography.bodySmall,
        color = MaterialTheme.colorScheme.primary,
        modifier = Modifier
          .padding(top = 8.dp)
          .clickable(
            enabled = license.licenseUrl != null,
            onClick = onLicenseClick,
          ),
      )
    }
  }
}

AppLicenses.allで全てのライセンス情報を取得し、LazyColumnで一覧表示しています。LicenseInfoにはartifactName、artifactId、copyrightHolders、licenseName、licenseUrlなどの情報が含まれており、自由にUIを構築できます。

license-scribe-hilt-plugin(オプション)

マルチモジュールプロジェクトでHiltを利用している場合に便利なオプションプラグインです。

ユースケース

  • マルチモジュールプロジェクトでHiltを利用している
  • featureモジュール(例: 設定画面)からOSSライセンス画面を起動したい

導入手順

appモジュールのbuild.gradle.ktsにプラグインを追加します。

// app/build.gradle.kts
plugins {
  id("net.syarihu.license-scribe") version "<latest-version>"
  id("net.syarihu.license-scribe-hilt") version "<latest-version>"
}

これにより、次のようなHiltモジュールが自動生成されます。

@Module
@InstallIn(SingletonComponent::class)
object LicenseScribeHiltModule {
  @Provides
  @Singleton
  fun provideLicenseProvider(): LicenseProvider = AppLicenses
}

featureモジュールでの利用

featureモジュールでは、runtime libraryのみを依存関係に追加します。

// feature/settings/build.gradle.kts
dependencies {
  implementation("net.syarihu.licensescribe:license-scribe-runtime:<latest-version>")
}

ViewModelでは、LicenseProviderインターフェースを通じてライセンス情報を取得できます。

@HiltViewModel
class LicenseViewModel @Inject constructor(
  private val licenseProvider: LicenseProvider
) : ViewModel() {
  val licenses: List<LicenseInfo> = licenseProvider.all
}

LicenseProviderはインターフェースなので、Hilt以外のDIフレームワークを使っている場合でも、生成されたAppLicensesをDIコンテナに登録すれば同様に利用できます。

便利機能の紹介

CIでのライセンスチェック

CIでライセンスの差分チェックを自動化したい場合は、Checkタスクを使用します。

./gradlew scribeLicensesDebugCheck

このタスクは、YAMLに未記録の依存関係(Missing)や削除済みの依存関係(Removed)が存在する場合にエラーで終了します。これにより、依存関係の追加・削除時にYAMLの更新漏れをCIで検出できます。

HTML Diff Report機能

license-scribe-gradle-pluginには、依存関係の変更を確認できるHTML Diff Report機能があります。リリース前に依存関係の漏れがないか確認したい場合に便利です。

./gradlew scribeLicensesDebugReport

このコマンドを実行すると、次のようなレポートが生成されます。

Diff Report

レポートでは、依存関係のステータスが色分けで表示されます。

  • Covered: YAMLに記録済みの依存関係
  • Missing: プロジェクトに存在するがYAMLに未記録の依存関係
  • Removed: YAMLに記録されているがプロジェクトから削除された依存関係

まとめ

license-scribe-pluginは、用途に応じたプラグインを提供しています。

要件 おすすめプラグイン
手軽にOSSライセンス画面を表示したい license-scribe-screen-plugin
UIをカスタマイズしたい、ライセンス情報をGit管理して意図しないライセンスの混入を検知したい license-scribe-gradle-plugin
マルチモジュール + Hilt license-scribe-gradle-plugin + license-scribe-hilt-plugin

ぜひ、プロジェクトの要件に合わせて活用してみてください。

リンク

Discussion