4

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 ..

zzxyz
  • 2,953
  • 1
  • 16
  • 31
  • Btw, I was not able to reproduce it, either with `2.8` or `3.14` – R2RT May 15 '19 at 19:29
  • Hmm...what's your gcc version? – zzxyz May 15 '19 at 19:30
  • `GNU Make 4.1`, `cmake 3.14.20190309-gba3e8`, `g++ (Ubuntu 7.4.0-1ubuntu1~18.04) 7.4.0` – R2RT May 15 '19 at 19:31
  • @R2RT - Mine are all identical. (well my cmake has today's date because I tried recompiling it..but still 3.14) – zzxyz May 15 '19 at 19:32
  • By any chance, don't you have two copies of `includeme.h` file? – R2RT May 15 '19 at 19:33
  • No, just the one. You did the whole subdirectory, object thing? That's definitely necessary to reproduce the problem. Works fine if you just have a static lib that includes a header outside its path. – zzxyz May 15 '19 at 19:34
  • Just a copy of your code. https://wetransfer.com/downloads/082d4ad9425cd185af5c546b74e7c98b20190515193804/48dd7d – R2RT May 15 '19 at 19:38
  • @R2RT - Your unzipped code reproduced the problem on my system...fascinating... – zzxyz May 15 '19 at 19:51
  • Well, maybe try actual edit, not just `touch` call. This and your filesystem are my last suspects. Ouch, but `ninja` works... Idk what's happening – R2RT May 15 '19 at 19:53

4 Answers4

5

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

R2RT
  • 2,061
  • 15
  • 25
  • Updated to 3.14 minimum, retested. I'll look into the other stuff. I think add_library isn't broken in the original, but the include_directories is used.. – zzxyz May 15 '19 at 19:09
  • @zzxyz `add_library` syntax is simple, it expects name, (optionally) type and sources. `submod` is not source. https://cmake.org/cmake/help/v3.14/command/add_library.html#id2 @downvoters, how can I improve this answer? – R2RT May 15 '19 at 19:17
  • ps--I think I had to set the linker language because of my broken add_library call. – zzxyz May 15 '19 at 19:35
  • "Barking up the wrong tree" was useful. See the addition to my question :) – zzxyz May 15 '19 at 19:44
  • @zzxyz I've adjusted answer to your edits, if part before "=== Obsolete fixups for original code ===", if it somehow explains part of your post which I tried to answer "Key being I've explicitly added a header to the source list and editing it still does not cause anything to get recompiled. ", do you mind to accept it? – R2RT May 15 '19 at 19:51
  • my depend.make looks except that the header file is missing. I'm guessing this is some toolchain problem resulting from my upgrade from 16.04. I think this has answered my question as far as it was possible to answer it :) – zzxyz May 15 '19 at 19:58
  • LOL, your last guess is my answer. Try using a relative path. – fdk1342 May 15 '19 at 20:12
  • Yep, I just learned that. @zzxyz, I have to ask you to unaccept my answer. – R2RT May 15 '19 at 20:13
  • As mentioned in my question, using a relative path fixed nothing. Tried this code on my WSL installation (fresh 18.04 rather than upgraded from 16.04) and everything worked as expected. Oh...sorry, let me try explicit...still, seems like a toolchain issue – zzxyz May 15 '19 at 20:18
  • @R2RT Thank you, I appreciate it. But let's just update your answer to just modify that it's CMake that generates the dependencies for the Makefile generator. I can't reproduce the issue with OP example and never had the time to go through `cmDependsC.cxx` to reproduce the issue to better understand it. – fdk1342 May 15 '19 at 20:22
  • Relative path did not fix anything. Hand editing the cmake files did not fix anything. The problem seems to be my `gnu make` or some system setting it is using. Problem is the make toolchain on the machine in question. The answer one edit ago was the best answer to MY question, which unfortunately I understand is unlikely to be useful to others. – zzxyz May 15 '19 at 20:25
  • Yeah, I am glad that I have helped, but there is no value to trespassing readers. I doubt I can write anything more than in linked SO question. I will edit it again then, to at least be a proper SO answer. – R2RT May 15 '19 at 20:28
  • FYI, `ninja` relies on gcc (using -MMD and -MF) or whatever compiler you are using to generate a properly formatted file for the object file so `ninja` can include it in `depfile` parameter. Not all supported versions of `make` and compilers are able to generate these kinds of files which is probably the main reason CMake has it's own dependency scanner. https://ninja-build.org/manual.html#ref_headers – fdk1342 May 15 '19 at 22:45
  • Is there a way to have `cmake` not using its "internal mechanism" for `make`? After a refactoring when header files are removed, I often encounter that its "internal mechanism" to track dependencies is not updating correctly. I'd prefer to have `make` to handle dependencies, not `cmake`. – Anne van Rossum Oct 24 '19 at 10:30
  • @AnnevanRossum I really doubt you can disable it. You could ask it as independent SO question. Only thing I can recommend is to file a bug report on CMake site and to try switching to `ninja` as core generator. – R2RT Oct 24 '19 at 10:51
3

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.

fdk1342
  • 3,274
  • 1
  • 16
  • 17
  • As stated in my answer, CMake does not track source-header dependencies. It only prepares `depend.make` files for object files with comment `# Empty dependencies file for submod. # This may be replaced when dependencies are built. ` – R2RT May 15 '19 at 19:44
  • 1
    @R2RT You stated that "Source-to-header dependencies are not managed by CMake". Of course it manages the dependencies. CMake generates and populates `depends.make` using the command `cmake -E cmake_depends "Unix Makefiles" ...`. And in turn `make` uses the contents of `depends.make` to determine when to regenerate the object file. If CMake doesn't include the required file in `depends.make` then it won't be rebuilt which is the problem OP is saying they are having. – fdk1342 May 15 '19 at 20:07
  • Yep, I read digged to it I am wrong... I had no idea `make` is not able to emit dependencies and thought it works like in `Ninja`. Given that, your answer is correct, not mine. http://www.aosabook.org/en/cmake.html – R2RT May 15 '19 at 20:12
  • Weird. Ok...So cmake is still likely the culprit. Also upvoted this answer – zzxyz May 15 '19 at 20:28
  • @zzxyz Unfortunately I've not been able to reproduce the issue with the example. I've tried to replicate the same build structure, file contents, and also tried using an in-source build and out-of-source build. But for me `includeme.h` was always included in `depend.make`. – fdk1342 May 15 '19 at 20:33
  • Yeah, can't reproduce this on my WSL install either. Pretty bizarre breakage, because the "problem machine" is doing....quite sophisticated builds on a regular basis without issue. Thanks very much for the help – zzxyz May 15 '19 at 20:38
0

Fred and R2RT both led me to the correct answer for my problem. My cmake build (a pull from the main github repo), was the issue. I switched to the distro build and this issue disappeared. I'm not quite sure why my build was a problem, but....there you have it. (cmake v. went from 3.14 to 3.10, but I believe my build of cmake 3.14 was the problem vs 3.14 specifically)

zzxyz
  • 2,953
  • 1
  • 16
  • 31
0

Have a similar problem too. Building object libraries with cmake, ninja and gcc causes a rebuild even without changes, if I switch the compiler to another one, some changes in the headers doesn't get detected and ninja thinks there is nothing to do.

I then switched to GNU make and now everything works as expected, although it is a bit slow compared to ninja. For me, this looks like a bug.

Stef
  • 21
  • 2