Gradleで特定のJarをtestRuntimeで使いたいけどWarからは除外したい
Twitterで@ruimoさんがつぶやかれていたので色々試してみた結果をまとめておきます。 (試行錯誤で徐々に追記しています。)
経緯
@ruimo 単にdependenciesで対象ライブラリをtestRuntimeの方に追加する、ではだめですか?
— Yasuharu Nakano (@nobeans) 2014, 4月 17
@ruimo なるほどー。 http://t.co/erMFnUOFyp とか http://t.co/bCT0piyH8F の辺が参考になりそうな気がします。
— Yasuharu Nakano (@nobeans) 2014, 4月 17
@ruimo http://t.co/bCT0piyH8F の質問にあるコードのようにwarプロジェクトのdependenciesの中で、子プロジェクトごとのexclude設定をすれば除外できますが、それも数の問題で煩雑、という話ですかね
— Yasuharu Nakano (@nobeans) 2014, 4月 17
@ruimo あーなるほど。結局war化するときに除外したいだけなのにexcludeがサポートされてない(?)ので困った感じですね
— Yasuharu Nakano (@nobeans) 2014, 4月 17
解決案
案1 標準APIで一応あるはずのexclude/excludes (効かない)
warタスクでもexcludesによるパターン除外が効けば良いんですけど、どうもできない感じですね...。パターン指定の仕方が悪いのかしら。Antパターン指定とかで試してみたんですが、何にも変化なしな感じ。
war { excludes = ["**/hoge*"] }
案2 warタスクで自前でフィルタリング
結局「warに入らなくしたいだけ」なので、余計なruntimeなどをいじるのではなくwarに入れる処理のことろで自前フィルタリングするのが良さげです。
war { // 自前でフィルタリングすると一応除外できた。 // この場合、推移的依存関係は使えないのですべての除外Jarを指定しなければならない。 def myExcludes = ["hogehoge", "foofoo"] classpath = classpath.collect { file -> // 除外リスト内のパタンと部分一致に入っているものはnullにしておいて後でfindAllで除外する。 myExcludes.any { file =~ it } ? null : file }.findAll { it } // null除外 }
どうでもいいけど、Groovyコレクション操作でfindAllの反対が欲しいなぁ。removeAllはJava標準APIに引きずられて破壊的なのが駄目な感じ。
で、この方式でWarに入るJarは除外できます。ただし、これだと推移的依存関係は辿れなくて、ファイル名マッチングをするしかないので、すべての除外Jarを指定しなければならないのが面倒です。上記でfoofooが実はbarbarにも依存してたりすると、Warの中にbarbarだけが含まれてしまいます。
案3 warタスクで自前でフィルタリング(推移的除外関係の自動解決版) (ただし、中途半端)
で、ちょっとトリッキーだけど解決策としてこんなのを考えてみました。
configurations { excludeFromWar } dependencies { //... // warタスクでの自前フィルタリングで、推移的依存関係を全て列挙したくないので、 // このように除外目的のconfigurationsを作ってしまうと楽な気がする。 excludeFromWar 'hoge:hogehoge:1.0' excludeFromWar 'foo:foofoo:1.2' } war { // 推移的依存関係を自動的に辿れるように除外用のconfigurationsを作ると便利。 classpath = classpath.collect { file -> // 除外リスト内のパタンと部分一致に入っているものはnullにしておいて後でfindAllで除外する。 configurations.excludeFromWar.contains(file) ? null : file }.findAll { it } // null除外 }
こうすると、たとえfoofooが実はbarbarに依存していても、foofooの除外指定からたどられて除外対象に含まれるのでbarbarも除外できます。
でも、これもまだ半端な実装になっていて、Warに含めたいbazbazがbarbarに依存していると、foofooの推移的除外関係から単純にbarbarを除外しようとするので、Warの中にbarbarが含まれず、bazbazがまともに動かなくなってしまいます。もうちょっとその辺まじめに実装すればよいかもしれませんが、特定プロジェクトの都合で確実に除外指定したいブツが分かってるなら、案2あたりで止めておく方がよさげ。
案4 providedRuntimeを使う方法 (ボツ)
Warプラグインで用意されているprovidedRutimeを使えば、もしかしてこれだけで良かったりする?
War プラグインは providedCompile と providedRuntime の2つの依存構成を追加します。 これらの構成は WAR アーカイブには追加されないという点を除けば、それぞれ compile、runtime と同じスコープを持ちます。 provided 構成が推移的に機能することは特筆すべき点です。 http://gradle.monochromeroad.com/docs/userguide/war_plugin.html
configurations { // まずはwarで不要なものをruntimeから除外してしまう。 runtime.exclude module: 'hogehoge' runtime.exclude module: 'foofoo' } dependencies { // warには不要であるがruntime/testRuntimeで必要なJarをあらためてprovidedRuntimeで宣言しなおす。 providedRuntime 'hoge:hogehoge:1.0' providedRuntime 'foo:foofoo:1.2' }
ruimoさんのユースケースだとこれで良いような気がしてます(動作確認してない)。
案5 providedRuntimeを使う方法(DRY版) (ボツ)
だいぶシンプルになりましたが、除外設定とprovidedRuntime指定の2箇所でJarの情報が分散してるのがDRYではありませんね。でも、そこは心配ありません。Groovyなのでこんな書き方ができます。
// こうして一元管理もできる。そう、Groovyならね。 def excludesFromWar = [ [group: 'hoge', module: 'hogehoge', version: '1.0'], [group: 'foo', module: 'foofoo', version: '1.2'], ] configurations { // まずはwarで不要なものをruntimeから除外してしまう。 excludesFromWar.each { runtime.exclude it } } dependencies { // warには不要であるがruntime/testRuntimeで必要なJarをあらためてprovidedRuntimeで宣言しなおす。 excludesFromWar.each { providedRuntime "${it.group}:${it.module}:${it.version}" } }
案6 直接testRuntime/testCompileに再設定する方法 (ボツ) [4/17 21:00頃追記]
providedRuntimeはテスト実行時(testRuntime)には入ってこないようです。 と、よく考えたらテストで使えれば良いのだから直接testRuntime、コンパイル時も必要ならtestCompileに設定すればいいじゃないか、と気がつきました。なお、testCompileにいれておけばtestRuntimeにも含まれます。
ということで、たぶんこれでFAな気がするんですがどうでしょうかね。
// こうして一元管理もできる。そう、Groovyならね。 def excludesFromWar = [ [group: 'org.codehaus.groovy', module: 'groovy-all', version: '2.1.6'], [group: 'org.spockframework', module: 'spock-core', version: '0.7-groovy-2.0'], ] configurations { // まずはwarで不要なものをruntimeから除外してしまう。 excludesFromWar.each { runtime.exclude it } } dependencies { compile project(':subproject-a') // warには不要であるがtestCompileで必要なJarをあらためて追加する。 excludesFromWar.each { testCompile "${it.group}:${it.module}:${it.version}" } }
できあがるWarファイルの中身: (groovy-allもspock-coreも含まれていない)
Length Date Time Name -------- ---- ---- ---- 0 04-17-14 21:36 META-INF/ 25 04-17-14 21:36 META-INF/MANIFEST.MF 0 04-17-14 21:36 WEB-INF/ 0 04-17-14 21:36 WEB-INF/lib/ 2329 04-17-14 21:35 WEB-INF/lib/subproject-a.jar -------- ------- 2354 5 files
Gradle上の依存関係:
$ gradle dep :dependencies ------------------------------------------------------------ Root project ------------------------------------------------------------ archives - Configuration for archive artifacts. No dependencies compile - Compile classpath for source set 'main'. \--- project :subproject-a \--- org.codehaus.groovy:groovy-all:2.1.6 default - Configuration for default artifacts. \--- project :subproject-a groovy - The Groovy libraries to be used for this Groovy project. (Deprecated) No dependencies providedCompile - Additional compile classpath for libraries that should not be part of the WAR archive. No dependencies providedRuntime - Additional runtime classpath for libraries that should not be part of the WAR archive. No dependencies runtime - Runtime classpath for source set 'main'. \--- project :subproject-a testCompile - Compile classpath for source set 'test'. +--- project :subproject-a | \--- org.codehaus.groovy:groovy-all:2.1.6 +--- org.codehaus.groovy:groovy-all:2.1.6 \--- org.spockframework:spock-core:0.7-groovy-2.0 +--- junit:junit-dep:4.10 | \--- org.hamcrest:hamcrest-core:1.1 -> 1.3 +--- org.codehaus.groovy:groovy-all:2.0.5 -> 2.1.6 \--- org.hamcrest:hamcrest-core:1.3 testRuntime - Runtime classpath for source set 'test'. \--- project :subproject-a
案7 直接testRuntime/testCompileに再設定する方法(正常動作版) [4/18 16:30 追記]
案6は実は駄目ですね。うまく動いてると勘違いしたのはサンプルコードがまずかったです。依存先のsrc/mainのクラスとして何かに外部モジュールBに更に依存していて、そのBに対する制御をメイン側のPJでやるというサンプルじゃないと駄目なのでした。
何が問題かというと、上の出力を見てわかるようにtestRuntimeに追加したはずの依存モジュールが入っていない(testRuntimeはtestCompileを引き継ぐので本来はtestCompileと同じだけの品揃えがあるべき)。
実は、configurationsでごっそりruntimeからexcludeしてしまうと、メインPJのruntime系の依存性もすべてexcludeされてしまうのでした...。
runtime
から除外してtestRuntime
に追加してるのに、excludeの評価が「runtime系全体」にかかるのはおそらく内部実装の都合上な印象を受けます。が、そうなっているものは仕方がない。
依存先モジュール/プロジェクトごとに個別に推移的依存性をexcludeすればOKなので、こうやれば意図通りに動きました。
// こうして一元管理もできる。そう、Groovyならね。 def shouldExcludesFromWar = [ [group: 'org.apache.commons', module: 'commons-lang3', version: '3.3.1'], ] def excludesFromWar = { // configurationsの代わりに個別にexcludeすれば、 // このプロジェクト自体のtestCompile/testRuntimeへの追加は意図通りに動作する。 shouldExcludesFromWar.each { exclude module: it.module } } dependencies { compile project(':subproject-a'), excludesFromWar compile 'org.codehaus.groovy:groovy-all:2.1.6' testCompile 'org.spockframework:spock-core:0.7-groovy-2.0' // warには不要であるがtestCompile/testRuntimeで必要なJarをあらためて追加する。 shouldExcludesFromWar.each { // test配下の実行時だけ必要な場合 testRuntime "${it.group}:${it.module}:${it.version}" // test配下のコンパイル時も必要な場合 //testCompile "${it.group}:${it.module}:${it.version}" } }
案8 直接testRuntime/testCompileに再設定する方法(超シンプル版) [4/18 16:30 追記]
http://stackoverflow.com/a/23131137/1257166 のOpal氏の2案目をみて気づきましたが、依存モジュールごとの推移的依存先について選択的ではなくごっそりすべてexcludeして良いなら、これだけでOKです。
dependencies { compile project(':subproject-a'), { transitive = false } compile 'org.codehaus.groovy:groovy-all:2.1.6' testCompile 'org.spockframework:spock-core:0.7-groovy-2.0' // warには不要であるがtestCompile/testRuntimeで必要なJarをあらためて追加する。 testRuntime 'org.apache.commons:commons-lang3:3.3.1' }
ぐるっと一周してすごくシンプルになりましたね。
参考コード
実際にテストまで動くサンプルは https://github.com/nobeans/gradle-war-excludes-jar-sample/ においてあります。
Groovy2.3.0-beta-1と噂のTraitを試してみた
Groovy2.3.0-beta-1がリリースされましたね。
目玉としてはやはりtraitでしょう。
今までのMetaClassによるメソッドの動的探索パスをいじる系の機能で似たようなことはできましたが、何かと限界(複数スレッドにおける動作の保証とかかなり泥臭い感じだったり、一度動的メソッドを追加すると取り除くのが大変だったり)がありましたが、それらを超えるために静的ソリューションとしてtraitが実装されました*1。
ドキュメントがすごくわかりやすくて、サンプルコードを上から順に読むだけで少なくともどのように使えるかは難なく理解できるでしょう。
最初の方を読んでるとJava8のインタフェース+デフォルトメソッドと同じかな?と誤解しますが、Groovyのtraitはステートフルなのです。つまりプロパティを定義して状態が持てる。もう完全に普通の多重継承です。とうぜんダイアモンド継承などのコンフリクト問題はありますが、ドキュメントにあるとおり、「自前実装最強」「それ以外は登場順の後勝ち」的なルールで明確に決定されますし、特定の実装を優先したい場合も簡単です。まあ、衝突が気になるような名前は避ければいいわけですし。詳しくはドキュメントのこの辺を読みましょう。
GroovyConsoleで軽く試してみましたが、非常にいい感じです。
いわゆるアカデミックな本家の"trait"や、"Scalaのtrait"とどの辺が違うのかあたりは気になるところです。
2014/4/9追記
ASTはこんな感じ。細かい点はおいといてなるほどねーな感じです。
@groovy.transform.Trait abstract interface public class Flyable extends java.lang.Object { @org.codehaus.groovy.transform.trait.Traits$Implemented abstract public java.lang.String fly() { } @org.codehaus.groovy.transform.trait.Traits$Implemented abstract public java.lang.String getName() { } @org.codehaus.groovy.transform.trait.Traits$Implemented abstract public void setName(java.lang.String value) { } } public class Bird implements Flyable, Flyable$Trait$FieldHelper, groovy.lang.GroovyObject extends java.lang.Object { private java.lang.String Flyable__name private static org.codehaus.groovy.reflection.ClassInfo $staticClassInfo public static transient boolean __$stMC private transient groovy.lang.MetaClass metaClass public static long __timeStamp public static long __timeStamp__239_neverHappen1397005006100 public Bird() { metaClass = /*BytecodeExpression*/ Flyable$Trait$Helper.$init$(this) } public void setName(java.lang.String arg1) { Flyable$Trait$Helper.setName(this, arg1) } public java.lang.String fly() { return Flyable$Trait$Helper.fly(this) } public java.lang.String getName() { return Flyable$Trait$Helper.getName(this) } static { __timeStamp__239_neverHappen1397005006100 = 0 __timeStamp = 1397005006100 Flyable$Trait$Helper.$static$init$(this) } @groovy.transform.CompileStatic public java.lang.String Flyable__name$get() { return Flyable__name } @groovy.transform.CompileStatic public void Flyable__name$set(java.lang.String val) { Flyable__name = val } public java.lang.Object this$dist$invoke$1(java.lang.String name, java.lang.Object args) { return this."$name"(* args ) } public void this$dist$set$1(java.lang.String name, java.lang.Object value) { this ."$name" = value } public java.lang.Object this$dist$get$1(java.lang.String name) { return this ."$name" } //.... } public class UFO implements groovy.lang.GroovyObject extends java.lang.Object { private static org.codehaus.groovy.reflection.ClassInfo $staticClassInfo public static transient boolean __$stMC private transient groovy.lang.MetaClass metaClass public static long __timeStamp public static long __timeStamp__239_neverHappen1397005006102 public UFO() { metaClass = /*BytecodeExpression*/ } public java.lang.Object this$dist$invoke$1(java.lang.String name, java.lang.Object args) { return this."$name"(* args ) } public void this$dist$set$1(java.lang.String name, java.lang.Object value) { this ."$name" = value } public java.lang.Object this$dist$get$1(java.lang.String name) { return this ."$name" } //... } abstract public static class Flyable$Trait$Helper implements groovy.lang.GroovyObject extends java.lang.Object { private static org.codehaus.groovy.reflection.ClassInfo $staticClassInfo public static transient boolean __$stMC private transient groovy.lang.MetaClass metaClass public Flyable$Trait$Helper() { metaClass = /*BytecodeExpression*/ } public static void $init$(Flyable $self) { (( $self ) as Flyable$Trait$FieldHelper).Flyable__name$set('trait') } public static void $static$init$(java.lang.Class<Flyable> $static$self) { } public static java.lang.String fly(Flyable $self) { return "I'm $name and flying!" } public static java.lang.String getName(Flyable $self) { return (( $self ) as Flyable$Trait$FieldHelper).Flyable__name$get() } public static void setName(Flyable $self, java.lang.String value) { (( $self ) as Flyable$Trait$FieldHelper).Flyable__name$set(value) } public java.lang.Object this$dist$invoke$1(java.lang.String name, java.lang.Object args) { return this."$name"(* args ) } public void this$dist$set$1(java.lang.String name, java.lang.Object value) { this ."$name" = value } public java.lang.Object this$dist$get$1(java.lang.String name) { return this ."$name" } public java.lang.Object methodMissing(java.lang.String name, java.lang.Object args) { return Flyable."$name"(* args ) } public void propertyMissing(java.lang.String name, java.lang.Object val) { Flyable."$name" = val } public java.lang.Object propertyMissing(java.lang.String name) { return Flyable."$name" } //... } abstract interface public static class Flyable$Trait$FieldHelper extends java.lang.Object { final public static java.lang.String Flyable__name abstract public void Flyable__name$set(java.lang.String val) { } abstract public java.lang.String Flyable__name$get() { } }
*1:実際の経緯がこのとおりかは自信なし
Grails/Gradleの「さっきのテストレポート」をAlfredに表示してもらう
はじめに
この記事は、G*(Groovy, Grails ..) Advent Calendar 2013の15日目として書かれたものです。 14日目は @grimroseさんでした。
最近某自作GrailsアプリにてGrails上でVert.xを利用してWebSocketのプッシュを実装したりしてたので、今回はその話を書こうかなと思っていたのですがやっぱりやめて、以前からそのうち書こうと思って寝かせていた開発Tipsネタの方がちょうど良い感じに熟してきたのでそちらを紹介することにします。
Grailsによる通知からのテストレポート表示(前回までのあらすじ)
以前、Grailsのアプリ起動完了やテストの結果をGrowlで通知するEvents.groovyが便利な件という記事を書きました。
Grailsのビルドイベントを、Grailsのイベント機構を利用して拾って、Growlで通知するというものです。 通知するだけではなくて、その通知ペインをクリックすると、そのテストレポートがブラウザで表示されるので、ユニットテストが結構捗ります。
また、Mac OS X LionでOS標準の通知機構ができたので、それを利用した方法もこちらで紹介されています。自分も今はこれをベースに使っています。
terminal-notifier
というMac OS標準のNotificationに通知するためのコマンドがあるので、growlnotify
コマンドの代わりにそれを使う感じです。
open
オプションを使うことで通知ペインのクリック時にURLを表示させることができます。
open
オプションにテストレポートHTMLファイルのURLfile://.../index.html
を指定することで、クリック時のブラウザ連携を実現することができるわけです。
他にもexecute
オプションで任意のコマンドを実行させたりもできます。
詳しくは↓あたりをどうぞ。
- ターミナルコマンドからMountain Lionの通知センターへ通知を表示させる『terminal-notifier』
Gradleによる通知からのテストレポート表示
さて、GradleもGrailsとは違う方式ながら、イベントリスナ機構を持っています。
- Chapter 42. 通知プラグイン
- Chapter 43. ビルド通知プラグイン
- Gradle Goodness: Getting Announcements from Gradle Build
Gradle標準(?)の実験的プラグインで通知アプリへの連携方法が提供されていて、それを使ってビルドイベントの前後で通知する方法がMr.Hakiのブログでまとめられています。
自分の場合は、取り回し重視で以下のシンプルなコードを改造して、直接terminal-notifier
コマンドを実行するようにして使っています。
- Gradle Growl Notifications
サンプルコードは最後にまとめて紹介するとして、先を急ぎます。
Alfredとは?
ここで突然ですが、AlfredというMac用のコマンドライン型ランチャがあります。ホットキーで表示される入力ペインにアプリケーション名を入力すると、インクリメンタルサーチで候補が絞り込まれていき、対象が見つかったところで選択してENTERで実行します。QuickSilverも有名ですが、自分はAlfredを愛用しています。
単なるアプリの起動だけではなく、Spotlightのようにファイル内容の検索もできますし、「カスタム検索(Custom Searches)」を独自定義すればシームレスに色々な情報にたどり着く基盤を築くことができます。
さっきのテストレポートをみたいんだけど...
さて、さきほど紹介したGrails/Gradleの通知→レポート表示の場合、通知が表示されている間にクリックしないとレポートにたどり着けません。 履歴を表示してクリックすることもできますが一手間増えますし、だからといって、Stickyに通知が残るようにすると逆にウザいことが多いのでいやな感じです。 また、通知からしばらくたって、コードを修正している最中にテストレポートを再確認したくなることもあります。 「さっきのテストレポートがみたいなぁ」と思うわけです。
みなさんは、IDEでコードを書いていて「あのソースファイルを修正しよう」と思ったときどうやってそのファイルを開いていますか? まさかエクスプローラ風のビューアからディレクトリ階層をたどってファイルを見つけてダブルクリックしてないですよね? EclipseでもIntelliJでも、リソース名/型名によるインクリメンタルサーチでファイルを開く機能があります。 「あのソースファイル」と思った時点でその名前が念頭にあるんですから、それを使って対象を開くのがもっとも頭にも手にもやさしい自然な方法な訳です。 VimmerならUniteプラグインの出番ですね。
さて、テストレポートの表示も同じです。 頭では「さっきのテストレポートがみたいなぁ」と思っています。 その欲求をできるだけストレートにアクションに結びつけるにはどうしたらよいか。 ここでAlfred(または類似のランチャ)が役立つわけです。
- 「あのアプリが起動したいなぁ」→ Alfredでアプリ名を指定して起動する
- 「さっきのテストレポートがみたいなぁ」→ Alfredで開けないかな?
というごく自然な流れですね。
Alfredで「さっきのテストレポート」を表示するには
Alfredのカスタム検索を使って、これを実現しましょう。
しかし、彼はGrails/Gradleの文脈なんて知らないので、「さっきのテストレポート」をAlfredに見つけさせるのは難しいです。 そこで、通知と同じようにビルドイベントのフックで仕込みをしてしまうことにしましょう。
- ビルド実行後のフックで「テストレポートのファイルURI」を固定パスの隠しファイルに書き込んでおく
- 1.で書き込んだ固定パスのファイルを開くようなカスタム検索をAlfredに定義する
これだけです。単純な戦略ですがこれで十分です。
あとは、Grailsの場合なら「さっき起動したアプリのホーム画面」もさくっと表示できると捗りますね。
というわけで、早速やってみましょう。
実際のコードサンプル
Grails
以下のコードを~/.grails/scripts/_Events.groovy
として保存します。
Gradle
以下のコードを~/.gradle/init.gradle
として保存します。
Alfred
以下の文字列をコピーして、Alfredの入力欄に貼り付けるだけでカスタム検索が登録できます。
ユーザのホームディレクトリを使うので、以下の__YOUR_ACCOUNT__
の部分を自分のアカウント名に書き換えてから、Alfredの入力欄に貼り付けてください。
- AlfredからのGradleテストレポート表示
alfredapp://customsearch/Gradle%20Open%20Latest%20Test-Report/gradletestreport/ascii/url=file:///Users/__YOUR_ACCOUNT__/.gradle/latestTestReport.html
- AlfredからのGrailsテストレポート表示
alfredapp://customsearch/Grails%20Open%20Latest%20Test-Report/grailstestreport/ascii/url=file:///Users/__YOUR_ACCOUNT__/.grails/latestTestReport.html
- AlfredからのGrailsローカル起動アプリのホーム画面表示
alfredapp://customsearch/Grails%20Open%20Latest%20Run-App/grailsrunapp/ascii/url=file:///Users/__YOUR_ACCOUNT__/.grails/latestRunApp.html
これでOKです。 あとは、↓の辺りからGrailsやGradleのアイコンを探して、"Drop icon"ゾーンにドラッグ&ドロップすれば、更に見た目がクールになります。
- Gradleのロゴアイコン
- Grailsのロゴアイコン
試してみる
まずはGradleプロジェクトをビルドしてテストも実行しましょう。 無事に以下のような通知が表示されたでしょうか?
表示されていれば裏できっと「さっきのテストレポート」情報も記録されているはず。 Alfredの入力ペインを表示させて「gradle」と入力してみると...
「Gradle Open Latest Test-Report」を選択すると、期待通りブラウザ上にテストレポートが表示されましたね。
Grailsプロジェクトがある人はそっちも是非ためしてみてください。
おわりに
他のOSやコマンドラインランチャでもこの方法が使えます。 Mac/Alfred使い以外の人も是非お試しを。
さて、明日の16日目は @nobusueさんです!
GroovyServとGradle Daemonとの違い
10/23 9:50 訂正&追記: GradleがデフォルトでDaemon起動すると書いてた件 (thanks to きょんくん)
元記事: http://dev.classmethod.jp/server-side/gradle/gradle-first-contact/
経緯的な
id:daisuke-mが昼過ぎに技術を語るいつものシリーズにGradleを追加した旨のツイートをしてました。 生粋のGradlerとしては喜び勇んでチェックしてみたんですが、「Gradleのインストール」であるべきところを「Groovyのインストール」と書いてたので、親切心でコメントしたわけです。
@daisuke_m "Groovyのインストール"->"Gradleの〜"
— Yasuharu Nakano (@nobeans) 2013, 10月 22
そこまでは良かったのですが、ちょっと調子に乗ってしまいまして、
@daisuke_m つ "ついでにせっかくなのでGroovyもインストールしておきましょう。`gvm install groovy`"
— Yasuharu Nakano (@nobeans) 2013, 10月 22
@daisuke_m 要るかどうかなんて関係ないのです。そこにgvmがあるから入れるのです。groovyを入れたらgroovyservも一緒に入れるのです。
— Yasuharu Nakano (@nobeans) 2013, 10月 22
と書いたところ、記事に晒し上げされてしまいました。 なんということでしょう。
そのままフェードアウトしてくれれば終わればまだ良かったのですが、自称怖くないモンスターの人に見付かって問い詰められているid:daisuke-mが忍びなさMAXでつい反応してしまったら、気がついたら私がGroovyServとGradle Daemonの違いについてなんか書かなければならない流れになっておりました。
ああ、恐ろしきかな因果の流れ。
GroovyServとは
Groovyコマンドを実行可能なJVMプロセスをサーバとして常駐させておいて、ネイティブコマンドを使ってTCP経由でコマンド引数をサーバに送り込み、評価した結果を再びTCP経由でもらって出力する、という拙作のツールです。
Groovyに限らずJVM系言語でスクリプトを書いた場合、JVMプロセスを起動して色々なクラスローディングする準備処理が遅くて、1秒ぐらいの待ち時間が発生します。
$ time groovy -e "println 'Hello'" Hello real 0m1.347s user 0m1.481s sys 0m0.168s
GroovyServをインストールして、クライアントコマンド経由で実行すると
$ time groovyclient -e "println 'Hello'" Hello real 0m0.057s user 0m0.002s sys 0m0.005s
すごく...はやいです...。
といっても、初回はサーバプロセスを起動するための待ち時間が数秒かかるんですけども。 一度起動したらそれ以降は上のような感じです。 はいはい、爆速爆速。
なお、mainクラスのエントリポイントを指定できればいいので、JythonとかClojureも同じように高速化できます。 Scalaのコンパイルも頑張れば早くなるようです。 Vim+QuickRunでgroovyConsole風にさくっと実行できるのにフル機能エディタ!みたいなこともできますし、SublimeText2からも似たようなことができます。
一応このネタで2013/5にGr8conf EU 2013というデンマーク コペンハーゲンのGroovy系カンファレンスで発表してきたりしました。
詳しくは、ちょっと雑多な感じですがユーザガイド(日本語)をどうぞ。
Gradle Daemonとは
Gradleも同様にJVM上で動作するツールなので、起動の待ち時間が大変遅いわけです。 毎回JVMプロセスを一から起動してると、さくさくビルドやテストを試せません。 GroovyServみたいな仕組みが欲しいですよね。
ということで、Gradle本体が同じような仕組みを用意したのがGradle Daemonです。 歴史的に言うと、GroovyServの方が若干早いはず。 パクられた!とは言いませんが、時期的なものを考えると多少は参考にされたんじゃないかと自意識過剰気味にアピールしておきます。どうかな。違うかも。
最近のGradleはデフォルトでDaemonを利用します。
GradleがデフォルトでDaemon起動すると書いたな、アレは噓だ。
--daemon
オプションが必要です。
以下はしれっと書き換えてあります。
すいません。
--daemon
オプションはまだデーモンがいなければ起動して使い、すでに居ればそれを使うというオプションです。
というわけで、適当なGradleプロジェクトで、2回gradle --daemon tasks
を実行してみると、2回目が劇的に早くなってるんじゃないかと思います。
たとえば、自宅のかなり古いMacBook mid 2008で試してみると、
$ gradle --daemon tasks ... Total time: 12.02 secs $ gradle --daemon tasks ... Total time: 2.52 secs
とこんな感じです。すごいですね。 jpsしてみると
$ jps 9376 Jps 4136 GradleDaemon
とデーモンプロセスが常駐してるのが分かるはず。
あと--daemon
オプションが付けられていようが、--no-daemon
というオプションを追加すれば、デーモンは無視して自前プロセスで実行できたりします。
デーモンプロセスありきでalias設定してあっても、一時的にはずせるわけですね。
また、デーモンプロセスが要らなくなったら、普通にkill
コマンドでもいいですが--stop
オプションを使えばgradleコマンド経由でコントロールできます。
個人的にはGradle Wrapperを優先しつつDaemonは必須で使いたいので、以下のようなaliasをシェルで設定して使っています。
gradle='if [ -x ./gradlew ]; then GRADLE_BIN=./gradlew; else GRADLE_BIN=\gradle; fi; $GRADLE_BIN --daemon'
こちらも詳しくはユーザガイド(日本語)をどうぞ。
ちなみにGroovyServもビルドにGradleを使っていて、Daemon様におかれましては大変お世話になっております。
GroovyServとGradle Daemonの違い
Gradle Daemonは、Gradleタスクの起動処理をショートカットする GroovyServは、Groovyスクリプトの起動処理をショートカットする
Gradle Daemonは、gradleコマンドのオプションですべて操作する GroovyServは、groovyclientコマンドのオプションで操作するほかに、サーバプロセス起動用のgroovyserverコマンドがある
Gradle Daemonは、ユーザの気付かないうちにデーモンプロセスが常駐してる GroovyServは、サーバプロセスの起動時にコンソール出力してアピールする
気の利いた大喜利風にオチがつけばいいのですが、実力的に無理でした。ごめんなさい。
結局、コンセプト的なものは一緒ですけど、用途が違うので細かいところで色々と違いますね。 あんまりまじめに比べるモノじゃないと思われます。 むしろGroovyServと比べるべきはNailgunとかDripあたり。
おまけ: NailgunとGroovyServの違い
Nailgunはよく知られたJVMプロセス常駐系アーキテクチャのツールです。 歴史的にはGroovyServよりも古いです。 古すぎて2005年以降はまったく更新されていません。 と思ったら、2012/11月にv0.9.1がリリースされててちょっと驚きました。
NailgunはピュアJava実装なのでビルトイン目的によく使われます。 というかAPI的にもそれっぽい感じ。 JRubyもNailgunを使ってますね。
GroovyServはGroovyで実装してるのでビルトイン目的には向いてません。
GroovyServは今更ピュアJava路線に変更するのは不可能ではないにしろ面倒つらい感じです。
ビルトインの代わりに、単にクライアントの引数でmainクラスのエントリポイントを指定するだけで色々なJVM言語の起動時間短縮を計れます。物は言いようです。
機能的な違いでいえば、下のような感じです。
- GroovyServのみサポート
- 両方ともサポート
- System#exit()が実行されてもJVMは落ちない
ほらこうやってみるとGroovyServ結構がんばってるでしょ? Nailgun側のメリットはビルトイン以外に思いつきませんが、他に何かあるかもしれません。 フェアじゃないのは単に知識不足です。すいません。
おまけ: DripとGroovyServの違い
Dripはシンプルで割り切ったアーキテクチャで中々興味深いところはありますが、アーキテクチャやサポート機能が違いすぎてGroovyServとはあまり競合しないふいんき。
簡単に言うと、
http://b.hatena.ne.jp/nobeans/20130904#bookmark-109556574
GroovyServのようなJVMプロセス常駐系/興味深いアプローチ/単一プロセスを使い回すとdirtyになっていくのでJVM引数,クラスパス,起動クラスのハッシュごとに異なるJVMデーモンを使う/コードベースが超小さい/bash限定は楽そう
という感じです。
Dripは、bashを前提にしているので色々実装がシンプルです。うらやましいです。 GroovyServは、Mac/Linux/Windowsで動作します。 現状ではWindowsサポートが若干手薄な気もしますが、次期バージョンではできるだけ環境ごとの機能差がないように頑張っています。
Dripは、カレントディレクトリ毎に別プロセスを起動します。 よって、複数プロセスが常駐します(一定時間でタイムアウトして消える)。 既存JVMプロセスに対するネイティブレベルでのCWD制御とかを不要にしたりしてます。割り切り方が素敵です。 GroovyServは、ポート毎に1つのサーバプロセスを用意しています。 普通はデフォルトポートで十分なので、サーバプロセスは1つだと思えばOKです。ある意味シンプル。 CWDなどは毎回力業で反映しています。
こんな感じでしょうか。
測定マシンについて一言
なお、上記記事中の性能的なものはすべて自宅の超古いMacbook 2008 Midで測定してます。 そうとう遅いです。 苦痛です。 今晩のAppleのイベントで新MBPでたら速攻で買い換えたい所存です。
まとめ
というわけで、GVMを使ってGradleをインストールしたら、ついでに
$ gvm install groovy $ gvm install groovyserv
すると良いと思います。 更に手が滑って
$ gvm install gaiden
とGaidenのインストールまで進んだあなたはファッションリーダとしての自覚と風格が感じられて頼もしい限りです。
標準バンドルのVisualVMのウィンドウが空っぽになってしまったらキャッシュを削除すべし
経緯的な
9月にTwitterにぼやいた件のその後。
7u40のretina対応のおかげでjvisualvmもくっきりきれいになってるなー
— Yasuharu Nakano (@nobeans) 2013, 9月 12
と思って、7u25と比較したりしてたら、メインウィンドウに何も表示されなくなってしまった...
— Yasuharu Nakano (@nobeans) 2013, 9月 12
7u40のjvisualvmが使い物にならなくなってしまった。再起動したら治るかな...
— Yasuharu Nakano (@nobeans) 2013, 9月 12
@cero_t 最初は普通に起動したんですけど、retinaとそうじゃないのを比較するために7u25に切り替えてjvisalvmを起動して、その後7u40のGUIをいじったら固まってしまい、その後は起動し直してもウィンドウ前面がグレイ状態(ビューがなし)という状況です
— Yasuharu Nakano (@nobeans) 2013, 9月 12
起動中にjvmを切り替えたせいかもしれないけど、アプリだけ再起動してもずっと同じ状態。7u25に切り替えて起動するとそっちは普通に使える。謎。OS再起動はまだしてない。
— Yasuharu Nakano (@nobeans) 2013, 9月 12
事象
事象としては、jvisualvmを起動するとこんな画面になって何もできないというもの。
Netbeansプラットフォームアプリケーションの素のウィンドウだけ表示してみました的な感じで、メニューバーも本来のVisualVMとは違ってまっさらな感じ。 かろうじてVisualVM臭がするのはAboutダイアログぐらいです。 ホントになにもできません。
http://visualvm.java.net/download.html からダウンロードしたApp版のVisualVMは普通に動きます。
ちなみに、Macです。他のOSで発生するかは知りません。
解決策
VisualVMのキャッシュが壊れていたようです。
/Users/me/Library/Caches/VisualVM/7u14
を削除してから起動したら、正常に動作しました。
ちなみに、7u40や7u45にバンドルされてるjvisualvmを使っても、ディレクトリ名は7u14
でした。
なんででしょうね。
本家Grailsユーザガイドを開くと自動的に日本語翻訳にリダイレクトするChrome拡張
Grailsユーザガイド日本語翻訳版とブックマークレットの紹介 - 豆無日記で、紹介したブックマークレットを毎回実行するのがダルすぎるので、Chrome拡張にしてみました。
以下のURLからChrome拡張をインストールしてください。
セキュリティの都合から野良サイトからの直接インストールはできないようなので*1、いったんダウンロードしてから拡張の管理画面にドラッグ&ドロップするとよいでしょう。
これで、http://grails.org/doc/latest/*
なサイトを開くと自動的にhttp://grails.jp/doc/latest/*
にリダイレクトされるようになります。
これでGoogle検索結果からのリンクでもさくっと日本語版が表示できますね。
コードという程のモノではない何かはGitHubにサラしてあります。
Grailsユーザガイド日本語翻訳版とブックマークレットの紹介
有志によるGrailsユーザガイドの日本語翻訳作業がずっと行われていたのですが、実は8月の頭にちょうど一区切り良い感じになりました。 そろそろリリースされそうなGrails 2.3向けに追記された部分などまだちょっと英語部分も残っていますが、基本機能についてはほぼすべて翻訳完了な感じです。
残念なことに検索しても出てくるのは本家の英語版だけだったりすることが多いです。
URLのドメイン部分がgrails.org
→grails.jp
と違うだけなので、ブラウザのアドレスバーを手作業で書き換えて日本語版を表示させたりして過ごしていますが、さすがに面倒になってきたのでブックマークレットを書いてみました。
- タイトル例
- Grailsユーザガイドを日本語化する
- URL
javascript:location.href=location.href.replace(/grails.org/,'grails.jp')
そのまんまですが、わりと便利な感じなのでよかったらどうぞ。