#!/usr/bin/env bash # # Summary: Create a Python virtualenv using the pyenv-virtualenv plugin # # Usage: pyenv virtualenv [-f|--force] [-u|--upgrade] [VIRTUALENV_OPTIONS] # pyenv virtualenv --version # pyenv virtualenv --help # # -u/--upgrade Upgrade existing virtualenv with migrating installed packages # -f/--force Install even if the version appears to be installed already # PYENV_VIRTUALENV_VERSION="20130614" VIRTUALENV_VERSION="${VIRTUALENV_VERSION:-1.9.1}" set -e [ -n "$PYENV_DEBUG" ] && set -x # Provide pyenv completions if [ "$1" = "--complete" ]; then exec pyenv-versions --bare fi if [ -z "$PYENV_ROOT" ]; then PYENV_ROOT="${HOME}/.pyenv" fi # Define library functions parse_options() { OPTIONS=() ARGUMENTS=() local arg option index for arg in "$@"; do if [ "${arg:0:1}" = "-" ]; then if [ "${arg:1:1}" = "-" ]; then OPTIONS[${#OPTIONS[*]}]="${arg:2}" else index=1 while option="${arg:$index:1}"; do [ -n "$option" ] || break OPTIONS[${#OPTIONS[*]}]="$option" index=$(($index+1)) done fi else ARGUMENTS[${#ARGUMENTS[*]}]="$arg" fi done } resolve_link() { $(type -p greadlink readlink | head -1) "$1" } abs_dirname() { local cwd="$(pwd)" local path="$1" while [ -n "$path" ]; do cd "${path%/*}" local name="${path##*/}" path="$(resolve_link "$name" || true)" done pwd cd "$cwd" } http() { local method="$1" local url="$2" local file="$3" [ -n "$url" ] || return 1 if type curl &>/dev/null; then "http_${method}_curl" "$url" "$file" elif type wget &>/dev/null; then "http_${method}_wget" "$url" "$file" else echo "error: please install \`curl\` or \`wget\` and try again" >&2 exit 1 fi } http_head_curl() { curl -qsILf "$1" >&4 2>&1 } http_get_curl() { curl -C - -o "${2:--}" -qsSLf "$1" } http_head_wget() { wget -q --spider "$1" >&4 2>&1 } http_get_wget() { wget -nv -c -O "${2:--}" "$1" } version() { local version="$(pyenv-exec python "${VIRTUALENV}" --version 2>/dev/null || true)" echo "pyenv-virtualenv ${PYENV_VIRTUALENV_VERSION} (virtualenv ${version:-unknown})" } usage() { # We can remove the sed fallback once pyenv 0.2.0 is widely available. pyenv-help virtualenv 2>/dev/null || sed -ne '/^#/!q;s/.//;s/.//;1,4d;p' < "$0" pyenv-exec python "${VIRTUALENV}" --help 2>/dev/null || true [ -z "$1" ] || exit "$1" } ensure_virtualenv() { local file="$1" local url="$2" [ -f "${file}" ] || { mkdir -p "$(dirname "${file}")" http get "${url}" "${file}" } } PYENV_VIRTUALENV_ROOT="$(abs_dirname "$0")/.." if [ -z "${PYENV_VIRTUALENV_CACHE_PATH}" ]; then PYENV_VIRTUALENV_CACHE_PATH="${PYTHON_BUILD_CACHE_PATH:-${PYENV_ROOT}/cache}" fi if [ -z "${PYENV_VIRTUALENV_SCRIPT_PATH}" ]; then PYENV_VIRTUALENV_SCRIPT_PATH="${PYENV_VIRTUALENV_ROOT}/libexec/pyenv-virtualenv" fi VIRTUALENV="${PYENV_VIRTUALENV_SCRIPT_PATH}/${VIRTUALENV_VERSION}/virtualenv.py" VIRTUALENV_URL="https://raw.github.com/pypa/virtualenv/${VIRTUALENV_VERSION}/virtualenv.py" VIRTUALENV_OPTIONS=() ensure_virtualenv "${VIRTUALENV}" "${VIRTUALENV_URL}" || { echo "pyenv-virtualenv: could not find virtualenv script: ${VIRTUALENV}" 1>&2 exit 1 } unset FORCE unset UPGRADE # Unset environment variables which starts with `VIRTUALENV_`. # These variables are reserved for virtualenv. unset VIRTUALENV_VERSION parse_options "$@" for option in "${OPTIONS[@]}"; do case "$option" in "f" | "force" ) FORCE=true ;; "h" | "help" ) usage 0 ;; "u" | "upgrade" ) UPGRADE=true ;; "version" ) version exit 0 ;; "q" ) # virtualenv short options VIRTUALENV_OPTIONS[${#VIRTUALENV_OPTIONS[*]}]="-$option" ;; * ) # virtualenv long options VIRTUALENV_OPTIONS[${#VIRTUALENV_OPTIONS[*]}]="--$option" ;; esac done # The first argument contains the source version to create virtualenv. VERSION_NAME="${ARGUMENTS[0]}" [ -n "$VERSION_NAME" ] || usage 1 if [ -z "$TMPDIR" ]; then TMP="/tmp" else TMP="${TMPDIR%/}" fi SEED="$(date "+%Y%m%d%H%M%S").$$" UPGRADE_PATH="${TMP}/pyenv-virtualenv.${SEED}" UPGRADE_LIST="${TMP}/pyenv-virtualenv.${SEED}.txt" PYTHON_BIN="$(PYENV_VERSION="${VERSION_NAME}" pyenv-which python)" if [ ! -x "${PYTHON_BIN}" ]; then echo "pyenv-virtualenv: could not find python executable: ${PYTHON_BIN}" 1>&2 exit 1 fi # find canonical name of python executable. # virtualenv will create "bin/python" executable as same name as its bootstraped python. if [ -L "${PYTHON_BIN}" ]; then while [ -L "${PYTHON_BIN}" ]; do # retrieve symlinks PYTHON_BIN="$(dirname "${PYTHON_BIN}")/$(resolve_link "${PYTHON_BIN}")" done else # python 2.6 and older don't have "bin/python" as symlink. # so we must traverse files like "bin/python*" to obtain canonical name. for python in ${PYENV_ROOT}/versions/${VERSION_NAME}/bin/python*; do if ( basename "$python" | grep '^python[0-9][0-9]*\.[0-9][0-9]*$' && cmp "$PYTHON_BIN" "$python" ) >/dev/null; then PYTHON_BIN="${python}" break fi done fi VIRTUALENV_NAME="${ARGUMENTS[1]##*/}" VIRTUALENV_PATH="${PYENV_ROOT}/versions/${VIRTUALENV_NAME}" VIRTUALENV_PYTHON_BIN="${VIRTUALENV_PATH}/bin/python" VIRTUALENV_PIP_BIN="${VIRTUALENV_PATH}/bin/pip" # Define `before_virtualenv` and `after_virtualenv` functions that allow # plugin hooks to register a string of code for execution before or # after the installation process. declare -a before_hooks after_hooks before_virtualenv() { local hook="$1" before_hooks["${#before_hooks[@]}"]="$hook" } after_virtualenv() { local hook="$1" after_hooks["${#after_hooks[@]}"]="$hook" } # Load plugin hooks. for script in $(pyenv-hooks virtualenv); do source "$script" done [ -d "${VIRTUALENV_PATH}" ] && PREFIX_EXISTS=1 # If the virtualenv exists, prompt for confirmation unless # the --force option was specified. if [ -d "${VIRTUALENV_PATH}/bin" ]; then if [ -z "$FORCE" ]; then echo "pyenv: ${VIRTUALENV_PATH} already exists" 1>&2 read -p "continue with installation? (y/N) " case "$REPLY" in y* | Y* ) ;; * ) exit 1 ;; esac fi if [ -n "$UPGRADE" ]; then if [ -x "${VIRTUALENV_PIP_BIN}" ]; then "${VIRTUALENV_PIP_BIN}" freeze > "${UPGRADE_LIST}" mv -f "${VIRTUALENV_PATH}" "${UPGRADE_PATH}" else echo "pyenv: pip is not installed in ${VIRTUALENV_PATH}" 1>&2 exit 1 fi fi fi # Execute `before_virtualenv` hooks. for hook in "${before_hooks[@]}"; do eval "$hook"; done # Plan cleanup on unsuccessful installation. cleanup() { [ -z "${PREFIX_EXISTS}" ] && rm -rf "$VIRTUALENV_PATH" } trap cleanup SIGINT # Invoke virtualenv and record exit status in $STATUS. STATUS=0 # virtualenv may download distribute/setuptools in current directory. # change to cache directory to reuse them between invocation. mkdir -p "${PYENV_VIRTUALENV_CACHE_PATH}" cd "${PYENV_VIRTUALENV_CACHE_PATH}" "${PYTHON_BIN}" "${VIRTUALENV}" "${VIRTUALENV_OPTIONS[@]}" "${VIRTUALENV_PATH}" || STATUS=$? # create symlink of `python' bound for actual executable if [ ! -f "$VIRTUALENV_PYTHON_BIN" ]; then if [ -f "${VIRTUALENV_PATH}/bin/$(basename "${PYTHON_BIN}")" ]; then ( cd "${VIRTUALENV_PATH}/bin" && ln -fs "$(basename "${PYTHON_BIN}")" python ) fi fi ## Migrate previously installed packages from requirements.txt if [ -n "$UPGRADE" ]; then UPGRADE_STATUS=0 if [ -x "${VIRTUALENV_PIP_BIN}" ]; then "${VIRTUALENV_PIP_BIN}" install --requirement "${UPGRADE_LIST}" || UPGRADE_STATUS=$? else echo "pyenv: pip is not installed in ${VIRTUALENV_PATH}" 1>&2 UPGRADE_STATUS=1 fi if [ "$UPGRADE_STATUS" == "0" ]; then rm -f "${UPGRADE_LIST}" rm -fr "${UPGRADE_PATH}" else { echo echo "UPGRADE FAILED" echo echo "Inspect or clean up the original tree at ${UPGRADE_PATH}" echo echo "Package list:" cat "${UPGRADE_LIST}" } 1>&2 STATUS="$UPGRADE_STATUS" fi fi # Execute `after_virtualenv` hooks for hook in "${after_hooks[@]}"; do eval "$hook"; done # Run `pyenv-rehash` after a successful installation. if [ "$STATUS" == "0" ]; then pyenv-rehash else cleanup fi exit "$STATUS"