深入理解gradle编译-语法篇

2019独角兽企业重金招聘Python工程师标准>>> hot3.png

9/26/2016 1:59:42 PM

深入理解gradle编译-语法篇

导读

本篇文章主要介绍Groovy编程语法。在Gradle构建(builds)上,使用Groovy语法编写了大量特定性的构建文件。其中包括DSL(Domain Specific Language)和构建文件中加入的一些Groovy代码声明。

Groovy语法旨在生成目标性编程语言,基于java开发,编译生成java字节代码。同时Groovy具有函数功能,它是一种面向对象语言,可以说是从C++到Java的下一代语言。

##1.Groovy语法初体验

首先我们要搭建一个Groovy语法试验环境,通过Groovy官网网站点击DownLoad下载,window和Linux都可以。笔者当前下载版本为:apache-groovy-sdk-2.4.7,解压进入bin目录,执行:

$ ./groovy --version
Groovy Version: 2.4.7 JVM: 1.7.0_79 Vendor: Oracle Corporation OS: Windows 7

也可以配置环境变量(首选):

$ groovy -v
Groovy Version: 2.4.7 JVM: 1.7.0_79 Vendor: Oracle Corporation OS: Windows 7

这里首先来看第一个实例A-1,使用Groovy语法输出Hello, World

[A-1]. Hello, World! in Groovy

println 'Hello, World!'

其中注意事项:

  • 分号;是可选的。如果添加了他们,可以正常工作,但他们不是必需的。
  • 圆括号()是可选的。如果编译器能够正确识别语法,去掉圆括号也是工作正常的。上例中println方法传入String类型的参数,这里没有使用圆括号。
  • 在Groovy中支持两种类型的String:单引号引用字符,像hello,为java.lang.String进程。双引号字符,允许修改。

在Groovy中没有原语。所有的变量使用类包装,如java.lang.Integer, java.lang.Character, and java.lang.Double。本地数据类型整数常量定义和浮点型定义如下所示:

[A-2. Groovy中的基础类型]

assert 3.class == Integer // 整数类型,常量定义
assert (3.5).class == BigDecimal // 浮点类型,常量定义,对应java.math.BigDecimal
assert 'abc' instanceof String // 单引号字符串
assert "abc" instanceof String // 双引号字符串,允许插入值
String name = 'Dolly'
assert "Hello, ${name}!" == 'Hello, Dolly!' // 双引号字符插入内容,全格式插入
assert "Hello, $name!" == 'Hello, Dolly!'	// 双引号字符插入内容,段格式,没有歧义情况下使用
assert "Hello, $name!" instanceof GString

注意,我们只可以直接引用常量方法,因为包装类已经做了初始化操作

Groovy允许我们使用明确的类型声明变量,像String,Date和Employee,使用def关键字,如下所示:

[A-3. 静态与动态数据类型]

Integer n = 3
Date now = new Date()

def x = 3
assert x.class == Integer
x = 'abc'
assert x.class == String
x = new Date()
assert x.class == Date

Java自动引入java.lang package。在Groovy中,下面的一些包是自动导入的:

  • java.lang
  • java.util
  • java.io
  • java.net
  • groovy.lang
  • groovy.util

java.math.BigIntegerjava.math.BigDecimal也是无需导入直接使用的。

###1.1 assert断言方法

assert方法在Groovy中执行其参数,依据Groovy断言,意思是:

  • 非零数字,即是真
  • 非空集合包含字符串,即是真
  • 非空引用,即是真
  • Boolean是true,即是真

具体断言如下例所示:

[A-4. The Groovy 断言]

assert 3; assert -1; assert !0
assert 'abc'; assert !''; assert !""
assert [3, 1, 4, 1, 5, 9]
assert ![]

断言通过任何信息不返回。断言失败则抛出异常,如下例所示,并附有大量的debug信息:

[A-5. 断言失败]

int x = 5; int y = 7
assert 12 == x + y // passes
assert 12 == 3 * x + 4.5 * y / (2/x + y**3) // fails

失败信息如下所示:

[A-6. 断言失败抛出异常]

Caught: Assertion failed:

assert 3 + 4 == 3.plus(5)
         |   |    |
         7   |    8
             false

Assertion failed:

assert 3 + 4 == 3.plus(5)
         |   |    |
         7   |    8
             false

        at oparator.run(oparator:1)

###1.2 操作符

在Groovy中,每一种操作符都对应一个方法调用。例如,+(加号),调用plus方法。在Groovy中被广泛的使用。下面一些例子:

[A-7. 操作符使用]

assert 3 + 4 == 3.plus(4)
assert 3 * 4 == 3.multiply(4)

assert 2**6 == 64
assert 2**6 == 2.power(6)

assert 'abc' * 3 == 'abcabcabc' // String.multiply(Number)
try {
	3 * 'abc'
} catch (MissingMethodException e) {
	// no Number.multiply(String) method
}

String s = 'this is a string'
assert s + ' and more' == 'this is a string and more'
assert s - 'is' == 'th is a string'
assert s - 'is' - 'is' == 'th  a string'

Date now = new Date()
Date tomorrow = now + 1 // Date.plus(Integer)
assert tomorrow - 1 == now // Date.minus(Integer)

注:Groovy使用较广泛的操作符,**就是一种

在Java中,使用==判断两个引用是否来自相同对象。在Groovy中,==调用equal方法,所以是检测的等价,而不是相等,就不再是对象之间的关系判断了。如果想判断引用之前的关系,使用is方法。

###1.3 集合

Groovy为集合提供了本地方法。使用方括号和独立的值来创建ArrayList数组。我们可以使用操作符将集合类型转成其他类型。集合也是拥有操作符的,实现类似plus, minus, and multiply实现如下所示:

[A-8. 集合实例]

def nums = [3, 1, 4, 1, 5, 9, 2, 6, 5]
assert nums instanceof ArrayList

Set uniques = nums as Set
assert uniques == [3, 1, 4, 5, 9, 2, 6] as Set

def sorted = nums as SortedSet
assert sorted == [1, 2, 3, 4, 5, 6, 9] as SortedSet
assert sorted instanceof TreeSet

assert nums[0] == 3
assert nums[1] == 1
assert nums[-1] == 5 // end of list
assert nums[-2] == 6

assert nums[0..3] == [3, 1, 4, 1] // two dots is a Range
assert nums[-3..-1] == [2, 6, 5]
assert nums[-1..-3] == [5, 6, 2]

String hello = 'hello'
assert 'olleh' == hello[-1..0] // Strings are collections too

在Groovy中,使用..表示从A到B之前的范围值。在这个范围上,开始于第一个位置,向后遍历至最后一个。负数位置表示从集合最后位置起,向前第几个位置。

Maps使用冒号:来分离键值对。通过方括号[position]来操作map结构中,类似于getAt和putAt方法,具体参考下例:

[A-9. Map实例化方法]

def map = [a:1, b:2, c:2]
assert map.getClass() == LinkedHashMap
// 将值赋值给map中的a键
assert map.a == 1
// 使用putAt方法
assert map['b'] == 2
// 使用getAt方法
assert map.get('c') == 2

###1.4 代码块Closure也称闭包

Groovy中有一个类被称为Closure,表示一块代码可以像一个对象一样使用。Closure类似于java 8中的lambda,将参数或是求值方法作为一个代码块。Groovy closures可以在外部修改定义的变量。

许多方法在Groovy中采用Closure作为参数。例如,集合方法中复制操作,就是一种典型的Closure实例,如下例所示:

[A-10. 使用groovy方便遍历每一个Closure参数]

def nums = [3, 1, 4, 1, 5, 9]

def doubles = [] // 空集合
nums.each { n -> // 取出一个值作为closure参数,变量名为n
	doubles << n * 2 // 左移操作符将内容加入集合
}
assert doubles == [6, 2, 8, 2, 10, 18]

这是一种很常用的方法,将集合数据拷贝到double变量中,但是也有一种更好的替代方法,叫做collect(用户自己调试的时候去掉中文注释)。使用collect方法将集合赋值给一个新的集合。这是一种简单的映射方法,只需要思考如何减少适配映射的过程即可。

[A-11. 使用collect方法,将集合赋给另一个集合]

def nums = [3, 1, 4, 1, 5, 9]
def doubles = nums.collect { it * 2 }
assert doubles == [6, 2, 8, 2, 10, 18]

当closure有一个简单参数,这里也不需要给参数命名,使用箭头操作符it即可。在这上面例子中,collect方法创建了doubles集合并使用it*2将nums中的每一个元素乘以2赋值给doubles变量。在《Gradle Recipes for Android》一书中,A-11代码实例中对变量doubles的赋值使用有错误,读者可以对比关注一下。

###1.4 POGOs

在java中最常使用getters和setters对对象的属性值进行读取和复制。Groovy提供了一个相似类叫做POGOs。如下所示:

[A-12. POGO一个例子]

import groovy.transform.Canonical
[@Canonical](https://my.oschina.net/u/2345489)
class Event {
	String name
	Date when
	int priority
}

这么一个小的类其实有很大的功能。

  • 默认Event类为public
  • 默认参数属性为private
  • 默认方法为public
  • Getter和Setter方法会为每个属性自动生成,不需要在通过public或者private去写
  • 默认构造函数和映射关系(attribute:value)都已经提供

POGO包含@canonical声明,有Abstract Syntax Tree(AST)转换。AST转换修改在编译的时候修改语法树生成。

其中@canonical声明是AST编撰中三种声明的集合:@ToString, @EqualsAndHashCode, and @TupleConstructor。所以@canonical声明在类中做了如下工作:

  • 重写toString方法,跟踪每一个属性值。
  • 重写equals方法,对每一个属性做非安全检查,并对等价做处理。
  • 重写hashCode方法,生成键值映射对。
  • 添加构造函数,并将属性初始化作为参数。

我们来看一下具体是使用,如下:

[A-13. 使用POGO的Event]

import groovy.transform.Canonical
[@Canonical](https://my.oschina.net/u/2345489)
class Event {
        String name
        Date when
        int priority
}
Event e1 = new Event(name: 'Android Studio 1.0',
                when: Date.parse('yyyy/MM/dd', '1973/07/21'), priority: 1)

Event e2 = new Event(name: 'Android Studio 1.0',
                when: Date.parse('yyyy/MM/dd', '1973/07/21'), priority: 1)
println e1.toString()
println e2.toString()

###1.5 Groovy在Gradle编译文件中使用

Gradle构建文件支持Groovy语法。这里简单做个说明,表示Groovy在Gradle中的使用。在Gradle编译文件中applay方法是工程的初始化。方法上括号是可选的,这里省略了。参数被设置一个叫plugin值为'com.android.application'

apply plugin: 'com.android.application'

在下例中,android表示一种数据,有DSL插件支持,使用closure作为一个参数。在closure中的属性方法,包括compileSdkVersion,其中圆括号是可以省略的。在Gradle的编译文件中,属性是需要是用等号=赋值的,等号会调用setter方法。Android插件为compileSdkVersion(23)增加setter方法,如setCompileSdkVersion(23)

android {
	compileSdkVersion 23
	buildToolsVersion "23.0.1"
}

同时,嵌套属性,像compileSdkVersion可以使用点的引用来替代,如下所示:

android.compileSdkVersion = 23

两种效果是一致的。

在最近的版本中,插件中增加了清除任务(clean task)在Gradle构建文件中。任务的名字叫做clean,具体是实例化Delete类,使用closure闭包。非常符合常规的做法,closure使用了圆括号,如下所示:

task clean(type: Delete) {
	delete rootProject.buildDir
}

上面的实现中调用了delete方法,传入参数为rootProject.buildDir。该值为rootProject属性,在工程的最顶层,并且buildDir的默认值为“build”目录,所以该任务是删除顶层工程的“build”目录。

注意:删除顶层工程的目录也会调用app子目录,同时删除子工程的build目录

编译使用了SDL,也就意味着它的参数在编译的时候使用。这里的fileTree方法使用了圆括号。其中的dir参数表示当前目录文件夹。include参数表示了Groovy文件列表。

dependencies {
	compile fileTree(dir: 'libs', include: ['*.jar'])
}

###1.6 额外信息

一本书Making Java Groovy,by Ken Kousen (Manning),讨论Groovy在java中的集成,也有一个章节讲述Gradle构建。Groovy决定性书籍Groovy in Action, Second Edition, by Dierk Konig,Paul King, et al. (Manning).

Groovy主页http://groovy-lang.org/

视频教程Groovy Programming Fundamentals,Practical Groovy Programming,和 Mastering Groovy Programming。

##2 Gradle基础

本文的核心在于介绍在Android环境下使用Gradle构建文件。Gradle是一个强大的构建工具,然后它被广泛的用在其他项目中。本节主要介绍Gradle基础。我们能够在Android构建文件中的使用的所有能力。

###2.1 安装gradle Gradle不依赖于其它部分,直接可通过ZIP下载。我们仅需要下载最新贡献的Gradle,开始使用它,地址为https://gradle.org/。安装起来非常容易:

  1. 下载并解压zip文件
  2. 为解压文件设置GRADLE_HOME环境变量,指向解压文件
  3. 将GRADLE_HOME目录下的bin目录添加到path下。

gradle命令可以在工程的根目录下执行,同时编译很多module。默认情况下编译文件叫做build.gradle,但是其它命名也是可以使用的。使用-b或者--build-file标志用来使用不同的构建文件。

作为替代,Gradle提供了一个封装(wrapper),可以第一次使用的时候自动下载和安装Gradle。该封装默认情况下会使用目录中的最新版本。

由上节我们知道Gradle构建文件是由Groovy语法编写,我们运行Gradle的时候不需要安装Groovy。Gradle内部已经集成了Groovy,可以用来直接构建。

我们想看Gradle的详细信息可以通过:

Gradle -v

输出结果为:

$ gradle -v

------------------------------------------------------------
Gradle 2.4
------------------------------------------------------------

Build time:   2015-05-05 08:09:24 UTC
Build number: none
Revision:     5c9c3bc20ca1c281ac7972643f1e2d190f2c943c

Groovy:       2.3.10
Ant:          Apache Ant(TM) version 1.9.4 compiled on April 29 2014
JVM:          1.7.0_79 (Oracle Corporation 24.79-b02)
OS:           Windows 7 6.1 amd64

###2.2 构建生命周期

Gradle的构建分为三个不同的阶段:

  • 初始化:读取环境配置文件init.gradlegradle.properties,设置所有在settings.gradle中罗列的子工程,也可以叫module。
  • 配置:评估所有的构建脚本并构建模型,包括DAG(directed acyclic graph),算是构建路线图。
  • 执行:执行所有期望执行的任务。

###2.3 Android工程 Gradle构建文件包含很多任务,他们加入到DAG中。Gradle是一个基于插件的体系结构,所以通过添加插件进行构建,我们可以添加任务和功能进行构建。

在Android世界之外最常用的插件就是Java插件。由于Gradle已经支持插件,我们只需要在build.gradle文件中加入apply命令,即可使用插件,如下所示:

apply plugin: 'com.android.application'

事实上,插件自身定义了一系列相关联的任务。查看任务的是否有效,在工程的根目录使用tasks,输出如下所示:

$ gradle tasks
Parallel execution is an incubating feature.
WARNING [Project: :walletapp] Support for libraries with same package name is deprecated and will be removed in a future release.
:tasks

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

Android tasks
-------------
androidDependencies - Displays the Android dependencies of the project.
signingReport - Displays the signing info for each variant.
sourceSets - Prints out all the source sets defined in this project.

Build tasks
-----------
assemble - Assembles all variants of all applications and secondary packages.
assembleAndroidTest - Assembles all the Test applications.
assembleDebug - Assembles all Debug builds.
assembleRelease - Assembles all Release builds.
build - Assembles and tests this project.
buildDependents - Assembles and tests this project and all projects that depend on it.
buildNeeded - Assembles and tests this project and all projects it depends on.
compileDebugAndroidTestSources
compileDebugSources
compileDebugUnitTestSources
compileReleaseSources
compileReleaseUnitTestSources
extractDebugAnnotations - Extracts Android annotations for the debug variant into the archive file
extractReleaseAnnotations - Extracts Android annotations for the release variant into the archive file
mockableAndroidJar - Creates a version of android.jar that's suitable for unit tests.

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 'wallet-app'. [incubating]
dependencies - Displays all dependencies declared in root project 'wallet-app'.
dependencyInsight - Displays the insight into a specific dependency in root project 'wallet-app'.
help - Displays a help message.
model - Displays the configuration model of root project 'wallet-app'. [incubating]
projects - Displays the sub-projects of root project 'wallet-app'.
properties - Displays the properties of root project 'wallet-app'.
tasks - Displays the tasks runnable from root project 'wallet-app' (some of the displayed tasks may belong to subprojects).

Install tasks
-------------
installDebug - Installs the Debug build.
installDebugAndroidTest - Installs the android (on device) tests for the Debug build.
uninstallAll - Uninstall all applications.
uninstallDebug - Uninstalls the Debug build.
uninstallDebugAndroidTest - Uninstalls the android (on device) tests for the Debug build.
uninstallRelease - Uninstalls the Release build.

Verification tasks
------------------
check - Runs all checks.
clean - Deletes the build directory.
connectedAndroidTest - Installs and runs instrumentation tests for all flavors on connected devices.
connectedCheck - Runs all device checks on currently connected devices.
connectedDebugAndroidTest - Installs and runs the tests for debug on connected devices.
deviceAndroidTest - Installs and runs instrumentation tests using all Device Providers.
deviceCheck - Runs all device checks using Device Providers and Test Servers.
lint - Runs lint on all variants.
lintDebug - Runs lint on the Debug build.
lintRelease - Runs lint on the Release build.
test - Run unit tests for all variants.
testDebugUnitTest - Run unit tests for the debug build.
testReleaseUnitTest - Run unit tests for the release build.

Other tasks
-----------
assembleDefault
jarDebugClasses
jarReleaseClasses
transformResourcesWithMergeJavaResForDebugUnitTest
transformResourcesWithMergeJavaResForReleaseUnitTest

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

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

BUILD SUCCESSFUL

Total time: 2.588 secs

任务列表显示哪些是有效的,但是没有展示他们之间的依赖关系。若想知道具体任务的依赖关系可以直接执行该任务(build):

$ gradle build

tasks任务格式是一个有向无环图(directed acyclic graph)。在这种情况下视图展示了任务之间的关系,我们从线上文档提出的一张图来说明,显示Java插件的DAC图。

每一种关系都使用了一种箭头指向了下一个部分,同时他们也是有很多关系的,这里在DAC上没有环路。运行构建task意味着:

  • 执行构建build,check和assemble任务必须被执行。
  • check任务依赖于test任务,test任务又依赖于testClasses和classes等等。

###2.4 仓库与依赖

当前的构建文件定义测试任务,但不是一个测试library。下面Java工程中典型的构建文件格式:

apply plugin: 'java'

repositories {
	jcenter()
}
dependencies {
	testCompile 'junit:junit:4.12'
}

Gradle为构建提供了一种Domain Specific Language (DSL)插件。仓库repositories和dependencies元素是DSL构建文件的一部分。

仓库是库的一种集合,可以通过命令将库文件缓存至本地,具体依赖于用户工程目录下.gradle文件。构建文件的库使用jcenter()方法连接。其它的构建库还有mavenCentral(),对应Maven公共核心库。如果我们的构建文件包含多个仓库,会根据每一个依赖轮流请求。

列出的依赖关系,在dependencies模块。一个依赖包含的库文件的基本信息(组、名称和版本),同时依赖配置是需要的。testCompile作为预定义的依赖配置,对于Java插件来讲,预定义的有:

  • compile
  • runtime
  • testCompile
  • testRuntime
  • archives
  • default

前四个是比较通用的,但是每一个都包含了较丰富的内容。例如,compile可以使整个项目能够进行编译,使得编译库有效,testCompile依赖增加了src/test/java代码树。JDBC驱动需要依赖runtime依赖,testRuntime仅是依赖数据库中的测试部分。

###2.5 自定义任务 Gradle DSL是可拓展的,我们不需要做任何操作,插件以及提供了该功能。迟早,每一次构建都变成自定义构建,Gradle希望我们记住这些。下面我们来看一下增加自定义任务的例子:

def task {
	doLast {
		println 'hello'
	}
}

doLast模块表示代码应该运行的时候执行。一些代码运行在配置的时候。

Gradle也提供了doFirst模块,但是不怎么常用。同时我们可以使用doLast模块的缩写,左移操作符。如下任务运行在执行时。这是很容易忽视语法,所以这种方法是不可取的,尽量写出doLast模块。

def task << {
	println 'hello'
}

Gradle API提供了很多有效的构建任务,也是可以被自定义的。如下所示,定义Copy task。

def copyOutputs(type: Copy) {
	from "$buildDir/outputs/apk"
	into '../results'
}

拷贝任务本身包括配置和执行时间的选择。在这种情况下,设置from和into属性并设置所需要的值。这种方法来配置现有的任务,它有利于告诉Gradle你想要什么,而不是指定执行什么操作。

###2.6 多工程构建

子目录工程作为独立的Gradle项目包含构建文件和依赖。事实上他们可以互相依赖。settings.gradle罗列了Gradle工程的所有子目录。在典型的Android app中,Settings.gradle包含了app目录,表示了应用程序实际代码的真正位置。

在多工程构建文件中,每一个子工程都有一个构建文件。为了在工程建公用模块,可以使用subprojectsallprojects模块,都可以重写配置文件中的Porject类。

##参考

译《Gradle Recipes for Android》-Just Enough Groovy to Get By

Groovy主页http://groovy-lang.org/

android studio

转载于:https://my.oschina.net/feiyangxiaomi/blog/759422

你可能感兴趣的:(java,runtime,python)