Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationEventAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
2
Help us understand the problem. What is going on with this article?

More than 1 year has passed since last update.

@bvlion

Google スプレッドシートで行うリリース管理( Android )

この記事はくふうカンパニー Advent Calendar 2019の18日目の記事となります<(_ _)>

おはこんばんにちは!

アプリのリリースは自動化されていますか?
私の個人的な Android アプリも多数の先人の知恵をお借りして、 fastlane で自動化しています。
この記事は、その運用のご紹介です。
(そういうやり方もあるんだな…程度にご覧いただけたら嬉しいです。)

はじめに

rbenv や gem をはじめとする Ruby の環境構築や、assembleRelease など Android の設定、Fastfile などの fastlane の記入内容に関しては細かく触れません。
ある程度、個人や会社でリリースまでできてる状態を前提に、私なりの変更点を記載させていただきます。

流れ

以下の流れでリリースされます。

  • master (とか fix )にリリース最終版を用意
  • Google スプレッドシートでリリース内容を記載
  • Google スプレッドシートでリリース(キーワードで発火してタグを打つまで)
  • タグを検知して Circle CI でビルド
  • workflow で fastlane を使用してリリース

Google スプレッドシート

内容は以下の通りです。

スクリーンショット 2019-12-16 9.35.45.png

GAS

release.gs
function release() {
  var sheet = SpreadsheetApp.getActiveSheet()
  if (sheet.getRange(sheet.getLastRow(), 6).getValue() != 'release') {
    return
  }
  var relaeseSelect = Browser.msgBox(sheet.getName() + ' を Play ストアにリリースします。', 'よろしいですか?', Browser.Buttons.OK_CANCEL)
  if (relaeseSelect == 'cancel') {
    Browser.msgBox('リリースを取り止めました。')
    return
  }
  if (relaeseSelect == 'ok') {

    if (!sheet.getRange(sheet.getLastRow(), 1).getValue()
        || !sheet.getRange(sheet.getLastRow(), 2).getValue()
        || !sheet.getRange(sheet.getLastRow(), 3).getValue()
        || !sheet.getRange(sheet.getLastRow(), 4).getValue()
        || !sheet.getRange(sheet.getLastRow(), 5).getValue()) {
      Browser.msgBox('記載内容が不足しています。')
      return
    }

    // リリースタグを打つ
    var releasePushUrl = 'https://api.github.com/repos/bvlion/' + sheet.getName() + '/releases'
    var headers = {
      'Authorization': 'token -----token-----'
    }
    var data = {
      tag_name: 'v' + sheet.getRange(sheet.getLastRow(), 2).getValue(),
      target_commitish: sheet.getRange(sheet.getLastRow(), 5).getValue(),
      name: 'v' + sheet.getRange(sheet.getLastRow(), 2).getValue(),
      body: sheet.getRange(sheet.getLastRow(), 4).getValue()
    }
    var options = {
     method : 'post',
     contentType: 'application/json',
     headers : headers,
     payload : JSON.stringify(data)
    }
    var response = UrlFetchApp.fetch(releasePushUrl, options)
    sheet.getRange(sheet.getLastRow(), 7).setValue(response)
  }
}

トリガー

スクリーンショット 2019-12-16 10.18.29.png

Google スプレッドシートでやっていること

見ての通りですが、シートを更新するとスクリプトが発火します。
スクリプトでは、最終行の特定カラムが「 release 」になっていて、かつ全ての項目が埋まっているかを確認し、 GitHub API 経由でリリースタグを打ちます。

Circle CI

Circle CI ではタグの変更を検知して、 GAS の内容を見に行き、ビルドからリリースまでを行います。

GAS

GAS では doGet を公開し、 versionCode や versionName を返すようにしています。

doGet
function doGet(e) {
  var ss = SpreadsheetApp.getActiveSpreadsheet()
  var sheet = ss.getSheetByName(e.parameter.app)

  if (sheet == null) {
    return ContentService.createTextOutput(JSON.stringify({error: 'sheet is null [' + e.parameter.app + ']'})).setMimeType(ContentService.MimeType.JSON);
  }

  var data = {
    code: sheet.getRange(sheet.getLastRow(), 1).getValue(),
    name: sheet.getRange(sheet.getLastRow(), 2).getValue(),
    english: sheet.getRange(sheet.getLastRow(), 3).getValue(),
    japanese: sheet.getRange(sheet.getLastRow(), 4).getValue()
  }

  return ContentService.createTextOutput(JSON.stringify(data)).setMimeType(ContentService.MimeType.JSON)
}

取得して整形

GAS から返される json を整形して、所定のファイルに書き出します。

create-version.rb
require "json"
require "uri"
require "net/http"

uri = URI.parse("https://script.google.com/macros/s/${hash}/exec?app=${app_name}")
redirect_url = Net::HTTP.get_response(uri)["location"]
response = Net::HTTP.get_response(URI.parse(redirect_url))
json = JSON.parse(response.body)

if json["error"] then
  File.open("exit_message", mode = "w") {|f|
    f.write(json["error"])
  }
  exit
end

File.open("dependencies/ext.gradle", mode = "w") {|f|
  f.write("ext {\n")
  f.write("  appVersionCode = ")
  f.write(json["code"])
  f.write("\n")
  f.write("  appVersionName = '")
  f.write(json["name"])
  f.write("'\n")
  f.write("}")
}

File.open("fastlane/metadata/android/en-US/changelogs/" + json["name"] + ".txt", mode = "w") {|f|
  f.write(json["english"])
}
File.open("fastlane/metadata/android/ja-JP/changelogs/" + json["name"] + ".txt", mode = "w") {|f|
  f.write(json["japanese"])
}

名称は何でもいいんですが、私は「 dependencies/ext.gradle 」という名称で versionCode と versionName を外出ししています。

Circle CI

バージョンを設定、ビルド、 fastlane でリリース、を行います。

config.yml
version: 2

jobs:
  version_setting:
    docker:
      - image: circleci/ruby:2.5.1

    steps:
      - checkout

      - run:
          name: set version
          command: ruby create-version.rb

      - run:
          name: Error Check
          command: |
            if [ -f exit_message ]; then
              echo `cat exit_message`
              circleci step halt
            fi

      - persist_to_workspace:
          root: .
          paths:
            - .
  build:
    docker:
      - image: circleci/android:api-29

    environment:
      JVM_OPTS: -Xmx3200m

    steps:
      - attach_workspace:
          at: .

      - restore_cache:
          key: jars-{{ checksum "build.gradle" }}-{{ checksum  "app/build.gradle" }}
      - run:
          name: gradle dependencies
          command: ./gradlew androidDependencies
      - save_cache:
          paths: ~/.gradle
          key: jars-{{ checksum "build.gradle" }}-{{ checksum  "app/build.gradle" }}
      - run:
          name: Build apk
          command: ./gradlew :app:assembleRelease

      - persist_to_workspace:
          root: .
          paths:
            - .

  deploy:
    docker:
      - image: circleci/ruby:2.5.1

    steps:
      - attach_workspace:
          at: .

      - run:
          name: bundler
          command: bundle install

      - restore_cache:
          key: gem-cache-{{ arch }}-{{ .Branch }}-{{ checksum "Gemfile.lock" }}
      - run:
          name: up play store
          command: bundle exec fastlane play_store
      - save_cache:
          key: gem-cache-{{ arch }}-{{ .Branch }}-{{ checksum "Gemfile.lock" }}
          paths: vendor/bundle

workflows:
  version: 2
  build_and_deploy:
    jobs:
      - version_setting:
          filters:
            tags:
              only: /v.*/
            branches:
              ignore: /.*/
      - build:
          requires:
            - version_setting
      - deploy:
          requires:
            - build

最後に…

個人のアプリなので、ほぼエラーハンドリングは入れていません。
また、 Circle CI の persist_to_workspace で「 . 」を指定して全体を渡してしまっているのも、いつか直さないとな〜とは感じつつ、大変便利に運用しています。
良し悪しがあるとは思いますが、個人的にバージョンコードや Play ストアへ表示するメッセージを git で管理しなくて良くなっている点がお気に入りです!

参考

配置の参考程度にリポジトリを作成しました。
GitHub

2
Help us understand the problem. What is going on with this article?
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
2
Help us understand the problem. What is going on with this article?