该专栏记录了在学习一个开发项目的过程中遇到的疑惑和问题。
其教学视频见:[C++高级教程]从零开始开发服务器框架(sylar)
上一篇:C++服务器框架开发10——日志系统1~9代码
C++服务器框架开发11——编译调试1/cmake学习
- 目前进度
- ubuntu下的cmake学习
- 简单样例
- 同一目录下多个源文件
- 不同目录下多个源文件
- 正式一点的组织结构
- 动态库和静态库的编译控制
- 对库进行链接
- 添加编译选项
- 添加控制选项
- 本来要生成多个bin或库文件,现在只想生成部分指定的bin或库文件
- 对于同一个bin文件,只想编译其中部分代码(使用宏来控制)
- 回顾图1
目前进度
学习到第6个视频的00:59,由于不了解编译,这次先学习下cmake。下图是CMakeLists.txt中的内容。
ubuntu下的cmake学习
参考自文章1
“CMake是一个跨平台的编译(Build)工具, 不同平台之间的编译方式遵循不同的规则,彼此不通用。因此 Cmake被提出,他统一了一套规则, 来描述所有平台的编译过程。
它允许开发者编写一种平台无关的 CMakeLists.txt 文件来定制整个编译流程,然后再根据目标用户的平台进一步生成所需的本地化 Makefile 和工程文件,如 Unix 的 Makefile 或 Windows 的 Visual Studio 工程。”
以下内容基本上都是学习的文章1。
简单样例
在文件目录下创建这两个文件,内容如下:
main.c,输出一个字符串。
#include <stdio.h>
int main(void)
{
printf("Hello World\n");
return 0;
}
CMakeLists.txt,其中各行的意思都有进行注释说明。
# 指定所需 CMake 的最低版本
cmake_minimum_required (VERSION 2.8)
# 项目的名称 demo
project (demo)
# 将名为 main.c 的源文件编译成一个名称为 main 的可执行文件
add_executable(main main.c)
在该目录下打开一个终端,执行cmake ./
得到makefile。
执行过程:
执行后目录下的文件:
执行make
进行编译。
执行过程:
执行后目录下多了一个main的可执行文件。
运行main看看效果:
如果想重新生成,可以先执行make clean
来删除已有的main文件。
同一目录下多个源文件
上面的例子是只有一个源文件的,如果有多个的话,如下:
main.c调用调用testFunc.h里声明的函数func()。
main.c:
#include <stdio.h>
#include "testFunc.h"
int main(void)
{
func(100);
return 0;
}
testFunc.h:
/*
** testFunc.h
*/
#ifndef _TEST_FUNC_H_
#define _TEST_FUNC_H_
void func(int data);
#endif
testFunc.c:
/*
** testFunc.c
*/
#include <stdio.h>
#include "testFunc.h"
void func(int data)
{
printf("data is %d\n", data);
}
CMakeLists.txt:
# 指定所需 CMake 的最低版本
cmake_minimum_required (VERSION 2.8)
# 项目的名称 demo
project (demo)
# 将名为 main.c 和testFunc.c的源文件编译成一个名称为 main 的可执行文件
add_executable(main main.c testFunc.c)
cmake 和make:
运行:
如果有很多源文件,这种方法就不太方便。可以使用aux_source_directory(dir var)
,这个命令将目录dir下的所有源文件存储在变量var中。
例子:
main.c:
#include <stdio.h>
#include "testFunc.h"
#include "testFunc1.h"
int main(void)
{
func(100);
func1(200);
return 0;
}
testFunc1.c:
/*
** testFunc1.c
*/
#include <stdio.h>
#include "testFunc1.h"
void func1(int data)
{
printf("data is %d\n", data);
}
testFunc1.h:
/*
** testFunc1.h
*/
#ifndef _TEST_FUNC1_H_
#define _TEST_FUNC1_H_
void func1(int data);
#endif
CMakeLists.txt:
# 指定所需 CMake 的最低版本
cmake_minimum_required (VERSION 2.8)
# 项目的名称 demo
project (demo)
#把当前目录下的源文件存放到变量SRC_LIST里
aux_source_directory(. SRC_LIST)
# 将SRC_LIST里的源文件编译成一个名称为 main 的可执行文件
add_executable(main ${SRC_LIST})
cmake 和make,并执行:
如果不想把所有源文件都进行编译,可以用set来指定想要编译的源文件,对应的CMakeLists.txt可以修改为如下:
# 指定所需 CMake 的最低版本
cmake_minimum_required (VERSION 2.8)
# 项目的名称 demo
project (demo)
#把当前目录下的源文件存放到变量SRC_LIST里
set( SRC_LIST
./main.c
./testFunc1.c
./testFunc.c)
# 将SRC_LIST里的源文件编译成一个名称为 main 的可执行文件
add_executable(main ${SRC_LIST})
不同目录下多个源文件
假设文件目录如下:
修改CMakeLists.txt
# 指定所需 CMake 的最低版本
cmake_minimum_required (VERSION 2.8)
# 项目的名称 demo
project (demo)
#由于main.c中include了两个.h文件,所以需要添加头文件的搜索路径
include_directories (test_func test_func1)
#把两个目录下的源文件分别存放到变量SRC_LIST和SRC_LIST1里
aux_source_directory (test_func SRC_LIST)
aux_source_directory (test_func1 SRC_LIST1)
# 将SRC_LIST里的源文件编译成一个名称为 main 的可执行文件
add_executable(main ${SRC_LIST} ${SRC_LIST1})
其中的include_directories (test_func test_func1)
作用已经在注释里了。
正式一点的组织结构
src:源文件目录
include:头文件目录
build:生成的对象文件
bin:可执行文件。
结构调整如下:
CMakeLists.txt:
# 指定所需 CMake 的最低版本
cmake_minimum_required (VERSION 2.8)
# 项目的名称 demo
project (demo)
#添加并构建子目录src
add_subdirectory(./src)
其中的add_subdirectory(src)
指定src为源文件的存放位置,camke时回去src目录进行编译。
src/CMakeLists.txt:
#添加头文件的搜索路径
include_directories (../include)
#把源文件存放到变量SRC_LIST里
aux_source_directory (. SRC_LIST)
# 将SRC_LIST里的源文件编译成一个名称为 main 的可执行文件
add_executable(main ${SRC_LIST} )
#设置存放可执行文件的位置
set(EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/bin)
其中set(EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/bin)
把存放可执行文件的位置设置为工程根目录下的bin目录。
为了让生成的对象文件放在build里,需要再build目录下打开终端,然后执行cmake ..
,接着make
另一种方法,可以把子目录src中的cmakelists内容放到外层目录中来,只用一个camkelists。如下:
CMakeLists.txt:
# 指定所需 CMake 的最低版本
cmake_minimum_required (VERSION 2.8)
# 项目的名称 demo
project (demo)
#添加头文件的搜索路径
include_directories (./include)
#把源文件存放到变量SRC_LIST里
aux_source_directory (./src SRC_LIST)
# 将SRC_LIST里的源文件编译成一个名称为 main 的可执行文件
add_executable(main ${SRC_LIST} )
#设置存放可执行文件的位置
set(EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/bin)
build目录下cmake ../
, 然后make
,最后执行。
动态库和静态库的编译控制
目录结构如下
把生成的库放在lib里。
CMakeLists.txt:
# 指定所需 CMake 的最低版本
cmake_minimum_required (VERSION 2.8)
# 项目的名称 demo
project (demo)
#把文件testFunc.c存放到变量SRC_LIST里
set (SRC_LIST ${PROJECT_SOURCE_DIR}/testFunc/testFunc.c)
#生成库,库名字为testFunc_shared,设置为SHARED(动态库,不设置的话默认为静态),源文件为SRC_LIST
add_library (testFunc_shared SHARED ${SRC_LIST})
#生成库,库名字为testFunc_static,设置为STATIC(静态库,不设置的话默认为静态),源文件为SRC_LIST
add_library (testFunc_static STATIC ${SRC_LIST})
#设置动态库testFunc_shared文件名称为libtestFunc.so
set_target_properties (testFunc_shared PROPERTIES OUTPUT_NAME "testFunc")
#设置静态库testFunc_static文件名称为libtestFunc.a
set_target_properties (testFunc_static PROPERTIES OUTPUT_NAME "testFunc")
#设置库文件的输出路径为./lib
set (LIBRARY_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/lib)
add_library
: 生成动态库或静态库。
set_target_properties
: 设置最终生成的库的名称。
LIBRARY_OUTPUT_PATH
: 库文件的默认输出路径。
build目录下cmake ../
, 然后make
。
查看子目录lib
对库进行链接
重新构建如下工程,并把上一小节的生成的库放到./testFunc/lib下。
main.c:
#include <stdio.h>
#include "testFunc.h"
int main(void)
{
func(100);
return 0;
}
testFunc.h:
/*
** testFunc.h
*/
#ifndef _TEST_FUNC_H_
#define _TEST_FUNC_H_
void func(int data);
#endif
CMakeLists.txt:
# 指定所需 CMake 的最低版本
cmake_minimum_required (VERSION 2.8)
# 项目的名称 demo
project (demo)
#设置可执行文件的输出路径为./bin
set (EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/bin)
#把文件main.c存放到变量SRC_LIST里
set (SRC_LIST ${PROJECT_SOURCE_DIR}/src/main.c)
# 添加头文件的搜索路径
include_directories (${PROJECT_SOURCE_DIR}/testFunc/inc)
#在指定目录./testFunc/lib查找制定库testFunc
find_library(TESTFUNC_LIB testFunc HINTS ${PROJECT_SOURCE_DIR}/testFunc/lib)
#将SRC_LIST里的源文件编译成一个名称为 main 的可执行文件
add_executable (main ${SRC_LIST})
#把目标文件与库文件进行链接
target_link_libraries (main ${TESTFUNC_LIB})
find_library
: 在指定目录下查找指定库。
target_link_libraries
: 把目标文件与库文件进行链接。
build目录下cmake ../
, 然后make
,最后执行。
查看可执行文件使用了那些库readelf -d ../bin/main
添加编译选项
有时编译程序时想添加一些编译选项,如-Wall
,-std=c++11
等,就可以使用add_compile_options来进行操作。
这里可以去看原文章的简单示例。
添加控制选项
如果想要在编译代码时只编译一些指定的源码,可以使用cmake的option命令。
本来要生成多个bin或库文件,现在只想生成部分指定的bin或库文件
如下结构:
main1.c:
// main1.c
#include <stdio.h>
int main(void)
{
printf("hello, this main1\n");
return 0;
}
main2.c:
// main2.c
#include <stdio.h>
int main(void)
{
printf("hello, this main2\n");
return 0;
}
CMakeLists.txt:
# 指定所需 CMake 的最低版本
cmake_minimum_required (VERSION 2.8)
# 项目的名称 demo
project (demo)
#设置一个option,名称为MYDEBUG,提示为"enable debug compilation",第3个参数的默认值是OFF。
option(MYDEBUG "enable debug compilation" OFF)
#设置存放可执行文件的位置
set (EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/bin)
#添加并构建子目录src
add_subdirectory(./src)
./src/CMakeLists.txt:
# 指定所需 CMake 的最低版本
cmake_minimum_required (VERSION 2.8)
#将源文件main1.c编译成一个名称为 main1 的可执行文件
add_executable(main1 main1.c)
#如果MYDEBUG为NO则将源文件main2.c编译成一个名称为 main2 的可执行文件
if (MYDEBUG)
add_executable(main2 main2.c)
else()
message(STATUS "Currently is not in debug mode")
endif()
build目录下cmake ../
, 然后make
,最后查看bin目录。
对于同一个bin文件,只想编译其中部分代码(使用宏来控制)
结构如下:
main.c:
#include <stdio.h>
int main(void)
{
#ifdef WWW1
printf("hello world1\n");
#endif
#ifdef WWW2
printf("hello world2\n");
#endif
return 0;
}
CMakeLists.txt:
# 指定所需 CMake 的最低版本
cmake_minimum_required (VERSION 2.8)
# 项目的名称 demo
project (demo)
#设置存放可执行文件的位置
set (EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/bin)
#设置一个option,名称为WWW1,提示为"print one message",第3个参数的默认值是OFF。
option(WWW1 "print one message" OFF)
#设置一个option,名称为WWW2,提示为"print another message",第3个参数的默认值是OFF。
option(WWW2 "print another message" OFF)
#使用add_definitions()函数控制代码的开启和关闭
if (WWW1)
add_definitions(-DWWW1)
endif()
if (WWW2)
add_definitions(-DWWW2)
endif()
#将源文件main.c编译成一个名称为 main 的可执行文件
add_executable(main main.c)
只想编译WWW1里的内容:
build目录下cmake ../ -DWWW1=ON -DWWW2=OFF
, 然后make
,最后执行。
只想编译WWW2里的内容:
build目录下cmake ../ -DWWW1=OFF -DWWW2=ON
, 然后make
,最后执行。
编译WWW1和WWW2里的内容:
build目录下cmake ../ -DWWW1=ON -DWWW2=ON
, 然后make
,最后执行。
注:每次重新编译时,记得把build和bin中的文件删掉,否则build中的文件会影响下次编译,比如我第一次编译设置了WWW1,如果我没有删除build中文件,那么如果我第二次编译时没设置WWW1,WWW1将是上次编译时的设置。
这是我对cmake的所有学习记录,学习资料来自https://blog.csdn.net/whahu1989/article/details/82078563。这篇文章非常详细,我基本上理解清楚了cmake的基本用法。
回顾图1
set(CMAKE_VERBOSE_MAKEFILE ON)
是显示原始编译信息,用于定位链接报错。
set(CMAKE_CXX_FLAGS "$ENV{CXXFLAGS} -rdynamic -O3 -fPIC -ggdb -std=c++11 -Wall -Wno-deprecated -Werror -Wno-unused-function")
是编译选项设置,具体可以看这两篇文章:2和3。
add_dependencies(test sylar)
是因为,编译可执行文件test时需要链接依sylar,而sylar也是通过编译得到的。所以设置这个后,当检测到依赖库sylar还没编译时,会先编译依赖库,然后再编译test。
这次笔记时隔很久,一是其他事情耽搁了,二是camke原先根本不会,三是这个笔记有点长。