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

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

rpmbuild-bot: Save timestamps of generated files in list.

This is used to check if the actual files match the ones that
were generated by rpmbuild-bot when performing uploading or
cleanup.

File size: 15.0 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 [ upload[=REPO] | test[=MODE] ] [-f]
34#
35# MYAPP is the name of the RPM package spec file (extension is optional,
36# .spec is assumed). The spec file is searched in the SPECS directory of the
37# rpmbuild tree (usually $USER/rpmbuild/SPECS, rpmbuild --eval='%_specdir'
38# will show the exact location). This may be overriden by giving a spec file
39# with a path specification.
40#
41# The second argument defines the command to perform. The default command is
42# "build". The following sections will describe each command.
43#
44# Building packages
45# -----------------
46#
47# The "build" is the main command which is used to generate packages for
48# distribution. This command does the following:
49#
50# 1. Build all RPM packages for all architectures specified in
51# $RPMBUILD_BOT_ARCH_LIST. The packages are stored in the RPMS
52# directory of the rpmbuild tree.
53# 2. Build the source RPM. Stored in the SRPMS directory.
54# 3. Create a ZIP package from the RPMs for the architecture specified
55# last in $RPMBUILD_BOT_ARCH_LIST.
56#
57# The build process for each architecture is stored in a log file in the logs
58# directory of the rpmbuild tree (`rpmbuild --eval='%_topdir'/logs`). Creation
59# of the source RPM and ZIP files is also logged, into separate log files.
60#
61# The "build" command will fail if the log directory contains files from a
62# successfull run of another "build" command for this package. This is to
63# prevent overwriting successfully built packages that are not yet uploaded to
64# the distribution repository (see the "upload" command). You should either
65# run the "upload" command or use the -f option to force removal of these
66# log files and the corresponding package files if you are absolutely sure they
67# should be discarded.
68#
69# Doing test builds
70# -----------------
71#
72# The "test" command is used to build packages for one architecture for testing
73# purposes. In this more, neither the source RPM nor the ZIP file is created.
74# Also, a special option is automatically passed to rpmbuild to clear the %dist
75# variable to indicate that this is a local, non-distribution build. Such
76# packages will always be "older" than the corresponding builds with %dist
77# so that they will be automatically updated on the next yum update to the
78# %dist ones. The packages built in "test" mode are NOT intended for
79# distribution, they are meant only for local testing before performing the
80# full build of everything.
81#
82# It is possible to configure which steps of the build to perform using the MODE
83# argument to the "test" command which may take one of the following values:
84#
85# prep Execute the %prep section of the spec file only.
86# build Execute the %build section only (requres %prep to be executed
87# before). May be run multiple times.
88# install Execute the %install sectuion only (requires "prep" and "build"
89# to be executed before). May be run multiple times.
90# pack Create the RPM packages (requires "prep", "build" and "install"
91# to be executed before). Note that this step will also execute
92# the %clean section so that a new "build" + "install" execution is
93# necessary for "pack" to succeed.
94#
95# When no MODE argument is given, all steps are executed in a proper order.
96#
97# The results of the "test" command are stored in a log file in the logs/test
98# directory of the rpmbuild tree. The previous log file, if any, is given a .bak
99# extension (the previous .bak file will be deleted).
100#
101# Uploading packages
102# ------------------
103#
104# The "upload" command will upload the packages built with the "build"
105# command to the official distribution channel configured in
106# rpmbuild-bot-env.sh. The REPO argument specifies one of the configured
107# repositories. When REPO is not given, the default repository is used
108# (usually experimental or similar).
109#
110# Note that the "upload" command needs log files from the "build" command
111# and will fail otherwise.
112#
113# Upon successful completion, the "upload" command will remove all uploaded
114# RPM and ZIP packages and will move all "build" log files to logs/archive.
115#
116# Return value
117# ------------
118#
119# The rpmbuild-bot.sh script will return a zero exit code upon successful
120# completion and non-zero otherwise. The log files should be inspected to
121# check for a reason of the failure.
122#
123
124#
125# Helpers.
126#
127
128run()
129{
130 "$@"
131 local rc=$?
132 if test $rc != 0 ; then
133 echo "ERROR: The following command failed with error code $rc:"
134 echo $@
135 exit $rc
136 fi
137}
138
139log_run()
140{
141 log="$1"
142 shift
143 "$@" >"$log" 2>&1
144 local rc=$?
145 if test $rc != 0 ; then
146 echo "ERROR: The following command failed with error code $rc:"
147 echo $@
148 echo "You will find more information in file '$log'."
149 echo "Here are the last 10 lines of output:"
150 echo ""
151 tail "$log" -n 10
152 exit $rc
153 fi
154}
155
156warn()
157{
158 echo "WARNING: $1"
159}
160
161die()
162{
163 echo "ERROR: $1"
164 exit 1
165}
166
167check_dir_var()
168{
169 eval local val="\$$1"
170 [ -n "$val" ] || die "$1 is empty."
171 [ -d "$val" ] || die "$1 is '$val', not a valid directory."
172}
173
174read_file_list()
175{
176 # $1 = file list list filename
177 # $2 = var name where to save read file names (optional)
178
179 local _read_file_list_ret=
180 # Check timestamps.
181 while read l; do
182 local f="${l#*|}"
183 local t="${l%%|*}"
184 [ "$f" = "$t" ] && die "Line '$l' in '$1' does not contain timestamps."
185 local a=`stat -c '%Y' "$f"`
186 if [ "$t" != "$a" ] ; then
187 die "Recorded timestamp $t doesn't match actual timestamp $a for file '$f'."
188 fi
189 _read_file_list_ret="$_read_file_list_ret${_read_file_list_ret:+ }$f"
190 done < "$1"
191 # Return the files (if requested).
192 [ -n "$2" ] && eval "$2=\$_read_file_list_ret"
193}
194
195usage()
196{
197 echo "Usage:"
198 sed -n -e "s/rpmbuild-bot.sh/${0##*/}/g" -e 's/^# > / /p' < "$0"
199 exit 255
200}
201
202build_cmd()
203{
204 # Check settings.
205 test -n "$RPMBUILD_BOT_ARCH_LIST" || die "RPMBUILD_BOT_ARCH_LIST is empty."
206
207 local base_arch="${RPMBUILD_BOT_ARCH_LIST##* }"
208
209 echo "Spec file: $spec_full"
210 echo "Targets: $RPMBUILD_BOT_ARCH_LIST + SRPM + ZIP ($base_arch)"
211
212 if [ -f "$spec_list" ] ; then
213 if [ -z "$force" ] ; then
214 die "File '$spec_list' already exists.
215This file indicates a successful build that was not yet uploaded.
216Either run the '"'upload'"' command to upload the generated RPM and ZIP
217packages to the distribution repository or use the -f option to
218force their removal if you are sure they should be discarded."
219 fi
220
221 echo "Detected successful build in $spec_list, cleaning up due to -f option..."
222 local files=
223 read_file_list "$spec_list" files
224 for f in $files; do
225 echo "Removing $f..."
226 run rm -f "$f"
227 done
228 unset files
229
230 echo "Removing $log_base.*.log and .list files..."
231 rm -f "$log_base".*.log "$log_base".*.list "$log_base".list
232 fi
233
234 # Generate RPMs.
235 for arch in $RPMBUILD_BOT_ARCH_LIST ; do
236 echo "Creating RPMs for '$arch' target (logging to $log_base.$arch.log)..."
237 log_run "$log_base.$arch.log" rpmbuild.exe --target=$arch -bb "$spec_full"
238 done
239
240 # Generate SRPM.
241 echo "Creating SRPM (logging to $log_base.srpm.log)..."
242 log_run "$log_base.srpm.log" rpmbuild -bs "$spec_full"
243
244 # Find SRPM file name in the log.
245 local src_rpm=`grep "^Wrote: \+.*\.src\.rpm$" "$log_base.srpm.log" | sed -e "s#^Wrote: \+##g"`
246 [ -n "$src_rpm" ] || die "Cannot find .src.rpm file name in '$log_base.srpm.log'."
247
248 # Find package version.
249 local ver_full="${src_rpm%.src.rpm}"
250 ver_full="${ver_full##*/}"
251 [ "${ver_full%%-[0-9]*}" = "$spec_name" ] || die \
252"SRPM name '${src_rpm##*/}' does not match .spec name ('$spec_name').
253Either rename '$spec_name.spec' to '${ver_full%%-[0-9]*}.spec' or set 'Name:' tag to '$spec_name'."
254 ver_full="${ver_full#${spec_name}-}"
255 [ -n "$ver_full" ] || die "Cannot deduce package version from '$src_rpm'."
256
257 # Find all RPM packages for the base arch (note the quotes around `` - it's to preserve multi-line result).
258 local rpms="`grep "^Wrote: \+.*\.\($base_arch\.rpm\|noarch\.rpm\)$" "$log_base.$base_arch.log" | sed -e "s#^Wrote: \+##g"`"
259 [ -n "$rpms" ] || die "Cannot find .$base_arch.rpm/.noarch.rpm file names in '$log_base.base_arch.log'."
260
261 local ver_full_zip=`echo $ver_full | tr . _`
262 local zip="$zip_dir/$spec_name-$ver_full_zip.zip"
263
264 # Generate ZIP.
265 echo "Creating ZIP (logging to $log_base.zip.log)..."
266 create_zip()
267 {(
268 run cd "$zip_dir"
269 rm -r "@unixroot" 2> /dev/null
270 # Note no quoters around $rpms - it's to split at EOL.
271 for f in $rpms ; do
272 echo "Unpacking $f..."
273 run rpm2cpio "$f" | cpio -idm
274 done
275 rm -f "$zip" 2> /dev/null
276 echo "Creating '$zip'..."
277 run zip -mry9 "$zip" "@unixroot"
278 )}
279 log_run "$log_base.zip.log" create_zip
280
281 local ver_list="$log_base.$ver_full.list"
282
283 # Generate list of all generated packages for further reference.
284 echo "Creating list file ($ver_list)..."
285 echo `stat -c '%Y' "$src_rpm"`"|$src_rpm" > "$ver_list"
286 echo `stat -c '%Y' "$zip"`"|$zip" >> "$ver_list"
287 # Save base arch RPMs.
288 for f in $rpms ; do
289 echo `stat -c '%Y' "$f"`"|$f" >> "$ver_list"
290 done
291 # Save other arch RPMs.
292 for arch in ${RPMBUILD_BOT_ARCH_LIST%${base_arch}} ; do
293 rpms="`grep "^Wrote: \+.*\.$arch\.rpm$" "$log_base.$arch.log" | sed -e "s#^Wrote: \+##g"`"
294 [ -n "$rpms" ] || die "Cannot find .$arch.rpm file names in '$log_base.arch.log'."
295 for f in $rpms ; do
296 echo `stat -c '%Y' "$f"`"|$f" >> "$ver_list"
297 done
298 done
299
300 # Everything succeeded. Symlink the list file so that "upload" can find it.
301 run ln -s "${ver_list##*/}" "$log_base.list"
302}
303
304test_cmd()
305{
306 echo "Spec file: $spec_full"
307
308 local base_arch="${RPMBUILD_BOT_ARCH_LIST##* }"
309 local cmds=
310
311 [ -z "$command_arg" ] && command_arg="all"
312
313 case "$command_arg" in
314 all)
315 cmds="$cmds -bb"
316 ;;
317 prep)
318 cmds="$cmds -bp --short-circuit"
319 ;;
320 build)
321 cmds="$cmds -bc --short-circuit"
322 ;;
323 install)
324 cmds="$cmds -bi --short-circuit"
325 ;;
326 pack)
327 cmds="$cmds -bb --short-circuit"
328 ;;
329 *)
330 die "Invalid test build sub-command '$command_arg'."
331 ;;
332 esac
333
334 local log_file="$log_dir/test/$spec_name.log"
335
336 if [ -f "$log_file" ] ; then
337 rm -f "$log_file.bak"
338 run mv "$log_file" "$log_file.bak"
339 fi
340
341 echo "Doing test RPM build for '$base_arch' target (logging to $log_file)..."
342 log_run "$log_file" rpmbuild.exe "--define=dist %nil" --target=$base_arch $cmds $spec_full
343
344 # Show the generated RPMs when appropriate.
345 if [ "$command_arg" = "all" -o "$command_arg" = "pack" ] ; then
346 local rpms=`grep "^Wrote: \+.*\.\($base_arch\.rpm\|noarch\.rpm\)$" "$log_file" | sed -e "s#^Wrote: \+##g"`
347 if [ -n "$rpms" ] ; then
348 echo "Successfully generated the following RPMs:"
349 for f in $rpms ; do
350 echo "$f"
351 done
352 else
353 warn "Cannot find .$base_arch.rpm/.noarch.rpm file names in '$log_file'."
354 fi
355 fi
356}
357
358upload_cmd()
359{
360 # Check settings.
361 test -n "$RPMBUILD_BOT_UPLOAD_REPO_LIST" || die "RPMBUILD_BOT_UPLOAD_REPO_LIST is empty."
362
363 local repo="$command_arg"
364 [ -z "$repo" ] && repo="${RPMBUILD_BOT_UPLOAD_REPO_LIST%% *}"
365
366 check_dir_var "RPMBUILD_BOT_UPLOAD_${repo}_DIR"
367
368 eval local base="\$RPMBUILD_BOT_UPLOAD_${repo}_DIR"
369
370 [ -f "$spec_list" ] || die \
371"File '$spec_list' is not found.
372You may need to build the packages using the 'build' command."
373
374 local files=
375 read_file_list "$spec_list" files
376 for f in $files; do
377 case "$f" in
378 *.src.rpm)
379 eval local d="$RPMBUILD_BOT_UPLOAD_REPO_LAYOUT_srpm"
380 ;;
381 *.*.rpm)
382 local arch="${f%.rpm}"
383 arch="${arch##*.}"
384 [ -n "$arch" ] || die "No arch spec in file name '$f' in '$spec_list'."
385 eval d="$RPMBUILD_BOT_UPLOAD_REPO_LAYOUT_rpm"
386 ;;
387 *.zip)
388 eval d="$RPMBUILD_BOT_UPLOAD_REPO_LAYOUT_zip"
389 ;;
390 *)
391 die "Unsupported file name '$f' in '$spec_list'."
392 ;;
393 esac
394 [ -d "$d" ] || die "'$d' is not a directory."
395 [ -f "$d/${f##*/}" -a -z "$force" ] && die \
396"File '$d/${f##*/}' already exists.
397Use the -f option to force uploading if you are sure the existing
398packages in the repository should be discarded."
399 echo "Copying $f to $d..."
400 run cp -p "$f" "$d"
401 done
402
403 # On success, delete the uploaded packages and archive log files.
404 for f in $files; do
405 echo "Removing $f..."
406 run rm -f "$f"
407 done
408
409 # Note: versioned .list file will remain in archive forever (for further reference).
410 echo "Removing old '$spec_name' logs from $log_dir/archive..."
411 rm -f "$log_dir/archive/$spec_name".*.log "$log_dir/archive/$spec_name".list
412 echo "Moving '$spec_name' logs to $log_dir/archive..."
413 run mv "$log_base".*.log "$log_base".*.list "$log_base".list "$log_dir/archive/"
414}
415
416#
417# Main.
418#
419
420# Parse command line.
421while [ -n "$1" ] ; do
422 case "$1" in
423 -*)
424 options="$*"
425 while [ -n "$1" ] ; do
426 case "$1" in
427 -f) force="-f"
428 ;;
429 *) usage
430 ;;
431 esac
432 shift
433 done
434 break
435 ;;
436 *)
437 if [ -z "$spec" ] ; then
438 spec="$1"
439 else
440 command="$1"
441 fi
442 ;;
443 esac
444 shift
445done
446
447[ -n "$spec" ] || usage
448[ -z "$command" ] && command="build"
449
450command_name="${command%=*}"
451command_arg="${command#*=}"
452[ "$command_name" = "$command_arg" ] && command_arg=
453
454case "$command_name" in
455 build|upload|test)
456 ;;
457 *) usage
458 ;;
459esac
460
461# Query all rpmbuild macros in a single run as it may be slow.
462eval `rpmbuild.exe --eval='rpmbuild_dir="%_topdir" ; spec_dir="%_specdir"' | tr '\\\' /`
463
464log_dir="$rpmbuild_dir/logs"
465zip_dir="$rpmbuild_dir/zip"
466
467spec=`echo $spec | tr '\\\' /`
468
469spec_name="${spec##*/}"
470
471if [ "$spec_name" = "$spec" ] ; then
472 # No path information, use SPECS
473 spec_full="$spec_dir/${spec_name%.spec}.spec"
474else
475 # Has some path, use it.
476 spec_full="${spec%.spec}.spec"
477fi
478
479spec_name="${spec_name%.spec}"
480
481[ -f "$spec_full" ] || die "Spec file '$spec_full' is not found."
482
483# Prepare some (non-rpmbuild-standard) directories.
484run mkdir -p "$log_dir"
485run mkdir -p "$log_dir/archive"
486run mkdir -p "$log_dir/test"
487run mkdir -p "$zip_dir"
488
489[ -z "$command" ] && command="build"
490
491command_name="${command%=*}"
492comand_arg="${command#*=}"
493[ "$command_name" = "$command_arg" ] && command_arg=
494
495log_base="$log_dir/$spec_name"
496spec_list="$log_base.list"
497
498echo "Package: $spec_name"
499echo "Command: $command $options"
500
501# Set up the rpmbuild-bot environment.
502. "${0%%.sh}-env.sh"
503
504run eval "${command_name}_cmd"
505
506echo "All done."
507
508exit 0
Note: See TracBrowser for help on using the repository browser.