JNI CMAKE函数

1,058 阅读11分钟

cmake_minimum_required

cmake_minimum_required是CMake中的一个命令,用于指定构建项目所需的最低CMake版本。这个命令通常位于CMakeLists.txt文件的开头,确保项目在不兼容的CMake版本下不会被错误地构建。

cmake_minimum_required的基本语法如下:

cmake_minimum_required(VERSION major.minor[.patch[.tweak]])
  • major.minor[.patch[.tweak]]表示所需的最低CMake版本,其中majorminorpatchtweak是版本号的组成部分。

例如,以下命令指定构建项目所需的最低CMake版本为3.10:

cmake_minimum_required(VERSION 3.10)

如果当前使用的CMake版本低于指定的最低版本,CMake将在配置阶段报错并终止构建过程。这有助于确保项目在具有兼容性的CMake版本下构建,避免因版本不兼容导致的潜在问题。

在 cmake_minimum_required 命令中,VERSION 是一个关键字,用于指定后面跟随的版本号。这个关键字告诉 CMake,我们接下来要提供一个版本号,然后 CMake 将会检查当前运行的 CMake 版本是否至少满足这个版本号。

例如,在以下语句中:

cmake_minimum_required(VERSION 3.10)

不能省略VERSION 

project

project 是一个 CMake 命令,用于定义项目的名称和版本信息。它还可以设置一些项目相关的变量,如编译器和语言标准。project 命令通常位于 CMakeLists.txt 文件的开头,紧跟在 cmake_minimum_required 命令之后。

以下是一个简单的示例:

cmake_minimum_required(VERSION 3.10)
project(MyProject VERSION 1.0.0)

在这个例子中,project 命令定义了项目名称为 "MyProject",版本号为 "1.0.0"。这会设置一些变量,如 PROJECT_NAMEPROJECT_VERSION 等,以便在后续的 CMake 脚本中使用。

project 命令还可以指定项目所使用的编程语言,例如:

project(MyProject VERSION 1.0.0 LANGUAGES CXX)

在这个例子中,我们指定了项目使用 C++ 语言。这将影响 CMake 在配置项目时选择编译器和设置编译选项。

在CMake中,通过 project 命令指定项目使用的编程语言可以影响以下两个主要方面:

  1. 编译器的选择:CMake会根据指定的编程语言选择合适的编译器。例如,如果你的项目中指定了 LANGUAGES CXX,CMake会寻找系统中可用的C++编译器。这可能是g++、clang++、或者在Windows上的MSVC等。CMake有一套内建的规则来确定在多个编译器可用的情况下应该选择哪个。
  2. 编译选项的设置:一旦确定了编译器,CMake会根据编译器类型和指定的编程语言来设置默认的编译选项。例如,对于C++,CMake可能会设置一些标准库的链接选项,或者根据你的 CMAKE_CXX_STANDARD 变量设置C++的标准版本。

比如设置c++17,你可以在 CMakeLists.txt 文件中设置 CMAKE_CXX_STANDARD 变量为 17 或更高的值。例如:

set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)

这将设置 C++ 标准为 C++17,并且如果编译器不支持 C++17,CMake 将无法生成构建文件。CMAKE_CXX_EXTENSIONS OFF 表示不使用编译器特定的扩展,确保你的代码是可移植的。

这些都是在CMake的配置阶段发生的,即当你运行 cmake 命令生成构建系统(如Makefiles或Ninja构建文件)时

如果你在 project 命令中指定了 C++ 作为编程语言,这并不意味着你不能在项目中使用 C 语言。实际上,你可以在同一个项目中同时使用多种编程语言。要实现这一点,你只需在 project 命令中同时指定 C 和 C++ 作为编程语言。例如:

project(MyProject VERSION 1.0.0 LANGUAGES C CXX)

这样,CMake 将为 C 和 C++ 代码配置合适的编译器和编译选项。你可以在项目中混合使用 C 和 C++ 代码,CMake 会根据文件扩展名(如 .c 和 .cpp)自动识别并使用相应的编译器进行编译。

如果我只写CXX不写C呢?

如果你在 project 命令中只指定了 C++(CXX)作为编程语言,例如:

project(MyProject VERSION 1.0.0 LANGUAGES CXX)

这意味着 CMake 将只为 C++ 代码配置编译器和编译选项。在这种情况下,如果你的项目中包含 C 语言代码,CMake 可能不会正确处理这些文件。

然而,如果你确实需要在项目中混合使用 C 和 C++ 代码,你可以稍后在 project 命令之后添加对 C 语言的支持,例如:

project(MyProject VERSION 1.0.0 LANGUAGES CXX)
enable_language(C)

这将允许 CMake 为 C 语言代码配置编译器和编译选项。但是,为了避免潜在的问题,最好一开始就在 project 命令中指定所有需要的编程语言。

target_include_directories

一定要在add_library之后执行,CMake 3.x之后 一定要有可见性修饰符 ,不然报错


`target_include_directories`是CMake中的一个命令,它用于为特定的目标(例如库或可执行文件)添加包含目录当编译器查找头文件时,它会在这些目录中搜索

`target_include_directories`的基本语法如下:

target_include_directories(target [SYSTEM] [BEFORE] <INTERFACE|PUBLIC|PRIVATE> [items1...] [<INTERFACE|PUBLIC|PRIVATE> [items2...] ...])


-   `target`是你要为其添加包含目录的目标名称。
-   `SYSTEM`是一个可选关键字,如果指定,那么这些目录将被视为系统目录,编译器可能会对来自这些目录的警告进行抑制。
-   `BEFORE`也是一个可选关键字,如果指定,那么这些目录将被添加到其他目录之前。
-   `<INTERFACE|PUBLIC|PRIVATE>`关键字用于指定目录的作用范围。`INTERFACE`表示目录只对目标的使用者有效,`PRIVATE`表示目录只对目标自身有效,`PUBLIC`表示目录对目标自身和其使用者都有效。
-   `items...`是你要添加的包含目录。

例如,以下命令将`/path/to/include`添加到目标`mylib`的公共包含目录中:

target_include_directories(mylib PUBLIC /path/to/include)


这意味着当你构建`mylib`时,编译器会在`/path/to/include`中搜索头文件;同时,当其他目标链接到`mylib`时,编译器也会在`/path/to/include`中搜索头文件

CMake 的 `target_link_libraries` 函数负责处理目标之间的依赖关系当你使用 `target_link_libraries` 将一个目标链接到另一个目标时,CMake 会根据可见性关键字(`INTERFACE``PUBLIC``PRIVATE`)来确定哪些包含目录可以被访问

例如,假设有两个库:`libA` 和 `libB``libB` 依赖于 `libA`CMakeLists.txt 文件中,你可以这样设置依赖关系:

target_include_directories(libA PRIVATE include_private) target_include_directories(libA INTERFACE include_interface) target_include_directories(libA PUBLIC include_public)

target_link_libraries(libB libA)


在这个例子中,`libB` 只能访问 `libA` 的 `include_interface` 和 `include_public` 目录。`include_private` 目录对 `libB` 是不可见的,因为它被标记为 `PRIVATE`。

这个可见性限制在编译和链接过程中起作用。当编译器处理 `libB` 的源代码时,它会在 `libA` 的 `include_interface` 和 `include_public` 目录中查找头文件。在链接过程中,编译器会确保 `libB` 只能访问这些允许的头文件。

### target_link_libraries

`target_link_libraries` 的参数用于指定目标之间的依赖关系、链接顺序和依赖传递。以下是参数的作用:

1.  目标(`<target>`):指定要链接的目标,如库或可执行文件。

1.  依赖库(`<library>`):指定目标所依赖的库。可以是多个库,顺序决定链接顺序。

1.  依赖关键字(`INTERFACE``PUBLIC``PRIVATE`):控制依赖关系的传递和头文件的可见性。

    -   `INTERFACE`:依赖仅对使用目标的其他目标可见,不影响目标本身。
    -   `PUBLIC`:依赖对目标本身和使用目标的其他目标都可见。
    -   `PRIVATE`:依赖仅对目标本身可见,不影响其他目标。

例如,有两个库 `libA` 和 `libB``libB` 依赖于 `libA`,可以使用 `target_link_libraries` 指定依赖关系和传递方式:

add_library(libA ...) add_library(libB ...)

libB 链接到 libA,依赖关系为 PUBLIC

target_link_libraries(libB PUBLIC libA)

在这个例子中,libB 链接到 libA,并使用 PUBLIC 关键字指定依赖关系对 libB 本身和使用 libB 的其他目标都可见。可以根据项目需求选择合适的依赖关键字。

对于C++的标准库,如<iostream><cstdlib>等,您无需使用target_link_libraries进行链接。编译器会自动处理这些标准库的链接。当您在代码中包含这些头文件并使用相应的功能时,编译器会确保在生成可执行文件或库文件时链接到正确的标准库。因此,您只需在代码中包含相应的头文件,无需额外的链接操作。

include_directories

include_directoriestarget_include_directories都用于在CMake中指定头文件的搜索路径,但它们的作用范围和使用方式有所不同。

include_directories是一个全局命令,它会影响所有后续定义的目标。当您使用include_directories指定路径时,这些路径将应用于当前CMakeLists.txt文件中定义的所有目标。

include_directories(${CMAKE_SOURCE_DIR}/include)
add_executable(my_app main.cpp)
add_library(my_lib my_lib.cpp)

在这个例子中,my_appmy_lib都会使用include_directories指定的头文件路径。

target_include_directories是一个目标命令,它只影响特定的目标。使用target_include_directories可以更精确地控制哪些目标需要特定的头文件路径。

add_executable(my_app main.cpp)
add_library(my_lib my_lib.cpp)

target_include_directories(my_app PRIVATE ${CMAKE_SOURCE_DIR}/include)
target_include_directories(my_lib PUBLIC ${CMAKE_SOURCE_DIR}/lib_include)

在这个例子中,my_appmy_lib分别使用不同的头文件路径。target_include_directories还允许您指定路径的可见性(PRIVATEPUBLICINTERFACE),以控制路径在目标及其依赖关系中的传播。

总之,include_directories适用于全局设置,而target_include_directories提供了更精确的目标级别控制。在现代CMake编程中,推荐使用target_include_directories以获得更好的模块化和封装。

add_library

add_library是CMake中的一个命令,用于创建并管理库(包括静态库、动态库、模块库或者对象库)。其基本语法如下:

add_library(<name> [STATIC|SHARED|MODULE|OBJECT]
            [EXCLUDE_FROM_ALL]
            source1 [source2 ...])

其中:

  • <name>:库的名称。
  • STATIC|SHARED|MODULE|OBJECT:指定库的类型。STATIC是静态库,SHARED是动态库或共享库,MODULE是模块库,OBJECT是对象库。如果不指定,默认是由BUILD_SHARED_LIBS选项决定的。
  • EXCLUDE_FROM_ALL:如果指定了这个选项,那么这个库不会被默认构建。
  • source1 [source2 ...]:库的源文件。

add_library命令的作用主要有以下几点:

  1. 定义库:创建一个新的库目标,可以是静态库、动态库、模块库或者对象库。库目标就像一个变量,可以在其他命令中引用。
  2. 管理源文件:指定库的源文件,CMake会自动处理源文件的依赖关系。
  3. 设置库的属性:可以使用set_target_properties命令设置库的属性,例如包含目录、编译选项等。
  4. 链接其他库:可以使用target_link_libraries命令链接其他库。

总的来说,add_library命令是CMake中创建和管理库的主要手段。

string

在CMake中,string函数用于处理字符串操作。它提供了一系列子命令,用于完成不同的字符串操作,如拼接、查找、替换、比较等。以下是一些常用的string子命令:

  • CONCAT:拼接字符串。
  • LENGTH:获取字符串长度。
  • SUBSTRING:提取子串。
  • FIND:查找子串在字符串中的位置。
  • REPLACE:替换字符串中的子串。
  • COMPARE:比较两个字符串。

以下是一个简单的CMake string函数示例:

set(str1 "Hello, ")
set(str2 "World!")
string(CONCAT result ${str1} ${str2})
message(STATUS "Result: ${result}")

这段CMake代码将两个字符串拼接并输出结果。在编写CMake脚本时,string函数可以帮助您处理字符串相关的任务。

string(TIMESTAMP COMPILE_TIME %Y%m%d-%H%M%S)是CMake中的一个string函数,用于获取当前时间戳并将其格式化为指定的字符串格式。在这个例子中,COMPILE_TIME是一个变量,用于存储格式化后的时间戳。

%Y%m%d-%H%M%S是一个格式字符串,表示以下组件:

  • %Y:四位数的年份(如2023)
  • %m:两位数的月份(如09)
  • %d:两位数的日期(如28)
  • %H:两位数的小时(如14)
  • %M:两位数的分钟(如30)
  • %S:两位数的秒钟(如45)

-%之间的字符(如-)会原样输出。

例如,如果当前时间是2023年9月28日14时30分45秒,COMPILE_TIME变量将被设置为20230928-143045。这个功能在CMake脚本中可以用于生成编译时间相关的信息,例如为生成的二进制文件添加时间戳。

set_target_properties

set_target_properties 是 CMake 中用于设置目标(例如库或可执行文件)属性的命令。它的语法如下:

set_target_properties(target1 target2 ...
                      PROPERTIES prop1 value1 prop2 value2 ...)

其中,target1target2 等是要设置属性的目标,prop1prop2 等是要设置的属性,而 value1value2 等是对应属性的值。

下面是一个简单的例子:

cmake_minimum_required(VERSION 3.10)
project(MyProject)

add_executable(my_executable main.cpp)

set_target_properties(my_executable
                      PROPERTIES
                      CXX_STANDARD 11
                      CXX_STANDARD_REQUIRED ON
                      RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin")

在这个例子中,我们创建了一个名为 my_executable 的可执行文件目标,然后使用 set_target_properties 设置了以下属性:

  • CXX_STANDARD:设置 C++ 标准为 C++11。
  • CXX_STANDARD_REQUIRED:如果编译器不支持 C++11,CMake 会报错。
  • RUNTIME_OUTPUT_DIRECTORY:设置可执行文件的输出目录为构建目录下的 bin 文件夹。

你可以在 CMake 文档中查看更多关于 set_target_properties 可用属性的信息:cmake.org/cmake/help/…

PROJECT_SOURCE_DIR

PROJECT_SOURCE_DIR 是一个 CMake 变量,表示离 CMakeLists.txt最近的那个目录。当你在 CMakeLists.txt 文件中使用 project() 命令定义项目名称时,CMake 会自动设置这个变量。

get_filename_component

get_filename_component是CMake的一个命令,用于获取文件路径的特定组件。这个命令可以用来获取文件名、目录路径、绝对路径等。

以下是一些使用get_filename_component的例子:

get_filename_component(MyDir "/home/user/foo.txt" PATH)

在这个例子中,MyDir将被设置为/home/user,这是文件路径的目录部分。

get_filename_component(MyName "/home/user/foo.txt" NAME)

在这个例子中,MyName将被设置为foo.txt,这是文件路径的文件名部分。

get_filename_component(MyAbsPath "/home/user/foo.txt" ABSOLUTE)

在这个例子中,MyAbsPath将被设置为/home/user/foo.txt的绝对路径。

这个命令非常有用,可以帮助你在处理文件路径时更加方便地获取你需要的信息。

file

GLOB_RECURSE 会递归搜索子目录,GLOB 只搜索当前目录。

如果你想获取一个目录下的所有子目录,你可以使用file(GLOB)命令。例如:

file(GLOB MyDirs RELATIVE "/home/user/dir" "/home/user/dir/*")

在这个例子中,MyDirs将包含/home/user/dir下的所有子目录和文件。如果你只想获取子目录,你可以使用GLOB_ONLYDIR选项:

file(GLOB MyDirs RELATIVE "/home/user/dir" "/home/user/dir/*" GLOB_ONLYDIR)

在这个例子中,MyDirs将只包含/home/user/dir下的子目录。

GLOB是CMake中file的一个参数,用于生成文件名或目录名的列表。这个命令可以接受一个或多个模式作为参数,然后返回匹配这些模式的所有文件或目录的列表。

例如,以下命令将返回当前目录下所有.cpp文件的列表:

file(GLOB MySources "*.cpp")

在这个例子中,MySources将包含当前目录下所有.cpp文件的名称。

GLOB命令还有一些选项,例如GLOB_RECURSE(递归地搜索所有子目录)和GLOB_ONLYDIR(只返回目录)。

请注意,GLOB命令在构建系统中可能会导致一些问题,因为它在构建时不会自动检测新的文件。因此,如果你在源目录中添加了新的文件,你可能需要手动重新运行CMake来更新文件列表。

file命令在CMake中是一个多功能命令,除了GLOB,它还有许多其他参数,包括:

  • READ:读取文件的内容到变量中。
  • WRITE:写入内容到文件中。
  • APPEND:在文件末尾追加内容。
  • RENAME:重命名或移动文件。
  • REMOVE:删除文件或目录。
  • COPY:复制文件或目录。
  • DOWNLOAD:下载文件。
  • UPLOAD:上传文件。
  • TOUCH:更新文件的修改时间。
  • TOUCH_NOCREATE:更新已存在文件的修改时间,但不创建新文件。
  • MAKE_DIRECTORY:创建目录。
  • REMOVE_RECURSE:递归删除目录及其内容。
  • GLOB_RECURSE:递归地生成匹配给定模式的文件或目录的列表。

这些参数使得file命令在CMake中非常强大和灵活,可以处理各种文件和目录操作。

获取指定目录下所有so文件

# 获取指定目录下的所有.so文件  
file(GLOB LIBS ${aaa}/${CMAKE_ANDROID_ARCH_ABI}/*.so)  
# 将所有.so文件链接到你的目标  
foreach(LIB ${LIBS})  
    message(文件 ${LIB})  
    target_link_libraries(test ${LIB})  
endforeach()

设置库文件输出目录

CMAKE_ARCHIVE_OUTPUT_DIRECTORY 和 CMAKE_LIBRARY_OUTPUT_DIRECTORY 都用于设置构建产物的输出目录,但它们的作用略有不同。

CMAKE_ARCHIVE_OUTPUT_DIRECTORY 用于设置静态库(如 .a 或 .lib 文件)的输出目录。当您使用 add_library 命令创建静态库时,CMake 会将生成的静态库文件放在这个目录下。

CMAKE_LIBRARY_OUTPUT_DIRECTORY 用于设置动态库(如 .so、.dll 或 .dylib 文件)和模块库的输出目录。当您使用 add_library 命令创建动态库或模块库时,CMake 会将生成的库文件放在这个目录下。

如果您希望将所有库文件(静态库、动态库和模块库)放在同一个目录下,可以同时设置这两个变量:

aux_source_directory

  • 作用: aux_source_directory( ) 会扫描指定目录  下的所有源文件(如 .c/.cc/.cpp/.cxx/.m/.mm 等,非递归),并把文件列表保存到变量 ,常用于传给 add_library 或 add_executable。
aux_source_directory(${CMAKE_CURRENT_SOURCE_DIR}/src SRC_LIST)

总结

add_library仅用于定义库目标,包括源文件和库类型(静态或共享)。编译和链接过程实际上是在生成和构建阶段执行的。add_library不负责处理库之间的依赖关系。相反,target_link_libraries用于指定目标库与其他库之间的链接关系,确保在编译和链接过程中正确处理依赖关系。

在CMake中,add_library用于创建库文件,而target_link_libraries用于将目标库链接到其他库。这是因为库文件通常包含一组函数和数据结构,但它们可能依赖于其他库中的功能。通过使用target_link_libraries,您可以确保在编译和链接过程中,依赖关系得到正确处理。这样可以避免未定义的符号或链接错误。总之,add_library用于生成库文件,target_link_libraries用于处理库之间的依赖关系。

编译和链接过程是在CMakeLists.txt执行完成后,构建系统会根据生成的构建规则执行编译和链接过程。AndroidStudio会自动执行相应的make指令。