#! /bin/bash -e set -o errtrace # build and test everything in a fresh docker installation myarch=$(dpkg --print-architecture) if test "${arch}" = "amd64"; then myarch="amd64|i386" fi mode= img= repos=() keys=() dns=() envs=("-e LANG=${LANG}" "-e HOME=${HOME}" "-e TERM=xterm" "-e DEBIAN_FRONTEND=noninteractive" "-e DEBCONF_NONINTERACTIVE_SEEN=true") dirs=("-v $(pwd):/workdir" "-v ${HOME}/.gnupg:${HOME}/.gnupg") packages=() targets="all check distcheck" commands=() arch=$((which dpkg > /dev/null 2> /dev/null && dpkg --print-architecture) || echo amd64) host= flags=() wait=0 commit= nopull=0 prepare=0 clean= if test -e ./build-in-docker.conf; then # you can preconfigure the variables in file build-in-docker.conf # if you do so, add the file to EXTRA_DIST in makefile.am source ./build-in-docker.conf fi while test $# -gt 0; do case "$1" in (-h|--help) cat < mode: deb, rpm, win, default: ${mode} -i, --image use given docker image instead of ${img} -a, --arch build for given hardware architecture -t, --targets targets specify build targets, default: ${targets} --host host for cross compiling, e.g. i686-w64-mingw32 -f, --flag add flag to ./bootstrap.sh or ./configure -r, --repo add given apt repository -k, --key add public key from url -n, --dns add ip as dns server -e, --env = set environment variable in docker -d, --dir access given directory read only -p, --package install extra debian packages -c, --cmd execute commands as root in docker -w, --wait on error keep docker container and wait for enter --clean run maintainer-clean before build --commit commit the container as image after setup, then exit --no-pull do not pull image before start (use local image) --prepare only prepare the image, then wait The option -i must be after -m, because mode sets a new default image The option -m must be after -t, because mode may be auto detected from targets The option -m must be after -h, because mode may set a host If target is either deb or rpm, mode is set to the same value If target is win, host is set to i686-w64-mingw32 The options -r -k -e -d -p -c can be repeated several times. The options -r -p -c allow an if-then-else contruct depending on the operating system: :::::: ::: Read as: On linux type use else use That means: If the distributer ID or codename in lsb_release matches regular expression , then is replaced, else is replaced. The three colons are for splitting from and part. E.g.: Install package curl on wheezy and npm on olter systems: $0 -p Debian|precise:::curl:::npm PERFORMANCE Each build starts with an empty image and first installs all dependencies. You can speed up the build process by using an image, where all dependencies are already installed. The disadvantage of this approach is, that missing dependencies are not detected, so installing all dependencies into an empty image is a test for the dependency specification. Create an image from ubuntu:trusty that contains all dependencies: $0 -i ubuntu:trusty --tag my/trusty Now you can use the image my/trusty, but since it is not stored in a repository, you have to either push it to a repository (here my is your name in docker hub), or simply use it local only and add the flag --no-pull: $0 -i my/trusty -t all As long as you work with my/trusty, the dependencies don't need to be donwloaded in each build. TESTING When you want to experiment with your builds, i.e. to trace a build problem, then option --prepare comes handy. It just starts an image with all dependencies, then waits and gives you information on how you can enter the image for manual tests in a temporary container. As soon as you hit the enter key, it cleans up the temporary container. $0 -i ubuntu:trusty --prepare EXAMPLE $0 -i mwaeckerlin/ubuntu:trusty-i386 \\ -t deb \\ -e ANDROID_HOME=/opt/local/android \\ -d /opt/local/android \\ -r universe \\ -r https://repository.mrw.sh \\ -k https://repository.mrw.sh/PublicKey \\ -p mrw-c++ EOF exit 0 ;; (-m|--mode) shift; mode="$1" if test -z "$img"; then case "$mode" in (deb|apt) img="mwaeckerlin/debbuildenv";; (rpm|zypper) img="opensuse:latest";; (yum) img="centos:latest";; (dnf) img="fedora:latest";; (win) img="mwaeckerlin/debbuildenv"; host="${host:---host=i686-w64-mingw32}" targets="all install" flags+=("--prefix=/workdir/usr") packages+=("mingw-w64") ;; (*) echo "**** ERROR: unknown mode '$1', try --help" 1>&2 exit 1 ;; esac fi ;; (-i|--image) shift; img="$1" ;; (-a|--arch) shift; arch="$1" ;; (-t|--targets) shift; targets="$1" if test "$1" = "deb" -o "$1" = "rpm"; then # set mode to same value set -- "-m" "$@" continue fi ;; (--host) shift; host="--host=$1" ;; (-f|--flag) shift; flags+=("$1") ;; (-r|--repo) shift; echo "OPTION: $1" repos+=("$1") ;; (-k|--key) shift; keys+=("$1") ;; (-e|--env) shift; envs+=("-e $1") ;; (-n|--dns) shift; dns+=("--dns $1") ;; (-d|--dirs) shift; dirs+=("-v $1:$1:ro") ;; (-p|--package) shift; packages+=("$1") ;; (-c|--cmd) shift; commands+=("$1") ;; (-w|--wait) wait=1 ;; (--clean) clean="--clean -c" ;; (--commit) shift; commit="$1" ;; (--no-pull) nopull=1 ;; (--prepare) prepare=1 ;; (*) echo "**** ERROR: unknown option '$1', try --help" 1>&2 exit 1 ;; esac if test $# -eq 0; then echo "**** ERROR: missing value, try --help" 2>61 exit 1 fi shift done function waitforinput() { set +x echo " ... now you can access the docker container as root or user:" echo " docker exec -it ${DOCKER_ID} bash" echo " docker exec -u $(id -u) -it ${DOCKER_ID} bash" echo -n " ... press enter to cleanup: " read } function traperror() { set +x local DOCKER_ID="$1" local err=($2) # error status local line="$3" # LINENO local linecallfunc="$4" local command="$5" local funcstack="$6" for e in ${err[@]}; do if test -n "$e" -a "$e" != "0"; then echo "<---" echo "ERROR: line $line - command '$command' exited with status: $e (${err[@]})" if [ "${funcstack}" != "main" -o "$linecallfunc" != "0" ]; then echo -n " ... Error at ${funcstack} " if [ "$linecallfunc" != "" ]; then echo -n "called at line $linecallfunc" fi echo fi if [ "$wait" -eq 1 ]; then waitforinput fi echo -n " ... cleanup docker: " docker stop "${DOCKER_ID}" || true docker rm "${DOCKER_ID}" echo "returning status: $e" echo "--->" exit $e fi done echo -n "SUCCESS ... cleanup docker: " docker rm -f "${DOCKER_ID}" exit 0 } function ifthenelse() { arg="$1" shift cmd="$*" DISTRIBUTOR=$(docker exec ${DOCKER_ID} lsb_release -si | sed 's, .*,,;s,.*,\L&,g') CODENAME=$(docker exec ${DOCKER_ID} lsb_release -cs) ARCH=$((docker exec ${DOCKER_ID} which dpkg > /dev/null 2> /dev/null && docker exec ${DOCKER_ID} dpkg --print-architecture) || echo amd64) case "$DISTRIBUTOR" in (opensuse) # code name may be not available, then set leap or tumbleweed if test "$CODENAME" = "n/a"; then CODENAME=$(docker exec ${DOCKER_ID} lsb_release -ds | sed "s,\($(docker exec ${DOCKER_ID} lsb_release -si | sed 's, ,\\|,g')\) *,,"';s, .*,,g;s,",,g;s,.*,\L&,g') fi ;; (fedora|mageia) # numeric code name CODENAME=$(docker exec ${DOCKER_ID} lsb_release -rs) ;; (centos) # only look at major number in centos CODENAME=$(docker exec ${DOCKER_ID} lsb_release -rs | sed 's,\..*,,') ;; esac if test "${arg/:::/}" = "${arg}"; then cmd_tmp="${cmd//ARG/${arg//@DISTRIBUTOR@/${DISTRIBUTOR}}}" docker exec ${DOCKER_ID} bash -c "${cmd_tmp//@CODENAME@/${CODENAME}}" else os="${arg%%:::*}" thenpart="${arg#*:::}" elsepart= if test "${thenpart/:::/}" != "${thenpart}"; then elsepart="${thenpart##*:::}" thenpart="${thenpart%%:::*}" fi if [[ "${DISTRIBUTOR}-${CODENAME}-${ARCH}" =~ ${os} ]]; then if test -n "${thenpart}"; then cmd_tmp="${cmd//ARG/${thenpart//@DISTRIBUTOR@/${DISTRIBUTOR}}}" docker exec ${DOCKER_ID} bash -c "${cmd_tmp//@CODENAME@/${CODENAME}}" fi else if test -n "${elsepart}"; then cmd_tmp="${cmd//ARG/${elsepart//@DISTRIBUTOR@/${DISTRIBUTOR}}}" docker exec ${DOCKER_ID} bash -c "${cmd_tmp//@CODENAME@/${CODENAME}}" fi fi fi } set -x if test -z "$img"; then img="mwaeckerlin/debbuildenv" fi test $nopull -eq 1 || docker pull $img DOCKER_ID=$(docker create ${dns[@]} ${dirs[@]} ${envs[@]} -w /workdir $img sleep infinity) trap 'traperror '"${DOCKER_ID}"' "$? ${PIPESTATUS[@]}" $LINENO $BASH_LINENO "$BASH_COMMAND" "${FUNCNAME[@]}" "${FUNCTION}"' SIGINT INT TERM EXIT if ! [[ $arch =~ $myarch ]]; then docker cp "/usr/bin/qemu-${arch}-static" "${DOCKER_ID}:/usr/bin/qemu-${arch}-static" fi docker start "${DOCKER_ID}" if ! docker exec ${DOCKER_ID} getent group $(id -g) > /dev/null 2>&1; then docker exec ${DOCKER_ID} groupadd -g $(id -g) $(id -gn) fi if ! docker exec ${DOCKER_ID} getent passwd $(id -u) > /dev/null 2>&1; then docker exec ${DOCKER_ID} useradd -m -u $(id -u) -g $(id -g) -d"${HOME}" $(id -un) fi docker exec ${DOCKER_ID} chown $(id -u):$(id -g) "${HOME}" if test -z "$mode"; then case "$targets" in (*deb*) mode=deb;; (*rpm*) mode=rpm;; (*) case "$img" in (*deb*|*ubuntu*|*debian*|*mint*) mode=deb;; (*rpm*|*fedora*|*centos*|*mageia*) mode=rpm;; (*mingw*|*win*) mode=win;; (*) mode=deb;; esac;; esac fi case "$mode" in (deb|apt|win) OPTIONS='-o Dpkg::Options::=--force-confdef -o Dpkg::Options::=--force-confnew -y --force-yes --no-install-suggests --no-install-recommends' docker exec ${DOCKER_ID} apt-get update ${OPTIONS} #docker exec ${DOCKER_ID} apt-get upgrade ${OPTIONS} docker exec ${DOCKER_ID} apt-get install ${OPTIONS} python-software-properties software-properties-common apt-transport-https dpkg-dev lsb-release wget || \ docker exec ${DOCKER_ID} apt-get install ${OPTIONS} software-properties-common apt-transport-https dpkg-dev lsb-release wget || \ docker exec ${DOCKER_ID} apt-get install ${OPTIONS} python-software-properties apt-transport-https dpkg-dev lsb-release wget; if [[ "${img}" =~ "ubuntu" ]]; then docker exec ${DOCKER_ID} apt-get install ${OPTIONS} locales docker exec ${DOCKER_ID} locale-gen ${LANG} docker exec ${DOCKER_ID} update-locale LANG=${LANG} fi if test -n "${keys[*]}"; then # fix dependency bug in cosmic and stretch docker exec ${DOCKER_ID} apt-get install ${OPTIONS} gnupg for key in "${keys[@]}"; do wget -O- "$key" \ | docker exec -i ${DOCKER_ID} apt-key add - done fi for repo in "${repos[@]}"; do ifthenelse "${repo}" "apt-add-repository 'ARG'" done docker exec ${DOCKER_ID} apt-get update ${OPTIONS} for package in "${packages[@]}"; do ifthenelse "${package}" "apt-get install ${OPTIONS} ARG" done for command in "${commands[@]}"; do ifthenelse "${command}" "ARG" done docker exec ${DOCKER_ID} ./resolve-debbuilddeps.sh ;; (rpm|yum|dnf|zypper|urpmi) if [[ "$img" =~ "centos" ]]; then docker exec ${DOCKER_ID} yum install -y redhat-lsb epel-release docker exec -i ${DOCKER_ID} bash -c 'cat > /etc/yum.repos.d/wandisco-svn.repo' <> /etc/yum.repos.d/wandisco-svn.repo' docker exec -i ${DOCKER_ID} bash -c 'cat >> /etc/yum.repos.d/wandisco-svn.repo' < /dev/null 2> /dev/null; then docker exec ${DOCKER_ID} bash -c "apt-get install ${OPTIONS} /workdir/*.deb" fi fi if test "$mode" = rpm -a "${targets//rpm/}" != "${targets}"; then if ls *.rpm > /dev/null 2> /dev/null; then docker exec ${DOCKER_ID} bash -c "${INSTALL_TOOL} /workdir/*.rpm" fi fi echo "done."