CMake ExternalProject 不解压依赖项

CMake ExternalProject does not unpack dependencies

我正在构建一个应用程序,它使用 tinyxml2 和我在 [=20] 中作为 .zip 文件提供的其他一些依赖项(即 IrrlichtIrrKlang) =] 我项目的子目录:

.
├── CMakeLists.txt
├── Dependencies
│   ├── irrKlang-1.5.0.zip
│   ├── irrKlang-32bit-1.5.0.zip
│   ├── irrKlang-64bit-1.5.0.zip
│   ├── irrlicht-1.8.4.zip
│   └── tinyxml2-master.zip
├── Editor
│   ├── CMakeLists.txt
│   └── Sources
│       └── main.cpp
└── Game
    ├── CMakeLists.txt
    └── Sources
        └── main.cpp

注意: 供参考,完整的资源可用 on GitHub,这里我偷工减料来提出问题更短。

顶层CMakeFiles.txt设置为:

cmake_minimum_required(VERSION 3.16 FATAL_ERROR)

project(shoot-them VERSION 1.0.1 LANGUAGES CXX)

include(ExternalProject)

# few platform-specific variables like MAKE_COMMAND, PLATFORM_ARCH, etc.

# libraries (see below)

add_subdirectory(Game)
add_subdirectory(Editor

IrrlichtIrrKlang 都带有针对 Windows x86 的预构建库,但不针对 Windows x64 和 OSX。因此我将它添加为这样的依赖项(使用 if(NOT IRRLICHT_LIBRARY_PATH) 只是将代码分成一个块:

if(NOT IRRLICHT_LIBRARY_PATH)
    ExternalProject_Add(irrlicht-dep
        URL ${CMAKE_CURRENT_LIST_DIR}/Dependencies/irrlicht-1.8.4.zip
        PREFIX Dependencies/irrlicht
        SOURCE_SUBDIR source/Irrlicht
        CONFIGURE_COMMAND ""
        BUILD_COMMAND "${MAKE_COMMAND}"
        INSTALL_COMMAND ""
    )

    ExternalProject_Get_Property(irrlicht-dep SOURCE_DIR)
    set(IRRLICHT_PATH ${SOURCE_DIR})

    add_library(irrlicht SHARED IMPORTED GLOBAL)

    set_target_properties(
        irrlicht PROPERTIES
        IMPORTED_LOCATION ${IRRLICHT_PATH}/lib/${IRRLICHT_PATH_SUFFIX}/Irrlicht${CMAKE_LINK_LIBRARY_SUFFIX}
        IMPORTED_IMPLIB ${IRRLICHT_PATH}/lib/${IRRLICHT_PATH_SUFFIX}/Irrlicht${CMAKE_IMPORT_LIBRARY_SUFFIX}
        INTERFACE_INCLUDE_DIRECTORIES ${IRRLICHT_PATH}/include
    )
endif()

我遵循与 IrrKlang 相同的原则。但是由于 tinyxml2 作为头文件和源文件出现,并且它与 CMakeLists.txt 打包在一起,我只是像这样包含它:

ExternalProject_Add(tinyxml2-dep
    URL ${CMAKE_CURRENT_LIST_DIR}/Dependencies/tinyxml2-master.zip
    PREFIX Dependencies/tinyxml2
    INSTALL_COMMAND ""
)

ExternalProject_Get_Property(tinyxml2-dep SOURCE_DIR)
set(TINYXML2_PATH ${SOURCE_DIR})

add_subdirectory(${TINYXML2_PATH})

我定义GameEditor两个子项目如下:

cmake_minimum_required(VERSION 3.16 FATAL_ERROR)

project(Editor VERSION 1.0.1 LANGUAGES CXX)

set(EXECUTABLE_NAME Editor)

set(SOURCES Sources/main.cpp)

# platform-specific variables

set_target_properties(${EXECUTABLE_NAME} PROPERTIES
    CXX_STANDARD 17
    CXX_STANDARD_REQUIRED ON
    CXX_EXTENSIONS ON
)

add_dependencies(${EXECUTABLE_NAME} tinyxml2 irrlicht)

target_link_libraries(${EXECUTABLE_NAME} PUBLIC ${LIBRARIES} tinyxml2 irrlicht)

if(NOT APPLE)
    # Copy libraries' DLLs
    add_custom_command(
        TARGET ${EXECUTABLE_NAME} POST_BUILD
        COMMAND ${CMAKE_COMMAND} -E copy_if_different $<TARGET_FILE:tinyxml2> $<TARGET_FILE_DIR:${EXECUTABLE_NAME}>
    )
endif()

# TODO: copy DLLs, not LIBs
add_custom_command(
    TARGET ${EXECUTABLE_NAME} POST_BUILD
    COMMAND ${CMAKE_COMMAND} -E copy_if_different $<TARGET_FILE:irrlicht> $<TARGET_FILE_DIR:${EXECUTABLE_NAME}>
)

我具体使用ExternalProject的方式是因为

  1. 我想修复我构建项目时使用的版本
  2. 我不想安装库污染我的系统(因此没有 INSTALL 步骤,尽管我可能完全误解了这个概念)
  3. 我不依赖于 3rd 方存储库已死(因此将 ZIP 文件与源一起发送)
  4. 我不依赖令人难以置信的过时(因此可能无法工作)查找 CMake 模块

公平地说:我不知道使用 CMake 构建项目的任何最佳实践,所以我很可能对以上所有内容完全错误,所以请纠正我.

当我在 Visual Studio 2019 年 Windows 构建这个项目时,它就像一个魅力。但是每当我尝试在 OSX 上构建东西时,我都会失败:

  1. none 的依赖项甚至被解压
  2. (因为第 1 页) 永远不会设置 ${TINYXML2_DIR}
  3. (因为 p.2) 无法找到 tinyxml2 目录,因此通过 add_subdirectory()
  4. 添加
  5. (因为第 3 页) $<TARGET_FILE:tinyxml2> 表达式不计算
  6. (作为全局结果) 项目未构建

我构建项目的方式比较简单:

cmake -Bbuild -H. && cmake --build build

我做错了什么?

此外,使用 CMake 处理第三方依赖项的正确方法是什么?

我非常清楚 CMake 技术上 只是一个 makefile(粗略地说,因为每个构建工具链都不同)生成器,所以我的问题更多是关于如何告诉 CMake 为应该用我的项目构建的每种类型的依赖项生成正确的构建文件(预构建,使用 CMake 从源构建,使用自定义命令从源构建) .我原以为 ExternalProject 应该可以解决这个问题,但显然在这个过程中出现了可怕的错误。

我已经尝试过@Mizux 在 中建议的两种解决方案,并通过两种不同的方法取得了一些成功。

1。 vcpkg

这可以说是两者中较容易的一个。它需要安装 vcpkg

例如参见this commit

创建清单文件,vcpkg.json在项目根目录下,列出项目使用的所有依赖项:

{
    "name": "PROJECT_NAME",
    "version-string": "0.1.0",
    "dependencies": [
        "irrlicht",
        "tinyxml2"
    ]
}

您还可以使用 CLI 通过使用 vcpkg install 命令生成清单。

使用 CMake 中的 find_package 到每个目标的 link 库 - 在子 CMakeLists.txt:

find_package(irrlicht CONFIG REQUIRED)
find_package(tinyxml2 CONFIG REQUIRED)

target_link_libraries(${EXECUTABLE_NAME} PUBLIC ${LIBRARIES} tinyxml2::tinyxml2 Irrlicht)

重要:配置项目时,将路径传递给vcpkg CMake模块:

cmake -S . -B build -DCMAKE_TOOLCHAIN_FILE=[vcpkg root]/scripts/buildsystems/vcpkg.cmake

2。 FetchContent + ExternalProject

由于我的项目依赖于 CMakeLists.txt 附带的库和不附带的库,所以这是一个很好的例子。

例如参见this commit

首先,正如@Mizux 提到的,FetchContent 在配置时工作——它会在您配置项目时下载并解压缩依赖项(调用 cmake -S . -B build)。然后,由于 irrlicht 不随 CMakeLists.txt 一起提供,您可以使用 ExternalProject_Add 通过自定义命令构建它(在我的例子中是 make )或将其添加为 sub-directory 到项目。

FetchContent部分:

include(FetchContent)

FetchContent_Declare(irrlicht
    URL ${CMAKE_CURRENT_LIST_DIR}/Dependencies/irrlicht-1.8.4.zip
)

FetchContent_GetProperties(irrlicht)

if(NOT irrlicht_POPULATED)
    FetchContent_Populate(irrlicht)
endif()

set(IRRLICHT_PATH ${irrlicht_SOURCE_DIR})

ExternalProject部分:

include(ExternalProject)

if(NOT WIN32)
    ExternalProject_Add(irrlicht-dep
        SOURCE_DIR "${IRRLICHT_PATH}/source/Irrlicht"
        BUILD_IN_SOURCE true
        CONFIGURE_COMMAND ""
        BUILD_COMMAND "${MAKE_COMMAND}"
    )
endif()

add_library(irrlicht SHARED IMPORTED GLOBAL)

set_target_properties(
    irrlicht PROPERTIES
    IMPORTED_LOCATION ${IRRLICHT_PATH}/lib/${IRRLICHT_PATH_SUFFIX}/Irrlicht${CMAKE_LINK_LIBRARY_SUFFIX}
    IMPORTED_IMPLIB ${IRRLICHT_PATH}/lib/${IRRLICHT_PATH_SUFFIX}/Irrlicht${CMAKE_IMPORT_LIBRARY_SUFFIX}
    INTERFACE_INCLUDE_DIRECTORIES ${IRRLICHT_PATH}/include
)

注意 ExternalProject 配置中的重要位:

  • irrlicht_SOURCE_DIR 变量由 FetchContent
  • 填充
  • SOURCE_DIR "${irrlicht_SOURCE_DIR}/source/Irrlicht" - 告诉 ExternalProject 解压源 在哪里;如果这个目录不为空(它应该不是,因为 FetchContent 应该在项目配置时填充它),ExternalProject 将跳过下载阶段
  • BUILD_IN_SOURCE true - 从源目录中构建
  • CONFIGURE_COMMAND "" - 跳过依赖配置阶段,所以不会为依赖
  • 执行cmake
  • BUILD_COMMAND "make" - 使用特定命令从源构建依赖项
  • if(NOT WIN32) - 仅使用 ExternalProject 构建 依赖;因为 Irrlicht 带有 pre-built 用于 Win32 的库,这应该为其他平台构建东西(包括 Win64