ハイブリッドアプリのライセンス一覧を自動生成しよう


スマホアプリを開発する際、オープンソースのライブラリを使用することが多いと思います。オープンソースには大抵何かしらのライセンスがあります。(MIT、Apache Software Licenseなど)
これらのライセンスでは頒布物(アプリ)に「成果物を使用していることを明記すること」(著作権表記)が条件に含まれていることが多いです。

なぜライセンス一覧が必要?

WebサイトだとJavaScriptなどのソースコードが閲覧できるため、ソースコード内にライセンス表記が含まれていれば、サイト内にライセンス用のページを作成する必要はありません。
しかし、iOS/AndroidのネイティブアプリやCordova(Monaca)アプリの場合はソースコードを閲覧できないため、アプリ内にライセンス用の画面を作成して、誰でも簡単に閲覧できるようにしなくてはなりません。

オープンソースのライブラリを使用する際は、ライセンスを必ず確認しましょう。

その他の条件

著作権表記はあくまで一条件です。ライセンスによっては、他にも色々条件があります。
例えば、GPL(GNU General Public License) だと、GPLのライブラリを取り込んだ著作物にもGPLライセンスを適用する必要がある、など。

本記事では、著作権表記に関する自動化のみ取り上げます。

前提

ハイブリッドアプリと書いていますが、今回はJavaScriptアプリのみ対象です!
筆者はCordova(Monaca)アプリを開発していますが、React Nativeなども流用できるかも?

余談ですが、ネイティブアプリだと、Androidはlicense-tools-pluginがおすすめ。
iOSはCocoaPodsを使っているなら、CocoaPodsの機能で自動生成できます。

ライセンス一覧の定義を自動生成

使用しているライセンス一覧の情報を手動で集めて画面を作成するのはすごく手間がかかります。
JavaScriptの場合、大抵のライブラリはnpmで管理しているため、package.jsonの情報から自動生成できないか考えました。
license-checkerというライブラリを使用すればできそうなのでやってみます。

インストール

npm install -g license-checker

実行

/path/toにライセンス情報が定義されたJSONファイルが出力されます。

license-checker --production --json > /path/to

画面に表示

出力されたJSONファイルを画面に表示します。
サンプルはOnsenUI+Vue.jsの例です。ビルドにはwebpackを使用しています。

webpackがあれば、JSONもimportできるので便利ですね!
webpackを使用しない場合は、XHRでJSONをロードしたり、事前にJSONをJavaScriptのオブジェクトに変換するnpm scriptsを作成すると良いです。

LicensesPage.vue
<template>
  <v-ons-page>
    <v-ons-toolbar>
      <div class="center">ライセンス</div>
    </v-ons-toolbar>
    <v-ons-list>
      <v-ons-list-item v-for="(license, key) in licenses" @click="onLicenseClick(license)">
        <div class="list-item__title">{{ key }} {{ license.publisher }} ({{ license.licenses }})</div>
        <div class="list-item__subtitle">{{ license.repository }}</div>
      </v-ons-list-item>
    </v-ons-list>
  </v-ons-page>
</template>

<script>
  import licensesJson from '/path/to'; // 自動生成したライセンスを定義したJSONファイルをimport

  export default {
    data() {
      return {
        licenses: licensesJson
      };
    },
    methods: {
      onLicenseClick(license) {
        // ライセンスをタップすると、ブラウザでリポジトリのページ(GitHubなど)を開く(cordova-plugin-inappbrowserをインストールしておく必要あり)
        window.open(license.repository, '_system');
      }
    }
  };
</script>

(補足)JSONのimport方法

webpack3までだと、json-loaderでJSONをimportできます。
webpack4からは本体に取り込まれたため、json-loaderのインストールは不要になりましたが、現時点では以下ページの通り考慮が必要でした。
https://qiita.com/uggds/items/2ee337c5843aae28a34a#error3-module-parse-failed-unexpected-token--in-json-at-position-0

画面イメージ

無駄な情報の排除

自動生成したJSONファイルには、アプリ自体のライセンス情報や、npmで管理している自作したライブラリの情報も含まれてしまいます。
また、AとB別々のライブラリが同じライブラリに依存しているが、バージョンが異なる場合、2件出力されてしまいます。
以下の方法で除外します。

自作ライブラリのpackage.jsonを編集

private=trueとして、自作ライブラリであることを明記します。

package.json
{
  "name": "hoge-app",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "private": true
}

ライブラリをインストール

npm i compare-versions --save

除外するスクリプト

2020/08/03追記
private=trueのライブラリの除外はlicense-checkerコマンドの--excludePrivatePackagesオプションで行えると情報提供いただきました。@kenken-py さん、ありがとうございます!

license-generator.js
const execSync = require('child_process').execSync;
const fs = require('fs');
const compareVersions = require('compare-versions');

const filePath = './src/assets/licenses/licenses.json';

// 元情報となるライセンス一覧のJSONを出力
const ret = execSync(`license-checker --production --json > ${filePath}`);
if (ret.error) {
  console.error('Error ' + JSON.stringify(ret.error, null, 4));
  process.exit(1);
}

// 出力されたJSONファイルを読み込む
const licenses = JSON.parse(fs.readFileSync(filePath, 'utf8'));
const licenseKeys = Object.keys(licenses);
const newLicenses = {};
const versions = {};

licenseKeys.forEach(key => {
  const license = licenses[key];
  delete license.path;
  delete license.licenseFile;
  // 自作ライブラリ(private=true)を除外
  if (!license.private) {
    const tmp = key.match(/(.+)@(.+)$/);
    const name = tmp[1];
    const version = tmp[2];
    if (!versions.hasOwnProperty(name)) {
      newLicenses[key] = license;
      versions[name] = version;
    } else if (compareVersions(versions[name], version) < 1) {
      // 重複している場合、最新バージョンのみ含める
      delete newLicenses[name+'@'+versions[name]];
      newLicenses[key] = license;
      versions[name] = version;
    }
  }
});

fs.writeFileSync(filePath, JSON.stringify(newLicenses, null, 4));

npm scripts

package.json
{
    "scripts": {
        "license-generate": "node license-generator.js"
    }
}

実行

npm run license-generate

ライセンス一覧を作ろう

ライブラリを作成するには大変な労力がかかっています。
ライブラリ作成者への感謝の意も込めて、上記方法などを活用してライセンス一覧を作成しましょう!