126cdb8e0d
The idea was given to me by Daniel Pfeifer (@purpleKarrot), who said it may actually help find ODR issues better. In any case, this simplifies the script and reduces the (otherwise overwhelming) number of targets.
129 lines
4.7 KiB
CMake
129 lines
4.7 KiB
CMake
# 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()
|