搭建自己的Gradle Build Cache Service(基于阿里云OSS)

Gradle深入解析 - Task原理(执行篇)中分析过gradle的缓存机制,知道gradle有本地和远程缓存的区分,它会记录下task的输入输出,可以从缓存中恢复输出来避免重新执行,以加快构建速度,那么我们如何来使用remote build cache部分呢?

使用remote build cache有2种方式

  1. dockerjar包的方式运行
  2. 自己写一个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,顺便记录一下endpointbucket name,没有记下来也没关系,后面可以在概览里面找到

然后在bucket页面找到权限控制 -> RAM访问控制,前往RAM控制台

如果没有用户要先创建用户,至少勾选OpenAPI调用访问

创建用户成功后会跳转到这个页面,这里需要记录下AccessKey IDAccessKey Secret,后面关闭页面就获取不到了

用户创建完后,给用户添加权限

权限选中OSS相关的,保险起见FullAccessReadOnlyAccess都选上了,因为我之前都配置过了所以显示是灰色的

至此阿里云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个非常关键的方法loadstore

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,调用writerwriteTo方法将数据导入到我们的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

默认有enabledpush2个参数

对于OSS来说还不够,以阿里云为例,还需要endpointbucketNamecredentials(主要用于配置accessKeyIdsecretAccessKey)

我们可以通过继承来实现自己的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注册BuildCacheBuildCacheServiceFactory

之后就可以在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

相关推荐
ᥬ 小月亮23 分钟前
Layui表格的分页下拉框新增“全部”选项
android·javascript·layui
sunly_10 小时前
Flutter:启动屏逻辑处理02:启动页
android·javascript·flutter
Sgq丶11 小时前
Android Studio 配置 proto
android·ide·android studio
_小马快跑_14 小时前
ConstraintLayout 中的ImageFilterView探索:处理图片圆角、亮度、饱和度、图片重叠等
android
IT-sec15 小时前
jquery-picture-cut 任意文件上传(CVE-2018-9208)
android·前端·javascript·安全·web安全·网络安全·jquery
xiaoduyyy16 小时前
【Android】RecyclerView回收复用机制
android
林北芒大果16 小时前
【Flutter】搭建Flutter开发环境,安卓开发
android·flutter
m0_7482302118 小时前
MySQL 数据库连接池爆满问题排查与解决
android·数据库·mysql
SunshineBrother18 小时前
Flutter求职、面试20+面试官总结:Dart篇
android·前端·flutter
鸿儒51718 小时前
利用adb工具安装卸载安卓平板(手机)软件
android·adb·智能手机