假设我有一个包含ExternalProject目标的CMake项目。该目标的BUILD_COMMAND执行一个shell脚本。
# demo/CMakeLists.txt
cmake_minimum_required(VERSION 3.14.1)
project(demo)
include(ExternalProject)
ExternalProject_Add(demo
SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}"
CONFIGURE_COMMAND ""
BUILD_COMMAND bash "${CMAKE_CURRENT_SOURCE_DIR}/test.sh"
INSTALL_COMMAND ""
BUILD_ALWAYS TRUE
)
该脚本调用make:
# demo/test.sh
cd $(dirname "${BASH_SOURCE[0]}")
make
它执行一个简单的Makefile:
# demo/Makefile
demo:
echo demo
这在并发构建exeternal项目时会产生以下警告消息(mkdir build && cd build && cmake .. && make -j8
):
jobserver unavailable: using -j1. Add `+' to parent make rule.
我从manual中读到,如果一个规则包含$(MAKE)
,那么它会得到特殊处理。因此,我将$(MAKE)
添加到BUILD_COMMAND的末尾,以尝试删除警告:BUILD_COMMAND bash "${CMAKE_CURRENT_SOURCE_DIR}/test.sh" $(MAKE)
即使实际上没有使用新添加的参数,警告也会消失。
但是现在我想在python中重新实现bash脚本:
# demo.py
from pathlib import Path
import subprocess as sp
SCRIPT_DIR = Path(__file__).resolve().parent
sp.run(['make'], cwd=SCRIPT_DIR)
并相应地更改BUILD_COMMAND:BUILD_COMMAND python3 "${CMAKE_CURRENT_SOURCE_DIR}/test.py" $(MAKE)
返回的警告带有或不带有最后一个参数$(MAKE)
,这是不期望的。
那么,如何解释这种不一致的行为呢?添加$(MAKE)
参数是消除该警告的可接受的解决方法吗?为什么这种变通方法只适用于bash而不适用于python?
所涉及的真实的项目要比上面的演示复杂得多。但关键思想是一样的:一个外部项目执行bash脚本来构建调用make而没有警告的软件,然后所有内容都用python重写,警告出现了,我不知道如何让它们消失。
我还尝试了BUILD_COMMAND -> bash script -> python script -> make,即修改原来的bash脚本,通过python脚本调用make,而不是直接调用make。在添加额外的python层后出现警告。所以我想知道是否有一些python做或不做的事情使事情有所不同。当python调用子make时,是什么原因导致子make无法从父make接收到手册中提到的完整信息?
我还尝试在原始bash/python脚本中向make命令添加-j8
参数。这两个脚本都显示了带有或不带有$(MAKE)
参数的警告,只是这次的警告消息是-jN forced in submake: disabling jobserver mode
,这似乎与手册中所说的相符,因此也符合预期。
我真的很想知道如何使python脚本的警告消失。
1条答案
按热度按时间wnavrhmk1#
你应该使用
subprocess.run(..., close_fds=False)
。默认值是close_fds=True
,根据Python的文档:如果
close_fds
为true,则除了0
、1
和2
之外的所有文件描述符都将在子进程执行之前关闭。make
打开一些内部FD,这些FD通常会继承到其子品牌,这些FD用于与其子品牌通信。使用subprocess.run()
的默认close_fds=True
,这些FD将被关闭,因此子make无法看到这些FD,因此父make无法与其子make通信。Bash(我猜还有所有合理的shell)在启动子进程之前不会关闭FD。这就是为什么Bash和Python的
subprocess.run()
之间存在差异。另一种解决方案是使用Python的
os.system()
,它“通过调用标准C函数system()
实现”,它在内部简单地调用sh -c
。