diff --git a/.circleci/config.yml b/.circleci/config.yml index 94ee4898ce57..c34fcaea1827 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -361,6 +361,27 @@ jobs: paths: - ./build + depreport: + <<: *defaults + steps: + - attach_workspace: + at: /hpx + - run: + name: Building HPX Dependency Report Tool + command: | + ninja -j2 tools.hpxdep + - run: + name: Building HPX Dependency Report Documentation + command: | + ninja -j1 depreport + no_output_timeout: 10m + - persist_to_workspace: + root: /hpx + paths: + - ./build + - store_artifacts: + path: /hpx/build/share/hpx/docs/report + docs-singlehtml: <<: *defaults steps: @@ -877,6 +898,10 @@ workflows: # have conflicts - tests.examples <<: *gh_pages_filter + - depreport: + requires: + - configure + <<: *gh_pages_filter - docs-singlehtml: requires: - configure @@ -896,6 +921,7 @@ workflows: - docs-html - docs-singlehtml - docs-latexpdf + - depreport <<: *docs_push_filter - install: requires: diff --git a/.cmake-format.py b/.cmake-format.py index 06a42a9fa290..cec542ab3005 100644 --- a/.cmake-format.py +++ b/.cmake-format.py @@ -359,7 +359,8 @@ 'MODULE_DEPENDENCIES': '+', 'SOURCES': '+'}, 'pargs': { 'flags': ['CUDA', - 'CONFIG_FILES'], + 'CONFIG_FILES', + 'NO_CONFIG_IN_GENERATED_HEADERS'], 'nargs': '1+'}}, 'add_hpx_performance_test': {'pargs': {'nargs': 2}}, 'add_hpx_pseudo_dependencies': {'pargs': {'nargs': 0}}, diff --git a/CMakeLists.txt b/CMakeLists.txt index f8326a9d3235..44d88b33ad43 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -119,6 +119,8 @@ unset(HPX_COMPONENTS CACHE) unset(HPX_EXPORT_TARGETS CACHE) unset(HPX_EXPORT_INTERNAL_TARGETS CACHE) unset(HPX_ENABLED_MODULES CACHE) +unset(HPX_CORE_ENABLED_MODULES CACHE) +unset(HPX_FULL_ENABLED_MODULES CACHE) unset(HPX_STATIC_PARCELPORT_PLUGINS CACHE) # ############################################################################## @@ -2217,14 +2219,6 @@ add_subdirectory(wrap) # ############################################################################## hpx_include(Documentation) -# ############################################################################## -# Target specification -# ############################################################################## -if(HPX_WITH_TOOLS OR HPX_WITH_TESTS_BENCHMARKS) - add_hpx_pseudo_target(tools) - add_subdirectory(tools) -endif() - # ############################################################################## # Add core configuration # ############################################################################## @@ -2250,6 +2244,9 @@ if(HPX_WITH_TESTS) add_subdirectory(tests) endif() +# ############################################################################## +# Target specification +# ############################################################################## if(HPX_WITH_EXAMPLES) add_subdirectory(examples) endif() @@ -2258,6 +2255,12 @@ if(HPX_WITH_DOCUMENTATION) add_subdirectory(docs) endif() +# this must come after docs as it creates a dependency on the target git_docs +if(HPX_WITH_TOOLS OR HPX_WITH_TESTS_BENCHMARKS) + add_hpx_pseudo_target(tools) + add_subdirectory(tools) +endif() + # Configure hpxrun.py configure_file( "${PROJECT_SOURCE_DIR}/cmake/templates/hpxrun.py.in" diff --git a/cmake/HPX_AddModule.cmake b/cmake/HPX_AddModule.cmake index dfbc01ac64ed..72039a81446a 100644 --- a/cmake/HPX_AddModule.cmake +++ b/cmake/HPX_AddModule.cmake @@ -5,10 +5,11 @@ # file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) include(HPX_ExportTargets) +include(HPX_PrintSummary) function(add_hpx_module libname modulename) # Retrieve arguments - set(options CUDA CONFIG_FILES) + set(options CUDA CONFIG_FILES NO_CONFIG_IN_GENERATED_HEADERS) set(one_value_args GLOBAL_HEADER_GEN) set(multi_value_args SOURCES @@ -43,20 +44,26 @@ function(add_hpx_module libname modulename) string(TOUPPER ${modulename} modulename_upper) # Mark the module as enabled (see hpx/libs/CMakeLists.txt) + set(modules ${HPX_ENABLED_MODULES}) + list(APPEND modules ${modulename}) + list(SORT modules) + list(REMOVE_DUPLICATES modules) + set(HPX_ENABLED_MODULES - ${HPX_ENABLED_MODULES} ${modulename} + ${modules} CACHE INTERNAL "List of enabled HPX modules" FORCE ) - list(SORT HPX_ENABLED_MODULES) - list(REMOVE_DUPLICATES HPX_ENABLED_MODULES) + + set(modules ${HPX_${libname_upper}_ENABLED_MODULES}) + list(APPEND modules ${modulename}) + list(SORT modules) + list(REMOVE_DUPLICATES modules) set(HPX_${libname_upper}_ENABLED_MODULES - ${HPX_${libname_upper}_ENABLED_MODULES} ${modulename} + ${modules} CACHE INTERNAL "List of enabled HPX modules in the ${libname} library" FORCE ) - list(SORT HPX_${libname_upper}_ENABLED_MODULES) - list(REMOVE_DUPLICATES HPX_${libname_upper}_ENABLED_MODULES) # Main directories of the module set(SOURCE_ROOT "${CMAKE_CURRENT_SOURCE_DIR}/src") @@ -93,6 +100,11 @@ function(add_hpx_module libname modulename) message(FATAL_ERROR "Invalid compatibility header ${compat_header}") endif() + set(config_file) + if(NOT ${modulename}_NO_CONFIG_IN_GENERATED_HEADERS) + set(config_file "#include \n") + endif() + list(GET compat_header 0 old_header) list(GET compat_header 1 new_header) configure_file( @@ -122,7 +134,12 @@ function(add_hpx_module libname modulename) set(global_header "${CMAKE_CURRENT_BINARY_DIR}/include/hpx/modules/${modulename}.hpp" ) - set(module_headers) + if(NOT ${modulename}_NO_CONFIG_IN_GENERATED_HEADERS) + set(module_headers "#include \n\n") + set(module_headers + "${module_headers}#if defined(HPX_HAVE_MODULE_${modulename_upper})\n" + ) + endif() foreach(header_file ${${modulename}_HEADERS}) # Exclude the files specified if((NOT (${header_file} IN_LIST ${modulename}_EXCLUDE_FROM_GLOBAL_HEADER)) @@ -131,6 +148,9 @@ function(add_hpx_module libname modulename) set(module_headers "${module_headers}#include <${header_file}>\n") endif() endforeach(header_file) + if(NOT ${modulename}_NO_CONFIG_IN_GENERATED_HEADERS) + set(module_headers "${module_headers}#endif\n") + endif() configure_file( "${PROJECT_SOURCE_DIR}/cmake/templates/global_module_header.hpp.in" "${global_header}" @@ -138,9 +158,13 @@ function(add_hpx_module libname modulename) set(generated_headers ${global_header}) endif() - set(config_entries_source - "${CMAKE_CURRENT_BINARY_DIR}/src/config_entries.cpp" - ) + set(has_config_info) + has_configuration_summary(${modulename} has_config_info) + if(has_config_info) + set(config_entries_source + "${CMAKE_CURRENT_BINARY_DIR}/src/config_entries.cpp" + ) + endif() # generate configuration header for this module set(config_header @@ -201,6 +225,12 @@ function(add_hpx_module libname modulename) set(module_library_type OBJECT) endif() + set(source_group_root ${SOURCE_ROOT}) + if(NOT sources AND NOT config_entries_source) + set(source_group_root "${HPX_SOURCE_DIR}/libs/src/") + set(sources "${source_group_root}/empty.cpp") + endif() + # create library modules add_library( hpx_${modulename} @@ -286,7 +316,7 @@ function(add_hpx_module libname modulename) ) add_hpx_source_group( NAME hpx_${modulename} - ROOT ${SOURCE_ROOT} + ROOT ${source_group_root} CLASS "Source Files" TARGETS ${sources} ) @@ -423,7 +453,6 @@ function(add_hpx_module libname modulename) add_subdirectory(${dir}) endforeach(dir) - include(HPX_PrintSummary) create_configuration_summary( " Module configuration (${modulename}):" "${modulename}" ) diff --git a/cmake/HPX_GenerateDependencyReport.cmake b/cmake/HPX_GenerateDependencyReport.cmake new file mode 100644 index 000000000000..ede5a9b057f2 --- /dev/null +++ b/cmake/HPX_GenerateDependencyReport.cmake @@ -0,0 +1,114 @@ +# Copyright (c) 2022 Hartmut Kaiser +# +# SPDX-License-Identifier: BSL-1.0 +# Distributed under the Boost Software License, Version 1.0. (See accompanying +# file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +# Generate HPX Dependency Report + +cmake_minimum_required(VERSION 3.18 FATAL_ERROR) + +separate_arguments(HPX_CORE_ENABLED_MODULES) +separate_arguments(HPX_FULL_ENABLED_MODULES) + +# common options +set(hpxdep_common_options --hpx-root "${HPX_SOURCE_DIR}" --hpx-build-root + "${HPX_BINARY_DIR}" +) + +set(HPXDEP_OUTPUT_DIR + "${HPX_BINARY_DIR}/share/hpx/docs/report/${HPX_DEPREPORT_VERSION}" +) + +# create module-list +set(hpxdep_module_list_output "module-list.txt") +message("Generating ${hpxdep_module_list_output}") + +set(module_list) +foreach(module ${HPX_CORE_ENABLED_MODULES}) + set(module_list ${module_list} "core/${module}\n") +endforeach() +foreach(module ${HPX_FULL_ENABLED_MODULES}) + set(module_list ${module_list} "full/${module}\n") +endforeach() +file(MAKE_DIRECTORY "${HPXDEP_OUTPUT_DIR}") +file(WRITE "${HPXDEP_OUTPUT_DIR}/${hpxdep_module_list_output}" ${module_list}) + +# module-overview, module-levels, and module-weights reports +macro(generate_module_overview_report option) + string(SUBSTRING ${option} 0 1 first_letter) + string(TOUPPER ${first_letter} first_letter) + string(REGEX REPLACE "^.(.*)" "${first_letter}\\1" option_cap "${option}") + + set(hpxdep_module_${option}_output "module-${option}.html") + message("Generating ${hpxdep_module_${option}_output}") + + # cmake-format: off + set(hpxdep_module_${option}_build_command + ${HPXDEP_OUTPUT_NAME} + ${hpxdep_common_options} + --module-list "${HPXDEP_OUTPUT_DIR}/${hpxdep_module_list_output}" + --html-title "\"HPX Module ${option_cap}\"" + --html + --module-${option} + ) + # cmake-format: on + + execute_process( + COMMAND ${hpxdep_module_${option}_build_command} + WORKING_DIRECTORY "${HPXDEP_OUTPUT_DIR}" + OUTPUT_FILE "${HPXDEP_OUTPUT_DIR}/${hpxdep_module_${option}_output}" + RESULT_VARIABLE hpxdep_result + ERROR_VARIABLE hpxdep_error + ) + if(NOT "${hpxdep_result}" EQUAL "0") + message( + FATAL_ERROR + "Generating ${hpxdep_module_${option}_output} failed: ${hpxdep_error}." + ) + endif() +endmacro() + +generate_module_overview_report("overview") +generate_module_overview_report("levels") +generate_module_overview_report("weights") + +# module-specific reports +macro(generate_module_report libname) + file(MAKE_DIRECTORY "${HPXDEP_OUTPUT_DIR}/${libname}") + foreach(module ${ARGN}) + set(module_name "${libname}/${module}") + set(hpxdep_module_output "${module_name}.html") + message("Generating ${hpxdep_module_output}") + + # cmake-format: off + set(hpxdep_module_build_command + ${HPXDEP_OUTPUT_NAME} + ${hpxdep_common_options} + --module-list "${HPXDEP_OUTPUT_DIR}/${hpxdep_module_list_output}" + --html-title "\"HPX Dependency Report for ${module_name}\"" + --html + --primary ${module_name} + --secondary ${module_name} + --reverse ${module_name} + ) + # cmake-format: on + + execute_process( + COMMAND ${hpxdep_module_build_command} + WORKING_DIRECTORY "${HPXDEP_OUTPUT_DIR}" + OUTPUT_FILE "${HPXDEP_OUTPUT_DIR}/${hpxdep_module_output}" + RESULT_VARIABLE hpxdep_result + ERROR_VARIABLE hpxdep_error + ) + if(NOT "${hpxdep_result}" EQUAL "0") + message( + FATAL_ERROR + "Generating ${hpxdep_module_output} failed: ${hpxdep_error}." + ) + endif() + endforeach() +endmacro() + +generate_module_report("core" ${HPX_CORE_ENABLED_MODULES}) +generate_module_report("full" ${HPX_FULL_ENABLED_MODULES}) diff --git a/cmake/HPX_PrintSummary.cmake b/cmake/HPX_PrintSummary.cmake index 877661e49ead..e96189eb650b 100644 --- a/cmake/HPX_PrintSummary.cmake +++ b/cmake/HPX_PrintSummary.cmake @@ -1,9 +1,30 @@ -# Copyright (c) 2017-2019 Hartmut Kaiser +# Copyright (c) 2017-2022 Hartmut Kaiser # # SPDX-License-Identifier: BSL-1.0 # Distributed under the Boost Software License, Version 1.0. (See accompanying # file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +function(has_configuration_summary module_name has_config_info) + + string(TOUPPER ${module_name} __module_name_uc) + get_property( + _variableNames GLOBAL PROPERTY HPX_MODULE_CONFIG_${__module_name_uc} + ) + list(LENGTH _variableNames _length) + if(${_length} GREATER_EQUAL 1) + set(${has_config_info} + TRUE + PARENT_SCOPE + ) + else() + set(${has_config_info} + FALSE + PARENT_SCOPE + ) + endif() + +endfunction() + function(create_configuration_summary message module_name) set(hpx_config_information) @@ -16,15 +37,6 @@ function(create_configuration_summary message module_name) set(upper_option_suffix "_${module_name_uc}") endif() - get_property( - DEFINITIONS_VARS GLOBAL - PROPERTY HPX_CONFIG_DEFINITIONS${upper_option_suffix} - ) - if(DEFINED DEFINITIONS_VARS) - list(SORT DEFINITIONS_VARS) - list(REMOVE_DUPLICATES DEFINITIONS_VARS) - endif() - get_property( _variableNames GLOBAL PROPERTY HPX_MODULE_CONFIG_${module_name_uc} ) @@ -36,6 +48,15 @@ function(create_configuration_summary message module_name) hpx_info("") hpx_info(${message}) + get_property( + DEFINITIONS_VARS GLOBAL + PROPERTY HPX_CONFIG_DEFINITIONS${upper_option_suffix} + ) + if(DEFINED DEFINITIONS_VARS) + list(SORT DEFINITIONS_VARS) + list(REMOVE_DUPLICATES DEFINITIONS_VARS) + endif() + foreach(_variableName ${_variableNames}) if(${_variableName}Category) @@ -87,14 +108,15 @@ function(create_configuration_summary message module_name) configure_file( "${HPX_SOURCE_DIR}/cmake/templates/${_template}" - "${HPX_BINARY_DIR}/${_base_dir_local}/config_strings.hpp" @ONLY + "${HPX_BINARY_DIR}/libs/core/config/include/${_base_dir_local}/config_strings.hpp" + @ONLY ) configure_file( "${HPX_SOURCE_DIR}/cmake/templates/${_template}" "${HPX_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/${_base_dir}/config_strings.hpp" @ONLY ) - else() + elseif(hpx_config_information) set(_base_dir_local "libs/${module_name}/src/") set(_template "config_defines_entries_for_modules.cpp.in") diff --git a/cmake/HPX_UpdateGitDocs.cmake b/cmake/HPX_UpdateGitDocs.cmake index 20dff7445a85..2013335dd556 100644 --- a/cmake/HPX_UpdateGitDocs.cmake +++ b/cmake/HPX_UpdateGitDocs.cmake @@ -85,6 +85,10 @@ if(HPX_WITH_GIT_BRANCH) PATTERN "*.buildinfo" EXCLUDE ) endif() + # special handling of dependency report files + if(EXISTS "${DOCS_SOURCE}/report") + file(COPY "${DOCS_SOURCE}/report" DESTINATION "${DOCS_BRANCH_DEST}") + endif() endif() # If a tag name has been set, we copy files to a corresponding directory @@ -115,6 +119,11 @@ if(HPX_WITH_GIT_TAG) ) endif() + # special handling of dependency report files + if(EXISTS "${DOCS_SOURCE}/report") + file(COPY "${DOCS_SOURCE}/report" DESTINATION "${DOCS_TAG_DEST}") + endif() + # If a tag name has been set and it is a suitable version number, we also copy # files to the "latest" directory. The regex only matches full version numbers # with three numerical components (X.Y.Z). It does not match release @@ -144,6 +153,11 @@ if(HPX_WITH_GIT_TAG) PATTERN "*.buildinfo" EXCLUDE ) endif() + + # special handling of dependency report files + if(EXISTS "${DOCS_SOURCE}/report") + file(COPY "${DOCS_SOURCE}/report" DESTINATION "${DOCS_LATEST_DEST}") + endif() endif() endif() diff --git a/cmake/templates/compatibility_header.hpp.in b/cmake/templates/compatibility_header.hpp.in index 21974c7aa20e..b024d8191f44 100644 --- a/cmake/templates/compatibility_header.hpp.in +++ b/cmake/templates/compatibility_header.hpp.in @@ -1,4 +1,4 @@ -// Copyright (c) 2020 STE||AR Group +// Copyright (c) 2020-2022 STE||AR Group // // SPDX-License-Identifier: BSL-1.0 // Distributed under the Boost Software License, Version 1.0. (See accompanying @@ -8,8 +8,7 @@ #pragma once -#include -#include +@config_file@#include #include <@new_header@> #if HPX_HAVE_DEPRECATION_WARNINGS diff --git a/cmake/templates/global_module_header.hpp.in b/cmake/templates/global_module_header.hpp.in index 114749924cd3..7da46f9307dc 100644 --- a/cmake/templates/global_module_header.hpp.in +++ b/cmake/templates/global_module_header.hpp.in @@ -1,4 +1,4 @@ -// Copyright (c) 2019-2020 STE||AR Group +// Copyright (c) 2019-2022 STE||AR Group // // SPDX-License-Identifier: BSL-1.0 // Distributed under the Boost Software License, Version 1.0. (See accompanying @@ -8,7 +8,4 @@ #pragma once -#include - -#if defined(HPX_HAVE_MODULE_@modulename_upper@) -@module_headers@#endif +@module_headers@ diff --git a/cmake/templates/hpxdep_index.html.in b/cmake/templates/hpxdep_index.html.in new file mode 100644 index 000000000000..706d45d6b427 --- /dev/null +++ b/cmake/templates/hpxdep_index.html.in @@ -0,0 +1,52 @@ + + + + HPX Dependency Report + + + + + + +
+ + STE||AR logo + + + +
+
+
+

@HPX_DEPREPORT_CAPTION@

+

Module Overview

+

Module Levels

+

Module Weights

+
+ + diff --git a/libs/CMakeLists.txt b/libs/CMakeLists.txt index 59ae647a386a..071918d4fce1 100644 --- a/libs/CMakeLists.txt +++ b/libs/CMakeLists.txt @@ -15,7 +15,7 @@ include(HPX_AddPseudoTarget) # gather headers and sources set(hpx_HEADERS) -set(hpx_SOURCES "${PROJECT_SOURCE_DIR}/libs/src/dummy.cpp") +set(hpx_SOURCES "${PROJECT_SOURCE_DIR}/libs/src/empty.cpp") set(hpx_external_objects_SOURCES) if(MSVC) @@ -317,7 +317,7 @@ foreach(lib ${HPX_LIBS}) # creation can move here as well. if(NOT ${lib} STREQUAL "full") string(TOUPPER ${lib} uppercase_lib) - add_library(hpx_${lib} SHARED src/dummy.cpp) + add_library(hpx_${lib} SHARED src/empty.cpp) target_compile_definitions(hpx_${lib} PRIVATE HPX_${uppercase_lib}_EXPORTS) set_target_properties(hpx_${lib} PROPERTIES FOLDER "Core") diff --git a/libs/core/config_registry/CMakeLists.txt b/libs/core/config_registry/CMakeLists.txt index 6191a8ac0a19..be3c0b4c1cda 100644 --- a/libs/core/config_registry/CMakeLists.txt +++ b/libs/core/config_registry/CMakeLists.txt @@ -8,7 +8,7 @@ list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake") set(config_registry_headers hpx/modules/config_registry.hpp) -set(config_registry_sources config_registry.cpp) +set(config_registry_sources config_entries.cpp config_registry.cpp) include(HPX_AddModule) add_hpx_module( diff --git a/libs/core/config_registry/src/config_entries.cpp b/libs/core/config_registry/src/config_entries.cpp new file mode 100644 index 000000000000..8a6c104d3cc3 --- /dev/null +++ b/libs/core/config_registry/src/config_entries.cpp @@ -0,0 +1,16 @@ +// Copyright (c) 2017-2022 Hartmut Kaiser +// +// SPDX-License-Identifier: BSL-1.0 +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// This module needs an empty config-registry entry for testing purposes + +#include + +namespace hpx { namespace config_registry_cfg { + + config_registry::add_module_config_helper add_config{ + {"config_registry", {}}}; + +}} // namespace hpx::config_registry_cfg diff --git a/libs/core/hashing/CMakeLists.txt b/libs/core/hashing/CMakeLists.txt index c6338343ac71..95cac8547d7f 100644 --- a/libs/core/hashing/CMakeLists.txt +++ b/libs/core/hashing/CMakeLists.txt @@ -23,6 +23,6 @@ add_hpx_module( SOURCES ${hashing_sources} HEADERS ${hashing_headers} COMPAT_HEADERS ${hashing_compat_headers} - MODULE_DEPENDENCIES hpx_config + MODULE_DEPENDENCIES hpx_config hpx_serialization CMAKE_SUBDIRS examples tests ) diff --git a/libs/core/logging/include/hpx/logging/format/formatters.hpp b/libs/core/logging/include/hpx/logging/format/formatters.hpp index 8a014482ce0a..f224e08bf47b 100644 --- a/libs/core/logging/include/hpx/logging/format/formatters.hpp +++ b/libs/core/logging/include/hpx/logging/format/formatters.hpp @@ -55,7 +55,7 @@ This will output something similar to: You pass the format string at construction. @code -#include +#include @endcode Internally, it uses hpx::util::date_time::microsec_time_clock. diff --git a/libs/core/logging/include/hpx/logging/format/named_write.hpp b/libs/core/logging/include/hpx/logging/format/named_write.hpp index 0c338780fe31..d0f65ebf30b1 100644 --- a/libs/core/logging/include/hpx/logging/format/named_write.hpp +++ b/libs/core/logging/include/hpx/logging/format/named_write.hpp @@ -53,7 +53,7 @@ and specify a %spacer between them. You have a %spacer string, and within it, you can escape your contained formatters. @code -#include +#include @endcode This allows you: diff --git a/libs/core/logging/include/hpx/logging/logging.hpp b/libs/core/logging/include/hpx/logging/logging.hpp index 9a9b4e6c69af..0acd6a0aa5c3 100644 --- a/libs/core/logging/include/hpx/logging/logging.hpp +++ b/libs/core/logging/include/hpx/logging/logging.hpp @@ -31,7 +31,7 @@ If you want to use @ref manipulator "formatters and destinations", then you can include this one instead: @code -#include +#include @endcode */ diff --git a/libs/core/preprocessor/CMakeLists.txt b/libs/core/preprocessor/CMakeLists.txt index 09e9b391753c..458430727550 100644 --- a/libs/core/preprocessor/CMakeLists.txt +++ b/libs/core/preprocessor/CMakeLists.txt @@ -29,7 +29,7 @@ set(preprocessor_sources) include(HPX_AddModule) add_hpx_module( - core preprocessor + core preprocessor NO_CONFIG_IN_GENERATED_HEADERS SOURCES ${preprocessor_sources} HEADERS ${preprocessor_headers} COMPAT_HEADERS ${preprocessor_compat_headers} diff --git a/libs/core/serialization/CMakeLists.txt b/libs/core/serialization/CMakeLists.txt index 970f202564d8..772da7578c2f 100644 --- a/libs/core/serialization/CMakeLists.txt +++ b/libs/core/serialization/CMakeLists.txt @@ -243,7 +243,6 @@ add_hpx_module( hpx_debugging hpx_errors hpx_format - hpx_hashing hpx_preprocessor hpx_type_support DEPENDENCIES ${serialization_optional_dependencies} diff --git a/libs/core/serialization/include/hpx/serialization/detail/polymorphic_intrusive_factory.hpp b/libs/core/serialization/include/hpx/serialization/detail/polymorphic_intrusive_factory.hpp index d7f3942861c2..fda57830c410 100644 --- a/libs/core/serialization/include/hpx/serialization/detail/polymorphic_intrusive_factory.hpp +++ b/libs/core/serialization/include/hpx/serialization/detail/polymorphic_intrusive_factory.hpp @@ -10,10 +10,10 @@ #include #include -#include #include #include +#include #include #include @@ -27,7 +27,7 @@ namespace hpx { namespace serialization { namespace detail { private: using ctor_type = void* (*) (); using ctor_map_type = - std::unordered_map; + std::unordered_map>; public: polymorphic_intrusive_factory() = default; diff --git a/libs/core/serialization/include/hpx/serialization/detail/polymorphic_nonintrusive_factory.hpp b/libs/core/serialization/include/hpx/serialization/detail/polymorphic_nonintrusive_factory.hpp index cd09de91500c..961986e3c579 100644 --- a/libs/core/serialization/include/hpx/serialization/detail/polymorphic_nonintrusive_factory.hpp +++ b/libs/core/serialization/include/hpx/serialization/detail/polymorphic_nonintrusive_factory.hpp @@ -14,7 +14,6 @@ #include #include #include -#include #include #include #include @@ -23,6 +22,7 @@ #include #include +#include #include #include #include @@ -107,9 +107,9 @@ namespace hpx::serialization::detail { public: using serializer_map_type = std::unordered_map; + function_bunch_type, std::hash>; using serializer_typeinfo_map_type = std::unordered_map; + std::string, std::hash>; HPX_CORE_EXPORT static polymorphic_nonintrusive_factory& instance(); diff --git a/libs/src/empty.cpp b/libs/src/empty.cpp new file mode 100644 index 000000000000..0705e138dede --- /dev/null +++ b/libs/src/empty.cpp @@ -0,0 +1,8 @@ +// Copyright (c) 2020 ETH Zurich +// +// SPDX-License-Identifier: BSL-1.0 +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// This file is intended for use in modules that don't have any source files on +// their own. diff --git a/tools/CMakeLists.txt b/tools/CMakeLists.txt index 4be2858e1b70..c58a17a16eba 100644 --- a/tools/CMakeLists.txt +++ b/tools/CMakeLists.txt @@ -1,11 +1,11 @@ -# Copyright (c) 2014-2019 Hartmut Kaiser +# Copyright (c) 2014-2022 Hartmut Kaiser # # SPDX-License-Identifier: BSL-1.0 # Distributed under the Boost Software License, Version 1.0. (See accompanying # file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) if(HPX_WITH_TOOLS) - set(subdirs inspect) + set(subdirs hpxdep inspect) endif() if(HPX_WITH_TESTS_BENCHMARKS) diff --git a/tools/hpxdep/CMakeLists.txt b/tools/hpxdep/CMakeLists.txt new file mode 100644 index 000000000000..b35e5ea32430 --- /dev/null +++ b/tools/hpxdep/CMakeLists.txt @@ -0,0 +1,56 @@ +# Copyright (c) 2015-2022 Hartmut Kaiser +# +# SPDX-License-Identifier: BSL-1.0 +# Distributed under the Boost Software License, Version 1.0. (See accompanying +# file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +# add hpxdep executable + +add_hpx_executable(hpxdep INTERNAL_FLAGS AUTOGLOB NOLIBS FOLDER "Tools/HPXDep") + +# Set the basic search paths for the generated HPX headers +target_include_directories(hpxdep PRIVATE ${PROJECT_BINARY_DIR}) +target_link_libraries(hpxdep PRIVATE hpx_core) + +# add dependencies to pseudo-target +add_hpx_pseudo_dependencies(tools.hpxdep hpxdep) + +# generate dependency report +if(HPX_WITH_GIT_TAG) + set(HPX_DEPREPORT_VERSION "${HPX_WITH_GIT_TAG}") + set(HPX_DEPREPORT_CAPTION "Tag ${HPX_WITH_GIT_TAG}") +elseif(HPX_WITH_GIT_BRANCH) + set(HPX_DEPREPORT_VERSION "${HPX_WITH_GIT_BRANCH}") + set(HPX_DEPREPORT_CAPTION "Branch ${HPX_WITH_GIT_BRANCH}") +else() + # HPX_VERSION is always available, use as fallback + set(HPX_DEPREPORT_VERSION "latest") + set(HPX_DEPREPORT_CAPTION "Version ${HPX_VERSION}${HPX_VERSION_TAG}") +endif() + +configure_file( + "${PROJECT_SOURCE_DIR}/cmake/templates/hpxdep_index.html.in" + "${PROJECT_BINARY_DIR}/share/hpx/docs/report/index.html" @ONLY +) + +# cmake-format: off +add_custom_target(depreport + DEPENDS hpxdep + COMMAND "${CMAKE_COMMAND}" + -DHPX_DEPREPORT_VERSION=${HPX_DEPREPORT_VERSION} + -DHPX_SOURCE_DIR:PATH="${PROJECT_SOURCE_DIR}" + -DHPX_BINARY_DIR:PATH="${PROJECT_BINARY_DIR}" + -DHPXDEP_OUTPUT_NAME:FILEPATH="$" + -DHPX_CORE_ENABLED_MODULES="${HPX_CORE_ENABLED_MODULES}" + -DHPX_FULL_ENABLED_MODULES="${HPX_FULL_ENABLED_MODULES}" + -P "${PROJECT_SOURCE_DIR}/cmake/HPX_GenerateDependencyReport.cmake" +) +# cmake-format: on + +set_target_properties( + depreport PROPERTIES FOLDER "Documentation/Dependency Report" +) + +if(TARGET git_docs) + add_dependencies(git_docs depreport) +endif() diff --git a/tools/hpxdep/hpxdep.cpp b/tools/hpxdep/hpxdep.cpp new file mode 100644 index 000000000000..1cd8ae9a275e --- /dev/null +++ b/tools/hpxdep/hpxdep.cpp @@ -0,0 +1,3338 @@ +// Copyright 2022 Hartmut Kaiser +// +// SPDX-License-Identifier: BSL-1.0 +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +// This was adapted from hpxdep - a tool to generate Boost dependency reports +// +// Copyright 2014-2020 Peter Dimov + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace fs = std::filesystem; + +static bool s_root_set = false; +static bool s_build_root_set = false; + +static fs::path s_hpx_root; +static fs::path s_hpx_build_root; + +// header -> module +static std::map s_header_map; + +// module -> headers +static std::map> s_module_headers; + +static std::set s_modules; + +// modules to use (ignore everything else) +static std::set s_modules_to_use; +static bool s_modules_to_use_option = false; + +static std::string module_name(std::string module) +{ + std::replace(module.begin(), module.end(), '~', '/'); + return module; +} + +static std::string module_tag_name(std::string module) +{ + std::replace(module.begin(), module.end(), '/', '~'); + return module; +} + +static std::string module_target_name(std::string module) +{ + std::replace(module.begin(), module.end(), '/', '_'); + return "hpx_" + module; +} + +static fs::path module_include_path(std::string const& module) +{ + return fs::path("libs") / module_name(module) / "include"; +} + +static fs::path module_source_path(std::string const& module) +{ + return fs::path("libs") / module_name(module) / "src"; +} + +static fs::path module_build_path(std::string const& module) +{ + return fs::path("libs") / module_name(module); +} + +static fs::path module_test_path(std::string const& module) +{ + return fs::path("libs") / module_name(module) / "test"; +} + +// static fs::path module_meta_path(std::string const& module) +// { +// return fs::path("libs") / module_name(module) / "meta"; +// } + +static void scan_module_headers_recursive( + std::string const& module, fs::path const& dir, std::size_t base_length) +{ + fs::recursive_directory_iterator it(dir), last; + + for (; it != last; ++it) + { + if (it->is_regular_file() && it->path().extension() == ".hpp") + { + std::string p2 = it->path().generic_string(); + p2 = p2.substr(base_length + 1); + + s_header_map[p2] = module; + s_module_headers[module].insert(p2); + } + } +} + +static void scan_module_headers(fs::path const& path) +{ + try + { + std::string module = module_tag_name( + path.generic_string().substr(5)); // strip "libs/" + + if (!s_modules_to_use_option || + s_modules_to_use.find(module) != s_modules_to_use.end()) + { + s_modules.insert(module); + } + + fs::path dir = path / "include"; + + scan_module_headers_recursive(module, dir, dir.generic_string().size()); + } + catch (fs::filesystem_error const& x) + { + std::cout << x.what() << std::endl; + } +} + +static void scan_submodules(fs::path const& path) +{ + fs::directory_iterator it(path), last; + + for (; it != last; ++it) + { + fs::directory_entry const& e = *it; + + if (!e.is_directory()) + { + continue; + } + + fs::path path = e.path(); + auto filename = it->path().filename(); + + if (filename != "full" && filename != "tests" && + fs::exists(path / "include")) + { + scan_module_headers(path); + } + + if (filename != "include" && fs::exists(path)) + { + scan_submodules(path); + } + } +} + +static void build_header_map(fs::path const& p) +{ + fs::current_path(p); + scan_submodules("libs"); +} + +static void scan_header_dependencies(std::string const& header, + std::istream& is, std::map>& deps, + std::map>& from) +{ + std::string line; + + while (std::getline(is, line)) + { + while (!line.empty() && (line[0] == ' ' || line[0] == '\t')) + { + line.erase(0, 1); + } + + if (line.empty() || line[0] != '#') + continue; + + line.erase(0, 1); + + while (!line.empty() && (line[0] == ' ' || line[0] == '\t')) + { + line.erase(0, 1); + } + + if (line.substr(0, 7) != "include") + continue; + + line.erase(0, 7); + + while (!line.empty() && (line[0] == ' ' || line[0] == '\t')) + { + line.erase(0, 1); + } + + if (line.size() < 2) + continue; + + char ch = line[0]; + + if (ch != '<' && ch != '"') + continue; + + if (ch == '<') + { + ch = '>'; + } + + line.erase(0, 1); + + std::string::size_type k = line.find_first_of(ch); + + if (k != std::string::npos) + { + line.erase(k); + } + + auto i = s_header_map.find(line); + + if (i != s_header_map.end()) + { + deps[i->second].insert(line); + from[line].insert(header); + } + else if (line.substr(0, 4) == "hpx/" && line.find("hpx/modules/") != 0) + { + deps["(unknown)"].insert(line); + from[line].insert(header); + } + } +} + +struct module_primary_actions +{ + virtual void heading(std::string const& module) = 0; + + virtual void module_start(std::string const& module) = 0; + virtual void module_end(std::string const& module) = 0; + + virtual void header_start(std::string const& header) = 0; + virtual void header_end(std::string const& header) = 0; + + virtual void from_header(std::string const& header) = 0; +}; + +static void scan_module_path(fs::path const& dir, bool remove_prefix, + std::map>& deps, + std::map>& from) +{ + std::size_t n = dir.generic_string().size(); + + if (fs::exists(dir)) + { + fs::recursive_directory_iterator it(dir), last; + + for (; it != last; ++it) + { + if (it->is_directory()) + { + continue; + } + + std::string header = it->path().generic_string(); + + if (remove_prefix) + { + header = header.substr(n + 1); + } + + std::ifstream is(it->path()); + + scan_header_dependencies(header, is, deps, from); + } + } +} + +static void scan_module_dependencies(std::string const& module, + module_primary_actions& actions, bool track_sources, bool track_tests, + bool include_self) +{ + // module -> [ header, header... ] + std::map> deps; + + // header -> included from [ header, header... ] + std::map> from; + + scan_module_path(module_include_path(module), true, deps, from); + + if (track_sources) + { + scan_module_path(module_source_path(module), false, deps, from); + } + + if (s_build_root_set) + { + fs::current_path(s_hpx_build_root); + scan_module_path(module_include_path(module), true, deps, from); + + if (track_sources) + { + scan_module_path(module_source_path(module), false, deps, from); + } + fs::current_path(s_hpx_root); + } + + if (track_tests) + { + scan_module_path(module_test_path(module), false, deps, from); + } + + actions.heading(module); + + for (auto i = deps.begin(); i != deps.end(); ++i) + { + if (i->first == module && !include_self) + continue; + + actions.module_start(i->first); + + for (auto j = i->second.begin(); j != i->second.end(); ++j) + { + actions.header_start(*j); + + std::set const& f = from[*j]; + + for (auto k = f.begin(); k != f.end(); ++k) + { + actions.from_header(*k); + } + + actions.header_end(*j); + } + + actions.module_end(i->first); + } +} + +// module depends on [ module, module... ] +static std::map> s_module_deps; + +// header is included by [header, header...] +static std::map> s_header_deps; + +// [ module, module... ] depend on module +static std::map> s_reverse_deps; + +// header includes [header, header...] +static std::map> s_header_includes; + +struct build_mdmap_actions : public module_primary_actions +{ + std::string module_; + std::string module2_; + std::string header_; + + void heading(std::string const& module) + { + module_ = module; + } + + void module_start(std::string const& module) + { + if (module != module_) + { + s_module_deps[module_].insert(module); + s_reverse_deps[module].insert(module_); + } + + module2_ = module; + } + + void module_end(std::string const& /*module*/) {} + + void header_start(std::string const& header) + { + header_ = header; + } + + void header_end(std::string const& /*header*/) {} + + void from_header(std::string const& header) + { + if (module_ != module2_) + { + s_header_deps[header_].insert(header); + } + + s_header_includes[header].insert(header_); + } +}; + +static void build_module_dependency_map(bool track_sources, bool track_tests) +{ + for (auto i = s_modules.begin(); i != s_modules.end(); ++i) + { + build_mdmap_actions actions; + scan_module_dependencies(*i, actions, track_sources, track_tests, true); + } +} + +static void output_module_primary_report(std::string const& module, + module_primary_actions& actions, bool track_sources, bool track_tests) +{ + try + { + scan_module_dependencies( + module, actions, track_sources, track_tests, false); + } + catch (fs::filesystem_error const& x) + { + std::cout << x.what() << std::endl; + } +} + +struct module_secondary_actions +{ + virtual void heading(std::string const& module) = 0; + + virtual void module_start(std::string const& module) = 0; + virtual void module_end(std::string const& module) = 0; + + virtual void module_adds(std::string const& module) = 0; +}; + +static void exclude(std::set& x, std::set const& y) +{ + for (auto i = y.begin(); i != y.end(); ++i) + { + x.erase(*i); + } +} + +static void output_module_secondary_report(std::string const& module, + std::set deps, module_secondary_actions& actions) +{ + actions.heading(module); + + deps.insert(module); + + // build transitive closure + + for (;;) + { + std::set deps2(deps); + + for (auto i = deps.begin(); i != deps.end(); ++i) + { + std::set deps3 = s_module_deps[*i]; + + exclude(deps3, deps); + + if (deps3.empty()) + { + continue; + } + + actions.module_start(*i); + + for (auto j = deps3.begin(); j != deps3.end(); ++j) + { + actions.module_adds(*j); + } + + actions.module_end(*i); + + deps2.insert(deps3.begin(), deps3.end()); + } + + if (deps == deps2) + { + break; + } + else + { + deps = deps2; + } + } +} + +static void output_module_secondary_report( + std::string const& module, module_secondary_actions& actions) +{ + output_module_secondary_report(module, s_module_deps[module], actions); +} + +struct header_inclusion_actions +{ + virtual void heading( + std::string const& header, std::string const& module) = 0; + + virtual void module_start(std::string const& module) = 0; + virtual void module_end(std::string const& module) = 0; + + virtual void header(std::string const& header) = 0; +}; + +static std::string module_for_header(std::string header) +{ + { + auto i = s_header_map.find(header); + + if (i != s_header_map.end()) + { + return i->second; + } + } + + if (header.substr(0, 5) == "libs/" || header.substr(0, 5) == "test/") + { + header = header.substr(5); + } + else + { + return std::string(); + } + + for (auto i = s_modules.begin(); i != s_modules.end(); ++i) + { + std::string module = module_name(*i); + if (header.substr(0, module.size() + 1) == module + '/') + { + return *i; + } + } + + return std::string(); +} + +static void output_header_inclusion_report( + std::string const& header, header_inclusion_actions& actions) +{ + std::string module = s_header_map[header]; + + actions.heading(header, module); + + std::set from = s_header_deps[header]; + + // classify 'from' dependencies by module + + // module -> [header, header...] + std::map> from2; + + for (auto i = from.begin(); i != from.end(); ++i) + { + from2[module_for_header(*i)].insert(*i); + } + + for (auto i = from2.begin(); i != from2.end(); ++i) + { + actions.module_start(i->first); + + for (auto j = i->second.begin(); j != i->second.end(); ++j) + { + actions.header(*j); + } + + actions.module_end(i->first); + } +} + +// output_module_primary_report + +struct module_primary_txt_actions : public module_primary_actions +{ + void heading(std::string const& module) + { + std::cout << "Primary dependencies for " << module_name(module) + << ":\n\n"; + } + + void module_start(std::string const& module) + { + std::cout << module_name(module) << ":\n"; + } + + void module_end(std::string const& /*module*/) + { + std::cout << "\n"; + } + + void header_start(std::string const& header) + { + std::cout << " <" << header << ">\n"; + } + + void header_end(std::string const& /*header*/) {} + + void from_header(std::string const& header) + { + std::cout << " from <" << header << ">\n"; + } +}; + +struct module_primary_html_actions : public module_primary_actions +{ + void heading(std::string const& module) + { + std::cout << "\n\n

Primary dependencies " + "for " + << module_name(module) << "

\n"; + } + + void module_start(std::string const& module) + { + std::cout << "

" + << module_name(module) << "

\n"; + } + + void module_end(std::string const& /*module*/) {} + + void header_start(std::string const& header) + { + std::cout << "

<" << header << ">

    \n"; + } + + void header_end(std::string const& /*header*/) + { + std::cout << "
\n"; + } + + void from_header(std::string const& header) + { + std::cout << "
  • from <" << header + << ">
  • \n"; + } +}; + +static void output_module_primary_report( + std::string const& module, bool html, bool track_sources, bool track_tests) +{ + if (html) + { + module_primary_html_actions actions; + output_module_primary_report( + module, actions, track_sources, track_tests); + } + else + { + module_primary_txt_actions actions; + output_module_primary_report( + module, actions, track_sources, track_tests); + } +} + +// output_module_secondary_report + +struct module_secondary_txt_actions : public module_secondary_actions +{ + void heading(std::string const& module) + { + std::cout << "Secondary dependencies for " << module_name(module) + << ":\n\n"; + } + + void module_start(std::string const& module) + { + std::cout << module_name(module) << ":\n"; + } + + void module_end(std::string const& /*module*/) + { + std::cout << "\n"; + } + + void module_adds(std::string const& module) + { + std::cout << " adds " << module_name(module) << "\n"; + } +}; + +struct module_secondary_html_actions : public module_secondary_actions +{ + std::string m2_; + + void heading(std::string const& module) + { + std::cout << "\n\n

    Secondary " + "dependencies for " + << module_name(module) << "

    \n"; + } + + void module_start(std::string const& module) + { + std::cout << "

    " << module_name(module) + << "

      \n"; + m2_ = module_name(module); + } + + void module_end(std::string const& /*module*/) + { + std::cout << "
    \n"; + } + + void module_adds(std::string const& module) + { + std::cout << "
  • adds " << module_name(module) + << "
  • \n"; + } +}; + +static void output_module_secondary_report(std::string const& module, bool html) +{ + if (html) + { + module_secondary_html_actions actions; + output_module_secondary_report(module, actions); + } + else + { + module_secondary_txt_actions actions; + output_module_secondary_report(module, actions); + } +} + +// output_header_report + +struct header_inclusion_txt_actions : public header_inclusion_actions +{ + void heading(std::string const& header, std::string const& module) + { + std::cout << "Inclusion report for <" << header << "> (in module " + << module_name(module) << "):\n\n"; + } + + void module_start(std::string const& module) + { + std::cout << " from " << module_name(module) << ":\n"; + } + + void module_end(std::string const& /*module*/) + { + std::cout << "\n"; + } + + void header(std::string const& header) + { + std::cout << " <" << header << ">\n"; + } +}; + +struct header_inclusion_html_actions : public header_inclusion_actions +{ + void heading(std::string const& header, std::string const& module) + { + std::cout << "

    Inclusion report for <" << header + << "> (in module " << module_name(module) + << ")

    \n"; + } + + void module_start(std::string const& module) + { + std::cout << "

    From " << module_name(module) + << "

      \n"; + } + + void module_end(std::string const& /*module*/) + { + std::cout << "
    \n"; + } + + void header(std::string const& header) + { + std::cout << "
  • <" << header << ">
  • \n"; + } +}; + +static void output_header_report(std::string const& header, bool html) +{ + if (html) + { + header_inclusion_html_actions actions; + output_header_inclusion_report(header, actions); + } + else + { + header_inclusion_txt_actions actions; + output_header_inclusion_report(header, actions); + } +} + +// output_module_reverse_report + +struct module_reverse_actions +{ + virtual void heading(std::string const& module) = 0; + + virtual void module_start(std::string const& module) = 0; + virtual void module_end(std::string const& module) = 0; + + virtual void header_start(std::string const& header) = 0; + virtual void header_end(std::string const& header) = 0; + + virtual void from_header(std::string const& header) = 0; +}; + +static void output_module_reverse_report( + std::string const& module, module_reverse_actions& actions) +{ + actions.heading(module); + + std::set const from = s_reverse_deps[module]; + + for (auto i = from.begin(); i != from.end(); ++i) + { + actions.module_start(*i); + + for (auto j = s_header_deps.begin(); j != s_header_deps.end(); ++j) + { + if (module_for_header(j->first) == module) + { + bool header_started = false; + + for (auto k = j->second.begin(); k != j->second.end(); ++k) + { + if (module_for_header(*k) == *i) + { + if (!header_started) + { + actions.header_start(j->first); + + header_started = true; + } + + actions.from_header(*k); + } + } + + if (header_started) + { + actions.header_end(j->first); + } + } + } + + actions.module_end(*i); + } +} + +struct module_reverse_txt_actions : public module_reverse_actions +{ + void heading(std::string const& module) + { + std::cout << "Reverse dependencies for " << module_name(module) + << ":\n\n"; + } + + void module_start(std::string const& module) + { + std::cout << module_name(module) << ":\n"; + } + + void module_end(std::string const& /*module*/) + { + std::cout << "\n"; + } + + void header_start(std::string const& header) + { + std::cout << " <" << header << ">\n"; + } + + void header_end(std::string const& /*header*/) {} + + void from_header(std::string const& header) + { + std::cout << " from <" << header << ">\n"; + } +}; + +struct module_reverse_html_actions : public module_reverse_actions +{ + void heading(std::string const& module) + { + std::cout << "\n\n

    Reverse dependencies " + "for " + << module_name(module) << "

    \n"; + } + + void module_start(std::string const& module) + { + std::cout << "

    " + << module_name(module) << "

    \n"; + } + + void module_end(std::string const& /*module*/) {} + + void header_start(std::string const& header) + { + std::cout << "

    <" << header << ">

      \n"; + } + + void header_end(std::string const& /*header*/) + { + std::cout << "
    \n"; + } + + void from_header(std::string const& header) + { + std::cout << "
  • from <" << header + << ">
  • \n"; + } +}; + +static void output_module_reverse_report(std::string const& module, bool html) +{ + if (html) + { + module_reverse_html_actions actions; + output_module_reverse_report(module, actions); + } + else + { + module_reverse_txt_actions actions; + output_module_reverse_report(module, actions); + } +} + +// module_level_report + +std::size_t const unknown_level = std::size_t(INT_MAX / 2); + +struct module_level_actions +{ + virtual void begin() = 0; + virtual void end() = 0; + + virtual void level_start(std::size_t level) = 0; + virtual void level_end(std::size_t level) = 0; + + virtual void module_start(std::string const& module) = 0; + virtual void module_end(std::string const& module) = 0; + + virtual void module2(std::string const& module, std::size_t level) = 0; +}; + +static void output_module_level_report(module_level_actions& actions) +{ + // build module level map + + std::map level_map; + + for (auto i = s_modules.begin(); i != s_modules.end(); ++i) + { + if (s_module_deps[*i].empty()) + { + level_map[*i] = 0; + // std::cerr << *i << ": " << 0 << std::endl; + } + else + { + level_map[*i] = unknown_level; + } + } + + // build transitive closure to see through cycles + + std::map> deps2 = s_module_deps; + + { + bool done; + + do + { + done = true; + + for (auto i = deps2.begin(); i != deps2.end(); ++i) + { + std::set tmp = i->second; + + for (auto j = i->second.begin(); j != i->second.end(); ++j) + { + std::set tmp2 = deps2[*j]; + tmp.insert(tmp2.begin(), tmp2.end()); + } + + if (tmp.size() != i->second.size()) + { + i->second = tmp; + done = false; + } + } + } while (!done); + } + + // compute acyclic levels + + for (std::size_t k = 1, n = s_modules.size(); k < n; ++k) + { + for (auto i = s_module_deps.begin(); i != s_module_deps.end(); ++i) + { + // i->first depends on i->second + + if (level_map[i->first] >= unknown_level) + { + std::size_t level = 0; + + for (auto j = i->second.begin(); j != i->second.end(); ++j) + { + level = (std::max)(level, level_map[*j] + 1); + } + + if (level == k) + { + level_map[i->first] = level; + // std::cerr << i->first << ": " << level << std::endl; + } + } + } + } + + // min_level_map[ M ] == L means the level is unknown, but at least L + std::map min_level_map; + + // initialize min_level_map for acyclic dependencies + + for (auto i = level_map.begin(); i != level_map.end(); ++i) + { + if (i->second < unknown_level) + { + min_level_map[i->first] = i->second; + } + } + + // compute levels for cyclic modules + + for (std::size_t k = 1, n = s_modules.size(); k < n; ++k) + { + for (auto i = s_module_deps.begin(); i != s_module_deps.end(); ++i) + { + if (level_map[i->first] >= unknown_level) + { + std::size_t level = 0; + + for (auto j = i->second.begin(); j != i->second.end(); ++j) + { + std::size_t jl = level_map[*j]; + + if (jl < unknown_level) + { + level = (std::max)(level, jl + 1); + } + else + { + std::size_t ml = min_level_map[*j]; + + if (deps2[*j].count(i->first) == 0) + { + // *j does not depend on i->first, so + // the level of i->first is at least + // 1 + the minimum level of *j + + ++ml; + } + + level = (std::max)(level, ml); + } + } + + min_level_map[i->first] = level; + } + } + } + + // reverse level map + + std::map> reverse_level_map; + + for (auto i = level_map.begin(); i != level_map.end(); ++i) + { + std::size_t level = i->second; + + if (level >= unknown_level) + { + std::size_t min_level = min_level_map[i->first]; + + if (min_level != 0) + { + level = min_level; + } + } + + reverse_level_map[level].insert(i->first); + } + + // output report + + actions.begin(); + + for (auto i = reverse_level_map.begin(); i != reverse_level_map.end(); ++i) + { + actions.level_start(i->first); + + for (auto j = i->second.begin(); j != i->second.end(); ++j) + { + actions.module_start(*j); + + std::set mdeps = s_module_deps[*j]; + + for (auto k = mdeps.begin(); k != mdeps.end(); ++k) + { + std::size_t level = level_map[*k]; + + if (level >= unknown_level) + { + std::size_t min_level = min_level_map[*k]; + + if (min_level != 0) + { + level = min_level; + } + } + + actions.module2(*k, level); + } + + actions.module_end(*j); + } + + actions.level_end(i->first); + } + + actions.end(); +} + +struct module_level_txt_actions : public module_level_actions +{ + std::size_t level_; + + void begin() + { + std::cout << "Module Levels:\n\n"; + } + + void end() {} + + void level_start(std::size_t level) + { + if (level >= unknown_level) + { + std::cout << "Level (undetermined):\n"; + } + else + { + std::cout << "Level " << level << ":\n"; + } + + level_ = level; + } + + void level_end(std::size_t /*level*/) + { + std::cout << "\n"; + } + + void module_start(std::string const& module) + { + std::cout << " " << module_name(module); + + if (level_ > 0) + { + std::cout << " ->"; + } + } + + void module_end(std::string const& /*module*/) + { + std::cout << "\n"; + } + + void module2(std::string const& module, std::size_t level) + { + std::cout << " " << module_name(module) << "("; + + if (level >= unknown_level) + { + std::cout << "-"; + } + else + { + std::cout << level; + } + + std::cout << ")"; + } +}; + +struct module_level_html_actions : public module_level_actions +{ + std::size_t level_; + + void begin() + { + std::cout << "

    Module Levels

    \n"; + } + + void end() + { + std::cout << "
    \n"; + } + + void level_start(std::size_t level) + { + if (level >= unknown_level) + { + std::cout << "

    Level undetermined

    \n"; + } + else + { + std::cout << "

    Level " << level + << "

    \n"; + } + + level_ = level; + } + + void level_end(std::size_t /*level*/) {} + + void module_start(std::string const& module) + { + std::cout << "

    " << module_name(module) + << "

    "; + } + + void module_end(std::string const& /*module*/) + { + std::cout << "

    \n"; + } + + void module2(std::string const& module, std::size_t level) + { + std::cout << " "; + + bool important = + level < unknown_level && level > 1 && level >= level_ - 1; + + if (important) + { + std::cout << ""; + } + + std::cout << module_name(module); + + if (level < unknown_level) + { + std::cout << "" << level << ""; + } + + if (important) + { + std::cout << ""; + } + } +}; + +static void output_module_level_report(bool html) +{ + if (html) + { + module_level_html_actions actions; + output_module_level_report(actions); + } + else + { + module_level_txt_actions actions; + output_module_level_report(actions); + } +} + +// module_overview_report + +struct module_overview_actions +{ + virtual void begin() = 0; + virtual void end() = 0; + + virtual void module_start(std::string const& module) = 0; + virtual void module_end(std::string const& module) = 0; + + virtual void module2(std::string const& module) = 0; +}; + +static void output_module_overview_report(module_overview_actions& actions) +{ + actions.begin(); + + for (auto i = s_modules.begin(); i != s_modules.end(); ++i) + { + actions.module_start(*i); + + std::set const mdeps = s_module_deps[*i]; + + for (auto j = mdeps.begin(); j != mdeps.end(); ++j) + { + actions.module2(*j); + } + + actions.module_end(*i); + } + + actions.end(); +} + +struct module_overview_txt_actions : public module_overview_actions +{ + bool deps_; + + void begin() + { + std::cout << "Module Overview:\n\n"; + } + + void end() {} + + void module_start(std::string const& module) + { + std::cout << module_name(module); + deps_ = false; + } + + void module_end(std::string const& /*module*/) + { + std::cout << "\n"; + } + + void module2(std::string const& module) + { + if (!deps_) + { + std::cout << " ->"; + deps_ = true; + } + + std::cout << " " << module_name(module); + } +}; + +struct module_overview_html_actions : public module_overview_actions +{ + void begin() + { + std::cout << "

    Module Overview

    \n"; + } + + void end() + { + std::cout << "
    \n"; + } + + void module_start(std::string const& module) + { + std::cout << "

    " + << module_name(module) + << "

    "; + } + + void module_end(std::string const& /*module*/) + { + std::cout << "

    \n"; + } + + void module2(std::string const& module) + { + std::cout << " " << module_name(module); + } +}; + +static void output_module_overview_report(bool html) +{ + if (html) + { + module_overview_html_actions actions; + output_module_overview_report(actions); + } + else + { + module_overview_txt_actions actions; + output_module_overview_report(actions); + } +} + +// list_dependencies + +struct list_dependencies_actions : public module_overview_actions +{ + void begin() {} + + void end() {} + + void module_start(std::string const& module) + { + std::cout << module_name(module) << " ->"; + } + + void module_end(std::string const& /*module*/) + { + std::cout << "\n"; + } + + void module2(std::string const& module) + { + if (module != "(unknown)") + { + std::cout << " " << module; + } + } +}; + +static void list_dependencies() +{ + list_dependencies_actions actions; + output_module_overview_report(actions); +} + +// + +static void output_html_header(std::string const& title, + std::string const& stylesheet, std::string const& prefix) +{ + std::cout << "\n"; + std::cout << "\n"; + std::cout << "" << title << "\n"; + + if (!stylesheet.empty()) + { + std::cout << stylesheet << "\n"; + } + + std::cout << "\n"; + std::cout << "\n"; + + if (!prefix.empty()) + { + std::cout << prefix << std::endl; + } +} + +static void output_html_footer(std::string const& footer) +{ + std::cout << "
    \n"; + std::cout << "

    " << footer << "

    \n"; + std::cout << "\n"; + std::cout << "\n"; +} + +static void enable_secondary( + bool& secondary, bool track_sources, bool track_tests) +{ + if (!secondary) + { + try + { + build_module_dependency_map(track_sources, track_tests); + } + catch (fs::filesystem_error const& x) + { + std::cout << x.what() << std::endl; + } + + secondary = true; + } +} + +static void list_modules() +{ + for (auto i = s_modules.begin(); i != s_modules.end(); ++i) + { + std::cout << module_name(*i) << "\n"; + } +} + +static void list_buildable() +{ + for (auto i = s_modules.begin(); i != s_modules.end(); ++i) + { + if (fs::exists(module_build_path(*i)) && + fs::exists(module_source_path(*i))) + { + std::cout << module_name(*i) << "\n"; + } + } +} + +// module_weight_report + +struct module_weight_actions +{ + virtual void begin() = 0; + virtual void end() = 0; + + virtual void weight_start(std::size_t weight) = 0; + virtual void weight_end(std::size_t weight) = 0; + + virtual void module_start(std::string const& module) = 0; + virtual void module_end(std::string const& module) = 0; + + virtual void module_primary_start() = 0; + virtual void module_primary( + std::string const& module, std::size_t weight) = 0; + virtual void module_primary_end() = 0; + + virtual void module_secondary_start() = 0; + virtual void module_secondary( + std::string const& module, std::size_t weight) = 0; + virtual void module_secondary_end() = 0; +}; + +static void output_module_weight_report(module_weight_actions& actions) +{ + // gather secondary dependencies + + struct build_secondary_deps : public module_secondary_actions + { + std::map>* pm_; + + explicit build_secondary_deps( + std::map>* pm) + : pm_(pm) + { + } + + std::string module_; + + void heading(std::string const& module) + { + module_ = module; + } + + void module_start(std::string const& /*module*/) {} + + void module_end(std::string const& /*module*/) {} + + void module_adds(std::string const& module) + { + (*pm_)[module_].insert(module); + } + }; + + std::map> secondary_deps; + + build_secondary_deps bsd(&secondary_deps); + + for (auto i = s_modules.begin(); i != s_modules.end(); ++i) + { + output_module_secondary_report(*i, bsd); + } + + // build weight map + + std::map> modules_by_weight; + + for (auto i = s_modules.begin(); i != s_modules.end(); ++i) + { + std::size_t w = s_module_deps[*i].size() + secondary_deps[*i].size(); + modules_by_weight[w].insert(*i); + } + + // output report + + actions.begin(); + + for (auto i = modules_by_weight.begin(); i != modules_by_weight.end(); ++i) + { + actions.weight_start(i->first); + + for (auto j = i->second.begin(); j != i->second.end(); ++j) + { + actions.module_start(*j); + + if (!s_module_deps[*j].empty()) + { + actions.module_primary_start(); + + for (auto k = s_module_deps[*j].begin(); + k != s_module_deps[*j].end(); ++k) + { + std::size_t w = + s_module_deps[*k].size() + secondary_deps[*k].size(); + actions.module_primary(*k, w); + } + + actions.module_primary_end(); + } + + if (!secondary_deps[*j].empty()) + { + actions.module_secondary_start(); + + for (auto k = secondary_deps[*j].begin(); + k != secondary_deps[*j].end(); ++k) + { + std::size_t w = + s_module_deps[*k].size() + secondary_deps[*k].size(); + actions.module_secondary(*k, w); + } + + actions.module_secondary_end(); + } + + actions.module_end(*j); + } + + actions.weight_end(i->first); + } + + actions.end(); +} + +struct module_weight_txt_actions : public module_weight_actions +{ + void begin() + { + std::cout << "Module Weights:\n\n"; + } + + void end() {} + + void weight_start(std::size_t weight) + { + std::cout << "Weight " << weight << ":\n"; + } + + void weight_end(std::size_t /*weight*/) + { + std::cout << "\n"; + } + + void module_start(std::string const& module) + { + std::cout << " " << module_name(module); + } + + void module_end(std::string const& /*module*/) + { + std::cout << "\n"; + } + + void module_primary_start() + { + std::cout << " ->"; + } + + void module_primary(std::string const& module, std::size_t weight) + { + std::cout << " " << module_name(module) << "(" << weight << ")"; + } + + void module_primary_end() {} + + void module_secondary_start() + { + std::cout << " ->"; + } + + void module_secondary(std::string const& module, std::size_t /*weight*/) + { + std::cout << " " << module_name(module); + } + + void module_secondary_end() {} +}; + +struct module_weight_html_actions : public module_weight_actions +{ + std::size_t weight_; + + void begin() + { + std::cout << "
    \n

    Module Weights

    \n"; + } + + void end() + { + std::cout << "
    \n"; + } + + void weight_start(std::size_t weight) + { + std::cout << "

    Weight " << weight + << "

    \n"; + weight_ = weight; + } + + void weight_end(std::size_t /*weight*/) {} + + void module_start(std::string const& module) + { + std::cout << "

    " << module_name(module) + << "

    "; + } + + void module_end(std::string const& /*module*/) + { + std::cout << "\n"; + } + + void module_primary_start() + { + std::cout << "

    "; + } + + void module_primary(std::string const& module, std::size_t weight) + { + std::cout << " "; + + bool heavy = weight >= 0.8 * weight_; + + if (heavy) + { + std::cout << ""; + } + + std::cout << module_name(module) << "" << weight << ""; + + if (heavy) + { + std::cout << ""; + } + } + + void module_primary_end() + { + std::cout << "

    "; + } + + void module_secondary_start() + { + std::cout << "

    "; + } + + void module_secondary(std::string const& module, std::size_t /*weight*/) + { + std::cout << " " << module_name(module); + } + + void module_secondary_end() + { + std::cout << "

    "; + } +}; + +static void output_module_weight_report(bool html) +{ + if (html) + { + module_weight_html_actions actions; + output_module_weight_report(actions); + } + else + { + module_weight_txt_actions actions; + output_module_weight_report(actions); + } +} + +// output_module_subset_report + +struct module_subset_actions +{ + virtual void heading(std::string const& module) = 0; + + virtual void module_start(std::string const& module) = 0; + virtual void module_end(std::string const& module) = 0; + + virtual void from_path(std::vector const& path) = 0; +}; + +static void add_module_headers( + fs::path const& dir, std::set& headers) +{ + if (fs::exists(dir)) + { + fs::recursive_directory_iterator it(dir), last; + + for (; it != last; ++it) + { + if (it->is_directory()) + { + continue; + } + + headers.insert(it->path().generic_string()); + } + } +} + +static void output_module_subset_report_(std::string const& module, + std::set const& headers, module_subset_actions& actions) +{ + // build header closure + + // header -> (header)* + std::map> inc2; + + // (header, header) -> path + std::map, std::vector> + paths; + + for (auto i = headers.begin(); i != headers.end(); ++i) + { + std::set& s = inc2[*i]; + + s = s_header_includes[*i]; + + for (auto j = s.begin(); j != s.end(); ++j) + { + std::vector& v = paths[std::make_pair(*i, *j)]; + + v.resize(0); + v.push_back(*i); + v.push_back(*j); + } + } + + for (;;) + { + bool r = false; + + for (auto i = inc2.begin(); i != inc2.end(); ++i) + { + std::set& s2 = i->second; + + for (auto j = s2.begin(); j != s2.end(); ++j) + { + std::set const& s = s_header_includes[*j]; + + for (auto k = s.begin(); k != s.end(); ++k) + { + if (s2.count(*k) == 0) + { + s2.insert(*k); + + std::vector const& v1 = + paths[std::make_pair(i->first, *j)]; + std::vector& v2 = + paths[std::make_pair(i->first, *k)]; + + v2 = v1; + v2.push_back(*k); + + r = true; + } + } + } + } + + if (!r) + break; + } + + // module -> header -> path [header -> header -> header] + std::map>> + subset; + + for (auto i = headers.begin(); i != headers.end(); ++i) + { + std::set const& s = inc2[*i]; + + for (auto j = s.begin(); j != s.end(); ++j) + { + std::string const& m = s_header_map[*j]; + + if (m.empty()) + continue; + + std::vector const& path = + paths[std::make_pair(*i, *j)]; + + if (subset.count(m) == 0 || subset[m].count(*i) == 0 || + subset[m][*i].size() > path.size()) + { + subset[m][*i] = path; + } + } + } + + actions.heading(module); + + for (auto i = subset.begin(); i != subset.end(); ++i) + { + if (i->first == module) + continue; + + actions.module_start(i->first); + + int k = 0; + + for (auto j = i->second.begin(); j != i->second.end() && k < 4; + ++j, ++k) + { + actions.from_path(j->second); + } + + actions.module_end(i->first); + } +} + +static void output_module_subset_report(std::string const& module, + bool track_sources, bool track_tests, module_subset_actions& actions) +{ + std::set headers = s_module_headers[module]; + + if (track_sources) + { + add_module_headers(module_source_path(module), headers); + } + + if (track_tests) + { + add_module_headers(module_test_path(module), headers); + } + + output_module_subset_report_(module, headers, actions); +} + +struct module_subset_txt_actions : public module_subset_actions +{ + void heading(std::string const& module) + { + std::cout << "Subset dependencies for " << module_name(module) + << ":\n\n"; + } + + void module_start(std::string const& module) + { + std::cout << module_name(module) << ":\n"; + } + + void module_end(std::string const& /*module*/) + { + std::cout << "\n"; + } + + void from_path(std::vector const& path) + { + for (auto i = path.begin(); i != path.end(); ++i) + { + if (i == path.begin()) + { + std::cout << " "; + } + else + { + std::cout << " -> "; + } + + std::cout << *i; + } + + std::cout << "\n"; + } +}; + +struct module_subset_html_actions : public module_subset_actions +{ + void heading(std::string const& module) + { + std::cout + << "\n\n

    Subset dependencies for " + << module << "

    \n"; + } + + void module_start(std::string const& module) + { + std::cout << "

    " + << module_name(module) << "

      \n"; + } + + void module_end(std::string const& /*module*/) + { + std::cout << "
    \n"; + } + + void from_path(std::vector const& path) + { + std::cout << "
  • "; + + for (auto i = path.begin(); i != path.end(); ++i) + { + if (i != path.begin()) + { + std::cout << " ⇢ "; + } + + std::cout << "" << *i << ""; + } + + std::cout << "
  • \n"; + } +}; + +static void output_module_subset_report( + std::string const& module, bool track_sources, bool track_tests, bool html) +{ + if (html) + { + module_subset_html_actions actions; + output_module_subset_report( + module, track_sources, track_tests, actions); + } + else + { + module_subset_txt_actions actions; + output_module_subset_report( + module, track_sources, track_tests, actions); + } +} + +// --list-exceptions + +static void list_exceptions() +{ + std::string lm; + + for (auto i = s_module_headers.begin(); i != s_module_headers.end(); ++i) + { + std::string module = module_name(i->first); + + std::string const prefix = "hpx/" + module; + std::size_t const n = prefix.size(); + + for (auto j = i->second.begin(); j != i->second.end(); ++j) + { + std::string const& header = *j; + + if (header.substr(0, n + 1) != prefix + '/' && + header != prefix + ".hpp") + { + if (lm != module) + { + std::cout << module << ":\n"; + lm = module; + } + + std::cout << " " << header << '\n'; + } + } + } +} + +// --test + +struct module_test_primary_actions : public module_primary_actions +{ + std::set& m_; + + explicit module_test_primary_actions(std::set& m) + : m_(m) + { + } + + void heading(std::string const& module) + { + std::cout << "Test dependencies for " << module << ":\n\n"; + } + + void module_start(std::string const& module) + { + std::cout << module_name(module) << "\n"; + m_.insert(module); + } + + void module_end(std::string const& /*module*/) {} + + void header_start(std::string const& /*header*/) {} + + void header_end(std::string const& /*header*/) {} + + void from_header(std::string const& /*header*/) {} +}; + +struct module_test_secondary_actions : public module_secondary_actions +{ + std::set& m_; + std::string m2_; + + explicit module_test_secondary_actions(std::set& m) + : m_(m) + { + } + + void heading(std::string const& /*module*/) {} + + void module_start(std::string const& module) + { + m2_ = module; + } + + void module_end(std::string const& /*module*/) {} + + void module_adds(std::string const& module) + { + if (m_.count(module) == 0) + { + std::cout << module << " (from " << m2_ << ")\n"; + m_.insert(module); + } + } +}; + +static void output_module_test_report(std::string const& module) +{ + std::set m; + + module_test_primary_actions a1(m); + output_module_primary_report(module, a1, true, true); + + std::cout << "\n"; + + bool secondary = false; + enable_secondary(secondary, true, false); + + std::set m2(m); + m2.insert(module); + + module_test_secondary_actions a2(m2); + + output_module_secondary_report(module, m, a2); +} + +// --cmake + +struct collect_primary_dependencies : public module_primary_actions +{ + std::set set_; + + void heading(std::string const&) {} + + void module_start(std::string const& module) + { + if (module == "(unknown)") + return; + + set_.insert(module); + } + + void module_end(std::string const& /*module*/) {} + + void header_start(std::string const& /*header*/) {} + + void header_end(std::string const& /*header*/) {} + + void from_header(std::string const& /*header*/) {} +}; + +static std::string module_cmake_name(std::string module) +{ + std::replace(module.begin(), module.end(), '~', '_'); + return module; +} + +// static int parse_cxxstd_line(char const* p) +// { +// while (*p == ' ' || *p == '\t') +// ++p; +// +// if (std::strncmp(p, "\"cxxstd\"", 8) != 0) +// return 0; +// p += 8; +// +// while (*p == ' ' || *p == '\t') +// ++p; +// +// if (*p != ':') +// return 0; +// ++p; +// +// while (*p == ' ' || *p == '\t') +// ++p; +// +// if (p[0] != '"') +// return 0; +// if (p[1] < '0' || p[1] > '9') +// return 0; +// if (p[2] < '0' || p[2] > '9') +// return 0; +// if (p[3] != '"') +// return 0; +// +// int r = (p[1] - '0') * 10 + (p[2] - '0'); +// +// if (r < 90) +// r += 100; +// +// return r; +// } + +static int module_cxxstd_requirement(std::string const&) +{ + return 117; +} + +static void output_module_cmake_report(std::string module) +{ + int cxxstd = module_cxxstd_requirement(module); + + std::cout + << "# Generated by `hpxdep --cmake " << module + << "`\n" + "# Copyright 2022 Hartmut Kaiser\n" + "# Copyright 2020, 2021 Peter Dimov\n" + "\n" + "# Distributed under the Boost Software License, Version 1.0.\n" + "# https://www.boost.org/LICENSE_1_0.txt\n" + "\n" + "cmake_minimum_required(VERSION 3.18)\n" + "\n"; + + module = module_tag_name(module); + + std::vector sources; + + fs::path srcpath = module_source_path(module); + + if (fs::exists(srcpath)) + { + fs::directory_iterator it(srcpath), last; + + for (; it != last; ++it) + { + if (!it->is_regular_file()) + continue; + + fs::path p = it->path(); + std::string ext = p.extension().string(); + + if (ext != ".cpp") + continue; + + std::string name = p.filename().string(); + + sources.push_back(name); + } + } + + std::string lm(module); + + std::replace(lm.begin(), lm.end(), '~', '_'); + + std::cout << "project(hpx_" << lm + << " VERSION \"${HPX_VERSION}\" LANGUAGES CXX)\n" + "\n"; + + collect_primary_dependencies a1; + output_module_primary_report(module, a1, false, false); + + if (!fs::exists(srcpath)) + { + // header-only library + + std::cout << "add_library(hpx_" << lm + << " INTERFACE)\n" + "add_library(HPX::" + << lm << " ALIAS hpx_" << lm + << ")\n" + "\n" + "target_include_directories(hpx_" + << lm + << " INTERFACE include)\n" + "\n"; + + if (!a1.set_.empty()) + { + std::cout << "target_link_libraries(hpx_" << lm + << "\n" + " INTERFACE\n"; + + for (auto i = a1.set_.begin(); i != a1.set_.end(); ++i) + { + std::cout << " HPX::" << module_cmake_name(*i) << "\n"; + } + + std::cout << ")\n" + "\n"; + } + + if (cxxstd >= 111) + { + std::cout << "target_compile_features(hpx_" << lm + << " INTERFACE cxx_std_" << cxxstd - 100 + << ")\n" + "\n"; + } + } + else + { + // compiled library + + std::cout << "add_library(hpx_" << lm << "\n"; + + for (auto i = sources.begin(); i != sources.end(); ++i) + { + std::cout << " src/" << *i << "\n"; + } + + std::cout << ")\n" + "\n" + "add_library(HPX::" + << lm << " ALIAS hpx_" << lm + << ")\n" + "\n" + "target_include_directories(hpx_" + << lm + << " PUBLIC include)\n" + "\n"; + + collect_primary_dependencies a2; + output_module_primary_report(module, a2, true, false); + + if (!a1.set_.empty() || !a2.set_.empty()) + { + std::cout << "target_link_libraries(hpx_" << lm << "\n"; + + if (!a1.set_.empty()) + { + std::cout << " PUBLIC\n"; + + for (auto i = a1.set_.begin(); i != a1.set_.end(); ++i) + { + a2.set_.erase(*i); + std::cout << " HPX::" << module_cmake_name(*i) << "\n"; + } + } + + if (!a2.set_.empty()) + { + std::cout << " PRIVATE\n"; + + for (auto i = a2.set_.begin(); i != a2.set_.end(); ++i) + { + std::cout << " HPX::" << module_cmake_name(*i) << "\n"; + } + } + + std::cout << ")\n" + "\n"; + } + + if (cxxstd >= 111) + { + std::cout << "target_compile_features(hpx_" << lm + << " PUBLIC cxx_std_" << cxxstd - 100 + << ")\n" + "\n"; + } + + std::string um(lm); + + for (auto i = um.begin(); i != um.end(); ++i) + { + *i = std::toupper(static_cast(*i)); + } + + std::cout << "target_compile_definitions(hpx_" << lm + << "\n" + " PUBLIC hpx_" + << um + << "_NO_LIB\n" + " PRIVATE hpx_" + << um + << "_SOURCE\n" + ")\n" + "\n" + "if(BUILD_SHARED_LIBS)\n" + " target_compile_definitions(hpx_" + << lm << " PUBLIC hpx_" << um + << "_DYN_LINK)\n" + "else()\n" + " target_compile_definitions(hpx_" + << lm << " PUBLIC hpx_" << um + << "_STATIC_LINK)\n" + "endif()\n" + "\n"; + } + + std::cout << "if(BUILD_TESTING AND EXISTS " + "\"${CMAKE_CURRENT_SOURCE_DIR}/test/CMakeLists.txt\")\n" + "\n" + " add_subdirectory(test)\n" + "\n" + "endif()\n"; +} + +// --brief + +struct module_brief_primary_actions : public module_primary_actions +{ + std::set& m_; + + explicit module_brief_primary_actions(std::set& m) + : m_(m) + { + } + + void heading(std::string const& /*module*/) + { + std::cout << "# Primary dependencies\n\n"; + } + + void module_start(std::string const& module) + { + std::cout << module_name(module) << "\n"; + m_.insert(module); + } + + void module_end(std::string const& /*module*/) {} + + void header_start(std::string const& /*header*/) {} + + void header_end(std::string const& /*header*/) {} + + void from_header(std::string const& /*header*/) {} +}; + +struct module_brief_secondary_actions : public module_secondary_actions +{ + std::set& m_; + + explicit module_brief_secondary_actions(std::set& m) + : m_(m) + { + } + + void heading(std::string const& /*module*/) + { + std::cout << "# Secondary dependencies\n\n"; + } + + void module_start(std::string const& /*module*/) {} + + void module_end(std::string const& /*module*/) {} + + void module_adds(std::string const& module) + { + if (m_.count(module) == 0) + { + std::cout << module_name(module) << "\n"; + m_.insert(module); + } + } +}; + +static void output_module_brief_report( + std::string const& module, bool track_sources, bool track_tests) +{ + std::set m; + + std::cout << "Brief dependency report for " << module_name(module) + << " (sources " << (track_sources ? "on" : "off") << ", tests " + << (track_tests ? "on" : "off") << "):\n\n"; + + module_brief_primary_actions a1(m); + output_module_primary_report(module, a1, track_sources, track_tests); + + std::cout << "\n"; + + std::set m2(m); + m2.insert(module); + + module_brief_secondary_actions a2(m2); + output_module_secondary_report(module, m, a2); +} + +// --list-missing-headers + +struct missing_header_actions : public module_primary_actions +{ + std::string module_, module2_; + + void heading(std::string const& module) + { + module_ = module; + } + + void module_start(std::string const& module) + { + module2_ = module; + } + + void module_end(std::string const& /*module*/) {} + + void header_start(std::string const& header) + { + if (module2_ == "(unknown)") + { + if (!module_.empty()) + { + std::cout << module_ << ":\n"; + module_.clear(); + } + + std::cout << " <" << header << ">\n"; + } + } + + void header_end(std::string const& /*header*/) {} + + void from_header(std::string const& header) + { + if (module2_ == "(unknown)") + { + std::cout << " from <" << header << ">\n"; + } + } +}; + +static void list_missing_headers(std::string const& module) +{ + missing_header_actions a; + output_module_primary_report(module, a, false, false); +} + +static void list_missing_headers() +{ + for (auto i = s_modules.begin(); i != s_modules.end(); ++i) + { + list_missing_headers(*i); + } +} + +// --pkgconfig + +struct primary_pkgconfig_actions : public module_primary_actions +{ + std::string version_; + std::string list_; + + void heading(std::string const&) {} + + void module_start(std::string const& module) + { + if (module == "(unknown)") + return; + + std::string m2(module_target_name(module)); + + if (!list_.empty()) + { + list_ += ", "; + } + + list_ += m2 + " = " + version_; + } + + void module_end(std::string const&) {} + + void header_start(std::string const&) {} + + void header_end(std::string const&) {} + + void from_header(std::string const&) {} +}; + +static void output_requires(std::string const& section, + std::string const& version, std::set const& s) +{ + bool first = true; + + for (auto i = s.begin(); i != s.end(); ++i) + { + if (first) + { + std::cout << section << ": "; + first = false; + } + else + { + std::cout << ", "; + } + + std::string m2(module_target_name(*i)); + std::cout << m2 << " = " << version; + } +} + +static void output_pkgconfig(std::string const& module, + std::string const& version, int argc, char const* argv[]) +{ + for (int i = 0; i < argc; ++i) + { + std::cout << argv[i] << '\n'; + } + + std::cout << '\n'; + + std::string m2(module_target_name(module)); + std::string m3(module_tag_name(module)); + + std::cout << "Name: " << m2 << '\n'; + std::cout << "Description: HPX C++ Module'" << module_name(module) << "'\n"; + std::cout << "Version: " << version << '\n'; + std::cout << "URL: https://github.com/STEllAR-GROUP/hpx/libs/" + << module_name(module) << '\n'; + std::cout << "Cflags: -I${includedir}\n"; + + if (fs::exists(module_build_path(module)) && + fs::exists(module_source_path(module))) + { + std::cout << "Libs: -L${libdir} -l" << m2 << "\n"; + } + + collect_primary_dependencies a1; + output_module_primary_report(m3, a1, false, false); + + if (!a1.set_.empty()) + { + output_requires("Requires", version, a1.set_); + std::cout << std::endl; + } + + collect_primary_dependencies a2; + output_module_primary_report(m3, a2, true, false); + + for (auto i = a1.set_.begin(); i != a1.set_.end(); ++i) + { + a2.set_.erase(*i); + } + + if (!a2.set_.empty()) + { + output_requires("Requires.private", version, a2.set_); + std::cout << std::endl; + } +} + +// --subset-for + +static void output_directory_subset_report( + std::string const& module, std::set const& headers, bool html) +{ + for (auto i = headers.begin(); i != headers.end(); ++i) + { + std::map> deps; + std::map> from; + + std::ifstream is(i->c_str()); + scan_header_dependencies(*i, is, deps, from); + + for (auto j = from.begin(); j != from.end(); ++j) + { + for (auto k = j->second.begin(); k != j->second.end(); ++k) + { + s_header_includes[*k].insert(j->first); + } + } + } + + if (html) + { + module_subset_html_actions actions; + output_module_subset_report_(module, headers, actions); + } + else + { + module_subset_txt_actions actions; + output_module_subset_report_(module, headers, actions); + } +} + +// list_buildable_dependencies + +struct list_buildable_dependencies_actions : public module_overview_actions +{ + std::set buildable_; + + std::set deps_; + bool headers_; + + list_buildable_dependencies_actions() + : headers_() + { + } + + void begin() + { + std::cout + << "# Generated by `hpxdep --list-buildable-dependencies`\n\n"; + } + + void end() {} + + void module_start(std::string const& module) + { + deps_.clear(); + headers_ = false; + + if (buildable_.count(module)) + { + std::cout << module << " ="; + } + } + + void module_end(std::string const& module) + { + if (buildable_.count(module)) + { + if (headers_) + { + std::cout << " headers"; + } + + for (auto i = deps_.begin(); i != deps_.end(); ++i) + { + std::cout << " " << *i; + } + + std::cout << " ;\n"; + } + } + + void module2(std::string const& module) + { + if (module == "(unknown)") + return; + + if (buildable_.count(module) == 0) + { + headers_ = true; + } + else + { + deps_.insert(module); + } + } +}; + +static void list_buildable_dependencies() +{ + list_buildable_dependencies_actions actions; + + for (auto i = s_modules.begin(); i != s_modules.end(); ++i) + { + if (fs::exists(module_build_path(*i)) && + fs::exists(module_source_path(*i))) + { + actions.buildable_.insert(*i); + } + } + + output_module_overview_report(actions); +} + +// + +static bool find_hpx_root() +{ + for (int i = 0; i < 32; ++i) + { + if (fs::exists("hpx.spdx")) + { + return true; + } + + fs::path p = fs::current_path(); + + if (p == p.root_path()) + { + return false; + } + + fs::current_path(p.parent_path()); + } + + return false; +} + +static bool is_hpx_root(fs::path const& p) +{ + return fs::exists(p / "hpx.spdx"); +} + +// teebuf + +class teebuf : public std::streambuf +{ +private: + std::streambuf* sb1_; + std::streambuf* sb2_; + +public: + teebuf(std::streambuf* sb1, std::streambuf* sb2) + : sb1_(sb1) + , sb2_(sb2) + { + } + +private: + virtual int overflow(int c) + { + int r1 = sb1_->sputc(c); + int r2 = sb2_->sputc(c); + + return r1 == EOF || r2 == EOF ? EOF : c; + } + + virtual int sync() + { + int r1 = sb1_->pubsync(); + int r2 = sb2_->pubsync(); + + return r1 == 0 && r2 == 0 ? 0 : -1; + } +}; + +// save_cout_rdbuf + +class save_cout_rdbuf +{ +private: + std::streambuf* sb_; + +public: + save_cout_rdbuf() + : sb_(std::cout.rdbuf()) + { + } + + ~save_cout_rdbuf() + { + std::cout.rdbuf(sb_); + } +}; + +static bool fill_modules_to_use(char const* module_list_filename) +{ + std::ifstream is(module_list_filename); + if (is.is_open()) + { + std::string module; + while (std::getline(is, module)) + { + s_modules_to_use.insert(module_tag_name(module)); + } + s_modules_to_use_option = true; + return true; + } + return false; +} + +std::string format_time(std::chrono::system_clock::time_point tp) +{ + std::stringstream ss; + auto t = std::chrono::system_clock::to_time_t(tp); + auto tp2 = std::chrono::system_clock::from_time_t(t); + if (tp2 > tp) + t = std::chrono::system_clock::to_time_t(tp - std::chrono::seconds(1)); + ss << std::put_time(std::localtime(&t), "%Y-%m-%d %T"); + return ss.str(); +} + +std::string format_version() +{ + std::stringstream ss; + ss << HPX_VERSION_MAJOR << "." << HPX_VERSION_MINOR << "." + << HPX_VERSION_SUBMINOR << HPX_VERSION_TAG + << " (" << std::string(HPX_HAVE_GIT_COMMIT, 10) + << ")"; + return ss.str(); +} + +// main + +int main(int argc, char const* argv[]) +{ + if (argc < 2) + { + std::cout + << "Usage:\n" + "\n" + " hpxdep --list-modules\n" + " hpxdep --list-buildable\n" + " hpxdep --list-dependencies\n" + " hpxdep --list-exceptions\n" + " hpxdep --list-missing-headers\n" + " hpxdep --list-buildable-dependencies\n" + "\n" + " hpxdep [options] --module-overview\n" + " hpxdep [options] --module-levels\n" + " hpxdep [options] --module-weights\n" + "\n" + " hpxdep [options] [--primary] \n" + " hpxdep [options] --secondary \n" + " hpxdep [options] --reverse \n" + " hpxdep [options] --subset \n" + " hpxdep [options] [--header]
    \n" + " hpxdep --test \n" + " hpxdep --cmake \n" + " hpxdep --pkgconfig [=] " + "[=]...\n" + " hpxdep [options] --subset-for \n" + " hpxdep --brief \n" + "\n" + " [options]: [--hpx-root ]\n" + " [--hpx-build-root ]\n" + " [--[no-]track-sources]\n" + " [--[no-]track-tests]\n" + " [--html-title ]\n" + " [--html-footer <footer>]\n" + " [--html-stylesheet <stylesheet>]\n" + " [--html-prefix <prefix>]\n" + " [--html]\n" + " [--module-list <module-list-file>]\n"; + + return -1; + } + + for (int i = 0; i < argc; ++i) + { + std::string option = argv[i]; + + if (option == "--hpx-root") + { + if (i + 1 < argc) + { + fs::path p(argv[++i]); + + if (is_hpx_root(p)) + { + s_hpx_root = p; + s_root_set = true; + } + else + { + std::cerr << "'" << p.string() + << "': not a valid HPX root.\n"; + return -2; + } + } + else + { + std::cerr << "'" << option << "': missing argument.\n"; + return -2; + } + } + else if (option == "--hpx-build-root") + { + if (i + 1 < argc) + { + fs::path p(argv[++i]); + s_hpx_build_root = p; + s_build_root_set = true; + } + else + { + std::cerr << "'" << option << "': missing argument.\n"; + return -2; + } + } + else if (option == "--module-list") + { + if (i + 1 < argc) + { + if (!fill_modules_to_use(argv[++i])) + { + std::cerr << "'" << option + << "': couldn't open module list file: '" + << argv[i] << ".\n"; + return -2; + } + } + } + } + + // collect all headers from source tree + if (!s_root_set && !find_hpx_root()) + { + char const* env_root = std::getenv("HPX_ROOT"); + + if (env_root && is_hpx_root(env_root)) + { + s_hpx_root = env_root; + s_root_set = true; + } + else + { + std::cerr << "hpxdep: Could not find HPX root.\n"; + return -2; + } + } + + try + { + if (s_build_root_set) + { + build_header_map(s_hpx_build_root); + } + build_header_map(s_hpx_root); + } + catch (fs::filesystem_error const& x) + { + std::cerr << x.what() << std::endl; + } + + bool html = false; + bool secondary = false; + bool track_sources = true; + bool track_tests = false; + + std::string report_time = format_time(std::chrono::system_clock::now()); + std::string report_version = format_version(); + + std::string html_title = "HPX Dependency Report"; + std::string html_footer = + "Generated on " + report_time + " from revision " + report_version; + + // clang-format off + std::string html_stylesheet = R"( + <style type="text/css"> + A { color: #06C; text-decoration: none; } + A:hover { text-decoration: underline; } + body { max-width: 60em; margin-left: auto; margin-right: + auto; color: #4A6484; font-family: sans-serif; + } + .logo { font-family: sans-serif; font-style: italic; } + .logo .upper { font-size: 48pt; font-weight: 800; } + .logo .lower { font-size: 17pt; } + .logo .description { font-size: small; } + .primary-list { font-size: small; } + .secondary-list { font-size: small; } + #module-overview .primary-list { margin-left: 1em; } + #module-levels h3 { margin-left: 1em; } + #module-levels .primary-list { margin-left: 2em; } + #module-weights h3 { margin-left: 1em; } + #module-weights .primary-list { margin-left: 2em; } + #module-weights .secondary-list { + margin-left: 2em; padding-left: 1em; border-left: 1px dotted; + } + </style>)"; + + std::string html_prefix = R"( + <table><tr> + <td><a href="https://github.com/STEllAR-GROUP/hpx"> + <img src="http://stellar-group.org/files/stellar100.png" alt="STE||AR logo" /> + </a></td> + <td><div class='logo'> + <div class='upper'>HPX</div> + <div class='lower'>Dependency Report</div> + <div class='description'>)" + + report_version + ", " + report_time + + "</div>" + + "</div></td></tr></table><hr/>"; + // clang-format on + + std::ostringstream captured_output; + teebuf tsb(std::cout.rdbuf(), captured_output.rdbuf()); + + save_cout_rdbuf scrdb; + + for (int i = 1; i < argc; ++i) + { + std::string option = argv[i]; + + if (option == "--hpx-root" || option == "--hpx-build-root" || + option == "--module-list") + { + ++i; + } + else if (option == "--list-modules") + { + list_modules(); + } + else if (option == "--list-buildable") + { + list_buildable(); + } + else if (option == "--title" || option == "--html-title") + { + if (i + 1 < argc) + { + html_title = argv[++i]; + + // strip potential quotes surrounding the title + std::size_t size = html_title.size(); + if (size > 2 && html_title[0] == '"' && + html_title[size - 1] == '"') + { + html_title = html_title.substr(1, size - 2); + } + } + } + else if (option == "--footer" || option == "--html-footer") + { + if (i + 1 < argc) + { + html_footer = argv[++i]; + } + } + else if (option == "--html-stylesheet") + { + if (i + 1 < argc) + { + html_stylesheet = argv[++i]; + } + } + else if (option == "--html-prefix") + { + if (i + 1 < argc) + { + html_prefix = argv[++i]; + } + } + else if (option == "--html") + { + if (!html) + { + html = true; + output_html_header(html_title, html_stylesheet, html_prefix); + } + } + else if (option == "--track-sources") + { + track_sources = true; + } + else if (option == "--no-track-sources") + { + track_sources = false; + } + else if (option == "--track-tests") + { + track_tests = true; + } + else if (option == "--no-track-tests") + { + track_tests = false; + } + else if (option == "--primary") + { + if (i + 1 < argc) + { + output_module_primary_report(module_tag_name(argv[++i]), html, + track_sources, track_tests); + } + } + else if (option == "--secondary") + { + if (i + 1 < argc) + { + enable_secondary(secondary, track_sources, track_tests); + output_module_secondary_report( + module_tag_name(argv[++i]), html); + } + } + else if (option == "--reverse") + { + if (i + 1 < argc) + { + enable_secondary(secondary, track_sources, track_tests); + output_module_reverse_report(module_tag_name(argv[++i]), html); + } + } + else if (option == "--header") + { + if (i + 1 < argc) + { + enable_secondary(secondary, track_sources, track_tests); + output_header_report(argv[++i], html); + } + } + else if (option == "--subset") + { + if (i + 1 < argc) + { + enable_secondary(secondary, track_sources, track_tests); + output_module_subset_report(module_tag_name(argv[++i]), + track_sources, track_tests, html); + } + } + else if (option == "--test") + { + if (i + 1 < argc) + { + output_module_test_report(module_tag_name(argv[++i])); + } + } + else if (option == "--cmake") + { + if (i + 1 < argc) + { + output_module_cmake_report(module_tag_name(argv[++i])); + } + } + else if (option == "--brief") + { + if (i + 1 < argc) + { + enable_secondary(secondary, track_sources, track_tests); + output_module_brief_report( + module_tag_name(argv[++i]), track_sources, track_tests); + } + } + else if (option == "--module-levels") + { + enable_secondary(secondary, track_sources, track_tests); + output_module_level_report(html); + } + else if (option == "--module-overview") + { + enable_secondary(secondary, track_sources, track_tests); + output_module_overview_report(html); + } + else if (option == "--module-weights") + { + enable_secondary(secondary, track_sources, track_tests); + output_module_weight_report(html); + } + else if (option == "--list-dependencies") + { + enable_secondary(secondary, track_sources, track_tests); + list_dependencies(); + } + else if (option == "--list-exceptions") + { + list_exceptions(); + } + else if (option == "--list-missing-headers") + { + list_missing_headers(); + } + else if (option == "--pkgconfig") + { + if (i + 2 < argc) + { + std::string module = module_tag_name(argv[++i]); + std::string version = argv[++i]; + + ++i; + + output_pkgconfig(module, version, argc - i, argv + i); + } + else + { + std::cerr << "'" << option << "': missing module or version.\n"; + } + + break; + } + else if (option == "--subset-for") + { + if (i + 1 < argc) + { + std::string module = module_tag_name(argv[++i]); + + enable_secondary(secondary, track_sources, track_tests); + + std::set<std::string> headers; + add_module_headers(module, headers); + + output_directory_subset_report(module, headers, html); + } + else + { + std::cerr << "'" << option << "': missing argument.\n"; + } + + break; + } + else if (option == "--list-buildable-dependencies") + { + enable_secondary(secondary, true, false); + list_buildable_dependencies(); + } + else if (option == "--capture-output") + { + std::cout.rdbuf(&tsb); + } + else if (option == "--compare-output") + { + if (i + 1 < argc) + { + std::string fn = argv[++i]; + std::fstream is(fn.c_str()); + + if (!is) + { + std::cerr << option << " '" << fn + << "': could not open file.\n"; + return 1; + } + + std::istreambuf_iterator<char> first(is), last; + std::string fc(first, last); + + if (fc != captured_output.str()) + { + std::cerr + << option << " '" << fn + << "': output does not match; expected output:\n---\n" + << fc << "---\n"; + return 1; + } + + std::cerr << option << " '" << fn << "': output matches.\n"; + captured_output.str(""); + } + else + { + std::cerr << "'" << option << "': missing argument.\n"; + return 1; + } + } + else if (s_modules.count(option)) + { + output_module_primary_report( + module_tag_name(option), html, track_sources, track_tests); + } + else if (s_header_map.count(option)) + { + enable_secondary(secondary, track_sources, track_tests); + output_header_report(module_tag_name(option), html); + } + else + { + std::cerr << "'" << option + << "': not an option, module or header.\n"; + } + } + + if (html) + { + output_html_footer(html_footer); + } +} diff --git a/tools/hpxdep/report.bat b/tools/hpxdep/report.bat new file mode 100644 index 000000000000..9f90dcb4b7ea --- /dev/null +++ b/tools/hpxdep/report.bat @@ -0,0 +1,29 @@ +@REM This is an example cmd.exe batch script +@REM that uses hpxdep.exe to generate a +@REM complete HPX dependency report. +@REM +@REM It needs to be run from the HPX root. +@REM +@REM Copyright 2022 Hartmut Kaiser +@REM Copyright 2014, 2015, 2017 Peter Dimov +@REM +@REM SPDX-License-Identifier: BSL-1.0 +@REM Distributed under the Boost Software License, Version 1.0. +@REM See accompanying file LICENSE_1_0.txt or copy at +@REM http://www.boost.org/LICENSE_1_0.txt + +SET HPXDEP=hpxdep.exe +SET OPTIONS=--hpx-root %1 --hpx-build-root %2 +SET OUTDIR=.\report + +mkdir %OUTDIR% +mkdir %OUTDIR%\core +mkdir %OUTDIR%\full + +%HPXDEP% --list-modules > %OUTDIR%\list-modules.txt + +%HPXDEP% %OPTIONS% --html-title "HPX Module Overview" --html --module-overview > %OUTDIR%\module-overview.html +%HPXDEP% %OPTIONS% --html-title "HPX Module Levels" --html --module-levels > %OUTDIR%\module-levels.html +%HPXDEP% %OPTIONS% --html-title "HPX Module Weights" --html --module-weights > %OUTDIR%\module-weights.html + +FOR /f %%i IN (%OUTDIR%\list-modules.txt) DO %HPXDEP% --html-title "HPX Dependency Report for %%i" %OPTIONS% --html --primary %%i --secondary %%i --reverse %%i > %OUTDIR%\%%i.html