ひだまりソケットは壊れない

ソフトウェア開発に関する話を書きます。 最近は主に Android アプリ、Windows アプリ (UWP アプリ)、Java 関係です。

まじめなことを書くつもりでやっています。 適当なことは 「一角獣は夜に啼く」 に書いています。

Gradle 4.9 の新しいタスク定義 API と Gradle Kotlin DSL 1.0 RC での対応

Gradle 4.9 の新しいタスク定義 API

Gradle 4.9 で、新しいタスク定義 API が導入されました。 まだ incubating です。

blog.gradle.org

パフォーマンス向上のため、タスクの設定を遅延実行する、というのがこの新しい API の導入の目的のようです。 古い API では Task インスタンスをすぐに生成していたのを、新 API ではまず Provider<Task> を生成して、必要になった段階で Task インスタンスを生成する、という形になります。

API から新 API へのマイグレーション

API から新 API への置き換えは

  • tasks.create(...)tasks.register(...)
  • tasks.withType(SomeType) { }tasks.withType(SomeType).configureEach { }
  • tasks.all { }tasks.configureEach { }
  • tasks.getByName(...)tasks.named(...)

という感じです。

Gradle Kotlin DSL 1.0 RC での対応

ちょうど昨日 Gradle 4.10 がリリースされましたね! めでたい!

Gradle 4.10 には Gradle Kotlin DSL 1.0-RC3 が載っており、Kotlin DSL の方でも新しいタスク定義 API がサポートされています。

  • introducing the new existing and registering delegated properties designed specifically with configuration avoidance in mind which expose NamedDomainObjectProvider<T> instead of the container element type.
Release 1.0-RC3 · gradle/kotlin-dsl · GitHub

下記のように使用できます。

// 既存タスクの設定。
val test by tasks.existing(Test::class) { ... }
// 新規タスクの宣言と設定。
val jacocoMerge by tasks.registering(JacocoMerge::class) { ... }

使ってみた

早速使ってみました。

Update Gradle (version 4.10) by nobuoka · Pull Request #13 · nobuoka/wd-image-processor · GitHub

小さいプロジェクトなので特にパフォーマンスの向上などは感じられませんが、移行自体は苦労なくできます。

Gradle のマルチモジュールプロジェクトで JaCoCo の結果を集計する

Java / Kotlin のコードカバレッジツールとして JaCoCo を使いたい。 Gradle のマルチモジュールプロジェクトでの JaCoCo の導入について記す。

f:id:nobuoka:20180814004742p:plain

(この図は JaCoCo によるコードカバレッジの集計結果の履歴を Codecov で表示した例。)

JaCoCo について

JaCoCo は、JVM 言語のコードカバレッジツールである。 Java Agent によるオンザフライ方式のバイトコード instrumentation によるカバレッジ計測が可能である。

以前、Kotlin のコードカバレッジツールについて書いたので、こちらも参考にどうぞ。

Gradle プロジェクトで JaCoCo を使ったカバレッジ計測を行う

Gradle の JaCoCo プラグインを使うことで、Gradle のプロジェクトで JaCoCo を使ったコードカバレッジ計測を手軽に行うことができる。 ちなみに Gradle 4.9 の段階ではこのプラグインは incubating である。

Junit 5 のテスト実行時にカバレッジを計測する

各モジュールのテスト実行時にコードカバレッジを計測するだけなら非常に簡単で、単に JaCoCo プラグインを適用するだけで良い。

While all tasks of type Test are automatically enhanced to provide coverage information when the java plugin has been applied, any task that implements JavaForkOptions can be enhanced by the JaCoCo plugin. That is, any task that forks Java processes can be used to generate coverage information.

The JaCoCo Plugin - Gradle User Manual

バージョンを表す変数の定義や repositories の定義などは省略しているが、おおよそ以下のようなビルドスクリプトJUnit 5 でのテスト実行時に JaCoCo によるコードカバレッジの計測がなされる。

apply plugin: 'kotlin'
apply plugin: 'jacoco'

test {
    useJUnitPlatform()
}

dependencies {
    implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"

    testCompileOnly("org.junit.jupiter:junit-jupiter-api:$junit_version")
    testRuntime("org.junit.jupiter:junit-jupiter-engine:$junit_version")
}

デフォルトでは、カバレッジの計測結果は build/jacoco/test.exec というファイルに出力されるようである。

複数モジュールのコードカバレッジの集計

難しいのはここからで、複数モジュールでのコードカバレッジをどう集計するかで私は結構悩んだ。 JaCoCo の結果集計のためのタスクとしては JacocoMerge というのがあるのだが、これをどう定義するのかが難しい。 (どのサブモジュールのカバレッジを集計するのかを明示的に指定するのであればそれほど大変ではないが、自動で全サブモジュールのカバレッジを集計するためのタスク定義をするのが難しい。)

結論としては、ルートプロジェクトに以下のようなタスク定義を行うことで対応した。 (ルートプロジェクトにも jacoco プラグインを適用している。)

task jacocoMerge(type: JacocoMerge) {
    // ルートプロジェクトの評価時にはまだサブプロジェクトは存在しないので、
    // 各プロジェクトの評価完了時に中の処理が走るように。
    gradle.afterProject { p, state ->
        // ルートプロジェクトと `jacoco` プラグインが適用されていないプロジェクトは除く。
        if (p.rootProject != p && p.plugins.hasPlugin('jacoco')) {
            executionData p.tasks.test.jacoco.destinationFile
            dependsOn(p.tasks.test)
        }
    }
}

大雑把に説明すると、jacocoMerge タスクの executionData に各サブモジュールのテスト実行時のカバレッジ計測結果を渡していき、かつ、jacocoMerge タスクの依存に各サブモジュールの test タスクを追加する、ということをしている。

gradle.afterProject を使っている理由や if 文を使っている理由はコメントに書いてあるとおりである。

Gradle のプロジェクトの評価順については下記ページを参照されたい。

集計結果のコードカバレッジのレポーティング

さらにレポーティングのタスクは、ルートプロジェクトに下記のように定義することで行える。 java プラグインが適用されている全サブモジュールのソースファイルをコードカバレッジのレポート対象に含める場合の例である。

task jacocoMergedReport(type: JacocoReport, dependsOn: [tasks.jacocoMerge]) {
    executionData jacocoMerge.destinationFile

    sourceDirectories = files()
    classDirectories = files()
    gradle.afterProject { p, state ->
        if (p.rootProject != p && p.plugins.hasPlugin('java')) {
            sourceDirectories = sourceDirectories + files([p.sourceSets.main.allJava.srcDirs])
            classDirectories = classDirectories + p.sourceSets.main.output
        }
    }

    reports {
        xml.enabled = true
        xml.destination file("${buildDir}/reports/jacoco/report.xml")
        html.destination file("${buildDir}/reports/jacoco/html")
    }
}

本当は JacocoReport#sourceSets を使いたかったが、内部で gradle.afterProject を使われていて期待する挙動にならなかったので諦めた。

ちなみに上の xml.destination の値は Codecov が期待するレポートファイルの位置である。

サンプル

下記プロジェクトで今回書いたコードカバレッジ取得の方法を行っている。

ちなみに CircleCI でのビルド時にコードカバレッジを取得して Codecov で可視化してる。

参考

Windows 10 で Git 環境を整える

自分用メモ。 昔は GitHub Desktop をインストールすれば Git Shell も使えるようになって PowerShell で快適 Git 生活が送れていたのだけど、最近 GitHub Desktop をインストールしてみたところ、どうやら Git Shell は同梱されないようになってしまったっぽい。

今ならこうすると良さそう、というメモ。

Git 環境を整える

GitHub Desktop インストール

人によっては全然いらないと思うけど、あったらあったで便利なので。

GitHub Desktop | Simple collaboration from your desktop

起動したら GitHub にログイン。 それで Git の設定でユーザー名やメールアドレスが自動的に設定されるので便利。

Git for Windows をインストール

GitHub Desktop にも git コマンドは同梱されているけど、GitHub Desktop とは独立してバージョンアップなどをできるように Git for Windows をインストールしておく。

Git - Downloading Package (Git 本家サイトの Git for Windows のダウンロードページ)
Git for Windows (こっちは Git for Windows のサイト)

インストール途中の選択肢で、push 時や pull 時の改行コードの変換をどうするか聞いてくれる。 が、as-is を選んでも改行コードが変換されないようにならなかった。 (なんでだろ。)

git config --global core.autoCRLF false

しょうがないので上記コマンドを後から実行した。

posh-git をインストール

PowerShell で Git が便利になる。 タブでサブコマンドやブランチ名の補完をしてくれたり、プロンプトに現在のブランチを表示してくれたりする。 (昔の GitHub Desktop に同梱されていた Git Shell で PowerShell を使う場合には自動で有効になってた。)

posh-git by dahlbyk
Git - PowershellでGitを使う

書かれてる通りにインストール。

必要な設定

文字化け (?) 対応

初期状態だと git log などで日本語の表示がおかしくなる。 文字化けというか、バイト値が 16 進数表記で連なる感じ。

git config --global core.pager "LESSCHARSET=utf-8 less"

という感じで、config の core.pager を 「LESSCHARSET=utf-8 less」 に設定すれば良さそう。

~\Documents\test [master]> git log --graph --decorate
* commit a5e5b4d21432f3fcba4ffbf1dd0d4bc598c4b051 (HEAD -> master)
  Author: Nobuoka Yu <nobuoka@vividcode.info>
  Date:   Wed Aug 8 23:33:26 2018 +0900

      <E3><81><93><E3><82><93><E3><81><AB><E3><81><A1><E3><81><AF>

~\Documents\test [master]> git config --global core.pager "LESSCHARSET=utf-8 less"

~\Documents\test [master]> git log --graph --decorate
* commit a5e5b4d21432f3fcba4ffbf1dd0d4bc598c4b051 (HEAD -> master)
  Author: Nobuoka Yu <nobuoka@vividcode.info>
  Date:   Wed Aug 8 23:33:26 2018 +0900

      こんにちは
テキストエディタの設定を 「メモ帳」 にする

デフォルトだと Vim が起動するが、日本語の表示がうまくされないのでメモ帳を使うようにする。

git config --global core.editor "notepad"

読んだ : 入門 Kubernetes

Kubernetes に興味を持ちつつ何も手を付けていなかったのだけど、会社に置かれてたのでまずは本を読んだ。

入門 Kubernetes

入門 Kubernetes

Kubernetes を何故使うのか、というところから始まり、Kubernetes における各種オブジェクトの大まかな概念の説明から使い方まで一通り解説されている。

読書メモ

Kubernetes を何故使うのか、という導入では以下のような内容が書かれている。

  • ベロシティ : 宣言的設定でイミュータブルなコンテナを扱うことで自己回復しやすくスケールもしやすくなっている。
  • サービスとチームのスケール : マイクロサービス化による開発チームのスケールや、アプリケーションやクラスタのスケールをしやすくする、など。
  • インフラの抽象化・アプリケーション実行のためのプラットフォーム : 『プロダクションレディマイクロサービス』 で言われていた 4 層モデルの 「アプリケーションプラットフォーム」 に該当する。 アプリケーションプラットフォーム側がアプリケーション開発側に対して API を提供することで、アプリケーションプラットフォーム側とアプリケーション側の責務を切り離す。
  • インフラの効率性。 アプリケーションのコンテナを同じ物理マシン (または仮想マシン) 上に同居させることができる。

便利。

プロダクションレディマイクロサービス ―運用に強い本番対応システムの実装と標準化

プロダクションレディマイクロサービス ―運用に強い本番対応システムの実装と標準化

それから、「Pod」 や 「ReplicaSet」、「DaemonSet」、「Deployment」、「Job」、「Service」、「Namespace」 といった概念について、本を通して解説される。 下記に今のところの自分の理解を書いておく。

  • Pod : コンテナの集まり。 Web サービスのアプリケーションの 1 インスタンスなどが Pod になる。
  • ReplicaSet : 特定種類の Pod の集まり。 Web サービスを複数インスタンスで動かす際などには ReplicaSet で複数 Pod を動かす、という形になる。
  • Deployment : ReplicaSet の履歴を保持したオブジェクト。 Web サービスに変更を加える際には、新バージョンに対応する ReplicaSet を Deployment に追加してアップデートを行う、という形。
  • Service : Kubernetes クラスタ上のアプリケーション同士の通信や、外部への公開のために使われる。

良かった

Kubernetes に入門するには良い本だった。 著者は Kubernetes の開発者なので、どういう思想でこうなっているのか、というのもところどころに書かれていて 「なるほどねー」 と言いながら読める。 本書をざーっと読んで概念を理解したら、あとは実際に Kubernetes を触って動かしていくと良さそう。 今なら Docker for WindowsKubernetes クラスタを簡単に用意できたりするので、軽く試すだけなら簡単にできる。

Kubernetes やっていこう。

Firefox の JS コンテキストのロケール指定方法

背景・目的

WebDriver 経由で geckodriver + Firefox を操作して JS の自動テストを行いたい。 その時、JS にロケール依存の処理が含まれていれば、テスト実行時にも Firefox の JS コンテキストのロケール指定を行いたい。

本記事では、Firefox 59 以降での、JS コンテキストのロケール指定方法について簡単に書いておく。

3 行まとめ

詳しい話

ECMAScript と国際化

ECMAScript における国際化のための仕様として ECMA-402 (ECMAScript Internationalization API Specification) というものがある。

この仕様により、例えば Date.prototype.toLocaleString メソッドなどが定義される。 このメソッドの呼び出し時にロケールを明示的に指定しなかった場合、使用されるロケールDefaultLocale 抽象操作によって決まる。 DefaultLocale実装依存である。

Firefox での DefaultLocale (JS コンテキストのデフォルトロケール)

Firefox における ECMA-402 の DefaultLocale の実装は、Firefoxロケールを返すようになっている模様。 ちゃんとしたドキュメントは見当たらなかったが、Bugzilla で教えてもらった。

JS context locale is tied to Firefox locale, not requested locale.

1475876 - intl.locale.requested doesn't affect to JS locale unless Language Pack is installed
Firefoxロケールの決まり方

で、Firefoxロケールの決まり方であるが、RFC 5656 をベースに、available locales と requested locales から決まるらしい。

Due to the imperfections in data matching, all operations on locales should always use a language negotiation algorithm to resolve the best available set of locales, based on the list of all available locales and an ordered list of requested locales.

Such algorithms may vary in sophistication and number of strategies. Mozilla’s solution is based on modified logic from RFC 5656.

Locale management — Mozilla Source Tree Docs 63.0a1 documentation

Available locales は、Firefox のパッケージに含まれているロケール (packaged locales) と、拡張機能としてインストールされた言語パックのロケール

In Gecko, available locales come from the Packaged Locales and the installed language packs. Language packs are a variant of web extensions providing just localized resources for one or more languages.

Locale management — Mozilla Source Tree Docs 63.0a1 documentation

デスクトップ版の Firefox では、Packaged locales は普通は 1 つのみ。 Android 版の方は 100 ぐらいのロケールを含むらしい。

When the Gecko application is being packaged it bundles a selection of locale resources to be available within it. At the moment, for example, most Firefox for Android builds come with almost 100 locales packaged into it, while Desktop Firefox comes with usually just one packaged locale.

Locale management — Mozilla Source Tree Docs 63.0a1 documentation

Requested locales は次のとおりで、intl.locale.requested pref に保持される。

After the sanitization, the value will be stored in a pref intl.locale.requested. The pref usually will store a comma separated list of valid BCP47 locale codes, but it can also have two special meanings:

  • If the pref is not set at all, Gecko will use the default locale as the requested one.
  • If the pref is set to an empty string, Gecko will look into OS app locales as the requested.

The former is the current default setting for Firefox Desktop, and the latter is the default setting for Firefox for Android.

Locale management — Mozilla Source Tree Docs 63.0a1 documentation

なので、基本的には使いたいロケールFirefox のパッケージに含まれるロケールか言語パックとしてインストールされたロケールであれば、intl.locale.requested にそのロケールを指定してやれば JS コンテキストのロケールがそのロケールになる。

en-US ロケールは特殊なロケール

日本語ロケールのデスクトップ版 Firefoxintl.locale.requested=en-US とした場合、英語の言語パックをインストールしていないと en-US ロケールにはならなさそう (available locales に含まれてないはずなので) なのだけど、実際には JS コンテキストのロケールは en-US になっているような挙動になった。

なんでだろー、と思ったのだけど、どうやら last fallback locale という特殊な扱いで en-US が使われてるらしい。

Gecko also support a notion of last fallback locale, which is currently hardcoded to “en-US”, and is the very final locale to try in case nothing else (including the default locale) works.

Locale management — Mozilla Source Tree Docs 63.0a1 documentation

悩み

というわけで、複数のロケールを切り替えて JS のテストをしたい場合は、言語パックをインストールすることになるっぽい。 もしくはロケールごとに Firefox のバイナリを用意するか。

言語パックのインストールがコマンドライン上で簡単にできればいいのだけど、ちょっと調べた感じでは大変そうで、どうしたものかなーと思っている。 簡単にできる方法があれば教えてください!

参考

Acknowledgement

この記事の内容は、株式会社 OND の仕事の一環として調べたものです。