如何跨平台地使用CMake ExternalProject_Add或其替代方案?

43

我希望构建一个第三方项目,该项目已经包含在我的项目的CMake中。为此可以使用ExternalProject_Add,但我发现它只能与特定生成器配合使用,并且我希望它可以轻松地在许多平台上工作。

例如,这是我的外部项目,添加了一个用于zlib的脚本,它有自己的CMakeLists.txt:

set(USE_PROJECT_CMAKE_MODULE_PATH "-DCMAKE_MODULE_PATH=${MAKE_MODULE_PATH}")
ExternalProject_Add(ZLIB
                    SOURCE_DIR ${CMAKE_SOURCE_DIR}/external/zlib
                    DOWNLOAD_COMMAND ""
                    UPDATE_COMMAND ""
                    CMAKE_ARGS
                       -DCMAKE_INSTALL_PREFIX=<INSTALL_DIR>
                       -DCMAKE_CXX_FLAGS=${CMAKE_CXX_FLAGS}
                       -DCMAKE_C_FLAGS=${CMAKE_C_FLAGS}
                       -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE}
                       ${USE_PROJECT_CMAKE_MODULE_PATH}
                    INSTALL_COMMAND "")

ExternalProject_Add_Step(ZLIB installInternally
                         COMMAND cd <BINARY_DIR> && make install
                         DEPENDEES install
                         ALWAYS 1)
ExternalProject_Get_Property(ZLIB install_dir)

if(UNIX)
    set(ZLIB_NAME libz)
else(UNIX)
    set(ZLIB_NAME zlib)
endif(UNIX)

add_library(zlib UNKNOWN IMPORTED)
set_property(TARGET zlib PROPERTY IMPORTED_LOCATION ${install_dir}/lib/${ZLIB_NAME}.a)
set(ZLIB_LIBRARIES zlib)
set(ZLIB_LIBRARIES_OPTIONAL ${ZLIB_LIBRARIES})
set(ZLIB_DIR ${install_dir} CACHE INTERNAL "zlib ROOT dir")
set(ZLIB_INCLUDE_DIRS ${install_dir}/include CACHE INTERNAL "zlib include dirs")
set(ZLIB_DEFINES "-msse2 -mfpmath=sse" CACHE INTERNAL "zlib defines")
这样做的问题在于它可以使用make,但无法在Xcode或Visual Studio中使用。也许有一种方法可以将传递给我的项目的CMake构建命令转发给ExternalProject_Add。
如何以跨平台的方式编写ExternalProject_Add调用,并尽可能减少代码复杂性?还是有更好的替代方案吗?

发现另一个可以达到这个目的的工具:https://conan.io/ - Andrew Hundt
2个回答

34

问题

-DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE}

对于单配置项目来说,这已经足够了。但是对于Xcode和Visual Studio,则需要在构建阶段设置CMAKE_CONFIGURATION_TYPES并调用build . --config。请参见我的答案

COMMAND cd <BINARY_DIR> && make install

当然,这仅适用于Makefile生成器。要实现跨平台,可以在ExternalProject_AddINSTALL_COMMAND中使用:--build . --target install --config

查看此模板文件,特别是以下代码行

ExternalProject_Add(
    "${current_project}"
    URL
    @HUNTER_PACKAGE_URL@
    URL_HASH
    SHA1=@HUNTER_PACKAGE_SHA1@
    DOWNLOAD_DIR
    "@HUNTER_PACKAGE_DOWNLOAD_DIR@"
    SOURCE_DIR
    "@HUNTER_PACKAGE_SOURCE_DIR@"
    INSTALL_DIR
    "@HUNTER_PACKAGE_INSTALL_PREFIX@"
        # Not used, just avoid creating Install/<name> empty directory
    BUILD_COMMAND ""
        # This command is empty because all necessary targets will
        # be built on install stage
    CMAKE_ARGS
    "-G@CMAKE_GENERATOR@"
    "-C@HUNTER_CACHE_FILE@"
    "-C@HUNTER_ARGS_FILE@"
    "-D${postfix_name}=${${postfix_name}}"
    "-DCMAKE_BUILD_TYPE=${configuration}"
    "-DCMAKE_CONFIGURATION_TYPES=${configuration}"
    "-DCMAKE_INSTALL_PREFIX=@HUNTER_PACKAGE_INSTALL_PREFIX@"
    "-DCMAKE_TOOLCHAIN_FILE=${CMAKE_TOOLCHAIN_FILE}"
    INSTALL_COMMAND
        "@CMAKE_COMMAND@"
        --build .
        --target install
        --config ${configuration}
        --
        ${jobs_option}
)

替代方案

或者有更好的替代方案吗?

你看过Hunter吗?

你可以像这个那样添加zlib:

hunter_add_package(ZLIB)
find_package(ZLIB CONFIG REQUIRED)
target_link_libraries(... ZLIB::zlib)

该代码适用于所有平台。第三方依赖项将在配置步骤中自动下载。以下是不同生成器/工具链的构建示例(build.py只是一个设置CMAKE_TOOLCHAIN_FILE-G/-B的CMake包装器):

build.py --toolchain mingw --config Release # MinGW Makefiles
build.py --toolchain vs-12-2013 --config Debug # Visual Studio 12 2013
build.py --toolchain xcode --config Release # Xcode
build.py --toolchain libcxx --config Release # Makefile with -stdlib=libc++ toolchain
build.py --toolchain ios-8-2 --config Release # Xcode with iOS SDK 8.2 toolchain
你可以完全控制第三方软件包的构建过程,包括选项构建类型或者并行任务数量。例如,你可以为zlib构建Debug、Release、MinSizeRel和RelWithDebInfo四种不同的构建类型,并将MinSizeRel链接到当前项目中:
> build.py --toolchain xcode --verbose --config MinSizeRel --fwd "HUNTER_CONFIGURATION_TYPES=Release;Debug;MinSizeRel;RelWithDebInfo"
/.../clang  /.../lib/libz-MinSizeRel.a ... -o /.../_builds/xcode/MinSizeRel/foo

> ls -la /.../.hunter/_Base/d1232c0/326318e/37e4682/Install/lib/libz*
   99056 /.../.hunter/_Base/d1232c0/326318e/37e4682/Install/lib/libz-MinSizeRel.a
  307872 /.../.hunter/_Base/d1232c0/326318e/37e4682/Install/lib/libz-RelWithDebInfo.a
  109536 /.../.hunter/_Base/d1232c0/326318e/37e4682/Install/lib/libz.a
  258904 /.../.hunter/_Base/d1232c0/326318e/37e4682/Install/lib/libzd.a

很酷,“--build…”很有用,你能解释一下它具体是在做什么吗?为什么构建命令是空的?我也对Hunter很感兴趣,所以我在那里创建了一个Github问题,并提出了一些问题。 - Andrew Hundt
@AndrewHundt 你能解释一下它到底在做什么吗? 它会执行几个操作,例如设置生成器 -G、构建目录(对于 xcode 工具链为 -B_builds/xcode,对于 vs-12-2013 工具链为 -B_builds/vs-12-2013)或一些环境变量。例如 mingw --config Release 等同于:set PATH=%MINGW_PATH%;%PATH% + cmake -H. -B_builds/mingw-Release "-GMinGW Makefiles" -DCMAKE_BUILD_TYPE=Release - user2288008
为什么构建命令是空的?你的意思是为什么没有“build.py --configure”的步骤?脚本会检查是否有“CMakeCache.txt”文件,然后运行CMake配置。由于CMake可以自动检测代码更改,因此接下来你只需要运行“cmake --build”即可。无论如何,“build.py”对于Hunter来说并不是必需的,它只是我发现非常有用的别名工具。 - user2288008
抱歉我的问题表述不够清晰,我想问的是为什么这个构建命令是空的。感谢您解释build.py,如果它只是简化了一些命令行参数,那实际上是一件好事! - Andrew Hundt
@AndrewHundt 我的意思是问这个构建命令为什么是空的。好的,我明白了。我已经添加了一些注释(https://github.com/ruslo/hunter/commit/892a63851222018f0b4beb262b740020c9cba4e6)。 - user2288008
显示剩余3条评论

0

CMake ExternalProject_Add 调用默认跨平台工作,并且仅在使用特定命令(仅适用于部分操作系统)时才会失败。

通常,CMAKE_ARGS 用于向外部项目构建中的每个超级构建单元传递信息。控制整个构建的每个微小部分的 CMakeLists.txt 文件使用 CMake 的声明性语法(例如,“add_library(library_name SHARED filename1.hpp filename1.cpp)”。CMake 将将此类语法转换为特定于您希望使用的特定构建系统的命令(例如,make 和 Ninja)。

上面关于 zlib 的示例部分无法跨平台,部分原因是 ExternalProject_Add_Step 包含“COMMAND cd && make install”,这仅在调用“cd”实际上是更改目录的正确方法并且调用“make”实际上是构建软件的正确方法的情况下才能正常工作。

CMake 的 -E 选项提供了一种调用基本操作(如更改/复制/创建/删除目录)而不做出任何假设的方式。

(顺便说一下,如果您使用诸如 Visual Studio 或 Xcode 等 IDE,则在使用 CMake 时可能需要调用一个或多个 IDE 生成器。例如,设置

-G "Eclipse CDT4 - Unix Makefiles" -DCMAKE_ECLIPSE_GENERATE_SOURCE_PROJECT=TRUE

这将导致在每个构建区域以及共享给所有构建的源代码区域中生成Eclipse项目。当然,如果您正在使用Xcode或Visual Studio,则必须为这些IDE替换适当的标志。或者,您可以考虑在所有平台上使用带有Ninja的Eclipse,尽管在撰写本文时,我并不完全确定Ninja是否已准备好用于非Linux、非Windows操作系统。


感谢您尝试回复,但我认为您的回复实际上与问题无关。我不是在问zlib,它只是一个例子。我特别询问如何使用cmake函数ExternalProject_Add,以便它可以跨平台工作,这样我就不需要为每个平台和配置实例化许多版本的该调用。 - Andrew Hundt
谢谢您指出我误解了您的问题。我已经修改了我的帖子,试图回答您真正想知道的内容。 - newbrific
我明白了,所以我想知道是否能用cmake -E build <BINARY_DIR>替换cd <BINARY_DIR> && make install,但我认为那样做不会执行我实际想要的安装步骤。此外,我仍然有一个问题,例如Xcode将文件放在buildDir/bin/Debug/binarybuildDir/bin/Release/binary中,而make将文件放在buildDir/bin/binary中。顺便说一下:必须考虑所有这些平台特定的差异有点令人沮丧,我希望CMake有一种更简洁的方法来处理它,我目前可能还没有发现。 - Andrew Hundt

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接