GeekFactory

int128.hatenablog.com

trait内のクロージャからプライベートメソッドが見えない

Groovy 2.3から導入されたtraitを使っていて、妙な事象に遭遇したのでまとめてみます。具体的には、traitの中でクロージャを使う場合に、クロージャからプライベートメソッドが見えない仕様があるようです。

例えば、下記のような trait T があるとします。

trait T {
  private a(x) { "--$x--" }

  // プライベートメソッドを直接呼んでいる
  def b(x)  { a(x) }

  // クロージャの中からプライベートメソッドを呼んでいる
  def c(xs) { xs.collect { x -> a(x) } }
}

下記のように、この trait を適用した class P および class Q を定義します。

// implementsでtraitを適用する
class P implements T {}

// withTraitsでtraitを適用する
class Q {}

P, Q に対してそれぞれメソッド b, c を呼び出します。メソッド b, c が期待通りに動くか検証してみます。

def p = new P()
println p.b('hoge')
println p.c(['hoge'])
def q = new Q().withTraits T
println q.b('hoge')
println q.c(['hoge'])

Groovy 2.3.6の仕様

実行結果は下記のようになりました。withTraits で適用すると、trait 内のメソッドクロージャを使うと、プライベートメソッドが見えないことが分かります。

--hoge--
[--hoge--]
--hoge--
groovy.lang.MissingMethodException: No signature of method: Q1_groovyProxy.a() is applicable for argument types: (java.lang.String) values: [hoge]
Possible solutions: b(java.lang.Object), c(java.lang.Object), any(), is(java.lang.Object), any(groovy.lang.Closure), wait()

Groovy 2.4.0の仕様

実行結果は下記のようになりました。いずれの適用方法でも、trait 内のクロージャからプライベートメソッドが見えないことが分かります。見えない仕様に統一されたのかもしれません。

--hoge--
groovy.lang.MissingMethodException: No signature of method: P.a() is applicable for argument types: (java.lang.String) values: [hoge]
Possible solutions: b(java.lang.Object), c(java.lang.Object), any(), is(java.lang.Object), any(groovy.lang.Closure), wait()
--hoge--
Caught: groovy.lang.MissingMethodException: No signature of method: Q1_groovyProxy.a() is applicable for argument types: (java.lang.String) values: [hoge]
Possible solutions: b(java.lang.Object), c(java.lang.Object), any(), is(java.lang.Object), any(groovy.lang.Closure), wait()

詳しい情報をお持ちでしたらぜひ教えてください。traitで拡張可能なDSLを作ろうとするとこの仕様はつらいですね。

Gradle SSH Plugin 1.0.2をリリースした

Gradle SSH Plugin 1.0.2をリリースしました.

SSH Pluginを使うと,下記のようにGradleからSSHでコマンドを実行したりファイルを送受信したりできます.

// build.gradle
plugins {
  id 'org.hidetake.ssh' version '1.0.2'
}

remotes {
  webServer {
    host = '192.168.1.101'
    user = 'jenkins'
    identity = file('id_rsa')
  }
}

task deploy << {
  ssh.run {
    session(remotes.webServer) {
      put 'example.war', '/webapps'
      execute 'sudo service tomcat restart'
    }
  }
}

詳しい使い方は https://gradle-ssh-plugin.github.io をご覧ください.

New Feature in 1.0.2

put コマンドでリモートのファイルに文字列を書き込めるようになりました.下記のようにファイルパス,文字列,バイト配列を指定できます.

// specify a file path, File object or Interable<File>
put file: 'local_file', into: '/remote/file'
put file: buildDir, into: '/remote/folder'
put files: files('local_file1', 'local_file2'), into: '/remote/folder'

// specify a string
put text: 'hello world', into: '/remote/script.sh'

// specify a byte array
put bytes: [0xff, 0xff] as byte[], into: '/remote/fixture.dat'

下記のように複数行の文字列も指定できます.

put text: '''#!/bin/sh
echo 'hello world'
echo 'hoge'
''', into: '/tmp/deploy.sh'

実は,この機能は2011年頃のプロトタイプでは実装済みでしたが,プロダクトではずっと放置していました.

Groovy SSH

Gradleではなく単体のコマンドラインツールDSLを使いたい場合はGroovy SSHが便利です.