Gradle 历险记(五):多模块的构建方式

最近一周都在搞这玩意了,写点文档给后人参考吧。

一、Gradle 对模块的描述

这里使用2个例子,只有一个模块,例如使用 Intellij 创建的新工程;只有两个模块(父子关系),例如使用 AndroidStudio 创建的默认 App。

1
2
3
4
5
6
7
8
9
10
11
12
➜  HelloGradle lsa
total 40
drwxr-xr-x 12 leadroyal staff 384B 7 19 18:04 .
drwxr-xr-x 7 leadroyal staff 224B 7 21 21:09 ..
drwxr-xr-x 4 leadroyal staff 128B 7 12 14:13 .gradle
drwxr-xr-x 7 leadroyal staff 224B 7 19 18:08 .idea
-rw-r--r-- 1 leadroyal staff 492B 7 19 18:04 build.gradle
drwxr-xr-x 3 leadroyal staff 96B 7 12 14:13 gradle
-rwxr-xr-x 1 leadroyal staff 5.2K 7 12 14:13 gradlew
-rw-r--r-- 1 leadroyal staff 2.2K 7 12 14:13 gradlew.bat
-rw-r--r-- 1 leadroyal staff 34B 7 12 14:13 settings.gradle
drwxr-xr-x 4 leadroyal staff 128B 7 18 15:55 src
  • .gradle 存放一些临时的东西,要 gitignore 掉
  • .idea intellij全家桶的临时文件,要 gitignore 掉
  • build.gradle 必备,描述当前模块的主要信息,经常需要编辑(例如sourceSets、plugin、dependencies等)
  • gradle 必备,存放描述当前 gradle 版本的文件和一个 wrapper
  • gradlew、gradlew.bat 必备,是启动脚本
  • settings.gradle 非必备,但经常有,描述模块的名称、目录
  • src,非必须,只是一般这么命名了,用来存放代码

再看看build.gradle 的内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
plugins {
id 'java'
}


group 'com.leadroyal'
version '1.0-SNAPSHOT'

sourceCompatibility = 1.8

repositories {
mavenCentral()
}

jar {
from {
configurations.runtime.collect { zipTree(it) }
}
manifest {
attributes 'Main-Class': "Hello"
}
}
dependencies {
testCompile group: 'junit', name: 'junit', version: '4.12'
compile 'com.qcloud:cos\_api:5.4.4'
compile group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: '2.9.5'

}

plugins是使用的插件,常见的有java、scala、shadow、springboot、maven,这种。

repositories是仓库,表示寻找依赖的地方,例如mavenCentrol、jCenter、mavenLocal这种。

jar 是指执行名为 "jar"的这个task 时需要做什么,这里写法不优雅,不要模仿,就是打包。

dependencies 是依赖,compile 是老一点的写法了,新写法是 implement,但还没有被废弃,compile 有两种格式。这个地方可能会 exclude 一些依赖中的依赖,或者 force 指定依赖版本。如果是依赖另一个本地 gradle 模块的话,使用compile project(":sub")这种格式。

再看看Android App的目录结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
➜  ctfposed lsa
total 88
drwxr-xr-x 18 leadroyal staff 576B 6 7 14:12 .
drwxr-xr-x 12 leadroyal staff 384B 6 1 16:00 ..
drwxr-xr-x 4 leadroyal staff 128B 4 20 2017 .gradle
drwxr-xr-x 12 leadroyal staff 384B 6 7 16:03 .idea
drwxr-xr-x 12 leadroyal staff 384B 5 28 10:55 app
-rw-r--r-- 1 leadroyal staff 498B 4 20 2017 build.gradle
-rw-r--r-- 1 leadroyal staff 862B 2 25 2017 ctfposed.iml
drwxr-xr-x 3 leadroyal staff 96B 2 25 2017 gradle
-rw-r--r-- 1 leadroyal staff 932B 11 28 2017 gradle.properties
-rwxr--r-- 1 leadroyal staff 4.9K 2 25 2017 gradlew
-rw-r--r-- 1 leadroyal staff 2.3K 2 25 2017 gradlew.bat
drwxr-xr-x 3 leadroyal staff 96B 2 3 17:35 lib
-rw-r--r-- 1 leadroyal staff 523B 2 25 2017 local.properties
-rw-r--r-- 1 leadroyal staff 15B 2 25 2017 settings.gradle

文件内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
➜  ctfposed cat build.gradle
// Top-level build file where you can add configuration options common to all sub-projects/modules.

buildscript {
repositories {
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:2.3.0'

// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}

allprojects {
repositories {
jcenter()
}
}

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

这个说了等于没说,里面啥也没。

再看看 settings.gradle,原来引用了名叫":app"的子模块,所以要到"app"这个目录下去找。

1
2
➜  ctfposed cat settings.gradle
include ':app'

看看子模块的目录,刚好和之前的一样,但是没有 settings.gradle。

1
2
3
4
5
6
7
8
9
10
11
12
13
➜  ctfposed lsa app
total 72
drwxr-xr-x 12 leadroyal staff 384B 5 28 10:55 .
drwxr-xr-x 18 leadroyal staff 576B 6 7 14:12 ..
drwxr-xr-x 3 leadroyal staff 96B 4 20 2017 .externalNativeBuild
-rw-r--r-- 1 leadroyal staff 7B 2 25 2017 .gitignore
-rw-r--r-- 1 leadroyal staff 343B 5 30 2017 CMakeLists.txt
-rw-r--r-- 1 leadroyal staff 11K 5 28 10:55 app.iml
drwxr-xr-x 6 leadroyal staff 192B 2 3 17:38 build
-rw-r--r-- 1 leadroyal staff 1.0K 4 14 23:06 build.gradle
drwxr-xr-x 5 leadroyal staff 160B 4 14 23:06 libs
-rw-r--r-- 1 leadroyal staff 667B 2 25 2017 proguard-rules.pro
drwxr-xr-x 5 leadroyal staff 160B 2 25 2017 src

看看里面的内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
➜  ctfposed cat app/build.gradle
apply plugin: 'com.android.application'

android {
compileSdkVersion 25
buildToolsVersion "25.0.2"
defaultConfig {
applicationId "com.leadroyal.ctfposed"
minSdkVersion 16
targetSdkVersion 25
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
ndk {
// Specifies the ABI configurations of your native
// libraries Gradle should build and package with your APK.
abiFilters 'armeabi-v7a'
}

}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
externalNativeBuild {
cmake {
path "CMakeLists.txt"
}
}
}

dependencies {
provided files('libs/XposedBridgeApi-54.jar')
provided files('libs/hotposed.jar')
compile 'com.android.support:appcompat-v7:25.1.0'
compile files('libs/core-1.58.0.0.jar')
}

嗯,使用了 com.android.application 插件,写了 android 这个东西,写了dependencies这个依赖。很清晰!

二、子模块使用独立的 build.gradle 的目录结构

最直观的就是 android 这个例子了,只有3个gradle文件。

1
2
3
4
5
6
7
➜  HelloWidget find . | grep \\w\\+\\.gradle
./app/build.gradle
./build.gradle
./settings.gradle

➜ HelloWidget cat ./settings.gradle
include ':app'

父模块里,settings.gradle 里写了子模块,叫 :app ,没有特别说明路径的话,就在app这个路径下。

子模块没有settings.gradle,这个无所谓,可有可无,也只是写几个名字而已。这也是我们最常见的格式,一父一子,父模块里写一些通用的设置,大部分代码都在子模块里。

再看另一个例子,我们手动创建Intellij项目,再多创建几个子模块。它们之间暂时没有依赖关系,相互虽然可能在 intellij 里会有提示,其实是相互不可见的那种。

这个 demo,在intellij里面操作非常方便,先创建空的 gradle 项目,会帮我们生成build.gradle 和 setting.gradle。

之后在里面创建新的gradle模块,选择Java类型,帮助我们自动生成 java 模板的 build.gradle。依法炮制,多创建几个java类型的 module,它们并没有什么联系,是相互独立的子项目。

1
2
3
4
5
6
➜  simple-gradle find . | grep \\w\\+\\.gradle
./part3/build.gradle
./part2/build.gradle
./part1/build.gradle
./build.gradle
./settings.gradle

这时候可以发现settings.gradle已经悄悄将它们 include 进来了。

1
2
3
4
5
➜ cat settings.gradle
rootProject.name = 'simple-gradle'
include 'part1'
include 'part2'
include 'part3'

在 build.gradle 里仍然是最开始的设置,这是第一种结构,子模块拥有独立的 build.gradle 文件。

右侧截图如图

三、子模块使用父模块的 build.gradle 的目录结构

还是上面那个例子,我们改成只使用父模块的一个 build.gradle 文件的格式。

改之前:

1
2
3
➜  simple-gradle cat build.gradle 
group 'com.leadroyal'
version '1.0-SNAPSHOT'

基本什么都没有。

改之后:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
➜  simple-gradle cat build.gradle
allprojects {
group 'com.leadroyal'
version '1.0-SNAPSHOT'
}
subprojects {
apply plugin: "java"
sourceCompatibility = 1.8
repositories {
mavenCentral()
}
}
project(":part1") {
dependencies {
testCompile group: 'junit', name: 'junit', version: '4.12'
}
}
project(":part2") {
dependencies {
testCompile group: 'junit', name: 'junit', version: '4.12'
}
}
project(":part3") {
dependencies {
testCompile group: 'junit', name: 'junit', version: '4.12'
}
}
project(":full") {
dependencies {
testCompile group: 'junit', name: 'junit', version: '4.12'
}
}

这里需要使用 project(":part1") 这种方式来引入子模块,这样就可以删掉原来在分散的 build.gradle 的各种配置。

同样,在右侧可以看到合理的结构。

这里还可以使用 allprojects 和 subprojects 来批量进行一些操作,非常舒服。区别在于,allprojects 包括自己,而subprojects 不包括自己。

四、子模块之间如何相互依赖

还是以这part1/2/3为例,假设1独立,3依赖2。再写个总的,同时依赖1/2/3。 平时我们依赖一般都是依赖 maven 库,这里依赖本地的 project 的话,要用

compile project(":project-name")

例如我们刚刚说的这个例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
➜  simple-gradlecat build.gradle 
allprojects {
group 'com.leadroyal'
version '1.0-SNAPSHOT'
}
subprojects {
apply plugin: "java"
sourceCompatibility = 1.8
repositories {
mavenCentral()
}
}
project(":part1") {
dependencies {
testCompile group: 'junit', name: 'junit', version: '4.12'
}
}
project(":part2") {
dependencies {
testCompile group: 'junit', name: 'junit', version: '4.12'
}
}
project(":part3") {
dependencies {
compile project(":part2")
testCompile group: 'junit', name: 'junit', version: '4.12'
}
}
project(":full") {
dependencies {
compile project(":part1")
compile project(":part2")
compile project(":part3")
testCompile group: 'junit', name: 'junit', version: '4.12'
}
}

写了4个类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
package com.example.pkg1;

public class Part1 {
}

package com.example.pkg2;

public class Part2 {
}

package com.example.pkg3;

import com.example.pkg2.Part2;

public class Part3 extends Part2 {
}

package com.example.full;

import com.example.pkg1.Part1;
import com.example.pkg2.Part2;
import com.example.pkg3.Part3;

public class Main {
public void func() {
Part1 p1 = new Part1();
Part2 p2 = new Part2();
Part3 p3 = new Part3();
}
}

这个配置是可以正常编译通过的。

五、命令行里的一些操作

./gradlew build

使用当前项目提供的 gradle 版本和配置。

gradle build

使用系统提供的 gradle 版本和配置。

gradle dependencies

打印所有编译选项的依赖。

gradle dependencies -q --configuration runtime

仅打印runtime执行时的依赖。

gradle part1:build

执行 part1这个 project 的 build 任务。