12
12
# It is assumed that the user invoking this script has permissions to use
13
13
# docker. On Linux this means the user must be in the `docker` group.
14
14
15
- DEFAULT_DOCKERFILE=.buildbot_dockerfile_default
16
15
17
16
# Arguments to enable PT and rr support in docker.
18
17
CAP_ARGS=" --cap-add CAP_PERFMON --cap-add SYS_PTRACE --security-opt seccomp=unconfined"
19
18
19
+ TRYCI_BUILD_CTXT=" ."
20
+ TRYCI_REMOTE_CLONE_DEPTH=100 # This is the same as on our CI server
21
+ TRYCI_DOCKERFILES=" "
22
+ TRYCI_DEFAULT_SCRIPT=" .buildbot.sh"
23
+ TRYCI_DOCKERFILE_BASE=.buildbot_dockerfile_
24
+ TRYCI_DEFAULT_DOCKERFILE=${TRYCI_DOCKERFILE_BASE} default
25
+ TRYCI_BUILD_PREFIX=" "
20
26
21
27
set -e
22
28
29
+ usage () {
30
+ cat << EOF
31
+ Runs a soft-dev CI job.
32
+ Must be run from the same directory as the job's .buildbot.sh file.
33
+
34
+ usage: tryci [-p] [-r server_name] [-b <ref>] [-h]
35
+
36
+ Options:
37
+ -p, --post-mortem
38
+ Attach a shell to the image to prod around if the build fails.
39
+
40
+ -r, --remote server_name
41
+ Specify the server \` server_name\` to run the CI job on over SSH.
42
+ Useful if you want to test on a remote CI environment.
43
+
44
+ -c, --checkout <ref>
45
+ Tryci will run a CI job from a clone of <ref> instead of the
46
+ working tree. Valid formats:
47
+ - Branch name (e.g., main, feature/x)
48
+ - Commit hash (e.g., a1b2c3d)
49
+ - Tag (e.g., v1.0.0)
50
+ - Remote-tracking branch (e.g., origin/main)
51
+ - Remote URL with branch (e.g., https://github.com/user/repo#branch)
52
+ Useful for debugging the exact version of a CI job that failed on CI.
53
+
54
+ -h, --help
55
+ Show this help message and exit.
56
+ EOF
57
+ }
58
+
23
59
error () { printf " \e[31m[ERROR]\e[0m %s\n" " $1 " >&2 ; }
24
60
25
- run_image () {
26
- # Extract the dockerfile suffix. E.g. for '.buildbot_dockerfile_myrepo'
27
- # it's 'myimage'.
28
- suffix=` echo " $1 " | sed -e ' s/^.buildbot_dockerfile_//' `
61
+ cleanup () {
62
+ if [[ -n " $tmpdir " && -d " $tmpdir " ]]; then
63
+ rm -rf -- " $tmpdir "
64
+ fi
65
+ }
66
+
67
+ trap cleanup EXIT
68
+
69
+ resolve_build_ctxt () {
70
+ # When we pass -c/--checkout <ref> we want the build context to be a clone
71
+ # of <ref>. There isn't really a nice way to do this in docker so this
72
+ # function resolves the build context depending on the contents of <ref>.
73
+
74
+ # First, the simple case: no '--checkout <ref>' option was used. We keep
75
+ # the default build context as the current working directory and generate a
76
+ # best-guess image prefix in the format: local-<pwd>:dirty.
77
+ if [ -z " $ref " ]; then
78
+ if [ ! -f ${TRYCI_DEFAULT_SCRIPT} ]; then
79
+ error " ${TRYCI_DEFAULT_SCRIPT} not found in directory: $pwd " .
80
+ exit 1
81
+ else
82
+ TRYCI_BUILD_PREFIX=" local-$( basename $( pwd) ) :dirty"
83
+ return
84
+ fi
85
+ fi
29
86
30
- # Generate an identifier for the repository.
31
- if [ " ${REPOSITORY} " != " " ]; then
32
- # Buildbot will set $REPOSITORY to a git url.
87
+ # Second, some --checkout <ref> was provided. If we get here we can't
88
+ # simply use the working tree anymore. We need to find out what <ref> is,
89
+ # clone it into a tmpdir, and set that as our build context.
90
+
91
+ tmpdir=$( mktemp -d) # removed with a cleanup trap on EXIT.
92
+ TRYCI_BUILD_CTXT=" $tmpdir "
93
+
94
+ if [[ " $ref " =~ ^(https? | git| ssh):// ]] || [[ " $ref " =~ ^[^/]+@[^:]+: ]]; then
95
+ # <ref> is a remote repo. We'll need to extract any branch/commit/tag
96
+ # that may have been provided. For example, if passed:
97
+ #
98
+ # '--checkout https://github.com/ykjit/yk#trying'
33
99
#
34
- # Transform URLs like `https://github.com/user/repo` into
35
- # `github.com_user_repo`.
36
- repo=` echo ${REPOSITORY} | \
37
- sed -E ' s/https:\/\/|git:\/\/(.*)/\1/g' | tr ' /' ' _' | sed -E ' s/_$//' `
100
+ # We must extract 'trying' and ensure our clone is checked out on that
101
+ # branch.
102
+ local base_url=" ${ref%%#* } "
103
+ local tag=$( [[ " $base_url " != " $ref " ]] && echo " ${ref#*# } " )
104
+
105
+ if [ -n " $tag " ]; then
106
+ git clone --no-checkout --depth=" $TRYCI_REMOTE_CLONE_DEPTH " " $base_url " " $tmpdir "
107
+ # FIXME: this can fail if the requested commit hash is deeper than
108
+ # TRYCI_REMOTE_CLONE_DEPTH.
109
+ git -C " $tmpdir " checkout " $tag "
110
+ else
111
+ git clone --depth=" $TRYCI_REMOTE_CLONE_DEPTH " " $base_url " " $tmpdir "
112
+ fi
113
+
114
+ # For the image tag, transform URLs like `https://github.com/user/repo`
115
+ # into `github.com_user_repo`.
116
+ local prefix=$( echo $base_url | \
117
+ sed -E ' s/https:\/\/|git:\/\/(.*)/\1/g' | \
118
+ tr ' /' ' _' | \
119
+ sed -E ' s/_$//' )
38
120
else
39
- # If repository isn't set, make a pseudo-name that can be used in place
40
- # of the proper repo identifier.
41
- dir=` pwd`
42
- repo=" local-` basename ${dir} ` "
121
+ # Finally, <ref> is branch/tag/commit of the local repo. This is a bit
122
+ # trickier because we want to ensure that any checkout only hits refs
123
+ # in our local git cache and never tries to pull from remote.
124
+ local prefix=" local-$( basename $( pwd) ) "
125
+ local tag=$ref
126
+ if git rev-parse --verify --quiet " $ref " > /dev/null; then
127
+ # Before we waste time doing anything, lets check if a CI build
128
+ # script even exists at the given ref.
129
+ if ! git cat-file -e $ref :$TRYCI_DEFAULT_SCRIPT 2> /dev/null; then
130
+ error " CI script '$TRYCI_DEFAULT_SCRIPT ' does not exist at revision: $ref "
131
+ exit 1
132
+ fi
133
+
134
+ git clone --no-checkout . " $tmpdir "
135
+
136
+ # This is the important part. By copying the refs and modules over
137
+ # from the working tree, we ensure that any subsequent `git
138
+ # submodule --init` call is either a no-op or checks out an older
139
+ # commit that we already have downloaded. It will never have to
140
+ # clone from the remote.
141
+ cp -r .git/refs " $tmpdir /.git/"
142
+ cp -r .git/modules " $tmpdir /.git/"
143
+ cp .git/config " $tmpdir /.git/config"
144
+
145
+ # Finally, we checkout out the desired ref.
146
+ git -C " $tmpdir " checkout " $ref "
147
+ else
148
+ echo " $ref does not exist locally. Please fetch first if you need it."
149
+ exit 1
150
+ fi
43
151
fi
44
152
45
- # Image name must be unique to the buildbot worker so that workers don't clash.
46
- image_tag=${LOGNAME} -${repo} -${suffix}
153
+ # This is important for projects like 'alloy' and 'yk' because we
154
+ # deliberatly did not clone recursively.
155
+ git -C " $tmpdir " submodule update --progress --init --recursive
156
+
157
+ if [[ " $tag " =~ ^[0-9a-fA-F]{6,40}$ ]]; then
158
+ # If <ref> contained a commit hash, we must shorten it to the first 6
159
+ # chars because docker tags have a strict length limit.
160
+ tag=" ${tag: 0: 6} "
161
+ fi
162
+ TRYCI_BUILD_PREFIX=" $prefix ${tag: +" :$tag " } "
163
+ }
47
164
48
- # The container will be run as the worker's "host user". The image is
49
- # expected to create a user with the same UID.
165
+ build_image () {
166
+ local dockerfile=" $TRYCI_BUILD_CTXT /$1 "
167
+ # Extract the dockerfile suffix. E.g. for '.buildbot_dockerfile_myrepo'
168
+ # it's 'myrepo'.
169
+ local suffix=$( echo " $1 " | sed -e " s/^${TRYCI_DOCKERFILE_BASE} //" )
170
+
171
+ # Create a unique image tag so that old docker image builds can be reused.
172
+ image_tag=${LOGNAME} -${TRYCI_BUILD_PREFIX} -${suffix}
50
173
ci_uid=` id -u`
51
174
52
- # Build an image for the CI job.
53
- docker build --build-arg CI_UID=${ci_uid} --build-arg CI_RUNNER=tryci -t ${image_tag} --file $1 .
175
+ docker build \
176
+ --build-arg CI_UID=" ${ci_uid} " \
177
+ --build-arg CI_RUNNER=tryci \
178
+ -t " ${image_tag} " \
179
+ --file " ${dockerfile} " \
180
+ " ${TRYCI_BUILD_CTXT} "
54
181
55
- # Run the CI job.
56
- #
57
- # We run the container with CAP_PERFMON capabilities to
58
- # allow perf_event_open() to work (for those repos requiring the use
59
- # of e.g. Intel PT).
60
- container_tag=` docker create ${CAP_ARGS} -u ${ci_uid} -v /opt/ykllvm_cache:/opt/ykllvm_cache:ro ${image_tag} `
182
+ container_tag=$( docker create \
183
+ ${CAP_ARGS} \
184
+ -u " ${ci_uid} " \
185
+ -v /opt/ykllvm_cache:/opt/ykllvm_cache:ro \
186
+ " ${image_tag} " )
187
+ }
188
+
189
+ run_image () {
61
190
docker start -a ${container_tag}
62
191
status=$?
63
192
@@ -82,17 +211,9 @@ run_image() {
82
211
return ${status}
83
212
}
84
213
85
- usage () {
86
- echo " Runs a soft-dev CI job."
87
- echo " Must be run from the same directory as the job's .buildbot.sh file."
88
- echo " usage: tryci [-p] [-r server_name]"
89
- echo " -p, --post-mortem Attach a shell to the image to prod around if the build fails."
90
- echo " -r, --remote server_name Specify the server server_name to run the CI job on over SSH."
91
- }
92
-
93
- # Parse arguments
94
214
pm=0
95
215
server=" "
216
+ ref=" "
96
217
97
218
while [ $# -gt 0 ]; do
98
219
case $1 in
@@ -102,8 +223,15 @@ while [ $# -gt 0 ]; do
102
223
;;
103
224
-r | --remote)
104
225
server=" $2 "
105
- shift
106
- shift
226
+ shift 2
227
+ ;;
228
+ -c | --checkout)
229
+ ref=" $2 "
230
+ shift 2
231
+ ;;
232
+ -h | --help)
233
+ usage
234
+ exit 0
107
235
;;
108
236
* )
109
237
usage
@@ -122,13 +250,25 @@ if [ ! -z ${server} ]; then
122
250
export DOCKER_HOST=" ssh://${server} "
123
251
fi
124
252
253
+ # Start by getting the build context for this CI job.
254
+ resolve_build_ctxt
125
255
126
- # Collect dockerfiles to test inside of.
127
- ci_dockerfiles=` ls .buildbot_dockerfile_* 2> /dev/null || true`
256
+ TRYCI_DOCKERFILES=$(
257
+ find " $TRYCI_BUILD_CTXT " \
258
+ -name " $TRYCI_DOCKERFILE_BASE *" \
259
+ -maxdepth 1 \
260
+ -type f \
261
+ -exec basename {} \; \
262
+ 2> /dev/null
263
+ )
264
+
265
+ if [ ! -f " $TRYCI_BUILD_CTXT /$TRYCI_DEFAULT_SCRIPT " ]; then
266
+ error " ${TRYCI_DEFAULT_SCRIPT} not found in repository."
267
+ fi
128
268
129
269
# If the repo doesn't define any images, then use the default image.
130
- if [ " ${ci_dockerfiles } " = " " ]; then
131
- cat << EOF > ${DEFAULT_DOCKERFILE }
270
+ if [ " ${TRYCI_DOCKERFILES } " = " " ]; then
271
+ cat << EOF > ${TRYCI_DEFAULT_DOCKERFILE }
132
272
FROM debian:bullseye
133
273
ARG CI_UID
134
274
RUN useradd -m -u \$ {CI_UID} ci
@@ -137,9 +277,9 @@ if [ "${ci_dockerfiles}" = "" ]; then
137
277
WORKDIR /ci
138
278
RUN chown \$ {CI_UID}:\$ {CI_UID} .
139
279
COPY --chown=\$ {CI_UID}:\$ {CI_UID} . .
140
- CMD sh -x .buildbot.sh
280
+ CMD sh -x ${TRYCI_DEFAULT_SCRIPT}
141
281
EOF
142
- ci_dockerfiles =${DEFAULT_DOCKERFILE }
282
+ TRYCI_DOCKERFILES =${TRYCI_DEFAULT_DOCKERFILE }
143
283
fi
144
284
145
285
# Sequentially run the images.
149
289
# buildbot run separate jobs in parallel.
150
290
num_failed=0
151
291
failed_dockerfiles=" "
152
- for dockerfile in ${ci_dockerfiles } ; do
292
+ for dockerfile in ${TRYCI_DOCKERFILES } ; do
153
293
echo " CI> Running ${dockerfile} ..."
154
294
rc=0
155
- run_image ${dockerfile} || rc=$?
295
+ build_image ${dockerfile}
296
+ run_image $container_tag || rc=$?
156
297
if [ $rc -eq 0 ]; then
157
298
echo " CI> ${dockerfile} : [ OK ]"
158
299
else
0 commit comments