ページ

2015/12/31

2015年振り返り

主にOSS活動を中心として、自分の今年の活動を簡単に振り返ってみる。
(書き方はバラバラだが)

2015/12/20

GradleのContinuous BuildでSpring Bootアプリ実行中にリソース変更を反映する

|

タイトルの内容は、もともとできるんじゃないの?という感じもするかもしれないが、それではうまくいかないケースがあり、それをGradleのContinuous Build機能を使ったら何とか実現できた、という話。

Continuous Build
http://gradle.org/feature-spotlight-continuous-build/
https://docs.gradle.org/current/userguide/continuous_build.html

少し長いので、順序立てて説明。

背景

Spring Boot + Gradle で開発している時、特にWebの画面をもつアプリを開発している場合は「gradlew bootRunでのアプリ起動中にThymeleafテンプレートやpropertiesなどの静的リソースを変更したら、再起動なしで反映するようにしたい」と考えると思う。

もともと Spring Boot 1.2.x までは、デフォルトで src/main/resources 以下がbootRunタスクのクラスパスに追加されていたため、変更して画面をリロードすればすぐに反映されていた。

Spring Boot 1.3.0では、Automatic Restart、Live Reloadといった機能が DevTools として実装されたのだが、どうも意図したように再起動・リロードしてくれない。静的リソースを変更しただけで再起動してしまうし、Live Reload もうまく動作していない。

またこの機能が入ったことにより、bootRun タスクでは src/main/resources をクラスパスに含めないのがデフォルトとなったのだが、結局うまく動作しないので以下のように build.gradle に設定して、1.2.x までのようにクラスパスに入れてくれるようにしている。

bootRun {
    addResources = true
}

しかし、この方法にはいくつか問題がある。

リソースが直接読み取られる

Spring Boot のドキュメントでも、bootRun.addResources = false をデフォルトにした理由として書いてあったのだが、この方法の問題は、 src/main/resources 以下のファイルの内容がそのまま Spring Boot に読み取られてしまうことだ。

bootRun は開発用のタスクであり、実際にリリースする場合は当然JARファイルにしてデプロイする。その際には src/main/resources が丸ごとJARに入って読み取られるわけではなく、processResources タスクでフィルタリングすることができ、ファイル内容を加工してからJARに入るようにしたり、一部のファイルだけを含めるようにしたりすることができる。
設定ファイルの一部に変数を埋め込んでおいて、processResources タスクで置換することもできる。

こうした処理をスキップして直接 src/main/resources 以下が読み取られてしまうので、processResources タスクなどでリソースに手を加える必要がある場合は、開発時とリリース時で異なる状態になってしまう。

プロパティファイルはnative2asciiした状態で管理しなければならない

Propertiesファイルの仕様としては、native2asciiをかけてUnicodeエスケープされていなければいけない。

だから、bootRunタスクでリソースを直接読み取らせるようにするならば、Unicodeエスケープされた状態でファイルを管理していなければならない。例えばIntelliJ で開発するなら Transparent native to ascii conversionの設定 をしておく必要がある。

これが非常に扱いづらい。
何と言っても、bootRun実行中にpropertiesを編集したりするとIntelliJが変換してくれないことがあるし、新規ファイルを追加した後はIntelliJを再起動しないと変換対象ファイルとして認識してくれなかったりする(これは動かしてみての推測で、仕様/不具合なのかどうかは不明)。

さらに、Unicodeエスケープされた状態のファイルをバージョン管理することになるので、ソースコードレビューする際にもUnicodeエスケープされた状態で差分が出てくる。

まあ、Thymeleafに限って言えば、native2asciiしてなくてもきちんと表示されるのだが、@PropertySourcesとかProperties#load()などを使ってプロパティを読み込む場合はきちんと変換していなければUnicode文字を表示できない。

解決策

というわけで、以下のように管理・開発できるのが望ましいなと考えた。

  • src/main/resources以下のファイルはヒューマンリーダブルな状態で管理する
  • アプリのビルド時にnative2asciiなどのファイル加工を実行する
  • バージョン管理するのは加工前のヒューマンリーダブルなほう
  • bootRunでの実行、JARファイルとしての実行、どちらも加工済みのファイルだけを参照する(開発・本番の差異は少なくする)
  • processResourcesなどによるファイル加工は、ファイル変更を検知して自動的に実行する

src/main/resources以下のファイルを加工するタスクは、processResourcesでもいいし独自のタスクでも構わない。
processResourcesはデフォルトではbuild/resources/mainにファイルを出力するので、独自タスクを用意する場合は、このディレクトリにアウトプットすればいい。
このタスクを手動実行すれば話は簡単なのだが、開発効率が下がるので避けたい。

そこで解決策となるのは、冒頭に書いた通り、Gradle に最近追加された Continuous Build という機能。-tオプションをつけてタスクを実行すると、そのタスクの入力ファイルの変更を検知してタスクを再実行してくれる。この機能を使って、src/main/resources以下のファイルを随時加工していけばいい。
フロントエンド界隈のGulpとかGruntなどだと既に提供されている機能だけど、Gradleでもできるようになっていた。

例えば、src/main/resources/messages.propertiesをnative2asciiする場合は以下のようにすればいい。

task native2ascii {
    inputs.files files("src/main/resources/messages.properties")
    doLast {
        ant.native2ascii(
            src: "${projectDir}/src/main/resources",
            dest: "${buildDir}/resources/main")
    }
}

そして、次のように-tをつけてnative2asciiタスクを実行すれば、messages.propertiesを変更するたびにbuild/resources/以下に変換され、出力される。

./gradlew -t native2ascii

なお、これだけだと当然bootRunタスクは動かないので、
別のターミナルでbootRunタスクを実行しておく必要がある。

これで、bootRun.addResources = trueとしていなくても、
アプリ実行中に静的リソースの編集→反映ができて開発効率も落ちず
ソースコードレビューもしやすくなる。

残りの問題

Gradleを同じプロジェクトの中で複数動かすため、時々タスクの起動に失敗してしまう。正確には、Configurating …と表示されたまま止まってしまう。
(一度両方のタスクを停止して起動しなおせば大概うまくいくが)

それから、2つのタスクを別ターミナルで実行しなければいけないという面倒さ。開発が進行中のプロジェクトでは、きちんと周知しないと「変更が反映されなくなった!」と不満が噴出するかもしれない。

この2つが特に問題にならなければ、この方法でうまくいきそうだ。

2015/12/16

Gradleで依存関係が含まれているかを判定する方法

|

Gradleのあるプロジェクトが、あるdependencyを直接持っているかどうかは以下で確認できる。

boolean hasDependency(Project project, String group, String module) {
    project.configurations.compile.incoming.resolutionResult.allComponents.any {
        it.group == group && it.name == module
    }
}

しかし、これだと推移的依存関係が判定できない。
つまり、「このプロジェクトにはライブラリAが含まれているか?」を判定したい場合に、「ライブラリAに依存しているライブラリB」を使用している場合は、上記の判定メソッドは false を返してしまう。
この推移的依存関係も判定するには次のようにする。

boolean hasDependency(Project project, String group, String module) {
    project.configurations.compile.incoming.resolutionResult.allComponents.findAll {
        it.getId() instanceof ModuleComponentIdentifier
    }.collect {
        it.getId() as ModuleComponentIdentifier
    }.any {
        it.group == group && it.module == module
    }
}

いずれの場合も、プロジェクトが評価された後でなければ動作しない。
つまり以下のようにafterEvaluateを使う必要がある。

afterEvaluate { Project project ->
    if (hasDependency(project, 'com.example', 'awesomelib')) {
        // 'com.example:awesomelib'が含まれていた場合の処理
    }
}

特に後者の”推移的依存関係が判定できる版”をGradleプラグインで使う場合、
Gradle TestKitでないと正しくテストできないので注意が必要。
普通のProjectBuilderを使ったテストでは
project.evaluate()などとしても正しい結果が得られない模様。

2015/12/16 23:36 追記

GradleプラグインのGroovyファイルでproject.evaluate()しても結果が得られなかったのは、

project.repositories {
    mavenCentral()
}

が入っていなかったからだった。
dependencyを解決できないはずだが、単にproject.evaluate()を呼ぶだけでは何も例外がスローされないらしい。

2015/12/11

IntelliJのインデックス対象から除外する方法

|

IntelliJ IDEAで開発していると、プロジェクト内のファイルが自動的にインデックスされて検索などに利用される。
この機能によって、何かのツールが生成したファイルやログファイルなど、スキャンしてほしくないファイルまでスキャンされ、しかもそれらが頻繁に変更されて何度もスキャンされるため非常に動作が重たくなってしまうことがある。

これを回避するには、build.gradleでideaプラグインを適用して除外してやればいい。

例えばVagrantを使っている場合は.vagrantディレクトリは無視すべきディレクトリなので以下のように書けばいい。

apply plugin: 'idea'

idea {
    module {
        excludeDirs += [
            file('.vagrant'),
        ]
    }
}

これで.vagrantディレクトリ以下のファイルはスキャンされなくなる。
(検索にもヒットしなくなるので注意)
.gitignoreに入れているものは上記のexclude設定も入れておく、という感じ。

Gradle実行時のメモリオプション指定方法

|

Gradleを実行する時のメモリを調整したい場合、いつでも
下記に説明されているようなorg.gradle.jvmargsを使えば良い
とつい最近まで思っていた。
https://docs.gradle.org/current/userguide/build_environment.html
しかしこのオプションが有効なのは、上記で説明されている通り
デーモンとして起動した場合だ。
デーモンとして起動しない場合は適用されないので注意。
だから、gradle.propertiesにorg.gradle.daemon=falseと書いておきながら
org.gradle.jvmargs=...とか書いていても意味がない。

デーモン起動でない場合にメモリなどのJVMパラメータを指定したい場合は
GRADLE_OPTS, JAVA_OPTSを使うといいらしい。
(これも上記ドキュメントで説明されているが)

先日、Travis CIでの実行時にのみメモリ調整が必要なケースがあり、上記の違いを理解した。
なおTravis CIならば、.travis.ymlに例えば

env:
  global:
  - GRADLE_OPTS="-Xmx1024m -Xms256m -XX:MaxPermSize=256m -XX:PermSize=256m"

とか書けばいい。

Spring BootのプロファイルにGitコミットハッシュ値を含める

|

今起動しているSpring Bootアプリはどのコミットでビルドされたものなのか?
を確認できるようにしたいと思い、やり方を探ってみた。

前提として、ビルドにはGradleを使う。
AndroidアプリなどでもGradleを使ってSHAハッシュ値を
アプリの中に含めたりしていたが、それと似たようなことをやる方法。

2015/12/06

AndroidのCIでbuild-toolsの新しいバージョンが見つからない場合の対処

|

久しぶりにAndroid-ObservableScrollViewのAndroid SDKのバージョンなどを
アップデートしたが、Travis CIでもwerckerでもビルドが失敗してしまった。

もちろんローカルでは成功している。

API Levelの問題などではなく、build-tools-23.0.2が見つからなかったというもの。

結論としては、toolsを先にアップデートすれば良い。

2015/12/01

AndroidのテストタスクだけGradleでログ出力する

|

久々のAndroid関連ネタ。
Android-ObservableScrollViewでは、なるべくカバレッジ100%になるようテストを書こうとしている。
しかしテストが増えてきたせいか、Travis CIでのテスト実行中に出力がない時間が10分以上続いてしまい、ビルドが失敗することが多くなってきた。
テスト系のタスクだけもう少しログ出力させられないか?
を解決する方法について紹介する。

まず、テスト系限定でなく、すべてのタスクに対して
INFOレベルでログ出力するのなら
Gradle的には以下のように–infoなどをつければいい。

./gradlew connectedCheck --info

しかしすべてのタスクがINFOレベルで出力されるのは
見づらくなるだけなので避けたい。