5

I'm developing my application using C++ and cmake.

I'd like to check each C++ header file includes required include files correctly.

Here is an example:

a.hpp

inline void func_a() {
}

b.hpp

// #include "a.hpp" is missing
inline void func_b() {
    func_a();
}

main.cpp

#include "a.hpp"
#include "b.hpp"

int main() {}

Demo: https://wandbox.org/permlink/kZqoNHMYARIB3bc1

b.hpp should include a.hpp. Let's say b.hpp missing include a.hpp. If main.cpp include a.hpp before b.hpp, no compile error is occurred. If include order is opposite, compile error is occurred.

I'd like to check this kind of problem.

I'm using fly-check on emacs. It checks this problem well. I'd like to some checking mechanism into my cmake build system.

For example, if I execute make depcheck, then compile error is detected.

I think that if I setup a cmake target that compiles all header files individually but not link, the expected compile error would be reported.

I couldn't find how to setup that, so far.

Is there any way to do that? Or other ways to achieve the goal ?

my header file inclusion policy

Each header file should include header files that contain required element. In other words, each header file should compile individually.

What I want to achieve

I want to know the way to automatically detect b.hpp is missing `#include "a.hpp" by tool assist. The tool means not editor. I guess that cmake could do that. I'm trying to find the way.

Takatoshi Kondo
  • 3,111
  • 17
  • 36
  • try https://include-what-you-use.org/ – Alan Birtles Sep 11 '19 at 08:28
  • *"I'm using fly-check on emacs. It checks this problem well."* - are you sure that it does? How does it deal with problem of indirect includes (when `b.hpp` includes `c.hpp` that includes `a.hpp`) and with problem of private includes (when `a.hpp` must not be included directly and `c.hpp` must be included instead)? – user7860670 Sep 11 '19 at 08:30
  • related: https://stackoverflow.com/q/3644293/4770166 https://stackoverflow.com/q/41214159/4770166 – RHertel Sep 11 '19 at 08:31
  • @AlanBirtles, thank you. I will check it. – Takatoshi Kondo Sep 11 '19 at 08:34
  • @VTT, I guess that fly-check compiles currently opened file directly on background. It doesn't compile from main.cpp. So it can detect func_a() is missing at b.hpp. – Takatoshi Kondo Sep 11 '19 at 08:36
  • Possible duplicate of [Google C++ Style Guide include order](https://stackoverflow.com/questions/54347804/google-c-style-guide-include-order) – Robert Andrzejuk Sep 11 '19 at 16:27
  • @RobertAndrzejuk, My goal is how to check missing include file. Not what is the best inclusion rule. – Takatoshi Kondo Sep 12 '19 at 01:31

2 Answers2

3

Conventional wisdom is to add source files to every header. Even if b.cpp includes only this line:

include "b.hpp" // Note, this should be the first include

That way, you can compile every cpp file in isolation, and a successful compilation means the corresponding header is self-contained.

Of course, if you have an implementation file already, then moving the corresponding header to be included first goes towards ensuring that.

StoryTeller - Unslander Monica
  • 165,132
  • 21
  • 377
  • 458
  • I believe this is what naïve include checkers do. Successful compilation does not actually mean that file includes necessary include files, for example lack of config include or lack of include of some template specialization will cause compilation to yield incorrect result so the header checked is not actually self-contained. – user7860670 Sep 11 '19 at 08:34
  • 1
    @VTT - One can find chinks in the armor of every method. It's still conventional wisdom that works for non-pathological cases. – StoryTeller - Unslander Monica Sep 11 '19 at 08:35
  • @StoryTeller, thank you for the answer. But I'm looking for a way that automatically find these kind of missing inclusion. Even if main.cpp successfully compiled by accident. – Takatoshi Kondo Sep 11 '19 at 08:39
3

@StoryTeller 's answer

Conventional wisdom is to add source files to every header.

is appropriate way to achieve the goal. It requires adding many source files. It is annoying work especially I develop a header only library.

How to automate that process?

I found a way to check missing include file on cmake. The strategy is compile each header files individually and directly.

Here is CMakeLists.txt

cmake_minimum_required(VERSION 3.8.2)
project(test_checker)

add_custom_target(chkdeps)

file(GLOB HDR_ROOT "*.hpp")
FOREACH (HDR ${HDR_ROOT})
    message(STATUS "${HDR}")
    get_filename_component(HDR_WE ${HDR} NAME_WE)
    SET(CHK_TARGET "${HDR_WE}.chk")
    add_custom_target(
        ${CHK_TARGET}
        COMMAND ${CMAKE_CXX_COMPILER} -c ${HDR}
        VERBATIM
    )
    add_dependencies(chkdeps ${CHK_TARGET})
ENDFOREACH ()

To check missing include files, execute make chkdeps.

In order to do only compile, I use add_custom_target. The custom target name is chkdeps (check dependencies). This is the target for all header files dependency checking.

I get the list of *.hpp using file(GLOB HDR_ROOT "*.hpp"). For each got files, I add custom target for only compile using add_custom_target.

I add the extension .chk to avoid conflict. For example if the file name is a.hpp then the target name is a.chk.

I execute the COMMAND ${CMAKE_CXX_COMPILER} with -c option. The -c option is for only compile. I only tested the cmake on Linux. I know setting compile option directly is not good for cross platform development. cmake might provides compile only cross platform mechanism. But I couldn't find it, so far.

Then I add dependency to chkdeps using add_dependencies. Due to this dependency, when I execute make chkdeps, all custom targets (a.chk and b.chk) run.

When I run make chkdeps, then I got the expected error "'func_a' was not declared in this scope" as follows.

make chkdeps                                                                                                                                                    
Built target a.chk
/home/kondo/work/tmp/fly_check/b.hpp: In function 'void func_b()':
/home/kondo/work/tmp/fly_check/b.hpp:3:5: error: 'func_a' was not declared in this scope; did you mean 'func_b'?
    3 |     func_a();
      |     ^~~~~~
      |     func_b
make[3]: *** [CMakeFiles/b.chk.dir/build.make:57: CMakeFiles/b.chk] Error 1
make[2]: *** [CMakeFiles/Makefile2:78: CMakeFiles/b.chk.dir/all] Error 2
make[1]: *** [CMakeFiles/Makefile2:113: CMakeFiles/chkdeps.dir/rule] Error 2
make: *** [Makefile:131: chkdeps] Error 2
Takatoshi Kondo
  • 3,111
  • 17
  • 36