在WSL下为OpenWRT交叉编译出CMake
最近想给家里的路由器写点东西,但习惯了CLion,又想能通过CLion进行远程开发&调试,可是好像配置远程调试需要目标机机器上装有CMake,然而OpenWRT是没有CMake的,找遍了OpenWRT的源也都没看到有CMake,于是决定通过openwrt-sdk来自行编译一个。
在这过程中遇到了很多坑,上网查资料也没找到具体的解决方法,折腾了几天终于搞定,现在记录下来。废话不多说,直接开干。
首先是需要的以及工具环境有:
- ubuntu(或者你是其他的linux发行版),我是懒得装双系统或是虚拟机软件了,直接上WSL
- 开发机上要装CMake
- 按照openwrt官网的指引,安装好交叉编译的各种依赖,详情查看 https://openwrt.org/docs/guide-developer/toolchain/install-buildsystem
我是ubuntu22.04,所以是安装这些内容
有了这些先决条件后,就可以下载openwrt-sdk来编译自己想要的项目了。首先还是去官网下载自己机器对应版本的openwrt-sdk,不知道或者忘了自己是哪个版本可以登陆ssh上去,这里就有写
或者是打开路由器管理页面,一般在页面下方会有写,类似 Powered by LuCI openwrt-23.05......
然后下载openwrt-sdk
解压,来到sdk的根路径下。
此时,按照官网的指引,需要先执行命令来更新软件包和依赖信息
./scripts/feeds update -a
./scripts/feeds install -a
但实际上不需要的,我就没有更新这些东西。直接写Makefile然后编译你的包就行了。
在package下创建cmake文件夹并创建Makefile
这个Makefile是有书写格式的,官网上也有介绍:https://openwrt.org/docs/guide-developer/packages
总的来说我们需要定义好这几个东西:
- Package/CMake —— 包的类别和标题、依赖等
- Build/Configure —— 编译前的配置,比如 configure 操作
- Package/CMake/install —— 定义如何安装,要复制那些可执行文件
我当时也没多想,先直接按照cmake的编译教程去写,也就是在Build/Configure执行 ./bootstrap
# 注意Makefile里只能用tab,不能用4个空格!
include $(TOPDIR)/rules.mk
PKG_NAME:=CMake
PKG_VERSION:=3.30.1
PKG_RELEASE:=1
PKG_SOURCE_URL:=https://github.com/Kitware/CMake.git
PKG_SOURCE_PROTO:=git
PKG_SOURCE_VERSION:=v3.30.1
include $(INCLUDE_DIR)/package.mk
define Package/CMake
SECTION:=utils
CATEGORY:=Utilities
TITLE:=CMake compile tool
endef
define Build/Configure
cd $(PKG_BUILD_DIR) && ./bootstrap
endef
define Package/CMake/install
$(INSTALL_DIR) $(1)/usr/bin
$(CP) $(PKG_BUILD_DIR)/bin/* $(1)/usr/bin/
endef
$(eval $(call BuildPackage,CMake))
然后回到openwrt-sdk根路径,执行make package/cmake/compile V=s
,第一次好像都会弹出make menuconfig的可视化界面,但是我没管他,直接exit掉。最后,也是意料之中的报错了:
这个报错的意思大概是说,在打包成ipk的时候发现依赖这些动态库,但是sdk没能在包里找到这些库文件。我寻思着,这些库文件在路由器上都有,既然已经编译完了,那我直接传可执行文件上去不就得了。果然,在编译目录下能找到可执行文件,那我直接传上去就行了。
上传后执行,我懵了,啊?
不对不对,仔细分析,这个情况应该是这个可执行文件根本就不是openwrt可以执行的,换句话说它就不是交叉编译的产物。这点其实在编译过程中我已经有了点怀疑:
这不就是在用ubuntu上的g++来编译吗?这能行吗,这不能行!
那问题出在哪呢?我想了想,应该是configure阶段,按照我Makefile里的脚本,是直接执行 ./bootstrap,而bootstrap的主要作用就是检测当前环境并生成Makefile,我这样直接执行那不还是检测的ubuntu环境吗?
重新翻翻openwrt的文档,好像是有针对configure传递交叉编译变量的方法:
那我先在cmake源码里试试
且不说我传的变量对不对,它好像根本就不支持 --build这个参数,查看了一下 --help,确实是没找到 --build和--host。
改为用 openwrt-toolchain 编译
我决定放弃用openwrt-sdk编译一个完整的ipk包了,直接使用toolchain里的编译工具,结合CMake的CMAKE_TOOLCHAIN_FILE,把交叉编译的工具链传递给CMake,然后用CMake来编译CMake,这也是CMake官方提供的一种编译方式。
安装openwrt-toolchain的过程就不多说了,也是在和sdk同样的页面处下载 toolchain包,然后解压(openwrt-sdk里其实自带了toolchain,就在staging_dir下的toolchain开头的文件夹内就是交叉编译工具,事实上建议用sdk的toolchain)。下载CMake的源码到随便一个路径下,为了方便我就放在projects下了
进入CMake并创建一个build目录用于编译构建
mkdir -p CMake/build
cd CMake/build
此时需要交叉编译的话,我们需要提供一个额外的.cmake文件,用来传递交叉编译的工具链的路径
touch openwrt_toolchain.cmake
vim openwrt_toolchain.cmake
# openwrt_toolchain.cmake
set(CMAKE_SYSTEM_NAME Linux)
set(CMAKE_SYSTEM_PROCESSOR openwrt)
set(toolchain /home/xiaoandi/github/blog/openwrt-toolchain-23.05.3-x86-64_gcc-12.3.0_musl.Linux-x86_64/toolchain-x86_64_gcc-12.3.0_musl)
# 指定交叉编译器的路径
set(CMAKE_C_COMPILER ${toolchain}/bin/x86_64-openwrt-linux-musl-gcc)
set(CMAKE_CXX_COMPILER ${toolchain}/bin/x86_64-openwrt-linux-musl-g++)
set(CMAKE_AR ${toolchain}/bin/x86_64-openwrt-linux-musl-ar)
set(CMAKE_LINKER ${toolchain}/bin/x86_64-openwrt-linux-musl-ld)
set(CMAKE_RANLIB ${toolchain}/bin/x86_64-openwrt-linux-ranlib)
set(CMAKE_NM ${toolchain}/bin/x86_64-openwrt-linux-nm)
set(CMAKE_OBJDUMP ${toolchain}/bin/x86_64-openwrt-linux-objdump)
set(CMAKE_OBJCOPY ${toolchain}/bin/x86_64-openwrt-linux-objcopy)
set(CMAKE_STRIP ${toolchain}/bin/x86_64-openwrt-linux-strip)
set(CMAKE_FIND_ROOT_PATH ${toolchain}/lib ${toolchain}/usr/lib ${toolchain}/include ${toolchain}/usr/include)
# 使find_program, find_library, find_path, find_file等命令
# 先在CMAKE_FIND_ROOT_PATH中搜索
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY BOTH)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)
然后执行
cmake .. -DCMAKE_TOOLCHAIN_FILE=./openwrt_toolchain.cmake
果然啊,问题可能会迟到,但不会缺席~
还是看看是什么情况吧,打开看看这个CMakeError.log,搜一下unique_ptr
这...看不太懂,但我疑惑的是为啥这里会include到我ubuntu上的头文件呢?
这个问题我琢磨了很久,甚至把241行的编译命令拿出来自行编译,也是不行。最后我才注意到第242行有一个warning:environment variable 'STAGING_DIR' not defined
通常warning我都直接忽略的,但此时也没有更好的办法,想着是不是因为这个环境变量没配置导致的?虽然好像看起来没啥关系,我决定还是试试。
我在openwrt_toolchain.cmake里对这个环境变量进行了设置
set(CMAKE_SYSTEM_NAME Linux)
set(CMAKE_SYSTEM_PROCESSOR openwrt)
set(toolchain /home/xiaoandi/github/blog/openwrt-toolchain-23.05.3-x86-64_gcc-12.3.0_musl.Linux-x86_64/toolchain-x86_64_gcc-12.3.0_musl)
set(ENV{STAGING_DIR} /home/xiaoandi/github/openwrt-sdk-23.05.3-x86-64_gcc-12.3.0_musl.Linux-x86_64/staging_dir)
# 指定交叉编译器的路径
set(CMAKE_C_COMPILER ${toolchain}/bin/x86_64-openwrt-linux-musl-gcc)
set(CMAKE_CXX_COMPILER ${toolchain}/bin/x86_64-openwrt-linux-musl-g++)
set(CMAKE_AR ${toolchain}/bin/x86_64-openwrt-linux-musl-ar)
set(CMAKE_LINKER ${toolchain}/bin/x86_64-openwrt-linux-musl-ld)
set(CMAKE_RANLIB ${toolchain}/bin/x86_64-openwrt-linux-ranlib)
set(CMAKE_NM ${toolchain}/bin/x86_64-openwrt-linux-nm)
set(CMAKE_OBJDUMP ${toolchain}/bin/x86_64-openwrt-linux-objdump)
set(CMAKE_OBJCOPY ${toolchain}/bin/x86_64-openwrt-linux-objcopy)
set(CMAKE_STRIP ${toolchain}/bin/x86_64-openwrt-linux-strip)
set(CMAKE_FIND_ROOT_PATH ${toolchain}/lib ${toolchain}/usr/lib ${toolchain}/include ${toolchain}/usr/include)
# 使find_program, find_library, find_path, find_file等命令
# 先在CMAKE_FIND_ROOT_PATH中搜索
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY BOTH)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)
其实我也不清楚这个staging_dir应该设置哪个路径,但是我看到之前openwrt-sdk的目录里,有一个staging_dir,我觉得设置为这个肯定不会错。删除掉CMakeCache.txt后,再次执行
这次是说缺少openssl的相关变量?我直接从路由器上拷贝下来 libcrypto.so.3 和 libssl.so.3,用这两个应该不会错,但是我在路由器上没找到openssl的头文件。我想着头文件应该都一样吧,直接用ubuntu上的头文件是否也行呢?
试试再说,删除CMakeCache.txt后执行
cmake .. -DCMAKE_TOOLCHAIN_FILE=./openwrt_toolchain.cmake -DOPENSSL_CRYPTO_LIBRARY=./libcrypto.so.3 -DOPENSSL_SSL_LIBRARY=./libssl.so.3 -DOPENSSL_INCLUDE_DIR=/usr/lib
又来新的问题了:
查看CMakeError.txt,发现这次的问题是CMake的测试代码在连接crypto时没找到库文件:
应该是在搜索libcrypto.so的时候没搜到,因为我直接拷贝下来的是libcrypto.so.3,所以只要创建一个软链接指向源文件即可(所以刚才让我传递的那几个变量有什么用呢?)
然后在openwrt_toolchain.cmake里添加CMAKE_FIND_ROOT_PATH,把当前的路径添加进去,让编译器能够搜索到当前路径下的库文件
清除CMakeCache.txt,重新构建,这次终于成功了,不容易啊...
接着make吧,果然还有问题在等着我
这次是缺少opensslconf.h,因为我是用开发机的头文件,果然还是不行吗,那就只有最后的方案了:先交叉编译出openssl,再编译cmake;
这里说一下,决定编译openssl是还有其他原因的。我有试过直接去下载一个opensslconf.h,然后再接着编译,最终能编译出来可执行文件,但是当我把他放到机器上时,运行时会提示缺少某些openssl的符号,经过后来的确认,应该是版本的问题:我的机器上openssl是3.0.14版本,如果我编译CMake 3.30.1版本,那么3.0.14的openssl确实是缺少符号的,而3.28.0的CMake能够使用3.0.14的openssl,虽然最后我编译的也是
3.28.0的CMake(因为截止到目前的CLion好像只支持到CMake 3.28.0),但是还有一点不容忽略的是,openwrt上自带,或者从源下载安装的libopenssl3,是不包含头文件的,后续想要使用openssl开发时,这就会是个问题,所以我干脆顺便把openssl也给编译了,在机器上装两个版本的openssl库文件以供后续使用。
编译openssl-dev
为了区别于目标机器上的libopenssl3,这里先把将要编译的称为openssl-dev
还是直接从github上拉取openssl的代码,openssl比起CMake就方便许多了,它的config命令直接提供了交叉编译的参数
cd openssl
mkdir build
# --prefix是输出目录,--cross-compile-prefix是指编译工具的路径,
# 比如../../toolchain-x86_64_gcc-12.3.0_musl/bin/x86_64-openwrt-linux-musl-
# 实际会用到的gcc就是../../toolchain-x86_64_gcc-12.3.0_musl/bin/x86_64-openwrt-linux-musl-gcc
# 实际上就是把交叉编译要用到的gcc/g++传递过去,注意这里一定要是完整路径,不能是相对路径
./config no-asm shared no-async --prefix=./build --cross-compile-prefix=/home/xiaoandi/github/blog/openwrt-toolchain-23.05.3-x86-64_gcc-12.3.0_musl.Linux-x86_64/toolchain-x86_64_gcc-12.3.0_musl/bin/x86_64-openwrt-linux-musl-
出现这样就是配置成功了
接着执行 make && make install
即可编译安装,完成后在./build里即可找到相应的头文件和库文件。
有了openssl-dev,我们就可以接着编译CMake啦。
让我们回到CMake/build里,修改openwrt_toolchain.cmake,把CMAKE_FIND_ROOT_PATH修改一下:
# openwrt_toolchain
set(CMAKE_SYSTEM_NAME Linux)
set(CMAKE_SYSTEM_PROCESSOR openwrt)
set(toolchain /home/xiaoandi/github/blog/openwrt-toolchain-23.05.3-x86-64_gcc-12.3.0_musl.Linux-x86_64/toolchain-x86_64_gcc-12.3.0_musl)
set(openssl-dev /home/xiaoandi/github/blog/openwrt-toolchain-23.05.3-x86-64_gcc-12.3.0_musl.Linux-x86_64/projects/openssl/build)
set(ENV{STAGING_DIR} /home/xiaoandi/github/openwrt-sdk-23.05.3-x86-64_gcc-12.3.0_musl.Linux-x86_64/staging_dir)
# 指定交叉编译器的路径
set(CMAKE_C_COMPILER ${toolchain}/bin/x86_64-openwrt-linux-musl-gcc)
set(CMAKE_CXX_COMPILER ${toolchain}/bin/x86_64-openwrt-linux-musl-g++)
set(CMAKE_AR ${toolchain}/bin/x86_64-openwrt-linux-musl-ar)
set(CMAKE_LINKER ${toolchain}/bin/x86_64-openwrt-linux-musl-ld)
set(CMAKE_RANLIB ${toolchain}/bin/x86_64-openwrt-linux-ranlib)
set(CMAKE_NM ${toolchain}/bin/x86_64-openwrt-linux-nm)
set(CMAKE_OBJDUMP ${toolchain}/bin/x86_64-openwrt-linux-objdump)
set(CMAKE_OBJCOPY ${toolchain}/bin/x86_64-openwrt-linux-objcopy)
set(CMAKE_STRIP ${toolchain}/bin/x86_64-openwrt-linux-strip)
# set(CMAKE_FIND_ROOT_PATH ${topdir}/projects/CMake/build ${toolchain}/lib ${toolchain}/usr/lib ${toolchain}/include ${toolchain}/usr/include)
set(CMAKE_FIND_ROOT_PATH ${openssl-dev}/lib64 ${openssl-dev}/include)
set(CMAKE_INSTALL_RPATH "/usr/local/openssl-dev/lib64")
set(CMAKE_INSTALL_RPATH_USE_LINK_PATH TRUE)
# 使find_program, find_library, find_path, find_file等命令
# 先在CMAKE_FIND_ROOT_PATH中搜索
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY BOTH)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)
实际上只需要添加openssl-dev的路径就可以了。这里说一下CMAKE_INSTALL_RPATH,因为我们需要在目标机上安装两个版本的openssl,其中自行编译的版本暂时只提供给我们编译的CMake使用,那么我把它放在一个单独的路径下 /usr/local/openssl-dev 但是如果不指定rpath,那么可执行文件会从默认的路径也就是PATH处开始搜索,那肯定会先搜到原来的libopenssl3,这不是我希望看到的,因此需要添加rpath,让可执行文件先从我指定的路径搜索库文件。
好了,开始编译吧,cmake的命令也要改一改,将我们编译的openssl路径传递给那几个参数
# 清除CMake缓存
rm CMakeCache.txt
mkdir output
# 注意,我最后加了 -DCMAKE_INSTALL_PREFIX=./output,用于编译完成后make install到指定路径下
cmake .. -DCMAKE_TOOLCHAIN_FILE=./openwrt_toolchain.cmake -DOPENSSL_CRYPTO_LIBRARY=/home/xiaoandi/github/blog/openwrt-toolchain-23.05.3-x86-64_gcc-12.3.0_musl.Linux-x86_64/projects/openssl/build/lib64/libcrypto.so.3 -DOPENSSL_SSL_LIBRARY=/home/xiaoandi/github/blog/openwrt-toolchain-23.05.3-x86-64_gcc-12.3.0_musl.Linux-x86_64/projects/openssl/build/lib64/libssl.so.3 -DOPENSSL_INCLUDE_DIR=/home/xiaoandi/github/blog/openwrt-toolchain-23.05.3-x86-64_gcc-12.3.0_musl.Linux-x86_64/projects/openssl/build/include -DCMAKE_INSTALL_PREFIX=./output
# 编译,这次我直接开8线程编译了,因为我知道肯定没问题了
make -j8 && make install
编译完成后在output目录下找到最终的产物
传上机器后,看一下可执行文件链接的动态库和rpath
rpath好像多了开发机上toolchain的路径,不过算了,能用就行。
注意
并不是只把bin里的可执行文件复制到机器上就完事了,同时 share/cmake-3.30目录也要复制过去,否则你执行cmake的时候会提示找不到CMAKE_ROOT!
share/cmake-3.30文件夹复制到 /usr/share/cmake-3.30
最后使用CLion连接,远程开发,完事。(可能还需要安装rsync、sftp等,不过这些都已经有ipk包了,不需要再折腾自行编译)
总结踩坑点
- 直接使用openwrt-sdk时,要确认交叉编译工具链参数是否正常传递给类似configure的命令,如果无法传递或不知道怎么传递,可以直接使用toolchain,自行配置交叉编译的gcc。但直接使用还是很麻烦,我建议是你先搞懂如何使用toolchain编译出你的项目的可执行文件,然后根据这个过程才能去写一个sdk下的Makefile
- 使用toolchain时,环境变量STARGING_DIR一定要设置好!路径应该是Openwrt-sdk里的staging_dir。这也是我为什么建议你还是用openwrt-sdk里的toolchain而不是只下载一个openwrt-toolchain包。事实上在官网里也有提到要设置STAGING_DIR:https://openwrt.org/docs/guide-developer/toolchain/crosscompile,但他写的有点模糊,我之前以为如果我是下载openwrt-toolchain这个包的话,把STAGING_DIR设置为openwrt-toolchain的根路径就行了,但实际上不是,对比openwrt-sdk/staging_dir,这里面除了toolchain目录还有其他的东西,host文件夹里甚至还有一套头文件和库文件,我猜测应该是必须要用到openwrt-sdk/staging_dir这个目录,单纯下载openwrt-toolchain包应该是没法用的。
- CMake依赖到openssl-dev(我姑且这么称吧),但是openwrt上你能安装到的只有libopenssl3,这个是不包含头文件的,其实有必要自行编译一个openssl-dev
- 编译完后不要忘记把share/cmake-3.30传到机器上,否则会提示找不到CMAKE_ROOT
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南