03 - 发布实战与排查手册

维度:发布操作步骤、真实实战记录、错误排查、踩坑清单、死代码清理、编译路线对比 本文基于 2026-07-14 两次真实发布(失败 → 成功)的完整记录


一、发布操作步骤

1.1 前置检查清单

cd /home/zbc/car/MiCarSettings
 
# 1. 账号是否配到正确来源(env 或 project.properties,不是 local.properties)
grep -q "^artifactory_user=" project.properties && echo "✅ project.properties 有账号" \
  || ([ -n "$artifactory_user" ] && echo "✅ 环境变量有账号" || echo "❌ 账号未配置或只在 local.properties")
 
# 2. skip 标志是否注释(app/build.gradle:174)
grep -q "^[^/]*artifactoryPublish.skip" app/build.gradle && echo "❌ skip 仍生效" || echo "✅ skip 已注释/移除"
 
# 3. 签名密钥存在
ls platform.keystore && echo "✅ keystore 就绪"
 
# 4. 磁盘空间(clean 全量编译需数 GB)
df -h . | tail -1

1.2 执行发布

# 方式一:用脚本(含 clean,全量编译,约 3-15 分钟)
./project_publish_xcd_app.sh        # 或 dcd / global
 
# 方式二:手动(推荐,可控制是否 clean)
./gradlew clean && ./gradlew updateApkAssembleXcdRelease    # 首次/代码变更后
./gradlew updateApkAssembleXcdRelease                        # 增量重发(apk 没变只重传,约 10-60s)

1.3 验证结果

# 看真实结果(不是后台任务 exit code)
grep -E "BUILD SUCCESSFUL|BUILD FAILED" /tmp/micar_publish_xcd.log
grep "Deploying artifact" /tmp/micar_publish_xcd.log          # 上传的制品 URL

二、实战记录(2026-07-14)

2.1 第一次:失败(401)

sequenceDiagram
    participant Dev as 开发者
    participant Gradle as Gradle
    participant Deploy as artifactoryDeploy
    participant Art as Artifactory

    Dev->>Gradle: clean + updateApkAssembleXcdRelease
    Note over Gradle: loadBuildConfig:<br/>artifactory_user=null(账号在<br/>local.properties,未被读)
    Gradle->>Gradle: 编译十几个模块 2m53s
    Gradle->>Gradle: assembleXcdRelease → 371MB apk ✅
    Gradle->>Deploy: artifactoryDeploy
    Deploy->>Art: PUT apk(无认证/空账号)
    Art-->>Deploy: 401 Unauthorized ❌
    Deploy-->>Gradle: BUILD FAILED

诊断过程:

  1. 日志 loadBuildConfig: artifactory_user=null(但注意这是 env 阶段打印,本身不证明最终为空)
  2. 确认账号配在 local.properties,但 loadBuildConfig 只读 env/project.properties(build.gradle:183-187)
  3. project.properties 只有 versionName/versionCode,无账号
  4. 根因:账号读取源不匹配 → 上传时空账号 → 401

2.2 修复

把账号配到 loadBuildConfig 能读到的来源(project.properties 或环境变量)。详见 02-认证机制 §四。

2.3 第二次:成功

./gradlew updateApkAssembleXcdRelease    # 不 clean,apk 已存在
> Task :app:assembleXcdRelease UP-TO-DATE     ← 跳过编译
> Task :app:artifactoryPublish
[pool-17-thread-1] Deploying artifact: .../releases/com/mi/car/MiCarSettings/1.0.0.194-dev-xcd/MiCarSettings-1.0.0.194-dev-xcd.apk
[pool-17-thread-1] Deploying artifact: .../releases/com/mi/car/MiCarSettings/1.0.0.194-dev-xcd/MiCarSettings-1.0.0.194-dev-xcd.pom
> Task :artifactoryDeploy
BUILD SUCCESSFUL in 13s

产物:com.mi.car:MiCarSettings:1.0.0.194-dev-xcd(apk 371MB + pom),仓库 releases


三、错误码对照

HTTP 码含义根因解法
401 Unauthorized认证失败账号 null / 错误 / 没读到配账号到 env 或 project.properties;见 02
403 Forbidden权限不足账号对但无 releases 写权限找管理员开仓库写权限;或改用有权限的仓库
409 Conflict版本冲突releases 不允许覆盖已存在版本递增 versionName/project.properties 的 versionCode
ResolveException(extractModule)jfrog 解析依赖图失败某依赖解析不出非致命,apk/pom 仍上传;只影响 build info

四、踩坑清单(详解)

坑 1:账号读取源不一致(最常见,导致 401)

  • 现象:401 Unauthorized,loadBuildConfig: artifactory_user=null
  • 根因:账号配在 local.properties,但 loadBuildConfig(build.gradle:183-187)只读 env/project.properties
  • 误导:app/upload.gradle(死代码)声称读 local.properties,但没被 apply
  • 解法:见 02 §四,三选一

坑 2:skip=true 历史遗留

  • 位置:app/build.gradle:174
  • 现象:./gradlew updateApkAssembleXcdRelease 执行成功但 Artifactory 里没有制品
  • 根因:artifactoryPublish.skip = true 让上传被跳过(任务执行但 deploy 0 个文件)
  • 解法:注释掉(当前已是 //artifactoryPublish.skip = true)

坑 3:死代码误导

  • 位置:app/publish.gradleapp/upload.gradle
  • 现象:看代码以为账号从 local.properties 读,实际不生效
  • 解法:清理(见 §五)

坑 4:后台任务 exit code 掩盖结果

  • 现象:run_in_background 的 gradle 任务报 exit 0,但实际 BUILD FAILED
  • 根因:脚本末尾 echo "EXIT_CODE=$?" 的 echo 本身 exit 0,掩盖了 gradle 的 exit 1
  • 解法:判断成败只看日志 BUILD SUCCESSFUL/FAILED,不看后台任务 exit code

坑 5:project.properties 泄露风险

  • 现象:把账号放 project.properties 后 git commit/push → 密码入库
  • 根因:project.properties 被 git 跟踪
  • 解法:账号优先环境变量;若用 properties,git status 确认未提交,或改用 local.properties + 方式 C(改代码)

坑 6:print 误导

  • 现象:loadBuildConfig: artifactory_user=null 让人以为账号没配
  • 根因:build.gradle:172 的 print 在读 env 之后、读 properties 之前
  • 解法:以 Deploying 后有无 401 为准,不以 print 为准

五、死代码清理建议

app/publish.gradleapp/upload.gradle 没被任何文件 apply(grep apply from 全仓确认),是旧配置。建议:

# 确认无引用
grep -rn "publish.gradle\|upload.gradle" --include="*.gradle" . | grep -v "/build/"
# 应只看到 app/publish.gradle:1: apply from: "upload.gradle"  (自身引用,无外部 apply)
 
# 删除(确认后)
git rm app/publish.gradle app/upload.gradle

清理收益:消除”账号读 local.properties”的误导,降低 401 排查成本。

若想保留 local.properties 配账号的能力,先把 upload.gradleloadArtifactoryAccountInfo 逻辑搬到生效的 loadBuildConfig(见 02 方式 C),再删死代码。


六、两条编译路线对比

路线命令产物用途
Artifactory 发布./project_publish_xcd_app.sh./gradlew updateApkAssembleXcdReleaseAPK 上传到 Artifactory(com.mi.car:MiCarSettings)独立 APK 发布,供 CI/OTA 拉取
源码树编译. build/envsetup.sh && lunch watt-userdebug && make CarSettings(build.sh)编进 Android 系统镜像系统 priv-app 集成,出整机镜像

两条路线并存:发布脚本给独立 APK 用,build.sh 给系统镜像用。


七、快速诊断命令合集

# 发布日志关键字
grep -E "BUILD SUCCESSFUL|BUILD FAILED|EXIT_CODE=" /tmp/micar_publish_xcd.log
grep -E "Deploying artifact|Status code: 401|Unauthorized" /tmp/micar_publish_xcd.log
grep -A12 "What went wrong" /tmp/micar_publish_xcd.log
 
# 账号配置检查(不打印密码值)
grep -c "^artifactory_user=" project.properties local.properties 2>/dev/null
[ -n "$artifactory_user" ] && echo "env 有账号" || echo "env 无账号"
 
# skip 状态
grep -n "skip" app/build.gradle
 
# 制品/产物
ls -la app/build/outputs/apk/xcd/release/*.apk
 
# git 跟踪检查(避免密码入库)
git ls-files | grep -E "^(project|local)\.properties$"

八、发布速查(复制即用)

# === 一次性配账号(选其一) ===
# A. 环境变量(推荐)
export artifactory_user='XXX'; export artifactory_password='XXX'
# B. 或写入 project.properties(注意别 commit)
 
# === 发布 ===
./gradlew updateApkAssembleXcdRelease 2>&1 | tee /tmp/publish.log
 
# === 看结果 ===
grep -E "BUILD SUCCESSFUL|BUILD FAILED|Deploying artifact|401" /tmp/publish.log