一、CmakeList的编写和参数详解



cmake 实践写的不错,可以google一下。



     使用cmake 很简单,只需要执行cmake, make 两个命令即可,用我工作中的一个工程举例说明。

      假设当前的项目代码在src 目录。 src 下有子目录:server, utility, lib, bin, build

      server -----  存放项目的主功能类文件

      utility  -----  存放项目要用到相关库文件,便已成为库文件存放到子目录lib 中

      lib       -----  存放utility 生成的库

      bin      -----  存放association 生成的二进制文件

      build   -----  编译目录,存放编译生成的中间文件   

      cmake 要求工程主目录和所有存放源代码子目录下都要编写CMakeLists.txt 文件,注意大小写(cm 大写,list中l 大写且落下s).

      src/CMakeLists.txt 文件如下:


1. #cmake file for project association                     #表示注释   
2.       #author:>---double__song  
3.       #created:>--2011/03/01  
4.   
5.       CMAKE_MINIMUM_REQUIRED(VERSION 2.8)    #cmake 最低版本要求,低于2.6 构建过程会被终止。   
6.   
7.       PROJECT(server_project)                          #定义工程名称  
8.   
9. "Project: SERVER")               #打印相关消息消息   
10. "Project Directory: ${PROJECT_SOURCE_DIR}")  
11.       SET(CMAKE_BUILE_TYPE DEBUG)                      #指定编译类型  
12. "-g -Wall")          #指定编译器   
13.         
14.       ADD_SUBDIRECTORY(utility)                              #添加子目录   
15.       ADD_SUBDIRECTORY(server)


      相关解释:

      1. CMakeLists.txt 文件中不区分大小写

      2. PROJECT(project_name)    定义工程名称

          语法:project(projectname [cxx] [c] [java])

          可以指定工程采用的语言,选项分别表示:C++, C, java, 如不指定默认支持所有语言

      3. MESSAGE(STATUS, "Content")  打印相关消息

          输出消息,供调试CMakeLists.txt 文件使用。

      4. SET(CMAKE_BUILE_TYPE DEBUG)  设置编译类型debug 或者release。 debug 版会生成相关调试信息,可以使用GDB 进行 

          调试;release不会生成调试信息。当无法进行调试时查看此处是否设置为debug.

      5. SET(CMAKE_C_FLAGS_DEBUG "-g -Wall") 设置编译器的类型

          CMAKE_C_FLAGS_DEBUG            ----  C 编译器

          CMAKE_CXX_FLAGS_DEBUG        ----  C++ 编译器

       6. ADD_SUBDIRECTORY(utility) 添加要编译的子目录

           为工程主目录下的存放源代码的子目录使用该命令,各子目录出现的顺序随意。

       如上便是工程server_project 主目录src 下的CMakeLists.txt 文件,下一篇我们解释子目录utiltiy中的CMakeLists.txt 文件。

 

子目录utility 下的CMakeLists.txt 文件如下:

1.  #Cmake file for library utility.a  
2.  #Author:       double__song  
3.  #Created:     2011/3/3  
4.   
5.  SET(SOURCE_FILES                                  #设置变量,表示所有的源文件  
6.  ConfigParser.cpp  
7.  StrUtility.cpp  
8.  )  
9.  INCLUDE_DIRECTORIES(                          #相关头文件的目录  
10.  /usr/local/include  
11.  ${PROJET_SOURCE_DIR}/utility  
12.  )  
13.   
14. LINK_DIRECTORIES(                                  #相关库文件的目录  
15. /usr/local/lib  
16. )  
17.   
18. ADD_LIBRARY(association ${SOURCE_FILES})         #生成静态链接库libassociation.a  
19. TARGET_LINK_LIBRARY(association core)                #依赖的库文件  
20.   
21. SET_TARGET_PROPERTIES(utility  PROPERTIES    #表示生成的执行文件所在路径  
22. RUNTIME_OUTPUT_DIRECTORY> "${PROJECT_SOURCE_DIR}/lib")



    相关解释:

    1. SET(SOURCE_FILES .....)  

    表示要编译的源文件,所有的源文件都要罗列到此处。set 设置变量,变量名SOURCE_FILES自定义。

    2. INCLUDE_DIRECTORY(...)  

    include头文件时搜索的所有目录

    变量PROJECT_SOURCE_DIR 表示工程所在的路径,系统默认的变量

    3. LINK_DIRECTORIES(...)

     库文件存放的目录,在程序连接库文件的时候要再这些目录下寻找对应的库文件

    4. ADD_LIBRARY(...) 

     表示生成静态链接库libassociaiton.a,由${PROJECT_SOURCE_DIR}代表的文件生成。

     语法:ADD_LIBRARY(libname [SHARED|STATIC]

     SHARED 表示生成动态库, STATIC表示生成静态库。

    5. TARGET_LINK_LIBRARY(association core) 

     表示库association 依赖core库文件

     6. SET_TARGET_PROPERTIES

     设置编译的库文件存放的目录,还可用于其他属性的设置。如不指定,

     生成的执行文件在当前编译目录下的各子目录下的build目录下,好拗口!简单一点:

     如指定在: ./src/lib 下

     不指定在: ./src/build/utility/build 目录下

     生成的中间文件在./src/build/utilty/build 目录下,不受该命令额影响


     子目录server 下的CMakeLists.txt 文件:

1. --------------------------------------------------------------------------------------------  
2.     SET(SOURCE_FILES  
3.     Gassociation.cpp  
4.     ConfigurationHandler.cpp  
5.     )  
6.     INCLUDE_DIRECTORIES(  
7.     /usr/local/include  
8.      ${PROJECT_SOURCE_DIR}/utility  
9.      ${PROJECT_SOURCE_DIR}/association  
10.     )  
11.     LINK_LIBRARIES(  
12.     /usr/local/lib  
13.     ${PROJECT_SOURCE_DIR}/lib  
14.     )  
15.     ADD_EXECUTABLE(server  ${SOURCE_FILES})  
16.     TARGET_LINK_LIBRARIES(server  
17.     utility  
18.     )  
19.     SET_TARGET_PROPERTIES(server  PROPERTIES    #表示生成的执行文件所在路径  
20. "${PROJECT_SOURCE_DIR}/bin")


     相关解释:

     1. ADD_EXECUTABLE()     #指定要生成的执行文件的名称server

      其他用法同utilty/CMakeLists.txt

     2. SET_TARGET_PROPERTIES

      设置生成的执行文件存放的路径,

      注意:

      执行文件server 依赖的子目录utility 子目录生成的静态库libutility.a,在指定的时候要写成:
      TARGET_LINK_LIBRARIES(server   utility)

      而不能写成:

      TARGET_LINK_LIBRARIES(server   libutility.a)

      否则编译总会提示找不到libutility库文件。

      但使用第三方的库却要指定成具体的库名,如:libACE-6.0.0.so

      这一点很诡异,暂时还没找到原因。

      完成对应的CMakeLists.txt 文件编写后,便可以进行编译了。

      编译:

            进入 ./src/build

            执行cmake ..

            make


       cmake 的使用很简单,更高级的应用好比版本信息,打包,安装等相关基本的应用后面会一一介绍,

      复杂的语法使用要参考官方文档。



二、Android Studio NDK CMake 指定so输出路径以及生成多个so的案例与总结


前文

一直想用Android Studio的新方式Cmake来编译JNI 代码,之前也尝试过,奈何有两个难题挡住了我 
1. 只能生成一个 so库,不能一次性生成多个 so库,之前的mk是可以有子模块的。 
2. 每次生成的so所在的目录不是在 jniLibs下,虽然知道如果打包,会将它打包进去,但就是觉得看不见它,想提供给别人用,还要去某个目录找。

经过尝试,这两个问题都可以解决了。

生成单个so的案例

demo下载地址: 

直接看CMakeLists.txt文件:

#指定需要CMAKE的最小版本
cmake_minimum_required(VERSION 3.4.1)


#C 的编译选项是 CMAKE_C_FLAGS
# 指定编译参数,可选
SET(CMAKE_CXX_FLAGS "-Wno-error=format-security -Wno-error=pointer-sign")

#设置生成的so动态库最后输出的路径
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${PROJECT_SOURCE_DIR}/../jniLibs/${ANDROID_ABI})

#设置头文件搜索路径(和此txt同个路径的头文件无需设置),可选
#INCLUDE_DIRECTORIES(${CMAKE_CURRENT_SOURCE_DIR}/common)

#指定用到的系统库或者NDK库或者第三方库的搜索路径,可选。
#LINK_DIRECTORIES(/usr/local/lib)


add_library( native-lib
             SHARED
             src/main/cpp/native-lib.cpp )

target_link_libraries( native-lib
                       log )

其中 各个设置都有说明。主要看这个:

set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${PROJECT_SOURCE_DIR}/../jniLibs/${ANDROID_ABI})
  • 1

它将会把生成的so库按照你在 build.gradle 指定的 abi分别放置在 jniLibs下 

cmakelist设置架构 cmakelist编写_库文件

非常好,先解决了第二个问题了。


生成多个so案例

还是上面那个demo,重新建一个module。

cpp的目录结构:


cmakelist设置架构 cmakelist编写_ci_02


直接看CMakeLists.txt文件:

#指定需要CMAKE的最小版本
cmake_minimum_required(VERSION 3.4.1)


#C 的编译选项是 CMAKE_C_FLAGS
# 指定编译参数,可选
SET(CMAKE_CXX_FLAGS "-Wno-error=format-security -Wno-error=pointer-sign")

#设置生成的so动态库最后输出的路径
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${PROJECT_SOURCE_DIR}/../jniLibs/${ANDROID_ABI})

#设置头文件搜索路径(和此txt同个路径的头文件无需设置),可选
#INCLUDE_DIRECTORIES(${CMAKE_CURRENT_SOURCE_DIR}/common)

#指定用到的系统库或者NDK库或者第三方库的搜索路径,可选。
#LINK_DIRECTORIES(/usr/local/lib)


#添加子目录,将会调用子目录中的CMakeLists.txt
ADD_SUBDIRECTORY(one)
ADD_SUBDIRECTORY(two)

不同的地方是改为添加子目录:

#添加子目录,将会调用子目录中的CMakeLists.txt
ADD_SUBDIRECTORY(one)
ADD_SUBDIRECTORY(two)

这样就会先去跑到子目录下的 one 和 two 的CmakeLists.txt,执行成功再返回。 
此时子目录one下的CmakeLists.txt:

#继承上一层的CMakeLists.txt的变量,也可以在这里重新赋值
#C 的编译选项是 CMAKE_C_FLAGS
# 指定编译参数,可选
#SET(CMAKE_CXX_FLAGS "-Wno-error=format-security -Wno-error=pointer-sign")

#生成so动态库
ADD_LIBRARY(one-lib SHARED one.cpp)

target_link_libraries(one-lib log)

子目录two下的CmakeLists.txt:

#继承上一层的CMakeLists.txt的变量,也可以在这里重新赋值
#C 的编译选项是 CMAKE_C_FLAGS
# 指定编译参数,可选
#SET(CMAKE_CXX_FLAGS "-Wno-error=format-security -Wno-error=pointer-sign")

#生成so动态库
ADD_LIBRARY(two-lib SHARED two.cpp)

target_link_libraries(two-lib log)

最后生成了以下两个so文件,并自动按照abi分别放置在了 jniLibs下:


cmakelist设置架构 cmakelist编写_cmakelist设置架构_03

第一个问题也成功了。

总结

最后,除了 设定abiFilters 必须在 build.gradle 
主要是发现CmakeLists.txt里 其实可以指定很多东西: 
1. so输出路径 CMAKE_LIBRARY_OUTPUT_DIRECTORY 
2. .a 静态库输出路径 CMAKE_ARCHIVE_OUTPUT_DIRECTORY 
2. 获取当前编译的abi , ANDROID_ABI 
3. 编译选项: 
CMAKE_C_FLAGS 
CMAKE_CXX_FLAGS 
CMAKE_CXX_FLAGS_DEBUG/CMAKE_CXX_FLAGS_RELEASE 
4. 子目录编译: ADD_SUBDIRECTORY 
5. #设置.c文件集合的变量

#当前cmakelists.txt所在目录的所有.c .cpp源文件
AUX_SOURCE_DIRECTORY(. SRC_LIST)

#增加其他目录的源文件到集合变量中
list(APPEND SRC_LIST
        ../common/1.c
        ../common/2.c
        ../common/3.c
        ../common/4.c
        ../common/5.c
        ../common/WriteLog.c
    )

#生成so库,直接使用变量代表那些.c文件集合
add_library(mylib SHARED ${SRC_LIST})

6._执行自定义命令:

# copy头文件到 静态库相同文件夹下
add_custom_command(TARGET myjni
  PRE_BUILD
  COMMAND echo "executing a copy command"
  COMMAND cp ${CMAKE_CURRENT_SOURCE_DIR}/myjni.h ${PROJECT_SOURCE_DIR}/../../../build/outputs/staticLib/myjni/${ANDROID_ABI}
  COMMENT "PRE_BUILD, so This command will be executed before building target myjni"
 )

最后,因为很多时候,JNI的参数还要转为C的方式,当我们在JAVA层写了native方法,android IDE自动提示红色,这时按下 ALT + ENTER 可以自动生成JNI下的方法声明,并且入参也帮我们转换好了。不过有时候这个插件不生效。

所以我写了一个JNI 的入参转为 C/C++的代码插件: JNI-Convert-Var,直接在 plugin 的仓库搜就有了。 
最近尝试实现android studio的ALT + ENTER 可以自动生成JNI下的方法声明,结果发现好多IntelliJ IDEA的接口不熟悉。 只能先放弃了,以下是我的逻辑:

当鼠标点击在 Native声明方法上时: 
1. 检查文件类型,如果为java就继续 
2. 获取当前行的上下共三行字符串数据,使用正则表达式获取native声明的完整方法。 
3. 检查当前模块目录下的jni或者cpp目录下的.c或者.cpp文件。 
4. 如果没有文件,弹窗让用户创建一个C/C++文件,并追加转换后(如何转换会有一个专门的类)的Java2C方法在文件末尾. 在IDE打开此文件。 
5. 如果JNI或者cpp目录有一个以上的C/C++文件, 弹窗让用户选择一个C/C++文件或者创建,之后打开文件追加转换后(如何转换会有一个专门的类)的Java2C方法在文件末尾. 。 在IDE打开此文件。

上面逻辑中:

  1. 文件类型 ,IntelliJ IDEA 的plugin开发API中可以获取到
  2. 获取当前行的上下共三行字符串数据 ,IntelliJ IDEA 的plugin开发API中可以获取到
  3. 模块目录的API暂时没找到
  4. 在IDE打开C/C++文件,不知道用什么接口

三、CMake之CMakeLists.txt编写入门



自定义变量

主要有隐式定义和显式定义两种。 

隐式定义的一个例子是PROJECT指令,它会隐式的定义< projectname >_BINARY_DIR< projectname >_SOURCE_DIR两个变量;显式定义使用SET指令构建自定义变量,比如:SET(HELLO_SRCmain.c)就可以通过${HELLO_SRC}来引用这个自定义变量了。

变量引用方式

${}进行变量的引用;在IF等语句中,是直接使用变量名而不通过${}取值。

常用变量

CMAKE_BINARY_DIR 

PROJECT_BINARY_DIR 

< projectname >_BINARY_DIR 

这三个变量指代的内容是一致的,如果是in-source编译,指得就是工程顶层目录;如果是out-of-source编译,指的是工程编译发生的目录。PROJECT_BINARY_DIR跟其它指令稍有区别,目前可以认为它们是一致的。CMAKE_SOURCE_DIR 

PROJECT_SOURCE_DIR 

< projectname >_SOURCE_DIR 

这三个变量指代的内容是一致的,不论采用何种编译方式,都是工程顶层目录。也就是在in-source编译时,他跟CMAKE_BINARY_DIR等变量一致。PROJECT_SOURCE_DIR跟其它指令稍有区别,目前可以认为它们是一致的。 

(out-of-source build与in-source build相对,指是否在CMakeLists.txt所在目录进行编译。)

CMAKE_CURRENT_SOURCE_DIR 
当前处理的CMakeLists.txt所在的路径,比如上面我们提到的src子目录。

CMAKE_CURRRENT_BINARY_DIR 

如果是in-source编译,它跟CMAKE_CURRENT_SOURCE_DIR一致;如果是out-of-source编译,指的是target编译目录。使用ADD_SUBDIRECTORY(src bin)可以更改这个变量的值。使用SET(EXECUTABLE_OUTPUT_PATH <新路径>)并不会对这个变量造成影响,它仅仅修改了最终目标文件存放的路径。

CMAKE_CURRENT_LIST_FILE 
输出调用这个变量的CMakeLists.txt的完整路径

CMAKE_CURRENT_LIST_LINE 
输出这个变量所在的行

CMAKE_MODULE_PATH 

这个变量用来定义自己的cmake模块所在的路径。如果工程比较复杂,有可能会自己编写一些cmake模块,这些cmake模块是随工程发布的,为了让cmake在处理CMakeLists.txt时找到这些模块,你需要通过SET指令将cmake模块路径设置一下。比如SET(CMAKE_MODULE_PATH,${PROJECT_SOURCE_DIR}/cmake) 

这时候就可以通过INCLUDE指令来调用自己的模块了。

EXECUTABLE_OUTPUT_PATH 
新定义最终结果的存放目录

LIBRARY_OUTPUT_PATH 
新定义最终结果的存放目录

PROJECT_NAME 

返回通过PROJECT指令定义的项目名称。

cmake调用环境变量的方式

$ENV{NAME}指令就可以调用系统的环境变量了。比如MESSAGE(STATUS "HOME dir: $ENV{HOME}")设置环境变量的方式是SET(ENV{变量名} 值)。

  1. CMAKE_INCLUDE_CURRENT_DIR 
    自动添加

CMAKE_CURRENT_BINARY_DIR和CMAKE_CURRENT_SOURCE_DIR

  1. 到当前处理的CMakeLists.txt,相当于在每个CMakeLists.txt加入:

INCLUDE_DIRECTORIES(${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR})

  1. CMAKE_INCLUDE_DIRECTORIES_PROJECT_BEFORE 
    将工程提供的头文件目录始终置于系统头文件目录的前面,当定义的头文件确实跟系统发生冲突时可以提供一些帮助。
  2. CMAKE_INCLUDE_PATH和CMAKE_LIBRARY_PATH

系统信息

CMAKE_MAJOR_VERSION

  • ,CMAKE主版本号,比如2.4.6中的2

CMAKE_MINOR_VERSION

  • ,CMAKE次版本号,比如2.4.6中的4

CMAKE_PATCH_VERSION

  • ,CMAKE补丁等级,比如2.4.6中的6

CMAKE_SYSTEM

  • ,系统名称,比如Linux-2.6.22

CMAKE_SYSTEM_NAME

  • ,不包含版本的系统名,比如Linux

CMAKE_SYSTEM_VERSION

  • ,系统版本,比如2.6.22

CMAKE_SYSTEM_PROCESSOR

  • ,处理器名称,比如i686

UNIX

  • ,在所有的类Unix平台为

TRUE

  • ,包括OSX和cygwin

WIN32

  • ,在所有的Win32平台为

TRUE

  • ,包括cygwin

主要的开关选项

  1. CMAKE_ALLOW_LOOSE_LOOP_CONSTRUCTS 
    用来控制

IF ELSE

  1. 语句的书写方式。
  2. BUILD_SHARED_LIBS 
    这个开关用来控制默认的库编译方式。如果不进行设置,使用

ADD_LIBRARY

  1. 并没有指定库类型的情况下,默认编译生成的库都是静态库;如果

SET(BUILD_SHARED_LIBSON)

  1. 后,默认生成的为动态库。
  2. CMAKE_C_FLAGS 
    设置C编译选项,也可以通过指令

ADD_DEFINITIONS()

  1. 添加。
  2. MAKE_CXX_FLAGS 
    设置C++编译选项,也可以通过指令

ADD_DEFINITIONS()

  1. 添加。

cMake常用指令

这里引入更多的cmake指令,为了编写的方便,将按照cmakeman page 的顺序介绍各种指令,不再推荐使用的指令将不再介绍。

基本指令

PROJECT(HELLO) 

指定项目名称,生成的VC项目的名称,使用${HELLO_SOURCE_DIR}表示项目根目录。INCLUDE_DIRECTORIES 

指定头文件的搜索路径,相当于指定gcc的-I参数 

INCLUDE_DIRECTORIES(${HELLO_SOURCE_DIR}/Hello) #增加Hello为include目录TARGET_LINK_LIBRARIES 

添加链接库,相同于指定-l参数 

TARGET_LINK_LIBRARIES(demoHello) #将可执行文件与Hello连接成最终文件demoLINK_DIRECTORIES 

动态链接库或静态链接库的搜索路径,相当于gcc的-L参数 

LINK_DIRECTORIES(${HELLO_BINARY_DIR}/Hello)#增加Hello为link目录ADD_DEFINITIONS 

向C/C++编译器添加-D定义,比如: 

ADD_DEFINITIONS(-DENABLE_DEBUG-DABC) 

参数之间用空格分割。如果代码中定义了:

#ifdef ENABLE_DEBUG

#endif
  • 1
  • 2
  • 3

CMAKE_C_FLAGS变量和CMAKE_CXX_FLAGS变量设置。ADD_DEPENDENCIES* 

定义target依赖的其它target,确保在编译本target之前,其它的target已经被构建。ADD_DEPENDENCIES(target-name depend-target1 depend-target2 ...)ADD_EXECUTABLE 

ADD_EXECUTABLE(helloDemo demo.cxx demo_b.cxx) 

指定编译,好像也可以添加.o文件,将cxx编译成可执行文件ADD_LIBRARY 

ADD_LIBRARY(Hellohello.cxx) #将hello.cxx编译成静态库如libHello.aADD_SUBDIRECTORY 

ADD_SUBDIRECTORY(Hello) #包含子目录ADD_TEST 

ENABLE_TESTING 

ENABLE_TESTING指令用来控制Makefile是否构建test目标,涉及工程所有目录。语法很简单,没有任何参数,ENABLE_TESTING()一般放在工程的主CMakeLists.txt中。 

ADD_TEST指令的语法是:ADD_TEST(testnameExename arg1 arg2 …) 

testname是自定义的test名称,Exename可以是构建的目标文件也可以是外部脚本等等,后面连接传递给可执行文件的参数。ENABLE_TESTING()指令,任何ADD_TEST都是无效的。比如前面的Helloworld例子,可以在工程主CMakeLists.txt中添加

ADD_TEST(mytest ${PROJECT_BINARY_DIR}/bin/main)
ENABLE_TESTING
  • 1
  • 2

make test来执行测试了。AUX_SOURCE_DIRECTORY 

基本语法是:AUX_SOURCE_DIRECTORY(dir VARIABLE),作用是发现一个目录下所有的源代码文件并将列表存储在一个变量中,这个指令临时被用来自动构建源文件列表,因为目前cmake还不能自动发现新添加的源文件。比如:

AUX_SOURCE_DIRECTORY(. SRC_LIST)
ADD_EXECUTABLE(main ${SRC_LIST})
  • 1
  • 2

FOR EACH指令来处理这个LIST。CMAKE_MINIMUM_REQUIRED 

语法为CMAKE_MINIMUM_REQUIRED(VERSION versionNumber [FATAL_ERROR]), 

比如:CMAKE_MINIMUM_REQUIRED(VERSION 2.5 FATAL_ERROR) 

如果cmake版本小与2.5,则出现严重错误,整个过程中止。

EXEC_PROGRAM 
在CMakeLists.txt处理过程中执行命令,并不会在生成的Makefile中执行。具体语法为:

EXEC_PROGRAM(Executable [directory in which to run] [ARGS <arguments to executable>] [OUTPUT_VARIABLE <var>] [RETURN_VALUE <var>])
  • 1

OUTPUT_VARIABLERETURN_VALUE分别定义两个变量。 

这个指令可以帮助在CMakeLists.txt处理过程中支持任何命令,比如根据系统情况去修改代码文件等等。举个简单的例子,我们要在src目录执行ls命令,并把结果和返回值存下来,可以直接在src/CMakeLists.txt中添加:

EXEC_PROGRAM(ls ARGS "*.c" OUTPUT_VARIABLE LS_OUTPUT RETURN_VALUE LS_RVALUE)
IF(not LS_RVALUE)
    MESSAGE(STATUS "ls result: " ${LS_OUTPUT})
ENDIF(not LS_RVALUE)

ls *.c的结果。关于IF语句,后面的控制指令会提到。

FILE指令 
文件操作指令,基本语法为:

FILE(WRITEfilename "message to write"... )
FILE(APPENDfilename "message to write"... )
FILE(READfilename variable)
FILE(GLOBvariable [RELATIVE path] [globbing expression_r_rs]...)
FILE(GLOB_RECURSEvariable [RELATIVE path] [globbing expression_r_rs]...)
FILE(REMOVE[directory]...)
FILE(REMOVE_RECURSE[directory]...)
FILE(MAKE_DIRECTORY[directory]...)
FILE(RELATIVE_PATHvariable directory file)
FILE(TO_CMAKE_PATHpath result)
FILE(TO_NATIVE_PATHpath result)

INCLUDE指令 
用来载入CMakeLists.txt文件,也用于载入预定义的cmake模块。

INCLUDE(file1[OPTIONAL])
INCLUDE(module[OPTIONAL])

OPTIONAL参数的作用是文件不存在也不会产生错误,可以指定载入一个文件,如果定义的是一个模块,那么将在CMAKE_MODULE_PATH中搜索这个模块并载入,载入的内容将在处理到INCLUDE语句是直接执行。

INSTALL指令

FIND_指令 
FIND_系列指令主要包含一下指令:

FIND_FILE(<VAR>name1 path1 path2 …)    VAR变量代表找到的文件全路径,包含文件名
FIND_LIBRARY(<VAR>name1 path1 path2 …)    VAR变量表示找到的库全路径,包含库文件名
FIND_PATH(<VAR>name1 path1 path2 …)   VAR变量代表包含这个文件的路径
FIND_PROGRAM(<VAR>name1 path1 path2 …)   VAR变量代表包含这个程序的全路径
FIND_PACKAGE(<name>[major.minor] [QUIET] [NO_MODULE] [[REQUIRED|COMPONENTS][componets...]])   用来调用预定义在CMAKE_MODULE_PATH下的Find<name>.cmake模块,也可以自己定义Find<name>模块,通过SET(CMAKE_MODULE_PATH dir)将其放入工程的某个目录中供工程使用,后面会详细介绍FIND_PACKAGE的使用方法和Find模块的编写。

FIND_LIBRARY示例:

FIND_LIBRARY(libXX11 /usr/lib)
IF(NOT libX)
    MESSAGE(FATAL_ERROR "libX not found")
ENDIF(NOT libX)

控制指令

IF指令,基本语法为:
IF(expression_r_r)
    #THEN section.
    COMMAND1(ARGS…)
    COMMAND2(ARGS…)
    …
ELSE(expression_r_r)
    #ELSE section.
    COMMAND1(ARGS…)
    COMMAND2(ARGS…)
    …
ENDIF(expression_r_r)

ELSEIF,总体把握一个原则,凡是出现IF的地方一定要有对应的ENDIF,出现ELSEIF的地方,ENDIF是可选的。表达式的使用方法如下:

IF(var)  如果变量不是:空, 0, N, NO, OFF, FALSE, NOTFOUND 或 <var>_NOTFOUND时,表达式为真。
IF(NOT var), 与上述条件相反。
IF(var1AND var2), 当两个变量都为真是为真。
IF(var1OR var2), 当两个变量其中一个为真时为真。
IF(COMMANDcmd), 当给定的cmd确实是命令并可以调用是为真。
IF(EXISTS dir) or IF(EXISTS file), 当目录名或者文件名存在时为真。
IF(file1IS_NEWER_THAN file2), 当file1比file2新,或者file1/file2其中有一个不存在时为真文件名请使用完整路径。
IF(IS_DIRECTORY dirname),  当dirname是目录时为真。
IF(variableMATCHES regex)

IF(string MATCHES regex) 当给定的变量或者字符串能够匹配正则表达式regex时为真。比如:

IF("hello" MATCHES "hello")
    MESSAGE("true")
ENDIF("hello" MATCHES "hello")
IF(variable LESS number)
IF(string LESS number)
IF(variable GREATER number)
IF(string GREATER number)
IF(variable EQUAL number)
IF(string EQUAL number)

数字比较表达式

IF(variable STRLESS string)
IF(string STRLESS string)
IF(variable STRGREATER string)
IF(string STRGREATER string)
IF(variable STREQUAL string)
IF(string STREQUAL string)

按照字母序的排列进行比较。

IF(DEFINED variable),如果变量被定义,为真。 

一个小例子,用来判断平台差异:

IF(WIN32)
    MESSAGE(STATUS“This is windows.”) #作一些Windows相关的操作
ELSE(WIN32)
    MESSAGE(STATUS“This is not windows”) #作一些非Windows相关的操作
ENDIF(WIN32)

ELSE(WIN32)之类的语句很容易引起歧义。 

这就用到了我们在 常用变量 一节提到的CMAKE_ALLOW_LOOSE_LOOP_CONSTRUCTS开关。可以SET(CMAKE_ALLOW_LOOSE_LOOP_CONSTRUCTSON),这时候就可以写成:

IF(WIN32)

ELSE()

ENDIF()

如果配合ELSEIF使用,可能的写法是这样:

IF(WIN32)
    #dosomething related to WIN32
ELSEIF(UNIX)
    #dosomething related to UNIX
ELSEIF(APPLE)
    #dosomething related to APPLE
ENDIF(WIN32)
WHILE指令

WHILE指令的语法是:

WHILE(condition)
    COMMAND1(ARGS…)
    COMMAND2(ARGS…)
    …
ENDWHILE(condition)

其真假判断条件可以参考IF指令。

FOREACH指令

FOREACH指令的使用方法有三种形式: 
(1)列表

FOREACH(loop_vararg1 arg2 …)
    COMMAND1(ARGS…)
    COMMAND2(ARGS…)
    …
ENDFOREACH(loop_var)

AUX_SOURCE_DIRECTORY的例子

AUX_SOURCE_DIRECTORY(.SRC_LIST)
FOREACH(F ${SRC_LIST})
    MESSAGE(${F})
ENDFOREACH(F)

(2)范围

FOREACH(loop_var RANGE total)

ENDFOREACH(loop_var)

从0到total以1为步进,举例如下:

FOREACH(VARRANGE 10)
    MESSAGE(${VAR})
ENDFOREACH(VAR)

最终得到的输出是:

0
1
2
3
4
5
6
7
8
9
10

(3)范围和步进

FOREACH(loop_var RANGE start stop [step])

ENDFOREACH(loop_var)

从start开始到stop结束,以step为步进。举例如下:

FOREACH(A RANGE 5 15 3)
    MESSAGE(${A})
ENDFOREACH(A)

最终得到的结果是:

5
8
11
14

ENDFOREACH指令,整个语句块才会得到真正的执行。

复杂的例子:模块的使用和自定义模块

INCLUDE指令显式的调用,FIND_PACKAGE指令是一个特例,可以直接调用预定义的模块。

其实纯粹依靠cmake本身提供的基本指令来管理工程是一件非常复杂的事情,所以cmake设计成了可扩展的架构,可以通过编写一些通用的模块来扩展cmake。

FindCURL模块的使用,然后基于前面的libhello共享库,编写一个FindHello.cmake模块。 

(一)使用FindCURL模块 

在/backup/cmake目录建立t5目录,用于存放CURL的例子。 

建立src目录,并建立src/main.c,内容如下:

#include<curl/curl.h>
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>

FILE*fp;

intwrite_data(void *ptr, size_t size, size_t nmemb, void *stream)

{
    int written = fwrite(ptr, size, nmemb, (FILE *)fp);
    return written;
}

int main()
{
    const char *path = “/tmp/curl-test”;
    const char *mode = “w”;
    fp = fopen(path,mode);
    curl_global_init(CURL_GLOBAL_ALL);
    CURL coderes;
    CURL *curl = curl_easy_init();

    curl_easy_setopt(curl, CURLOPT_URL, “http://www.linux-ren.org”);
    curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_data);
    curl_easy_setopt(curl, CURLOPT_VERBOSE, 1);
    res = curl_easy_perform(curl);
    curl_easy_cleanup(curl);
}

www.linux-ren.org的首页并写入/tmp/curl-test文件中CmakeLists.txt,如下:

PROJECT(CURLTEST)
ADD_SUBDIRECTORY(src)

建立src/CmakeLists.txt

ADD_EXECUTABLE(curltest main.c)

现在自然是没办法编译的,我们需要添加curl的头文件路径和库文件。

方法1: 

直接通过INCLUDE_DIRECTORIESTARGET_LINK_LIBRARIES指令添加:src/CMakeLists.txt中添加:

INCLUDE_DIRECTORIES(/usr/include)
TARGET_LINK_LIBRARIES(curltestcurl)

然后建立build目录进行外部构建即可。 
现在要探讨的是使用cmake提供的FindCURL模块。

方法2: 

使用FindCURL模块。向src/CMakeLists.txt中添加:

FIND_PACKAGE(CURL)
IF(CURL_FOUND)
    INCLUDE_DIRECTORIES(${CURL_INCLUDE_DIR})
    TARGET_LINK_LIBRARIES(curltest${CURL_LIBRARY})
ELSE(CURL_FOUND)
    MESSAGE(FATAL_ERROR”CURL library not found”)
ENDIF(CURL_FOUND)

Find<name>.cmake模块,使用方法一般如上例所示,每一个模块都会定义以下几个变量:

<name>_FOUND
<name>_INCLUDE_DIR or <name>_INCLUDES
<name>_LIBRARY or <name>_LIBRARIES

<name>_FOUND来判断模块是否被找到,如果没有找到,按照工程的需要关闭某些特性、给出提醒或者中止编译,上面的例子就是报出致命错误并终止构建。 

如果<name>_FOUND为真,则将<name>_INCLUDE_DIR加入INCLUDE_DIRECTORIES,将<name>_LIBRARY加入TARGET_LINK_LIBRARIES中。<name>_FOUND来控制工程特性:

SET(mySourcesviewer.c)
SET(optionalSources)
SET(optionalLibs)

FIND_PACKAGE(JPEG)

IF(JPEG_FOUND)
    SET(optionalSources${optionalSources} jpegview.c)
    INCLUDE_DIRECTORIES(${JPEG_INCLUDE_DIR} )
    SET(optionalLibs${optionalLibs} ${JPEG_LIBRARIES} )
    ADD_DEFINITIONS(-DENABLE_JPEG_SUPPORT)
ENDIF(JPEG_FOUND)

IF(PNG_FOUND)
    SET(optionalSources${optionalSources} pngview.c)
    INCLUDE_DIRECTORIES(${PNG_INCLUDE_DIR} )
    SET(optionalLibs${optionalLibs} ${PNG_LIBRARIES} )
    ADD_DEFINITIONS(-DENABLE_PNG_SUPPORT)
ENDIF(PNG_FOUND)

ADD_EXECUTABLE(viewer${mySources} ${optionalSources}
TARGET_LINK_LIBRARIES(viewer${optionalLibs}

通过判断系统是否提供了JPEG库来决定程序是否支持JPEG功能。

(二)编写属于自己的FindHello模块 

接下来在t6示例中演示如何自定义FindHELLO模块并使用这个模块构建工程。在/backup/cmake/中建立t6目录,并在其中建立cmake目录用于存放我们自己定义的FindHELLO.cmake模块,同时建立src目录,用于存放我们的源文件。cmake/FindHELLO.cmake模块

FIND_PATH(HELLO_INCLUDE_DIR hello.h /usr/include/hello /usr/local/include/hello)

FIND_LIBRARY(HELLO_LIBRARY NAMES hello PATH /usr/lib /usr/local/lib)

IF(HELLO_INCLUDE_DIR AND HELLO_LIBRARY)
    SET(HELLO_FOUNDTRUE)
ENDIF(HELLO_INCLUDE_DIR AND HELLO_LIBRARY)

IF(HELLO_FOUND)
    IF(NOT HELLO_FIND_QUIETLY)
        MESSAGE(STATUS"Found Hello: ${HELLO_LIBRARY}")
    ENDIF(NOT HELLO_FIND_QUIETLY)
ELSE(HELLO_FOUND)
    IF(HELLO_FIND_REQUIRED)
        MESSAGE(FATAL_ERROR"Could not find hello library")
    ENDIF(HELLO_FIND_REQUIRED)
ENDIF(HELLO_FOUND)

FIND_PACKAGE指令:

FIND_PACKAGE(<name>[major.minor] [QUIET] [NO_MODULE] [[REQUIRED|COMPONENTS][componets...]])

FIND_PACKAGE指令,其实它可以使用多种参数:QUIET参数,对应与我们编写的FindHELLO中的HELLO_FIND_QUIETLY,如果不指定这个参数,就会执行:

MESSAGE(STATUS"Found Hello: ${HELLO_LIBRARY}")

REQUIRED参数,其含义是指这个共享库是否是工程必须的,如果使用了这个参数,说明这个链接库是必备库,如果找不到这个链接库,则工程不能编译。对应于FindHELLO.cmake模块中的HELLO_FIND_REQUIRED变量。 

同样,我们在上面的模块中定义了HELLO_FOUND,HELLO_INCLUDE_DIRHELLO_LIBRARY变量供开发者在FIND_PACKAGE指令中使用。src/main.c,内容为:

#include<hello.h>
int main()
{
    HelloFunc();
    return 0;
}

src/CMakeLists.txt文件,内容如下:

FIND_PACKAGE(HELLO)
IF(HELLO_FOUND)
    ADD_EXECUTABLE(hellomain.c)
    INCLUDE_DIRECTORIES(${HELLO_INCLUDE_DIR})
    TARGET_LINK_LIBRARIES(hello${HELLO_LIBRARY})
ENDIF(HELLO_FOUND)

为了能够让工程找到 FindHELLO.cmake 模块(存放在工程中的cmake目录)我们在主工程文件 CMakeLists.txt 中加入:

SET(CMAKE_MODULE_PATH${PROJECT_SOURCE_DIR}/cmake)

(三)使用自定义的FindHELLO模块构建工程 
仍然采用外部编译的方式,建立build目录,进入目录运行:

cmake ..

我们可以从输出中看到:

FoundHello: /usr/lib/libhello.so

FIND_PACKAGE(HELLO)修改为FIND_PACKAGE(HELLO QUIET),

不会看到上面的输出。接下来就可以使用make命令构建工程,运行:

./src/hello

可以得到输出

HelloWorld

说明工程成功构建。

(四)如果没有找到hellolibrary呢? 

我们可以尝试将/usr/lib/libhello.x移动到/tmp目录,这样按照FindHELLO模块的定义,找不到hellolibrary了,我们再来看一下构建结果:

cmake ..

仍然可以成功进行构建,但是这时候是没有办法编译的。

FIND_PACKAGE(HELLO)FIND_PACKAGE(HELLO REQUIRED),将hellolibrary定义为工程必须的共享库。 

这时候再次运行

cmake ..

我们得到如下输出:

CMakeError: Could not find hello library.

因为找不到libhello.x,所以,整个Makefile生成过程被出错中止。

一些问题

1.怎样区分debug、release版本 

建立debug/release两目录,分别在其中执行cmake -D CMAKE_BUILD_TYPE=Debug(或Release),需要编译不同版本时进入不同目录执行make即可:

Debug版会使用参数-g;
Release版使用-O3–DNDEBUG

DEBUG版设置编译参数DDEBUG

IF(DEBUG_mode)
    add_definitions(-DDEBUG)
ENDIF()

cmake时增加参数即可,例如cmake -D DEBUG_mode=ON2.怎样设置条件编译 

例如debug版设置编译选项DEBUG,并且更改不应改变CMakelist.txt 

使用option command,eg:

option(DEBUG_mode"ON for debug or OFF for release" ON)
IF(DEBUG_mode)
    add_definitions(-DDEBUG)
ENDIF()

cmake生成makefile,然后make edit_cache编辑编译选项;Linux下会打开一个文本框,可以更改,改完后再make生成目标文件——emacs不支持make edit_cache

局限:这种方法不能直接设置生成的makefile,而是必须使用命令在make前设置参数;对于debug、release版本,相当于需要两个目录,分别先cmake一次,然后分别makeedit_cache一次;

makefile


四、cmake 基本命令 & 交叉编译配置 & 模块的编写



cmake 基本命令:

cmake_minimum_required(VERSION 2.8.2 FATAL_ERROR)

project("ProjName")

// 不推荐使用add_definitions来设置编译选项,因为其作用如同cmake -D
add_definitions(
    -std=c++11 # Or -std=c++0x
    -Wall
    -Wfatal-errors
    -DXXX      #等于gcc -DXXX
    # Other flags
)

// 一般通过下条语句设置编译选项
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -Wall -Wfatal-errors -fPIC")

//变量:CMAKE_PREFIX_PATH: Path used for searching by FIND_XXX(), with appropriate suffixes added。
set(CMAKE_PREFIX_PATH /path/path/path)

//引用环境变量
export FOO=/use/lib  # 在bash中
$ENV{FOO}  # 在CMakeLists.txt中

//头文件路径
include_directories(
    include
    relative/path
    /absolute/path/
)

//链接库目录
link_directories(directory1 directory2 ...)

//链接函数库
target_link_libraries(a.out mylib ompl) //可以是cmake中的target,也可以是某个目录中的库文件,如 libompl.so,等同于 gcc -lompl

//源文件路径,在子目录中
add_subdirectory (
    someDirectory
    src/otherDirecotry
)

//寻找某个目录中的所有源文件,格式:aux_source_directory(<dir> <variable>)
aux_source_directory(src _srcFiles)

//生成库文件
add_library(mylib [SHARED|STATIC] 
  mylib.cpp
  other.cpp
)

//生成可执行程序
add_executable(a.out
    main.cpp 
    src/func.cpp
)

//设置变量
set(someVariable "some string")

//打印消息
message(STATUS "some status ${someVariable}")

//list操作
list(REMOVE_ITEM _srcFiles "src/f4.cpp")    //从变量中去掉某一项
list(APPEND <list> <element> [<element> ...])   //添加某一项到变量中

//检查编译器是否支持某一个编译选项
CHECK_CXX_COMPILER_FLAG("-std=c++11" COMPILER_SUPPORTS_CXX11)
CHECK_CXX_COMPILER_FLAG("-std=c++0x" COMPILER_SUPPORTS_CXX0X)
if(COMPILER_SUPPORTS_CXX11)
    set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11")
elseif(COMPILER_SUPPORTS_CXX0X)
    set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++0x")
else()
        message(STATUS "The compiler ${CMAKE_CXX_COMPILER} has no C++11 support. Please use a different C++ compiler.")
endif()

常用 find_package 找 boost 库和头文件:

# Put this in your CMakeLists.txt file (change any options from OFF to ON if you want):

set(Boost_USE_STATIC_LIBS OFF)  # 不写这几个就是默认设置
set(Boost_USE_MULTITHREADED ON)  
set(Boost_USE_STATIC_RUNTIME OFF) 

find_package(Boost 1.59.0 COMPONENTS *boost libraries here*  REQUIRED)  // 如果只是头文件库,则不需要写 COMPONENTS, 直接 find_package(Boost 1.59.0 xxx REQUIRED)

if(Boost_FOUND)
    include_directories(${Boost_INCLUDE_DIRS}) 
    add_executable(progname file1.cxx file2.cxx) 
    target_link_libraries(progname ${Boost_LIBRARIES})
endif()
# Obviously you need to put the libraries you want where I put *boost libraries here*. 
# For example, if you're using the filesystem and regex library you'd write:

find_package(Boost 1.59.0 COMPONENTS filesystem regex REQUIRED)  # 一般包含头文件是 <boost/xxx/*>, 则 COMPONENTS 后面就写 xxx
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

cmake marco & function

set(var "ABC")

macro(Moo arg)  # 定义macro
  message("arg = ${arg}")
  set(arg "abc")
  message("# After change the value of arg.")
  message("arg = ${arg}")
endmacro()
message("=== Call macro ===")
Moo(${var})  # 调用macro

function(Foo arg)  # 定义函数
  message("arg = ${arg}")
  set(arg "abc")
  message("# After change the value of arg.")
  message("arg = ${arg}")
endfunction()
message("=== Call function ===")
Foo(${var})  # 调用函数
and the output is

=== Call macro ===
arg = ABC
# After change the value of arg.
arg = ABC
=== Call function ===
arg = ABC
# After change the value of arg.
arg = abc

#注意,marco的调用相当于c/c++的预编译器,只会进行字符串替换(这就是为啥 arg 没有被改变);而函数则可以进行变量操作

//常用变量
CMAKE_SOURCE_DIR ( 相当于工程根目录 ) 
 this is the directory, from which cmake was started, i.e. the topCMAKE_CURRENT_SOURCE_DIR 
 this is the directory where the currently processed CMakeLists.txt is located inPROJECT_SOURCE_DIR ( =CMAKE_SOURCE_DIR 相当于工程根目录 ) 
 contains the full path to the root of your project source directory, i.e. to the nearest directory where CMakeLists.txt contains the PROJECT() commandCMAKE_PREFIX_PATH (用于找 Findxxx.cmake文件,找 库 和 头文件) 
 Path used for searching by FIND_XXX(), with appropriate suffixes added.CMAKE_INSTALL_PREFIX ( 安装目录 ) 
 Install directory used by install. 
 If “make install” is invoked or INSTALL is built, this directory is prepended onto all install directories. This variable defaults to /usr/local on UNIX and c:/Program Files on Windows.


例如: cmake .. -DCMAKE_INSTALL_PREFIX=/my/paht/to/install 

cmake 配置交叉编译环境:

// cmake的交叉编译环境设置,创建文件toolchain.cmake,添加以下内容:

# this one is important
SET(CMAKE_SYSTEM_NAME Linux)
#this one not so much
SET(CMAKE_SYSTEM_VERSION 1)

# specify the cross compiler
SET(CMAKE_C_COMPILER   /opt/arm/arm-linux-gnueabihf-gcc)
SET(CMAKE_CXX_COMPILER /opt/arm/arm-linux-gnueabihf-g++)

# where is the target environment 
SET(CMAKE_FIND_ROOT_PATH  /opt/arm/install)

# search for programs in the build host directories
SET(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
# for libraries and headers in the target directories
SET(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
SET(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)

// If this file is named toolchain.cmake and is located in your home directory
// and you are building in the subdirectory build then you can do:

~/src$ cd build
~/src/build$ cmake -DCMAKE_TOOLCHAIN_FILE=~/toolchain.cmake ..

注意: 在交叉编译的时候,如果某些 FindXXX.cmake 模块中有类似 pkg_search_module 或者 pkg_check_modules

pkg-config

可以找到相应的模块的 FindXXX.cmake 替换其 pkg-config

如果不想让 pkg-config 被执行,可以试着:

SET(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM ONLY)  // 即不让cmake的 find_program 去host环境中找可执行文件,所以 pkg_search_module 这种命令应该会失败。

如果 cmake cache了一些变量,需要重新运行cmake,只需要删除 CMakeCache.txt 文件即可


关于如何编写自己的 Findxxx.cmake 文件:

尊重原作,以下部分复制了该作者的部分文件内容,see link

set(MYSIMPLEPACKAGE_ROOT_DIR
    "${MYSIMPLEPACKAGE_ROOT_DIR}"
    CACHE
    PATH
    "Directory to search")

if(CMAKE_SIZEOF_VOID_P MATCHES "8")
    set(_LIBSUFFIXES /lib64 /lib)
else()
    set(_LIBSUFFIXES /lib)
endif()

find_library(MYSIMPLEPACKAGE_LIBRARY
    NAMES
    mysimplepackage
    PATHS
    "${MYSIMPLEPACKAGE_ROOT_DIR}"
    PATH_SUFFIXES
    "${_LIBSUFFIXES}")

# Might want to look close to the library first for the includes.
get_filename_component(_libdir "${MYSIMPLEPACKAGE_LIBRARY}" PATH)

find_path(MYSIMPLEPACKAGE_INCLUDE_DIR
    NAMES
    mysimplepackage.h
    HINTS
    "${_libdir}" # the library I based this on was sometimes bundled right next to its include
    "${_libdir}/.."
    PATHS
    "${MYSIMPLEPACKAGE_ROOT_DIR}"
    PATH_SUFFIXES
    include/)

# There's a DLL to distribute on Windows - find where it is.
set(_deps_check)
if(WIN32)
    find_file(MYSIMPLEPACKAGE_RUNTIME_LIBRARY
        NAMES
        mysimplepackage.dll
        HINTS
        "${_libdir}")
    set(MYSIMPLEPACKAGE_RUNTIME_LIBRARIES
        "${MYSIMPLEPACKAGE_RUNTIME_LIBRARY}")
    get_filename_component(MYSIMPLEPACKAGE_RUNTIME_LIBRARY_DIRS
        "${MYSIMPLEPACKAGE_RUNTIME_LIBRARY}"
        PATH)
    list(APPEND _deps_check MYSIMPLEPACKAGE_RUNTIME_LIBRARY)
else()
    get_filename_component(MYSIMPLEPACKAGE_RUNTIME_LIBRARY_DIRS
        "${MYSIMPLEPACKAGE_LIBRARY}"
        PATH)
endif()

include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(MySimplePackage
    DEFAULT_MSG
    MYSIMPLEPACKAGE_LIBRARY
    MYSIMPLEPACKAGE_INCLUDE_DIR
    ${_deps_check})

if(MYSIMPLEPACKAGE_FOUND)
    set(MYSIMPLEPACKAGE_LIBRARIES "${MYSIMPLEPACKAGE_LIBRARY}")
    set(MYSIMPLEPACKAGE_INCLUDE_DIRS "${MYSIMPLEPACKAGE_INCLUDE_DIR}")
    mark_as_advanced(MYSIMPLEPACKAGE_ROOT_DIR)
endif()

mark_as_advanced(MYSIMPLEPACKAGE_INCLUDE_DIR
    MYSIMPLEPACKAGE_LIBRARY
    MYSIMPLEPACKAGE_RUNTIME_LIBRARY)