Fabian Knorr
Fabian Knorr

Reputation: 3204

Get full C++ compiler command line

In CMake, the flags for the C++ compiler can be influenced in various ways: setting CMAKE_CXX_FLAGS manually, using add_definitions(), forcing a certain C++ standard, and so forth.

In order to compile a target in the same project with different rules (a precompiled header, in my case), I need to reproduce the exact command that is used to compile files added by a command like add_executable() in this directory.

Reading CMAKE_CXX_FLAGS only returns the value set to it explicitly, CMAKE_CXX_FLAGS_DEBUG and siblings only list default Debug/Release options. There is a special functions to retrieve the flags from add_definitions() and add_compiler_options(), but none seem to be able to return the final command line.

How can I get all flags passed to the compiler into a CMake variable?

Upvotes: 11

Views: 3905

Answers (5)

ma5ter
ma5ter

Reputation: 41

Use

set(CMAKE_EXPORT_COMPILE_COMMANDS true)

and get compile_commands.json

Upvotes: 0

Jan15
Jan15

Reputation: 549

There actually is a fairly clean way to do this at compile time using CXX_COMPILER_LAUNCHER:

If you have a script 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("")

You can set the target's properties as follows:

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
)

This will print the exact compilation command at compile time.

Upvotes: 1

Artem Pisarenko
Artem Pisarenko

Reputation: 123

Short answer

It's not possible to assign final value of compiler command line to variable in CMake script, working in all use cases.

Long answer

Unfortunately, even solution accepted as answer still not gets all compiler flags. As gets noted in comments, there are Transitive Usage Requirements. It's a modern and proper way to write CMake files, getting more and more popular. Also you may have some compile options defined using generator expressions (they look like variable references but will not expand when needed).

Consider having following example:

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);

If you try to call proposed GET_COMPILER_FLAGS macro with myexe target, you will get resulting output -DPLATFORM_$<PLATFORM_ID> instead of expected -DPLATFORM_Linux -DUSING_MY_LIB.

This is because there are two stages between invoking CMake and getting build system generated:

  1. Processing. At this stage CMake reads and executes commands from cmake script(s), particularly, variable values getting evaluated and assigned. At this moment CMake just collecting all required info and being prepared to generate build system (makefiles).
  2. Generating. CMake uses values of special variables and properties, being left at end of processed scripts to finally decide and form generated output. This is where it constructs final command line for compiler according to its internal algorithm, not avaliable for scripting.

Target properties which might be retrieved at processing stage with get_target_property(...) or get_property(... TARGET ...) aren't complete (even when invoked at the end of script). At generating stage CMake walks through each target dependency tree (recursively) and appends properties values according to transitive usage requirements (PUBLIC and INTERFACE tagged values gets propagated).

Although, there are workarounds, depending on what final result you aiming to achieve. This is possible by applying generator expressions, which allows use final values of properties of any target (defined at processing stage)... but later!

Two general possibilites are avaliable:

  1. Generate any output file based on template, which content contains variable references and/or generator expressions, and defined as either string variable value, or input file. It's not flexible due to very limited support of conditional logic (i.e. you cannot use complex concatenations available only with nested foreach() loops), but has advantages, that no further actions required and content described in platform-independent way. Use file(GENERATE ...) command variant. Note, that it behaves differently from file (WRITE ...) variant.
  2. Add custom target (and/or custom command) which implements further usage of expanded value. It's platform dependent and requires user to additionally invoke make (either with some special target, or include to all target), but has advantage, that it's flexible enough because you may implement shell script (but without executable bit).

Example demonstrating solution with combining these options:

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
  )

After calling CMake build directory will contain file script.sh and invoking make mycustomtarget will print to console:

Platform: Linux
myexe compile definitions: PLATFORM_Linux USING_MY_LIB

Upvotes: 0

Fabian Knorr
Fabian Knorr

Reputation: 3204

To answer my own question: It seems like the only way of getting all compiler flags is to reconstruct them from the various sources. The code I'm working with now is the following (for 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 ()

This could be extended to also include options induced by add_compiler_options() and more.

Upvotes: 4

Chris Kitching
Chris Kitching

Reputation: 2655

Easiest way is to use make VERBOSE=1 when compiling.

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

This will do a single-threaded build, and make will print every shell command it runs just before it runs it. So you'll see output like:

[  0%] Building CXX object Whatever.cpp.o
<huge scary build command it used to build Whatever.cpp>

Upvotes: 3

Related Questions