CMake: Как я могу сделать вывод add_custom_command актуальным по умолчанию? - PullRequest
5 голосов
/ 22 января 2020

У меня есть программная среда, основанная на CMake 3.11.4 и Python 3.7.

В моих библиотеках / программах есть файл config.txt, описывающий их зависимости в указанном мной формате. Затем у меня есть сценарий Python (scripts/configure.py), который генерирует CMakeLists.txt на лету, а затем вызывает CMake для создания решения, которое может быть построено в Visual Studio 2015.

Я хочу Python для автоматического повторного запуска при редактировании config.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} )

Вот что я делаю:

  • Я запускаю свой сценарий (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;
}

prg/prgname.txt:

myprogram

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() )
  • Добавьте CMake и Python к вашему PATH
  • Из папки скриптов запустите python configure.py myproject
  • Откройте build/myproject/myproject.sln
  • Нажмите "compile all" и вы Вы увидите неожиданное сообщение Generating CMakeLists.txt в журнале

Ответы [ 2 ]

0 голосов
/ 31 января 2020

Вот «обходной путь», а не «решение».

Я просто добавляю файл состояния, чтобы сообщить, когда генерировать вызов из VS следует пропустить, потому что я знаю, что решение является современным:

Добавить новый аргумент в анализатор:

parser.add_argument('--from_vs', action='store_true', help='identify that configure is ran from VS to prevent useless regeneration, don't set this manually please')

Правильно поддерживать этот файл из самого скрипта:

Сделать этот новый аргумент установленным при запуске из VS :

run_args = " ".join( [ x.replace("\\","/") for x in sys.argv] )
if not args.from_vs:
    run_args += " --from_vs"
maincmakelists.write( "add_custom_command( OUTPUT " + cmakelistspath.replace("\\","/") + " COMMAND python " + run_args + " WORKING_DIRECTORY " + working_directory.replace("\\","/") + " DEPENDS " + prgnamepath.replace("\\","/") + ")\n" )

Это делает бесполезным вызов VS для настройки при первом запросе на генерацию и пропускает, поэтому необходимые вызовы будут работать как положено.

Редактировать

На самом деле, это так не работает так, как ожидалось. Потому что VS запускает скрипт каждый раз для каждой конфигурации. Поэтому после того, как вы соберете один раз в режиме Release, переключение на Debug приведет к повторной генерации CMakeLists.txt, а это не должно произойти, потому что первое поколение записало False в файле vs_force_up_to_date. Это слишком наивное решение.

Вместо этого решение, которое я окончательно принял, состоит в том, чтобы передать путь ко всем входным файлам (prgname.txt) и выходным файлам (CMakeLists.txt) в сценарий и проверить, все выходы более поздние, чем все входы, пропустите генерацию. Затем, что бы ни делал неожиданный вызов скрипта VS, он будет корректно обрабатываться самим скриптом.

0 голосов
/ 24 января 2020

add_custom_command создает команду, от которой не зависит ни одна цель. Из-за этого он не будет выполнен. Я понятия не имею, почему Visual Studio запускает его в любом случае в первый раз, но на Linux сценарий Python никогда не запускается.

Чтобы он работал правильно, вам также нужна цель. В этом случае вы можете использовать add_custom_target с параметром ALL и указать выходные данные пользовательской команды в качестве ее зависимости. Таким образом, пользовательская цель будет запускать команду при необходимости.

Просто добавьте строку типа

        maincmakelists.write( "add_custom_target( Configure ALL DEPENDS " + cmakelistspath.replace("\\","/") + " )\n" )

после той, где вы пишете add_custom_command, и она должна работать. Это было сделано для меня.

Обратите внимание, что настраиваемая цель всегда будет выполняться, но настраиваемая команда будет выполняться только при изменении файла prgname.txt .

...