123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128 |
- # Copyright Louis Dionne 2013-2017
- # Distributed under the Boost Software License, Version 1.0.
- # (See accompanying file LICENSE.md or copy at http://boost.org/LICENSE_1_0.txt)
- #
- #
- # This CMake module provides a function generating a unit test to make sure
- # that every public header can be included on its own.
- #
- # When a C++ library or application has many header files, it can happen that
- # a header does not include all the other headers it depends on. When this is
- # the case, it can happen that including that header file on its own will
- # break the compilation. This CMake module generates a dummy executable
- # comprised of many .cpp files, each of which includes a header file that
- # is part of the public API. In other words, the executable is comprised
- # of .cpp files of the form:
- #
- # #include <the/public/header.hpp>
- #
- # and then exactly one `main` function. If this succeeds to compile, it means
- # that the header can be included on its own, which is what clients expect.
- # Otherwise, you have a problem. Since writing these dumb unit tests by hand
- # is tedious and repetitive, you can use this CMake module to automate this
- # task.
- # add_header_test(<target> [EXCLUDE_FROM_ALL] [EXCLUDE excludes...] HEADERS headers...)
- #
- # Generates header-inclusion unit tests for all the specified headers.
- #
- # This function creates a target which builds a dummy executable including
- # each specified header file individually. If this target builds successfully,
- # it means that all the specified header files can be included individually.
- #
- # Parameters
- # ----------
- # <target>:
- # The name of the target to generate.
- #
- # HEADERS headers:
- # A list of header files to generate the inclusion tests for. All headers
- # in this list must be represented as relative paths from the root of the
- # include directory added to the compiler's header search path. In other
- # words, it should be possible to include all headers in this list as
- #
- # #include <${header}>
- #
- # For example, for a library with the following structure:
- #
- # project/
- # doc/
- # test/
- # ...
- # include/
- # boost/
- # hana.hpp
- # hana/
- # transform.hpp
- # tuple.hpp
- # pair.hpp
- # ...
- #
- # When building the unit tests for that library, we'll add `-I project/include'
- # to the compiler's arguments. The list of public headers should therefore contain
- #
- # boost/hana.hpp
- # boost/hana/transform.hpp
- # boost/hana/tuple.hpp
- # boost/hana/pair.hpp
- # ...
- #
- # Usually, all the 'public' header files of a library should be tested for
- # standalone inclusion. A header is considered 'public' if a client should
- # be able to include that header on its own.
- #
- # [EXCLUDE excludes]:
- # An optional list of headers or regexes for which no unit test should be
- # generated. Basically, any header in the list specified by the `HEADERS`
- # argument that matches anything in `EXCLUDE` will be skipped.
- #
- # [EXCLUDE_FROM_ALL]:
- # If provided, the generated target is excluded from the 'all' target.
- #
- function(add_header_test target)
- cmake_parse_arguments(ARGS "EXCLUDE_FROM_ALL" # options
- "" # 1 value args
- "HEADERS;EXCLUDE" # multivalued args
- ${ARGN})
- if (NOT ARGS_HEADERS)
- message(FATAL_ERROR "The `HEADERS` argument must be provided.")
- endif()
- if (ARGS_EXCLUDE_FROM_ALL)
- set(ARGS_EXCLUDE_FROM_ALL "EXCLUDE_FROM_ALL")
- else()
- set(ARGS_EXCLUDE_FROM_ALL "")
- endif()
- foreach(header ${ARGS_HEADERS})
- set(skip FALSE)
- foreach(exclude ${ARGS_EXCLUDE})
- if (${header} MATCHES ${exclude})
- set(skip TRUE)
- break()
- endif()
- endforeach()
- if (skip)
- continue()
- endif()
- get_filename_component(filename "${header}" NAME_WE)
- get_filename_component(directory "${header}" DIRECTORY)
- set(source "${CMAKE_CURRENT_BINARY_DIR}/headers/${directory}/${filename}.cpp")
- if (NOT EXISTS "${source}")
- file(WRITE "${source}" "#include <${header}>")
- endif()
- list(APPEND sources "${source}")
- endforeach()
- set(standalone_main "${CMAKE_CURRENT_BINARY_DIR}/headers/_standalone_main.cpp")
- if (NOT EXISTS "${standalone_main}")
- file(WRITE "${standalone_main}" "int main() { }")
- endif()
- add_executable(${target}
- ${ARGS_EXCLUDE_FROM_ALL}
- ${sources}
- "${CMAKE_CURRENT_BINARY_DIR}/headers/_standalone_main.cpp"
- )
- endfunction()
|