CrazyAirhead

疯狂的傻瓜,傻瓜也疯狂——傻方能执著,疯狂才专注!

0%

如何使用Gradle离线编译

目的

无外网环境下使用Gradle离线编译,本文算是对Gradle Offline Build的翻译和补充。

问题

最近的一个项目中,我需要在一个非常严格的环境CI服务器上使用Gradle编译。这些限制是:

  • 没有因特网
  • 没有安装Gradle
  • 没有Maven的本地仓库

显而易见,这些限制将导致项目无法编译,是因为:

  • 没有安装Gradle
  • 无法获取依赖

解决方案

可以通过下面的两步解决无法编译的问题:

  1. 使用Gradle编译
  2. 使用Gradle管理依赖

让我们一步一步的来解决这个问题。

Gradle Wrapper

简单来说,Gradle Wrapper可以让你不需要安装Gradle也可以使用的Gradle来编译的一种方式。它通过运行脚步来下载Gradle执行文件。(查看更多Gradle Wapper的内容

但是CI 服务器没有外网,如何下载Gradle执行文件呢?对于本来就使用Gradle的项目,我们可以将gradle-wrapper.propertiesdistributionUrl修改为本地路径,比如,原有配置:

1
2
3
4
5
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-all.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

修改后配置,其他配置保持不变。

1
distributionUrl=gradle-6.5-all.zip

然后把gradle-6.5-all.zip文件放到${projectDir}/gradle/wrapper目录下,你应该可以通过源码管理来添加。

这样,你可以在没有安装Gradle的情况下也可以使用Gradle编译了。如果是Mac或或者Linux,通过

1
./gradlew clean build

本地依赖

接下来,我们需要通过Gradle管理依赖,并灵活的加载依赖。我们需要一种存储依赖的方式并有条件的把依赖加载进来。

修改build.gradle文件,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
ext.libs = "$projectDir/libs"
ext.compileLib = "${libs}/compile"
ext.runtimeLib = "${libs}/runtime"
ext.testCompileLib = "${libs}/testCompile"
ext.testRuntimeLib = "${libs}/testRuntime"

dependencies {
if (gradle.startParameter.isOffline()) {
compile fileTree(dir: compileLib)
runtime fileTree(dir: runtimeLib)
testCompile fileTree(dir: testCompileLib)
testRuntime fileTree(dir: testRuntimeLib)
} else {
compile 'com.google.guava:guava:18.0'
testCompile group: 'junit', name: 'junit', version: '4.11'
}
}

通过这个配置,如果使用Gradle的offline模式,它会从项目的子目录中加载依赖。否则的话,他会从Gradle Home目录或者下载依赖。我选择offline参数,我觉得是最贴合这个目的的。当然,你也可以使用其他参数。

那如何存储依赖呢?我们可以编写一个copyToLibs任务。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
task deleteLibs(type: Delete) {
delete 'libs/compile'
delete 'libs/runtime'
delete 'libs/testCompile'
delete 'libs/testRuntime'
}

//注意原文中的 task copyToLibs(dependsOn: 'deleteLibs') << {的"<<"会提示错误,可能是老版本的语法。
task copyToLibs(dependsOn: 'deleteLibs') {
['compile', 'runtime', 'testCompile', 'testRuntime'].each { scope ->
copy {
from configurations.getByName(scope).files
into "${libs}/${scope}"
}
}
}

把编译跑通的过程是这样的,在本地开发的时候,在推送的Git无服务器之前,需要先运行copyToLibs,将所有需要的依赖文件拷贝到本地,然后将这些依赖添加到Git中。

在CI服务上,运行./gradlew --offline clean build。你运行的方式可能是不一样的,但一定要确定使用了--offline参数,只有这样Gradle才知道是从本地,而不是从Gradle Home或者因特网下载依赖。

原文作者还很贴心的做了个Demo

碰到问题及处理

虽然原文已经写得很清楚了,但自己编译时还是碰到了一些问题:

  1. 使用非离线模式时,正常编译,文件下载成功了,但使用离线模式时,提示找不到依赖

新版本中copyToLibs任务会随build任务执行,在离线模式时,文件已经被删除了,只留下很多0字节的依赖文件。

暂时的解决办法是在使用离线编译时,将deleteLibscopyToLibs的任务屏蔽掉,当然应该还用其他的办法,我暂时没有尝试。

  1. 在编译Spring Cloud项目时,提示部分依赖找不到。

了解了Gradle离线编译的大致逻辑之后,在线编译打包出来jar包之后,将jar包里面的所有依赖放到本地的目录中,之后编译通过。

  1. 部分代码使用了Lombok插件的@Data注解,但离线编译时提示无法找到对于的getter/setter方法。

临时出来办法,删除@Data注解,通过IDEA的getter/setter生成功能,修改对应的类,重新编译。

  1. 在使用本文方面前,我们尝试拷贝Maven库的方式来处理,但是Gradle会提示Caches里找不到对应的依赖包。

进一步了解Gradle的目录结构,看看是否有更好的离线编译方法。

参考资料

Gradle Offline Build

欢迎关注我的其它发布渠道