在 Ninja 中使用 ExternalProject 下载步骤

Posted

技术标签:

【中文标题】在 Ninja 中使用 ExternalProject 下载步骤【英文标题】:Using an ExternalProject download step with Ninja 【发布时间】:2018-05-17 21:13:41 【问题描述】:

这似乎是一个没有明确答案的常见问题。

情况是:当构建依赖它的目标时,我们想要在构建时安装第 3 方依赖项。大致是这样的:

ExternalProject_Add(target-ep
    DOWNLOAD_COMMAND <whatever>
    BUILD_COMMAND ""
    INSTALL_COMMAND ""
    CONFIGURE_COMMAND "")

add_library(target-imp STATIC IMPORTED)
set_target_properties(target-imp PROPERTIES
    INTERFACE_INCLUDE_DIRECTORIES /path/to/install/include
    IMPORTED_LOCATION /path/to/install/lib/libwhatever.a)

add_library(target INTERFACE)
target_link_libraries(target INTERFACE target-imp)
add_dependencies(target target-ep)

(因为cmake issue 15052,这里探戈需要三个)

当使用 Unix Makefiles 作为生成器时,效果很好。仅按需安装依赖项,所有构建都正常工作。

但是,在 Ninja 上,这会立即失败,如下所示:

ninja: error: '/path/to/install/lib/libwhatever.a', needed by 'something', missing and no known rule to make it

这是因为 Ninja 扫描依赖项的方式与 Make 不同(请参阅 ninja issue 760)。所以我们要做的实际上就是告诉 Ninja 这个外部依赖是存在的。我们可以这样做:

ExternalProject_Add(target-ep
    DOWNLOAD_COMMAND <whatever>
    BUILD_BYPRODUCTS /path/to/install/lib/libwhatever.a
    BUILD_COMMAND ""
    INSTALL_COMMAND ""
    CONFIGURE_COMMAND "")

不幸的是,这也失败了:

No build step for 'target-ep'ninja: error: mkdir(/path/to/install): Permission denied

这是因为我的下载步骤有权写入该路径,但无论 mkdir 命令由底层 add_custom_command()ExternalProject_Add() 运行,都没有。

所以:

    这可能在所有与 Ninja 和 CMake 吗? (版本不是问题,如果能解决问题,我可以使用最新的 CMake) 如果有某种方法可以解决明确列出BUILD_BYPRODUCTS 的问题,有没有一种方法可以简单地传达将要安装的整个目录是副产品?也就是说,/path/to/install/* 是副产品?

【问题讨论】:

您指定了构建副产品,但给出了一个空的构建命令。如果您让构建步骤执行某些操作,错误会改变吗? @CraigScott 我尝试将echo hi 用作BUILD_COMMAND。我的忍者输出现在更友好了,但仍然功能失调。 如果我的IMPORTED_LOCATION 中有错字,我只能重现您原来的问题。我无法重现权限问题。也许如果您可以发布一个重现您的问题的最小工作示例,我们可以从那里进行调查。 @CraigScott 这绝对与打字错误无关。这对 make 非常有用,如果我预先运行下载步骤,它会非常有用。很难发布一个演示权限问题的最小示例 - 需要从权限问题开始...... 【参考方案1】:

ExternalProject 的隐藏mkdir 步骤(所有其他步骤直接或间接依赖)总是尝试创建完整的目录集,即使它们不会被使用。你可以看到这个here。作为参考,它这样做:

ExternalProject_Add_Step($name mkdir
  COMMENT "Creating directories for '$name'"
  COMMAND $CMAKE_COMMAND -E make_directory $source_dir
  COMMAND $CMAKE_COMMAND -E make_directory $binary_dir
  COMMAND $CMAKE_COMMAND -E make_directory $install_dir
  COMMAND $CMAKE_COMMAND -E make_directory $tmp_dir
  COMMAND $CMAKE_COMMAND -E make_directory $stamp_dir$cfgdir
  COMMAND $CMAKE_COMMAND -E make_directory $download_dir
  COMMAND $CMAKE_COMMAND -E make_directory $log_dir   # This one only since CMake 3.13
  )

Unix 系统上的默认安装位置可能是/usr/local,所以如果您没有对它尝试创建的所有目录的写入权限,那么这可能与您的问题有关。我建议您检查每个位置的权限,并确保它们已经存在或可写。或者,您可以指定构建树本地的安装目录,这样即使不使用它,也至少可以始终创建它(参见下面的示例)。

如果你使用 Ninja,它的依赖检查会比 make 更严格。你有target-ep 进行提供libwhatever.a 的下载,所以你需要BUILD_BYPRODUCTS 告诉忍者target-ep 是创建该文件的原因。正如您所发现的,如果您不这样做,那么target-imp 将指向一个最初不存在的库,并且 Ninja 正确地抱怨它丢失并且它不知道如何创建它。如果您提供BUILD_BYPRODUCTS,那么构建步骤不应该为空是有道理的,因此您可能需要做一些事情作为构建步骤,即使它只是一个BUILD_COMMAND,实际上并没有做任何有意义的事情。

target-ep 的以下修改后的定义有望为您提供帮助:

ExternalProject_Add(target-ep
    INSTALL_DIR $CMAKE_CURRENT_BUILD_DIR/dummyInstall
    DOWNLOAD_COMMAND <whatever>
    BUILD_BYPRODUCTS /path/to/install/lib/libwhatever.a
    BUILD_COMMAND $CMAKE_COMMAND -E echo_append
    INSTALL_COMMAND ""
    CONFIGURE_COMMAND "")

您最初的问题也会导致对错误目标的依赖。 target-imp 应该依赖于 target-ep,但你有 target 依赖于 target-ep。正确的依赖关系可以这样表达:

 add_dependencies(target-imp target-ep)

使用BUILD_BYPRODUCTS 选项,Ninja 已经知道上述依赖关系,但其他生成器需要它,包括 make。

您尚未指定您的&lt;whatever&gt; 下载命令的作用,但我假设它负责确保库在执行时将存在于/path/to/install/lib/libwhatever.a。您也可以尝试将DOWNLOAD_COMMAND 设为空,并将&lt;whatever&gt; 改为BUILD_COMMAND

解决您的具体问题:

    Ninja 和 CMake 是否可以做到这一点? (版本不是问题,如果能解决问题,我可以使用最新的 CMake)

是的,我验证了上述方法适用于 Ninja 1.8.2,用于使用 CMake 3.11.0 在 macOS 上的虚拟测试项目。我希望它可以与 CMake 3.2 或更高版本一起使用(那时添加了对 BUILD_BYPRODUCTS 选项的支持)。

    如果有某种方法可以解决明确列出 BUILD_BYPRODUCTS 的问题,是否有一种方法可以简单地说明将要安装的整个目录是副产品?也就是说,/path/to/install/* 是副产品?

不太可能。 Ninja 怎么会知道这样一个目录中的预期内容?获得可靠依赖项的唯一方法是明确列出预期存在的每个文件,在您的情况下使用 BUILD_BYPRODUCTS

【讨论】:

添加缺少的BUILD_BYPRODUCTS 为我解决了这个问题。请注意,您可以在 ExternalProject_Add 中使用 &lt;INSTALL_DIR&gt;&lt;BINARY_DIR&gt; 等,这些都记录在 ExternalProject_Add 文档中。【参考方案2】:

如果您愿意在配置时下载,可以关注此post。它使用 google-test 作为示例,但我对其他依赖项使用了相同的技术。只需将您的ExternalProject 代码放在一个单独的文件中,例如“CMakeLists.txt.dependencies”,然后使用execute_process 启动另一个cmake。我首先使用configure_file 将配置信息注入到外部项目中,并将其复制到构建树中。

configure_file(CMakeLists.txt.dependency.in dependency/CMakeLists.txt)
execute_process(COMMAND "$CMAKE_COMMAND" -G "$CMAKE_GENERATOR" .
        WORKING_DIRECTORY "$CMAKE_BINARY_DIR/dependency" )
execute_process(COMMAND "$CMAKE_COMMAND" --build .
        WORKING_DIRECTORY "$CMAKE_BINARY_DIR/dependency" )

我在配置时执行此操作,因此 find_packagefind_library 命令可以处理依赖项。

现在你使用什么生成器都没有关系了。

【讨论】:

非常想在构建时下载,而不是配置时。

以上是关于在 Ninja 中使用 ExternalProject 下载步骤的主要内容,如果未能解决你的问题,请参考以下文章

VS2013中使用ninja构建WebRTC并调试

在 Ninja 中使用 ExternalProject 下载步骤

[openharmony]liteos-a系统编译之ninja

Ninja 框架使用 Maven 安装错误

在 macOS 上使用 Ninja 进行 CMake GUI

Ninja嘛意思?