本文共 7316 字,大约阅读时间需要 24 分钟。
关于我对Gradle的翻译,以Github上的项目及http://gradledoc.qiniudn.com 上的文档为准。如发现翻译有误的地方,将首先在以上两个地方更新。因时间精力问题,博客中发表的译文基本不会同步修改。
另外,目前Gradle1.12版本的文档已经翻译完并进入校稿阶段,校稿的方式为到该项目https://github.com/msdx/gradledoc 提交issue或是pull request。校稿的结果不只是在此版本更新,也会用于改善Gradle下一版本(2.0)文档的翻译。
Gradle 插件打包了可以复用的构建逻辑块,这些逻辑可以在不同的项目和构建中使用。Gradke 允许你实现你自己的自定义插件,因此你可以重用你的构建逻辑,并且与他人分享。
你可以使用你喜欢的任何一种语言来实现一个自定义插件,只要这个实现最终能够提供编译的字节码。在这里的例子中,我们将使用Gradle来作为实现语言。如果你想的话,你也可以使用Java或者是Scala来代替。
有几个地方可以让你放这个插件的源码。
你可以在构建脚本中直接包含这个插件的源码。这样做的好处是,你不需要再做什么,这个插件就能够被自动地编译并且包含到这个构建脚本的类路径当中。然而,在这个构建脚本脚本之外,这个插件是不可见的,因此你不能够在你定义这个插件的脚本以外的地方来复用它。
buildSrc
项目 你可以把插件的源码放在
目录中。Gradle将会编译和测试这个插件,并且使它在构建脚本的类路径中可用。这个插件在该构建所使用的每一个构建脚本当中都是可见的。然而,它在这个构建之外并不可见,因为你不能在定义它的这个构建之外的其他地方来重用这个插件。rootProjectDir
/buildSrc/src/main/groovy
有关buildSrc
项目的更详细信息,请参阅 。
你可以为你的插件创建一个单独的项目。这个项目会输出和发布一个JAR文件,然后你可以在多个构建中使用,并且分享出去。一般来说,这个JAR可能包含一些自定义的插件,或者是捆绑几个相关的任务类到一个单独的库当中。或者是上面两者都有。
在我们的例子中,为了简单,我们将从在构建脚本中编写插件开始。然后我们会看看创建一个单独的项目的方式。
想创建一个自定义插件,你需要写一个类实现 接口。当这个插件被用在一个project上时,Gradle会实例化这个插件,并且调用这个插件实例的 方法。这个project对象会被作为一个参数传进去,该参数可以让插件用于对这个project配置它所需要的东西。下面的例子包含了一个问候语插件,它把一个 hello
任务添加到project中。
示例 58.1. 自定义插件
build.gradle
apply plugin: GreetingPluginclass GreetingPlugin implements Plugin{ void apply(Project project) { project.task('hello') << { println "Hello from the GreetingPlugin" } }}
gradle -q hello
的输出结果
> gradle -q helloHello from the GreetingPlugin
需要注意的一点是,对于一个给定的插件,每一个配置使用它的项目都会创建一个新的实例。
大部分插件都需要从构建脚本中获得一些配置。实现这种目的的其中一种做法是使用 扩展对象。Gradle 与 对象会有关联,该对象可以帮助追踪传给插件的所有配置和属性。通过你的插件相关的扩展容器,你可以获取用户的输入。要获取输入,只需将一个Java Bean兼容类添加到扩展容器的扩展列表中。对于一个插件而言,Groovy是一种不错的语言选择,因为普通的旧Groovy对象包含了一个Java Bean所需要的所有的getter和setter方法。
让我们向项目中添加一个简单的扩展对象。在这里,我们向该project添加了一个 greeting
扩展对象,它能够让你配置问候内容。
示例 58.2. 自定义插件扩展
build.gradle
apply plugin: GreetingPlugingreeting.message = 'Hi from Gradle'class GreetingPlugin implements Plugin{ void apply(Project project) { // Add the 'greeting' extension object project.extensions.create("greeting", GreetingPluginExtension) // Add a task that uses the configuration project.task('hello') << { println project.greeting.message } }}class GreetingPluginExtension { def String message = 'Hello from GreetingPlugin'}
gradle -q hello
的输出结果
> gradle -q helloHi from Gradle
在这个示例中,GreetingPluginExtension
是一个plain old Groovy 对象,它有一个成员变量 message
。这个扩展对象以greeting
名字添加到插件列表中。然后该对象变为有效的一个项目属性,与这个扩展对象的名称相同。
通常情况下,你会有几个相关的属性,所以你需要在单个插件上指定。Gradle 为每一个扩展对象添加了一个配置闭包块,因此你可以把这些配置分组。下面是如何分组的示例。
示例 58.3. 使用配置闭包的自定义插件
build.gradle
apply plugin: GreetingPlugingreeting { message = 'Hi' greeter = 'Gradle'}class GreetingPlugin implements Plugin{ void apply(Project project) { project.extensions.create("greeting", GreetingPluginExtension) project.task('hello') << { println "${project.greeting.message} from ${project.greeting.greeter}" } }}class GreetingPluginExtension { String message String greeter}
gradle -q hello
的输出结果
> gradle -q helloHi from Gradle
在这个例子中,通过 greeting
闭包,多个配置可以被组合在一起。在构建脚本中,这个闭包块的名字(greeting
)需要和扩展对象的名字相匹配。然后,当这个闭包被执行的时候,这个扩展对象的变量会被映射到基于标准的Groovy闭包代理功能的闭包里的这些变量中。
当开发自定义任务和插件时,接收输入配置的文件位置能使得在灵活性上表现得更好。为此,你可以利用 方法来尽可能晚地把值解析到文件中。
示例 58.4. 文件属性的惰性评估
build.gradle
class GreetingToFileTask extends DefaultTask { def destination File getDestination() { project.file(destination) } @TaskAction def greet() { def file = getDestination() file.parentFile.mkdirs() file.write "Hello!" }}task greet(type: GreetingToFileTask) { destination = { project.greetingFile }}task sayGreeting(dependsOn: greet) << { println file(greetingFile).text}greetingFile = "$buildDir/hello.txt"
Output of gradle -q sayGreeting
> gradle -q sayGreetingHello!
在这个例子中,我们配置了 greet
任务的 destination
属性为一个闭包, 它将在最后通过 方法将闭包中的返回值转为一个文件对象。你会注意到,在上面的例子中,我们是在已经配置了在作用中使用 greetingFile
属性之后才指定它的值。这种懒评估的主要好处是当设置文件属性时它能够接收任何值,并在读取这个属性的时候去解析这个值。
现在我们将移动我们的插件到一个单独的项目中,这样我们就可以发布它,并与他人分享。这个项目只是一个简单的Groovy项目,它将产生一个包含插件类的JAR包。下面是该项目的一个简单的构建脚本。它配置使用Groovy插件,并且添加Gradle API 作为编译时依赖。
示例 58.5. 自定义插件的构建
build.gradle
apply plugin: 'groovy'dependencies { compile gradleApi() compile localGroovy()}
注意: 此例子的代码可以在Gradle的二进制文件或源码中的 samples/customPlugin/plugin
里看到。
那么,Gradle是怎么找到 实现的?答案是你需要在jar文件里的META-INF/gradle-plugins
目录中提供一个属性文件,让它与你的插件名相匹配。
示例 58.6. 编写一个自定义插件
src/main/resources/META-INF/gradle-plugins/greeting.properties
implementation-class=org.gradle.GreetingPlugin
注意,属性的文件名要和插件名相匹配,放在resources夹里,并且implementation-class
属性要标识 的实现类。
要在一个构建脚本中使用一个插件,你需要先将这个插件的类添加到构建脚本的classpath中。要做到这一点,你要使用在中描述的 buildscript { }
块。下面的示例展示了当包含了插件的JAR文件已经被发布到一个本地仓库时,你可以怎么做。
示例 58.7. 在另一个项目中使用一个自定义插件
build.gradle
buildscript { repositories { maven { url uri('../repo') } } dependencies { classpath group: 'org.gradle', name: 'customPlugin', version: '1.0-SNAPSHOT' }}apply plugin: 'greeting'
当你要测试你的插件实现时,你可以使用 类来创建 实例去使用你的任务类。
示例 58.8. 测试自定义插件
src/test/groovy/org/gradle/GreetingPluginTest.groovy
class GreetingPluginTest { @Test public void greeterPluginAddsGreetingTaskToProject() { Project project = ProjectBuilder.builder().build() project.apply plugin: 'greeting' assertTrue(project.tasks.hello instanceof GreetingTask) }}
Gradle 提供了一些实用工具类,能够良好地在Gradle构建语言中使用,用于维护对象集合。
示例 58.9. 管理域对象
build.gradle
apply plugin: DocumentationPluginbooks { quickStart { sourceFile = file('src/docs/quick-start') } userGuide { } developerGuide { }}task books << { books.each { book -> println "$book.name -> $book.sourceFile" }}class DocumentationPlugin implements Plugin{ void apply(Project project) { def books = project.container(Book) books.all { sourceFile = project.file("src/docs/$name") } project.extensions.books = books }}class Book { final String name File sourceFile Book(String name) { this.name = name }}
gradle -q books
的输出结果
> gradle -q booksdeveloperGuide -> /home/user/gradle/samples/userguide/organizeBuildLogic/customPluginWithDomainObjectContainer/src/docs/developerGuidequickStart -> /home/user/gradle/samples/userguide/organizeBuildLogic/customPluginWithDomainObjectContainer/src/docs/quick-startuserGuide -> /home/user/gradle/samples/userguide/organizeBuildLogic/customPluginWithDomainObjectContainer/src/docs/userGuide
方法创建了一个 实例,该实例具体许多有用的方法,用于管理及配置这些对象。为了使用任意一种project.container
类型的方法,必须公开一个 “name
” 属性,作为该对象的唯一且恒定的名称。容器方法里的project.container(Class)
通过尝试调用该类带有一个string参数的构造方法创建了新实例,该参数是这个对象所需的名称。请参阅上面的链接,查看project.container
允许自定义实例化策略的的其他重载方法。