inc/python
490430db
 #!/bin/bash
 #
 # **inc/python** - Python-related functions
 #
 # Support for pip/setuptools interfaces and virtual environments
 #
 # External functions used:
 # - GetOSVersion
 # - is_fedora
 # - is_suse
 # - safe_chown
 
 # Save trace setting
 INC_PY_TRACE=$(set +o | grep xtrace)
 set +o xtrace
 
 
8c2ce6ea
 # Global Config Variables
 
5509ed57
 # PROJECT_VENV contains the name of the virtual environment for each
8c2ce6ea
 # project.  A null value installs to the system Python directories.
 declare -A PROJECT_VENV
 
 
490430db
 # Python Functions
 # ================
 
 # Get the path to the pip command.
 # get_pip_command
 function get_pip_command {
ddc3839b
     local version="$1"
     # NOTE(dhellmann): I don't know if we actually get a pip3.4-python
     # under any circumstances.
     which pip${version} || which pip${version}-python
490430db
 
     if [ $? -ne 0 ]; then
ddc3839b
         die $LINENO "Unable to find pip${version}; cannot continue"
490430db
     fi
 }
 
5509ed57
 # Get the path to the directory where python executables are installed.
490430db
 # get_python_exec_prefix
 function get_python_exec_prefix {
433a9b10
     local xtrace
     xtrace=$(set +o | grep xtrace)
2b564763
     set +o xtrace
     if [[ -z "$os_PACKAGE" ]]; then
         GetOSVersion
     fi
     $xtrace
 
490430db
     if is_fedora || is_suse; then
         echo "/usr/bin"
     else
         echo "/usr/local/bin"
     fi
 }
 
60996b1b
 # Wrapper for ``pip install`` that only installs versions of libraries
 # from the global-requirements specification.
 #
 # Uses globals ``REQUIREMENTS_DIR``
 #
 # pip_install_gr packagename
 function pip_install_gr {
     local name=$1
ada886dd
     local clean_name
     clean_name=$(get_from_global_requirements $name)
60996b1b
     pip_install $clean_name
 }
 
ddc3839b
 # Determine the python versions supported by a package
 function get_python_versions_for_package {
     local name=$1
     cd $name && python setup.py --classifiers \
         | grep 'Language' | cut -f5 -d: | grep '\.' | tr '\n' ' '
 }
 
490430db
 # Wrapper for ``pip install`` to set cache and proxy environment variables
41d6f858
 # Uses globals ``OFFLINE``, ``PIP_VIRTUAL_ENV``,
635a5ba9
 # ``PIP_UPGRADE``, ``TRACK_DEPENDS``, ``*_proxy``,
490430db
 # pip_install package [package ...]
 function pip_install {
e208d060
     local xtrace result
433a9b10
     xtrace=$(set +o | grep xtrace)
490430db
     set +o xtrace
ebdd9ac5
     local upgrade=""
490430db
     local offline=${OFFLINE:-False}
     if [[ "$offline" == "True" || -z "$@" ]]; then
         $xtrace
         return
     fi
 
cb658fab
     time_start "pip_install"
 
ebdd9ac5
     PIP_UPGRADE=$(trueorfalse False PIP_UPGRADE)
     if [[ "$PIP_UPGRADE" = "True" ]] ; then
         upgrade="--upgrade"
     fi
 
490430db
     if [[ -z "$os_PACKAGE" ]]; then
         GetOSVersion
     fi
     if [[ $TRACK_DEPENDS = True && ! "$@" =~ virtualenv ]]; then
         # TRACK_DEPENDS=True installation creates a circular dependency when
5509ed57
         # we attempt to install virtualenv into a virtualenv, so we must global
490430db
         # that installation.
         source $DEST/.venv/bin/activate
         local cmd_pip=$DEST/.venv/bin/pip
         local sudo_pip="env"
     else
2b564763
         if [[ -n ${PIP_VIRTUAL_ENV:=} && -d ${PIP_VIRTUAL_ENV} ]]; then
             local cmd_pip=$PIP_VIRTUAL_ENV/bin/pip
             local sudo_pip="env"
         else
ada886dd
             local cmd_pip
ddc3839b
             cmd_pip=$(get_pip_command $PYTHON2_VERSION)
2b564763
             local sudo_pip="sudo -H"
ddc3839b
             if python3_enabled; then
                 # Look at the package classifiers to find the python
                 # versions supported, and if we find the version of
                 # python3 we've been told to use, use that instead of the
                 # default pip
                 local package_dir=${!#}
                 local python_versions
                 if [[ -d "$package_dir" ]]; then
                     python_versions=$(get_python_versions_for_package $package_dir)
                     if [[ $python_versions =~ $PYTHON3_VERSION ]]; then
                         cmd_pip=$(get_pip_command $PYTHON3_VERSION)
                     fi
                 fi
             fi
2b564763
         fi
490430db
     fi
 
635a5ba9
     cmd_pip="$cmd_pip install"
05aa3846
     # Always apply constraints
     cmd_pip="$cmd_pip -c $REQUIREMENTS_DIR/upper-constraints.txt"
635a5ba9
 
ddc3839b
     # FIXME(dhellmann): Need to force multiple versions of pip for
     # packages like setuptools?
ada886dd
     local pip_version
     pip_version=$(python -c "import pip; \
490430db
                         print(pip.__version__.strip('.')[0])")
     if (( pip_version<6 )); then
         die $LINENO "Currently installed pip version ${pip_version} does not" \
             "meet minimum requirements (>=6)."
     fi
 
     $xtrace
     $sudo_pip \
6a83c423
         http_proxy="${http_proxy:-}" \
         https_proxy="${https_proxy:-}" \
         no_proxy="${no_proxy:-}" \
cd8824ac
         PIP_FIND_LINKS=$PIP_FIND_LINKS \
635a5ba9
         $cmd_pip $upgrade \
490430db
         $@
e208d060
     result=$?
490430db
 
eeb7bda5
     # Also install test requirements
713fd2f6
     local test_req="${!#}/test-requirements.txt"
e208d060
     if [[ $result == 0 ]] && [[ -e "$test_req" ]]; then
eeb7bda5
         echo "Installing test-requirements for $test_req"
         $sudo_pip \
             http_proxy=${http_proxy:-} \
             https_proxy=${https_proxy:-} \
             no_proxy=${no_proxy:-} \
             PIP_FIND_LINKS=$PIP_FIND_LINKS \
635a5ba9
             $cmd_pip $upgrade \
eeb7bda5
             -r $test_req
e208d060
         result=$?
490430db
     fi
cb658fab
 
     time_stop "pip_install"
e208d060
     return $result
490430db
 }
 
d5ac7852
 # get version of a package from global requirements file
 # get_from_global_requirements <package>
 function get_from_global_requirements {
     local package=$1
ada886dd
     local required_pkg
     required_pkg=$(grep -i -h ^${package} $REQUIREMENTS_DIR/global-requirements.txt | cut -d\# -f1)
d5ac7852
     if [[ $required_pkg == ""  ]]; then
         die $LINENO "Can't find package $package in requirements"
     fi
     echo $required_pkg
 }
 
490430db
 # should we use this library from their git repo, or should we let it
 # get pulled in via pip dependencies.
 function use_library_from_git {
     local name=$1
     local enabled=1
46f8cb7f
     [[ ${LIBS_FROM_GIT} = 'ALL' ]] || [[ ,${LIBS_FROM_GIT}, =~ ,${name}, ]] && enabled=0
490430db
     return $enabled
 }
 
c71973eb
 # determine if a package was installed from git
 function lib_installed_from_git {
     local name=$1
     pip freeze 2>/dev/null | grep -- "$name" | grep -q -- '-e git'
 }
 
 # check that everything that's in LIBS_FROM_GIT was actually installed
 # correctly, this helps double check issues with library fat fingering.
 function check_libs_from_git {
     local lib=""
     local not_installed=""
     for lib in $(echo ${LIBS_FROM_GIT} | tr "," " "); do
         if ! lib_installed_from_git "$lib"; then
             not_installed+=" $lib"
         fi
     done
     # if anything is not installed, say what it is.
     if [[ -n "$not_installed" ]]; then
         die $LINENO "The following LIBS_FROM_GIT were not installed correct: $not_installed"
     fi
 }
 
490430db
 # setup a library by name. If we are trying to use the library from
 # git, we'll do a git based install, otherwise we'll punt and the
 # library should be installed by a requirements pull from another
 # project.
 function setup_lib {
     local name=$1
     local dir=${GITDIR[$name]}
     setup_install $dir
 }
 
5509ed57
 # setup a library by name in editable mode. If we are trying to use
490430db
 # the library from git, we'll do a git based install, otherwise we'll
 # punt and the library should be installed by a requirements pull from
 # another project.
 #
 # use this for non namespaced libraries
 function setup_dev_lib {
     local name=$1
     local dir=${GITDIR[$name]}
     setup_develop $dir
 }
 
 # this should be used if you want to install globally, all libraries should
 # use this, especially *oslo* ones
0842b814
 #
 # setup_install project_dir [extras]
 # project_dir: directory of project repo (e.g., /opt/stack/keystone)
 # extras: comma-separated list of optional dependencies to install
 #         (e.g., ldap,memcache).
 #         See http://docs.openstack.org/developer/pbr/#extra-requirements
 # The command is like "pip install <project_dir>[<extras>]"
490430db
 function setup_install {
     local project_dir=$1
0842b814
     local extras=$2
     _setup_package_with_constraints_edit $project_dir "" $extras
490430db
 }
 
 # this should be used for projects which run services, like all services
0842b814
 #
 # setup_develop project_dir [extras]
 # project_dir: directory of project repo (e.g., /opt/stack/keystone)
 # extras: comma-separated list of optional dependencies to install
 #         (e.g., ldap,memcache).
 #         See http://docs.openstack.org/developer/pbr/#extra-requirements
 # The command is like "pip install -e <project_dir>[<extras>]"
490430db
 function setup_develop {
     local project_dir=$1
0842b814
     local extras=$2
     _setup_package_with_constraints_edit $project_dir -e $extras
490430db
 }
 
 # determine if a project as specified by directory is in
 # projects.txt. This will not be an exact match because we throw away
 # the namespacing when we clone, but it should be good enough in all
 # practical ways.
 function is_in_projects_txt {
     local project_dir=$1
ada886dd
     local project_name
     project_name=$(basename $project_dir)
2ba4a721
     grep -q "/$project_name\$" $REQUIREMENTS_DIR/projects.txt
490430db
 }
 
 # ``pip install -e`` the package, which processes the dependencies
 # using pip before running `setup.py develop`
 #
05aa3846
 # Updates the constraints from REQUIREMENTS_DIR to reflect the
 # future installed state of this package. This ensures when we
 # install this package we get the from source version.
490430db
 #
05aa3846
 # Uses globals ``REQUIREMENTS_DIR``
0842b814
 # _setup_package_with_constraints_edit project_dir flags [extras]
 # project_dir: directory of project repo (e.g., /opt/stack/keystone)
 # flags: pip CLI options/flags
 # extras: comma-separated list of optional dependencies to install
 #         (e.g., ldap,memcache).
 #         See http://docs.openstack.org/developer/pbr/#extra-requirements
 # The command is like "pip install <flags> <project_dir>[<extras>]"
 function _setup_package_with_constraints_edit {
490430db
     local project_dir=$1
     local flags=$2
0842b814
     local extras=$3
490430db
 
c8c1c615
     # Normalize the directory name to avoid
     # "installation from path or url cannot be constrained to a version"
     # error.
     # REVISIT(yamamoto): Remove this when fixed in pip.
     # https://github.com/pypa/pip/pull/3582
     project_dir=$(cd $project_dir && pwd)
 
635a5ba9
     if [ -n "$REQUIREMENTS_DIR" ]; then
         # Constrain this package to this project directory from here on out.
ada886dd
         local name
         name=$(awk '/^name.*=/ {print $3}' $project_dir/setup.cfg)
7c838616
         $REQUIREMENTS_DIR/.venv/bin/edit-constraints \
             $REQUIREMENTS_DIR/upper-constraints.txt -- $name \
             "$flags file://$project_dir#egg=$name"
635a5ba9
     fi
 
0842b814
     setup_package $project_dir "$flags" $extras
490430db
 
 }
 
 # ``pip install -e`` the package, which processes the dependencies
 # using pip before running `setup.py develop`
0842b814
 #
490430db
 # Uses globals ``STACK_USER``
0842b814
 # setup_package project_dir [flags] [extras]
 # project_dir: directory of project repo (e.g., /opt/stack/keystone)
 # flags: pip CLI options/flags
 # extras: comma-separated list of optional dependencies to install
 #         (e.g., ldap,memcache).
 #         See http://docs.openstack.org/developer/pbr/#extra-requirements
 # The command is like "pip install <flags> <project_dir>[<extras>]"
490430db
 function setup_package {
     local project_dir=$1
     local flags=$2
0842b814
     local extras=$3
 
     # if the flags variable exists, and it doesn't look like a flag,
     # assume it's actually the extras list.
     if [[ -n "$flags" && -z "$extras" && ! "$flags" =~ ^-.* ]]; then
         extras=$flags
         flags=""
     fi
 
     if [[ ! -z "$extras" ]]; then
         extras="[$extras]"
     fi
490430db
 
0842b814
     pip_install $flags "$project_dir$extras"
490430db
     # ensure that further actions can do things like setup.py sdist
     if [[ "$flags" == "-e" ]]; then
         safe_chown -R $STACK_USER $1/*.egg-info
     fi
 }
 
ddc3839b
 # Report whether python 3 should be used
 function python3_enabled {
     if [[ $USE_PYTHON3 == "True" ]]; then
         return 0
     else
         return 1
     fi
 }
 
 # Install python3 packages
 function install_python3 {
     if is_ubuntu; then
         apt_get install python3.4 python3.4-dev
     fi
 }
490430db
 
 # Restore xtrace
 $INC_PY_TRACE
 
 # Local variables:
 # mode: shell-script
 # End: