Make 和 CMake 详细使用教程
1. 概述
Make 和 CMake 是 C/C++ 开发中最常用的两种构建工具:
- Make:通过 Makefile 定义构建规则,直接执行编译链接操作
- CMake:跨平台构建系统生成器,通过 CMakeLists.txt 生成 Makefile 或其他构建文件
本文将详细介绍这两种工具的使用方法,包含丰富的示例和详细注释。
2. Make 工具详细教程
2.1 Makefile 基本结构
Makefile 由以下几部分组成:
简单 Makefile 示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| TARGET = hello_world
SRCS = main.cpp utils.cpp
OBJS = $(SRCS:.cpp=.o)
CC = g++
CFLAGS = -Wall -g -O0
all: $(TARGET)
$(TARGET): $(OBJS) $(CC) $(CFLAGS) -o $@ $^
%.o: %.cpp $(CC) $(CFLAGS) -c $< -o $@
clean: rm -f $(OBJS) $(TARGET)
|
2.2 Make 变量
Make 变量分为两种:普通变量和递归变量
1 2 3 4 5 6 7 8 9 10 11
| VAR := value
VAR = value
VAR ?= default_value
VAR += additional_value
|
2.3 自动变量
自动变量用于简化规则定义:
2.4 Make 函数
Make 提供了丰富的函数用于处理字符串和文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| $(subst from,to,text) $(patsubst pattern,replacement,text) $(strip string) $(findstring find,text) $(filter pattern...,text) $(filter-out pattern...,text)
$(wildcard pattern) $(dir names...) $(notdir names...) $(suffix names...) $(basename names...)
SRCS = $(wildcard src/*.cpp)
OBJS = $(patsubst src/%.cpp,obj/%.o,$(SRCS))
|
2.5 条件语句
Make 支持条件判断:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| DEBUG ?= 1
ifeq ($(DEBUG), 1) CFLAGS += -g -DDEBUG TARGET := $(TARGET)_debug else CFLAGS += -O2 endif
ifneq ($(OS), Windows_NT) LDFLAGS += -lpthread endif
|
2.6 高级 Makefile 示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55
| PROJECT_NAME = myapp
SRC_DIR = src INC_DIR = include OBJ_DIR = obj BIN_DIR = bin
SRCS = $(wildcard $(SRC_DIR)/*.cpp)
OBJS = $(patsubst $(SRC_DIR)/%.cpp,$(OBJ_DIR)/%.o,$(SRCS))
CC = g++ CFLAGS = -Wall -I$(INC_DIR) -std=c++17
LD = g++ LDFLAGS = -lm
TARGET = $(BIN_DIR)/$(PROJECT_NAME)
all: $(TARGET)
$(OBJ_DIR) $(BIN_DIR): mkdir -p $@
$(OBJ_DIR)/%.o: $(SRC_DIR)/%.cpp | $(OBJ_DIR) $(CC) $(CFLAGS) -c $< -o $@
$(TARGET): $(OBJS) | $(BIN_DIR) $(LD) $(LDFLAGS) $^ -o $@
clean: rm -rf $(OBJ_DIR) $(BIN_DIR)
run: $(TARGET) ./$(TARGET)
debug: $(TARGET) gdb ./$(TARGET)
.PHONY: all clean run debug
|
3. CMake 工具详细教程
3.1 CMakeLists.txt 基本结构
CMakeLists.txt 由以下几部分组成:
简单 CMakeLists.txt 示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| cmake_minimum_required(VERSION 3.10)
project(HelloWorld CXX)
set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON)
add_executable(hello_world main.cpp utils.cpp)
target_include_directories(hello_world PRIVATE include)
target_link_libraries(hello_world PRIVATE m)
|
3.2 CMake 变量
CMake 变量分为几种类型:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| set(VAR value)
set(VAR value CACHE STRING "Description")
set(ENV{VAR} value)
set(VAR $ENV{VAR})
unset(VAR)
if(NOT DEFINED VAR) set(VAR default_value) endif()
|
3.3 目标定义
CMake 支持多种目标类型:
1 2 3 4 5 6 7 8 9 10 11
| add_executable(target_name source1.cpp source2.cpp)
add_library(lib_name STATIC source1.cpp source2.cpp)
add_library(lib_name SHARED source1.cpp source2.cpp)
add_library(lib_name OBJECT source1.cpp source2.cpp)
|
3.4 目标属性
CMake 允许为目标设置各种属性:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| target_compile_options(target_name PRIVATE -Wall -Wextra)
target_include_directories(target_name PUBLIC include/public PRIVATE include/private )
target_link_libraries(target_name PUBLIC lib1 PRIVATE lib2 )
target_compile_definitions(target_name PRIVATE DEBUG=1)
|
3.5 查找外部依赖
CMake 提供了 find_package 命令查找外部依赖:
1 2 3 4 5 6 7 8 9 10 11
| find_package(Boost REQUIRED COMPONENTS filesystem system)
target_link_libraries(target_name PRIVATE Boost::filesystem Boost::system)
find_package(OpenCV REQUIRED)
target_link_libraries(target_name PRIVATE ${OpenCV_LIBS})
|
3.6 构建类型
CMake 支持多种构建类型:
1 2 3 4 5 6 7 8 9 10 11
| if(NOT CMAKE_BUILD_TYPE) set(CMAKE_BUILD_TYPE Release CACHE STRING "Build type" FORCE) endif()
if(CMAKE_BUILD_TYPE STREQUAL "Debug") target_compile_definitions(target_name PRIVATE DEBUG=1) endif()
|
3.7 安装规则
CMake 支持定义安装规则:
1 2 3 4 5 6 7 8 9 10 11 12
| install(TARGETS target_name RUNTIME DESTINATION bin LIBRARY DESTINATION lib ARCHIVE DESTINATION lib )
install(DIRECTORY include/ DESTINATION include)
install(FILES config.ini DESTINATION etc)
|
3.8 高级 CMakeLists.txt 示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96
| cmake_minimum_required(VERSION 3.14)
project(MyProject VERSION 1.0.0 LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_EXTENSIONS OFF)
if(NOT CMAKE_BUILD_TYPE) set(CMAKE_BUILD_TYPE Release CACHE STRING "Build type" FORCE) endif()
set(SRC_DIR ${CMAKE_CURRENT_SOURCE_DIR}/src) set(INC_DIR ${CMAKE_CURRENT_SOURCE_DIR}/include) set(TEST_DIR ${CMAKE_CURRENT_SOURCE_DIR}/tests)
file(GLOB_RECURSE SOURCES ${SRC_DIR}/*.cpp) file(GLOB_RECURSE HEADERS ${INC_DIR}/*.hpp)
add_executable(${PROJECT_NAME} ${SOURCES} ${HEADERS})
target_include_directories(${PROJECT_NAME} PUBLIC $<BUILD_INTERFACE:${INC_DIR}> $<INSTALL_INTERFACE:include> PRIVATE ${SRC_DIR} )
target_compile_options(${PROJECT_NAME} PRIVATE $<$<CXX_COMPILER_ID:GNU>:-Wall -Wextra -pedantic> $<$<CXX_COMPILER_ID:Clang>:-Wall -Wextra -pedantic> $<$<CXX_COMPILER_ID:MSVC>:/W4> )
target_link_libraries(${PROJECT_NAME} PRIVATE m )
include(CTest) if(BUILD_TESTING) add_subdirectory(tests) endif()
install(TARGETS ${PROJECT_NAME} EXPORT ${PROJECT_NAME}-targets RUNTIME DESTINATION bin LIBRARY DESTINATION lib ARCHIVE DESTINATION lib )
install(DIRECTORY ${INC_DIR}/ DESTINATION include)
install(EXPORT ${PROJECT_NAME}-targets FILE ${PROJECT_NAME}-targets.cmake NAMESPACE ${PROJECT_NAME}:: DESTINATION lib/cmake/${PROJECT_NAME} )
include(CMakePackageConfigHelpers) write_basic_package_version_file( ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}-config-version.cmake VERSION ${PROJECT_VERSION} COMPATIBILITY SameMajorVersion )
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/${PROJECT_NAME}-config.cmake.in ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}-config.cmake @ONLY )
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}-config.cmake ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}-config-version.cmake DESTINATION lib/cmake/${PROJECT_NAME} )
|
4. 实际项目示例
4.1 Make 项目示例
假设我们有以下项目结构:
1 2 3 4 5 6 7
| my_project/ ├── include/ │ └── utils.h ├── src/ │ ├── main.cpp │ └── utils.cpp └── Makefile
|
Makefile 内容
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51
| PROJECT = my_app
SRC_DIR = src INC_DIR = include OBJ_DIR = obj BIN_DIR = bin
CC = g++ CFLAGS = -Wall -I$(INC_DIR) -std=c++17
LD = g++ LDFLAGS = -lm
SRCS = $(wildcard $(SRC_DIR)/*.cpp) OBJS = $(patsubst $(SRC_DIR)/%.cpp,$(OBJ_DIR)/%.o,$(SRCS)) TARGET = $(BIN_DIR)/$(PROJECT)
all: $(TARGET)
$(OBJ_DIR) $(BIN_DIR): mkdir -p $@
$(OBJ_DIR)/%.o: $(SRC_DIR)/%.cpp | $(OBJ_DIR) $(CC) $(CFLAGS) -c $< -o $@
$(TARGET): $(OBJS) | $(BIN_DIR) $(LD) $(LDFLAGS) $^ -o $@
clean: rm -rf $(OBJ_DIR) $(BIN_DIR)
run: $(TARGET) ./$(TARGET)
debug: $(TARGET) gdb ./$(TARGET)
.PHONY: all clean run debug
|
4.2 CMake 项目示例
假设我们有以下项目结构:
1 2 3 4 5 6 7 8 9 10 11 12
| my_project/ ├── include/ │ └── mylib/ │ └── utils.hpp ├── src/ │ ├── main.cpp │ └── mylib/ │ └── utils.cpp ├── tests/ │ ├── CMakeLists.txt │ └── test_utils.cpp └── CMakeLists.txt
|
根目录 CMakeLists.txt 内容
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| cmake_minimum_required(VERSION 3.14)
project(MyProject VERSION 1.0.0 LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_EXTENSIONS OFF)
if(NOT CMAKE_BUILD_TYPE) set(CMAKE_BUILD_TYPE Release CACHE STRING "Build type" FORCE) endif()
add_subdirectory(src/mylib)
add_executable(myapp src/main.cpp)
target_link_libraries(myapp PRIVATE MyLib)
target_include_directories(myapp PRIVATE src)
include(CTest) if(BUILD_TESTING) add_subdirectory(tests) endif()
|
src/mylib/CMakeLists.txt 内容
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| file(GLOB_RECURSE LIB_SOURCES "*.cpp") file(GLOB_RECURSE LIB_HEADERS "../../include/mylib/*.hpp")
add_library(MyLib ${LIB_SOURCES} ${LIB_HEADERS})
target_include_directories(MyLib PUBLIC $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/../../include> $<INSTALL_INTERFACE:include> PRIVATE ${CMAKE_CURRENT_SOURCE_DIR} )
target_compile_options(MyLib PRIVATE $<$<CXX_COMPILER_ID:GNU>:-Wall -Wextra -pedantic> $<$<CXX_COMPILER_ID:Clang>:-Wall -Wextra -pedantic> $<$<CXX_COMPILER_ID:MSVC>:/W4> )
|
tests/CMakeLists.txt 内容
1 2 3 4 5 6 7 8 9 10 11
| find_package(GTest REQUIRED)
add_executable(test_utils test_utils.cpp)
target_link_libraries(test_utils PRIVATE MyLib GTest::GTest GTest::Main)
gtest_discover_tests(test_utils)
|
5. 总结
Make 和 CMake 是 C/C++ 开发中最常用的构建工具:
- Make:适合简单项目,通过 Makefile 直接定义构建规则
- CMake:适合复杂项目和跨平台开发,通过 CMakeLists.txt 生成 Makefile 或其他构建文件
选择建议:
- 小型项目或需要精确控制构建过程:使用 Make
- 大型项目、跨平台项目或需要管理复杂依赖:使用 CMake
- 实际开发中,通常将两者结合使用:CMake 生成 Makefile,然后使用 Make 执行构建
通过本文的教程和示例,您应该能够熟练使用 Make 和 CMake 构建各种规模的 C/C++ 项目。