bin/mdw-build: Do all of the log descriptor setting in one place.
[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=mdw-setup}
81 : ${distcheck=yes}
82 : ${debian=yes}
83 : ${clean=yes}
84 : ${vpath=yes}
85 : ${native=yes}
86 : ${make=make}
87 default_depends sbuild sbuildsrv
88 default_depends sign signkey
89 default_depends upload uploadpath
90 default_depends dput dputtarget
91 : ${DEB_BUILD_OPTIONS=parallel=4}; export DEB_BUILD_OPTIONS
92
93 ###--------------------------------------------------------------------------
94 ### Parse options.
95
96 prog=${0##*/}
97
98 usage () {
99 cat <<EOF
100 Usage: $prog [-v] BUILDOPT
101
102 Build options:
103
104 [no]checkout[=REV]
105 [no]release
106 [no]setup[=RUNE]
107 [no]distcheck
108 [no]debian
109 [no]upload[=SERVER:PATH]
110 [no]dput[=TARGET]
111 [no]clean
112 [no]vpath
113 [no]sbuild[=SERVER]
114 [no]sign[=KEYID]
115 [no]native
116 make=MAKE
117 EOF
118 }
119
120 ## Parse simple options.
121 verbose=no
122 while getopts "hv" opt; do
123 case "$opt" in
124 h) usage; exit 0 ;;
125 v) verbose=yes ;;
126 *) exit 1 ;;
127 esac
128 done
129 shift $((OPTIND - 1))
130
131 ## Parse the build options.
132 maybe_set () {
133 var=$1 want=$2
134 eval "p=\${$want+t}\${$want-nil}"
135 case $p in
136 t) eval $var=yes ;;
137 nil) echo >&2 "$prog: $want not set"; exit 1 ;;
138 esac
139 }
140 for opt; do
141 case "$opt" in
142 checkout) checkout=yes checkoutrev=HEAD ;;
143 checkout=*) checkout=yes checkoutrev=${opt#*=} ;;
144 release) build=release ;;
145 norelease) build=test ;;
146 setup) setup=yes setupcmd=mdw-setup ;;
147 setup=*) setup=yes setupcmd=${opt#*=} ;;
148 upload) maybe_set upload uploadpath ;;
149 upload=*) upload=yes uploadpath=${opt#*=} ;;
150 sign) maybe_set sign signkey ;;
151 sign=*) sign=yes signkey=${opt#*=} ;;
152 sbuild) maybe_set sbuild sbuildsrv ;;
153 sbuild=*) sbuild=yes sbuildsrv=${opt#*=} ;;
154 dput) maybe_set dput dputtarget ;;
155 dput=*) dput=yes dputtarget=${opt#*=} ;;
156 make=*) make=${opt#*=} ;;
157
158 distcheck | debian | clean | vpath | native)
159 eval "$opt=yes"
160 ;;
161 nocheckout | nosetup | nodistcheck | nodebian | \
162 noupload | nodput | noclean | novpath | nonative | nosbuild | nosign)
163 eval "${opt#no}=no"
164 ;;
165 *)
166 usage >&2
167 exit 1
168 ;;
169 esac
170 done
171
172 ## Parse DEB_BUILD_OPTIONS.
173 jobs=1
174 set -- $DEB_BUILD_OPTIONS
175 for opt; do
176 case "$opt" in
177 parallel=*) jobs=${opt#*=} ;;
178 esac
179 done
180
181 makeopts=""
182 case $jobs in 1) ;; *) makeopts="$makeopts -j$jobs" ;; esac
183
184 ###--------------------------------------------------------------------------
185 ### Utility functions.
186
187 ## File descriptor assignments:
188 ##
189 ## 0 -- original stdin (never touched)
190 ## 1, 2 -- stdout, stderr, redirected to 3 while running comamnds
191 ## log -- original stderr (verbose), or logfile (quiet); captures command
192 ## output
193 ## diag -- null (verbose), or logfile (quiet); primary diagnostic output
194 ## term -- orginal stderr; secondary diagnostic output (with colours)
195
196 notify () {
197 colour=$1 message=$2
198 echo $message >&$diag
199 echo "$(tput bold; tput setaf $colour)$message$(tput sgr0; tput op)" >&$term
200 }
201
202 fail () {
203 notify 1 "!!! $*"
204 exit 1
205 }
206
207 warn () {
208 case $build in
209 release) fail "$*" ;;
210 *) notify 5 "??? $*" ;;
211 esac
212 }
213
214 info () {
215 notify 6 "--- $*"
216 }
217
218 assign () {
219 info "$1 = $2"
220 eval "$1=$2"
221 }
222
223 runx () {
224 notify 2 "+++ $*"
225 nice "$@" 2>&$log {log}>&- {diag}>&- {term}>&- || fail "$1: exit $?"
226 }
227
228 run () { runx "$@" >&$log; }
229
230 yesno () {
231 echo -n "(test $*)" >&$diag
232 if "$@" >&$diag 2>&$diag {log}>&- {diag}>&- {term}>&-; then
233 echo "(yes)" >&$diag
234 echo yes
235 else
236 echo "(no)" >&$diag
237 echo no
238 fi
239 }
240
241 ###--------------------------------------------------------------------------
242 ### Do the building.
243
244 ## Find the top-level package directory.
245 while [ ! -f configure.ac -a ! -f configure.in -a \
246 ! -f .links -a ! -d .git ]; do
247 case "$(pwd)" in
248 /)
249 fail "couldn't find top-level directory"
250 ;;
251 esac
252 cd ..
253 done
254 toppath=$(pwd)
255
256 ## Build any necessary qualifiers.
257 qual= sep=.
258 case ${SBOX_SESSION_DIR+t},${DEB_BUILD_ARCH+t} in
259 t,t) qual=$qual$sep$DEB_BUILD_ARCH; sep=- ;;
260 t,*) fail "unknown build arch" ;;
261 esac
262
263 ## Construct the output directory.
264 releasepath=$toppath/dist-$build$qual
265 chmod -R +w $releasepath 2>/dev/null || :
266 rm -rf $releasepath 2>/dev/null || :
267 mkdir $releasepath
268 exec {term}>&2
269 case $verbose in
270 no)
271 exec {diag}>$releasepath/mdw-build.log {log}>&$diag ||
272 fail "Failed to create log."
273 ;;
274 yes)
275 exec {diag}>/dev/null {log}>&2
276 ;;
277 esac
278
279 ## Repeat the earlier assignments for tbe benefit of the logfile.
280 assign toppath $toppath
281 assign releasepath $releasepath
282
283 ## Do we have a Git repository?
284 case "$checkout,$setup,$(yesno [ -d $toppath/.git ])" in
285 yes,no,*)
286 fail "Inconsistent options: can't check out without setup."
287 ;;
288 yes,yes,no)
289 info "No Git repository found."
290 checkout=no gitver=none
291 ;;
292 yes,yes,yes)
293 cd $toppath
294 [ "$(git ls-files -m)" = "" ] ||
295 warn "working tree has uncommitted changes"
296 ;;
297 esac
298
299 ## Is there Debian build equipment?
300 case "$debian,$(yesno [ -d $toppath/debian ])" in
301 yes,no)
302 info "No debian directory found."
303 debian=no debver=none
304 ;;
305 no,*)
306 debver=none
307 ;;
308 yes,yes)
309 debver=$(dpkg-parsechangelog | sed -n 's/^Version: //p')
310 debsrc=$(dpkg-parsechangelog | sed -n 's/^Source: //p')
311 debname=$(git config user.name) debemail=$(git config user.email)
312 ;;
313 esac
314
315 ## Maybe check out a copy of the source.
316 case "$checkout" in
317 yes)
318 cd $releasepath
319 run git clone -sn $toppath/.git _source
320 assign srcpath $releasepath/_source
321 cd $srcpath
322 run git update-ref refs/heads/mdw-build $checkoutrev ""
323 run git symbolic-ref HEAD refs/heads/mdw-build
324 run git read-tree --reset refs/heads/mdw-build
325 run git checkout-index -afu
326 assign gitversion "$(git describe --abbrev=4)"
327 ;;
328 no)
329 assign srcpath $toppath
330 ;;
331 esac
332
333 ## Check the version number.
334 hack_dch_p=no
335 case "$gitversion,$debver" in
336 none,* | *,none)
337 ;;
338 *)
339 dvref=$(echo "$debver" | tr '~' '_')
340 if [ "$gitversion" = "$dvref" ]; then
341 assign debversion "$debver"
342 else
343 warn "Git version $gitversion doesn't match Debian version $debver"
344 hack_dch=yes
345 dver=$(echo $gitversion | sed 's/-/+/; s/-/./g')
346 case $debver in *~) dver=$debver$dver ;; esac
347 assign debversion "$dver"
348 now=$(date -R)
349 fi
350 ;;
351 esac
352
353 ## Maybe refresh the build machinery.
354 case "$setup" in
355 yes)
356 run $setupcmd
357 ;;
358 esac
359
360 ## Initialize the build directory.
361 case "$vpath,$(yesno [ -e $srcpath/configure ])" in
362 yes,yes)
363 assign buildpath $releasepath/_build
364 mkdir $buildpath
365 cd $buildpath
366 run $srcpath/configure
367 ;;
368 no,yes)
369 info "VPATH build disabled"
370 assign buildpath $srcpath
371 distcheck=no
372 cd $srcpath
373 run ./configure
374 ;;
375 *,no)
376 info "no configure script"
377 assign buildpath $srcpath
378 cd $srcpath
379 ;;
380 esac
381
382 ## Discover the release name.
383 cat >find-distdir.mk <<'EOF'
384 include Makefile
385 print-distdir:
386 @echo >&$(fd) $(distdir)
387 EOF
388 assign distdir \
389 $({ $make -f find-distdir.mk print-distdir fd=$t >/dev/null 2>&1; } {t}>&1)
390
391 ## Get a tarball distribution.
392 case "$distcheck" in
393 yes)
394 run $make $makeopts distcheck
395 ;;
396 no)
397 run $make $makeopts dist
398 ;;
399 esac
400
401 cd $releasepath
402
403 case $native in
404 yes)
405 if ! tar tf $buildpath/$distdir.tar.gz 2>/dev/null | grep -q RELEASE
406 then
407 fail "missing RELEASE file in distribution"
408 fi
409 ;;
410 esac
411
412 run mv $buildpath/$distdir.tar.gz .
413 case $build,$sign in
414 release,yes)
415 run gpg -u$signkey -ab -o$distdir.tar.gz.gpg $distdir.tar.gz
416 ;;
417 esac
418
419 ## Maybe build the Debian packages.
420 case "$debian" in
421 yes)
422 run tar xvfz $distdir.tar.gz
423 cd $distdir
424 case $hack_dch in
425 yes)
426 cat - debian/changelog >debian/changelog.new <<EOF
427 $debsrc ($debversion) experimental; urgency=low
428
429 * Hacking in process, not intended for release.
430
431 -- $debname <$debemail> $now
432
433 EOF
434 mv debian/changelog.new debian/changelog
435 ;;
436 esac
437 sbuildargs=$sbuildsrv
438 case $sbuild,$build in
439 yes,release)
440 case $sign in yes) sbuildargs="-k$signkey $sbuildargs" ;; esac
441 ;;
442 yes,*)
443 if [ -d $toppath/dist-$build.pkgs ]; then
444 sbuildargs="-p$toppath/dist-$build.pkgs $sbuildargs"
445 fi
446 ;;
447 esac
448 case $sbuild,$build,$sign in
449 yes,*) run mdw-sbuild $sbuildargs ;;
450 no,release,yes) run dpkg-buildpackage -k$signkey ;;
451 no,*) run dpkg-buildpackage -us -uc ;;
452 esac
453 ;;
454 esac
455
456 ## Maybe upload Debian packages.
457 cd $releasepath
458 case "$upload,$build" in
459 yes,test) info "Test build: not uploading." ;;
460 yes,release) run rsync $distdir.tar.gz $distdir.tar.gz.gpg $uploadpath ;;
461 esac
462 case "$debian,$upload,$dput,$build" in
463 yes,yes,yes,release) run dput -f $dputtarget *.changes ;;
464 esac
465
466 ## Tidy up.
467 case "$clean" in
468 yes)
469 rm -rf $releasepath/$distdir
470 rm -rf $releasepath/_source
471 rm -rf $releasepath/_build
472 ;;
473 esac
474
475 ## Done.
476 info "All OK."
477
478 ###----- That's all, folks --------------------------------------------------