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