Browse Source

Clang: support creating a target for imported modules

Ben Boeckel 1 year ago
parent
commit
429902ebad
2 changed files with 154 additions and 0 deletions
  1. 1 0
      Help/manual/cmake-cxxmodules.7.rst
  2. 153 0
      Modules/Compiler/Clang-CXX-CXXImportStd.cmake

+ 1 - 0
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)
 

+ 153 - 0
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 \"$<$<STREQUAL:$<TARGET_PROPERTY:TYPE>,STATIC_LIBRARY>:$<TARGET_OBJECTS:__cmake_cxx${std}>>\")\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 ()