Unyablog.

のにれんのブログ

CircleCI で Android Library Project を GitHub Pages にデプロイ

こんにちは。最近 build.gradle をゴニョゴニョするのに慣れてきました。

ここ数日、 PhotoLinkViewerモジュール分けしたいなーと思って一部分を切り離しました

切り離したものは他のアプリでも使いたいので、プロジェクトとして独立させることにしています。

ここで切り離し元から参照するには 2つほど方法があり、

  1. submodule などでプロジェクト直下等に置いて管理する

  2. aar 化してレポジトリに置き、 build.gradledependencies に組み込む。

という感じです。

submodule で管理する方法を使えば ManifestMergerAndroidManifest をよしなにマージしてくれるので、切り離し元の AndroidManifest に Activity の情報を書く必要がありません。

aar はその点自分で書かないといけなくて不便*1 なのですが、submodule は push や pull を頻繁にしないといけないのが面倒だなあ...などと考えた結果、結局 aar を使うことにしました。

aar を生成する

ライブラリプロジェクトから aar を生成するようにするにはまず build.gradle で設定する必要があります。

// "./repository/" にレポジトリを作成
def repo = new File(rootDir, "repository")
// "./repository/snapshots" にスナップショットレポジトリを作成
def snapshotRepo = new File(rootDir, "repository")

group = '{{ group name }}'
version = '{{ version name }}'

apply plugin: 'maven'

uploadArchives {
    repositories.mavenDeployer {
        repository url: "file://${repo.absolutePath}"
        snapshotRepository url: "file://${snapshotRepo.absolutePath}"

        // pom情報
        pom.artifactId = '{{ artifact id }}'
        pom.project {
            packaging 'aar'
            name '{{ project name }}'
            url '{{ siteUrl }}'
            licenses {
                license {
                    name '{{ license }}'
                    url '{{ url }}'
                }
            }
            developers {
                developer {
                    id '{{ name }}'
                    email '{{ mail address }}'
                }
            }
            scm {
                connection '{{ gitUrl }}'
                developerConnection '{{ gitUrl }}'
                url '{{ siteUrl }}'
            }
        }
    }
}

// 以下、sources.jar を生成する場合に必要。生成すると切り離し元からモジュールのソースコードが見れる。
task sourcesJar(type: Jar) {
    from android.sourceSets.main.java.srcDirs
    classifier = 'sources'
}

artifacts {
    archives sourcesJar
}

のように追記します。{{ ~ }} は適宜置き換えてください*2

そうすると uploadArchives というタスクが追加されるはずなので、

$ ./gradlew clean :{{ module name }}:uploadArchives

とすると、プロジェクトの ./repository/ 以下に aarpom が生成されるはずです。

これを gh-pages ブランチに push すれば、すでに GitHub Pages がレポジトリ代わりになります。

CircleCI で自動的に生成、push する

しかし毎回 commit して push して...となると面倒です。いい感じに自動化する方法としては、

  1. CircleCI 内で Bintray 等に gradle plugin を使ってアップロードする
  2. CircleCI 内で直接 gh-pages ブランチcommit, push させる

等があるでしょう。

1の場合、下の記事を参考にすると簡単にできます。

これを用いると CircleCI 内でそのタスクを実行するだけで Bintray にアップロードできて楽なのですが、このライブラリは自分ぐらいしか使わないし、割りと頻繁に更新する(SNAPSHOT を使っていく)ようにしたかったので gh-pages を使うようにしました。*3

ということで、まずは CircleCI が GitHub レポジトリに push できるようにします。これはプロジェクトの設定から Permissions -> Checkout SSH Keys と辿れば push 権限を追加できる画面が出て来るので、設定して SSH Key を追加させます。

f:id:nonylene:20160131200900p:plain

ちなみに僕は GitHub レポジトリの Collaborator に bot を追加させて、そのアカウントで行うようにしました。

そして、ビルドし push させるシェルスクリプトを書きます。

#!/usr/bin/env bash

git config --global user.email "{{ mail }}"
git config --global user.name "{{ name }}"

# 以前の ./repository/ 以下を gh-pages ブランチから持ってくる
git branch gh-pages origin/gh-pages
git checkout gh-pages -- repository/

./gradlew --stacktrace clean :{{ module name }}:uploadArchives

# ファイルを残したままブランチ変更
git symbolic-ref HEAD refs/heads/gh-pages
git reset

git add repository/
# 変更点があれば commit, push
if [ -n "$(git diff --cached --exit-code)" ]; then
    CI_RELEASE_DATE=`date +"%Y%m%d%H%M%S"`
    # 環境変数から GitHub レポジトリ情報を取得
    CI_REMOTE_REPOSITORY="git@github.com:${CIRCLE_PROJECT_USERNAME}/${CIRCLE_PROJECT_REPONAME}.git"

    git commit -m "[auto] SNAPSHOT-${CI_RELEASE_DATE}"
    git push ${CI_REMOTE_REPOSITORY} "gh-pages"
fi

なぜ current branch には commit しないかというと、コンフリクトを防ぐためです。(gh-pages は CircleCI のみが管理するのでコンフリクトしない)

なので、以前の ./repository/ 以下を gh-pages から持ってきて、current branch 上でビルドし、その後ファイルを残したままブランチを gh-pages に切り替えて commit ということをしています。*4

そして、最後に circle.ymlmaster に push されたらデプロイするようにします。

deployment:
  publish:
    branch: master
    commands:
      - ./gh-push.sh

ここで parallel: true とかするとコンフリクト不可避なので注意。

こうして botレポジトリに push してくれました。

f:id:nonylene:20160131201531p:plain

あとは自分の GitHub Pages のレポジトリURL を build.gradle に加え、さらに dependencies にライブラリを加えれば完了です。

// 例
repositories {
    jcenter()
    maven {
        url "http://{{ base address }}/repository"
    }
}

dependencies {
    compile "{{ group }}:{{ artifact id }}:{{ version }}"
}

f:id:nonylene:20160131201734p:plain

無事インポートできました。

参考URL

*1:暗黙的Intent 等で起動できないだけで、コードから Intent を使うと起動できるようです。

*2:個人用レポジトリならここまで丁寧でなくても十分ですが

*3:さすがに Bintray で10分に一回 publish したりするのはアレかなぁとか

*4:以前のレポジトリmaven-metadata.xml を作成する上で必須で、これがないと aar が更新されたことを認識できない