oss-licenses-plugin 사용해보기

최근 개인 프로젝트를 진행하면서, 사용한 오픈소스 라이센스 라이브러리들에 대해 표시할 메뉴를 만들 일이 있었다.

기존에는 하나하나 조사했던 반면, 최근에 Google이 ‘oss-service-plugin’ 라는 플러그인을 만든 것을 알아내서 사용해보기로 했다.

사용

최상단의 build.gradle에 아래 의존성을 추가한다.

dependencies {
   ...
   classpath 'com.google.android.gms:oss-licenses-plugin:0.10.2'
   ...
}

(버전 참조: https://maven.google.com/web/index.html?q=oss-licenses-plugin#com.google.android.gms:oss-licenses-plugin:0.10.2)

그 다음, 앱 모듈의 build.gradle에 아래 의존성을 추가한다.

...
apply plugin: 'com.google.android.gms.oss-licenses-plugin'

...

dependencies {
   implementation 'com.google.android.gms:play-services-oss-licenses:17.0.0'
}

(버전 참조: https://maven.google.com/web/index.html?q=play-services-oss-licenses#com.google.android.gms:play-services-oss-licenses:17.0.0)

마지막으로, Setting 등의 공간에 메뉴를 추가하고 클릭했을 때 아래의 코드를 실행한다.

startActivity(Intent(requireContext(), OssLicensesMenuActivity::class.java))

적용 사진

OssLicensesMenuActivity 를 실행하면 위와 같이 사용한 라이브러리의 목록이 나오고, 각 목록을 클릭하면 해당 라이브러리의 라이센스 문서가 나온다.

위 처럼 의존성을 적어주고 startActivity를 해주는 것 만으로도 쉽게 구현할 수 있었다.

내부 살펴보기

기재한 플러그인인 ‘com.google.android.gms.oss-licenses-plugin’ 은 아래와 같은 작업을 가지고 있다.

getDependencies

코드: https://github.com/google/play-services-plugins/blob/master/oss-licenses-plugin/src/main/groovy/com/google/android/gms/oss/licenses/plugin/DependencyTask.groovy

configuration 에서 의존성의 목록(ResolvedDependency, https://gradle.github.io/gradle-script-kotlin-docs/api/org.gradle.api.artifacts/-resolved-dependency/) 과 그의 child dependency 를 재귀로 통해 가져와, build/generated/dependencies.json 를 구성한다.

...
{
       "group": "com.google.dagger",
       "version": "2.28",
       "fileLocation": "C:\\Users\\winds\\.gradle\\caches\\modules-2\\files-2.1\\com.google.dagger\\dagger\\2.28\\3c1b86e40d4957297d6fde6bdce74b3f48aac49d\\dagger-2.28.jar",
       "name": "dagger"
},
...

generateLicenses

코드: https://github.com/google/play-services-plugins/blob/master/oss-licenses-plugin/src/main/groovy/com/google/android/gms/oss/licenses/plugin/LicensesTask.groovy

getDependencies에서 생성한 dependencies.json를 가지고 한 개씩 순회하면서 아래 작업을 진행한다.

  • 만일, 라이브러리의 그룹이 구글 라이브러리면, (com.google.android.gms 또는 com.google.firebase) addLicensesFromPom() 를 실행하고 그의 transitive licenses(전이된 라이센스) 를 추가한다.
    • 구글 라이브러리 이면, 각자의 라이브러리 파일에 “third_party_licenses.json”, “third_party_licenses.txt” 가 있으므로 Zip 파일에서 해당 파일을 얻어 appendLicense() 를 실행한다.
  • 아니라면, addLicensesFromPom() 를 실행한다.

addLicensesFromPom() 는 아래와 같은 작업을 진행한다.

  1. 아키텍트 파일에서 .pom 파일을 가져온다.
  2. .pom 파일에서 라이센스 정보를 가져와 third_party_licenses 파일에 작성한다.
  3. “${start}:${content.length} ${group}:${name}” 처럼 각 라이브러리에 대해 정보를 third_party_license_metadata 에 추가한다.

즉, third_party_licenses 파일은 표시할 모든 라이브러리 라이센스에 대한 내용을 가지고 있고, third_party_license_metadata 는 특정 라이브러리의 라이센스를 표시할 메타데이터를 가지고 있다.

third_party_license_metadata 에는 아래의 데이터들을 볼 수 있다.

0:46 androidx.databinding:databinding-runtime
48:46 androidx.lifecycle:lifecycle-common
96:46 androidx.annotation:annotation
144:46 androidx.arch.core:core-common
192:46 androidx.databinding:databinding-common
240:46 androidx.collection:collection
288:46 androidx.lifecycle:lifecycle-runtime
336:46 androidx.databinding:viewbinding
384:46 androidx.databinding:databinding-adapters
432:46 com.google.firebase:firebase-installations
480:46 androidx.localbroadcastmanager:localbroadcastmanager
528:47 com.google.android.gms:play-services-measurement-impl

그리고, third_pary_licenses에는 아래의 데이터들을 볼 수 있다.

http://www.apache.org/licenses/LICENSE-2.0.txt
http://www.apache.org/licenses/LICENSE-2.0.txt
http://www.apache.org/licenses/LICENSE-2.0.txt
http://www.apache.org/licenses/LICENSE-2.0.txt
http://www.apache.org/licenses/LICENSE-2.0.txt
http://www.apache.org/licenses/LICENSE-2.0.txt
http://www.apache.org/licenses/LICENSE-2.0.txt
http://www.apache.org/licenses/LICENSE-2.0.txt
http://www.apache.org/licenses/LICENSE-2.0.txt
http://www.apache.org/licenses/LICENSE-2.0.txt
http://www.apache.org/licenses/LICENSE-2.0.txt
https://developer.android.com/studio/terms.html

  Copyright (c) 2005-2011, The Android Open Source Project

  Licensed under the Apache License, Version 2.0 (the "License");
  you may not use this file except in compliance with the License.

  Unless required by applicable law or agreed to in writing, software

이 데이터들을 가지고, OssLicensesMenuActivity 를 실행시키면 내부 코드에서 third_pary_licenses와 third_party_license_metadata 를 가지고 목록을 표시하거나, 내용을 표시하는 것을 알 수 있다.

for(int var6 = 0; var6 < var5; ++var6) {
  String var7;
  int var8 = (var7 = var4[var6]).indexOf(32);
  String[] var9;
  boolean var14 = (var9 = var7.substring(0, var8).split(":")).length == 2 && var8 > 0;
  String var10002 = String.valueOf(var7);
  String var10001;
  if (var10002.length() != 0) {
      var10001 = "Invalid license meta-data line:\n".concat(var10002);
  } else {
      String var10003 = new String;
      var10001 = var10003;
      var10003.<init>("Invalid license meta-data line:\n");
  }

  String var13 = var10001;
  if (!var14) {
      throw new IllegalStateException(String.valueOf(var13));
  }

  long var10 = Long.parseLong(var9[0]);
  int var12 = Integer.parseInt(var9[1]);
  var3.add(zzc.zza(var7.substring(var8 + 1), var10, var12, var1));
}

Generate License Document with license-tool-plugin

도입

안드로이드 앱을 개발하거나, 심지어 어떠한 프로그램을 개발할 때에 오픈소스 라이브러리는 필수화가 되었다. 사용하는 개발자(End-Developer) 입장에서는 그 기능을 위해 사용하는 시간을 줄일 수도 있고, 오히려 앱의 기능적인 부분의 향상을 가져올 수 있기도 하다.

이 오픈 소스 라이브러리를 사용하기 위해서는 각 라이브러리가 추구하고 있는 오픈 소스 라이센스의 조항을 따를 필요가 있는데, 주로 Apache License, MIT, LGPL, GPL 등 유명한 오픈 소스 라이센스를 많이 쓰고, 어떤 개발자는 <a href="http://www.wtfpl.net" rel="nofollow">Do What the Fuck You Want to Public License</a> (너가 x대로 굴릴 수 있는 공개 라이센스) 라는 라이센스를 쓰기도 한다.

위에서 예를 든 WTFPL 말고 4개 라이센스는 공통적인 조항을 가지고 있는데, 바로 라이센스 사용에 대한 고지 사항을 프로그램 내에 고지하는 것이다.

사진 출처: https://www.bignerdranch.com/blog/open-source-licenses-and-android/

위와 같이 별도 화면인 팝업을 띄워 사용한 오픈소스 소프트웨어의 종류, 라이센스의 이름, 라이센스 사본을 표시하는 것이다.

최근 앱을 개발하면서 라이브러리를 쓰는 일이 일상 다반사가 되었는데, Gradle 에 Dependencies 에 나온 implementation 수를 세보면 30개를 넘는 경우가 많다.

이 때 앱 내에 라이선스를 고지해야 될 때가 온다면 상당히 피곤할 것이다. 그래서, 이를 해결하기 위해 Gradle 의 Dependencies 를 분석해서 자동으로 라이센스 고지 html를 만들어주는 플러그인이 나왔는데, 그 중 그나마 괜찮게 나오는 것이 오늘 소개할 license-tools-plugin (https://github.com/cookpad/license-tools-plugin) 이다.

플러그인 불러오기

buildscript {
    repositories {
        jcenter()
    }

    dependencies {
        classpath 'com.cookpad.android.licensetools:license-tools-plugin:1.3.0'
    }
}

apply plugin: 'com.cookpad.android.licensetools'

build.gradle 에 classpath를 적고, apply plugin 에 해당 플러그인을 적는다.

라이센스 체크하기

해당 플러그인을 적용하면 크게 2개의 gradlew task 가 생긴다. checkLicensesgenerateLicensePage 가 주인공이다. 첫 번째 Task는 build.gradle 의 dependencies 를 전부 검사하여 리스트를 출력하는 것이고, 두 번째 generateLicensePage 는 밑에서 생성할 license.yml 를 기반으로 html 파일을 만드는 것이다.

checkLicenses 를 실행하면, # Libraries not listed 라는 문구 밑에 yml 형식의 텍스트가 나온다.

이 내용들을 app/license.yml 로 옮겨적는다.

그러면 copyrightHolder, license 가 비어있는 경우가 있을텐데 이 부분 만큼은 수동으로 적어야 한다. 상당히 귀찮지만, 일단 https://gist.github.com/WindSekirun/85a25ca5ed602e21295c580fc4cff433 이쪽에 최근 앱 만들 때 사용했던 licenses.yml 가 있다. 필요한 정보는 모두 채워넣었으니 사용하면 될 것 같다.

그렇게 licenses.yml 를 작성하고 나면, 라이센스 파일을 생성할 준비가 모두 되었다.

라이센스 문서 만들기

generateLicensePage 를 실행하면, app/src/main/assets 에 licenses.html 파일이 생성된다.ㅇ

오류 해결: 프로젝트를 여러 개 두고 있는 프로젝트의 경우

흔히 implementation project(path: ':BaseApp') 방식으로 프로젝트를 dependencies 로 추가하는데, 이럴 경우 checkLicenses, generateLicensePage 를 실행할 경우 configuration 오류로 실행이 되지 않는다.
해당 문제는 아래 코드를 사용하여 프로젝트를 dependencies 로 추가하면 된다.

if (project.gradle.startParameter.taskNames.contains("checkLicenses") ||
            project.gradle.startParameter.taskNames.contains("generateLicensePage")) {
        implementation project(path: ':BaseApp', configuration: 'default')
    } else {
        implementation project(path: ':BaseApp')
    }

마무리

중간 license.yml 을 채워넣는건 찾아서 넣어야 하기에 귀찮지만, 적어도 스캔을 해준다는 점에서는 일거리를 약간이라도 줄일 수 있지 않을까 싶다.