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

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

rpmbuild-bot: Add clean and remove commands.

These are emergency commands used to cancel the successful build
that was not yet uploaded (clean) and that was already uploaded
(remove).

File size: 18.5 KB
RevLine 
[467]1#!/bin/sh
2
[518]3#
[728]4# rpmbuild-bot.sh: RPM Build Bot version 1.0.1.
[518]5#
[724]6# Author: Dmitriy Kuminov <coding@dmik.org>
[518]7#
[724]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.
[518]10#
[728]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#
[724]20# Synopsis
21# --------
22#
[728]23# This script performs a build of RPM packages from a given .spec file in
[724]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#
[738]33# > rpmbuild-bot.sh SPEC[=VERSION]
34# > [ upload[=REPO] | test[=MODE] | clean | remove[=REPO] ]
35# > [-f]
[724]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#
[728]43# The second argument defines the command to perform. The default command is
[724]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#
[738]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# Removing packages
128# -----------------
129#
130# The "remove" command allows to remove a particular version of the packages
131# built with the "build" command and uploaded with the "upload" command from a
132# repository. This is useful when the successful build needs to be canceled for
133# some reason (wrong source tree state, wrong patches etc.). Note that normally
134# you don't need to use this command; it's an emergency-only tool.
135#
136# The "remove" command needs log files from the "build" and "upload" commands
137# and will fail otherwise. It also requires the VERSION argument for the SPEC
138# parameter to be given (to specify the version of the packages to remove) and
139# accepts the REPO argument just like the "upload" command does (to specify a
140# repository to remove the packages from).
141#
142# Note that the log files from the "build" and "upload" commands are also removed
143# by this command upon sucessful package removal.
144#
[724]145# Return value
146# ------------
147#
148# The rpmbuild-bot.sh script will return a zero exit code upon successful
149# completion and non-zero otherwise. The log files should be inspected to
150# check for a reason of the failure.
151#
[467]152
[724]153#
154# Helpers.
155#
156
[467]157run()
158{
159 "$@"
160 local rc=$?
161 if test $rc != 0 ; then
162 echo "ERROR: The following command failed with error code $rc:"
163 echo $@
164 exit $rc
165 fi
166}
167
168log_run()
169{
170 log="$1"
171 shift
[724]172 "$@" >"$log" 2>&1
[467]173 local rc=$?
174 if test $rc != 0 ; then
175 echo "ERROR: The following command failed with error code $rc:"
176 echo $@
[724]177 echo "You will find more information in file '$log'."
178 echo "Here are the last 10 lines of output:"
179 echo ""
180 tail "$log" -n 10
[467]181 exit $rc
182 fi
183}
184
[724]185warn()
186{
187 echo "WARNING: $1"
188}
189
[467]190die()
191{
[724]192 echo "ERROR: $1"
193 exit 1
[467]194}
195
[724]196check_dir_var()
197{
198 eval local val="\$$1"
199 [ -n "$val" ] || die "$1 is empty."
200 [ -d "$val" ] || die "$1 is '$val', not a valid directory."
201}
[467]202
[737]203read_file_list()
204{
205 # $1 = file list list filename
206 # $2 = var name where to save read file names (optional)
[738]207 # $3 = function name to call for each file (optional)
[737]208
[738]209 local list="$1"
[737]210 local _read_file_list_ret=
[738]211
[737]212 # Check timestamps.
213 while read l; do
[738]214 local file="${l#*|}"
215 local ts="${l%%|*}"
216 [ "$file" = "$ts" ] && die "Line '$l' in '$list' does not contain timestamps."
217 [ -n "$3" ] && eval $3
218 [ -f "$file" ] || die "File '$file' is not found."
219 echo "Checking tmestamp of $file..."
220 local act_ts=`stat -c '%Y' "$file"`
221 if [ "$ts" != "$act_ts" ] ; then
222 die "Recorded timestamp $ts doesn't match actual timestamp $act_ts for '$file'."
[737]223 fi
[738]224 _read_file_list_ret="$_read_file_list_ret${_read_file_list_ret:+ }$file"
225 done < "$list"
[737]226 # Return the files (if requested).
227 [ -n "$2" ] && eval "$2=\$_read_file_list_ret"
228}
229
[724]230usage()
231{
232 echo "Usage:"
233 sed -n -e "s/rpmbuild-bot.sh/${0##*/}/g" -e 's/^# > / /p' < "$0"
234 exit 255
235}
[467]236
[724]237build_cmd()
238{
239 # Check settings.
240 test -n "$RPMBUILD_BOT_ARCH_LIST" || die "RPMBUILD_BOT_ARCH_LIST is empty."
[467]241
[724]242 local base_arch="${RPMBUILD_BOT_ARCH_LIST##* }"
243
244 echo "Spec file: $spec_full"
245 echo "Targets: $RPMBUILD_BOT_ARCH_LIST + SRPM + ZIP ($base_arch)"
246
247 if [ -f "$spec_list" ] ; then
248 if [ -z "$force" ] ; then
249 die "File '$spec_list' already exists.
250This file indicates a successful build that was not yet uploaded.
251Either run the '"'upload'"' command to upload the generated RPM and ZIP
252packages to the distribution repository or use the -f option to
253force their removal if you are sure they should be discarded."
254 fi
255
256 echo "Detected successful build in $spec_list, cleaning up due to -f option..."
[737]257 local files=
258 read_file_list "$spec_list" files
259 for f in $files; do
[724]260 echo "Removing $f..."
[737]261 run rm -f "$f"
262 done
263 unset files
[724]264
265 echo "Removing $log_base.*.log and .list files..."
266 rm -f "$log_base".*.log "$log_base".*.list "$log_base".list
267 fi
268
269 # Generate RPMs.
270 for arch in $RPMBUILD_BOT_ARCH_LIST ; do
271 echo "Creating RPMs for '$arch' target (logging to $log_base.$arch.log)..."
[729]272 log_run "$log_base.$arch.log" rpmbuild.exe --target=$arch -bb "$spec_full"
[724]273 done
274
275 # Generate SRPM.
276 echo "Creating SRPM (logging to $log_base.srpm.log)..."
[729]277 log_run "$log_base.srpm.log" rpmbuild -bs "$spec_full"
[724]278
279 # Find SRPM file name in the log.
280 local src_rpm=`grep "^Wrote: \+.*\.src\.rpm$" "$log_base.srpm.log" | sed -e "s#^Wrote: \+##g"`
281 [ -n "$src_rpm" ] || die "Cannot find .src.rpm file name in '$log_base.srpm.log'."
282
[728]283 # Find package version.
[724]284 local ver_full="${src_rpm%.src.rpm}"
[736]285 ver_full="${ver_full##*/}"
286 [ "${ver_full%%-[0-9]*}" = "$spec_name" ] || die \
287"SRPM name '${src_rpm##*/}' does not match .spec name ('$spec_name').
288Either rename '$spec_name.spec' to '${ver_full%%-[0-9]*}.spec' or set 'Name:' tag to '$spec_name'."
289 ver_full="${ver_full#${spec_name}-}"
[724]290 [ -n "$ver_full" ] || die "Cannot deduce package version from '$src_rpm'."
291
[728]292 # Find all RPM packages for the base arch (note the quotes around `` - it's to preserve multi-line result).
293 local rpms="`grep "^Wrote: \+.*\.\($base_arch\.rpm\|noarch\.rpm\)$" "$log_base.$base_arch.log" | sed -e "s#^Wrote: \+##g"`"
[724]294 [ -n "$rpms" ] || die "Cannot find .$base_arch.rpm/.noarch.rpm file names in '$log_base.base_arch.log'."
295
296 local ver_full_zip=`echo $ver_full | tr . _`
297 local zip="$zip_dir/$spec_name-$ver_full_zip.zip"
298
299 # Generate ZIP.
300 echo "Creating ZIP (logging to $log_base.zip.log)..."
301 create_zip()
302 {(
303 run cd "$zip_dir"
304 rm -r "@unixroot" 2> /dev/null
[728]305 # Note no quoters around $rpms - it's to split at EOL.
306 for f in $rpms ; do
[724]307 echo "Unpacking $f..."
308 run rpm2cpio "$f" | cpio -idm
309 done
310 rm -f "$zip" 2> /dev/null
311 echo "Creating '$zip'..."
312 run zip -mry9 "$zip" "@unixroot"
313 )}
314 log_run "$log_base.zip.log" create_zip
315
316 local ver_list="$log_base.$ver_full.list"
317
318 # Generate list of all generated packages for further reference.
319 echo "Creating list file ($ver_list)..."
[737]320 echo `stat -c '%Y' "$src_rpm"`"|$src_rpm" > "$ver_list"
321 echo `stat -c '%Y' "$zip"`"|$zip" >> "$ver_list"
[724]322 # Save base arch RPMs.
[737]323 for f in $rpms ; do
324 echo `stat -c '%Y' "$f"`"|$f" >> "$ver_list"
[724]325 done
326 # Save other arch RPMs.
327 for arch in ${RPMBUILD_BOT_ARCH_LIST%${base_arch}} ; do
[728]328 rpms="`grep "^Wrote: \+.*\.$arch\.rpm$" "$log_base.$arch.log" | sed -e "s#^Wrote: \+##g"`"
[724]329 [ -n "$rpms" ] || die "Cannot find .$arch.rpm file names in '$log_base.arch.log'."
[728]330 for f in $rpms ; do
[737]331 echo `stat -c '%Y' "$f"`"|$f" >> "$ver_list"
[724]332 done
333 done
334
335 # Everything succeeded. Symlink the list file so that "upload" can find it.
336 run ln -s "${ver_list##*/}" "$log_base.list"
337}
338
339test_cmd()
340{
341 echo "Spec file: $spec_full"
342
343 local base_arch="${RPMBUILD_BOT_ARCH_LIST##* }"
[732]344 local cmds=
[724]345
346 [ -z "$command_arg" ] && command_arg="all"
347
348 case "$command_arg" in
349 all)
350 cmds="$cmds -bb"
351 ;;
352 prep)
353 cmds="$cmds -bp --short-circuit"
354 ;;
355 build)
356 cmds="$cmds -bc --short-circuit"
357 ;;
358 install)
359 cmds="$cmds -bi --short-circuit"
360 ;;
361 pack)
362 cmds="$cmds -bb --short-circuit"
363 ;;
364 *)
365 die "Invalid test build sub-command '$command_arg'."
366 ;;
367 esac
368
369 local log_file="$log_dir/test/$spec_name.log"
370
371 if [ -f "$log_file" ] ; then
372 rm -f "$log_file.bak"
373 run mv "$log_file" "$log_file.bak"
374 fi
375
376 echo "Doing test RPM build for '$base_arch' target (logging to $log_file)..."
[732]377 log_run "$log_file" rpmbuild.exe "--define=dist %nil" --target=$base_arch $cmds $spec_full
[724]378
379 # Show the generated RPMs when appropriate.
380 if [ "$command_arg" = "all" -o "$command_arg" = "pack" ] ; then
381 local rpms=`grep "^Wrote: \+.*\.\($base_arch\.rpm\|noarch\.rpm\)$" "$log_file" | sed -e "s#^Wrote: \+##g"`
382 if [ -n "$rpms" ] ; then
383 echo "Successfully generated the following RPMs:"
[737]384 for f in $rpms ; do
[724]385 echo "$f"
386 done
387 else
388 warn "Cannot find .$base_arch.rpm/.noarch.rpm file names in '$log_file'."
389 fi
390 fi
391}
392
[738]393repo_dir_for_file()
394{
395 # $1 = input file name
396 # $2 = var name to save dir to
397
398 [ -n "$1" -a -n "$2" ] || die "Invalid arguments."
399
400 local _repo_dir_for_file_ret=
401 case "$1" in
402 *.src.rpm)
403 eval _repo_dir_for_file_ret="$RPMBUILD_BOT_UPLOAD_REPO_LAYOUT_srpm"
404 ;;
405 *.*.rpm)
406 local arch="${1%.rpm}"
407 arch="${arch##*.}"
408 [ -n "$arch" ] || die "No arch spec in file name '$1'."
409 eval _repo_dir_for_file_ret="$RPMBUILD_BOT_UPLOAD_REPO_LAYOUT_rpm"
410 ;;
411 *.zip)
412 eval _repo_dir_for_file_ret="$RPMBUILD_BOT_UPLOAD_REPO_LAYOUT_zip"
413 ;;
414 esac
415
416 eval "$2=\$_repo_dir_for_file_ret"
417}
418
[724]419upload_cmd()
420{
421 # Check settings.
422 test -n "$RPMBUILD_BOT_UPLOAD_REPO_LIST" || die "RPMBUILD_BOT_UPLOAD_REPO_LIST is empty."
423
424 local repo="$command_arg"
425 [ -z "$repo" ] && repo="${RPMBUILD_BOT_UPLOAD_REPO_LIST%% *}"
426
427 check_dir_var "RPMBUILD_BOT_UPLOAD_${repo}_DIR"
428
429 eval local base="\$RPMBUILD_BOT_UPLOAD_${repo}_DIR"
430
431 [ -f "$spec_list" ] || die \
432"File '$spec_list' is not found.
[737]433You may need to build the packages using the 'build' command."
[724]434
[737]435 local files=
436 read_file_list "$spec_list" files
437 for f in $files; do
[738]438 local d=
439 repo_dir_for_file "$f" d
440 [ -n "$d" ] || die "Unsupported file name '$f' in '$spec_list'."
[724]441 [ -d "$d" ] || die "'$d' is not a directory."
442 [ -f "$d/${f##*/}" -a -z "$force" ] && die \
443"File '$d/${f##*/}' already exists.
444Use the -f option to force uploading if you are sure the existing
445packages in the repository should be discarded."
446 echo "Copying $f to $d..."
447 run cp -p "$f" "$d"
[737]448 done
[724]449
450 # On success, delete the uploaded packages and archive log files.
[737]451 for f in $files; do
[724]452 echo "Removing $f..."
[737]453 run rm -f "$f"
454 done
[724]455
456 # Note: versioned .list file will remain in archive forever (for further reference).
457 echo "Removing old '$spec_name' logs from $log_dir/archive..."
458 rm -f "$log_dir/archive/$spec_name".*.log "$log_dir/archive/$spec_name".list
459 echo "Moving '$spec_name' logs to $log_dir/archive..."
460 run mv "$log_base".*.log "$log_base".*.list "$log_base".list "$log_dir/archive/"
461}
462
[738]463clean_cmd()
464{
465 [ -f "$spec_list" ] || die \
466"File '$spec_list' is not found.
467You man need to build the packages using the 'build' command."
468
469 local files=
470 read_file_list "$spec_list" files
471
472 for f in $files; do
473 echo "Removing $f..."
474 run rm -f "$f"
475 done
476
477 echo "Removing '$spec_name' logs from $log_dir..."
478 rm -f "$log_base".*.log "$log_base".*.list "$log_base".list
479}
480
481remove_cmd()
482{
483 # Check settings.
484 [ -n "$spec_ver" ] || die "SPEC parameter lacks version specification."
485
486 test -n "$RPMBUILD_BOT_UPLOAD_REPO_LIST" || die "RPMBUILD_BOT_UPLOAD_REPO_LIST is empty."
487
488 local repo="$command_arg"
489 [ -z "$repo" ] && repo="${RPMBUILD_BOT_UPLOAD_REPO_LIST%% *}"
490
491 check_dir_var "RPMBUILD_BOT_UPLOAD_${repo}_DIR"
492
493 eval local base="\$RPMBUILD_BOT_UPLOAD_${repo}_DIR"
494
495 local ver_list="$log_dir/archive/$spec_name.$spec_ver.list"
496 [ -f "$ver_list" ] || die "File '$ver_list' is not found."
497
498 local files=
499 read_file_list "$ver_list" files 'local dir=; repo_dir_for_file $file dir; file="${dir}/${file##*/}"'
500
501 for f in $files; do
502 echo "Removing $f..."
503 run rm -f "$f"
504 done
505
506 echo "Removing $ver_list..."
507 run rm -f "$ver_list"
508
509 # Also remove the logs of last "build" if we are removing the last "build" package.
510 if [ -L "$log_dir/archive/$spec_name.list" -a \
511 `readlink "$log_dir/archive/$spec_name.list"` = "$spec_name.$spec_ver.list" ] ; then
512 echo "Removing '$spec_name' logs from $log_dir/archive..."
513 rm -f "$log_dir/archive/$spec_name".*.log "$log_dir/archive/$spec_name".list
514 fi
515}
516
[724]517#
518# Main.
519#
520
521# Parse command line.
522while [ -n "$1" ] ; do
523 case "$1" in
524 -*)
525 options="$*"
526 while [ -n "$1" ] ; do
527 case "$1" in
528 -f) force="-f"
529 ;;
530 *) usage
531 ;;
532 esac
533 shift
534 done
535 break
[518]536 ;;
[724]537 *)
538 if [ -z "$spec" ] ; then
539 spec="$1"
540 else
541 command="$1"
542 fi
[518]543 ;;
[724]544 esac
545 shift
[518]546done
547
[724]548[ -n "$spec" ] || usage
549[ -z "$command" ] && command="build"
[467]550
[738]551spec_ver="${spec#*=}"
552spec="${spec%=*}"
553[ "$spec" = "$spec_ver" ] && spec_ver=
554
[724]555command_name="${command%=*}"
556command_arg="${command#*=}"
557[ "$command_name" = "$command_arg" ] && command_arg=
[467]558
[738]559need_spec_file=
560
561# Validate commands.
[724]562case "$command_name" in
[738]563 build|test)
564 need_spec_file=1
[724]565 ;;
[738]566 upload|clean|remove)
567 ;;
[724]568 *) usage
569 ;;
570esac
[467]571
[724]572# Query all rpmbuild macros in a single run as it may be slow.
573eval `rpmbuild.exe --eval='rpmbuild_dir="%_topdir" ; spec_dir="%_specdir"' | tr '\\\' /`
[467]574
[724]575log_dir="$rpmbuild_dir/logs"
576zip_dir="$rpmbuild_dir/zip"
[467]577
[724]578spec=`echo $spec | tr '\\\' /`
[467]579
[724]580spec_name="${spec##*/}"
581
582if [ "$spec_name" = "$spec" ] ; then
583 # No path information, use SPECS
584 spec_full="$spec_dir/${spec_name%.spec}.spec"
585else
586 # Has some path, use it.
587 spec_full="${spec%.spec}.spec"
588fi
589
590spec_name="${spec_name%.spec}"
591
[738]592[ -z "$need_spec_file" -o -f "$spec_full" ] || die "Spec file '$spec_full' is not found."
[724]593
594# Prepare some (non-rpmbuild-standard) directories.
595run mkdir -p "$log_dir"
596run mkdir -p "$log_dir/archive"
597run mkdir -p "$log_dir/test"
598run mkdir -p "$zip_dir"
599
600[ -z "$command" ] && command="build"
601
602command_name="${command%=*}"
603comand_arg="${command#*=}"
604[ "$command_name" = "$command_arg" ] && command_arg=
605
606log_base="$log_dir/$spec_name"
607spec_list="$log_base.list"
608
609echo "Package: $spec_name"
610echo "Command: $command $options"
611
612# Set up the rpmbuild-bot environment.
613. "${0%%.sh}-env.sh"
614
615run eval "${command_name}_cmd"
616
[467]617echo "All done."
[724]618
619exit 0
Note: See TracBrowser for help on using the repository browser.