Hello. This is my first post.
Abstract
Specify generated files and their action, instead of add_dependencies()
How we are doing now
Assume just lib2.c depends on the generated header.
# CMakeLists.txt
cmake_minimum_required(VERSION 3.13)
add_subdirectory(inc)
add_library(lib1 lib1.c)
add_library(lib2 lib2.c) # depends on generated header
target_include_directories(lib2 PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/inc)
add_dependencies(lib2 headergen)
add_library(lib12 lib12.c)
target_link_libraries(lib12 INTERFACE lib1 lib2)
add_executable(main main.c)
target_link_libraries(main PRIVATE lib12)
- add_dependencies(lib2 headergen) is essential.
- lib12 and main depend on headergen as order-only. It is not intended.
# inc/CMakeLists.txt
set(template_file ${CMAKE_CURRENT_SOURCE_DIR}/template.h)
set(generated_file ${CMAKE_CURRENT_BINARY_DIR}/generated.h)
add_custom_command(
OUTPUT ${generated_file}
COMMAND ${CMAKE_COMMAND} -E copy_if_different
${template_file}
${generated_file}
DEPENDS ${template_file}
)
add_custom_target(headergen DEPENDS ${generated_file})
Proposal
Add an ability to specify headergen as “dependency of file generator”
It may be similar to include_directories thing rather than order-only dependency. It may be transitive.
For example
If a new command would be introduced;
add_library(lib2 lib2.c)
target_include_directories(lib2 PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/inc)
target_include_generated_files(lib2 PRIVATE headergen) # May be custom target or generated file
If include_directories() could distinguish directories and targets;
add_library(lib2 lib2.c)
target_include_directories(lib2 PRIVATE
${CMAKE_CURRENT_BINARY_DIR}/inc
headergen # May be custom target or generated file
)
Implementation
- If a generator is capable of dyndep, headergen shall be converted to “a dyndep scanner to discover dependencies”. headergen will be invoked when a scanner discovered dependency to generated.h
- A generator is responsible to extract file list to pass to a scanner as “list of missing files to be generated”
- For other generators, headergen may be treated as local order-only dependencies, or global order-only dependencies (like add_dependencies)
Thoughts to granularity of scanning
It seems already-implemented dyndep facility emits per-file scanner. I think it is too fine-granularly. Per-target scanner would satisfy.
Requirements to the scanner
- The scanner should traverse include path with missing file list.
- The scanner should not miss “actually referenced missing files”.
- Redundant files may be acceptable. “The scanner reports them but they are actually not referenced.” Gross scanner is acceptable (like cmake_depends) Of course, this is suboptimal.
- If the scanner is not a gross scanner, it may stop when it met unknown missing files.
- The scanner may omit reporting static files. Just missing files may be reported. DEPFILE (in compilation) shall cover them.
- That said, the scanner should traverse all include files. Just may omit emission.
- The scanner should emit its DEPFILE for the scanner itself. It should contain all scanned files, even if they were not emitted.
Makefiles generator’s “cmake_depends” could be diverted to this, but I guess it’d be hard to use it from Ninja generator.
For now, an external scanner may be specified.
Future of the scanner
The scanner may be capable of scanning also pre-compiled headers, like Clang’s “modules cache”
Then, an external scanner, for example clang-scan-deps, shall be specified.
Background and history
I have been experimenting ninja dyndep for weeks. I achieved highly parallelized build.ninja for Clang/LLVM tree.
See, “Clang can be built within one minute”, https://twitter.com/chapuni/status/1401519362058555393
For it, I implemented in CMake, “automatically discover add_dependencies() to generated header file and convert them to dyndep”
It works fine with LLVM tree but I don’t think it would be suitable to generic use.
- I don’t think all projects would be ready to discover_dyndep. I guess some of them would be immature.
- Introducing a new policy might be an option.
- It was hard to dissolve cmake_object_order_depends chain.
- Dissolving add_dependencies() would not help cutting unintended dependencies.
I concluded I should introduce more simple scheme. (see proposal above)
Thank you.