start of manylinux ci
authorrobin <robin@reportlab.com>
Fri, 23 Jun 2017 11:38:34 +0100
changeset 8 a39bdd5144d6
parent 7 12f08e267b71
child 9 55f05d532826
start of manylinux ci
manylinux/build-wheels
manylinux/common_utils.sh
manylinux/container-build-wheels.sh
manylinux/container-test-wheels.sh
manylinux/library_builders.sh
manylinux/manylinux_utils.sh
manylinux/test-wheels
rl_ci_tools.py
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/manylinux/build-wheels	Fri Jun 23 11:38:34 2017 +0100
@@ -0,0 +1,25 @@
+#!/bin/bash
+set -ev
+UNICODE_WIDTHS='16 32'
+PYTHON_VERSIONS='2.7 3.3 3.4 3.5 3.6'
+ARCHS='x86_64 i686'
+IMAGESRC=quay.io/pypa
+REPO=${REPO:-https://bitbucket.org/rptlab/reportlab}
+REQUIREMENT=${REPO:-$(basename ${REPO})}
+DOCKER_SCRIPT=${DOCKER_SCRIPT:-container-build-wheels.sh}
+#IMAGESRC=rl
+sudo rm -rf wheels wheelsu
+mkdir wheels wheelsu
+for arch in ${ARCHS}; do
+	sudo rm -rf wheelhouse wheels_unfixed
+	mkdir wheelhouse wheels_unfixed
+	DOCKER_IMAGE=${IMAGESRC}/manylinux1_${arch}
+	docker pull $DOCKER_IMAGE
+	docker run --rm \
+		-e PYTHON_VERSIONS="$PYTHON_VERSIONS" \
+		-e UNICODE_WIDTHS="$UNICODE_WIDTHS" \
+		-e REPO="$REPO" \
+		-e REEQUIREMENT="$REQUIREMENT" \
+		-e ARCH="$arch" \
+		-v $(pwd):/io ${DOCKER_IMAGE} /io/rl_ci_tools/manylinux/${DOCKER_SCRIPT}
+done
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/manylinux/common_utils.sh	Fri Jun 23 11:38:34 2017 +0100
@@ -0,0 +1,200 @@
+#!/bin/bash
+# Utilities for both OSX and Docker Linux
+# Python should be on the PATH
+set -e
+
+MULTIBUILD_DIR=$(dirname "${BASH_SOURCE[0]}")
+if [ $(uname) == "Darwin" ]; then IS_OSX=1; fi
+
+function abspath {
+    python -c "import os.path; print(os.path.abspath('$1'))"
+}
+
+function relpath {
+    # Path of first input relative to second (or $PWD if not specified)
+    python -c "import os.path; print(os.path.relpath('$1','${2:-$PWD}'))"
+}
+
+function realpath {
+    python -c "import os; print(os.path.realpath('$1'))"
+}
+
+function lex_ver {
+    # Echoes dot-separated version string padded with zeros
+    # Thus:
+    # 3.2.1 -> 003002001
+    # 3     -> 003000000
+    echo $1 | awk -F "." '{printf "%03d%03d%03d", $1, $2, $3}'
+}
+
+function unlex_ver {
+    # Reverses lex_ver to produce major.minor.micro
+    # Thus:
+    # 003002001 -> 3.2.1
+    # 003000000 -> 3.0.0
+    echo "$((10#${1:0:3}+0)).$((10#${1:3:3}+0)).$((10#${1:6:3}+0))"
+}
+
+function strip_ver_suffix {
+    echo $(unlex_ver $(lex_ver $1))
+}
+
+function is_function {
+    # Echo "true" if input argument string is a function
+    # Allow errors during "set -e" blocks.
+    (set +e; echo $($(declare -Ff "$1") > /dev/null && echo true))
+}
+
+function gh-clone {
+    git clone https://github.com/$1
+}
+
+function rm_mkdir {
+    # Remove directory if present, then make directory
+    local path=$1
+    if [ -z "$path" ]; then echo "Need not-empty path"; exit 1; fi
+    if [ -d "$path" ]; then rm -rf $path; fi
+    mkdir $path
+}
+
+function untar {
+    local in_fname=$1
+    if [ -z "$in_fname" ];then echo "in_fname not defined"; exit 1; fi
+    local extension=${in_fname##*.}
+    case $extension in
+        tar) tar xf $in_fname ;;
+        gz|tgz) tar zxf $in_fname ;;
+        bz2) tar jxf $in_fname ;;
+        zip) unzip $in_fname ;;
+        xz) unxz -c $in_fname | tar xf ;;
+        *) echo Did not recognize extension $extension; exit 1 ;;
+    esac
+}
+
+function fetch_unpack {
+    # Fetch input archive name from input URL
+    # Parameters
+    #    url - URL from which to fetch archive
+    #    archive_fname (optional) archive name
+    #
+    # If `archive_fname` not specified then use basename from `url`
+    # If `archive_fname` already present at download location, use that instead.
+    local url=$1
+    if [ -z "$url" ];then echo "url not defined"; exit 1; fi
+    local archive_fname=${2:-$(basename $url)}
+    local arch_sdir="${ARCHIVE_SDIR:-archives}"
+    # Make the archive directory in case it doesn't exist
+    mkdir -p $arch_sdir
+    local out_archive="${arch_sdir}/${archive_fname}"
+    # Fetch the archive if it does not exist
+    if [ ! -f "$out_archive" ]; then
+        curl -L $url > $out_archive
+    fi
+    # Unpack archive, refreshing contents
+    rm_mkdir arch_tmp
+    (cd arch_tmp && untar ../$out_archive && rsync --delete -avh * ..)
+}
+
+function clean_code {
+    local repo_dir=${1:-$REPO_DIR}
+    local build_commit=${2:-$BUILD_COMMIT}
+    [ -z "$repo_dir" ] && echo "repo_dir not defined" && exit 1
+    [ -z "$build_commit" ] && echo "build_commit not defined" && exit 1
+    (cd $repo_dir \
+        && git fetch origin \
+        && git checkout $build_commit \
+        && git clean -fxd \
+        && git reset --hard \
+        && git submodule update --init --recursive)
+}
+
+function build_wheel_cmd {
+    # Builds wheel with named command, puts into $WHEEL_SDIR
+    #
+    # Parameters:
+    #     cmd  (optional, default "pip_wheel_cmd"
+    #        Name of command for builing wheel
+    #     repo_dir  (optional, default $REPO_DIR)
+    #
+    # Depends on
+    #     REPO_DIR  (or via input argument)
+    #     WHEEL_SDIR  (optional, default "wheelhouse")
+    #     BUILD_DEPENDS (optional, default "")
+    #     MANYLINUX_URL (optional, default "") (via pip_opts function)
+    local cmd=${1:-pip_wheel_cmd}
+    local repo_dir=${2:-$REPO_DIR}
+    [ -z "$repo_dir" ] && echo "repo_dir not defined" && exit 1
+    local wheelhouse=$(abspath ${WHEEL_SDIR:-wheelhouse})
+    if [ -n "$(is_function "pre_build")" ]; then pre_build; fi
+    if [ -n "$BUILD_DEPENDS" ]; then
+        pip install $(pip_opts) $BUILD_DEPENDS
+    fi
+    (cd $repo_dir && $cmd $wheelhouse)
+    repair_wheelhouse $wheelhouse
+}
+
+function pip_wheel_cmd {
+    local abs_wheelhouse=$1
+    pip wheel $(pip_opts) -w $abs_wheelhouse --no-deps .
+}
+
+function bdist_wheel_cmd {
+    # Builds wheel with bdist_wheel, puts into wheelhouse
+    #
+    # It may sometimes be useful to use bdist_wheel for the wheel building
+    # process.  For example, versioneer has problems with versions which are
+    # fixed with bdist_wheel:
+    # https://github.com/warner/python-versioneer/issues/121
+    local abs_wheelhouse=$1
+    python setup.py bdist_wheel
+    cp dist/*.whl $abs_wheelhouse
+}
+
+function build_pip_wheel {
+    # Standard wheel building command with pip wheel
+    build_wheel_cmd "pip_wheel_cmd" $@
+}
+
+function build_bdist_wheel {
+    # Wheel building with bdist_wheel. See bdist_wheel_cmd
+    build_wheel_cmd "bdist_wheel_cmd" $@
+}
+
+function build_wheel {
+    # Set default building method to pip
+    build_pip_wheel $@
+}
+
+function pip_opts {
+    [ -n "$MANYLINUX_URL" ] && echo "--find-links $MANYLINUX_URL"
+}
+
+function get_platform {
+    # Report platform as given by uname
+    python -c 'import platform; print(platform.uname()[4])'
+}
+
+function install_wheel {
+    # Install test dependencies and built wheel
+    #
+    # Pass any input flags to pip install steps
+    #
+    # Depends on:
+    #     WHEEL_SDIR  (optional, default "wheelhouse")
+    #     TEST_DEPENDS  (optional, default "")
+    #     MANYLINUX_URL (optional, default "") (via pip_opts function)
+    local wheelhouse=$(abspath ${WHEEL_SDIR:-wheelhouse})
+    if [ -n "$TEST_DEPENDS" ]; then
+        pip install $(pip_opts) $@ $TEST_DEPENDS
+    fi
+    # Install compatible wheel
+    pip install $(pip_opts) $@ \
+        $(python $MULTIBUILD_DIR/supported_wheels.py $wheelhouse/*.whl)
+}
+
+function install_run {
+    # Depends on function `run_tests` defined in `config.sh`
+    install_wheel
+    mkdir tmp_for_test
+    (cd tmp_for_test && run_tests)
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/manylinux/container-build-wheels.sh	Fri Jun 23 11:38:34 2017 +0100
@@ -0,0 +1,72 @@
+#!/bin/bash
+# Run with:
+#	 docker run --rm -v $PWD:/io quay.io/pypa/manylinux1_x86_64 /io/rl_ci_tools/manylinux/reportlab-wheels.sh
+# or something like:
+#	 docker run --rm -e PYTHON_VERSIONS=2.7 -v $PWD:/io quay.io/pypa/manylinux1_x86_64 /io/rl_ci_tools/manylinux/reportlab-wheels.sh
+# or:
+#	 docker run --rm -e PYTHON_VERSIONS=2.7 -v $PWD:/io quay.io/pypa/manylinux1_x86_64 /io/rl_ci_tools/manylinux/reportlab-wheels.sh
+set -e
+
+UNICODE_WIDTHS=${UNICODE_WIDTHS:-16 32}
+WHEELHOUSE=${WHEELHOUSE:-/io/rl_ci_tools/manylinux/wheelhouse}
+WHEELS_UNFIXED=${WHEELS_UNFIXED:-/io/rl_ci_tools/manylinux/wheels_unfixed}
+
+mark(){
+	echo "######################################################################################"
+	[ "$#" -gt 0 ] && echo "$@" && echo "######################################################################################"
+	}
+
+# Manylinux, openblas version, lex_ver etc etc
+mark source /io/rl_ci_tools/manylinux/manylinux_utils.sh
+source /io/rl_ci_tools/manylinux/manylinux_utils.sh
+mark source /io/rl_ci_tools/manylinux/library_builders.sh
+source /io/rl_ci_tools/manylinux/library_builders.sh
+mark "$(env)"
+
+#mark build_jpeg
+#build_jpeg
+#mark build_tiff
+#build_tiff
+#mark build_openjpeg
+#build_openjpeg
+#mark build_lcsm2
+#build_lcms2
+#mark build_libwebp
+#build_libwebp
+mark build_freetype
+build_freetype
+#yum install -y tk-devel
+#yum install mercurial
+
+PYLO=${PYLO:-2.7}
+PYHI=${PYHI:-3.7}
+
+# Directory to store wheels
+rm_mkdir $(basename "${WHEELS_UNFIXED}")
+
+OPATH="$PATH"
+export RL_MANYLINUX=1
+
+# Compile wheels
+for PYTHON in ${PYTHON_VERSIONS}; do
+	[ $(lex_ver $PYTHON) -lt $(lex_ver $PYLO) ] && continue
+	[ $(lex_ver $PYTHON) -ge $(lex_ver $PYHI) ] && continue
+	for uw in ${UNICODE_WIDTHS}; do
+		[ $(lex_ver $PYTHON) -ge $(lex_ver 3.3) ] && [ "$uw" != 32 ] && continue
+		(
+		export UNICODE_WIDTH="$uw"
+		mark "building reportlab wheel PYTHON=$PYTHON UNICODE_WIDTH=$UNICODE_WIDTH"
+		PP="$(cpython_path $PYTHON $UNICODE_WIDTH)"
+		PY=$PP/bin/python
+		mark "platform=$($PY -mplatform) sys.platform=$($PY -c'import sys;print(sys.platform)') os.name=$($PY -c'import os;print(os.name)')"
+		export PATH="$PP/bin:$OPATH"
+		PIP="$PP/bin/pip"
+		$PIP install wheel setuptools -U
+		echo "Building reportlab for Python $PYTHON"
+		$PIP wheel --no-binary=:all: --no-deps -w "${WHEELS_UNFIXED}" -e "${REPO}#egg=${REQUIREMENT}"
+		)
+	done
+done
+
+# Bundle external shared libraries into the wheels
+repair_wheelhouse "${WHEELS_UNFIXED}" "${WHEELHOUSE}"
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/manylinux/container-test-wheels.sh	Fri Jun 23 11:38:34 2017 +0100
@@ -0,0 +1,81 @@
+#!/bin/bash
+# Run with:
+#	 docker run --rm -v $PWD:/io quay.io/pypa/manylinux1_x86_64 /io/build_reportlab.sh
+# or something like:
+#	 docker run --rm -e PYTHON_VERSIONS=2.7 -v $PWD:/io quay.io/pypa/manylinux1_x86_64 /io/build_reportlab.sh
+# or:
+#	 docker run --rm -e PYTHON_VERSIONS=2.7 -v $PWD:/io quay.io/pypa/manylinux1_x86_64 /io/build_reportlab.sh
+set -e
+
+UNICODE_WIDTHS=${UNICODE_WIDTHS:-16 32}
+WHEELHOUSE=${WHEELHOUSE:-/io/wheelhouse}
+WHEELS_UNFIXED=${WHEELS_UNFIXED:-/io/wheels_unfixed}
+
+mark(){
+	echo "######################################################################################"
+	[ "$#" -gt 0 ] && echo "$@" && echo "######################################################################################"
+	}
+
+# Manylinux, openblas version, lex_ver etc etc
+mark source /io/rl_ci_utils/manylinux/manylinux_utils.sh
+source /io/rl_ci_utils/manylinux/manylinux_utils.sh
+mark source /io/rl_ci_utils/manylinux/library_builders.sh
+source /io/rl_ci_utils/manylinux/library_builders.sh
+mark "$(env)"
+
+#mark build_jpeg
+#build_jpeg
+#mark build_tiff
+#build_tiff
+#mark build_openjpeg
+#build_openjpeg
+#mark build_lcsm2
+#build_lcms2
+#mark build_libwebp
+#build_libwebp
+#mark build_freetype
+#build_freetype
+#yum install -y tk-devel
+
+PYLO=${PYLO:-2.7}
+PYHI=${PYHI:-3.7}
+
+OPATH="$PATH"
+export RL_MANYLINUX=1
+ENV=/io/test-env
+SRC=/io/${REQUIREMENT}-src
+
+# Compile wheels
+for PYTHON in ${PYTHON_VERSIONS}; do
+	[ $(lex_ver $PYTHON) -lt $(lex_ver $PYLO) ] && continue
+	[ $(lex_ver $PYTHON) -ge $(lex_ver $PYHI) ] && continue
+	for uw in ${UNICODE_WIDTHS}; do
+		[ $(lex_ver $PYTHON) -ge $(lex_ver 3.3) ] && [ "$uw" != 32 ] && continue
+		(
+		export UNICODE_WIDTH="$uw"
+		mark "building reportlab wheel PYTHON=$PYTHON UNICODE_WIDTH=$UNICODE_WIDTH"
+		PP="$(cpython_path $PYTHON $UNICODE_WIDTH)"
+		PY=$PP/bin/python
+		mark "platform=$($PY -mplatform) sys.platform=$($PY -c'import sys;print(sys.platform)') os.name=$($PY -c'import os;print(os.name)')"
+		export PATH="$PP/bin:$OPATH"
+		PIP="$PP/bin/pip"
+		echo "Testing ${REQUIREMENT} for Python $PYTHON"
+		$PIP install virtualenv
+		rm -rf $ENV
+		$PY -mvirtualenv $ENV
+		(
+		cd $ENV
+		. $ENV/bin/activate
+		EPY=$ENV/bin/python
+		EPIP=$ENV/bin/pip
+		[ "${REQUIREMENT}" = "reportlab" ] && $EPIP install --find-links=/io/TEST-WHEELS pillow
+		$EPIP install --find-links=/io/wheels ${REQUIREMENT}
+		rm -f /tmp/eee
+		$EPY setup.py test | tee /tmp/eee
+		grep -q 'OK' /tmp/eee && R="##### OK  " || R="!!!!! FAIL"
+		echo "${R} PYTHON=${PYTHON} UNICODE_WIDTH=${UNICODE_WIDTH} ARCH=${ARCH}" >> /io/test-results.txt
+		deactivate
+		)
+		)
+	done
+done
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/manylinux/library_builders.sh	Fri Jun 23 11:38:34 2017 +0100
@@ -0,0 +1,199 @@
+# Recipes for building some libaries
+OPENBLAS_VERSION="${OPENBLAS_VERSION:-0.2.18}"
+# We use system zlib by default - see build_new_zlib
+ZLIB_VERSION="${ZLIB_VERSION:-1.2.8}"
+LIBPNG_VERSION="${LIBPNG_VERSION:-1.6.21}"
+BZIP2_VERSION="${BZIP2_VERSION:-1.0.6}"
+FREETYPE_VERSION="${FREETYPE_VERSION:-2.6.3}"
+TIFF_VERSION="${TIFF_VERSION:-4.0.6}"
+OPENJPEG_VERSION="${OPENJPEG_VERSION:-2.1}"
+LCMS2_VERSION="${LCMS2_VERSION:-2.7}"
+GIFLIB_VERSION="${GIFLIB_VERSION:-5.1.3}"
+LIBWEBP_VERSION="${LIBWEBP_VERSION:-0.5.0}"
+XZ_VERSION="${XZ_VERSION:-5.2.2}"
+LIBYAML_VERSION="${LIBYAML_VERSION:-0.1.5}"
+SZIP_VERSION="${SZIP_VERSION:-2.1}"
+HDF5_VERSION="${HDF5_VERSION:-1.8.17}"
+LIBAEC_VERSION="${LIBAEC_VERSION:-0.3.3}"
+BUILD_PREFIX="${BUILD_PREFIX:-/usr/local}"
+ARCHIVE_SDIR=${ARCHIVE_DIR:-archives}
+ENABLE_STATIC=${ENABLE_STATIC:-}
+
+# Set default compilation flags and OSX flag variable
+if [ $(uname) == "Darwin" ]; then
+    # Dual arch build by default
+    ARCH_FLAGS=${ARCH_FLAGS:-"-arch i386 -arch x86_64"}
+    # Only set CFLAGS, FFLAGS if they are not already defined.  Build functions
+    # can override the arch flags by setting CFLAGS, FFLAGS
+    export CFLAGS="${CFLAGS:-$ARCH_FLAGS}"
+    export FFLAGS="${FFLAGS:-$ARCH_FLAGS}"
+    IS_OSX=1
+fi
+
+function build_simple {
+    local name=$1
+    local version=$2
+    local url=$3
+    if [ -e "${name}-stamp" ]; then
+        return
+    fi
+    local name_version="${name}-${version}"
+    local targz=${name_version}.tar.gz
+    fetch_unpack $url/$targz
+    (cd $name_version \
+        && ./configure --prefix=$BUILD_PREFIX ${ENABLE_STATIC} \
+        && make \
+        && make install)
+    touch "${name}-stamp"
+}
+
+function build_openblas {
+    if [ -e openblas-stamp ]; then return; fi
+    if [ -d "OpenBLAS" ]; then
+        (cd OpenBLAS && git clean -fxd && git reset --hard)
+    else
+        git clone https://github.com/xianyi/OpenBLAS
+    fi
+    (cd OpenBLAS \
+        && git checkout "v${OPENBLAS_VERSION}" \
+        && make DYNAMIC_ARCH=1 USE_OPENMP=0 NUM_THREADS=64 > /dev/null \
+        && make PREFIX=$BUILD_PREFIX install)
+    touch openblas-stamp
+}
+
+function build_zlib {
+    # Gives an old but safe version
+    if [ -e zlib-stamp ]; then return; fi
+    # OSX has zlib already
+    if [ -z "$IS_OSX" ]; then yum install -y zlib-devel; fi
+    touch zlib-stamp
+}
+
+function build_new_zlib {
+    # Careful, this one may cause yum to segfault
+    build_simple zlib $ZLIB_VERSION http://zlib.net
+}
+
+function build_jpeg {
+    if [ -e jpeg-stamp ]; then return; fi
+    fetch_unpack http://ijg.org/files/jpegsrc.v9b.tar.gz
+    (cd jpeg-9b \
+        && ./configure --prefix=$BUILD_PREFIX \
+        && make \
+        && make install)
+    touch jpeg-stamp
+}
+
+function build_libpng {
+    build_zlib
+    build_simple libpng $LIBPNG_VERSION http://download.sourceforge.net/libpng
+}
+
+function build_bzip2 {
+    if [ -e bzip2-stamp ]; then return; fi
+    fetch_unpack http://bzip.org/${BZIP2_VERSION}/bzip2-${BZIP2_VERSION}.tar.gz
+    (cd bzip2-${BZIP2_VERSION} \
+        && make -f Makefile-libbz2_so \
+        && make install PREFIX=$BUILD_PREFIX)
+    touch bzip2-stamp
+}
+
+function build_tiff {
+    build_zlib
+    build_jpeg
+    build_openjpeg
+    build_xz
+    build_simple tiff $TIFF_VERSION http://download.osgeo.org/libtiff
+}
+
+function build_openjpeg {
+    if [ -e openjpeg-stamp ]; then return; fi
+    local cmake=cmake
+    if [ -n "$IS_OSX" ]; then
+        brew install cmake
+    else
+        yum install -y cmake28
+        cmake=cmake28
+    fi
+    fetch_unpack https://github.com/uclouvain/openjpeg/archive/version.${OPENJPEG_VERSION}.tar.gz
+    (cd openjpeg-version.${OPENJPEG_VERSION} \
+        && $cmake -DCMAKE_INSTALL_PREFIX=$BUILD_PREFIX . \
+        && make install)
+    touch openjpeg-stamp
+}
+
+function build_lcms2 {
+    build_tiff
+    build_simple lcms2 $LCMS2_VERSION http://downloads.sourceforge.net/project/lcms/lcms/$LCMS2_VERSION
+}
+
+function build_giflib {
+    build_simple giflib $GIFLIB_VERSION http://downloads.sourceforge.net/project/giflib
+}
+
+function build_xz {
+    build_simple xz $XZ_VERSION http://tukaani.org/xz
+}
+
+function build_libwebp {
+    if [ -e libwebp-stamp ]; then return; fi
+    build_libpng
+    build_tiff
+    build_giflib
+    fetch_unpack https://storage.googleapis.com/downloads.webmproject.org/releases/webp/libwebp-${LIBWEBP_VERSION}.tar.gz
+    (cd libwebp-${LIBWEBP_VERSION} && \
+        ./configure --enable-libwebpmux --enable-libwebpdemux --prefix=$BUILD_PREFIX \
+        && make \
+        && make install)
+    touch libwebp-stamp
+}
+
+function build_freetype {
+    build_libpng
+    build_bzip2
+    build_simple freetype $FREETYPE_VERSION http://download.savannah.gnu.org/releases/freetype
+}
+
+function build_libyaml {
+    build_simple yaml $LIBYAML_VERSION http://pyyaml.org/download/libyaml
+}
+
+function build_szip {
+    # Build szip without encoding (patent restrictions)
+    if [ -e szip-stamp ]; then return; fi
+    build_zlib
+    local szip_url=https://www.hdfgroup.org/ftp/lib-external/szip/
+    fetch_unpack ${szip_url}/$SZIP_VERSION/src/szip-$SZIP_VERSION.tar.gz
+    (cd szip-$SZIP_VERSION \
+        && ./configure --enable-encoding=no --prefix=$BUILD_PREFIX \
+        && make \
+        && make install)
+    touch szip-stamp
+}
+
+function build_hdf5 {
+    if [ -e hdf5-stamp ]; then return; fi
+    build_zlib
+    # libaec is a drop-in replacement for szip
+    build_libaec
+    local hdf5_url=https://www.hdfgroup.org/ftp/HDF5/releases
+    fetch_unpack $hdf5_url/hdf5-$HDF5_VERSION/src/hdf5-$HDF5_VERSION.tar.gz
+    (cd hdf5-$HDF5_VERSION \
+        && ./configure --with-szlib=$BUILD_PREFIX --prefix=$BUILD_PREFIX \
+        && make \
+        && make install)
+    touch hdf5-stamp
+}
+
+function build_libaec {
+    if [ -e libaec-stamp ]; then return; fi
+    local root_name=libaec-0.3.3
+    local tar_name=${root_name}.tar.gz
+    # Note URL will change for each version
+    fetch_unpack https://gitlab.dkrz.de/k202009/libaec/uploads/48398bd5b7bc05a3edb3325abfeac864/${tar_name}
+    (cd $root_name \
+        && ./configure --prefix=$BUILD_PREFIX \
+        && make \
+        && make install)
+    touch libaec-stamp
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/manylinux/manylinux_utils.sh	Fri Jun 23 11:38:34 2017 +0100
@@ -0,0 +1,51 @@
+#!/bin/bash
+# Useful utilities common across manylinux1 builds
+
+MULTIBUILD_DIR=$(dirname "${BASH_SOURCE[0]}")
+source $MULTIBUILD_DIR/common_utils.sh
+
+# UNICODE_WIDTH selects "32"=wide (UCS4) or "16"=narrow (UCS2/UTF16) builds
+UNICODE_WIDTH="${UNICODE_WIDTH:-32}"
+
+function get_platform {
+    # Report platform as given by uname
+    python -c 'import platform; print(platform.uname()[4])'
+}
+
+function cpython_path {
+    # Return path to cpython given
+    # * version (of form "2.7")
+    # * u_width ("16" or "32" default "32")
+    #
+    # For back-compatibility "u" as u_width also means "32"
+    local py_ver="${1:-2.7}"
+    local u_width="${2:-${UNICODE_WIDTH}}"
+    local u_suff=u
+    # Back-compatibility
+    if [ "$u_width" == "u" ]; then u_width=32; fi
+    # For Python >= 3.3, "u" suffix not meaningful
+    if [ $(lex_ver $py_ver) -ge $(lex_ver 3.3) ] ||
+        [ "$u_width" == "16" ]; then
+        u_suff=""
+    elif [ "$u_width" != "32" ]; then
+        echo "Incorrect u_width value $u_width"
+        exit 1
+    fi
+    local no_dots=$(echo $py_ver | tr -d .)
+    echo "/opt/python/cp${no_dots}-cp${no_dots}m${u_suff}"
+}
+
+function repair_wheelhouse {
+    local in_dir=$1
+    local out_dir=${2:-$in_dir}
+    for whl in $in_dir/*.whl; do
+        if [[ $whl == *none-any.whl ]]; then  # Pure Python wheel
+            if [ "$in_dir" != "$out_dir" ]; then cp $whl $out_dir; fi
+        else
+            auditwheel repair $whl -w $out_dir/
+            # Remove unfixed if writing into same directory
+            if [ "$in_dir" == "$out_dir" ]; then rm $whl; fi
+        fi
+    done
+    chmod -R a+rwX $out_dir
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/manylinux/test-wheels	Fri Jun 23 11:38:34 2017 +0100
@@ -0,0 +1,23 @@
+#!/bin/bash
+UNICODE_WIDTHS='16 32'
+PYTHON_VERSIONS='2.7 3.3 3.4 3.5 3.6'
+ARCHS='x86_64 i686'
+IMAGESRC=quay.io/pypa
+REPO=${REPO:-https://bitbucket.org/rptlab/reportlab}
+REQUIREMENT=${REPO:-$(basename ${REPO})}
+DOCKER_SCRIPT=${DOCKER_SCRIPT:-container-test-wheels.sh}
+sudo rm -rf test-src
+hg clone ${REPO} test-src
+[ -n "$REVISION" ] && (cd test-src && hg up "$REVISION")
+#IMAGESRC=rl
+for arch in ${ARCHS}; do
+	DOCKER_IMAGE=${IMAGESRC}/manylinux1_${arch}
+	docker run --rm \
+		-e PYTHON_VERSIONS="$PYTHON_VERSIONS" \
+		-e UNICODE_WIDTHS="$UNICODE_WIDTHS" \
+		-e REPO="$REPO" \
+		-e REEQUIREMENT="$REQUIREMENT" \
+		-e ARCH="$arch" \
+		-v $(pwd):/io ${IMAGESRC}/manylinux1_${arch} /io/rl_ci_tools/manylinux/${DOCKER_SCRIPT}
+done
+sudo rm -rf reportlab-src test-env
--- a/rl_ci_tools.py	Mon Jun 19 15:11:35 2017 +0100
+++ b/rl_ci_tools.py	Fri Jun 23 11:38:34 2017 +0100
@@ -1,4 +1,4 @@
-VERSION='0.0.2'
+VERSION='0.0.3'
 import os, sys, glob, time, json
 PROG=os.path.basename(sys.argv[0])
 debug=verbosity=0