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

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

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

Gradle の build scans 機能でビルドの情報を収集してブラウザ上で確認・共有できるぞ

Gradle に build scans 機能が追加されていることに気づいた。

Build scans are an important tool for developing and maintaining Gradle builds. They provide insights into exactly what your builds are doing, helping you identify problems with the build environment, dependencies, performance, and more. They can also help you understand and improve the build more generally, and make collaborating with others easier.

Build Scan Plugin User Manual

Gradle ビルドの情報を収集して、Gradle Enterprise (有償製品) か scans.gradle.com (Gradle が運営している無償の build scan サーバー) に送信し、web ブラウザ上で閲覧できるようにする、というもの。

試してみたところ、以下のような感じで閲覧できた。

Gradle の中身について詳しくなくても、なんとなくパフォーマンスの情報がわかったりしてなかなか便利。 (上のビルドは単純なものなのでそこまで有効活用できる気はしないけど、大規模なプロジェクトだったり、プラグインなどの開発をしていると有効活用できるのではないか。)

使い方

ドキュメントを見ればすぐに使える。

最初に試すときには、build.gradle ファイルはいじらずに ./gradlew --scan build という感じで --scan オプションを付けてやると自動的に build scan プラグインが適用された状態になる。 送信先は scans.gradle.com で、送信前には scans.gradle.com terms of service への同意確認がある。

収集される情報

上記部分に書かれている。 公開したくない情報が含まれないかどうかは注意が必要。

Gradle のマルチモジュールプロジェクトで maven-publish プラグインを使う場合の依存関係

Gradle でビルド成果物 (build artifact) を Maven リポジトリに公開するためのプラグインとして、maven-publish プラグインがある。

maven-publish プラグインとマルチプロジェクトの依存関係

簡単な例

Java ライブラリのプロジェクトがあったとして、JAR ファイルを Maven リポジトリに公開するための build.gradle の記述は以下のようになる。

// Java ライブラリのビルド用の設定などは省略

apply plugin: 'maven-publish'

publishing {
    publications {
        mavenJava(MavenPublication) {
            from components.java
        }
    }
}

これで publishToMavenLocal タスクなどが使えるようになるので、例えばローカルホスト上の Maven リポジトリに公開するには ./gradlew publishToMavenLocal コマンドを実行すればよい。

依存関係

上の例では、ソフトウェアコンポーネントとして java コンポーネントを指定している。 このとき、依存関係としては runtime コンフィギュレーションのものが使われる。

Name Provided By Artifacts Dependencies
java The Java Plugin Generated jar file Dependencies from 'runtime' configuration
web The War Plugin Generated war file No dependencies
Maven Publishing (new) - Gradle User Manual

マルチモジュールプロジェクトでの依存関係

例えば :foo プロジェクトと :bar プロジェクトからなるマルチモジュールプロジェクトで、:bar プロジェクトから :foo プロジェクトに依存している場合に、出力される pom.xml に記載される依存関係はどうなるのか?? 具体的には bar/build.gradle に以下のように書かれている状況である。

dependencies {
    compile project(':foo')
}

これはドキュメントには書かれていないが、maven-publish プラグインソースコードを確認したところ、依存先プロジェクトの設定に応じて自動的に依存先ライブラリの指定がなされるようになっていた。

例えば、依存先プロジェクトで maven-publish プラグインが使われていない場合は、プロジェクトのグループやプロジェクト名、バージョンが使われるようである。 依存先プロジェクトで maven-publish プラグインが使われており、特定の条件を満たす publication が存在する場合は、その publication で指定されているグループ・artifactId・バージョンが使われる。

publishing {
    publications {
        maven(MavenPublication) {
            groupId 'org.example'
            artifactId 'foo-sample'
            version '1.1'

            from components.java
        }
    }
}

foo/build.gradle に上のように書かれていたら、./gradlew :bar:publishToMavenLocal タスクで公開される成果物の pom ファイルには以下の内容が含まれることになる。

    <dependency>
      <groupId>org.example</groupId>
      <artifactId>foo-sample</artifactId>
      <version>1.1</version>
      <scope>compile</scope>
    </dependency>

気を付ける必要があること

依存先プロジェクトの中で maven-publish プラグインが使用されて、publication が定義されている必要がある。

下記のように、親プロジェクトで子プロジェクトの publication をまとめて定義することもできるのだが、そうすると依存関係の解決時に 「依存先プロジェクトでは publication がない」 という扱いになってしまって、デフォルト値が使用される。

publishing {
    publications {
        mavenFoo(MavenPublication) {
            groupId 'org.example'
            artifactId 'foo-sample'
            version '1.1'

            from findProject(':foo').components.java
        }

        mavenBar(MavenPublication) {
            groupId 'org.example'
            artifactId 'bar-sample'
            version '1.1'

            from findProject(':bar').components.java
        }
    }
}

私はこれでハマってしまった。 (Exposed の build.gradle を見て、親の build.gradle で子プロジェクトの publication を宣言していたので、それを参考にした、という。) 注意されたし。

Ktor で例外発生時に Sentry にクラッシュレポートを送る

最近、個人で web アプリケーションを書くのに Ktor (Kotlin の web アプリケーションフレームワーク) を使っています。

Ktor では、1 つ以上の 「インターセプタ (interceptor)」 から成る 「パイプライン (pipeline)」 にリクエストを通すことで、HTTP リクエストに対する処理が実行されます。 各インターセプタがリクエストに対して処理を行う、という形です。 パイプラインについての詳細なドキュメントはまだ未整備な状態 *1 ですが、Application の説明の方にパイプラインについても簡単に書かれています。

Sentry にクラッシュレポートを送る

今回は、後段のインターセプタで例外が発生した際に Sentry にクラッシュレポートを送るためのインターセプタを書いてみました。

Application クラスの intercept メソッド *2 を呼び、インターセプタを追加します。

fun Application.main() {
    // ... (略) ...
    Sentry.init(sentryDsn)
    // Application#intercept メソッドでインターセプタを追加する。
    intercept(ApplicationCallPipeline.Call) {
        try {
            proceed()
        } catch (e: Exception) {
            // 追加情報としてリクエストのエンドポイントの情報を与える。
            Sentry.getContext().addExtra(
                "Request endpoint",
                "${call.request.httpMethod.value} ${call.request.uri}")
            Sentry.capture(e)
            throw e
        } finally {
            Sentry.clearContext()
        }
    }
    // ... (略) ...

Sentry クラスなどは Sentry 公式の sentry ライブラリのものです。

悩み事

Ktor ではコルーチンが使われているため、各リクエストに対して 1 スレッドが割り当てられる、というわけではありません。 一方で、sentry ライブラリのコンテキストマネージャはデフォルトだとスレッドローカル変数を使用してコンテキストが分かれるような作りになっていて、Ktor の処理の中の様々な場所で Sentry のコンテキストを触ろうとするとスレッド違いなどを意識しないといけなくて難しそう、だと思ってます。

上の例では同じスレッド上でコンテキストを触っているので問題ないのですが、もうちょっと込み入ったことをしたくなると不便そうです。 コンテキスト管理をスレッドローカル変数でやるのは悪い文化。

Ktor でクラッシュレポートを Sentry に送るライブラリ

ライブラリもあります。 が、まだスナップショットでしかリリースしてなさそう? なので私は避けました。

本記事でのバージョン

  • Kotlin 1.2.10
  • Ktor 0.9.0
  • sentry 1.6.4

*1:ページとしては存在するが、まだ記載されていない

*2:Application クラスが継承している Pipeline クラスが実装している

Rust のプレリュード (prelude) とは何か

use std::io::prelude::*; というコードを見かけて、「プレリュード (prelude) ってなんだっけ」 と思ったので調べた。

Rust のプレリュード

まず、Rust で真にプレリュードと呼ばれるものは std::prelude というモジュールらしい。

Rust comes with a variety of things in its standard library. However, if you had to manually import every single thing that you used, it would be very verbose. But importing a lot of things that a program never uses isn't good either. A balance needs to be struck.

The prelude is the list of things that Rust automatically imports into every Rust program. It's kept as small as possible, and is focused on things, particularly traits, which are used in almost every single Rust program.

std::prelude - Rust

std::prelude モジュールに含まれるものは、すべての Rust プログラムで自動的にインポートされる。 このような仕組みになっているのは、よく使うものも全て手動でインポートする必要があるならばコードが煩くなってしまうため。

他のプレリュード

Rust の真のプレリュードは上に述べた通りだが、それ以外にも、複数の型を使用する際のインポートをより便利にするパターンとしてプレリュードの名が使われている。

Other preludes

Preludes can be seen as a pattern to make using multiple types more convenient. As such, you'll find other preludes in the standard library, such as std::io::prelude. Various libraries in the Rust ecosystem may also define their own preludes.

The difference between 'the prelude' and these other preludes is that they are not automatically use'd, and must be imported manually. This is still easier than importing all of their constituent components.

std::prelude - Rust

例えば std::io::prelude というモジュールは、IO 周りでよく使う型のインポートを楽にするためのモジュールである。 以下のようにして使われる。

use std::io::prelude::*;

真のプレリュードとは違い、自動的にインポートされるわけではなく、あくまで手動のインポートを楽にするためのパターンとのこと。 (なので、prelude という名前じゃなくても挙動的には良さそうである。)

CircleCI 2.0 で複数ファイルからキャッシュキーを生成する便利な方法

CircleCI 2.0 のウリの一つが、1.0 より柔軟になったキャッシュ機能である。 キャッシュのキーを自分で決定し、ファイル群をキャッシュすることができる。 そして、キャッシュのキーの決定には、ブランチ名を使ったり、指定のファイルのチェックサムを用いたりできる。 たとえば pom.xml のファイルの内容に応じてキーを変更したい場合は m2-{{ checksum "pom.xml" }} という風に記述する。 (下記参考ページのサンプルより。)

さて、ファイルの内容が異なっていたらキーを変えたい、というとき、その対象のファイルが複数個存在することが多いと思う。 記法的には m2-{{ checksum "pom.xml" }}-{{ checksum "..." }}-{{ checksum "..." }} という風に複数ファイルを指定できるが、もっと書きやすくしたい。

そこで、私は 「キーの計算対象のファイル群の md5sum コマンドの出力を集めたファイルを 1 つ用意して、それをキー計算に用いる」 ということをしている。

具体的には、以下のような感じである。 Maven の依存ライブラリをキャッシュするため、まず、.mvn/wrapper/maven-wrapper.properties やプロジェクト中の全ての pom.xml に対する md5sum の出力を ~/cache-key-source-m2 に書き出している。

      - run:
          name: Calculate cache key for Maven dependencies
          command: |
            {
              md5sum .mvn/wrapper/maven-wrapper.properties
              md5sum $(find . -name 'pom.xml' | sort -r)
            } > ~/cache-key-source-m2

そして、~/cache-key-source-m2 ファイルだけを対象にキーを計算している。

      - restore_cache:
          keys:
            - m2-deps-v1-{{ checksum "~/cache-key-source-m2" }}
            - m2-deps-v1-

Android のプロジェクトなどでも、settings.gradle と全ての build.gradle を対象にしてキー計算をしたいということが多い。 そのような場合でも活用できるだろう。 (より良い記述方法があったら教えてください!)

関連ページ