解决 Lycium 增量编译中的产物丢失问题,使其能在鸿蒙PC上运行
·
解决 Lycium 增量编译中的产物丢失问题
问题描述
在使用 Lycium++ 构建系统时,发现了一个增量编译的问题:
# 第一次编译成功
$ ./build.sh tree
ALL JOBS DONE!!!
# 手动删除产物目录
$ rm -rf lycium/usr/tree lycium/output
# 再次编译,期望重新生成产物
$ ./build.sh tree
ALL JOBS DONE!!! # ← 立即完成,没有实际编译
# 检查产物
$ ls lycium/usr/tree
ls: lycium/usr/tree: No such file or directory # ❌ 产物未生成
问题现象
- ✅ 第一次编译成功,生成所有产物
- ❌ 删除
lycium/usr/或lycium/output/目录后 - ❌ 再次编译会立即完成(ALL JOBS DONE),但不生成产物
- ❌ 必须删除
outerrepo/tree/才能触发重新编译
问题分析
编译系统的增量编译机制
Lycium 使用 lycium/usr/hpk_build.csv 文件记录已编译的库:
pkgname,version,architecture
tree,2.2.1,arm64-v8a
zlib,1.3.1,arm64-v8a
openssl,3.0.0,arm64-v8a
执行流程
1. build.sh 主流程
main() {
preparetoolchain # 准备工具链
checkbuildenv # 检查环境
readdonelibs "$LYCIUM_ROOT/usr/hpk_build.csv" # ← 读取已编译记录
collectcommunitylibs # 收集社区库
findarglibsdir $* # 查找待编译库
prepareshell # 准备脚本
buildhpk # 执行编译
cleanhpkdir # 清理
}
2. readdonelibs() 函数
readdonelibs() {
if [ -f $1 ]; then
count=0
while read line; do
libinfos=(${line//,/ })
libname=${libinfos[0]} # 提取库名
# 添加到已完成列表
donelibs[$count]=$libname
count=$((count+1))
done < $1
fi
donelist=(${donelibs[@]})
}
读取 CSV 文件,将所有库名存入 donelibs 数组。
3. makelibsdir() 函数
makelibsdir() {
jobs=($*)
for job in ${jobs[@]}; do
doneflags=false
libname=${job##*/} # 提取库名
# 检查是否在已编译列表中
for donelib in ${donelibs[@]}; do
if [ $donelib == $libname ]; then
doneflags=true # 标记为已完成
fi
done
# ⚠️ 关键问题:直接跳过已标记的库
if $doneflags; then
continue # 不加入编译队列
fi
# 只有未标记的库才加入编译队列
if [[ -d $job && -f $job/HPKBUILD ]]; then
hpkPaths[${#hpkPaths[@]}]=$job
fi
done
}
问题根源
核心问题:编译系统只检查 CSV 记录,不验证实际产物是否存在。
┌─────────────────────────────────────────┐
│ hpk_build.csv 中有 tree 的记录 │
│ → makelibsdir() 认为已编译 │
│ → 跳过 tree,不加入编译队列 │
│ → 即使 usr/tree/ 被删除 │
│ → 也不会重新编译 │
└─────────────────────────────────────────┘
为什么删除 outerrepo/tree/ 可以触发编译?
findarglibsdir() {
outerlib=$LYCIUM_ROOT/$outerrepodir/$lib
if [[ -d $outerlib && -f $outerlib/HPKBUILD ]]; then
tmplibs[${#tmplibs[@]}]=$outerlib
else
# outerrepo/tree 不存在,重新克隆
python3 load_outer_parts.py --manifestfile=...
fi
makelibsdir ${tmplibs[@]}
}
删除 outerrepo/tree/ 后:
- Git 会重新克隆仓库
build_hpk.sh重新链接到新目录- 新的构建过程不受旧 CSV 记录影响(因为重新链接了脚本)
解决方案
方案 1:手动清理编译记录(临时方案)
清理特定库
cd lycium
# 方法 A:使用 sed 删除特定行
sed -i.bak '/^tree,/d' usr/hpk_build.csv
# 方法 B:使用 grep 过滤
grep -v '^tree,' usr/hpk_build.csv > usr/hpk_build.csv.tmp
mv usr/hpk_build.csv.tmp usr/hpk_build.csv
# 重新编译
./build.sh tree
清空所有记录
# 完全清空 CSV(谨慎使用!)
> lycium/usr/hpk_build.csv
# 或删除整个文件
rm lycium/usr/hpk_build.csv
# 重新编译所有库
./build.sh
方案 2:添加强制编译参数(推荐)
修改 build.sh,添加 --force 参数支持。
修改代码
main() {
# 准备编译工具链
preparetoolchain
# 检查编译环境
checkbuildenv
# ====== 新增:解析命令行参数 ======
FORCE_BUILD=false
LIBS=()
for arg in "$@"; do
if [ "$arg" == "--force" ] || [ "$arg" == "-f" ]; then
FORCE_BUILD=true
echo "Force rebuild mode enabled"
else
LIBS+=("$arg")
fi
done
# ====== 修改:条件读取编译记录 ======
if [ "$FORCE_BUILD" == "false" ]; then
# 读取编译记录
readdonelibs "$LYCIUM_ROOT/usr/hpk_build.csv"
else
echo "Skipping build record check"
fi
# 搜集 communitydir 目录库
collectcommunitylibs $communitydir
# ====== 修改:使用 LIBS 数组 ======
if [ ${#LIBS[@]} -ne 0 ]; then
# 搜集指定的libs
findarglibsdir "${LIBS[@]}"
else
# 搜集所有可编译的库
findhpkdir $hpksdir
fi
# 准备脚本软链接
prepareshell ${hpkPaths[@]}
# 编译
buildhpk
# 清理
cleanhpkdir
unset LYCIUM_BUILD_OS LYCIUM_ROOT CLANG_VERSION
}
使用方式
# 强制重新编译单个库
./build.sh --force tree
# 强制重新编译多个库
./build.sh --force tree zlib openssl
# 强制重新编译所有库
./build.sh --force
# 简写形式
./build.sh -f tree
方案 3:智能检查产物(最佳方案)
修改 makelibsdir() 函数,在检查 CSV 记录时同时验证产物是否存在。
修改代码
makelibsdir() {
jobs=($*)
for job in ${jobs[@]}
do
doneflags=false
libname=${job##*/} # 截取库名
# 检查是否在已编译列表中
for donelib in ${donelibs[@]}
do
if [ $donelib == $libname ]
then
# ====== 新增:验证产物是否存在 ======
artifact_dir="$LYCIUM_ROOT/usr/$libname"
if [ -d "$artifact_dir" ] && [ "$(ls -A $artifact_dir 2>/dev/null)" ]; then
# 产物存在,跳过编译
doneflags=true
echo "Skipping $libname (already built with artifacts)"
else
# 产物不存在,清理记录并重新编译
echo "Warning: $libname marked as done but artifacts missing, rebuilding..."
# 从 CSV 中删除该记录
if [ -f "$LYCIUM_ROOT/usr/hpk_build.csv" ]; then
sed -i.bak "/^$libname,/d" "$LYCIUM_ROOT/usr/hpk_build.csv"
fi
doneflags=false
fi
fi
done
if $doneflags
then
continue
fi
if [[ -d $job && -f $job/HPKBUILD ]]
then
hpkPaths[${#hpkPaths[@]}]=$job
fi
done
}
优点
- ✅ 自动检测产物缺失
- ✅ 自动清理无效记录
- ✅ 无需手动干预
- ✅ 保持增量编译的性能优势
验证测试
# 第一次编译
$ ./build.sh tree
Start building tree 2.2.1!
...
Build tree 2.2.1 end!
ALL JOBS DONE!!!
# 删除产物
$ rm -rf lycium/usr/tree lycium/output
# 第二次编译(自动检测并重新编译)
$ ./build.sh tree
Warning: tree marked as done but artifacts missing, rebuilding...
Start building tree 2.2.1!
...
Build tree 2.2.1 end!
ALL JOBS DONE!!!
方案 4:清理脚本(辅助工具)
创建一个清理脚本简化操作。
创建脚本
#!/bin/bash
# Lycium 清理脚本
# 用于清理编译记录和产物
LYCIUM_ROOT=$(cd $(dirname ${BASH_SOURCE[0]}); pwd)
show_help() {
cat << EOF
Usage: $0 [OPTIONS] [LIBRARY...]
清理 Lycium 构建产物和记录
OPTIONS:
-h, --help 显示此帮助信息
-a, --all 清理所有库的记录和产物
-r, --record 仅清理编译记录(保留产物)
-o, --output 仅清理 output 目录
-u, --usr 仅清理 usr 目录
EXAMPLES:
$0 tree # 清理 tree 的记录和产物
$0 -r tree # 仅清理 tree 的编译记录
$0 --all # 清理所有记录和产物
$0 tree zlib openssl # 清理多个库
EOF
}
clean_library() {
local libname=$1
local clean_record=${2:-true}
local clean_usr=${3:-true}
local clean_output=${4:-true}
echo "Cleaning $libname..."
# 清理编译记录
if [ "$clean_record" == "true" ]; then
if [ -f "$LYCIUM_ROOT/usr/hpk_build.csv" ]; then
sed -i.bak "/^$libname,/d" "$LYCIUM_ROOT/usr/hpk_build.csv"
echo " ✓ Removed from build record"
fi
fi
# 清理 usr 目录
if [ "$clean_usr" == "true" ]; then
if [ -d "$LYCIUM_ROOT/usr/$libname" ]; then
rm -rf "$LYCIUM_ROOT/usr/$libname"
echo " ✓ Removed usr/$libname/"
fi
fi
# 清理 output 目录
if [ "$clean_output" == "true" ]; then
for arch_dir in "$LYCIUM_ROOT/output"/*; do
if [ -d "$arch_dir" ]; then
rm -f "$arch_dir/${libname}"*.tar.gz "$arch_dir/${libname}".hnp
echo " ✓ Removed output artifacts"
fi
done
fi
}
# 解析参数
CLEAN_RECORD=true
CLEAN_USR=true
CLEAN_OUTPUT=true
LIBS=()
while [[ $# -gt 0 ]]; do
case $1 in
-h|--help)
show_help
exit 0
;;
-a|--all)
echo "Cleaning all libraries..."
rm -f "$LYCIUM_ROOT/usr/hpk_build.csv"
rm -rf "$LYCIUM_ROOT/usr"/*
rm -rf "$LYCIUM_ROOT/output"/*
echo "✓ All cleaned"
exit 0
;;
-r|--record)
CLEAN_USR=false
CLEAN_OUTPUT=false
shift
;;
-o|--output)
CLEAN_RECORD=false
CLEAN_USR=false
shift
;;
-u|--usr)
CLEAN_RECORD=false
CLEAN_OUTPUT=false
shift
;;
*)
LIBS+=("$1")
shift
;;
esac
done
# 清理指定的库
if [ ${#LIBS[@]} -eq 0 ]; then
echo "Error: No library specified"
echo "Use --help for usage information"
exit 1
fi
for lib in "${LIBS[@]}"; do
clean_library "$lib" "$CLEAN_RECORD" "$CLEAN_USR" "$CLEAN_OUTPUT"
done
echo ""
echo "Cleanup completed. You can now rebuild with:"
echo " ./build.sh ${LIBS[@]}"
使用示例
# 赋予执行权限
chmod +x lycium/clean.sh
# 清理单个库
./clean.sh tree
# 清理多个库
./clean.sh tree zlib openssl
# 只清理编译记录(保留产物)
./clean.sh -r tree
# 只清理产物(保留记录)
./clean.sh -u tree
# 清理所有
./clean.sh --all
最佳实践
1. 开发调试时
# 修改了 HPKBUILD 后强制重新编译
./build.sh --force tree
# 或先清理再编译
./clean.sh tree && ./build.sh tree
2. CI/CD 环境
# 完全清理后构建(确保干净环境)
./clean.sh --all
./build.sh all_libs
3. 日常使用
# 正常编译(使用增量编译)
./build.sh tree
# 产物丢失时自动重建(方案 3 实现后)
./build.sh tree # 自动检测并重建
总结
问题本质
Lycium 的增量编译机制依赖 CSV 文件记录,但不验证实际产物:
| 检查项 | 当前实现 | 改进后 |
|---|---|---|
| CSV 记录 | ✅ 检查 | ✅ 检查 |
| 产物目录存在 | ❌ 不检查 | ✅ 检查 |
| 产物文件存在 | ❌ 不检查 | ✅ 检查 |
解决方案对比
| 方案 | 优点 | 缺点 | 推荐度 |
|---|---|---|---|
| 方案 1:手动清理 | 简单快速 | 需要记住命令 | ⭐⭐ |
| 方案 2:强制参数 | 用户控制 | 每次都要加参数 | ⭐⭐⭐ |
| 方案 3:智能检查 | 自动处理 | 需要修改核心代码 | ⭐⭐⭐⭐⭐ |
| 方案 4:清理脚本 | 功能完整 | 额外维护脚本 | ⭐⭐⭐⭐ |
建议
- 短期:使用方案 1(手动清理)+ 方案 4(清理脚本)
- 长期:实现方案 3(智能检查)+ 方案 2(强制参数)作为备选
问题类型: 增量编译逻辑缺陷
影响范围: 所有使用 Lycium 构建的库
解决状态: 已提供多种解决方案
推荐方案: 方案 3(智能检查产物)
关键词: Lycium, 增量编译, hpk_build.csv, 产物验证, 构建系统
更多推荐




所有评论(0)