chiark / gitweb /
bin/mdw-build: Make a note of the persistent file-descriptor assignments.
[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 || fail "$1: exit $?"
228 }
229
230 run () { runx "$@" >&3; }
231
232 yesno () {
233   echo -n "(test $*)" >&4
234   if "$@" >&4 2>&4; 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 --------------------------------------------------