CMake教程及例子

转 自:  

LaineGates的专栏, http://blog.csdn.net/lainegates


CMake 简明教程(1)---第一个例子

原文网址:http://cmake.org/cmake/help/cmake_tutorial.html

教程中所有的代码都可以在这里找到:http://public.kitware.com/cgi-bin/viewcvs.cgi/CMake/Tests/Tutorial/



工作中总是免不了要使用cmake,这真是一个神器啊,简单易用,跨平台。但之前没有好好学习它,这次将官网的教程翻译,之后再将自己实际使用时遇到的问题添加上。


第一个例子是从源代码编译出可执行文件。对于简单的例子,一个两行cmake脚本的CMakeLists.txt 文件就够了。这就是整个cmake教程的起点。这个CMakeLists.txt代码如下:

[python]  view plain copy
  1. cmake_minimum_required (VERSION 2.6)  
  2. project (Tutorial)  
  3. add_executable(Tutorial tutorial.cxx)  

需要说明的是本教程中的函数全部使用小写。cmake不区分大小写。教程中的tutorial.cxx文件的代码用于计算实数的平方根,它的初始代码如下:

[cpp]  view plain copy
  1. // A simple program that computes the square root of a number  
  2. #include   
  3. #include   
  4. #include   
  5. int main (int argc, char *argv[])  
  6. {  
  7.   if (argc < 2)  
  8.     {  
  9.     fprintf(stdout,"Usage: %s number\n",argv[0]);  
  10.     return 1;  
  11.     }  
  12.   double inputValue = atof(argv[1]);  
  13.   double outputValue = sqrt(inputValue);  
  14.   fprintf(stdout,"The square root of %g is %g\n",  
  15.           inputValue, outputValue);  
  16.   return 0;  
  17. }  

添加版本号和用于配置的头文件

接下来我们会为我们代码和可执行文件添加版本号。当然你可以在代码文件中依次添加,但使用cmake文件添加要灵活得多。要添加版本号,CMakeLists.txt文件内容大致如下:

[python]  view plain copy
  1. cmake_minimum_required (VERSION 2.6)  
  2. project (Tutorial)  
  3. # The version number.  
  4. set (Tutorial_VERSION_MAJOR 1)  
  5. set (Tutorial_VERSION_MINOR 0)  
  6.    
  7. # configure a header file to pass some of the CMake settings  
  8. # to the source code  
  9. # 之后cmake会根据这一行,在将”Tutorial.Config.h.in”复制为"${PROJECT_BINARY_DIR}/TutorialConfig.h”时,配置”Tutorial.Config.h”中的一些内容  
  10. configure_file (  
  11.   "${PROJECT_SOURCE_DIR}/TutorialConfig.h.in"  
  12.   "${PROJECT_BINARY_DIR}/TutorialConfig.h"  
  13.   )  
  14.    
  15. # add the binary tree to the search path for include files  
  16. # so that we will find TutorialConfig.h  
  17. include_directories("${PROJECT_BINARY_DIR}")  
  18.    
  19. # add the executable  
  20. add_executable(Tutorial tutorial.cxx)  

因为配置文件的内容会被写入编译出的文件中,我们必须告诉cmake这些文件的路径。我们在代码目录中新建一个名为“TutorialConfig.h.in”的文件,并在其中添加如下两行:

[cpp]  view plain copy
  1. // the configured options and settings for Tutorial  
  2. #define Tutorial_VERSION_MAJOR @Tutorial_VERSION_MAJOR@  // 在使用cmake配置代码工程时被替换,使用编译器编译之前已经成为了正常的c代码  
  3. #define Tutorial_VERSION_MINOR @Tutorial_VERSION_MINOR@  

当cmake为头文件配置“@Tutorial_VERSION_MAJOR@” and “@Tutorial_VERSION_MINOR@”两个值时,会从CMakeLists.txt中找到相应的值来替换配置文件中对应行。接下来,我们修改”Tutorial.cxx”文件,让它include配置头文件,并让它使用版本号。修改后的”Tutorial.cxx”内容如下:

[cpp]  view plain copy
  1. // A simple program that computes the square root of a number  
  2. #include   
  3. #include   
  4. #include   
  5. #include "TutorialConfig.h"  
  6.    
  7. int main (int argc, char *argv[])  
  8. {  
  9.   if (argc < 2)  
  10.     {  
  11.     fprintf(stdout,"%s Version %d.%d\n",  
  12.             argv[0],  
  13.             Tutorial_VERSION_MAJOR,  
  14.             Tutorial_VERSION_MINOR);  
  15.     fprintf(stdout,"Usage: %s number\n",argv[0]);  
  16.     return 1;  
  17.     }  
  18.   double inputValue = atof(argv[1]);  
  19.   double outputValue = sqrt(inputValue);  
  20.   fprintf(stdout,"The square root of %g is %g\n",  
  21.           inputValue, outputValue);  
  22.   return 0;  
  23. }  

主要的变化是include 了 “TutorialConfig.h”头文件,并在运行时输出了版本号。



CMake 简明教程(2)---编译库文件

这一次,我们会给我们的工程添加一个库。这个库包含我们自定义的计算实数平方根的函数。之后工程编译出的可执行文件会使用这个库的平方根计算函数,而不是在Toturial.cxx文件中include一个函数。这次我们将生成这个库的代码放到一个叫MathFunctions的子目录。CMakeLists.txt需要包含如下行:

[python]  view plain copy
  1. add_library(MathFunctions mysqrt.cxx)  

名为“sqrt.cxx”的文件包含一个叫做mysqrt的函数,这个函数提供与默认sqrt函数相似的功能。为了使用新编译出的库,我们需要在CMakeLists.txt文件靠前的位置使用add_subdirectory函数告诉cmake 在使用这个库之前编译这个库。同时,我们也要使用include_directories来告诉cmake 在哪里

找MathFunctions/mysqrt.h。添加库的代码大致如下:

[python]  view plain copy
  1. include_directories ("${PROJECT_SOURCE_DIR}/MathFunctions")  
  2. add_subdirectory (MathFunctions)   
  3.    
  4. # add the executable  
  5. add_executable (Tutorial tutorial.cxx)  
  6. target_link_libraries (Tutorial MathFunctions)  

接下来,我们让使用这个库变为可选的。当然在这个教程中这一操作意义不大,但是在之后开发人员使用的时候,例如决定是否使用某个第三方库,这一功会变得很必要。要实现这一功能,第一步是在CMakeLists.txt文件中添加一个选项,代码大致如下:

[python]  view plain copy
  1. # should we use our own math functions?  
  2. option (USE_MYMATH   
  3.         "Use tutorial provided math implementation" ON)   

这些代码会cmake gui界面中显示一个默认为ON的选项,用户可以根据需要选择是否启用它。这一选项会被保存在缓存文件中,这样用户就不必须在每次启动这个cmake项目时都重新设置。接下来要做的就是根据选项决定是否编译和链接MathFunctions库,体现在代码上就是在CMakeLists.txt靠前位置添加如下代码:

[python]  view plain copy
  1. # add the MathFunctions library?  
  2. #  
  3. if (USE_MYMATH)  
  4.   include_directories ("${PROJECT_SOURCE_DIR}/MathFunctions")  
  5.   add_subdirectory (MathFunctions)  
  6.   set (EXTRA_LIBS ${EXTRA_LIBS} MathFunctions)  
  7. endif (USE_MYMATH)  
  8.    
  9. # add the executable  
  10. add_executable (Tutorial tutorial.cxx)  
  11. target_link_libraries (Tutorial  ${EXTRA_LIBS})  


变量”USE_MYMATH”就是用来确定是否编译和链接MathFunctions库。需要注意通常会有一个变量(这个例子中是EXTRA_LIBS)收集可选的之后要被编译到可执行文件的库。这是一个常用来保持较大且有很多可选库的项目清洁的方法。对应新的选项,代码也会有相应的变化:

[cpp]  view plain copy
  1. // A simple program that computes the square root of a number  
  2. #include   
  3. #include   
  4. #include   
  5. #include "TutorialConfig.h"  
  6. #ifdef USE_MYMATH   // 这一宏的使用还要和cmake文件配合详见后文  
  7. #include "MathFunctions.h"  
  8. #endif  
  9.    
  10. int main (int argc, char *argv[])  
  11. {  
  12.   if (argc < 2)  
  13.     {  
  14.     fprintf(stdout,"%s Version %d.%d\n", argv[0],  
  15.             Tutorial_VERSION_MAJOR,  
  16.             Tutorial_VERSION_MINOR);  
  17.     fprintf(stdout,"Usage: %s number\n",argv[0]);  
  18.     return 1;  
  19.     }  
  20.    
  21.   double inputValue = atof(argv[1]);  
  22.    
  23. #ifdef USE_MYMATH   // 使用定义的宏  
  24.   double outputValue = mysqrt(inputValue);  
  25. #else  
  26.   double outputValue = sqrt(inputValue);  
  27. #endif  
  28.    
  29.   fprintf(stdout,"The square root of %g is %g\n",  
  30.           inputValue, outputValue);  
  31.   return 0;  
  32. }  

在代码中,我们使用了宏USE_MYMATH。这一宏是由CMake通过TutorialConfig.h.in配置文件提供给代码文件的,要实现它需要在TutorialConfig.h.in添加如下代码:

[cpp]  view plain copy
  1. #cmakedefine USE_MYMATH  


CMake 简明教程(3)---安装及测试

原文网址:http://cmake.org/cmake/help/cmake_tutorial.html

教程中所有的代码都可以在这里找到:http://public.kitware.com/cgi-bin/viewcvs.cgi/CMake/Tests/Tutorial/


这一节中,我们会为项目添加安装和测试规则。安装规则(install rule)可直接添加,对于linux和mac用记来讲,install太常用了,因为类Unix系统的库支持方面做得确实比windows好。windows用户可能不大熟悉,其实也简单,就是把编译好的文件进行一些处理(比如mac上需要使用otool修改库文件使用的支持库的路径,默认都是绝对路径)后复制到用户指定的位置。要安装MathFunctions库,需要在MathFunctions的CMakeLists.txt中添加如下两行:

[python]  view plain copy
  1. install (TARGETS MathFunctions DESTINATION bin)  
  2. install (FILES MathFunctions.h DESTINATION include)  

而对于这个项目,需要添加如下几行来告诉cmake如何install可执行文件及配置头文件:

[python]  view plain copy
  1. # add the install targets  
  2. install (TARGETS Tutorial DESTINATION bin)  
  3. install (FILES "${PROJECT_BINARY_DIR}/TutorialConfig.h"          
  4.          DESTINATION include)  

这是install相关的所有内容了。这时你能够编译这个项目的代码,然后输入”make install”(或者在IDE编译INSTALL项目),之后相应的头文件,库,可执行文件都会被按规则install到需要的位置。cmake有一个变量CMAKE_INSTALL_PREFIX就是用来指定install目录的。



添加测试的过程也是很直接的。在顶层的CMakeLists.txt文件末尾添加一些基本的测试来确实项目是正常运行的。

[python]  view plain copy
  1. # does the application run  
  2. add_test (TutorialRuns Tutorial 25)  
  3.    
  4. # does it sqrt of 25  
  5. add_test (TutorialComp25 Tutorial 25)  
  6.    
  7. set_tests_properties (TutorialComp25   
  8.   PROPERTIES PASS_REGULAR_EXPRESSION "25 is 5")  
  9.    
  10. # does it handle negative numbers  
  11. add_test (TutorialNegative Tutorial -25)  
  12. set_tests_properties (TutorialNegative  
  13.   PROPERTIES PASS_REGULAR_EXPRESSION "-25 is 0")  
  14.    
  15. # does it handle small numbers  
  16. add_test (TutorialSmall Tutorial 0.0001)  
  17. set_tests_properties (TutorialSmall  
  18.   PROPERTIES PASS_REGULAR_EXPRESSION "0.0001 is 0.01")  
  19.    
  20. # does the usage message work?  
  21. add_test (TutorialUsage Tutorial)  
  22. set_tests_properties (TutorialUsage  
  23.   PROPERTIES   
  24.   PASS_REGULAR_EXPRESSION "Usage:.*number")  

第一个测试用例只是简单地确定项目可正常运行,没有出现异常退出的情况,且最后返回一个0。这是一个CTest的基本形式。接下来的几个测试使用了PASS_REGULAR_EXPRESSION属性来检测输出结果为指定字符串。在这段代码示例中,正常情况下会输出字符串,输入数据有问题时倒输出使用说明。如果你想要添加很多测试的话,最好写一个函数,例如:

[python]  view plain copy
  1. #define a macro to simplify adding tests, then use it  
  2. macro (do_test arg result)  
  3.   add_test (TutorialComp${arg} Tutorial ${arg})  
  4.   set_tests_properties (TutorialComp${arg}  
  5.     PROPERTIES PASS_REGULAR_EXPRESSION ${result})  
  6. endmacro (do_test)  
  7.    
  8. # do a bunch of result based tests  
  9. do_test (25 "25 is 5")  
  10. do_test (-25 "-25 is 0")  

CMake 简明教程(4)---添加系统检测

原文网址:http://cmake.org/cmake/help/cmake_tutorial.html

教程中所有的代码都可以在这里找到:http://public.kitware.com/cgi-bin/viewcvs.cgi/CMake/Tests/Tutorial/




这一节,让我们考虑这种情况,我们的工程使用了一些目标平台不支持的代码。下面的例子中我们会添加一些代码,这些代码与目标平台是否有log和exp函数有关。当然,几乎每个平台都包含这些函数,但在这个例子中,我们假设它不存在。如果某个平台中有log函数,我们会直接使用平台函数。我们首先在CMakeLists.txt开头使用CheckFunctionExits.cmake宏测试这些函数是否可用,代码如下:

[python]  view plain copy
  1. # does this system provide the log and exp functions?  
  2. include (CheckFunctionExists.cmake)  
  3. check_function_exists (log HAVE_LOG)  
  4. check_function_exists (exp HAVE_EXP)  

接下来,如果平台上存在这些bovi,我们会如下修改TutorialConfig.h.in来定义这些变量:

[cpp]  view plain copy
  1. // does the platform provide exp and log functions?  
  2. #cmakedefine HAVE_LOG  
  3. #cmakedefine HAVE_EXP  

切记在对TutorialConfig.h文件使用cofigure_file函数之前检测log和exp非常必要。configure_file函数按cmake当前配置会间接配置了文件。最后,在mysqrt 函数中,如果在当前系统上log和exp可用,我们提供一个基于log和exp可选的实现。



CMake 简明教程(5)---编译中使用间接生成的文件

原文网址:http://cmake.org/cmake/help/cmake_tutorial.html
教程中所有的代码都可以在这里找到:http://public.kitware.com/cgi-bin/viewcvs.cgi/CMake/Tests/Tutorial/




在这一节中,我们会展示如何在项目的编译过程中使用一个间接生成的文件。例如我们建立一个保存事先计算好的平方根的表做为编译的一部分,之后将这个表编译到我们的应用中。要完成这一功能,我们首先需要一个程序生成这个表。我们用在MathFunctions子目录下新建的MakeTable.cxx来完成这一工作:


[cpp]  view plain copy
  1. // A simple program that builds a sqrt table   
  2. #include   
  3. #include   
  4. #include   
  5.    
  6. int main (int argc, char *argv[])  
  7. {  
  8.   int i;  
  9.   double result;  
  10.    
  11.   // make sure we have enough arguments  
  12.   if (argc < 2)  
  13.     {  
  14.     return 1;  
  15.     }  
  16.     
  17.   // open the output file  
  18.   FILE *fout = fopen(argv[1],"w");  
  19.   if (!fout)  
  20.     {  
  21.     return 1;  
  22.     }  
  23.     
  24.   // create a source file with a table of square roots  
  25.   fprintf(fout,"double sqrtTable[] = {\n");  
  26.   for (i = 0; i < 10; ++i)  
  27.     {  
  28.     result = sqrt(static_cast<double>(i));  
  29.     fprintf(fout,"%g,\n",result);  
  30.     }  
  31.    
  32.   // close the table with a zero  
  33.   fprintf(fout,"0};\n");  
  34.   fclose(fout);  
  35.   return 0;  
  36. }  

注意,这个表是使用C++代码生成的,并且输出文件名也通过参数传了过去。接下来就是在CMakeLists.txt中添加合适的命令来编译MakeTable 可执行文件,然后在编译过程中运行这个可执行文件。要实现这一功能,需要添加一些代码,如下:

[python]  view plain copy
  1. # first we add the executable that generates the table  
  2. add_executable(MakeTable MakeTable.cxx)  
  3.    
  4. # add the command to generate the source code  
  5. add_custom_command (  
  6.   OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/Table.h   #输出文件  
  7.   COMMAND MakeTable ${CMAKE_CURRENT_BINARY_DIR}/Table.h  # MakeTable是第二行编译出的可执行文件,接下来是输出文件名  
  8.   DEPENDS MakeTable  
  9.   )  
  10.    
  11. # add the binary tree directory to the search path for   
  12. # include files  
  13. include_directories( ${CMAKE_CURRENT_BINARY_DIR} )  
  14.    
  15. # add the main library  
  16. add_library(MathFunctions mysqrt.cxx ${CMAKE_CURRENT_BINARY_DIR}/Table.h  )  

首先,编译MakeTable可执行文件的过程如往常一样。然后,我们添加一个通过运行MakeTable生成Table.h的自定义命令。之后,我们让cmake知道mysqrt.cxx依赖于生成的Table.h。这是通过将生成的Table.h添加到MathFunctions所需的代码文件实现的。我们也需要include生成的Table.h的目录,让编译器找得到Table.h。

当编译项目时,MakeTable会被首先编译出来。之后MakeTable会被运行来生成Table.h。最后,include了Table.h的mysqt.cxx文件会被编译来生成MathFunctions库。

现在,完整的顶层的CMakeLists.txt如下所示:


[python]  view plain copy
  1. cmake_minimum_required (VERSION 2.6)  
  2. project (Tutorial)  
  3.    
  4. # The version number.  
  5. set (Tutorial_VERSION_MAJOR 1)  
  6. set (Tutorial_VERSION_MINOR 0)  
  7.    
  8. # does this system provide the log and exp functions?  
  9. include (${CMAKE_ROOT}/Modules/CheckFunctionExists.cmake)  
  10.    
  11. check_function_exists (log HAVE_LOG)  
  12. check_function_exists (exp HAVE_EXP)  
  13.    
  14. # should we use our own math functions  
  15. option(USE_MYMATH   
  16.   "Use tutorial provided math implementation" ON)  
  17.    
  18. # configure a header file to pass some of the CMake settings  
  19. # to the source code  
  20. configure_file (  
  21.   "${PROJECT_SOURCE_DIR}/TutorialConfig.h.in"  
  22.   "${PROJECT_BINARY_DIR}/TutorialConfig.h"  
  23.   )  
  24.    
  25. # add the binary tree to the search path for include files  
  26. # so that we will find TutorialConfig.h  
  27. include_directories ("${PROJECT_BINARY_DIR}")  
  28.    
  29. # add the MathFunctions library?  
  30. if (USE_MYMATH)  
  31.   include_directories ("${PROJECT_SOURCE_DIR}/MathFunctions")  
  32.   add_subdirectory (MathFunctions)  
  33.   set (EXTRA_LIBS ${EXTRA_LIBS} MathFunctions)  
  34. endif (USE_MYMATH)  
  35.    
  36. # add the executable  
  37. add_executable (Tutorial tutorial.cxx)  
  38. target_link_libraries (Tutorial  ${EXTRA_LIBS})  
  39.    
  40. # add the install targets  
  41. install (TARGETS Tutorial DESTINATION bin)  
  42. install (FILES "${PROJECT_BINARY_DIR}/TutorialConfig.h"          
  43.          DESTINATION include)  
  44.    
  45. # does the application run  
  46. add_test (TutorialRuns Tutorial 25)  
  47.    
  48. # does the usage message work?  
  49. add_test (TutorialUsage Tutorial)  
  50. set_tests_properties (TutorialUsage  
  51.   PROPERTIES   
  52.   PASS_REGULAR_EXPRESSION "Usage:.*number"  
  53.   )  
  54.    
  55.    
  56. #define a macro to simplify adding tests  
  57. macro (do_test arg result)  
  58.   add_test (TutorialComp${arg} Tutorial ${arg})  
  59.   set_tests_properties (TutorialComp${arg}  
  60.     PROPERTIES PASS_REGULAR_EXPRESSION ${result}  
  61.     )  
  62. endmacro (do_test)  
  63.    
  64. # do a bunch of result based tests  
  65. do_test (4 "4 is 2")  
  66. do_test (9 "9 is 3")  
  67. do_test (5 "5 is 2.236")  
  68. do_test (7 "7 is 2.645")  
  69. do_test (25 "25 is 5")  
  70. do_test (-25 "-25 is 0")  
  71. do_test (0.0001 "0.0001 is 0.01")  

TutorialConfig.h内容如下:

[cpp]  view plain copy
  1. // the configured options and settings for Tutorial  
  2. #define Tutorial_VERSION_MAJOR @Tutorial_VERSION_MAJOR@  
  3. #define Tutorial_VERSION_MINOR @Tutorial_VERSION_MINOR@  
  4. #cmakedefine USE_MYMATH  
  5.    
  6. // does the platform provide exp and log functions?  
  7. #cmakedefine HAVE_LOG  
  8. #cmakedefine HAVE_EXP  

MathFunctions子目录的CMakeLists.txt内容如下:

[python]  view plain copy
  1. # first we add the executable that generates the table  
  2. add_executable(MakeTable MakeTable.cxx)  
  3. # add the command to generate the source code  
  4. add_custom_command (  
  5.   OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/Table.h  
  6.   DEPENDS MakeTable  
  7.   COMMAND MakeTable ${CMAKE_CURRENT_BINARY_DIR}/Table.h  
  8.   )  
  9. # add the binary tree directory to the search path   
  10. # for include files  
  11. include_directories( ${CMAKE_CURRENT_BINARY_DIR} )  
  12.    
  13. # add the main library  
  14. add_library(MathFunctions mysqrt.cxx ${CMAKE_CURRENT_BINARY_DIR}/Table.h)  
  15.    
  16. install (TARGETS MathFunctions DESTINATION bin)  
  17. install (FILES MathFunctions.h DESTINATION include)  

CMake 简明教程(6)---生成安装文件

原文网址:http://cmake.org/cmake/help/cmake_tutorial.html
教程中所有的代码都可以在这里找到:http://public.kitware.com/cgi-bin/viewcvs.cgi/CMake/Tests/Tutorial/


接下来,设想我们想发布我们的项目以便他人使用。我们想在很多平台上发布编译结果和代码。这个过程和之前的安装(install)和测试不同。这个例子中我们会编译出类似于cygwin,debian,rpm等支持安装和包管理的安装包。要完成这一功能,我们要使用CPack来生成对应平台的安装包。在代码上,我们需要在顶层CMakeLists.txt中添加几行:

[python]  view plain copy
  1. # build a CPack driven installer package  
  2. include (InstallRequiredSystemLibraries)  
  3. set (CPACK_RESOURCE_FILE_LICENSE    
  4.      "${CMAKE_CURRENT_SOURCE_DIR}/License.txt")  
  5. set (CPACK_PACKAGE_VERSION_MAJOR "${Tutorial_VERSION_MAJOR}")  
  6. set (CPACK_PACKAGE_VERSION_MINOR "${Tutorial_VERSION_MINOR}")  
  7. include (CPack)  

这就是所有代码了。我们首先include了InstallRequiredSystemLibraries。这一模块会添加任何这个项目在这个平台所需要的所有运行支持库。接下来我们设置一些CPack变量,如版本文件Liscenes.txt,版本号等。版本号就是我们之前在例子中使用的。最后,我们添加CPack模块,这个模块会使用这些变量和这个系统的其他属性来生成安装包。


接下来,就是按通常的方式编译项目,之后运行CPack。要编译一个安装包,你需要输入如下命令:

[python]  view plain copy
  1. cpack -C CPackConfig.cmake  

要生成一个代码安装包,输入如下命令:

[python]  view plain copy
  1. cpack -C CPackSourceConfig.cmake  

———————————————— 分割线—————————————————————————————————

第二篇

0. 前言
一个多月前,由于工程项目的需要,匆匆的学习了一下cmake的使用方法,现在有时间拿出来整理一下。本文假设你已经学会了cmake的使用方法,如果你还不会使用cmake,请参考相关资料之后再继续向下看。
本文中介绍的是生成可执行程序的方法和步骤,生成动态库和静态库的方法与此有所不同,随后会介绍动态库和静态库项目中cmake的编写方法。
本文参考《CMake Practice》这篇文章完成,旨在指导用户快速使用CMake,如果需要更详细的内容,请通读《CMake Practice》这篇文章。下载路径:http://sewm.pku.edu.cn/src/paradise/reference/CMake%20Practice.pdf
1. 项目目录结构
我们项目的名称为CRNode,假设我们项目的所有文件存放再~/workspace/CRNode,之后没有特殊说明的话,我们所指的目录都以此目录为相对路径。
我们的目录结构如下:

~/workspace/CRNode
  ├─ src
  │  ├─ rpc
  │  │  ├─ CRMasterCaller.h
  │  │  ├─ CRMasterCaller.cc
  │  │  ├─ CRNode.h
  │  │  ├─ CRNode.cc
  │  │  ├─ Schd_constants.h
  │  │  ├─ Schd_constants.cc
  │  │  ├─ CRMaster.h
  │  │  ├─ CRMaster.cc
  │  │  ├─ CRNode_server.skeleton.h
  │  │  ├─ CRNode_server.skeleton.cc
  │  │  ├─ Schd_types.h
  │  │  └─ Schd_types.cc
  │  ├─ task
  │  │  ├─ TaskExecutor.h
  │  │  ├─ TaskExecutor.cc
  │  │  ├─ TaskMonitor.h
  │  │  └─ TaskMonitor.cc
  │  ├─ util
  │  │  ├─ Const.h
  │  │  ├─ Const.cc
  │  │  ├─ Globals.h
  │  │  ├─ Globals.cc
  │  │  ├─ Properties.h
  │  │  ├─ Properties.cc
  │  │  ├─ utils.h
  │  │  └─ utils.cc
  │  ├─ main.cc
  │  └─ CMakeLists.txt
  ├─ doc
  │  └─ crnode.txt
  ├─ COPYRIGHT
  ├─ README
  ├─ crnode.sh
  └─ CMakeLists.txt

其中,src存放源代码文件和一个CMakeLists.txt文件,CMakeLists文件的编写我们稍候介绍;doc目录中存放项目 的帮助文档,该文档以及COPYRIGHT和README一起安装到/usr/share/doc/crnode目录中;COPYRIGHT文件存放项目 的版权信息,README存放一些说明性文字;crnode.sh存放CRNode的启动命令;CMakeLists.txt文件稍候介绍。
除此之外,项目还依赖两个外部库:Facebook开发的thrift库,其头文件存放在/usr/include/thrift目录中;log4cpp库,其头文件存放再/usr/include下。
2. CMakeLists.txt文件
本工程中使用了两个CMakeLists.txt文件,分别项目的根目录(即~/workspace/CRNode目录,下同)和src目录中 (参考以上目录结构)。我们先给出两个CMakeLists.txt的内容,在下一节中再对两个CMakeLists.txt进行详细介绍。两个 CMakeLists.txt文件的内容分别如下:
2.1 根目录中CMakeLists内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
cmake_minimum_required (VERSION 2.6)

project (CRNode)

ADD_SUBDIRECTORY(src bin)

#SET(CMAKE_INSTALL_PREFIX ${PROJECT_BINARY_DIR})
SET(CMAKE_INSTALL_PREFIX /usr/local)

INSTALL(PROGRAMS crnode.sh DESTINATION bin)

INSTALL(FILES COPYRIGHT README DESTINATION share/doc/crnode)

INSTALL(DIRECTORY doc/ DESTINATION share/doc/crnode)

2.2 src/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
INCLUDE_DIRECTORIES(/usr/include/thrift)

SET(SRC_LIST main.cc
        rpc/CRMasterCaller.cpp
        rpc/CRNode_server.skeleton.cpp
        rpc/Schd_constants.cpp
        rpc/CRMaster.cpp
        rpc/CRNode.cpp
        rpc/Schd_types.cpp
        task/TaskExecutor.cpp
        task/TaskMoniter.cpp
        util/Const.cpp
        util/Globals.cc
        util/utils.cc
        util/Properties.cpp
        )

ADD_EXECUTABLE(crnode ${SRC_LIST})

TARGET_LINK_LIBRARIES(crnode log4cpp thrift)

INSTALL(TARGETS crnode
        RUNTIME DESTINATION bin
)

3. CMake语法
A. 变量使用${}方式取值,但是在 IF 控制语句中是直接使用变量名;
B. 指令(参数 1 参数 2…),参数使用括弧括起,参数之间使用空格或分号分开;
C. 指令是大小写无关的,参数和变量是大小写相关的。但,推荐你全部使用大写指令。

4. CMakeLists.txt剖析
4.1 cmake_minimum_required命令

1
cmake_minimum_required (VERSION 2.6)

规定cmake程序的最低版本。这行命令是可选的,我们可以不写这句话,但在有些情况下,如果CMakeLists.txt文件中使用了一些高版本cmake特有的一些命令的时候,就需要加上这样一行,提醒用户升级到该版本之后再执行cmake。

4.2 project命令

3
project (CRNode)

指定项目的名称。项目最终编译生成的可执行文件并不一定是这个项目名称,而是由另一条命令确定的,稍候我们再介绍。
但是这个项目名称还是必要的,在cmake中有两个预定义变量:< projectname >_BINARY_DIR以及< projectname >_SOURCE_DIR,在我们的项目中,两个变量分别为:CRNode_BINARY_DIR和CRNode_SOURCE_DIR。内部编译 情况下两者相同,后面我们会讲到外部编译,两者所指代的内容会有所不同。要理解这两个变量的定义,我们首先需要了解什么是“外部构建(out-of- source build)”,我们将在下一小节中介绍“外部构建”。
同时cmake还预定义了PROJECT_BINARY_DIR和PROJECT_SOURCE_DIR变量。在我们的项目 中,PROJECT_BINARY_DIR等同于CRNode_BINARY_DIR,PROJECT_SOURCE_DIR等同于 CRNode_SOURCE_DIR。在实际的应用用,我强烈推荐使用PROJECT_BINARY_DIR和PROJECT_SOURCE_DIR变 量,这样即使项目名称发生变化也不会影响CMakeLists.txt文件。

4.3 外部构建
假设我们此时已经完成了两个CMakeLists.txt文件的编写,可以执行cmake命令生成Makefile文件了。此时我们由两种方法可以执行cmake、编译和安装:

1
2
cmake .
make

或者

1
2
3
4
mkdir build
cd build
cmake ..
make

两种方法最大的不同在于执行cmake和make的工作路径不同。第一种方法中,cmake生成的所有中间文件和可执行文件都会存放在项目 目录中;而第二种方法中,中间文件和可执行文件都将存放再build目录中。第二种方法的优点显而易见,它最大限度的保持了代码目录的整洁。同时由于第二 种方法的生成、编译和安装是发生在不同于项目目录的其他目录中,所以第二种方法就叫做“外部构建”。
回到之前的疑问,再外部构建的情况下,PROJECT_SOURCE_DIR指向的目录同内部构建相同,仍然为~/workspace /CRNode,而PROJECT_BINARY_DIR则有所不同,指向~/workspace/CRNode/build目录。
当然,cmake强烈推荐使用外部构建的方法。

4.4 ADD_SUBDIRECTORY命令

5
ADD_SUBDIRECTORY(src bin)

ADD_SUBDIRECTORY(source_dir [binary_dir] [EXCLUDE_FROM_ALL])这个指令用于向当前工程添加存放源文件的子目录,并可以指定中间二进制和目标二进制存放的位置。 EXCLUDE_FROM_ALL 参数的含义是将这个目录从编译过程中排除。比如,工程的 example,可能就需要工程构建完成后,再进入 example 目录单独进行构建。
在我们的项目中,我们添加了src目录到项目中,而把对应于src目录生成的中间文件和目标文件存放到bin目录下,在上一节举例中“外部构建”的情况下,中间文件和目标文件将存放在build/srcobj目录下。

4.5 SET命令

8
SET(CMAKE_INSTALL_PREFIX /usr/local)

现阶段,只需要了解SET命令可以用来显式的定义变量即可。在以上的例子中,我们显式的将CMAKE_INSTALL_PREFIX的值定 义为/usr/local,如此在外部构建情况下执行make install命令时,make会将生成的可执行文件拷贝到/usr/local/bin目录下。
当然,可执行文件的安装路径CMAKE_INSTALL_PREFIX也可以在执行cmake命令的时候指定,cmake参数如下:

cmake -DCMAKE_INSTALL_PREFIX=/usr ..

如果cmake参数和CMakeLists.txt文件中都不指定该值的话,则该值为默认的/usr/local。

4.6 INCLUDE_DIRECTORIES命令

1
INCLUDE_DIRECTORIES(/usr/include/thrift)

INCLUDE_DIRECTORIES类似gcc中的编译参数“-I”,指定编译过程中编译器搜索头文件的路径。当项目需要的头文件不在 系统默认的搜索路径时,需要指定该路径。在我们的项目中,log4cpp所需的头文件都存放在/usr/include下,不需要指定;但thrift的 头文件没有存放在系统路径下,需要指定搜索其路径。

4.7 ADD_EXECUTABLE和ADD_LIBRARY

3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
SET(SRC_LIST main.cc
        rpc/CRMasterCaller.cpp
        rpc/CRNode_server.skeleton.cpp
        rpc/Schd_constants.cpp
        rpc/CRMaster.cpp
        rpc/CRNode.cpp
        rpc/Schd_types.cpp
        task/TaskExecutor.cpp
        task/TaskMoniter.cpp
        util/Const.cpp
        util/Globals.cc
        util/utils.cc
        util/Properties.cpp
        )

ADD_EXECUTABLE(CRNode ${SRC_LIST})

ADD_EXECUTABLE定义了这个工程会生成一个文件名为 CRNode 的可执行文件,相关的源文件是 SRC_LIST 中定义的源文件列表。需要注意的是,这里的CRNode和之前的项目名称没有任何关系,可以任意定义。

4.8 EXECUTABLE_OUTPUT_PATH和LIBRARY_OUTPUT_PATH
我们可以通过 SET 指令重新定义 EXECUTABLE_OUTPUT_PATH 和 LIBRARY_OUTPUT_PATH 变量来指定最终的目标二进制的位置(指最终生成的CRNode可执行文件或者最终的共享库,而不包含编译生成的中间文件)。
命令如下:

SET(EXECUTABLE_OUTPUT_PATH ${PROJECT_BINARY_DIR}/bin)
SET(LIBRARY_OUTPUT_PATH ${PROJECT_BINARY_DIR}/lib)

需要注意的是,在哪里 ADD_EXECUTABLE 或 ADD_LIBRARY,如果需要改变目标存放路径,就在哪里加入上述的定义。

4.9 TARGET_LINK_LIBRARIES命令

20
TARGET_LINK_LIBRARIES(CRNode log4cpp thrift)

这句话指定在链接目标文件的时候需要链接的外部库,其效果类似gcc的编译参数“-l”,可以解决外部库的依赖问题。

4.10 INSTALL命令
在执行INSTALL命令的时候需要注意CMAKE_INSTALL_PREFIX参数的值。该参数在3.5中已经有所介绍。其命令形式如下:

INSTALL(TARGETS targets...
	[[ARCHIVE|LIBRARY|RUNTIME]
	[DESTINATION < dir >]
	[PERMISSIONS permissions...]
	[CONFIGURATIONS
	[Debug|Release|...]]
	[COMPONENT < component >]
	[OPTIONAL]
	] [...])

参数中的 TARGETS 后面跟的就是我们通过 ADD_EXECUTABLE 或者 ADD_LIBRARY 定义的目标文件,可能是可执行二进制、动态库、静态库。
DESTINATION 定义了安装的路径,如果路径以/开头,那么指的是绝对路径,这时候CMAKE_INSTALL_PREFIX 其实就无效了。如果你希望使用 CMAKE_INSTALL_PREFIX 来定义安装路径,就要写成相对路径,即不要以/开头,那么安装后的路径就是${CMAKE_INSTALL_PREFIX} /< destination 定义的路径>
你不需要关心 TARGETS 具体生成的路径,只需要写上 TARGETS 名称就可以了。
非目标文件的可执行程序安装(比如脚本之类):

INSTALL(PROGRAMS files... DESTINATION < dir >
	[PERMISSIONS permissions...]
	[CONFIGURATIONS [Debug|Release|...]]
	[COMPONENT < component >]
	[RENAME < name >] [OPTIONAL])

跟上面的 FILES 指令使用方法一样,唯一的不同是安装后权限为OWNER_EXECUTE, GROUP_EXECUTE, 和 WORLD_EXECUTE,即 755 权限目录的安装。
安装一个目录的命令如下:

INSTALL(DIRECTORY dirs... DESTINATION < dir >
	[FILE_PERMISSIONS permissions...]
	[DIRECTORY_PERMISSIONS permissions...]
	[USE_SOURCE_PERMISSIONS]
	[CONFIGURATIONS [Debug|Release|...]]
	[COMPONENT < component >]
	[[PATTERN < pattern > | REGEX < regex >]
	[EXCLUDE] [PERMISSIONS permissions...]] [...])

DIRECTORY 后面连接的是所在 Source 目录的相对路径,但务必注意:abc 和 abc/有很大的区别。如果目录名不以/结尾,那么这个目录将被安装为目标路径下的 abc,如果目录名以/结尾,代表将这个目录中的内容安装到目标路径,但不包括这个目录本身。我们来看一个例子:

1
2
3
4
5
INSTALL(DIRECTORY icons scripts/ DESTINATION share/myproj
	PATTERN "CVS" EXCLUDE
	PATTERN "scripts/*"
	PERMISSIONS OWNER_EXECUTE OWNER_WRITE OWNER_READ
	GROUP_EXECUTE GROUP_READ)

这条指令的执行结果是:
将 icons 目录安装到 < prefix >/share/myproj,将 scripts/中的内容安装到< prefix >/share/myproj,不包含目录名为 CVS 的目录,对于 scripts/*文件指定权限为 OWNER_EXECUTE OWNER_WRITE OWNER_READ GROUP_EXECUTE GROUP_READ。
因为crnode.txt 要安装到/< prefix >/share/doc/crnode,所以我们不能直接安装整个 doc 目录,这里采用的方式是安装 doc 目录中的内容,也就是使用”doc/”在工程文件中添加:

1
INSTALL(DIRECTORY doc/ DESTINATION share/doc/crnode)

5. 编译安装
编译安装结果如下:

[root@sim91 build]# cmake ..
-- Configuring done
-- Generating done
-- Build files have been written to: /home/fify/workspace/CRNode/build

[root@sim91 build]# make
Scanning dependencies of target crnode
[  7%] Building CXX object srcobj/CMakeFiles/crnode.dir/main.cc.o
[ 15%] Building CXX object srcobj/CMakeFiles/crnode.dir/rpc/CRMasterCaller.cpp.o
[ 23%] Building CXX object srcobj/CMakeFiles/crnode.dir/rpc/CRNode_server.skeleton.cpp.o
[ 30%] Building CXX object srcobj/CMakeFiles/crnode.dir/rpc/Schd_constants.cpp.o
[ 38%] Building CXX object srcobj/CMakeFiles/crnode.dir/rpc/CRMaster.cpp.o
[ 46%] Building CXX object srcobj/CMakeFiles/crnode.dir/rpc/CRNode.cpp.o
[ 53%] Building CXX object srcobj/CMakeFiles/crnode.dir/rpc/Schd_types.cpp.o
[ 61%] Building CXX object srcobj/CMakeFiles/crnode.dir/task/TaskExecutor.cpp.o
[ 69%] Building CXX object srcobj/CMakeFiles/crnode.dir/task/TaskMoniter.cpp.o
[ 76%] Building CXX object srcobj/CMakeFiles/crnode.dir/util/Const.cpp.o
[ 84%] Building CXX object srcobj/CMakeFiles/crnode.dir/util/Globals.cc.o
[ 92%] Building CXX object srcobj/CMakeFiles/crnode.dir/util/utils.cc.o
[100%] Building CXX object srcobj/CMakeFiles/crnode.dir/util/Properties.cpp.o
Linking CXX executable crnode

[root@sim91 build]# make install
[100%] Built target crnode
Install the project...
-- Install configuration: ""
-- Installing: /usr/local/bin/crnode.sh
-- Installing: /usr/local/share/doc/crnode/COPYRIGHT
-- Installing: /usr/local/share/doc/crnode/README
-- Installing: /usr/local/share/doc/crnode
-- Installing: /usr/local/share/doc/crnode/crnode.txt
-- Installing: /usr/local/bin/crnode

大功告成!更多内容请参考《CMake Practice》,再次对《CMake Practice》的作者表示感谢!


—————————————————转载完——————————————————————————————
个人认为第二篇对我个人帮助更大,以及《CMake Practice》这本书写的很好。。。

现在感觉在Linux下编程这方面还是很吃力,一个是本科不是学软件和计算机的,感觉差了好多的课,编译原理什么的完全都搞不懂,一部分原因也是在win平台下的VS太强大了,平时写程序也不用考虑这么多,现在发现各种程序都要在Linux的平台下运行,各种东西都要学习。。。

马上要去MSRA实习了,希望不要给老师丢人,更不能给自己丢人,还要考toefl,gre,还要申请。。。要追的女朋友也要去日本。。。我能不能去成日本还是个未知数。。。真是感觉自己有点跟不上了。。。


你可能感兴趣的:(cmake)