Revision control
++ # Copyright (C) 2025 The Qt Company Ltd.
++ # SPDX-License-Identifier: BSD-3-Clause
++
++ function(qt_internal_generate_module_map target)
++ if(NOT QT_FEATURE_clang_module_maps)
++ return()
++ endif()
++
++ get_target_property(module_has_headers ${target} _qt_module_has_headers)
++ if(NOT module_has_headers)
++ # Skip NO_MODULE_HEADERS or NO_SYNC_QT modules
++ return()
++ endif()
++
++ get_target_property(is_private_module ${target} _qt_is_private_module)
++ if(is_private_module)
++ return()
++ endif()
++
++ get_target_property(is_internal_module ${target} _qt_is_internal_module)
++ if(is_internal_module)
++ return()
++ endif()
++
++ get_target_property(is_public_module ${target} _qt_is_public_module)
++ if(NOT is_public_module)
++ return()
++ endif()
++
++ qt_internal_collect_module_headers(module_headers ${target})
++
++ qt_internal_module_info(module "${target}")
++
++ set(found_non_utility_header FALSE)
++ foreach(header IN LISTS module_headers_public)
++ get_source_file_property(utility_header "${header}" _qt_utility_header)
++ get_filename_component(header_name ${header} NAME)
++ if("${header_name}" STREQUAL "qt${module_lower}global.h")
++ # Treat as utility header
++ elseif(NOT utility_header)
++ set(found_non_utility_header TRUE)
++ break()
++ endif()
++ endforeach()
++
++ if(NOT found_non_utility_header)
++ return()
++ endif()
++
++ string(JOIN "\n" module_map_content
++ "$<$<BOOL:$<TARGET_PROPERTY:${target},FRAMEWORK>>:framework >module ${module} {"
++ " requires cplusplus17"
++ "\n"
++ )
++
++ string(APPEND module_map_content "@SYNCQT_GENERATED_HEADER_LIST@")
++
++ string(APPEND module_map_content "\n")
++
++ qt_internal_collect_direct_target_dependencies(${target} target_deps)
++ foreach(dep IN LISTS target_deps)
++ if(dep MATCHES "^Qt::")
++ string(REGEX REPLACE "Qt" "${QT_CMAKE_EXPORT_NAMESPACE}" dep ${dep})
++ elseif(NOT dep MATCHES "^${QT_CMAKE_EXPORT_NAMESPACE}::")
++ continue()
++ endif()
++ if(dep MATCHES "Private$")
++ continue()
++ endif()
++ if(NOT TARGET ${dep})
++ continue()
++ endif()
++
++ get_target_property(is_private_module ${dep} _qt_is_private_module)
++ if(is_private_module)
++ continue()
++ endif()
++ get_target_property(is_internal_module ${dep} _qt_is_internal_module)
++ if(is_internal_module OR dep MATCHES ".*Platform")
++ continue()
++ endif()
++
++ # Not every Qt target we link is a Qt module. Static builds in particular
++ # may pull in plugins, e.g. via qt_import_qml_plugins(), and those don't
++ # have module maps of their own to use.
++ get_target_property(dep_interface_name ${dep} _qt_module_interface_name)
++ if(NOT dep_interface_name)
++ continue()
++ endif()
++
++ qt_internal_module_info(dep_module "${dep}")
++ string(APPEND module_map_content " use ${dep_module}\n")
++ endforeach()
++
++ # Auto link to the required library/framework. Unfortunately the module map
++ # can only have a single link line, so for static builds we're not able to
++ # list all the transitive dependencies needed to use this module.
++ get_target_property(is_header_module ${target} _qt_is_header_module)
++ if(NOT is_header_module)
++ string(APPEND module_map_content "\n"
++ " link "
++ "$<$<BOOL:$<TARGET_PROPERTY:${target},FRAMEWORK>>:framework >"
++ "\"$<TARGET_FILE_BASE_NAME:${target}>\"\n"
++ )
++ endif()
++
++ string(APPEND module_map_content "\n"
++ " export *\n"
++ "}\n")
++
++
++ set(module_map_path "${module_clang_modules_dir}/module.modulemap")
++ set(module_map_disable_path "${module_map_path}.disable.yaml")
++
++ # Clang reaches a framework's module map through the symlink at the bundle
++ # root, so that's the spelling to hand out, not the one we write to.
++ set(module_map_lookup_path "${module_clang_modules_lookup_dir}/module.modulemap")
++
++ qt_internal_generate_module_map_disable_overlay("${target}"
++ "${module_map_disable_path}" "${module_map_lookup_path}.disable.yaml")
++
++ get_target_property(is_framework ${target} FRAMEWORK)
++ if(NOT is_framework)
++ qt_install(FILES ${module_map_path} ${module_map_disable_path}
++ DESTINATION "${module_install_interface_include_dir}")
++ endif()
++
++ set_target_properties(${target} PROPERTIES _qt_module_map_build_path "${module_map_path}")
++
++ # Exported as a path relative to QT_BUILD_DIR so it stays valid for a relocated Qt.
++ file(RELATIVE_PATH relative_module_map_path "${QT_BUILD_DIR}" "${module_map_lookup_path}")
++ set_target_properties(${target} PROPERTIES _qt_module_map "${relative_module_map_path}")
++ set_property(TARGET "${target}" APPEND PROPERTY EXPORT_PROPERTIES "_qt_module_map")
++
++ set(condition_args "")
++ if(QT_MULTI_CONFIG_FIRST_CONFIG)
++ list(APPEND condition_args CONDITION "$<CONFIG:${QT_MULTI_CONFIG_FIRST_CONFIG}>")
++ endif()
++ file(GENERATE
++ OUTPUT "${module_modulemap_intermediate_path}"
++ CONTENT "${module_map_content}"
++ ${condition_args}
++ )
++ endfunction()
++
++ # Generates a Clang VFS overlay next to the module's Clang module map, masking
++ # the module map by redirecting it to an empty file.
++ #
++ # The module maps are generated and shipped to support Swift consumers. Normal
++ # C++ consumers won't pick them up automatically, unless they build with explicit
++ # support for C++ Clang modules via -fmodules -fcxx-modules. While our module
++ # maps should work fine for C++ usage too, we want to avoid any risk of regression
++ # for C++ consumers, so if we detect C++ Clang modules enablement we automatically
++ # pass the overlay during compilation via -ivfsoverlay, making the includes textual
++ # again, without affecting the module maps of any other library, or those of the
++ # platform SDK.
++ #
++ # The overlay is propagated to consumers that enable QT_NO_CLANG_MODULE_MAPS,
++ # which _qt_internal_finalize_clang_module_maps does automatically on detecting
++ # that the consuming target builds with C++ Clang modules.
++ function(qt_internal_generate_module_map_disable_overlay target overlay_path lookup_path)
++ # Redirecting to a file that doesn't exist makes Clang report the module map
++ # as missing, which is explicitly supported, and is what we want. Redirecting
++ # to an empty file would still leave a module map for Clang to load. Both the
++ # redirected and the redirected-to path are relative to the overlay itself,
++ # which keeps the overlay valid for a relocated Qt.
++ string(JOIN "\n" overlay_content
++ "{"
++ " 'version': 0,"
++ " 'root-relative': 'overlay-dir',"
++ " 'overlay-relative': true,"
++ " 'roots': ["
++ " { 'name': 'module.modulemap', 'type': 'file',"
++ " 'external-contents': 'nonexistent.modulemap' }"
++ " ]"
++ "}"
++ ""
++ )
++
++ qt_configure_file(OUTPUT "${overlay_path}" CONTENT "${overlay_content}")
++
++ # Clang matches the masked module map by string, against the path it looked
++ # the module map up by, and 'root-relative: overlay-dir' resolves it against
++ # the overlay's own directory as spelled on the command line. So the overlay
++ # has to be passed by its lookup path for the masking to take effect.
++ #
++ # The overlay sits at the same location relative to the build dir as it does
++ # relative to the install prefix, for both framework and non-framework builds.
++ file(RELATIVE_PATH relative_overlay_path "${QT_BUILD_DIR}" "${lookup_path}")
++
++ set(is_disabled "$<BOOL:$<TARGET_PROPERTY:QT_NO_CLANG_MODULE_MAPS>>")
++ target_compile_options(${target} INTERFACE
++ "$<BUILD_INTERFACE:$<${is_disabled}:-ivfsoverlay${lookup_path}>>"
++ "$<INSTALL_INTERFACE:$<${is_disabled}:-ivfsoverlay$<INSTALL_PREFIX>/${relative_overlay_path}>>"
++ )
++ endfunction()