Gradle之多项目与混合构建

Posted Android开发中文站

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Gradle之多项目与混合构建相关的知识,希望对你有一定的参考价值。

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的特点是:

    1. 包含多个独立模块,且每个模块可独自进行构建,如android应用构建;

    2. 各个子项目可以拥有独立的构建脚本,注意子项目的构建脚本是非必须的;

    3. 根项目的配置文件可以被整个项目所共享,即公共配置应该在根项目的构建脚本声明;

    4. 不一定都是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:子项目的配置行为分为两种,不仅能配置项目构建的通用配置,还能实现特定配置、项目过滤配置等功能:

    1. common behavior:公共的配置,同样借助于allprojects和subprojects来完成;

    2. 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.producerMessage
    
    task consume {
        doLast {
            println("Consuming message: " + message)
        }
    }
  10. lib依赖:Gradle支持lib依赖,比如jar、aar等,需要借助于gradle的compile来完成依赖的构建,因此构建task的执行顺序也能得以保证。

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

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

    1. 直接切换目录到对应的子项目下,使用gradle命令运行;

    2. 在根目录下执行指定子项目的的构建,如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. 混合构建方式有两种使用方式:

    1. 命令行方式:gradle –include-build root_project task

    2. Setting.gradle脚本方式:使用includeBuild将外部工程引入。

  4. 混合构建的局限性:

    1. 不支持native项目

    2. 必须包含settings.gradle文件

    3. 混合构建不能嵌套

    4. 要保证所有参与构建的rootProject.name的唯一性,包括顶层项目名

  5. 依赖替换:通过dependencySubstitution完成includeBuild中相关模块的替换。

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

    my-utils的相关settings.gradle:

  7. 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
    :run
    The answer is 42
    
    BUILD SUCCESSFUL
    
    Total time: 1.142 secs
  8. 总结,从上述实例来看混合编译有点类似于动态添加构建依赖: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 more
    
    BUILD FAILED
    
    Total time: 1.518 secs
    Task spend time:


以上是关于Gradle之多项目与混合构建的主要内容,如果未能解决你的问题,请参考以下文章

下一代构建工具:Gradle

Gradle多项目构建与jar包发布

Intellij 处理混合项目(maven+gradle)

Android java项目添加kotlin混合开发环境配置

Flutter混合开发实战

跟Maven一样流行的Gradle,构建Java项目实战