目录
CMake Hello World
一、单个源文件的编译
//main.c #incldue<stdio.h> int main() { printf("Hello World!/n"); return 0; }
在main.c相同目录下再编写一个CMakeLists.txt文件,内容如下:
project(HELLO) set(SRC_LIST main.c) add_executable(hello ${SRC_LIST})
同时,在相同目录下创建一个build子文件夹,用来进行外部编译
进入build文件夹,调用cmake:
$ cmake .. $ make
cmake将会按照CMakeLists.txt中的规则生成对应的MakeFile,make 之后得到可执行目标文件 hello
$ ./hello $ Hello World!
当前的目录结构:
+-- main.c +-- CMakeLists.txt +-- /build +--hello
第一行的 project 其实很重要,一般来说该语句在整个工程中只需要出现一次,该语句所在 CMakeLists.txt 的目录将会是其所有子目录中CMakeLists.txt 中变量 PROJECT_SOURCE_DIR
的值。
cmake会自动定义两个变量:PROJECT_BINARY_DIR
表示工程生成的二进制文件的目录,通常是 cmake 命令执行的目录PROJECT_SOURCE_DIR
表示工程源文件所在的目录,理解为 project 语句声明所在的目录
可以通过语句:message("This is source dir: " ${PROJECT_SOURCE_DIR})
来显示 PROJECT_SOURCE_DIR 的默认目录。set(SRC_LIST main.c)
设置 SRC_LIST
的值为 main.caddexecutable(hello ${SRC_LIST})
从 SRC_LIST
指定的文件生成可执行文件 hello
二、多文件编译
将原来的main.c拆分成三个文件
//hello.h 头文件 void hello(const char * name);
//hello.c #include<stdio.h> #include"hello.h" void hello(const char * name) { printf("Hello %s!\n",name); }
//main.c #include"hello.h" int main() { hello("World!"); return 0; }
CMakeLists.txt:
project(HELLO) set(SRC_LIST main.c hello.c) message("This is PROJECT_BINARY_DIR :" ${PROJECT_BINARY_DIR}) message("This is PROJECT_SOURCE_DIR :" ${PROJECT_SOURCE_DIR}) add_executable(hello ${SRC_LIST})
三、生成一个静态库,链接该库
通过 hello.c 生成一个静态库 libmyhello.a
修改CMakeLists.txt文件如下
project(Hello) set(LIB_SRC hello.c) set(APP_SRC main.c) add_library(myhello ${LIB_SRC}) add_executable(hello ${APP_SRC}) target_link_libraries(hello myhello)
有一点需要注意的是 add_library(myhello ${LIB_SRC})
得到的静态库存档文件的名称会自动加 lib 前缀,也就是得到的是名为libmyhello.a
的静态库,最后target_link_libraries(hello myhello)
查找静态库的时候,查找的是名为libmyhello.a
的文件!
四、将源文件、库文件、头文件放在不同的文件夹中
现在为项目根目录下的 main.c hello.c hello.h 各自建立文件夹,使之看起来更像一个工程。
这时需要为每一个文件夹都配置一个CMakeLists.txt文件。
为各个源代码创建文件夹之后的工程目录如下:
+-- CMakeLists.txt +-- /src +-- main.c +-- CMakeLists.txt +-- /lib +-- hello.c +-- hello.h +-- CMakeLists.txt +-- /build
项目根目录下的CMakeLists.txt文件修改为:
project(Hello) cmake_minimum_required(VERSION 3.5) message("SOURCE_DIR: " ${PROJECT_SOURCE_DIR}) set(SRC_DIR ${PROJECT_SOURCE_DIR}/src) set(LIB_DIR ${PROJECT_SOURCE_DIR}/lib) set(BIN_DIR ${PROJECT_SOURCE_DIR}/bin) add_subdirectory(${SRC_DIR} ${BIN_DIR}) add_subdirectory(${LIB_DIR} lib)
前面提到 PROJECT_SOURCE_DIR
的值是 project 声明所在的位置,所以这个值可以作为该工程目录中的一个全局变量。
add_subdirectory(${SRC_DIR} ${BIN_DIR})
中第一个参数表示所要添加的子目录的名字,第二个参数则表示该子目录中的文件编译之后生成的中间文件及结果保存的位置。
所以SRC_LIST
中编译的结果将会保存在工程根目录下的二级目录bin
中,add_subdirectory(${LIB_DIR} lib)
则表示LIB_DIR
中文件编译的结果将会保存在执行 cmake 命令目录下的 lib 文件夹中,也就是 build 下的三级目录 lib,而不是工程根目录的下的二级目录 lib
当不使用第二个参数时,中间文件及结果将会保存在 build 目录下的 src 及 lib 目录中。
src 中的 CMakeLists.txt
include_directories(${PROJECT_SOURCE_DIR}/lib) set(APP_SRC main.c) add_executable(hello ${APP_SRC}) target_link_libraries(hello myhello)
因为 src 文件夹会被 add 到工程根目录中,所以${PROJECT_SOURCE_DIR}
可以作为一个全局变量使用。表示头文件所在目录为工程根目录下的 lib 文件夹
lib 中的 CMakeLists.txt
set(LIB_SRC hello.c) add_library(myhello ${LIB_SRC})
单独指定目标文件及库文件的位置
Documents/ ├── bin │ ├── ... │ ├── hello │ └── Makefile ├── build │ ├── ... │ ├── lib │ │ ├── CMakeFiles │ │ │ ├── ... │ │ ├── libmyhello.a │ │ └── Makefile │ └── Makefile ├── CMakeLists.txt ├── lib │ ├── CMakeLists.txt │ ├── hello.c │ ├── hello.h └── src ├── CMakeLists.txt └── main.c
按照前面的编译规则,我们最后得到的目录树如上。Documents是我们的工程根目录,可执行目标文件 hello 保存在二级目录 bin 下,静态库存档文件 libmyhello.a 保存在 build 下的三级目录 lib 中。
为了实现将编译得到的“精华”与各种 CMake 中间文件分离,可以进一步指定“精华文件”的保存位置。
lib/CMakeLists.txt
set(LIB_SRC hello.c) add_library(myhello ${LIB_SRC}) set(LIBRARY_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/build_res/lib)
src/CMakeLists.txt:
include_directories(${PROJECT_SOURCE_DIR}/lib) set(APP_SRC main.c) set(EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/build_res/bin) add_executable(hello ${APP_SRC}) target_link_libraries(hello myhello)
通过分别指定 LIBRARY_OUTPUT_PATH
和EXECUTABLE_OUTPUT_PATH
,可以将 make 编译得到的 hello 和 libmyhello.a 与 CMake 中间文件分离,分别保存在工程根目录下的 build_res/bin 和 build_res/lib 中。而 CMake 中间文件将会和 Makefile 一起保存在 build 目录中。
五、使用动态库
install 指令
CMAKE_INSTALL_PREFIX
变量常用的使用方法如下:cmake -DCMAKE_INSTALL_PREFIX=/usr .
INSTALL(TARGETS targets... [[ARCHIVE|LIBRARY|RUNTIME] [DESTINATION <dir>] [PERMISSIONS permissions...] [CONFIGURATIONS[Debug|Release|...]] [COMPONENT <component>] [OPTIONAL]] [...])
targets
参数是通过add_executable
和add_library
设置的目标文件名,目标文件三种类型,ARCHIVE
,LIBRARY
,RUNTIME
分别对应静态库,动态库和可执行目标文件。DESTINATION
指定的路径默认为相对CMAKE_INSTALL_PREFIX
的相对路径。
install(TARGETS myrun mylib mystaticlib RUNTIME DESTINATION bin LIBRARY DESTINATION lib ARCHIVE DESTINATION libstatic )
上述指令,将可执行目标文件myrun安装到 ${CMAKE_INSTALL_PREFIX}/bin
下,将共享库mylib安装到${CMAKE_INSTALL_PREFIX}/lib
,将静态库mystaticlib安装到${CMAKE_INSTALL_PREFIX}/libstatic
下。如果DESTINATINO
指定的路径为绝对路径,则CMAKE_INSTALL_PREFIX
无效。
例如文件组织如下:
t3/ ├── build ├── CMakeLists.txt ├── lib │ ├── CMakeLists.txt │ ├── hello.c │ └── hello.h └── main.c
顶层CMakeLists.txt内容如下:
project(HELLO) cmake_minimum_required(VERSION 3.5) set(LIBRARY_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/res_lib) set(PROJ_SRC main.c) add_subdirectory(lib) add_executable(mainhello ${PROJ_SRC}) target_link_libraries(mainhello hello) install(TARGETS mainhello RUNTIME DESTINATION install_bin)
lib文件夹中的CMakeLists.txt
set(LIBHELLO_SRC hello.c) add_library(hello SHARED ${LIBHELLO_SRC}) add_library(hello_static STATIC ${LIBHELLO_SRC}) set_target_properties(hello_static PROPERTIES OUTPUT_NAME "hello") set_target_properties(hello PROPERTIES VERSION 1.2 SOVERSION 1) install(TARGETS hello hello_static LIBRARY DESTINATION install_lib ARCHIVE DESTINATION install_static)
在build中执行
cmake -DCMAKE_INSTALL_PREFIX=../ .. make sudo make install
-DCMAKE_INSTALL_PREFIX
的默认地址为/usr/local
install 指令还可用于文件、目录、脚本等等目标。
使用外部共享库和头文件
之前已经使用过include_directories
指令
include_directories([AFTER|BEFORE] [SYSTEM] dir1 dir2 ...)
set(CMAKE_INCLUDE_DIRECTORIES_BEFORE ON)
将变量CMAKE_INCLUDE_DIRECTORIES_BEFORE
设置为ON,表示手动添加include路径的优先级在已有路径之前。对应的还有after
link_directories
设置需要链接的库的路径target_link_libraries(target library1 <debug | optimized> library2 ...)
用来为target添加需要链接的库。
find_path(myHeader NAMES hello.h PATHS /usr/inlcude /usr/include/hello)
findpath 指令用于在指定文件夹中寻找指定文件,并将该文件所在文件夹赋值给对应的变量
find_library与find_path作用类似,但是find_library会将文件路径赋值给变量而不是文件所在文件夹的路径。
默认的搜索路径通过环境变量CMAKE_INCLUDE_PATH
和CMAKE_LIBRARY_PATH
指定。上述两个环境变量只表示 cmake 中 find_path 和 find_library 指令默认的搜索路径,并不表示 cmake 会自动从上述路径中搜索头文件和库文件。换句话说,如果不使用 find_指 令,那么这两个环境变量就是无效的。
假设之前的头文件 hello.h 被安装到文件夹 /usr/local/include
,编译之后得到的共享库 libhello.so libhello.so.1 libhello.so.1.2 被安装到文件夹 /usr/local/install_lib
现在为了使用之前编译得到的共享库,我们编写main.c使用该库。main.c保存在工程根目录 t3 的 src 中
#include <hello.h> int main.c { HelloFunc(); return 0; }
t3 中的 CMakeLists.txt:
project(HELLOLIB) cmake_minimum_required(VERSION 3.5) find_path(myheader hello.h) if(myheader) message("myheader: " ${myheader}) # myheader 保存的是hello.h所在文件夹的路径 include_directories(${myheader}) endif(myheader) find_library(mylibrary hello) if(mylibrary) # mylibrary 保存的是 hello 库文件本身的路径 # 所以不需要 link_directories message("mylibrary: " ${mylibrary}) endif(mylibrary) add_subdirectory(src)
src中的CMakeLists.txr
set(SRC_LIST main.c) add_executable(mainhello ${SRC_LIST}) # mylibrary 继承自 parent CMakeLists.txt 生成的对象 target_link_libraries(mainhello ${mylibrary}) install(TARGETS mainhello RUNTIME DESTINATION install_bin)
整个工程文件夹结构如下:
t3/ ├── build ├── CMakeLists.txt └── src ├── CMakeLists.txt └── main.c
进入 build 目录,设置环境变量,开始编译流程
# find_path 的默认路径 export CMAKE_INCLUDE_PATH=/usr/local/include # find_lirary 的默认路径 export CMAKE_LIBRARY_PATH=/usr/local/install_lib cmake -DCMAKE_INSTALL_PREFIX=../ .. -- The C compiler identification is GNU 5.4.0 -- The CXX compiler identification is GNU 5.4.0 -- Check for working C compiler: /usr/bin/cc -- Check for working C compiler: /usr/bin/cc -- works -- Detecting C compiler ABI info -- Detecting C compiler ABI info - done -- Detecting C compile features -- Detecting C compile features - done -- Check for working CXX compiler: /usr/bin/c++ -- Check for working CXX compiler: /usr/bin/c++ -- works -- Detecting CXX compiler ABI info -- Detecting CXX compiler ABI info - done -- Detecting CXX compile features -- Detecting CXX compile features - done myheader: /usr/local/include mylibrary: /usr/local/lib/libhello.so -- Configuring done -- Generating done -- Build files have been written to: /home/hzq0/Documents/t3/build
可以看到myheader
的值为/usr/local/include, 而mylibrary
的值为/usr/local/lib/libhello.so
cmake VERBOSE=1 /usr/bin/cmake -H/home/hzq0/Documents/t3 -B/home/hzq0/Documents/t3/build --check-build-system CMakeFiles/Makefile.cmake 0 /usr/bin/cmake -E cmake_progress_start /home/hzq0/Documents/t3/build/CMakeFiles /home/hzq0/Documents/t3/build/CMakeFiles/progress.marks make -f CMakeFiles/Makefile2 all make[1]: Entering directory '/home/hzq0/Documents/t3/build' make -f src/CMakeFiles/mainhello.dir/build.make src/CMakeFiles/mainhello.dir/depend make[2]: Entering directory '/home/hzq0/Documents/t3/build' cd /home/hzq0/Documents/t3/build && /usr/bin/cmake -E cmake_depends "Unix Makefiles" /home/hzq0/Documents/t3 /home/hzq0/Documents/t3/src /home/hzq0/Documents/t3/build /home/hzq0/Documents/t3/build/src /home/hzq0/Documents/t3/build/src/CMakeFiles/mainhello.dir/DependInfo.cmake --color= Dependee "/home/hzq0/Documents/t3/build/src/CMakeFiles/mainhello.dir/DependInfo.cmake" is newer than depender "/home/hzq0/Documents/t3/build/src/CMakeFiles/mainhello.dir/depend.internal". Dependee "/home/hzq0/Documents/t3/build/src/CMakeFiles/CMakeDirectoryInformation.cmake" is newer than depender "/home/hzq0/Documents/t3/build/src/CMakeFiles/mainhello.dir/depend.internal". Scanning dependencies of target mainhello make[2]: Leaving directory '/home/hzq0/Documents/t3/build' make -f src/CMakeFiles/mainhello.dir/build.make src/CMakeFiles/mainhello.dir/build make[2]: Entering directory '/home/hzq0/Documents/t3/build' [ 50%] Building C object src/CMakeFiles/mainhello.dir/main.c.o cd /home/hzq0/Documents/t3/build/src && /usr/bin/cc -I/usr/local/include -o CMakeFiles/mainhello.dir/main.c.o -c /home/hzq0/Documents/t3/src/main.c [100%] Linking C executable mainhello cd /home/hzq0/Documents/t3/build/src && /usr/bin/cmake -E cmake_link_script CMakeFiles/mainhello.dir/link.txt --verbose=1 /usr/bin/cc CMakeFiles/mainhello.dir/main.c.o -o mainhello /usr/local/install_lib/libhello.so -Wl,-rpath,/usr/local/install_lib: make[2]: Leaving directory '/home/hzq0/Documents/t3/build' [100%] Built target mainhello make[1]: Leaving directory '/home/hzq0/Documents/t3/build' /usr/bin/cmake -E cmake_progress_start /home/hzq0/Documents/t3/build/CMakeFiles 0 make install [100%] Built target mainhello Install the project... -- Install configuration: "" -- Installing: /home/hzq0/Documents/t3/install_bin/mainhello -- Set runtime path of "/home/hzq0/Documents/t3/install_bin/mainhello" to ""
注意
hzq0@ceph0:~/Documents/t3/build/src$ ldd mainhello linux-vdso.so.1 => (0x00007fffe7be6000) libhello.so.1 => /usr/local/install_lib/libhello.so.1 (0x00007f5289427000) libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f528905d000) /lib64/ld-linux-x86-64.so.2 (0x00007f5289629000) hzq0@ceph0:~/Documents/t3/build/src$ cd ../../install_bin/ hzq0@ceph0:~/Documents/t3/install_bin$ ldd mainhello linux-vdso.so.1 => (0x00007ffe56dfa000) libhello.so.1 => not found libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f9f91c61000) /lib64/ld-linux-x86-64.so.2 (0x00007f9f9202b000)
make 得到的可执行目标文件与make install得到的可执行文件的重定位表部分不同。前者需要链接的共享库的本机路径被写入可执行文件,而后者没有。加载器加载后者时是从系统默认的、或者环境变量LD_LIBRARY_PATH
附加的地址寻找动态链接库,而前面安装共享库是我们没有将其安装在默认路径,所以后者在运行时找不到该共享库。