diff --git a/Help/manual/cmake-cxxmodules.7.rst b/Help/manual/cmake-cxxmodules.7.rst index 9ffb529d10..8f4ff44f20 100644 --- a/Help/manual/cmake-cxxmodules.7.rst +++ b/Help/manual/cmake-cxxmodules.7.rst @@ -92,6 +92,7 @@ Compilers which CMake natively supports module dependency scanning include: Support for ``import std`` is limited to the following toolchain and standard library combinations: +* Clang 18.1.2 and newer with ``-stdlib=libc++`` * MSVC toolset 14.36 and newer (provided with Visual Studio 17.6 Preview 2 and newer) diff --git a/Modules/Compiler/Clang-CXX-CXXImportStd.cmake b/Modules/Compiler/Clang-CXX-CXXImportStd.cmake new file mode 100644 index 0000000000..3934db92ff --- /dev/null +++ b/Modules/Compiler/Clang-CXX-CXXImportStd.cmake @@ -0,0 +1,153 @@ +function (_cmake_cxx_import_std std variable) + if (NOT CMAKE_CXX_STANDARD_LIBRARY STREQUAL "libc++") + return () + endif () + + execute_process( + COMMAND + "${CMAKE_CXX_COMPILER}" + ${CMAKE_CXX_COMPILER_ID_ARG1} + -print-file-name=libc++.modules.json + OUTPUT_VARIABLE _clang_libcxx_modules_json_file + ERROR_VARIABLE _clang_libcxx_modules_json_file_err + RESULT_VARIABLE _clang_libcxx_modules_json_file_res + OUTPUT_STRIP_TRAILING_WHITESPACE + ERROR_STRIP_TRAILING_WHITESPACE) + if (_clang_libcxx_modules_json_file_res) + return () + endif () + + # Without this file, we do not have modules installed. + if (NOT EXISTS "${_clang_libcxx_modules_json_file}") + return () + endif () + + if (CMAKE_CXX_COMPILER_VERSION VERSION_LESS "18.1.2") + # The original PR had a key spelling mismatch internally. Do not support it + # and instead require a release known to have the fix. + # https://github.com/llvm/llvm-project/pull/83036 + return () + endif () + + file(READ "${_clang_libcxx_modules_json_file}" _clang_libcxx_modules_json) + string(JSON _clang_modules_json_version GET "${_clang_libcxx_modules_json}" "version") + string(JSON _clang_modules_json_revision GET "${_clang_libcxx_modules_json}" "revision") + # Require version 1. + if (NOT _clang_modules_json_version EQUAL "1") + return () + endif () + + string(JSON _clang_modules_json_nmodules LENGTH "${_clang_libcxx_modules_json}" "modules") + # Don't declare the target without any modules. + if (NOT _clang_modules_json_nmodules) + return () + endif () + + # Declare the target. + set(_clang_libcxx_target "") + # Clang 18 does not provide the module initializer for the `std` modules. + # Create a static library to hold these. Hope that Clang 19 can provide this, + # but never run the code. + string(APPEND _clang_libcxx_target + "add_library(__cmake_cxx${std} STATIC)\n") + string(APPEND _clang_libcxx_target + "target_sources(__cmake_cxx${std} INTERFACE \"$<$,STATIC_LIBRARY>:$>\")\n") + string(APPEND _clang_libcxx_target + "set_property(TARGET __cmake_cxx${std} PROPERTY EXCLUDE_FROM_ALL 1)\n") + string(APPEND _clang_libcxx_target + "set_property(TARGET __cmake_cxx${std} PROPERTY CXX_SCAN_FOR_MODULES 1)\n") + string(APPEND _clang_libcxx_target + "set_property(TARGET __cmake_cxx${std} PROPERTY CXX_MODULE_STD 0)\n") + string(APPEND _clang_libcxx_target + "target_compile_features(__cmake_cxx${std} PUBLIC cxx_std_${std})\n") + + set(_clang_modules_is_stdlib 0) + set(_clang_modules_include_dirs_list "") + set(_clang_modules_module_paths "") + get_filename_component(_clang_modules_dir "${_clang_libcxx_modules_json_file}" DIRECTORY) + + # Add module sources. + math(EXPR _clang_modules_json_nmodules_range "${_clang_modules_json_nmodules} - 1") + foreach (_clang_modules_json_modules_idx RANGE 0 "${_clang_modules_json_nmodules_range}") + string(JSON _clang_modules_json_module GET "${_clang_libcxx_modules_json}" "modules" "${_clang_modules_json_modules_idx}") + + string(JSON _clang_modules_json_module_source GET "${_clang_modules_json_module}" "source-path") + string(JSON _clang_modules_json_module_is_stdlib GET "${_clang_modules_json_module}" "is-std-library") + string(JSON _clang_modules_json_module_local_arguments GET "${_clang_modules_json_module}" "local-arguments") + string(JSON _clang_modules_json_module_nsystem_include_directories LENGTH "${_clang_modules_json_module_local_arguments}" "system-include-directories") + + if (NOT IS_ABSOLUTE "${_clang_modules_json_module_source}") + string(PREPEND _clang_modules_json_module_source "${_clang_modules_dir}/") + endif () + list(APPEND _clang_modules_module_paths + "${_clang_modules_json_module_source}") + + if (_clang_modules_json_module_is_stdlib) + set(_clang_modules_is_stdlib 1) + endif () + + math(EXPR _clang_modules_json_module_nsystem_include_directories_range "${_clang_modules_json_module_nsystem_include_directories} - 1") + foreach (_clang_modules_json_modules_system_include_directories_idx RANGE 0 "${_clang_modules_json_module_nsystem_include_directories_range}") + string(JSON _clang_modules_json_module_system_include_directory GET "${_clang_modules_json_module_local_arguments}" "system-include-directories" "${_clang_modules_json_modules_system_include_directories_idx}") + + if (NOT IS_ABSOLUTE "${_clang_modules_json_module_system_include_directory}") + string(PREPEND _clang_modules_json_module_system_include_directory "${_clang_modules_dir}/") + endif () + list(APPEND _clang_modules_include_dirs_list + "${_clang_modules_json_module_system_include_directory}") + endforeach () + endforeach () + + # Split the paths into basedirs and module paths. + set(_clang_modules_base_dirs_list "") + set(_clang_modules_files "") + foreach (_clang_modules_module_path IN LISTS _clang_modules_module_paths) + get_filename_component(_clang_module_dir "${_clang_modules_module_path}" DIRECTORY) + + list(APPEND _clang_modules_base_dirs_list + "${_clang_module_dir}") + string(APPEND _clang_modules_files + " \"${_clang_modules_module_path}\"") + endforeach () + list(REMOVE_DUPLICATES _clang_modules_base_dirs_list) + set(_clang_modules_base_dirs "") + foreach (_clang_modules_base_dir IN LISTS _clang_modules_base_dirs_list) + string(APPEND _clang_modules_base_dirs + " \"${_clang_modules_base_dir}\"") + endforeach () + + # If we have a standard library module, suppress warnings about reserved + # module names. + if (_clang_modules_is_stdlib) + string(APPEND _clang_libcxx_target + "target_compile_options(__cmake_cxx${std} PRIVATE -Wno-reserved-module-identifier)\n") + endif () + + # Set up include directories. + list(REMOVE_DUPLICATES _clang_modules_include_dirs_list) + set(_clang_modules_include_dirs "") + foreach (_clang_modules_include_dir IN LISTS _clang_modules_include_dirs_list) + string(APPEND _clang_modules_include_dirs + " \"${_clang_modules_include_dir}\"") + endforeach () + string(APPEND _clang_libcxx_target + "target_include_directories(__cmake_cxx${std} PRIVATE ${_clang_modules_include_dirs})\n") + + # Create the file set for the modules. + string(APPEND _clang_libcxx_target + "target_sources(__cmake_cxx${std} + PUBLIC + FILE_SET std TYPE CXX_MODULES + BASE_DIRS ${_clang_modules_base_dirs} + FILES ${_clang_modules_files})\n") + + # Wrap the `__cmake_cxx${std}` target in a check. + string(PREPEND _clang_libcxx_target + "if (NOT TARGET \"__cmake_cxx${std}\")\n") + string(APPEND _clang_libcxx_target + "endif ()\n") + string(APPEND _clang_libcxx_target + "add_library(__CMAKE::CXX${std} ALIAS __cmake_cxx${std})\n") + + set("${variable}" "${_clang_libcxx_target}" PARENT_SCOPE) +endfunction ()