✂️

Google OSS License Gradle Pluginのデータをパースする

2022/04/15に公開

背景

Cookpad社が提供していたLicenseToolsPluginを公私にわたり大変お世話になっていましたが、ついにDeprecatedとなりリポジトリもアーカイブになりました。

同じような機能をもったライブラリの移行先としていろいろなものがありますが、有力な候補の1つとしてGoogleが提供するOSS License Gradle Pluginがあります。

Gradle Pluginを導入するだけでほとんど設定をする必要もなく画面まで作れてしまうのですがその代わりカスタマイズ性はほとんどありません。

また、ライブラリで提供されるのはActivityのみでJetpack Composeで作っているアプリに導入するのは若干微妙です。

この記事では、ライブラリが提供するUIを使わずにUIを自作するために、OSS License Gradle Pluginが生成するデータをパースする方法を紹介します。

導入

公式ガイドを参照しながら導入を行いますが、今回ライブラリのUIは使わないのでGradle Pluginの導入のみ行います。

build.gradle.kts
buildscript {
  repositories {
    ...
    google()
  }
  dependencies {
    ...
    classpath("com.google.android.gms:oss-licenses-plugin:0.10.5")
  }
}
app/build.gradle.kts
plugins {
    id("com.android.application")
    id("com.google.android.gms.oss-licenses-plugin")
}

生成されるデータ

Gradle Pluginを導入したあとビルドを行うと以下2つのファイルが生成されます。

  • res/raw/third_party_license_metadata
  • res/raw/third_party_licenses

これらのファイルの中身を見てみるとthird_party_licensesには大量のライセンスが記載されているのがわかります。ライセンスはURLだけのものやライセンス本文がそのまま記載されているものなど様々です。

これだけでは、どのライブラリがどのライセンスなのか全くわかりません。それを知るにはthird_party_license_metadataを見ていく必要があります。

metadataファイルの形式

third_party_license_metadataの中は次のようになっています。

0:47 coil-compose
48:46 Android Lifecycle Kotlin Extensions
48:46 Compose Geometry
48:46 Android Navigation Common Kotlin Extensions
130:47 play-services-safetynet
178:1732 Protobuf Nano
1911:2500 zlib
4412:1481 darts_clone
...

後半はライブラリ名だということがわかります。

では前半はなんでしょうか?これはライセンスファイルのうちどの部分がこのライブラリのライセンスであるかを示しています。

具体的には次のような構造です

<offset(byte)>:<length(byte)> <library name>

ライセンスのパース

では実際にパースしてみます。

まずは third_party_license_metadata からライブラリの一覧を作成します。今回ファイルの読み込みにはOkioを使っています。

data class Library(
    val name: String,
    val offset: Int,
    val length: Int,
)

suspend fun loadLibraries(context: Context): List<Library> {
    return withContext(Dispatchers.IO) {
        context.resources
            .openRawResource(R.raw.third_party_license_metadata)
            .source()
            .buffer()
            .use {
                val libraries = mutableListOf<Library>()
                while (true) {
                    val line = it.readUtf8Line() ?: break
                    val (position, name) = line.split(' ', limit = 2)
                    val (offset, length) = position.split(':').map { it.toInt() }
                    libraries.add(Library(name, offset, length))
                }
                libraries.toList()
            }
    }
}

次にライブラリのoffsetとlengthからライセンスを取得します。

suspend fun loadLicense(context: Context, library: Library): String {
    return withContext(Dispatchers.IO) {
        val license: String
        context.resources
            .openRawResource(R.raw.third_party_licenses)
            .source()
            .buffer()
            .use {
                it.skip(library.offset.toLong())
                license = it.readUtf8(library.length.toLong())
            }
        license
    }
}

アプリで使っているライブラリの量や種類にもよりますが、ライセンス本文が記載されているライブラリが多くある場合、一度にライセンスを読み込むとOOMを起こす可能性があります。データ保持方法や読み込むタイミングには気をつけてください。

拙作のアプリでは、最初Libraryクラスにライセンスまで含めたところリスト作成する段階でOOMとなったため、ライセンス部分はリストから詳細画面に遷移した際に読み込むように変更しています。

まとめ

GoogleのOSS License Pluginが生成するデータを自身でパースする方法を紹介しました。

ただし、この方法は公式に案内されているものではないため将来的にデータ形式など変わる可能性は大いにあります。

また、このような手間をかけるくらいであればライセンス表示のための他のライブラリを使うことを検討したほうがいいかもしれません。

この記事で紹介した方法を使うメリットは、Googleが用意した仕組みに一部乗っかれることぐらいです。

reference

Discussion