source: rpmbuild-bot/rpmbuild-bot.sh@ 739

Last change on this file since 739 was 739, checked in by dmik, 9 years ago

rpmbuild-bot: Add support for clean=test command.

This cleans up the results of the "test" command instead
of "build".

File size: 19.5 KB
Line 
1#!/bin/sh
2
3#
4# rpmbuild-bot.sh: RPM Build Bot version 1.0.1.
5#
6# Author: Dmitriy Kuminov <coding@dmik.org>
7#
8# This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
9# WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
10#
11# History
12# -------
13#
14# 1.0.1 [01.04.2016]
15# - Fix bad variable name error.
16# - Understand .CMD extension in REXX wrapper.
17# 1.0 [01.04.2016]
18# - Initial version.
19#
20# Synopsis
21# --------
22#
23# This script performs a build of RPM packages from a given .spec file in
24# controlled environment to guarantee consistent results when building RPMs
25# different machines. It uses a separate file rpmbuild-bot-env.sh located
26# in the same directory to set up the environment and control the build
27# process. The main purpose of this script is to build RPM packages for a
28# specific distribution channel maintaining distribution-siecific rules.
29#
30# Usage
31# -----
32#
33# > rpmbuild-bot.sh SPEC[=VERSION]
34# > [ upload[=REPO] | test[=MODE] | clean[=test] | remove[=REPO] ]
35# > [-f]
36#
37# MYAPP is the name of the RPM package spec file (extension is optional,
38# .spec is assumed). The spec file is searched in the SPECS directory of the
39# rpmbuild tree (usually $USER/rpmbuild/SPECS, rpmbuild --eval='%_specdir'
40# will show the exact location). This may be overriden by giving a spec file
41# with a path specification.
42#
43# The second argument defines the command to perform. The default command is
44# "build". The following sections will describe each command.
45#
46# Building packages
47# -----------------
48#
49# The "build" is the main command which is used to generate packages for
50# distribution. This command does the following:
51#
52# 1. Build all RPM packages for all architectures specified in
53# $RPMBUILD_BOT_ARCH_LIST. The packages are stored in the RPMS
54# directory of the rpmbuild tree.
55# 2. Build the source RPM. Stored in the SRPMS directory.
56# 3. Create a ZIP package from the RPMs for the architecture specified
57# last in $RPMBUILD_BOT_ARCH_LIST.
58#
59# The build process for each architecture is stored in a log file in the logs
60# directory of the rpmbuild tree (`rpmbuild --eval='%_topdir'/logs`). Creation
61# of the source RPM and ZIP files is also logged, into separate log files.
62#
63# The "build" command will fail if the log directory contains files from a
64# successfull run of another "build" command for this package. This is to
65# prevent overwriting successfully built packages that are not yet uploaded to
66# the distribution repository (see the "upload" command). You should either
67# run the "upload" command or use the -f option to force removal of these
68# log files and the corresponding package files if you are absolutely sure they
69# should be discarded.
70#
71# Doing test builds
72# -----------------
73#
74# The "test" command is used to build packages for one architecture for testing
75# purposes. In this more, neither the source RPM nor the ZIP file is created.
76# Also, a special option is automatically passed to rpmbuild to clear the %dist
77# variable to indicate that this is a local, non-distribution build. Such
78# packages will always be "older" than the corresponding builds with %dist
79# so that they will be automatically updated on the next yum update to the
80# %dist ones. The packages built in "test" mode are NOT intended for
81# distribution, they are meant only for local testing before performing the
82# full build of everything.
83#
84# It is possible to configure which steps of the build to perform using the MODE
85# argument to the "test" command which may take one of the following values:
86#
87# prep Execute the %prep section of the spec file only.
88# build Execute the %build section only (requres %prep to be executed
89# before). May be run multiple times.
90# install Execute the %install sectuion only (requires "prep" and "build"
91# to be executed before). May be run multiple times.
92# pack Create the RPM packages (requires "prep", "build" and "install"
93# to be executed before). Note that this step will also execute
94# the %clean section so that a new "build" + "install" execution is
95# necessary for "pack" to succeed.
96#
97# When no MODE argument is given, all steps are executed in a proper order.
98#
99# The results of the "test" command are stored in a log file in the logs/test
100# directory of the rpmbuild tree. The previous log file, if any, is given a .bak
101# extension (the previous .bak file will be deleted).
102#
103# Uploading packages
104# ------------------
105#
106# The "upload" command will upload the packages built with the "build"
107# command to the official distribution channel configured in
108# rpmbuild-bot-env.sh. The REPO argument specifies one of the configured
109# repositories. When REPO is not given, the default repository is used
110# (usually experimental or similar).
111#
112# Note that the "upload" command needs log files from the "build" command
113# and will fail otherwise.
114#
115# Upon successful completion, the "upload" command will remove all uploaded
116# RPM and ZIP packages and will move all "build" log files to logs/archive.
117#
118# Cleaning packages
119# -----------------
120#
121# The "clean" command will delete packages built with the "build" command
122# and their log files without uploading them to a repository. This is useful
123# when the successful build needs to be canceled for some reason (wrong source
124# tree state, wrong patches etc.). Note that normally you don't need to use
125# this command; it's an emergency-only tool.
126#
127# The "clean" command needs log files from the "build" command and will fail
128# otherwise.
129#
130# If the "clean" command is given a "test" argument, it will clean up the
131# results of the "test" command instead of "build". The log file from the
132# "test" command needs to be present or the command will fail.
133#
134# Removing packages
135# -----------------
136#
137# The "remove" command allows to remove a particular version of the packages
138# built with the "build" command and uploaded with the "upload" command from a
139# repository. This is useful when the successful build needs to be canceled for
140# some reason (wrong source tree state, wrong patches etc.). Note that normally
141# you don't need to use this command; it's an emergency-only tool.
142#
143# The "remove" command needs log files from the "build" and "upload" commands
144# and will fail otherwise. It also requires the VERSION argument for the SPEC
145# parameter to be given (to specify the version of the packages to remove) and
146# accepts the REPO argument just like the "upload" command does (to specify a
147# repository to remove the packages from).
148#
149# Note that the log files from the "build" and "upload" commands are also removed
150# by this command upon sucessful package removal.
151#
152# Return value
153# ------------
154#
155# The rpmbuild-bot.sh script will return a zero exit code upon successful
156# completion and non-zero otherwise. The log files should be inspected to
157# check for a reason of the failure.
158#
159
160#
161# Helpers.
162#
163
164run()
165{
166 "$@"
167 local rc=$?
168 if test $rc != 0 ; then
169 echo "ERROR: The following command failed with error code $rc:"
170 echo $@
171 exit $rc
172 fi
173}
174
175log_run()
176{
177 log="$1"
178 shift
179 "$@" >"$log" 2>&1
180 local rc=$?
181 if test $rc != 0 ; then
182 echo "ERROR: The following command failed with error code $rc:"
183 echo $@
184 echo "You will find more information in file '$log'."
185 echo "Here are the last 10 lines of output:"
186 echo ""
187 tail "$log" -n 10
188 exit $rc
189 fi
190}
191
192warn()
193{
194 echo "WARNING: $1"
195}
196
197die()
198{
199 echo "ERROR: $1"
200 exit 1
201}
202
203check_dir_var()
204{
205 eval local val="\$$1"
206 [ -n "$val" ] || die "$1 is empty."
207 [ -d "$val" ] || die "$1 is '$val', not a valid directory."
208}
209
210read_file_list()
211{
212 # $1 = file list list filename
213 # $2 = var name where to save read file names (optional)
214 # $3 = function name to call for each file (optional)
215
216 local list="$1"
217 local _read_file_list_ret=
218
219 # Check timestamps.
220 while read l; do
221 local file="${l#*|}"
222 local ts="${l%%|*}"
223 [ "$file" = "$ts" ] && die "Line '$l' in '$list' does not contain timestamps."
224 [ -n "$3" ] && eval $3
225 [ -f "$file" ] || die "File '$file' is not found."
226 echo "Checking tmestamp of $file..."
227 local act_ts=`stat -c '%Y' "$file"`
228 if [ "$ts" != "$act_ts" ] ; then
229 die "Recorded timestamp $ts doesn't match actual timestamp $act_ts for '$file'."
230 fi
231 _read_file_list_ret="$_read_file_list_ret${_read_file_list_ret:+ }$file"
232 done < "$list"
233 # Return the files (if requested).
234 [ -n "$2" ] && eval "$2=\$_read_file_list_ret"
235}
236
237usage()
238{
239 echo "Usage:"
240 sed -n -e "s/rpmbuild-bot.sh/${0##*/}/g" -e 's/^# > / /p' < "$0"
241 exit 255
242}
243
244build_cmd()
245{
246 local base_arch="${RPMBUILD_BOT_ARCH_LIST##* }"
247
248 echo "Spec file: $spec_full"
249 echo "Targets: $RPMBUILD_BOT_ARCH_LIST + SRPM + ZIP ($base_arch)"
250
251 if [ -f "$spec_list" ] ; then
252 if [ -z "$force" ] ; then
253 die "File '$spec_list' already exists.
254This file indicates a successful build that was not yet uploaded.
255Either run the '"'upload'"' command to upload the generated RPM and ZIP
256packages to the distribution repository or use the -f option to
257force their removal if you are sure they should be discarded."
258 fi
259
260 echo "Detected successful build in $spec_list, cleaning up due to -f option..."
261 local files=
262 read_file_list "$spec_list" files
263 for f in $files; do
264 echo "Removing $f..."
265 run rm -f "$f"
266 done
267 unset files
268
269 echo "Removing $log_base.*.log and .list files..."
270 rm -f "$log_base".*.log "$log_base".*.list "$log_base".list
271 fi
272
273 # Generate RPMs.
274 for arch in $RPMBUILD_BOT_ARCH_LIST ; do
275 echo "Creating RPMs for '$arch' target (logging to $log_base.$arch.log)..."
276 log_run "$log_base.$arch.log" rpmbuild.exe --target=$arch -bb "$spec_full"
277 done
278
279 # Generate SRPM.
280 echo "Creating SRPM (logging to $log_base.srpm.log)..."
281 log_run "$log_base.srpm.log" rpmbuild -bs "$spec_full"
282
283 # Find SRPM file name in the log.
284 local src_rpm=`grep "^Wrote: \+.*\.src\.rpm$" "$log_base.srpm.log" | sed -e "s#^Wrote: \+##g"`
285 [ -n "$src_rpm" ] || die "Cannot find .src.rpm file name in '$log_base.srpm.log'."
286
287 # Find package version.
288 local ver_full="${src_rpm%.src.rpm}"
289 ver_full="${ver_full##*/}"
290 [ "${ver_full%%-[0-9]*}" = "$spec_name" ] || die \
291"SRPM name '${src_rpm##*/}' does not match .spec name ('$spec_name').
292Either rename '$spec_name.spec' to '${ver_full%%-[0-9]*}.spec' or set 'Name:' tag to '$spec_name'."
293 ver_full="${ver_full#${spec_name}-}"
294 [ -n "$ver_full" ] || die "Cannot deduce package version from '$src_rpm'."
295
296 # Find all RPM packages for the base arch (note the quotes around `` - it's to preserve multi-line result).
297 local rpms="`grep "^Wrote: \+.*\.\($base_arch\.rpm\|noarch\.rpm\)$" "$log_base.$base_arch.log" | sed -e "s#^Wrote: \+##g"`"
298 [ -n "$rpms" ] || die "Cannot find .$base_arch.rpm/.noarch.rpm file names in '$log_base.base_arch.log'."
299
300 local ver_full_zip=`echo $ver_full | tr . _`
301 local zip="$zip_dir/$spec_name-$ver_full_zip.zip"
302
303 # Generate ZIP.
304 echo "Creating ZIP (logging to $log_base.zip.log)..."
305 create_zip()
306 {(
307 run cd "$zip_dir"
308 rm -r "@unixroot" 2> /dev/null
309 # Note no quoters around $rpms - it's to split at EOL.
310 for f in $rpms ; do
311 echo "Unpacking $f..."
312 run rpm2cpio "$f" | cpio -idm
313 done
314 rm -f "$zip" 2> /dev/null
315 echo "Creating '$zip'..."
316 run zip -mry9 "$zip" "@unixroot"
317 )}
318 log_run "$log_base.zip.log" create_zip
319
320 local ver_list="$log_base.$ver_full.list"
321
322 # Generate list of all generated packages for further reference.
323 echo "Creating list file ($ver_list)..."
324 echo `stat -c '%Y' "$src_rpm"`"|$src_rpm" > "$ver_list"
325 echo `stat -c '%Y' "$zip"`"|$zip" >> "$ver_list"
326 # Save base arch RPMs.
327 for f in $rpms ; do
328 echo `stat -c '%Y' "$f"`"|$f" >> "$ver_list"
329 done
330 # Save other arch RPMs.
331 for arch in ${RPMBUILD_BOT_ARCH_LIST%${base_arch}} ; do
332 rpms="`grep "^Wrote: \+.*\.$arch\.rpm$" "$log_base.$arch.log" | sed -e "s#^Wrote: \+##g"`"
333 [ -n "$rpms" ] || die "Cannot find .$arch.rpm file names in '$log_base.arch.log'."
334 for f in $rpms ; do
335 echo `stat -c '%Y' "$f"`"|$f" >> "$ver_list"
336 done
337 done
338
339 # Everything succeeded. Symlink the list file so that "upload" can find it.
340 run ln -s "${ver_list##*/}" "$log_base.list"
341}
342
343test_cmd()
344{
345 echo "Spec file: $spec_full"
346
347 local base_arch="${RPMBUILD_BOT_ARCH_LIST##* }"
348 local cmds=
349
350 [ -z "$command_arg" ] && command_arg="all"
351
352 case "$command_arg" in
353 all)
354 cmds="$cmds -bb"
355 ;;
356 prep)
357 cmds="$cmds -bp --short-circuit"
358 ;;
359 build)
360 cmds="$cmds -bc --short-circuit"
361 ;;
362 install)
363 cmds="$cmds -bi --short-circuit"
364 ;;
365 pack)
366 cmds="$cmds -bb --short-circuit"
367 ;;
368 *)
369 die "Invalid test build sub-command '$command_arg'."
370 ;;
371 esac
372
373 local log_file="$log_dir/test/$spec_name.log"
374
375 if [ -f "$log_file" ] ; then
376 rm -f "$log_file.bak"
377 run mv "$log_file" "$log_file.bak"
378 fi
379
380 echo "Doing test RPM build for '$base_arch' target (logging to $log_file)..."
381 log_run "$log_file" rpmbuild.exe "--define=dist %nil" --target=$base_arch $cmds $spec_full
382
383 # Show the generated RPMs when appropriate.
384 if [ "$command_arg" = "all" -o "$command_arg" = "pack" ] ; then
385 local rpms=`grep "^Wrote: \+.*\.\($base_arch\.rpm\|noarch\.rpm\)$" "$log_file" | sed -e "s#^Wrote: \+##g"`
386 if [ -n "$rpms" ] ; then
387 echo "Successfully generated the following RPMs:"
388 for f in $rpms; do
389 echo "$f"
390 done
391 else
392 warn "Cannot find .$base_arch.rpm/.noarch.rpm file names in '$log_file'."
393 fi
394 fi
395}
396
397repo_dir_for_file()
398{
399 # $1 = input file name
400 # $2 = var name to save dir to
401
402 [ -n "$1" -a -n "$2" ] || die "Invalid arguments."
403
404 local _repo_dir_for_file_ret=
405 case "$1" in
406 *.src.rpm)
407 eval _repo_dir_for_file_ret="$RPMBUILD_BOT_UPLOAD_REPO_LAYOUT_srpm"
408 ;;
409 *.*.rpm)
410 local arch="${1%.rpm}"
411 arch="${arch##*.}"
412 [ -n "$arch" ] || die "No arch spec in file name '$1'."
413 eval _repo_dir_for_file_ret="$RPMBUILD_BOT_UPLOAD_REPO_LAYOUT_rpm"
414 ;;
415 *.zip)
416 eval _repo_dir_for_file_ret="$RPMBUILD_BOT_UPLOAD_REPO_LAYOUT_zip"
417 ;;
418 esac
419
420 eval "$2=\$_repo_dir_for_file_ret"
421}
422
423upload_cmd()
424{
425 # Check settings.
426 test -n "$RPMBUILD_BOT_UPLOAD_REPO_LIST" || die "RPMBUILD_BOT_UPLOAD_REPO_LIST is empty."
427
428 local repo="$command_arg"
429 [ -z "$repo" ] && repo="${RPMBUILD_BOT_UPLOAD_REPO_LIST%% *}"
430
431 check_dir_var "RPMBUILD_BOT_UPLOAD_${repo}_DIR"
432
433 eval local base="\$RPMBUILD_BOT_UPLOAD_${repo}_DIR"
434
435 [ -f "$spec_list" ] || die \
436"File '$spec_list' is not found.
437You may need to build the packages using the 'build' command."
438
439 local files=
440 read_file_list "$spec_list" files
441 for f in $files; do
442 local d=
443 repo_dir_for_file "$f" d
444 [ -n "$d" ] || die "Unsupported file name '$f' in '$spec_list'."
445 [ -d "$d" ] || die "'$d' is not a directory."
446 [ -f "$d/${f##*/}" -a -z "$force" ] && die \
447"File '$d/${f##*/}' already exists.
448Use the -f option to force uploading if you are sure the existing
449packages in the repository should be discarded."
450 echo "Copying $f to $d..."
451 run cp -p "$f" "$d"
452 done
453
454 # On success, delete the uploaded packages and archive log files.
455 for f in $files; do
456 echo "Removing $f..."
457 run rm -f "$f"
458 done
459
460 # Note: versioned .list file will remain in archive forever (for further reference).
461 echo "Removing old '$spec_name' logs from $log_dir/archive..."
462 rm -f "$log_dir/archive/$spec_name".*.log "$log_dir/archive/$spec_name".list
463 echo "Moving '$spec_name' logs to $log_dir/archive..."
464 run mv "$log_base".*.log "$log_base".*.list "$log_base".list "$log_dir/archive/"
465}
466
467clean_cmd()
468{
469 if [ "$command_arg" = "test" ] ; then
470 # Cleanup after "test" command.
471 local base_arch="${RPMBUILD_BOT_ARCH_LIST##* }"
472 local log_file="$log_dir/test/$spec_name.log"
473
474 [ -f "$log_file" ] || die "File '$test_log' is not found."
475
476 local rpms=`grep "^Wrote: \+.*\.\($base_arch\.rpm\|noarch\.rpm\)$" "$log_file" | sed -e "s#^Wrote: \+##g"`
477 if [ -n "$rpms" ] ; then
478 for f in $rpms; do
479 echo "Removing $f..."
480 run rm -f "$f"
481 done
482 echo "Removing $log_file[.bak]..."
483 run rm -f "$log_file" "$log_file".bak
484 else
485 die "Cannot find .$base_arch.rpm/.noarch.rpm file names in '$log_file'."
486 fi
487
488 return
489 fi
490
491 # Cleanup after "build command".
492 [ -f "$spec_list" ] || die \
493"File '$spec_list' is not found.
494You man need to build the packages using the 'build' command."
495
496 local files=
497 read_file_list "$spec_list" files
498
499 for f in $files; do
500 echo "Removing $f..."
501 run rm -f "$f"
502 done
503
504 echo "Removing '$spec_name' logs from $log_dir..."
505 rm -f "$log_base".*.log "$log_base".*.list "$log_base".list
506}
507
508remove_cmd()
509{
510 # Check settings.
511 [ -n "$spec_ver" ] || die "SPEC parameter lacks version specification."
512
513 test -n "$RPMBUILD_BOT_UPLOAD_REPO_LIST" || die "RPMBUILD_BOT_UPLOAD_REPO_LIST is empty."
514
515 local repo="$command_arg"
516 [ -z "$repo" ] && repo="${RPMBUILD_BOT_UPLOAD_REPO_LIST%% *}"
517
518 check_dir_var "RPMBUILD_BOT_UPLOAD_${repo}_DIR"
519
520 eval local base="\$RPMBUILD_BOT_UPLOAD_${repo}_DIR"
521
522 local ver_list="$log_dir/archive/$spec_name.$spec_ver.list"
523 [ -f "$ver_list" ] || die "File '$ver_list' is not found."
524
525 local files=
526 read_file_list "$ver_list" files 'local dir=; repo_dir_for_file $file dir; file="${dir}/${file##*/}"'
527
528 for f in $files; do
529 echo "Removing $f..."
530 run rm -f "$f"
531 done
532
533 echo "Removing $ver_list..."
534 run rm -f "$ver_list"
535
536 # Also remove the logs of last "build" if we are removing the last "build" package.
537 if [ -L "$log_dir/archive/$spec_name.list" -a \
538 `readlink "$log_dir/archive/$spec_name.list"` = "$spec_name.$spec_ver.list" ] ; then
539 echo "Removing '$spec_name' logs from $log_dir/archive..."
540 rm -f "$log_dir/archive/$spec_name".*.log "$log_dir/archive/$spec_name".list
541 fi
542}
543
544#
545# Main.
546#
547
548# Parse command line.
549while [ -n "$1" ] ; do
550 case "$1" in
551 -*)
552 options="$*"
553 while [ -n "$1" ] ; do
554 case "$1" in
555 -f) force="-f"
556 ;;
557 *) usage
558 ;;
559 esac
560 shift
561 done
562 break
563 ;;
564 *)
565 if [ -z "$spec" ] ; then
566 spec="$1"
567 else
568 command="$1"
569 fi
570 ;;
571 esac
572 shift
573done
574
575[ -n "$spec" ] || usage
576[ -z "$command" ] && command="build"
577
578spec_ver="${spec#*=}"
579spec="${spec%=*}"
580[ "$spec" = "$spec_ver" ] && spec_ver=
581
582command_name="${command%=*}"
583command_arg="${command#*=}"
584[ "$command_name" = "$command_arg" ] && command_arg=
585
586need_spec_file=
587
588# Validate commands.
589case "$command_name" in
590 build|test)
591 need_spec_file=1
592 ;;
593 upload|clean|remove)
594 ;;
595 *) usage
596 ;;
597esac
598
599# Query all rpmbuild macros in a single run as it may be slow.
600eval `rpmbuild.exe --eval='rpmbuild_dir="%_topdir" ; spec_dir="%_specdir"' | tr '\\\' /`
601
602log_dir="$rpmbuild_dir/logs"
603zip_dir="$rpmbuild_dir/zip"
604
605spec=`echo $spec | tr '\\\' /`
606
607spec_name="${spec##*/}"
608
609if [ "$spec_name" = "$spec" ] ; then
610 # No path information, use SPECS
611 spec_full="$spec_dir/${spec_name%.spec}.spec"
612else
613 # Has some path, use it.
614 spec_full="${spec%.spec}.spec"
615fi
616
617spec_name="${spec_name%.spec}"
618
619[ -z "$need_spec_file" -o -f "$spec_full" ] || die "Spec file '$spec_full' is not found."
620
621# Prepare some (non-rpmbuild-standard) directories.
622run mkdir -p "$log_dir"
623run mkdir -p "$log_dir/archive"
624run mkdir -p "$log_dir/test"
625run mkdir -p "$zip_dir"
626
627[ -z "$command" ] && command="build"
628
629command_name="${command%=*}"
630comand_arg="${command#*=}"
631[ "$command_name" = "$command_arg" ] && command_arg=
632
633log_base="$log_dir/$spec_name"
634spec_list="$log_base.list"
635
636echo "Package: $spec_name"
637echo "Command: $command $options"
638
639# Set up the rpmbuild-bot environment.
640. "${0%%.sh}-env.sh"
641
642# Check common settings.
643test -n "$RPMBUILD_BOT_ARCH_LIST" || die "RPMBUILD_BOT_ARCH_LIST is empty."
644
645run eval "${command_name}_cmd"
646
647echo "All done."
648
649exit 0
Note: See TracBrowser for help on using the repository browser.