mirror-llvm-project/libcxx/utils/test-at-commit
Louis Dionne 7f140055b7
[libc++] Pin down the compiler in the various benchmark scripts and jobs (#211563)
We have various scripts that build and test the library at pinpointed
commits: benchmark-historical, run-benchmarks, build-at-commit and
test-at-commit. They were handling the compiler in different ways: some
scripts would just run the libc++ build (or test suite configuration)
without specifying the compiler, which means the $CXX environment
variable was used if present. Other scripts (e.g. run-benchmarks) would
accept the compiler as an argument, but would fail to pass it down when
configuring the test suite, which led to issues.

This patch passes the compiler explicitly in all scripts: this removes
any potential confusion around how the compiler should be specified (env
var or argument). The only exception is build-at-commit, where the
compiler is still specified by passing the appropriate CMake arguments.
The reason for this exception is that passing arguments to CMake is
actually the way we want to configure aspects of the build (and the test
suite) in the long term, it's just that the test suite doesn't support
this cleanly due to the CMake/Lit split at the moment.

In the longer term, `test-at-commit` should also lose its `--compiler`
argument in favour of being able to pass CMake parameters to the test
suite configuration, but we are not there yet.
2026-07-23 13:21:11 -04:00

145 lines
6.4 KiB
Python
Executable file

#!/usr/bin/env python3
import argparse
import os
import pathlib
import subprocess
import sys
import tempfile
PARENT_DIR = pathlib.Path(os.path.dirname(os.path.abspath(__file__)))
LIT_CONFIG_FILE = """
#
# This testing configuration handles running the test suite against a version
# of libc++ installed at the given path.
#
config.substitutions.append(('%{{flags}}',
'-pthread' + (' -isysroot {{}}'.format('@CMAKE_OSX_SYSROOT@') if '@CMAKE_OSX_SYSROOT@' else '')
))
config.substitutions.append(('%{{compile_flags}}', '-nostdinc++ -I {INSTALL_ROOT}/include/c++/v1 -I %{{libcxx-dir}}/test/support'))
config.substitutions.append(('%{{link_flags}}', '-nostdlib++ -L {INSTALL_ROOT}/lib -Wl,-rpath,{INSTALL_ROOT}/lib -lc++'))
config.substitutions.append(('%{{exec}}', '%{{executor}} --execdir %{{temp}} -- '))
import os, site
site.addsitedir(os.path.join('@LIBCXX_SOURCE_DIR@', 'utils'))
import libcxx.test.params, libcxx.test.config
libcxx.test.config.configure(
libcxx.test.params.DEFAULT_PARAMETERS,
libcxx.test.features.DEFAULT_FEATURES,
config,
lit_config
)
"""
# Unofficial list of directories required to build libc++. This is a best guess that should work
# when checking out the monorepo at most commits, but it's technically not guaranteed to work
# (especially for much older commits).
LIBCXX_REQUIRED_DIRECTORIES = [
'libcxx',
'libcxxabi',
'llvm/cmake',
'llvm/utils/llvm-lit',
'llvm/utils/lit',
'runtimes',
'cmake',
'third-party/benchmark',
'libc'
]
def checkout_subdirectories(git_repo, commit, paths, destination):
"""
Produce a copy of the specified Git-tracked files/directories at the given commit.
The resulting files and directories at placed at the given location.
"""
with tempfile.TemporaryDirectory() as tmp:
tmpfile = os.path.join(tmp, 'archive.tar.gz')
git_archive = ['git', '-C', git_repo, 'archive', '--format', 'tar.gz', '--output', tmpfile, commit, '--'] + list(paths)
subprocess.check_call(git_archive)
os.makedirs(destination, exist_ok=True)
subprocess.check_call(['tar', '-x', '-z', '-f', tmpfile, '-C', destination])
def exists_in_commit(git_repo, commit, path):
"""
Return whether the given path (file or directory) existed at the given commit.
"""
cmd = ['git', '-C', git_repo, 'show', f'{commit}:{path}']
result = subprocess.call(cmd, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
return result == 0
def directory_path(string):
if os.path.isdir(string):
return pathlib.Path(string)
else:
raise NotADirectoryError(string)
def main(argv):
parser = argparse.ArgumentParser(
prog='test-at-commit',
description='Test the provided libc++ installation against the test suite at the specified commit (or '
'the currently checked-out sources by default). This makes it easier to perform historical '
'analyses of libc++ behavior, gather historical performance data, bisect issues, and so on.')
parser.add_argument('--build-dir', '-B', type=pathlib.Path, required=True,
help='Path to create the build directory for running the test suite at. The results of the tests '
'are located in that directory after the run.')
parser.add_argument('--libcxx-installation', type=pathlib.Path, required=True,
help='Path to the directory where a copy of libc++ to run tests on is installed.')
parser.add_argument('--compiler', type=pathlib.Path, required=True,
help='Path to the compiler to use to run the tests.')
parser.add_argument('--test-suite-commit', type=str, required=False,
help='Commit to use for the test suite. If left unspecified, the currently checked-out version of the '
'test suite is used. Otherwise, the requested version is checked out in a separate directory and '
'that version of the test suite is used.')
parser.add_argument('lit_options', nargs=argparse.REMAINDER,
help='Optional arguments passed to lit when running the tests. Should be provided last and '
'separated from other arguments with a `--`.')
parser.add_argument('--git-repo', type=directory_path, default=pathlib.Path(os.getcwd()),
help='Optional path to the Git repository to use. By default, the current working directory is used.')
args = parser.parse_args(argv)
args.build_dir = args.build_dir.resolve()
args.libcxx_installation = args.libcxx_installation.resolve()
# Gather lit options
lit_options = []
if args.lit_options is not None:
if args.lit_options[0] != '--':
raise ArgumentError('For clarity, Lit options must be separated from other options by --')
lit_options = args.lit_options[1:]
# This is the list of directories that must be cleaned up before we return
tempdirs = []
try:
# If needed, check out the test suite at the commit we're going to use for the suite
if args.test_suite_commit is None:
test_suite_sources = args.git_repo
else:
tempdirs.append(tempfile.TemporaryDirectory())
test_suite_sources = pathlib.Path(tempdirs[-1].name)
checkout_dirs = [d for d in LIBCXX_REQUIRED_DIRECTORIES if exists_in_commit(args.git_repo, args.test_suite_commit, d)]
checkout_subdirectories(args.git_repo, args.test_suite_commit, checkout_dirs, test_suite_sources)
# Configure the test suite in the specified build directory
args.build_dir.mkdir(parents=True, exist_ok=True)
lit_cfg = (args.build_dir / 'temp_lit_cfg.cfg.in').absolute()
with open(lit_cfg, 'w') as f:
f.write(LIT_CONFIG_FILE.format(INSTALL_ROOT=args.libcxx_installation))
test_suite_cmd = ['cmake', '-B', args.build_dir, '-S', test_suite_sources / 'runtimes', '-G', 'Ninja']
test_suite_cmd += ['-D', f'CMAKE_CXX_COMPILER={args.compiler}']
test_suite_cmd += ['-D', 'LLVM_ENABLE_RUNTIMES=libcxx;libcxxabi']
test_suite_cmd += ['-D', 'LIBCXXABI_USE_LLVM_UNWINDER=OFF']
test_suite_cmd += ['-D', f'LIBCXX_TEST_CONFIG={lit_cfg}']
subprocess.check_call(test_suite_cmd)
# Run the specified tests against the built library
lit_cmd = [PARENT_DIR / 'libcxx-lit', args.build_dir] + lit_options
subprocess.check_call(lit_cmd)
finally:
for d in tempdirs:
d.cleanup()
if __name__ == '__main__':
main(sys.argv[1:])