#!/usr/bin/env bash # The Heroku Python Buildpack. This script accepts parameters for a build # directory, a cache directory, and a directory for app environment variables. # Warning: there are a few hacks in this script to accommodate excellent builds # on Heroku. No guarantee for external compatibility is made. However, # everything should work fine outside of the Heroku environment, if the # environment is setup correctly. # Usage: # # $ bin/compile # Fail fast and fail hard. set -eo pipefail [ "$BUILDPACK_XTRACE" ] && set -o xtrace # 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 # Static configurations for virtualenv caches. VIRTUALENV_LOC=".heroku/venv" LEGACY_TRIGGER="lib/python2.7" DEFAULT_PYTHON_VERSION="python-2.7.13" DEFAULT_PYTHON_STACK="cedar-14" PYTHON_EXE="/app/.heroku/python/bin/python" PIP_VERSION="9.0.1" SETUPTOOLS_VERSION="32.1.0" # Common Problem Warnings export WARNINGS_LOG=$(mktemp) export RECOMMENDED_PYTHON_VERSION=$DEFAULT_PYTHON_VERSION # Setup bpwatch export PATH=$PATH:$ROOT_DIR/vendor/:$ROOT_DIR/vendor/bpwatch LOGPLEX_KEY="t.b90d9d29-5388-4908-9737-b4576af1d4ce" export BPWATCH_STORE_PATH=$CACHE_DIR/bpwatch.json BUILDPACK_VERSION=v28 # Setup pip-pop (pip-diff) export PATH=$PATH:$ROOT_DIR/vendor/pip-pop # 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 unset RECEIVE_DATA RUN_KEY BUILD_INFO DEPLOY LOG_TOKEN DYNO unset CYTOKINE_LOG_FILE GEM_PATH # Setup buildpack instrumentation. bpwatch init $LOGPLEX_KEY bpwatch build python $BUILDPACK_VERSION $REQUEST_ID bpwatch start compile # Syntax sugar. source $BIN_DIR/utils # Import collection of warnings. source $BIN_DIR/warnings # we need to put a bunch of symlinks in there later mkdir -p /app/.heroku # Set up outputs under new context PROFILE_PATH="$BUILD_DIR/.profile.d/python.sh" EXPORT_PATH="$BIN_DIR/../export" GUNICORN_PROFILE_PATH="$BUILD_DIR/.profile.d/python.gunicorn.sh" # We'll need to send these statics to other scripts we `source`. export BUILD_DIR CACHE_DIR BIN_DIR PROFILE_PATH EXPORT_PATH # Prepend proper environment variables for Python use. export PATH=/app/.heroku/python/bin:/app/.heroku/vendor/bin:$PATH export PYTHONUNBUFFERED=1 export LANG=en_US.UTF-8 export C_INCLUDE_PATH=/app/.heroku/vendor/include:/app/.heroku/python/include:$C_INCLUDE_PATH export CPLUS_INCLUDE_PATH=/app/.heroku/vendor/include:/app/.heroku/python/include:$CPLUS_INCLUDE_PATH export LIBRARY_PATH=/app/.heroku/vendor/lib:/app/.heroku/python/lib:$LIBRARY_PATH export LD_LIBRARY_PATH=/app/.heroku/vendor/lib:/app/.heroku/python/lib:$LD_LIBRARY_PATH export PKG_CONFIG_PATH=/app/.heroku/vendor/lib/pkg-config:/app/.heroku/python/lib/pkg-config:$PKG_CONFIG_PATH # Switch to the repo's context. cd $BUILD_DIR # Warn for lack of Procfile. if [[ ! -f Procfile ]]; then puts-warn 'Warning: Your application is missing a Procfile. This file tells Heroku how to run your application.' puts-warn 'Learn more: https://devcenter.heroku.com/articles/procfile' fi # Experimental pre_compile hook. bpwatch start pre_compile source $BIN_DIR/steps/hooks/pre_compile bpwatch stop pre_compile # 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=$STACK fi # Pipenv Python version support. source $BIN_DIR/steps/pipenv-python-version # If no runtime given, assume default version. if [ ! -f runtime.txt ]; then echo $DEFAULT_PYTHON_VERSION > runtime.txt fi # Prepare 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 mkdir -p .heroku cp -R $CACHE_DIR/.heroku/python .heroku/ &> /dev/null || true cp -R $CACHE_DIR/.heroku/python-stack .heroku/ &> /dev/null || true cp -R $CACHE_DIR/.heroku/python-version .heroku/ &> /dev/null || true cp -R $CACHE_DIR/.heroku/vendor .heroku/ &> /dev/null || true cp -R $CACHE_DIR/.heroku/venv .heroku/ &> /dev/null || true if [[ -d $CACHE_DIR/.heroku/src ]]; then cp -R $CACHE_DIR/.heroku/src .heroku/ &> /dev/null || true fi bpwatch stop restore_cache mkdir -p $(dirname $PROFILE_PATH) mkdir -p /app/.heroku/src if [[ $BUILD_DIR != '/app' ]]; then # python expects to reside in /app, so set up symlinks # we will not remove these later so subsequent buildpacks can still invoke it ln -nsf $BUILD_DIR/.heroku/python /app/.heroku/python ln -nsf $BUILD_DIR/.heroku/vendor /app/.heroku/vendor ln -nsf $BUILD_DIR/.heroku/venv /app/.heroku/venv # Note: .heroku/src is copied in later. fi # Install Python. source $BIN_DIR/steps/python # Sanity check for setuptools/distribute. source $BIN_DIR/steps/setuptools # Pipenv support. source $BIN_DIR/steps/pipenv # If no requirements.txt file given, assume `setup.py develop` is intended. if [ ! -f requirements.txt ]; then echo "-e ." > requirements.txt fi # Uninstall removed dependencies with Pip. source $BIN_DIR/steps/pip-uninstall # Mercurial support. source $BIN_DIR/steps/mercurial # Pylibmc support. source $BIN_DIR/steps/pylibmc # Libffi support. source $BIN_DIR/steps/cryptography # Support for Geo libraries. sub-env $BIN_DIR/steps/geo-libs # GDAL support. source $BIN_DIR/steps/gdal # Install dependencies with Pip (where the magic happens). source $BIN_DIR/steps/pip-install # Support for NLTK corpora. sub-env $BIN_DIR/steps/nltk # Support for pip install -e. rm -fr $BUILD_DIR/.heroku/src deep-cp /app/.heroku/src $BUILD_DIR/.heroku/src # Django collectstatic support. sub-env $BIN_DIR/steps/collectstatic # Create .profile script for application runtime 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:$LIBRARY_PATH' 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/ # Install sane-default script for $WEB_CONCURRENCY and $FORWARDED_ALLOW_IPS. cp $ROOT_DIR/vendor/python.gunicorn.sh $GUNICORN_PROFILE_PATH # Experimental post_compile hook. bpwatch start post_compile source $BIN_DIR/steps/hooks/post_compile bpwatch stop post_compile set +e # rewrite build dir in egg links to /app so things are found at runtime find .heroku/python/lib/python*/site-packages/ -name "*.pth" -print0 2> /dev/null | xargs -r -0 -n 1 sed -i -e "s#$(pwd)#/app#" &> /dev/null set -e set +e # Support for PyPy find .heroku/python/lib-python/*/site-packages/ -name "*.pth" -print0 2> /dev/null | xargs -r -0 -n 1 sed -i -e "s#$(pwd)#/app#" &> /dev/null set -e # Store new artifacts in cache. bpwatch start dump_cache rm -rf $CACHE_DIR/.heroku/python rm -rf $CACHE_DIR/.heroku/python-version rm -rf $CACHE_DIR/.heroku/python-stack rm -rf $CACHE_DIR/.heroku/vendor rm -rf $CACHE_DIR/.heroku/venv rm -rf $CACHE_DIR/.heroku/src mkdir -p $CACHE_DIR/.heroku cp -R .heroku/python $CACHE_DIR/.heroku/ cp -R .heroku/python-version $CACHE_DIR/.heroku/ cp -R .heroku/python-stack $CACHE_DIR/.heroku/ &> /dev/null || true cp -R .heroku/vendor $CACHE_DIR/.heroku/ &> /dev/null || true cp -R .heroku/venv $CACHE_DIR/.heroku/ &> /dev/null || true if [[ -d .heroku/src ]]; then cp -R .heroku/src $CACHE_DIR/.heroku/ &> /dev/null || true fi bpwatch stop dump_cache # Fin. bpwatch stop compile