bin/mdw-build: Force use of `bash' to allow file descriptors >= 10.
[profile] / bin / mdw-build
1 #! /bin/bash
2 ###
3 ### Build script for Debian packages
4 ###
5 ### (c) 2008 Mark Wooding
6 ###
7
8 ###----- Licensing notice ---------------------------------------------------
9 ###
10 ### This program is free software; you can redistribute it and/or modify
11 ### it under the terms of the GNU General Public License as published by
12 ### the Free Software Foundation; either version 2 of the License, or
13 ### (at your option) any later version.
14 ###
15 ### This program is distributed in the hope that it will be useful,
16 ### but WITHOUT ANY WARRANTY; without even the implied warranty of
17 ### MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 ### GNU General Public License for more details.
19 ###
20 ### You should have received a copy of the GNU General Public License
21 ### along with this program; if not, write to the Free Software Foundation,
22 ### Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
23
24 ###--------------------------------------------------------------------------
25 ### Conventions for build systems.
26 ###
27 ### This script is designed to work with a variety of `make'-based build
28 ### systems, but there are a number of conventions which must be followed if
29 ### this is going to work properly.
30 ###
31 ### * There must be a `configure.ac', `configure.in', or `.links' file, or
32 ### a `.git' directory in the project top-level, so that we can find it.
33 ###
34 ### * The following `make' variables must be assigned in the top-level
35 ### Makefile, after `mdw-build' has constructed it.
36 ###
37 ### distdir The name of the top-level project directory in the
38 ### source distribution, and the base name for
39 ### distribution archives; should be of the form
40 ### `PROJECT-VERSION'.
41 ###
42 ### The following `make' targets must be available in the top-level
43 ### Makefile.
44 ###
45 ### dist Write to $(distdir).tar.gz a source distribution of
46 ### the package.
47 ###
48 ### distcheck As for `dist', but also build and test the project.
49 ###
50 ### * The source distribution constructed by `make dist' must contain a file
51 ### $(distdir)/RELEASE containing the release name. This isn't currently
52 ### tested, but it might be later.
53
54 set -e
55
56 ###--------------------------------------------------------------------------
57 ### Configuration.
58
59 unset checkout checkoutrev
60 unset setup setupcmd
61 unset sign signkey
62 unset sbuild sbuildsrv
63 unset upload uploadpath
64 unset dput dputtarget
65 unset build distcheck debian clean vpath native
66 for i in \
67 "/etc/mdw-build.conf" \
68 "${XDG_CONFIG_HOME-$HOME/.config}/mdw-build.conf" \
69 "./.mdw-build.conf"
70 do
71 if [ -f "$i" ]; then . "$i"; fi
72 done
73 default_depends () {
74 var=$1 want=$2
75 eval "p=\${$var+t} q=\${$want+t}"
76 case $p,$q in t,*) ;; *,t) eval "$var=yes" ;; *) eval "$var=no" ;; esac
77 }
78 : ${checkout=yes} ${checkoutrev=HEAD}
79 : ${build=test}
80 : ${setup=yes} ${setupcmd=!guess}
81 : ${distcheck=yes}
82 : ${debian=yes}
83 : ${clean=yes}
84 : ${vpath=yes}
85 : ${native=yes}
86 : ${make=make}
87 : ${test=yes}
88 default_depends sbuild sbuildsrv
89 default_depends sign signkey
90 default_depends upload uploadpath
91 default_depends dput dputtarget
92 : ${DEB_BUILD_OPTIONS=parallel=4}; export DEB_BUILD_OPTIONS
93
94 ###--------------------------------------------------------------------------
95 ### Parse options.
96
97 prog=${0##*/}
98
99 usage () {
100 cat <<EOF
101 Usage: $prog [-v] BUILDOPT
102
103 Build options:
104
105 [no]checkout[=REV]
106 [no]release
107 [no]setup[=RUNE]
108 [no]distcheck
109 [no]debian
110 [no]upload[=SERVER:PATH]
111 [no]dput[=TARGET]
112 [no]clean
113 [no]vpath
114 [no]sbuild[=SERVER]
115 [no]sign[=KEYID]
116 [no]test
117 [no]native
118 make=MAKE
119 EOF
120 }
121
122 ## Parse simple options.
123 verbose=no
124 while getopts "hv" opt; do
125 case "$opt" in
126 h) usage; exit 0 ;;
127 v) verbose=yes ;;
128 *) exit 1 ;;
129 esac
130 done
131 shift $((OPTIND - 1))
132
133 ## Parse the build options.
134 maybe_set () {
135 var=$1 want=$2
136 eval "p=\${$want+t}\${$want-nil}"
137 case $p in
138 t) eval $var=yes ;;
139 nil) echo >&2 "$prog: $want not set"; exit 1 ;;
140 esac
141 }
142 for opt; do
143 case "$opt" in
144 checkout) checkout=yes checkoutrev=HEAD ;;
145 checkout=*) checkout=yes checkoutrev=${opt#*=} ;;
146 release) build=release ;;
147 norelease) build=test ;;
148 setup) setup=yes setupcmd=!guess ;;
149 setup=*) setup=yes setupcmd=${opt#*=} ;;
150 upload) maybe_set upload uploadpath ;;
151 upload=*) upload=yes uploadpath=${opt#*=} ;;
152 sign) maybe_set sign signkey ;;
153 sign=*) sign=yes signkey=${opt#*=} ;;
154 sbuild) maybe_set sbuild sbuildsrv ;;
155 sbuild=*) sbuild=yes sbuildsrv=${opt#*=} ;;
156 dput) maybe_set dput dputtarget ;;
157 dput=*) dput=yes dputtarget=${opt#*=} ;;
158 make=*) make=${opt#*=} ;;
159
160 distcheck | debian | clean | vpath | native | test)
161 eval "$opt=yes"
162 ;;
163 nocheckout | nosetup | nodistcheck | nodebian | \
164 noupload | nodput | noclean | novpath | nonative | notest | \
165 nosbuild | nosign )
166 eval "${opt#no}=no"
167 ;;
168 *)
169 usage >&2
170 exit 1
171 ;;
172 esac
173 done
174
175 ## Parse DEB_BUILD_OPTIONS.
176 jobs=1
177 set -- $DEB_BUILD_OPTIONS
178 for opt; do
179 case "$opt" in
180 parallel=*) jobs=${opt#*=} ;;
181 nocheck) test=no ;;
182 esac
183 done
184
185 makeopts=""
186 case $jobs in 1) ;; *) makeopts="$makeopts -j$jobs" ;; esac
187
188 ###--------------------------------------------------------------------------
189 ### Utility functions.
190
191 ## File descriptor assignments:
192 ##
193 ## 0 -- original stdin (never touched)
194 ## 1, 2 -- stdout, stderr, redirected to 3 while running comamnds
195 ## log -- logfile and original stderr (verbose), or logfile only (quiet);
196 ## captures command output
197 ## diag -- logfile; primary diagnostic output
198 ## term -- orginal stderr; secondary diagnostic output (with colours)
199
200 notify () {
201 colour=$1 message=$2
202 echo $message >&$diag
203 echo "$(tput bold; tput setaf $colour)$message$(tput sgr0; tput op)" >&$term
204 }
205
206 fail () {
207 notify 1 "!!! $*"
208 exit 1
209 }
210
211 warn () {
212 case $build in
213 release) fail "$*" ;;
214 *) notify 5 "??? $*" ;;
215 esac
216 }
217
218 info () {
219 notify 6 "--- $*"
220 }
221
222 assign () {
223 info "$1 = $2"
224 eval "$1=$2"
225 }
226
227 runx () {
228 notify 2 "+++ $*"
229 nice "$@" 2>&$log {log}>&- {diag}>&- {term}>&- || fail "$1: exit $?"
230 }
231
232 run () { runx "$@" >&$log; }
233
234 yesno () {
235 echo -n "(test $*)" >&$diag
236 if "$@" >&$diag 2>&$diag {log}>&- {diag}>&- {term}>&-; then
237 echo "(yes)" >&$diag
238 echo yes
239 else
240 echo "(no)" >&$diag
241 echo no
242 fi
243 }
244
245 ###--------------------------------------------------------------------------
246 ### Do the building.
247
248 ## Some preflight checks.
249 case $test,$build in
250 no,release) fail "refusing to make release build without testing" ;;
251 esac
252 case $test,$distcheck in
253 no,yes)
254 info "forcing \`distcheck' off because tsting disabled"
255 distcheck=no
256 ;;
257 esac
258
259 ## Find the top-level package directory.
260 while [ ! -f configure.ac -a ! -f configure.in -a \
261 ! -f .links -a ! -d .git ]; do
262 case "$(pwd)" in
263 /)
264 fail "couldn't find top-level directory"
265 ;;
266 esac
267 cd ..
268 done
269 toppath=$(pwd)
270
271 ## Build any necessary qualifiers.
272 qual= sep=.
273 case ${SBOX_SESSION_DIR+t},${DEB_BUILD_ARCH+t} in
274 t,t) qual=$qual$sep$DEB_BUILD_ARCH; sep=- ;;
275 t,*) fail "unknown build arch" ;;
276 esac
277
278 ## Construct the output directory.
279 releasepath=$toppath/dist-$build$qual
280 chmod -R +w $releasepath 2>/dev/null || :
281 rm -rf $releasepath 2>/dev/null || :
282 mkdir $releasepath
283 logfile=$releasepath/mdw-build.log
284 exec {term}>&2
285 exec {diag}>>$logfile || fail "Failed to create log."
286 case $verbose in
287 no) exec {log}>&$diag ;;
288 yes) exec {log}> >(tee -a $logfile >&$term {term}>&- {diag}>&-) ;;
289 esac
290
291 ## Repeat the earlier assignments for tbe benefit of the logfile.
292 assign toppath $toppath
293 assign releasepath $releasepath
294 assign logfile $logfile
295
296 ## Do we have a Git repository?
297 case "$checkout,$setup,$(yesno [ -d $toppath/.git ])" in
298 yes,no,*)
299 fail "Inconsistent options: can't check out without setup."
300 ;;
301 yes,yes,no)
302 info "No Git repository found."
303 checkout=no gitver=none
304 ;;
305 yes,yes,yes)
306 cd $toppath
307 [ "$(git ls-files -m)" = "" ] ||
308 warn "working tree has uncommitted changes"
309 ;;
310 esac
311
312 ## Is there Debian build equipment?
313 case "$debian,$(yesno [ -d $toppath/debian ])" in
314 yes,no)
315 info "No debian directory found."
316 debian=no debver=none
317 ;;
318 no,*)
319 debver=none
320 ;;
321 yes,yes)
322 debver=$(dpkg-parsechangelog | sed -n 's/^Version: //p')
323 debsrc=$(dpkg-parsechangelog | sed -n 's/^Source: //p')
324 debname=$(git config user.name) debemail=$(git config user.email)
325 ;;
326 esac
327
328 ## Maybe check out a copy of the source.
329 case "$checkout" in
330 yes)
331 cd $releasepath
332 run git clone -sn $toppath/.git _source
333 assign srcpath $releasepath/_source
334 cd $srcpath
335 run git update-ref refs/heads/mdw-build $checkoutrev ""
336 run git symbolic-ref HEAD refs/heads/mdw-build
337 run git read-tree --reset refs/heads/mdw-build
338 run git checkout-index -afu
339 assign gitversion "$(git describe --abbrev=4)"
340 ;;
341 no)
342 assign srcpath $toppath
343 ;;
344 esac
345
346 ## Check the version number.
347 hack_dch_p=no
348 case "$gitversion,$debver" in
349 none,* | *,none)
350 ;;
351 *)
352 dvref=$(echo "$debver" | tr '~' '_')
353 if [ "$gitversion" = "$dvref" ]; then
354 assign debversion "$debver"
355 else
356 warn "Git version $gitversion doesn't match Debian version $debver"
357 hack_dch=yes
358 dver=$(echo $gitversion | sed 's/-/+/; s/-/./g')
359 case $debver in *~) dver=$debver$dver ;; esac
360 assign debversion "$dver"
361 now=$(date -R)
362 fi
363 ;;
364 esac
365
366 ## Maybe refresh the build machinery.
367 case "$setup" in
368 yes)
369 case $setupcmd in
370 !guess)
371 if [ -f .links ]; then setupcmd=mdw-setup
372 elif [ -x autogen.sh ]; then setupcmd=./autogen.sh
373 elif [ -x setup ]; then setupcmd=./setup
374 elif [ -f configure.ac ]; then setupcmd="autoreconf -is"
375 else setupcmd=mdw-setup
376 fi
377 ;;
378 esac
379 run $setupcmd
380 ;;
381 esac
382
383 ## Initialize the build directory.
384 case "$vpath,$(yesno [ -e $srcpath/configure ])" in
385 yes,yes)
386 assign buildpath $releasepath/_build
387 mkdir $buildpath
388 cd $buildpath
389 run $srcpath/configure
390 ;;
391 no,yes)
392 info "VPATH build disabled"
393 assign buildpath $srcpath
394 distcheck=no
395 cd $srcpath
396 run ./configure
397 ;;
398 *,no)
399 info "no configure script"
400 assign buildpath $srcpath
401 cd $srcpath
402 ;;
403 esac
404
405 ## Discover the release name.
406 cat >find-distdir.mk <<'EOF'
407 include Makefile
408 print-distdir:
409 @bash -c 'echo >&$(fd) $(distdir)'
410 EOF
411 assign distdir \
412 $({ $make -f find-distdir.mk print-distdir fd=$t >/dev/null 2>&1; } {t}>&1)
413
414 ## Get a tarball distribution.
415 case "$distcheck" in
416 yes)
417 run $make $makeopts distcheck
418 ;;
419 no)
420 run $make $makeopts dist
421 ;;
422 esac
423
424 cd $releasepath
425
426 case $native in
427 yes)
428 if ! tar tf $buildpath/$distdir.tar.gz 2>/dev/null | grep -q RELEASE
429 then
430 fail "missing RELEASE file in distribution"
431 fi
432 ;;
433 esac
434
435 run mv $buildpath/$distdir.tar.gz .
436 case $build,$sign in
437 release,yes)
438 run gpg -u$signkey -ab -o$distdir.tar.gz.gpg $distdir.tar.gz
439 ;;
440 esac
441
442 ## Maybe build the Debian packages.
443 case "$debian" in
444 yes)
445 run tar xvfz $distdir.tar.gz
446 cd $distdir
447 case $hack_dch in
448 yes)
449 cat - debian/changelog >debian/changelog.new <<EOF
450 $debsrc ($debversion) experimental; urgency=low
451
452 * Hacking in process, not intended for release.
453
454 -- $debname <$debemail> $now
455
456 EOF
457 mv debian/changelog.new debian/changelog
458 ;;
459 esac
460 sbuildargs=$sbuildsrv
461 case $sbuild,$build in
462 yes,release)
463 case $sign in yes) sbuildargs="-k$signkey $sbuildargs" ;; esac
464 ;;
465 yes,*)
466 if [ -d $toppath/dist-$build.pkgs ]; then
467 sbuildargs="-p$toppath/dist-$build.pkgs $sbuildargs"
468 fi
469 ;;
470 esac
471 case "$sbuild,$test, $DEB_BUILD_OPTIONS " in
472 yes,no,*) sbuildargs="-T $sbuildargs" ;;
473 *" nocheck "*) ;;
474 no,no,*)
475 DEB_BUILD_OPTIONS=${DEB_BUILD_OPTIONS+"$DEB_BUILD_OPTIONS nocheck"}
476 ;;
477 esac
478 case $sbuild,$build,$sign in
479 yes,*) run mdw-sbuild $sbuildargs ;;
480 no,release,yes) run dpkg-buildpackage -k$signkey ;;
481 no,*) run dpkg-buildpackage -us -uc ;;
482 esac
483 ;;
484 esac
485
486 ## Maybe upload Debian packages.
487 cd $releasepath
488 case "$upload,$build" in
489 yes,test) info "Test build: not uploading." ;;
490 yes,release) run rsync $distdir.tar.gz $distdir.tar.gz.gpg $uploadpath ;;
491 esac
492 case "$debian,$upload,$dput,$build" in
493 yes,yes,yes,release) run dput -f $dputtarget *.changes ;;
494 esac
495
496 ## Tidy up.
497 case "$clean" in
498 yes)
499 rm -rf $releasepath/$distdir
500 rm -rf $releasepath/_source
501 rm -rf $releasepath/_build
502 ;;
503 esac
504
505 ## Done.
506 info "All OK."
507
508 ###----- That's all, folks --------------------------------------------------