Gradle复合构建

本文是边看Gradle英文文档边做的笔记:Chapter 11. Composite builds
建议看的时候动手实验一下,帮助理解

什么是复合构建

简单地说,Gradle的复合构建就是一个构建包含了其它构建。
Gradle的复合构建跟多项目构建很相似,
唯一的区别是多项目构建引入的是单个project(引入的java依赖可以使用),
而复合构建引入的是一个完整的构建(除了引入的java依赖,引入的task也可以使用)。

可以先看以下代码示例,有个直观的区分:

多项目构建:

文件:my-app/build.gradle:

dependencies {
    compile project(':util')
    compile project(':web')    
}
//这里假设有一个my-app的构建,my-app构建依赖了util工程和web工程。

复合构建:

文件:my-app/build.gradle:

dependencies {
    compile "org.sample:util:1.0"
    compile "org.sample:web:1.0"
}
//my-app构建没有直接依赖util和web。而是声明了对util和web的构建结果(class文件)的依赖

多项目构建和复合构建非常相似,因此比较难区分,我是从两种构建方式关注点上区分的。

  • 多项目构建关注点是:如何组织多个项目,项目之间往往是存在业务逻辑联系的。比如一个商城项目包含了前台部分和后台管理部分,那可以分成两个子项目来做(fun-shop/front和fun-shop/admin)
  • 复合构建关注点是:如何整合多个独立的项目,被引入的项目是可以独立运行的,但不一定有逻辑联系。复合构建就像万能胶水一样,能整合独立的gradle项目进来。再拿上面的商城举例,如果这个商城项目要引入微信支付,这时公司早就有人写好了一个独立的微信支付调用的模块(就叫awesome-wepay吧,假设人家也是用gradle构建的)。你只需要调用他封装好的简单的代码即可。但是由于人家的awesome-wepay是一个独立项目,而不是一开始就属于你的fun-shop项目(awesome-wepay不是fun-shop的子项目,两者关系是相互独立的),那这个时候,就需要用Gradle的复合构建功能来引入人家的awesome-wepay到你的fun-shop了。

复合构建适用的场景:

  • 对多个独立开发的库进行组合
  • 对一个复杂的多项目构建进行拆分,拆分成多个独立的模块

被复合构建(my-app)引用的构建(util、web),只会向复合构建提供构建的结果,而配置信息是不会被导入到复合构建中的。

如果被复合构建(my-app)包含的构建(util、web)中有依赖可以满足复合构建的依赖,会优先采用(util、web)该依赖。

Gradle默认会自行决定是否采用引入构建的依赖,但是官方建议最好显式声明(11.4, “Declaring the dependencies substituted by an included build”)

复合构建(my-app)可以直接声明对包含的构建(util、web)的task的依赖,
但是被包含的构建(util、web)由于它们是独立的,
被包含的构建(util、web)不能依赖复合构建(my-app)中的task,
也不能依赖其它被包含的构建的task。

如何声明复合构建

首先需要声明复合构建的依赖

下面的多个例子演示如何将2个独立开发的构建组合到一个复合构建里面。
在例子里面,my-utils是一个多项目构建,包含了2个java库的构建(number-utils和string-utils),
而my-app则是一个复合构建,my-app使用了这两个java库里面的函数。
需要注意的是,my-app不是直接依赖my-utils,而是声明对my-utils的两个java库的打包结果的依赖

声明my-app的依赖项:
my-app/build.gradle

apply plugin: 'java'
apply plugin: 'application'
apply plugin: 'idea'

group "org.sample"
version "1.0"

mainClassName = "org.sample.myapp.Main"

dependencies {
    compile "org.sample:number-utils:1.0"//<--依赖number-utils子项目的产出结果
    compile "org.sample:string-utils:1.0"//<--依赖string-utils子项目的产出结果
}

repositories {
    jcenter()
}

方式1:通过gradle命令行的--include-build选项实现复合构建
目录结构是这样的:

/samples/compositeBuilds/basic
  |--my-app
         |--build.gradle
         |--src/main/java
  |--my-utils
         |--number-utils
                  |--src/main/java
         |--string-utils
                  |--src/main/java
         |--build.gradle
         |--setting.gradle

在my-app目录下运行命令行:gradle --include-build ../my-utils run即可实现复合构建
这个命令行意思是:
通过gradle --include-build ../my-utils命令告诉gradle先去执行my-utils的构建,my-utils如无意外会产出两个java库的构建,number-utils和string-utils目录下会出现build目录,build目录就是构建的产出结果。
然后,由于my-app/build.gradle中声明了对number-utils和string-utils产出结果的依赖(就是下面的那部分代码),所以my-app的构建也能成功执行。

dependencies {
    compile "org.sample:number-utils:1.0"//<--依赖number-utils子项目的产出结果
    compile "org.sample:string-utils:1.0"//<--依赖string-utils子项目的产出结果
}

最后,对my-app执行run任务,即运行my-app的代码。

方式2:在setting.gradle文件中使用includeBuild语句声明复合构建

实现如下:

my-app/build.gradle的配置依旧不变

......
dependencies {
    compile "org.sample:number-utils:1.0"//<--依赖number-utils子项目的产出结果
    compile "org.sample:string-utils:1.0"//<--依赖string-utils子项目的产出结果
}
......

my-app/setting.gradle

rootProject.name = 'my-app'
includeBuild '../my-utils'

方式2-补充:不修改my-app/setting.gradle文件实现复合构建

方式2中通过在my-app/setting.gradle文件中添加includeBuild '../my-utils'配置来引入my-utils构建。
如果有特定场景要求不能修改my-app项目的任何文件该怎么办呢?这时可以采取另一种方式来实现,建立一个新的构建(即一个目录),在新构建中引入my-app和my-utils,注意,原来是在my-app中引入my-utils的。

实现如下:

目录结构是这样的:

/samples/compositeBuilds/basic
  |--composite
         |--build.gradle
         |--setting.gradle
  |--my-app
         |--build.gradle
         |--src/main/java
  |--my-utils
         |--number-utils
                  |--src/main/java
         |--string-utils
                  |--src/main/java
         |--build.gradle
         |--setting.gradle

composite/setting.gradle:

//在composite构建中引入my-app构建和my-utils构建
rootProject.name='adhoc'
includeBuild '../my-app'
includeBuild '../my-utils'

由于我们新建了一个名为composite的构建来包含my-app构建和my-utils构建,但是composite构建到目前为止是没有run任务的(原来的my-app构建中定义了mainClassName = "org.sample.myapp.Main"所以能执行run任务),那该怎么办呢?
答案就是我们在composite构建中定义一个的run任务,把该run任务的执行委派给my-app构建的run任务:
composite/build.gradle:

apply plugin: 'idea'

defaultTasks 'run'

task run {
    //composite构建中定义一个run任务,委派给my-app的run任务
    dependsOn gradle.includedBuild('my-app').task(':run')
}

注意:一个构建需要满足什么条件才能被复合构建引入?

  • 必须要有settings.gradle文件
  • 构建本身不能是复合构建
  • 构建的rootProject.name属性不能与其它被引入的构建的该属性相同
  • 构建的rootProject.name属性不能与复合构建的最顶层级工程的该属性相同
  • 构建的rootProject.name属性不能与复合构建的该属性相同

相关推荐