diff --git a/cmake/link_vt.cmake b/cmake/link_vt.cmake index 1fb71103be..16e3ebd636 100644 --- a/cmake/link_vt.cmake +++ b/cmake/link_vt.cmake @@ -183,15 +183,9 @@ function(link_target_with_vt) message(STATUS "link_target_with_vt: fmt=${ARG_LINK_FMT}") endif() - if(${vt_external_fmt}) - target_link_libraries( - ${ARG_TARGET} PUBLIC ${ARG_BUILD_TYPE} fmt::fmt - ) - else() - target_link_libraries( - ${ARG_TARGET} PUBLIC ${ARG_BUILD_TYPE} ${FMT_LIBRARY} - ) - endif() + target_link_libraries( + ${ARG_TARGET} PUBLIC ${ARG_BUILD_TYPE} ${FMT_LINK_TARGET} + ) endif() diff --git a/cmake/load_bundled_libraries.cmake b/cmake/load_bundled_libraries.cmake index 26658dff73..c555e63150 100644 --- a/cmake/load_bundled_libraries.cmake +++ b/cmake/load_bundled_libraries.cmake @@ -20,7 +20,7 @@ endif() # CLI11 always included in the build add_subdirectory(${PROJECT_LIB_DIR}/CLI) -# use included fmt or external one +# Use an external fmt package or configure VT's shared DARMA fmt provider. if(${vt_external_fmt}) # user should provide 'fmt_DIR' or 'fmt_ROOT' to CMake (unless fmt is installed in system libs) if(fmt_ROOT) @@ -31,11 +31,23 @@ if(${vt_external_fmt}) message(STATUS "vt_external_fmt = ON but neither fmt_DIR nor fmt_ROOT is provided!") endif() find_package(fmt 10.2.1 REQUIRED) - + set(FMT_LIBRARY "") + set(FMT_LINK_TARGET fmt::fmt) + set(vt_uses_parent_fmt 0) else() - set(FMT_LIBRARY fmt) - add_subdirectory(${PROJECT_LIB_DIR}/fmt) - set_darma_compiler_flags(${FMT_LIBRARY}) + include(setup_fmt) + darma_setup_fmt( + SOURCE_DIR "${PROJECT_LIB_DIR}/fmt" + VENDORED_TARGET fmt + OUT_LIBRARY FMT_LIBRARY + OUT_LINK_TARGET FMT_LINK_TARGET + POST_ADD_COMMAND set_darma_compiler_flags + ) + if(FMT_LIBRARY) + set(vt_uses_parent_fmt 0) + else() + set(vt_uses_parent_fmt 1) + endif() endif() # yaml-cpp always included in the build diff --git a/cmake/setup_fmt.cmake b/cmake/setup_fmt.cmake new file mode 100644 index 0000000000..fdc8429144 --- /dev/null +++ b/cmake/setup_fmt.cmake @@ -0,0 +1,174 @@ +include_guard(GLOBAL) + +# Configure one compiled fmt provider across a hierarchy of DARMA projects. +# +# The first project in a hierarchy builds its vendored fmt. Later projects +# reuse that fmt::fmt target and only contribute their local include layout +# (for example fmt-vt, fmt, or fmt-lb). Exact FMT_VERSION equality is required +# so local headers never call into an incompatible compiled fmt implementation. +# +# Copy this file into each repository and replace add_subdirectory(fmt) with: +# +# include(cmake/setup_fmt.cmake) +# darma_setup_fmt( +# SOURCE_DIR lib/fmt +# VENDORED_TARGET fmt +# OUT_LIBRARY FMT_LIBRARY +# OUT_LINK_TARGET FMT_LINK_TARGET +# ) +# +# VT, comm, and loc use lib/fmt + fmt; LB uses tpl/fmt + fmt-lb. +# Optional POST_ADD_COMMAND names a project function which is called only when +# the current project owns and compiles the vendored target. +function(darma_setup_fmt) + cmake_parse_arguments( + PARSE_ARGV 0 + DARMA_FMT + "" + "SOURCE_DIR;VENDORED_TARGET;OUT_LIBRARY;OUT_LINK_TARGET;POST_ADD_COMMAND" + "" + ) + + foreach(required_arg + SOURCE_DIR VENDORED_TARGET OUT_LIBRARY OUT_LINK_TARGET) + if(NOT DARMA_FMT_${required_arg}) + message(FATAL_ERROR "darma_setup_fmt requires ${required_arg}") + endif() + endforeach() + + if(IS_ABSOLUTE "${DARMA_FMT_SOURCE_DIR}") + set(fmt_source_dir "${DARMA_FMT_SOURCE_DIR}") + else() + get_filename_component( + fmt_source_dir + "${DARMA_FMT_SOURCE_DIR}" + ABSOLUTE + BASE_DIR "${CMAKE_CURRENT_SOURCE_DIR}" + ) + endif() + + file(GLOB fmt_base_headers "${fmt_source_dir}/include/*/base.h") + list(LENGTH fmt_base_headers fmt_base_header_count) + if(NOT fmt_base_header_count EQUAL 1) + message( + FATAL_ERROR + "Expected one fmt base.h below ${fmt_source_dir}/include, found " + "${fmt_base_header_count}" + ) + endif() + + list(GET fmt_base_headers 0 fmt_base_header) + file(STRINGS + "${fmt_base_header}" + fmt_version_line + REGEX "^# *define FMT_VERSION [0-9]+" + LIMIT_COUNT 1 + ) + if(NOT fmt_version_line MATCHES "FMT_VERSION +([0-9]+)") + message(FATAL_ERROR "Could not read FMT_VERSION from ${fmt_base_header}") + endif() + set(local_fmt_version "${CMAKE_MATCH_1}") + + # The first DARMA project in the CMake hierarchy owns fmt. It need not be + # the top-level CMake project: consumers commonly add VT as a subdirectory + # without configuring fmt themselves (for example vt-sample-project). + if(NOT TARGET fmt::fmt) + # fmt is compiled as ordinary C++20 and its module target is disabled by + # the caller. Prevent CMP0155 from requiring a compiler dependency scanner + # (for example clang-scan-deps) for these non-module sources. + set(CMAKE_CXX_SCAN_FOR_MODULES OFF) + add_subdirectory("${fmt_source_dir}") + + if(NOT TARGET ${DARMA_FMT_VENDORED_TARGET}) + message( + FATAL_ERROR + "${fmt_source_dir} did not create target " + "'${DARMA_FMT_VENDORED_TARGET}'" + ) + endif() + # VT and comm already expose fmt::fmt. LB's renamed vendored target is + # fmt-lb, so supply the common provider alias here when it is absent. + if(NOT TARGET fmt::fmt) + add_library(fmt::fmt ALIAS ${DARMA_FMT_VENDORED_TARGET}) + endif() + + if(DARMA_FMT_POST_ADD_COMMAND) + if(NOT COMMAND ${DARMA_FMT_POST_ADD_COMMAND}) + message( + FATAL_ERROR + "Unknown POST_ADD_COMMAND: ${DARMA_FMT_POST_ADD_COMMAND}" + ) + endif() + cmake_language( + CALL ${DARMA_FMT_POST_ADD_COMMAND} ${DARMA_FMT_VENDORED_TARGET} + ) + endif() + + set_property( + TARGET ${DARMA_FMT_VENDORED_TARGET} + PROPERTY INTERFACE_DARMA_FMT_VERSION "${local_fmt_version}" + ) + set(fmt_owned_library "${DARMA_FMT_VENDORED_TARGET}") + message( + STATUS + "${PROJECT_NAME}: building vendored fmt ${local_fmt_version}" + ) + else() + get_target_property(fmt_provider_target fmt::fmt ALIASED_TARGET) + if(NOT fmt_provider_target) + set(fmt_provider_target fmt::fmt) + endif() + get_target_property( + parent_fmt_version + ${fmt_provider_target} + INTERFACE_DARMA_FMT_VERSION + ) + if(NOT parent_fmt_version) + message( + FATAL_ERROR + "The parent fmt::fmt target was not configured by setup_fmt.cmake; " + "its version cannot be verified." + ) + endif() + if(NOT local_fmt_version STREQUAL parent_fmt_version) + message( + FATAL_ERROR + "fmt version mismatch: ${PROJECT_NAME} has ${local_fmt_version}, " + "but the parent provider has ${parent_fmt_version}." + ) + endif() + + # The projects intentionally use different include directory names while + # sharing fmt's ABI. Make this project's headers visible through the one + # parent provider without installing or compiling another fmt library. + target_include_directories( + ${fmt_provider_target} INTERFACE + $ + ) + + # The provider may install a different header layout (for example fmt-lb). + # Consumers of this project's installed headers still need its local fmt + # include names, even though they link to the parent's compiled library. + if(FMT_INC_DIR) + set(fmt_install_include_dir "${FMT_INC_DIR}") + else() + include(GNUInstallDirs) + set(fmt_install_include_dir "${CMAKE_INSTALL_INCLUDEDIR}") + endif() + install( + DIRECTORY "${fmt_source_dir}/include/" + DESTINATION "${fmt_install_include_dir}" + COMPONENT fmt-core + FILES_MATCHING PATTERN "*.h" + ) + + set(fmt_owned_library "") + message( + STATUS + "${PROJECT_NAME}: reusing parent fmt ${parent_fmt_version}" + ) + endif() + + set(${DARMA_FMT_OUT_LIBRARY} "${fmt_owned_library}" PARENT_SCOPE) + set(${DARMA_FMT_OUT_LINK_TARGET} fmt::fmt PARENT_SCOPE) +endfunction() diff --git a/cmake/trace_only_functions.cmake b/cmake/trace_only_functions.cmake index 8584aa652f..4cabf84356 100644 --- a/cmake/trace_only_functions.cmake +++ b/cmake/trace_only_functions.cmake @@ -162,7 +162,9 @@ function(create_trace_only_target) CONFIGURATIONS ${build_type_list} ) - install(TARGETS ${FMT_LIBRARY} EXPORT ${VT_TRACE_LIB}) + if(FMT_LIBRARY) + install(TARGETS ${FMT_LIBRARY} EXPORT ${VT_TRACE_LIB}) + endif() export( TARGETS ${VT_TRACE_LIB} diff --git a/cmake/vtConfig.cmake.in b/cmake/vtConfig.cmake.in index bb68c85a20..cc1c6c52e7 100644 --- a/cmake/vtConfig.cmake.in +++ b/cmake/vtConfig.cmake.in @@ -27,7 +27,7 @@ endif() set (VT_INCLUDE_DIRS "@CMAKE_INSTALL_PREFIX@/include") set (VT_CXX_COMPILER "@CMAKE_CXX_COMPILER@") -if (@vt_external_fmt@) +if (@vt_external_fmt@ OR @vt_uses_parent_fmt@) set (fmt_DIR @fmt_DIR@) set (fmt_ROOT @fmt_ROOT@) find_dependency(fmt REQUIRED HINTS @fmt_DIR@ @fmt_ROOT@) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 6572399cae..6f81975a8d 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -300,7 +300,9 @@ install( install(TARGETS ${BROTLI_LIBRARY} EXPORT ${VIRTUAL_TRANSPORT_LIBRARY}) install(TARGETS ${ENG_FORMAT_LIBRARY} EXPORT ${VIRTUAL_TRANSPORT_LIBRARY}) -install(TARGETS ${FMT_LIBRARY} EXPORT ${VIRTUAL_TRANSPORT_LIBRARY}) +if(FMT_LIBRARY) + install(TARGETS ${FMT_LIBRARY} EXPORT ${VIRTUAL_TRANSPORT_LIBRARY}) +endif() install(TARGETS ${YAMLCPP_LIBRARY} EXPORT ${VIRTUAL_TRANSPORT_LIBRARY}) install(TARGETS ${JSON_LIBRARY} EXPORT ${VIRTUAL_TRANSPORT_LIBRARY})