TestHeaders.cmake 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128
  1. # Copyright Louis Dionne 2013-2017
  2. # Distributed under the Boost Software License, Version 1.0.
  3. # (See accompanying file LICENSE.md or copy at http://boost.org/LICENSE_1_0.txt)
  4. #
  5. #
  6. # This CMake module provides a function generating a unit test to make sure
  7. # that every public header can be included on its own.
  8. #
  9. # When a C++ library or application has many header files, it can happen that
  10. # a header does not include all the other headers it depends on. When this is
  11. # the case, it can happen that including that header file on its own will
  12. # break the compilation. This CMake module generates a dummy executable
  13. # comprised of many .cpp files, each of which includes a header file that
  14. # is part of the public API. In other words, the executable is comprised
  15. # of .cpp files of the form:
  16. #
  17. # #include <the/public/header.hpp>
  18. #
  19. # and then exactly one `main` function. If this succeeds to compile, it means
  20. # that the header can be included on its own, which is what clients expect.
  21. # Otherwise, you have a problem. Since writing these dumb unit tests by hand
  22. # is tedious and repetitive, you can use this CMake module to automate this
  23. # task.
  24. # add_header_test(<target> [EXCLUDE_FROM_ALL] [EXCLUDE excludes...] HEADERS headers...)
  25. #
  26. # Generates header-inclusion unit tests for all the specified headers.
  27. #
  28. # This function creates a target which builds a dummy executable including
  29. # each specified header file individually. If this target builds successfully,
  30. # it means that all the specified header files can be included individually.
  31. #
  32. # Parameters
  33. # ----------
  34. # <target>:
  35. # The name of the target to generate.
  36. #
  37. # HEADERS headers:
  38. # A list of header files to generate the inclusion tests for. All headers
  39. # in this list must be represented as relative paths from the root of the
  40. # include directory added to the compiler's header search path. In other
  41. # words, it should be possible to include all headers in this list as
  42. #
  43. # #include <${header}>
  44. #
  45. # For example, for a library with the following structure:
  46. #
  47. # project/
  48. # doc/
  49. # test/
  50. # ...
  51. # include/
  52. # boost/
  53. # hana.hpp
  54. # hana/
  55. # transform.hpp
  56. # tuple.hpp
  57. # pair.hpp
  58. # ...
  59. #
  60. # When building the unit tests for that library, we'll add `-I project/include'
  61. # to the compiler's arguments. The list of public headers should therefore contain
  62. #
  63. # boost/hana.hpp
  64. # boost/hana/transform.hpp
  65. # boost/hana/tuple.hpp
  66. # boost/hana/pair.hpp
  67. # ...
  68. #
  69. # Usually, all the 'public' header files of a library should be tested for
  70. # standalone inclusion. A header is considered 'public' if a client should
  71. # be able to include that header on its own.
  72. #
  73. # [EXCLUDE excludes]:
  74. # An optional list of headers or regexes for which no unit test should be
  75. # generated. Basically, any header in the list specified by the `HEADERS`
  76. # argument that matches anything in `EXCLUDE` will be skipped.
  77. #
  78. # [EXCLUDE_FROM_ALL]:
  79. # If provided, the generated target is excluded from the 'all' target.
  80. #
  81. function(add_header_test target)
  82. cmake_parse_arguments(ARGS "EXCLUDE_FROM_ALL" # options
  83. "" # 1 value args
  84. "HEADERS;EXCLUDE" # multivalued args
  85. ${ARGN})
  86. if (NOT ARGS_HEADERS)
  87. message(FATAL_ERROR "The `HEADERS` argument must be provided.")
  88. endif()
  89. if (ARGS_EXCLUDE_FROM_ALL)
  90. set(ARGS_EXCLUDE_FROM_ALL "EXCLUDE_FROM_ALL")
  91. else()
  92. set(ARGS_EXCLUDE_FROM_ALL "")
  93. endif()
  94. foreach(header ${ARGS_HEADERS})
  95. set(skip FALSE)
  96. foreach(exclude ${ARGS_EXCLUDE})
  97. if (${header} MATCHES ${exclude})
  98. set(skip TRUE)
  99. break()
  100. endif()
  101. endforeach()
  102. if (skip)
  103. continue()
  104. endif()
  105. get_filename_component(filename "${header}" NAME_WE)
  106. get_filename_component(directory "${header}" DIRECTORY)
  107. set(source "${CMAKE_CURRENT_BINARY_DIR}/headers/${directory}/${filename}.cpp")
  108. if (NOT EXISTS "${source}")
  109. file(WRITE "${source}" "#include <${header}>")
  110. endif()
  111. list(APPEND sources "${source}")
  112. endforeach()
  113. set(standalone_main "${CMAKE_CURRENT_BINARY_DIR}/headers/_standalone_main.cpp")
  114. if (NOT EXISTS "${standalone_main}")
  115. file(WRITE "${standalone_main}" "int main() { }")
  116. endif()
  117. add_executable(${target}
  118. ${ARGS_EXCLUDE_FROM_ALL}
  119. ${sources}
  120. "${CMAKE_CURRENT_BINARY_DIR}/headers/_standalone_main.cpp"
  121. )
  122. endfunction()