跨平台 CMake 项目结构示例

顶层只 include() 进来,最终暴露 IMPORTED 目标,最干净。
若系统里已经能 find_package() 到,就直接用系统包;否则走脚本里的 fallback,自己创建 IMPORTED 目标。


目录结构

MyApp/
├── CMakeLists.txt
├── cmake/
│   ├── openssl.cmake
│   ├── zlib.cmake
│   └── utils.cmake        # 可选(这里没用到,可放公共宏)
└── src/
    ├── CMakeLists.txt
    └── main.cpp

1) 顶层 CMakeLists.txt

cmake_minimum_required(VERSION 3.20)
project(MyApp LANGUAGES CXX)

set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

# 让 include(openssl) 之类能直接找到 cmake/ 里的脚本
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake")

# —— 第三方库:每个脚本内部自己处理 find_package/fallback,并最终提供 IMPORTED 目标
include(openssl)   # 提供 OpenSSL::SSL / OpenSSL::Crypto
include(zlib)      # 提供 ZLIB::ZLIB

add_subdirectory(src)

2) cmake/openssl.cmake

# 目标:确保存在 OpenSSL::SSL 与 OpenSSL::Crypto 两个可链接目标

# 1) 先静默尝试系统包(支持 OPENSSL_ROOT 提示)
cmake_policy(PUSH)
cmake_policy(SET CMP0074 NEW)  # 允许 <PackageName>_ROOT
find_package(OpenSSL QUIET)
cmake_policy(POP)

if(OpenSSL_FOUND)
  message(STATUS "[3rdparty] Using system OpenSSL: ${OPENSSL_VERSION}")
  # 已经有 OpenSSL::SSL / OpenSSL::Crypto,无需再做
  return()
endif()

# 2) 没找到 → fallback:基于常见安装位置或用户指定根目录自行定位
message(STATUS "[3rdparty] System OpenSSL not found, using fallback")

# 允许外部传入 OPENSSL_ROOT(优先)
set(OPENSSL_ROOT "" CACHE PATH "Root directory of OpenSSL (optional)")

# 依据平台给出一些默认 hint
set(_OPENSSL_HINTS)
if(OPENSSL_ROOT)
  list(APPEND _OPENSSL_HINTS "${OPENSSL_ROOT}")
endif()

if(WIN32)
  # 典型预编译包路径(可按需改)
  list(APPEND _OPENSSL_HINTS "C:/OpenSSL-Win64" "C:/Program Files/OpenSSL-Win64")
elseif(APPLE)
  list(APPEND _OPENSSL_HINTS "/opt/homebrew/opt/openssl@3" "/usr/local/opt/openssl@3")
else()  # Linux
  list(APPEND _OPENSSL_HINTS "/usr" "/usr/local")
endif()

# 定位头文件与库
find_path(OPENSSL_INCLUDE_DIR
  NAMES openssl/ssl.h
  HINTS ${_OPENSSL_HINTS}
  PATH_SUFFIXES include
  REQUIRED)

find_library(OPENSSL_SSL_LIBRARY
  NAMES ssl libssl
  HINTS ${_OPENSSL_HINTS}
  PATH_SUFFIXES lib lib64
  REQUIRED)

find_library(OPENSSL_CRYPTO_LIBRARY
  NAMES crypto libcrypto
  HINTS ${_OPENSSL_HINTS}
  PATH_SUFFIXES lib lib64
  REQUIRED)

# 创建 IMPORTED 目标(最简单稳妥的 UNKNOWN + IMPORTED_LOCATION)
add_library(OpenSSL::SSL UNKNOWN IMPORTED)
set_target_properties(OpenSSL::SSL PROPERTIES
  IMPORTED_LOCATION "${OPENSSL_SSL_LIBRARY}"
  INTERFACE_INCLUDE_DIRECTORIES "${OPENSSL_INCLUDE_DIR}"
)

add_library(OpenSSL::Crypto UNKNOWN IMPORTED)
set_target_properties(OpenSSL::Crypto PROPERTIES
  IMPORTED_LOCATION "${OPENSSL_CRYPTO_LIBRARY}"
  INTERFACE_INCLUDE_DIRECTORIES "${OPENSSL_INCLUDE_DIR}"
)

# 可选:把两个库互相关联(有些平台/版本需要)
target_link_libraries(OpenSSL::SSL INTERFACE OpenSSL::Crypto)

注:若在 MSVC 下你有区分 Debug/Release 的 .lib,可另外提供
IMPORTED_LOCATION_DEBUG / IMPORTED_LOCATION_RELEASE 两个属性;或直接用系统 find_package(OpenSSL)


3) cmake/zlib.cmake

# 目标:确保存在 ZLIB::ZLIB 目标

# 1) 先用系统包
find_package(ZLIB QUIET)
if(ZLIB_FOUND)
  message(STATUS "[3rdparty] Using system ZLIB: ${ZLIB_VERSION}")
  return()
endif()

# 2) 没找到 → fallback
message(STATUS "[3rdparty] System ZLIB not found, using fallback")

set(ZLIB_ROOT "" CACHE PATH "Root directory of zlib (optional)")
set(_ZLIB_HINTS)
if(ZLIB_ROOT)
  list(APPEND _ZLIB_HINTS "${ZLIB_ROOT}")
endif()
if(WIN32)
  list(APPEND _ZLIB_HINTS "C:/zlib" "C:/Program Files/zlib")
else()
  list(APPEND _ZLIB_HINTS "/usr" "/usr/local" "/opt/homebrew")
endif()

find_path(ZLIB_INCLUDE_DIR
  NAMES zlib.h
  HINTS ${_ZLIB_HINTS}
  PATH_SUFFIXES include
  REQUIRED)

find_library(ZLIB_LIBRARY
  NAMES z zlib
  HINTS ${_ZLIB_HINTS}
  PATH_SUFFIXES lib lib64
  REQUIRED)

add_library(ZLIB::ZLIB UNKNOWN IMPORTED)
set_target_properties(ZLIB::ZLIB PROPERTIES
  IMPORTED_LOCATION "${ZLIB_LIBRARY}"
  INTERFACE_INCLUDE_DIRECTORY "${ZLIB_INCLUDE_DIR}"  # 兼容旧拼写
  INTERFACE_INCLUDE_DIRECTORIES "${ZLIB_INCLUDE_DIR}"
)

4) src/CMakeLists.txt

add_executable(myapp main.cpp)

# 直接像系统包一样使用 IMPORTED 目标
target_link_libraries(myapp
  PRIVATE
    OpenSSL::SSL
    OpenSSL::Crypto
    ZLIB::ZLIB
)

5) src/main.cpp

#include <iostream>
#include <zlib.h>
#include <openssl/ssl.h>

int main() {
    std::cout << "zlib version: " << zlibVersion() << std::endl;
#if defined(OPENSSL_VERSION_TEXT)
    std::cout << "OpenSSL: " << OPENSSL_VERSION_TEXT << std::endl;
#else
    std::cout << "OpenSSL present." << std::endl;
#endif
    return 0;
}

使用方式

系统已有库(推荐)

cmake -S . -B build -G "Ninja Multi-Config"
cmake --build build --config Release

自定义根目录(fallback)

cmake -S . -B build -G "Ninja Multi-Config" \
  -DOPENSSL_ROOT="C:/OpenSSL-Win64" \
  -DZLIB_ROOT="C:/zlib"
cmake --build build --config Release

这样顶层只负责 include(openssl) / include(zlib),每个子脚本内部自己处理 find_package() 与 fallback,并把 一致的 IMPORTED 目标暴露给项目使用;新增库时只需在 cmake/ 里再加一个 xxx.cmake 即可,风格统一、无命名冲突。

posted @ 2025-08-13 15:31  丘狸尾  阅读(19)  评论(0)    收藏  举报