Gradle之多项目与混合构建
Posted Android开发中文站
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Gradle之多项目与混合构建相关的知识,希望对你有一定的参考价值。
Multi-project builds
随着大型的单项目构建发展所面临的各种挑战,多项目构建应运而生。
多项目构建的结构如下:包括一个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
Multi-project builds的特点是:
包含多个独立模块,且每个模块可独自进行构建,如android应用构建;
各个子项目可以拥有独立的构建脚本,注意子项目的构建脚本是非必须的;
根项目的配置文件可以被整个项目所共享,即公共配置应该在根项目的构建脚本声明;
不一定都是build.gradle,也可以是.gradle后缀结尾
注意:不管是单个模块或多个模块task的构建,都会在task执行之前完成所有的构建配置(比如插件下载与使用等)。因此,小的配置的变动都需要重新配置整个项目。虽然gradle提出了Configuration on demand的方式,但稳定性难以保证。
在Multi-project builds中,从配置的作用范围来看,可粗略地分为cross project configuration和subproject configuration两种。
首先是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的方式来实现。
另一是subproject configuration:子项目的配置行为分为两种,不仅能配置项目构建的通用配置,还能实现特定配置、项目过滤配置等功能:
common behavior:公共的配置,同样借助于allprojects和subprojects来完成;
specific behavior:特定配置,可在私有构建脚本上或在公共构建脚本中使用project来完成;
task依赖:无依赖的同名task,执行顺序是字母顺序。为了保证的多个task的执行顺序,可借助于dependsOn来实现有序执行。如下dependsOn的用法:
task action(dependsOn: ":producer:action") { doLast { println("Consuming message: ${rootProject.producerMessage}") } }
属性依赖:配置脚本中的属性同样可能存在读写的顺序依赖,可通过evaluationDependsOn解决这个问题,该evaluationDependsOn能保证依赖的项目会先执行,如下例子:
evaluationDependsOn(':producer') def message = rootProject.producerMessage task consume { doLast { println("Consuming message: " + message) } }
lib依赖:Gradle支持lib依赖,比如jar、aar等,需要借助于gradle的compile来完成依赖的构建,因此构建task的执行顺序也能得以保证。
局部编译:gradle -a build命令可加快编译速度,该命令不会编译依赖项目。
多项目构建的执行方式:
直接切换目录到对应的子项目下,使用gradle命令运行;
在根目录下执行指定子项目的的构建,如gradle :services:webservice:build,注意依赖也会参与执行。
小结:虽然Gradle的依赖管理非常灵活、功能强大,但如果在构建脚本当中滥用allprojects, subprojects、project、evaluationDependsOn等便利方法是会带来了构建项目之间的耦合问题,比较好的解耦方式是使用dependencies进行管理。
Composite builds
面对日益复杂的构建系统,为了解决多项目构建代码的限制,Gradle又提出了Composite builds,称为混合构建。其目的是为了拆分大型的多项目构建。
混合构建的项目结构通常如下,可以认为参与构建的项目都是独立的项目,拥有settings.gradle脚本:
composite ├── rootProject1 │ ├── build.gradle │ ├── settings.gradle │ └── src │ └── main │ └── java │ └── App.java └── rootProject2 ├── build.gradle ├── settings.gradle └── src └── main └── java └── Api.java
混合构建方式有两种使用方式:
命令行方式:gradle –include-build root_project task
Setting.gradle脚本方式:使用includeBuild将外部工程引入。
混合构建的局限性:
不支持native项目
必须包含settings.gradle文件
混合构建不能嵌套
要保证所有参与构建的rootProject.name的唯一性,包括顶层项目名
依赖替换:通过dependencySubstitution完成includeBuild中相关模块的替换。
实例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 :run The answer is 42 BUILD SUCCESSFUL Total time: 1.142 secs
总结,从上述实例来看混合编译有点类似于动态添加构建依赖:my-utils是一独立的项目,并已经发布到了远程仓库。当要频繁的改变my-util源码时,避免不了要编译、发包等繁琐过程,而通过混合编译的方式使得my-utils可直接以源码的方式参与my-app的编译。
Android实践Composite builds
结论:对于library为jar的独立项目与Android项目一起Composite builds是可支持,前提是先发布一个jar到远程或本地仓库,然后在对目标jar包进行compile,之后就可以使用混合编译。
兼容性问题:对于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之多项目与混合构建的主要内容,如果未能解决你的问题,请参考以下文章