How can I let CMake check whether my headers are self-sufficient?
Asked Answered
T

2

7

Setup

I have a project that builds and runs fine with CMake. My project setup is like this:

├── CMakeLists.txt
|
├── include/
│   └── standalone/
│       └── x.hpp
|
├── src/
    └── standalone/
        └── main.cpp

The contents of my headers is like this:

// ------x.hpp--------
#pragma once
#include <iostream>

class X
{
public:
  void hello()
  {
    std::cout << "Hello World!\n"; // needs <iostream>
  }
};

// -------main.cpp-------
#include <standalone/x.hpp>

int main()
{
  X x;
  x.hello();
}

I use the following CMakeLists.txt

cmake_minimum_required(VERSION 2.8)
project(standalone)

###############################################################
# Compiler settings
###############################################################

# use C++11 features
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11")

# set warning level
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Werror -pedantic -pedantic-errors -Wall -Wextra")

###############################################################
# header dependencies
###############################################################

# compile against project headers
include_directories(${PROJECT_SOURCE_DIR}/include)

# the header files
file(GLOB_RECURSE header_files FOLLOW_SYMLINKS "include/*.hpp")

# the source files
file(GLOB_RECURSE source_files FOLLOW_SYMLINKS "src/*.cpp")

###############################################################
# build target
###############################################################

# the program depends on the header files and the source files
add_executable(main ${header_files} ${source_files})

The sequence of commands mkdir build, cd build, cmake .., make, and ./main correctly prints "Hello World!" without warnings.

Problem

The above setup is correct. But suppose <iostream> was not included in x.hpp but in main.cpp instead. Then the program would still have built correctly, but the x.hpp would not be a standalone header. So I would like to test for self-sufficiency of my headers, i.e. for every header I would like to compile a small test program

#include "some_header.hpp"
int main() {}

However, if I add the following section to the CMakeList.txt

###############################################################
# header self-sufficiency
###############################################################

set(CMAKE_REQUIRED_FLAGS ${CMAKE_CXX_FLAGS})
message("REQUIRED_FLAGS=${CMAKE_REQUIRED_FLAGS}")
set(CMAKE_REQUIRED_INCLUDES ${CMAKE_SOURCE_DIR}/include)
message("REQUIRED_INCLUDES=${CMAKE_REQUIRED_INCLUDES}")
include(CheckIncludeFiles)
check_include_files("${header_files}" IS_STANDALONE)

The macro ${header_files} correctly expands to the header x.hpp, but the check_include_files() command does not correctly compile it

REQUIRED_FLAGS= -std=c++11 -Werror -pedantic -pedantic-errors -Wall -Wextra
REQUIRED_INCLUDES=/home/rein/projects/standalone/include
-- Looking for include file /home/rein/projects/standalone/include/standalone/x.hpp
-- Looking for include file /home/rein/projects/standalone/include/standalone/x.hpp - not found.

Question

Apparantly I am missing some configuration variable that lets CMake search in the right place. Even for correct headers, check_include_files() does not work. What do I need to do to make this work? Only when correct headers are deemed correct, can I go on and test incorrect headers.

NOTE Unless it's absolutey necessary, I am not interested in shell scripts or elaborate CMake for loops that directly call TRY_COMPILE or something like that. AFAICS, that's what the CheckIncludeFiles module is for, and I'd like to know how to correctly configure that, if at all possible.

Tessellated answered 7/5, 2013 at 22:8 Comment(3)
This answer to a question about Self-sufficient headers in C/C++ has a script, chkhdr, that can be used for testing headers self-containedness.Yasmineyasu
@JonathanLeffler Thanks, yes I was aware of your script, and will check it out (in the mean time I have found another script, see my answer). It's disappointing that it is not straightforward with CMake (I am trying to keep things Windows-compatible).Tessellated
One reason I didn't post it as an answer is that I'm not sure how to integrate it into CMake. Sorry I can't help there.Yasmineyasu
B
6

For C++ headers rather than C headers, you need to use CheckIncludeFileCXX. This module also differs from CheckIncludeFiles in that you can only pass a single file at a time to this macro.

It could well also be that you need to set CMAKE_REQUIRED_INCLUDES, or one of the other variables listed in the docs.

For example, if some of your headers refer to eachother (as in #include "h1.hpp"), then you'll need:

set(CMAKE_REQUIRED_FLAGS ${CMAKE_CXX_FLAGS})
set(CMAKE_REQUIRED_INCLUDES ${CMAKE_SOURCE_DIR}/include)
include(CheckIncludeFileCXX)
foreach(project_header ${project_headers})
  get_filename_component(header_name ${project_header} NAME_WE)
  check_include_file_cxx("${project_header}" ${header_name}_IS_STANDALONE)
endforeach()

There's a little more info in the CMake wiki article How To Write Platform Checks.

Bedraggled answered 7/5, 2013 at 22:31 Comment(8)
Thanks for your answer. I modified my question to a self-sufficient example. Your suggestion does not work, however. Do I need to write a loop over all the headers and write check_include_file() (singular, not plural!) to get this right?Tessellated
You shouldn't have to, but it would be better to do so. It's more how the function was designed to be used I think, and it'll give you more fine-grained results. Just now, if you pass 4 headers into the command and any one fails, the whole command fails (which is what you're seeing). So it could be that one of your headers actually isn't self-sufficient. Also, in your example I see you've got set(CMAKE_REQUIRED_INCLUDE ...) rather than set(CMAKE_REQUIRED_INCLUDES ...) - probably a transcription error I guess?Bedraggled
You were right, I made a typo on my question (not in my code!): there should be a final S in CMAKE_REQUIRED_INCLUDES. Note that I modified my question to an example consisting of a single header file and a single source file.Tessellated
See my own answer for why I think this cannot be made to work. Or can your answer actually be made to work for my setup?Tessellated
@rhalbersma I've updated my answer to what should hopefully be a workable solution now. Sorry I never spotted the C vs C++ issue before!Bedraggled
Thanks for digging further! Your solution worked! BTW, I noticed that CheckIncludeFileCXX only checks whether the header is standalone, i.e. it compiles #include "bla.hpp" int main(){}, but it doesn't check the include guards by compiling #include "bla.hpp" #include "blah.hpp" int main(){}. Do you have any idea how to do that, or should I ask a new question for that?Tessellated
@rhalbersma Yes - you should probably ask a new question for that I guess. I'm not sure if that might be beyond the scope of the CheckIncludeFileCXX module. It would be unusual, but I suppose you could have a header which only #included other headers which were properly guarded, and/or defined typedefs, so there would be no problem with it missing guards itself. If the intent is just to check it compiles OK, that's fine. If the intent is to enforce a coding policy it would fail in that regard.Bedraggled
OK, I just ran a quick grep -L -nr "#pragma once" . and it didn't find anything (yeah, I hate the standard include guards)Tessellated
T
1

After browsing the CMakeFiles/CMakeError.log output, it appeara there are two fundamental limitations to check_include_files() that cannot be overcome with the allowed configuration options (there appears to be some confirmation of these points):

  1. the C compiler is always called, not the C++ compiler. This means that -std=C++11 is not a valid command line option to pass.
  2. the standard system include paths (/usr/include etc.) are ignored even when added manually with set(CMAKE_REQUIRED_INCLUDES ${CMAKE_SOURCE_DIR}/include "/usr/include"). The #include <iostream> inside x.hpp is therefore not correctly found.

The error log has this fragment:

/usr/bin/gcc   -I/home/rein/projects/standalone/include    -o CMakeFiles/cmTryCompileExec4052324189.dir/CheckIncludeFiles.c.o   -c /home/rein/projects/standalone/build/CMakeFiles/CMakeTmp/CheckIncludeFiles.c
In file included from /home/rein/projects/standalone/build/CMakeFiles/CMakeTmp/CheckIncludeFiles.c:2:0:
/home/rein/projects/standalone/include/standalone/x.hpp:2:20: fatal error: iostream: No such file or directory
compilation terminated.

This essentially limits the idea of checking for self-sufficiency to system C headers. It appears I will have to seek out shell script alternatives, e.g. the one on this SO question.

UPDATE: thanks to @Fraser 's updated answer, my question is now resolved by doing check_include_file_cxx with the compiler flags and include paths set beforehand.

Tessellated answered 8/5, 2013 at 18:15 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.