Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

cmake/make (OBJECT) dependency issue--not rebuilding when header changes

I've managed to reproduce an issue I'm having with a much larger project. I think this is as minimal as I can make it

Key being I've explicitly added a header to the source list and editing it still does not cause anything to get recompiled.

~/src/test2/_build£ cat ../CMakeLists.txt
cmake_minimum_required (VERSION 3.14)
set (CMAKE_CXX_STANDARD 11)
# various explicit CXX sets are necessary for this tiny project and don't exist in larger original
project(moduletest CXX)
set (HEADER_DIR "${CMAKE_CURRENT_SOURCE_DIR}")
add_subdirectory(submod)
add_library(moduletest_static STATIC "$<TARGET_OBJECTS:submod>")
set_target_properties(moduletest_static PROPERTIES LINKER_LANGUAGE CXX)

~/src/test2/_build£ cat ../submod/CMakeLists.txt
include_directories (${HEADER_DIR})
add_library(submod OBJECT compileme.cpp ../includeme.h)

~/src/test2/_build£ cat ../submod/compileme.cpp
#include "includeme.h"
int function()
{
  return 5;
}

Make output is as follows:

~/src/test2/_build£ touch ../submod/compileme.cpp
~/src/test2/_build£ make
[ 50%] Building CXX object submod/CMakeFiles/submod.dir/compileme.cpp.o
[ 50%] Built target submod
[100%] Linking CXX static library libmoduletest_static.a
[100%] Built target moduletest_static
~/src/test2/_build£ touch ../includeme.h
~/src/test2/_build£ make
[ 50%] Built target submod
[100%] Built target moduletest_static

If I remove the use of include_directories, and just #include "../includeme.h" in my cpp file, everything works correctly, regardless of my add_library call. (But this is definitely not an option in my real project)

I should also note that the "Scanning dependencies of target submod" is something of a red herring. In my larger project, I tried touching a single cpp file so that this occurred. Other cpp files that should have compiled still did not.

Use of target_include_directories did not fix anything, regardless of absolute/relative paths.

Problem goes away with cmake -GNinja ..

like image 427
zzxyz Avatar asked Mar 05 '23 00:03

zzxyz


2 Answers

My initial answer was:

You are most likely barking at wrong tree. Source-to-header dependencies are not managed by CMake, but underlying generator, as headers do not take part of libraries compilation directly. In your case it is make duty to detect dirty header and rebuild .cpp file. Headers can be listed in sources, but it only makes them visible in few IDEs and adds sanity check whenever they exist.

But for case of GNU make it is not exactly the truth. Newer tools, like ninja are able to emit source-to-header dependencies when they compile code. So CMake only tracks down library-to-source dependencies and underlying tool keeps track of source-to-header.

For make, CMake uses its internal mechanism to create depend.make files for object .cpp.o files. The algorithm is not perfect and may fail for absolute paths passed by include_directories.

For reference, initially (after generation step) it creates depend.make files with comment:

# Empty dependencies file for submod.
# This may be replaced when dependencies are built.

Once make is called and objects are built first time, the file gets filled with:

# CMAKE generated file: DO NOT EDIT!
# Generated by "Unix Makefiles" Generator, CMake Version 3.14

submod/CMakeFiles/submod.dir/compileme.cpp.o: ../includeme.h
submod/CMakeFiles/submod.dir/compileme.cpp.o: ../submod/compileme.cpp

Thanks @Fred, for making me read all about it.

Also this issue was already discussed on SO: Handling header files dependencies with cmake

And historical reading: http://www.aosabook.org/en/cmake.html

like image 90
R2RT Avatar answered Apr 07 '23 02:04

R2RT


I'm just going by memory from the last time I've looked at how CMake generates the file dependencies for "source files" when using the Makefile generator.

The step "Scanning dependencies" is when CMake scans the "source files" and creates a dependency build rules for that "source file". It does this using REGEXs and a few other rules (it skips some of the pre-processing rules). It tends to skip what it considers a system header file which can include a file that is found when using an absolute include path. Think of it as a false positive.

The usual workarounds are to not use include_directories() and avoid absolute paths for when using target_include_directories(). So try using target_include_directories( submod PRIVATE .. ) instead.

like image 24
fdk1342 Avatar answered Apr 07 '23 02:04

fdk1342