gradle DSL 到 api:如何完成从 DSL 到 class 方法调用的转换?

gradle DSL to api: how translation from DSL to class method call is done?

我正在努力深入了解 gradle 的工作原理。

让我们以 build.gradle 脚本中的基本 dependencies {} 声明为例。 dependencies 是 Project 对象上的一个方法。

DSL 项目对象 = org.gradle.api.Project 接口:

void dependencies​(Closure configureClosure)
  Configures the dependencies for this project.
  This method executes the given closure against the DependencyHandler for this project. The 
  DependencyHandler is passed to the closure as the closure's delegate.

因此:方法接收配置闭包作为参数并确定闭包的委托对象是 DependencyHandler class,然后针对它的委托对象执行闭包。

有这个例子:

dependencies {
//    configurationName dependencyNotation
    implementation 'commons-lang:commons-lang:2.6'
}

这在 DependencyHandler class 的方法 add 的调用中翻译:

  Dependency    add​(String configurationName, Object dependencyNotation)    Adds a dependency to the given configuration.

现在的问题是:

行的翻译到底是怎么做的

 implementation 'commons-lang:commons-lang:2.6'

进入 class 方法调用(例如 DependencyHandler.add()),谁负责?

我的印象是文档中缺少解释,例如:此委托对象 DependencyHandler 上的默认方法是 add(...),所以每个 configClosure 的行,如果匹配符号 configurationName dependencyNotation, 会被翻译成delegate的对象默认方法

但这只是一种可能的解释。

想法是:我给出了一个包含多行的闭包,这是针对恰好有方法 add() 的委托对象再次执行的,并且神奇地为每一行调用了这个方法......这是怎么发生的,基于什么机制? Project.dependencies() 方法是这样做的,是委托对象本身,还是其他一些 groovy 特定机制等

谢谢。

如果您想更深入地了解 Gradle(或其 DSL)的工作原理,您可以随时查看实际的源代码。但是,由于这不是了解如何编写构建脚本所必需的,因此未包含在文档中。

关于你的具体例子,我不得不承认我并不完全知道它是如何完成的,但我有一个猜测。如果其他人有更好的见解,请随时证明我是错误的。

虽然 Gradle 在某些情况下确实使用 AST 转换来扩展常规 Groovy 语法(例如 task definition syntax),但我认为它们只是依赖动态方法来定义依赖项。

Groovy 是一种动态语言。这包括一个名为 methodMissing 的方法,它可以由任何 class 定义,并且只要在该 class:

的对象上调用缺少的方法,就会调用该方法
class Example {
    def methodMissing(String name, args) {
        println name
    }
}

def example = new Example()
example.method1()

您可以找到 more detailed example in Mr. Hakis blog.

由于 Groovy 允许省略带有参数的方法调用的括号,因此您的示例 implementation 'commons-lang:commons-lang:2.6' 基本上只是调用方法 implementation 并使用依赖表示法字符串作为其参数。

现在 Gradle 可以通过 methodMissing 捕获这些调用,然后在配置实际存在时调用 DependencyHandler.add()。这允许您在构建脚本中动态添加配置:

configurations {
    myConfig
}

dependencies {
    myConfig 'commons-lang:commons-lang:2.6'
}