Получить полную командную строку компилятора C ++

В CMake на флаги компилятора C ++ можно влиять по-разному: установка CMAKE_CXX_FLAGS вручную, использование add_definitions(), принудительное использование определенного стандарта C ++ и т. Д.

Чтобы скомпилировать цель в том же проекте с разными правилами (в моем случае это предварительно скомпилированный заголовок), мне нужно воспроизвести точную команду, которая используется для компиляции файлов, добавленных командой типа add_executable() в этом каталоге.

Чтение CMAKE_CXX_FLAGS возвращает только явно заданное для него значение, CMAKE_CXX_FLAGS_DEBUG и его братья и сестры перечисляют только параметры отладки / выпуска по умолчанию. Существуют специальные функции для получения флагов из add_definitions() и add_compiler_options(), но, похоже, ни одна из них не может вернуть окончательную командную строку.

Как я могу передать все флаги компилятору в переменную CMake?


person Fabian Knorr    schedule 04.03.2016    source источник


Ответы (4)


Чтобы ответить на мой собственный вопрос: похоже, что единственный способ получить все флаги компилятора - это восстановить их из различных источников. Код, с которым я сейчас работаю, следующий (для GCC):

macro (GET_COMPILER_FLAGS TARGET VAR)
    if (CMAKE_COMPILER_IS_GNUCXX)
        set(COMPILER_FLAGS "")

        # Get flags form add_definitions, re-escape quotes
        get_target_property(TARGET_DEFS ${TARGET} COMPILE_DEFINITIONS)
        get_directory_property(DIRECTORY_DEFS COMPILE_DEFINITIONS)
        foreach (DEF ${TARGET_DEFS} ${DIRECTORY_DEFS})
            if (DEF)
                string(REPLACE "\"" "\\\"" DEF "${DEF}")
                list(APPEND COMPILER_FLAGS "-D${DEF}")
            endif ()
        endforeach ()

        # Get flags form include_directories()
        get_target_property(TARGET_INCLUDEDIRS ${TARGET} INCLUDE_DIRECTORIES)
        foreach (DIR ${TARGET_INCLUDEDIRS})
            if (DIR)
                list(APPEND COMPILER_FLAGS "-I${DIR}")
            endif ()
        endforeach ()

        # Get build-type specific flags
        string(TOUPPER ${CMAKE_BUILD_TYPE} BUILD_TYPE_SUFFIX)
        separate_arguments(GLOBAL_FLAGS UNIX_COMMAND
                "${CMAKE_CXX_FLAGS} ${CMAKE_CXX_FLAGS_${BUILD_TYPE_SUFFIX}}")
        list(APPEND COMPILER_FLAGS ${GLOBAL_FLAGS})

        # Add -std= flag if appropriate
        get_target_property(STANDARD ${TARGET} CXX_STANDARD)
        if ((NOT "${STANDARD}" STREQUAL NOTFOUND) AND (NOT "${STANDARD}" STREQUAL ""))
            list(APPEND COMPILER_FLAGS "-std=gnu++${STANDARD}")
        endif ()
    endif ()
    set(${VAR} "${COMPILER_FLAGS}")
endmacro ()

Это может быть расширено, чтобы также включать параметры, вызванные add_compiler_options() и другими.

person Fabian Knorr    schedule 05.03.2016
comment
Это не работает, если некоторые определения наследуются транзитивно из связанных библиотек. - person random; 31.07.2018

Самый простой способ - использовать make VERBOSE=1 при компиляции.

cd my-build-dir
cmake path-to-my-sources
make VERBOSE=1

Это сделает однопоточную сборку, и make будет печатать каждую выполняемую команду оболочки непосредственно перед ее запуском. Итак, вы увидите такой результат:

[  0%] Building CXX object Whatever.cpp.o
<huge scary build command it used to build Whatever.cpp>
person Chris Kitching    schedule 05.03.2016
comment
Это, безусловно, полезно, но мне нужно повторно использовать флаги компилятора в том же проекте, поэтому мне как-то нужно передать их в переменную. - person Fabian Knorr; 05.03.2016
comment
Это сложнее. Я считаю, что ваш единственный вариант - объединить все различные источники флагов вместе. Предварительно скомпилированные заголовки являются источником серьезных проблем с CMake. У многих флагов также есть противоположность, которая отменяет более раннюю положительную версию (поэтому, если вам просто нужно отключить несколько вещей, вы можете перевернуть их, добавив дополнительные флаги). Думаю, вы видели это: http://stackoverflow.com/questions/148570/using-pre-compiled-headers-with-cmake - person Chris Kitching; 05.03.2016

На самом деле есть довольно чистый способ сделать это во время компиляции с помощью CXX_COMPILER_LAUNCHER:

Если у вас есть скрипт print_args.py

#!/usr/bin/env python
import sys
import argparse

print(" ".join(sys.argv[1:]))
# we need to produce an output file so that the link step does not fail
p = argparse.ArgumentParser()
p.add_argument("-o")
args, _ = p.parse_known_args()
with open(args.o, "w") as f:
    f.write("")

Вы можете установить свойства цели следующим образом:

add_library(${TARGET_NAME} ${SOURCES})
set_target_properties(${TARGET_NAME} PROPERTIES
      CXX_COMPILER_LAUNCHER
        ${CMAKE_CURRENT_SOURCE_DIR}/print_args.py
)
# this tells the linker to not actually link. Which would fail because output file is empty
set_target_properties(${TARGET_NAME} PROPERTIES
      LINK_FLAGS
        -E
)

Это напечатает точную команду компиляции во время компиляции.

person Jan15    schedule 13.12.2019

Короткий ответ

Невозможно присвоить окончательное значение командной строки компилятора переменной в сценарии CMake, работает во всех случаях использования.

Длинный ответ

К сожалению, даже решение, принятое в качестве ответа, по-прежнему не получает все флаги компилятора. Как отмечается в комментариях, есть Требования к переходному использованию. Это современный и правильный способ записи файлов CMake, который становится все более популярным. Также у вас могут быть некоторые параметры компиляции, определенные с помощью выражений генератора (они выглядят как ссылки на переменные, но не расширяются при необходимости).

Рассмотрим следующий пример:

add_executable(myexe ...);
target_compile_definitions(myexe PRIVATE "PLATFORM_$<PLATFORM_ID>");
add_library(mylib ...);
target_compile_definitions(mylib INTERFACE USING_MY_LIB);
target_link_libraries(myexe PUBLIC mylib);

Если вы попытаетесь вызвать предложенный макрос GET_COMPILER_FLAGS с myexe target, вы получите результат -DPLATFORM_$<PLATFORM_ID> вместо ожидаемого -DPLATFORM_Linux -DUSING_MY_LIB.

Это связано с тем, что между вызовом CMake и созданием системы сборки есть два этапа:

  1. Обработка. На этом этапе CMake считывает и выполняет команды из сценария (-ов) cmake, в частности, значения переменных, которые оцениваются и назначаются. На данный момент CMake просто собирает всю необходимую информацию и готовится создать систему сборки (файлы сборки).
  2. Генерация. CMake использует значения специальных переменных и properties, оставаясь в конце обработанных сценариев для окончательного решения и сформировать сгенерированный вывод. Здесь он создает финальную командную строку для компилятора в соответствии со своим внутренним алгоритмом, недоступным для сценариев.

Целевые свойства, которые могут быть получены на этапе обработки с помощью get_target_property(...) или get_property(... TARGET ...), не являются полными (даже при вызове в конце сценария). На этапе генерации CMake проходит через каждое целевое дерево зависимостей (рекурсивно) и добавляет значения свойств в соответствии с требованиями транзитивного использования (распространяются значения с тегами PUBLIC и INTERFACE).

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

Возможны две общие возможности:

  1. Сгенерируйте любой выходной файл на основе шаблона, содержимое которого содержит ссылки на переменные и / или выражения генератора и определено либо как значение строковой переменной, либо как входной файл. Он не является гибким из-за очень ограниченной поддержки условной логики (то есть вы не можете использовать сложные конкатенации, доступные только с вложенными foreach() циклами), но имеет преимущества, заключающиеся в том, что не требуются дополнительные действия и контент, описанный независимым от платформы способом. Используйте вариант команды файл (GENERATE ...). Обратите внимание, что он ведет себя иначе, чем вариант file (WRITE ...).
  2. Добавьте настраиваемую цель (и / или настраиваемую команду), которая реализует дальнейшее использование расширенного значения. Он зависит от платформы и требует, чтобы пользователь дополнительно вызвал make (либо с какой-то специальной целью, либо включил в all цель), но имеет то преимущество, что он достаточно гибкий, потому что вы можете реализовать сценарий оболочки (но без исполняемого бита).

Пример, демонстрирующий решение с объединением этих опций:

set(target_name "myexe")
file(GENERATE OUTPUT script.sh CONTENT "#!/bin/sh\n echo \"${target_name} compile definitions: $<TARGET_PROPERTY:${target_name},COMPILE_DEFINITIONS>\"")
add_custom_target(mycustomtarget
  COMMAND echo "\"Platform: $<PLATFORM_ID>\""
  COMMAND /bin/sh -s < script.sh
  )

После вызова CMake каталог сборки будет содержать файл script.sh, а вызов make mycustomtarget выведет на консоль:

Platform: Linux
myexe compile definitions: PLATFORM_Linux USING_MY_LIB
person Artem Pisarenko    schedule 05.11.2019