本文记录在开源项目Termony中使用命令 OHOS_ARCH=aarch64 OHOS_ABI=arm64-v8a sh ./create-hnp.sh 构建 Libunistring 1.3 的完整过程,包括环境、构建链路、关键日志、常见问题与解决方案、产物验证与重建方法,便于复现与运维。

📖 Libunistring 简介

Libunistring 是一个用于操作 Unicode 字符串的 C 库,提供了丰富的 Unicode 字符串处理功能。它是 GNU 项目的一部分,为需要处理国际化字符串的应用程序提供了强大的支持。

🎯 Libunistring 的作用与重要性

Libunistring 是 Unicode 字符串处理的核心库,提供了:

  • Unicode 字符串操作:完整的 Unicode 字符串处理功能
  • 字符编码转换:支持多种字符编码之间的转换
  • 大小写转换:Unicode 感知的大小写转换
  • 字符分类:Unicode 字符类型和属性查询
  • 文本规范化:Unicode 文本规范化(NFC、NFD、NFKC、NFKD)
  • 文本宽度计算:计算文本显示宽度(用于终端显示)

🔧 Libunistring 核心特性

1. 字符串操作模块
  • unistr.h:基本 Unicode 字符串操作
    • 字符串创建、复制、连接、比较
    • 字符串搜索、替换、分割
    • 字符串长度计算、子串提取
  • uniconv.h:字符编码转换
    • 支持多种字符编码(UTF-8、UTF-16、UTF-32、ISO-8859-* 等)
    • 编码检测和转换
    • 错误处理
2. 文本处理模块
  • unicase.h:大小写转换
    • Unicode 感知的大小写转换
    • 大小写折叠
    • 大小写比较
  • unictype.h:字符分类
    • Unicode 字符类型查询
    • 字符属性查询
    • 字符范围检查
  • uninorm.h:文本规范化
    • NFC(规范化形式 C)
    • NFD(规范化形式 D)
    • NFKC(兼容规范化形式 C)
    • NFKD(兼容规范化形式 D)
3. 文本布局模块
  • uniwidth.h:文本宽度计算
    • 计算文本显示宽度
    • 支持全角和半角字符
    • 用于终端和 GUI 显示
  • unigbrk.h:字素边界
    • 字素簇检测
    • 字素边界定位
  • uniwbrk.h:词边界
    • 词边界检测
    • 分词功能
  • unilbrk.h:行边界
    • 行断行检测
    • 文本换行处理
4. 应用场景
  • 国际化应用:处理多语言文本
  • 文本编辑器:Unicode 文本编辑功能
  • 网络应用:处理国际化域名和 URL
  • 数据库:Unicode 字符串存储和查询
  • 文本处理工具:文本分析和处理

🚀 构建入口与环境

  • 📝 执行命令OHOS_ARCH=aarch64 OHOS_ABI=arm64-v8a sh ./create-hnp.sh
  • 🔧 入口脚本create-hnp.sh
    • 检查必需的环境变量 OHOS_ARCHOHOS_ABI
    • 导出 LC_CTYPETOOL_HOMEOHOS_SDK_HOME
    • 执行 make -C build-hnp
  • 📦 顶层构建build-hnp/Makefile
    • PKGS 变量定义需要构建的包列表(包含 libunistring
    • 通过 check-pkgs 机制自动检测 PKGS 变化并触发重新构建
    • 自动合并 external-hnp 目录下的外部 HNP 包
    • base.hnp 依赖所有包的 .stamp 和外部 HNP 包
    • 总目标 all: copy,打包 base.hnp 并拷贝到 entry/hnp/$(OHOS_ABI)

⚙️ Libunistring 包的构建配置

  • 📁 包目录build-hnp/libunistring/Makefile
    • 继承通用规则:include ../utils/Makefrag
    • 源地址:$(GNU_MIRROR)/gnu/libunistring/libunistring-1.3.tar.xz
    • 版本:1.3
  • ⚙️ Autotools 配置参数
    • --prefix=$(PREFIX) - 安装前缀(/data/app/base.org/base_1.0
    • --host $(OHOS_ARCH)-unknown-linux-musl - 目标平台
    • --disable-static - 禁用静态库
    • --enable-shared - 构建共享库
  • 🔨 构建流程
    1. 下载源码包(支持多镜像回退)
    2. 解包并进入 temp/libunistring-1.3 目录
    3. 运行 ./configure 配置构建系统
    4. 使用 make -j $(nproc) 并行编译
    5. 使用 make install 安装
    6. 执行 ELF strip 减小体积
    7. 复制到 ../sysroot
  • 🔧 通用工具链与路径build-hnp/utils/Makefrag
    • CC/CXX/LD/AR/RANLIB/... 均指向 OHOS SDK 的 LLVM 工具链
    • 下载支持多镜像回退:wgetcurl,主镜像失败时自动尝试备用镜像

📋 关键日志与过程节点

  • 📥 下载与解包
    • 从 GNU 镜像下载 libunistring-1.3.tar.xz
    • 完成解包并进入 temp/libunistring-1.3 目录
    • 下载规则支持多镜像回退:wgetcurl 兜底
  • ⚙️ 配置阶段
    • 运行 ./configure --prefix=... --disable-static --enable-shared --host=aarch64-unknown-linux-musl ...
    • 配置警告:using cross tools not prefixed with host triplet(不影响构建)
    • 标准头与接口探测全面通过(stdio.hwchar.hpthread.harpa/inet.h 等)
    • 链接器 ld.lld 与工具链 llvm-ar/llvm-ranlib 检测成功
    • 配置成功,启用共享库构建
    • 生成 Makefile 和构建配置
  • 🔨 编译与安装
    • 使用 make -j $(nproc) 并行编译
    • 成功编译生成 libunistring.so.5.2.0 和全套头文件
    • 使用 make install 安装到临时前缀
    • 执行 llvm-strip 剥离共享库符号
    • 复制到 ../sysroot
  • 📦 打包
    • 完成 base.hnp 重打包,拷贝产物到 entry/hnp/arm64-v8a/
    • Libunistring 库已成功打包到 base.hnp

✅ 产物验证

📦 检查打包文件

ls build-hnp/base.hnp  # 应存在
ls entry/hnp/arm64-v8a/*.hnp  # 应包含 base.hnp 与 base-public.hnp

🔍 检查库文件和头文件

# 检查库文件
ls -lh build-hnp/sysroot/lib/libunistring.so*
file build-hnp/sysroot/lib/libunistring.so.5.2.0

# 检查主要头文件
ls -lh build-hnp/sysroot/include/unistr.h
ls -lh build-hnp/sysroot/include/uniconv.h
ls -lh build-hnp/sysroot/include/unicase.h
ls -lh build-hnp/sysroot/include/unictype.h

# 检查辅助头文件目录
ls -lh build-hnp/sysroot/include/unistring/*.h

✅ 构建验证结果

  • ✅ Libunistring 库已成功安装:
    • libunistring.so.5.2.0 (2.0M) - 主库文件
    • libunistring.so.5 - 版本符号链接
    • libunistring.so - 通用符号链接
  • ✅ 文件类型:ELF 64-bit LSB shared object, ARM aarch64
  • ✅ 动态链接:interpreter /lib/ld-musl-aarch64.so.1
  • ✅ 已剥离符号:stripped
  • ✅ 头文件已安装:
    • unistr.h (24K) - Unicode 字符串操作
    • uniconv.h (7.4K) - 字符编码转换
    • unicase.hunictype.huninorm.huniwidth.h
    • unistring/ 子目录中的辅助头文件
  • ✅ 已打包到 base.hnp 中(2,033,704 字节)

💻 终端中执行的示例命令

📝 Libunistring 编程示例

1. 基本字符串操作(unistr.h)
// 示例:基本 Unicode 字符串操作
#include <unistr.h>
#include <stdio.h>

int main() {
    // 创建 Unicode 字符串
    uint8_t *str1 = u8_strdup((uint8_t *)"Hello, 世界");
    uint8_t *str2 = u8_strdup((uint8_t *)"Hello, World");
    
    // 获取字符串长度(字符数,不是字节数)
    size_t len1 = u8_strlen(str1);
    size_t len2 = u8_strlen(str2);
    printf("Length of str1: %zu\n", len1);
    printf("Length of str2: %zu\n", len2);
    
    // 字符串比较
    int cmp = u8_strcmp(str1, str2);
    if (cmp == 0) {
        printf("Strings are equal\n");
    } else if (cmp < 0) {
        printf("str1 < str2\n");
    } else {
        printf("str1 > str2\n");
    }
    
    // 字符串连接
    uint8_t *result = u8_strconcat(str1, (uint8_t *)" - ", str2, NULL);
    printf("Concatenated: %s\n", result);
    
    // 清理内存
    free(str1);
    free(str2);
    free(result);
    
    return 0;
}

// 编译命令
// gcc -o test_unistr test_unistr.c -lunistring
2. 字符编码转换(uniconv.h)
// 示例:字符编码转换
#include <uniconv.h>
#include <stdio.h>
#include <stdlib.h>

int main() {
    // UTF-8 字符串
    const char *utf8_str = "Hello, 世界";
    
    // 转换为 UTF-16
    size_t utf16_len;
    uint16_t *utf16_str = u8_to_u16((uint8_t *)utf8_str, strlen(utf8_str), NULL, &utf16_len);
    
    if (utf16_str != NULL) {
        printf("UTF-16 length: %zu\n", utf16_len);
        
        // 转换回 UTF-8
        size_t utf8_len;
        uint8_t *utf8_result = u16_to_u8(utf16_str, utf16_len, NULL, &utf8_len);
        
        if (utf8_result != NULL) {
            printf("UTF-8 result: %s\n", utf8_result);
            free(utf8_result);
        }
        
        free(utf16_str);
    }
    
    return 0;
}

// 编译命令
// gcc -o test_uniconv test_uniconv.c -lunistring
3. 大小写转换(unicase.h)
// 示例:大小写转换
#include <unicase.h>
#include <unistr.h>
#include <stdio.h>
#include <stdlib.h>

int main() {
    // 原始字符串
    uint8_t *str = u8_strdup((uint8_t *)"Hello, 世界");
    
    // 转换为小写
    size_t lower_len;
    uint8_t *lower = u8_tolower(str, u8_strlen(str), NULL, NULL, &lower_len);
    
    if (lower != NULL) {
        printf("Lowercase: %s\n", lower);
        free(lower);
    }
    
    // 转换为大写
    size_t upper_len;
    uint8_t *upper = u8_toupper(str, u8_strlen(str), NULL, NULL, &upper_len);
    
    if (upper != NULL) {
        printf("Uppercase: %s\n", upper);
        free(upper);
    }
    
    free(str);
    return 0;
}

// 编译命令
// gcc -o test_unicase test_unicase.c -lunistring
4. 字符分类(unictype.h)
// 示例:字符分类
#include <unictype.h>
#include <unistr.h>
#include <stdio.h>

int main() {
    uint8_t *str = u8_strdup((uint8_t *)"Hello123世界");
    size_t len = u8_strlen(str);
    
    for (size_t i = 0; i < len; i++) {
        ucs4_t ch = u8_strchr(str, len, str[i]) ? u8_next(str[i], str + i, len - i) : 0;
        
        if (ch != 0) {
            printf("Character: %c (U+%04X)\n", (char)ch, ch);
            printf("  Is letter: %d\n", uc_is_alpha(ch));
            printf("  Is digit: %d\n", uc_is_digit(ch));
            printf("  Is space: %d\n", uc_is_space(ch));
            printf("  Is upper: %d\n", uc_is_upper(ch));
            printf("  Is lower: %d\n", uc_is_lower(ch));
        }
    }
    
    free(str);
    return 0;
}

// 编译命令
// gcc -o test_unictype test_unictype.c -lunistring
5. 文本规范化(uninorm.h)
// 示例:文本规范化
#include <uninorm.h>
#include <unistr.h>
#include <stdio.h>
#include <stdlib.h>

int main() {
    uint8_t *str = u8_strdup((uint8_t *)"Café");
    size_t len = u8_strlen(str);
    
    // NFC 规范化
    size_t nfc_len;
    uint8_t *nfc = u8_normalize(UNINORM_NFC, str, len, NULL, &nfc_len);
    
    if (nfc != NULL) {
        printf("NFC normalized: %s\n", nfc);
        free(nfc);
    }
    
    // NFD 规范化
    size_t nfd_len;
    uint8_t *nfd = u8_normalize(UNINORM_NFD, str, len, NULL, &nfd_len);
    
    if (nfd != NULL) {
        printf("NFD normalized: %s\n", nfd);
        free(nfd);
    }
    
    free(str);
    return 0;
}

// 编译命令
// gcc -o test_uninorm test_uninorm.c -lunistring
6. 文本宽度计算(uniwidth.h)
// 示例:文本宽度计算
#include <uniwidth.h>
#include <unistr.h>
#include <stdio.h>

int main() {
    uint8_t *str1 = u8_strdup((uint8_t *)"Hello");
    uint8_t *str2 = u8_strdup((uint8_t *)"世界");
    uint8_t *str3 = u8_strdup((uint8_t *)"Hello世界");
    
    // 计算显示宽度
    int width1 = u8_width(str1, u8_strlen(str1), "UTF-8");
    int width2 = u8_width(str2, u8_strlen(str2), "UTF-8");
    int width3 = u8_width(str3, u8_strlen(str3), "UTF-8");
    
    printf("Width of '%s': %d\n", str1, width1);
    printf("Width of '%s': %d\n", str2, width2);
    printf("Width of '%s': %d\n", str3, width3);
    
    free(str1);
    free(str2);
    free(str3);
    
    return 0;
}

// 编译命令
// gcc -o test_uniwidth test_uniwidth.c -lunistring
7. 字符串搜索和替换
// 示例:字符串搜索和替换
#include <unistr.h>
#include <stdio.h>
#include <stdlib.h>

int main() {
    uint8_t *str = u8_strdup((uint8_t *)"Hello, 世界! Hello, World!");
    uint8_t *search = u8_strdup((uint8_t *)"Hello");
    uint8_t *replace = u8_strdup((uint8_t *)"Hi");
    
    // 查找子串
    uint8_t *found = u8_strstr(str, search);
    if (found != NULL) {
        printf("Found '%s' at position %ld\n", search, found - str);
    }
    
    // 替换(简化示例)
    // 注意:实际替换需要手动实现或使用其他函数
    
    free(str);
    free(search);
    free(replace);
    
    return 0;
}

// 编译命令
// gcc -o test_search test_search.c -lunistring
8. 字符串分割
// 示例:字符串分割
#include <unistr.h>
#include <stdio.h>
#include <stdlib.h>

int main() {
    uint8_t *str = u8_strdup((uint8_t *)"apple,banana,orange");
    uint8_t *delim = u8_strdup((uint8_t *)",");
    
    // 分割字符串
    uint8_t *token = u8_strtok(str, delim, NULL);
    int count = 0;
    
    while (token != NULL) {
        printf("Token %d: %s\n", count++, token);
        token = u8_strtok(NULL, delim, NULL);
    }
    
    free(str);
    free(delim);
    
    return 0;
}

// 编译命令
// gcc -o test_split test_split.c -lunistring
9. 实际应用示例
// 示例:国际化字符串处理工具
#include <unistr.h>
#include <unicase.h>
#include <uninorm.h>
#include <stdio.h>
#include <stdlib.h>

// 函数:规范化并转换为小写
uint8_t *normalize_and_lowercase(const char *input) {
    uint8_t *str = u8_strdup((uint8_t *)input);
    size_t len = u8_strlen(str);
    
    // NFC 规范化
    size_t nfc_len;
    uint8_t *nfc = u8_normalize(UNINORM_NFC, str, len, NULL, &nfc_len);
    free(str);
    
    if (nfc == NULL) return NULL;
    
    // 转换为小写
    size_t lower_len;
    uint8_t *lower = u8_tolower(nfc, nfc_len, NULL, NULL, &lower_len);
    free(nfc);
    
    return lower;
}

int main() {
    const char *test_strings[] = {
        "Café",
        "Hello, 世界",
        "Привет",
        "こんにちは"
    };
    
    for (int i = 0; i < 4; i++) {
        uint8_t *result = normalize_and_lowercase(test_strings[i]);
        if (result != NULL) {
            printf("Input: %s\n", test_strings[i]);
            printf("Normalized & Lowercase: %s\n", result);
            printf("\n");
            free(result);
        }
    }
    
    return 0;
}

// 编译命令
// gcc -o test_i18n test_i18n.c -lunistring
10. 使用 pkg-config
# 使用 pkg-config 获取编译和链接标志
pkg-config --cflags libunistring
pkg-config --libs libunistring

# 编译示例
gcc -o program program.c $(pkg-config --cflags --libs libunistring)

# 检查库版本
pkg-config --modversion libunistring

# 检查库是否存在
pkg-config --exists libunistring && echo "Library found"

🧪 功能验证脚本

#!/bin/bash
# Libunistring 库验证脚本

LIBUNISTRING_LIB="build-hnp/sysroot/lib"
LIBUNISTRING_INCLUDE="build-hnp/sysroot/include"

echo "=== Libunistring 库验证 ==="

# 检查库文件
if [ -f "$LIBUNISTRING_LIB/libunistring.so.5.2.0" ]; then
    echo "✓ libunistring.so.5.2.0: 存在"
    file "$LIBUNISTRING_LIB/libunistring.so.5.2.0"
    echo "文件大小: $(ls -lh "$LIBUNISTRING_LIB/libunistring.so.5.2.0" | awk '{print $5}')"
    echo "架构信息: $(file "$LIBUNISTRING_LIB/libunistring.so.5.2.0" | grep -o "ARM aarch64")"
else
    echo "✗ libunistring.so.5.2.0: 缺失"
fi

# 检查符号链接
for link in libunistring.so libunistring.so.5; do
    if [ -L "$LIBUNISTRING_LIB/$link" ]; then
        echo "✓ $link: 符号链接 -> $(readlink "$LIBUNISTRING_LIB/$link")"
    else
        echo "✗ $link: 缺失"
    fi
done

# 检查主要头文件
echo ""
echo "=== 头文件验证 ==="
for header in unistr.h uniconv.h unicase.h unictype.h uninorm.h uniwidth.h; do
    if [ -f "$LIBUNISTRING_INCLUDE/$header" ]; then
        echo "✓ $header: 存在"
        echo "  文件大小: $(ls -lh "$LIBUNISTRING_INCLUDE/$header" | awk '{print $5}')"
    else
        echo "✗ $header: 缺失"
    fi
done

# 检查辅助头文件目录
echo ""
echo "=== 辅助头文件验证 ==="
if [ -d "$LIBUNISTRING_INCLUDE/unistring" ]; then
    echo "✓ unistring/ 目录: 存在"
    echo "  包含文件:"
    ls -1 "$LIBUNISTRING_INCLUDE/unistring"/*.h 2>/dev/null | wc -l | xargs echo "  数量:"
else
    echo "✗ unistring/ 目录: 缺失"
fi

# 测试 pkg-config(如果可用)
echo ""
echo "=== pkg-config 验证 ==="
if command -v pkg-config >/dev/null 2>&1; then
    export PKG_CONFIG_PATH="build-hnp/sysroot/lib/pkgconfig:$PKG_CONFIG_PATH"
    if pkg-config --exists libunistring 2>/dev/null; then
        echo "✓ pkg-config 支持: 是"
        echo "  版本: $(pkg-config --modversion libunistring)"
        echo "  CFLAGS: $(pkg-config --cflags libunistring)"
        echo "  LIBS: $(pkg-config --libs libunistring)"
    else
        echo "✗ pkg-config 支持: 否"
    fi
else
    echo "⚠ pkg-config: 未安装"
fi

🐛 常见问题与处理

❌ 问题 1:交叉工具链警告

  • 🔍 症状:配置警告 using cross tools not prefixed with host triplet
  • 🔎 原因:交叉编译工具链未使用标准的三元组前缀
  • ✅ 解决方法
    • 这是配置警告,不影响构建
    • 工具链已正确检测和使用
    • 位置:配置阶段日志

❌ 问题 2:GNU 镜像不可达

  • 🔍 症状:下载失败,无法获取源码包
  • 🔎 原因:网络问题或 GNU 镜像访问受限
  • ✅ 解决方法
    • 手动下载源码包放置到 build-hnp/libunistring/download/libunistring-1.3.tar.xz
    • 通用下载逻辑支持备用镜像与重试(wgetcurl
    • 位置:build-hnp/utils/Makefrag:61-69

❌ 问题 3:依赖关系问题

  • 🔍 症状:其他包(如 libidn2)构建失败,提示找不到 libunistring
  • 🔎 原因libunistring 未先构建,或构建顺序不正确
  • ✅ 解决方法
    • 确保 libunistring 在依赖它的包之前构建
    • 建议构建顺序:libunistring → libidn2 → c-ares → openssl → curl
    • 位置:build-hnp/Makefile:PKGS 顺序

❌ 问题 4:静态/共享库选择

  • 🔍 症状:需要静态库但构建时使用了 --disable-static
  • 🔎 原因:当前配置禁用了静态库以减小体积
  • ✅ 解决方法
    • 如果需要静态库,修改 CONFIG_ARGS--enable-static --disable-shared
    • 或同时启用:--enable-static --enable-shared
    • 位置:build-hnp/libunistring/Makefile:6

❌ 问题 5:链接失败

  • 🔍 症状:链接错误,无法找到 libunistring 符号
  • 🔎 原因:链接时未指定 -lunistring 或库路径不正确
  • ✅ 解决方法
    • 确保链接时添加 -lunistring
    • 使用 pkg-config --libs libunistring 获取正确的链接标志
    • 检查 LD_LIBRARY_PATH 是否包含库路径
    • 位置:编译和链接阶段

❌ 问题 6:头文件未找到

  • 🔍 症状:编译错误 unistr.h: No such file or directory
  • 🔎 原因:头文件路径未正确设置
  • ✅ 解决方法
    • 确保编译时添加 -I$(prefix)/include
    • 使用 pkg-config --cflags libunistring 获取正确的编译标志
    • 检查头文件是否已安装到 sysroot/include
    • 位置:编译阶段

❌ 问题 7:架构不支持

  • 🔍 症状Unsupported OHOS_ARCH=
  • 🔎 原因:传入的架构不在支持列表中
  • ✅ 解决方法
    • 确保传入支持架构(aarch64x86_64
    • 位置:build-hnp/Makefile:1-45

🔄 重建与扩展

  • 🔧 重建单包

    make -C build-hnp rebuild-libunistring  # 触发子包重新编译并刷新 .stamp
    
  • 🧹 清理

    make -C build-hnp clean  # 清理 sysroot、所有 .stamp 和 PKGS_MARKER
    
  • 📦 扩展:Libunistring 是 Unicode 字符串处理的基础库,被许多网络和文本处理库依赖

  • 🔄 自动重建机制

    • 修改 PKGS 后,check-pkgs 会自动检测变化并触发重新构建
    • 新增外部 HNP 包到 external-hnp 目录后,会自动合并到 base.hnp

💡 实践建议

  • 🔧 构建配置:使用共享库构建以减少体积,适合大多数场景
  • 🚀 依赖管理:确保 libunistring 在依赖它的包之前构建
  • 📦 编程接口:使用 pkg-config 获取正确的编译和链接标志
  • 🔗 模块使用:根据需求选择合适的模块(unistr、uniconv、unicase 等)
  • 🌐 国际化支持:Libunistring 为国际化应用提供了强大的 Unicode 支持

📝 结论与建议

  • ✅ 本次已在 aarch64 环境下完成 Libunistring 1.3 的交叉编译与打包,libunistring 库已安装到 sysroot 并纳入 HNP 包。
  • 💡 为保证构建稳定
    • 使用共享库构建以减少体积
    • 确保在依赖它的包之前构建 libunistring
    • 配置警告不影响构建,可以忽略
    • 确保通过 create-hnp.sh 触发构建以获得完整环境变量
    • 利用 check-pkgs 机制自动检测包列表变化,无需手动清理
    • Libunistring 为 Unicode 字符串处理提供了强大的库支持
    • 常见陷阱包括依赖关系问题、静态/共享库选择、链接失败;当前已通过构建顺序和配置参数处理
    • 按网络栈阶段顺序(libunistring → libidn2 → c-ares → openssl → curl)进行构建,确保上层网络库的功能完整与探测稳定
    • 已完成 aarch64 目标下 Libunistring 的交叉编译与打包,库与头文件进入 sysroot 并纳入 HNP 包

📚 以上为 Libunistring 构建的深度解读与实践记录。

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐