bin/mdw-build: Close our file descriptors when running subcommands.
[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 ## 3 -- original stderr (verbose), or logfile (quiet); captures command
192 ## output
193 ## 4 -- null (verbose), or logfile (quiet); primary diagnostic output
194 ## 5 -- orginal stderr; secondary diagnostic output (with colours)
195
196 exec 3>&2 4>/dev/null 5>&2
197
198 notify () {
199 colour=$1 message=$2
200 echo $message >&4
201 echo "$(tput bold; tput setaf $colour)$message$(tput sgr0; tput op)" >&5
202 }
203
204 fail () {
205 notify 1 "!!! $*"
206 exit 1
207 }
208
209 warn () {
210 case $build in
211 release) fail "$*" ;;
212 *) notify 5 "??? $*" ;;
213 esac
214 }
215
216 info () {
217 notify 6 "--- $*"
218 }
219
220 assign () {
221 info "$1 = $2"
222 eval "$1=$2"
223 }
224
225 runx () {
226 notify 2 "+++ $*"
227 "$@" 2>&3 3>&- 4>&- 5>&- || fail "$1: exit $?"
228 }
229
230 run () { runx "$@" >&3; }
231
232 yesno () {
233 echo -n "(test $*)" >&4
234 if "$@" >&4 2>&4 3>&- 4>&- 5>&-; then
235 echo "(yes)" >&4
236 echo yes
237 else
238 echo "(no)" >&4
239 echo no
240 fi
241 }
242
243 ###--------------------------------------------------------------------------
244 ### Do the building.
245
246 ## Find the top-level package directory.
247 while [ ! -f configure.ac -a ! -f configure.in -a \
248 ! -f .links -a ! -d .git ]; do
249 case "$(pwd)" in
250 /)
251 fail "couldn't find top-level directory"
252 ;;
253 esac
254 cd ..
255 done
256 assign toppath $(pwd)
257 assign srcpath $toppath
258
259 ## Build any necessary qualifiers.
260 qual= sep=.
261 case ${SBOX_SESSION_DIR+t},${DEB_BUILD_ARCH+t} in
262 t,t) qual=$qual$sep$DEB_BUILD_ARCH; sep=- ;;
263 t,*) fail "unknown build arch" ;;
264 esac
265
266 ## Construct the output directory.
267 assign releasepath $srcpath/dist-$build$qual
268 chmod -R +w $releasepath 2>/dev/null || :
269 rm -rf $releasepath 2>/dev/null || :
270 mkdir $releasepath
271 case $verbose in
272 no)
273 exec 4>$releasepath/mdw-build.log 3>&4 ||
274 fail "Failed to create log."
275 ;;
276 esac
277
278 ## Do we have a Git repository?
279 case "$checkout,$setup,$(yesno [ -d $srcpath/.git ])" in
280 yes,no,*)
281 fail "Inconsistent options: can't check out without setup."
282 ;;
283 yes,yes,no)
284 info "No Git repository found."
285 checkout=no gitver=none
286 ;;
287 yes,yes,yes)
288 cd $srcpath
289 [ "$(git ls-files -m)" = "" ] ||
290 warn "working tree has uncommitted changes"
291 ;;
292 esac
293
294 ## Is there Debian build equipment?
295 case "$debian,$(yesno [ -d $srcpath/debian ])" in
296 yes,no)
297 info "No debian directory found."
298 debian=no debver=none
299 ;;
300 no,*)
301 debver=none
302 ;;
303 yes,yes)
304 debver=$(dpkg-parsechangelog | sed -n 's/^Version: //p')
305 debsrc=$(dpkg-parsechangelog | sed -n 's/^Source: //p')
306 debname=$(git config user.name) debemail=$(git config user.email)
307 ;;
308 esac
309
310 ## Maybe check out a copy of the source.
311 case "$checkout" in
312 yes)
313 cd $releasepath
314 run git clone -sn $srcpath/.git _source
315 assign srcpath $releasepath/_source
316 cd $srcpath
317 run git update-ref refs/heads/mdw-build $checkoutrev ""
318 run git symbolic-ref HEAD refs/heads/mdw-build
319 run git read-tree --reset refs/heads/mdw-build
320 run git checkout-index -afu
321 assign gitversion "$(git describe --abbrev=4)"
322 ;;
323 esac
324
325 ## Check the version number.
326 hack_dch_p=no
327 case "$gitversion,$debver" in
328 none,* | *,none)
329 ;;
330 *)
331 dvref=$(echo "$debver" | tr '~' '_')
332 if [ "$gitversion" = "$dvref" ]; then
333 assign debversion "$debver"
334 else
335 warn "Git version $gitversion doesn't match Debian version $debver"
336 hack_dch=yes
337 dver=$(echo $gitversion | sed 's/-/+/; s/-/./g')
338 case $debver in *~) dver=$debver$dver ;; esac
339 assign debversion "$dver"
340 now=$(date -R)
341 fi
342 ;;
343 esac
344
345 ## Maybe refresh the build machinery.
346 case "$setup" in
347 yes)
348 run $setupcmd
349 ;;
350 esac
351
352 ## Initialize the build directory.
353 case "$vpath,$(yesno [ -e $srcpath/configure ])" in
354 yes,yes)
355 assign buildpath $releasepath/_build
356 mkdir $buildpath
357 cd $buildpath
358 run $srcpath/configure
359 ;;
360 no,yes)
361 info "VPATH build disabled"
362 assign buildpath $srcpath
363 distcheck=no
364 cd $srcpath
365 run ./configure
366 ;;
367 *,no)
368 info "no configure script"
369 assign buildpath $srcpath
370 cd $srcpath
371 ;;
372 esac
373
374 ## Discover the release name.
375 cat >find-distdir.mk <<'EOF'
376 include Makefile
377 print-distdir:
378 @echo >&3 $(distdir)
379 EOF
380 assign distdir \
381 $({ $make -f find-distdir.mk print-distdir >/dev/null 2>&1; } 3>&1)
382
383 ## Get a tarball distribution.
384 case "$distcheck" in
385 yes)
386 run $make $makeopts distcheck
387 ;;
388 no)
389 run $make $makeopts dist
390 ;;
391 esac
392
393 cd $releasepath
394
395 case $native in
396 yes)
397 if ! tar tf $buildpath/$distdir.tar.gz 2>/dev/null | grep -q RELEASE
398 then
399 fail "missing RELEASE file in distribution"
400 fi
401 ;;
402 esac
403
404 run mv $buildpath/$distdir.tar.gz .
405 case $build,$sign in
406 release,yes)
407 run gpg -u$signkey -ab -o$distdir.tar.gz.gpg $distdir.tar.gz
408 ;;
409 esac
410
411 ## Maybe build the Debian packages.
412 case "$debian" in
413 yes)
414 run tar xvfz $distdir.tar.gz
415 cd $distdir
416 case $hack_dch in
417 yes)
418 cat - debian/changelog >debian/changelog.new <<EOF
419 $debsrc ($debversion) experimental; urgency=low
420
421 * Hacking in process, not intended for release.
422
423 -- $debname <$debemail> $now
424
425 EOF
426 mv debian/changelog.new debian/changelog
427 ;;
428 esac
429 sbuildargs=$sbuildsrv
430 case $sbuild,$build in
431 yes,release)
432 case $sign in yes) sbuildargs="-k$signkey $sbuildargs" ;; esac
433 ;;
434 yes,*)
435 if [ -d $toppath/dist-$build.pkgs ]; then
436 sbuildargs="-p$toppath/dist-$build.pkgs $sbuildargs"
437 fi
438 ;;
439 esac
440 case $sbuild,$build,$sign in
441 yes,*) run mdw-sbuild $sbuildargs ;;
442 no,release,yes) run dpkg-buildpackage -k$signkey ;;
443 no,*) run dpkg-buildpackage -us -uc ;;
444 esac
445 ;;
446 esac
447
448 ## Maybe upload Debian packages.
449 cd $releasepath
450 case "$upload,$build" in
451 yes,test) info "Test build: not uploading." ;;
452 yes,release) run rsync $distdir.tar.gz $distdir.tar.gz.gpg $uploadpath ;;
453 esac
454 case "$debian,$upload,$dput,$build" in
455 yes,yes,yes,release) run dput -f $dputtarget *.changes ;;
456 esac
457
458 ## Tidy up.
459 case "$clean" in
460 yes)
461 rm -rf $releasepath/$distdir
462 rm -rf $releasepath/_source
463 rm -rf $releasepath/_build
464 ;;
465 esac
466
467 ## Done.
468 info "All OK."
469
470 ###----- That's all, folks --------------------------------------------------