Gradle:タスクをプログラム的に呼び出す方法は存在しない

これがどうしてもわからないし、世界的にも悩んでいる人が多いようだ。そして、公式の回答としては「その方法は提供していない」というものだ。そんなバカなと思うかもしれないが、どうやってもうまくいかない。

やりたいこと

例えばCopyタスクがあり、以下のようにタスクを作成するとする。

task copyit(type:Copy) {
  from 'test.txt'
  into 'folder'
}

これを単純に別のタスクから任意のタイミングで呼び出したいだけなのである。

task other {
  // copyitを呼び出す
}

これができない。どうしてもできない。以下にする他はない。

task other(dependsOn: 'copyit')

なぜこれがやりたいか

通常はもちろんdependsOnを使えば良いことなのであるが、特にこれをやりたい理由というものがある。それは、「タスクのパラメータについて別の書き方をしたい」ということだ。

例えば、以下のようなタスクを作成する。

task sample(type: CopyEx) {
  fromInto 'test.txt', 'folder'
}

そして、実際の処理としてはCopyタスクに任せると言ったことである。単にこれだけだ。これさえもできない。こんな簡単なこともGraldeではできないのである。

こんなところにも一貫性がなく行き当たりばったりのGradleの設計のまずさが良く現れていると思う。

可能なケース

しかし、可能なケースも存在する。@TaskActionアノテーションによって実行メソッドが指定されているタスクの場合である。

単純なケース

まず単純な場合を考えてみる。

class SomeTask extends DefaultTask {
  String message;
  @TaskAction
  void something() {
    println 'message is ' + message
  }
}

task someTask(type: SomeTask) {
  message = 'hello'  
}

gradle someTaskとしてみると結果は以下である。

:someTask
message is hello

直接インスタンスは作成できない

ところが以下のようにすると

def t = new SomeTask()

以下のエラーになる

> Task of type 'SomeTask' has been instantiated directly which is not supported. Tasks can only be created using the DSL.

どういうわけか、SomeTaskはConcreteクラスでもないのにインスタンス生成ができないのだ。なぜなのかさっぱりわからない。

サブクラス化は?

では、以下のようにサブクラス化するとどうなるか?

class SomeTask extends DefaultTask {
  String message;
  @TaskAction
  void something() {
    println 'message is ' + message
  }
}

class OtherTask extends SomeTask {
  int value
  void something() {
    message = 'hello(' + value +')'
    super.something()
  }
}


task other(type: OtherTask) {
  value = 123
}

結果としては、

:other
message is hello(123)

TaskActionでの実行メソッド指定があるか?

みたところ@TaskActionで実行メソッドを指定しているケースでは、サブクラス化を行うことができ、サブクラスの中で簡便なパラメータ指定ができるようだ。

@TaskActionでの実行メソッド指定があるかどうかは、TaskActionアノテーションがされているメソッドの有無で確認できる。例えば以下である。

SomeTask.getMethods().each {
  if (it.getAnnotation(TaskAction.class) != null) 
    println it
}

こうなる。

public void SomeTask.something()

ProGuardTaskに適用した例

これをProGuardTaskに適用してみた。

common.gradle

buildscript {
  repositories.mavenCentral()
  dependencies {
    classpath 'net.sf.proguard:proguard-gradle:5.3.2'
  }
} 
ext.ProGuardTask = proguard.gradle.ProGuardTask

ext.ProGuardTaskEx = ProGuardTaskEx
class ProGuardTaskEx extends proguard.gradle.ProGuardTask {

  /** クラス名称とmainメソッドをkeepするアノテーション */
  void keepAsJavaMainAnno(String...args) {
    args.each {
      keep '@' + it + ' class * {\
        public static void main(String[]);\
      }'
    }
  }

  /** 指定フィールド名称をkeepするアノテーション */
  void keepFieldNameAnno(String...args) {
    args.each {
      keepclassmembers 'class * {\
        @' + it + '\
        <fields>;\
      }'
    }
  }

  /** 指定メソッド名称をkeepするアノテーション */
  void keepMethodNameAnno(String...args) {
    args.each {
      keepclassmembers 'class * {\
        @' + it + '\
        <methods>;\
      }'
    }
  }

  /** ライブラリjarリスト */
  void libraryJars(Object...args) {
    args.each {
      libraryjars it
    }
  }

  /** 警告しない */
  void dontWarn(String...args) {  
    args.each {
      dontwarn it
    }
  }  

  /** クラス名称のみを維持するクラス用アノテーション */
  void keepClassNameAnno(String...args) {
    args.each {
      keep '@' + it + ' class * {\
      }'
    }
  }

  /** 指定クラスをextendsするクラスのクラス名称を維持する */
  void keepClassNameExtends(String...args) {
    args.each {
      keep 'class * extends ' + it
    }
  }

  /** 指定パッケージ以下すべてをすべて維持する */
  void keepAllUnder(String...args) {
    args.each {
      keep 'class ' + it + '.** {\
        *;\
      }'
    }
  }

  /**  Enumの必須フィールド・メソッドを保持 */
  boolean keepEnum = true;

  /** 良く使用する標準的オプション */
  boolean standardOptions = true    

  @TaskAction
  public void proguard() {

    if (keepEnum) {
      keepclassmembers 'class * extends java.lang.Enum {\
        public static **[] values();\
        public static ** valueOf(java.lang.String);\
        public String desc;\
      }'
    }
    if (standardOptions) {
      dontnote
      dontshrink
      dontoptimize      
      useuniqueclassmembernames
      renamesourcefileattribute 'SourceFile'
      keepattributes 'Exceptions,InnerClasses,Signature,Deprecated,SourceFile,\
        LineNumberTable,LocalVariable*Table,*Annotation*,Synthetic,EnclosingMethod'        
    }
    super.proguard();
  }
}

build.gradle

apply from: 'common.gradle'

def JRE8_HOME='c:/...'

task guard(type: ProGuardTaskEx) {  

  libraryJars "${JRE8_HOME}/lib/rt.jar", 
    "${JRE8_HOME}/lib/ext/jfxrt.jar", 
    configurations.compile



  keepClassNameAnno  'foobar.common.pgkeep.DontMove'

  keepFieldNameAnno 'foobar.common.orm.FieldMap', 
    'foobar.common.pgkeep.KeepField', 
    'foobar.recLucene.RlFieldAttr'

  keepMethodNameAnno 'foobar.common.pgkeep.KeepMethod', 
    'foobar.digester.CDMethod'

  keepClassNameExtends 'org.apache.log4j.Appender'
  keepAsJavaMainAnno 'foobar.startup.KeepMain'  
}