Template to bootstrap configure/automake projects for C++, libtool, Qt, NodeJS, PHP, Shell-Scripts, etc. Extends autotools, by building packages for Debian and RPM, specifying generic library dependencies, creating desktop applications with icons, etc. https://mrw.sh/development/bootstrap-build-environment
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

446 lines
16 KiB

#! /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 <<EOF
SYNOPSIS
$0 [OPTIONS]
OPTIONS
-h, --help show this help
-m, --mode <type> mode: deb, rpm, win, default: ${mode}
-i, --image <image> use given docker image instead of ${img}
-a, --arch <arch> build for given hardware architecture
-t, --targets targets specify build targets, default: ${targets}
--host <target-arch> host for cross compiling, e.g. i686-w64-mingw32
-f, --flag <flag> add flag to ./bootstrap.sh or ./configure
-r, --repo <url> add given apt repository
-k, --key <url> add public key from url
-n, --dns <ip> add ip as dns server
-e, --env <var>=<val> set environment variable in docker
-d, --dir <dir> access given directory read only
-p, --package <pkg> install extra debian packages
-c, --cmd <command> execute commands as root in docker
-w, --wait on error keep docker container and wait for enter
--clean run maintainer-clean before build
--commit <image> 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:
<os>:::<A>:::<B>
<os>:::<A>
Read as: On linux type <os> use <A> else use <B>
That means: If the distributer ID or codename in lsb_release
matches regular expression <os>, then <A> is replaced, else <B> is replaced.
The three colons are for splitting <os> from <A> and <B> 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' <<EOF
[WandiscoSVN]
name=Wandisco SVN Repo
EOF
docker exec -i ${DOCKER_ID} bash -c 'echo "baseurl=http://opensource.wandisco.com/centos/$(lsb_release -sr | sed '"'"'s,[^0-9].*,,'"'"')/svn-'$(svn --version | head -1 | sed 's,[^0-9]*\([0-9]\+\.[0-9]\+\).*,\1,')'/RPMS/$(uname -i)/" >> /etc/yum.repos.d/wandisco-svn.repo'
docker exec -i ${DOCKER_ID} bash -c 'cat >> /etc/yum.repos.d/wandisco-svn.repo' <<EOF
enabled=1
gpgcheck=0
EOF
fi
INSTALL_TOOL=$((docker exec ${DOCKER_ID} test -x /usr/bin/zypper && echo zypper install -y) || (docker exec ${DOCKER_ID} test -x /usr/bin/dnf && echo dnf install -y) || (docker exec ${DOCKER_ID} test -x /usr/bin/yum && echo yum install -y) || (docker exec ${DOCKER_ID} test -x /usr/sbin/urpmi && echo urpmi --auto))
if test "$INSTALL_TOOL" = "urpmi --auto" -o "$INSTALL_TOOL" = "zypper install -y"; then
LSB_RELEASE=lsb-release
else
LSB_RELEASE=/usr/bin/lsb_release
fi
docker exec ${DOCKER_ID} ${INSTALL_TOOL} rpm-build automake libtool subversion gcc-c++ pkgconfig wget $LSB_RELEASE
if docker exec ${DOCKER_ID} test -x /usr/bin/dnf; then
docker exec ${DOCKER_ID} dnf install -y 'dnf-command(config-manager)'
fi
i=0
for key in "${keys[@]}"; do
docker exec -i ${DOCKER_ID} wget -Orpm-key "$key"
docker exec -i ${DOCKER_ID} rpm --import rpm-key
docker exec -i ${DOCKER_ID} rm rpm-key
done
for repo in "${repos[@]}"; do
INSTALL_REPO=$((docker exec ${DOCKER_ID} test -x /usr/bin/zypper && echo zypper ar) || (docker exec ${DOCKER_ID} test -x /usr/bin/dnf && echo dnf config-manager --add-repo) || (docker exec ${DOCKER_ID} test -x /usr/bin/yum && echo yum-config-manager --add-repo) || (docker exec ${DOCKER_ID} test -x /usr/sbin/urpmi && echo false))
ifthenelse "${repo}" "${INSTALL_REPO} ARG"
((++i))
done
for package in "${packages[@]}"; do
ifthenelse "${package}" "${INSTALL_TOOL} ARG"
done
for command in "${commands[@]}"; do
ifthenelse "${command}" "ARG"
done
docker exec ${DOCKER_ID} ./resolve-rpmbuilddeps.sh
;;
esac
FLAGS=()
for f in "${flags[@]}"; do
FLAGS+=($(ifthenelse "$f" "echo 'ARG'"))
done
if test -n "$commit"; then
docker commit ${DOCKER_ID} "$commit"
echo "commited new image: $commit"
exit 0
fi
if test $prepare -eq 1; then
waitforinput
exit 0
fi
docker exec -u $(id -u):$(id -g) ${DOCKER_ID} ./bootstrap.sh ${clean} -t "${targets}" ${host} "${FLAGS[@]}"
# last check: try to install built deb or rpm files (if not already cleaned up)
# not supported in trusty and jessie
if test "$mode" = deb -a "${img//trusty/}" = "${img}" -a "${img//jessie/}" = "${img}"; then
if test "${targets//deb/}" != "${targets}" && ls *.deb > /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."