在Gradle深入解析 - Task原理(执行篇)中分析过gradle的缓存机制,知道gradle有本地和远程缓存的区分,它会记录下task的输入输出,可以从缓存中恢复输出来避免重新执行,以加快构建速度,那么我们如何来使用remote build cache部分呢?
使用remote build cache有2种方式
- docker 或jar包的方式运行
- 自己写一个build cache service
2种方式有什么区别呢?
个人感觉第一种比较适合小团队,小团队人数不多,一起开发一个项目,docker运行服务在一个服务机器上就行,对于大型团队来说,每个项目都配置一次docker比较麻烦,也不容易将不同团队数据隔离
自己写一个插件来实现build cache service可以解决这个问题,build cache service和自己的OSS服务器通信,自己的OSS服务器可以隔离不同团队(不同的团队申请不同的Bucket),而且不同每次重新配置docker服务,只需要apply一下plugin,然后改一下配置即可,非常方便
本篇针对第二种方式进行讲解,第一种方式可以参考以下一些文章的相关部分
Build Cache Node User Manual | Gradle Enterprise Docs
使用本地和远程的Gradle构建缓存加快构建速度
Android-Studio编译速度优化
Using Gradle Build Cache Server
google androidx团队开源了一个GitHub - androidx/gcp-gradle-build-cache,就是利用Google Cloud Storage来实现这个需求的
但是国内最大的OSS服务商还是阿里云,这个插件不支持,于是我仿照它的实现写了一个基于阿里云OSS的插件GitHub - neas-neas/aliyun-gradle-build-cache,用法类似
下面我们就从这个插件的用法展开,详细讲解如何创建一个阿里云OSS Bucket,如何配置gradle文件,后面再简单测试一下效果讲起
再分析其实现具体实现过程,你也可以基于自己公司的OSS服务实现一个自己的build cache server
阿里云OSS有一定的免费额度,超过额度会收费,你可以在简单的项目里面测试
使用方法
1. 创建阿里云OSS Bucket
首先去阿里云OSS官网创建OSS Bucket oss.console.aliyun
点击立即创建开始创建Bucket,顺便记录一下endpoint 和bucket name,没有记下来也没关系,后面可以在概览里面找到
然后在bucket页面找到权限控制 -> RAM访问控制,前往RAM控制台
如果没有用户要先创建用户,至少勾选OpenAPI调用访问
创建用户成功后会跳转到这个页面,这里需要记录下AccessKey ID 和AccessKey Secret,后面关闭页面就获取不到了
用户创建完后,给用户添加权限
权限选中OSS相关的,保险起见FullAccess 和ReadOnlyAccess都选上了,因为我之前都配置过了所以显示是灰色的
至此阿里云OSS的Bucket就创建完成了,现在就可以去项目中进行相关的配置
2. 配置gradle
在settings.gradle 中添加如下配置,强调一下是settings,kts脚本类似这里就不列出来了
groovy
buildscript {
repositories {
mavenCentral()
}
dependencies {
classpath 'io.github.neas-neas:alibuildcache:1.0.0'
}
}
apply plugin: 'io.github.neas-neas.alibuildcache'
import io.github.neas.AliyunBuildCache
import io.github.neas.AliyunBuildCacheServiceFactory
import io.github.neas.ExportedAliyunCredentials
buildCache {
registerBuildCacheService(AliyunBuildCache, AliyunBuildCacheServiceFactory)
remote(AliyunBuildCache) {
endpoint = 'your-aliyun-oss-endpoint'
bucketName = "bucket-name"
credentials = new ExportedAliyunCredentials("your-access-key-id", "your-secret-key")
push = true //System.getenv().containsKey("CI")
}
}
新方式引入插件如下,这种方式要上传到gradle plugin portal,文章发布时还没有审核完毕(太慢了他们),等审核完才能使用,目前只能使用上面那种旧方式
bash
plugins {
id("io.github.neas-neas.alibuildcache") version "1.0.0"
}
然后之前在创建Bucket时记录的信息这里就需要用上了
endpoint
bucketName
accessKeyId
secretKey
和我们记录时的参数名字一样,不需要过多解释了,对座入号填入即可
照官方推荐push
只在CI机器上开,developer的机器拉缓存就行,不过为了测试我们这里先改为true好了
3. 测试效果
所有都配置好了,我们来测试一下看看情况
shell
rm -r ~/.gradle/caches/build-cache-1
./gradlew clean build -i
rm删除掉本地build cache 缓存,因为我们要看remote cache ,本地缓存优先级更高,所以要排除它 -i
可以在输出中看到相关日志
第一次执行,看看compileJava
输出
因为没有缓存,所以第一次执行会store到阿里云OSS
再执行一下上面的命令,再看看compileJava
的输出
因为没有本地缓存(被我们提前rm了,不rm的话第一步执行时也会在本地进行保存的),我们可以看到compileJava
task被标记为了FROM_CACHE,2次的cache key完全一致,而且日志输出了是从阿里云OSS下载下来的
有这个key,我们也可以去阿里云OSS Bucket文件管理页面找到对应的文件,将它下载下来看看它到底是什么
其实它就是个tar包压缩文件,可以进行解压看看里面到底有什么,这个RemoteBuildCache
就是我测试项目里面的代码文件了,你也可以对你的class文件反编译看看是否一致。这部分在Gradle深入解析 - Task原理(执行篇)其实有讲过了
此外针对android项目建议同时使用上GitHub - gradle/android-cache-fix-gradle-plugin: Gradle plugin that fixes Android build caching problems,agp插件对cache的实现有些问题,这个插件可以规避一些此类的问题
具体实现
关系图示意如下
核心方法
BuildCacheService
最主要的是实现BuildCacheService
这个接口,接口有2个非常关键的方法load
和store
java
public interface BuildCacheService extends Closeable {
boolean load(BuildCacheKey key, BuildCacheEntryReader reader) throws BuildCacheException;
void store(BuildCacheKey key, BuildCacheEntryWriter writer) throws BuildCacheException;
void close() throws IOException;
}
load
就是拿key去请求OSS获取文件,然后将获取到的InputStream对接给reader
store
就是将文件保存到OSS,调用writer
的writeTo
方法将数据导入到我们的OutputStream,再将OutputStream转为InputStream传给OSS服务器
阿里云OSS有提供SDK,按照其SDK文档就行对应方法调用即可
BuildCache
java
public interface BuildCache {
boolean isEnabled();
void setEnabled(boolean enabled);
boolean isPush();
void setPush(boolean enabled);
}
BuildCache 是用于配置BuildCacheService
的,类似于写gradle plugin时的extension
默认有enabled
和push
2个参数
对于OSS来说还不够,以阿里云为例,还需要endpoint
、bucketName
、credentials
(主要用于配置accessKeyId
和secretAccessKey
)
我们可以通过继承来实现自己的BuildCache类,比如
kotlin
abstract class AliyunBuildCache(
) : AbstractBuildCache() {
lateinit var endpoint: String
var credentials: AliyunCredentials = DefaultAliyunCredentials
}
BuildCacheServiceFactory
java
public interface BuildCacheServiceFactory<T extends BuildCache> {
BuildCacheService createBuildCacheService(T configuration, Describer describer);
}
这个接口是用来创建BuildCacheService
,看看AliyunBuildCacheServiceFactory
的实现,调用describer
结合buildCache
进行一些描述性质的配置,然后用buildCache
去创建AliyunBuildCacheService
实例 ![[aliyun-gradle-plugin-factory.png]]
Describer
java
interface Describer {
Describer type(String type);
Describer config(String name, String value);
}
用于描述BuildCacheService
type
网络请求的是http,本地的是directory,自定义的比如我这里是aliyun
config
配置一些tag,例如bucketName的值,endpoint
这些信息可以被build scan插件收集,对于判断缓存命中率有一定帮助
建立关联
需要在Plugin
里面去绑定关系
kotlin
class AliyunGradleBuildCachePlugin : Plugin<Settings> {
override fun apply(settings: Settings) {
settings.buildCache.registerBuildCacheService(
AliyunBuildCache::class.java,
AliyunBuildCacheServiceFactory::class.java
)
}
}
注意这是一个针对Setting
的Plugin,调用registerBuildCacheService
注册BuildCache
和BuildCacheServiceFactory
之后就可以在settings.gradle
脚本对BuildCache
进行配置了,例如上面提到过的
groovy
buildCache {
remote(AliyunBuildCache) {
endpoint = 'your-end-point'
bucketName = "bucket-name"
credentials = new ExportedAliyunCredentials("your-access-key-id", "your-secret-key")
push = true
}
}
至此关于Build Cache 的使用和原理大致都进行了说明,Build Cache 在实际使用中如果感觉效果不佳,那还需要从多个方面去提升它的缓存命中率,有几个对其命中率有影响的因素,例如执行gradle的JVM的版本不同,Java/Kotlin编译时的JVM版本不同,classpath变更等,可能需要通过build scan 等构建信息收集方式去进行具体分析,以提升缓存命中率。关于构建信息的收集可以参考我之前的如何像build scan一样收集gradle构建信息
参考资料
GitHub - androidx/gcp-gradle-build-cache
Docker
使用本地和远程的Gradle构建缓存加快构建速度
Android-Studio编译速度优化
Using Gradle Build Cache Server. Fastest way to work is to avoid doing... | by César Ferreira | Medium
Build Cache Node User Manual | Gradle Enterprise Docs
GitHub - rnett/github-actions-gradle-cache