[libc++][modules] Adds the C++23 std module.
authorMark de Wever <koraq@xs4all.nl>
Tue, 28 Feb 2023 19:29:26 +0000 (20:29 +0100)
committerMark de Wever <koraq@xs4all.nl>
Thu, 15 Jun 2023 16:14:15 +0000 (18:14 +0200)
The patch is based on D144994.

D151030 added the module definitions for the module std.
This patch wires in the module and enables the basic testing.

Some notable features are missing:
- There is no test that libc++ can be fully imported as a module.
- This lacks the parts for the std.compat module.
- The module is not shipped with libc++.

Implements parts of
- P2465R3 Standard Library Modules std and std.compat

Reviewed By: ldionne, aaronmondal, #libc

Differential Revision: https://reviews.llvm.org/D151814

24 files changed:
libcxx/CMakeLists.txt
libcxx/cmake/caches/Generic-module-std-cxx23.cmake [new file with mode: 0644]
libcxx/cmake/caches/Generic-modules-lsv.cmake
libcxx/cmake/caches/Generic-modules.cmake
libcxx/docs/Contributing.rst
libcxx/docs/Modules.rst [new file with mode: 0644]
libcxx/docs/ReleaseNotes.rst
libcxx/docs/index.rst
libcxx/modules/.clang-format [new file with mode: 0644]
libcxx/modules/CMakeLists.txt [new file with mode: 0644]
libcxx/modules/CMakeLists.txt.in [new file with mode: 0644]
libcxx/modules/std/string.cppm
libcxx/test/CMakeLists.txt
libcxx/test/configs/cmake-bridge.cfg.in
libcxx/test/libcxx/module_std.gen.py [new file with mode: 0644]
libcxx/test/libcxx/modules_include.gen.py
libcxx/test/tools/clang_tidy_checks/CMakeLists.txt
libcxx/test/tools/clang_tidy_checks/header_exportable_declarations.cpp [new file with mode: 0644]
libcxx/test/tools/clang_tidy_checks/header_exportable_declarations.hpp [new file with mode: 0644]
libcxx/test/tools/clang_tidy_checks/libcpp_module.cpp
libcxx/utils/ci/buildkite-pipeline.yml
libcxx/utils/ci/run-buildbot
libcxx/utils/libcxx/test/dsl.py
libcxx/utils/libcxx/test/params.py

index 0152232..3c2fc7f 100644 (file)
@@ -97,6 +97,12 @@ option(LIBCXX_ENABLE_VENDOR_AVAILABILITY_ANNOTATIONS
    the shared library they shipped should turn this on and see `include/__availability`
    for more details." OFF)
 option(LIBCXX_ENABLE_CLANG_TIDY "Whether to compile and run clang-tidy checks" OFF)
+# TODO MODULES Remove this option and test for the requirements (CMake/Clang) instead.
+option(LIBCXX_ENABLE_STD_MODULES
+   "Whether to enable the building the C++23 `std` module. This feature is
+    experimental and has additional dependencies. Only enable this when
+    interested in testing or developing this module. See
+    https://libcxx.llvm.org/Modules.html for more information." OFF)
 
 if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
   set(LIBCXX_DEFAULT_TEST_CONFIG "llvm-libc++-shared-gcc.cfg.in")
@@ -410,6 +416,7 @@ set(LIBCXX_STATIC_OUTPUT_NAME "c++" CACHE STRING "Output name for the static lib
 if(LLVM_ENABLE_PER_TARGET_RUNTIME_DIR AND NOT APPLE)
   set(LIBCXX_LIBRARY_DIR ${LLVM_LIBRARY_OUTPUT_INTDIR}/${LLVM_DEFAULT_TARGET_TRIPLE})
   set(LIBCXX_GENERATED_INCLUDE_DIR "${LLVM_BINARY_DIR}/include/c++/v1")
+  set(LIBCXX_GENERATED_MODULE_DIR "${LLVM_BINARY_DIR}/modules/c++/v1")
   set(LIBCXX_GENERATED_INCLUDE_TARGET_DIR "${LLVM_BINARY_DIR}/include/${LLVM_DEFAULT_TARGET_TRIPLE}/c++/v1")
   set(LIBCXX_INSTALL_LIBRARY_DIR lib${LLVM_LIBDIR_SUFFIX}/${LLVM_DEFAULT_TARGET_TRIPLE} CACHE PATH
       "Path where built libc++ libraries should be installed.")
@@ -423,9 +430,11 @@ else()
   if(LLVM_LIBRARY_OUTPUT_INTDIR)
     set(LIBCXX_LIBRARY_DIR ${LLVM_LIBRARY_OUTPUT_INTDIR})
     set(LIBCXX_GENERATED_INCLUDE_DIR "${LLVM_BINARY_DIR}/include/c++/v1")
+    set(LIBCXX_GENERATED_MODULE_DIR "${LLVM_BINARY_DIR}/modules/c++/v1")
   else()
     set(LIBCXX_LIBRARY_DIR ${CMAKE_BINARY_DIR}/lib${LIBCXX_LIBDIR_SUFFIX})
     set(LIBCXX_GENERATED_INCLUDE_DIR "${CMAKE_BINARY_DIR}/include/c++/v1")
+    set(LIBCXX_GENERATED_MODULE_DIR "${CMAKE_BINARY_DIR}/modules/c++/v1")
   endif()
   set(LIBCXX_GENERATED_INCLUDE_TARGET_DIR "${LIBCXX_GENERATED_INCLUDE_DIR}")
   set(LIBCXX_INSTALL_LIBRARY_DIR lib${LIBCXX_LIBDIR_SUFFIX} CACHE PATH
@@ -856,6 +865,9 @@ endfunction()
 add_subdirectory(include)
 add_subdirectory(src)
 add_subdirectory(utils)
+if (LIBCXX_ENABLE_STD_MODULES)
+  add_subdirectory(modules)
+endif()
 
 set(LIBCXX_TEST_DEPS "cxx_experimental")
 
@@ -867,6 +879,10 @@ if (LIBCXX_ENABLE_CLANG_TIDY)
   list(APPEND LIBCXX_TEST_DEPS cxx-tidy)
 endif()
 
+if (LIBCXX_ENABLE_STD_MODULES)
+  list(APPEND LIBCXX_TEST_DEPS generate-cxx-modules generate-test-module-std)
+endif()
+
 if (LIBCXX_INCLUDE_BENCHMARKS)
   add_subdirectory(benchmarks)
 endif()
diff --git a/libcxx/cmake/caches/Generic-module-std-cxx23.cmake b/libcxx/cmake/caches/Generic-module-std-cxx23.cmake
new file mode 100644 (file)
index 0000000..6475bd7
--- /dev/null
@@ -0,0 +1,4 @@
+set(LIBCXX_ENABLE_STD_MODULES ON CACHE BOOL "")
+set(LIBCXX_TEST_PARAMS "enable_modules=std;std=c++23" CACHE STRING "")
+set(LIBCXXABI_TEST_PARAMS "std=c++23" CACHE STRING "")
+
index b44424f..66b7613 100644 (file)
@@ -1,2 +1,2 @@
-set(LIBCXX_TEST_PARAMS "enable_modules=True;enable_modules_lsv=True" CACHE STRING "")
+set(LIBCXX_TEST_PARAMS "enable_modules=clang;enable_modules_lsv=True" CACHE STRING "")
 set(LIBCXXABI_TEST_PARAMS "${LIBCXX_TEST_PARAMS}" CACHE STRING "")
index 29b4d4b..0b7d55c 100644 (file)
@@ -1,2 +1,2 @@
-set(LIBCXX_TEST_PARAMS "enable_modules=True" CACHE STRING "")
+set(LIBCXX_TEST_PARAMS "enable_modules=clang" CACHE STRING "")
 set(LIBCXXABI_TEST_PARAMS "${LIBCXX_TEST_PARAMS}" CACHE STRING "")
index 5e7f2aa..cfae4d6 100644 (file)
@@ -44,6 +44,7 @@ sure you don't forget anything:
 - Did you update the synopsis of the relevant headers?
 - Did you update the relevant files to track implementation status (in ``docs/Status/``)?
 - Did you mark all functions and type declarations with the :ref:`proper visibility macro <visibility-macros>`?
+- Did you add all new named declarations to the ``std`` module?
 - If you added a header:
 
   - Did you add it to ``include/module.modulemap.in``?
diff --git a/libcxx/docs/Modules.rst b/libcxx/docs/Modules.rst
new file mode 100644 (file)
index 0000000..e9339f9
--- /dev/null
@@ -0,0 +1,226 @@
+.. _ModulesInLibcxx:
+
+=================
+Modules in libc++
+=================
+
+.. warning:: Modules are an experimental feature. It has additional build
+             requirements and not all libc++ configurations are supported yet.
+
+             The work is still in an early developement state and not
+             considered stable nor complete
+
+This page contains information regarding C++23 module support in libc++.
+There are two kinds of modules available in Clang
+
+ * `Clang specific modules <https://clang.llvm.org/docs/Modules.html>`_
+ * `C++ modules <https://clang.llvm.org/docs/StandardCPlusPlusModules.html>`_
+
+This page mainly discusses the C++ modules. In C++20 there are also header units,
+these are not part of this document.
+
+Overview
+========
+
+The module sources are stored in ``.cppm`` files. Modules need to be available
+as BMIs, which are ``.pcm`` files for Clang. BMIs are not portable, they depend
+on the compiler used and its compilation flags. Therefore there needs to be a
+way to distribute the ``.cppm`` files to the user and offer a way for them to
+build and use the ``.pcm`` files. It is expected this will be done by build
+systems in the future. To aid early adaptor and build system vendors libc++
+currently ships a CMake project to aid building modules.
+
+.. note:: This CMake file is intended to be a temporary solution and will
+          be removed in the future. The timeline for the removal depends
+          on the availability of build systems with proper module support.
+
+What works
+~~~~~~~~~~
+
+ * Building BMIs
+ * Running tests using the ``std`` module
+ * Using the ``std`` module in external projects
+ * The following "parts disabled" configuration options are supported
+
+   * ``LIBCXX_ENABLE_LOCALIZATION``
+   * ``LIBCXX_ENABLE_WIDE_CHARACTERS``
+
+Some of the current limitations
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+ * There is no official build system support, libc++ has experimental CMake support
+ * Requires CMake 3.26
+ * Requires Ninja 1.11
+ * Requires a recent Clang 17
+ * The path to the compiler may not be a symlink, ``clang-scan-deps`` does
+   not handle that case properly
+ * Only C++23 is tested
+ * Libc++ is not tested with modules instead of headers
+ * The module ``.cppm`` files are not installed
+ * The experimental ``PSTL`` library is not supported
+ * Clang supports modules using GNU extensions, but libc++ does not work using
+   GNU extensions.
+ * Clang:
+    * Including headers after importing the ``std`` module may fail. This is
+      hard to solve and there is a work-around by first including all headers
+      `bug report <https://github.com/llvm/llvm-project/issues/61465>`__.
+
+Blockers
+~~~~~~~~
+
+  * libc++
+
+    * Currently the tests only test with modules enabled, but do not import
+      modules instead of headers. When converting tests to using modules there
+      are still failures. These are under investigation.
+
+    * It has not been determined how to fully test libc++ with modules instead
+      of headers.
+
+  * Clang
+
+    * Some concepts do not work properly
+      `bug report <https://github.com/llvm/llvm-project/issues/62943>`__.
+
+
+Using in external projects
+==========================
+
+Users need to be able to build their own BMI files.
+
+.. note:: The requirements for users to build their own BMI files will remain
+   true for the forseeable future. For now this needs to be done manually.
+   Once libc++'s implementation is more mature we will reach out to build
+   system vendors, with the goal that building the BMI files is done by
+   the build system.
+
+Currently this requires a local build of libc++ with modules enabled. Since
+modules are not part of the installation yet, they are used from the build
+directory. First libc++ needs to be build with module support enabled.
+
+.. code-block:: bash
+
+  $ git clone https://github.com/llvm/llvm-project.git
+  $ cd llvm-project
+  $ mkdir build
+  $ cmake -G Ninja -S runtimes -B build -DLIBCXX_ENABLE_STD_MODULES=ON -DLLVM_ENABLE_RUNTIMES="libcxx;libcxxabi;libunwind"
+  $ ninja -C build
+
+The above ``build`` directory will be referred to as ``<build>`` in the
+rest of these instructions.
+
+This is a small sample program that uses the module ``std``. It consists of a
+``CMakeLists.txt`` and a ``main.cpp`` file.
+
+.. code-block:: cpp
+
+  import std;
+
+  int main() { std::cout << "Hello modular world\n"; }
+
+.. code-block:: cmake
+
+  cmake_minimum_required(VERSION 3.26.0 FATAL_ERROR)
+  project("module"
+    LANGUAGES CXX
+  )
+
+  #
+  # Set language version used
+  #
+
+  # At the moment only C++23 is tested.
+  set(CMAKE_CXX_STANDARD 23)
+  set(CMAKE_CXX_STANDARD_REQUIRED YES)
+  # Libc++ doesn't support compiler extensions for modules.
+  set(CMAKE_CXX_EXTENSIONS OFF)
+
+  #
+  # Enable modules in CMake
+  #
+
+  # This is required to write your own modules in your project.
+  if(CMAKE_VERSION VERSION_LESS "3.27.0")
+    set(CMAKE_EXPERIMENTAL_CXX_MODULE_CMAKE_API "2182bf5c-ef0d-489a-91da-49dbc3090d2a")
+  else()
+    set(CMAKE_EXPERIMENTAL_CXX_MODULE_CMAKE_API "aa1f7df0-828a-4fcd-9afc-2dc80491aca7")
+  endif()
+  set(CMAKE_EXPERIMENTAL_CXX_MODULE_DYNDEP 1)
+
+  #
+  # Import the modules from libc++
+  #
+
+  include(FetchContent)
+  FetchContent_Declare(
+    std
+    URL "file://${LIBCXX_BUILD}/modules/c++/v1/"
+    DOWNLOAD_EXTRACT_TIMESTAMP TRUE
+  )
+  FetchContent_GetProperties(std)
+  if(NOT std_POPULATED)
+    FetchContent_Populate(std)
+    add_subdirectory(${std_SOURCE_DIR} ${std_BINARY_DIR} EXCLUDE_FROM_ALL)
+  endif()
+
+  #
+  # Adjust project compiler flags
+  #
+
+  add_compile_options($<$<COMPILE_LANGUAGE:CXX>:-fprebuilt-module-path=${CMAKE_BINARY_DIR}/_deps/std-build/CMakeFiles/std.dir/>)
+  add_compile_options($<$<COMPILE_LANGUAGE:CXX>:-nostdinc++>)
+  # The include path needs to be set to be able to use macros from headers.
+  # For example from, the headers <cassert> and <version>.
+  add_compile_options($<$<COMPILE_LANGUAGE:CXX>:-isystem>)
+  add_compile_options($<$<COMPILE_LANGUAGE:CXX>:${LIBCXX_BUILD}/include/c++/v1>)
+
+  #
+  # Adjust project linker flags
+  #
+
+  add_link_options($<$<COMPILE_LANGUAGE:CXX>:-nostdlib++>)
+  add_link_options($<$<COMPILE_LANGUAGE:CXX>:-L${LIBCXX_BUILD}/lib>)
+  add_link_options($<$<COMPILE_LANGUAGE:CXX>:-Wl,-rpath,${LIBCXX_BUILD}/lib>)
+  # Linking against std is required for CMake to get the proper dependencies
+  link_libraries(std c++)
+
+  #
+  # Add the project
+  #
+
+  add_executable(main)
+  target_sources(main
+    PRIVATE
+      main.cpp
+  )
+
+Building this project is done with the following steps, assuming the files
+``main.cpp`` and ``CMakeLists.txt`` are copied in the current directory.
+
+.. code-block:: bash
+
+  $ mkdir build
+  $ cmake -G Ninja -S . -B build -DCMAKE_CXX_COMPILER=<path-to-compiler> -DLIBCXX_BUILD=<build>
+  $ ninja -C build
+  $ build/main
+
+.. warning:: ``<path-to-compiler>`` should point point to the real binary and
+             not to a symlink.
+
+.. warning:: When using these examples in your own projects make sure the
+             compilation flags are the same for the ``std`` module and your
+             project. Some flags will affect the generated code, when these
+             are different the module cannot be used. For example using
+             ``-pthread`` in your project and not in the module will give
+             errors like
+
+             ``error: POSIX thread support was disabled in PCH file but is currently enabled``
+
+             ``error: module file _deps/std-build/CMakeFiles/std.dir/std.pcm cannot be loaded due to a configuration mismatch with the current compilation [-Wmodule-file-config-mismatch]``
+
+If you have questions about modules free free to ask them in the ``#libcxx``
+channel on `LLVM's Discord server <https://discord.gg/jzUbyP26tQ>`__.
+
+If you think you've found a bug please it using the `LLVM bug tracker
+<https://github.com/llvm/llvm-project/issues>`_. Please make sure the issue
+you found is not one of the known bugs or limitations on this page.
index 9e477b2..a3ecb8e 100644 (file)
@@ -35,6 +35,9 @@ see the `releases page <https://llvm.org/releases/>`_.
 What's New in Libc++ 17.0.0?
 ============================
 
+There is an experimental implementation of the C++23 ``std`` module. See
+:ref:`ModulesInLibcxx` for more information.
+
 Implemented Papers
 ------------------
 - P2520R0 - ``move_iterator<T*>`` should be a random access iterator
@@ -161,3 +164,9 @@ Build System Changes
 
 - ``LIBCXX_ENABLE_FSTREAM`` is not supported anymore, please use ``LIBCXX_ENABLE_FILESYSTEM=OFF`` if your
   platform does not have support for a filesystem.
+
+- The lit test parameter ``enable_modules`` changed from a Boolean to an enum. The changes are
+
+  - ``False`` became ``none``. This option does not test with modules enabled.
+  - ``True`` became ``clang``. This option tests using Clang modules.
+  - ``std`` is a new optional and tests with the experimental C++23 ``std`` module.
index 6d5a03e..ba0fe38 100644 (file)
@@ -39,6 +39,7 @@ Getting Started with libc++
    BuildingLibcxx
    TestingLibcxx
    Contributing
+   Modules
    ReleaseProcedure
    Status/Cxx14
    Status/Cxx17
diff --git a/libcxx/modules/.clang-format b/libcxx/modules/.clang-format
new file mode 100644 (file)
index 0000000..69dcdad
--- /dev/null
@@ -0,0 +1,3 @@
+BasedOnStyle: InheritParentConfig
+
+NamespaceIndentation: All
diff --git a/libcxx/modules/CMakeLists.txt b/libcxx/modules/CMakeLists.txt
new file mode 100644 (file)
index 0000000..0070780
--- /dev/null
@@ -0,0 +1,146 @@
+if (CMAKE_VERSION VERSION_LESS 3.26)
+  message(WARNING "The libc++ modules won't be available because the CMake version is too old. Update to CMake 3.26 or later.")
+  return()
+endif()
+
+# The headers of Table 24: C++ library headers [tab:headers.cpp]
+# and the headers of Table 25: C++ headers for C library facilities [tab:headers.cpp.c]
+set(LIBCXX_SOURCES_MODULE_STD
+  std.cppm
+  std/algorithm.cppm
+  std/any.cppm
+  std/array.cppm
+  std/atomic.cppm
+  std/barrier.cppm
+  std/bit.cppm
+  std/bitset.cppm
+  std/cassert.cppm
+  std/cctype.cppm
+  std/cerrno.cppm
+  std/cfenv.cppm
+  std/cfloat.cppm
+  std/charconv.cppm
+  std/chrono.cppm
+  std/cinttypes.cppm
+  std/climits.cppm
+  std/clocale.cppm
+  std/cmath.cppm
+  std/codecvt.cppm
+  std/compare.cppm
+  std/complex.cppm
+  std/concepts.cppm
+  std/condition_variable.cppm
+  std/coroutine.cppm
+  std/csetjmp.cppm
+  std/csignal.cppm
+  std/cstdarg.cppm
+  std/cstddef.cppm
+  std/cstdint.cppm
+  std/cstdio.cppm
+  std/cstdlib.cppm
+  std/cstring.cppm
+  std/ctime.cppm
+  std/cuchar.cppm
+  std/cwchar.cppm
+  std/cwctype.cppm
+  std/deque.cppm
+  std/exception.cppm
+  std/execution.cppm
+  std/expected.cppm
+  std/filesystem.cppm
+  std/flat_map.cppm
+  std/flat_set.cppm
+  std/format.cppm
+  std/forward_list.cppm
+  std/fstream.cppm
+  std/functional.cppm
+  std/future.cppm
+  std/generator.cppm
+  std/initializer_list.cppm
+  std/iomanip.cppm
+  std/ios.cppm
+  std/iosfwd.cppm
+  std/iostream.cppm
+  std/iostream.cppm
+  std/istream.cppm
+  std/iterator.cppm
+  std/latch.cppm
+  std/limits.cppm
+  std/list.cppm
+  std/locale.cppm
+  std/map.cppm
+  std/mdspan.cppm
+  std/memory.cppm
+  std/memory_resource.cppm
+  std/mutex.cppm
+  std/new.cppm
+  std/numbers.cppm
+  std/numeric.cppm
+  std/optional.cppm
+  std/ostream.cppm
+  std/print.cppm
+  std/queue.cppm
+  std/random.cppm
+  std/ranges.cppm
+  std/ratio.cppm
+  std/regex.cppm
+  std/scoped_allocator.cppm
+  std/semaphore.cppm
+  std/set.cppm
+  std/shared_mutex.cppm
+  std/source_location.cppm
+  std/span.cppm
+  std/spanstream.cppm
+  std/sstream.cppm
+  std/stack.cppm
+  std/stacktrace.cppm
+  std/stdexcept.cppm
+  std/stdexcept.cppm
+  std/stdfloat.cppm
+  std/stop_token.cppm
+  std/streambuf.cppm
+  std/string.cppm
+  std/string_view.cppm
+  std/strstream.cppm
+  std/syncstream.cppm
+  std/system_error.cppm
+  std/thread.cppm
+  std/tuple.cppm
+  std/type_traits.cppm
+  std/typeindex.cppm
+  std/typeinfo.cppm
+  std/unordered_map.cppm
+  std/unordered_set.cppm
+  std/utility.cppm
+  std/valarray.cppm
+  std/variant.cppm
+  std/vector.cppm
+  std/version.cppm
+)
+
+# TODO MODULES the CMakeLists.txt in the install directory is only temporary
+# When that is removed the configured file can use the substitution
+# LIBCXX_GENERATED_INCLUDE_TARGET_DIR avoiding this set.
+# Also clean up the parts needed to generate the install version.
+set(LIBCXX_CONFIGURED_INCLUDE_DIR ${LIBCXX_GENERATED_INCLUDE_TARGET_DIR})
+configure_file(
+  "CMakeLists.txt.in"
+  "${LIBCXX_GENERATED_MODULE_DIR}/CMakeLists.txt"
+  @ONLY
+)
+
+set(_all_modules "${LIBCXX_GENERATED_MODULE_DIR}/CMakeLists.txt")
+foreach(file ${LIBCXX_SOURCES_MODULE_STD})
+  set(src "${CMAKE_CURRENT_SOURCE_DIR}/${file}")
+  set(dst "${LIBCXX_GENERATED_MODULE_DIR}/${file}")
+  add_custom_command(OUTPUT ${dst}
+    DEPENDS ${src}
+    COMMAND ${CMAKE_COMMAND} -E copy_if_different ${src} ${dst}
+    COMMENT "Copying CXX module ${file}")
+  list(APPEND _all_modules "${dst}")
+endforeach()
+
+add_custom_target(generate-cxx-modules
+  ALL DEPENDS
+    ${_all_modules}
+)
diff --git a/libcxx/modules/CMakeLists.txt.in b/libcxx/modules/CMakeLists.txt.in
new file mode 100644 (file)
index 0000000..3cf20ae
--- /dev/null
@@ -0,0 +1,67 @@
+cmake_minimum_required(VERSION 3.26)
+
+project(libc++-modules LANGUAGES CXX)
+
+# Enable CMake's module support
+if(CMAKE_VERSION VERSION_LESS "3.27.0")
+  set(CMAKE_EXPERIMENTAL_CXX_MODULE_CMAKE_API "2182bf5c-ef0d-489a-91da-49dbc3090d2a")
+else()
+  set(CMAKE_EXPERIMENTAL_CXX_MODULE_CMAKE_API "aa1f7df0-828a-4fcd-9afc-2dc80491aca7")
+endif()
+set(CMAKE_EXPERIMENTAL_CXX_MODULE_DYNDEP 1)
+
+# Default to C++ extensions being off. Libc++'s modules support have trouble
+# with extensions right now.
+set(CMAKE_CXX_EXTENSIONS OFF)
+
+# Propagates the CMake options to the modules.
+#
+# This uses the std module hard-coded since the std.compat module does not
+# depend on these flags.
+macro(compile_define_if_not condition def)
+  if (NOT ${condition})
+    target_compile_definitions(std PRIVATE ${def})
+  endif()
+endmacro()
+macro(compile_define_if condition def)
+  if (${condition})
+    target_compile_definitions(std PRIVATE ${def})
+  endif()
+endmacro()
+
+if(NOT @LIBCXX_ENABLE_THREADS@ OR NOT @LIBCXXABI_ENABLE_THREADS@ OR NOT @LIBCXX_ENABLE_MONOTONIC_CLOCK@)
+  message(FATAL_ERROR "Modules without thread support is not yet implemented.")
+endif()
+if(NOT @LIBCXX_ENABLE_FILESYSTEM@)
+  message(FATAL_ERROR "Modules without filesystem support is not yet implemented.")
+endif()
+if(NOT @LIBCXX_ENABLE_RANDOM_DEVICE@)
+  message(FATAL_ERROR "Modules without randome device support is not yet implemented.")
+endif()
+if(NOT @LIBCXX_ENABLE_UNICODE@)
+  message(FATAL_ERROR "Modules without Unicode support is not yet implemented.")
+endif()
+if(NOT @LIBCXX_ENABLE_EXCEPTIONS@ OR NOT @LIBCXXABI_ENABLE_EXCEPTIONS@)
+  message(FATAL_ERROR "Modules without exception support is not yet implemented.")
+endif()
+
+add_library(std)
+target_sources(std
+  PUBLIC FILE_SET cxx_modules TYPE CXX_MODULES FILES
+    @LIBCXX_SOURCES_MODULE_STD@
+)
+
+target_compile_definitions(std PRIVATE _LIBCPP_ENABLE_EXPERIMENTAL)
+target_include_directories(std SYSTEM PRIVATE @LIBCXX_CONFIGURED_INCLUDE_DIR@)
+
+target_compile_options(std
+  PUBLIC
+    -nostdinc++
+    -Wno-reserved-module-identifier
+    -Wno-reserved-user-defined-literal
+    @LIBCXX_COMPILE_FLAGS@
+)
+set_target_properties(std
+  PROPERTIES
+    OUTPUT_NAME   "c++std"
+)
index 371c9e9..9696d58 100644 (file)
@@ -71,10 +71,15 @@ export namespace std {
   // [basic.string.hash], hash support
   using std::hash;
 
+  // TODO MODULES is this a bug?
+#if 1
+  using std::operator""s;
+#else
   inline namespace literals {
     inline namespace string_literals {
       // [basic.string.literals], suffix for basic_string literals
       using std::literals::string_literals::operator""s;
     } // namespace string_literals
   }   // namespace literals
+#endif
 } // namespace std
index 78d3e2f..23081f6 100644 (file)
@@ -68,6 +68,32 @@ if (LIBCXX_INCLUDE_TESTS)
     "Running libcxx tests"
     ${CMAKE_CURRENT_BINARY_DIR}
     DEPENDS cxx-test-depends)
+
+  if(LIBCXX_ENABLE_STD_MODULES)
+    # Generates the modules used in the test.
+    # Note the test will regenerate this with the proper setting
+    # - the right DCMAKE_CXX_STANDARD
+    # - the right test compilation flags
+    # Since modules depend on these flags there currently is no way to
+    # avoid generating these for the tests. The advantage of the
+    # pre generation is that less build information needs to be shared
+    # in the bridge.
+    add_custom_command(
+        OUTPUT "${CMAKE_BINARY_DIR}/test/__config_module__/CMakeCache.txt"
+        COMMAND
+        ${CMAKE_COMMAND}
+            "-G${CMAKE_GENERATOR}"
+            "-DCMAKE_MAKE_PROGRAM=${CMAKE_MAKE_PROGRAM}"
+            "-B${CMAKE_BINARY_DIR}/test/__config_module__"
+            "-H${LIBCXX_GENERATED_MODULE_DIR}"
+            "-DCMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER}"
+            "-DCMAKE_CXX_STANDARD=23"
+            "-DCMAKE_EXPORT_COMPILE_COMMANDS=ON"
+            )
+  add_custom_target(generate-test-module-std
+      DEPENDS "${CMAKE_BINARY_DIR}/test/__config_module__/CMakeCache.txt"
+      COMMENT "Builds generic module std.")
+  endif()
 endif()
 
 if (LIBCXX_GENERATE_COVERAGE)
index e2c86f7..9067115 100644 (file)
@@ -29,5 +29,13 @@ config.substitutions.append(('%{libcxx}', '@LIBCXX_SOURCE_DIR@'))
 config.substitutions.append(('%{include}', '@LIBCXX_GENERATED_INCLUDE_DIR@'))
 config.substitutions.append(('%{target-include}', '@LIBCXX_GENERATED_INCLUDE_TARGET_DIR@'))
 config.substitutions.append(('%{lib}', '@LIBCXX_LIBRARY_DIR@'))
+config.substitutions.append(('%{module}', '@LIBCXX_GENERATED_MODULE_DIR@'))
 config.substitutions.append(('%{executor}', '@LIBCXX_EXECUTOR@'))
 config.substitutions.append(('%{test-tools}', '@LIBCXX_TEST_TOOLS_PATH@'))
+
+# The test needs to manually rebuild the module. The compiler flags used in the
+# test need to be the same as the compiler flags used to generate the module.
+# In the future, when CMake can generated modules this may no longer be
+# necessary.
+# TODO MODULES whether it's possible to remove this substitution.
+config.substitutions.append(('%{cmake}', '@CMAKE_COMMAND@'))
diff --git a/libcxx/test/libcxx/module_std.gen.py b/libcxx/test/libcxx/module_std.gen.py
new file mode 100644 (file)
index 0000000..7873178
--- /dev/null
@@ -0,0 +1,227 @@
+# ===----------------------------------------------------------------------===##
+#
+# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+# See https://llvm.org/LICENSE.txt for license information.
+# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+#
+# ===----------------------------------------------------------------------===##
+
+# Test that all named declarations with external linkage match the
+# exported declarations in their associated module partition.
+# Then it tests the sum of the exported declarations in the module
+# partitions matches the export of the std module.
+
+# Note the test of the std module requires all partitions to be tested
+# first. Since lit tests have no dependencies, this means the test needs
+# to be one monolitic test. Since the test doesn't take very long it's
+# not a huge issue.
+
+# RUN: %{python} %s %{libcxx}/utils
+
+import sys
+
+sys.path.append(sys.argv[1])
+from libcxx.test.header_information import toplevel_headers
+
+BLOCKLIT = (
+    ""  # block Lit from interpreting a RUN/XFAIL/etc inside the generation script
+)
+
+### Remove the headers that have no module associated with them
+
+# Note all C-headers using .h are filtered in the loop.
+
+# These headers are not available in C++23, but in older language Standards.
+toplevel_headers.remove("ccomplex")
+toplevel_headers.remove("ciso646")
+toplevel_headers.remove("cstdbool")
+toplevel_headers.remove("ctgmath")
+
+# Ignore several declarations found in the includes.
+#
+# Part of these items are bugs other are not yet implemented features.
+SkipDeclarations = dict()
+
+# See comment in the header.
+SkipDeclarations["cuchar"] = ["std::mbstate_t", "std::size_t"]
+
+# Not in the synopsis.
+SkipDeclarations["cwchar"] = ["std::FILE"]
+
+# The operators are added for private types like __iom_t10.
+SkipDeclarations["iomanip"] = ["std::operator<<", "std::operator>>"]
+
+SkipDeclarations["iosfwd"] = ["std::ios_base", "std::vector"]
+
+# This header also provides declarations in the namespace that might be
+# an error.
+SkipDeclarations["filesystem"] = [
+    "std::filesystem::operator==",
+    "std::filesystem::operator!=",
+]
+
+# This is a specialization for a private type
+SkipDeclarations["iterator"] = ["std::pointer_traits"]
+
+# TODO MODULES
+# This definition is declared in string and defined in istream
+# This declaration should be part of string
+SkipDeclarations["istream"] = ["std::getline"]
+
+# P1614 (at many places) and LWG3519 too.
+SkipDeclarations["random"] = [
+    "std::operator!=",
+    # LWG3519 makes these hidden friends.
+    # Note the older versions had the requirement of these operations but not in
+    # the synopsis.
+    "std::operator<<",
+    "std::operator>>",
+    "std::operator==",
+]
+
+# Declared in the forward header since std::string uses std::allocator
+SkipDeclarations["string"] = ["std::allocator"]
+# TODO MODULES remove zombie names
+# https://libcxx.llvm.org/Status/Cxx20.html#note-p0619
+SkipDeclarations["memory"] = [
+    "std::return_temporary_buffer",
+    "std::get_temporary_buffer",
+]
+
+# TODO MODULES this should be part of ios instead
+SkipDeclarations["streambuf"] = ["std::basic_ios"]
+
+# include/__type_traits/is_swappable.h
+SkipDeclarations["type_traits"] = [
+    "std::swap",
+    # TODO MODULES gotten through __functional/unwrap_ref.h
+    "std::reference_wrapper",
+]
+
+# Add declarations in headers.
+#
+# Some headers have their defines in a different header, which may have
+# additional declarations.
+ExtraDeclarations = dict()
+# This declaration is in the ostream header.
+ExtraDeclarations["system_error"] = ["std::operator<<"]
+
+# Adds an extra header file to scan
+#
+#
+ExtraHeader = dict()
+# locale has a file and not a subdirectory
+ExtraHeader["locale"] = "v1/__locale$"
+ExtraHeader["thread"] = "v1/__threading_support$"
+ExtraHeader["ranges"] = "v1/__fwd/subrange.h$"
+
+# The extra header is needed since two headers are required to provide the
+# same definition.
+ExtraHeader["functional"] = "v1/__compare/compare_three_way.h$"
+
+# Create empty file with all parts.
+print(
+    f"""\
+//--- module_std.sh.cpp
+// UNSUPPORTED{BLOCKLIT}: c++03, c++11, c++14, c++17, c++20
+
+// REQUIRES{BLOCKLIT}: has-clang-tidy
+// REQUIRES{BLOCKLIT}: use_module_std
+
+// The GCC compiler flags are not always compatible with clang-tidy.
+// UNSUPPORTED{BLOCKLIT}: gcc
+
+// RUN{BLOCKLIT}: echo -n > %t.all_partitions
+"""
+)
+
+# Validate all module parts.
+for header in toplevel_headers:
+    if header.endswith(".h"):  # Skip C compatibility headers
+        continue
+
+    # Dump the information as found in the module's cppm file.
+    print(
+        f"// RUN{BLOCKLIT}: %{{clang-tidy}} %{{module}}/std/{header}.cppm "
+        "  --checks='-*,libcpp-header-exportable-declarations' "
+        "  -config='{CheckOptions: [ "
+        "    {"
+        "      key: libcpp-header-exportable-declarations.Filename, "
+        f"     value: {header}.cppm"
+        "    }, {"
+        "      key: libcpp-header-exportable-declarations.FileType, "
+        "      value: ModulePartition"
+        "    }, "
+        "  ]}' "
+        "  --load=%{test-tools}/clang_tidy_checks/libcxx-tidy.plugin "
+        "  -- %{flags} %{compile_flags} "
+        f"| sort > %t.{header}.module"
+    )
+    print(f"// RUN{BLOCKLIT}: cat  %t.{header}.module >> %t.all_partitions")
+
+    # Dump the information as found in the module by using the header file(s).
+    skip_declarations = " ".join(SkipDeclarations.get(header, []))
+    if skip_declarations:
+        skip_declarations = (
+            "{"
+            "  key: libcpp-header-exportable-declarations.SkipDeclarations, "
+            f' value: "{skip_declarations}" '
+            "}, "
+        )
+
+    extra_declarations = " ".join(ExtraDeclarations.get(header, []))
+    if extra_declarations:
+        extra_declarations = (
+            " {"
+            "  key: libcpp-header-exportable-declarations.ExtraDeclarations, "
+            f' value: "{extra_declarations}" '
+            "}, "
+        )
+
+    extra_header = ExtraHeader.get(header, "")
+    if extra_header:
+        extra_header = (
+            "{"
+            "  key: libcpp-header-exportable-declarations.ExtraHeader, "
+            f' value: "{extra_header}" '
+            "}, "
+        )
+
+    # Clang-tidy needs a file input
+    print(f'// RUN{BLOCKLIT}: echo "#include <{header}>" > %t.{header}.cpp')
+    print(
+        f"// RUN{BLOCKLIT}: %{{clang-tidy}} %t.{header}.cpp "
+        "  --checks='-*,libcpp-header-exportable-declarations' "
+        "  -config='{CheckOptions: [ "
+        f"   {{key: libcpp-header-exportable-declarations.Filename, value: {header}}}, "
+        "    {key: libcpp-header-exportable-declarations.FileType, value: Header}, "
+        f"   {skip_declarations} {extra_declarations} {extra_header}, "
+        "  ]}' "
+        "  --load=%{test-tools}/clang_tidy_checks/libcxx-tidy.plugin "
+        "  -- %{flags} %{compile_flags} "
+        f" | sort > %t.{header}.include"
+    )
+
+    # Compare the cppm and header file(s) return the same results.
+    print(f"// RUN{BLOCKLIT}: diff -u %t.{header}.module %t.{header}.include")
+
+
+# Merge the data of the parts
+print(f"// RUN{BLOCKLIT}: sort -u -o %t.all_partitions %t.all_partitions")
+
+# Dump the information as found in std.cppm.
+print(
+    f"// RUN{BLOCKLIT}: %{{clang-tidy}} %{{module}}/std.cppm "
+    "  --checks='-*,libcpp-header-exportable-declarations' "
+    "  -config='{CheckOptions: [ "
+    "    {key: libcpp-header-exportable-declarations.Header, value: std.cppm}, "
+    "    {key: libcpp-header-exportable-declarations.FileType, value: Module}, "
+    "  ]}' "
+    f" --load=%{{test-tools}}/clang_tidy_checks/libcxx-tidy.plugin "
+    "  -- %{flags} %{compile_flags} "
+    "  | sort > %t.module"
+)
+
+
+# Compare the sum of the parts with the main module.
+print(f"// RUN{BLOCKLIT}: diff -u %t.all_partitions %t.module")
index 565def8..3a93885 100644 (file)
@@ -22,6 +22,8 @@ for header in public_headers:
 //--- {header}.compile.pass.cpp
 // RUN{BLOCKLIT}: %{{cxx}} %s %{{flags}} %{{compile_flags}} -fmodules -fcxx-modules -fmodules-cache-path=%t -fsyntax-only
 
+// UNSUPPORTED{BLOCKLIT}: use_module_std
+
 // GCC doesn't support -fcxx-modules
 // UNSUPPORTED{BLOCKLIT}: gcc
 
index 1a9f921..1cd1f49 100644 (file)
@@ -16,6 +16,7 @@ endif()
 
 set(SOURCES
     abi_tag_on_virtual.cpp
+    header_exportable_declarations.cpp
     hide_from_abi.cpp
     proper_version_checks.cpp
     qualify_declval.cpp
diff --git a/libcxx/test/tools/clang_tidy_checks/header_exportable_declarations.cpp b/libcxx/test/tools/clang_tidy_checks/header_exportable_declarations.cpp
new file mode 100644 (file)
index 0000000..b73d893
--- /dev/null
@@ -0,0 +1,240 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "clang-tidy/ClangTidyCheck.h"
+#include "clang-tidy/ClangTidyModuleRegistry.h"
+
+#include "clang/Basic/Module.h"
+
+#include "llvm/ADT/ArrayRef.h"
+
+#include "header_exportable_declarations.hpp"
+
+#include <iostream>
+#include <iterator>
+#include <ranges>
+#include <algorithm>
+
+template <>
+struct clang::tidy::OptionEnumMapping<libcpp::header_exportable_declarations::FileType> {
+  static llvm::ArrayRef<std::pair<libcpp::header_exportable_declarations::FileType, llvm::StringRef>> getEnumMapping() {
+    static constexpr std::pair<libcpp::header_exportable_declarations::FileType, llvm::StringRef> Mapping[] = {
+        {libcpp::header_exportable_declarations::FileType::Header, "Header"},
+        {libcpp::header_exportable_declarations::FileType::ModulePartition, "ModulePartition"},
+        {libcpp::header_exportable_declarations::FileType::Module, "Module"}};
+    return ArrayRef(Mapping);
+  }
+};
+
+namespace libcpp {
+header_exportable_declarations::header_exportable_declarations(
+    llvm::StringRef name, clang::tidy::ClangTidyContext* context)
+    : clang::tidy::ClangTidyCheck(name, context),
+      filename_(Options.get("Filename", "")),
+      file_type_(Options.get("FileType", header_exportable_declarations::FileType::Unknown)),
+      extra_header_(Options.get("ExtraHeader", "")) {
+  if (filename_.empty())
+    llvm::errs() << "No filename is provided.\n";
+
+  switch (file_type_) {
+  case header_exportable_declarations::FileType::Header:
+    /* DO NOTHING */
+    break;
+  case header_exportable_declarations::FileType::Module:
+  case header_exportable_declarations::FileType::ModulePartition:
+    if (!extra_header_.empty())
+      llvm::errs() << "Extra headers are not allowed for modules.\n";
+    if (Options.get("SkipDeclarations"))
+      llvm::errs() << "Modules may not skip declarations.\n";
+    if (Options.get("ExtraDeclarations"))
+      llvm::errs() << "Modules may not have extra declarations.\n";
+    break;
+  case header_exportable_declarations::FileType::Unknown:
+    llvm::errs() << "No file type is provided.\n";
+    break;
+  }
+
+  std::optional<llvm::StringRef> list = Options.get("SkipDeclarations");
+  // TODO(LLVM-17) Remove clang 15 work-around.
+#if defined(__clang_major__) && __clang_major__ < 16
+  if (list) {
+    std::string_view s = *list;
+    auto b             = s.begin();
+    auto e             = std::find(b, s.end(), ' ');
+    while (b != e) {
+      decls_.emplace(b, e);
+      if (e == s.end())
+        break;
+      b = e + 1;
+      e = std::find(b, s.end(), ' ');
+    }
+  }
+#else  // defined(__clang_major__) && __clang_major__ < 16
+  if (list)
+    for (auto decl : std::views::split(*list, ' ')) {
+      std::string s;
+      std::ranges::copy(decl, std::back_inserter(s)); // use range based constructor
+      decls_.emplace(std::move(s));
+    }
+#endif // defined(__clang_major__) && __clang_major__ < 16
+
+  list = Options.get("ExtraDeclarations");
+  // TODO(LLVM-17) Remove clang 15 work-around.
+#if defined(__clang_major__) && __clang_major__ < 16
+  if (list) {
+    std::string_view s = *list;
+    auto b             = s.begin();
+    auto e             = std::find(b, s.end(), ' ');
+    while (b != e) {
+      std::cout << "using " << std::string_view{b, e} << ";\n";
+      if (e == s.end())
+        break;
+      b = e + 1;
+      e = std::find(b, s.end(), ' ');
+    }
+  }
+#else  // defined(__clang_major__) && __clang_major__ < 16
+  if (list)
+    for (auto decl : std::views::split(*list, ' '))
+      std::cout << "using " << std::string_view{decl.data(), decl.size()} << ";\n";
+#endif // defined(__clang_major__) && __clang_major__ < 16
+}
+
+void header_exportable_declarations::registerMatchers(clang::ast_matchers::MatchFinder* finder) {
+  // there are no public names in the Standard starting with an underscore, so
+  // no need to check the strict rules.
+  using namespace clang::ast_matchers;
+
+  switch (file_type_) {
+  case FileType::Header:
+
+    finder->addMatcher(
+        namedDecl(
+            // Looks at the common locations where headers store their data
+            // * header
+            // * __header/*.h
+            // * __fwd/header.h
+            anyOf(isExpansionInFileMatching(("v1/__" + filename_ + "/").str()),
+                  isExpansionInFileMatching(extra_header_),
+                  isExpansionInFileMatching(("v1/__fwd/" + filename_ + "\\.h$").str()),
+                  isExpansionInFileMatching(("v1/" + filename_ + "$").str())),
+            unless(hasAncestor(friendDecl())))
+            .bind("header_exportable_declarations"),
+        this);
+    break;
+  case FileType::ModulePartition:
+    finder->addMatcher(namedDecl(isExpansionInFileMatching(filename_)).bind("header_exportable_declarations"), this);
+    break;
+  case FileType::Module:
+    finder->addMatcher(namedDecl().bind("header_exportable_declarations"), this);
+    break;
+  case header_exportable_declarations::FileType::Unknown:
+    llvm::errs() << "This should be unreachable.\n";
+    break;
+  }
+}
+
+/// Returns the qualified name of a declaration.
+///
+/// There is a small issue with qualified names. Typically the name returned is
+/// in the namespace \c std instead of the namespace \c std::__1. Except when a
+/// name is declared both in the namespace \c std and in the namespace
+/// \c std::__1. In that case the returned value will adjust the name to use
+/// the namespace \c std.
+///
+/// The reason this happens is due to some parts of libc++ using
+/// \code namespace std \endcode instead of
+/// \code _LIBCPP_BEGIN_NAMESPACE_STD \endcode
+/// Some examples
+/// * cstddef has bitwise operators for the type \c byte
+/// * exception has equality operators for the type \c exception_ptr
+/// * initializer_list has the functions \c begin and \c end
+static std::string get_qualified_name(const clang::NamedDecl& decl) {
+  std::string result = decl.getQualifiedNameAsString();
+
+  if (result.starts_with("std::__1::"))
+    result.erase(5, 5);
+
+  return result;
+}
+
+static bool is_viable_declaration(const clang::NamedDecl* decl) {
+  // Declarations nested in records are automatically exported with the record itself.
+  if (!decl->getDeclContext()->isNamespace())
+    return false;
+
+  // Declarations that are a subobject of a friend Declaration are automatically exported with the record itself.
+  if (decl->getFriendObjectKind() != clang::Decl::FOK_None)
+    return false;
+
+  // *** Function declarations ***
+
+  if (clang::CXXMethodDecl::classof(decl))
+    return false;
+
+  if (clang::CXXDeductionGuideDecl::classof(decl))
+    return false;
+
+  if (clang::FunctionDecl::classof(decl))
+    return true;
+
+  if (clang::CXXConstructorDecl::classof(decl))
+    return false;
+
+  // implicit constructors disallowed
+  if (const auto* r = llvm::dyn_cast_or_null<clang::RecordDecl>(decl))
+    return !r->isLambda() && !r->isImplicit();
+
+  // *** Unconditionally accepted declarations ***
+  return llvm::isa<clang::EnumDecl, clang::VarDecl, clang::ConceptDecl, clang::TypedefNameDecl, clang::UsingDecl>(decl);
+}
+
+/// Returns the name is a reserved name.
+///
+/// Detected reserved names are names starting with __ or _[A-Z].
+/// These names can be in the namespace std or any namespace inside std. For
+/// example std::ranges contains reserved names to implement the Niebloids.
+///
+/// This test misses 2 candidates which are not used in libc++
+/// * any identifier with two underscores not at the start
+/// * a name with a leading underscore in the global namespace
+bool is_reserved_name(const std::string& name) {
+  std::size_t pos = name.find("::_");
+  if (pos == std::string::npos)
+    return false;
+
+  if (pos + 3 > name.size())
+    return false;
+
+  return name[pos + 3] == '_' || std::isupper(name[pos + 3]);
+}
+
+void header_exportable_declarations::check(const clang::ast_matchers::MatchFinder::MatchResult& result) {
+  if (const auto* decl = result.Nodes.getNodeAs<clang::NamedDecl>("header_exportable_declarations"); decl != nullptr) {
+    if (!is_viable_declaration(decl))
+      return;
+
+    std::string name = get_qualified_name(*decl);
+    if (is_reserved_name(name))
+      return;
+
+    // For modules (std, std.compat) only take the declarations exported from the partitions.
+    // Making sure no declatations of headers are compared.
+    if (file_type_ == FileType::Module)
+      if (clang::Module* M = decl->getOwningModule(); M && M->Kind != clang::Module::ModulePartitionInterface)
+        return;
+
+    if (decls_.contains(name))
+      return;
+
+    std::cout << "using " << std::string{name} << ";\n";
+    decls_.insert(name);
+  }
+}
+
+} // namespace libcpp
diff --git a/libcxx/test/tools/clang_tidy_checks/header_exportable_declarations.hpp b/libcxx/test/tools/clang_tidy_checks/header_exportable_declarations.hpp
new file mode 100644 (file)
index 0000000..119bcd3
--- /dev/null
@@ -0,0 +1,32 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "clang-tidy/ClangTidyCheck.h"
+
+#include "llvm/ADT/StringRef.h"
+
+#include <fstream>
+#include <set>
+#include <string>
+
+namespace libcpp {
+class header_exportable_declarations : public clang::tidy::ClangTidyCheck {
+public:
+  explicit header_exportable_declarations(llvm::StringRef, clang::tidy::ClangTidyContext*);
+  void registerMatchers(clang::ast_matchers::MatchFinder*) override;
+  void check(const clang::ast_matchers::MatchFinder::MatchResult&) override;
+
+  enum class FileType { Header, ModulePartition, Module, Unknown };
+
+private:
+  llvm::StringRef filename_;
+  FileType file_type_;
+  llvm::StringRef extra_header_;
+  std::set<std::string> decls_;
+};
+} // namespace libcpp
index 9ecb41e..16acf32 100644 (file)
@@ -10,6 +10,7 @@
 #include "clang-tidy/ClangTidyModuleRegistry.h"
 
 #include "abi_tag_on_virtual.hpp"
+#include "header_exportable_declarations.hpp"
 #include "hide_from_abi.hpp"
 #include "proper_version_checks.hpp"
 #include "qualify_declval.hpp"
@@ -21,6 +22,7 @@ class LibcxxTestModule : public clang::tidy::ClangTidyModule {
 public:
   void addCheckFactories(clang::tidy::ClangTidyCheckFactories& check_factories) override {
     check_factories.registerCheck<libcpp::abi_tag_on_virtual>("libcpp-avoid-abi-tag-on-virtual");
+    check_factories.registerCheck<libcpp::header_exportable_declarations>("libcpp-header-exportable-declarations");
     check_factories.registerCheck<libcpp::hide_from_abi>("libcpp-hide-from-abi");
     check_factories.registerCheck<libcpp::proper_version_checks>("libcpp-cpp-version-check");
     check_factories.registerCheck<libcpp::robust_against_adl_check>("libcpp-robust-against-adl");
index e1c6f91..548e9a2 100644 (file)
@@ -146,6 +146,27 @@ steps:
           limit: 2
     timeout_in_minutes: 120
 
+  - label: "C++23 Module std"
+    command: "libcxx/utils/ci/run-buildbot generic-module-std-cxx23"
+    artifact_paths:
+      - "**/test-results.xml"
+      - "**/*.abilist"
+    env:
+        # Note modules require and absolute path for clang-scan-deps
+        # https://github.com/llvm/llvm-project/issues/61006
+        CC: "/usr/lib/llvm-${LLVM_HEAD_VERSION}/bin/clang"
+        CXX: "/usr/lib/llvm-${LLVM_HEAD_VERSION}/bin/clang++"
+        CMAKE: "/opt/bin/cmake"
+        ENABLE_CLANG_TIDY: "On"
+    agents:
+      queue: "libcxx-builders"
+      os: "linux"
+    retry:
+      automatic:
+        - exit_status: -1  # Agent was lost
+          limit: 2
+    timeout_in_minutes: 120
+
   - label: "C++11"
     command: "libcxx/utils/ci/run-buildbot generic-cxx11"
     artifact_paths:
index 59f66a8..cd8a7e3 100755 (executable)
@@ -35,9 +35,12 @@ ${PROGNAME} [options] <BUILDER>
 Environment variables
 CC                  The C compiler to use, this value is used by CMake. This
                     variable is optional.
+
 CXX                 The C++ compiler to use, this value is used by CMake. This
                     variable is optional.
 
+CMAKE               The CMake binary to use. This variable is optional.
+
 CLANG_FORMAT        The clang-format binary to use when generating the format
                     ignore list.
 
@@ -89,8 +92,24 @@ INSTALL_DIR="${BUILD_DIR}/install"
 # version will generally work with the Clang shipped in Xcode (e.g. if Clang
 # knows about -std=c++20, the CMake bundled in Xcode will probably know about
 # that flag too).
-if xcrun --find ninja &>/dev/null; then NINJA="$(xcrun --find ninja)"; else NINJA="ninja"; fi
-if xcrun --find cmake &>/dev/null; then CMAKE="$(xcrun --find cmake)"; else CMAKE="cmake"; fi
+if xcrun --find ninja &>/dev/null; then
+    NINJA="$(xcrun --find ninja)"
+elif which ninja &>/dev/null; then
+    # The current implementation of modules needs the absolute path to the ninja
+    # binary.
+    # TODO MODULES Is this still needed when CMake has libc++ module support?
+    NINJA="$(which ninja)"
+else
+    NINJA="ninja"
+fi
+
+if [ -z "${CMAKE}" ]; then
+    if xcrun --find cmake &>/dev/null; then
+        CMAKE="$(xcrun --find cmake)"
+    else
+        CMAKE="cmake"
+    fi
+fi
 
 function clean() {
     rm -rf "${BUILD_DIR}"
@@ -178,8 +197,8 @@ check-format)
     fi
     ${GIT_CLANG_FORMAT} \
         --diff \
-        --extensions ',h,hpp,c,cpp,inc,ipp' HEAD~1 \
-        -- $(find libcxx/{benchmarks,include,src}/ -type f | grep -vf libcxx/utils/data/ignore_format.txt) \
+        --extensions ',h,hpp,c,cpp,cppm,inc,ipp' HEAD~1 \
+        -- $(find libcxx/{benchmarks,include,modules,src}/ -type f | grep -vf libcxx/utils/data/ignore_format.txt) \
         | tee ${BUILD_DIR}/clang-format.patch
     # Check if the diff is empty, fail otherwise.
     ! grep -q '^--- a' ${BUILD_DIR}/clang-format.patch
@@ -379,6 +398,9 @@ generic-with_llvm_unwinder)
     generate-cmake -DLIBCXXABI_USE_LLVM_UNWINDER=ON
     check-runtimes
 ;;
+#
+# Module builds
+#
 generic-modules)
     clean
     generate-cmake -C "${MONOREPO_ROOT}/libcxx/cmake/caches/Generic-modules.cmake"
@@ -391,6 +413,12 @@ generic-modules-lsv)
     check-runtimes
     check-abi-list
 ;;
+generic-module-std-cxx23)
+    clean
+    generate-cmake -C "${MONOREPO_ROOT}/libcxx/cmake/caches/Generic-module-std-cxx23.cmake"
+    check-runtimes
+    check-abi-list
+;;
 #
 # Parts removed
 #
index 8a753f5..77a7096 100644 (file)
@@ -12,6 +12,7 @@ import pipes
 import platform
 import re
 import shutil
+import subprocess
 import tempfile
 
 import libcxx.test.format
@@ -338,6 +339,12 @@ def featureTestMacros(config, flags=""):
     }
 
 
+def _getSubstitution(substitution, config):
+  for (orig, replacement) in config.substitutions:
+    if orig == substitution:
+      return replacement
+  raise ValueError('Substitution {} is not in the config.'.format(substitution))
+
 def _appendToSubstitution(substitutions, key, value):
     return [(k, v + " " + value) if k == key else (k, v) for (k, v) in substitutions]
 
@@ -430,6 +437,30 @@ class AddFlag(ConfigAction):
     def pretty(self, config, litParams):
         return "add {} to %{{flags}}".format(self._getFlag(config))
 
+class BuildStdModule(ConfigAction):
+  def applyTo(self, config):
+    build = os.path.join(config.test_exec_root, '__config_module__')
+
+    std = _getSubstitution('%{cxx_std}', config)
+    if std == 'cxx26':
+        # This fails to work properly. It might be due to
+        #   CMAKE_CXX_STANDARD 26
+        # does not work in CMake 3.26, it requires the upcomming CMake 3.27.
+        # TODO MODULES test whether this is fixed with CMake 3.27.
+        std = '17'
+    elif std == 'cxx23':
+        std = '23'
+    else:
+        std = '17' # Not allowed for modules
+
+    flags = _getSubstitution('%{flags}', config)
+    cmake = _getSubstitution('%{cmake}', config)
+
+    subprocess.check_call([cmake, "-DCMAKE_CXX_STANDARD=" + std, f"-DCMAKE_CXX_FLAGS={flags}", build], env={})
+    subprocess.check_call([cmake, "--build", build], env={})
+
+  def pretty(self, config, litParams):
+    return "building std module with flags {}".format(_getSubstitution('%{flags}', config))
 
 class AddFlagIfSupported(ConfigAction):
     """
index 2e71978..7de8078 100644 (file)
@@ -24,6 +24,7 @@ _warningFlags = [
     "-Wno-noexcept-type",
     "-Wno-aligned-allocation-unavailable",
     "-Wno-atomic-alignment",
+    "-Wno-reserved-module-identifier",
     # GCC warns about places where we might want to add sized allocation/deallocation
     # functions, but we know better what we're doing/testing in the test suite.
     "-Wno-sized-deallocation",
@@ -73,6 +74,15 @@ def getStdFlag(cfg, std):
     return None
 
 
+_allModules = ["none", "clang", "std"]
+
+
+def getModuleFlag(cfg, enable_modules):
+    if enable_modules in _allModules:
+        return enable_modules
+    return None
+
+
 DEFAULT_PARAMETERS = [
     Parameter(
         name="target_triple",
@@ -104,18 +114,32 @@ DEFAULT_PARAMETERS = [
     ),
     Parameter(
         name="enable_modules",
-        choices=[True, False],
-        type=bool,
-        default=False,
-        help="Whether to build the test suite with Clang modules enabled.",
-        actions=lambda modules: [
+        choices=_allModules,
+        type=str,
+        help="Whether to build the test suite with modules enabled. Select "
+        "`clang` for Clang modules and `std` for C++23 std module",
+        default=lambda cfg: next(s for s in _allModules if getModuleFlag(cfg, s)),
+        actions=lambda enable_modules: [
             AddFeature("modules-build"),
             AddCompileFlag("-fmodules"),
             AddCompileFlag(
                 "-fcxx-modules"
             ),  # AppleClang disregards -fmodules entirely when compiling C++. This enables modules for C++.
         ]
-        if modules
+        if enable_modules == "clang"
+        else [
+            AddFeature("use_module_std"),
+            AddCompileFlag("-DTEST_USE_MODULE"),
+            AddCompileFlag("-DTEST_USE_MODULE_STD"),
+            AddCompileFlag(
+                lambda cfg: "-fprebuilt-module-path="
+                + os.path.join(
+                    cfg.test_exec_root, "__config_module__/CMakeFiles/std.dir"
+                )
+            ),
+            BuildStdModule(),
+        ]
+        if enable_modules == "std"
         else [],
     ),
     Parameter(