近期需要使用grpc在目标主机使用,参考了官方示例,大部分网上教程完成了本篇grpc的交叉编译,并对参考的教程做出了引用
交叉编译grpc
实验环境:
docker ubuntu16.04
cmake version 3.16.1
gcc version 7.5.0
set(PACKAGE_NAME “grpc”)
set(PACKAGE_VERSION “1.28.2”)
set(gRPC_CORE_VERSION “9.0.0”)
- 目标:C++中能够使用 grpc(静态/动态链接库皆可,本文根据官方cmake的编译示例,最后生成的是静态)
!!! Note 如果使用的是make或者参考的非官方的方法,可见其他注意事项,强烈建议根据官方方法,虽然可能可能也会遇到许多问题(官方以及非官方方法本人最终都在该环境下编译成功)
1 安装交叉编译库
添加进环境变量
sudo vim .zshrc/.bashrc
export PATH="$PATH:/cross-compile_toolchain/toolchain/bin"
export STAGING_DIR="/cross-compile_toolchain/toolchain/bin:$STAGING_DIR"
❯ source ~/.zshrc/.bashrc
❯ arm-linux-gcc -v
2 Pre-requisites
安装必要的依赖工具,这里使用cmake进行编译
❯ sudo apt-get install build-essential autoconf libtool pkg-config
❯ sudo apt-get install cmake automake
ubuntu16.04自带的cmake和gcc版本可能会在build中发生意想不到的错误!
同时查看源码中的CMakeLists.txt可以发现
cmake_minimum_required(VERSION 3.5.1)
# We can install dependencies from submodules if we're running
# CMake v3.13 or newer.
if(CMAKE_VERSION VERSION_LESS 3.13)
set(_gRPC_INSTALL_SUPPORTED_FROM_MODULE OFF)
else()
set(_gRPC_INSTALL_SUPPORTED_FROM_MODULE ON)
endif()
目前还不知道该版本编译是否有gcc cmake具体的版本要求。
2.1 ubuntu16.04升级gcc/g++/cmake
# Install CMake 3.16
❯ apt-get update && apt-get install -y wget
❯ wget -q -O cmake-linux.sh https://github.com/Kitware/CMake/releases/download/v3.16.1/cmake-3.16.1-Linux-x86_64.sh
❯ sh cmake-linux.sh -- --skip-license --prefix=/usr
❯ rm cmake-linux.sh
❯ ❯ cmake -v
# update gcc/g++
❯ sudo apt-get install -y software-properties-common
❯ sudo add-apt-repository ppa:ubuntu-toolchain-r/test
❯ sudo apt update
❯ sudo apt install g++-7 -y
❯ sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-7 60 \
--slave /usr/bin/g++ g++ /usr/bin/g++-7
❯ sudo update-alternatives --config gcc
❯ gcc -v
❯ g++ -v
3 安装源代码
这里选用 v1.28.2
❯ git clone https://github.com/grpc/grpc.git docker-grpc
❯ cd docker-grp
# 切换到指定版本
❯ git checkout v1.28.2
# 下载第三方依赖库
❯ git submodule update --init
git submodule update --init失败可查看
4 构建(使用CMake)
You can use CMake to cross-compile gRPC for another architecture. In order to do so, you will first need to build protoc and grpc_cpp_plugin for the host architecture. These tools are used during the build of gRPC, so we need copies of executables that can be run natively.
☝出自官方参考教程Cross-compiling中,可以看到grpc交叉编译时的一些注意事项:
- gRPC交叉编译之前,需要先交叉编译好protobuf
- gRPC需要先生成宿主机平台的protobuf和gRPC
- 原因是在gRPC交叉编译的过程中,需要使用对应的编译平台的bin文件(protoc,grpc_cpp_plugin等等)
所以第一步是需要在宿主机平台完成gRPC和protobuf的安装。
4.1 宿主机平台的构建
mkdir -p "cmake/build"
pushd "cmake/build"
cmake \
-DCMAKE_BUILD_TYPE=Release \
-DgRPC_INSTALL=ON \
-DgRPC_BUILD_TESTS=OFF \
-DgRPC_SSL_PROVIDER=package \
../..
sudo make install
popd
!!! Note 如果想要编译动态库.so文件,运行cmake时添加-DBUILD_SHARED_LIBS=ON
构建完成后可在 /usr/local/lib下发现grpc及子模块的静态连接库,类似 libgrpc.a/libgrpc++.a/…/libgrpc_plugin_support.a/…
同时也可以发现 protoc也已经生成
❯ protoc --version
libprotoc 3.11.2
4.2 宿主机平台下的测试
编译cpp/helloworld
cd grpc/examples/cpp/helloworld/
mkdir build
cd build/
cmake ..
make
启动服务和客户端
# 启动服务端,监听在50051端口
./greeter_server
Server listening on 0.0.0.0:50051
# 启动客户端,服务端返回Hello world
./greeter_client
Greeter received: Hello world
4.3 交叉编译
参考官方示例
write_cross_toolchain.sh(代码取自官方,具体设置根据目标主机和个人情况而定)
# Write a toolchain file to use for cross-compiling.
# /tmp/toolchain.cmake 存放 toolchain的路径,自行决定
# CMAKE_SYSTEM_PROCESSOR 可以根据目标主机中的文件属性查看 file filename
# CMAKE_STAGING_PREFIX 交叉编译时要安装到的路径,编译成功后的链接库就在这儿,建议同 toolchain.cmake路径相同
# CMAKE_C_COMPILER/CMAKE_CXX_COMPILER 交叉编译的工具链,根据目标主机下载配置
cat > /tmp/toolchain.cmake <<'EOT'
SET(CMAKE_SYSTEM_NAME Linux)
SET(CMAKE_SYSTEM_PROCESSOR aarch64)
set(CMAKE_STAGING_PREFIX /tmp/stage)
set(CMAKE_C_COMPILER /usr/bin/aarch64-linux-gnu-gcc-6)
set(CMAKE_CXX_COMPILER /usr/bin/aarch64-linux-gnu-g++-6)
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)
EOT
./write_cross_toolchain.sh
mkdir -p "cmake/build_arm"
pushd "cmake/build_arm"
cmake -DCMAKE_TOOLCHAIN_FILE=/tmp/toolchain.cmake \ # 同上步骤中的 toolchain存放路径
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_INSTALL_PREFIX=/tmp/install \ # 安装路径
../..
make install
popd
成功后可以在 CMAKE_STAGING_PREFIX下发现 grpc及其依赖库的静态链接
❯ readelf -h libcrypto.a
文件:libgrpc++.a(codegen_init.cc.o)
ELF 头:
Magic: 7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00
类别: ELF32
数据: 2 补码,小端序 (little endian)
Version: 1 (current)
OS/ABI: UNIX - System V
ABI 版本: 0
类型: REL (可重定位文件)
系统架构: ARM
版本: 0x1
入口点地址: 0x0
程序头起点: 0 (bytes into file)
Start of section headers: 236 (bytes into file)
标志: 0x5000000, Version5 EABI
Size of this header: 52 (bytes)
Size of program headers: 0 (bytes)
Number of program headers: 0
Size of section headers: 40 (bytes)
Number of section headers: 10
Section header string table index: 7
❯ objdump -p libgrpc++.a
在归档文件 libgrpc++.a 中:
insecure_credentials.cc.o: 文件格式 elf32-little
make install 可能遇到各种各样的问题:
4.4 目标主机平台下的测试
!!! warning 注意下述的 DCMAKE_TOOLCHAIN_FILE、Dabsl_DIR和 DProtobuf_DIR必须和交叉编译时设置的 toolchain.cmake/CMAKE_STAGING_PREFIX的路径相同
# Build helloworld example for ARM.
# As above, it will find and use protoc and grpc_cpp_plugin
# for the host architecture.
mkdir -p "examples/cpp/helloworld/cmake/build_arm"
pushd "examples/cpp/helloworld/cmake/build_arm"
cmake -DCMAKE_TOOLCHAIN_FILE=/tmp/toolchain.cmake \
-DCMAKE_BUILD_TYPE=Release \
-Dabsl_DIR=/tmp/stage/lib/cmake/absl \
-DProtobuf_DIR=/tmp/stage/lib/cmake/grpc \
../..
make "-j${GRPC_CPP_DISTRIBTEST_BUILD_COMPILER_JOBS}"
popd
将 /build_arm中生成的可执行文件拷贝到目标主机进行测试即可,现象同在宿主机下的测试
5 可能遇到的问题
获取第三方依赖库失败
打开 .gitmodules进行手动下载,列举grpc中的部分 modules
[submodule "third_party/googleapis"]
path = third_party/googleapis
url = https://github.com/googleapis/googleapis.git
[submodule "third_party/protoc-gen-validate"]
path = third_party/protoc-gen-validate
url = https://github.com/envoyproxy/protoc-gen-validate.git
[submodule "third_party/udpa"]
path = third_party/udpa
url = https://github.com/cncf/udpa.git
[submodule "third_party/libuv"]
path = third_party/libuv
url = https://github.com/libuv/libuv.git
可以看到 modules的具体存放路径以及 url,这里仅提供一下两种途径
- github镜像地址:https://github.com.cnpmjs.org
- 将https://github.com换成https://github.com.cnpmjs.org即可
- gitee
plugin-notfound
- 出现类似以下的错误
[ 70%] Running gRPC C++ protocol buffer compiler on src/proto/grpc/reflection/v1alpha/reflection.proto _gRPC_CPP_PLUGIN-NOTFOUND: program not found or is not executable Please specify a program using absolute path or make sure the program is available in your PATH system variable --grpc_out: protoc-gen-grpc: Plugin failed with status code 1. make: *** [all] Error 2
是因为交叉编译版本的grpc_cpp_plugin在宿主机中无法运行的问题
- 解决方案
- 替换/bins/opt中对应的文件,用之前步骤中安装的宿主机版本的grpc_cpp_plugin等文件即可通过编译
- 最直接有效的方法是从头在宿主机安装一遍
execinfo-notfound
- 出现类似以下的错误
In file included from /home/autowise/Work/wxz_workspace/docker-grpc/third_party/abseil-cpp/absl/debugging/stacktrace.cc:46:0: /home/autowise/Work/wxz_workspace/docker-grpc/third_party/abseil-cpp/absl/debugging/internal/stacktrace_generic-inl.inc:14:22: fatal error: execinfo.h: No such file or directory compilation terminated. third_party/abseil-cpp/absl/debugging/CMakeFiles/absl_stacktrace.dir/build.make:62: recipe for target 'third_party/abseil-cpp/absl/debugging/CMakeFiles/absl_stacktrace.dir/stacktrace.cc.o' failed
- 解决方案
- 一般会在当时构建目标主机固件中的目录下找到
- 将其复制到 ./third_party/abseil-cpp/absl/debugging/internal/stacktrace_generic-inl.inc
- 修改 stacktrace_generic-inl.inc中的 #include <execinfo.h>为 #include “execinfo.h”
package-configuration-notfound
- 出现类似以下的错误
CMake Error at CMakeLists.txt:103 (find_package): Could not find a configuration file for package "Protobuf" that is compatible with requested version "". The following configuration files were considered but not accepted: /usr/local/lib/cmake/protobuf/protobuf-config.cmake, version: 3.11.2.0 (64bit)
- 解决方案
- 一般可能是配置环境的变量时候可工具链不兼容
- echo $GRPC_CROSS_AROPTS
- export GRPC_CROSS_AROPTS=“cr --target=elf32-little” # 指定交叉编译环境变量(32位)或者
- export GRPC_CROSS_AROPTS=“cr --target=elf64-little” # 指定交叉编译环境变量(64位)
libtool: *.la was moved
- 出现类似以下的警告
libtool: warning: library '/toolchain/toolchain-arm_cortex-a7_gcc-5.2.0_musl-1.1.16_eabi/bin/../lib/gcc/arm-linux-muslgnueabi/5.2.0/../../../../arm-linux-muslgnueabi/lib/libstdc++.la' was moved.
参考
libtool提供统一的接口,隐藏了不同平台间库的名称的差异等细节,生成一个抽象的后缀名为la高层库libxx.la(其实是个文本文件),并将该库对其它库的依赖关系,都写在该la的文件中
该文件中的dependency_libs记录该库依赖的所有库(其中有些是以.la文件的形式加入的);libdir则指出了库的安装位置;
library_names记录了共享库的名字;old_library记录了静态库的名字
- 解决方案
- 一般不会影响后续的安装
- 一般时libdir出现错误,消除的办法就是将 .la中的 libdir修改成现在的路径(libdir的路径一般由构建交叉编译工具链时生成)
libtool error: relink ‘libprotoc.la’ with the above command before installing it
- 可能的原因
- 交叉编译找的是宿主机的库,所以读不了
- 在configure的时把路径修改
sudo: arm-linux-gcc: command not found
首先确定配置了环境变量,在此不再赘述
(我直接在主机ubuntu20,没有使用docker时遇到相同的问题)
-
可能的原因
- 执行./configure或者make install时,加了sudo,变成了root的工作环境和root的权限
- 此时是在root下做的,而arm-linux-gnueabihf-gcc在用户的工作环境中才能找到。所以产生了错误
-
解决方案
- 执行./configure之前,先用sudo -i命令取得root权限。然后再执行./configure(这里不建议直接修改系统环境变量)
CMake error / usr / bin / ld:找不到-lpthreads
是在docker(ubuntu16.04 cmake13 gcc5)环境,编译宿主机的测试demo(/cpp/helloworld)时出现该错误的
实际原因没有找到,后续更新了cmake gcc未再出现
其他注意事项
完整删除 protoc
一般进行重构时或者系统已存在 protoc,需要将其完全删除(当然也可以指定路径)
❯ which protoc
/usr/local/bin/protoc
sudo rm /usr/local/bin/protoc //执行文件
sudo rm -rf /usr/local/include/google //头文件
sudo rm -rf /usr/local/lib/lib
❯ which protoc
protoc not found # 删除干净
其他方法
宿主机安装 protobuf -> 宿主机安装 grpc -> 交叉编译 protobuf -> 交叉编译 grpc
参考资料1
参考资料2
参考资料3
安装 protobuf
!!! error 注意:不用手动安装protobuf,不然版本可能和grcp不匹配,必须在 grpc 执行 git submodule update --init 命令之后生成的 third_party/protobuf 里面编译安装对应的 protobuf
❯ cd third_party/protobuf/
❯ ./autogen.sh
❯ ./configure --prefix=/usr/local
❯ make
❯ sudo make install
❯ sudo ldconfig # 使得新安装的动态库能被加载
❯ protoc --version
安装 grpc
交叉编译 protobuf
交叉编译 grpc
!!! Note 需要指定protoc这个程序的路径,这个程序就是我们上面在宿主机安装gRPC的时候,生成的bin文件
export GRPC_CROSS_COMPILE=true # 打开交叉编译
export GRPC_CROSS_AROPTS="cr --target=elf32-little" # 指定交叉编译环境变量(32位)
make -j8 HAS_PKG_CONFIG=false \ # 将grpc编译设置为静态
CC=arm-linux-gcc \
CXX=arm-linux-g++ \
RANLIB=arm-linux-gcc-ranlib \
LD=arm-linux-ld \
LDXX=arm-linux-g++ \
AR=arm-linux-muslgnueabi-ar \
STRIP=arm-linux-muslgnueabi-strip \
prefix=/cross_dynamiclink_lib/grpc \
PROTOBUF_CONFIG_OPTS="--host=arm-linux --with-protoc=/grpc/third_party/protobuf/install/bin/protoc"