Jenkins 构建失败排查记录:mvn -U 把新版依赖被远程旧版覆盖

起因

某天我在 bgin 分支提了一个改动,触发 Jenkins 构建后发现失败,报错信息是:

ERROR /opt/jenkins/data/workspace/.../KaspaCoinbaseLedgerImportService.java:353,53 cannot find symbol

ERROR symbol: variable SYSTEM_TYPE_KASPA_WALLET

ERROR location: class com.intax.core.constants.KaspaImportServiceConstants

奇怪的是:

  • 这个常量明明已经在 intax-core 的 bgin 分支上定义了
  • 本地编译完全没问题
  • 其他环境(metaalpha 等)构建正常,只有 bgin 失败
  • 而且失败发生在 intax-data 模块,跟我这次改的 intax-web-tob 一点关系都没有

排查了一通,挖出了一个挺反直觉的 bug,记录下来。

项目背景

这是个多仓的 Maven 工程:

  • intax-platform(主项目,一个 Maven 父 pom)
  • intax-core(基础工具库,被其他项目依赖)
  • intax-data(数据模块,依赖 intax-core)
  • intax-web-tob(Web 模块,依赖 intax-core)

四个仓独立 git 管理,各自维护 bgin、metaalpha 等多条客户分支。Jenkins 通过参数化构建,根据 BRANCH 和 BUILDENVIRONMENT 参数拉对应分支、用对应 maven profile 打包。

最近有人在 bgin 分支上做了 kaspa-wallet 改造:

  • intax-core 加了个常量 SYSTEM_TYPE_KASPA_WALLET = "Kaspa Wallet"
  • intax-data 引用了这个常量

代码本身没毛病,问题出在 Jenkins 编译流程上。

Pipeline 流程梳理

Step 1 --- Checkout Parent Project 拉 intax-platform 主仓代码

Step 2 --- Checkout Submodules 拉 intax-core / intax-data / intax-web-tob 三个子仓代码

Step 3 --- Build Parent Project 在主仓根目录跑 mvn clean install,reactor 模式一次性编译所有模块,jar 装进本地 ~/.m2

Step 4 --- Build Data Module (并行) cp -r 复制源码到 -bgin 后缀目录,单独再编 intax-data 和 intax-web-tob,产出最终 jar

Step 5 --- Archive JARs

Step 6 --- Deploy

Step 7 --- Cleanup

前两步只是 git clone / git pull / git reset --hard,纯拉代码不编译。

第三步才是第一次编译。这一步在主项目根目录跑 mvn clean install,Maven reactor 模式把所有模块(包括 intax-core / intax-data / intax-web-tob)一次性全编了,jar 装进 Jenkins 本地的 ~/.m2/repository/com/ruoyi/ 缓存目录。这一步是成功的,新版 intax-core(带 SYSTEM_TYPE_KASPA_WALLET)进了本地缓存。

第四步单独再编 intax-data 和 intax-web-tob,就在这里出问题。

根因:一个反直觉的矛盾

第四步的 mvn 命令里带了 -U 参数:

mvn clean package -U -P${BUILDENVIRONMENT} -DskipTests

-U 的语义是"强制去远程仓库刷新 SNAPSHOT 依赖"。

听起来很合理 ------ "我要拿最新的"。但在这套 Pipeline 里,这个参数变成了灾难,原因是一个非常隐蔽的矛盾:

┌─────────────────────────────────┬─────────────────────────────────────────────────────┬───────────────────────────────────────────────────┐

│ 哪里的 intax-core │ 当前状态 │ 谁写进去的 │

├─────────────────────────────────┼─────────────────────────────────────────────────────┼───────────────────────────────────────────────────┤

│ Jenkins 服务器本地 ~/.m2 │ 第三步刚装进来的新版(带 SYSTEM_TYPE_KASPA_WALLET) │ 这次 Pipeline 自己 │

├─────────────────────────────────┼─────────────────────────────────────────────────────┼───────────────────────────────────────────────────┤

│ 远程仓库(内网 Nexus / 公共镜像) │ 早就 deploy 上去的旧版(无 SYSTEM_TYPE_KASPA_WALLET) │ 历史上某次构建,可能几周前从别的分支 deploy 上去的 │

└─────────────────────────────────┴─────────────────────────────────────────────────────┴───────────────────────────────────────────────────┘

注意:mvn install 只装本地,不会往远程推。要往远程推得用 mvn deploy,这个 Pipeline 里没用。

所以每次构建,实际发生的事是这样的:

第三步:在本地默默装一份新版 intax-core

第四步:启动 mvn -U

"我去远程问问有没有更新的?"

远程递回那本祖传旧版 intax-core(还是几周前的)

maven 把远程旧版下载下来,覆盖了本地刚装的新版

intax-data 编译时取依赖 → 从本地缓存拿到那个被覆盖的旧版

找不到 SYSTEM_TYPE_KASPA_WALLET → cannot find symbol → 失败

生产者(Step 3)只写本地,消费者(Step 4)又跑去读远程,两边目标完全不一致,导致新版每次都被旧版覆盖。

为什么别的环境没事,只有 bgin 出问题

  • metaalpha 等其他分支:这些分支根本没有 kaspa-wallet 的改造,intax-core 不带这个常量,intax-data 也不引用。所以即使本地新版被远程"等价的旧版"覆盖,实际内容一样,不影响编译。
  • bgin 分支:只有 bgin 新增了这个常量、新增了对它的使用。intax-data 必须拿到 bgin 版的 intax-core 才能编过,但 maven 反复给它一份旧的 → 永远失败。

也就是说,这个 bug 是潜伏很久了的,bgin 一旦引入"远程没有的新依赖",就立刻暴露。

为什么我的 commit 跟这事一点关系没有

我这次改的是 intax-web-tob 的报表代码,完全没碰 kaspa 相关。Jenkins 日志里 intax-web-tob 那个 stage 是 BUILD SUCCESS,只有 intax-data 那个 stage 报 cannot find symbol。

也就是说,就算我把我的 commit 完全 revert 掉,Jenkins 还是会因为这个 bug 失败。这是 kaspa-wallet 那批 commit + Jenkins Pipeline -U 参数的组合 bug,跟我的提交无关。

修复

两步:

  1. 清掉本地缓存里被污染的旧 jar(已被覆盖的现状要清掉,否则下次还是用这个旧的):

rm -rf /root/.m2/repository/com/ruoyi/intax-core

rm -rf /root/.m2/repository/com/ruoyi/intax-data

rm -rf /root/.m2/repository/com/ruoyi/intax-web-tob

  1. 改 Jenkinsfile,去掉 3 处 -U(根治,防止下次再污染):

stage('Build Parent Project') {

dir(env.PARENT_DIR) {

复制代码
     sh 'mvn clean install -U -DskipTests'
复制代码
     sh 'mvn clean install -DskipTests'

}
}

stage('Build Data') {

dir(buildDir) {

复制代码
     sh "mvn clean package -U -P${params.BUILDENVIRONMENT} -DskipTests"
复制代码
     sh "mvn clean package -P${params.BUILDENVIRONMENT} -DskipTests"

}
}

stage('Build Web-TOB') {

dir(buildDir) {

复制代码
     sh "mvn clean package -U -P${params.BUILDENVIRONMENT} -DskipTests"
复制代码
     sh "mvn clean package -P${params.BUILDENVIRONMENT} -DskipTests"

}
}

去掉 -U 之后,maven 只用本地缓存里 Step 3 刚装的新版 jar,不会再去远程问。

复盘:几个反直觉的点

  1. mvn install 的命名容易误导

英文 install 听起来像"装到正式位置去",实际上只是把 jar 拷贝到本地 ~/.m2/ 缓存。要往远程仓库推得用 mvn deploy,完全不同的命令。

  1. mvn -U 不一定让你拿到"更新"的版本

-U 的逻辑是"去远程检查是否有更新的 SNAPSHOT"。它根据 maven-metadata.xml 的 lastUpdated 时间戳决定要不要下载。如果远程那份 jar 是几周前 deploy 的,本地是几分钟前 install 的,理论上本地更新,但 Maven 的实际行为受 timestamp 解析逻辑和 SNAPSHOT 版本号(5.2.0-20251112.143022-7 这种)影响,边缘情况下会出现下载远程旧版覆盖本地新版。

  1. 这种 bug 表现得像"代码出问题"

报错 cannot find symbol 长得像源码缺失。但源码是好的(git reset --hard origin/bgin 之后工作区文件验证过)。问题在 Maven 依赖解析层,源码层面看不出来。要定位必须看 Jenkins 日志里 Downloading from ... 这些行,才能发现远程在搞鬼。

  1. 多客户分支 + 共享 Maven coords 的天坑

com.ruoyi:intax-core:5.2.0-SNAPSHOT 这个 coords 所有分支共用。bgin 装一份、metaalpha 装一份、main 装一份,远程仓库里永远只有最后 deploy 的那一份,谁的版本谁就赢。如果远程被 main 分支占了,bgin 永远拿不到自己的新代码。

要根治这个,要么:

  • 不同分支用不同的版本号(比如 5.2.0-bgin-SNAPSHOT)
  • 或者不要在 CI 流程里依赖远程 SNAPSHOT,Reactor 内部全编(也就是把 Step 4 去掉,Step 3 reactor 一次编完直接产 jar)
  • 或者像本次的修复一样,只用本地缓存,关掉 -U

总结

┌──────────┬────────────────────────────────────────────────────────────────────────────────────────────────┐

│ 维度 │ 内容 │

├──────────┼────────────────────────────────────────────────────────────────────────────────────────────────┤

│ 表象 │ bgin 分支 Jenkins 构建失败 cannot find symbol SYSTEM_TYPE_KASPA_WALLET │

├──────────┼────────────────────────────────────────────────────────────────────────────────────────────────┤

│ 真因 │ Jenkinsfile 第 4 步 mvn -U 主动去远程仓库刷新 intax-core,远程的旧版覆盖了第 3 步本地刚装的新版 │

├──────────┼────────────────────────────────────────────────────────────────────────────────────────────────┤

│ 本质矛盾 │ Step 3 只写本地缓存(install),Step 4 又去读远程(-U),生产者和消费者目标不一致 │

├──────────┼────────────────────────────────────────────────────────────────────────────────────────────────┤

│ 修复 │ 1) 清 ~/.m2 里被污染的旧 jar;2) Jenkinsfile 去掉 -U(3 处) │

├──────────┼────────────────────────────────────────────────────────────────────────────────────────────────┤

│ 教训 │ mvn install 只装本地 ≠ 推远程;-U 不一定让你拿到真正最新的;多分支共用 Maven coords 是个长期隐患 │

└──────────┴────────────────────────────────────────────────────────────────────────────────────────────────┘

整件事下来最深的感悟是:CI 排错时,代码层面看不出问题不代表代码有问题,有时候 Maven / Pipeline 这层的"自作聪明"才是真凶。

相关推荐
AOwhisky8 小时前
Redis 学习笔记(第三期):持久化与主从复制
运维·数据库·redis·笔记·学习·云计算
c238568 小时前
Linux C++ 进度条进阶美化与工程化封装
linux·运维·服务器
李小白668 小时前
第四天-WEB服务器基本原理,IIS服务
运维·服务器·前端
2401_834636999 小时前
Nginx 从入门到实战:静态 / 动态站点、PHP 部署与反向代理全解析
运维·nginx·php
aosky10 小时前
一台电脑配置多个 SSH Key 对应不同的 GitHub 账号
运维·ssh·github
云登指纹浏览器10 小时前
WebDriver反检测技术详解:如何让自动化脚本看起来像真实浏览器
运维·自动化·跨境电商
xmtxz11 小时前
计算机网络基础课程学习心得:从理论抽象到硬核实战的进阶之路
运维·学习
RisunJan11 小时前
Linux命令-pgrep (通过进程名查找进程 ID)
linux·运维
信创工程师-小杨12 小时前
Linux内网环境如何解决依赖的问题
linux·运维·服务器
java知路12 小时前
linux yum 下载docker安装包及依赖安装包,并离线安装
linux·运维·docker