React Nativeを利用したアプリ開発でライブラリのライセンスをJSXで表示したい

# モチベーション

React Nativeを利用した開発で利用しているライブラリをAndroid, iOSごとにjson形式でまとめて、React NativeのViewから表示させたい。この仕組みを、できるところは自動化をさせたい。その手順や躓きポイントをまとめた。

### この記事について

[Unipos Advent Calendar 2022](https://qiita.com/advent-calendar/2022/unipos)の2日目の記事です。

## 用意するもの

### ライセンス出力で利用
- [react-native-oss-license](https://github.com/k-tomoyasu/react-native-oss-license)
- [LicensePlist](https://github.com/mono0926/LicensePlist)
- [LicenseToolsPlugin](https://github.com/cookpad/LicenseToolsPlugin)

※LicenseToolsPluginはアーカイブされているため、現在だと[AboutLibraries](https://github.com/mikepenz/AboutLibraries)を利用する方がよさそう。

### 自動化で利用

- Node.js
- Ruby
- sed
- jq
- plutil
- echo
- macOS


## :rocket: 手順

CLIで`react-native-oss-license`を動かしただけでは、ラインセンスを独自の形式でアプリに表示するまでの道のりが大きく、躓きそうなポイントがいくつかある。自分なりに、できるだけどうすると自動化できそうかを手順としてまとめた。

Android, iOSでそれぞれ手順が異なる。以下に、大まかな手順を記す。

### :robot: Android

1. JS側からライセンス情報をyaml形式で出力
1. 1.で出力したyamlから、ライセンス情報を出力したyaml形式に則った形式で、`#LICENSE#`や`#COPYRIGHT_HOLDER`など抜けているところをググって埋めた状態のyamlを作成しておく
1. 1.2.のyamlを合体させたyamlを作成する (`JS.yaml`とする)
1. Android側からライセンス情報をyaml形式で出力
1. 4.で出力したyamlから、ライセンス情報を出力したyaml形式に則った形式で、`#LICENSE#`や`#COPYRIGHT_HOLDER`など抜けているところをググって埋めた状態のyamlを作成しておく
1. 4.5.のyamlを合体させたyamlを作成する (`Android.yaml`とする)
1. JS.yamlとAndroid.yamlを合体させる (`JS&Android.yaml`とする)
1. `LicenseToolsPlugin`のコマンドでライセンス情報が正しく入力できている確認する
1. `JS&Android.yaml`からjsonに変換する (`android.json`とする)
1. `android.json`をjsxで読み込み、`FlatList`に表示する。

### :apple: iOS

1. JS側からライセンス情報をplist形式で出力
1. 1.で出力したplistから、ライセンス情報を出力したyaml形式に則った形式で、`#LICENSE#`や`#COPYRIGHT_HOLDER`など抜けているところをググって埋めた状態のyamlを作成しておく
1. 2.3.のplistを合体させる (`JS.plist`とする)
1. iOS側からライセンス情報をyaml形式で出力 (`iOS.plist`とする)
1. `JS.plist`をjsonにする (`JS.json`とする)
1. `iOS.plist`をjsonにする (`iOS.json`とする)
1. `JS.json`と`iOS.json`を合体させる (`ios.json`とする)
1. `ios.json`を`jsx`で読み込み、`FlatList`に表示する


## 手順の自動化で詰まりそうなポイント

上記の手順を実際に行うにはそれなりの労力が必要になる。自動化できる部分は自動化させたい。`react-native-oss-license`にはどうすればライセンスを出力できるのかが[まとまっている](https://github.com/k-tomoyasu/react-native-oss-license/tree/master/sample)。ただ、それだけだと自動化するときに難しいところもある。そのため、自動化するときに、自分が躓いてしまったポイントをまとめた。


### `#LICENSE#`や`#COPYRIGHT_HOLDER`など抜けているところ埋めるって辛くない？

これは大変じゃないかと思うが、実際大変。ライブラリによってはライセンスがあるものの、サーバーからダウンロードしてきたファイルの中にライセンスの記入が含まれていないものが数多く存在する。そのようなライブラリの`#LICENSE#`や`#COPYRIGHT_HOLDER`の項目を1つ1つググって埋めていくという手作業が必要になる。これはどうしようもないように思っている。

ライブラリの更新時にできるだけ再度同じ調査をしたくないので、初回、調べた内容を、CLIで出力されたyaml形式と同じ形で記入する。

下記のようなフォーマットでライセンスは出力される。
```yaml
- artifact: example:+
  copyrightHolder: #COPYRIGHT_HOLDER
  license: #LICENSE#
  licenseUrl: https://example.com
  url: https://example.com
```

下記のように変更した状態のyamlを作成しておく。
```yaml
- artifact: example:+
  copyrightHolder: example
  license: example
  licenseUrl: https://example.com
  url: https://example.com
```

次回以降は、この埋めた状態のyamlを、ライセンス出力のyamlと合成することで、もう一度ググるという必要がだいぶ減る。


### `LicenseToolsPlugin`の未対応のライセンスが存在する

ライセンスも日夜、新しいものが登場する。そのようなライセンスを持つライブラリを利用すると、`LicenseToolsPlugin`ではエラーになる。

例えば下記のように表示されることがある。

```log
* What went wrong:
Execution failed for task ':app:generateLicensePage'.
> java.io.FileNotFoundException: File not found: template/licenses/Unlicense.html
```

[Unlicense](https://unlicense.org/)というライセンス形態を利用できないと表示される(意訳)。


作成したyamlが正しいかどうかを確認するために、`generateLicensePage`を実行しているとする。この場合は、このライセンスを持つライブラリの部分を`generateLicensePage`の実行時のみコメントアウトすることで対処できる。そして、`generateLicensePage`が終わり次第、コメントアウトした部分をアンコメントする。
(※LicenseToolsPluginをforkしてUnlicenseにも対応させれば......)

コメントアウトには、JSから`sed`コマンドを利用した。
```sh
`sed -i '' -e '/${artifact名}/,+${artifact名からそのライブラリの部分を示す行数}s/^/#/' ${yamlファイルのパス}`
```
アンコメントにも、JSから`sed`コマンドを利用した。
```sh
`sed -i '' -e '/${artifact名}/,+${artifact名からそのライブラリの部分を示す行数}s/#//' ${yamlファイルのパス}`
```

### yamlをjsonにするには？

jsxでライブラリのライセンスを表示させたかったのでyamlをjsonにしたい。そのため、JS側から`ruby`と`jq`を利用して、yamlからjsonに変換した。最終的に作成されるjsonにfomatをかける意味でjqを利用していた。下記コマンドを実行した後に、`writeFile`でコマンドの中身を書き込み、jsonファイルを作成した。

```sh
`ruby -rjson -ryaml -e 'print YAML.load(STDIN.read).to_json' < ${yamlファイルのパス} | jq .`
```

### `./gradlew checkLicenses`の実行結果に余計な文章も混じる

`./gradlew checkLicenses`を実行すると、Android側のライセンス情報をyaml形式でshellに出力してくれる。だが、コマンドのログには、yaml以外の部分も存在していて、そのままだと取り扱いづらい。

書きのように出力される。

```yaml
何らかの文字列がたくさん
何らかの文字列がたくさん
- artifact: example:+
  copyrightHolder: example
  license: example
  licenseUrl: https://example.com
  url: https://example.com
何らかの文字列がたくさん
何らかの文字列がたくさん
```

そこで、JS側でsedを使って、yaml形式の部分だけを抜き取った。

```sh
`echo -e "${./gradlew checkLicensesの結果}" | sed -n -E -e '/- .*:.*|  .*:.*/p'`
```

`- .*:.*`にて、`- artifact: example:+`部分を抜き出し、`  .*:.*`部分で、`  copyrightHolder: example`のような部分を抜き出した。

試したこととして、[js-yaml](https://github.com/nodeca/js-yaml)を利用した。結果として、yaml記法に則っていない部分があると、`js-yaml`ではエラーになってしまうようで、代理でsedコマンドを利用するように変更した。


### plistからjsonにどうやって変換するの？

macOSにはplutilというコマンドが同梱されているようだったので、このコマンドをJS側から利用することにした。

```sh
`plutil -convert json ${plistファイルのパス} -o -`
```

### JSXでjsonって読み込めるの？

普通に読み込めるみたい。そのままimportすると利用できるようだった。

```jsx
import {License} from 'license.json';
```

TypeScriptとTSXを利用している場合は、型定義をしておくことで、importしたときに型がついた状態で、TSX上で操作できる。

```typescript
declare module '*/license.json' {
  export type License = {
    artifact: string;
    省略
  };
  const Licenses: License[];
  export default Licenses;
}
```

## まとめ

React Nativeを利用した開発で、ライブラリのライセンスをjsonとしてまとめて、JSXで表示するまでのざっくり手順をまとめた。その際に躓きそうな手順もまとめた。OTA (Over The Air) を利用していた都合上、Android側やiOS側のネイティブコードに変更がないようにしたかったので、jsonにまとめるという方式を取った。しかし、実際、大変だった。

`LicenseToolsPlugin`はアーカイブになったため、`AboutLibraries`に置きかえる作業をしなければ......

最後に、ライブラリの作成者様方に感謝を。`react-native-oss-license`や`LicenseToolsPlugin`、`LicensePlist`などがなければ、もっと膨大な作業が発生したと思う。ありがとうございました。

## 参考
- [react-native-oss-license/sample at master · k-tomoyasu/react-native-oss-license · GitHub](https://github.com/k-tomoyasu/react-native-oss-license/tree/master/sample)