Commit | Line | Data |
---|---|---|
b94830d9 MW |
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 | : ${parallel=-j3} | |
37 | : ${DEB_BUILD_OPTIONS=parallel=4}; export DEB_BUILD_OPTIONS | |
38 | ||
39 | ###-------------------------------------------------------------------------- | |
40 | ### Some utilities. | |
41 | ||
42 | prog=${0##*/} | |
43 | ||
44 | fail () { echo >&2 "$prog: $*"; exit 1; } | |
45 | usage () { echo "usage: $prog [-ain] [-t TARGET] COMMAND [ARGUMENTS ...]"; } | |
46 | fail_usage () { usage >&2; exit 1; } | |
47 | ||
48 | want_1 () { | |
49 | what=$1 pat=$2 type=$3; shift 3 | |
50 | for i in "$@"; do | |
51 | [ $type "$i" ] || fail "$what not found: \`$i'" | |
52 | done | |
53 | case $# in | |
54 | 1) ;; | |
55 | *) fail "expected exactly one $what matching \`$pat', but found $#" ;; | |
56 | esac | |
57 | echo "$1" | |
58 | } | |
59 | ||
60 | ###-------------------------------------------------------------------------- | |
61 | ### Parse options. | |
62 | ||
63 | bogusp=nil archp=nil indepp=nil makeopts="" | |
64 | unset targets | |
65 | ||
66 | while getopts "haint:" opt; do | |
67 | case $opt in | |
68 | h) | |
69 | usage | |
70 | cat <<EOF | |
71 | ||
72 | Options: | |
73 | -h Show this help text. | |
74 | -a Build only architecture-dependent packages. | |
75 | -i Build only architecture-neutral packages. | |
76 | -n Don't actually do the build. | |
77 | -t TARGET Build in TARGET build environment. | |
78 | ||
79 | Commands available: | |
80 | ||
81 | dir PROJECT/VERSION | |
82 | Return a freshly-made directory for the source code to | |
83 | go in. | |
84 | ||
85 | build BUILDDIR | |
86 | Build the package placed in BUILDDIR, which should contain | |
87 | exactly one \`.dsc' file, and whatever source archive files | |
88 | are necessary. | |
89 | EOF | |
90 | exit | |
91 | ;; | |
92 | a) archp=t ;; | |
93 | i) indepp=t ;; | |
94 | n) makeopts="${makeopts+$makeopts }-n" ;; | |
95 | t) targets="${targets+$targets }$OPTARG" ;; | |
96 | *) bogusp=nil ;; | |
97 | esac | |
98 | done | |
99 | shift $(( $OPTIND - 1 )) | |
100 | ||
101 | case $bogusp in t) fail_usage ;; esac | |
102 | case $archp,$indepp in nil,nil) archp=t indepp=t ;; esac | |
103 | case ${targets+t} in t) ;; *) targets=$default_targets ;; esac | |
104 | ||
105 | ###-------------------------------------------------------------------------- | |
106 | ### Main work. | |
107 | ||
108 | case "$#,$1" in | |
109 | 0,*) fail_usage ;; | |
110 | *,*,*) fail "bad command name \`$1'" ;; | |
111 | ||
112 | 2,dir) | |
113 | ## dirname PROJECT/VERSION | |
114 | ||
115 | ## Try to create a fresh build directory. | |
116 | dist=$2 | |
117 | case "$dist" in */*/*) fail "bad distribution name \`$dist'" ;; esac | |
118 | proj=${dist%/*} ver=${dist#*/} | |
119 | cd "$buildroot" | |
120 | mkdir -p "$proj" | |
121 | cd "$proj" | |
122 | i=0 | |
123 | winp=nil | |
124 | while [ $i -lt 50 ]; do | |
125 | i=$(( $i + 1 )) | |
126 | ||
127 | ## Find a sequence number different from all of the existing builds of | |
128 | ## this version. | |
129 | nn=1 | |
130 | for j in "$ver#"*; do | |
131 | case "$j" in "$ver#*") break ;; esac | |
132 | n=${j##*\#} | |
133 | if [ $nn -le $n ]; then nn=$(( $n + 1 )); fi | |
134 | done | |
135 | ||
136 | ## Try to make the build directory. This might not work if we're | |
137 | ## racing with another process, but that's why we're trying in a loop. | |
138 | if mkdir "$ver#$nn" >/dev/null 2>&1; then | |
139 | winp=t | |
140 | break | |
141 | fi | |
142 | ||
143 | ## Make sure it actually failed because a directory appeared, rather | |
144 | ## than for some other reason. | |
145 | [ -e "$ver#$nn" ] || \ | |
146 | fail "unexpectedly couldn't create \`$buildroot/$dist#$nn'" | |
147 | done | |
148 | ||
149 | ## Make sure we actually succeeded. | |
150 | case $winp in t) ;; *) fail "failed to create build directory" ;; esac | |
151 | ||
152 | ## Done. | |
153 | echo "$buildroot/$dist#$nn" | |
154 | ;; | |
155 | ||
156 | *,dir) | |
157 | echo >&2 "usage: $prog dir PROJECT/VERSION"; exit 1 ;; | |
158 | ||
159 | 2,build) | |
160 | ## build BUILDDIR | |
161 | ||
162 | ## Track down the build directory. | |
163 | builddir=$2 | |
164 | cd "$builddir" | |
165 | dsc=$(want_1 "file" "*.dsc" -f *.dsc) | |
166 | ||
167 | ## Figure out which targets need building. If the `.dsc' file isn't | |
168 | ## telling, assume it needs building everywhere and let sbuild(1) sort | |
169 | ## out the mess. | |
170 | os=$(dpkg-architecture -qDEB_HOST_ARCH_OS) | |
171 | unset first rest; anyp=nil depp=nil allp=nil | |
172 | wantarchs=$(sed -n '/^[Aa]rchitecture:/ s/^[^:]*: *//p' "$dsc") | |
173 | : ${wantarchs:=any} | |
174 | ||
175 | ## Work through the available targets assigning builds to them. This is | |
176 | ## actually a little tricky. | |
177 | for t in $targets; do | |
178 | ||
179 | ## Work through the architectures which we can build. | |
180 | for arch in $wantarchs; do | |
181 | case $arch in | |
182 | all) | |
183 | ## Package suitable for all architectures. | |
184 | ||
185 | ## If we don't want to build architecture-neutral packages then | |
186 | ## there's nothing to do. | |
187 | case $indepp in nil) continue ;; esac | |
188 | ||
189 | ## Pick this up if nobody has volunteered. However, we should be | |
190 | ## ready to let some other architecture build this if it's going | |
191 | ## to build some architecture-dependent package too. | |
192 | case $anyp in nil) first=$t anyp=t allp=t ;; esac | |
193 | ;; | |
194 | *) | |
195 | ## There's at least one architecture-specific package. | |
196 | ||
197 | ## If we don't want to build architecture-specific package then | |
198 | ## there's nothing to do. | |
199 | case $archp in nil) continue ;; esac | |
200 | ||
201 | ## If we can't build it then we shouldn't try. | |
202 | if ! dpkg-architecture -a"$os-${t#*-}" -i"$arch"; then | |
203 | continue | |
204 | fi | |
205 | ||
206 | ## Decide whether we should take responsibility for the | |
207 | ## architecture-neutral packages. If nobody's claimed them yet, | |
208 | ## or the previous claimant wasn't building architecture-specific | |
209 | ## packages, we should take over. | |
210 | case $depp in | |
211 | nil) first=$t depp=t anyp=t ;; | |
212 | t) rest="${rest+$rest }$t" ;; | |
213 | esac | |
214 | ;; | |
215 | esac | |
216 | done | |
217 | done | |
218 | ||
219 | ## If we never found a match then we can't do anything. | |
220 | case $anyp in nil) echo "$prog: no packages to build"; exit 0 ;; esac | |
221 | ||
222 | ## Figure out the right options to use. | |
223 | case $indepp in | |
224 | t) firstopt="--arch-all" ;; | |
225 | nil) firstopt="--no-arch-all" ;; | |
226 | esac | |
227 | case $archp in | |
228 | t) ;; | |
229 | nil) firstopt="$firstopt --debbuildopt=-A" ;; | |
230 | esac | |
231 | ||
232 | ## Build a cheesy makefile to run these in parallel. | |
233 | cat >build.mk <<EOF | |
234 | ### -*-makefile-*- | |
235 | DSC = $dsc | |
236 | FIRST = $first | |
237 | REST = $rest | |
01c036af MW |
238 | sbuild-wrap = \\ |
239 | t=\$@; \\ | |
240 | { echo started >build-status.\$\$t; \\ | |
241 | sbuild \\ | |
242 | --dist=\$\${t%-*} --arch=\$\${t\#*-} \\ | |
243 | --chroot=\$@ --verbose \$1 \$(DSC); \\ | |
244 | rc=\$\$?; case \$\$rc in \\ | |
245 | 0) echo ok >build-status.\$\$t ;; \\ | |
246 | *) echo failed rc=\$\$rc >build-status.\$\$t ;; \\ | |
247 | esac; } | \\ | |
248 | while IFS= read -r line; do \\ | |
249 | printf "%s: %s\n" "\$\$t" "\$\$line"; \\ | |
250 | done; \\ | |
251 | read st _ <build-status.\$\$t && \\ | |
252 | case \$\$st in ok) exit 0 ;; *) exit 1 ;; esac | |
b94830d9 | 253 | all: \$(FIRST) \$(REST) |
01c036af MW |
254 | \$(FIRST):; \$(call sbuild-wrap,$firstopt) |
255 | \$(REST):; \$(call sbuild-wrap,--no-arch-all) | |
b94830d9 MW |
256 | EOF |
257 | ||
01c036af MW |
258 | ## Make some marker files to say things are in progress. |
259 | for i in $first $rest; do echo "starting" >build-status.$i; done | |
260 | ||
b94830d9 MW |
261 | ## And we're ready to go. |
262 | mkfifo pipeout | |
263 | cat pipeout& catpid=$! | |
264 | set +e; make -fbuild.mk $parallel $makeopts -k all >pipeout | |
265 | rc=$?; set -e | |
266 | wait $! | |
01c036af | 267 | rm build.mk pipeout build-status.* |
b94830d9 MW |
268 | find . -maxdepth 1 -type l -exec rm {} \; |
269 | exit $rc | |
270 | ;; | |
271 | build,*) | |
272 | echo >&2 "usage: $prog build BUILDDIR"; exit 1 ;; | |
273 | ||
274 | *) | |
275 | fail "unknown command \`$1'" | |
276 | ;; | |
277 | esac | |
278 | ||
279 | ###----- That's all, folks -------------------------------------------------- |