在 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。
您尚未指定您的<whatever>
下载命令的作用,但我假设它负责确保库在执行时将存在于/path/to/install/lib/libwhatever.a
。您也可以尝试将DOWNLOAD_COMMAND
设为空,并将<whatever>
改为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
中使用 <INSTALL_DIR>
、<BINARY_DIR>
等,这些都记录在 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_package
和 find_library
命令可以处理依赖项。
现在你使用什么生成器都没有关系了。
【讨论】:
非常想在构建时下载,而不是配置时。以上是关于在 Ninja 中使用 ExternalProject 下载步骤的主要内容,如果未能解决你的问题,请参考以下文章
在 Ninja 中使用 ExternalProject 下载步骤
[openharmony]liteos-a系统编译之ninja