From 338dd5f213ecd4dd2f18f8220cfdb76b1388f026 Mon Sep 17 00:00:00 2001 From: Moritz Beutel Date: Sun, 2 Feb 2020 19:46:57 +0100 Subject: [PATCH 1/3] Generate a target for checking package dependencies --- modules/InstallBasicPackageFiles.cmake | 86 ++++++++++++++++++++++++++ 1 file changed, 86 insertions(+) diff --git a/modules/InstallBasicPackageFiles.cmake b/modules/InstallBasicPackageFiles.cmake index d00e80d9b..a251bfde3 100644 --- a/modules/InstallBasicPackageFiles.cmake +++ b/modules/InstallBasicPackageFiles.cmake @@ -27,6 +27,7 @@ # [UPPERCASE_FILENAMES | LOWERCASE_FILENAMES] # [DEPENDENCIES " [...]" ...] # [PRIVATE_DEPENDENCIES " [...]" ...] +# [NO_PACKAGE_DEPENDENCIES_CHECK] # [INCLUDE_FILE | INCLUDE_CONTENT ] # [COMPONENT ] # (default = "") # ) @@ -59,6 +60,13 @@ # argument, since the ``PRIVATE_DEPENDENCIES`` argument would work only when # :variable:`BUILD_SHARED_LIBS` is disabled. # +# If an export set is installed, and if testing is enabled (cf. +# :cmake:command:`enable_testing`), an extra target +# ``check-package-dependencies`` is generated which fails to build if one of +# the exported targets has a dependency which is not listed among +# ``DEPENDENCIES`` or ``PRIVATE_DEPENDENCIES``. This check can be suppressed +# by setting the ``NO_PACKAGE_DEPENDENCIES_CHECK`` option. +# # When using a custom template file, the ``@PACKAGE_DEPENDENCIES@`` # string is replaced with the code checking for the dependencies # specified by these two argument. @@ -239,6 +247,7 @@ function(INSTALL_BASIC_PACKAGE_FILES _Name) NO_EXPORT NO_SET_AND_CHECK_MACRO NO_CHECK_REQUIRED_COMPONENTS_MACRO + NO_PACKAGE_DEPENDENCIES_CHECK UPPERCASE_FILENAMES LOWERCASE_FILENAMES NO_COMPATIBILITY_VARS # Deprecated @@ -670,6 +679,83 @@ ${_compatibility_vars} export(${_export_cmd} NAMESPACE ${_IBPF_NAMESPACE} FILE "${_IBPF_EXPORT_DESTINATION}/${_targets_filename}") + + # To validate package dependencies, generate a subproject which consumes our build directory as a package + # and contains an executable target which links to all targets our package exports. Then bind the subproject + # to a target using `ExternalProject_Add()`. + + # In order to reuse the same compiler toolchain for the subproject, we need to pick a language that is supported + # by the toolchain. + get_property(_enabled_languages GLOBAL PROPERTY ENABLED_LANGUAGES) + if("CXX" IN_LIST _enabled_languages) + set(_probe_lang "CXX") + set(_probe_lang_ext ".cpp") + elseif("C" IN_LIST _enabled_languages) + set(_probe_lang "C") + set(_probe_lang_ext ".c") + elseif("Fortran" IN_LIST _enabled_languages) + set(_probe_lang "Fortran") + set(_probe_lang_ext ".f77") + elseif("CUDA" IN_LIST _enabled_languages) + set(_probe_lang "CUDA") + set(_probe_lang_ext ".cu") + elseif("CSharp" IN_LIST _enabled_languages) + set(_probe_lang "CSharp") + set(_probe_lang_ext ".cs") + elseif("OBJC" IN_LIST _enabled_languages) + set(_probe_lang "OBJC") + set(_probe_lang_ext ".m") + elseif("OBJCPP" IN_LIST _enabled_languages) + set(_probe_lang "OBJCPP") + set(_probe_lang_ext ".mm") + elseif("Swift" IN_LIST _enabled_languages) + set(_probe_lang "Swift") + set(_probe_lang_ext ".swift") + elseif("ASM" IN_LIST _enabled_languages) + set(_probe_lang "ASM") + set(_probe_lang_ext ".asm") + else() + message(AUTHOR_WARNING "No known language is available; cannot check package dependencies") + set(_IBPF_NO_PACKAGE_DEPENDENCIES_CHECK TRUE) + endif() + + # Only check the package dependencies if testing was previously enabled with a call to `enable_testing()`, and + # if `NO_PACKAGE_DEPENDENCIES_CHECK` was not set. + if(CMAKE_TESTING_ENABLED AND NOT _IBPF_NO_PACKAGE_DEPENDENCIES_CHECK) + # Generate an empty source file; the subproject is only configured, never built. + file(WRITE "${_IBPF_EXPORT_DESTINATION}/check-package-dependencies/main${_probe_lang_ext}" "") + + # Generate the subproject definition file: a project with a single executable target that links all exported + # targets. Unfortunately CMake doesn't currently support enumerating the targets in an export set (cf. + # https://gitlab.kitware.com/cmake/cmake/issues/19333), hence we parse the targets file generated by CMake + # for `add_library( ... IMPORTED)` statements. Note that this needs to be done in the subproject + # because CMake writes the targets file only *after* the current CMake script has been processed. + file(WRITE "${_IBPF_EXPORT_DESTINATION}/check-package-dependencies/CMakeLists.txt" +"cmake_minimum_required(VERSION 3.12) +project(${_Name}-check-package-dependencies LANGUAGES ${_probe_lang}) +find_package(${_Name} ${_IBPF_VERSION} EXACT REQUIRED CONFIG) +add_executable(${_Name}-check-package-dependencies \"main${_probe_lang_ext}\") +list(INSERT CMAKE_MODULE_PATH 0 \"${_IBPF_EXPORT_DESTINATION}\") +file(STRINGS \"${_IBPF_EXPORT_DESTINATION}/${_targets_filename}\" _imported_targets) +foreach(_target \${_imported_targets}) + if(_target MATCHES \"^[ ]*add_library\\\\(([A-Za-z:_-][A-Za-z0-9:_-]*) .* IMPORTED([ ]*)\\\\)[ ]*$\") + target_link_libraries(${_Name}-check-package-dependencies PRIVATE \${CMAKE_MATCH_1}) + endif() +endforeach()") + + # There is no `CONFIG_ALWAYS` argument for `ExternalProject_Add()`. But `ExternalProject_Add()` will re-run + # the configuration if any of the config arguments changes, so we force a new configuration run by passing + # a timestamp as an argument. + string(TIMESTAMP _timestamp UTC) + + include(ExternalProject) + ExternalProject_Add(check-package-dependencies + SOURCE_DIR "${_IBPF_EXPORT_DESTINATION}/check-package-dependencies" + BINARY_DIR "${_IBPF_EXPORT_DESTINATION}/check-package-dependencies/build" + CMAKE_ARGS "--no-warn-unused-cli -D_ibpf_timestamp=${_timestamp}" + BUILD_COMMAND "" # never build + INSTALL_COMMAND "") # never install + endif() endif() # Export build directory if CMAKE_EXPORT_PACKAGE_REGISTRY is set. From ecdbcdf58b88c2373296280c858f41fcb62d5085 Mon Sep 17 00:00:00 2001 From: Moritz Beutel Date: Sun, 2 Feb 2020 21:09:03 +0100 Subject: [PATCH 2/3] Very minor changes --- modules/InstallBasicPackageFiles.cmake | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/modules/InstallBasicPackageFiles.cmake b/modules/InstallBasicPackageFiles.cmake index a251bfde3..a26c19dcf 100644 --- a/modules/InstallBasicPackageFiles.cmake +++ b/modules/InstallBasicPackageFiles.cmake @@ -681,7 +681,7 @@ ${_compatibility_vars} FILE "${_IBPF_EXPORT_DESTINATION}/${_targets_filename}") # To validate package dependencies, generate a subproject which consumes our build directory as a package - # and contains an executable target which links to all targets our package exports. Then bind the subproject + # and defines an executable target which links to all targets our package exports. Then bind the subproject # to a target using `ExternalProject_Add()`. # In order to reuse the same compiler toolchain for the subproject, we need to pick a language that is supported @@ -722,6 +722,7 @@ ${_compatibility_vars} # Only check the package dependencies if testing was previously enabled with a call to `enable_testing()`, and # if `NO_PACKAGE_DEPENDENCIES_CHECK` was not set. if(CMAKE_TESTING_ENABLED AND NOT _IBPF_NO_PACKAGE_DEPENDENCIES_CHECK) + # Generate an empty source file; the subproject is only configured, never built. file(WRITE "${_IBPF_EXPORT_DESTINATION}/check-package-dependencies/main${_probe_lang_ext}" "") From d8db91b6701fa6b0dd0c8efd97768b2f0cddd6fd Mon Sep 17 00:00:00 2001 From: Moritz Beutel Date: Mon, 3 Feb 2020 12:05:58 +0100 Subject: [PATCH 3/3] Don't raise a warning if `NO_PACKAGE_DEPENDENCIES_CHECK` is defined --- modules/InstallBasicPackageFiles.cmake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/InstallBasicPackageFiles.cmake b/modules/InstallBasicPackageFiles.cmake index a26c19dcf..938c17681 100644 --- a/modules/InstallBasicPackageFiles.cmake +++ b/modules/InstallBasicPackageFiles.cmake @@ -714,7 +714,7 @@ ${_compatibility_vars} elseif("ASM" IN_LIST _enabled_languages) set(_probe_lang "ASM") set(_probe_lang_ext ".asm") - else() + elseif(NOT _IBPF_NO_PACKAGE_DEPENDENCIES_CHECK) message(AUTHOR_WARNING "No known language is available; cannot check package dependencies") set(_IBPF_NO_PACKAGE_DEPENDENCIES_CHECK TRUE) endif()