使用动态对象组配置gradle插件扩展的正确方法

Sho*_*orn 4 groovy gradle gradle-plugin

我正在尝试编写自己的gradle插件,它需要能够配置一组对象 - 这些对象中有多少以及它们被调用的内容取决于用户.

用于创建具有高级可定制性的自定义gradle插件的doco非常差.它提到project.container()了做这种事情的方法,但我无法弄清楚如何使它在我的用例中工作.

这是我的插件配置DSL的一个示例:

teregrin {
  terraformVersion = '0.6.6'

  root("dev"){
    accessKey = "flobble"
  }

  root("prd"){
  }
}
Run Code Online (Sandbox Code Playgroud)

这是我的插件扩展对象,允许我配置它:

class TeregrinPluginExtension {
  boolean debug = false
  boolean forceUnzip = false
  String terraformVersion = null

  Set<TeregrinRoot> roots = []

  def root(String name, Closure c){
    def newRoot = new TeregrinRoot(name)
    c.setDelegate(newRoot)
    c()
    roots << newRoot
  }

}
Run Code Online (Sandbox Code Playgroud)

这些扩展以标准方式连接在我的插件中:

 project.extensions.create("teregrin", TeregrinPluginExtension)
Run Code Online (Sandbox Code Playgroud)

这样做没问题,但这是一种非常丑陋的配置风格,并不是典型的gradle DSL风格.

如何将我的插件配置DSL更改为:

teregrin {
  terraformVersion = '0.6.6'

  roots {
    dev {
      accessKey = "flobble"
    }

    prd {
    }
  }
}
Run Code Online (Sandbox Code Playgroud)

Opa*_*pal 7

实现此类DSL的另一种方法是使用扩展容器:

apply plugin: SamplePlugin

whatever {
  whateverVersion = '0.6.6'
  conf {
    dev {}
    qa {}
    prod {
      accessKey = 'prod'
    }
  }
}

task printWhatever << {
  println whatever.whateverVersion
  whatever.conf.each { c ->
    println "$c.name -> $c.accessKey"
  }
}

class SamplePlugin implements Plugin<Project> {
  void apply(Project project) {
    project.extensions.create('whatever', SampleWhatever)
    project.whatever.extensions.conf = project.container(SampleConf)
    project.whatever.conf.all {
      accessKey = 'dev'
    }
  }
}

class SampleWhatever {
  String whateverVersion
}

class SampleConf {
  final String name
  String accessKey

  SampleConf(String name) {
    this.name = name
  }
}
Run Code Online (Sandbox Code Playgroud)

而实现这种DSL的groovy方式是元编程 - 你需要methodMissing在这种特殊情况下实现.下面是一个非常简单的示例,演示了它的工作原理:

class SomeExtension {
  def devConf = new SomeExtensionConf()

  void methodMissing(String name, args) {

    if ('dev'.equals(name)) {
        def c = args[0]
        c.resolveStrategy = Closure.DELEGATE_FIRST
        c.delegate = devConf
        c()
    } else {
      throw new MissingMethodException("Could not find $name method")
    }
  }

  def getDev() {
    devConf
  }
}

class SomeExtensionConf {
  def accessKey
}

project.extensions.create('some', SomeExtension)

some {
  dev {
    accessKey = 'lol'
  }
}

assert 'lol'.equals(some.dev.accessKey)
Run Code Online (Sandbox Code Playgroud)

当然它没有错误检查 - 因此args需要验证每个参数的大小和类型 - 为简洁起见省略了它.

当然,没有必要创建一个单独的类为每个配置(我的意思是dev,prod等).创建一个包含配置的类,并将其全部存储在Mapwhere键为配置名称的位置.

你可以在这里找到一个演示.