llvm-project/bolt/utils/nfc-check-setup.py
Paschalis Mpeis 3408f7b42f
[BOLT] Guard llvm-bolt-wrapper logic of NFC-Mode behind a flag (#146209)
Buildbot (`BOLTBuilder`) no longer relies on a wrapper script to run
tests. This
patch guards the wrapper logic under a flag that is disabled by default.
This
it allows to:
- Eliminate the need for special handling in some tests.
- Fix the issue of a wrapper loop (described below)
- Simplify the NFC-Mode setup.

**Background:**
Previously, tests ran unconditionally, which also compiled any missing
utilities
and the unit tests.

The `nfc-check-setup.py` created:
- `llvm-bolt.new`, renamed from the current compilation
- `llvm-bolt.old`, built from the previous SHA
- `llvm-bolt`: a python wrapper pointing to `llvm-bolt.new`

Current behaviour and wrapper issue:
As before, the old/new binaries identify whether a patch affects BOLT.
If so,
`ninja check-bolt` builds missing dependencies and run tests,
overwriting the
`llvm-bolt` wrapper with a binary.

However, if Ninja reports:
```
ninja: no work to do.
```

the wrapper remains in place. If the next commit also does no work,
`nfc-check-setup.py` renames the existing wrapper to `llvm-bolt.new`,
causing an
infinite loop.

Allowing to disable the wrapper logic prevents this scenario and
simplifies the flow.


**Test plan:**

Creates llvm-bolt.new and llvm-bolt.old and stays on previous revision:
```
./nfc-check-setup.py build
```

Creates llvm-bolt.new and llvm-bolt.old and returns on current revision:
```
./nfc-check-setup.py build --switch-back
```

Creates llvm-bolt.new and llvm-bolt.old, returns on current revision,
and
creates a wrapper:
```
./nfc-check-setup.py build --switch-back --create-wrapper
```

Creates llvm-bolt.new and llvm-bolt.old, and passes an invalid argument
to the
wrapper:
```
./nfc-check-setup.py build --switch-back --create-wrapper --random-arg
```
2025-07-22 09:04:00 +03:00

180 lines
5.8 KiB
Python
Executable File

#!/usr/bin/env python3
import argparse
import os
import re
import shlex
import subprocess
import sys
import textwrap
def get_relevant_bolt_changes(dir: str) -> str:
# Return a list of bolt source changes that are relevant to testing.
all_changes = subprocess.run(
shlex.split("git show HEAD --name-only --pretty=''"),
cwd=dir,
text=True,
stdout=subprocess.PIPE,
)
keep_bolt = subprocess.run(
shlex.split("grep '^bolt'"),
input=all_changes.stdout,
text=True,
stdout=subprocess.PIPE,
)
keep_relevant = subprocess.run(
shlex.split(
"grep -v -e '^bolt/docs' -e '^bolt/utils/docker' -e '^bolt/utils/dot2html'"
),
input=keep_bolt.stdout,
text=True,
stdout=subprocess.PIPE,
)
return keep_relevant.stdout
def get_git_ref_or_rev(dir: str) -> str:
# Run 'git symbolic-ref -q --short HEAD || git rev-parse --short HEAD'
cmd_ref = "git symbolic-ref -q --short HEAD"
ref = subprocess.run(
shlex.split(cmd_ref), cwd=dir, text=True, stdout=subprocess.PIPE
)
if not ref.returncode:
return ref.stdout.strip()
cmd_rev = "git rev-parse --short HEAD"
return subprocess.check_output(shlex.split(cmd_rev), cwd=dir, text=True).strip()
def main():
parser = argparse.ArgumentParser(
description=textwrap.dedent(
"""
This script builds two versions of BOLT (with the current and
previous revision).
"""
)
)
parser.add_argument(
"build_dir",
nargs="?",
default=os.getcwd(),
help="Path to BOLT build directory, default is current " "directory",
)
parser.add_argument(
"--create-wrapper",
default=False,
action="store_true",
help="Sets up llvm-bolt as a symlink to llvm-bolt-wrapper. Passes the options through to llvm-bolt-wrapper.",
)
parser.add_argument(
"--check-bolt-sources",
default=False,
action="store_true",
help="Create a marker file (.llvm-bolt.changes) if any relevant BOLT sources are modified",
)
parser.add_argument(
"--switch-back",
default=False,
action="store_true",
help="Checkout back to the starting revision",
)
parser.add_argument(
"--cmp-rev",
default="HEAD^",
help="Revision to checkout to compare vs HEAD",
)
# When creating a wrapper, pass any unknown arguments to it. Otherwise, die.
args, wrapper_args = parser.parse_known_args()
if not args.create_wrapper and len(wrapper_args) > 0:
parser.parse_args()
bolt_path = f"{args.build_dir}/bin/llvm-bolt"
source_dir = None
# find the repo directory
with open(f"{args.build_dir}/CMakeCache.txt") as f:
for line in f:
m = re.match(r"LLVM_SOURCE_DIR:STATIC=(.*)", line)
if m:
source_dir = m.groups()[0]
if not source_dir:
sys.exit("Source directory is not found")
# build the current commit
subprocess.run(
shlex.split("cmake --build . --target llvm-bolt"), cwd=args.build_dir
)
# rename llvm-bolt
os.replace(bolt_path, f"{bolt_path}.new")
# memorize the old hash for logging
old_ref = get_git_ref_or_rev(source_dir)
if args.check_bolt_sources:
marker = f"{args.build_dir}/.llvm-bolt.changes"
if os.path.exists(marker):
os.remove(marker)
file_changes = get_relevant_bolt_changes(source_dir)
# Create a marker file if any relevant BOLT source files changed.
if len(file_changes) > 0:
print(f"BOLT source changes were found:\n{file_changes}")
open(marker, "a").close()
# determine whether a stash is needed
stash = subprocess.run(
shlex.split("git status --porcelain"),
cwd=source_dir,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
text=True,
).stdout
if stash:
# save local changes before checkout
subprocess.run(shlex.split("git stash push -u"), cwd=source_dir)
# check out the previous/cmp commit
subprocess.run(shlex.split(f"git checkout -f {args.cmp_rev}"), cwd=source_dir)
# get the parent commit hash for logging
new_ref = get_git_ref_or_rev(source_dir)
# build the previous commit
subprocess.run(
shlex.split("cmake --build . --target llvm-bolt"), cwd=args.build_dir
)
# rename llvm-bolt
os.replace(bolt_path, f"{bolt_path}.old")
# symlink llvm-bolt-wrapper
if args.create_wrapper:
script_dir = os.path.dirname(os.path.abspath(__file__))
wrapper_path = f"{script_dir}/llvm-bolt-wrapper.py"
try:
# set up llvm-bolt-wrapper.ini
ini = subprocess.check_output(
shlex.split(f"{wrapper_path} {bolt_path}.old {bolt_path}.new")
+ wrapper_args,
text=True,
)
with open(f"{args.build_dir}/bin/llvm-bolt-wrapper.ini", "w") as f:
f.write(ini)
# symlink llvm-bolt-wrapper
os.symlink(wrapper_path, bolt_path)
except Exception as e:
sys.exit("Failed to create a wrapper:\n" + str(e))
if args.switch_back:
if stash:
subprocess.run(shlex.split("git stash pop"), cwd=source_dir)
subprocess.run(shlex.split(f"git checkout {old_ref}"), cwd=source_dir)
else:
print(
f"The repository {source_dir} has been switched from {old_ref} "
f"to {new_ref}. Local changes were stashed. Switch back using\n\t"
f"git checkout {old_ref}\n"
)
print(
f"Build directory {args.build_dir} is ready to run BOLT tests, e.g.\n"
"\tbin/llvm-lit -sv tools/bolt/test\nor\n"
"\tbin/llvm-lit -sv tools/bolttests"
)
if __name__ == "__main__":
main()