#!/usr/bin/env bash # This script serves as the # [**Python Buildpack**](https://github.com/heroku/heroku-buildpack-python) # compiler. # # A [buildpack](http://devcenter.heroku.com/articles/buildpacks) is an # adapter between a Python application and Heroku's runtime. # # You can intreract with the Heroku API directly with [heroku.py](https://github.com/heroku/heroku.py/). # # See also: [Release history](/changelog.html), [Detection](/detect.html). # # ## Usage # Compiling an app into a slug is simple: # # $ bin/compile # ## Assumptions # # This buildpack makes the following assumptions: # # - The desired Python VM is available on the base system. # - Library dependencies are available on the base system. # - Django applications should not require any platform-specific configuration. #
# ## Context # 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 # The detected application type (`Python`|`Python/Django`). NAME=$($BIN_DIR/detect $BUILD_DIR) # Where to store the Pip download cache. CACHED_DIRS=".heroku" PIP_DOWNLOAD_CACHE=${PIP_DOWNLOAD_CACHE:-$CACHE_DIR/pip_downloads} # Static configurations for virtualenv caches. LEGACY_VIRTUALENV_LOC="." MODERN_VIRTUALENV_LOC=".heroku/venv" LEGACY_VIRTUALENV_DIRS="bin include lib" LEGACY_VIRTUALENV_TRIGGER="lib/python2.7" # Python version. This will be used in the future to specify custom Pythons. PYTHON_VERSION="2.7.2" PYTHON_EXE="python2.7" # Sanitizing environment variables. unset GIT_DIR unset PYTHONHOME unset PYTHONPATH # We'll need to send these statics to other scripts we `source`. export PIP_DOWNLOAD_CACHE BUILD_DIR CACHE_DIR BIN_DIR # Syntax sugar. indent() { RE="s/^/ /" [ $(uname) == "Darwin" ] && sed -l "$RE" || sed -u "$RE" } function virtualenv (){ python "$ROOT_DIR/vendor/virtualenv-1.7/virtualenv.py" "$@" } function puts-step (){ echo "-----> $@" } function puts-warn (){ echo " ! $@" } # ## Build Time # # Switch to the repo's context. cd $BUILD_DIR # Experimental pre_compile hook. source $BIN_DIR/steps/hooks/pre_compile # ### Sanity Checks # # Just a little peace of mind. # If no requirements given, assume `setup.py develop`. if [ ! -f requirements.txt ]; then puts-step "No requirements.txt provided; assuming dist package." echo "-e ." > requirements.txt fi # ### The Cache mkdir -p $CACHE_DIR # Nice defaults. LEGACY_VIRTUALENV=false VIRTUALENV_LOC=$MODERN_VIRTUALENV_LOC # Support "old-style" virtualenvs. if [ -d $CACHE_DIR/$LEGACY_VIRTUALENV_TRIGGER ]; then LEGACY_VIRTUALENV=true VIRTUALENV_LOC=$LEGACY_VIRTUALENV_LOC CACHED_DIRS=$LEGACY_VIRTUALENV_DIRS # Warn for a checked-in virtualenv. if [ -d "lib" ] || [ -d "bin" ]; then puts-warn "You have a virtualenv checked in. You should ignore the appropriate paths in your repo. See http://devcenter.heroku.com/articles/gitignore for more info."; fi # Reject a conflicting checked-in virtualenv. if [ -f "lib/python2.7" ]; then puts-warn "Checked-in virtualenv conflict." exit 1; fi fi # Restore old artifacts from the cache. for dir in $CACHED_DIRS; do cp -R $CACHE_DIR/$dir . &> /dev/null || true done set +e # Create set-aside `.heroku` folder. mkdir .heroku &> /dev/null HEROKU_DIR_STATUS=$? # TODO: This is a new app, disable injection. # [ $HEROKU_DIR_STATUS -eq 0 ] && { #TODO: touch .heroku/injection_disabled # } set -e # ### Virtualenv Setup # # Create the virtualenv. Rebuild if corrupt. # TODO: Bootstrap a bottled Python VM... set +e puts-step "Preparing Python interpreter ($PYTHON_VERSION)" puts-step "Creating Virtualenv version $(virtualenv --version)" # Try to create the virtualenv. OUT=$(virtualenv --python $PYTHON_EXE --distribute --never-download --prompt='(venv) ' $VIRTUALENV_LOC 2>&1) # If there's an error, purge and recreate. [ $? -ne 0 ] && { puts-warn "Virtualenv corrupt, rebuilding." for dir in $VIRTUALENV_DIRS; do rm -fr $dir &> /dev/null || true done OUT=$(virtualenv --python $PYTHON_EXE --distribute --never-download --prompt='(venv) ' $VIRTUALENV_LOC ) } echo "$OUT" | indent set -e # Pylibmc support. # See [`bin/steps/pylibmc`](pylibmc.html). source $BIN_DIR/steps/pylibmc # Activate the Virtualenv. puts-step "Activating virtualenv" source $VIRTUALENV_LOC/bin/activate # Install Mercurial if it appears to be required. if (grep -Fiq "hg+" requirements.txt) then pip install --use-mirrors mercurial | indent fi # Install dependencies with Pip. puts-step "Installing dependencies using pip version $(pip --version | awk '{print $2}')" pip install --use-mirrors -r requirements.txt --src ./.heroku/src | indent # Do additional application hackery if applications appears to be a Django app. # Optionally, disable all Django-specific changes with `DISABLE_INJECTION` env. # # See [`bin/steps/django`](django.html). if [ "$NAME" = "Python/Django" ]; then source $BIN_DIR/steps/django/init fi # Make Virtualenv's paths relative for portability. set +e OUT=$(virtualenv --python $PYTHON_EXE --relocatable $VIRTUALENV_LOC) [ $? -ne 0 ] && { puts-warn "Error making virtualenv relocatable" echo "$OUT" | indent exit 1 } set -e # ### Finalize # # Store new artifacts in cache. for dir in $CACHED_DIRS; do rm -rf $CACHE_DIR/$dir cp -R $dir $CACHE_DIR/ done # ### Fin. # Experimental post_compile hook. source $BIN_DIR/steps/hooks/post_compile # Fork me on GitHub