摘要
本文记录了将 Whois 工具(版本 5.5.10)成功交叉编译到 HarmonyOS PC 平台的完整过程。Whois 是一个用于查询域名注册信息的命令行工具,在交叉编译过程中遇到了版本信息生成、依赖库检测、系统函数缺失、翻译文件构建等多个挑战。通过系统性的问题排查和解决,最终成功构建并打包为 HNP(HarmonyOS Native Package)格式。
1. 背景
1.1 项目需求
- 目标平台: HarmonyOS PC(ARM64 架构)
- 构建平台: macOS(ARM64)
- Whois 版本: 5.5.10
- 构建系统: Makefile(基于 Autotools)
- 输出格式: HNP 包
1.2 技术挑战
Whois 使用传统的 Makefile 构建系统,在交叉编译场景下,主要挑战包括:
- 版本信息生成 : 需要从
debian/changelog生成version.h - 依赖库检测 :
pkg-config工具缺失导致依赖检测失败 - 系统函数缺失 : HarmonyOS SDK 可能没有提供
getpass函数 - 翻译文件构建 :
msgfmt工具可能出现段错误 - 安装路径配置 : 需要正确设置
DESTDIR和prefix
2. 环境准备
2.1 工具链信息
HarmonyOS SDK 提供的交叉编译工具链位于:
/Users/jianguo/Desktop/ohosdk/native/llvm/bin/
关键工具:
clang: C 编译器ld.lld: 链接器llvm-ar: 归档工具llvm-ranlib: 索引工具
2.2 编译标志
bash
CFLAGS="-fPIC -D__MUSL__=1 -D__OHOS__ -fstack-protector-strong \
--target=aarch64-linux-ohos \
--sysroot=/path/to/sysroot"
LDFLAGS="-fuse-ld=${LD} --target=aarch64-linux-ohos \
--sysroot=/path/to/sysroot"
3. 问题排查与解决
3.1 问题一:IDSTRING 未定义错误
错误信息:
whois.c:81:26: error: use of undeclared identifier 'IDSTRING'
const char *client_tag = IDSTRING;
^
原因分析 :
whois.c 中使用了 IDSTRING 宏,该宏定义在 version.h 文件中。但 version.h 文件不存在,需要通过 make_version_h.pl 脚本从 debian/changelog 生成。
解决方案 :
在构建前生成 version.h 文件:
bash
# 生成 version.h(make clean 可能会删除它)
if [ ! -f "version.h" ] && [ -f "debian/changelog" ] && [ -f "make_version_h.pl" ]; then
echo "Generating version.h from debian/changelog..."
perl make_version_h.pl debian/changelog > version.h || {
echo "Error: Failed to generate version.h"
exit 1
}
fi
# 检查 version.h 是否存在
if [ ! -f "version.h" ]; then
echo "Error: version.h not found and could not be generated"
exit 1
fi
生成的 version.h 文件内容:
c
#define VERSION "5.6.5"
#define IDSTRING "Md5.6.5"
3.2 问题二:pkg-config 缺失
错误信息:
/bin/sh: pkg-config: command not found
原因分析 :
pkg-config 工具不存在,导致 Makefile 中的依赖库检测失败。虽然这些检测不是致命的,但会产生大量警告。
解决方案 :
创建一个临时的 pkg-config 包装脚本:
bash
# 设置 PKG_CONFIG 为空命令(如果不存在),避免构建失败
if ! command -v pkg-config >/dev/null 2>&1; then
echo "Warning: pkg-config not found, using false command"
# 创建一个临时的 pkg-config 包装脚本
PKG_CONFIG_WRAPPER=$(mktemp)
cat > "${PKG_CONFIG_WRAPPER}" << 'EOF'
#!/bin/sh
exit 1
EOF
chmod +x "${PKG_CONFIG_WRAPPER}"
export PKG_CONFIG="${PKG_CONFIG_WRAPPER}"
fi
这个包装脚本返回失败状态,让 Makefile 的依赖检测失败但不影响构建。
3.3 问题三:getpass 函数未实现
错误信息:
mkpasswd.c:402:13: warning: call to undeclared function 'getpass'
mkpasswd.c:402:11: error: incompatible integer to pointer conversion
ld.lld: error: undefined symbol: getpass
原因分析 :
mkpasswd.c 中使用了 getpass 函数来读取密码,但 HarmonyOS SDK 可能没有提供该函数的实现。getpass 是一个 POSIX 函数,用于从终端读取密码(不回显)。
解决方案 :
实现一个自定义的 getpass 函数:
c
/* Ensure getpass is declared and implemented */
#if !defined(HAVE_READPASSPHRASE)
/* HarmonyOS may not have getpass, provide a simple implementation */
#if !defined(getpass)
#include <termios.h>
#include <signal.h>
static char *getpass_buffer = NULL;
static void getpass_sigint(int sig) {
(void)sig;
if (getpass_buffer) {
free(getpass_buffer);
getpass_buffer = NULL;
}
signal(SIGINT, SIG_DFL);
raise(SIGINT);
}
char *getpass(const char *prompt) {
struct termios old_termios, new_termios;
FILE *tty;
int c;
size_t len = 0;
size_t size = 128;
/* Free previous buffer if exists */
if (getpass_buffer) {
free(getpass_buffer);
getpass_buffer = NULL;
}
/* Open controlling terminal */
tty = fopen("/dev/tty", "r+");
if (!tty) {
tty = stdin;
}
/* Save terminal settings */
if (tcgetattr(fileno(tty), &old_termios) == 0) {
new_termios = old_termios;
new_termios.c_lflag &= ~(ECHO | ECHOE | ECHOK | ECHONL);
tcsetattr(fileno(tty), TCSAFLUSH, &new_termios);
}
/* Set up signal handler */
signal(SIGINT, getpass_sigint);
/* Print prompt */
fprintf(tty, "%s", prompt);
fflush(tty);
/* Allocate buffer */
getpass_buffer = malloc(size);
if (!getpass_buffer) {
if (tty != stdin) fclose(tty);
return NULL;
}
/* Read password */
while ((c = fgetc(tty)) != EOF && c != '\n' && c != '\r') {
if (len + 1 >= size) {
size *= 2;
char *new_buf = realloc(getpass_buffer, size);
if (!new_buf) {
free(getpass_buffer);
getpass_buffer = NULL;
if (tty != stdin) fclose(tty);
return NULL;
}
getpass_buffer = new_buf;
}
getpass_buffer[len++] = c;
}
getpass_buffer[len] = '\0';
/* Print newline */
fprintf(tty, "\n");
fflush(tty);
/* Restore terminal settings */
if (tcgetattr(fileno(tty), &old_termios) == 0) {
tcsetattr(fileno(tty), TCSAFLUSH, &old_termios);
}
/* Restore signal handler */
signal(SIGINT, SIG_DFL);
if (tty != stdin) {
fclose(tty);
}
return getpass_buffer;
}
#endif
#endif
实现特点:
- 使用
termiosAPI 禁用终端回显 - 从控制终端读取密码
- 处理
SIGINT信号,确保中断时清理资源 - 使用静态缓冲区存储密码(符合 POSIX
getpass规范) - 支持动态扩展缓冲区大小
3.4 问题四:安装路径错误
错误信息:
install: chmod 755 /usr/bin/: Operation not permitted
install: /usr/bin/whois: Operation not permitted
原因分析 :
Makefile 默认安装到 /usr/bin/,这是系统目录,需要 root 权限。在交叉编译场景下,应该安装到指定的安装目录。
解决方案 :
设置 DESTDIR 和 prefix 环境变量:
bash
# 设置安装路径
export DESTDIR=${TREE_INSTALL_HNP_PATH}
export prefix=/usr
这样文件会安装到 ${TREE_INSTALL_HNP_PATH}/usr/bin/,而不是系统的 /usr/bin/。
3.5 问题五:翻译文件构建失败
错误信息:
msgfmt --statistics --check --verbose --output-file=cs.mo cs.po
make[1]: *** [cs.mo] Segmentation fault: 11
原因分析 :
msgfmt 工具在构建翻译文件时出现段错误。这可能是工具本身的问题,或者是交叉编译环境导致的。
解决方案 :
将翻译文件构建设为可选,失败不影响整体构建:
bash
# 构建主程序(跳过翻译文件,因为 msgfmt 可能有问题)
make VERBOSE=1 whois mkpasswd || {
echo "Error: Failed to build whois or mkpasswd"
exit 1
}
# 安装主程序(跳过翻译文件安装)
make install-whois install-mkpasswd install-bashcomp || {
echo "Error: Failed to install whois or mkpasswd"
exit 1
}
# 尝试安装翻译文件(如果失败不影响整体构建)
make install-pos || {
echo "Warning: Failed to install translation files, continuing..."
}
3.6 问题六:构建结果路径输出
需求 :
构建成功后,需要打印生成的 HNP 包和 tar 包的路径,方便用户查找和使用。
解决方案 :
在构建脚本末尾添加结果输出:
bash
# 打包 HNP
HNP_FILE="${ARCHIVE_PATH}/whois.hnp"
${HNP_TOOL} pack -i ${TREE_INSTALL_HNP_PATH} -o ${ARCHIVE_PATH}/ || {
echo "Error: Failed to pack HNP file"
exit 1
}
# 打包 tar.gz
TAR_FILE="${ARCHIVE_PATH}/ohos_whois_5.5.10.tar.gz"
tar -zvcf ${TAR_FILE} whois_5.5.10/ || {
echo "Error: Failed to create tar archive"
exit 1
}
# 打印构建结果
echo ""
echo "=========================================="
echo "Build completed successfully!"
echo "=========================================="
echo "HNP Package: ${HNP_FILE}"
echo "Tar Archive: ${TAR_FILE}"
echo "Installation Path: ${TREE_INSTALL_HNP_PATH}"
echo "=========================================="
echo ""
4. 最终构建脚本
完整的构建脚本 build_ohos.sh 包含以下关键步骤:
4.1 版本信息生成
bash
make clean
# 生成 version.h(make clean 可能会删除它)
if [ ! -f "version.h" ] && [ -f "debian/changelog" ] && [ -f "make_version_h.pl" ]; then
echo "Generating version.h from debian/changelog..."
perl make_version_h.pl debian/changelog > version.h || {
echo "Error: Failed to generate version.h"
exit 1
}
fi
4.2 pkg-config 处理
bash
# 设置 PKG_CONFIG 为空命令(如果不存在),避免构建失败
if ! command -v pkg-config >/dev/null 2>&1; then
echo "Warning: pkg-config not found, using false command"
PKG_CONFIG_WRAPPER=$(mktemp)
cat > "${PKG_CONFIG_WRAPPER}" << 'EOF'
#!/bin/sh
exit 1
EOF
chmod +x "${PKG_CONFIG_WRAPPER}"
export PKG_CONFIG="${PKG_CONFIG_WRAPPER}"
fi
4.3 构建和安装
bash
# 设置安装路径
export DESTDIR=${TREE_INSTALL_HNP_PATH}
export prefix=/usr
# 构建主程序(跳过翻译文件)
make VERBOSE=1 whois mkpasswd || {
echo "Error: Failed to build whois or mkpasswd"
exit 1
}
# 安装主程序
make install-whois install-mkpasswd install-bashcomp || {
echo "Error: Failed to install whois or mkpasswd"
exit 1
}
# 尝试安装翻译文件(可选)
make install-pos || {
echo "Warning: Failed to install translation files, continuing..."
}
4.4 打包和输出
bash
# 打包 HNP 和 tar.gz
# ... (见问题六的解决方案)
# 打印构建结果
echo ""
echo "=========================================="
echo "Build completed successfully!"
echo "=========================================="
echo "HNP Package: ${HNP_FILE}"
echo "Tar Archive: ${TAR_FILE}"
echo "Installation Path: ${TREE_INSTALL_HNP_PATH}"
echo "=========================================="
echo ""
5. HNP 包配置
创建 hnp.json 配置文件:
json
{
"type": "hnp-config",
"name": "whois",
"version": "5.5.10",
"install": {}
}
6. 构建结果
成功构建后,生成以下内容:
whois_5.5.10/
├── usr/
│ ├── bin/
│ │ ├── whois # 主程序
│ │ └── mkpasswd # 密码生成工具
│ └── share/
│ └── man/
│ ├── man1/
│ │ ├── whois.1
│ │ └── mkpasswd.1
│ └── man5/
│ └── whois.conf.5
├── etc/
│ └── bash_completion.d/
│ ├── whois
│ └── mkpasswd
└── hnp.json # HNP 包配置
7. 关键要点总结
7.1 版本信息管理
- 生成时机 : 在
make clean之后生成version.h - 生成工具 : 使用
make_version_h.pl从debian/changelog提取版本信息 - 检查机制: 生成后检查文件是否存在,确保构建可以继续
7.2 依赖库检测
- pkg-config 包装: 创建临时脚本返回失败状态
- 可选依赖: 大多数依赖库检测失败不影响构建
- 清理机制: 构建完成后清理临时文件
7.3 系统函数实现
- getpass 实现 : 使用
termiosAPI 实现密码输入 - 信号处理 : 正确处理
SIGINT信号 - 内存管理: 使用静态缓冲区(符合 POSIX 规范)
- 错误处理: 完善的错误处理和资源清理
7.4 构建流程优化
- 分离构建: 主程序和翻译文件分开构建
- 可选组件: 翻译文件构建失败不影响主程序
- 错误处理: 每个步骤都有错误检查和退出机制
- 结果输出: 构建完成后打印包文件路径
8. 常见问题
Q1: 为什么需要实现 getpass 函数?
A: HarmonyOS SDK 可能没有提供 getpass 函数的实现。mkpasswd 工具需要这个函数来安全地读取用户密码(不回显)。我们实现了一个符合 POSIX 规范的替代实现。
Q2: 为什么翻译文件构建失败不影响构建?
A: 翻译文件(.mo 文件)是可选的,主要用于多语言支持。主程序(whois 和 mkpasswd)的功能不依赖于翻译文件,即使翻译文件构建失败,主程序仍然可以正常使用。
Q3: 为什么使用静态缓冲区存储密码?
A: POSIX getpass 函数规范要求使用静态缓冲区。虽然这在多线程环境中可能不安全,但对于 mkpasswd 这种单线程命令行工具来说是可以接受的。
Q4: pkg-config 包装脚本的作用是什么?
A: Makefile 使用 pkg-config 来检测可选依赖库(如 libidn2、libidn 等)。如果 pkg-config 不存在,这些检测会失败,但不会影响构建。包装脚本返回失败状态,让检测失败但不产生错误信息。
9. 参考资源
10. 版本信息
- Whois 版本: 5.5.10
- 构建日期: 2024年
- 目标平台: HarmonyOS PC (ARM64)
- 构建平台: macOS (ARM64)
- 工具链: HarmonyOS SDK LLVM