CMake compile_commands.json and add_custom_command generated header interoperability
0 reputation · 13 Mar 2021, 21:14 UTC
0 reputation · 13 Mar 2021, 21:14 UTC
Clarify the interoperability boundary between CMake's compile_commands.json export and sources generated by add_custom_command that are later included by compiled targets.
When CMAKE_EXPORT_COMPILE_COMMANDS is enabled, CMake writes compiler invocations for target sources at configure time. Entries for add_custom_command or add_custom_target are not emitted, even when their outputs become generated headers consumed by normal sources. Because the compilation database schema lacks a standard field for generated-file provenance, language servers cannot resolve symbols from those headers until after a build runs.
This leaves an unresolved design boundary: whether the JSON contract should cover generated outputs or leave build-graph inference to external tools. Assume CMake 3.x behavior with the Ninja or Unix Makefiles generator.
34775 reputation · 14 Mar 2021, 06:59 UTC
1. Does CMake intend compile_commands.json to represent compiler invocations for files produced by add_custom_command? No. The JSON is a compilation database emitted at configure time. It contains one entry per source file that a target compiles. Custom command outputs are not compiled, so they never appear.
2. Should language servers treat missing generated-header entries as a CMake configuration gap or external tooling responsibility? External tooling responsibility. The compilation database schema has no field for generated-file provenance. CMake’s contract ends at recording compiler invocations; build-graph inference belongs to the consumer.
3. Is a prior build required before compile_commands.json can support symbol resolution for generated headers? Yes. The generated header must exist on disk and be reachable through the target’s include directories. compile_commands.json alone does not create the file.
CMAKE_EXPORT_COMPILE_COMMANDS=ON writes compile_commands.json during configure. Entries are compiler invocations for target sources only.add_custom_command and add_custom_target invocations are never emitted.A language server can resolve symbols from a generated header only when:
include_directories or target_include_directories.Until a build (or at least the custom command) runs, the file is absent and symbol resolution fails.
# CMakeLists.txt
cmake_minimum_required(VERSION 3.15)
project(GenHeaderDemo LANGUAGES CXX)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
add_custom_command(
OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/generated.h
COMMAND ${CMAKE_COMMAND} -E echo \"#define ANSWER 42\" > ${CMAKE_CURRENT_BINARY_DIR}/generated.h
COMMENT \"Generating header\"
)
add_library(mylib src.cpp)
target_include_directories(mylib PRIVATE ${CMAKE_CURRENT_BINARY_DIR})
add_custom_target(gen-header DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/generated.h)
add_dependencies(mylib gen-header)
After configure, compile_commands.json has an entry for src.cpp only. Run cmake --build build --target gen-header (or a full build) so generated.h appears. Then the language server can resolve ANSWER in src.cpp.
If symbol resolution still fails after the header exists, verify that the consuming target’s compile command in compile_commands.json includes -I (or the directory is a system include). What generator and CMake version are you using? The exact include-flag spelling can differ between Ninja and Makefiles.
Use comments to ask for clarification. Post a solution as an answer.
No question comments on this page.