[libc++] Add a CI configuration with static libc++/libc++abi

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

GitOrigin-RevId: c504c68facc925ba76e57b9b606346d428983963
diff --git a/cmake/caches/Generic-static.cmake b/cmake/caches/Generic-static.cmake
new file mode 100644
index 0000000..4fe910c
--- /dev/null
+++ b/cmake/caches/Generic-static.cmake
@@ -0,0 +1,10 @@
+set(LIBCXX_ENABLE_SHARED OFF CACHE BOOL "")
+set(LIBCXXABI_ENABLE_SHARED OFF CACHE BOOL "")
+set(LIBUNWIND_ENABLE_SHARED OFF CACHE BOOL "")
+
+# TODO: We should switch this to a from-sratch config with static libraries
+# instead and get rid of these options.
+set(LIBCXX_LINK_TESTS_WITH_SHARED_LIBCXXABI OFF CACHE BOOL "")
+set(LIBCXX_LINK_TESTS_WITH_SHARED_LIBCXX OFF CACHE BOOL "")
+set(LIBCXXABI_LINK_TESTS_WITH_SHARED_LIBCXXABI OFF CACHE BOOL "")
+set(LIBCXXABI_LINK_TESTS_WITH_SHARED_LIBCXX OFF CACHE BOOL "")
diff --git a/utils/ci/buildkite-pipeline.yml b/utils/ci/buildkite-pipeline.yml
index 6ef42f3..dfc06fd 100644
--- a/utils/ci/buildkite-pipeline.yml
+++ b/utils/ci/buildkite-pipeline.yml
@@ -111,6 +111,17 @@
         - exit_status: -1  # Agent was lost
           limit: 2
 
+  - label: "Static libraries"
+    command: "libcxx/utils/ci/run-buildbot generic-static"
+    artifact_paths:
+      - "**/test-results.xml"
+    agents:
+      queue: "libcxx-builders"
+    retry:
+      automatic:
+        - exit_status: -1  # Agent was lost
+          limit: 2
+
   - label: "GCC/C++20"
     command: "libcxx/utils/ci/run-buildbot generic-gcc"
     artifact_paths:
diff --git a/utils/ci/run-buildbot b/utils/ci/run-buildbot
index eb48ebd..38d9bc4 100755
--- a/utils/ci/run-buildbot
+++ b/utils/ci/run-buildbot
@@ -86,14 +86,14 @@
 }
 
 function check-cxx-cxxabi() {
+    echo "--- Installing libc++ and libc++abi to a fake location"
+    ${NINJA} -vC "${BUILD_DIR}" install-cxx install-cxxabi
+
     echo "+++ Running the libc++ tests"
     ${NINJA} -vC "${BUILD_DIR}" check-cxx
 
     echo "+++ Running the libc++abi tests"
     ${NINJA} -vC "${BUILD_DIR}" check-cxxabi
-
-    echo "--- Installing libc++ and libc++abi to a fake location"
-    ${NINJA} -vC "${BUILD_DIR}" install-cxx install-cxxabi
 }
 
 # TODO: The goal is to test this against all configurations. We should also move
@@ -183,6 +183,13 @@
     generate-cmake -C "${MONOREPO_ROOT}/libcxx/cmake/caches/Generic-noexceptions.cmake"
     check-cxx-cxxabi
 ;;
+generic-static)
+    export CC=clang
+    export CXX=clang++
+    clean
+    generate-cmake -C "${MONOREPO_ROOT}/libcxx/cmake/caches/Generic-static.cmake"
+    check-cxx-cxxabi
+;;
 generic-32bit)
     export CC=clang
     export CXX=clang++
@@ -194,9 +201,7 @@
     export CC=gcc
     export CXX=g++
     clean
-    # FIXME: Re-enable experimental testing on GCC. GCC cares about the order
-    #        in which we link -lc++experimental, which causes issues.
-    generate-cmake -DLIBCXX_ENABLE_EXPERIMENTAL_LIBRARY=OFF
+    generate-cmake
     check-cxx-cxxabi
 ;;
 generic-asan)
diff --git a/utils/libcxx/test/config.py b/utils/libcxx/test/config.py
index 955ef79..57b729b 100644
--- a/utils/libcxx/test/config.py
+++ b/utils/libcxx/test/config.py
@@ -124,7 +124,7 @@
         self.configure_obj_root()
         self.cxx_stdlib_under_test = self.get_lit_conf('cxx_stdlib_under_test', 'libc++')
         self.cxx_library_root = self.get_lit_conf('cxx_library_root', self.libcxx_obj_root)
-        self.abi_library_root = self.get_lit_conf('abi_library_root', None)
+        self.abi_library_root = self.get_lit_conf('abi_library_root') or self.cxx_library_root
         self.cxx_runtime_root = self.get_lit_conf('cxx_runtime_root', self.cxx_library_root)
         self.abi_runtime_root = self.get_lit_conf('abi_runtime_root', self.abi_library_root)
         self.configure_compile_flags()
diff --git a/utils/libcxx/test/dsl.py b/utils/libcxx/test/dsl.py
index 012d13a..7f4dcec 100644
--- a/utils/libcxx/test/dsl.py
+++ b/utils/libcxx/test/dsl.py
@@ -211,9 +211,12 @@
   return {m: int(v.rstrip('LlUu')) for (m, v) in allMacros.items() if m.startswith('__cpp_')}
 
 
-def _addToSubstitution(substitutions, key, value):
+def _appendToSubstitution(substitutions, key, value):
   return [(k, v + ' ' + value) if k == key else (k, v) for (k, v) in substitutions]
 
+def _prependToSubstitution(substitutions, key, value):
+  return [(k, value + ' ' + v) if k == key else (k, v) for (k, v) in substitutions]
+
 
 class ConfigAction(object):
   """
@@ -285,7 +288,7 @@
   def applyTo(self, config):
     flag = self._getFlag(config)
     assert hasCompileFlag(config, flag), "Trying to enable flag {}, which is not supported".format(flag)
-    config.substitutions = _addToSubstitution(config.substitutions, '%{flags}', flag)
+    config.substitutions = _appendToSubstitution(config.substitutions, '%{flags}', flag)
 
   def pretty(self, config, litParams):
     return 'add {} to %{{flags}}'.format(self._getFlag(config))
@@ -304,7 +307,7 @@
   def applyTo(self, config):
     flag = self._getFlag(config)
     assert hasCompileFlag(config, flag), "Trying to enable compile flag {}, which is not supported".format(flag)
-    config.substitutions = _addToSubstitution(config.substitutions, '%{compile_flags}', flag)
+    config.substitutions = _appendToSubstitution(config.substitutions, '%{compile_flags}', flag)
 
   def pretty(self, config, litParams):
     return 'add {} to %{{compile_flags}}'.format(self._getFlag(config))
@@ -312,7 +315,7 @@
 
 class AddLinkFlag(ConfigAction):
   """
-  This action adds the given flag to the %{link_flags} substitution.
+  This action appends the given flag to the %{link_flags} substitution.
 
   The flag can be a string or a callable, in which case it is called with the
   configuration to produce the actual flag (as a string).
@@ -323,10 +326,29 @@
   def applyTo(self, config):
     flag = self._getFlag(config)
     assert hasCompileFlag(config, flag), "Trying to enable link flag {}, which is not supported".format(flag)
-    config.substitutions = _addToSubstitution(config.substitutions, '%{link_flags}', flag)
+    config.substitutions = _appendToSubstitution(config.substitutions, '%{link_flags}', flag)
 
   def pretty(self, config, litParams):
-    return 'add {} to %{{link_flags}}'.format(self._getFlag(config))
+    return 'append {} to %{{link_flags}}'.format(self._getFlag(config))
+
+
+class PrependLinkFlag(ConfigAction):
+  """
+  This action prepends the given flag to the %{link_flags} substitution.
+
+  The flag can be a string or a callable, in which case it is called with the
+  configuration to produce the actual flag (as a string).
+  """
+  def __init__(self, flag):
+    self._getFlag = lambda config: flag(config) if callable(flag) else flag
+
+  def applyTo(self, config):
+    flag = self._getFlag(config)
+    assert hasCompileFlag(config, flag), "Trying to enable link flag {}, which is not supported".format(flag)
+    config.substitutions = _prependToSubstitution(config.substitutions, '%{link_flags}', flag)
+
+  def pretty(self, config, litParams):
+    return 'prepend {} to %{{link_flags}}'.format(self._getFlag(config))
 
 
 class AddOptionalWarningFlag(ConfigAction):
@@ -344,7 +366,7 @@
     flag = self._getFlag(config)
     # Use -Werror to make sure we see an error about the flag being unsupported.
     if hasCompileFlag(config, '-Werror ' + flag):
-      config.substitutions = _addToSubstitution(config.substitutions, '%{compile_flags}', flag)
+      config.substitutions = _appendToSubstitution(config.substitutions, '%{compile_flags}', flag)
 
   def pretty(self, config, litParams):
     return 'add {} to %{{compile_flags}}'.format(self._getFlag(config))
diff --git a/utils/libcxx/test/params.py b/utils/libcxx/test/params.py
index fef2543..99bd1f4 100644
--- a/utils/libcxx/test/params.py
+++ b/utils/libcxx/test/params.py
@@ -90,7 +90,7 @@
             help="Whether to enable tests for experimental C++ libraries (typically Library Fundamentals TSes).",
             actions=lambda experimental: [] if not experimental else [
               AddFeature('c++experimental'),
-              AddLinkFlag('-lc++experimental')
+              PrependLinkFlag('-lc++experimental')
             ]),
 
   Parameter(name='long_tests', choices=[True, False], type=bool, default=True,