如何将C++ Makefile旧项目迁移至CMake并集成GoogleTest框架
问题背景
我正在将基于Makefile构建的老旧C++项目改造为使用CMakeLists.txt构建、集成GoogleTest测试框架的现代化项目,目前无法正确处理各类依赖关系。
现有项目目录结构
Project-LG src/ app/ main.cpp A.cpp A.hpp B.cpp B.hpp C.cpp C.hpp ... cpp-utils/ D.cpp D.hpp E.hpp F.hpp json/ json.hpp hiredis/ # 自带Makefile的子项目 orane2/ # 自带Makefile的子项目 rmr/ # 自带Makefile的子项目 spdlog/ # 头文件库,存在嵌套目录 Makefile
原有src目录下Makefile内容
CC=gcc CPP=g++ SRCDIR = . APPDIR = $(SRCDIR)/app UTILSDIR = $(SRCDIR)/cpp-utils TARGETDIR =./../out OBJDIR =./../out/obj APP_OBJDIR =$(OBJDIR)/app UTILS_OBJDIR =$(OBJDIR)/cpp-utils LIBSDIR = ./../libs INCLUDE_FILE = \ -Iapp \ -Icpp-utils \ -Irmr/si \ -Irmr/si/si95 \ -Irmr/common \ -Ijson \ -Iorane2 \ -I. \ -Ihiredis SUBDIRS := \ $(SRCDIR)/rmr \ $(SRCDIR)/orane2 \ $(SRCDIR)/hiredis LFLAGS = LIBS += -pthread -static-libstdc++ \ -L$(LIBSDIR) -l:librmr.a -l:liborane2.a -l:hiredis.a XAPP_NAME_STR := '"app-lg"' CFLAGS += -Wall -g -O2 -DXAPP_NAME=$(XAPP_NAME_STR) $(INCLUDE_FILE) CPPFLAGS = $(CFLAGS) -fPIC -std=c++17 APP_EXE ?= xapp APP_SOURCES := $(shell find $(APPDIR) -maxdepth 1 -name '*.cpp' -printf '%f\n') UTILS_SOURCES := $(shell find $(UTILSDIR) -maxdepth 1 -name '*.cpp' -printf '%f\n') APP_OBJS := $(addprefix $(APP_OBJDIR)/, $(APP_SOURCES:%.cpp=%.o)) UTILS_OBJS := $(addprefix $(UTILS_OBJDIR)/, $(UTILS_SOURCES:%.cpp=%.o)) .SUFFIXES: .c~.o .PHONY: all $(SUBDIRS) clean all: $(SUBDIRS) $(APP_EXE) $(APP_EXE): $(APP_OBJS) $(UTILS_OBJS) @mkdir -p $(TARGETDIR) @mkdir -p $(OBJDIR) $(CPP) $(CFLAGS) $(LFLAGS) $(APP_OBJS) $(UTILS_OBJS) $(LIBS) -o $(TARGETDIR)/$@ $(APP_OBJDIR)/%.o : $(APPDIR)/%.cpp @mkdir -p $(OBJDIR) @mkdir -p $(APP_OBJDIR) $(CPP) -c $(CPPFLAGS) $(LFLAGS) $< -o $@ $(UTILS_OBJDIR)/%.o : $(UTILSDIR)/%.cpp @mkdir -p $(OBJDIR) @mkdir -p $(UTILS_OBJDIR) $(CPP) -c $(CPPFLAGS) $(LFLAGS) $< -o $@ $(SUBDIRS): $(MAKE) -C $@ clean: rm -rf $(TARGETDIR)
核心难点
- 不清楚如何在CMake构建流程中调用现有子目录的Makefile完成依赖库编译
- 不知道如何正确添加GoogleTest框架并处理其全部关联依赖
解决方案
一、调用子目录现有Makefile编译依赖库
使用CMake内置的ExternalProject模块即可直接调用原有Makefile完成第三方库编译,无需修改子项目任何构建逻辑,配置步骤如下:
- 在项目根目录新建顶层
CMakeLists.txt,先配置基础编译参数cmake_minimum_required(VERSION 3.16) project(Project-LG CXX) # 对齐原Makefile的编译选项 set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_FLAGS "-Wall -g -O2 -fPIC -DXAPP_NAME='\"app-lg\"'") # 对齐原Makefile的输出目录规则 set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR}/libs) set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR}/out) include(ExternalProject) - 逐个配置带Makefile的子项目,将编译出的静态库包装为CMake可识别的导入目标
# 配置hiredis ExternalProject_Add(hiredis_build SOURCE_DIR ${CMAKE_SOURCE_DIR}/src/hiredis CONFIGURE_COMMAND "" BUILD_COMMAND $(MAKE) -C ${CMAKE_SOURCE_DIR}/src/hiredis INSTALL_COMMAND "" BUILD_BYPRODUCTS ${CMAKE_SOURCE_DIR}/libs/libhiredis.a ) add_library(hiredis STATIC IMPORTED) set_target_properties(hiredis PROPERTIES IMPORTED_LOCATION ${CMAKE_SOURCE_DIR}/libs/libhiredis.a INTERFACE_INCLUDE_DIRECTORIES ${CMAKE_SOURCE_DIR}/src/hiredis ) add_dependencies(hiredis hiredis_build) # 配置orane2 ExternalProject_Add(orane2_build SOURCE_DIR ${CMAKE_SOURCE_DIR}/src/orane2 CONFIGURE_COMMAND "" BUILD_COMMAND $(MAKE) -C ${CMAKE_SOURCE_DIR}/src/orane2 INSTALL_COMMAND "" BUILD_BYPRODUCTS ${CMAKE_SOURCE_DIR}/libs/liborane2.a ) add_library(orane2 STATIC IMPORTED) set_target_properties(orane2 PROPERTIES IMPORTED_LOCATION ${CMAKE_SOURCE_DIR}/libs/liborane2.a INTERFACE_INCLUDE_DIRECTORIES ${CMAKE_SOURCE_DIR}/src/orane2 ) add_dependencies(orane2 orane2_build) # 配置rmr,补全所有需要的头文件搜索路径 ExternalProject_Add(rmr_build SOURCE_DIR ${CMAKE_SOURCE_DIR}/src/rmr CONFIGURE_COMMAND "" BUILD_COMMAND $(MAKE) -C ${CMAKE_SOURCE_DIR}/src/rmr INSTALL_COMMAND "" BUILD_BYPRODUCTS ${CMAKE_SOURCE_DIR}/libs/librmr.a ) add_library(rmr STATIC IMPORTED) set_target_properties(rmr PROPERTIES IMPORTED_LOCATION ${CMAKE_SOURCE_DIR}/libs/librmr.a INTERFACE_INCLUDE_DIRECTORIES "${CMAKE_SOURCE_DIR}/src/rmr;${CMAKE_SOURCE_DIR}/src/rmr/si;${CMAKE_SOURCE_DIR}/src/rmr/si/si95;${CMAKE_SOURCE_DIR}/src/rmr/common" ) add_dependencies(rmr rmr_build)注意:如果子项目Makefile编译后库存放在子项目自身目录而非
libs目录,将IMPORTED_LOCATION和BUILD_BYPRODUCTS的路径替换为实际库文件位置即可。 - 配置自有代码与头文件-only库,编译主程序
# 配置头文件搜索路径,spdlog、json都是头文件库,直接加根目录即可自动识别嵌套头文件 include_directories( ${CMAKE_SOURCE_DIR}/src/app ${CMAKE_SOURCE_DIR}/src/cpp-utils ${CMAKE_SOURCE_DIR}/src/json ${CMAKE_SOURCE_DIR}/src/spdlog ) # 收集自有业务源码 file(GLOB APP_SOURCES ${CMAKE_SOURCE_DIR}/src/app/*.cpp) file(GLOB UTILS_SOURCES ${CMAKE_SOURCE_DIR}/src/cpp-utils/*.cpp) # 生成主可执行文件,链接所有依赖,注意静态库链接顺序:被依赖的库放在后面 add_executable(xapp ${APP_SOURCES} ${UTILS_SOURCES}) target_link_libraries(xapp PRIVATE rmr orane2 hiredis pthread -static-libstdc++ )
二、集成GoogleTest测试框架
使用CMake内置的FetchContent模块直接引入GTest源码编译,自动处理所有依赖关联,不需要提前手动安装库:
- 在顶层
CMakeLists.txt中追加测试相关配置enable_testing() include(FetchContent) FetchContent_Declare( googletest # 可提前将GTest源码放入项目third_party目录,直接引用本地路径即可 SOURCE_DIR ${CMAKE_SOURCE_DIR}/third_party/googletest ) # 避免Windows平台下GTest覆盖项目编译选项 set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) FetchContent_MakeAvailable(googletest) - 在项目根目录新建
tests目录存放测试用例,在目录下新建CMakeLists.txt,示例配置如下:# 测试A模块的可执行文件,引入对应依赖的源码 add_executable(test_A test_A.cpp ${CMAKE_SOURCE_DIR}/src/app/A.cpp ${CMAKE_SOURCE_DIR}/src/cpp-utils/D.cpp ) # 链接GTest和业务依赖库 target_link_libraries(test_A PRIVATE GTest::gtest_main rmr orane2 hiredis pthread ) # 自动注册测试用例 include(GoogleTest) gtest_discover_tests(test_A) - 在顶层
CMakeLists.txt末尾追加子目录声明,将测试纳入构建流程add_subdirectory(tests)
三、构建执行命令
使用标准CMake流程即可完成构建与测试:
mkdir build && cd build cmake .. make -j$(nproc) # 运行所有测试用例 ctest
内容的提问来源于stack exchange,提问作者Dan Shmirer
相关产品推荐
相关产品推荐

