# Distributed under the MIT License. # See LICENSE.txt for details. # FindCharm.cmake # # Finds a suitable Charm++ installation and exposes it via imported targets. # # This module requires you have set the `CHARM_ROOT` variable to a Charm++ # installation directory, e.g.: # # cmake -D CHARM_ROOT=/path/to/charm++/build-dir # # This is to ensure that SpECTRE builds with the Charm++ installation that you # intended to use. The `CHARM_ROOT/bin` directory should contain the Charm++ # compiler `charmc`. All information on include directories, library paths, # compiler flags etc. will be retrieved by invoking `charmc` with the # `-print-building-blocks` flag. Note that we do _not_ use `charmc` to wrap the # CMake compiler configuration. Instead, the information retrieved from `charmc` # is made available to CMake via imported targets. # # This module supports CMake "components" to load additional Charm++ modules. # You can load additional Charm++ modules like this: # # find_package(Charm 7.0.0 COMPONENTS EveryLB) # # This module exposes the following targets: # # - `Charmxx::charmxx`: All Charm++ libraries. # - `Charmxx::pup`: Only the Charm++ PUP serialization library. # - `Charmxx::main`: Defines the Charm++ main function. Link to compile # executables. # - `CharmModuleInit`: Provides the generated definitions of Charm++'s # module-init functions. option(CHARM_SHARED_LIBS "Link Charm++ shared libraries. Defaults to the \ value of 'BUILD_SHARED_LIBS' but can be overriden to enforce shared or static \ libraries for Charm++." ${BUILD_SHARED_LIBS}) option(CHARM_USE_MPI "Find and link MPI with Charm++. An attempt is made to \ determine automatically if MPI is needed, but this flag can be turned on to \ enable linking MPI if the automatic check fails." OFF) option(CHARM_TRACE_PROJECTIONS "Charm++ was built with projections support on. \ Enables '-tracemode projections'" OFF) option(CHARM_TRACE_SUMMARY "Charm++ was built with projections support on. \ Enables '-tracemode summary'" OFF) if (DEFINED ENV{CHARM_ROOT} AND "${CHARM_ROOT}" STREQUAL "") set(CHARM_ROOT "$ENV{CHARM_ROOT}") endif() if (NOT EXISTS "${CHARM_ROOT}") if ("${CHARM_ROOT}" STREQUAL "") message( FATAL_ERROR "CHARM_ROOT was not set. Pass it as a command-line arg: " "cmake -D CHARM_ROOT=/path/to/charm++/build-dir") endif() message( FATAL_ERROR "CHARM_ROOT=${CHARM_ROOT} does not exist. " "Please pass it as a command-line definition to cmake, i.e. " "cmake -D CHARM_ROOT=/path/to/charm++/build-dir" ) endif () # Find the Charm compiler `charmc` first so we can retrieve build info from it find_program(CHARM_COMPILER NAMES charmc PATH_SUFFIXES bin HINTS ${CHARM_ROOT} ENV CHARM_ROOT NO_DEFAULT_PATH DOC "The full path to the charm++ compiler 'charmc'" ) if(CHARM_COMPILER STREQUAL "CHARM_COMPILER-NOTFOUND") message(FATAL_ERROR "The Charm++ compiler 'charmc' could not be found. Make " "sure you have set 'CHARM_ROOT' to the path that contains 'bin/charmc'.") endif() # Assemble options for invoking `charmc`: # - Use static or shared libs if(CHARM_SHARED_LIBS) list(APPEND CHARMC_OPTIONS "-charm-shared") endif() # - Not linking the main function by default. We expose an extra target that # links it because we sometimes want to link Charm++ without a main function. list(APPEND CHARMC_OPTIONS "-nomain") list(APPEND CHARMC_OPTIONS "-nomain-module") if(CHARM_TRACE_PROJECTIONS) list(APPEND CHARMC_OPTIONS "-tracemode projections") endif() if(CHARM_TRACE_SUMMARY) list(APPEND CHARMC_OPTIONS "-tracemode summary") endif() # - Request optional Charm++ modules. They can be specified as `COMPONENTS` when # calling CMake's `find_package`. if(Charm_FIND_COMPONENTS) list(JOIN Charm_FIND_COMPONENTS "," CHARM_COMPONENTS_JOINED) list(APPEND CHARMC_OPTIONS "-modules ${CHARM_COMPONENTS_JOINED}") endif() # Invoke 'charmc' so that it outputs build info list(JOIN CHARMC_OPTIONS " " CHARMC_OPTIONS_JOINED) set(CHARM_BUILDING_BLOCKS_CMD "${CHARM_COMPILER} -print-building-blocks ${CHARMC_OPTIONS_JOINED}") message(STATUS "Charm++ 'charmc' is invoked as: ${CHARM_BUILDING_BLOCKS_CMD}") execute_process( COMMAND bash -c "${CHARM_BUILDING_BLOCKS_CMD}" OUTPUT_VARIABLE CHARM_BUILDING_BLOCKS ERROR_VARIABLE CHARM_BUILDING_BLOCKS_ERROR OUTPUT_STRIP_TRAILING_WHITESPACE ) if(NOT CHARM_BUILDING_BLOCKS) if(CHARM_BUILDING_BLOCKS_ERROR MATCHES "lib_so directory not found" AND CHARM_SHARED_LIBS) message(STATUS "Charm++ is not built with shared-library support so we " "fall back to static libraries. Build Charm++ with the '--build-shared' " "option to enable shared libraries.") set(CHARM_SHARED_LIBS OFF) list(REMOVE_ITEM CHARMC_OPTIONS "-charm-shared") list(JOIN CHARMC_OPTIONS " " CHARMC_OPTIONS_JOINED) set(CHARM_BUILDING_BLOCKS_CMD "${CHARM_COMPILER} -print-building-blocks ${CHARMC_OPTIONS_JOINED}") execute_process( COMMAND bash -c "${CHARM_BUILDING_BLOCKS_CMD}" OUTPUT_VARIABLE CHARM_BUILDING_BLOCKS ERROR_VARIABLE CHARM_BUILDING_BLOCKS_ERROR OUTPUT_STRIP_TRAILING_WHITESPACE ) else() message(FATAL_ERROR "Failed retrieving Charm++ building blocks with " "command:\n ${CHARM_BUILDING_BLOCKS_CMD}\nPlease make sure the Charm++ " "version is at least 6.9.0. Error message:\n" "${CHARM_BUILDING_BLOCKS_ERROR}") endif() endif() string(REGEX REPLACE ";" "\\\\;" CHARM_BUILDING_BLOCKS_LIST "${CHARM_BUILDING_BLOCKS}") string(REGEX REPLACE "\n" ";" CHARM_BUILDING_BLOCKS_LIST "${CHARM_BUILDING_BLOCKS_LIST}") # Load the charm building blocks into CMake variables foreach(CHARM_BUILDING_BLOCK IN LISTS CHARM_BUILDING_BLOCKS_LIST) if(CHARM_BUILDING_BLOCK MATCHES "^([^=]+)='(.*)'$") set(${CMAKE_MATCH_1} "${CMAKE_MATCH_2}") string(STRIP "${${CMAKE_MATCH_1}}" ${CMAKE_MATCH_1}) elseif() message(SEND_ERROR "Unexpected output from charmc: ${CHARM_BUILDING_BLOCK}") endif() endforeach() # Validate the charm building blocks list(APPEND CHARM_REQUIRED_BUILDING_BLOCKS CHARM_CXX CHARM_CXX_FLAGS CHARM_LDXX_FLAGS CHARMINC # Not needed but available, could be added if needed: # CHARM_CC # CHARM_LD # CHARM_LDXX # CHARM_CC_FLAGS # CHARM_LD_FLAGS # CHARMBIN ) if(CHARM_SHARED_LIBS) list(APPEND CHARM_REQUIRED_BUILDING_BLOCKS CHARMLIBSO) else() list(APPEND CHARM_REQUIRED_BUILDING_BLOCKS CHARMLIB) endif() foreach(CHARM_REQUIRED_BUILDING_BLOCK IN LISTS CHARM_REQUIRED_BUILDING_BLOCKS) if(NOT ${CHARM_REQUIRED_BUILDING_BLOCK}) message(FATAL_ERROR "Could not find ${CHARM_REQUIRED_BUILDING_BLOCK} " "variable in output of command: ${CHARM_BUILDING_BLOCKS_CMD}") endif() endforeach() # Split flags into lists separate_arguments(CHARM_CXX_FLAGS) separate_arguments(CHARM_LDXX_FLAGS) # Define variables with standard names for compatibility, though these should # not be used outside this script. get_filename_component(CHARM_INCLUDE_DIR ${CHARMINC} ABSOLUTE) set(CHARM_INCLUDE_DIRS ${CHARM_INCLUDE_DIR}) if(CHARM_SHARED_LIBS) set(CHARM_LIBRARIES ${CHARMLIBSO}) else() set(CHARM_LIBRARIES ${CHARMLIB}) endif() # Find version file if(EXISTS "${CHARM_INCLUDE_DIR}/charm-version.h") set(CHARM_VERSION_FILE_VERSION "6_11") set(CHARM_VERSION_FILE_LOCATION "${CHARM_INCLUDE_DIR}/charm-version.h") elseif(EXISTS "${CHARM_INCLUDE_DIR}/VERSION") set(CHARM_VERSION_FILE_VERSION "pre_6_11") set(CHARM_VERSION_FILE_LOCATION "${CHARM_INCLUDE_DIR}/VERSION") elseif(EXISTS "${CHARM_ROOT}/VERSION") set(CHARM_VERSION_FILE_VERSION "pre_6_11") set(CHARM_VERSION_FILE_LOCATION "${CHARM_ROOT}/VERSION") else() message(FATAL_ERROR "Failed to find Charm++ version file") endif() # Parse version from file file(READ "${CHARM_VERSION_FILE_LOCATION}" CHARM_VERSION_FILE) if(CHARM_VERSION_FILE_VERSION STREQUAL "6_11") # Since version 6.11 the file is C++-compatible if(CHARM_VERSION_FILE MATCHES "#define CHARM_VERSION_MAJOR ([0-9]+)") set(CHARM_VERSION_MAJOR ${CMAKE_MATCH_1}) else() message(FATAL_ERROR "Could not parse CHARM_VERSION_MAJOR from file: " "${CHARM_VERSION_FILE_LOCATION}") endif() if(CHARM_VERSION_FILE MATCHES "#define CHARM_VERSION_MINOR ([0-9]+)") set(CHARM_VERSION_MINOR ${CMAKE_MATCH_1}) else() message(FATAL_ERROR "Could not parse CHARM_VERSION_MINOR from file: " "${CHARM_VERSION_FILE_LOCATION}") endif() if(CHARM_VERSION_FILE MATCHES "#define CHARM_VERSION_PATCH ([0-9]+)") set(CHARM_VERSION_PATCH ${CMAKE_MATCH_1}) else() message(FATAL_ERROR "Could not parse CHARM_VERSION_PATCH from file: " "${CHARM_VERSION_FILE_LOCATION}") endif() elseif(CHARM_VERSION_FILE_VERSION STREQUAL "pre_6_11") # Before version 6.11 the file contains only a string string(REGEX REPLACE "\n" "" CHARM_VERSION_FILE "${CHARM_VERSION_FILE}") string( REGEX REPLACE "([0-9])1([0-9])0([0-9])" "\\1;1\\2;\\3" CHARM_VERSIONS_PARSED ${CHARM_VERSION_FILE} ) list(GET CHARM_VERSIONS_PARSED 0 CHARM_VERSION_MAJOR) list(GET CHARM_VERSIONS_PARSED 1 CHARM_VERSION_MINOR) list(GET CHARM_VERSIONS_PARSED 2 CHARM_VERSION_PATCH) endif() set(CHARM_VERSION "${CHARM_VERSION_MAJOR}.${CHARM_VERSION_MINOR}.${CHARM_VERSION_PATCH}") # Filter the compiler and linker flags: # - Remove the macOS deployment target so the CMake setting is used. list(FILTER CHARM_CXX_FLAGS EXCLUDE REGEX "^-mmacosx-version-min=") list(FILTER CHARM_LDXX_FLAGS EXCLUDE REGEX "^-mmacosx-version-min=") # - Remove the C++ standard flag so the CMake setting is used. list(FILTER CHARM_CXX_FLAGS EXCLUDE REGEX "^-std=") # - Remove the standard library flag so the CMake setting is used. list(FILTER CHARM_CXX_FLAGS EXCLUDE REGEX "^-stdlib=") list(FILTER CHARM_LDXX_FLAGS EXCLUDE REGEX "^-stdlib=") # - Remove the -fPIC and -fPIE flags so the CMake setting is used. list(FILTER CHARM_CXX_FLAGS EXCLUDE REGEX "-fPI[CE]") list(FILTER CHARM_CXX_FLAGS EXCLUDE REGEX "-fpi[ce]") list(FILTER CHARM_LDXX_FLAGS EXCLUDE REGEX "-fPI[CE]") list(FILTER CHARM_LDXX_FLAGS EXCLUDE REGEX "-fpi[ce]") # - Remove the -rdynamic flag so the CMake setting is used. This flag is needed # for `backtrace` functions. We handle the flag in our own build system # (instead of taking it over from Charm++). list(FILTER CHARM_CXX_FLAGS EXCLUDE REGEX "-dynamic") list(FILTER CHARM_LDXX_FLAGS EXCLUDE REGEX "-rdynamic") # - Extract the include directories so we can set them properly with CMake. # That's better because they are declared as "SYSTEM", which silences warnings # from those headers. list(FILTER CHARM_CXX_FLAGS EXCLUDE REGEX "^-I${CHARMINC}$") set(CHARM_INCLUDE_EXTRA ${CHARM_CXX_FLAGS}) list(FILTER CHARM_INCLUDE_EXTRA INCLUDE REGEX "^-I.+") list(FILTER CHARM_CXX_FLAGS EXCLUDE REGEX "^-I.+") list(TRANSFORM CHARM_INCLUDE_EXTRA REPLACE "^-I" "") # Filter out relative paths to `./proc_management` that are added for some # backends (ucx and ofi), since those paths don't seem to exist list(FILTER CHARM_INCLUDE_EXTRA EXCLUDE REGEX "^[.]/proc_management") list(APPEND CHARM_INCLUDE_DIRS ${CHARM_INCLUDE_EXTRA}) # - Remove the rpath linker argument, since CMake adds it automatically. list(FILTER CHARM_LDXX_FLAGS EXCLUDE REGEX "^-Wl,-rpath,${CHARM_LIBRARIES}/?$") # - Extract lib directories list(FILTER CHARM_LDXX_FLAGS EXCLUDE REGEX "^-L${CHARM_LIBRARIES}$") set(CHARM_LIBRARIES_EXTRA ${CHARM_LDXX_FLAGS}) list(FILTER CHARM_LIBRARIES_EXTRA INCLUDE REGEX "^-L.+") list(FILTER CHARM_LDXX_FLAGS EXCLUDE REGEX "^-L.+") list(TRANSFORM CHARM_LIBRARIES_EXTRA REPLACE "^-L" "") list(APPEND CHARM_LIBRARIES ${CHARM_LIBRARIES_EXTRA}) # - Remove the link directory and find the libraries on the system so we can # configure them properly with CMake. That's more robust when switching # between static and shared libs builds. Instead of parsing the list of libs # below we could just hard-code lib names, but that may break with future # Charm versions and could be annoying to debug. However, it would give us # more control over which libs to link. Ideally, Charm++ would provide # exported targets with these libs. set(CHARM_LIB_NAMES ${CHARM_LDXX_FLAGS}) list(FILTER CHARM_LIB_NAMES INCLUDE REGEX "^-l.+") list(FILTER CHARM_LDXX_FLAGS EXCLUDE REGEX "^-l.+") list(TRANSFORM CHARM_LIB_NAMES REPLACE "^-l" "") list(JOIN CHARM_LIB_NAMES ", " CHARM_LIB_NAMES_OUTPUT) message(STATUS "Charm++ lib names: ${CHARM_LIB_NAMES_OUTPUT}") set(CHARM_LIBS_TO_FIND ${CHARM_LIB_NAMES}) # Also find the 'ckmain' library, which isn't included in the list of lib names # because we invoked charmc with '-nomain' list(APPEND CHARM_LIBS_TO_FIND ckmain) foreach(CHARM_LIB_NAME IN LISTS CHARM_LIBS_TO_FIND) # First, find the lib _only_ in CHARM_LIBRARIES. This ensures we prefer the # shared libs in the separate CHARM_ROOT/lib_so directory if requested. find_library(CHARM_LIB_${CHARM_LIB_NAME} NAMES ${CHARM_LIB_NAME} HINTS ${CHARM_LIBRARIES} NO_DEFAULT_PATH ) # Second, fall back to a wider search on the system find_library(CHARM_LIB_${CHARM_LIB_NAME} NAMES ${CHARM_LIB_NAME} HINTS ENV LIBRARY_PATH ) if ("${CHARM_LIB_${CHARM_LIB_NAME}}" STREQUAL "CHARM_LIB_${CHARM_LIB_NAME}-NOTFOUND") message(SEND_ERROR "Could not find Charm++ library '${CHARM_LIB_NAME}'. " "Make sure you have built Charm++ with support for this library, and you \ have loaded the appropriate modules.") endif() list(APPEND CHARM_LIBS ${CHARM_LIB_${CHARM_LIB_NAME}}) # Collect libs for the PUP serialization library so we can link it separately # if we want. The PUP functionality is in `conv-util` in Charm++ 6.10.2 and # in the combined `converse` lib in later versions. if(CHARM_LIB_NAME STREQUAL conv-util OR CHARM_LIB_NAME STREQUAL converse) list(APPEND PUP_LIBS ${CHARM_LIB_${CHARM_LIB_NAME}}) endif() endforeach() # - Extract libraries that are linked as object files (`conv-static` in Charm++ # 6.10.2). set(CHARM_LIB_conv-static ${CHARM_LDXX_FLAGS}) list(FILTER CHARM_LIB_conv-static INCLUDE REGEX "conv-static.o$") list(FILTER CHARM_LDXX_FLAGS EXCLUDE REGEX "conv-static.o$") # Report remaining flags that will be ignored. They have been used to compile # Charm++ but may not be compatible with the compiler that the SpECTRE build is # configured with. They may also include flags that have been chosen differently # for the SpECTRE build, e.g. optimization settings. The ignored flags are # printed out to catch cases where adding one of the flags could be useful. list(JOIN CHARM_CXX_FLAGS " " CHARM_CXX_FLAGS_OUTPUT) message(STATUS "These Charm++ compiler flags will be ignored: " "${CHARM_CXX_FLAGS_OUTPUT}") list(JOIN CHARM_LDXX_FLAGS " " CHARM_LDXX_FLAGS_OUTPUT) message(STATUS "These Charm++ linker flags will be ignored: " "${CHARM_LDXX_FLAGS_OUTPUT}") # Invoke `charmc` to generate definitions for the module-init functions. # # `charmc` writes a tiny temporary `moduleinit$$.C` file with a few functions # that often do nothing, compiles it and links it into every Charm module. We # have `charmc` generate this file and compile it into an object library that # can be linked into executables. The interface for this could be improved on # the Charm++ side. See this upstream issue: # https://github.com/UIUC-PPL/charm/issues/3210 set(CHARM_MODULEINIT_CMD "${CHARM_COMPILER} ${CHARMC_OPTIONS_JOINED} -o unused -save") execute_process( COMMAND bash -c "(${CHARM_MODULEINIT_CMD} || mv moduleinit*.C CharmModuleInit.C) \ && rm moduleinit*" WORKING_DIRECTORY ${CMAKE_BINARY_DIR}/tmp/ ERROR_VARIABLE CHARM_MODULEINIT_ERROR ) configure_file( ${CMAKE_BINARY_DIR}/tmp/CharmModuleInit.C ${CMAKE_BINARY_DIR} ) add_library(CharmModuleInit OBJECT ${CMAKE_BINARY_DIR}/CharmModuleInit.C) # -w -- suppress all warnings because this is charm-generated source set_property(TARGET CharmModuleInit APPEND PROPERTY COMPILE_OPTIONS $<$:-fPIC -w>) # Use the charm building blocks to construct imported targets # - Imported target with all Charm libs add_library(Charmxx::charmxx INTERFACE IMPORTED) target_include_directories(Charmxx::charmxx INTERFACE ${CHARM_INCLUDE_DIRS}) target_link_libraries(Charmxx::charmxx INTERFACE ${CHARM_LIBS}) # - Target just for the PUP serialization library. add_library(Charmxx::pup INTERFACE IMPORTED) target_include_directories(Charmxx::pup INTERFACE ${CHARM_INCLUDE_DIRS}) target_link_libraries(Charmxx::pup INTERFACE ${PUP_LIBS}) # - Target that defines the Charm++ main function. add_library(Charmxx::main INTERFACE IMPORTED) target_link_libraries( Charmxx::main INTERFACE Charmxx::charmxx ${CHARM_LIB_ckmain} ${CHARM_LIB_conv-static} ) # Add a preprocessing definition to indicate that a Charm++ main module is # available. This is used to conditionally compile Charm++-specific code # that assumes it is running in a Charm++ environment, such as the # `Parallel/CharmRegistration.hpp` code. target_compile_definitions( Charmxx::main INTERFACE SPECTRE_CHARM_HAS_MAIN ) # Find and link MPI if necessary # # We currently check if `CHARM_CXX` obviously calls into an MPI compiler wrapper # to turn MPI linking on automatically. In the future it may be necessary to # further extend support for Charm++ wrapping other compiler wrappers and turn # them into CMake targets, as is done with MPI here. if (CHARM_CXX MATCHES ".*mpicxx.*") set(CHARM_USE_MPI ON) endif() if (CHARM_USE_MPI) find_package(MPI REQUIRED) target_link_libraries(Charmxx::charmxx INTERFACE MPI::MPI_CXX) endif() include(FindPackageHandleStandardArgs) find_package_handle_standard_args( Charm # Only these paths are input variables, i.e. they are searched on the system # and can be specified on the command line to select the Charm installation. REQUIRED_VARS CHARM_COMPILER VERSION_VAR CHARM_VERSION ) mark_as_advanced( CHARM_COMPILER CHARM_INCLUDE_DIR CHARM_USE_MPI CHARM_VERSION_MAJOR CHARM_VERSION_MINOR CHARM_VERSION_PATCH CHARM_VERSION )