chiark / gitweb /
Merge remote-tracking branches 'crybaby/master', 'gibson/master' and 'mdwdev/master'
[profile] / bin / mdw-sbuild-server
1 #! /bin/sh -e
2 ###
3 ### Build a Debian package on supported architectures
4 ###
5 ### (c) 2016 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 ### Configuration.
26
27 unset buildroot default_targets parallel
28 for i in \
29   "/etc/mdw-sbuild.conf" \
30   "${XDG_CONFIG_HOME-$HOME/.config}/mdw-sbuild.conf"
31 do
32   if [ -f "$i" ]; then . "$i"; fi
33 done
34 : ${buildroot=$HOME/build}
35 : ${default_targets="wheezy-amd64 wheezy-i386"}
36 : ${DEB_BUILD_OPTIONS=parallel=4}; export DEB_BUILD_OPTIONS
37
38 ###--------------------------------------------------------------------------
39 ### Some utilities.
40
41 prog=${0##*/}
42
43 fail () { echo >&2 "$prog: $*"; exit 1; }
44 usage () { echo "usage: $prog [-aiknT] [-t TARGET] [-A DBPARGS] COMMAND [ARGUMENTS ...]"; }
45 fail_usage () { usage >&2; exit 1; }
46
47 want_1 () {
48   what=$1 pat=$2 type=$3; shift 3
49   for i in "$@"; do
50     [ $type "$i" ] || fail "$what not found: \`$i'"
51   done
52   case $# in
53     1) ;;
54     *) fail "expected exactly one $what matching \`$pat', but found $#" ;;
55   esac
56   echo "$1"
57 }
58
59 run () {
60   case $notreally in
61     t) echo "+ $*" ;;
62     nil) nice "$@" ;;
63   esac
64 }
65
66 decor () {
67   tag=$1 marker=$2
68   while IFS= read -r line; do
69     printf "%-21s %c %s\n" "$tag" "$marker" "$line"
70   done
71 }
72
73 ###--------------------------------------------------------------------------
74 ### Parse options.
75
76 bogusp=nil archp=nil indepp=nil keepon=nil notreally=nil
77 unset targets dbpargs
78
79 while getopts "haint:A:T" opt; do
80   case $opt in
81     h)
82       usage
83       cat <<EOF
84
85 Options:
86         -h              Show this help text.
87         -a              Build only architecture-dependent packages.
88         -i              Build only architecture-neutral packages.
89         -k              Keep going even if one fails.
90         -n              Don't actually do the build.
91         -t TARGET       Build in TARGET build environment.
92         -A ARGS         Pass ARGS to \`dpkg-buildpackage'.
93         -T              Don't run the tests.
94
95 Commands available:
96
97         dir PROJECT/VERSION
98                 Return a freshly-made directory for the source code to
99                 go in.
100
101         build BUILDDIR
102                 Build the package placed in BUILDDIR, which should contain
103                 exactly one \`.dsc' file, and whatever source archive files
104                 are necessary.
105 EOF
106       exit
107       ;;
108     a) archp=t ;;
109     i) indepp=t ;;
110     k) keepon=t ;;
111     n) notreally=t ;;
112     t) targets="${targets+$targets }$OPTARG" ;;
113     A) dbpargs="${dbpargs+$dbpargs }$OPTARG" ;;
114     T)
115       case " $DEB_BUILD_OPTIONS " in
116         *" nocheck "*) ;;
117         *) DEB_BUILD_OPTIONS=${DEB_BUILD_OPTIONS+"$DEB_BUILD_OPTIONS "} nocheck ;;
118       esac
119       ;;
120     *) bogusp=nil ;;
121   esac
122 done
123 shift $(( $OPTIND - 1 ))
124
125 case $bogusp in t) fail_usage ;; esac
126 case $archp,$indepp in nil,nil) archp=t indepp=t ;; esac
127 case ${targets+t} in t) ;; *) targets=$default_targets ;; esac
128
129 ###--------------------------------------------------------------------------
130 ### Main work.
131
132 case "$#,$1" in
133   0,*) fail_usage ;;
134   *,*,*) fail "bad command name \`$1'" ;;
135
136   2,dir)
137     ## dirname PROJECT/VERSION
138
139     ## Try to create a fresh build directory.
140     dist=$2
141     case "$dist" in */*/*) fail "bad distribution name \`$dist'" ;; esac
142     proj=${dist%/*} ver=${dist#*/}
143     cd "$buildroot"
144     mkdir -p "$proj"
145     cd "$proj"
146     i=0
147     winp=nil
148     while [ $i -lt 50 ]; do
149       i=$(( $i + 1 ))
150
151       ## Find a sequence number different from all of the existing builds of
152       ## this version.
153       nn=1
154       for j in "$ver#"*; do
155         case "$j" in "$ver#*") break ;; esac
156         n=${j##*\#}
157         if [ $nn -le $n ]; then nn=$(( $n + 1 )); fi
158       done
159
160       ## Try to make the build directory.  This might not work if we're
161       ## racing with another process, but that's why we're trying in a loop.
162       if mkdir "$ver#$nn" >/dev/null 2>&1; then
163         winp=t
164         cd "$ver#$nn"
165         break
166       fi
167
168       ## Make sure it actually failed because a directory appeared, rather
169       ## than for some other reason.
170       [ -e "$ver#$nn" ] || \
171         fail "unexpectedly couldn't create \`$buildroot/$dist#$nn'"
172     done
173
174     ## Make sure we actually succeeded.
175     case $winp in t) ;; *) fail "failed to create build directory" ;; esac
176
177     ## Make an empty directory for dependency packages.
178     mkdir -p pkgs/
179
180     ## Done.
181     echo "$buildroot/$dist#$nn"
182     ;;
183
184   *,dir)
185     echo >&2 "usage: $prog dir PROJECT/VERSION"; exit 1 ;;
186
187   2,build)
188     ## build BUILDDIR
189
190     ## Track down the build directory.
191     builddir=$2
192     cd "$builddir"
193     dsc=$(want_1 "file" "*.dsc" -f *.dsc)
194
195     ## Figure out which targets need building.  If the `.dsc' file isn't
196     ## telling, assume it needs building everywhere and let sbuild(1) sort
197     ## out the mess.
198     os=$(dpkg-architecture -qDEB_HOST_ARCH_OS)
199     unset first rest; anyp=nil depp=nil allp=nil
200     wantarchs=$(sed -n '/^[Aa]rchitecture:/ s/^[^:]*: *//p' "$dsc")
201     : ${wantarchs:=any}
202     unset buildarchs buildarchs_seen=:
203
204     ## Work through the available targets assigning builds to them.  This is
205     ## actually a little tricky.
206     for t in $targets; do
207
208       ## Dissect the target name.
209       suite=${t%%-*} archs=${t#*-}
210       case $archs in
211         */*) target=${archs%/*} host=${archs#*/} ;;
212         *) target=$archs host=$archs; t=$suite-$target/$host ;;
213       esac
214       case $buildarchs_seen in
215         *:$target:*)
216           ;;
217         *)
218           buildarchs=${buildarchs+$buildarchs }$target
219           buildarchs_seen=$buildarchs_seen$target:
220           ;;
221       esac
222
223       ## Work through the architectures which we can build.
224       for arch in $wantarchs; do
225         case $arch in
226           all)
227             ## Package suitable for all architectures.
228
229             ## If we don't want to build architecture-neutral packages then
230             ## there's nothing to do.
231             case $indepp in nil) continue ;; esac
232
233             ## Pick this up if nobody has volunteered.  However, we should be
234             ## ready to let some other architecture build this if it's going
235             ## to build some architecture-dependent package too.
236             case $anyp in nil) first=$t anyp=t allp=t ;; esac
237             ;;
238           *)
239             ## There's at least one architecture-specific package.
240
241             ## If we don't want to build architecture-specific packages then
242             ## there's nothing to do.
243             case $archp in nil) continue ;; esac
244
245             ## If we can't build it then we shouldn't try.
246             if ! dpkg-architecture -a"$os-$target" -i"$arch"; then
247               continue
248             fi
249
250             ## Decide whether we should take responsibility for the
251             ## architecture-neutral packages.  If nobody's claimed them yet,
252             ## or the previous claimant wasn't building architecture-specific
253             ## packages, we should take over.
254             case $depp in
255               nil) first=$t depp=t anyp=t ;;
256               t) rest="${rest+$rest }$t" ;;
257             esac
258             ;;
259         esac
260       done
261     done
262
263     ## If we never found a match then we can't do anything.
264     case $anyp in nil) echo "$prog: no packages to build"; exit 0 ;; esac
265
266     ## Figure out the right options to use.
267     case $indepp in
268       t) firstopt="--arch-all" ;;
269       nil) firstopt="--no-arch-all" ;;
270     esac
271     case $archp in
272       t) ;;
273       nil) firstopt="$firstopt --no-arch-any" ;;
274     esac
275
276     ## Sort out the additional packages.  This is rather annoying, because
277     ## sbuild(1) does this in a really stupid way.
278     rm -rf pkgs.*
279     for a in $buildarchs; do
280       mkdir pkgs.$a
281       for f in $(dpkg-scanpackages -a$a pkgs/ |
282                     sed -n '/^Filename: /s///p')
283       do
284         ln $f pkgs.$a/
285       done
286     done
287
288     ## Build the builds sequentially.  Tests can conflict with each other,
289     ## e.g., over port numbers.
290     rc=0 buildopt=$firstopt
291     for t in $first $rest; do
292       host=${t##*/} full=${t%/*}
293       suite=${full%%-*} target=${full#*-}
294
295       ## And we're ready to go.
296       exec 3>&1
297       thisrc=$(
298         { { { { set +e
299                 run sbuild --extra-package=$pkgs.$target \
300                     --dist=$suite --build=$host --host=$target \
301                     --chroot=$suite-$host --verbose $buildopt $dsc \
302                     ${dbpargs+--debbuildopts="$dbpargs"} \
303                     3>&- 4>&- 5>&-
304                 echo $? >&5
305               } |
306                 decor "$full" "|" >&4; } 2>&1 |
307               decor "$full" "*" >&4; } 4>&1 |
308             cat -u >&3; } 5>&1 </dev/null)
309       exec 3>&-
310       case $thisrc in 0) ;;
311         *)
312           echo failed rc=$thisrc >$stat; rc=$thisrc
313           case $keepon in nil) break ;; esac
314           ;;
315       esac
316       buildopt=--no-arch-all
317     done
318     exit $rc
319     ;;
320   build,*)
321     echo >&2 "usage: $prog build BUILDDIR"; exit 1 ;;
322
323   *)
324     fail "unknown command \`$1'"
325     ;;
326 esac
327
328 ###----- That's all, folks --------------------------------------------------