diff --git a/CHANGELOG.md b/CHANGELOG.md index 7035f6f061291630674b1aaa97940c900db594c6..de0a783ec162af753b584e95f1a3f28742f5ea1e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -73,6 +73,13 @@ In order to build the DUNE core modules you need at least the following software * Libraries that _consume_ `Dune::${EXPORT_NAME}` will only be forward compatible with Dune 2.10. * Libraries that _consume_ `<lib>` will be supported until compatibility with Dune 2.9 is not required anymore. +- Generation of `config.h` is overhauled and split in a public and a private config file. Only + the public file is installed and consumed by down-stream modules. For compatibility, the + old single file is created, too. + +- The CMake macro `finalize_dune_project` no longer has an optional argument, a config file is + always created. + - Remove the `ALLOW_CXXFLAGS_OVERWRITE` configure option. The `CXXFLAGS` overload is still turned on for the JIT compiled Python modules. See the description of the MR diff --git a/bin/duneproject b/bin/duneproject index 2a62b24c38103d9ba1acf48c08abdb1b63c76d52..eb073981a35ddb6a5b89cfbf422c0f5ee4d4169f 100755 --- a/bin/duneproject +++ b/bin/duneproject @@ -428,7 +428,7 @@ add_subdirectory(doc) add_subdirectory(cmake/modules) # finalize the dune project, e.g. generating config.h etc. -finalize_dune_project(GENERATE_CONFIG_H_CMAKE) +finalize_dune_project() M_DELIM ################## PROJECT.PC.IN ################## diff --git a/cmake/modules/DuneProject.cmake b/cmake/modules/DuneProject.cmake index 85f590968407abf1b87ff3739873924a7a35150f..5dca0b36caee5c76705cb76a0142be0335424382 100644 --- a/cmake/modules/DuneProject.cmake +++ b/cmake/modules/DuneProject.cmake @@ -28,16 +28,16 @@ Initialize and finalize a Dune module. .. code-block:: cmake - finalize_dune_project([<generate-config-h>]) + finalize_dune_project() This function needs to be run at the end of every top-level ``CMakeLists.txt`` file. Among other things it creates the cmake package configuration file and package version file. Modules can add additional entries to these files by setting the variable ``${ProjectName}_INIT``. - If any argument ``<generate-config-h>`` is passed to this macro, a - ``config.h`` file is created containing preprocessor definitions about found - packages and module information. + If the file ``config.h.cmake`` is found, a ``config.h`` is created + containing preprocessor definitions about found packages and module + information. #]=======================================================================] include_guard(GLOBAL) @@ -114,6 +114,8 @@ macro(dune_project) include_directories("${PROJECT_SOURCE_DIR}") include_directories("${CMAKE_CURRENT_BINARY_DIR}") include_directories("${CMAKE_CURRENT_SOURCE_DIR}") + include_directories("${CMAKE_CURRENT_BINARY_DIR}/include") + include_directories("${CMAKE_CURRENT_BINARY_DIR}/include_private") add_definitions(-DHAVE_CONFIG_H) # Create custom target for building the documentation @@ -172,6 +174,11 @@ macro(finalize_dune_project) ### CREATE CONFIG FILE ### ########################## + # deprecation warning introduced with Dune 2.10 + if(NOT "${ARGC}" EQUAL 0) + message(DEPRECATION "Passing arguments to finalize_dune_project is no longer needed.") + endif() + #create cmake-config files for installation tree include(CMakePackageConfigHelpers) include(GNUInstallDirs) @@ -268,7 +275,7 @@ endif()") #create cmake-config files for build tree - set(PACKAGE_CMAKE_INSTALL_INCLUDEDIR ${PROJECT_SOURCE_DIR}) + set(PACKAGE_CMAKE_INSTALL_INCLUDEDIR ${PROJECT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR}/include) set(PACKAGE_CMAKE_INSTALL_DATAROOTDIR ${PROJECT_BINARY_DIR}) set(PACKAGE_DOXYSTYLE_DIR ${PROJECT_SOURCE_DIR}/doc/doxygen) set(PACKAGE_SCRIPT_DIR ${PROJECT_SOURCE_DIR}/cmake/scripts) @@ -279,9 +286,14 @@ endif()") set(PACKAGE_PREFIX_DIR ${PROJECT_SOURCE_DIR}) macro(set_and_check _var _file) set(\${_var} \"\${_file}\") - if(NOT EXISTS \"\${_file}\") - message(FATAL_ERROR \"File or directory \${_file} referenced by variable \${_var} does not exist !\") + if(\"_\${_file}_\" STREQUAL \"__\") + message(FATAL_ERROR \"File or directory referenced by variable \${_var} is unset !\") endif() + foreach(_f \${_file}) + if(NOT EXISTS \"\${_f}\") + message(FATAL_ERROR \"File or directory \${_f} referenced by variable \${_var} does not exist !\") + endif() + endforeach(_f) endmacro()") set(MODULE_INSTALLED OFF) @@ -423,23 +435,33 @@ endif()") ### HEADER CONFIG FILEs ### ########################### - if("${ARGC}" EQUAL "1") - message(STATUS "Adding custom target for config.h generation") - dune_regenerate_config_cmake() - # add a target to generate config.h.cmake - if(NOT TARGET OUTPUT) - add_custom_target(OUTPUT config_collected.h.cmake - COMMAND dune_regenerate_config_cmake()) - endif() - # actually write the config.h file to disk - # using generated file - configure_file(${CMAKE_CURRENT_BINARY_DIR}/config_collected.h.cmake - ${CMAKE_CURRENT_BINARY_DIR}/config.h) - else() - message(STATUS "Not adding custom target for config.h generation") - # actually write the config.h file to disk - configure_file(config.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config.h) + # generate and install headers file: ${ProjectName}-config.hh and ${ProjectName}-config-private.hh + file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/include/) + if(EXISTS ${PROJECT_SOURCE_DIR}/config.h.cmake) + dune_parse_module_config_file(${ProjectName} FILE ${PROJECT_SOURCE_DIR}/config.h.cmake) + + # configure private config file + file(WRITE ${PROJECT_BINARY_DIR}/include_private/${ProjectName}-config-private.hh.cmake "${${ProjectName}_CONFIG_PRIVATE_HH}") + configure_file(${CMAKE_CURRENT_BINARY_DIR}/include_private/${ProjectName}-config-private.hh.cmake ${CMAKE_CURRENT_BINARY_DIR}/include_private/${ProjectName}-config-private.hh) + + # configure and install public config file + file(WRITE ${PROJECT_BINARY_DIR}/include/${ProjectName}-config.hh.cmake "${${ProjectName}_CONFIG_HH}\n${${ProjectName}_CONFIG_BOTTOM_HH}") + # parse again dune.module file of current module to set PACKAGE_* variables + dune_module_information(${PROJECT_SOURCE_DIR} QUIET) + configure_file(${CMAKE_CURRENT_BINARY_DIR}/include/${ProjectName}-config.hh.cmake ${CMAKE_CURRENT_BINARY_DIR}/include/${ProjectName}-config.hh) + install(FILES ${CMAKE_CURRENT_BINARY_DIR}/include/${ProjectName}-config.hh DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) + endif() + + message(STATUS "Adding custom target for config.h generation") + dune_regenerate_config_cmake() + # add a target to generate config.h.cmake + if(NOT TARGET OUTPUT) + add_custom_target(OUTPUT config_collected.h.cmake + COMMAND dune_regenerate_config_cmake()) endif() + # actually write the config.h file to disk + # using generated file + configure_file(${CMAKE_CURRENT_BINARY_DIR}/config_collected.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config.h) if(PROJECT_NAME STREQUAL CMAKE_PROJECT_NAME) feature_summary(WHAT ALL) @@ -452,64 +474,114 @@ endmacro(finalize_dune_project) # Internal macros # ------------------------------------------------------------------------ +# parse config.h.cmake file and return public, private and bottom parts of the header +# as ${module}_CONFIG_HH, ${module}_CONFIG_PRIVATE_HH, and ${module}_CONFIG_BOTTOM_HH +function(dune_parse_module_config_file module) + cmake_parse_arguments(ARG "" "FILE" "" ${ARGN}) + dune_module_to_uppercase(upper ${module}) + + file(READ "${ARG_FILE}" _config) + string(REGEX REPLACE + ".*/\\*[ ]*begin[ ]+${module}[^\\*]*\\*/(.*)/[/\\*][ ]*end[ ]*${module}[^\\*]*\\*/" "\\1" + _tfile "${_config}") + # extract the private section + string(REGEX MATCH "/[\\*][ ]*begin private.*/[\\*][ ]*end[ ]*private[^\\*]\\*/" _tprivate "${_tfile}") + string(REGEX REPLACE ".*/\\*[ ]*begin[ ]+private[^\\*]*\\*/(.*)/[/\\*][ ]*end[ ]*private[^\\*]*\\*/" "\\1" _config_private "${_tprivate}" ) + string(REGEX REPLACE "(.*)/[\\*][ ]*begin private.*/[\\*][ ]*end[ ]*private[^\\*]\\*/(.*)" "\\1\\2" _ttfile "${_tfile}") + + # extract the bottom section + string(REGEX MATCH "/[\\*][ ]*begin bottom.*/[\\*][ ]*end[ ]*bottom[^\\*]\\*/" _tbottom "${_ttfile}") + string(REGEX REPLACE ".*/\\*[ ]*begin[ ]+bottom[^\\*]*\\*/(.*)/[/\\*][ ]*end[ ]*bottom[^\\*]*\\*/" "\\1" _config_bottom "${_tbottom}" ) + string(REGEX REPLACE "(.*)/[\\*][ ]*begin bottom.*/[\\*][ ]*end[ ]*bottom[^\\*]\\*/(.*)" "\\1\\2" _config "${_ttfile}") + + # resolve headers of dependencies + unset(_headers) + foreach(_deps ${${module}_DEPENDS} ${${module}_SUGGESTS}) + split_module_version(${_deps} dep_name dep_version) + set(_headers "${_headers} +#if __has_include(<${dep_name}-config.hh>) + #include <${dep_name}-config.hh> +#endif +") + endforeach() + + # resolve public header part + set(HAVE_${upper} ${${module}_FOUND} PARENT_SCOPE) + set(${module}_CONFIG_HH +" +#ifndef ${upper}_CONFIG_HH +#define ${upper}_CONFIG_HH + +/* Define to 1 if you have module ${module} available */ +#cmakedefine01 HAVE_${upper} + +${_config} + +${_headers} + +#endif // ${upper}_CONFIG_HH +" PARENT_SCOPE) + + # resolve private header part + set(${module}_CONFIG_PRIVATE_HH +" +#ifndef ${upper}_CONFIG_PRIVATE_HH +#define ${upper}_CONFIG_PRIVATE_HH + +${_config_private} + +#include <${module}-config.hh> + +#endif // ${upper}_CONFIG_PRIVATE_HH +" PARENT_SCOPE) + + # resolve bottom header part + # TODO: when legacy config file is removed private and bottom parts can be + # merged into one, with dependency headers in between + set(${module}_CONFIG_BOTTOM_HH +" +#ifndef ${upper}_CONFIG_BOTTOM_HH +#define ${upper}_CONFIG_BOTTOM_HH + +${_config_bottom} + +#endif // ${upper}_CONFIG_BOTTOM_HH +" PARENT_SCOPE) +endfunction() + + # Creates a new config_collected.h.cmake file in ${CMAKE_CURRENT_BINARY_DIR} that # consists of entries from ${CMAKE_CURRENT_SOURCE_DIR}/config.h.cmake # and includes non-private entries from the config.h.cmake files # of all dependent modules. Finally config.h is created from config_collected.h.cmake. macro(dune_regenerate_config_cmake) - set(CONFIG_H_CMAKE_FILE "${PROJECT_BINARY_DIR}/config_collected.h.cmake") - if(EXISTS ${PROJECT_SOURCE_DIR}/config.h.cmake) - file(READ ${PROJECT_SOURCE_DIR}/config.h.cmake _file) - string(REGEX MATCH - "/[\\*/][ ]*begin[ ]+${ProjectName}.*\\/[/\\*][ ]*end[ ]*${ProjectName}[^\\*]*\\*/" - _myfile "${_file}") - endif() - # overwrite file with new content - file(WRITE ${CONFIG_H_CMAKE_FILE} "/* config.h. Generated from config_collected.h.cmake by CMake. - It was generated from config_collected.h.cmake which in turn is generated automatically - from the config.h.cmake files of modules this module depends on. */" - ) - - foreach(_dep ${ProjectName} ${ALL_DEPENDENCIES}) - dune_module_to_uppercase(upper ${_dep}) - set(HAVE_${upper} ${${_dep}_FOUND}) - file(APPEND ${CONFIG_H_CMAKE_FILE} - "\n\n/* Define to 1 if you have module ${_dep} available */ -#cmakedefine01 HAVE_${upper}\n") - endforeach() - # add previous module specific section + # collect header parts from all dependencies + unset(collected_config_file) + unset(collected_config_file_bottom) foreach(_dep ${ALL_DEPENDENCIES}) - foreach(_mod_conf_file ${${_dep}_PREFIX}/config.h.cmake - ${${_dep}_PREFIX}/share/${_dep}/config.h.cmake) + foreach(_mod_conf_file ${${_dep}_PREFIX}/config.h.cmake ${${_dep}_PREFIX}/share/${_dep}/config.h.cmake) if(EXISTS ${_mod_conf_file}) - file(READ "${_mod_conf_file}" _file) - string(REGEX REPLACE - ".*/\\*[ ]*begin[ ]+${_dep}[^\\*]*\\*/(.*)/[/\\*][ ]*end[ ]*${_dep}[^\\*]*\\*/" "\\1" - _tfile "${_file}") - # strip the private section - string(REGEX REPLACE "(.*)/[\\*][ ]*begin private.*/[\\*][ ]*end[ ]*private[^\\*]\\*/(.*)" "\\1\\2" _ttfile "${_tfile}") - - # extract the bottom section - string(REGEX MATCH "/[\\*][ ]*begin bottom.*/[\\*][ ]*end[ ]*bottom[^\\*]\\*/" _tbottom "${_ttfile}") - string(REGEX REPLACE ".*/\\*[ ]*begin[ ]+bottom[^\\*]*\\*/(.*)/[/\\*][ ]*end[ ]*bottom[^\\*]*\\*/" "\\1" ${_dep}_CONFIG_H_BOTTOM "${_tbottom}" ) - string(REGEX REPLACE "(.*)/[\\*][ ]*begin bottom.*/[\\*][ ]*end[ ]*bottom[^\\*]\\*/(.*)" "\\1\\2" _file "${_ttfile}") - - # append bottom section - if(${_dep}_CONFIG_H_BOTTOM) - set(CONFIG_H_BOTTOM "${CONFIG_H_BOTTOM} ${${_dep}_CONFIG_H_BOTTOM}") - endif() - - file(APPEND ${CONFIG_H_CMAKE_FILE} "${_file}") + dune_parse_module_config_file(${_dep} FILE ${_mod_conf_file}) + set(collected_config_file "${collected_config_file}\n${${_dep}_CONFIG_HH}\n") + set(collected_config_file_bottom "${collected_config_file_bottom}\n${${_dep}_CONFIG_BOTTOM_HH}\n") endif() endforeach() endforeach() - # parse again dune.module file of current module to set PACKAGE_* variables - dune_module_information(${PROJECT_SOURCE_DIR} QUIET) - file(APPEND ${CONFIG_H_CMAKE_FILE} "\n${_myfile}") - # append CONFIG_H_BOTTOM section at the end if found - if(CONFIG_H_BOTTOM) - file(APPEND ${CONFIG_H_CMAKE_FILE} "${CONFIG_H_BOTTOM}") + + # collect header parts from this module + if(EXISTS ${PROJECT_SOURCE_DIR}/config.h.cmake) + dune_parse_module_config_file(${ProjectName} FILE ${PROJECT_SOURCE_DIR}/config.h.cmake) endif() + + # write collected header into config.h.cmake + file(WRITE ${PROJECT_BINARY_DIR}/config_collected.h.cmake " +${collected_config_file} +${${ProjectName}_CONFIG_HH} +${${ProjectName}_CONFIG_PRIVATE_HH} +${collected_config_file_bottom} +${${ProjectName}_CONFIG_BOTTOM_HH} +") + endmacro(dune_regenerate_config_cmake) diff --git a/cmake/scripts/index.rst.in b/cmake/scripts/index.rst.in index 7be798f9461b4eb613cfc62ceed066fde85db378..8b85c298faadb29df7c4cfc98baf01c056866326 100644 --- a/cmake/scripts/index.rst.in +++ b/cmake/scripts/index.rst.in @@ -10,7 +10,7 @@ Introduction ============ .. toctree:: - :maxdepth: 2 + :maxdepth: 3 @CMAKE_DOC_DEPENDENCIES@ diff --git a/doc/buildsystem/dune-common.rst b/doc/buildsystem/dune-common.rst index 902b6ba81970fa2d3afd4d819c7cade30eccc199..70d60d51accd7fc4f3c9996b9a56ebf10fbbffd1 100644 --- a/doc/buildsystem/dune-common.rst +++ b/doc/buildsystem/dune-common.rst @@ -2,28 +2,182 @@ SPDX-FileCopyrightInfo: Copyright © DUNE Project contributors, see file LICENSE.md in module root SPDX-License-Identifier: LicenseRef-GPL-2.0-only-with-DUNE-exception + =========== dune-common =========== -.. _whatis: +.. _buildsystem: -What is CMake anyway? -===================== +--------------------- +The DUNE Build System +--------------------- + + +DUNE provides a set of utilities to aid the set up of a project. Most of the +utilities are CMake scripts that automate common use cases or required steps +needed to use a dune project. Among other things, these utilities have + +* Automatic intra-module project configuration +* Find package scripts for commonly used software in the DUNE context +* Testing utilities +* Configuration files +* Setup of DUNE python bindings + +.. _duneproject: + +Dune Project +============ + +In this documentation pages, we call *dune project* in the sense of the build +system those projects that call CMake functions ``dune_project()`` and +``finalize_dune_project()``. A typical *dune project* usually looks like this: + +.. code-block:: + :caption: CMakeLists.txt + + cmake_minimum_required(VERSION 3.16) + project("dune-foo" CXX) + + # find dune-common + find_package(dune-common) + + # include dune-common modules in the current CMake path + list(APPEND CMAKE_MODULE_PATH "${dune-common_MODULE_PATH}") + + # include the dune macros from dune-common + include(DuneMacros) + + # initialize the dune project context + dune_project() -CMake... + # ... -- is an open source build system tool developed at KITware. -- offers a one-tool-solution to all building tasks, like configuring, building, linking, testing and packaging. -- is a build system generator: It supports a set of backends called *generators* -- is portable -- is controlled by ONE rather simple language + # finalize the dune project context + finalize_dune_project() +Then, the project will be able to use the build system utilities provided by dune. + +The minimum required version to build Dune with CMake is 3.16. You can install CMake through your favorite package manager or downloading source code from `KITWare <http://www.cmake.org>`_ -The minimum required version to build Dune with CMake is 3.16. -.. _howtouse: +.. _configfile: + +Configuration File Header +========================= + +*Dune projects* may provide a configuration file template ``config.h.cmake``. +This file will be parsed by the build system and provides you with a C++ header +file based on the configuration options at configuration time. + +.. code-block:: + :caption: config.h.cmake + + /* begin private */ + + /* Everything within begin/end private will be exclusively generated to the current project */ + + #define DUNE_FOO_BUILD_OPTION 1 + + /* end private */ + + /* Everything outside of begin/end private will be generated to all of the downstream projects using the module */ + + #define DUNE_FOO_HEADER_OPTION 0 + + +DUNE generates two version of configuration files based on the configuration template ``config.h.cmake``: + +* ``config.h.cmake`` [configuration template, installed] +* ``${ProjectName}-config.hh`` [eager configuration instantiatiation - used in header files - installed] +* ``${ProjectName}-config-private.hh`` [eager configuration instantiatiation - used in binaries - not installed] +* ``config.h`` [lazy configuration instantiatiation - used in binaries - not installed] + +**Eager generation** + The configuration files are generated at configuration time of the project in + question. This means that once the project runs the configuration stage on + cmake, its configuration template is instantiated in c++ header files and can + be consumed by other projects without any further intervention of cmake. + - ``${ProjectName}-config-private.hh`` [used in binaries - not installed] + - ``${ProjectName}-config.hh`` [used in header files - installed] + +**Lazy generation** + The configuration files are generated at configuration time of the consumer + project. This means that every time a downstream project runs the + configuration stage on cmake, the configuration template of all preceding + projects are instantiated with the cmake options of the consumer project. + This requires the consumer project to be a dune-project in the sense of the + cmake build system. + - ``config.h`` [used in binaries - not installed] + + +Example +^^^^^^^ + +Suppose that project ``dune-bar`` depends on ``dune-foo`` and they have the +following configuration template files: + +.. code-block:: + :caption: dune-foo/config.h.cmake + + #cmakedefine OPTION_FOO 1 + + +The options ``OPTION_FOO`` will be defined depending on whether the variable +``OPTION_FOO`` is present in cmake at the time of the generation of the +configuration template file. This means that the contents of the generated files +will look like this. + +=========================================================== ======================== ======================== + ``dune-foo-config.hh`` ``config.h`` +=========================================================== ======================== ======================== +``OPTION_FOO=1`` in dune-foo & ``OPTION_FOO=1`` in dune-bar ``#define OPTION_FOO 1`` ``#define OPTION_FOO 1`` +``OPTION_FOO=1`` in dune-foo & ``OPTION_FOO=0`` in dune-bar ``#define OPTION_FOO 1`` - +``OPTION_FOO=0`` in dune-foo & ``OPTION_FOO=0`` in dune-bar - - +``OPTION_FOO=0`` in dune-foo & ``OPTION_FOO=1`` in dune-bar - ``#define OPTION_FOO 1`` +=========================================================== ======================== ======================== + +Whether the combination of those options are valid and possible, is a +responsibility of the developer of the ``dune-foo`` module. Very often the +second combination is invalid and impossible to generate whereas the fourth is a +feature for late discovery of dependencies. + +Since both ``dune-foo-config.hh`` and ``config.h`` are guarded with a +``HAVE_DUNE_FOO_CONFIG_HH``, the first apearence will determine which version is +being used. Thus, an executable in dune-bar with the following structure + +.. code-block:: + :caption: dune-bar.cc + + #ifdef HAVE_CONFIG_H + #include "config.h" + #endif + + #include <dune/foo/feature.hh> // already includes dune-foo-config.hh + + /*...*/ + + +will be use the lazy or the eager configuration options depending on whether +``HAVE_CONFIG_H`` is defined. + +In the mid term, we want to remove the lazy configuration file generation +because: + +* Projects aren't fully configured even after installation and distribution. + Thus, c++ source code has unclear interpretation for the consumer of the + project. +* In practice, this forces all downstream users to also be *dune project* in the + sense of the cmake build system. + + +.. _faq: + +-------------------------------- +Frequently Asked Questions (FAQ) +-------------------------------- + How do I use Dune with CMake? =============================