如何在CMake构建中每次运行Python脚本?

7

目前我需要在每次运行CMake时运行一个Python脚本,该脚本生成一个.qrc文件。我不能使用Qt Designer,必须使用CMake。 正在使用set(CMAKE_AUTORCC ON),但是每当添加资源或更改名称时都会失败,因此需要使用python脚本。脚本本身已经生成了输出,一切都正常工作,因此我不需要来自CMake本身的输出。我目前尝试了以下方法:

include(FindPythonInterp)
set (py_cmd "QRC_Updater.py")
execute_process(
                  COMMAND ${PYTHON_EXECUTABLE} ${py_cmd}
                  WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}
                  RESULT_VARIABLE py_result
               )
message(STATUS "Python result: ${py_result})

它可以工作,但并非每次都执行。它只在修改CMakeLists.txt时执行。

因此,在一些搜索后,人们建议使用

add_custom_target(...)

并且

add_custom_command(...)

我也尝试了这个:

add_custom_target(
   always_run_target ALL
   DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/__header.h
   )

add_custom_command(
    OUTPUT
        ${CMAKE_CURRENT_BINARY_DIR}/__header.h
        ${CMAKE_CURRENT_BINARY_DIR}/header.h
    COMMAND ${PYTHON_EXECUTABLE} ${py_cmd}
    )

这个问题与脚本根本没有运行有关。我知道脚本没有运行,因为我在Notepad++中打开了该文件,它也没有要求我切换到新版本的文件,就像在execute_process()运行后那样。其他变化的add_custom_command()也不会运行该脚本。在此运行时没有任何错误,除非我不包含${PYTHON_EXECUTABLE},这会导致“%1不是有效的Win32命令”。那么,如何在CMake中使Python脚本每次都能有效运行?

编辑: 这里的答案都不起作用。 如何在构建时始终运行命令,而不管任何依赖关系?


1
我实际上在 add_custom_command 中使用了 WORKING_DIRECTORY,并得到了相同的结果。脚本从未运行。 - Anthony
@Tsyvarev 我也试过那个方法。脚本从未运行。如果你看评论,它展示了我已经尝试过的几乎一模一样的方法。那个问题的 OP 也说它不可靠。 - Anthony
好的,如果某些“应该工作”的东西对您不起作用,那么这是一个信号,告诉我们更多关于您的环境。我看到你在使用Windows。你使用哪个CMake生成器(Visual Studio,NMake,MinGW)?你如何运行构建过程(IDE中的按钮,makecmake --build)?您说add_custom_target创建了一个新项目 - 在Visual Studio中,每个目标实际上都是一个新项目。 - Tsyvarev
这台机器是 Windows 7 机器。使用的 CMake GUI 版本为3.7.2,但生成器使用 Visual Studio 14 2015。从 Visual Studio 2015 发送构建命令调用 CMake 进行构建。我不需要一个新项目,因为此脚本需要运行的项目已经存在。 - Anthony
嗯,就我所猜测的来看,在Visual Studio中,您实际上是构建了一些特定目标,而不是默认目标。因此,自定义目标未被执行。为使其正常工作,请使您构建的目标依赖于始终运行的目标:add_dependency(<your-target> always_run_target) - Tsyvarev
显示剩余8条评论
2个回答

4

您需要向自定义命令添加依赖项,以检查OriginalHeader.h文件的更改,并在其更改时重新生成__header.h和header.h文件。

add_executable(MyExe main.cpp ${CMAKE_CURRENT_BINARY_DIR}/__header.h)

add_custom_target(
        always_run_target ALL
        DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/__header.h
)

add_custom_command(
        OUTPUT
            ${CMAKE_CURRENT_BINARY_DIR}/__header.h
            ${CMAKE_CURRENT_BINARY_DIR}/header.h
        DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/OriginalHeader.h
        COMMAND python ${py_cmd}
)

我只是用Python替换了${PYTHON_EXECUTABLE}。 我能够运行下面的Python脚本(如下),每当检测到OriginalHeader.h中的更改或__header.h/header.h不存在时,就会打印、创建一个目录并生成两个文件__header.h和header.h。
import os

print("TEST")

if not os.path.exists("TESTDIR"):
    os.makedirs("TESTDIR")
with open("header.h", 'w+'):
    os.utime("header.h", None)

1
我不需要从中创建可执行文件,我只需要每次运行我的Python脚本。__header.h是一个假文件,用来欺骗CMake每次运行,而header.h是一个我不关心的文件,因为Python脚本创建了我需要的一切。对此我很抱歉没有具体说明。我不能依赖于检测到更改,因为人们不应该直接更改.qrc文件。它需要在项目构建时由脚本创建,以考虑添加、删除和名称更改。 - Anthony
好的,那么为什么自定义命令应该输出 __header.h? 它应该依赖于它吗?不是吧。 - Noki
从我现在看到的情况来看,如果__header.h和header.h不存在,并且在项目第一次构建时也不会存在,那么它应该调用Python脚本。 在custom_command中添加depends应该会触发__header.h和header.h的重新生成,从而再次调用Python脚本。 - Noki
只有在显式调用新目标的项目进行构建时,脚本才会被执行,并且在需要它的项目被调用构建时不会执行。否则该项目将正常构建。 - Anthony
那么,告诉我是否正确,您的用例是:
  • 用户将资源文件添加到资源文件夹中,或者为目标重命名一个资源文件
  • 然后他想要使用最新的资源进行构建
  • 运行CMake命令(或生成器)进行构建
  • 脚本正在运行,使用最新的资源生成qrc文件。
  • 项目的目标依赖于qrc文件,因此正在构建。
  • 项目正在运行,并且可以正常使用新资源。
- Noki
显示剩余6条评论

2

如果您一直在构建所有目标和单独的目标,我有一个解决方案。

在构建all时运行Python脚本

我有一个单独的generate_header.cmake文件,并将其添加到项目的根级目录中,该文件包含以下内容:

# File: generate_header.cmake
#############################

find_package(PythonInterp)
find_package(Python)

function(generate_headers target_name)
    if (PYTHON_FOUND)
        add_custom_target(${target_name} ALL
            COMMAND 
               ${PYTHON_EXECUTABLE} ${py_cmd}
            OUTPUT 
               ${CMAKE_CURRENT_BINARY_DIR}/__header.h
               ${CMAKE_CURRENT_BINARY_DIR}/header.h  
        )
    endif()
endfunction()

在根级别的CMakeLists.txt中,我已经添加了以下内容:

include(generate_header)

# ... other project stuff

generate_headers(always_run_target)

现在每次我构建all,即在调用cmake.exe时传递--target all,我的脚本就会运行。

注意:构建其他单独的目标不会重新生成头文件。

在构建单独的目标时运行Python脚本

如果您想为单独的目标生成,则需要为每个目标调用add_custom_command。请注意,如果您有多个目标,则在构建all时也会调用它,因此它将多次调用脚本:

add_custom_command(
    TARGET name_of_your_target
    PRE_BUILD # Call this command pre-build
    COMMAND ${PYTHON_EXECUTABLE} ${py_cmd}
    COMMENT "Generate Headers"
)    

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