如何以跨平台的方式自动下载C++依赖项 + CMake?

33

我想要实现以下工作流程:

  1. 从Windows系统(或其他任何平台)的代码库中检出。

  2. 运行某些工具以获取依赖项,包括库和头文件,并将它们放置在正确的位置(例如在Windows上:"\Program Files\Microsoft Visual Studio 10.0\VC\Lib" 和 "\Includes" 文件夹中)。

  3. 运行CMake(在Windows上创建MSVS项目)。

  4. 打开MSVS项目并进行编译。

我希望可以在多个平台上使用这个工作流程。

我不想手动下载依赖项。

如何在不将依赖项存储在代码库中的情况下实现这一目标?有什么最佳解决方案?


2
你解决问题了吗?我一直在寻找类似npm的东西来构建具有依赖关系的C / C ++程序。 - hg.
据我所知,目前还没有适用于C++的跨平台软件包管理器。 - JBeurer
4个回答

33

CMake中可以使用file(DOWNLOAD URL PATH)下载文件,结合自定义命令来下载和解压:

set(MY_URL "http://...")
set(MY_DOWNLOAD_PATH "path/to/download/to")
set(MY_EXTRACTED_FILE "path/to/extracted/file")

if (NOT EXISTS "${MY_DOWNLOAD_PATH}")
    file(DOWNLOAD "${MY_URL}" "${MY_DOWNLOAD_PATH}")
endif()

add_custom_command(
    OUTPUT "${MY_EXTRACTED_FILE}"
    COMMAND command to unpack
    DEPENDS "${MY_DOWNLOAD_PATH}")

你的目标应该依赖于自定义命令的输出,然后当你运行CMake时,该文件将会被下载,而在构建时,该文件将被提取并使用。

这一切都可以打包成一个宏以使其更易于使用。

你也可以考虑使用CMake模块ExternalProject,它可能会满足你的需求。


1
但这并不是一种跨平台的设置和安装依赖项的方法,也不能从公共存储库下载依赖项。 - JBeurer
使用CMake的file(DOWNLOAD ...)或ExternalProject是跨平台的。如果依赖项是软件包,则在Windows上可以使用msiexec进行MSI软件包安装;在Linux上,这取决于发行版:对于Red Hat衍生版本,使用RPM软件包的yumrpm,对于Debian衍生版本,使用DEB软件包的aptdpkg - Silas Parker
是的,下载命令是跨平台的,但这不是一种跨平台安装依赖项的方式。每个平台通常针对相同的依赖项有不同的文件和软件包(因此也有不同的下载链接)。你可以说我正在寻找像 Maven 一样适用于 C++ 依赖项并能与 CMake 很好地配合的工具。 - JBeurer
2
@JBeurer 如果您实际上需要预先安装这些依赖项,那么这可能根本不是一个好主意;相反,只需在安装说明中添加软件所需的任何依赖项,并让用户手动安装它。对于那些想要自动化一切的人来说,这可能听起来并不令人兴奋,但这绝对是更安全的方式,更重要的是,它有效 - hiobs
@skyhisi 实际上,对于 FILE(DOWNLOAD....,第二个参数是文件,因此您无法指定路径。 - Rodolfo

9

从cmake 3.11开始,有一个新的功能:FetchContent

您可以在配置期间使用它来获取依赖项,例如获取强大的cmake-scripts

include(FetchContent)

FetchContent_Declare(
  cmake_scripts
  URL https://github.com/StableCoder/cmake-scripts/archive/master.zip)
FetchContent_Populate(cmake_scripts)
message(STATUS "cmake_scripts is available in " ${cmake_scripts_SOURCE_DIR})

我更喜欢获取压缩的源代码而不是直接签出。但是FetchContent也允许定义一个git仓库。

但它不会自动解决传递依赖关系。 - followait
第一行中的链接文档建议使用FetchContent_MakeAvailable()而不是FetchContent_Populate()。 - Kevin Yin

4

在CMake的世界里:

vcpkg

vcpkg是一个用于Windows、Linux和macOS的C++库管理器。它可以与CMake无缝集成 - 有关详细信息,请参见此处

Conan

Conan是一个C/C++包管理器。它还具有与CMake集成的策略

CMake with ExternalProject_Add

CMakeList.txt.in:

cmake_minimum_required(VERSION 2.8.2)

project(googletest-download NONE)

include(ExternalProject)
ExternalProject_Add(googletest
  GIT_REPOSITORY    https://github.com/google/googletest.git
  GIT_TAG           master
  SOURCE_DIR        "${CMAKE_BINARY_DIR}/googletest-src"
  BINARY_DIR        "${CMAKE_BINARY_DIR}/googletest-build"
  CONFIGURE_COMMAND ""
  BUILD_COMMAND     ""
  INSTALL_COMMAND   ""
  TEST_COMMAND      ""
)

CMakeList.txt:

cmake_minimum_required(VERSION 3.8)

# Download and unpack googletest at configure time
configure_file(CMakeLists.txt.in googletest-download/CMakeLists.txt)
execute_process(COMMAND ${CMAKE_COMMAND} -G "${CMAKE_GENERATOR}" .
  RESULT_VARIABLE result
  WORKING_DIRECTORY ${CMAKE_BINARY_DIR}/googletest-download )
if(result)
  message(FATAL_ERROR "CMake step for googletest failed: ${result}")
endif()
execute_process(COMMAND ${CMAKE_COMMAND} --build .
  RESULT_VARIABLE result
  WORKING_DIRECTORY ${CMAKE_BINARY_DIR}/googletest-download )
if(result)
  message(FATAL_ERROR "Build step for googletest failed: ${result}")
endif()

# Prevent overriding the parent project's compiler/linker
# settings on Windows
set(gtest_force_shared_crt ON CACHE BOOL "" FORCE)

# Add googletest directly to our build. This defines
# the gtest and gtest_main targets.
add_subdirectory(${CMAKE_BINARY_DIR}/googletest-src
                 ${CMAKE_BINARY_DIR}/googletest-build)

# The gtest/gtest_main targets carry header search path
# dependencies automatically when using CMake 2.8.11 or
# later. Otherwise we have to add them here ourselves.
if (CMAKE_VERSION VERSION_LESS 2.8.11)
  include_directories("${gtest_SOURCE_DIR}/include")
endif()

# Now simply link against gtest or gtest_main as needed. Eg
add_executable(example example.cpp)
target_link_libraries(example gtest_main)
add_test(NAME example_test COMMAND example)

example.cpp

#include <iostream>

#include "gtest/gtest.h"

TEST(sample_test_case, sample_test)
{
    EXPECT_EQ(1, 1);
}

在CMake的范围之外:

我建议你不要使用CMake!使用Bazel

例如,如果你想使用gtest

工作区

workspace(name = "GTestDemo")

load("@bazel_tools//tools/build_defs/repo:git.bzl", "git_repository")

git_repository(
    name = "googletest",
    #tag = "release-1.8.1",
    commit = "2fe3bd994b3189899d93f1d5a881e725e046fdc2",
    remote = "https://github.com/google/googletest",
    shallow_since = "1535728917 -0400",
)

构建

cc_test(
    name = "tests",
    srcs = ["test.cpp"],
    copts = ["-isystem external/gtest/include"],
    deps = [
        "@googletest//:gtest_main",
    ],

)

text.cpp

#include <iostream>

#include "gtest/gtest.h"

TEST(sample_test_case, sample_test)
{
    EXPECT_EQ(1, 1);
}

如何运行测试?
bazel test //...

例如,如果您想使用boost

工作区

workspace(name = "BoostFilesystemDemo")

load("@bazel_tools//tools/build_defs/repo:git.bzl", "git_repository")

# Fetch Boost repo
git_repository(
    name = "com_github_nelhage_rules_boost",
    commit = "49066b7ccafce2609a3d605e3667af3f07e8547c",
    remote = "https://github.com/Vertexwahn/rules_boost",
    shallow_since = "1559083909 +0200",
)

load("@com_github_nelhage_rules_boost//:boost/boost.bzl", "boost_deps")

boost_deps()

建立

cc_binary(
    name = "FilesystemTest",
    srcs = ["main.cpp"],
    defines = ["BOOST_ALL_NO_LIB"],
    deps = [
        "@boost//:filesystem",
    ],
)

main.cpp

#include <iostream>
#include <boost/filesystem.hpp>

using namespace boost::filesystem;

int main(int argc, char* argv[])
{
    if (argc < 2)
    {
        std::cout << "Usage: tut1 path\n";
        return 1;
    }
    std::cout << argv[1] << " " << file_size(argv[1]) << '\n';
    return 0;
}

如何构建:
bazel build //...

如何运行:
bazel run //:FilesystemTest

如果您想生成一个Visual Studio解决方案,请使用lavender。不幸的是,lavender只是实验性的,并且需要一些改进。但我认为,在这里花费努力比让CMake与所有依赖项配合工作更有意义。还有一些项目试图实现Bazel CMake互操作性。

-59

实现这一点的最佳方法是消除您的依赖关系。

依赖关系是有害的。

消除它们而不是依赖它们。

说真的。

您不想手动下载它们,也不想将它们存储在您的存储库中,您的客户也不想为您下载它们。事实上,您的编译器甚至不想编译它们。

与其添加C++库依赖项,不如切换到Java...

同时,建议查看CMake的ExternalProject模块,这是基于CMake的构建中最接近非存储库自动依赖项下载、配置、构建和安装的方法。


10
不行,不能做。转换到Java?不。不。不。求上苍不要啊啊啊啊! - JBeurer
11
我认为这是一个有趣的观点;然而,它的措辞像在挑起争端,而且没有考虑用户的需求。 - pope
2
我猜一个人的幽默是另一个人的"引战贴"。而且我认为在我的回答和其他得到更多赞的回答中,对ExternalProject的引用是最接近满足用户需求的东西了。这里没有什么魔法般的解决办法;C++没有像Maven那样的银弹。如果http://ryppl.org项目真的成为现实,它将超越ExternalProject。目前来说,这是我能给出的最好的指引。 - DLRdave
4
点赞了。到目前为止,有6个人看到了有趣的一面,有25个人没有。 - brettwhiteman
1
让我们一遍又一遍地从头开始重新创建SDL2和IMGUI。 - IC_
显示剩余3条评论

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