Jenkins「鸿蒙打包」环境配置教程

以 192.168..(root / Jenkins#123)上的 Jenkins 任务 「鸿蒙打包」 为准整理
(job 地址:http://192.0.0.168/.
.
:8080/job/鸿蒙打包/ ,完整 config.xml 见 harmony-cicd/job_鸿蒙打包_config.xml)。
适用场景:已有原生鸿蒙工程(含 hvigorfile.ts),上传 zip → ohpm + hvigor 构建 → 产出 har / app / hap。


1. 方案总览

项内容
打包方式纯命令行工具链(ohpm + hvigorw),不依赖 HBuilderX / DevEco IDE
输入用户在构建页上传鸿蒙工程压缩包 harmonyProject.zip(需含 hvigorfile.ts)
输出$WORKSPACE/output/ 下的 *.har、*.app、*.hap,由 Jenkins ArtifactArchiver 归档
构建类型参数 AppBuildType:release(默认)/ debug
SDKOpenHarmony SDK 5.0.3.135(/opt/harmonyos/sdk),向下兼容 5.0.0(12) 工程
JDKOpenJDK 21(/usr/lib/jvm/java-21-openjdk-amd64,Jenkins 2.555.2 运行于其上)
Node/opt/nodejs(ohpm/hvigor 依赖)
服务器192.168.*.*(Ubuntu,Jenkins 直装 8080 端口,非容器)

流程示意:

浏览器构建页 ──上传 harmonyProject.zip──▶ Jenkins 任务「鸿蒙打包」
                                            │
                                            ▼
                            构建脚本(8 步,详见第 5 节)
                            解压 → 定位工程根 → 修 json5 → ohpm install
                            → assembleHar(软性) → assembleApp(硬性)
                                            │
                                            ▼
                            $WORKSPACE/output/*.app|har|hap → 页面下载

2. 第一步:安装基础软件

# 1) JDK 21(Jenkins 与 hvigor 都用它)
apt install -y openjdk-21-jdk
java -version   # 应显示 openjdk 21.0.7

# 2) Node.js(ohpm/hvigor 运行时)
mkdir /opt/nodejs && tar -xzf node-vXX.tar.xz -C /opt/nodejs --strip-components=1

# 3) Jenkins 2.555.2(war 直装,监听 8080;升级原因:Java 21 兼容)
#    /var/lib/jenkins 为 JENKINS_HOME

3. 第二步:安装 DevEco commandline-tools 与 SDK

# 目录布局
/opt/harmonyos/
├── commandline-tools/     # DevEco 命令行工具 5.0.3.135(bin/ 下有 hvigorw、ohpm、codelinter 等)
│   ├── bin/hvigorw
│   ├── ohpm/bin/ohpm
│   └── hvigor/bin/
├── sdk/                   # OpenHarmony SDK 5.0.3.135
└── projects/              # 构建工作区(harmony-app 为本任务的解压目录)

⚠️ 若工具链版本校验报错,可把 commandline-tools 内 version.txt 改写为高版本号伪装
(244 现状:5.0.3.135 伪装成 5.1.0.849 绕过版本检查)。仅原生工程打包时通常不需要。

4. 第三步:配置环境变量 /etc/profile.d/harmonyos.sh

构建脚本第一步就 source 它,内容必须与下面一致:

# HarmonyOS / OpenHarmony Build Environment
export HARMONY_HOME=/opt/harmonyos
export OHOS_BASE_SDK_HOME=/opt/harmonyos/sdk
export OHOS_HOME=/opt/harmonyos/commandline-tools
export DEVECO_SDK_HOME=/opt/harmonyos/sdk
export JAVA_HOME=/usr/lib/jvm/java-21-openjdk-amd64
export NODE_HOME=/opt/nodejs
export PATH=/opt/nodejs/bin:/opt/harmonyos/commandline-tools/bin:/opt/harmonyos/commandline-tools/ohpm/bin:/opt/harmonyos/commandline-tools/hvigor/bin:$PATH

验证:su - jenkins -c '. /etc/profile.d/harmonyos.sh && which ohpm hvigorw' 两个都能找到即通过。
(ohpm 也可以先 ohpm config set registry <内网镜像> 按需配置。)

5. 第四步:创建 Jenkins 任务「鸿蒙打包」

Jenkins → 新建 Item → Freestyle project → 命名 鸿蒙打包,按以下配置:

5.1 参数化构建(2 个参数)

类型名称说明
File ParameterharmonyProject.zip鸿蒙工程压缩包(需包含 hvigorfile.ts,兼容 5.0.0(12) 及以上)
Choice ParameterAppBuildType构建类型:release / debug,默认 release

任务描述(description)建议写:

鸿蒙 HarmonyOS 打包(基于 5.0.0(12),SDK 5.0.3 向下兼容):上传鸿蒙工程压缩包,解压到 /opt/harmonyos/projects 后用 ohpm + hvigor 构建,产出 har 与 app 包

其他设置:SCM = None;不配触发器(手动触发);禁止并发构建 勾选(构建会占用 /opt/harmonyos/projects/harmony-app 固定目录)。

5.2 构建步骤(Execute Shell,完整脚本)

set -e
echo "====== 鸿蒙打包构建 ======"
echo "Build Type: ${AppBuildType}"
echo "Workspace:  ${WORKSPACE}"

# 加载鸿蒙环境变量
. /etc/profile.d/harmonyos.sh
export HOME=/var/lib/jenkins

ZIP_NAME="harmonyProject.zip"
TARGET_DIR="/opt/harmonyos/projects/harmony-app"

# 1. 定位上传的压缩包(优先 workspace 副本,兜底搜 build 目录与 /tmp)
ZIP_FILE="${WORKSPACE}/${ZIP_NAME}"
if [ ! -f "${ZIP_FILE}" ]; then
    ZIP_FILE=$(find "${JENKINS_HOME}/jobs" -path "*fileParameters*" -name "${ZIP_NAME}" -mmin -60 2>/dev/null | head -1)
fi
if [ ! -f "${ZIP_FILE}" ]; then
    ZIP_FILE=$(find /tmp -maxdepth 3 -name "${ZIP_NAME}" -mmin -60 2>/dev/null | head -1)
fi
if [ ! -f "${ZIP_FILE}" ]; then
    echo "ERROR: 未找到上传的 ${ZIP_NAME}"
    exit 1
fi
echo "压缩包: ${ZIP_FILE} ($(du -h "${ZIP_FILE}" | cut -f1))"

# 2. 清理并解压到鸿蒙目录(剔除 Mac 压缩包的 __MACOSX/.DS_Store 垃圾)
echo "=== 解压到 ${TARGET_DIR} ==="
rm -rf "${TARGET_DIR}"
mkdir -p "${TARGET_DIR}"
unzip -q "${ZIP_FILE}" -d "${TARGET_DIR}" -x "__MACOSX/*" "*.DS_Store"

# 3. 定位工程根目录(根工程的 build-profile.json5 必含 "modules" 数组;取层级最浅者,
#    避免误判 entry 等模块目录——它们也有 hvigorfile.ts 但没有 modules 数组)
HV_FILE=$(find "${TARGET_DIR}" -name hvigorfile.ts -not -path "*__MACOSX*" -type f | while read -r f; do
    d=$(dirname "${f}")
    if [ -f "${d}/build-profile.json5" ] && grep -q '"modules"' "${d}/build-profile.json5"; then
        printf '%s\t%s\n' "$(echo "${d#${TARGET_DIR}}" | tr -cd '/' | wc -c)" "${f}"
    fi
done | sort -n | head -1 | cut -f2)
if [ -z "${HV_FILE}" ]; then
    echo "ERROR: 压缩包中未找到有效的鸿蒙工程根目录(hvigorfile.ts + 含 modules 的 build-profile.json5)"
    exit 1
fi
PROJ_ROOT=$(dirname "${HV_FILE}")
echo "工程根目录: ${PROJ_ROOT}"
cd "${PROJ_ROOT}"

# 3.5 修复 Mac 导出工程常见问题:modules 数组中同名仅大小写不同的重复条目
#     (Linux 文件系统大小写敏感,hvigor 报 Duplicated modules)。
#     用括号深度跟踪定位每个模块条目的行区间,仅删除重复条目行,其余原样保留。
python3 - "${PROJ_ROOT}/build-profile.json5" <<'PYEOF'
import re, sys
p = sys.argv[1]
lines = open(p, encoding='utf-8').read().splitlines(True)
mi = None
for i, l in enumerate(lines):
    if '"modules"' in l and '[' in l:
        mi = i
        break
if mi is None:
    sys.exit(0)

def net(l):
    return l.count('[') - l.count(']') + l.count('{') - l.count('}')

depth = net(lines[mi])
blocks, cur, i = [], None, mi + 1
while i < len(lines):
    l = lines[i]
    s = l.strip()
    d = depth
    depth += net(l)
    if cur is None and s == '{' and d == 1:
        cur = i
    elif cur is not None and s in ('}', '},') and depth == 1:
        blk = ''.join(lines[cur:i + 1])
        m = re.search(r'"name"\s*:\s*"([^"]+)"', blk)
        blocks.append((cur, i + 1, m.group(1) if m else None))
        cur = None
    if depth <= 0:
        break
    i += 1
if not blocks:
    sys.exit(0)

seen, drop = set(), []
for a, b, nm in blocks:
    if not nm:
        continue
    k = nm.lower()
    if k in seen:
        drop.append((a, b, nm))
    else:
        seen.add(k)
if drop:
    dropset = set()
    for a, b, nm in drop:
        dropset.update(range(a, b))
        print("FIXED: removed duplicated module entry %s" % nm)
    open(p, 'w', encoding='utf-8').write(''.join(
        l for idx, l in enumerate(lines) if idx not in dropset))
    print("FIXED: modules %d -> %d" % (len(blocks), len(blocks) - len(drop)))
PYEOF

# 4. 补齐缺失的 hvigor 配置(基于 5.0.0(12) 模型)
if [ ! -f hvigor/hvigor-config.json5 ]; then
    echo "WARN: 缺少 hvigor/hvigor-config.json5,生成默认配置 (modelVersion 5.0.0)"
    mkdir -p hvigor
    printf '{\n  "modelVersion": "5.0.0",\n  "dependencies": {},\n  "execution": {},\n  "logging": {},\n  "debugging": {},\n  "nodeOptions": {}\n}\n' > hvigor/hvigor-config.json5
fi

# 5. 定位 hvigorw:优先项目自带,否则用全局命令行工具
if [ -f ./hvigorw ]; then
    chmod +x ./hvigorw
    HVIGOR_CMD=./hvigorw
else
    HVIGOR_CMD=/opt/harmonyos/commandline-tools/bin/hvigorw
fi
echo "hvigorw: ${HVIGOR_CMD}"

# 6. 安装 ohpm 依赖(含所有模块)
echo "=== ohpm install --all ==="
ohpm install --all

# 7. 构建 har + app 包(工程可能不含 har 模块,assembleHar 失败不中断;assembleApp 为硬性要求)
echo "=== assembleHar (${AppBuildType}) ==="
if ! "${HVIGOR_CMD}" assembleHar --mode module -p product=default -p buildMode=${AppBuildType} --no-daemon; then
    echo "WARN: assembleHar 失败或工程无 har 类型模块,继续打包 app"
fi

echo "=== assembleApp (${AppBuildType}) ==="
# assembleApp 为工程级任务(打包所有 hap 为 .app),必须用 --mode project
"${HVIGOR_CMD}" assembleApp --mode project -p product=default -p buildMode=${AppBuildType} --no-daemon

# 8. 收集构建产物(仅收集 build 输出目录,避免混入依赖包)
echo "=== 收集构建产物 ==="
OUT_DIR="${WORKSPACE}/output"
rm -rf "${OUT_DIR}"
mkdir -p "${OUT_DIR}"
find "${PROJ_ROOT}" -path "*/build/*" -not -path "*/cache/*" \( -name "*.har" -o -name "*.app" -o -name "*.hap" \) -type f | while read -r f; do
    echo "Found: ${f}"
    cp -f "${f}" "${OUT_DIR}/"
done

echo "=== 产物列表 ==="
ls -lh "${OUT_DIR}"
if [ -z "$(ls "${OUT_DIR}" 2>/dev/null)" ]; then
    echo "ERROR: 未生成任何 har/app 产物"
    exit 1
fi

echo "====== 鸿蒙打包完成 ======"

5.3 构建后操作(归档产物)

  • Archive the artifacts:output/*.har,output/*.app,output/*.hap
  • 留空"只有在构建成功时归档"以外的选项,保持默认即可。

6. 脚本设计要点(为什么这么写)

  1. set -e + 兜底查找:File Parameter 的上传文件不一定落在 workspace(Jenkins 会存到
    $JENKINS_HOME/jobs/<job>/builds/<n>/fileParameters/ 或 /tmp),所以脚本按
    workspace → jobs 目录 → /tmp 三级查找,且都限定 mmin -60(只认 1 小时内的文件,防误用旧包)。
  2. Mac zip 防御:macOS 压缩的 zip 带 __MACOSX/、.DS_Store,解压时 -x 排除,
    否则 find 定位工程根目录时会被垃圾目录干扰。
  3. 工程根目录判定:模块目录(entry 等)也有 hvigorfile.ts,但只有根工程的
    build-profile.json5 含 "modules" 数组;候选多个时取层级最浅的。
  4. 大小写重复模块自动修复:Mac(大小写不敏感文件系统)导出的工程可能 modules 里有
    Entry/entry 两个条目,Linux 上 hvigor 直接报 Duplicated modules。脚本用括号深度
    跟踪行区间,只删重复条目、不动其他内容,构建日志会打 FIXED: removed duplicated module entry xxx。
  5. hvigor 配置补齐:DevEco 导出的工程可能缺 hvigor/hvigor-config.json5,按 modelVersion
    5.0.0 生成默认值。
  6. assembleHar 软失败 / assembleApp 硬失败:普通 app 工程没有 har 类型模块,assembleHar
    失败属正常;assembleApp(--mode project)才是把所有 hap 合成 .app 的工程级任务,失败即失败。
  7. **产物收集只认 */build/***:-not -path "*/cache/*" 排除 ohpm 缓存里的第三方 har。

7. 使用方式

7.1 页面手动构建

  1. 打开 http://192.168.1.244:8080/job/%E9%B8%BF%E8%92%99%E6%89%93%E5%8C%85/ → Build with Parameters
  2. 选择 AppBuildType,点击文件框上传 harmonyProject.zip
  3. 点 Build,构建页看控制台输出
  4. 构建成功后在构建详情页 Build Artifacts 下载 output/*.app / *.har / *.hap

7.2 API 触发(可选)

带 File Parameter 的 API 触发需要 crumb + multipart 表单:

# 1) 取 crumb(需会话 cookie,否则 403)
CRUMB=$(curl -s -c /tmp/jk.cookie -u <user>:<apiToken> \
  "http://192.168.1.244:8080/crumbIssuer/api/json" | grep -oE '"crumb":"[^"]+"' | cut -d'"' -f4)

# 2) 触发构建(zip 走 -F 上传,AppBuildType 走 -F 表单字段)
curl -b /tmp/jk.cookie -u <user>:<apiToken> \
  -H "Jenkins-Crumb: ${CRUMB}" \
  -F "harmonyProject.zip=@./my-harmony-project.zip" \
  -F "AppBuildType=release" \
  "http://192.168.1.244:8080/job/鸿蒙打包/buildWithParameters"

注意:API Token 在 Jenkins → 用户 → Security 里生成,不能用登录密码(新版 Jenkins 默认禁用)。

8. 已知坑与排查

症状根因处理
ERROR: 未找到上传的 harmonyProject.zip上传文件落在 fileParameters 目录且超 60 分钟,或确实没传重新构建并确认选择文件后再点 Build;必要时放宽 mmin
Duplicated modulesMac 导出工程 modules 大小写重复脚本 3.5 自动修复;若仍报错检查手工改动过的 build-profile.json5
Duplicated files found in module entry(uni-app x 工程才有)release product 缺 nativeLib.filter.enableOverridepython3 /opt/harmonyos/patch_release_nativelib.py <工程>/harmony-configs/build-profile.json5 release
ohpm install 拉包失败内网无法访问 ohpm 官方源ohpm config set registry 指向内网/镜像源
hvigorw 报 JDK 版本不兼容JAVA_HOME 指向错误确认 /etc/profile.d/harmonyos.sh 里 JAVA_HOME=JDK21
构建成功但 Artifacts 为空工程产物在非 build 目录或后缀不同看 === 产物列表 === 与 Found: 行,调整 find 条件
上传大 zip 后 403 / 连接重置反代或 Tomcat 上传限制该任务是 Jenkins 直装 8080 无反代;如经 nginx 需调 client_max_body_size
Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐