Android跨平台编译 —— BOOST

前言

    android studio在2.2开始已经全面接入了cmake,用来编译jni代码。所以我们的跨平台编译同样需要与时俱进,使用cmake来完成工作。如果你不是很了解cmake,也不用担心,跟着本文探索下去,能够了解一些最基本的用法(PS:博主之前也是完全不会,摸着石头过河的,现在也还是不会……)。

    另外ndk版本使用了最新的r16b(16.1.4479499),这给之后带来了一些些问题。在最新的ndk版本中,已经将编译工具从gcc迁移到了clang,默认会使用clang进行编译。

    toolchain地址位于 ${ANDROID_SDK}/ndk-bundle/toolchains/llvm内。关于llvm和clang的关系可以参考这张图。(更详细的内容就需要自己查了,不是本文内容。)

           

    ok既然使用到了clang,那么使用的c++标准库STL自然是libc++标准库。

    我们在代码中使用的头文件,比如string.h,都会位于${ANDROID_SDK}/ndk-bundle/source文件夹内,不同的stl库会使用不同的文件路径。

    还有一部分在sysroot文件夹中,比如jni.h这个文件。

    还有一个非常重要的东西在platforms文件夹中,这个文件夹包含了不同的android版本,同时每个版本中都有不同的指令集文件夹。每一个文件夹模拟了一个真机运行环境。

    另外在开始之前,建议自己阅读下面几篇文章:

    https://developer.android.com/studio/projects/add-native-code.html

    https://developer.android.com/ndk/guides/cmake.html#variables

    https://developer.android.com/ndk/guides/cpp-support.html#hr

    当然也请打开cmake的官方文档以便查阅。

    

正文

    boost的编译实际上并不算复杂,比如我们可以直接使用

                        https://github.com/moritz-wundke/Boost-for-Android

    这个github项目已经替你实现了,这里我使用了1.65.1的版本。主要问题还是集中在如何放到自己项目中。

    我们知道cmake提供了一个方法叫做find_package,具体参数请参见官方文档。作用是使用cmake提供的脚本自动搜索并载入相关的库。比如我们需要载入boost的库:

           FIND_PACKAGE(Boost COMPONENTS filesystem REQUIRED)

    Boost是库的名字,我们只需要载入其中一个组件filesystem。REQUIRED表示必须载入,没找到就报错。

    实际上当调用find_package之后,cmake就回去寻找一个cmake的脚本,比如boost就回去寻找FindBoost.cmake的脚本。在android studio的环境下,他会到/Users/yxwang/Library/Android/sdk/cmake/3.6.4111459/share/cmake-3.6/Modules 这个文件夹下寻找。

    正常情况,我们可以使用 

set(Boost_ADDITIONAL_VERSIONS "1.65" "1.65.1")
set(Boost_NO_SYSTEM_PATHS ON)
set(BOOST_ROOT ${PROJECT_SOURCE_DIR}/src/main/cpp/boost/${ANDROID_ABI})
set(Boost_USE_STATIC_LIBS ON)
FIND_PACKAGE(Boost COMPONENTS filesystem REQUIRED)

    这几行代码来设置一些先决条件,然后findboost.cmake脚本就能正常运行,并且替我们设置好一些变量。(其中BOOST_ROOT 表示boost的地址)

    但是我发现我这边运行会出现问题,最后导致find失败,于是read the fucking source code!!

    最终发现,问题出在了其中一行find_path命令上,理论上能根据设置的路径能够最终找到需要的头文件地址,并且存放在变量Boost_INCLUDE_DIR中。

    但是我mac上发现find_path失效了。一定是缺失了某个配置,导致寻找路径出现了错误。甚至连find_path(VAL NAMES a.jpg PATH /somepath) 这种的绝对寻找都找不到…… PS在非ndk环境中似乎运行成功。如果有知道的朋友请一定留言告诉我……毕竟我是个小白  

    那么怎么办呢?既然你找不到,那就我直接来告诉你,所以代码就变成了这样

set(Boost_ADDITIONAL_VERSIONS "1.65" "1.65.1")
set(Boost_NO_SYSTEM_PATHS ON)
set(BOOST_ROOT ${PROJECT_SOURCE_DIR}/src/main/cpp/boost/${ANDROID_ABI})
set(Boost_INCLUDE_DIR ${BOOST_ROOT}/include/boost-1_65_1 )
set(Boost_LIBRARY_DIR ${BOOST_ROOT}/lib )
set(Boost_USE_STATIC_LIBS ON)

FIND_PACKAGE(Boost COMPONENTS filesystem REQUIRED)

    我手动设置了Boost_INCLUDE_DIR的位置和 Boost_LIBRARY_DIR的位置。 (请注意大小写,这个大小写多浪费了我半天时间……)

    好的,然后就可以像引入其他静态或者动态那样使用了比如,引入头文件

include_directories(${Boost_INCLUDE_DIR})

    比如引入Link库

target_link_libraries(TKAT PRIVATE ${Boost_LIBRARIES} )

    实际上调用了find_package之后还给你定义很多components的变量规则都一样,比如BOOST_FILESYSTEM_LIBRARY就表示filesystem单个库。

问题

    这里笔者遇到了一个非常严重的问题,armeabi-v7a指令集的库打包失败。这是一个很严重的问题,如果这个使用最广的指令集包打不出来,基本上就等于没有作用。

    看了build-android.sh,发现其中有一部分

if [ "$TOOLSET" = "clang" ]; then
      cp configs/user-config-boost-${BOOST_VER}.jam $BOOST_DIR/tools/build/src/user-config.jam || exit 1
      for FILE in configs/user-config-boost-${BOOST_VER}-*.jam; do
          ARCH="`echo $FILE | sed s%configs/user-config-boost-${BOOST_VER}-%% | sed s/[.]jam//`"
          if [ "$ARCH" = "common" ]; then
              continue
          fi
          JAMARCH="`echo ${ARCH} | tr -d '_-'`" # Remove all dashes, bjam does not like them
          sed "s/%ARCH%/${JAMARCH}/g" configs/user-config-boost-${BOOST_VER}-common.jam >> $BOOST_DIR/tools/build/src/user-config.jam || exit 1
          cat configs/user-config-boost-${BOOST_VER}-$ARCH.jam >> $BOOST_DIR/tools/build/src/user-config.jam || exit 1
          echo ';' >> $BOOST_DIR/tools/build/src/user-config.jam || exit 1
      done
  else
      cp configs/user-config-boost-${BOOST_VER}.jam $BOOST_DIR/tools/build/v2/user-config.jam || exit 1
  fi

    这部分的内容就是将这个项目预先设置好的一些编译参数拷贝到user-config.jam中,然后放入真正的源代码中用于编译。

    其中会用到一些文件

                                   

    比我我们需要编译armeabi-v7a的指令集时,脚本会将 user-config-boost-1_65_1.jam user-config-boost-1_65_1-common.jam  user-config-boost-1_65_1-armeabi-v7a.jam进行合并。然后编译的时候就会出现类似

"/Users/yxwang/Library/Android/sdk/ndk-bundle/toolchains/llvm/prebuilt/darwin-x86_64/bin/clang++" -x c++ -O3 -O3 -Wno-inline -Wall -fexceptions -frtti -ffunction-sections -funwind-tables -fstack-protector-strong -Wno-invalid-command-line-argument -Wno-unused-command-line-argument -no-canonical-prefixes -I/Users/yxwang/Library/Android/sdk/ndk-bundle/sources/cxx-stl/llvm-libc++/include -I/Users/yxwang/Library/Android/sdk/ndk-bundle/sources/cxx-stl/llvm-libc++abi/include -I/Users/yxwang/Library/Android/sdk/ndk-bundle/sources/android/support/include -DANDROID -Wa,--noexecstack -Wformat -Werror=format-security -DNDEBUG -O2 -g -gcc-toolchain /Users/yxwang/Library/Android/sdk/ndk-bundle/toolchains/arm-linux-androideabi-4.9/prebuilt/linux-x86_64 -target armv7-none-linux-androideabi -march=armv7-a -mfloat-abi=softfp -mfpu=vfpv3-d16 -mthumb -fpic --std=c++1z -fno-integrated-as --sysroot /Users/yxwang/Library/Android/sdk/ndk-bundle/sysroot -isystem /Users/yxwang/Library/Android/sdk/ndk-bundle/sysroot/usr/include/arm-linux-androideabi -D__ANDROID_API__=15 -DBOOST_ALL_NO_LIB=1 -DBOOST_SYSTEM_STATIC_LINK=1 -DNDEBUG -I"." -c -o "../build/build/armeabi-v7a/boost/bin.v2/libs/system/build/clang-darwin-armeabiv7a/release/link-static/target-os-android/threading-multi/error_code.o" "libs/system/src/error_code.cpp"

    这样的编译命令。

    我仔细查看了这个命令,并且和其他项目成功编译时的命令进行比较,最终发现一个问题

-gcc-toolchain /Users/yxwang/Library/Android/sdk/ndk-bundle/toolchains/arm-linux-androideabi-4.9/prebuilt/linux-x86_64

    这个参数问题,因为我使用的是mac os系统,所以这个toolchain的地址是错误的,应该是

    /toolchains/arm-linux-androideabi-4.9/prebuilt/darwin-x86_64 (最后的平台文件夹名字不对)

    我们可以在user-config-boost-1_65_1-armeabi-v7a.jam中对这个参数进行修改。

    修改后就能正常打包出来。

    PS: 他原来 <compileflags>-target
                    <compileflags>armv7-none-linux-androideabi15 

    好像是这样写的,但是我改成了

                    <compileflags>-target
                    <compileflags>armv7-none-linux-androideabi

    因为我发现正常编译的情况下target就是这么写的。

    

   很重要,当出现编译失败的时候,对编译命令进行对比查看是一个不错的debug方式

    在使用编译出来的system库的时候遇到了一个问题,

FAILED: : && /Users/yxwang/Library/Android/sdk/ndk-bundle/toolchains/llvm/prebuilt/darwin-x86_64/bin/clang++  --target=aarch64-none-linux-android --gcc-toolchain=/Users/yxwang/Library/Android/sdk/ndk-bundle/toolchains/aarch64-linux-android-4.9/prebuilt/darwin-x86_64 --sysroot=/Users/yxwang/Library/Android/sdk/ndk-bundle/sysroot -fPIC -isystem /Users/yxwang/Library/Android/sdk/ndk-bundle/sysroot/usr/include/aarch64-linux-android -D__ANDROID_API__=21 -g -DANDROID -ffunction-sections -funwind-tables -fstack-protector-strong -no-canonical-prefixes -Wa,--noexecstack -Wformat -Werror=format-security -std=c++11 -frtti -fexceptions --std=c++1z -O0 -fno-limit-debug-info  -Wl,--exclude-libs,libgcc.a -Wl,--exclude-libs,libatomic.a --sysroot /Users/yxwang/Library/Android/sdk/ndk-bundle/platforms/android-21/arch-arm64 -Wl,--build-id -Wl,--warn-shared-textrel -Wl,--fatal-warnings -L/Users/yxwang/Library/Android/sdk/ndk-bundle/sources/cxx-stl/llvm-libc++/libs/arm64-v8a -Wl,--no-undefined -Wl,-z,noexecstack -Qunused-arguments -Wl,-z,relro -Wl,-z,now -shared -Wl,-soname,libTKATCLIENTBACKENDNETWORK.so -o ../../../../build/intermediates/cmake/debug/obj/arm64-v8a/libTKATCLIENTBACKENDNETWORK.so src/main/cpp/client/backend/tkat.client.backend.network/CMakeFiles/TKATCLIENTBACKENDNETWORK.dir/src/network-driver.cpp.o src/main/cpp/client/backend/tkat.client.backend.network/CMakeFiles/TKATCLIENTBACKENDNETWORK.dir/src/socket-id.cpp.o  ../../../../src/main/cpp/boost/arm64-v8a/lib/libboost_system.a ../../../../build/intermediates/cmake/debug/obj/arm64-v8a/libTKAT.so -latomic -lm "/Users/yxwang/Library/Android/sdk/ndk-bundle/sources/cxx-stl/llvm-libc++/libs/arm64-v8a/libc++.a" && :
src/main/cpp/client/backend/tkat.client.backend.network/CMakeFiles/TKATCLIENTBACKENDNETWORK.dir/src/network-driver.cpp.o: In function `__cxx_global_var_init':
/Users/yxwang/workspace/gittest/CmakeTest/app/.externalNativeBuild/cmake/debug/arm64-v8a/../../../../src/main/cpp/boost/arm64-v8a/include/boost-1_65_1/boost/system/error_code.hpp:207: undefined reference to `b
oost::system::generic_category()'
src/main/cpp/client/backend/tkat.client.backend.network/CMakeFiles/TKATCLIENTBACKENDNETWORK.dir/src/network-driver.cpp.o: In function `__cxx_global_var_init.1':
/Users/yxwang/workspace/gittest/CmakeTest/app/.externalNativeBuild/cmake/debug/arm64-v8a/../../../../src/main/cpp/boost/arm64-v8a/include/boost-1_65_1/boost/system/error_code.hpp:209: undefined reference to `boost::system::generic_category()'
src/main/cpp/client/backend/tkat.client.backend.network/CMakeFiles/TKATCLIENTBACKENDNETWORK.dir/src/network-driver.cpp.o: In function `__cxx_global_var_init.2':
/Users/yxwang/workspace/gittest/CmakeTest/app/.externalNativeBuild/cmake/debug/arm64-v8a/../../../../src/main/cpp/boost/arm64-v8a/include/boost-1_65_1/boost/system/error_code.hpp:211: undefined reference to `boost::system::system_category()'
src/main/cpp/client/backend/tkat.client.backend.network/CMakeFiles/TKATCLIENTBACKENDNETWORK.dir/src/network-driver.cpp.o: In function `boost::asio::error::get_system_category()':
/Users/yxwang/workspace/gittest/CmakeTest/app/.externalNativeBuild/cmake/debug/arm64-v8a/../../../../src/main/cpp/boost/arm64-v8a/include/boost-1_65_1/boost/asio/error.hpp:230: undefined reference to `boost::system::system_category()'
src/main/cpp/client/backend/tkat.client.backend.network/CMakeFiles/TKATCLIENTBACKENDNETWORK.dir/src/network-driver.cpp.o: In function `error_code':
/Users/yxwang/workspace/gittest/CmakeTest/app/.externalNativeBuild/cmake/debug/arm64-v8a/../../../../src/main/cpp/boost/arm64-v8a/include/boost-1_65_1/boost/system/error_code.hpp:440: undefined reference to `boost::system::system_category()'
src/main/cpp/client/backend/tkat.client.backend.network/CMakeFiles/TKATCLIENTBACKENDNETWORK.dir/src/network-driver.cpp.o: In function `boost::system::error_category::std_category::equivalent(int, std::__ndk1::error_condition const&) const':
/Users/yxwang/workspace/gittest/CmakeTest/app/.externalNativeBuild/cmake/debug/arm64-v8a/../../../../src/main/cpp/boost/arm64-v8a/include/boost-1_65_1/boost/syste
m/error_code.hpp:657: undefined reference to `boost::system::generic_category()'
/Users/yxwang/workspace/gittest/CmakeTest/app/.externalNativeBuild/cmake/debug/arm64-v8a/../../../../src/main/cpp/boost/arm64-v8a/include/boost-1_65_1/boost/system/error_code.hpp:660: undefined reference to `boost::system::generic_category()'
src/main/cpp/client/backend/tkat.client.backend.network/CMakeFiles/TKATCLIENTBACKENDNETWORK.dir/src/network-driver.cpp.o: In function `boost::system::error_category::std_category::equivalent(std::__ndk1::error_code const&, int) const':
/Users/yxwang/workspace/gittest/CmakeTest/app/.externalNativeBuild/cmake/debug/arm64-v8a/../../../../src/main/cpp/boost/arm64-v8a/include/boost-1_65_1/boost/system/error_code.hpp:687: undefined reference to `boost::system::generic_category()'
/Users/yxwang/workspace/gittest/CmakeTest/app/.externalNativeBuild/cmake/debug/arm64-v8a/../../../../src/main/cpp/boost/arm64-v8a/include/boost-1_65_1/boost/system/error_code.hpp:690: undefined reference to `boost::system::generic_category()'
/Users/yxwang/workspace/gittest/CmakeTest/app/.externalNativeBuild/cmake/debug/arm64-v8a/../../../../src/main/cpp/boost/arm64-v8a/include/boost-1_65_1/boost/system/error_code.hpp:702: undefined reference to `boost::system::generic_category()'
clang++: error: linker command failed with exit code 1 (use -v to see invocation)
ninja: build stopped: subcommand failed.
:app:externalNativeBuildDebug FAILED

    就是找不到system模块中的generic_category方法的定义,但是我们确确实实

target_link_libraries(TKATCLIENTBACKENDNETWORK PUBLIC ${Boost_SYSTEM_LIBRARY})

    link了这个library。难道是libboost_system.a 打包出现错误,没有把这两个方法定义进去?于是使用了nm命令查看

localhost:lib yxwang$ nm -g --defined-only libboost_system.a 

error_code.o:
0000000000000000 V DW.ref.__gxx_personality_v0
0000000000000000 W _ZN5boost6system14error_category12std_categoryD0Ev
0000000000000000 W _ZN5boost6system14error_category12std_categoryD2Ev
0000000000000000 W _ZN5boost6system14error_categoryD2Ev
0000000000000000 T _ZN5boost6system15system_categoryEv
0000000000000000 T _ZN5boost6system16generic_categoryEv
0000000000000000 B _ZN5boost6system6throwsE
0000000000000000 W _ZNK5boost6system14error_category10equivalentERKNS0_10error_codeEi
0000000000000000 W _ZNK5boost6system14error_category10equivalentEiRKNS0_15error_conditionE
0000000000000000 W _ZNK5boost6system14error_category12std_category4nameEv
0000000000000000 W _ZNK5boost6system14error_category23default_error_conditionEi
0000000000000000 W _ZNKSt6__ndk121__basic_string_commonILb1EE20__throw_length_errorEv
0000000000000000 W _ZNSt6__ndk112basic_stringIcNS_11char_traitsIcEENS_9allocatorIcEEED2Ev
0000000000000000 V _ZTIN5boost12noncopyable_11noncopyableE
0000000000000000 V _ZTIN5boost6system14error_category12std_categoryE
0000000000000000 V _ZTIN5boost6system14error_categoryE
0000000000000000 V _ZTSN5boost12noncopyable_11noncopyableE
0000000000000000 V _ZTSN5boost6system14error_category12std_categoryE
0000000000000000 V _ZTSN5boost6system14error_categoryE
0000000000000000 V _ZTVN5boost6system14error_category12std_categoryE

    我们发现确确实实存在这两个方法

0000000000000000 T _ZN5boost6system15system_categoryEv
0000000000000000 T _ZN5boost6system16generic_categoryEv

    那么问题出在哪里?查看报错的地方

#ifdef BOOST_ERROR_CODE_HEADER_ONLY
    inline const error_category &  system_category() BOOST_SYSTEM_NOEXCEPT;
    inline const error_category &  generic_category() BOOST_SYSTEM_NOEXCEPT;
#else
    BOOST_SYSTEM_DECL const error_category &  system_category() BOOST_SYSTEM_NOEXCEPT;
    BOOST_SYSTEM_DECL const error_category &  generic_category() BOOST_SYSTEM_NOEXCEPT;
#endif

    第一也没有问题……等等,当BOOST_ERROR_CODE_HEADER_ONLY定义和不定义的时候头文件定义的方法是不一样的。

    会不会是我在编译库的时候打到静态库中的方法和当前调用的时候使用的方法不同?也就是我打包的时候定义了BOOST_ERROR_CODE_HEADER_ONLY这个值,但是在当前使用的时候没有定义(或者反过来。)

    如果是这样,当然会导致找不到方法,因为方法定义根本不同,那么在libboost_system.a查找符号表的时候当然也找不到。

    所以我尝试在cmake中添加

target_compile_definitions(TKATCLIENTBACKENDNETWORK PRIVATE BOOST_ERROR_CODE_HEADER_ONLY)

    表示在编译的时候定义变量BOOST_ERROR_CODE_HEADER_ONLY。然后发现错误消失了。

    所以尽量保证库在编译的时候和在使用的时候使用了相同的configuer。

    PS:最新的Boost-for-android已经支持了1.66.0,并且也已经修复了上述的问题。但是我在使用armeabi-v7a的时候又出现了其他问题。在编译的时候报出   

                                        no archive symbol table (run ranlib)

    解决方案,打开项目中的configs/user-config-boost-1_66_0-common.jam文件,我们看到虽然我们已经配置了  <archiver>$(AndroidBinariesPath)/llvm-ar 但是我们没有配置ranlib的地址。

    PS: 是否使用这个ar是一件值得研究的问题,因为根据ndk-bundle/build/cmake/android.toolchain.cmake 文件,我们同样可以找打ar 和ranlib的配置地址,但是并不是使用llvm-ar这个ar。

    那么ranlib到底在哪里呢?这个是根据不同的指令集有不同地址的,在armeabi-v7a中,地址是$(AndroidNDKRoot)/toolchains/arm-linux-androideabi-4.9/prebuilt/${PlatformOS}-x86_64/bin/arm-linux-androideabi-ranlib

    所以我的解决方案是,打开configs/user-config-boost-1_66_0-armeabi-v7a.jam 并且在最上面添加<ranlib>$(AndroidNDKRoot)/toolchains/arm-linux-androideabi-4.9/prebuilt/${PlatformOS}-x86_64/bin/arm-linux-androideabi-ranlib 

    使用Boost的时候有一个比较大的问题,就是boost库实在太大了,有非常多的头文件,而且头文件之间的相互依赖关系比较复杂,很有可能我们只使用了Boost中非常简单的功能,但是不得不把整个boost的所有头文件全部放入。为了解决这个问题,boost提供了一个bcp的工具用于裁剪boost库。

    首先运行

                bootstrap.sh

    然后

                ./b2 tools/bcp

    编译bcp工具,在dist/bin中会生成一个bcp工具。

    ./bcp --boost=<boost更目录> [需要用到的头文件,比如 boost/algorithm/string.hpp] 输出地址

    就能裁剪bcp。裁剪出来的东西包含了一些lib的cpp文件,我们可以通过b2来编译它,但是由于我是ndk编译,暂时还没研究出怎么去设置编译,所以直接复制了头文件,配合上篇编译出来的库使用,用以缩减头文件数量。

猜你喜欢

转载自my.oschina.net/zzxzzg/blog/1621360