diff --git a/cmake/cmaize/package_managers/cmake/cmake_package_manager.cmake b/cmake/cmaize/package_managers/cmake/cmake_package_manager.cmake index e0bde015..f24b2cb0 100644 --- a/cmake/cmaize/package_managers/cmake/cmake_package_manager.cmake +++ b/cmake/cmaize/package_managers/cmake/cmake_package_manager.cmake @@ -189,7 +189,7 @@ cpp_class(CMakePackageManager PackageManager) PackageSpecification(GET "${_fi_package_specs}" _fi_pkg_name name) - message("Looking for ${_fi_pkg_name}") + message(STATUS "Looking for ${_fi_pkg_name}") CMakePackageManager(register_dependency "${self}" @@ -199,9 +199,10 @@ cpp_class(CMakePackageManager PackageManager) ) Dependency(find_dependency "${_fi_depend}" _fi_found) + Dependency(get "${_fi_depend}" _fi_dep_name name) if( NOT "${_fi_found}" OR - "${${_fi_pkg_name}_DIR}" STREQUAL "${_fi_pkg_name}_DIR-NOTFOUND" + "${${_fi_dep_name}_DIR}" STREQUAL "${_fi_dep_name}_DIR-NOTFOUND" ) cpp_return("") endif() @@ -209,14 +210,14 @@ cpp_class(CMakePackageManager PackageManager) # Make sure that FIND_TARGET is populated with a name if("${_fi_FIND_TARGET}" STREQUAL "") if("${_fi_BUILD_TARGET}" STREQUAL "") - set(_fi_FIND_TARGET "${_fi_pkg_name}") + set(_fi_FIND_TARGET "${_fi_dep_name}") else() set(_fi_FIND_TARGET "${_fi_BUILD_TARGET}") endif() endif() # Create an installed target - set(_fi_depend_root_path "${${_fi_pkg_name}_DIR}") + set(_fi_depend_root_path "${${_fi_dep_name}_DIR}") InstalledTarget(ctor _fi_tgt "${_fi_FIND_TARGET}" "${_fi_depend_root_path}" ) diff --git a/cmake/cmaize/package_managers/cmake/impl_/generate_package_config.cmake b/cmake/cmaize/package_managers/cmake/impl_/generate_package_config.cmake index 3dfe4e59..024aa872 100644 --- a/cmake/cmaize/package_managers/cmake/impl_/generate_package_config.cmake +++ b/cmake/cmaize/package_managers/cmake/impl_/generate_package_config.cmake @@ -21,7 +21,9 @@ macro( __gpc_pkg_name ) - + message(VERBOSE "Generating package config for ${__gpc_pkg_name}") + list(APPEND CMAKE_MESSAGE_INDENT " ") + set(__gpc_targets ${ARGN}) _cmaize_generated_by_cmaize(__gpc_file_contents) @@ -38,6 +40,11 @@ macro( CMaizeProject(get_target "${__gpc_proj}" __gpc_tgt_obj "${__gpc_targets_i}" ) + + CMaizeTarget(target "${__gpc_tgt_obj}" __gpc_tgt_name) + message(VERBOSE "Processing target: \"${__gpc_tgt_name}\"") + list(APPEND CMAKE_MESSAGE_INDENT " ") + BuildTarget(GET "${__gpc_tgt_obj}" __gpc_tgt_deps depends) list(LENGTH __gpc_tgt_deps __gpc_tgt_deps_len) @@ -56,7 +63,7 @@ macro( endif() foreach(__gpc_tgt_deps_i ${__gpc_tgt_deps}) - message(DEBUG "Processing dependency: ${__gpc_tgt_deps_i}") + message(VERBOSE "Processing dependency: \"${__gpc_tgt_deps_i}\"") # Skip dependency processing if this is not a target managed # by the CMaize project @@ -68,7 +75,7 @@ macro( ) if(NOT __gpc_is_cmaize_tgt) message( - DEBUG + VERBOSE "Skipping ${__gpc_tgt_deps_i}. It is not target " "managed by CMaize." ) @@ -80,7 +87,7 @@ macro( cpp_contains(_gpc_dep_is_proj_tgt "${__gpc_tgt_deps_i}" "${__gpc_targets}") if(_gpc_dep_is_proj_tgt) message( - DEBUG + VERBOSE "Skipping ${__gpc_tgt_deps_i}. It is a target defined " "by this project." ) @@ -90,9 +97,26 @@ macro( # Check if it is a dependency to be built and redirect the # installation to the ``external`` directory CMaizeProject(get_target - "${__gpc_proj}" __gpc_tgt_deps_i_obj "${__gpc_tgt_deps_i}" + "${__gpc_proj}" __gpc_tgt_deps_i_obj "${__gpc_tgt_deps_i}" ALL ) + # Skip dependencies that are just empty interface libraries. This + # is the type for a dummy target for optional dependencies that + # were not enabled in ``cmaize_find*_optional_dependency()`` + CMaizeTarget(target "${__gpc_tgt_deps_i_obj}" __gpc_tgt_deps_i_name) + get_target_property(__gpc_tgt_deps_i_type "${__gpc_tgt_deps_i_name}" TYPE) + CMaizeTarget(GET "${__gpc_tgt_deps_i_obj}" _gpc_tgt_depts_i_install_path install_path) + if(("${__gpc_tgt_deps_i_type}" STREQUAL "INTERFACE_LIBRARY") AND ("${_gpc_tgt_depts_i_install_path}" STREQUAL "")) + message( + VERBOSE + "Skipping ${__gpc_tgt_deps_i}. It is an empty interface " + "library target." + ) + continue() + endif() + + list(APPEND CMAKE_MESSAGE_INDENT " ") + CMakePackageManager(GET "${self}" __gpc_dependencies dependencies) cpp_map(GET "${__gpc_dependencies}" __gpc_dep_obj "${__gpc_tgt_deps_i}") @@ -114,7 +138,11 @@ macro( "find_dependency(${__gpc_tgt_deps_i} COMPONENTS ${__gpc_dep_build_tgt_name})\n" ) endif() + + list(POP_BACK CMAKE_MESSAGE_INDENT) endforeach() + + list(POP_BACK CMAKE_MESSAGE_INDENT) endforeach() # Add a space between the dependency imports and component imports @@ -167,6 +195,7 @@ macro( "${CMAKE_CURRENT_BINARY_DIR}/${__gpc_pkg_name}Config.cmake.in" ) + list(POP_BACK CMAKE_MESSAGE_INDENT) cpp_return("${__gpc_output_file}") endmacro() diff --git a/cmake/cmaize/package_managers/cmake/impl_/install_package.cmake b/cmake/cmaize/package_managers/cmake/impl_/install_package.cmake index c7100e3c..8c5cea52 100644 --- a/cmake/cmaize/package_managers/cmake/impl_/install_package.cmake +++ b/cmake/cmaize/package_managers/cmake/impl_/install_package.cmake @@ -212,7 +212,10 @@ function(_cpm_install_package_impl self _ip_pkg_name) # Actually append the aggregated paths to the current target's # INSTALL_RPATH - CMaizeTarget(get_property "${_ip_tgt_obj_i}" _install_rpath INSTALL_RPATH) + CMaizeTarget(has_property "${_ip_tgt_obj_i}" _has_install_rpath INSTALL_RPATH) + if(_has_install_rpath) + CMaizeTarget(get_property "${_ip_tgt_obj_i}" _install_rpath INSTALL_RPATH) + endif() list(APPEND _install_rpath ${_dep_install_path}) list(APPEND _install_rpath ${_dep_install_rpath}) CMaizeTarget(set_property "${_ip_tgt_obj_i}" INSTALL_RPATH "${_install_rpath}") diff --git a/cmake/cmaize/project/cmaize_project.cmake b/cmake/cmaize/project/cmaize_project.cmake index edbb3e19..a165570a 100644 --- a/cmake/cmaize/project/cmaize_project.cmake +++ b/cmake/cmaize/project/cmaize_project.cmake @@ -351,7 +351,7 @@ cpp_class(CMaizeProject) # :param _ct_target_name: Identifying name for the target. This can match # name of either the CMake target or CMaizeTarget # object, but is required to do match them. - # :type _ct_target_name: desc + # :type _ct_target_name: desc or target # :param **kwargs: Additional keyword arguments may be necessary. # # :Keyword Arguments: @@ -360,9 +360,6 @@ cpp_class(CMaizeProject) # * **ALL** (*bool*) -- # Flag to indicate that both the build and installed targets should # be checked. - # * **NAME** (*desc* or *target*) -- - # Identifying name for a target contained in the current Cmaize - # project. This keyword argument is **required**. # # :returns: CMaizeTarget found (TRUE) or not (FALSE). # :rtype: bool diff --git a/cmake/cmaize/targets/cxx_target.cmake b/cmake/cmaize/targets/cxx_target.cmake index af7e75aa..d00ef25b 100644 --- a/cmake/cmaize/targets/cxx_target.cmake +++ b/cmake/cmaize/targets/cxx_target.cmake @@ -149,9 +149,11 @@ cpp_class(CXXTarget BuildTarget) CXXTarget(_access_level "${self}" _scf_access_level) if(NOT "${_scf_cxx_std}" STREQUAL "") + message(VERBOSE "Setting CXX standard to \"cxx_std_${_scf_cxx_std}\"") target_compile_features( "${_scf_tgt_name}" - "${_scf_access_level}" "cxx_std_${_scf_cxx_std}" + "${_scf_access_level}" + "cxx_std_${_scf_cxx_std}" ) endif() endfunction() @@ -185,7 +187,9 @@ cpp_class(CXXTarget BuildTarget) # Set up public includes CXXTarget(_access_level "${self}" _sid_access_level) + message(VERBOSE "Setting build interface include directories to") foreach(_sid_inc_dir_i ${_sid_inc_dirs}) + message(VERBOSE " ${_sid_inc_dir_i}") target_include_directories( "${_sid_tgt_name}" "${_sid_access_level}" @@ -194,18 +198,23 @@ cpp_class(CXXTarget BuildTarget) endforeach() # Set up installation includes + message(VERBOSE "Setting install interface include directories to") + message(VERBOSE " include") target_include_directories( "${_sid_tgt_name}" "${_sid_access_level}" - $ + $ ) if(NOT "${_sid_access_level}" STREQUAL "INTERFACE") + message(VERBOSE "Setting private header include directories to") + message(VERBOSE " ${_sid_src_dir}") + # Set up private header includes target_include_directories( "${_sid_tgt_name}" PRIVATE - "${_sid_src_dir}" + "${_sid_src_dir}" ) endif() @@ -228,9 +237,10 @@ cpp_class(CXXTarget BuildTarget) # underlying target name cmaize_replace_project_targets(_sll_deps ${_sll_deps}) + message(VERBOSE "Registering link library dependencies") CXXTarget(_access_level "${self}" _sll_access_level) foreach(_sll_dep_i ${_sll_deps}) - message(DEBUG "Registering ${_sll_dep_i} as a dependency of ${_sll_name}.") + message(VERBOSE " ${_sll_dep_i}") target_link_libraries( "${_sll_name}" "${_sll_access_level}" "${_sll_dep_i}" ) @@ -251,6 +261,7 @@ cpp_class(CXXTarget BuildTarget) CXXTarget(GET "${self}" _sph_inc_files includes) # TODO: This may hang if '_sph_inc_files' is empty + message(VERBOSE "Setting PUBLIC_HEADER to \"${_sph_inc_files}\"") CXXTarget(set_property "${self}" PUBLIC_HEADER "${_sph_inc_files}") endfunction() @@ -269,6 +280,7 @@ cpp_class(CXXTarget BuildTarget) CXXTarget(GET "${self}" _ss_src_files sources) # Sources for a CXX target should be private + message(VERBOSE "Setting sources to \"${_ss_src_files}\"") target_sources( "${_ss_tgt_name}" PRIVATE diff --git a/cmake/cmaize/targets/installed_target.cmake b/cmake/cmaize/targets/installed_target.cmake index 688f5c26..806b6406 100644 --- a/cmake/cmaize/targets/installed_target.cmake +++ b/cmake/cmaize/targets/installed_target.cmake @@ -56,6 +56,7 @@ cpp_class(InstalledTarget CMaizeTarget) endif() InstalledTarget(SET "${self}" root_path "${_ctor_root}") + CMaizeTarget(SET "${self}" install_path "${_ctor_root}") endfunction() diff --git a/cmake/cmaize/user_api/add_library.cmake b/cmake/cmaize/user_api/add_library.cmake index cb55790c..21bce626 100644 --- a/cmake/cmaize/user_api/add_library.cmake +++ b/cmake/cmaize/user_api/add_library.cmake @@ -35,13 +35,15 @@ include(cmaize/utilities/replace_project_targets) #]] function(cmaize_add_library _cal_tgt_name) - message(VERBOSE "Registering library target: ${_cal_tgt_name}") + message(VERBOSE "Creating library target: ${_cal_tgt_name}") + list(APPEND CMAKE_MESSAGE_INDENT " ") set(_cal_one_value_args LANGUAGE) cmake_parse_arguments(_cal "" "${_cal_one_value_args}" "" ${ARGN}) # Default to CXX if no language is given if("${_cal_LANGUAGE}" STREQUAL "") + message(VERBOSE "No language set, enabling \"CXX\"") set(_cal_LANGUAGE "CXX") endif() @@ -82,24 +84,32 @@ function(cmaize_add_library _cal_tgt_name) INSTALL_PATH "${_cal_install_path}" ) - # Loop over each dependency. This is currently done by looking - # up the dependencies by name from the CMaizeProject, but later + # Loop over each dependency. This is currenjtly done by looking + # up the dependencies by name from the CMaizeProect, but later # we should make each CMaize target hold references to its # dependencies cpp_get_global(_cal_top_proj CMAIZE_TOP_PROJECT) BuildTarget(GET "${_cal_tgt_obj}" _dep_list depends) + + message(VERBOSE "Building INSTALL_RPATH") + list(APPEND CMAKE_MESSAGE_INDENT " ") + foreach(dependency ${_dep_list}) # Fetch the dependency's target object CMaizeProject(get_target - "${_cal_top_proj}" _dep_tgt_obj "${dependency}" + "${_cal_top_proj}" _dep_tgt_obj "${dependency}" ALL ) # Skip the dependency if it is not managed by CMaize, since # those won't have install path information if("${_dep_tgt_obj}" STREQUAL "") + message(VERBOSE "Skipping \"${dependency}\" not managed by CMaize.") continue() endif() + message(VERBOSE "From ${dependency}") + list(APPEND CMAKE_MESSAGE_INDENT " ") + # Get the install path for the dependency CMaizeTarget(GET "${_dep_tgt_obj}" _dep_install_path install_path) @@ -134,13 +144,30 @@ function(cmaize_add_library _cal_tgt_name) get_property "${_cal_tgt_obj}" _install_rpath INSTALL_RPATH ) endif() - list(APPEND _install_rpath ${_dep_install_path}) - list(APPEND _install_rpath ${_dep_install_rpath}) - CMaizeTarget( - set_property "${_cal_tgt_obj}" INSTALL_RPATH "${_install_rpath}" + + if(NOT "${_dep_install_path}" STREQUAL "") + message(VERBOSE "Adding \"${_dep_install_path}\"") + list(APPEND _install_rpath ${_dep_install_path}) + else() + message(VERBOSE "No install path to append") + endif() + + if(NOT "${_dep_install_rpath}" STREQUAL "") + message(VERBOSE "Adding \"${_dep_install_rpath}\"") + list(APPEND _install_rpath ${_dep_install_rpath}) + else() + message(VERBOSE "No install RPATH to append") + endif() + + CMaizeTarget(set_property + "${_cal_tgt_obj}" INSTALL_RPATH "${_install_rpath}" ) + + list(POP_BACK CMAKE_MESSAGE_INDENT) endforeach() + list(POP_BACK CMAKE_MESSAGE_INDENT) + list(POP_BACK CMAKE_MESSAGE_INDENT) endfunction() #[[[ @@ -192,8 +219,10 @@ function(cmaize_add_cxx_library _cacl_tgt_obj _cacl_tgt_name) # is a library or interface library list(LENGTH _cacl_source_files _cacl_source_files_n) if("${_cacl_source_files_n}" GREATER 0) + message(VERBOSE "Creating \"${_cacl_tgt_name}\" as a CXXLibrary") CXXLibrary(CTOR _cacl_lib_obj "${_cacl_tgt_name}") else() + message(VERBOSE "Creating \"${_cacl_tgt_name}\" as a CXXInterfaceLibrary") CXXInterfaceLibrary(CTOR _cacl_lib_obj "${_cacl_tgt_name}") endif() diff --git a/cmake/cmaize/user_api/dependencies/find_dependency.cmake b/cmake/cmaize/user_api/dependencies/find_dependency.cmake index 34218170..d136f2e8 100644 --- a/cmake/cmaize/user_api/dependencies/find_dependency.cmake +++ b/cmake/cmaize/user_api/dependencies/find_dependency.cmake @@ -31,8 +31,10 @@ include(cmaize/user_api/dependencies/impl_/find_dependency) # :param **kwargs: Keyword arguments describing the behavior of the search. See # the documentation for _fob_parse_arguments for a complete list. # -# :raises UNKNOWN_PM: When ``pm_name`` does not correspond to a known package -# manager. Strong throw guarantee. +# :raises PACKAGE_NOT_FOUND: CMaize was unable to locate the package specified. +# Strong throw guarantee. +# :raises UNKNOWN_PM: ``PACKAGE_MANAGER`` does not correspond to a known +# package manager. Strong throw guarantee. #]] function(cmaize_find_dependency _cfd_name) diff --git a/cmake/cmaize/user_api/dependencies/find_optional_dependency.cmake b/cmake/cmaize/user_api/dependencies/find_optional_dependency.cmake index 56af4e1e..7caa7669 100644 --- a/cmake/cmaize/user_api/dependencies/find_optional_dependency.cmake +++ b/cmake/cmaize/user_api/dependencies/find_optional_dependency.cmake @@ -46,6 +46,11 @@ include(cmaize/user_api/dependencies/find_dependency) # :type flag: desc # :param kwargs: Keyword arguments to forward to ``cmaize_find_dependency``. # See the documentation for ``cmaize_find_dependency`` for the full list. +# +# :raises PACKAGE_NOT_FOUND: CMaize was unable to locate the package specified. +# Strong throw guarantee. +# :raises UNKNOWN_PM: ``PACKAGE_MANAGER`` does not correspond to a known +# package manager. Strong throw guarantee. #]] function(cmaize_find_optional_dependency _cfod_name _cfod_flag) @@ -54,11 +59,107 @@ function(cmaize_find_optional_dependency _cfod_name _cfod_flag) _check_optional_flag("${_cfod_flag}") + # Get the top CMaize project so we can look up the target that + # was just created from cmaize_find_or_build_dependency() + cpp_get_global(_cfod_top_proj_obj CMAIZE_TOP_PROJECT) + + set(_cfod_tgt_obj "") if("${${_cfod_flag}}") + message(VERBOSE "\"${_cfod_name}\" enabled with ${_cfod_flag} = ${${_cfod_flag}}") + + # Find or build the dependency target, adding it to the cmaize project. cmaize_find_dependency("${_cfod_name}" ${ARGN}) - target_compile_definitions("${_cfod_name}" INTERFACE "${_cfod_flag}") - elseif(NOT TARGET "${_cfod_name}") - add_library("${_cfod_name}" INTERFACE) + + # cmaize_find_dependency() raises PACKAGE_NOT_FOUND if dependency is + # not found, so no further checking if the target exists is needed + + # Get the target that was found + CMaizeProject(get_target + "${_cfod_top_proj_obj}" + _cfod_tgt_obj + "${_cfod_name}" + INSTALLED + ) + + # CMaizeProject(get_target should never fail to find the target at + # this point. + # NOTE: This should be a cpp_assert call, but currently an empty string + # does not evaluate to false in cpp_assert, so an alternative was used. + if("${_cfod_tgt_obj}" STREQUAL "") + cpp_raise(TargetNotFound + "Target was not found in the project, but it should have been \ + added by cmaize_find_or_build_dependency()!" + ) + endif() + + # Add the flag to the compile definitions of the dependency target. + # NOTE: It seems that 'target_compile_definitions' will not override + # an existing definition, instead prepending the new definition: + # https://gitlab.kitware.com/cmake/cmake/-/issues/24099. + # To override an existing compile definition here, we would need + # to check the INTERFACE_COMPILE_FEATURES target property and + # replace the corresponding value if it exists. + CMaizeTarget(target "${_cfod_tgt_obj}" _cfod_tgt_name) + target_compile_definitions( + "${_cfod_tgt_name}" INTERFACE "${_cfod_flag}" + ) + else() + message(VERBOSE "\"${_cfod_name}\" disabled with ${_cfod_flag} = ${${_cfod_flag}}") + + # Even if the flag was not set, we still need a dummy target when the + # dependency is not included. This target should be set up such that + # it is effectively a no-op when used during the build process later + # on, with its only purpose being to propagate the compile definition + # matching the fobod flag that toggles the target. + # + # This target should be defined on the CMaize side as well as CMake side: + # CMaize side -> Create a CMaizeTarget object containing the name of the + # CMake target. + # CMake side -> add_library("" INTERFACE) + + # TODO: Currently entries in the package manager are not being + # considered, nor is the dummy package being registered with it. Since + # that is typically where many of the kwargs are parsed, it is + # difficult to get the NAME value or the build or find target names + # without creating a Dependency object here. + + # TODO: What happens when a package is disabled for a dependency, but + # enabled in the top level package? However unlikely, it is a + # state that should be handled here. + + # Use the ALL flag to check both installed and built targets in the + # project, since an existing target may be stored under either. + CMaizeProject(check_target + "${_cfod_top_proj_obj}" + _cfod_tgt_found + "${_cfod_name}" + ALL + ) + + # If CMaize finds that the target is already defined for the project, + # just use that and move on. Someone has done our work for us already. + # + # Otherwise, we need to create it here. + if(NOT "${_cfod_tgt_found}") + # Wrap the target in a CMaizeTarget + CMaizeTarget(ctor + _cfod_tgt_obj + "${_cfod_name}" + ) + + # Add the dummy CMaize target to the project + # TODO: Should this be added as an INSTALLED target? + CMaizeProject(add_target + "${_cfod_top_proj_obj}" + "${_cfod_name}" + "${_cfod_tgt_obj}" + ) + + # Create the dummy CMake target if it doesn't exist yet + if(NOT TARGET "${_cfod_name}") + add_library("${_cfod_name}" INTERFACE) + endif() + endif() endif() endfunction() diff --git a/cmake/cmaize/user_api/dependencies/find_or_build_dependency.cmake b/cmake/cmaize/user_api/dependencies/find_or_build_dependency.cmake index c6b3ceab..9eb2abd7 100644 --- a/cmake/cmaize/user_api/dependencies/find_or_build_dependency.cmake +++ b/cmake/cmaize/user_api/dependencies/find_or_build_dependency.cmake @@ -31,6 +31,9 @@ include(cmaize/user_api/dependencies/impl_/find_dependency) # :type _fobdc_name: desc # :param **kwargs: Additional keyword arguments may be necessary. # See _fob_parse_arguemnts for up to date list. +# +# :raises UNKNOWN_PM: ``PACKAGE_MANAGER`` does not correspond to a known +# package manager. Strong throw guarantee. #]] function(cmaize_find_or_build_dependency _fobd_name) diff --git a/cmake/cmaize/user_api/dependencies/find_or_build_optional_dependency.cmake b/cmake/cmaize/user_api/dependencies/find_or_build_optional_dependency.cmake index e20e492f..81d5ffd9 100644 --- a/cmake/cmaize/user_api/dependencies/find_or_build_optional_dependency.cmake +++ b/cmake/cmaize/user_api/dependencies/find_or_build_optional_dependency.cmake @@ -1,4 +1,4 @@ -# Copyright 2024 CMakePP +# Copyright 2024, 2025 CMakePP # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -16,14 +16,29 @@ include_guard() include(cmaize/user_api/dependencies/impl_/check_optional_flag) include(cmaize/user_api/dependencies/find_or_build_dependency) +include(cmaize/targets/cmaize_target) #[[[ -# Wraps the process of finding, or building, an optional dependency. +# Wraps the process of finding or building an optional dependency. # # This method is largely the same as cmaize_find_optional_dependency, but -# instead wraps a call to ``cmaize_find_or_build_dependency``. +# instead wraps a call to ``cmaize_find_or_build_dependency``. However, when +# the provided option is enabled, the optional flag is also added as a compile +# definition on the dependency target. This propagates the optional flag as a +# compile definition to any targets that depend on this target. # -# :param name: The name of the dependency. +# When the option is disabled, an empty interface library target is created +# instead, essentially a no-op when added as a link target. This ensures that +# a target always exists and the CMaize target identifier can be used +# regardless of whether the optional dependency is enabled or not. +# +# .. note:: +# +# The compile definition for the optional flag that is added to the target +# will NOT include the value assigned to the optional flag. +# +# :param name: A name for the dependency for CMaize. This is assumed to also +# be the name of the package to be found unless ``NAME`` is provided. # :type name: desc # :param flag: The variable to use as a flag. Used to name the compile # definition. @@ -31,22 +46,115 @@ include(cmaize/user_api/dependencies/find_or_build_dependency) # :param kwargs: Keyword arguments to forward to # ``cmaize_find_or_build_dependency``. See the documentation for # ``cmaize_find_or_build_dependency`` for the full list. +# +# :raises UNKNOWN_PM: ``PACKAGE_MANAGER`` does not correspond to a known +# package manager. Strong throw guarantee +# :raises TargetNotFound: Target does not exist on the CMaize project after +# ``cmaize_find_or_build_dependency()``. ??? throw guarantee. #]] function(cmaize_find_or_build_optional_dependency _cfobod_name _cfobod_flag) _check_optional_flag("${_cfobod_flag}") + # Get the top CMaize project so we can look up the target that + # was just created from cmaize_find_or_build_dependency() + cpp_get_global(_cfobod_top_proj_obj CMAIZE_TOP_PROJECT) + + set(_cfobod_tgt_obj "") if("${${_cfobod_flag}}") + message(VERBOSE "\"${_cfobod_name}\" enabled with ${_cfobod_flag} = ${${_cfobod_flag}}") + # Find or build the dependency target, adding it to the cmaize project cmaize_find_or_build_dependency("${_cfobod_name}" ${ARGN}) + + # Use the ALL flag to check both installed and built targets in the + # project, since the target may be stored under either + CMaizeProject(get_target + "${_cfobod_top_proj_obj}" + _cfobod_tgt_obj + "${_cfobod_name}" + ALL + ) + + # CMaizeProject(get_target should never fail to find this target, + # it will be added by cmaize_find_or_build_dependency() in one form + # or another. + # NOTE: This could be a cpp_assert call, but currently an empty string + # does not evaluate to false in cpp_assert, so an alternative was used. + if("${_cfobod_tgt_obj}" STREQUAL "") + cpp_raise(TargetNotFound + "Target was not found in the project, but it should have been \ + added by cmaize_find_or_build_dependency()!" + ) + endif() + + # Add the flag to the compile definitions of the dependency target. + # NOTE: It seems that 'target_compile_definitions' will not override + # an existing definition, instead prepending the new definition: + # https://gitlab.kitware.com/cmake/cmake/-/issues/24099. + # To override an existing compile definition here, we would need + # to check the INTERFACE_COMPILE_FEATURES target property and + # replace the corresponding value if it exists. + CMaizeTarget(target "${_cfobod_tgt_obj}" _cfobod_tgt_name) target_compile_definitions( - "${_cfobod_name}" INTERFACE "${_cfobod_flag}" + "${_cfobod_tgt_name}" INTERFACE "${_cfobod_flag}" + ) + else() + message(VERBOSE "\"${_cfobod_name}\" disabled with ${_cfobod_flag} = ${${_cfobod_flag}}") + + # Even if the flag was not set, we still need a dummy target when the + # dependency is not included. This target should be set up such that + # it is effectively a no-op when used during the build process later + # on, with its only purpose being to propagate the compile definition + # matching the fobod flag that toggles the target. + # + # This target should be defined on the CMaize side as well as CMake side: + # CMaize side -> Create a CMaizeTarget object containing the name of the + # CMake target. + # CMake side -> add_library("" INTERFACE) + + # TODO: Currently entries in the package manager are not being + # considered, nor is the dummy package being registered with it. Since + # that is typically where many of the kwargs are parsed, it is + # difficult to get the NAME value or the build or find target names + # without creating a Dependency object here. + + # TODO: What happens when a package is disabled for a dependency, but + # enabled in the top level package? However unlikely, it is a state + # that should be handled here. + + # Use the ALL flag to check both installed and built targets in the + # project, since the target may be stored under either. + CMaizeProject(check_target + "${_cfobod_top_proj_obj}" + _cfobod_tgt_found + "${_cfobod_name}" + ALL ) - elseif(NOT TARGET "${_cfobod_name}") + # If CMaize finds that the target is already defined for the project, + # just use that and move on. Someone has done our work for us already. + # + # Otherwise, we need to create it here. + if(NOT "${_cfobod_tgt_found}") + # Wrap the target in a CMaizeTarget + CMaizeTarget(ctor + _cfobod_tgt_obj + "${_cfobod_name}" + ) - add_library("${_cfobod_name}" INTERFACE) + # Add the dummy CMaize target to the project + CMaizeProject(add_target + "${_cfobod_top_proj_obj}" + "${_cfobod_name}" + "${_cfobod_tgt_obj}" + ) + # Create the dummy CMake target if it doesn't exist yet + if(NOT TARGET "${_cfobod_name}") + add_library("${_cfobod_name}" INTERFACE) + endif() + endif() endif() endfunction() diff --git a/cmake/cmaize/user_api/dependencies/impl_/find_dependency.cmake b/cmake/cmaize/user_api/dependencies/impl_/find_dependency.cmake index 11cd5e57..52327d2f 100644 --- a/cmake/cmaize/user_api/dependencies/impl_/find_dependency.cmake +++ b/cmake/cmaize/user_api/dependencies/impl_/find_dependency.cmake @@ -19,8 +19,9 @@ include(cmaize/user_api/dependencies/impl_/parse_arguments) include(cmaize/package_managers/package_manager) #[[[ -# Factorization from cmaize_find_dependency and cmaize_find_or_build_dependency. +# Checks if dependency is already installed. # +# Factorization from cmaize_find_dependency and cmaize_find_or_build_dependency. # The first part of both cmaize_find_dependency and # cmaize_find_or_build_dependency is the same. This function factors that common # code out into one function. @@ -40,9 +41,8 @@ include(cmaize/package_managers/package_manager) # :param **kwargs: The arguments to forward to _fob_parse_arguments. See # _fob_parse_arguments documentation for more details. # -# :raises UNKNOWN_PM: When ``pm_name`` does not correspond to a known package -# manager. Strong throw guarantee. -# +# :raises UNKNOWN_PM: When ``PACKAGE_MANAGER`` does not correspond to a known +# package manager. Strong throw guarantee. #]] function(_cmaize_find_dependency _fd_tgt _fd_pm _fd_package_specs _fd_project _fd_name) @@ -64,7 +64,6 @@ function(_cmaize_find_dependency _fd_tgt _fd_pm _fd_package_specs _fd_project _f CMaizeProject(add_target "${_fd_project}" "${_fd_name}" "${_fd_tgt_tmp}" INSTALLED ) - CMaizeProject(check_target "${_fd_project}" was_found "${_fd_name}" INSTALLED) endif() set("${_fd_tgt}" "${_fd_tgt_tmp}" PARENT_SCOPE) diff --git a/cmake/cmaize/user_api/dependencies/impl_/get_package_manager.cmake b/cmake/cmaize/user_api/dependencies/impl_/get_package_manager.cmake index 9bd126e4..9280abe7 100644 --- a/cmake/cmaize/user_api/dependencies/impl_/get_package_manager.cmake +++ b/cmake/cmaize/user_api/dependencies/impl_/get_package_manager.cmake @@ -33,7 +33,6 @@ include(cmaize/project/cmaize_project) # # :raises UNKNOWN_PM: When ``pm_name`` does not correspond to a known package # manager. Strong throw guarantee. -# #]] function(_fob_get_package_manager _gpm_pm _gpm_project _gpm_pm_name) diff --git a/tests/cmaize/user_api/dependencies/test_find_optional_dependency.cmake b/tests/cmaize/user_api/dependencies/test_find_optional_dependency.cmake index 251787ab..9a632a8f 100644 --- a/tests/cmaize/user_api/dependencies/test_find_optional_dependency.cmake +++ b/tests/cmaize/user_api/dependencies/test_find_optional_dependency.cmake @@ -34,6 +34,10 @@ function("${test_find_optional_dependency}") ct_add_section(NAME "is_disabled_empty") function("${is_disabled_empty}") + # Create a project + set(proj_name "is_disabled_empty") + project("${proj_name}") + cmaize_project("${proj_name}") ct_assert_target_does_not_exist(not_real_empty) @@ -48,6 +52,10 @@ function("${test_find_optional_dependency}") ct_add_section(NAME "is_disabled_false") function("${is_disabled_false}") + # Create a project + set(proj_name "is_disabled_false") + project("${proj_name}") + cmaize_project("${proj_name}") ct_assert_target_does_not_exist(not_real_false) diff --git a/tests/cmaize/user_api/dependencies/test_find_or_build_optional_dependency.cmake b/tests/cmaize/user_api/dependencies/test_find_or_build_optional_dependency.cmake index da09834e..b85693ba 100644 --- a/tests/cmaize/user_api/dependencies/test_find_or_build_optional_dependency.cmake +++ b/tests/cmaize/user_api/dependencies/test_find_or_build_optional_dependency.cmake @@ -34,6 +34,10 @@ function("${test_find_or_build_optional_dependency}") ct_add_section(NAME "is_disabled_empty") function("${is_disabled_empty}") + # Create a project + set(proj_name "is_disabled_empty") + project("${proj_name}") + cmaize_project("${proj_name}") ct_assert_target_does_not_exist(not_real_empty) @@ -48,6 +52,10 @@ function("${test_find_or_build_optional_dependency}") ct_add_section(NAME "is_disabled_false") function("${is_disabled_false}") + # Create a project + set(proj_name "is_disabled_empty") + project("${proj_name}") + cmaize_project("${proj_name}") ct_assert_target_does_not_exist(not_real_false)