文章目录
学习要点
这个部分尽量还是结合实际项目的时候加深学习即可,如果没有实际项目的话只需要稍微了解一下既可以。
Makefile
基本介绍
依赖也可以称为条件
参考文档:https://www.yuque.com/docs/share/8495ea21-9fdb-4e7b-aca2-babd86751e39?
Makefile工作原理
例子
例1
jyh:
@echo "hello jyh"
all:
@echo "hello all"
test:
@echo "hello test"
clean:
@echo "hello clean"
实验结果
例2
all: test
@echo "hello all"
test:
@echo "hello test"
实验结果
hello test
hello all
例3:伪对象 和 多个依赖执行顺序
.PHONY: main clean
simple: main.o foo.o
@echo "simple: main.o foo.o"
gcc -o simple main.o foo.o
main.o: main.c
@echo "gcc -o main.o -c main.c"
gcc -o main.o -c main.c
foo.o: foo.c
@echo "gcc -o foo.o -c foo.c"
gcc -o foo.o -c foo.c
clean:
rm simple main.o foo.o
先执行依赖main.o 在执行foo.o
并且,如果第一次make之后,只修改foo.c那么再次 make的时候就只执行 目标foo.o的命令
.PHONY
是一个伪目标,可以有效防止在Makefile文件中定义的可执行命令的目标规则和工作目录下的实际文件出现名称冲突,如果出现重名,选择的是Makefile文件中的目标,并提高了执行Makefile的性能。一句话:就是防止目标和当前目录下文件同名
例4:变量
.PHONY: clean
#编译器名
CC = gcc
RM = rm
EXE = simple
OBJS = main.o foo.o foo2.o
$(EXE): $(OBJS)
$(CC) -o $(EXE) $(OBJS)
main.o: main.c
$(CC) -o main.o -c main.c
foo.o: foo.c
$(CC) -o foo.o -c foo.c
foo2.o: foo2.c
$(CC) -o foo2.o -c foo2.c
clean:
$(RM) $(EXE) $(OBJS)
结果
jyhlinux@ubuntu:~/share/src-makefile/1.5.1$ make
gcc -o main.o -c main.c
gcc -o foo.o -c foo.c
gcc -o foo2.o -c foo2.c
gcc -o simple main.o foo.o foo2.o
jyhlinux@ubuntu:~/share/src-makefile/1.5.1$ ./simple
This is foo ()!
This is foo2 ()!
例5:自动变量
.PHONY: all
all: first second third
#$@ 目标名字
@echo "\$$@ = $@"
#$^ 表示所有依赖
@echo "$$^ = $^"
#$< 表示左边第一个依赖(先决条件)
@echo "$$< = $<"
first:
@echo "1 first"
second:
@echo "2 second"
third:
@echo "3 third"
如果想采用 echo 输出
$
,则必需用两个连着的$
。还有就是,$@
对于 Shell 也有特殊的意思,我们需要在“$$@”之前再加一个脱字符‘\’
输出结果
jyhlinux@ubuntu:~/share/src-makefile/1.5.2$ make
1 first
2 second
3 third
$@ = all
$^ = first second third
$< = first
例6:用变量设置头文件路径和库文件路径
CROSS =
# 定义CC为gcc编译
CC = $(CROSS)gcc
# 定义CXX为g++编译
CXX = $(CROSS)g++
# 定义DEBUG 方式为 -g -O2
DEBUG = -g -O2
CFLAGS = $(DEBUG) -Wall -c
RM = rm -rf
# /定义SRC为当前工程目录下所有的.cpp文件
SRCS = $(wildcard ./*.c)
# 定义OBJS为SRCS对应的.o文件
OBJS = $(patsubst %.c, %.o, $(SRCS))
# 定义HEADER_PATH为当前工程中的头文件路径
HEADER_PATH = -I ./include/
# 定义LIB_PATH为当前工程中的头文件路径
LIB_PATH = -L ./lib/
# 输出当前LIB_PATH中的内容
$(warning LIB_PATH)
# 制定LIBS链接库的名称
LIBS=-lpthread
# lib中的库文件名称为libpthread.so
# 定义当前生成的版本
VERSION = 1.0.0
# 定义生成可执行文件的名称
TARGET = simple.$(VERSION)
$(TARGET) : $(OBJS)
# 告诉编译器生成可执行文件时库存放的目录,以及库的名字
$(CXX) $^ -o $@ $(LIB_PATH) $(LIBS)
$(OBJS):%.o : %.c
#告诉编译器申城中间文件时头文件的所在目录
$(CXX) $(CFLAGS) $< -o $@ $(HEADER_PATH)
clean:
$(RM) $(TARGET) *.o
wildcard是通配符函数,通过它可以得到我们所需的文件;
patsubst 函数是用来进行字符串替换,形式为:
$(patsubst pattern, replacement, text)Makefile中的%表示通配符,而*表示任意字符
举例,%.o表示匹配所有的.o文件,注意是用于匹配的,有点类似于可以当做变量用。
*.o是表示所有的.o文件。
CMake
参考资料
https://www.yuque.com/docs/share/d66b3695-cd45-43ca-bb3f-87f51ae589a6?
介绍
CMake是一个跨平台的安装( 编译 )工具,可以用简单的语句来描述所有平台的安装 编译过程 。他能够输出各种各样的 makefile 或者 project 文件,能测试 编译器所支持的 C++ 特性 类似 UNIX 下的 automake 。只是 CMake 的 文件名 取名为CMakeLists.txt(不区分大小写) 。 Cmake 并不直接建构出最终的软件,而是产生标准的建构档(如 Unix 的 Makefile 或 Windows Visual C++ 的 projects/workspaces ),然后再依一般的建构方式使用。
本质上CMake就是基于MakeFile做二次开发
使用方法
下面通过例子介绍CMake基本使用方法。
例1
#CMakeLists.txt
# 单个目录实现
# CMake 最低版本号要求
cmake_minimum_required (VERSION 2.8)
# 工程,他不是执行文件名
PROJECT(Darren)
# 手动加入文件 ${变量名}} ,比如${SRC_LIST}
SET(SRC_LIST main.c)
set(SRC_LIST2 main2.c)
# MESSAGE和echo类似 ;STATUS表示输出前缀为—的信息
MESSAGE(STATUS "PROJECT_BINARY_DIR DIR " ${PROJECT_BINARY_DIR})
MESSAGE(STATUS "PROJECT_SOURCE_DIR DIR " ${PROJECT_SOURCE_DIR})
# 生产执行文件名0voice 0voice2
# 相当于 gcc -o 0Voice ${SRC_LIST}
ADD_EXECUTABLE(0voice ${SRC_LIST})
ADD_EXECUTABLE(0voice2 ${SRC_LIST2})
执行时需要如下步骤:
-
在包含 CMakeList.txt文件下 输入
cmake . #.代表当前目录下
会生成Makefile文件和一些中间文件(这里可以进行优化处理)
-
输入 make 完成编译生成可执行文件
0voice
和0voice2
-
然后运行测试
红框部分是中间过程文件可以放到一个自己新建的文件一般取名为 build,如果在内层路径调用外层的CMakeLists.txt
可以通过下方语句
jyhlinux@ubuntu:~/share/src-cmake/2.1-1$ mkdir build
jyhlinux@ubuntu:~/share/src-cmake/2.1-1$ cd build/
jyhlinux@ubuntu:~/share/src-cmake/2.1-1/build$ cmake ..
(省略).......
......
jyhlinux@ubuntu:~/share/src-cmake/2.1-1/build$ ls
CMakeCache.txt CMakeFiles cmake_install.cmake Makefile
cmake …代表到外层路径执行CMakeLists.txt
例2:单个文件目录实现
目录结构
根目录下的 CMakeLists.txt代码:
# CMake 最低版本号要求
cmake_minimum_required (VERSION 2.8)
PROJECT(0VOICE)
MESSAGE(STATUS "0voice top PROJECT_BINARY_DIR " ${PROJECT_BINARY_DIR})
MESSAGE(STATUS "0voice top PROJECT_SOURCE_DIR " ${PROJECT_SOURCE_DIR})
# CMAKE_CURRENT_SOURCE_DIR它的CMakeLists.txt所在的当前源目
MESSAGE(STATUS "0voice top CMAKE_CURRENT_SOURCE_DIR " ${CMAKE_CURRENT_SOURCE_DIR})
# 添加子目录,表示该目录下也有CMakeLists.txt
# 输入cmake 或 makefile命令会同时执行
ADD_SUBDIRECTORY(src)
#INSTALL(FILES COPYRIGHT README DESTINATION share/doc/cmake/0voice)
# 安装doc到 share/doc/cmake/0voice目录
# 默认/usr/local/
#指定自定义目录,比如 cmake -DCMAKE_INSTALL_PREFIX=/tmp/usr ..
INSTALL(DIRECTORY doc/ DESTINATION share/doc/cmake/0voice)
子目录(src)下的CMakeLists.txt
# 单个目录实现
# CMake 最低版本号要求
cmake_minimum_required (VERSION 2.8)
# 工程
# PROJECT(0VOICE)
# 手动加入文件
SET(SRC_LIST main.c)
MESSAGE(STATUS "0voice sub PROJECT_BINARY_DIR " ${PROJECT_BINARY_DIR})
MESSAGE(STATUS "0voice sub PROJECT_SOURCE_DIR " ${PROJECT_SOURCE_DIR})
# CMAKE_CURRENT_SOURCE_DIR它的CMakeLists.txt所在的当前源目
MESSAGE(STATUS "0voice sub CMAKE_CURRENT_SOURCE_DIR " ${CMAKE_CURRENT_SOURCE_DIR})
ADD_EXECUTABLE(youzi ${SRC_LIST})
# 演示
ADD_EXECUTABLE(youzi2 ${SRC_LIST})
# 将执行文件安装到bin目录
# 默认/usr/local/
#指定自定义目录,比如 cmake -DCMAKE_INSTALL_PREFIX=/tmp/usr ..
INSTALL(TARGETS youzi RUNTIME DESTINATION bin)
INSTALL(TARGETS youzi2 RUNTIME DESTINATION bin)
创建好 build目录,并输入 cmake … 后执行结果如下:
再执行make之后,得到子目录下CMakeLists.txt生成的可执行文件(youzi1和youzi2)在build下的src文件夹中
例3:多个目录实现-子目录编译成库文件
目录结构
3.1-1/CMakeLists.txt
# CMake 最低版本号要求
cmake_minimum_required (VERSION 2.8)
PROJECT(0VOICE)
ADD_SUBDIRECTORY(src)
# FILES代表文件
# DIRECTORY 代表目录
# make install时将当前目录下doc/ 内容 拷贝到 /tmp/usr/share/doc/cmake/0voice 文件夹下
INSTALL(DIRECTORY doc/ DESTINATION /tmp/usr/share/doc/cmake/0voice)
3.1-1/src/CMakeLists.txt
# 单个目录实现
# CMake 最低版本号要求
cmake_minimum_required (VERSION 2.8)
# 工程
PROJECT(0VOICE)
# 手动加入文件
SET(SRC_LIST main.c)
# PROJECT_BINARY_DIR表示可执行文件所在目录,通常是build/src
MESSAGE(STATUS "THIS IS BINARY DIR " ${PROJECT_BINARY_DIR})
MESSAGE(STATUS "THIS IS SOURCE DIR " ${PROJECT_SOURCE_DIR})
# 添加头文件路径
INCLUDE_DIRECTORIES("${CMAKE_CURRENT_SOURCE_DIR}/dir1")
# 相对路径的方式
INCLUDE_DIRECTORIES(dir1)
MESSAGE(STATUS "CMAKE_CURRENT_SOURCE_DIR -> " ${CMAKE_CURRENT_SOURCE_DIR})
# 添加 dir1 子目录
ADD_SUBDIRECTORY("${CMAKE_CURRENT_SOURCE_DIR}/dir1")
# 添加头文件路径
INCLUDE_DIRECTORIES("${CMAKE_CURRENT_SOURCE_DIR}/dir2")
# 添加 dir2 子目录
ADD_SUBDIRECTORY("${CMAKE_CURRENT_SOURCE_DIR}/dir2")
# 用SRC_LIST所存的名字的源文件来生成可执行文件 darren
ADD_EXECUTABLE(darren ${SRC_LIST} )
TARGET_LINK_LIBRARIES(darren dir1 dir2)
# 将执行文件安装到bin目录(其实就是拷贝)
INSTALL(TARGETS darren RUNTIME DESTINATION bin)
3.1-1/src/dir1/CMakeLists.txt
# 加载所有的源码,和makefile wildcard类似
# 将当前目录下所有源文件名字存入DIR_SRCS中
AUX_SOURCE_DIRECTORY(. DIR_SRCS)
# SET(DIR_SRCS dir1.c dir12.c)
# 默认是静态库
# 将DIR_SRCS中记录的源文件生成动态库,名字为 libdir1.so
ADD_LIBRARY (dir1 SHARED ${DIR_SRCS})
# ADD_LIBRARY (dir1 ${DIR_SRCS})
3.1-1/src/dir2/CMakeLists.txt
AUX_SOURCE_DIRECTORY(. DIR_SRCS)
# 默认是静态库
# ADD_LIBRARY(dir2 SHARED ${DIR_SRCS})
ADD_LIBRARY(dir2 ${DIR_SRCS})
实验结果
安装文件到某个路径
默认路径为: /usr/local/
自定义安装路径(此时处于build目录下 ): cmake -DCMAKE_INSTALL_PREFIX=/tmp/usr …
例4:多个目录实现-子目录使用源码编译
目录结构
3.2-1/CMakeLists.txt
# CMake 最低版本号要求
cmake_minimum_required (VERSION 2.8)
PROJECT(0VOICE)
ADD_SUBDIRECTORY(src)
MESSAGE(STATUS "TOP CMAKE_CURRENT_SOURCE_DIR -> " ${CMAKE_CURRENT_SOURCE_DIR})
#INSTALL(FILES COPYRIGHT README DESTINATION share/doc/cmake/0voice)
# INSTALL(DIRECTORY doc/ DESTINATION share/doc/cmake/0voice)
实验结果
3.2-1/src/CMakeLists.txt
# 单个目录实现
# CMake 最低版本号要求
cmake_minimum_required (VERSION 2.8)
# 工程
# PROJECT(0VOICE)
# 手动加入文件
SET(SRC_LIST main.c)
MESSAGE(STATUS "THIS IS BINARY DIR " ${PROJECT_BINARY_DIR})
MESSAGE(STATUS "THIS IS SOURCE DIR " ${PROJECT_SOURCE_DIR})
# 设置子目录
set(SUB_DIR_LIST "${CMAKE_CURRENT_SOURCE_DIR}/dir1" "${CMAKE_CURRENT_SOURCE_DIR}/dir2")
# 对于SUB_DIR_LIST中的每一个串存在SUB_DIR中
foreach(SUB_DIR ${SUB_DIR_LIST})
# 自动添加头文件路径
INCLUDE_DIRECTORIES(${SUB_DIR})
#遍历源文件 将SUB_DIR内容追加到 SRC_LIST
aux_source_directory(${SUB_DIR} SRC_LIST)
MESSAGE(STATUS "SUB_DIR-> " ${SUB_DIR})
MESSAGE(STATUS "SRC_LIST-> " ${SRC_LIST})
endforeach()
# 手动 添加头文件路径
# INCLUDE_DIRECTORIES("dir1")
# INCLUDE_DIRECTORIES("dir2")
# 将三个源文件编译成 可执行文件
ADD_EXECUTABLE(darren ${SRC_LIST} )
# 将执行文件安装到bin目录
INSTALL(TARGETS darren RUNTIME DESTINATION bin)
例3和例4的相同点
例3是将子目录编译成库文件,再给到上一级进行链接生成可执行文件
例4是将子目录的源文件和上一级源文件一同编译
例5:生成库-生成动态库
带参数进行cmake
cmake DCMAKE_BUILD_TYPE=Debug … #CMAKE_BUILD_TYPE参数设置为Debug
cmakeDCMAKE_BUILD_TYPE=Release …
4.1/CMakeLists.txt
cmake_minimum_required(VERSION 2.8s)
project("shared")
# 设置release版本还是debug版本
if(${CMAKE_BUILD_TYPE} MATCHES "Release")
# 不带-g
MESSAGE(STATUS "Release版本")
SET(BuildType "Release")
else()
# -g
SET(BuildType "Debug")
MESSAGE(STATUS "Debug版本")
endif()
#设置lib库目录
# RELEASE_DIR 自定义的变量
SET(RELEASE_DIR ${PROJECT_SOURCE_DIR}/release)
# debug和release版本目录不一样
#设置生成的so动态库最后输出的路径
SET(LIBRARY_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/${BuildType})
MESSAGE(STATUS "LIBRARY_OUTPUT_PATH: " ${LIBRARY_OUTPUT_PATH})
# 设置编译参数
ADD_COMPILE_OPTIONS(-fPIC)
# 查找当前目录下的所有源文件
# 并将名称保存到 DIR_LIB_SRCS 变量
AUX_SOURCE_DIRECTORY(. DIR_LIB_SRCS)
# 默认生成静态库链接库Dir1
#ADD_LIBRARY (Dir1 ${DIR_LIB_SRCS})
# SHARED生成动态库存到 LIBRARY_OUTPUT_PATH路径下
ADD_LIBRARY (Dir1 SHARED ${DIR_LIB_SRCS})
实验结果
例6:生成库-生成静态库+安装到指定目录
4.2/CMakeLists.txt
cmake_minimum_required(VERSION 3.21)
project("static")
# 设置release版本还是debug版本
if(${CMAKE_BUILD_TYPE} MATCHES "Release")
MESSAGE(STATUS "Release版本")
SET(BuildType "Release")
else()
SET(BuildType "Debug")
MESSAGE(STATUS "Debug版本")
endif()
#设置lib库目录
SET(RELEASE_DIR ${PROJECT_SOURCE_DIR}/release)
# debug和release版本目录不一样
#设置生成的so动态库最后输出的路径
SET(LIBRARY_OUTPUT_PATH ${RELEASE_DIR}/linux/${BuildType})
ADD_COMPILE_OPTIONS(-fPIC)
# 查找当前目录下的所有源文件
# 并将名称保存到 DIR_LIB_SRCS 变量
AUX_SOURCE_DIRECTORY(. DIR_LIB_SRCS)
# 生成静态库链接库Dir1
ADD_LIBRARY (Dir1 ${DIR_LIB_SRCS})
# !!!将【库文件】安装到lib目录,即默认为/usr/local/lib/libDir1.a
# 这里libDir1.a中的libxxx.a是自动补全的
INSTALL(TARGETS Dir1 ARCHIVE DESTINATION lib)
# !!!将【头文件】include,即默认为/usr/local/lib/dir.h
# 相当于将./dir1.h 拷贝到 /usr/local/lib/dir.h
INSTALL(FILES dir1.h DESTINATION include)
#INSTALL(FILES *.h DESTINATION include)
实验结果
例7:调用动态库
目录结构
CMakeLists.txt
# 单个目录实现
# CMake 最低版本号要求
cmake_minimum_required (VERSION 2.8)
# 工程
PROJECT(0VOICE)
# 手动加入文件
SET(SRC_LIST main.c)
MESSAGE(STATUS "THIS IS BINARY DIR " ${PROJECT_BINARY_DIR})
MESSAGE(STATUS "THIS IS SOURCE DIR " ${PROJECT_SOURCE_DIR})
# 指定头文件所在目录
INCLUDE_DIRECTORIES("${CMAKE_CURRENT_SOURCE_DIR}/lib")
# 指定链接库所在目录
LINK_DIRECTORIES("${CMAKE_CURRENT_SOURCE_DIR}/lib")
# 引用动态库
ADD_EXECUTABLE(darren ${SRC_LIST})
# 链接 动态库libDir1.so 生成可执行文件 darren
TARGET_LINK_LIBRARIES(darren Dir1) # 正确
# TARGET_LINK_LIBRARIES(darren libDir1) # 错误
TARGET_LINK_LIBRARIES(darren libDir1.so) # 正确动态库名字要注意!!
#下面方式是强制找静态库
#TARGET_LINK_LIBRARIES(darren libDir1.a)
#TARGET_LINK_LIBRARIES(darren lDir1)
例7:调用静态库
若同时存在动态库和静态库,则会优先连接动态库
# 单个目录实现
# CMake 最低版本号要求
cmake_minimum_required (VERSION 2.8)
# 工程
PROJECT(0VOICE)
# 手动加入文件
SET(SRC_LIST main.c)
MESSAGE(STATUS "THIS IS BINARY DIR " ${PROJECT_BINARY_DIR})
MESSAGE(STATUS "THIS IS SOURCE DIR " ${PROJECT_SOURCE_DIR})
INCLUDE_DIRECTORIES("${CMAKE_CURRENT_SOURCE_DIR}/lib")
LINK_DIRECTORIES("${CMAKE_CURRENT_SOURCE_DIR}/lib")
# 引用动态库
# 用源文件生成可执行文件
ADD_EXECUTABLE(darren ${SRC_LIST})
# 优先连接动态库
#TARGET_LINK_LIBRARIES(darren Dir1)
# !!!强制使用静态库 完整的库文件名libDir1.a
#TARGET_LINK_LIBRARIES(darren lDir1)
TARGET_LINK_LIBRARIES(darren libDir1.a)
设置安装目录
前面的代码中已提及过,不再赘述
如:
INSTALL(DIRECTORY doc/ DESTINATION share/doc/cmake/0voice)
添加子目录注意事项
有如下目录结构的程序
注意
./CMakeLists.txt 设置 ./src为子目录,则cmake时只能调用./src/CMakeLists.txt
而./src/CMakeLists.txt 设置./src/dir1和 ./src/dir2为子目录,则可以调用其对应目录下的CMakeLists.txt
读取环境变量
SET(CMAKE_CXX_FLAGS "$ENV{CXXFLAGS} -Wall -O0 -gdwarf-3 -g -pg ")
其中 $ENV{CXXFLAGS} 表示读取环境变量 CXXFLAGS
心得
第一遍学习先看一下,第二遍过来的时候稍微敲一下练习的cmake代码体验一下。