CMake:默认情况下如何使 add_custom_command 输出保持最新?

jpo*_*o38 5 c++ cmake visual-studio-2015

我有一个基于 CMake 3.11.4 和 Python 3.7 的软件环境。

我的库/程序有一个config.txt文件,以我指定的格式描述它们的依赖关系。然后,我有一个 Python 脚本 ( scripts/configure.py),它会动态生成CMakeLists.txt,然后调用 CMake 来生成可由 Visual Studio 2015 构建的解决方案。

我希望 Pythonconfig.txt在用户编辑时自动再次运行。

所以我让我的 Python 脚本在生成的CMakeLists.txt. 以下是名为“myproject”的项目的外观,其中包括两个库“lib1”和“lib2”。

${SDE_ROOT_DIR}/build/myproject/CMakeLists.txt 包含:

# Automatically re-run configure project when an input file changes:
set( PROJECT_DEPENDENCIES )
list( APPEND PROJECT_DEPENDENCIES "${SDE_ROOT_DIR}/lib/lib1/config.txt" )
list( APPEND PROJECT_DEPENDENCIES "${SDE_ROOT_DIR}/lib/lib2/config.txt" )
ADD_CUSTOM_COMMAND( OUTPUT ${SDE_ROOT_DIR}/build/myproject/CMakeLists.txt COMMAND tools/python/Python370/python.exe scripts/configure.py myproject WORKING_DIRECTORY ${SDE_ROOT_DIR} DEPENDS ${PROJECT_DEPENDENCIES} )
Run Code Online (Sandbox Code Playgroud)

这是我所做的:

  • 我运行我的脚本 ( scripts/configure.py myproject) 来CMakeLists.txt生成 Visual Studio 解决方案。
  • 然后我打开解决方案
  • 我第一次构建时,它会报告Generating CMakeLists.txt并且我看到我的脚本scripts/configure.py被调用。这不是预期的!
  • 我第二次构建时,什么也没有发生。还行吧。
  • 如果我编辑config.txt,下次构建时我会看到Generating CMakeLists.txt并看到我的脚本scripts/configure.py被调用。那挺好的。

除了第一次编译项目时我的脚本运行之外,这几乎是我的预期。由于CMakeLists.txt刚刚生成并且绝对比 更新config.txt,我不明白为什么它需要CMakeLists.txt再次生成。

知道我可能做错了什么吗?是否有任何额外的命令我应该添加到CMakeLists.txt使这个自定义命令的输出默认为“最新”?


这是一个 MCVE(config.txt被替换为prgname.txt):

prg/main.cpp

#include <iostream>

int main( int argc, char* argv[] )
{
    std::cout << "Hello World!" << std::endl;
    return 0;
}
Run Code Online (Sandbox Code Playgroud)

prg/prgname.txt

myprogram
Run Code Online (Sandbox Code Playgroud)

scripts/configure.py

import sys
import subprocess
import argparse
import os
from contextlib import contextmanager

@contextmanager
def pushd(newDir):
    previousDir = os.getcwd()
    os.chdir(newDir)
    yield
    os.chdir(previousDir)

def configure_project():

    # check configuration args
    parser = argparse.ArgumentParser(description="CMakeLists generator.")
    parser.add_argument('project',  metavar='project', type=str, help='project name')
    args = parser.parse_args()

    working_directory = os.getcwd()

    project = args.project

    buildfolder = os.path.normpath(os.path.join( os.path.dirname(os.path.abspath(__file__)), os.pardir, "build", project ))

    if not os.path.isdir(buildfolder):
        os.makedirs(buildfolder)

    prgsourcefolder = os.path.normpath(os.path.join( os.path.dirname(os.path.abspath(__file__)), os.pardir, "prg" ))
    prgbuildfolder = os.path.join( buildfolder, "prg" )
    if not os.path.isdir(prgbuildfolder):
        os.makedirs(prgbuildfolder)

    prgnamepath = os.path.join( prgsourcefolder, "prgname.txt" )
    with open( prgnamepath, "r" ) as prgnamefile:
        prgname = prgnamefile.read()

    with open( os.path.join( prgbuildfolder, "CMakeLists.txt" ), "w" ) as cmakelists:
        cmakelists.write( "add_executable(" + prgname + " " + os.path.join(prgsourcefolder,"main.cpp").replace("\\","/") + ")\n" )

    cmakelistspath = os.path.join( buildfolder, "CMakeLists.txt" )
    with open( cmakelistspath, "w" ) as maincmakelists:
        maincmakelists.write( "cmake_minimum_required(VERSION 3.11)\n" )
        maincmakelists.write( "project(" + project + ")\n" )
        maincmakelists.write( "add_subdirectory(prg)\n" )

        maincmakelists.write( "add_custom_command( OUTPUT " + cmakelistspath.replace("\\","/") + " COMMAND python " + " ".join( [ x.replace("\\","/") for x in sys.argv] ) + " WORKING_DIRECTORY " + working_directory.replace("\\","/") + " DEPENDS " + prgnamepath.replace("\\","/") + ")\n" )

    # Run CMake:
    with pushd( buildfolder ):
        cmd = ['cmake.exe', '-G', 'Visual Studio 14 2015 Win64', buildfolder]
        proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
        while True:
            out = proc.stdout.read(1)
            if proc.poll() != None:
                break   
            sys.stdout.write(out.decode())
            sys.stdout.flush()
        proc.wait()

if __name__ == "__main__":
    import sys
    sys.exit( configure_project() )
Run Code Online (Sandbox Code Playgroud)
  • 将 CMake 和 Python 添加到您的 PATH
  • 从脚本文件夹运行 python configure.py myproject
  • 打开 build/myproject/myproject.sln
  • 点击“全部编译”,您将Generating CMakeLists.txt在日志中看到意外消息

LHL*_*ini 0

add_custom_command创建一个没有目标依赖的命令。因此,它不会被执行。我不知道为什么 Visual Studio 第一次运行它,但在 Linux 上 Python 脚本永远不会运行。

为了使其正常工作,您还需要一个目标。在这种情况下,您可以使用add_custom_target选项ALL并将自定义命令的输出指定为其依赖项。这样,自定义目标将在需要时运行该命令。

只需添加一行,例如

        maincmakelists.write( "add_custom_target( Configure ALL DEPENDS " + cmakelistspath.replace("\\","/") + " )\n" )
Run Code Online (Sandbox Code Playgroud)

在你写的地方之后add_custom_command,它应该可以工作。它对我有用。

请注意,自定义目标将始终运行,但自定义命令仅在修改prgname.txt文件时才会运行。