#!/usr/bin/env bash # Usage: # # $ bin/compile # Fail fast and fail hard. set -eo pipefail # Prepend proper path for virtualenv hackery. This will be deprecated soon. export PATH=:/usr/local/bin:$PATH # Paths. BIN_DIR=$(cd $(dirname $0); pwd) # absolute path ROOT_DIR=$(dirname $BIN_DIR) BUILD_DIR=$1 CACHE_DIR=$2 ENV_DIR=$3 CACHED_DIRS=".heroku" # Static configurations for virtualenv caches. VIRTUALENV_LOC=".heroku/venv" LEGACY_TRIGGER="lib/python2.7" PROFILE_PATH="$BUILD_DIR/.profile.d/python.sh" DEFAULT_PYTHON_VERSION="python-2.7.8" DEFAULT_PYTHON_STACK="cedar" PYTHON_EXE="/app/.heroku/python/bin/python" PIP_VERSION="1.5.6" SETUPTOOLS_VERSION="5.4.1" # Setup bpwatch export PATH=$PATH:$ROOT_DIR/vendor/bpwatch LOGPLEX_KEY="t.b90d9d29-5388-4908-9737-b4576af1d4ce" export BPWATCH_STORE_PATH=$CACHE_DIR/bpwatch.json BUILDPACK_VERSION=v28 # Support Anvil Build_IDs [ ! "$SLUG_ID" ] && SLUG_ID="defaultslug" [ ! "$REQUEST_ID" ] && REQUEST_ID=$SLUG_ID [ ! "$STACK" ] && STACK=$DEFAULT_PYTHON_STACK # Sanitizing environment variables. unset GIT_DIR PYTHONHOME PYTHONPATH LD_LIBRARY_PATH LIBRARY_PATH bpwatch init $LOGPLEX_KEY bpwatch build python $BUILDPACK_VERSION $REQUEST_ID TMP_APP_DIR=$CACHE_DIR/tmp_app_dir bpwatch start compile # We'll need to send these statics to other scripts we `source`. export BUILD_DIR CACHE_DIR BIN_DIR PROFILE_PATH # Syntax sugar. source $BIN_DIR/utils # Directory Hacks for path consistiency. APP_DIR='/app' TMP_APP_DIR=$CACHE_DIR/tmp_app_dir # Copy Anvil app dir to temporary storage... bpwatch start anvil_appdir_stage if [ "$SLUG_ID" ]; then mkdir -p $TMP_APP_DIR deep-mv $APP_DIR $TMP_APP_DIR else deep-rm $APP_DIR fi bpwatch stop anvil_appdir_stage # Copy Application code in. bpwatch start appdir_stage deep-mv $BUILD_DIR $APP_DIR bpwatch stop appdir_stage # Set new context. ORIG_BUILD_DIR=$BUILD_DIR BUILD_DIR=$APP_DIR # Prepend proper path buildpack use. export PATH=$BUILD_DIR/.heroku/python/bin:$BUILD_DIR/.heroku/vendor/bin:$PATH export PYTHONUNBUFFERED=1 export LANG=en_US.UTF-8 export C_INCLUDE_PATH=/app/.heroku/vendor/include:$BUILD_DIR/.heroku/vendor/include:/app/.heroku/python/include export CPLUS_INCLUDE_PATH=/app/.heroku/vendor/include:$BUILD_DIR/.heroku/vendor/include:/app/.heroku/python/include export LIBRARY_PATH=/app/.heroku/vendor/lib:$BUILD_DIR/.heroku/vendor/lib:/app/.heroku/python/lib export LD_LIBRARY_PATH=/app/.heroku/vendor/lib:$BUILD_DIR/.heroku/vendor/lib:/app/.heroku/python/lib export PKG_CONFIG_PATH=/app/.heroku/vendor/lib/pkg-config:$BUILD_DIR/.heroku/vendor/lib/pkg-config:/app/.heroku/python/lib/pkg-config # Switch to the repo's context. cd $BUILD_DIR # Experimental pre_compile hook. bpwatch start pre_compile source $BIN_DIR/steps/hooks/pre_compile bpwatch stop pre_compile # If no requirements given, assume `setup.py develop`. if [ ! -f requirements.txt ]; then echo "-e ." > requirements.txt fi # Sticky runtimes. if [ -f $CACHE_DIR/.heroku/python-version ]; then DEFAULT_PYTHON_VERSION=$(cat $CACHE_DIR/.heroku/python-version) fi # Stack fallback for non-declared caches. if [ -f $CACHE_DIR/.heroku/python-stack ]; then CACHED_PYTHON_STACK=$(cat $CACHE_DIR/.heroku/python-stack) else CACHED_PYTHON_STACK=$DEFAULT_PYTHON_STACK fi # If no runtime given, assume default version. if [ ! -f runtime.txt ]; then echo $DEFAULT_PYTHON_VERSION > runtime.txt fi # ### The Cache mkdir -p $CACHE_DIR # Purge "old-style" virtualenvs. bpwatch start clear_old_venvs [ -d $CACHE_DIR/$LEGACY_TRIGGER ] && rm -fr $CACHE_DIR/.heroku/bin $CACHE_DIR/.heroku/lib $CACHE_DIR/.heroku/include [ -d $CACHE_DIR/$VIRTUALENV_LOC ] && rm -fr $CACHE_DIR/.heroku/venv $CACHE_DIR/.heroku/src bpwatch stop clear_old_venvs # Restore old artifacts from the cache. bpwatch start restore_cache for dir in $CACHED_DIRS; do cp -R $CACHE_DIR/$dir . &> /dev/null || true done bpwatch stop restore_cache set +e # Create set-aside `.heroku` folder. mkdir .heroku &> /dev/null set -e mkdir -p $(dirname $PROFILE_PATH) set +e PYTHON_VERSION=$(cat runtime.txt) # Install Python. if [ -f .heroku/python-version ]; then if [ ! $(cat .heroku/python-version) = $PYTHON_VERSION ]; then bpwatch start uninstall_python puts-step "Found runtime $(cat .heroku/python-version), removing" rm -fr .heroku/python bpwatch stop uninstall_python else SKIP_INSTALL=1 fi fi if [ ! $STACK = $CACHED_PYTHON_STACK ]; then bpwatch start uninstall_python puts-step "Stack changed, re-installing runtime" rm -fr .heroku/python unset SKIP_INSTALL bpwatch stop uninstall_python fi if [ ! "$SKIP_INSTALL" ]; then bpwatch start install_python puts-step "Installing runtime ($PYTHON_VERSION)" # Prepare destination directory. mkdir -p .heroku/python curl http://lang-python.s3.amazonaws.com/$STACK/runtimes/$PYTHON_VERSION.tar.gz -s | tar zxv -C .heroku/python &> /dev/null if [[ $? != 0 ]] ; then puts-warn "Requested runtime ($PYTHON_VERSION) is not available for this stack ($STACK)." puts-warn "Aborting. More info: https://devcenter.heroku.com/articles/python-support" exit 1 fi bpwatch stop install_python # Record for future reference. echo $PYTHON_VERSION > .heroku/python-version echo $STACK > .heroku/python-stack FRESH_PYTHON=true hash -r fi # If Pip isn't up to date: if [ "$FRESH_PYTHON" ] || [[ ! $(pip --version) == *$PIP_VERSION* ]]; then WORKING_DIR=$(pwd) bpwatch start prepare_environment bpwatch start install_setuptools # Prepare it for the real world # puts-step "Installing Setuptools ($SETUPTOOLS_VERSION)" cd $ROOT_DIR/vendor/ tar zxf setuptools-$SETUPTOOLS_VERSION.tar.gz cd $ROOT_DIR/vendor/setuptools-$SETUPTOOLS_VERSION/ python setup.py install &> /dev/null cd $WORKING_DIR bpwatch stop install_setuptoools bpwatch start install_pip # puts-step "Installing Pip ($PIP_VERSION)" cd $ROOT_DIR/vendor/ tar zxf pip-$PIP_VERSION.tar.gz cd $ROOT_DIR/vendor/pip-$PIP_VERSION/ python setup.py install &> /dev/null cd $WORKING_DIR bpwatch stop install_pip bpwatch stop prepare_environment fi set -e hash -r # Pylibmc support. # See [`bin/steps/pylibmc`](pylibmc.html). bpwatch start pylibmc_install source $BIN_DIR/steps/pylibmc bpwatch stop pylibmc_install # Install Mercurial if it appears to be required. if (grep -Fiq "hg+" requirements.txt) then bpwatch start mercurial_install /app/.heroku/python/bin/pip install mercurial | cleanup | indent bpwatch stop mercurial_install fi # Install dependencies with Pip. puts-step "Installing dependencies with pip" [ ! "$FRESH_PYTHON" ] && bpwatch start pip_install [ "$FRESH_PYTHON" ] && bpwatch start pip_install_first /app/.heroku/python/bin/pip install -r requirements.txt --exists-action=w --src=./.heroku/src --allow-all-external | cleanup | indent [ ! "$FRESH_PYTHON" ] && bpwatch stop pip_install [ "$FRESH_PYTHON" ] && bpwatch stop pip_install_first # Django collectstatic support. bpwatch start collectstatic sub-env $BIN_DIR/steps/collectstatic bpwatch stop collectstatic # ### Finalize # # Set context environment variables. set-env PATH '$HOME/.heroku/python/bin:$PATH' set-env PYTHONUNBUFFERED true set-env PYTHONHOME /app/.heroku/python set-env LIBRARY_PATH /app/.heroku/vendor/lib:/app/.heroku/python/lib set-env LD_LIBRARY_PATH '/app/.heroku/vendor/lib:/app/.heroku/python/lib:$LD_LIBRARY_PATH' set-default-env LANG en_US.UTF-8 set-default-env PYTHONHASHSEED random set-default-env PYTHONPATH /app/ # Experimental post_compile hook. bpwatch start post_compile source $BIN_DIR/steps/hooks/post_compile bpwatch stop post_compile # Store new artifacts in cache. bpwatch start dump_cache for dir in $CACHED_DIRS; do rm -rf $CACHE_DIR/$dir cp -R $dir $CACHE_DIR/ done bpwatch stop dump_cache # ### Fin. bpwatch start appdir_commit deep-mv $BUILD_DIR $ORIG_BUILD_DIR bpwatch stop appdir_commit bpwatch start anvil_appdir_commit if [ "$SLUG_ID" ]; then deep-mv $TMP_APP_DIR $APP_DIR fi bpwatch stop anvil_appdir_commit bpwatch stop compile