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

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

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

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

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

5 ответов

Решение

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

Самый простой способ - это использовать 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>

На самом деле есть довольно простой способ сделать это во время компиляции с помощью 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
)

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

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

Невозможно присвоить окончательное значение командной строки компилятора переменной в сценарии 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 использует значения специальных переменных и свойств, оставаясь в конце обработанных сценариев, чтобы окончательно решить и сформировать сгенерированный вывод. Здесь он создает финальную командную строку для компилятора в соответствии со своим внутренним алгоритмом, недоступным для сценариев.

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

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

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

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

Пример демонстрации решения с объединением этих опций:

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

Использовать

      set(CMAKE_EXPORT_COMPILE_COMMANDS true)

и получить compile_commands.json

Другие вопросы по тегам