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

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

rpmbuild-bot: Disallow different .spec and main package names.

This is to enforce consistency and avoid possible naming conflicts
as we use the main package name for many things (like log file names)
and it must be unique.

File size: 14.2 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
174usage()
175{
176 echo "Usage:"
177 sed -n -e "s/rpmbuild-bot.sh/${0##*/}/g" -e 's/^# > / /p' < "$0"
178 exit 255
179}
180
181build_cmd()
182{
183 # Check settings.
184 test -n "$RPMBUILD_BOT_ARCH_LIST" || die "RPMBUILD_BOT_ARCH_LIST is empty."
185
186 local base_arch="${RPMBUILD_BOT_ARCH_LIST##* }"
187
188 echo "Spec file: $spec_full"
189 echo "Targets: $RPMBUILD_BOT_ARCH_LIST + SRPM + ZIP ($base_arch)"
190
191 if [ -f "$spec_list" ] ; then
192 if [ -z "$force" ] ; then
193 die "File '$spec_list' already exists.
194This file indicates a successful build that was not yet uploaded.
195Either run the '"'upload'"' command to upload the generated RPM and ZIP
196packages to the distribution repository or use the -f option to
197force their removal if you are sure they should be discarded."
198 fi
199
200 echo "Detected successful build in $spec_list, cleaning up due to -f option..."
201 while read f; do
202 echo "Removing $f..."
203 rm -f "$f"
204 done < "$spec_list"
205
206 echo "Removing $log_base.*.log and .list files..."
207 rm -f "$log_base".*.log "$log_base".*.list "$log_base".list
208 fi
209
210 # Generate RPMs.
211 for arch in $RPMBUILD_BOT_ARCH_LIST ; do
212 echo "Creating RPMs for '$arch' target (logging to $log_base.$arch.log)..."
213 log_run "$log_base.$arch.log" rpmbuild.exe --target=$arch -bb "$spec_full"
214 done
215
216 # Generate SRPM.
217 echo "Creating SRPM (logging to $log_base.srpm.log)..."
218 log_run "$log_base.srpm.log" rpmbuild -bs "$spec_full"
219
220 # Find SRPM file name in the log.
221 local src_rpm=`grep "^Wrote: \+.*\.src\.rpm$" "$log_base.srpm.log" | sed -e "s#^Wrote: \+##g"`
222 [ -n "$src_rpm" ] || die "Cannot find .src.rpm file name in '$log_base.srpm.log'."
223
224 # Find package version.
225 local ver_full="${src_rpm%.src.rpm}"
226 ver_full="${ver_full##*/}"
227 [ "${ver_full%%-[0-9]*}" = "$spec_name" ] || die \
228"SRPM name '${src_rpm##*/}' does not match .spec name ('$spec_name').
229Either rename '$spec_name.spec' to '${ver_full%%-[0-9]*}.spec' or set 'Name:' tag to '$spec_name'."
230 ver_full="${ver_full#${spec_name}-}"
231 [ -n "$ver_full" ] || die "Cannot deduce package version from '$src_rpm'."
232
233 # Find all RPM packages for the base arch (note the quotes around `` - it's to preserve multi-line result).
234 local rpms="`grep "^Wrote: \+.*\.\($base_arch\.rpm\|noarch\.rpm\)$" "$log_base.$base_arch.log" | sed -e "s#^Wrote: \+##g"`"
235 [ -n "$rpms" ] || die "Cannot find .$base_arch.rpm/.noarch.rpm file names in '$log_base.base_arch.log'."
236
237 local ver_full_zip=`echo $ver_full | tr . _`
238 local zip="$zip_dir/$spec_name-$ver_full_zip.zip"
239
240 # Generate ZIP.
241 echo "Creating ZIP (logging to $log_base.zip.log)..."
242 create_zip()
243 {(
244 run cd "$zip_dir"
245 rm -r "@unixroot" 2> /dev/null
246 # Note no quoters around $rpms - it's to split at EOL.
247 for f in $rpms ; do
248 echo "Unpacking $f..."
249 run rpm2cpio "$f" | cpio -idm
250 done
251 rm -f "$zip" 2> /dev/null
252 echo "Creating '$zip'..."
253 run zip -mry9 "$zip" "@unixroot"
254 )}
255 log_run "$log_base.zip.log" create_zip
256
257 local ver_list="$log_base.$ver_full.list"
258
259 # Generate list of all generated packages for further reference.
260 echo "Creating list file ($ver_list)..."
261 echo "$src_rpm" > "$ver_list"
262 echo "$zip" >> "$ver_list"
263 # Save base arch RPMs.
264 for f in "$rpms" ; do
265 echo "$f" >> "$ver_list"
266 done
267 # Save other arch RPMs.
268 for arch in ${RPMBUILD_BOT_ARCH_LIST%${base_arch}} ; do
269 rpms="`grep "^Wrote: \+.*\.$arch\.rpm$" "$log_base.$arch.log" | sed -e "s#^Wrote: \+##g"`"
270 [ -n "$rpms" ] || die "Cannot find .$arch.rpm file names in '$log_base.arch.log'."
271 for f in $rpms ; do
272 echo "$f" >> "$ver_list"
273 done
274 done
275
276 # Everything succeeded. Symlink the list file so that "upload" can find it.
277 run ln -s "${ver_list##*/}" "$log_base.list"
278}
279
280test_cmd()
281{
282 echo "Spec file: $spec_full"
283
284 local base_arch="${RPMBUILD_BOT_ARCH_LIST##* }"
285 local cmds=
286
287 [ -z "$command_arg" ] && command_arg="all"
288
289 case "$command_arg" in
290 all)
291 cmds="$cmds -bb"
292 ;;
293 prep)
294 cmds="$cmds -bp --short-circuit"
295 ;;
296 build)
297 cmds="$cmds -bc --short-circuit"
298 ;;
299 install)
300 cmds="$cmds -bi --short-circuit"
301 ;;
302 pack)
303 cmds="$cmds -bb --short-circuit"
304 ;;
305 *)
306 die "Invalid test build sub-command '$command_arg'."
307 ;;
308 esac
309
310 local log_file="$log_dir/test/$spec_name.log"
311
312 if [ -f "$log_file" ] ; then
313 rm -f "$log_file.bak"
314 run mv "$log_file" "$log_file.bak"
315 fi
316
317 echo "Doing test RPM build for '$base_arch' target (logging to $log_file)..."
318 log_run "$log_file" rpmbuild.exe "--define=dist %nil" --target=$base_arch $cmds $spec_full
319
320 # Show the generated RPMs when appropriate.
321 if [ "$command_arg" = "all" -o "$command_arg" = "pack" ] ; then
322 local rpms=`grep "^Wrote: \+.*\.\($base_arch\.rpm\|noarch\.rpm\)$" "$log_file" | sed -e "s#^Wrote: \+##g"`
323 if [ -n "$rpms" ] ; then
324 echo "Successfully generated the following RPMs:"
325 for f in "$rpms" ; do
326 echo "$f"
327 done
328 else
329 warn "Cannot find .$base_arch.rpm/.noarch.rpm file names in '$log_file'."
330 fi
331 fi
332}
333
334upload_cmd()
335{
336 # Check settings.
337 test -n "$RPMBUILD_BOT_UPLOAD_REPO_LIST" || die "RPMBUILD_BOT_UPLOAD_REPO_LIST is empty."
338
339 local repo="$command_arg"
340 [ -z "$repo" ] && repo="${RPMBUILD_BOT_UPLOAD_REPO_LIST%% *}"
341
342 check_dir_var "RPMBUILD_BOT_UPLOAD_${repo}_DIR"
343
344 eval local base="\$RPMBUILD_BOT_UPLOAD_${repo}_DIR"
345
346 [ -f "$spec_list" ] || die \
347"File '$spec_list' is not found.
348You man need to build the packages using the 'build' command."
349
350 while read f; do
351 case "$f" in
352 *.src.rpm)
353 eval local d="$RPMBUILD_BOT_UPLOAD_REPO_LAYOUT_srpm"
354 ;;
355 *.*.rpm)
356 local arch="${f%.rpm}"
357 arch="${arch##*.}"
358 [ -n "$arch" ] || die "No arch spec in file name '$f' in '$spec_list'."
359 eval d="$RPMBUILD_BOT_UPLOAD_REPO_LAYOUT_rpm"
360 ;;
361 *.zip)
362 eval d="$RPMBUILD_BOT_UPLOAD_REPO_LAYOUT_zip"
363 ;;
364 *)
365 die "Unsupported file name '$f' in '$spec_list'."
366 ;;
367 esac
368 [ -d "$d" ] || die "'$d' is not a directory."
369 [ -f "$d/${f##*/}" -a -z "$force" ] && die \
370"File '$d/${f##*/}' already exists.
371Use the -f option to force uploading if you are sure the existing
372packages in the repository should be discarded."
373 echo "Copying $f to $d..."
374 run cp -p "$f" "$d"
375 done < "$spec_list"
376
377 # On success, delete the uploaded packages and archive log files.
378 while read f; do
379 echo "Removing $f..."
380 rm -f "$f"
381 done < "$spec_list"
382
383 # Note: versioned .list file will remain in archive forever (for further reference).
384 echo "Removing old '$spec_name' logs from $log_dir/archive..."
385 rm -f "$log_dir/archive/$spec_name".*.log "$log_dir/archive/$spec_name".list
386 echo "Moving '$spec_name' logs to $log_dir/archive..."
387 run mv "$log_base".*.log "$log_base".*.list "$log_base".list "$log_dir/archive/"
388}
389
390#
391# Main.
392#
393
394# Parse command line.
395while [ -n "$1" ] ; do
396 case "$1" in
397 -*)
398 options="$*"
399 while [ -n "$1" ] ; do
400 case "$1" in
401 -f) force="-f"
402 ;;
403 *) usage
404 ;;
405 esac
406 shift
407 done
408 break
409 ;;
410 *)
411 if [ -z "$spec" ] ; then
412 spec="$1"
413 else
414 command="$1"
415 fi
416 ;;
417 esac
418 shift
419done
420
421[ -n "$spec" ] || usage
422[ -z "$command" ] && command="build"
423
424command_name="${command%=*}"
425command_arg="${command#*=}"
426[ "$command_name" = "$command_arg" ] && command_arg=
427
428case "$command_name" in
429 build|upload|test)
430 ;;
431 *) usage
432 ;;
433esac
434
435# Query all rpmbuild macros in a single run as it may be slow.
436eval `rpmbuild.exe --eval='rpmbuild_dir="%_topdir" ; spec_dir="%_specdir"' | tr '\\\' /`
437
438log_dir="$rpmbuild_dir/logs"
439zip_dir="$rpmbuild_dir/zip"
440
441spec=`echo $spec | tr '\\\' /`
442
443spec_name="${spec##*/}"
444
445if [ "$spec_name" = "$spec" ] ; then
446 # No path information, use SPECS
447 spec_full="$spec_dir/${spec_name%.spec}.spec"
448else
449 # Has some path, use it.
450 spec_full="${spec%.spec}.spec"
451fi
452
453spec_name="${spec_name%.spec}"
454
455[ -f "$spec_full" ] || die "Spec file '$spec_full' is not found."
456
457# Prepare some (non-rpmbuild-standard) directories.
458run mkdir -p "$log_dir"
459run mkdir -p "$log_dir/archive"
460run mkdir -p "$log_dir/test"
461run mkdir -p "$zip_dir"
462
463[ -z "$command" ] && command="build"
464
465command_name="${command%=*}"
466comand_arg="${command#*=}"
467[ "$command_name" = "$command_arg" ] && command_arg=
468
469log_base="$log_dir/$spec_name"
470spec_list="$log_base.list"
471
472echo "Package: $spec_name"
473echo "Command: $command $options"
474
475# Set up the rpmbuild-bot environment.
476. "${0%%.sh}-env.sh"
477
478run eval "${command_name}_cmd"
479
480echo "All done."
481
482exit 0
Note: See TracBrowser for help on using the repository browser.