Infinito Nirone 7

Androidとか自転車とか

Robolectric を offline mode で動作させる

Robolectric でテストを実行すると、初めの方のテストでなにかを Maven Central からダウンロードしているログが出力されることがあります。これは Robolectric がテスト実行時に必要な依存をダウンロードしているもので、Android SDK のなかのクラスを JVM で実行可能にしている jar が入っています。

ローカルでテストを動かす分には初めてテストを実行するときにしかお目にかかることはありませんが、CI のような環境の場合、CI が走るたびに Robolectric の依存解決が動きます。 また、Robolectric はどの SDK version でテストを動かすかを動的に指定できるので、@Config(sdk = [21]) とか @Config(sdk = [25]) とかが付いているテストケースがあるたびに、必要な jar を適宜取りに行くようになっています。 そうすると、@Config アノテーションの数だけ jar のダウンロード時間が必要になるので、CI だと結構な時間が依存解決に費やされてしまいます。

そこで Robolectric には offline mode というのがあって、あらかじめ jar をローカルに保存しそのパスを教えてあげると、テスト実行時には Maven Central へアクセスせずともテストが実行できるようになっています。 これで時間の節約ができるというわけです。

事前にダウンロードする Gradle スクリプト

medium.com

実はすでにこれを実現するスクリプトが公開されています。

問題点

概ね offline mode の設定と jar の場所の設定はこのスクリプトで問題ありませんが、複数の jar が必要なときにうまく動きません。

上記の記事で書かれたスクリプトの dependencies を次のように書いたとします。

configurations {
    robolectricRuntime
}

dependencies {
    robolectricRuntime "org.robolectric:android-all:4.1.2_r1-robolectric-r1"
    robolectricRuntime "org.robolectric:android-all:4.3_r2-robolectric-r1"
    robolectricRuntime "org.robolectric:android-all:4.4_r1-robolectric-r2"
    robolectricRuntime "org.robolectric:android-all:5.0.2_r3-robolectric-r0"
    robolectricRuntime "org.robolectric:android-all:7.1.0_r7-robolectric-r1"
    robolectricRuntime "org.robolectric:android-all:8.1.0-robolectric-4611349"
}

テストでは ICS, JB, K, L, N, O のそれぞれの android-all について jar が必要な状態です。 複数必要なので dependencies の指定も複数並べることになりますが、robolectricRuntime のどの dependency も GroupId と Artifact が同じで Version のみが異なるため、Gradle はこの列挙された dependencies の中から最新のものしか解決しません。 結果、jar を放り込むディレクトリには O の android-all の jar ファイルしかいないことになります。

どう解決するか

Configuration ごとに dependency の解決が行われることを考えると、必要な android-all の jar ごとに Configuration を作れば良いことになります。

configurations {
    robolectricRuntimeICS
    robolectricRuntimeJB
    robolectricRuntimeK
    robolectricRuntimeL
    robolectricRuntimeN
    robolectricRuntimeO
}

dependencies {
    robolectricRuntimeICS "org.robolectric:android-all:4.1.2_r1-robolectric-r1"
    robolectricRuntimeJB "org.robolectric:android-all:4.3_r2-robolectric-r1"
    robolectricRuntimeK "org.robolectric:android-all:4.4_r1-robolectric-r2"
    robolectricRuntimeL "org.robolectric:android-all:5.0.2_r3-robolectric-r0"
    robolectricRuntimeN "org.robolectric:android-all:7.1.0_r7-robolectric-r1"
    robolectricRuntimeO "org.robolectric:android-all:8.1.0-robolectric-4611349"
}

そして jar を copy するタスクの中身を次のように書き換えます。

rootProject.task(type: Copy, overwrite: true, "downloadRobolectricDependencies") {
    configurations.all { configuration ->
        if (configuration.name.startsWith("robolectricRuntime")) {
            from configuration
        }
    }
    into robolectricDependenciesFolder
}

これで Configuration ごとに別の android-all の jar をダウンロードしてローカルにコピーするまでが実現できました。

あとは ./gradlew downloadRobolectricDependencies とするだけですべての jar がダウンロードできます。

Android Plugin for Gradle 3.1.0 で The SourceSet 'xxx' is not recognized by Android Gradle Plugin

Android Studio が 3.1.0 になったのに合わせて Android Plugin for Gradle も 3.1.0 が出ました。

Android Plugin for Gradle Release Notes | Android Studio

リリースノートには記述がありませんが、alpha のころから存在する問題として、3.1.0 にあげると"The SourceSet 'xxx' is not recognized by Android Gradle Plugin"というエラーでビルドが失敗する場合があるようです。 たまたま自分もこの問題にぶちあたったので、何がダメでどうするとよいかをメモしておきます。

次のような build.gradle がある前提です。 minSdktypeのふたつの flavorDimensions があり、variantFilter で api19FlavorA だけを無視する設定です。

android {
  flavorDimensions 'minSdk', 'type'

  productFlavors {
    api19 {
      dimension 'minSdk'
      minSdkVersion 19
    }
    api21 {
      dimension 'minSdk'
      minSdkVersion 21
    }
    flavorA {
      dimension 'type'
    }
    flavorB {
      dimension 'type'
    }
  }

  variantFilter { variant ->
    def names = variant.flavors*.name
    if (names.contains('api19') && names.contains('flavorA') {
      setIgnore(true)
    }
  }
}

このとき api19FlavorA の SourceSet を設定すると、タイトルにあるようなThe SourceSet 'xxx' is not recognized by Android Gradle Pluginというエラーが発生します。 Android Plugin for Gradle 3.0.x まではとくにエラーになるものではありませんでしたが、3.1.0 からはダメなようです。

上記のビルドスクリプトでは variantFilter で variant を setIgnore(true) する条件が ProductFlavor の名前だけなので、不注意でないかぎり無視した ProductFlavor の SourceSet を設定することはないと思いますが、無視する条件が環境によって変わる場合は、それにあわせて SourceSet の設定を変えてやる必要があります。

def disable_condition = // Condition to disable api19Flavor variant

android {
  // ...

  variantFilter { variant ->
    def names = variant.flavors*.name
    if (disable_condition && names.contains('api19') && names.contains('flavorA') {
      setIgnore(true)
    }
  }

  sourceSets {
    if (!disable_condition) {
      api19FlavorA {
        // api19FlavorA SourceSet configuration under a specific condition
      }
    }
    api19FlavorB {
      // always configure SourceSet for api19FlavorB
    }
    // ...
  }
}

割とよくあるパターンでリアディレイラーを壊す実績を解除した話

おそらくスポーツ自転車を買った人なら誰もが、納車のタイミングでお店の人に説明を受けるであろう「ディレイラーハンガーが曲がる」パターンでリアディレイラーが昇天してしまう体験を通勤チャリに乗って帰宅している最中に体験したのでメモしておきます。

ディレイラーハンガー is 何

スポーツ自転車の変速機は外にむき出しで、かつ一点で支えられている上に結構長さがあるので、ほんのちょっとでも力がかかると簡単に折れ曲がります。このとき変速機が車体に直接くっついていると、一緒に車体まで巻き添えにしてしまい車体ごと買い換えないといけなくなって辛いので、ディレイラーハンガーというのを車体に取り付け、そのディレイラーハンガーに変速機(リアディレイラー)を取り付けることで、たとえ折れ曲がってもディレイラーハンガーで負荷を吸収して車体までダメージが行かないようにしています。ただしやはりリアディレイラーのついているところが急所であることには変わりないので、ディレイラーハンガーがほんのちょっとでも曲がってしまうと変速がおかしくなったり、最悪後ろの車輪にリアディレイラーが巻き込まれて破損します。

どのタイミングで壊れたのか

ちょうど渋滞のなかでユルユル進んでいる最中に漕ぎ出して間もないタイミングで壊れました。自転車の後ろからけたたましい音がして車輪がロックしたのですぐに止めて、あーこれが例のアレだってことに気が付きました。 スピードがそんなに出てなかったこともあって転倒するなどの事故にはならなかったので本当にホッとしています。

結果

f:id:KeithYokoma:20180323210856j:plain

このとおりです。スピードがそこまで出てなかったとはいえ車輪に巻き込まれたので見事にヒョエエエエエエッッ!!!!となってます。

壊れたのに気がついて道の脇に寄せて、とりあえずディレイラーを取り外せる大きさの六角レンチがないのでハンガーまるごとワイヤーも解除して車体から取り外しました。その時は気づきませんでしたが家に帰ってよく見てみるとハンガーがえらい勢いで曲がってたので、そりゃあ車輪に巻き込まれるよねって感じでした。

今後

車体にあうディレイラーハンガーを探すのと、ディレイラーそのものも修理不能なので買い換える必要があります。といってもしばらくはチャリ通ができなそうなのでぼちぼちやっていきます。

ちなみにほんのちょっとしたディレイラーハンガーの曲がりならお店の人に見てもらうと直してもらえます。ですが極端に曲がってると直せないですし、直せるレベルでも交換できるなら交換したほうがいいですね。アルミ製とはいえ力のかかり方によっては簡単に曲がってしまうので。

現場からは以上です。

29歳になったので

いえーい!🍖たべるぞ!

干芋のリストはこちらです。https://www.amazon.jp/gp/registry/wishlist/2Y880HXG49I0M

CircleCI 2.0 で GCR に置いた private な Docker イメージを使ってビルドする

今日は Wercker がビルドキューを無限に積んでビルドしてくれなくなってしまったので、急遽 CircleCI 2.0 を試すことにしました。 メモリが足りずに OOM Killer にデーモンが殺されたり、desugar 中になにかが失敗しているようですが、とりあえず一番大事な「GCR にある private な Docker イメージでビルドを開始する」ことは可能なことがわかったのでよしです。公式にドキュメントが見当たらないのでこの記事で参考にしてください。

config.yml

Wercker の設定ファイルとほぼ同じように指定できますが、認証情報とタグの構造が違います。 認証情報は auth に持っていて、タグは image の部分に書きます。

version: 2
jobs:
  build:
    docker:
      # specify the version you desire here
      - image: gcr.io/your_account/your_registry_name:tag
        auth:
          username: _json_key
          password: $GCR_JSON_KEY_FILE

あとは環境変数の設定に GCR_JSON_KEY_FILE を追加すれば終わりです。

DroidKaigi 2018 やっていきed

「やっていき」だと未来の話だけど、やっていきが完了した話はどうやって言うのがいいか分からなかったので、edをつけてみました。

はい。DroidKaigi 2018 が無事に終わりました。自分の参加歴はスピーカーとしては 2 回目・スタッフとしては 4 回目(フル参戦)です。

自分の中での一番のハイライトはやはり、Romain Guy 氏と Chet Haase 氏と話ができたこと。今でもまだまだ英語力は足りなくて、このお二人のような気の利いたジョークは日本語でもままならないなって感じもするんですが、何はなくともとにかくいつも YouTube 等の画面の向こう側にいた「めちゃくちゃ話しを聞きたい人たち」が、自分の目の前にいていつでも話ができる状況にとてもテンションが上りました。たとえどんなにミーハーであろうとも、そして実際話したことはエンジニアリングとはあまり関係のないほぼ世間話のような内容だったとしても、これまでで一番貴重な体験になったようにおもいます。

スピーカーとしては、開催一週間くらい前までウンウン悩んでた資料がうまく(?)まとまって、当日は自分のよく知る話をひたすら 50 分ぶっ通しでやる実績を解除しました。去年は 30 分でしたが、今年はもう少し掘り下げたところも話しつつ全体像も捉えられるようにまとめて 50 分となりました。

speakerdeck.com

最後の最後、まとめで「これ実は Android Auto だけが使ってる話じゃないんだよね」と言うことで、タイトルが「Android Auto」である意味を盛大にひっくり返してしまいましたが、今思い返してもよくまぁこんなに内容てんこ盛りにしたなぁと自分でも思うので、次はやりすぎない程度に濃い話ができるようにしたいです。今後の目標は、どこかで完全英語版の発表をやることです。資料だけでなくスピーチも英語にするのが目標です。いやでも、過去に海外で完全英語版のプレゼンはしたことあるな……

年々 DroidKaigi の規模が大きくなってきており、それは参加者数だけでなく、会場の規模も、DroidKaigi に協賛してくださる企業の数も、そしてスタッフの人数も大きく成長し続けています。スタッフとしての事前の準備では会議を重ねて情報を共有し、知見のストックを Kibela で管理する流れが今年から導入されました。また、当日はインカムでものすごい量の情報があちこちから飛び交うので、大事な情報が流れていってしまわないよう運営アプリを作りました。各セッションルームの状況だったり、司会の人が話す内容の共有だったり、その他スタッフ全体に共有する情報を Push したりする目的でつくってみました。アプリの性質上公式アプリのように Google Play Store では公開していませんが、わりと公式アプリと同じような作りになっている部分もあったりします。技術カンファレンスの運営にカンファレンスで習得した技術を導入するというのは、言われてみればそういう目的でカンファレンスが開かれているのだから自然なことのようにも思いますが、今振り返ってみると結構面白い体験でした。

全日程が終了し、いろいろな方が思い思いの打ち上げを楽しんでいるのも見届け、今日は DroidKaigi Day3 ということで最後のお片付けをしました。カンファレンスで使う機材や資材には、会場からお借りしているもの・レンタル業者に依頼して借りているもの・自分たちが所有して使っているもの等といろいろあります。借りたものは所定の手順で返却をすればよいのですが、自分たちの所有しているものは自分たちで持って帰るか処分する必要があります。ある程度は宅配便等で保管場所に送ることもできますが、中には自分たちで倉庫に運ぶ必要のあるものもあります。今日はそういったものたちを運ぶ、ロジスティクスのお仕事の日でした。ハイエースクラスの車を運転した経験がここでいきるとは思ったこともなかったですが、これも貴重な体験でした。

また次回もやっていき!!

DroidKaigi 2018 Day 2 Room 5 で"詳解 Android Auto"という発表をします

droidkaigi.jp

気がついたら仕様の総ページ数が 200 を超えていて、超大作でお送りいたします。 Android Auto という取っ掛かりではありますが、幅広く応用の効く話を中心に据えていて、というか Android Auto の実体がほぼそういう潰しの効くフレームワークの上に成り立っているといって過言ではないのですが、そういう話をします。 2日目のランチ後という時間帯に50分の長丁場なセッションで、かつ自分が見たくて見たくて楽しみにしていたセッションが複数同時に走っていてん゛ん゛ん゛ん゛!゛!゛となっていますが、自分の発表も負けないくらい Android Auto とはなんぞやという話をします。

ちなみに先日の Shibuya.apk では Romain Guy 氏に握手してもらって最高でした!