目录
[2.2.检测 OpenCL 版本](#2.2.检测 OpenCL 版本)
[2.3.模块模式核心 - find_path 查找 OpenCL 头文件目录](#2.3.模块模式核心 - find_path 查找 OpenCL 头文件目录)
[2.5.按【平台 + 位数】查找 OpenCL 库文件](#2.5.按【平台 + 位数】查找 OpenCL 库文件)
[2.9.模块模式 兼容 现代 CMake 的「导入目标」](#2.9.模块模式 兼容 现代 CMake 的「导入目标」)
[3.如何使用这份 FindOpenCL.cmake(2 种用法,都支持)](#3.如何使用这份 FindOpenCL.cmake(2 种用法,都支持))
1.简介
在CMake的安装目录.\CMake\share\cmake-4.0\Modules下有个模块模式下查找OpenCL库的FindOpenCL.cmake文件,里面包含了跨平台(Windows/Linux/Mac)、跨位数(32/64 位)、自动查找系统中的 OpenCL 库,并完成:头文件定位、库文件定位、OpenCL 版本自动检测、封装标准变量、生成现代 CMake 导入目标 OpenCL::OpenCL等知识点。可以直接通过 find_package(OpenCL REQUIRED) 调用,完美兼容模块模式的所有规范。
FindOpenCL.cmake内容如下:
cpp
# Distributed under the OSI-approved BSD 3-Clause License. See accompanying
# file LICENSE.rst or https://cmake.org/licensing for details.
#[=======================================================================[.rst:
FindOpenCL
----------
.. versionadded:: 3.1
Finds Open Computing Language (OpenCL)
.. versionadded:: 3.10
Detection of OpenCL 2.1 and 2.2.
Imported Targets
^^^^^^^^^^^^^^^^
.. versionadded:: 3.7
This module defines :prop_tgt:`IMPORTED` target ``OpenCL::OpenCL``, if
OpenCL has been found.
Result Variables
^^^^^^^^^^^^^^^^
This module defines the following variables::
OpenCL_FOUND - True if OpenCL was found
OpenCL_INCLUDE_DIRS - include directories for OpenCL
OpenCL_LIBRARIES - link against this library to use OpenCL
OpenCL_VERSION_STRING - Highest supported OpenCL version (eg. 1.2)
OpenCL_VERSION_MAJOR - The major version of the OpenCL implementation
OpenCL_VERSION_MINOR - The minor version of the OpenCL implementation
The module will also define two cache variables::
OpenCL_INCLUDE_DIR - the OpenCL include directory
OpenCL_LIBRARY - the path to the OpenCL library
#]=======================================================================]
set(_OPENCL_x86 "(x86)")
function(_FIND_OPENCL_VERSION)
include(CheckSymbolExists)
include(CMakePushCheckState)
set(CMAKE_REQUIRED_QUIET ${OpenCL_FIND_QUIETLY})
cmake_push_check_state()
foreach(VERSION "3_0" "2_2" "2_1" "2_0" "1_2" "1_1" "1_0")
set(CMAKE_REQUIRED_INCLUDES "${OpenCL_INCLUDE_DIR}")
if(EXISTS ${OpenCL_INCLUDE_DIR}/Headers/cl.h)
check_symbol_exists(
CL_VERSION_${VERSION}
"${OpenCL_INCLUDE_DIR}/Headers/cl.h"
OPENCL_VERSION_${VERSION})
else()
check_symbol_exists(
CL_VERSION_${VERSION}
"${OpenCL_INCLUDE_DIR}/CL/cl.h"
OPENCL_VERSION_${VERSION})
endif()
if(OPENCL_VERSION_${VERSION})
string(REPLACE "_" "." VERSION "${VERSION}")
set(OpenCL_VERSION_STRING ${VERSION} PARENT_SCOPE)
string(REGEX MATCHALL "[0-9]+" version_components "${VERSION}")
list(GET version_components 0 major_version)
list(GET version_components 1 minor_version)
set(OpenCL_VERSION_MAJOR ${major_version} PARENT_SCOPE)
set(OpenCL_VERSION_MINOR ${minor_version} PARENT_SCOPE)
break()
endif()
endforeach()
cmake_pop_check_state()
endfunction()
find_path(OpenCL_INCLUDE_DIR
NAMES
CL/cl.h OpenCL/cl.h
PATHS
ENV "PROGRAMFILES(X86)"
ENV "PROGRAMFILES"
$ENV{PROGRAMFILES${_OPENCL_x86}}/OpenCLHeaders
$ENV{PROGRAMFILES}/OpenCLHeaders
ENV AMDAPPSDKROOT
ENV INTELOCLSDKROOT
ENV NVSDKCOMPUTE_ROOT
ENV CUDA_PATH
ENV ATISTREAMSDKROOT
ENV OCL_ROOT
/usr/local/cuda
/opt/cuda
PATH_SUFFIXES
include
OpenCL/common/inc
"AMD APP/include")
_FIND_OPENCL_VERSION()
if(WIN32)
if(CMAKE_SIZEOF_VOID_P EQUAL 4)
find_library(OpenCL_LIBRARY
NAMES OpenCL
PATHS
ENV "PROGRAMFILES(X86)"
ENV "PROGRAMFILES"
$ENV{PROGRAMFILES${_OPENCL_x86}}/OpenCL-ICD-Loader
$ENV{PROGRAMFILES}/OpenCL-ICD-Loader
ENV AMDAPPSDKROOT
ENV INTELOCLSDKROOT
ENV CUDA_PATH
ENV NVSDKCOMPUTE_ROOT
ENV ATISTREAMSDKROOT
ENV OCL_ROOT
PATH_SUFFIXES
"AMD APP/lib/x86"
lib/x86
lib/Win32
OpenCL/common/lib/Win32)
elseif(CMAKE_SIZEOF_VOID_P EQUAL 8)
find_library(OpenCL_LIBRARY
NAMES OpenCL
PATHS
ENV "PROGRAMFILES(X86)"
ENV "PROGRAMFILES"
$ENV{PROGRAMFILES${_OPENCL_x86}}/OpenCL-ICD-Loader
$ENV{PROGRAMFILES}/OpenCL-ICD-Loader
ENV AMDAPPSDKROOT
ENV INTELOCLSDKROOT
ENV CUDA_PATH
ENV NVSDKCOMPUTE_ROOT
ENV ATISTREAMSDKROOT
ENV OCL_ROOT
PATH_SUFFIXES
"AMD APP/lib/x86_64"
lib/x86_64
lib/x64
lib
OpenCL/common/lib/x64)
endif()
else()
if(CMAKE_SIZEOF_VOID_P EQUAL 4)
find_library(OpenCL_LIBRARY
NAMES OpenCL
PATHS
ENV AMDAPPSDKROOT
ENV CUDA_PATH
/usr/local/cuda
/opt/cuda
PATH_SUFFIXES
lib/x86
lib)
elseif(CMAKE_SIZEOF_VOID_P EQUAL 8)
find_library(OpenCL_LIBRARY
NAMES OpenCL
PATHS
ENV AMDAPPSDKROOT
ENV CUDA_PATH
/usr/local/cuda
/opt/cuda
PATH_SUFFIXES
lib/x86_64
lib/x64
lib
lib64)
endif()
endif()
unset(_OPENCL_x86)
set(OpenCL_LIBRARIES ${OpenCL_LIBRARY})
set(OpenCL_INCLUDE_DIRS ${OpenCL_INCLUDE_DIR})
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(
OpenCL
REQUIRED_VARS OpenCL_LIBRARY OpenCL_INCLUDE_DIR
VERSION_VAR OpenCL_VERSION_STRING)
mark_as_advanced(
OpenCL_INCLUDE_DIR
OpenCL_LIBRARY)
if(OpenCL_FOUND AND NOT TARGET OpenCL::OpenCL)
if(OpenCL_LIBRARY MATCHES "/([^/]+)\\.framework$")
add_library(OpenCL::OpenCL INTERFACE IMPORTED)
set_target_properties(OpenCL::OpenCL PROPERTIES
INTERFACE_LINK_LIBRARIES "${OpenCL_LIBRARY}")
else()
add_library(OpenCL::OpenCL UNKNOWN IMPORTED)
set_target_properties(OpenCL::OpenCL PROPERTIES
IMPORTED_LOCATION "${OpenCL_LIBRARY}")
endif()
set_target_properties(OpenCL::OpenCL PROPERTIES
INTERFACE_INCLUDE_DIRECTORIES "${OpenCL_INCLUDE_DIRS}")
endif()
下面就以这个为例来讲讲模块模式的.cmake文件怎么来写。
2.详细分析
2.1.定义私有变量
cpp
# 定义私有变量,存储字符串 (x86),下划线开头是CMake的【私有变量规范】
# 作用:拼接环境变量 PROGRAMFILES(x86),避免硬编码,提升可维护性
# 规范:CMake中所有自定义的「内部临时变量」都建议加下划线前缀,防止污染全局变量
set(_OPENCL_x86 "(x86)")
CMake 中无真正的私有变量 ,只是通过「下划线前缀」约定这是内部变量,外部不要使用,最后会用unset清理。
2.2.检测 OpenCL 版本
这是整个脚本的核心逻辑之一,封装了「自动检测 OpenCL 版本」的独立逻辑,解耦代码,是 CMake 工程化的标准写法。
cpp
# 定义无参函数:_FIND_OPENCL_VERSION,函数名下划线开头=内部函数
function(_FIND_OPENCL_VERSION)
# 引入2个核心内置模块
include(CheckSymbolExists) # 核心:检查头文件中是否存在指定宏/符号
include(CMakePushCheckState) # 核心:保存/恢复CMake的检查状态,防止污染全局配置
# 控制检查时的输出静默:如果用户调用find_package时加了QUIET,这里就不打印日志
set(CMAKE_REQUIRED_QUIET ${OpenCL_FIND_QUIETLY})
# 保存当前CMake的检查状态(CMAKE_REQUIRED_INCLUDES等),函数结束后恢复
# 作用:隔离函数内的配置修改,不影响全局,必加!
cmake_push_check_state()
# 从高版本到低版本遍历OpenCL标准版本,找到当前库支持的最高版本即退出
# OpenCL版本宏定义规范:头文件中用 CL_VERSION_3_0、CL_VERSION_2_2 这类宏标识版本
foreach(VERSION "3_0" "2_2" "2_1" "2_0" "1_2" "1_1" "1_0")
# 设置检查符号时需要的头文件目录 → 就是上面find_path找到的OpenCL_INCLUDE_DIR
set(CMAKE_REQUIRED_INCLUDES "${OpenCL_INCLUDE_DIR}")
# ✨ 核心:EXISTS判断 + 兼容不同的头文件安装路径
# 情况1:部分OpenCL库的头文件放在 include/Headers/cl.h 下(AMD/Intel SDK)
if(EXISTS ${OpenCL_INCLUDE_DIR}/Headers/cl.h)
check_symbol_exists(CL_VERSION_${VERSION} "${OpenCL_INCLUDE_DIR}/Headers/cl.h" OPENCL_VERSION_${VERSION})
# 情况2:标准安装路径 include/CL/cl.h (绝大多数场景)
else()
check_symbol_exists(CL_VERSION_${VERSION} "${OpenCL_INCLUDE_DIR}/CL/cl.h" OPENCL_VERSION_${VERSION})
endif()
# 如果检测到该版本的宏定义存在 → 说明当前库支持该版本
if(OPENCL_VERSION_${VERSION})
# 把版本号的下划线替换为点:3_0 → 3.0,符合版本号规范
string(REPLACE "_" "." VERSION "${VERSION}")
# ✨ PARENT_SCOPE:把变量传递到【函数外部的父作用域】,核心!
set(OpenCL_VERSION_STRING ${VERSION} PARENT_SCOPE)
# 正则提取版本号的主、次版本号:3.0 → [3,0]
string(REGEX MATCHALL "[0-9]+" version_components "${VERSION}")
list(GET version_components 0 major_version)
list(GET version_components 1 minor_version)
# 传递主、次版本号到父作用域
set(OpenCL_VERSION_MAJOR ${major_version} PARENT_SCOPE)
set(OpenCL_VERSION_MINOR ${minor_version} PARENT_SCOPE)
break() # 找到最高版本,跳出循环,无需继续检测低版本
endif()
endforeach()
# 恢复之前保存的CMake检查状态,和cmake_push_check_state成对出现
cmake_pop_check_state()
endfunction()
1)EXISTS
EXISTS 是 CMake if() 判断语句的专属条件表达式 ,不是变量、不是函数 ,作用是:检查指定路径下的「文件」或「文件夹」是否存在。
cpp
# 语法1:判断【文件/文件夹】是否存在(通用)
if(EXISTS "路径")
# 路径存在时执行的逻辑
endif()
# 语法2:反向判断【不存在】(更常用,处理兜底逻辑)
if(NOT EXISTS "路径")
# 路径不存在时执行的逻辑
endif()
✅ EXISTS 对 文件 和 文件夹 都生效,不区分「是文件还是目录」,只要路径存在就返回TRUE;✅ 支持 绝对路径 、相对路径 ,是 CMake 跨平台的核心判断语法,Windows/Linux/macOS 完全通用;
EXISTS 只能判断「路径是否存在」,但无法区分「这个路径是文件,还是文件夹」,CMake 提供了 3 个专属判断表达式 ,和 EXISTS 一起用,是黄金搭档,必学必用:
cpp
# 1. 判断路径是否为【文件夹/目录】
if(IS_DIRECTORY "路径")
# 2. 判断路径是否为【普通文件】(非目录、非软链接)
if(IS_REGULAR_FILE "路径")
# 3. 判断路径是否为【软链接/符号链接】(Linux/macOS为主,Windows少用)
if(IS_SYMLINK "路径")
cpp
# 判断:是【目录】且【存在】
if(EXISTS ${CMAKE_PROGRAM_FILES}/Qt AND IS_DIRECTORY ${CMAKE_PROGRAM_FILES}/Qt)
message(STATUS "找到Qt安装目录:${CMAKE_PROGRAM_FILES}/Qt")
endif()
# 判断:是【文件】且【存在】
if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/main.cpp AND IS_REGULAR_FILE ${CMAKE_CURRENT_SOURCE_DIR}/main.cpp)
message(STATUS "找到主程序源码文件")
endif()
2)check_symbol_exists
cpp
check_symbol_exists(
<要检测的符号名> # 必填:宏/函数/变量名,比如CL_VERSION_3_0
<头文件路径/头文件名> # 必填:头文件的完整路径 或 标准头文件名,用双引号包裹
<结果存储变量名> # 必填:布尔变量,检测后自动赋值,TRUE=存在,FALSE=不存在
)
check_symbol_exists 是 CMake 的内置检测模块函数 ,属于 CMake 标准库(CheckSymbolExists.cmake),不是 CMake 原生指令 ,使用前必须通过 include(CheckSymbolExists) 引入模块。
在指定的C/C++ 头文件 中,通过预编译方式快速检测「某个符号是否存在」,检测结果存入一个布尔变量中(存在 = TRUE,不存在 = FALSE)。
核心优势:只做预编译检查,不编译完整源码、不进行链接 ,速度极快、兼容性拉满,是 CMake 中检测头文件内容的工业级标准方案。
能检测的「符号」类型(全部支持),支持 3 类 C/C++ 语法中的「符号」,优先级从高到低:
- 宏定义 (
#define XXX)→ ✅【最常用,你的 OpenCL 代码就是这个场景】,比如CL_VERSION_3_0、CL_VERSION_2_0; - 函数声明 (
void func();)→ ✅ 检测头文件中是否声明了某个函数,比如clCreateContext; - 全局变量声明 (
extern int var;)→ ✅ 检测头文件中是否声明了某个全局变量。
示例如下:
cpp
include(CheckSymbolExists)
# 检测 stdio.h 中是否有 printf 函数声明
check_symbol_exists(printf "stdio.h" HAVE_PRINTF)
# 检测 stdlib.h 中是否有 __STDC_VERSION__ 宏定义
check_symbol_exists(__STDC_VERSION__ "stdlib.h" HAVE_C_STD)
if(HAVE_PRINTF)
message(STATUS "检测到:stdio.h 中存在 printf 函数")
endif()
2.3.模块模式核心 - find_path 查找 OpenCL 头文件目录
cpp
find_path(OpenCL_INCLUDE_DIR
NAMES
CL/cl.h OpenCL/cl.h # 要查找的头文件名,兼容2种命名规范
PATHS # 查找的候选路径(优先级从上到下)
ENV "PROGRAMFILES(X86)" # Windows 32位软件目录(环境变量)
ENV "PROGRAMFILES" # Windows 64位软件目录(环境变量)
$ENV{PROGRAMFILES${_OPENCL_x86}}/OpenCLHeaders # 拼接:PROGRAMFILES(x86)/OpenCLHeaders
$ENV{PROGRAMFILES}/OpenCLHeaders # PROGRAMFILES/OpenCLHeaders
ENV AMDAPPSDKROOT # AMD OpenCL SDK 环境变量
ENV INTELOCLSDKROOT # Intel OpenCL SDK 环境变量
ENV NVSDKCOMPUTE_ROOT # NVIDIA CUDA OpenCL 环境变量
ENV CUDA_PATH # NVIDIA CUDA 简化环境变量
ENV ATISTREAMSDKROOT # 旧版AMD SDK 环境变量
ENV OCL_ROOT # 通用OpenCL根目录环境变量
/usr/local/cuda # Linux默认CUDA路径
/opt/cuda # Linux备选CUDA路径
PATH_SUFFIXES # 路径后缀:在上面的PATHS后拼接这些后缀,查找头文件
include
OpenCL/common/inc
"AMD APP/include") # 兼容AMD SDK的特殊目录结构
1)PROGRAMFILES
ENV "环境变量名" 和 $ENV{环境变量名} 是差不多的
cpp
# 下面两行代码,在find_path的PATHS里,完全等价!
ENV "PROGRAMFILES(X86)"
$ENV{PROGRAMFILES(X86)}
cpp
# 下面两行代码,完全等价!
ENV AMDAPPSDKROOT
$ENV{AMDAPPSDKROOT}
前者为什么必须加双引号?
因为环境变量名是 PROGRAMFILES(X86) → 变量名中包含圆括号 () ,CMake 的语法规则是:如果环境变量名 / 路径中包含「特殊字符」(括号、空格、中横线等),必须用双引号包裹,否则 CMake 解析失败!
补充:
PROGRAMFILES变量名本身无特殊字符,加不加引号都可以:ENV PROGRAMFILES等价ENV "PROGRAMFILES"。
这是 Windows 系统原生的环境变量,不是 CMake 预定义变量,所有 Windows 电脑默认都有:
ENV "PROGRAMFILES(X86)"→ 对应路径:C:\Program Files (x86)→ 存放32 位软件 / 库ENV "PROGRAMFILES"→ 对应路径:C:\Program Files→ 存放64 位软件 / 库
2) PATH_SUFFIXES
PATH_SUFFIXES 和 PATHS 是黄金搭档,作用是:在 PATHS 的每一个路径后面,自动拼接这些后缀,再查找头文件。
比如:ENV "PROGRAMFILES" → 会自动查找 C:\Program Files\include、C:\Program Files\OpenCL/common/inc 等路径,进一步提升兼容性。
2.4.调用版本检测函数
cpp
_FIND_OPENCL_VERSION()
调用上面定义的函数,基于找到的头文件目录,自动检测 OpenCL 版本,并把版本信息写入全局变量OpenCL_VERSION_STRING/OpenCL_VERSION_MAJOR/OpenCL_VERSION_MINOR。
2.5.按【平台 + 位数】查找 OpenCL 库文件
cpp
# 只对Windows平台做特殊处理
if(WIN32)
# ✨ 32位编译 (CMAKE_SIZEOF_VOID_P=4)
if(CMAKE_SIZEOF_VOID_P EQUAL 4)
find_library(OpenCL_LIBRARY
NAMES OpenCL # 要查找的库文件名:Windows下是OpenCL.lib
PATHS # 候选路径,和头文件一致,都是OpenCL相关环境变量
ENV "PROGRAMFILES(X86)"
ENV "PROGRAMFILES"
$ENV{PROGRAMFILES${_OPENCL_x86}}/OpenCL-ICD-Loader
$ENV{PROGRAMFILES}/OpenCL-ICD-Loader
ENV AMDAPPSDKROOT
ENV INTELOCLSDKROOT
ENV CUDA_PATH
ENV NVSDKCOMPUTE_ROOT
ENV ATISTREAMSDKROOT
ENV OCL_ROOT
PATH_SUFFIXES # ✨ 32位库的专属路径后缀,精准匹配
"AMD APP/lib/x86"
lib/x86
lib/Win32
OpenCL/common/lib/Win32)
# ✨ 64位编译 (CMAKE_SIZEOF_VOID_P=8)
elseif(CMAKE_SIZEOF_VOID_P EQUAL 8)
find_library(OpenCL_LIBRARY
NAMES OpenCL
PATHS 同上...
PATH_SUFFIXES # ✨ 64位库的专属路径后缀
"AMD APP/lib/x86_64"
lib/x86_64
lib/x64
lib
OpenCL/common/lib/x64)
endif()
# Linux / Mac 平台
else()
# 32位编译
if(CMAKE_SIZEOF_VOID_P EQUAL 4)
find_library(OpenCL_LIBRARY
NAMES OpenCL # Linux/Mac下是libOpenCL.so / libOpenCL.dylib
PATHS 各种CUDA/SDK路径
PATH_SUFFIXES lib/x86 lib)
# 64位编译
elseif(CMAKE_SIZEOF_VOID_P EQUAL 8)
find_library(OpenCL_LIBRARY
NAMES OpenCL
PATHS 各种CUDA/SDK路径
PATH_SUFFIXES lib/x86_64 lib/x64 lib lib64) # Linux下64位库常用lib64
endif()
endif()
1) CMAKE_SIZEOF_VOID_P
变量值 = C/C++ 语言中 void* 指针类型的字节大小(sizeof (void*)) ,只有两个固定值,对应唯一含义,这是计算机体系的通用标准:
cpp
CMAKE_SIZEOF_VOID_P EQUAL 4 → 当前编译的是【32位(x86)】程序
CMAKE_SIZEOF_VOID_P EQUAL 8 → 当前编译的是【64位(x64/amd64)】程序
- 32 位程序:CPU 的寻址位数为 32 位 ,最大寻址空间 4GB,指针变量需要存储内存地址,因此占 4 字节
- 64 位程序:CPU 的寻址位数为 64 位 ,寻址空间远超 4GB,指针变量占 8 字节
这个规则是 C/C++ 的通用标准,CMake 只是读取了编译器的这个底层属性,这个判断方式是 CMake 中判断编译位数的「唯一官方标准」,绝对可靠,无任何兼容性问题。
cpp
# 判断是否为64位编译(你的代码中用的最多)
if(CMAKE_SIZEOF_VOID_P EQUAL 8)
# 64位专属逻辑
endif()
# 判断是否为32位编译
if(CMAKE_SIZEOF_VOID_P EQUAL 4)
# 32位专属逻辑
endif()
2) 涉及其它知识点
find_library→ 模块模式核心:查找库文件(静态库 / 动态库)- 多环境变量 / 多路径后缀 → 兼容所有 OpenCL 库的安装规范
- 优先级适配 → 先找 SDK,再找系统默认路径
2.6.封装模块模式的「标准导出变量」
cpp
# 模块模式的【强制规范】:必须导出 XXX_INCLUDE_DIRS / XXX_LIBRARIES 变量
# 兼容老旧CMake工程的变量式写法:include_directories(${OpenCL_INCLUDE_DIRS})
set(OpenCL_LIBRARIES ${OpenCL_LIBRARY})
set(OpenCL_INCLUDE_DIRS ${OpenCL_INCLUDE_DIR})
固定规范:所有FindXXX.cmake都要定义这两个变量,是模块模式的「约定俗成」。
2.7.模块模式的收尾-合法性校验
cpp
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(
OpenCL # 包名
REQUIRED_VARS OpenCL_LIBRARY OpenCL_INCLUDE_DIR # 必须找到的核心变量
VERSION_VAR OpenCL_VERSION_STRING) # 版本号变量,支持VERSION参数校验
- 检查
REQUIRED_VARS中的变量是否都有有效值 → 有则设置OpenCL_FOUND = TRUE,无则设置FALSE - 如果用户调用
find_package(OpenCL REQUIRED),且库没找到 → 直接终止 CMake 并报错 - 结合
VERSION_VAR可以支持版本筛选:find_package(OpenCL REQUIRED VERSION 2.0) - 自动处理
QUIET/REQUIRED参数,无需手动写判断逻辑
2.8.隐藏高级变量
cpp
mark_as_advanced(
OpenCL_INCLUDE_DIR
OpenCL_LIBRARY)
把这两个变量标记为「高级变量」,在 CMake-GUI/ccmake 的可视化界面中默认隐藏,只有勾选「显示高级变量」才会看到,减少界面杂乱,CMake 工程化规范。
2.9.模块模式 兼容 现代 CMake 的「导入目标」
模块模式(传统变量式) + 配置模式(现代目标式)的完美兼容,让用户既可以用老旧的
${OpenCL_LIBRARIES},也可以用推荐的target_link_libraries(xxx PRIVATE OpenCL::OpenCL)!
cpp
# 如果找到OpenCL库,且全局中没有创建过 OpenCL::OpenCL 目标 → 创建导入目标
if(OpenCL_FOUND AND NOT TARGET OpenCL::OpenCL)
# 特殊处理:MacOS下的.framework格式的库(苹果的包格式)
if(OpenCL_LIBRARY MATCHES "/([^/]+)\\.framework$")
add_library(OpenCL::OpenCL INTERFACE IMPORTED)
set_target_properties(OpenCL::OpenCL PROPERTIES
INTERFACE_LINK_LIBRARIES "${OpenCL_LIBRARY}")
# 常规情况:Windows(.lib) / Linux(.so) / Mac(.dylib) 的普通库
else()
# 创建一个「未知类型的导入库目标」:UNKNOWN IMPORTED
add_library(OpenCL::OpenCL UNKNOWN IMPORTED)
# 设置导入目标的【库文件路径】
set_target_properties(OpenCL::OpenCL PROPERTIES
IMPORTED_LOCATION "${OpenCL_LIBRARY}")
endif()
# 为导入目标设置【头文件路径】,链接时自动传递,无需手动include_directories
set_target_properties(OpenCL::OpenCL PROPERTIES
INTERFACE_INCLUDE_DIRECTORIES "${OpenCL_INCLUDE_DIRS}")
endif()
- 老旧写法:
include_directories(${OpenCL_INCLUDE_DIRS})+target_link_libraries(xxx ${OpenCL_LIBRARIES}) - 现代写法:
target_link_libraries(xxx PRIVATE OpenCL::OpenCL) - 导入目标会自动传递所有依赖属性(头文件、库路径、编译选项),是 CMake 的推荐写法,没有之一!
3.如何使用这份 FindOpenCL.cmake(2 种用法,都支持)
现代 CMake 推荐写法(导入目标,无变量):
cpp
# 设置自定义模块路径(FindOpenCL.cmake所在目录)
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_CURRENT_SOURCE_DIR})
# 查找OpenCL库
find_package(OpenCL REQUIRED)
# 直接链接导入目标,无需任何其他配置!
target_link_libraries(YourApp PRIVATE OpenCL::OpenCL)
传统 CMake 变量写法(兼容老旧工程):
cpp
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_CURRENT_SOURCE_DIR})
find_package(OpenCL REQUIRED)
# 手动设置头文件 + 链接库
include_directories(${OpenCL_INCLUDE_DIRS})
target_link_libraries(YourApp PRIVATE ${OpenCL_LIBRARIES})
4.总结
总结FindXXX.cmake的设计思想,归纳一下编写高质量 CMake 脚本的核心逻辑:
- 定义私有变量:下划线开头,存储临时数据,避免污染全局;
- 封装核心逻辑为函数:版本检测、路径处理等独立逻辑封装为函数,解耦代码;
- find_path 查找头文件 :兼容多路径、多命名规范,设置
XXX_INCLUDE_DIR; - 执行辅助逻辑:版本检测、兼容性处理等;
- CMAKE_SIZEOF_VOID_P 分位数 :按 32/64 位编译目标,用
find_library查找库文件,设置XXX_LIBRARY; - 清理私有变量:unset 临时变量,规范;
- 封装标准变量 :设置
XXX_INCLUDE_DIRS/XXX_LIBRARIES,兼容传统写法; - 合法性校验 :调用
find_package_handle_standard_args,设置XXX_FOUND; - 隐藏高级变量:mark_as_advanced,优化可视化体验;
- 创建导入目标 :
XXX::XXX,兼容现代 CMake 的目标导向写法。