Extending GENERATED property to additional compiler output files?

In certain configurations, output files may be produced in the build directory that CMake is unaware of.

For example, when compiling C++ code with the -ftest-coverage -fprofile-arcs flags to produce coverage data, each compiled source.cpp file will result in the creation of both an object file (source.cpp.o) and a source.cpp.gcno coverage data file in the output directory (typically CMakeFiles/${Target_NAME}.dir/).

Later, when the compiled program is executed and coverage analysis performed, those two files will be joined by a third source.cpp.gcda file.

Because CMake has no knowledge of either the .gcno or the .gcda file, they are not considered GENERATED output files, and will not be removed when make clean is run on the build tree (assuming the Unix Makefile generator is in use).

And because none of those files exist at the time the build tree is generated, even brute-force methods like globbing for those filenames in CMakeLists.txt won’t work.

Is there any way to “expand”, either explicitly or implicitly, CMake’s list of output files created (or potentially created) during the build process / inside the build tree, so that the additional object “sidecar” files are also considered to be GENERATED files, so that they’ll be removed when make clean is run?

There is the ADDITIONAL_CLEAN_FILES directory property. You can list files in that property and they will be added to the list of files that will be removed on a make clean. I suspect that will get pretty tedious though, since you’d have to manually specify the coverage output files for every target and you’re talking about removing files that are in an implementation-specific location (meaning there’s no guarantee that location won’t change in the future, though that seems unlikely).

An alternative would be to create a custom target that searches recursively for all files that match *.gcda or *.gcno in the build tree and removes them. That target would need to be invoked explicitly rather than relying on make clean, but it would be more reliable and also wouldn’t be tied to a particular CMake generator or internal implementation detail.

The lack of cleanup for the additional files isn’t purely an issue of neurotic housekeeping, BTW. It can impact operations in the build tree, especially when combined with generated source files. Consider the following scenario:

  1. A build tree is generated, and make run to compile all of the defined targets.
  2. make clean is subsequently run, clearing out all of the object files and the generated source files, but not the .gcno files that were created alongside the object files.
  3. A coverage-reporting target is run which depends on some of the build tree’s targets, but not all of them.

Because coverage reporting tools typically perform recursive scans for .gcno files, and only the dependent targets will be rebuilt before the coverage tool runs, the leftover source.cpp.gcno files contain references to generated source files which no longer exist, which will cause warnings to be emitted.

The geninfo tool in lcov, for example, will display messages similar to the following for each leftover .gcno file produced when compiling AUTOMOC results:

Processing src/CMakeFiles/target.dir/target_autogen/mocs_compilation.cpp.gcno
geninfo: WARNING: could not open 
/path/to/build/src/target_autogen/IDENTIFIER/moc_QObjectSource1.cpp
geninfo: WARNING: could not open 
/path/to/build/src/target_autogen/IDENTIFIER/moc_QObjectSource2.cpp
geninfo: WARNING: some exclusion markers may be ignored

Yeah, and the AUTOMOC situation highlights another difficulty with that approach, unfortunately — because the files’ creation already relies on an implementation-specific process, even the names and (especially) paths of the source files are dynamically generated, in addition to the output files.

In fact, the source file location does change, since the IDENTIFIER part of the AUTOMOC output path is a (semi-?)random string. (Though that doesn’t really affect the output files, since those are predictably created as CMakeFiles/target1.dir/target2_autogen/mocs_compilation.cpp.gc{no,da}.)