Gradle构建初探

KinglyJn      2017-06-23

DSL (domain specific language)

即所谓领域专用语言,其基本思想是“求专不求全”,不像通用目的语言那样目标范围涵盖一切软件问题,而是专门针对某一特定问题的计算机语言。 -DSL之于程序员正如伽南地之于以色列人,是最初也是最终的梦想。几乎自计算机发明伊始,人们就开始谈论DSL使用DSL了。 -前几年迅速走红的Ruby on Rails就被誉为“Web开发领域专用语言”


DSL 约等于整洁的代码

从概念上说,程序的编写过程就是把业务领域中的问题通过代码或者程序模型表达出来: 计算机的程序模型较为单一(归根结底都是运算和存储) 在面向对象技术成为主流的今天,通常情况下,计算机程序不太可能做到与业务领域中的概念一致,或者具有某些直觉的对应。因此,软件的修改和可维护性并没有想象中的容易。我们必须不断地将业务领域中的概念转换成相应的代码模型,然后再进行修改。这种间接性直接造成了软件的复杂度。 而DSL的主要目的就是要消除这样的复杂度(或者说,以构造DSL的复杂度代替这种复杂度),DSL就要是要以贴近业务领域的方式来构造软件。因此,DSL的简洁性往往是一种思维上的简洁性,使我们不用费太多的气力就能看懂代码所对应的业务含义。


DSL多以文本代码的形式出现

多年来软件工程实践表明文本代码是最有效率的编辑形式。但是一些特殊领域,文本代码并不是最佳的表现形式,为了更好的贴近业务领域中的概念,我们可能会选择使用一些图形化的DSL。如:[DSM(Domain Specific Modeling)工具GEMS(Generic Eclipse Modeling System)中就大量地使用了不同的图形化的DSL来表述系统的各个不同侧面。

Gradle向我们提供了一整套DSL,所以在很多时候我们写的代码似乎已经脱离了groovy,但是在底层依然是执行的groovy为了从命令行运行gradle测试样例,首先配置环境变量,然后检查测试:

> gradle -v
------------------------------------------------------------
Gradle 3.5
------------------------------------------------------------

Build time:   2017-04-10 13:37:25 UTC
Revision:     b762622a185d59ce0cfc9cbc6ab5dd22469e18a6

Groovy:       2.4.10
Ant:          Apache Ant(TM) version 1.9.6 compiled on June 29 2015
JVM:          1.8.0_131 (Oracle Corporation 25.131-b11)
OS:           Mac OS X 10.12.2 x86_64


初尝禁果

新建个文件夹Test,文件夹下建个文件build.gradle,打开文件,写个简单的代码

task helloWorld << {
    println "Hello World"
}

执行gradle helloWorld

> gradle helloWorld
:helloWorld
Hello World
BUILD SUCCESSFUL
Total time: 3.714 secs

同时,这时候发现已经自动在Test目录下创建了.gradle文件。上面helloWorld后的“«”表示追加的意思,即向helloWorld中加入执行过程,使用doLast可以达到同样效果,这也是gradle官方建议的方式:

task helloWorldTwo {
   doLast {
      println 'helloWorldTwo'
   }
}

如果需要向Task的最前面加入执行过程,我们可以使用doFirst:

task helloWorldThree {
   doFirst {
      println 'helloWorldThree'}
}


关于 task

Gradle将当前目录下的build.gradle文件作为项目的构建文件。在上面的例子中,我们创建了一个名为helloWorld的Task,在执行gradle命令时,我们指定执行这个helloWorld Task。这里的helloWorld是一个DefaultTask类型的对象,这也是定义一个Task时的默认类型,当然我们也可以显式地声明Task的类型,甚至可以自定义一个Task类型。下面再看一个小case:

我们在Test文件夹下建一个src目录,建一个dst目录,src目录下建立一个文件,命名为here.txt,然后在build.gradle中append一个task:

task helloWorld << {
    println "Hello World"
}
task copyFile(type: Copy){
    from "src"
    into "dst"
}

代码中(type:Copy)就是“显式地声明Task的类型”,helloworld没有就是默认得DefaultTask类型咯。执行命令:

> gradle copyFile
:copyFile
BUILD SUCCESSFUL
Total time: 2.645 secs
D:\Test>gradle copyFile
:copyFile
BUILD SUCCESSFUL
Total time: 3.361 secs

好了! here.txt也跑到dst中去啦!简单吧!加一把火,我们来看一下当前目录下(即Test目录文件,这里也可以将这个目录理解为一个project,不过还没写有生产力的代码,哈哈哈)定义的task

> gradle tasks
:tasks

------------------------------------------------------------
All tasks runnable from root project
------------------------------------------------------------

Build Setup tasks
-----------------
init - Initializes a new Gradle build. [incubating]
wrapper - Generates Gradle wrapper files. [incubating]

Help tasks
----------
components - Displays the components produced by root project 'Test'. [incubating]
dependencies - Displays all dependencies declared in root project 'Test'.
dependencyInsight - Displays the insight into a specific dependency in root project 'Test'.
help - Displays a help message.
model - Displays the configuration model of root project 'Test'. [incubating]
projects - Displays the sub-projects of root project 'Test'.
properties - Displays the properties of root project 'Test'.
tasks - Displays the tasks runnable from root project 'Test'.

Other tasks
-----------
copyFile
helloWorld

To see all tasks and more detail, run gradle tasks --all

To see more detail about a task, run gradle help --task <task>

BUILD SUCCESSFUL

Total time: 2.895 secs

是不是很清晰呢,展示了各种task类型,task的作用,以及另外两个task相关的命令,相信聪明的你也一看就懂。Gradle本身的领域对象主要有Project和Task。Project为Task提供了执行上下文,所有的Plugin要么向Project中添加用于配置的Property,要么向Project中添加不同的Task。一个Task表示一个逻辑上较为独立的执行过程,比如编译Java源代码,拷贝文件,打包Jar文件,甚至可以是执行一个系统命令或者调用Ant。另外,一个Task可以读取和设置Project的Property以完成特定的操作。是不是很屌的样子。task关键字其实是一个groovy中方法的调用,该方法属于Project,而大括号之间的内容则表示传递给task()方法的一个闭包。


task间的依赖

task之间是可以存在依赖关系,比如TaskA依赖TaskB,那么在执行TaskA时,Gradle会先执行TaskB,再执行TaskA。我们可以在定义一个Task的同时声明它的依赖关系:

task helloWorldFour(dependsOn:helloWorldThree) << {
    println 'hellohelloWorldFour'
}

或者

task helloWorldFour << {
    println 'hellohelloWorldFour'
}
helloWorldFour.dependsOn helloWorldThree


可配置的task

一个Task除了执行操作之外,还可以包含多个Property,其中有Gradle为每个Task默认定义了一些Property,比如description,logger等。 另外,每一个特定的Task类型还可以含有特定的Property,比如Copy的from和to等。 当然,我们还可以动态地向Task中加入额外的Property。在执行一个Task之前,我们通常都需要先设定Property的值。

task helloWorld << {
   description = "this is helloWorld" 
   println description
}

或者通过调用Task的configure()方法完成Property的设置:

task helloWorld << {
   println description
}
helloWorld.configure {
   description = "this is helloWorld" 
}


花式task

task showDescription1 << {
   description = 'this is task showDescription'
   println description
}

task showDescription2 << {
   println description
}
showDescription2.description = 'this is task showDescription'

task showDescription3 << {
   println description
}
showDescription3 {
   description = 'this is task showDescription'
}

对于每一个Task,Gradle都会在Project中创建一个同名的Property,所以我们可以将该Task当作Property来访问,showDescription2便是这种情况。另外,Gradle还会创建一个同名的方法,该方法接受一个闭包,我们可以使用该方法来配置Task,showDescription3便是这种情况。 耶!简单吧!


关于 Groovy

Gradle是一种声明式的构建工具。

  • 在执行时,Gradle并不会一开始便顺序执行build.gradle文件中的内容,而是分为两个阶段,第一个阶段是配置阶段,然后才是实际的执行阶段。
  • 配置阶段,Gradle将读取所有build.gradle文件的所有内容来配置Project和Task等,比如设置Project和Task的Property,处理Task之间的依赖关系等。
  • Gradle的DSL只是Groovy语言的内部DSL,也必须遵循Groovy的语法规则。
  • Groovy语言中的两个概念,一个是Groovy中的Bean概念,一个是Groovy闭包的delegate机制。

groovy bean

Groovy中的Bean和Java中的Bean有一个很大的不同,即Groovy动态的为每一个字段都会自动生成getter和setter,并且我们可以通过像访问字段本身一样调用getter和setter

class GroovyBeanExample {
   private String name
}

def bean = new GroovyBeanExample()
bean.name = 'this is name' //实际调用的是"bean.setName('this is name')"
println bean.name  //实际调用的是
"println bean.getName()"

采用像直接访问的方式的目的是为了增加代码的可读性,使它更加自然,而在内部,Groovy依然是在调用setter和getter方法。


groovy 闭包的delegate机制

简单来说,delegate机制可以使我们将一个闭包中的执行代码的作用对象设置成任意其他对象。

class Child {
   private String name
}
class Parent {
   Child child = new Child();
   void configChild(Closure c) {
      c.delegate = child
      c.setResolveStrategy Closure.DELEGATE_FIRST
      c()
   }
}

def parent = new Parent()
parent.configChild {
name = "child name"
}

println parent.child.name

在上面的例子中,当调用configChild()方法时,并没有指出name属性是属于Child的,但是它的确是在设置Child的name属性。 事实上光从该方法的调用中,我们根本不知道name是属于哪个对象的,你可能会认为它是属于Parent的。 真实情况是,在默认情况下,name的确被认为是属于Parent的,但是我们在configChild()方法的定义中做了手脚,使其不再访问Parent中的name(Parent也没有name属性),而是Child的name。 在configChild()方法中,我们将该方法接受的闭包的delegate设置成了child,然后将该闭包的ResolveStrategy设置成了DELEGATE_FIRST。这样,在调用configChild()时,所跟闭包中代码被代理到了child上,即这些代码实际上是在child上执行的。 此外,闭包的ResolveStrategy在默认情况下是OWNER_FIRST,即它会先查找闭包的owner(这里即parent),如果owner存在,则在owner上执行闭包中的代码。这里我们将其设置成了DELEGATE_FIRST,即该闭包会首先查找delegate(本例中即child),如果找到,该闭包便会在delegate上执行。

在使用Gradle时,我们并没有像上面的parent.configChild()一样指明方法调用的对象,而是在build.gradle文件中直接调用task(),apply()和configuration()等方法。这是 因为在没有说明调用对象的情况下,Gradle会自动将调用对象设置成当前Project。 比如调用apply()方法和调用project.apply()方法的效果是一样的。查查Gradle的Project文档,你会发现这些方法都是Project类的方法。 对于configurations()方法,该方法实际上会将所跟闭包的delegate设置成ConfigurationContainer,然后在该ConfigurationContainer上执行闭包中的代码。再比如,dependencies()方法,该方法会将所跟闭包的delegate设置成DependencyHandler。


gradle常用命令

初始化项目结构: gradle init [--type java-library]
执行多个任务: gradle [-q] test1 test2
排除任务: gradle test2 -x test1
选择要执行的构建: gradle -b xxx/myproject.gradle test1
只执行一个项目的构建: gradle :zdemo-gradle-dao:build

列出项目和项目依赖关系: gradle -q projects
					gradle -q dependencies p1:dependencies p2:dependencies
					gradle -q p1:dependencies --configuration test1
					gradle -q p1:dependencyInsight --dependency p2 --configuration test1

列出构建脚本依赖关系:	gradle buildEnvironment

列出任务和任务细节: gradle -q tasks
				gradle help --task test1	

列出项目属性:	gradle -q p1:properties

gradle后台进程:gradle -q task1 --status
禁用gradle后台进程:$USER_HOME/.gradle/gradle.properties 中配置 org.gradle.daemon = FALSE
				 或者 gradle --stop 停止现有的gradle后台进程
				 
使用gradle ui: gradle --gui &


gradle属性设置

Gradle还为我们提供了多种方法来自定义Project的Property。

  • 在构建脚本中,你所调用的任何一个方法,如果在构建脚本中未定义,它将被委托给 Project 对象。
  • 在构建脚本中,你所访问的任何一个属性,如果在构建脚本里未定义,它也会被委托给 Project 对象。


标准 project 属性

Project对象提供了一些在构建脚本中可用的标准的属性。下表列出了常用的几个属性。

名称				类型					默认值
-------------------------------------------------
project			Project				Project实例
name			String				项目目录的名称。
path			String				项目的绝对路径。
description		String				项目的描述。
projectDir		File				包含生成脚本的目录。
buildDir		File				projectDir/build
group			Object				未指定
version			Object				未指定
ant				AntBuilder			AntBuilder实例


Gradle属性的各种设置方法

从Gradle 启动的 JVM,你可以使用 -D 命令行选项向它传入一个系统属性。 Gradle 命令的-D选项和 java 命令的 -D 选项有着同样的效果。

task task1 {
     doLast {
         println System.properties['aaa']
     }
}

//运行
> gradle -q task1 -Daaa=123
123

也可以通过 -P来设置属性,-P属性则会被直接加载到Gradle领域对象上。

task task1 {
     doLast {
         println "$aaa"
     }
}

//运行
> gradle -q task1 -Paaa=123
123

也可以使用ext设置属性,ext准确的说是Gradle领域对象(即project对象实例,准确地说ext对象其实是DefaultExtraPropertiesExtension对象的一个实例)的一个属性,我们可以将自定义的属性添加到ext对象上,Build.gradle中的其它代码片段可以使用。

ext {
	aaa = 123456
}

task task1 {
     doLast {
         println "$aaa"
     }
}

//运行
> gradle -q task1
123456
> gradle -q properties |grep aaa
aaa: 123456
> gradle properties | grep ext
ext: org.gradle.api.internal.plugins.DefaultExtraPropertiesExtension@56fbfd01

也可以在项目根目录下 或 $USER_HOME/.gradle/ 目录下建立名为gradle.properties文件,在该文件中定义需要的属性。这些属性在Gradle构建Gradle领域对象(即project对象实例)时会被自动加到project对象实例中作为其属性被直接调用。

gradle.properties

aaa = 12345678

build.gradle

task task1 {
     doLast {
         println "$aaa"
     }
}

//运行
> gradle -q task1
123456


同时我们也可以在 gradle.properties创建系统属性。如果有systemProp.前缀的属性会被识别为系统属性。

gradle.properties

systemProp.aaa = 12345678

build.gradle

task task1 {
     doLast {
         println System.properties['aaa']
     }
}

//运行
> gradle -q task1
12345678


gradle任务

//定义默认的任务
defaultTasks 'clean', 'run'

task clean {
	doLast {
		println 'default cleaning'
	}
}

task run {
	doLast {
		println 'default running'
	}
}


// 最基本的任务
task task1 {
	doLast {
		println "------$task1.name-----"
		String str = "hello WORLD!"
		println "origin str: " + str
		println "upper case: " + str.toUpperCase()

		4.times {
			print "$it"
		}
		println ""

		def files = file('.').listFiles().sort()
		files.each {File file ->
			//if (file.isFile()) {
				println "$file.name"
			//}
		}
	}
}


// 任务依赖测试
task task2(dependsOn: 'task1') {
	doLast {
		println "------task2-----"	
	}
}


// 动态任务测试
4.times {counter ->
	task "dycTask$counter" {
		doLast {
			println "-----dycTask$counter-----"
		}
	}
}

// 通过API进行任务间的通信
dycTask0.dependsOn task1,task2 
dycTask0.doFirst {
	println "-----dycTask doFirst1-----"	
}
dycTask0.doFirst {
    println "-----dycTask doFirst2-----"
}
dycTask0.doLast {
	println "-----dycTask doLast1-----"
}
dycTask0.doLast {
	println "-----dycTask doLast2-----"
}

// 使用 ext.propName 的方式给任务增加自定义属性
task customPropTask {
	ext.admin = "kinglyjn"
}

task printCustomPropTask {
	doLast {
		println customPropTask.admin		
	}
}

// 使用方法
def add(i, j) {
	return i+j;
}

task useMethodTask {
	doLast {
		println add(1,2)
	}
}


// 依赖任务的不同输出
gradle.taskGraph.whenReady {taskGraph ->
	if (taskGraph.hasTask(release)) {
		version = '1.0'
	} else {
		version = '1.0-SNAPSHOT'
	}
}

task distribution {
	doLast {
		println "distribution version: $version"
	}
}

task release(dependsOn:'distribution') {
	doLast {
		println "-----we released now-----"
	}
}


gradle依赖管理

repositories {
  	mavenLocal()
	//maven { url "file:///Library/apache-maven-3.3.9/repository" }
  
    mavenCentral()
  	jcenter()
  
  	maven {
        url "http://repo.mycompany.com/maven2"
      	//credentials {
        //    username 'your_username'
        //    password 'your_password'
        //}
    }
  	ivy {
        url "http://repo.mycompany.com/repo"
    }
 	ivy {
        url "../local-repo"
    }
}


依赖关系:

  • compile: 源代码(src/main/java)编译时的依赖,最常用
  • runtime: 源代码(src/main/java)执行时依赖
  • testCompile: 测试代码(src/main/test)编译时的依赖
  • testRuntime: 测试代码(src/main/java)执行时的依赖
  • archives: 项目打包(e.g.jar)时的依赖


声明依赖:

dependencies {
    compile group: 'org.hibernate', name: 'hibernate-core', version: '3.6.7.Final'
}

dependencies {
    compile 'org.hibernate:hibernate-core:3.6.7.Final'
}

dependencies {
	//library是另一个module的名字
   	compile project(':library')
}

dependencies {
   //java
   compile files('spring-core.jar', 'spring-aap.jar')
   compile fileTree(dir: 'deps', include: '*.jar')
   //studio中一般这么写
   compile fileTree(dir: 'libs', include: ['*.jar'])
}


/* 单个依赖 */
compile group:'log4j', name:'log4j', version:'1.2.17'
// 简写 => compile 'log4j:log4j:1.2.17'
 
/* 以数组形式添加多个依赖*/
compile 'joda-time:joda-time:2.9.2', 'log4j:log4j:1.2.17'
 
/* 闭包形式,以添加额外配置*/
compile (group:'log4j', name:'log4j', version:'1.2.17'){
    // ... 额外配置
}
/* 等价于 */
compile ('log4j:log4j:1.2.17'){
    // ... 额外配置
}

依赖常见的一些问题

很多库依赖于其他库,例如a.jar依赖b.jar,在Gradle中,只需添加a.jar即可,Gradle将自动把a依赖的所有库全部下载。但是,有时你并不想让Gradle自动去做这件事情,比如你希望明明白白地知道添加哪些库,可以配置transitive实现,编译时报错,你就可以知道进一步需添加哪些库。

dependencies {
    // transitive 属性默认为 true
    compile group:'log4j',name:'log4j',version:'1.2.17',transitive:false
}

另一种情况是,依赖传递可能会导致版本冲突,即依赖传递下载的库可能与项目依赖的另一个库版本冲突,这种情况下可以排除一些库,而下载其他所有的依赖库,即选择性排除。

dependencies {
    compile ('commons-httpclient:commons-httpclient:3.1'){
        exclude group:'commons-codec' //排除该group的依赖
        // exclude group:'commons-codec',module:'commons-codec'
        // group是必选项,module可选
    }
}

另外,版本冲突时十分常见的,比如下面的例子

// 库 a 传递性依赖库 b-1.2,与添加的b-1.1冲突
dependencies {
    compile 'a:a:1.0'
    compile 'b:b:1.1'
}

Gradle解决冲突有以下几种方式:

  • 最近版本策略(默认):上例将忽略b-1.1,而下载b-1.2
  • 冲突失败策略:发生冲突时,编译失败(有些新版本库并不兼容之前的,因此这个库可以让开发者主动作出选择)
  • 强制指定版本策略:发生冲突时,使用开发者指定的版本
/* 冲突失败策略设置*/
configurations.all {
    resolutionStrategy {  failOnVersionConflict() }
}
/* 强制指定版本策略设置*/
dependencies {
    compile group:'b',name:'b',version:'1.1',force:true
}

Gradle的动态依赖:

dependencies {
    /* 选择1以上任意一个版本,这使发生版本冲突的几率变小*/
    compile group:'b',name:'b',version:'1.+'
    /* 选择最新的版本,避免直接指定版本号 */
    compile group:'a',name:'a',version:'latest.integration'
}

Gradle依赖的其他设置:

// 指定库文件类型ext  默认为jar,可选属性为war、zip
compile group:'b',name:'b',version:'1.1',ext:'war'

// 使用分类器(classifiers) 例如提供了2种包,a-1.0-dev.war, a-1.0-dev.jar
compile group:'b',name:'b',version:'1.1',classifier:'dev',ext:'war'

// 替换传递依赖的版本
compile group:'a',name:'a',version:'l.0' {
    dependencies 'b:b:1.1'
}

常用的命令:

(1) 查看所有依赖库
> gradle dependencies

(2) 查看指定配置(详见 1.3)的依赖库
> gradle dependencies --configuration <configuration>
     gradle dependencies --configuration compile //查看编译时依赖
     gradle dependencies --configuration runtime //查看运行时依赖


Gradle依赖分组

Gradle对依赖进行分组,允许编译时使用一组依赖,运行时使用另一组依赖。每一组依赖称为一个Configuration,在声明依赖时,我们实际上是在设置不同的Configuration。

要定义一个Configuration,我们可以通过以下方式完成:studio一般不需要设置,应该是有默认的,即为classpath

configurations {
   myDependency
}

通过dependencies()方法向myDependency中加入实际的依赖项:

dependencies {
//下面的myDependency是关键
   myDependency 'org.apache.commons:commons-lang3:3.0'
}
//类似studio中的classpath
dependencies {
   classpath 'com.android.tools.build:gradle:1.3.0'
}
//还有 这里的compile,testCompile
dependencies {
    compile project(':library')
    compile 'com.android.support:recyclerview-v7:22.2.1'
    compile 'com.android.support:design:22.2.1'
    compile 'com.evernote:android-intent:1.0.1'
    testCompile 'junit:junit:4.8.2' 
}

myDependency,classpath,compile,testCompile都是Configuration(一组依赖)。 除了myDependency都不使我们定义的,为啥呢,android Plugin会自动定义compile和testCompile分别用于编译Java源文件和编译Java测试源文件。classpath应该是用于所有,我类推的。 Gradle还允许我们声明对其他Project或者文件系统的依赖。

dependencies {
//library是另一个module的名字
   compile project(':library')
}

对于本地文件系统中的Jar文件,我们可以通过以下方式声明对其的依赖:

dependencies {
   //java
   compile files('spring-core.jar', 'spring-aap.jar')
   compile fileTree(dir: 'deps', include: '*.jar')
   //studio中一般这么写
   compile fileTree(dir: 'libs', include: ['*.jar'])
}


gradle发布项目

发布项目到本地:

uploadArchives {
    repositories {
       flatDir {
           dirs 'repos'
       }
    }
}

// 运行gradle uploadArchives 进行构建和发布

发布到ivy仓库:

uploadArchives {
    repositories {
        ivy {
            credentials {
                username "username"
                password "pw"
            }
            url "http://repo.mycompany.com"
        }
    }
}

// 运行gradle uploadArchives 进行构建和发布

发布到maven仓库:

apply plugin: 'maven'
uploadArchives {
    repositories {
        mavenDeployer {
            repository(url: "file:///Users/zhangqingli/test/javatest/p2/local/repos/")
        }
    }
}

// 运行gradle uploadArchives 进行构建和发布

或者使用maven-publish插件:

apply plugin: 'maven-publish'

publishing {
  	publishToLocal(MavenPublication) {
      	from components.java
  	}
  	
  	repositories {
      	maven {
          	name "myRepos"
          	url "xxx"
      	}
  	}
}


改造默认的项目目录

//源码路径
sourceSets {
	main {
    	java {
          	srcDirs = ['src'] //用不同目录的列表代替约定的源代码目录,默认为src/main/java
    	}
	}
  	test {
      	java {
          	srcDirs = ['test'] //
      	}
  	}
}

//输出路径
buildDir = 'out' //改变项目输出路径到out目录


jar文件特殊配置

jar {
    manifest {
        attributes 'Implementation-Title': 'Gradle Quickstart',
                   'Implementation-Version': version
    }
}


gradle插件和自定义插件

Gradle最常用的Plugin便是java Plugin了。和其他Plugin一样,java Plugin并没有什么特别的地方,只是向Project中引入了多个Task和Property。当然,java Plugin也有比较与众不同的地方,其中之一便是它在项目中引入了构建生命周期的概念,就像Maven一样。但是,和Maven不同的是,Gradle的项目构建生命周期并不是Gradle的内建机制,而是由Plugin自己引入的。此外我们还可以自定义插件:

apply plugin: DateAndTimePlugin

dateAndTime {
    timeFormat = 'HH:mm:ss.SSS'
    dateFormat = 'MM/dd/yyyy'
}

class DateAndTimePlugin implements Plugin<Project> {
    //该接口定义了一个apply()方法,在该方法中,我们可以操作Project,
    //比如向其中加入Task,定义额外的Property等。
    void apply(Project project) {
        project.extensions.create("dateAndTime", DateAndTimePluginExtension)

        project.task('showTime') << {
            println "Current time is " + new Date().format(project.dateAndTime.timeFormat)
        }

        project.tasks.create('showDate') << {
            println "Current date is " + new Date().format(project.dateAndTime.dateFormat)
        }
    }
}
//每个Gradle的Project都维护了一个ExtenionContainer,
//我们可以通过project.extentions进行访问
//比如读取额外的Property和定义额外的Property等。
//向Project中定义了一个名为dateAndTime的extension
//并向其中加入了2个Property,分别为timeFormat和dateFormat
class DateAndTimePluginExtension {
    String timeFormat = "MM/dd/yyyyHH:mm:ss.SSS"
    String dateFormat = "yyyy-MM-dd"
}

每一个自定义的Plugin都需要实现Plugin接口,除了给Project编写Plugin之外,我们还可以为其他Gradle类编写Plugin。以上是在build.gradle文件中直接定义Plugin,还可以在当前工程中、单独的项目中创建Plugin,一般情况不需要了解。


gradle自定义任务

Gradle中的Task要么是由不同的Plugin引入的,要么是我们自己在build.gradle文件中直接创建的。

  • 在build.gradle文件中直接定义 需要定义的Task类型不多时 Gradle其实就是groovy代码,所以在build.gradle文件中,我们便可以定义Task类。
class HelloWorldTask extends DefaultTask {
    //@Optional,表示在配置该Task时,message是可选的。
    @Optional
    String message = 'I am jjx'
    //@TaskAction表示该Task要执行的动作,即在调用该Task时,hello()方法将被执行
    @TaskAction
    def hello(){
        println "hello world $message"
    }
}

//hello使用了默认的message值
task hello(type:HelloWorldTask)

//重新设置了message的值
task helloOne(type:HelloWorldTask){
   message ="I am a android developer"
}
  • 在当前工程中定义Task类型 只能应用在当前module中,没什么卵用,下面是全局可用的
  • 在单独的项目中定义Task类型 项目中存在大量的自定义Task类型时,在另外的一个gradle文件中定义这些Task,然后再apply到build.gradle文件中。

使用定义的任务:

//这是插件
apply plugin: 'com.android.application'
//这里gradle-quality.gradle就是另外单独定义了task的gradle
apply from: '../build-config/gradle-quality.gradle'


构建多模块项目

Gradle为每个build.gradle都会创建一个相应的module领域对象,在编写Gradle脚本时,我们实际上是在操作诸如module这样的Gradle领域对象。在多module的项目中,我们会操作多个module领域对象。Gradle提供了强大的多module构建支持 要创建多module的Gradle项目,我们首先需要在根(Root)Project中加入名为settings.gradle的配置文件,该文件应该包含各个子module(其实就是一个子project)的名称。如setting.gradle中:

include 'library', 'demo'

类似module(子project)的build.gradle,(Root)Project也有自己的build.gradle,在里面通常设置:

allprojects {
    repositories {
        jcenter()
    }
    //通常studio项目没有,咱自己加的
   apply plugin: 'idea'
   task allTask << {
      println project.name
   }
}

allprojects()方法将repositories配置一次性地应用于所有的module(子Project)和root-project本身,当然也包括定义的Task,这个task配置到所有module里面了和root-project。subprojects()方法用于配置所有的子Project(不包含根Project)。


Tags:


Share: