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

Correct way to configure gradle plugin extensions with groups of dynamic objects

我正在尝试编写自己的 gradle 插件,它需要能够配置一组对象 - 这些对象的数量以及它们的名称由用户决定。

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

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

teregrin {
  terraformVersion = '0.6.6'

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

  root("prd"){
  }
}

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

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
  }

}

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

 project.extensions.create("teregrin", TeregrinPluginExtension)

这工作正常,但这是一种非常丑陋的配置风格,不是典型的 gradle DSL 的风格。

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

teregrin {
  terraformVersion = '0.6.6'

  roots {
    dev {
      accessKey = "flobble"
    }

    prd {
    }
  }
}

实现这种 DSL 的 gradle 方法是使用 extensions and containers:

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
  }
}

虽然 groovy 实现此类 DSL 的方法是元编程 - 您需要在这种特殊情况下实现 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)

当然它没有错误检查 - 所以 args 每个参数的大小和类型都需要验证 - 为了简洁起见,它被省略了。

当然没有必要为每个配置创建一个单独的 class(我的意思是 devprod 等)。创建一个 class 来保存配置并将它们全部存储在 Map 中,其中键是配置名称。

您可以找到演示 here