[libcxx-commits] [libcxx] [libc++] Test existence of generated headers in the modulemap (PR #209312)

Louis Dionne via libcxx-commits libcxx-commits at lists.llvm.org
Tue Aug 4 08:27:34 PDT 2026


https://github.com/ldionne updated https://github.com/llvm/llvm-project/pull/209312

>From 717e8c1ec35426a2c411421427b38c3e279a42b0 Mon Sep 17 00:00:00 2001
From: Louis Dionne <ldionne.2 at gmail.com>
Date: Wed, 9 Apr 2025 21:35:27 -0400
Subject: [PATCH 1/5] [libc++] Test existence of generated headers in the
 modulemap

The __config_site and __assertion_handler headers were previously
excluded from the modulemap test, but since we now include them in
the modulemap despite being generated, we can test it.
---
 libcxx/test/libcxx/headers_in_modulemap.sh.py | 8 +++++---
 libcxx/utils/libcxx/header_information.py     | 9 +--------
 2 files changed, 6 insertions(+), 11 deletions(-)

diff --git a/libcxx/test/libcxx/headers_in_modulemap.sh.py b/libcxx/test/libcxx/headers_in_modulemap.sh.py
index d4a18a20c8926..f7670365bc521 100644
--- a/libcxx/test/libcxx/headers_in_modulemap.sh.py
+++ b/libcxx/test/libcxx/headers_in_modulemap.sh.py
@@ -1,10 +1,12 @@
-# RUN: %{python} %s %{libcxx-dir}/utils
+# RUN: %{python} %s %{libcxx-dir}/utils %{include-dir}
 
+import pathlib
 import sys
 sys.path.append(sys.argv[1])
-from libcxx.header_information import all_headers, libcxx_include
+from libcxx.header_information import all_headers
 
-with open(libcxx_include / "module.modulemap.in") as f:
+include = pathlib.Path(sys.argv[2])
+with open(include / "module.modulemap") as f:
     modulemap = f.read()
 
 isHeaderMissing = False
diff --git a/libcxx/utils/libcxx/header_information.py b/libcxx/utils/libcxx/header_information.py
index a505d37b65b81..bc8c586c6980a 100644
--- a/libcxx/utils/libcxx/header_information.py
+++ b/libcxx/utils/libcxx/header_information.py
@@ -9,7 +9,6 @@
 import pathlib, functools
 
 libcxx_root = pathlib.Path(__file__).resolve().parent.parent.parent
-libcxx_include = libcxx_root / "include"
 assert libcxx_root.exists()
 
 def _is_header_file(file):
@@ -102,13 +101,6 @@ def is_cxx03_frozen_header(self) -> bool:
 
     def is_in_modulemap(self) -> bool:
         """Returns whether a header should be listed in the modulemap."""
-        # TODO: Should `__config_site` be in the modulemap?
-        if self._name == "__config_site":
-            return False
-
-        if self._name == "__assertion_handler":
-            return False
-
         # exclude libc++abi files
         if self._name in ["cxxabi.h", "__cxxabi_config.h"]:
             return False
@@ -151,6 +143,7 @@ def __hash__(self) -> int:
 
 
 # Commonly-used sets of headers
+libcxx_include = libcxx_root / "include"
 all_headers = [Header(p.relative_to(libcxx_include).as_posix()) for p in libcxx_include.rglob("[_a-z]*") if _is_header_file(p)]
 all_headers += [Header("__config_site"), Header("__assertion_handler")] # Headers generated during the build process
 public_headers = [h for h in all_headers if h.is_public()]

>From 02662c25923f8c1166835acfc2a41267858ee0a2 Mon Sep 17 00:00:00 2001
From: Louis Dionne <ldionne.2 at gmail.com>
Date: Mon, 3 Aug 2026 12:42:47 -0400
Subject: [PATCH 2/5] Create per-target modulemap

---
 libcxx/include/CMakeLists.txt                 | 19 ++++++++++++++++---
 libcxx/include/target.modulemap.in            |  3 +++
 libcxx/test/libcxx/headers_in_modulemap.sh.py |  7 ++++++-
 libcxx/utils/libcxx/header_information.py     |  1 +
 4 files changed, 26 insertions(+), 4 deletions(-)
 create mode 100644 libcxx/include/target.modulemap.in

diff --git a/libcxx/include/CMakeLists.txt b/libcxx/include/CMakeLists.txt
index 8ac94b1b00751..fb77defb92ac2 100644
--- a/libcxx/include/CMakeLists.txt
+++ b/libcxx/include/CMakeLists.txt
@@ -1694,9 +1694,11 @@ set(files
 configure_file("__config_site.in" "${LIBCXX_GENERATED_INCLUDE_TARGET_DIR}/__config_site" @ONLY)
 configure_file("${LIBCXX_ASSERTION_HANDLER_FILE}" "${LIBCXX_GENERATED_INCLUDE_DIR}/__assertion_handler" COPYONLY)
 
-# We generate the modulemap file so that we can include __config_site in it. For now, we don't know how to
-# make __config_site modular when per-target runtime directories are used.
-if (NOT LLVM_ENABLE_PER_TARGET_RUNTIME_DIR)
+# We generate the modulemap file so that we can include __config_site in it. When using a target-specific include
+# directory, give it its own modulemap.
+if (LLVM_ENABLE_PER_TARGET_RUNTIME_DIR)
+  configure_file("target.modulemap.in" "${LIBCXX_GENERATED_INCLUDE_TARGET_DIR}/module.modulemap" COPYONLY)
+else()
   set(LIBCXX_CONFIG_SITE_MODULE_ENTRY "textual header \"__config_site\"")
 endif()
 configure_file("module.modulemap.in" "${LIBCXX_GENERATED_INCLUDE_DIR}/module.modulemap" @ONLY)
@@ -1704,6 +1706,9 @@ configure_file("module.modulemap.in" "${LIBCXX_GENERATED_INCLUDE_DIR}/module.mod
 set(_all_includes "${LIBCXX_GENERATED_INCLUDE_TARGET_DIR}/__config_site"
                   "${LIBCXX_GENERATED_INCLUDE_DIR}/__assertion_handler"
                   "${LIBCXX_GENERATED_INCLUDE_DIR}/module.modulemap")
+if (LLVM_ENABLE_PER_TARGET_RUNTIME_DIR)
+  list(APPEND _all_includes "${LIBCXX_GENERATED_INCLUDE_TARGET_DIR}/module.modulemap")
+endif()
 foreach(f ${files})
   set(src "${CMAKE_CURRENT_SOURCE_DIR}/${f}")
   set(dst "${LIBCXX_GENERATED_INCLUDE_DIR}/${f}")
@@ -1772,6 +1777,14 @@ if (LIBCXX_INSTALL_HEADERS)
     PERMISSIONS OWNER_READ OWNER_WRITE GROUP_READ WORLD_READ
     COMPONENT cxx-headers)
 
+  # Install the per-target modulemap when we use per-target include dirs.
+  if (LLVM_ENABLE_PER_TARGET_RUNTIME_DIR)
+    install(FILES "${LIBCXX_GENERATED_INCLUDE_TARGET_DIR}/module.modulemap"
+      DESTINATION "${LIBCXX_INSTALL_INCLUDE_TARGET_DIR}"
+      PERMISSIONS OWNER_READ OWNER_WRITE GROUP_READ WORLD_READ
+      COMPONENT cxx-headers)
+  endif()
+
   if (NOT CMAKE_CONFIGURATION_TYPES)
     add_custom_target(install-cxx-headers
       DEPENDS cxx-headers
diff --git a/libcxx/include/target.modulemap.in b/libcxx/include/target.modulemap.in
new file mode 100644
index 0000000000000..77735e5ad7564
--- /dev/null
+++ b/libcxx/include/target.modulemap.in
@@ -0,0 +1,3 @@
+module std_config_site {
+  textual header "__config_site"
+}
diff --git a/libcxx/test/libcxx/headers_in_modulemap.sh.py b/libcxx/test/libcxx/headers_in_modulemap.sh.py
index f7670365bc521..e44937b49409f 100644
--- a/libcxx/test/libcxx/headers_in_modulemap.sh.py
+++ b/libcxx/test/libcxx/headers_in_modulemap.sh.py
@@ -1,13 +1,18 @@
-# RUN: %{python} %s %{libcxx-dir}/utils %{include-dir}
+# RUN: %{python} %s %{libcxx-dir}/utils %{include-dir} %{target-include-dir}
 
 import pathlib
 import sys
 sys.path.append(sys.argv[1])
 from libcxx.header_information import all_headers
 
+# Check both the generic modulemap and the target-specific modulemap, if there is one.
 include = pathlib.Path(sys.argv[2])
+target_include = pathlib.Path(sys.argv[3])
 with open(include / "module.modulemap") as f:
     modulemap = f.read()
+if target_include.resolve() != include.resolve():
+    with open(target_include / "module.modulemap") as f:
+        modulemap += f.read()
 
 isHeaderMissing = False
 for header in all_headers:
diff --git a/libcxx/utils/libcxx/header_information.py b/libcxx/utils/libcxx/header_information.py
index 841e1149d464e..7fc1cb6d46366 100644
--- a/libcxx/utils/libcxx/header_information.py
+++ b/libcxx/utils/libcxx/header_information.py
@@ -15,6 +15,7 @@ def _is_header_file(file):
     """Returns whether the given file is a header file, i.e. not a directory or the modulemap file."""
     return not file.is_dir() and not file.name in [
         "module.modulemap.in",
+        "target.modulemap.in",
         "CMakeLists.txt",
         "libcxx.imp",
         "__config_site.in",

>From eb89bb5f35cfee3e16f9603f20835cb079e38a1e Mon Sep 17 00:00:00 2001
From: Louis Dionne <ldionne.2 at gmail.com>
Date: Tue, 4 Aug 2026 09:03:34 -0400
Subject: [PATCH 3/5] Don't generate target modulemap

---
 libcxx/include/CMakeLists.txt                            | 6 +++---
 libcxx/include/{target.modulemap.in => target.modulemap} | 0
 libcxx/utils/libcxx/header_information.py                | 2 +-
 3 files changed, 4 insertions(+), 4 deletions(-)
 rename libcxx/include/{target.modulemap.in => target.modulemap} (100%)

diff --git a/libcxx/include/CMakeLists.txt b/libcxx/include/CMakeLists.txt
index fb77defb92ac2..d446eb7b894e9 100644
--- a/libcxx/include/CMakeLists.txt
+++ b/libcxx/include/CMakeLists.txt
@@ -1694,10 +1694,10 @@ set(files
 configure_file("__config_site.in" "${LIBCXX_GENERATED_INCLUDE_TARGET_DIR}/__config_site" @ONLY)
 configure_file("${LIBCXX_ASSERTION_HANDLER_FILE}" "${LIBCXX_GENERATED_INCLUDE_DIR}/__assertion_handler" COPYONLY)
 
-# We generate the modulemap file so that we can include __config_site in it. When using a target-specific include
-# directory, give it its own modulemap.
+# We generate the main modulemap file so that we can include __config_site in it. When using a target-specific
+# include directory, give it its own modulemap.
 if (LLVM_ENABLE_PER_TARGET_RUNTIME_DIR)
-  configure_file("target.modulemap.in" "${LIBCXX_GENERATED_INCLUDE_TARGET_DIR}/module.modulemap" COPYONLY)
+  configure_file("target.modulemap" "${LIBCXX_GENERATED_INCLUDE_TARGET_DIR}/module.modulemap" COPYONLY)
 else()
   set(LIBCXX_CONFIG_SITE_MODULE_ENTRY "textual header \"__config_site\"")
 endif()
diff --git a/libcxx/include/target.modulemap.in b/libcxx/include/target.modulemap
similarity index 100%
rename from libcxx/include/target.modulemap.in
rename to libcxx/include/target.modulemap
diff --git a/libcxx/utils/libcxx/header_information.py b/libcxx/utils/libcxx/header_information.py
index 7fc1cb6d46366..ee067d5eae264 100644
--- a/libcxx/utils/libcxx/header_information.py
+++ b/libcxx/utils/libcxx/header_information.py
@@ -15,7 +15,7 @@ def _is_header_file(file):
     """Returns whether the given file is a header file, i.e. not a directory or the modulemap file."""
     return not file.is_dir() and not file.name in [
         "module.modulemap.in",
-        "target.modulemap.in",
+        "target.modulemap",
         "CMakeLists.txt",
         "libcxx.imp",
         "__config_site.in",

>From cb79ca769a6664ec67cae8ca37c6bb0fdec4a62b Mon Sep 17 00:00:00 2001
From: Louis Dionne <ldionne.2 at gmail.com>
Date: Tue, 4 Aug 2026 11:26:47 -0400
Subject: [PATCH 4/5] Handle target modulemap in lint headers

---
 libcxx/test/libcxx/lint/lint_headers.sh.py | 1 +
 1 file changed, 1 insertion(+)

diff --git a/libcxx/test/libcxx/lint/lint_headers.sh.py b/libcxx/test/libcxx/lint/lint_headers.sh.py
index ab237c968da7e..34eb5c000d237 100644
--- a/libcxx/test/libcxx/lint/lint_headers.sh.py
+++ b/libcxx/test/libcxx/lint/lint_headers.sh.py
@@ -12,6 +12,7 @@ def exclude_from_consideration(path):
     return (
         path.endswith(".txt")
         or path.endswith(".modulemap.in")
+        or os.path.basename(path) == "target.modulemap"
         or os.path.basename(path) == "__config"
         or os.path.basename(path) == "__config_site.in"
         or os.path.basename(path) == "libcxx.imp"

>From a6c1b4a51955b13add6c4d5704a1329ee10c4ca0 Mon Sep 17 00:00:00 2001
From: Louis Dionne <ldionne.2 at gmail.com>
Date: Tue, 4 Aug 2026 11:27:18 -0400
Subject: [PATCH 5/5] Frozen c++03 headers too

---
 libcxx/test/libcxx-03/lint/lint_headers.sh.py | 1 +
 1 file changed, 1 insertion(+)

diff --git a/libcxx/test/libcxx-03/lint/lint_headers.sh.py b/libcxx/test/libcxx-03/lint/lint_headers.sh.py
index ab237c968da7e..34eb5c000d237 100644
--- a/libcxx/test/libcxx-03/lint/lint_headers.sh.py
+++ b/libcxx/test/libcxx-03/lint/lint_headers.sh.py
@@ -12,6 +12,7 @@ def exclude_from_consideration(path):
     return (
         path.endswith(".txt")
         or path.endswith(".modulemap.in")
+        or os.path.basename(path) == "target.modulemap"
         or os.path.basename(path) == "__config"
         or os.path.basename(path) == "__config_site.in"
         or os.path.basename(path) == "libcxx.imp"



More information about the libcxx-commits mailing list