Skip to content

Commit decc8fe

Browse files
[DevOps] Add devops/scripts/sycl-bisect.bash (#6534)
This is a utility for running git-bisect on sycl-like branches that may contain upstream LLVM commits in the bisect range. Its main feature is automatically merging these commits into a sycl-based branch before testing them, and supports conveniently testing commits by specifying a LIT test to run or a custom bash command.
1 parent 33746d8 commit decc8fe

File tree

2 files changed

+341
-0
lines changed

2 files changed

+341
-0
lines changed

devops/scripts/sycl-bisect.bash

Lines changed: 206 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,206 @@
1+
#!/bin/bash
2+
3+
# This is a script for running git-bisect on sycl, sycl-web, and sycl pulldown
4+
# branches. This is mostly a standard git-bisect, except that any upstream
5+
# commits that aren't sycl-based must be merged to a sycl-based branch before
6+
# being tested. When possible, this is done in a secondary worktree to avoid
7+
# clobbering files, which enables incremental builds and significantly improves
8+
# build times nearer to the end of the bisection.
9+
10+
# Parse options.
11+
OPTS=$(getopt -n sycl-bisect-test-commit.bash -o 'ht:c:b' -l 'help,test:,command:,command-allow-bisect-codes' -- "$@")
12+
if [[ $? != 0 ]]; then
13+
exit 1
14+
fi
15+
eval set -- "$OPTS"
16+
unset OPTS TEST COMMAND COMMAND_ALLOW_BISECT_CODES
17+
while true; do
18+
case "$1" in
19+
'-h'|'--help')
20+
cat <<HELP
21+
Usage:
22+
23+
$0 [opts] <bad commit> <good commits...>
24+
25+
-h,--help Print this help message
26+
-t,--test <test> Test commits with LIT test <test>, a test file
27+
path relative to the current working directory
28+
-c,--command <command> Test commits with command <command>, which
29+
will be executed for each commit via bash -c
30+
in the current working directory
31+
-b,--command-allow-bisect-codes Pass 125 and >127 return codes to git-bisect
32+
directly when using --command to allow the
33+
command to skip commits or stop bisection. If
34+
this option is not present, any non-zero
35+
return value is interpreted as a bad commit.
36+
37+
Examples:
38+
39+
"Clang :: SemaSYCL/accessor_inheritance.cpp" started failing on sycl-web because
40+
of one of the commits merged by 96f730774ac4. To find which one of these 96
41+
commits caused this test failure, sycl-bisect.bash can be run like this:
42+
43+
$ devops/scripts/sycl-bisect.bash 96f730774ac4 96f730774ac4^ --test clang/test/SemaSYCL/accessor_inheritance.cpp
44+
HELP
45+
exit 0
46+
;;
47+
'-t'|'--test')
48+
export TEST="$2"
49+
shift 2
50+
;;
51+
'-c'|'--command')
52+
export COMMAND="$2"
53+
shift 2
54+
;;
55+
'-b'|'--command-allow-bisect-codes')
56+
export COMMAND_ALLOW_BISECT_CODES=1
57+
shift
58+
;;
59+
'--')
60+
shift
61+
break
62+
;;
63+
*)
64+
echo "Unexpected argument: $1" >&2
65+
exit 1
66+
;;
67+
esac
68+
done
69+
70+
# Validate options.
71+
if [[ "$1" == "" ]] || [[ "$2" == "" ]]; then
72+
echo "A bad rev and at least one good rev must be passed" >&2
73+
exit 1
74+
fi
75+
if [[ "$TEST" == "" ]] && [[ "$COMMAND" == "" ]]; then
76+
echo "--test <test> or --command <command> must be used to specify a test" >&2
77+
exit 1
78+
fi
79+
if [[ "$TEST" != "" ]] && [[ "$COMMAND" != "" ]]; then
80+
echo "Only one of --test <test> or --command <command> is allowed" >&2
81+
exit 1
82+
fi
83+
if [[ "$COMMAND_ALLOW_BISECT_CODES" != "" ]] && [[ "$COMMAND" == "" ]]; then
84+
echo "--command-allow-bisect-codes is only allowed with --command <command>" >&2
85+
exit 1
86+
fi
87+
88+
# Save the current working directory before switching to the repository's
89+
# top-level directory. Tests will be run relative to the original working
90+
# directory.
91+
export TEST_WD="$PWD"
92+
TOPLEVEL="$(git rev-parse --show-toplevel)"
93+
if [[ "$TOPLEVEL" == "" ]]; then
94+
exit 1
95+
fi
96+
cd "$TOPLEVEL"
97+
98+
# A commit that should be on all sycl-based branches this script works with.
99+
# This is set to the initial sycl-specific commit.
100+
export SYCL_ANCESTOR=1e0b4966ba9a
101+
102+
# This commit should be in the repository.
103+
if ! git rev-parse $SYCL_ANCESTOR &>/dev/null; then
104+
echo "Current repository is not sycl-based" >&2
105+
exit 1
106+
fi
107+
108+
# Along with all the bad/good revs.
109+
for REV in "$@"; do
110+
if ! git rev-parse --verify "$REV" &>/dev/null; then
111+
echo "'$REV' is not a valid revision" >&2
112+
exit 1
113+
fi
114+
done
115+
116+
# Make sure the build directory is set up; if not, tell the user to run
117+
# the configure step.
118+
if [[ ! -f build/CMakeCache.txt ]]; then
119+
echo "The build directory doesn't seem to be configured yet." >&2
120+
echo "Please run the configure step as documented in sycl/doc/GetStartedGuide.md:" >&2
121+
echo " https://intel.github.io/llvm-docs/GetStartedGuide.html#build-dpc-toolchain" >&2
122+
exit 1
123+
fi
124+
125+
# If this is a LIT test, make sure FileCheck and other testing utilities are
126+
# built.
127+
if [[ ! -f build/bin/FileCheck ]]; then
128+
echo "FileCheck not found; building test-depends"
129+
cmake --build build -- test-depends -j $(nproc)
130+
fi
131+
132+
# Determines if the passed commit is sycl-based or not.
133+
function is_sycl_based_commit {
134+
git merge-base --is-ancestor $SYCL_ANCESTOR "$1" 2>/dev/null
135+
}
136+
137+
# Checks if this commit/branch is a candidate for SYCL_DESCENDANT. For that to
138+
# be the case, it needs to be sycl-based and a descendant of the bad and good
139+
# commits.
140+
readonly -a REQUIRED_ANCESTORS=($SYCL_ANCESTOR "$@")
141+
function check_sycl_descendant {
142+
local ancestor
143+
for ancestor in "${REQUIRED_ANCESTORS[@]}"; do
144+
git merge-base --is-ancestor "$ancestor" "$1" &>/dev/null || return 1
145+
done
146+
export SYCL_DESCENDANT="$1"
147+
return 0
148+
}
149+
150+
# If SYCL_DESCENDANT isn't specified, poke around until a suitable commit/branch
151+
# is found.
152+
function find_sycl_descendant {
153+
[[ "$SYCL_DESCENDANT" != "" ]] && return
154+
155+
# Try the bad rev first.
156+
check_sycl_descendant "${REQUIRED_ANCESTORS[1]}" && return
157+
158+
# Try "sycl" and "sycl-web", both locally and in all the remotes.
159+
check_sycl_descendant "sycl" && return
160+
check_sycl_descendant "sycl-web" && return
161+
local remote
162+
for remote in $(git remote); do
163+
check_sycl_descendant "$remote/sycl" && return
164+
check_sycl_descendant "$remote/sycl-web" && return
165+
done
166+
167+
# Try all of the local branches.
168+
local branch
169+
for branch in $(git branch); do
170+
check_sycl_descendant "$branch" && return
171+
done
172+
173+
# Give up and ask the user to pick one.
174+
echo "Unable to find a sycl-based branch containing all bad/good commits" >&2
175+
echo "Please specify one with SYCL_DESCENDANT" >&2
176+
exit 1
177+
}
178+
find_sycl_descendant
179+
180+
# Try creating a worktree for out-of-tree merges. If this is successful, set a
181+
# trap to clean it up when bisection is complete.
182+
echo "Attempting to set up sycl-bisect-merge worktree..."
183+
git worktree add sycl-bisect-merge \
184+
&& trap "git worktree remove sycl-bisect-merge" EXIT
185+
186+
# Save test-commit-sycl-bisect.bash to a temporary file to use during the
187+
# bisection. Otherwise, git-bisect might check out a commit with a substantially
188+
# different version than sycl-bisect.bash expects, or it might check out a
189+
# commit where test-commit-sycl-bisect.bash doesn't exist at all.
190+
SCRIPT_DIR="$(dirname "${BASH_SOURCE[0]}")"
191+
TEST_COMMIT="$(mktemp --tmpdir test-commit-XXX.bash)"
192+
if [[ -f "$TEST_COMMIT" ]]; then
193+
if [[ "$(trap -p EXIT)" != "" ]]; then
194+
trap "git worktree remove sycl-bisect-merge ; rm $TEST_COMMIT" EXIT
195+
else
196+
trap "rm $TEST_COMMIT" EXIT
197+
fi
198+
cp "$SCRIPT_DIR/test-commit-sycl-bisect.bash" "$TEST_COMMIT"
199+
else
200+
TEST_COMMIT="$SCRIPT_DIR/test-commit-sycl-bisect.bash"
201+
fi
202+
203+
# Do the bisection.
204+
git bisect start --no-checkout "$@" || exit
205+
git bisect run bash "$TEST_COMMIT"
206+
git bisect reset
Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
#!/bin/bash
2+
3+
# This is a helper script for sycl-bisect.bash to test individual commits. It
4+
# must be run in the repository's top-level directory, and relies on some
5+
# environment variables set by sycl-bisect.bash to carry out merges and testing:
6+
#
7+
# SYCL_ANCESTOR: A ref on the sycl branch that is an ancestor of all of the
8+
# sycl-based commits in the bisect range; this is required for merging upstream
9+
# commits.
10+
# SYCL_DESCENDANT: A ref descended from both $SYCL_ANCESTOR and BISECT_HEAD;
11+
# this is required for finding the right point to merge upstream commits.
12+
# TEST_WD: The original working directory of sycl-bisect.bash, which should be
13+
# used as the base directory for TEST or working directory for COMMAND.
14+
# TEST: A LIT test to run, if COMMAND is not set.
15+
# COMMAND: A bash command to run, if TEST is not set.
16+
# COMMAND_ALLOW_BISECT_CODES: If this is set, return codes from COMMAND are
17+
# passed through directly to sycl-bisect so they can be used to skip commits
18+
# or stop bisection.
19+
20+
# Indicates to git-bisect to skip this commit
21+
function bad_commit { exit 125; }
22+
23+
# Indicates to git-bisect that it should exit
24+
function stop_bisect { exit 128; }
25+
26+
# Sanity check the environment variables and working directory.
27+
if [[ "$TEST" == "" ]] && [[ "$COMMAND" == "" ]]; then
28+
echo "TEST or COMMAND must be set" >&2
29+
stop_bisect
30+
fi
31+
if [[ "$TEST" != "" ]] && [[ "$COMMAND" != "" ]]; then
32+
echo "TEST and COMMAND must not both be set" >&2
33+
stop_bisect
34+
fi
35+
if [[ "$TEST_WD" == "" ]]; then
36+
echo "TEST_WD must be set" >&2
37+
stop_bisect
38+
fi
39+
if [[ "$SYCL_ANCESTOR" == "" ]]; then
40+
echo "SYCL_ANCESTOR must be set" >&2
41+
stop_bisect
42+
fi
43+
TOPLEVEL="$(git rev-parse --show-toplevel)"
44+
if [[ "$(readlink -f .)" != "$TOPLEVEL" ]]; then
45+
echo "Not at top-level directory of repository" >&2
46+
stop_bisect
47+
fi
48+
if ! git rev-parse BISECT_HEAD &>/dev/null; then
49+
echo "No BISECT_HEAD found; make sure you're using git-bisect's --no-checkout mode" >&2
50+
stop_bisect
51+
fi
52+
53+
# Determines if this commit is sycl-based or not.
54+
function is_sycl_based_commit {
55+
git merge-base --is-ancestor $SYCL_ANCESTOR "$1" 2>/dev/null
56+
}
57+
58+
if ! is_sycl_based_commit "$SYCL_DESCENDANT" || ! git merge-base --is-ancestor BISECT_HEAD "$SYCL_DESCENDANT"; then
59+
echo "SYCL_DESCENDANT not set correctly" >&2
60+
stop_bisect
61+
fi
62+
63+
# Checks if there's a sycl-bisect-merge worktree for out-of-tree merges set up.
64+
function has_sycl_bisect_merge_tree {
65+
grep "gitdir: $TOPLEVEL/.git" sycl-bisect-merge/.git &>/dev/null
66+
}
67+
68+
# If BISECT_HEAD is already sycl-based, just check it out directly.
69+
if is_sycl_based_commit BISECT_HEAD; then
70+
git checkout BISECT_HEAD || stop_bisect
71+
72+
# Otherwise, attempt a merge.
73+
else
74+
75+
# Find the next merge commit on the path to $SYCL_DESCENDANT from BISECT_HEAD.
76+
NEXT_MERGE=$(git rev-list --ancestry-path --merges "BISECT_HEAD..$SYCL_DESCENDANT" | tail -n 1)
77+
78+
# Merges aren't used upstream, so this merge should be a sycl commit.
79+
is_sycl_based_commit $NEXT_MERGE || stop_bisect
80+
81+
# If BISECT_HEAD is the LLVM commit being merged, check out the merge commit
82+
# directly.
83+
if [[ "$(git rev-parse BISECT_HEAD)" == "$(git rev-parse $NEXT_MERGE^2)" ]]; then
84+
git checkout $NEXT_MERGE || stop_bisect
85+
86+
# Otherwise, create a new merge in a separate worktree and check that out.
87+
else
88+
89+
# Switch to the worktree (if present) and check out the last sycl commit
90+
# that doesn't have BISECT_HEAD as an ancestor.
91+
BISECT_HEAD=$(git rev-parse BISECT_HEAD)
92+
SYCL_BISECT_MERGE_TREE=0
93+
if has_sycl_bisect_merge_tree; then
94+
SYCL_BISECT_MERGE_TREE=1
95+
cd sycl-bisect-merge
96+
else
97+
echo "NOTE: consider creating a sycl-bisect-merge tree to enable incremental compilation:" >&2
98+
echo "$ git worktree add sycl-bisect-merge" >&2
99+
fi
100+
git checkout $NEXT_MERGE^1 || stop_bisect
101+
102+
# Attempt an automatic merge; back out the merge and skip this commit if it
103+
# fails.
104+
if ! git merge --quiet --no-edit $BISECT_HEAD; then
105+
git merge --abort
106+
bad_commit
107+
fi
108+
MERGED=$(git rev-parse HEAD)
109+
110+
# Switch back to the main worktree and check out the new merge commit.
111+
if [[ $SYCL_BISECT_MERGE_TREE == 1 ]]; then
112+
cd ..
113+
git checkout $MERGED || stop_bisect
114+
fi
115+
fi
116+
fi
117+
118+
# Attempt a build; skip this commit if it fails.
119+
cmake --build build -- deploy-sycl-toolchain -j $(nproc) || bad_commit
120+
121+
# If a lit test is specified, run it with llvm-lit.
122+
if [[ "$TEST" != "" ]]; then
123+
build/bin/llvm-lit "$TEST_WD/$TEST"
124+
exit
125+
fi
126+
127+
# Otherwise, run the test command. If COMMAND_ALLOW_BISECT_CODES is not set,
128+
# replace any non-zero return codes with 1.
129+
cd "$TEST_WD"
130+
if [[ "$COMMAND_ALLOW_BISECT_CODES" == "" ]]; then
131+
bash -c "$COMMAND" || exit 1
132+
fi
133+
134+
# Otherwise, just run the command.
135+
bash -c "$COMMAND"

0 commit comments

Comments
 (0)