记录一次艰难的C++依赖项配置过程
本文最后更新于 979 天前,其中的信息可能已经有所发展或是发生改变。

几天前,我想要制作一款C++实现的后端云对接库,预计将要实现Java版类库的完整功能。我准备使用libcurl来完成https请求和证书固定的任务。

在项目配置过程中,我希望项目能在Linux下直接运行,以方便开发过程中的调试工作。又希望正式打包时,能一次性将Android的四个ABI都构建出来,并且包含Android平台的可执行文件。于是,我使用了sh构建脚本。

在该脚本中,手动指定Android NDK的位置和使用的交叉编译工具链,以及编译的目标SDK版本号。在cmake中判断sh传入的环境变量来为特定的平台使用特定的工具链,至此为止一切正常。

使用libcurl需要手动编译libcurl的依赖项,我先是从网络上下载了为Android预编译的二进制文件,可是在cmake中使用时编译器链接总出错,我以为是我的cmake查找路径配置错了,可是AI反复检查我的cmake,给出的建议都是检查查找路径。后终于发现是网络上下载的二进制文件格式不对。使用readelf工具检查后,发现在arm和arm64文件夹下的静态库都是x86格式的。故开始手动编译依赖。

编译过程

要使编译得到的libcurl支持https,那么编译过程中必须引入openssl依赖,而且libcurl还依赖libz。libz编译相对简单,首先编译libz。

环境变量

在编译过程中,需要定义以下环境变量,以便构建工具能找到需要的编译器。

#!/bin/bash
#NDK路径,openssl需要ANDROID_NDK_ROOT变量,所以把它export一下
export ANDROID_NDK_ROOT=$HOME/Library/Android/sdk/ndk/23.1.7779620

#编译平台,我这里是linux,所以是linux-x86_64
HOST_TAG=linux-x86_64

#Android api版本
MIN_SDK_VERSION=23

#工具链路径
TOOLCHAIN=$ANDROID_NDK_ROOT/toolchains/llvm/prebuilt/$HOST_TAG

#把工具链加到PATH环境变量
PATH=$TOOLCHAIN/bin:$PATH

#输出目录,在build目录下
BUILD_DIR=$PWD/build

编译libz

解压zlib,并cd到zlib目录下

tar xvf zlib-1.2.13.tar.gz
cd zlib-1.2.13

配置编译工具链,就是配置C/C++编译器、汇编器、链接器的路径,由于Android已经放弃了gcc,现在的编译器是clang和clang++。

要适用于所有CPU架构,我们要编译armv8、armv7、x86、x86_64四个平台。他们会使用不同的clang来编译。我们用TARGET_HOST来区分。 aarch64-linux-android表示要编译armv8 armv7a-linux-androideabi表示要编译armv7a i686-linux-android表示要编译x86 x86_64-linux-android表示要编译x86_64

TARGET_HOST=aarch64-linux-android
ANDROID_ARCH=arm64-v8a
AR=$TOOLCHAIN/bin/llvm-ar
CC=$TOOLCHAIN/bin/$TARGET_HOST$MIN_SDK_VERSION-clang
AS=$CC
CXX=$TOOLCHAIN/bin/$TARGET_HOST$MIN_SDK_VERSION-clang++
LD=$TOOLCHAIN/bin/ld
RANLIB=$TOOLCHAIN/bin/llvm-ranlib
STRIP=$TOOLCHAIN/bin/llvm-strip

配置好工具链后执行configure。–prefix指定编译完成后软件的安装目录。–static只编译静态库。

INSTALL_DIR=$BUILD_DIR/zlib
./configure --prefix=$INSTALL_DIR/$ANDROID_ARCH --static
#上面设置了BUILD_DIR为根目录build文件夹,所以$INSTALL_DIR/$ANDROID_ARCH的值为 build/zlib/arm64-v8a

再执行make、make install。

make

make install

install后,在根目录build/curl下,出现了arm64-v8a。里面就是arm64-v8a版本的zlib库。

我把四个架构的编译写成shell文件。

build-zlib.sh

#!/bin/bash

tar xzf zlib-1.2.13.tar.gz

source ./build-env.sh

INSTALL_DIR=$BUILD_DIR/zlib

if [ ! -d $INSTALL_DIR ]; then
    mkdir -p $INSTALL_DIR
fi

cd zlib-1.2.13


function build() {
    make distclean
    TARGET_HOST=$1
    ANDROID_ARCH=$2
    AR=$TOOLCHAIN/bin/llvm-ar
    CC=$TOOLCHAIN/bin/$TARGET_HOST$MIN_SDK_VERSION-clang
    AS=$CC
    CXX=$TOOLCHAIN/bin/$TARGET_HOST$MIN_SDK_VERSION-clang++
    LD=$TOOLCHAIN/bin/ld
    RANLIB=$TOOLCHAIN/bin/llvm-ranlib
    STRIP=$TOOLCHAIN/bin/llvm-strip

    ./configure --prefix=$INSTALL_DIR/$ANDROID_ARCH --static

    make -j8
    make install
    make distclean
}

build aarch64-linux-android arm64-v8a
build armv7a-linux-androideabi armeabi-v7a
build i686-linux-android x86
build x86_64-linux-android x86_64

cd ..

rm -rf cd zlib-1.2.13

编译openssl

通过上面的zlib编译,openssl的编译流程大致是一样的,解压代码什么的就没必要记录了。

有点不一样的是openssl的Configure是大写的。并且它自己支持了Android,需要通过参数传递给它。

然后就是我使用r21的NDK编译时会找不到一些头文件,可能是由于我安装llvm的时候损坏了NDK,换了一个之后就好了。

android-arm64表示编译64位的arm版本。

no-unit-test表示不需要单元测试。

no-shared表示不需要动态库。

-DANDROID_API=$MIN_SDK_VERSION传递Android api版本。

–prefix指定Android目录

./Configure android-arm64 no-unit-test no-shared -D__ANDROID_API__=$MIN_SDK_VERSION --prefix=$INSTALL_DIR/$ANDROID_ARCH

同样写成shell文件

build-openssl.sh

#!/bin/bash

tar xzf openssl-3.0.7.tar.gz

source ./build-env.sh

INSTALL_DIR=$BUILD_DIR/openssl

if [ ! -d $INSTALL_DIR ]; then
    mkdir -p $INSTALL_DIR
fi

cd openssl-3.0.7

function build() {
    TARGET_HOST=$1
    ANDROID_ARCH=$2
    OPENSSL_ARCH=$3
    AR=$TOOLCHAIN/bin/llvm-ar
    CC=$TOOLCHAIN/bin/$TARGET_HOST$MIN_SDK_VERSION-clang
    AS=$CC
    CXX=$TOOLCHAIN/bin/$TARGET_HOST$MIN_SDK_VERSION-clang++
    LD=$TOOLCHAIN/bin/ld
    RANLIB=$TOOLCHAIN/bin/llvm-ranlib
    STRIP=$TOOLCHAIN/bin/llvm-strip

    ./Configure $OPENSSL_ARCH no-unit-test no-shared -D__ANDROID_API__=$MIN_SDK_VERSION --prefix=$INSTALL_DIR/$ANDROID_ARCH

    make -j8
    make install_sw
    make distclean
}

build aarch64-linux-android arm64-v8a android-arm64
build armv7a-linux-androideabi armeabi-v7a android-arm
build i686-linux-android x86 android-x86
build x86_64-linux-android x86_64 android-x86_64


cd ..

rm -rf openssl-3.0.7

编译curl

准备完zlib和openssl之后,就可以编译curl了。同样是老套路,configure,make,make install。

–host和–target把编译器平台传给它。

–prefix依旧还是安装目录。

–with-zlib和–with-openssl就用到了我们上面编译出来的zlib和openssl。我们把目录传给它。

–disable-shared表示不编译动态库,我只需要静态库。

./configure --host=$TARGET_HOST \
            --target=$TARGET_HOST \
            --prefix=$INSTALL_DIR/$ANDROID_ARCH \
            --with-zlib=$BUILD_DIR/zlib/$ANDROID_ARCH \
            --with-openssl=$BUILD_DIR/openssl/$ANDROID_ARCH \
            --disable-shared

curl的完整编译文件如下

build-curl.sh

#!/bin/bash

tar xzf curl-7.88.0.tar.xz

source ./build-env.sh

INSTALL_DIR=$BUILD_DIR/curl

if [ ! -d $INSTALL_DIR ]; then
    mkdir -p $INSTALL_DIR
fi

cd curl-7.88.0


function build() {
    export TARGET_HOST=$1
    export ANDROID_ARCH=$2
    export AR=$TOOLCHAIN/bin/llvm-ar
    export CC=$TOOLCHAIN/bin/$TARGET_HOST$MIN_SDK_VERSION-clang
    export AS=$CC
    export CXX=$TOOLCHAIN/bin/$TARGET_HOST$MIN_SDK_VERSION-clang++
    export LD=$TOOLCHAIN/bin/ld
    export RANLIB=$TOOLCHAIN/bin/llvm-ranlib
    export STRIP=$TOOLCHAIN/bin/llvm-strip

    ./configure --host=$TARGET_HOST \
                --target=$TARGET_HOST \
                --prefix=$INSTALL_DIR/$ANDROID_ARCH \
                --with-zlib=$BUILD_DIR/zlib/$ANDROID_ARCH \
                --with-openssl=$BUILD_DIR/openssl/$ANDROID_ARCH \
                --with-pic --disable-shared

    make -j8
    make install
    make clean
}


build aarch64-linux-android arm64-v8a
build armv7a-linux-androideabi armeabi-v7a
build i686-linux-android x86
build x86_64-linux-android x86_64

cd ..

rm -rf cd curl-7.88.0

链接问题

在使用上面编译得到的库文件的时候,出现了很多问题。

首先,是stderr,stdout和stdin这三个C语言的标准输入输出链接出错。链接器表示找不到这些符号,但是它们都是C标准库的一部分,应该是默认包含的。网络上给出了一则消息误导了我,我找到一则消息指出Google在NDK r21开始删除了C的标准输入输出,理由是在Android上有特殊的输入输出设备。但是这些内容都是在我使用的库中引用的,我不能直接移除他们。尝试覆盖stderr等定义未果之后,我发现链接libc之后,能解决标准库的链接异常,但是生成的可执行文件会抛出空指针异常,无法运行。而且我注意到x86的架构编译过程失败了。

通过跟踪日志发现,构建x86时,编译工具链查找失败了,是因为x86工具链命名和其他架构不一致,导致找不到编译器。所以我将cmake修改成如下样式:

if (${CMAKE_ANDROID_ARCH_ABI} STREQUAL arm64-v8a)
        set(CMAKE_CXX_COMPILER ${CMAKE_ANDROID_NDK}toolchains/llvm/prebuilt/linux-x86_64/bin/aarch64-linux-android23-clang++)
    elseif (${CMAKE_ANDROID_ARCH_ABI} STREQUAL armeabi-v7a)
        set(CMAKE_CXX_COMPILER ${CMAKE_ANDROID_NDK}toolchains/llvm/prebuilt/linux-x86_64/bin/armv7a-linux-androideabi23-clang++)
    elseif (${CMAKE_ANDROID_ARCH_ABI} STREQUAL x86)
        set(CMAKE_CXX_COMPILER ${CMAKE_ANDROID_NDK}toolchains/llvm/prebuilt/linux-x86_64/bin/i686-linux-android23-clang++)
    elseif (${CMAKE_ANDROID_ARCH_ABI} STREQUAL x86_64)
        set(CMAKE_CXX_COMPILER ${CMAKE_ANDROID_NDK}toolchains/llvm/prebuilt/linux-x86_64/bin/x86_64-linux-android23-clang++)
    else ()
        message(FATAL_ERROR "无法解析的架构:${CMAKE_ANDROID_ARCH_ABI}")
    endif ()

这样,cmake可以适应所有的目标ABI。

然后,我发现在我的构建脚本中,通过cmake命令行参数指定了NDK路径和目标API,考虑到这可能会覆盖CMakeLists.txt中的设置,我将它们修改成和CMakeLists.txt中一致之后,标准库的链接问题消失了。

总结

过程中最诡异的问题其实就是最后标准输入输出库链接出错的问题。原因是因为我编译依赖项的时候使用的是NDK r23,目标API是23。而构建脚本中通过命令行参数将NDK指向了r21,目标API也被覆写为21,低于依赖项的版本,导致链接出错。如果在遇到标准库中的内容链接出错,很有可能是使用的工具链和依赖项的工具链不一致,需要仔细检查各项配置。

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇