nashcft's blog

時々何か書く。

Deploy to Bitrise.io で生成した artifact のインストールページの QR code を作る

社内に需要があったので

調べればこれについて言及のある記事がいくつも出てくるけど、だいたいが一連のビルドフローに関する解説記事の一部で触れられているという感じで目的の情報に辿り着くのが手間な状況なので QR code だけにフォーカスした記事を作ろうという動機で書いている。

手順は大まかに以下の2 (or 3) ステップ:

  1. Deploy to Bitrise.io で public install page を生成
  2. Create install page QR code で 1. で作ったページのURLの QR code を生成
  3. QR code の画像URL $BITRISE_PUBLIC_INSTALL_PAGE_QR_CODE_IMAGE_URL を任意の用途に使用する

Deploy to Bitrise.io で public install page を生成

Workflow Editor でいうと "Enable public page for the App?", bitrise.yml でいうと is_enable_public_pagetrue にする。1.6.0 現在 default true なので、自分で false にしてあるのでなければそこの設定をいじる必要はなし。

Create install page QR code で public install page URL の QR code を生成する

github.com

これ

生成対象が default で $BITRISE_PUBLIC_INSTALL_PAGE_URL になってるので追加するだけで良い。必要であればお好みで生成される画像のサイズを調節することができる。

生成された QR code を使う

上記のステップによって $BITRISE_PUBLIC_INSTALL_PAGE_QR_CODE_IMAGE_URLQR code の画像URLが設定されるので適当に使えばOK。例えば Slack にビルド結果を通知しているのであれば、 "A URL to an image file that will be displayed inside the attachment" (image_url) にこの環境変数を設定すれば通知されたメッセージに QR code が表示されるようになる。

bitrise.yml

- deploy-to-bitrise-io:
    inputs:
    - deploy_path: "<path to your artifact>"
- create-install-page-qr-code: {}
# Slack 通知で使う場合
- slack:
    inputs:
    # other configurations...
    - image_url: "$BITRISE_PUBLIC_INSTALL_PAGE_QR_CODE_IMAGE_URL"

Bitrise Test Reports について調べてみた記録

先週 Bitrise の build log に "Test Reports" って項目が増えてて、JUnit とかのテスト結果をアップロードしたらGUIでいい感じにみられるようにしてくれる機能が追加されたことを知った。公式のドキュメントを読んだ感じだと Bitrise から提供されてるいくつかの test step の後に Deploy to Bitrise.io 使ったらアップロードされるよと書いてある他に、対応しているファイル形式についても書いてあった。それによると plistJUnit XML に対応しているとのことで、じゃあアップロードの仕組みがわかれば指定されてる test step を使わなくてもできるのでは、と思って色々試したり step のコードを読んだ結果として面倒くさくなって諦めたことについて書き残しておく。

ドキュメントを読んでやってみたこと

私が今 Android アプリ開発をやっているということで読んだのは以下2つ:

特に2つ目の以下の記述について注目した:

You can check your Android unit test results on the Test Reports page. The Android Unit Test Step generates and exports unit test reports into the $BITRISE_TEST_DEPLOY_DIR folder. Then the Deploy to Bitrise.io Step exports those reports from the $BITRISE_TEST_DEPLOY_DIR folder to the respective build’s Test Reports page where you can view the test results.

ここの記述から $BITRISE_TEST_DEPLOY_DIR にテスト結果 (<module>/build/test-results) を放り込んだらアップロードできるのではと考えて適当にテスト結果をコピーしてみたところ、何もアップロードされず Test Reports には何も記録されなかった。

コードを読んでわかったこと

どうもファイルがあれば良いという単純な話ではなさそうなので、Test Reports へのアップロードを担当している Deploy to Bitrise.io step と、テスト結果の収集方法についての参考に Android Unit Test step のコードを追ってみた。

github.com

github.com

Deploy to Bitrise.io の方は、まず main.go の以下の部分が対応している:

https://github.com/bitrise-steplib/steps-deploy-to-bitrise-io/blob/f1ce02dacdb35d56c05b3e6fc1522ad53828ca33/main.go#L229-L246

config.TestDeployDir$BITRISE_TEST_DEPLOY_DIR を指しているのでそこに対象のテスト結果を配置することは間違っていないようだ。なので、test.ParseTestResults が何をしているか紐解く必要がある。これは repository の test/test.go に記述されている。

https://github.com/bitrise-steplib/steps-deploy-to-bitrise-io/blob/f1ce02dacdb35d56c05b3e6fc1522ad53828ca33/test/test.go#L120-L212

ParseTestResults は長いのでこちらに載せないが、要点をまとめると、

  1. testRootDir ($BITRISE_TEST_DEPLOY_DIR) 配下の各ディレクトリ (以後 testDir) に対して走査を行う
  2. testDir から step-info.json を取得する、当該ファイルがない、もしくは指定された scheme に合致しない場合は対象ディレクトリに対する処理をスキップ
  3. testDir 配下の各ディレクトリに対してファイル取得を行う
  4. 取得した各ファイルに対して、対応フォーマットだった場合には内容を読み取って返却する resultsResult 型のデータ構造として追加する、その際 testDir 配下の各ディレクトリに test-info.json が存在することを期待している

という感じになる。ここから、単にテスト結果のディレクトリを $BITRISE_TEST_DEPLOY_DIR に配置しただけでは、step-info.json がないのでアップロード対象にならないので Test Reports で表示できないということがわかる。また、仮に step-info.json が存在したとしても、さらに各 test phase (Android というか gradle でいうところの各 task, testDebugUnitTest とか testReleaseUnitTest とか) に対して test-info.json が無いといけない。

test-info.json については関数内で test-name という string 型の property があれば良いことがわかるが、 step-info.json の必要な scheme については model.TestResultStepInfo を見る必要がある。これは別の repository にあって、該当するのは以下のリンク先にある。

https://github.com/bitrise-io/bitrise/blob/master/models/models.go#L128-L134

とりあえず必要な scheme は↓のとおり

{
  id: string,
  version: string,
  title: string,
  number: number
}

ということでアップロード側が対象を認識する部分についてはなんとなくわかったので、今度は対象を作って配置する側で、主にテスト結果ファイルや step-info.jsontest-info.json の中身がどうなっていればよいのか? を見るために Android Unit Test の repository をのぞいてみる。

とりあえず main.go から、関係するのはこの辺: https://github.com/bitrise-steplib/bitrise-step-android-unit-test/blob/dd2535d711f8515202beb3f02ef43c9b2c268b5b/main.go#L215-L233

artifact によって unique なディレクトリを作って、そこに xml を export してるっぽいことが読み取れる。ディレクトリは <module>-<variant> って名前。Export 処理については testaddon/testaddon.go の方で、test-info.json 作って xml$BITRISE_TEST_RESULT_DIR/<module>-<variant>/ の下にコピーしてるだけ。 $BITRISE_TEST_DEPLOY_DIRサブディレクトリを指す環境変数だそうな。

さて step-info.json についてまるで触れられてないけどどういうことなんでしょう。この辺でもういいやってなって諦めた。

追記 (2019-06-16)

追記終わり

さいごに

多分 test の後に script step で JSON をいい感じに作ってテスト結果と一緒に配置すれば指定の test step を使わなくても Test Reports が使えるのだろうけど、調査だけで怠くなったのと完全にレールを外れたことしてるなって感じて、それを解決方法にしたくないという気持ちになったので試してない。

Android で Test Reports を使いたい人は大人しく Android Unit Test を使おう、というべきところなのだろうけど、この step も問題があって、1 step で1つのモジュールに対してしかテストを実行できないし、project root 直下のモジュールしか認識できないのでグループ分けとかでもっと下の階層にモジュールが存在する場合は Android Unit Test でそれを実行できないし、例えば JaCoCo とかで coverage report を作成している場合はそれ用の task を実行してると思うけど Android Unit Test は モジュールと build variant しか指定できなくて test<Variant>UnitTest とか基本の test task しか実行できないので coverage report が欲しかったら別途 step を実行しなきゃできないとか、とにかく融通がきかない。

Android project で任意の gradle task を使ってテストを実行したい場合には Gradle Unit Test という step があるけど、当然こっちには Test Reports 向けの export 処理は入ってなくて、やっぱり欲しいって人はいて feature request が既に投稿されている。

discuss.bitrise.io

また、Flutter project 向けにも Test Reports に対応する request も今日投稿されていた。

discuss.bitrise.io

このような感じで、現状 Test Reports を活用するには Bitrise 側の準備というか状況が全然足りてなくて、みんながみんな自分の use case に応じた feature request を送ったり、これからも送られるだろうなという感じなのだけど、その要望に対してそれぞれの test step に Test Reports 向けの処理を埋め込むという解決法をとると、これまで作ったものにもそうだし、これから新たに test step が追加される場合にも逐一その処理を追加することになって手間だよなーと感じたし、現行の実装を見るにテスト結果を格納しているディレクトリの場所について限定された想定しかされてなくて融通効かなさそうなの嫌だなーって思ったので、独立した step にして project の種類や構成、使用してる step に依存しないようにしてほしいなーってことで私も feature request を投稿した。

discuss.bitrise.io

個人的には repository 中を適当に走査して、対応する format のファイルを全部 $BITRISE_TEST_RESULT_DIR に放り込んで、あとは必要な json を適当に生成してよしなに体裁を整えてくれれば良くて、例えば複数モジュールに対するテスト結果があったとしても、全部1つのグループにまとめられてしまっても構わないと考えてる。まあファイルの存在するディレクトリの情報を取っておいて、それを元に metadata とか格納先ディレクトリを区別するとかでできるのかもしれないけど。

この機能周辺に関しては悪い意味での easy に寄ったスタートしてるなーって感想で、触っててストレスしか感じないような現状なので早く使いやすくなってほしい...

Bitrise: Repository で bitrise.yml を管理している場合の Bitrise Start Build による workflow の並列実行を行う

Bitrise は基本的に1つのトリガーに対して1つの workflow しか実行させられないが、 Bitrise Start Build という step を用いることで、複数の workflow を並列に実行できるようになっている。以下では主に GitHub などの repository で bitrise.yml を管理・運用しながら Bitrise を利用している場合の Bitrise Start Build の導入方法や、実際に私が導入した際の雑感などを書いていく。

事前準備

Bitrise Start Build を実行するには access token が必要で、これは各個人のアカウントで生成する personal access token のことである。なので、適当なアカウントで Account setting -> Security から token を生成し、Workflow Editor の Secrets 適当な名前で登録しておく。

(寄り道) GUI でのセットアップ

今回の主題は repository で管理している bitrise.yml で運用している場合の導入方法についてだが、yaml を書く際のドキュメントは存在しない*1ので、その場合でも先にGUI側でお試し workflow を作ってどのような設定があるか見てみるのが良い。Bitrise Start Build についてもGUI側での設定方法ならば上記の access token についても含めて公式のドキュメントにおおむね書かれている。大雑把に要点を挙げると

  • Bitrise Start Build step を追加
  • 並列実行したい workflow を羅列
  • 用意した access token を登録

という感じ。

また、ドキュメントには記述がないが、実行元の workflow で定義した環境変数を引き継ぐ為の設定 Environments to share というものがあり、これによって1つの workflow に対して実行元 (設定した環境変数の値) に応じて振る舞いを変更する、といったことにも対応可能になっている。

ついでに、 Bitrise Start Build の step の後に他の step を追加して、並列実行した workflow の実行後にそれを動かすようにしたい場合以下の2種類の方法がある。

  • Bitrise Start Build の設定にある Wait for buildstrue にする
  • Bitrise Wait for Build という step を Bitrise Start Build の後に追加する

これらの振る舞いは完全に一緒っぽいのでそのうちどちらかがなくなるのではないか。

Repository でbitrise.yml を管理している場合のセットアップ

bitrise.yml での記述

yaml における Bitrise Start Build の step 名は build-router-start であり、設定など含めると以下のようになる:

  steps:
  # ssh activation, cloning repository, etc...
  - build-router-start:
      inputs:
      - access_token: "$ACCESS_TOKEN"  # 事前準備で用意した access token
      - workflows: |- # 実行する workflow のリスト
          foo
          bar
          baz
      - wait_for_build: false # optional: default false
      - environment_key_list: "$KEY1\n$KEY2" # optional

これでめでたく workflow の並列化が完了... ではない。単に上記の step を追加しただけで実行すると以下のようなエラーで終了してしまう:

Failed to start build, error: failed to get response, statuscode: 400, body: {"status":"error","message":"workflow (foo) did not match any workflows defined in app config","slug":"[REDACTED]","service":"bitrise"}

これはなぜかというと、現在の Bitrise Start Build で実行可能な workflow はGUI側で定義されているもののみだからである。
ここで諦めて大人しくGUI側に workflow の定義を移すこともできるが、折角変更の管理ができるように repository 側で持たせているのだから、その状態を維持したままで並列実行を動かせるようにしたい。とはいえ repository 側の bitrise.yml だけではどうにもできないので、GUI側への最小限の変更で動かせるようにする方法を紹介する。

GUI 側での設定

今の所参考になる解決策は以下の discussion で挙げられている手法かと思われる。

discuss.bitrise.io

要点は以下の3つ:

  • GUI側で、Bitrise Start Build で実行する対象と同名の workflow を定義する
  • 環境変数を用いて、実行するべき workflow を保存しておく
  • bitrise run環境変数で保存していた名前の workflow を実行する

ちなみに上のリンク先の定義は改善の余地があって、自前で WORKFLOW_TO_RUN のような環境変数を用意しなくても、$BITRISE_TRIGGERED_WORKFLOW_ID というデフォルト環境変数があり、同名の workflow を定義しているならこれが使えるので、こちらを使うことで定義をスッキリさせることができる。

  run_from_repo:
    steps:
    - activate-ssh-key: {}
    - git-clone: {}
    - bitrise-run:
        title: continue from repo
        inputs:
        - workflow_id: "$BITRISE_TRIGGERED_WORKFLOW_ID"
  foo:
    after_run:
    - run_from_repo
  bar:
    after_run:
    - run_from_repo
  baz:
    after_run:
    - run_from_repo

この方法を用いる場合の注意点として、bitrise-run で実行した workflow 内の script step で set -e をつけておかないとタスクが失敗したにも関わらずその後の処理も続いて workflow が成功扱いになってしまうみたいなことがあったので共有しておく。

雑感

  • 現時点では Bitrise Start Build はGUI側のみでの workflow 構築しか想定されておらず、repository で bitrise.yml を管理している場合には定義が repository 側とGUI側に散ってしまい嬉しくない。Bitrise 的にはGUI側で全てを完結してほしそうにしているが、 workflow の変更管理がそれだけではできないのでGUIで完結させたいなら早く運用でカバーする以外の管理方法と提供してくれという気持ち。
  • wait: true にしている場合、実行したコンテナの結果を待っている間そのコンテナは占有されたままとなるので、CircleCI と比べるとコンテナが枯渇しやすい。
  • Bitrise では現在 branch 単位でしかキャッシュを持てず、他にコンテナやCIパイプライン間でファイルを共有するすべがないので、たとえ wait: true にしたとしてもそれぞれの workflow の成果物を収集してまとめて何かをするというのはできなさそう。
    • 基本的には build_router_start を実行する workflow はそれのみを step として持ち、自己完結した workflow を並列実行して自身は wait しない、という構成にするのが一番無駄が無い気がする。
    • Danger に色々な仕事をさせていて、成果物の収集は互いに独立しているので並列化したかったのだけど残念...

まあ CircleCI の workflow とかと比べても洗練されてないというか色々足りてないなーという印象だけど、まだリリースされてそれほど時間が経っていないことだし、今後に期待という感じだろうか。

References

*1:Bitrise は基本的にGUI側での設定の仕方しかドキュメントにまとめてくれない

Android で unidirectional data flow で設計することについてのメモ

Android と Flux とか unidirectional data flow とよばれるものの関係についてかんがえていることのメモ書きのために自分の過去の tweet をまとめる場所

******

From: U-NEXT (2016/05~2019/02)
To: CyberAgent (2019/03~)

2/15が最終出社日でした。

U-NEXTのみなさまには大変お世話になりました。火曜に退職決まって金曜に最終出社という唐突ムーヴ失礼しました。タイミングが良かったんです。結局挨拶もそこそこみたいな感じになってしまったので話し足りなかったとかあったらご飯とかお酒とか誘ってください。

ところで昨日以下のようなツイートをしました。

これはどういうことかというと、既に内定承諾はしているのですが、入社までの期間が短いため配属がまだ調整中だけど入社手続きをもう進めちゃうねみたいな状況で、具体的な配属がまだ決まってないので決まってからブログでも書こうということでした。でも待ってたら来週末くらいとかになるかも知れないし、イベント的にそこまで引っ張る必要もないよなーって思ったのでもう書いてしまっています。

追記:
配属はマッチングエージェントでした。タップル誕生の Android アプリ開発などをやります。

www.matchingagent.co.jp

追記終わり

というわけで、サイバーエージェントのみなさまこれからよろしくお願いします。人事のみなさまにおかれましては月の半ばにもかかわらず「今月で退職できるみたいだから来月入社しますね!」という唐突ムーヴすみません。本当にタイミングが良かったんです。あと多分最初に連絡をもらった時の「明日🍣どうですか?」の件をいまだに面白がってるのだと思います。

なお、この記事は以下のレギュレーションに則って書かれました。

DroidKaigi 2019 感想

DroidKaigi 2019 に参加してきた。

droidkaigi.jp

セッションのノートとかは Scrapbox にまとめてある。これを書いている時点でまとめきれていないものがあって #WIP ってついてるのがそれ。 scrapbox.io

感想

聴いたセッションで特に印象的だったもの

Deep Dive to fido.fido2 Packages

最近とんとセキュリティ関係のトピックに触れてないなーって思ってて、なんか最近FIDOっていうのが注目されてるらしいので何も知らないけどとりあえず聴いてみて雰囲気だけでも掴んでみよう!というモチベーションで聴きに行った。実際セッションを聴いてFIDOについてどれだけ理解を得たかというとまだふわっとしかできてないと思うのだけど、認証まわりのどんな課題に対してFIDOがどんなアプローチをしているのかとか、attestation と assertion の大まかな流れとか、 fido.fido2 周辺のAPIにどんなのがあってどういう風に使って認証のUIを実現するのかとか、これから学習していくための良い introduction だなーと思う。
Android でFIDOの認証フローを実装するのは、送受信するデータのプロパティが多くてゴツい印象があったけどUIに関してはAPIから Intent もらって launch するだけでUIそのものを実装しなくて良く、そんなに手間ではないように感じたので開発してるサービスでFIDOの認証採用するよ!ってなった時にシュッと実装できるようになっておくとよさそう。
それと最後にアプリの実装だけでなくFIDOを採用するにあたってカスタマーサポートやサーバサイドなど各方面と、特にユーザの認証のリカバリまわりでうまくオペレーションできるように連携しましょうねって話があってこういうの大事だよねと思った。この辺の話は同じ日の最後のセッション "Chrome + WebAuthn で実現できるパスワードレスなユーザー認証体験と開発者の課題" でより進んだ言及があったので、サービスでの運用まで情報を拾っておきたい場合は合わせて見ておきたい。

ところで新しい概念について覚えたり理解しようとするのにいっぱいいっぱいでノートがろくに書けなかったのでまた録画見て理解したことを整理してノートにまとめないとなーってなってる。

R8、Proguard徹底比較

聴く前はパフォーマンスとか shrink の比較とかかなーって雑に考えてたら Dalvik バイトコードを読み始めたり R8 の最適化の一つである lambda group がどのように行われるかを R8 の実装を追いかけながら解説したりとなかなかアツい展開になってすごく満足度が高かった。
SDKとかもそうだけど私はあんまりそういうののソースコード読まないよなーって気づいて、document だけじゃなくてコードも読んだ方がいいのかなー今年はその点を頑張ろうかなーともやもや思ったのであった。

あと、発表者の方も自分で言っていたけど発表のボリュームに対して時間が足りなくて余裕のない感じだったので50分枠だったらなあと思わずにはいられない...

multi-module Androidアプリケーション

マルチモジュールっていうとビルド時間を短縮するみたいな文脈で語られがちな印象があったけど、依存関係の強制で設計のほころびを起こしにくくするというのはなるほどってなった。確かに Java から Kotlin になって失った package private の代わり(?) の internal を活用する設計とか考えるとそうなるよねーというのはあって、そういう文脈でもマルチモジュール化の流れはあるのかーという納得があった。
あとはビルド時間の短縮のためには各モジュールのサイズとか依存関係の形にも注意が必要だとか、まだ本格的にマルチモジュール化を行ったことがない身としては知見にあふれるセッションだったし、スライドもよくまとまっているうえになんというか発表が落ち着いててわかりやすいというか聴きやすいトークだったなあという印象がある。

Lifecycle, LiveData, ViewModels - The inner wiring

droidkaigi.jp

これ聴くつもりだったのだけど当日疲れてていいやってなってパスしたところTLが盛り上がっててこれ絶対楽しかったやつだ... ってなってた。録画がもう公開されてるしあとで見る。

全体の感想

去年の DroidKaigi 2018 が DroidKaigi 初参加だったので去年との比較になるのだけど、なんだかセッションのバラエティが偏ってるように感じてどのセッションを聴くのか決めるのに少し悩んでしまったのがある。ただこれは単にネガティブな感想だけではなくて、同じトピックであっちは同じ時間帯に聴きたいセッションあるからこっちにしようみたいなことができて助かったみたいなのもあり、セッション選定とか時間割組み立てとかやっぱり難しいんだろうなと思った。
他には設計・テストみたいな大きなトピックとクロスプラットフォーム関係が多いなーという印象があった。前者は Android アプリ開発界隈が成熟してきてるのかなーって感覚で、後者はやっぱり注目度が上がっているのだなという感じ。
クロスプラットフォーム系の何か1つは習得しておきたさがあるのだけど、今のところ web も AndroidiOS もそれぞれ人数揃ってて精通しているメンバーがいるようなところにいるのであんまり採用するモチベーションがないよなってなってて優先度が上がらない...
あと今回のPWAのセッションで話があったようにPWAの web アプリをモバイルアプリとして PlayStore で公開できるようになったなどあるので iOS も同じような流れがあるならPWAの方が需要でそうな気がしてちょっと立場が厳しくなるのではないかみたいなのをちょっと気にしている。

展示ブースは色々見て回れて、今年もバリスタコーヒー頂けたし Kotlin Quiz では7問中6問正答で Kotlin ももっときちんと勉強しないとなーってなったし Twitter でよく見るような方とお話できたしとても楽しかった。

運営・登壇者の皆さまお疲れ様でした。とても楽しく刺激的な2日間でした。来年もまた参加できたらなと思います。おわり。

Redux に関する昨日の出来事

前提: 当時の私の Redux に対する認識や知識

  • Flux: MVC, MVP, MVVM などと同じレイヤーで扱われるアーキテクチャパターン
  • Redux: Flux を実現するための具体的な実装、ライブラリ
  • Flux については 2014年末くらいに割としっかり調べてて情報を追っていたりした
  • Redux はそういうライブラリがあって今メジャーな存在になってるな程度の知識
    • あとは Elm から影響を強く受けているとかそういう話くらい

疑問

モバイル (Android, iOS) 界隈で "redux" について言及する時に特定の実装を指していないような気がするんだけどこの場合の "redux" って何だ?

投げた一連のツイート

初めて知ったこと

誤解? してたこと

  • Android にも iOS にも reduxjs/redux に相当するような "redux" という実装 (= ライブラリ) は存在しない
    • iOS には ReSwift というライブラリが存在して、それが reduxjs/redux に相当する
      • ReSwift の redux 部分の前身として ReduxKit というのがあった
    • Android には無いっぽい?
  • "redux" という語は特定の実装を指す
    • Three principles に則っていればそれは redux と呼べるみたいな認識っぽい
    • つまり一種のパターンとして認識されていることになる

Android 界隈に関しては reduxjs/redux のコンセプトだけ輸入して各々実装をしているという状況のように見えるので私の混乱の原因はそれっぽい。上に書いたように iOS は ReSwift (ReduxKit) が redux の実装として使われているようだけど、実装と共に redux が伝えられたのかは調べてないので不明なのと、あと名前が変わって "redux" という語がなくなってしまったので結局 "redux" はコンセプトだけを指すようになっていると認識している。

結局 reduxjs/redux がJS界隈でどのように普及・認識されていって、それがどのような形でモバイル界隈に輸入されていったのか知らないまま適当な発言をしていたという感じで、今もよくわかっていない。