Android LibraryをGitHub ActionsでビルドしGitHub Packagesで公開する

この記事は[ZOZOテクノロジーズ #1 Advent Calendar 2019](https://qiita.com/advent-calendar/2019/zozo_tech) 1日目の記事になります。

また、今年は全部で5つのAdvent Calendarが公開されています。

- [ZOZOテクノロジーズ #2 Advent Calendar 2019](https://qiita.com/advent-calendar/2019/zozo_tech2)
- [ZOZOテクノロジーズ #3 Advent Calendar 2019](https://qiita.com/advent-calendar/2019/zozo_tech3)
- [ZOZOテクノロジーズ #4 Advent Calendar 2019](https://qiita.com/advent-calendar/2019/zozo_tech4)
- [ZOZOテクノロジーズ #5 Advent Calendar 2019](https://qiita.com/advent-calendar/2019/zozo_tech5)

# 概要

先月のGitHub UniverseでGitHub Actionsと[GitHub Packages](https://help.github.com/en/github/managing-packages-with-github-packages/about-github-packages)(旧GitHub Package Registry)が正式リリースされました。GitHub PackagesはGitHubと統合されたパッケージのホスティングサービスで、ソースコードとその成果物であるパッケージを一括で管理できます。

> integrated permissions management and billing

とあるので、package単位での課金がサポートされるかもしれませんね。

[Supported clients and formats](https://help.github.com/en/github/managing-packages-with-github-packages/about-github-packages#supported-clients-and-formats)を見るとGitHub Packagesは以下のクライアントとファイルフォーマットをサポートします。

| Package client | Language | Package format | Description |
|:--|:--|:--|:--|
| npm | JavaScript | package.json | Node package manager |
| gem | Ruby | Gemfile | RubyGems package manager |
| mvn | Java | pom.xml | Apache Maven project management and comprehension tool |
| gradle | Java | build.gradle or build.gradle.kts | Gradle build automation tool for Java |
| docker | N/A | Dockerfile | Docker container management platform |
| nuget | .NET | nupkg | NuGet package management for .NET |

Apache Mavenのpom.xmlフォーマットに対応しており、また、クライアントとしてGradleをサポートしています。したがって、GitHub PackagesにAndroid Libraryをホスティングし、Androidプロジェクトから参照することができそうです。

この記事では、Android Libraryの作成、GitHub Packagesへの公開設定とGitHub Actionsを使用した自動アップロード、そして公開したライブラリのAndroidプロジェクトからの利用方法についてまとめます。 

# サンプルコード

https://github.com/horie1024/github-packages-android-sample

# 作業環境

Android Studioのバージョンは3.5.2を使用しています。

# Android Libraryの作成

ライブラリを作成してみます。次のようなHelloクラスを作成しライブラリとして公開してみます。

```kotlin:Hello.kt
class Hello {
    companion object {
        fun world(name: String) = "Hello World ${name}!"
    }
}
```

使い方は次の通りです。
 
```kotlin
Hello.world(name = "Horie1024") // Hello World Horie1024!
```

## プロジェクトの作成

Android Developersの[プロジェクトの作成](https://developer.android.com/studio/projects/create-project)を参考にプロジェクトを作成します。

Android Studioのメインメニューから [File] > [New] > [New Project]を選択します。Create New Projectウィザードが表示されるので、「Empty Activity」を選択してNextをクリックします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/43438/0dc1e11d-a0e4-cde5-0eb2-ef2455345ecc.png)

プロジェクトの設定をします。各項目の詳細は「[プロジェクトを設定する](https://developer.android.com/studio/projects/create-project?hl=ja#configure)」を参照してください。Finishをクリックするとプロジェクトが作成されます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/43438/abf5d292-afb3-7eaf-327d-aca58a12f629.png)

## Libraryモジュールの作成

Libraryモジュールを作成します。ライブラリのコードは、Libraryモジュールに書いていきます。Android Developersの[Android ライブラリの作成](https://developer.android.com/studio/projects/android-library)を参考にLibraryモジュールを作成します。

Android Studioのメインメニューから [File] > [New] > [New Module]を選択します。Create New Moduleウィザードが表示されるので、「Android Library」を選択してNextをクリックします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/43438/9c4876fc-b5a1-1fea-f743-2b66868f9e6f.png)

ライブラリの名前、コードのMinimum SDKバージョンを指定して[Finish] をクリックします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/43438/f3a15ec5-2b49-6c23-b6a1-30d17c03a6fd.png)

「mylibrary」というLibraryモジュールが作成されました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/43438/1d3390a3-d2a9-0abe-686c-835f52e8a4d0.png)

## appモジュールからの参照

ライブラリの開発を行う場合、appモジュールから参照できた方が便利です。Libraryモジュールを作成すると`settings.gradle`が次のように更新されていることを確認します。

```gradle:settings.gradle
include ':app', ':mylibrary'
```

appモジュールの`build.gradle`の`dependencies`にブロックに次の行を追加します。

```gradle:app/build.gradle
dependencies {
    implementation project(":mylibrary")
}
```

これでappモジュールからLibraryモジュールのコードを参照できるようになり、次のように使用できます。

```kotlin:MainActivity.kt

import com.horie1024.mylibrary.Hello

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        hello_world.text = Hello.world(name = "Horie1024")
    }
}
```

ビルドしてアプリを起動するとこんな感じです。

<img width="400" alt="image.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/43438/d1d20f7d-79bd-6dc5-0708-ac4e1f10c9b7.png">

# GitHub Packagesへの公開設定

作成したライブラリをGitHub Packagesへアップロードして公開します。公開するための設定は[Configuring Gradle for use with GitHub Packages](https://help.github.com/en/github/managing-packages-with-github-packages/configuring-gradle-for-use-with-github-packages#authenticating-with-a-personal-access-token)にまとめられています。必要になる設定は次の2つです。

- 認証設定
- ライブラリの公開設定

[Maven Publish Plugin](https://docs.gradle.org/current/userguide/publishing_maven.html)を使用して設定しますので、Libraryモジュールのbuild.gradleの先頭に`apply 'maven-publish'`を追加します。

```gradle:mylibrary/build.gradle
apply 'maven-publish'
```

## 認証設定

GitHub Packagesでのライブラリの公開、ダウンロード、削除には認証が必要になり、GitHubのユーザ名とAccess Tokenが必要になります。

Libraryモジュールのbuild.gradleに`publishing`ブロックを定義し、そこに設定を追加します。`url`はライブラリの公開先で、`https://maven.pkg.github.com/OWNER/REPOSITORY`の形で指定します。そして、`credentials`の`usename`にGitHubのユーザー名、`password`にAccess Tokenを指定します。

```gradle:mylibrary/build.gradle
publishing {
    repositories {
        maven {
            name = "GitHubPackages"
            url = uri("https://maven.pkg.github.com/horie1024/github-packages-android-sample")
            credentials {
                username = project.findProperty("gpr.user") ?: System.getenv("USERNAME")
                password = project.findProperty("gpr.token") ?: System.getenv("TOKEN")
            }
        }
    }
}
```

## ライブラリの公開設定

GitHub Packagesがサポートするpackageのフォーマット(`pom.xml`)にライブラリのソースコードを変換する必要があります。

`publishing`に`publications`ブロックを定義し、ライブラリの公開設定を追加します。ここで①の設定を書かない場合、ライブラリの推移的依存関係が含まれなくなります。

```gradle:mylibrary/build.gradle

apply 'maven-publish'

task sourceJar(type: Jar) {
    from android.sourceSets.main.java.srcDirs
    archiveClassifier.set("sources")
}

publishing {
    publications {
        maven(MavenPublication) {
            groupId "com.horie1024"
            artifactId "github-packages-sample-library"
            version "$VERSION"
            artifact sourceJar
            artifact "$buildDir/outputs/aar/mylibrary-release.aar"

            // include any transitive dependencies・・①
            pom {
                withXml {
                    def dependenciesNode = asNode().appendNode('dependencies')

                    project.configurations.implementation.allDependencies.each {
                        if (it.group != null || it.name != null || it.version != null || it.name == "unspecified") return

                        def dependencyNode = dependenciesNode.appendNode('dependency')
                        dependencyNode.appendNode('groupId', it.group)
                        dependencyNode.appendNode('artifactId', it.name)
                        dependencyNode.appendNode('version', it.version)
                    }
                }
            }
        }
    }
    repositories {
        maven {
            name = "GitHubPackages"
            url = uri("https://maven.pkg.github.com/horie1024/github-packages-android-sample")
            credentials {
                username = project.findProperty("gpr.user") ?: System.getenv("USERNAME")
                password = project.findProperty("gpr.token") ?: System.getenv("TOKEN")
            }
        }
    }
}
```

また、ライブラリの公開設定は[wupdigital/android-maven-publish](https://github.com/wupdigital/android-maven-publish)を使用するとより簡単に記述できます。ライブラリを使用する側から、ライブラリのKotlinのソースコードを追うことができなかったので今回は使用を見送っています。

## 手動での公開

Access TokenはGitHubの[Settings] > [Developer settings] > [Personal access tokens] から作成します。scopeは`repo`、`write:packages`、`read:packages`にチェックを付けて生成します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/43438/c6a45b07-33cd-56e2-5577-c024cff58c8e.png)

ユーザー名とtokenを`gradle.properties`に次のように定義するか、環境変数として定義します。

```txt:gradle.properties
gpr.user = horie1024
gpr.token = 123456789abcdefghijklmnopqrstuvwxyz
```

次のコマンドを実行するとGitHub Packagesへライブラリが公開されます。

```bash
./gradlew assembleRelease publish
```

そして、公開が成功すると[Repositoryのpackagesページ](https://github.com/horie1024/github-packages-android-sample/packages)に次のようなページが追加されます。これで作成したライブラリを公開できました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/43438/59bcffa4-3fd3-580c-3337-3e32657fa098.png)

# GitHub Actionsの設定

手動での公開ができたのでGitHub Actionsで自動化しましょう。

今回はmasterブランチにコミットがpushされたタイミングでGitHub Packagesにライブラリが公開されるようにします。これは次のymlをプロジェクトトップの`.github/workflows`以下に配置することで実現します。

```yml:publish_library.yml

name: "Publish library to GitHub Packages"
on:
  push:
    branches:
      - master

jobs:
  publish:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@master
    - name: Publish library
      env:
        USERNAME: horie1024
        TOKEN: ${{ secrets.GITHUB_TOKEN }}
      run: ./gradlew assembleRelease publish
```

## triggerの設定

masterブランチにコミットがpushされたタイミングでワークフローがトリガーされて欲しいので、[on](https://help.github.com/en/actions/automating-your-workflow-with-github-actions/workflow-syntax-for-github-actions#on)で制御します。次のように設定することでmasterブランチにpushされた場合にワークフローをトリガーすることができます。

```yml
on:
  push:
    branches:
      - master
```

## jobの設定

ワークフローは`job`単位で実行されていきます。ここでは`publish` jobを定義します。

`runs-on`はjobを実行する環境で`ubuntu-latest`を指定します。`steps`はjobを構成する一連のタスクです。1つ目のstepでは`uses: actions/checkout@master`でソースコードをチェックアウトし、2つ目の`Publish library` stepでライブラリの公開を実行します。

Publish library stepでは、`env`でstep内でのみ有効な環境変数を定義しています。ここで`TOKEN`には`secrets.GITHUB_TOKEN`を指定します。`GITHUB_TOKEN`はワークフロー内で自動的に作られる環境変数です([詳細はこちら](https://help.github.com/ja/actions/automating-your-workflow-with-github-actions/authenticating-with-the-github_token))。また、GITHUB_TOKENにどのようなパーミッションが与えられているかは[こちら](https://help.github.com/ja/actions/automating-your-workflow-with-github-actions/authenticating-with-the-github_token#permissions-for-the-github_token)から確認でき、GitHub Packagesについてもread/writeパーミッションを持っています。

そして、`run`で`./gradlew publish`を実行することでライブラリが公開されます。

```yml
jobs:
  publish:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@master
    - name: Publish library
      env:
        USERNAME: horie1024
        TOKEN: ${{ secrets.GITHUB_TOKEN }}
      run: ./gradlew assembleRelease publish
```

# 公開したライブラリの利用

GitHub Packagesで公開したライブリを利用するには、認証設定が必要です。**現時点(2019/12/01)でpublicなrepositoryの場合でも認証が必要**になります。

## 認証設定

プロジェクトトップの`build.gradle`内、allprojectsブロックのrepositoriesブロックにライブラリの公開設定と同様の設定を追加します。

```gradle:build.gradle
// Top-level build file where you can add configuration options common to all sub-projects/modules.

buildscript {
    ext.kotlin_version = '1.3.60'
    repositories {
        google()
        jcenter()
        
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:3.5.2'
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
        // NOTE: Do not place your application dependencies here; they belong
        // in the individual module build.gradle files
    }
}

allprojects {
    repositories {
        google()
        jcenter()

        // repositoriesにGitHub Packagesを追加
        maven {
            name = "GitHubPackages"
            url = uri("https://maven.pkg.github.com/horie1024/github-packages-android-sample")
            credentials {
                username = USERNAME // GitHubのユーザ名
                password = ACCESS_TOKEN // 発行したAccess Tokenを指定
            }
        }
    }
}

```

Access Tokenは、`read:packages` scopeを指定して作成します。用途としてはライブラリをダウンロードするだけなのでscopeは絞った方が良いでしょう。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/43438/fb2957df-d72b-6179-72a6-49463a5141df.png)

## dependenciesへの追加

dependenciesブロックにライブラリを追加し、「Sync Project with Gradle files」を実行すると公開したライブラリを使用できるようになります。

```gradle:app/build.gradle
android {
    // androidブロックの定義
}

dependencies {

    // 公開したライブラリをimplementationに追加
    implementation 'com.horie1024:github-packages-sample-library:1.0.0'
}

```

# まとめ

Androidのライブラリ作成してGitHub Packagesで公開してみました。GitHub Actionsと組み合わせるとライブラリの自動公開まで簡単に実現でき、今後社内向けのAndroidライブラリはGitHub Packagesでホスティングしていこうと考えています。

明日は@kenz_firespeedさんによる「[今風の画像アップローダーを作る](https://firespeed.org/diary.php?diary=kenz-1925)」です。