CMake进阶:模块模式示例FindOpenCL.cmake详解

目录

1.简介

2.详细分析

2.1.定义私有变量

[2.2.检测 OpenCL 版本](#2.2.检测 OpenCL 版本)

[2.3.模块模式核心 - find_path 查找 OpenCL 头文件目录](#2.3.模块模式核心 - find_path 查找 OpenCL 头文件目录)

2.4.调用版本检测函数

[2.5.按【平台 + 位数】查找 OpenCL 库文件](#2.5.按【平台 + 位数】查找 OpenCL 库文件)

2.6.封装模块模式的「标准导出变量」

2.7.模块模式的收尾-合法性校验

2.8.隐藏高级变量

[2.9.模块模式 兼容 现代 CMake 的「导入目标」](#2.9.模块模式 兼容 现代 CMake 的「导入目标」)

[3.如何使用这份 FindOpenCL.cmake(2 种用法,都支持)](#3.如何使用这份 FindOpenCL.cmake(2 种用法,都支持))

4.总结


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 版本

CMake进阶: 检查函数/符号存在性、检查类型/关键字/表达式有效性和检查编译器特性

这是整个脚本的核心逻辑之一,封装了「自动检测 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_0CL_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 头文件目录

CMake指令:查找文件(find_file)、查找目录(find_path)、查找库文件(find_library)

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\includeC:\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 的目标导向写法。
相关推荐
蜜汁小强2 小时前
macOS 上管理不同版本的python
开发语言·python·macos
肥硕之虎2 小时前
从原理到实操:php://filter 伪协议玩转文件包含漏洞
开发语言·网络安全·php
a努力。2 小时前
中国电网Java面试被问:RPC序列化的协议升级和向后兼容
java·开发语言·elasticsearch·面试·职场和发展·rpc·jenkins
csbysj20202 小时前
Bootstrap4 徽章(Badges)
开发语言
码农水水2 小时前
得物Java面试被问:大规模数据的分布式排序和聚合
java·开发语言·spring boot·分布式·面试·php·wpf
AI_56782 小时前
Airflow“3分钟上手”教程:用Python定义定时数据清洗任务
开发语言·人工智能·python
大只鹅2 小时前
Stream使用
java·开发语言
Ulyanov2 小时前
PyVista三维战场仿真实战
开发语言·python·tkinter·pyvista·gui开发
Yupureki2 小时前
《算法竞赛从入门到国奖》算法基础:入门篇-离散化
c语言·数据结构·c++·算法·visual studio