项目版本号从 1.0.19 升到 1.0.20,全量构建,结果红了。报错只有一句,落在 nebula-client 模块:

1
2
3
4
5
[ERROR] Could not resolve dependencies for ...:nebula-client:jar:1.0.20-SNAPSHOT
[ERROR] dependency: ...:nebula-proto-conf:jar:1.0.20-SNAPSHOT (compile)
[ERROR] ... was not found ... during a previous attempt.
[ERROR] This failure was cached ... not reattempted until the
[ERROR] update interval ... has elapsed

这个报错最迷惑的地方在于:nebula-proto-conf 明明是本项目自己的一个模块,源码就在同一个工程里。自己的模块,为什么会找不到,还要跑去私有仓库拉?
更离谱的是,报错的 nebula-client 的 pom 里,压根没有 nebula-proto-conf 的依赖。

先看工程结构

先把这个项目长什么样讲清楚,不然后面会一头雾水。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
nebula-ad(广告管理平台 · Maven 多模块)
├── nebula-fdn # 基础设施层
├── nebula-base # 基础配置
├── nebula-common # 通用工具
├── nebula-client # 客户端模块 ← 报错就出在这
├── nebula-conf # 广告配置
├── nebula-manager # 广告投放管理
├── nebula-effect # 效果追踪
├── nebula-monitor # 广告监控
├── nebula-search # 检索
├── nebula-startup # 应用入口
├── nebula-sdk # SDK
└── nebula-proto # 协议层(protobuf + gRPC,版本统一跟随项目版本)
├── nebula-proto-manager
├── nebula-proto-conf ← 被“传递依赖”的那个模块
└── nebula-proto-common

项目里的版本号统一在根目录用 dependencyManagement 管理。坑就在这儿,后面细说。

排查依赖

排查 Maven 依赖,一般先看它是直接依赖还是传递依赖。

翻了下 nebula-client 的 pom,没有直接依赖 nebula-proto-conf。那多半是某个直接依赖把它带进来的。接下来就是找,到底是哪个包引入了 nebula-proto-conf:

1
mvn dependency:tree -Dincludes=com.nebula.proto:nebula-proto-conf

输出

1
2
3
[INFO] com.nebula:nebula-client:jar:1.0.20-SNAPSHOT
[INFO] \- com.nebula:resume-facade:jar:1.0.23-SNAPSHOT:compile
[INFO] \- com.nebula.proto:nebula-proto-conf:jar:1.0.20-SNAPSHOT:compile

当然,也可以在 IDEA 的 Maven 工具窗口里点 Show Dependencies,直接搜 JAR 名称定位。

依赖路径是:
nebula-client -> resume-facade -> nebula-proto-conf
看到这里,疑点就来了:1.0.20 是这次刚改的版本号,resume-facade 怎么可能声明的是 1.0.20? 去翻 resume-facade 的实际依赖,发现它写的是 1.0.8。

问题拼图

到这里,事情基本清楚了:

  1. resume-facade 传递依赖 nebula-proto-conf,它声明的是 1.0.8;
  2. 根 pom 的 dependencyManagement 把版本统一改写成了 1.0.20;
  3. nebula-client 对 nebula-proto-conf 只是传递依赖,pom 里没有直接声明;
  4. Maven 在 reactor 拓扑排序时,识别不到这条依赖边;
  5. 于是构建顺序里,nebula-client 被排到了 nebula-proto-conf 之前——后者还没产出 1.0.20 的 jar;
  6. 本地没有、去私服也拉不到,失败被缓存,后续模块全部 SKIPPED。

单独看哪一步都不是大问题,凑一块才有问题。版本升级只是那个把雷点着的火苗。

Maven 决定模块构建顺序时,主要认的是「pom 里直接声明的依赖」。它会在依赖里找 groupId:artifactId 能对上 reactor 内模块的,据此建一条「A 依赖 B,所以 B 排前面」的边。

但 client -> proto-conf 这条边,是 facade 在传递依赖里带进来的,排序阶段根本不可见。于是 Maven 只能按模块声明顺序排,把 client 排到了 proto-conf 前面。

解决方案

解决问题有两个选择:

  1. nebula-client 显式声明 nebula-proto-conf 的依赖;
  2. 调整项目 modules 中的顺序,把 nebula-proto-conf 放到前面。

我选择了第一种。

显式声明后,Maven 就能识别出 nebula-client -> nebula-proto-conf 这条直接的依赖边,拓扑排序会自动把 nebula-proto-conf 排到前面,直接用自己构造的结果,问题根治。

调整 modules 顺序虽然这一次也能跑通,但它把依赖关系藏进了模块列表的顺序里,是个脆弱契约。以后谁重排一次 modules,或者加个新模块,又会在莫名其妙的地方复现。