Gentoo Archives: gentoo-commits

From: Fabian Groffen <grobian@g.o>
To: gentoo-commits@l.g.o
Subject: [gentoo-commits] repo/proj/prefix:master commit in: scripts/auto-bootstraps/
Date: Thu, 21 Feb 2019 11:38:16
Message-Id: 1550749029.b7c0b89992e7b3673ad3e3ba667b81ce9868b69c.grobian@gentoo
1 commit: b7c0b89992e7b3673ad3e3ba667b81ce9868b69c
2 Author: Fabian Groffen <grobian <AT> gentoo <DOT> org>
3 AuthorDate: Thu Feb 21 11:37:09 2019 +0000
4 Commit: Fabian Groffen <grobian <AT> gentoo <DOT> org>
5 CommitDate: Thu Feb 21 11:37:09 2019 +0000
6 URL: https://gitweb.gentoo.org/repo/proj/prefix.git/commit/?id=b7c0b899
7
8 scripts/auto-bootstraps: scripts to perform unattended bootstraps
9
10 This includes the scripts that generate the results output of
11 bootstrap.prefix.bitzolder.nl.
12
13 Signed-off-by: Fabian Groffen <grobian <AT> gentoo.org>
14
15 scripts/auto-bootstraps/analyse_result.py | 178 ++++++++++++++++++++++++++++
16 scripts/auto-bootstraps/dobootstrap | 167 ++++++++++++++++++++++++++
17 scripts/auto-bootstraps/process_uploads.sh | 60 ++++++++++
18 scripts/auto-bootstraps/update_distfiles.py | 29 +++++
19 4 files changed, 434 insertions(+)
20
21 diff --git a/scripts/auto-bootstraps/analyse_result.py b/scripts/auto-bootstraps/analyse_result.py
22 new file mode 100755
23 index 0000000000..885c7fc9e7
24 --- /dev/null
25 +++ b/scripts/auto-bootstraps/analyse_result.py
26 @@ -0,0 +1,178 @@
27 +#!/usr/bin/env python3
28 +
29 +import os
30 +import glob
31 +import re
32 +import time
33 +import html
34 +
35 +resultsdir='./results'
36 +
37 +def find_last_stage(d):
38 + """
39 + Returns the last stage worked on.
40 + Bootstraps define explicitly stages 1, 2 and 3, we define some more
41 + on top of those as follows:
42 + 0 - bootstrap didn't even start (?!?) or unknown status
43 + 1 - stage 1 failed
44 + 2 - stage 2 failed
45 + 3 - stage 3 failed
46 + 4 - emerge -e world failed
47 + 5 - finished successfully
48 + """
49 +
50 + def stage_success(stagelog):
51 + with open(stagelog, 'rb') as f:
52 + line = f.readlines()[-1]
53 + res = re.match(r'^\* stage[123] successfully finished',
54 + line.decode('utf-8', 'ignore'))
55 + return res is not None
56 +
57 + if not os.path.exists(os.path.join(d, '.stage1-finished')):
58 + log = os.path.join(d, 'stage1.log')
59 + if not os.path.exists(log):
60 + return 0 # nothing exists, assume not started
61 + if not stage_success(log):
62 + return 1
63 +
64 + if not os.path.exists(os.path.join(d, '.stage2-finished')):
65 + log = os.path.join(d, 'stage2.log')
66 + if not os.path.exists(log) or not stage_success(log):
67 + return 2 # stage1 was success, so 2 must have failed
68 +
69 + if not os.path.exists(os.path.join(d, '.stage3-finished')):
70 + log = os.path.join(d, 'stage3.log')
71 + if not os.path.exists(log) or not stage_success(log):
72 + return 3 # stage2 was success, so 3 must have failed
73 +
74 + # if stage 3 was success, we went onto emerge -e system, if that
75 + # failed, portage would have left a build.log behind
76 + logs = glob.glob(d + "/portage/*/*/temp/build.log")
77 + if len(logs) > 0:
78 + return 4
79 +
80 + # ok, so it must have been all good then
81 + return 5
82 +
83 +def get_err_reason(arch, dte, err):
84 + rdir = os.path.join(resultsdir, arch, '%d' % dte)
85 +
86 + if err == 0:
87 + return "bootstrap failed to start"
88 + if err >= 1 and err <= 3:
89 + stagelog = os.path.join(rdir, 'stage%d.log' % err)
90 + if os.path.exists(stagelog):
91 + line = None
92 + with open(stagelog, 'rb') as f:
93 + errexp = re.compile(r'^( \* (ERROR:|Fetch failed for)|emerge: there are no) ')
94 + for line in f:
95 + res = errexp.match(line.decode('utf-8', 'ignore'))
96 + if res:
97 + break
98 + if not line:
99 + return '<a href="%s/stage%d.log">stage %d</a> failed' % \
100 + (os.path.join(arch, '%d' % dte), err, err)
101 + return '<a href="%s/stage%d.log">stage %d</a> failed<br />%s' % \
102 + (os.path.join(arch, '%d' % dte), err, err, \
103 + html.escape(line.decode('utf-8', 'ignore')))
104 + else:
105 + return 'stage %d did not start' % err
106 + if err == 4:
107 + msg = "'emerge -e system' failed while emerging"
108 + logs = glob.glob(rdir + "/portage/*/*/temp/build.log")
109 + for log in logs:
110 + cat, pkg = log.split('/')[-4:-2]
111 + msg = msg + ' <a href="%s/temp/build.log">%s/%s</a>' % \
112 + (os.path.join(arch, '%d' % dte, "portage", cat, pkg), \
113 + cat, pkg)
114 + return msg
115 +
116 +def analyse_arch(d):
117 + last_fail = None
118 + last_succ = None
119 + fail_state = None
120 + with os.scandir(d) as it:
121 + for f in sorted(it, key=lambda x: (x.is_dir(), x.name), reverse=True):
122 + if not f.is_dir(follow_symlinks=False):
123 + continue
124 + date = int(f.name)
125 + res = find_last_stage(os.path.join(d, f.name))
126 + if res == 5:
127 + if not last_succ:
128 + last_succ = date
129 + elif not last_fail:
130 + last_fail = date
131 + fail_state = res
132 + if last_succ and last_fail:
133 + break
134 +
135 + return (last_fail, fail_state, last_succ)
136 +
137 +archs = {}
138 +with os.scandir(resultsdir) as it:
139 + for f in sorted(it, key=lambda x: (x.is_dir(), x.name)):
140 + if not f.is_dir(follow_symlinks=False):
141 + continue
142 + arch = f.name
143 + fail, state, suc = analyse_arch(os.path.join(resultsdir, arch))
144 + archs[arch] = (fail, state, suc)
145 + if not suc:
146 + color = '\033[1;31m' # red
147 + elif fail and suc < fail:
148 + color = '\033[1;33m' # yellow
149 + else:
150 + color = '\033[1;32m' # green
151 + endc = '\033[0m'
152 + print("%s%24s: suc %8s fail %8s%s" % (color, arch, suc, fail, endc))
153 +
154 +# generate html edition
155 +with open(os.path.join(resultsdir, 'index.html'), "w") as h:
156 + h.write("<html>")
157 + h.write("<head><title>Gentoo Prefix bootstrap results</title></head>")
158 + h.write("<body>")
159 + h.write("<h2>Gentoo Prefix bootstraps</h2>")
160 + h.write('<table border="1px">')
161 + h.write("<th>architecture</th>")
162 + h.write("<th>last successful run</th><th>last failed run</th>")
163 + h.write("<th>failure</th>")
164 + for arch in archs:
165 + fail, errcode, suc = archs[arch]
166 + if not suc:
167 + state = 'red'
168 + elif fail and suc < fail:
169 + state = 'orange'
170 + else:
171 + state = 'limegreen'
172 +
173 + h.write('<tr>')
174 +
175 + h.write('<td bgcolor="%s" nowrap="nowrap">' % state)
176 + h.write(arch)
177 + h.write("</td>")
178 +
179 + h.write("<td>")
180 + if suc:
181 + h.write('<a href="%s/%s">%s</a>' % (arch, suc, suc))
182 + else:
183 + h.write('<i>never</i>')
184 + h.write("</td>")
185 +
186 + h.write("<td>")
187 + if fail:
188 + h.write('<a href="%s/%s">%s</a>' % (arch, fail, fail))
189 + else:
190 + h.write('<i>never</i>')
191 + h.write("</td>")
192 +
193 + h.write("<td>")
194 + if fail and (not suc or fail > suc):
195 + h.write(get_err_reason(arch, fail, errcode))
196 + h.write("</td>")
197 +
198 + h.write("</tr>")
199 + h.write("</table>")
200 + now = time.strftime('%Y-%m-%d %H:%M', time.gmtime())
201 + h.write("<p><i>generated: %s</i></p>" % now)
202 + h.write("<p>See also <a href='https://dev.azure.com/12719821/12719821/_build?definitionId=6'>awesomebytes</a></p>")
203 + h.write("</body>")
204 + h.write("</html>")
205
206 diff --git a/scripts/auto-bootstraps/dobootstrap b/scripts/auto-bootstraps/dobootstrap
207 new file mode 100755
208 index 0000000000..521f644acf
209 --- /dev/null
210 +++ b/scripts/auto-bootstraps/dobootstrap
211 @@ -0,0 +1,167 @@
212 +#!/usr/bin/env bash
213 +
214 +BOOTSTRAP="${BASH_SOURCE[0]%/*}/bootstrap-prefix.sh"
215 +BOOTURL="http://rsync.prefix.bitzolder.nl/scripts/bootstrap-prefix.sh"
216 +UPLOAD="rsync1.prefix.bitzolder.nl::gentoo-portage-bootstraps"
217 +
218 +do_fetch() {
219 + local FETCHCOMMAND
220 + # Try to find a download manager, we only deal with wget,
221 + # curl, FreeBSD's fetch and ftp.
222 + if [[ x$(type -t wget) == "xfile" ]] ; then
223 + FETCH_COMMAND="wget -O -"
224 + [[ $(wget -h) == *"--no-check-certificate"* ]] && \
225 + FETCH_COMMAND+=" --no-check-certificate"
226 + elif [[ x$(type -t curl) == "xfile" ]] ; then
227 + FETCH_COMMAND="curl -f -L"
228 + else
229 + echo "could not download ${1##*/}"
230 + exit 1
231 + fi
232 +
233 + ${FETCH_COMMAND} "${*}" || exit 1
234 +}
235 +
236 +do_prepare() {
237 + local bitw=$1
238 + local dte=$2
239 + local bootstrap
240 +
241 + if [[ -n ${RESUME} && -n ${bitw} && -n ${dte} ]] ; then
242 + bootstrap=bootstrap${bitw}-${dte}/bootstrap-prefix.sh
243 + elif [[ -n ${DOLOCAL} ]] ; then
244 + bootstrap=${BOOTSTRAP}
245 + else
246 + bootstrap=dobootstrap-do_prepare-$$
247 + do_fetch ${BOOTURL} > ${bootstrap}
248 + fi
249 +
250 + local chost=$(${BASH} ${bootstrap} chost.guess x)
251 + case ${chost} in
252 + *86-*)
253 + if [[ ${bitw} == 64 ]] ; then
254 + chost=x86_64-${chost#*-}
255 + else
256 + bitw=32
257 + chost=i386-${chost#*-}
258 + fi
259 + ;;
260 + x86_64-*)
261 + if [[ ${bitw} == 32 ]] ; then
262 + chost=i386-${chost#*-}
263 + else
264 + bitw=64
265 + chost=x86_64-${chost#*-}
266 + fi
267 + ;;
268 + powerpc-*)
269 + bitw=32
270 + ;;
271 + sparc-*)
272 + if [[ ${bitw} == 64 ]] ; then
273 + chost=sparcv9-${chost#*-}
274 + else
275 + bitw=32
276 + chost=sparc-${chost#*-}
277 + fi
278 + ;;
279 + sparcv9-*|sparc64-*)
280 + if [[ ${bitw} == 32 ]] ; then
281 + chost=sparc-${chost#*-}
282 + else
283 + bitw=64
284 + chost=sparcv9-${chost#*-}
285 + fi
286 + ;;
287 + *)
288 + echo "unhandled CHOST: ${chost}"
289 + rm -f dobootstrap-do_prepare-$$
290 + exit 1
291 + ;;
292 + esac
293 +
294 + [[ -z ${dte} ]] && dte=$(date "+%Y%m%d")
295 + EPREFIX=${PWD}/bootstrap${bitw}-${dte}
296 + [[ -n ${OVERRIDE_EPREFIX} ]] && EPREFIX=${OVERRIDE_EPREFIX}
297 +
298 + local bootstrapscript=$(realpath ${BASH_SOURCE[0]} 2>/dev/null)
299 + if [[ -z ${bootstrapscript} ]] ; then
300 + local b=${BASH_SOURCE[0]}
301 + cd "${b%/*}"
302 + bootstrapscript=$(pwd -P)/${b##*/}
303 + fi
304 + echo "EPREFIX=${EPREFIX}"
305 + mkdir -p "${EPREFIX}"
306 + if [[ ${bootstrap} == dobootstrap-do_prepare-$$ ]] ; then
307 + mv "${bootstrap}" "${EPREFIX}"/bootstrap-prefix.sh
308 + elif [[ ${bootstrap} != "${EPREFIX}"/bootstrap-prefix.sh ]] ; then
309 + cp "${bootstrap}" "${EPREFIX}"/bootstrap-prefix.sh
310 + fi
311 + cd "${EPREFIX}" || exit 1
312 +
313 + # optional program to keep the machine from sleeping
314 + # macOS/BSD: caffeinate
315 + keepalive=$(type -P caffeinate)
316 + [[ -x ${keepalive} ]] && keepalive+=" -i -m -s" || keepalive=
317 +
318 + env -i \
319 + HOME=${EPREFIX} \
320 + SHELL=/bin/bash \
321 + TERM=${TERM} \
322 + USER=${USER} \
323 + CHOST=${chost} \
324 + GENTOO_MIRRORS="http://distfileslocal/" \
325 + EPREFIX=${EPREFIX} \
326 + ${DOLOCAL+DOLOCAL=1} \
327 + ${RESUME+RESUME=1} \
328 + ${LATEST_TREE_YES+LATEST_TREE_YES=1} \
329 + ${TREE_FROM_SRC+TREE_FROM_SRC=}${TREE_FROM_SRC} \
330 + ${keepalive} /bin/bash -l -c "${BASH} ${bootstrapscript} bootstrap"
331 +
332 + if [[ -n ${DOPUBLISH} ]] ; then
333 + rsync -q /dev/null ${UPLOAD}/${HOSTNAME}-$$/
334 + rsync -q /dev/null ${UPLOAD}/${HOSTNAME}-$$/${chost}/
335 + rsync -rltv \
336 + --exclude=work/ \
337 + --exclude=homedir/ \
338 + --exclude=files \
339 + --exclude=distdir/ \
340 + --exclude=image/ \
341 + {stage,.stage}* \
342 + bootstrap-prefix.sh \
343 + startprefix \
344 + usr/portage/distfiles \
345 + var/tmp/portage \
346 + var/log/emerge.log \
347 + ${UPLOAD}/${HOSTNAME}-$$/${chost}/${dte}/
348 + rsync -q /dev/null ${UPLOAD}/${HOSTNAME}-$$/${chost}/${dte}/push-complete/
349 + fi
350 +}
351 +
352 +do_bootstrap() {
353 + chmod 755 bootstrap-prefix.sh || exit 1
354 + ${BASH} ./bootstrap-prefix.sh ${EPREFIX} noninteractive
355 +}
356 +
357 +case $1 in
358 + bootstrap)
359 + do_bootstrap
360 + ;;
361 + local)
362 + export DOLOCAL=1
363 + do_prepare $2
364 + ;;
365 + resume)
366 + export RESUME=1
367 + do_prepare "$2" ${3:-${BOOTSTRAP_DATE}}
368 + ;;
369 + *)
370 + if [[ ${0} == /net/* ]] ; then
371 + echo "internal host, activating local and DOPUBLISH"
372 + export DOLOCAL=1
373 + export DOPUBLISH=1
374 + fi
375 + do_prepare $1
376 + ;;
377 +esac
378 +
379
380 diff --git a/scripts/auto-bootstraps/process_uploads.sh b/scripts/auto-bootstraps/process_uploads.sh
381 new file mode 100755
382 index 0000000000..52bb09ed7f
383 --- /dev/null
384 +++ b/scripts/auto-bootstraps/process_uploads.sh
385 @@ -0,0 +1,60 @@
386 +#!/usr/bin/env bash
387 +
388 +UPLOADDIR="./uploads"
389 +RESULTSDIR="./results"
390 +
391 +didsomething=
392 +for d in ${UPLOADDIR}/* ; do
393 + if [[ ! -d "${d}" ]] ; then
394 + rm -f "${d}"
395 + continue
396 + fi
397 +
398 + # structure: randomid/chost/date
399 + # chost/date should be the only thing in randomid/ check this
400 + set -- "${d}"/*/*
401 + if [[ $# -ne 1 ]] || [[ ! -d "$1" ]] ; then
402 + rm -Rf "${d}"
403 + continue
404 + fi
405 +
406 + dir=${1#${d}/}
407 + # skip this thing from auto-processing if it is new platform
408 + [[ -d ${RESULTSDIR}/${dir%/*} ]] || continue
409 + # skip this thing if it already exists
410 + [[ -d ${RESULTSDIR}/${dir} ]] && continue
411 + # skip this thing if it isn't complete yet
412 + [[ -d ${d}/${dir}/push-complete ]] || continue
413 +
414 + # only copy over what we expect, so we leave any uploaded cruft
415 + # behind
416 + mkdir "${RESULTSDIR}/${dir}"
417 + for f in \
418 + stage{1,2,3}.log \
419 + .stage{1,2,3}-finished \
420 + bootstrap-prefix.sh \
421 + emerge.log \
422 + startprefix \
423 + distfiles ;
424 + do
425 + [[ -e "${d}/${dir}/${f}" ]] && \
426 + mv "${d}/${dir}/${f}" "${RESULTSDIR}/${dir}"/
427 + done
428 + if [[ -e "${d}/${dir}/portage" ]] ; then
429 + for pkg in "${d}/${dir}/portage"/*/* ; do
430 + w=${pkg#${d}/}
431 + mkdir -p "${RESULTSDIR}/${w}"
432 + [[ -e "${pkg}"/build-info ]] && \
433 + mv "${pkg}"/build-info "${RESULTSDIR}/${w}"/
434 + [[ -e "${pkg}"/temp ]] && \
435 + mv "${pkg}"/temp "${RESULTSDIR}/${w}"/
436 + done
437 + fi
438 + chmod -R o+rX,go-w "${RESULTSDIR}/${dir}"
439 + rm -Rf "${d}"
440 +
441 + [[ -e "${RESULTSDIR}/${dir}"/distfiles ]] && \
442 + ./update_distfiles.py "${RESULTSDIR}/${dir}"/distfiles > /dev/null
443 + didsomething=1
444 +done
445 +[[ -n ${didsomething} ]] && ./analyse_result.py > /dev/null
446
447 diff --git a/scripts/auto-bootstraps/update_distfiles.py b/scripts/auto-bootstraps/update_distfiles.py
448 new file mode 100755
449 index 0000000000..8f44f7fa20
450 --- /dev/null
451 +++ b/scripts/auto-bootstraps/update_distfiles.py
452 @@ -0,0 +1,29 @@
453 +#!/usr/bin/env python3.6
454 +
455 +import hashlib
456 +import os
457 +import sys
458 +
459 +distfilessrc='./distfiles'
460 +
461 +def hash_file(f):
462 + hsh = hashlib.new('sha1')
463 + with open(f, 'rb') as fle:
464 + hsh.update(fle.read())
465 + return hsh.hexdigest()
466 +
467 +with os.scandir(path=sys.argv[1]) as it:
468 + for f in it:
469 + if not f.is_file() or f.name.startswith('.'):
470 + continue
471 + srcfile = os.path.join(sys.argv[1], f.name)
472 + h = hash_file(srcfile)
473 + distname = os.path.join(distfilessrc,
474 + f.name + "@" + h).lower()
475 + if os.path.exists(distname):
476 + print("DUP %s" % distname.split('/')[-1])
477 + os.remove(srcfile)
478 + os.link(distname, srcfile, follow_symlinks=False)
479 + else:
480 + print("NEW %s" % distname.split('/')[-1])
481 + os.link(srcfile, distname)