摘要

本文详细介绍了使用 lycium 框架将 nginx 1.26.2 交叉编译适配到 OpenHarmony 系统的完整解决方案。文章涵盖了在 macOS 主机上进行 ARM 交叉编译时遇到的核心技术挑战,包括 configure 脚本的运行时检测问题、类型大小探测失败等,并提供了不修改原库代码的创新性解决方案。最终成功在 macOS 上完成了 nginx 的鸿蒙交叉编译,生成了可在 OpenHarmony 设备上运行的 ARM 32-bit 和 ARM 64-bit 可执行文件。

关键词: OpenHarmony, nginx, 交叉编译, lycium, macOS


1. 引言

1.1 背景

随着万物互联时代的到来,OpenHarmony 作为面向全场景的分布式操作系统,正在快速发展。将成熟的开源软件移植到 OpenHarmony 平台,是丰富其生态系统的重要途径。nginx 作为全球最流行的 Web 服务器和反向代理服务器,其高性能、高并发的特性使其成为 OpenHarmony 设备端服务的理想选择。

1.2 挑战

将 nginx 移植到 OpenHarmony 面临以下挑战:

  1. 交叉编译环境差异: nginx 的 configure 脚本设计之初并未充分考虑交叉编译场景
  2. 运行时检测机制: configure 脚本通过编译并运行测试程序来检测系统特性,这在交叉编译环境中无法工作
  3. 不修改原库代码的约束: 为便于后续版本升级和维护,需要在不修改 nginx 原始代码的前提下完成适配

1.3 解决方案概述

本文提出了一种基于 lycium 框架的适配方案,通过在 HPKBUILD 构建脚本中对 nginx 的 auto 脚本进行运行时修改,成功解决了上述挑战。该方案的核心优势是完全不修改 nginx 原库代码,所有适配工作都在构建脚本层面完成。


2. lycium 框架简介

2.1 框架概述

lycium 是一个专门用于 OpenHarmony 第三方 C/C++ 库交叉编译的框架。它借鉴了 Arch Linux 的 PKGBUILD 机制,提供了一套标准化的构建流程。

2.2 HPKBUILD 文件结构

HPKBUILD 是 lycium 的核心配置文件,定义了库的元信息和构建流程:

# 包元信息
pkgname=nginx           # 包名
pkgver=1.26.2          # 版本号
pkgdesc="..."          # 描述
url="https://nginx.org/"
archs=("armeabi-v7a" "arm64-v8a")  # 目标架构
depends=("pcre2" "openssl" "zlib-ng")  # 依赖库

# 构建流程函数
prepare()   # 准备阶段:解压、打补丁
build()     # 编译阶段:configure、make
package()   # 打包阶段:make install
check()     # 测试阶段
cleanbuild() # 清理阶段

2.3 构建流程

┌─────────────┐    ┌─────────────┐    ┌─────────────┐    ┌─────────────┐
│  download   │───▶│   prepare   │───▶│    build    │───▶│   package   │
│  源码下载   │    │  环境准备   │    │  编译构建   │    │  安装打包   │
└─────────────┘    └─────────────┘    └─────────────┘    └─────────────┘

3. nginx 交叉编译的技术挑战

3.1 问题一:C 编译器检测失败

现象:

checking for C compiler ... found but is not working
./configure: error: C compiler arm-linux-ohos-clang is not found

根本原因分析:

nginx 的 auto/cc/name 脚本使用以下逻辑检测编译器:

ngx_feature_run=yes  # 关键:要求运行编译后的测试程序

当设置 ngx_feature_run=yes 时,configure 会编译一个测试程序并尝试执行它。在交叉编译场景下,编译出的是 ARM 二进制文件,无法在 x86_64 的 macOS 主机上运行,导致检测失败。

3.2 问题二:数据类型大小检测失败

现象:

checking for int size ...auto/types/sizeof: line 43: objs/autotest: cannot execute binary file
./configure: error: can not detect int size

根本原因分析:

nginx 的 auto/types/sizeof 脚本通过以下方式获取类型大小:

# 编译测试程序
ngx_test="$CC ... -o $NGX_AUTOTEST $NGX_AUTOTEST.c ..."
eval "$ngx_test"

# 运行测试程序获取 sizeof 值
ngx_size=`$NGX_AUTOTEST`  # 第 43 行:执行 ARM 二进制文件失败

这是交叉编译的经典难题:目标平台的类型大小可能与主机平台不同,必须通过某种方式获取正确的值。

3.3 问题三:功能特性检测失败

nginx 的 auto/unix 脚本包含大量功能检测,许多都使用了运行时检测:

ngx_feature_run=yes    # 需要运行测试程序
ngx_feature_run=value  # 需要运行测试程序并获取返回值
ngx_feature_run=bug    # 需要运行测试程序检测 bug

这些检测在交叉编译环境中都会失败。

3.4 问题四:路径硬编码问题

现象:
在鸿蒙设备上运行 nginx 时,报错找不到配置文件或日志文件,路径显示为 macOS 编译机器上的绝对路径:

nginx: [alert] could not open error log file: open() "/path/to/build/machine/logs/error.log" failed

根本原因分析:

nginx 的 configure 脚本通过 --prefix 参数设置安装前缀,该路径会被硬编码到二进制文件中:

// src/core/nginx.c 第 1042-1044 行
ngx_str_set(&cycle->conf_prefix, NGX_PREFIX);
ngx_str_set(&cycle->prefix, NGX_PREFIX);

如果使用绝对路径如 --prefix=$LYCIUM_ROOT/usr/nginx/$ARCH,则编译主机上的绝对路径会被编译进二进制文件,导致在鸿蒙设备上无法找到对应路径。

3.5 问题五:鸿蒙系统兼容性问题

现象一:FIOASYNC 警告

nginx: [alert] ioctl(FIOASYNC) failed while spawning "worker process" (25: Not a tty)

现象二:页面空白无法加载
nginx 启动成功,端口监听正常,但浏览器访问时页面一直转圈,无法加载静态文件。

根本原因分析:

  1. FIOASYNC 问题: 鸿蒙系统的 musl libc 对 FIOASYNC ioctl 调用支持有限,该调用用于设置异步 I/O 通知。此警告通常不影响基本功能。

  2. sendfile 问题: nginx 默认启用 sendfile on,这是一个零拷贝系统调用用于高效传输文件。鸿蒙系统对 sendfile 的实现可能存在兼容性问题,导致静态文件无法正确传输。


4. 解决方案设计与实现

4.1 设计原则

  1. 不修改原库代码: 所有修改在 HPKBUILD 脚本中完成
  2. 运行时修改: 在 prepare() 阶段使用 sed/cat 修改 auto 脚本
  3. 架构感知: 针对不同目标架构提供正确的预设值
  4. 平台兼容: 仅在 macOS 上应用修改,Linux 环境保持原有行为

4.2 解决方案一:跳过编译器运行检测

prepare() 函数中修改 auto/cc/name

# 将需要运行测试程序的检测改为仅编译检测
sed -i.bak 's/ngx_feature_run=yes/ngx_feature_run=no/g' auto/cc/name

原理: 当 ngx_feature_run=no 时,nginx 只检查测试程序是否能成功编译,不再尝试运行它。

4.3 解决方案二:提供预设的类型大小值

完全替换 auto/types/sizeof 脚本,使用基于目标架构的预设值:

# ARM 32-bit 类型大小
case "$ngx_type" in
    int)                ngx_size=4 ;;
    long)               ngx_size=4 ;;  # 32-bit: long 是 4 字节
    "long long")        ngx_size=8 ;;
    "void *")           ngx_size=4 ;;  # 32-bit: 指针是 4 字节
    size_t)             ngx_size=4 ;;
    off_t)              ngx_size=8 ;;
    time_t)             ngx_size=4 ;;
    sig_atomic_t)       ngx_size=4 ;;
esac

# ARM 64-bit 类型大小
case "$ngx_type" in
    int)                ngx_size=4 ;;
    long)               ngx_size=8 ;;  # 64-bit: long 是 8 字节
    "long long")        ngx_size=8 ;;
    "void *")           ngx_size=8 ;;  # 64-bit: 指针是 8 字节
    size_t)             ngx_size=8 ;;
    off_t)              ngx_size=8 ;;
    time_t)             ngx_size=8 ;;
    sig_atomic_t)       ngx_size=4 ;;
esac

关键差异说明:

类型 ARM 32-bit ARM 64-bit
int 4 字节 4 字节
long 4 字节 8 字节
void * 4 字节 8 字节
size_t 4 字节 8 字节

4.4 解决方案三:禁用功能运行时检测

批量修改 auto/unix 中的所有运行时检测:

sed -i.bak 's/ngx_feature_run=yes/ngx_feature_run=no/g' auto/unix
sed -i.bak 's/ngx_feature_run=value/ngx_feature_run=no/g' auto/unix
sed -i.bak 's/ngx_feature_run=bug/ngx_feature_run=no/g' auto/unix

影响分析: 这会导致某些功能检测不够精确,但对于 OpenHarmony 这类 Linux 兼容系统,大多数 POSIX 功能都是可用的,仅编译检测通常足够。

4.5 解决方案四:预设字节序

ARM 处理器使用小端字节序,直接在 prepare() 中设置:

sed -i.bak 's/ngx_feature_run=value/ngx_feature_run=no/g' auto/endianness
mkdir -p objs
echo '#define NGX_HAVE_LITTLE_ENDIAN 1' > objs/ngx_auto_config.h.tmp

4.6 解决方案五:使用相对路径避免硬编码

为解决路径硬编码问题,在 build() 函数中使用相对路径配置 nginx:

./configure \
    --crossbuild=$host \
    --prefix=../ \
    --conf-path=conf/nginx.conf \
    --error-log-path=logs/error.log \
    --pid-path=logs/nginx.pid \
    --lock-path=logs/nginx.lock \
    --http-log-path=logs/access.log \
    # ... 其他参数

关键配置说明:

参数 说明
--prefix ../ 使用上一级目录,必须以 / 结尾
--conf-path conf/nginx.conf 配置文件相对路径
--error-log-path logs/error.log 错误日志相对路径
--pid-path logs/nginx.pid PID 文件相对路径
--http-log-path logs/access.log 访问日志相对路径

重要: prefix 必须以 / 结尾。nginx 在运行时通过 -p 参数指定 prefix 时会自动补全斜杠,但使用编译时的 NGX_PREFIX 宏时不会自动添加。如果使用 --prefix=..(不带斜杠),会导致路径拼接错误(如 ..html 而非 ../html)。

原理: 当 --prefix=../ 时,nginx 从 sbin/ 目录运行会将 prefix 解析为上一级目录。例如从 /data/nginx/sbin/ 运行 ./nginx,prefix 会解析为 /data/nginx/,从而正确找到 conf/logs/ 等目录。

4.7 解决方案六:鸿蒙系统兼容性配置

针对鸿蒙系统的兼容性问题,需要在 nginx.conf 中进行以下配置:

worker_processes  1;

error_log  logs/error.log;
pid        logs/nginx.pid;

events {
    worker_connections  1024;
}

http {
    include       mime.types;
    default_type  application/octet-stream;

    # 关键:禁用 sendfile,解决鸿蒙系统静态文件无法加载的问题
    sendfile        off;

    keepalive_timeout  65;

    server {
        listen       8080;
        server_name  localhost;

        location / {
            root   html;
            index  index.html index.htm;
        }

        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
            root   html;
        }
    }
}

关键配置说明:

配置项 说明
sendfile off 禁用零拷贝传输,解决静态文件无法加载问题
listen 8080 使用非特权端口,避免权限问题
worker_processes 1 单进程模式,简化调试

4.8 完整的 prepare() 函数

prepare() {
    # 设置交叉编译环境
    if [ $ARCH == "armeabi-v7a" ]; then
        setarm32ENV
        host=arm-linux-ohos
        ngx_machine=armv7l
    elif [ $ARCH == "arm64-v8a" ]; then
        setarm64ENV
        host=aarch64-linux-ohos
        ngx_machine=aarch64
    fi

    cd $builddir

    # 仅在 macOS 上应用修改
    if [ "$LYCIUM_BUILD_OS" == "Darwi" ]; then
        # 1. 跳过编译器运行检测
        sed -i.bak 's/ngx_feature_run=yes/ngx_feature_run=no/g' auto/cc/name

        # 2. 替换 sizeof 脚本(根据架构选择预设值)
        cat > auto/types/sizeof << 'EOF'
        # ... 预设的类型大小检测脚本
        EOF

        # 3. 禁用 auto/unix 的运行时检测
        sed -i.bak 's/ngx_feature_run=yes/ngx_feature_run=no/g' auto/unix
        sed -i.bak 's/ngx_feature_run=value/ngx_feature_run=no/g' auto/unix
        sed -i.bak 's/ngx_feature_run=bug/ngx_feature_run=no/g' auto/unix

        # 4. 设置字节序
        sed -i.bak 's/ngx_feature_run=value/ngx_feature_run=no/g' auto/endianness
    fi

    cd $OLDPWD
}

5. 依赖库的适配

nginx 依赖三个核心库,它们也需要适配:

5.1 pcre2(正则表达式库)

pcre2 使用标准的 CMake 构建系统,交叉编译相对简单,lycium 框架已有现成适配。

5.2 openssl(加密库)

openssl 有完善的交叉编译支持,通过指定目标平台即可完成编译,lycium 框架已有现成适配。

5.3 zlib-ng(压缩库)

zlib-ng 在 macOS 上交叉编译时遇到了 libtool 兼容性问题:

问题: macOS 的 libtool 无法处理 ARM 格式的目标文件

解决方案: 在 HPKBUILD 中替换 libtool 为 OHOS SDK 的 llvm-ar:

if [ "$LYCIUM_BUILD_OS" == "Darwi" ]; then
    sed -i.bak "s|^AR=libtool|AR=${AR}|g" Makefile
    sed -i.bak "s|ARFLAGS=-o|ARFLAGS=rcs|g" Makefile
fi

6. 构建与验证

6.1 执行构建

cd lycium
./build.sh nginx

6.2 构建输出

Build OS Darwin
Start building nginx 1.26.2!
Compileing OpenHarmony armeabi-v7a nginx 1.26.2 libs...
Compileing OpenHarmony arm64-v8a nginx 1.26.2 libs...
Build nginx 1.26.2 end!
ALL JOBS DONE!!!

6.3 验证编译产物

$ file lycium/usr/nginx/armeabi-v7a/sbin/nginx
ELF 32-bit LSB pie executable, ARM, EABI5 version 1 (SYSV), dynamically linked

$ file lycium/usr/nginx/arm64-v8a/sbin/nginx
ELF 64-bit LSB pie executable, ARM aarch64, version 1 (SYSV), dynamically linked

6.4 设备端部署与验证

6.4.1 目录结构

部署到鸿蒙设备后,nginx 目录结构如下:

nginx/
├── sbin/
│   ├── nginx              # 主程序
│   └── libc++_shared.so   # 运行时依赖库
├── conf/
│   ├── nginx.conf         # 主配置文件
│   ├── mime.types         # MIME 类型配置
│   └── ...
├── logs/                  # 日志目录(需要写权限)
└── html/
    ├── index.html         # 默认首页
    └── 50x.html           # 错误页面
6.4.2 部署步骤
# 1. 推送到 OpenHarmony 设备
hdc file send lycium/usr/nginx/arm64-v8a /data/local/tmp/nginx

# 2. 进入设备 shell
hdc shell

# 3. 设置权限
cd /data/local/tmp/nginx
chmod +x sbin/nginx
chmod 755 logs
6.4.3 运行方式

由于使用了 --prefix=.. 编译,nginx 会自动将路径解析为可执行文件所在目录的上一级。直接从 sbin/ 目录运行即可:

# 进入 sbin 目录
cd /data/local/tmp/nginx/sbin

# 测试配置文件
./nginx -t

# 启动 nginx
./nginx

# 重新加载配置
./nginx -s reload

# 停止 nginx
./nginx -s stop

路径解析示例:

当从 /data/local/tmp/nginx/sbin/ 目录运行 ./nginx 时:

  • prefix 解析为: /data/local/tmp/nginx/sbin/../ = /data/local/tmp/nginx/
  • 配置文件: /data/local/tmp/nginx/conf/nginx.conf
  • 错误日志: /data/local/tmp/nginx/logs/error.log
6.4.4 验证服务
# 检查版本
./nginx --version

在这里插入图片描述

在这里插入图片描述


7. 技术总结

7.1 核心创新点

  1. 运行时脚本修改技术: 通过在 prepare() 阶段使用 sed 和 cat 命令修改 nginx 的 auto 脚本,实现了不修改原库代码的适配

  2. 架构感知的类型大小预设: 根据目标架构(ARM 32-bit/64-bit)提供正确的数据类型大小,避免了运行时探测的需求

  3. 平台条件编译: 通过检测 LYCIUM_BUILD_OS 环境变量,仅在 macOS 上应用修改,保持 Linux 环境的原有行为

  4. 相对路径配置: 使用 --prefix=../ 配置 nginx(注意必须以 / 结尾),使其自动解析为可执行文件上一级目录,避免将编译主机的绝对路径硬编码到二进制文件中

  5. 鸿蒙系统兼容性适配: 禁用 sendfile 系统调用,解决鸿蒙系统上静态文件无法加载的问题

7.2 方案优势

优势 说明
零侵入性 不修改 nginx 原始代码,便于版本升级
跨平台支持 同时支持 macOS 和 Linux 作为构建主机
多架构支持 同时支持 ARM 32-bit 和 ARM 64-bit 目标
可移植部署 使用相对路径,可部署到任意目录
鸿蒙兼容 针对鸿蒙系统特性进行配置优化
可维护性 所有适配逻辑集中在 HPKBUILD 文件中

7.3 已知问题

问题 现象 解决方案
FIOASYNC 警告 ioctl(FIOASYNC) failed (25: Not a tty) 可忽略,不影响功能
sendfile 不兼容 静态文件无法加载,页面空白 在 nginx.conf 中设置 sendfile off
端口权限 80 端口需要 root 权限 使用 8080 等非特权端口

7.4 适用范围

本文介绍的技术方案不仅适用于 nginx,还可以推广到其他使用类似 configure 脚本的开源项目。关键是识别出哪些检测依赖于运行测试程序,并提供相应的绕过或预设值方案。


8. 结论

本文详细介绍了使用 lycium 框架将 nginx 适配到 OpenHarmony 系统的完整解决方案。通过创新性的运行时脚本修改技术,成功解决了交叉编译环境下 configure 脚本的运行时检测问题,实现了在 macOS 主机上完成 nginx 的鸿蒙交叉编译。

该方案的核心价值在于完全不修改原库代码,这对于开源软件的长期维护和版本升级具有重要意义。同时,本文介绍的技术思路和方法论可以推广应用到其他第三方库的 OpenHarmony 适配工作中。


参考资料

  1. nginx 官方文档: https://nginx.org/en/docs/
  2. OpenHarmony 官方文档: https://www.openharmony.cn/
  3. lycium 框架源码: tpc_c_cplusplus/lycium/
  4. OHOS NDK 交叉编译指南
    等非特权端口 |

7.4 适用范围

本文介绍的技术方案不仅适用于 nginx,还可以推广到其他使用类似 configure 脚本的开源项目。关键是识别出哪些检测依赖于运行测试程序,并提供相应的绕过或预设值方案。


8. 结论

本文详细介绍了使用 lycium 框架将 nginx 适配到 OpenHarmony 系统的完整解决方案。通过创新性的运行时脚本修改技术,成功解决了交叉编译环境下 configure 脚本的运行时检测问题,实现了在 macOS 主机上完成 nginx 的鸿蒙交叉编译。

该方案的核心价值在于完全不修改原库代码,这对于开源软件的长期维护和版本升级具有重要意义。同时,本文介绍的技术思路和方法论可以推广应用到其他第三方库的 OpenHarmony 适配工作中。


参考资料

  1. nginx 官方文档: https://nginx.org/en/docs/
  2. OpenHarmony 官方文档: https://www.openharmony.cn/
  3. lycium 框架源码: tpc_c_cplusplus/lycium/
  4. OHOS NDK 交叉编译指南
Logo

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

更多推荐