Gradle之多项目与混合构建

来源:互联网 发布:ktv小姐怎么出台 知乎 编辑:程序博客网 时间:2024/06/14 11:42

Multi-project builds

  1. 随着大型的单项目构建发展所面临的各种挑战,多项目构建应运而生。

  2. 多项目构建的结构如下:包括一个root顶层项目(包括build和setting脚本),之下再包含一个或多个子项目(包含build脚本):

    root-project├── build.gradle├── gradle│   └── wrapper│       ├── gradle-wrapper.jar│       └── gradle-wrapper.properties├── gradlew├── gradlew.bat├── log├── settings.gradle└── sub-project    ├── build.gradle    └── src        ├── main        │   └── java        │       └── App.java        └── test            └── java                └── AppTest.java
  3. Multi-project builds的特点是:

    • 包含多个独立模块,且每个模块可独自进行构建,如Android应用构建;
    • 各个子项目可以拥有独立的构建脚本,注意子项目的构建脚本是非必须的;
    • 根项目的配置文件可以被整个项目所共享,即公共配置应该在根项目的构建脚本声明;
    • 不一定都是build.gradle,也可以是.gradle后缀结尾
  4. 注意:不管是单个模块或多个模块task的构建,都会在task执行之前完成所有的构建配置(比如插件下载与使用等)。因此,小的配置的变动都需要重新配置整个项目。虽然gradle提出了Configuration on demand的方式,但稳定性难以保证。

  5. 在Multi-project builds中,从配置的作用范围来看,可粗略地分为cross project configuration和subproject configuration两种。

  6. 首先是cross project configuration:构建的配置是跨越脚本而覆盖整个项目的,比如在某个子项目的配置脚本中直接修改另一个子项目的配置脚本:

    Closure cl = { task -> println "I'm $task.project.name" }task('hello').doLast(cl)project(':bluewhale') {    task('hello').doLast(cl)}

    使用allprojects会更简洁,注意:allprojects作用的范围是本项目及其子项目

    allprojects {    task hello {        doLast { task ->            println "I'm $task.project.name"        }    }}

    总之,相比其它的构建系统,gradle的这种配置方式是以configuration injection的方式来实现。

  7. 另一是subproject configuration:子项目的配置行为分为两种,不仅能配置项目构建的通用配置,还能实现特定配置、项目过滤配置等功能:

    • common behavior:公共的配置,同样借助于allprojects和subprojects来完成;
    • specific behavior:特定配置,可在私有构建脚本上或在公共构建脚本中使用project来完成;
  8. task依赖:无依赖的同名task,执行顺序是字母顺序。为了保证的多个task的执行顺序,可借助于dependsOn来实现有序执行。如下dependsOn的用法:

    task action(dependsOn: ":producer:action") {    doLast {        println("Consuming message: ${rootProject.producerMessage}")    }}
  9. 属性依赖:配置脚本中的属性同样可能存在读写的顺序依赖,可通过evaluationDependsOn解决这个问题,该evaluationDependsOn能保证依赖的项目会先执行,如下例子:

    evaluationDependsOn(':producer')def message = rootProject.producerMessagetask consume {    doLast {        println("Consuming message: " + message)    }}
  10. lib依赖:Gradle支持lib依赖,比如jar、aar等,需要借助于gradle的compile来完成依赖的构建,因此构建task的执行顺序也能得以保证。

  11. 局部编译:gradle -a build命令可加快编译速度,该命令不会编译依赖项目。

  12. 多项目构建的执行方式:

    • 直接切换目录到对应的子项目下,使用gradle命令运行;
    • 在根目录下执行指定子项目的的构建,如gradle :services:webservice:build,注意依赖也会参与执行。
  13. 小结:虽然Gradle的依赖管理非常灵活、功能强大,但如果在构建脚本当中滥用allprojects, subprojects、project、evaluationDependsOn等便利方法是会带来了构建项目之间的耦合问题,比较好的解耦方式是使用dependencies进行管理。

Composite builds

  1. 面对日益复杂的构建系统,为了解决多项目构建代码的限制,Gradle又提出了Composite builds,称为混合构建。其目的是为了拆分大型的多项目构建。

  2. 混合构建的项目结构通常如下,可以认为参与构建的项目都是独立的项目,拥有settings.gradle脚本:

    composite├── rootProject1│   ├── build.gradle│   ├── settings.gradle│   └── src│       └── main│           └── java│               └── App.java└── rootProject2    ├── build.gradle    ├── settings.gradle    └── src        └── main            └── java                └── Api.java
  3. 混合构建方式有两种使用方式:

    • 命令行方式:gradle –include-build root_project task
    • Setting.gradle脚本方式:使用includeBuild将外部工程引入。
  4. 混合构建的局限性:

    • 不支持native项目
    • 必须包含settings.gradle文件
    • 混合构建不能嵌套
    • 要保证所有参与构建的rootProject.name的唯一性,包括顶层项目名
  5. 依赖替换:通过dependencySubstitution完成includeBuild中相关模块的替换。

  6. 实例basic工程结构如下图,my-utils项目参与了my-app的混合编译,各自拥有独立构建脚本build.gradle和settings.gradle:
    这里写图片描述
    my-utils的相关settings.gradle:

    rootProject.name = 'my-utils'include 'number-utils', 'string-utils'

    my-utils的相关build.gradle:

    apply plugin: 'idea'subprojects {    apply plugin: 'java'    apply plugin: 'idea'    group "org.sample"    version "1.0"    repositories {        jcenter()    }}project(":string-utils") {    dependencies {        compile "org.apache.commons:commons-lang3:3.4"    }}

    my-app的相关settings.gradle:

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

    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"    compile "org.sample:string-utils:1.0"}repositories {    jcenter()}

    my-app的实例代码,调用了my-utils代码:

    public class Main {    public static void main(String... args) {        new Main().printAnswer();    }    public void printAnswer() {        String output = Strings.concat(" The answer is    ", Numbers.add(19, 23));        System.out.println(output);    }}

    最后命令行编译运行:

    gradle run[composite-build] Configuring build: D:\gradle_demo\compositeBuilds\basic\my-utils:compileJava:my-utils:number-utils:compileJava UP-TO-DATE:my-utils:number-utils:processResources UP-TO-DATE:my-utils:number-utils:classes UP-TO-DATE:my-utils:number-utils:jar UP-TO-DATE:my-utils:string-utils:compileJava UP-TO-DATE:my-utils:string-utils:processResources UP-TO-DATE:my-utils:string-utils:classes UP-TO-DATE:my-utils:string-utils:jar UP-TO-DATE:compileJava UP-TO-DATE:processResources UP-TO-DATE:classes UP-TO-DATE:runThe answer is 42BUILD SUCCESSFULTotal time: 1.142 secs
  7. 总结,从上述实例来看混合编译有点类似于动态添加构建依赖:my-utils是一独立的项目,并已经发布到了远程仓库。当要频繁的改变my-util源码时,避免不了要编译、发包等繁琐过程,而通过混合编译的方式使得my-utils可直接以源码的方式参与my-app的编译。

Android实践Composite builds

  1. 结论:对于library为jar的独立项目与Android项目一起Composite builds是可支持,前提是先发布一个jar到远程或本地仓库,然后在对目标jar包进行compile,之后就可以使用混合编译。

  2. 兼容性问题:对于aar目前暂时无法支持混合编译。如下是报错的信息:

       FAILURE: Build failed with an exception.* What went wrong:A problem occurred configuring project ':app'.> java.lang.NullPointerException (no error message)* Try:Run with --info or --debug option to get more log output.* Exception is:org.gradle.api.ProjectConfigurationException: A problem occurred configuring project ':app'.        at org.gradle.configuration.project.LifecycleProjectEvaluator.addConfigurationFailure(LifecycleProjectEvaluator.java:94)        at org.gradle.configuration.project.LifecycleProjectEvaluator.notifyAfterEvaluate(LifecycleProjectEvaluator.java:89)        at org.gradle.configuration.project.LifecycleProjectEvaluator.doConfigure(LifecycleProjectEvaluator.java:76)        at org.gradle.configuration.project.LifecycleProjectEvaluator.access$000(LifecycleProjectEvaluator.java:33)        at org.gradle.configuration.project.LifecycleProjectEvaluator$1.execute(LifecycleProjectEvaluator.java:53)        at org.gradle.configuration.project.LifecycleProjectEvaluator$1.execute(LifecycleProjectEvaluator.java:50)        at org.gradle.internal.Transformers$4.transform(Transformers.java:169)        at org.gradle.internal.progress.DefaultBuildOperationExecutor.run(DefaultBuildOperationExecutor.java:106)        at org.gradle.internal.progress.DefaultBuildOperationExecutor.run(DefaultBuildOperationExecutor.java:61)        at org.gradle.configuration.project.LifecycleProjectEvaluator.evaluate(LifecycleProjectEvaluator.java:50)        at org.gradle.api.internal.project.DefaultProject.evaluate(DefaultProject.java:628)        at org.gradle.api.internal.project.DefaultProject.evaluate(DefaultProject.java:129)        at org.gradle.execution.TaskPathProjectEvaluator.configure(TaskPathProjectEvaluator.java:35)        at org.gradle.execution.TaskPathProjectEvaluator.configureHierarchy(TaskPathProjectEvaluator.java:62)        at org.gradle.configuration.DefaultBuildConfigurer.configure(DefaultBuildConfigurer.java:38)        at org.gradle.initialization.DefaultGradleLauncher$1.execute(DefaultGradleLauncher.java:161)        at org.gradle.initialization.DefaultGradleLauncher$1.execute(DefaultGradleLauncher.java:158)        at org.gradle.internal.Transformers$4.transform(Transformers.java:169)        at org.gradle.internal.progress.DefaultBuildOperationExecutor.run(DefaultBuildOperationExecutor.java:106)        at org.gradle.internal.progress.DefaultBuildOperationExecutor.run(DefaultBuildOperationExecutor.java:56)        at org.gradle.initialization.DefaultGradleLauncher.doBuildStages(DefaultGradleLauncher.java:158)        at org.gradle.initialization.DefaultGradleLauncher.doBuild(DefaultGradleLauncher.java:119)        at org.gradle.initialization.DefaultGradleLauncher.run(DefaultGradleLauncher.java:102)        at org.gradle.launcher.exec.GradleBuildController.run(GradleBuildController.java:71)        at org.gradle.tooling.internal.provider.ExecuteBuildActionRunner.run(ExecuteBuildActionRunner.java:28)        at org.gradle.launcher.exec.ChainingBuildActionRunner.run(ChainingBuildActionRunner.java:35)        at org.gradle.launcher.exec.InProcessBuildActionExecuter.execute(InProcessBuildActionExecuter.java:41)        at org.gradle.launcher.exec.InProcessBuildActionExecuter.execute(InProcessBuildActionExecuter.java:26)        at org.gradle.tooling.internal.provider.ContinuousBuildActionExecuter.execute(ContinuousBuildActionExecuter.java:75)        at org.gradle.tooling.internal.provider.ContinuousBuildActionExecuter.execute(ContinuousBuildActionExecuter.java:49)        at org.gradle.tooling.internal.provider.ServicesSetupBuildActionExecuter.execute(ServicesSetupBuildActionExecuter.java:44)        at org.gradle.tooling.internal.provider.ServicesSetupBuildActionExecuter.execute(ServicesSetupBuildActionExecuter.java:29)        at org.gradle.launcher.daemon.server.exec.ExecuteBuild.doBuild(ExecuteBuild.java:67)        at org.gradle.launcher.daemon.server.exec.BuildCommandOnly.execute(BuildCommandOnly.java:36)        at org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:120)        at org.gradle.launcher.daemon.server.exec.WatchForDisconnection.execute(WatchForDisconnection.java:47)        at org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:120)        at org.gradle.launcher.daemon.server.exec.ResetDeprecationLogger.execute(ResetDeprecationLogger.java:26)        at org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:120)        at org.gradle.launcher.daemon.server.exec.RequestStopIfSingleUsedDaemon.execute(RequestStopIfSingleUsedDaemon.java:34)        at org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:120)        at org.gradle.launcher.daemon.server.exec.ForwardClientInput$2.call(ForwardClientInput.java:74)        at org.gradle.launcher.daemon.server.exec.ForwardClientInput$2.call(ForwardClientInput.java:72)        at org.gradle.util.Swapper.swap(Swapper.java:38)        at org.gradle.launcher.daemon.server.exec.ForwardClientInput.execute(ForwardClientInput.java:72)        at org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:120)        at org.gradle.launcher.daemon.server.exec.LogAndCheckHealth.execute(LogAndCheckHealth.java:55)        at org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:120)        at org.gradle.launcher.daemon.server.exec.LogToClient.doBuild(LogToClient.java:60)        at org.gradle.launcher.daemon.server.exec.BuildCommandOnly.execute(BuildCommandOnly.java:36)        at org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:120)        at org.gradle.launcher.daemon.server.exec.EstablishBuildEnvironment.doBuild(EstablishBuildEnvironment.java:72)        at org.gradle.launcher.daemon.server.exec.BuildCommandOnly.execute(BuildCommandOnly.java:36)        at org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:120)        at org.gradle.launcher.daemon.server.exec.StartBuildOrRespondWithBusy$1.run(StartBuildOrRespondWithBusy.java:50)        at org.gradle.launcher.daemon.server.DaemonStateCoordinator$1.run(DaemonStateCoordinator.java:297)        at org.gradle.internal.concurrent.ExecutorPolicy$CatchAndRecordFailures.onExecute(ExecutorPolicy.java:54)        at org.gradle.internal.concurrent.StoppableExecutorImpl$1.run(StoppableExecutorImpl.java:40)Caused by: java.lang.NullPointerException        at com.android.build.gradle.internal.DependencyManager.addDependency(DependencyManager.java:745)        at com.android.build.gradle.internal.DependencyManager.resolveConfiguration(DependencyManager.java:378)        at com.android.build.gradle.internal.DependencyManager.resolveDependencies(DependencyManager.java:263)        at com.android.build.gradle.internal.DependencyManager.resolveDependencies(DependencyManager.java:166)        at com.android.build.gradle.internal.TaskManager.resolveDependencies(TaskManager.java:375)        at com.android.build.gradle.internal.VariantManager.lambda$createVariantData$3(VariantManager.java:607)        at com.android.builder.profile.ThreadRecorder.record(ThreadRecorder.java:81)        at com.android.build.gradle.internal.VariantManager.createVariantData(VariantManager.java:603)        at com.android.build.gradle.internal.VariantManager.createVariantDataForProductFlavors(VariantManager.java:793)        at com.android.build.gradle.internal.VariantManager.populateVariantDataList(VariantManager.java:469)        at com.android.builder.profile.ThreadRecorder.record(ThreadRecorder.java:81)        at com.android.build.gradle.internal.VariantManager.createAndroidTasks(VariantManager.java:263)        at com.android.build.gradle.BasePlugin.lambda$createAndroidTasks$6(BasePlugin.java:601)        at com.android.builder.profile.ThreadRecorder.record(ThreadRecorder.java:81)        at com.android.build.gradle.BasePlugin.createAndroidTasks(BasePlugin.java:596)        at com.android.build.gradle.BasePlugin.lambda$null$4(BasePlugin.java:526)        at com.android.builder.profile.ThreadRecorder.record(ThreadRecorder.java:81)        at com.android.build.gradle.BasePlugin.lambda$createTasks$5(BasePlugin.java:522)        at org.gradle.internal.event.BroadcastDispatch$ActionInvocationHandler.dispatch(BroadcastDispatch.java:93)        at org.gradle.internal.event.BroadcastDispatch$ActionInvocationHandler.dispatch(BroadcastDispatch.java:82)        at org.gradle.internal.event.AbstractBroadcastDispatch.dispatch(AbstractBroadcastDispatch.java:44)        at org.gradle.internal.event.BroadcastDispatch.dispatch(BroadcastDispatch.java:79)        at org.gradle.internal.event.BroadcastDispatch.dispatch(BroadcastDispatch.java:30)        at org.gradle.internal.dispatch.ProxyDispatchAdapter$DispatchingInvocationHandler.invoke(ProxyDispatchAdapter.java:93)        at com.sun.proxy.$Proxy16.afterEvaluate(Unknown Source)        at org.gradle.configuration.project.LifecycleProjectEvaluator.notifyAfterEvaluate(LifecycleProjectEvaluator.java:82)        ... 56 moreBUILD FAILEDTotal time: 1.518 secsTask spend time:
原创粉丝点击