
简介Gradle 8.6 全量发行包面向 Java、Android 及其他 JVM 语言开发者也适合需要研究构建工具内部实现、缓存机制与插件体系的进阶用户。压缩包约 209.99MB共 2000 个文件其中以 1962 个 Java 源文件为主体辅以 34 个 properties 配置、3 个 txt 与 1 个 pdf 说明文档便于从源码、配置和文档多个层面理解构建流程与模块组织。内容围绕自定义加密密钥配置缓存、构建初始化脚手架、构建创作 API 更新及性能优化等改进展开读者可据此考察缓存安全策略、项目初始化模板、插件生命周期、依赖解析与任务执行逻辑并对照核心实现梳理构建自动化中的关键设计。已有 1673 人学习适合希望深入掌握 Gradle 8.6 构建能力、排查复杂构建问题或开发高质量插件的开发者参考也为离线查阅、源码研读与构建工具二次研究提供完整样本。1. gradle-8.6-all.zip 不是绿色安装包先想清楚它该落在哪个目录又见到有人把 gradle-8.6-all.zip 解压到 D 盘根目录再把环境变量指向它然后构建是跑起来了可换一台机器就得从头再摆一遍。这个压缩包属于 Gradle 官方发行版的可解压分发包并不是那种开箱即用的绿色软件-all相比-bin多带了 Gradle 自身的源码和 DSL 文档。它真正有价值的地方是能被放进 wrapper 的缓存目录让一台连不上外网的机器用 gradle-8.6 跑完整构建。适合三类人要写自定义 Gradle 插件、要在离线环境里复现构建、以及被 IDE 反复下载发行版折腾过的从业者。2. -all、-bin 与 wrapper 缓存三个必须先分清的概念下载之前如果不把这三件事分清后面大概率会出现「包明明在本地构建时还是去网上下载」的情况。那并不是玄学而是 Gradle 的发行版缓存按 URL 做了隔离你以为它按版本号存放其实它的实际机制和你预想的并不一样。2.1 -all 多出来的 src 和 docs 到底给谁用解开压缩包看目录结构最直观bin是启动脚本lib是全部 jargradle-core、gradle-api、kotlin-dsl 都在里面init.d放初始化脚本LICENSE和NOTICE是协议文件。-all额外多出两样——docs是 DSL 参考和 javadocsrc是 Gradle 自己的源码。这两样东西平时用不上但在两个场景里价值很高。第一个是写自定义插件在buildSrc或者独立插件工程里实现PluginProject时你在 IDE 里按住 Ctrl 点进project.getTasks()-bin给你的是反编译出来的占位代码-all给的是带注释的真实实现。第二个是排查 Gradle 内部行为比如 configuration cache 序列化失败时报了一串莫名其妙的类型你能直接翻源码确认它是哪一步做的检查而不是靠猜。维度gradle-8.6-bin.zipgradle-8.6-all.zip压缩包内容bin、lib、init.d、协议文件在 bin 全部内容之外多出 docs 与 srcIDE 里点进 Gradle API反编译占位代码真实源码与注释离线查 DSL 参考只能靠记忆docs 目录里是完整文档写自定义插件的调试体验断点能打读源码难受断点与源码对照着看缓存目录由 distributionUrl 决定与 bin 完全隔离互不复用选型就这么简单纯业务项目、只跑build、test、publish用-bin就够工程里有buildSrc、有自研插件、或者你经常需要在离线环境里翻文档直接上-all多出来的体积换来的是长期可读性。我一般给团队的标准模板就写-all省得后面有人要写插件时再回头换。2.2 wrapper 缓存目录名是一串 Base36不是版本号这是最容易出错的一点。wrapper 把发行版放在${GRADLE_USER_HOME}/wrapper/dists/gradle-8.6-all/hash/最后那级hash不是时间戳也不是随机数而是distributionUrl 字符串的 MD5 摘要转成 Base36。Gradle 这么设计有它的道理同一个版本号来自官方地址、来自内网地址、来自本地文件三者被视为三个不同的发行版各自独立缓存避免不同来源的包混在一起。理解了这个机制很多现象就能解释清楚了。你手动把 zip 放进 dists 目录wrapper 又新建一个目录重新下载说明你放的目录名和它算出来的目录名对不上。你在gradle-wrapper.properties里把地址从-bin改成-all发现要重新下一遍因为 URL 变了hash 跟着变。你在内网改写了域名hash 同样会变。目录之下还有两个文件值得记住gradle-8.6-all.zip是原始压缩包gradle-8.6-all.zip.ok是安装完成标记解压出来的gradle-8.6/是真正被使用的发行版。另外还有gradle-8.6-all.zip.lck那是进程锁多个 daemon 同时启动时用它做互斥。缺了.okwrapper 会认为上次安装没做完重新走一遍解压甚至重新下载。提示zipStoreBase和zipStorePath控制 zip 存放位置默认与解压目录一致。把这两个值改到别的盘只影响原始 zip 的落点不影响wrapper/dists的结构。2.3 先校验 SHA-256再谈离线离线环境里最怕的其实不是版本不对而是包在流转过程中被改过、被工具重打包过、或者下到一半断了。校验成本极低顺手做掉。# Linux / macOS先算一遍把结果贴进变更单或 README sha256sum gradle-8.6-all.zip shasum -a 256 gradle-8.6-all.zip # macOS 自带的是 shasum # Windows PowerShell等价写法 Get-FileHash .\gradle-8.6-all.zip -Algorithm SHA256 # 官方同时发布了 .sha256 文件直接比对最省事 curl -fsSLO https://services.gradle.org/distributions/gradle-8.6-all.zip.sha256 cat gradle-8.6-all.zip.sha256逻辑说明sha256sum输出的是 64 位十六进制摘要与官方.sha256文件里的值逐字符比对即可。参数上没什么可调的重点是不要凭记忆把一串 hash 写进配置每次都以当次下载到的.sha256文件为准。这个值后面会写进distributionSha256Sum一旦写进去zip 内容有任何字节变化wrapper 会直接拒绝启动这是内网流转里最便宜的一道防篡改措施。3. 三种把 8.6 接进项目的方式从自动下载到手动落盘同一种诉求有三种实现路径选择依据是「目标机器能不能上网」和「这份 zip 要给多少个项目复用」。能上网的走自动下载最省心完全隔离的走手动落盘介于两者之间的把 zip 挂到共享盘、用file:协议指向它。3.1 最稳的方式让 wrapper 自己下只要本机有一个能用的 gradle 可执行文件任意 8.x 都行在项目根目录执行一条命令就能把 wrapper 生成好。# --distribution-type all 是关键不加默认生成 -bin gradle wrapper --gradle-version 8.6 --distribution-type all # 生成后确认这几个文件都在 ls -l gradlew gradlew.bat gradle/wrapper/gradle-wrapper.jar gradle/wrapper/gradle-wrapper.properties命令说明--gradle-version决定distributionUrl里写哪个版本号--distribution-type决定后缀是-bin还是-all。生成的gradle-wrapper.jar负责引导下载gradlew是 Unix 侧启动脚本gradlew.bat是 Windows 侧。生成的 properties 文件长这样每一行都值得看一眼# gradle/wrapper/gradle-wrapper.properties distributionBaseGRADLE_USER_HOME distributionPathwrapper/dists distributionUrlhttps\://services.gradle.org/distributions/gradle-8.6-all.zip networkTimeout10000 validateDistributionUrltrue zipStoreBaseGRADLE_USER_HOME zipStorePathwrapper/distsdistributionUrl里那个反斜杠是.properties格式对冒号的转义不能删删了会被当成 key 的分隔符。validateDistributionUrltrue表示启动时先探一下 URL 是否可达内网环境里如果探测被墙掉会报错可以临时改成false但更推荐直接换成file:协议。networkTimeout单位是毫秒内网走代理下载慢的话调到 30000 更合适。执行./gradlew --version会触发首次下载包会落到 2.2 说的那个 hash 目录里。这一步在有网机器上跑通再把整个wrapper/dists目录带走是最省事的复制方式。3.2 手动落盘算准目录名、补上 .ok 标记完全隔离的机器上只能手动放。关键是先把目录名算对。下面这段 Python 就是把 Gradle 的命名规则复现出来。import hashlib DIGITS 0123456789abcdefghijklmnopqrstuvwxyz def base36(value: int) - str: 把整数转成 Base36 字符串Gradle 的 PathAssembler 就是这么做的 if value 0: return 0 out [] while value: value, rem divmod(value, 36) out.append(DIGITS[rem]) return .join(reversed(out)) def dist_hash(distribution_url: str) - str: distributionUrl 的 MD5 摘要 - Base36得到缓存目录名 digest hashlib.md5(distribution_url.encode(utf-8)).digest() return base36(int.from_bytes(digest, big)) if __name__ __main__: # 注意这里必须是 properties 里去掉反斜杠转义之后的完整 URL url https://services.gradle.org/distributions/gradle-8.6-all.zip print(dist_hash(url))逻辑与参数说明hashlib.md5在这里只用来生成目录名与安全无关不要拿它做完整性校验。int.from_bytes(digest, big)把 16 字节摘要按大端解释成一个大整数再转 Base36这一步和 Gradle 内部实现一致。传进去的 URL 必须与 properties 文件中解转义后的字符串逐字符相同协议是https还是http、后缀是-all还是-bin、末尾有没有空格任何一点差异都会算出一个完全不同的目录名然后你会发现 wrapper 又去下载了。目录名算好之后落盘动作就四步set -e VER8.6 HASH$(python3 dist_hash.py) # 上一步脚本的输出 DIST$HOME/.gradle/wrapper/dists/gradle-${VER}-all/${HASH} mkdir -p $DIST cp gradle-${VER}-all.zip $DIST/ # 原始 zip 留在目录里不要删 cd $DIST unzip -q gradle-${VER}-all.zip # 解出 gradle-8.6/ 这一层 touch gradle-${VER}-all.zip.ok # 安装完成标记缺了它前功尽弃 # 自检这三条都应该有输出 ls -d $DIST/gradle-${VER} ls $DIST/gradle-${VER}/bin/gradle ls $DIST/gradle-${VER}-all.zip.ok参数说明unzip -q的-q只是静默输出不影响结果touch创建的空文件就是.ok标记名字必须是「zip 文件名 .ok」写成gradle-8.6.ok是不认的。自检那三条命令建议固定成习惯尤其是第二条它确认了bin/gradle在正确层级——多套一层目录是最常见的翻车点。3.3 distributionUrl 指向本地文件如果这份 zip 要服务多个项目逐个塞缓存目录太笨。把它放到共享盘或者构建机的固定路径然后让 properties 直接指过去。# Linux / macOS 构建机 distributionUrlfile\:/opt/dist/gradle-8.6-all.zip # Windows 构建机注意盘符前的三条斜杠 # distributionUrlfile\:///D:/toolchain/gradle-8.6-all.zipfile:后面的冒号同样需要反斜杠转义。这种写法的好处是 zip 只有一份改一次全项目生效坏处是路径成了硬编码不同机器上路径不一致时会直接报找不到。折中做法是在gradle.properties里留一个变量用-P或环境变量注入但这需要改构建脚本简单项目没必要。这种写法下distributionSha256Sum依然生效wrapper 读文件后会先校验再解压比走网络时的校验更有意义——因为本地文件被误改的概率其实不低。3.4 三条命令验证到底用的是哪个 Gradle装完不要凭感觉用命令确认。./gradlew --version # 看 Gradle、Kotlin、Groovy、JVM、OS 五行 ./gradlew -q javaToolchains # 看 toolchain 探测到哪些 JDK find $HOME/.gradle/wrapper/dists -maxdepth 3 -name *.ok # 确认安装标记第一条输出的第一行是 Gradle 版本第二行 Kotlin第三行 Groovy,第四行 JVM第五行 OS。重点看第一行是不是 8.6以及 JVM 那行是不是你预期的 JDK——Gradle 8.6 跑在 JDK 8 到 21 上都行但跑在哪个 JDK 上会直接影响构建脚本里能用的语法。第二条列出 toolchain 探测结果离线环境里这一条尤其要看探测不到 JDK 会在编译阶段才报错。第三条确认.ok标记存在没有标记就说明缓存不完整。4. 离线构建--offline 只解决一半问题很多人以为把发行版塞进缓存、加上--offline就万事大吉结果第一次跑就卡在依赖解析上。--offline的作用范围比想象中窄得多它只保证「不发起网络请求」不保证「本地有货」。4.1 --offline 到底关掉了什么加上这个参数后Gradle 会禁用所有仓库的网络访问包括插件仓库、依赖仓库、以及 artifact 的远程查询。一旦某个模块在本地缓存里找不到匹配的版本构建直接失败报错通常是No cached version of com.xxx:yyy:1.2.3 available for offline mode。也就是说--offline是一道限制只能约束行为它阻止 Gradle 去联网但并不会补充依赖。真正决定离线能否构建成功的是~/.gradle/caches/modules-2里有没有那批 jar、pom 和元数据。发行版和依赖缓存是两套完全独立的东西前者解决「用哪个 Gradle 跑」后者解决「编译时链接什么」很多人只做了前者然后在编译阶段才失败。还有一个细节--offline对动态版本1.2.、latest.release很不友好因为动态版本必须去远程查最新离线状态下会直接用缓存里已有的解析结果可能与在线时不一致。生产构建应尽量把版本固定下来这和后面第 6 章说的版本目录是同一件事。4.2 把依赖预热进本地缓存标准做法是在有网机器上用同一份 wrapper、同一套 JVM 参数先跑一遍再把缓存搬过去。# 1) 强制刷新并解析一遍运行时依赖把 pom / jar / module 元数据拉全 ./gradlew --refresh-dependencies dependencies --configuration runtimeClasspath # 2) 完整构建一次插件、注解处理器、测试依赖才会被拉下来 ./gradlew build -x test # 3) 打包缓存只搬 modules-2 最稳 tar -czf gradle-cache-8.6.tgz -C $HOME .gradle/caches/modules-2逻辑说明第 1 步的--refresh-dependencies会忽略本地已有的过期标记重新走一遍解析确保元数据是最新的dependencies任务只打印依赖树但解析过程本身就会把文件下载到缓存。第 2 步必须做因为插件buildscript classpath 上的那些和注解处理器不在普通依赖树里只跑dependencies是拉不到的。第 3 步只搬modules-2因为caches/transforms-*里是产物转换结果跨机器、跨路径搬运容易失效让它在新机器上重新生成更省心。搬到目标机器后解压到$GRADLE_USER_HOME/caches/再跑./gradlew build --offline验证。如果报某个模块找不到就回到有网机器上单独解析那个配置别指望一次性全对——依赖是逐步收敛的第一次搬完总会有遗漏。4.3 init.gradle 统一内网仓库公司内网一般有自建的 Maven 仓库把所有项目的仓库地址统一起来比在每个build.gradle里改要省事得多。// 放在 $GRADLE_USER_HOME/init.d/offline-repos.gradle def internal http://repo.internal/maven2 settingsEvaluated { settings - settings.pluginManagement { repositories { maven { url uri(internal) allowInsecureProtocol true // 内网 http 必须显式允许 } gradlePluginPortal() } } } allprojects { buildscript { repositories { maven { url uri(internal) allowInsecureProtocol true } } } repositories { maven { url uri(internal) allowInsecureProtocol true } mavenLocal() } }说明init.d目录下的脚本会在每个构建开始前执行settingsEvaluated保证在 settings 求值完成后才注入插件仓库时序不对的话pluginManagement会报只读。allowInsecureProtocol true是 Gradle 6.0 之后对 http 仓库的强制要求内网环境里不加这一行会直接拒绝解析。顺序上把内网仓库放在最前面公共仓库放后面做兜底离线时前面的仓库命中率越高越好。新项目更推荐在settings.gradle.kts里用dependencyResolutionManagement配RepositoriesMode.FAIL_ON_PROJECT_REPOS把仓库锁在 settings 层子项目里写repositories直接编译失败。这样内网规范是强制的而不是靠自觉。4.4 把 GRADLE_USER_HOME 迁出系统盘缓存的目录默认在用户目录下一台机器上几个项目加上几个版本几十 GB 很常见系统盘吃紧时构建会跟着变慢。# 临时生效适合 CI 单次任务 GRADLE_USER_HOME/data/gradle ./gradlew build --offline # 长期生效写进 shell 配置同时记得在 IDE 里单独指定 export GRADLE_USER_HOME/data/gradle迁移时要把wrapper/dists、caches、daemon、notifications一起搬只搬一部分等于没搬。特别注意daemon/8.6/目录daemon 的注册信息在那里搬漏了会看到 daemon 反复重启。还有一点很容易忽略IDE 里的 Gradle 用户主目录是独立设置项命令行设了环境变量IDE 未必跟着走两边用两套缓存的话你在命令行预热好的依赖IDE 里照样要重新下。这个问题不排查会浪费很多时间。5. 避坑五份「装完还是跑不起来」的排查记录下面这几条都是实际踩过的按「现象 → 原因 → 解决」记遇到时对照着看比自己猜快得多。5.1 装了包构建开头还是卡在 Downloading现象日志里反复出现Downloading https://services.gradle.org/distributions/gradle-8.6-all.zip内网机器上卡到超时失败但目录里明明有 zip。原因缺少gradle-8.6-all.zip.ok标记文件。wrapper 判断「是否已安装」看的是这个标记不是看目录存不存在、也不是看 zip 在不在。手动放包时漏了touch它每次都认为上次安装没完成。解决进到对应 hash 目录touch gradle-8.6-all.zip.ok然后确认gradle-8.6/bin/gradle存在。用 3.2 的自检三条命令走一遍三条都有输出才算干净。5.2 包放对了位置wrapper 又新建一个目录重新下现象wrapper/dists/gradle-8.6-all/下出现了两个 hash 目录一个是你的一个是空的或者正在下载的。原因目录名与当前distributionUrl算出来的 Base36 不一致。最常见的差异有四种协议从 https 改成 http、后缀从-bin改成-all、URL 末尾多了空格、内网把域名改写成了另一个地址。解决用 3.2 的脚本直接拿 properties 文件里那行 URL去掉反斜杠转义重新算一次把 zip 挪到新目录并补.ok。更省事的办法是先让 wrapper 失败一次它会把创建的目录建出来你把这个目录名抄下来直接用。5.3 启动脚本报找不到主类现象Could not find or load main class org.gradle.launcher.GradleMain或者提示gradle-8.6/lib/xxx.jar不存在。原因解压层级错了。常见两种情况直接把 zip 解压到了 dists 目录少了一层版本目录或者在 dists 目录里又建了一层同名目录变成.../hash/gradle-8.6/gradle-8.6/lib。解决ls $DIST的输出应该直接看到gradle-8.6/这一个目录加 zip 和.ok文件进到gradle-8.6/里应该直接看到bin、lib、docs、src。多一层就mv上来少一层就重新解压到正确位置。5.4 toolchain 报找不到匹配的 JDK现象No matching toolchains found for requested specification: {languageVersion17}但机器上确实装过 JDK 17。原因toolchain 自动探测只认标准安装路径和显式配置的路径。离线环境下 JDK 自动下载foojay resolver根本拉不到插件探测失败就直接报错。解决在gradle.properties里显式写路径并关掉自动下载# 路径按实际安装位置改多个用逗号分隔 org.gradle.java.installations.paths/opt/jdk-17,/opt/jdk-21 org.gradle.java.installations.auto-detecttrue org.gradle.java.installations.auto-downloadfalse改完执行./gradlew -q javaToolchains确认能列出来再跑构建。5.5 缓存被锁或权限不足现象Could not open cache .../journal-1/file-access.bin、Permission denied或者构建莫名卡在Waiting to acquire exclusive lock。原因用sudo跑过一次缓存文件属主变成 root之后普通用户读写不了或者两个 daemon、两个终端在抢同一份缓存锁。解决先./gradlew --stop停掉所有 daemon再sudo chown -R $(id -u):$(id -g) ~/.gradle把属主改回来。CI 环境里更稳的做法是每个 job 用独立的GRADLE_USER_HOME从根上避免共享缓存互相干扰。6. 把 8.6 钉死distributionSha256Sum 与依赖校验前面几步解决的是「能不能用」这一步解决的是「半年后还能不能跑出一样的结果」。发行版和依赖都会漂移钉死它们要靠两个东西wrapper 的校验和、以及依赖校验元数据。第一件事把 SHA-256 写进 wrapper 配置。这个值来自官方发布的.sha256文件或者你自己在可信环境里算出来并登记过的那串。# gradle/wrapper/gradle-wrapper.properties distributionUrlhttps\://services.gradle.org/distributions/gradle-8.6-all.zip distributionSha256Sum这里填官方 .sha256 文件里的那串值开启之后wrapper 在解压前会先校验内容有任何字节改动都会直接拒绝启动报错信息里会给出实际值与期望值。代价是每次更换发行版都要同步更新这一行换来的是「包里被塞了东西」这类问题在启动阶段就暴露而不是等到构建产出可疑产物才发现。第二件事给依赖也加上校验。执行一次下面的命令Gradle 会把当前解析到的所有 artifact 的摘要写进gradle/verification-metadata.xml# 生成 sha256 校验元数据随项目一起提交 ./gradlew --write-verification-metadata sha256 help # 之后每次构建默认按严格模式校验 ./gradlew build这个文件提交进版本库之后任何人改动依赖版本、或者仓库里同版本号的内容被替换构建都会失败并提示校验不通过。维护方式是「有变更就重新生成对应片段」不要手改手改容易把整个components段落写坏。再往前一步是版本目录。把 Gradle 版本、插件版本、依赖版本集中到gradle/libs.versions.toml里构建脚本只引用别名。这样做最实在的好处是离线环境里要核对「这台机器上到底该有哪些 jar」时只看一个文件就够不用在十几个build.gradle里翻。我一般会把 wrapper 的版本号和 toml 里的 plugin 版本放在同一个变更里改避免出现「gradle 升到 8.6 但插件还停留在老版本」的错配——那种问题在线时表现为警告离线时往往直接构建失败而且报错位置和真正的原因隔了好几层。注意distributionSha256Sum一旦写死换用file:协议指向本地 zip 时同样会校验所以本地那份 zip 必须是没被工具重新打包过的原始文件。有人用解压软件重新压缩一遍再放上去校验必然不过。从那以后我每次往离线环境搬 Gradle都会强制走一遍算 hash 目录、放 zip、解压、补.ok、跑--version和javaToolchains两条命令、最后--offline构建一次。这六步花不到五分钟但省下来的排查时间往往是几个小时。希望帮到你。本文还有配套的精品资源点击获取