| 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 |
| 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 |
| 253 | all: \$(FIRST) \$(REST) |
| 254 | \$(FIRST):; \$(call sbuild-wrap,$firstopt) |
| 255 | \$(REST):; \$(call sbuild-wrap,--no-arch-all) |
| 256 | EOF |
| 257 | |
| 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 | |
| 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 $! |
| 267 | rm build.mk pipeout build-status.* |
| 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 -------------------------------------------------- |