chiark / gitweb /
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 --------------------------------------------------