# ImportedTargetHelpers.cmake - various helper functions for use in find modules. # # Copyright (c) 2022-2023 Dominic Clark # # Redistribution and use is allowed according to the terms of the New BSD license. # For details see the accompanying COPYING-CMAKE-SCRIPTS file. # If the version variable is not yet set, build the source linked to the target, # run it, and set the version variable to the output. Useful for libraries which # do not expose the version information in a header where it can be extracted # with regular expressions, but do provide a function to get the version. # # Usage: # determine_version_from_source( # # The cache variable in which to store the computed version # # The target which the source will link to # # The source code to determine the version # ) function(determine_version_from_source _version_out _target _source) # Return if we already know the version, or the target was not found if(NOT "${${_version_out}}" STREQUAL "" OR NOT TARGET "${_target}") return() endif() # Return with a notice if cross-compiling, since we are unlikely to be able # to run the compiled source if(CMAKE_CROSSCOMPILING) message( "${_target} was found but the version could not be determined automatically.\n" "Set the cache variable `${_version_out}` to the version you have installed." ) return() endif() # Write the source code to a temporary file string(SHA1 _source_hash "${_source}") set(_source_file "${CMAKE_CURRENT_BINARY_DIR}/${_source_hash}.cpp") file(WRITE "${_source_file}" "${_source}") # Build and run the temporary file to get the version # TODO CMake 3.25: Use the new signature for try_run which has a NO_CACHE # option and doesn't require separate file management. try_run( _dvfs_run_result _dvfs_compile_result "${CMAKE_CURRENT_BINARY_DIR}" SOURCES "${_source_file}" LINK_LIBRARIES "${_target}" CXX_STANDARD 20 RUN_OUTPUT_VARIABLE _run_output COMPILE_OUTPUT_VARIABLE _compile_output ) # Clean up the temporary file file(REMOVE "${_source_file}") # Set the version if the run was successful, using a cache variable since # this version check may be relatively expensive. Otherwise, log the error # and inform the user. if(_dvfs_run_result EQUAL "0") set("${_version_out}" "${_run_output}" CACHE INTERNAL "Version of ${_target}") else() message(DEBUG "${_compile_output}") message( "${_target} was found but the version could not be determined automatically.\n" "Set the cache variable `${_version_out}` to the version you have installed." ) endif() endfunction() # Search for a package using config mode. If this fails to find the desired # target, use the specified fallbacks and add the target if they succeed. Set # the variables `prefix_LIBRARY`, `prefix_INCLUDE_DIRS`, and `prefix_VERSION` # if found for the caller to pass to `find_package_handle_standard_args`. # # Usage: # find_package_config_mode_with_fallback( # # The package to search for with config mode # # The target to expect from config mode, or define if not found # LIBRARY_NAMES names... # Possible library names to search for as a fallback # INCLUDE_NAMES names... # Possible header names to search for as a fallback # [PKG_CONFIG ] # The pkg-config name to search for as a fallback # [LIBRARY_HINTS hints...] # Locations to look for libraries # [INCLUDE_HINTS hints...] # Locations to look for headers # [DEPENDS dependencies...] # Dependencies of the target - added to INTERFACE_LINK_LIBRARIES, and will fail if not found # [PREFIX ] # The prefix for result variables - defaults to the package name # ) function(find_package_config_mode_with_fallback _fpcmwf_PACKAGE_NAME _fpcmwf_TARGET_NAME) # Parse remaining arguments set(_options "") set(_one_value_args "PKG_CONFIG" "PREFIX") set(_multi_value_args "LIBRARY_NAMES" "LIBRARY_HINTS" "INCLUDE_NAMES" "INCLUDE_HINTS" "DEPENDS") cmake_parse_arguments(PARSE_ARGV 2 _fpcmwf "${_options}" "${_one_value_args}" "${_multi_value_args}") # Compute result variable names if(NOT DEFINED _fpcmwf_PREFIX) set(_fpcmwf_PREFIX "${_fpcmwf_PACKAGE_NAME}") endif() set(_version_var "${_fpcmwf_PREFIX}_VERSION") set(_library_var "${_fpcmwf_PREFIX}_LIBRARY") set(_include_var "${_fpcmwf_PREFIX}_INCLUDE_DIRS") # Try config mode if possible find_package("${_fpcmwf_PACKAGE_NAME}" CONFIG QUIET) if(TARGET "${_fpcmwf_TARGET_NAME}") # Extract package details from existing target get_target_property("${_library_var}" "${_fpcmwf_TARGET_NAME}" LOCATION) get_target_property("${_include_var}" "${_fpcmwf_TARGET_NAME}" INTERFACE_INCLUDE_DIRECTORIES) if(DEFINED "${_fpcmwf_PACKAGE_NAME}_VERSION") set("${_version_var}" "${${_fpcmwf_PACKAGE_NAME}_VERSION}") endif() else() # Check whether the dependencies exist foreach(_dependency IN LISTS _fpcmwf_DEPENDS) if(NOT TARGET "${_dependency}") return() endif() endforeach() # Attempt to find the package using pkg-config, if we have it and it was requested set(_pkg_config_prefix "${_fpcmwf_PKG_CONFIG}_PKG") if(DEFINED _fpcmwf_PKG_CONFIG) find_package(PkgConfig QUIET) if(PKG_CONFIG_FOUND) pkg_check_modules("${_pkg_config_prefix}" QUIET "${_fpcmwf_PKG_CONFIG}") if("${${_pkg_config_prefix}_FOUND}") set("${_version_var}" "${${_pkg_config_prefix}_VERSION}") endif() endif() endif() # Find the library and headers using the results from pkg-config as a guide find_library("${_library_var}" NAMES ${_fpcmwf_LIBRARY_NAMES} HINTS ${${_pkg_config_prefix}_LIBRARY_DIRS} ${_fpcmwf_LIBRARY_HINTS} ) find_path("${_include_var}" NAMES ${_fpcmwf_INCLUDE_NAMES} HINTS ${${_pkg_config_prefix}_INCLUDE_DIRS} ${_fpcmwf_INCLUDE_HINTS} ) # Create an imported target if we succeeded in finding the package if(${_library_var} AND ${_include_var}) add_library("${_fpcmwf_TARGET_NAME}" UNKNOWN IMPORTED) set_target_properties("${_fpcmwf_TARGET_NAME}" PROPERTIES IMPORTED_LOCATION "${${_library_var}}" INTERFACE_INCLUDE_DIRECTORIES "${${_include_var}}" INTERFACE_LINK_LIBRARIES "${_fpcmwf_DEPENDS}" ) endif() mark_as_advanced("${_library_var}" "${_include_var}") endif() # Return results to caller if(DEFINED "${_version_var}") set("${_version_var}" "${${_version_var}}" PARENT_SCOPE) else() unset("${_version_var}" PARENT_SCOPE) endif() set("${_library_var}" "${${_library_var}}" PARENT_SCOPE) set("${_include_var}" "${${_include_var}}" PARENT_SCOPE) endfunction() # Given a library in vcpkg, find appropriate debug and release versions. If only # one version exists, use it as the release version, and do not set the debug # version. # # Usage: # get_vcpkg_library_configs( # # Variable in which to store the path to the release version of the library # # Variable in which to store the path to the debug version of the library # # Known path to some version of the library # ) function(get_vcpkg_library_configs _release_out _debug_out _library) # We want to do all operations within the vcpkg directory file(RELATIVE_PATH _lib_relative "${VCPKG_INSTALLED_DIR}" "${_library}") # Return early if we're not using vcpkg if(IS_ABSOLUTE _lib_relative OR _lib_relative MATCHES "^\\.\\./") set("${_release_out}" "${_library}" PARENT_SCOPE) return() endif() string(REPLACE "/" ";" _path_bits "${_lib_relative}") # Determine whether we were given the debug or release version list(FIND _path_bits "debug" _debug_index) if(_debug_index EQUAL -1) # We have the release version, so use it set(_release_lib "${_library}") # Try to find a debug version too list(FIND _path_bits "lib" _lib_index) if(_lib_index GREATER_EQUAL 0) list(INSERT _path_bits "${_lib_index}" "debug") list(INSERT _path_bits 0 "${VCPKG_INSTALLED_DIR}") string(REPLACE ";" "/" _debug_lib "${_path_bits}") if(NOT EXISTS "${_debug_lib}") # Debug version does not exist - only use given version unset(_debug_lib) endif() endif() else() # We have the debug version, so try to find a release version too list(REMOVE_AT _path_bits "${_debug_index}") list(INSERT _path_bits 0 "${VCPKG_INSTALLED_DIR}") string(REPLACE ";" "/" _release_lib "${_path_bits}") if(NOT EXISTS "${_release_lib}") # Release version does not exist - only use given version set(_release_lib "${_library}") else() # Release version exists, so use given version as debug set(_debug_lib "${_library}") endif() endif() # Set output variables appropriately if(_debug_lib) set("${_release_out}" "${_release_lib}" PARENT_SCOPE) set("${_debug_out}" "${_debug_lib}" PARENT_SCOPE) else() set("${_release_out}" "${_release_lib}" PARENT_SCOPE) unset("${_debug_out}" PARENT_SCOPE) endif() endfunction()