@@ -86,7 +86,7 @@ set_terminal_colors() {
86 _Red='\e[0;31m'
86 _Red='\e[0;31m'
87 _Green='\e[0;32m'
87 _Green='\e[0;32m'
88 _Yellow='\e[0;33m'
88 _Yellow='\e[0;33m'
89− _Blue='\e[0;34m'
89+ _Blue='\e[0;94m'
90 _Violet='\e[0;35m'
90 _Violet='\e[0;35m'
91 _Cyan='\e[0;36m'
91 _Cyan='\e[0;36m'
@@ -95,12 +95,12 @@ set_terminal_colors() {
95 _BRed='\e[1;31m'
95 _BRed='\e[1;31m'
96 _BGreen='\e[1;32m'
96 _BGreen='\e[1;32m'
97 _BYellow='\e[1;33m'
97 _BYellow='\e[1;33m'
98− _BBlue='\e[1;34m'
98+ _BBlue='\e[1;94m'
99 _BPurple='\e[1;35m'
99 _BPurple='\e[1;35m'
100 _BCyan='\e[1;36m'
100 _BCyan='\e[1;36m'
103−if [ ! -p /dev/stdout ]; then
103+if [ ! -p /dev/stdout ] && [ ! "$TERM" = 'dumb' ] && [ ! "$TERM" = 'unknown' ]; then
104 set_terminal_colors
104 set_terminal_colors
@@ -152,6 +152,12 @@ err_msg() { echo -e "${_BRed}ERROR:${_creset} $*" >&2; }
152warn_msg() { echo -e "${_BBlue}WARN:${_creset} $*" >&2; }
152warn_msg() { echo -e "${_BBlue}WARN:${_creset} $*" >&2; }
153info_msg() { echo -e "${_BYellow}INFO:${_creset} $*" >&2; }
153info_msg() { echo -e "${_BYellow}INFO:${_creset} $*" >&2; }
158+ echo -e "${_Blue}${tag:0:10}${_creset}$*"
155clean_stdin() {
161clean_stdin() {
156 if [[ $(uname -s) != 'Darwin' ]]; then
162 if [[ $(uname -s) != 'Darwin' ]]; then
157 while read -r -n1 -t 0.1; do : ; done
163 while read -r -n1 -t 0.1; do : ; done
@@ -496,6 +502,203 @@ service_is_available() {
496 return "$exit_val"
502 return "$exit_val"
509+PYTHON="${PYTHON:=python$PY}"
510+PY_ENV="${PY_ENV:=local/py${PY}}"
511+PY_ENV_BIN="${PY_ENV}/bin"
512+PY_ENV_REQ="${PY_ENV_REQ:=${REPO_ROOT}/requirements*.txt}"
514+# List of python packages (folders) or modules (files) installed by command:
516+PYOBJECTS="${PYOBJECTS:=.}"
518+# folder where the python distribution takes place
519+PYDIST="${PYDIST:=dist}"
521+# folder where the intermediate build files take place
522+PYBUILD="${PYBUILD:=build/py${PY}}"
524+# https://www.python.org/dev/peps/pep-0508/#extras
525+#PY_SETUP_EXTRAS='[develop,test]'
526+PY_SETUP_EXTRAS="${PY_SETUP_EXTRAS:=[develop,test]}"
528+PIP_BOILERPLATE=( pip wheel setuptools )
530+# shellcheck disable=SC2120
533+ # usage: pyenv [vtenv_opts ...]
535+ # vtenv_opts: see 'pip install --help'
537+ # Builds virtualenv with 'requirements*.txt' (PY_ENV_REQ) installed. The
538+ # virtualenv will be reused by validating sha256sum of the requirement
542+ sha256sum "${PYTHON}" \
547+ if ! pyenv.OK > /dev/null; then
548+ rm -f "${PY_ENV}/${PY_ENV_REQ}.sha256"
549+ pyenv.drop > /dev/null
550+ build_msg PYENV "[virtualenv] installing ${PY_ENV_REQ} into ${PY_ENV}"
552+ "${PYTHON}" -m venv "$@" "${PY_ENV}"
553+ "${PY_ENV_BIN}/python" -m pip install -U "${PIP_BOILERPLATE[@]}"
555+ for i in ${PY_ENV_REQ}; do
556+ pip_req=( "${pip_req[@]}" "-r" "$i" )
560+ [ "$VERBOSE" = "1" ] && set -x
561+ # shellcheck disable=SC2086
562+ "${PY_ENV_BIN}/python" -m pip install "${pip_req[@]}" \
563+ && sha256sum ${PY_ENV_REQ} > "${PY_ENV}/requirements.sha256"
572+ # probes if pyenv exists and runs the script from pyenv.check
574+ [ "$_pyenv_OK" == "OK" ] && return 0
576+ if [ ! -f "${PY_ENV_BIN}/python" ]; then
577+ build_msg PYENV "[virtualenv] missing ${PY_ENV_BIN}/python"
581+ if [ ! -f "${PY_ENV}/requirements.sha256" ] \
582+ || ! sha256sum --check --status <"${PY_ENV}/requirements.sha256" 2>/dev/null; then
583+ build_msg PYENV "[virtualenv] requirements.sha256 failed"
584+ sed 's/^/ [virtualenv] - /' <"${PY_ENV}/requirements.sha256"
589+ | "${PY_ENV_BIN}/python" 2>&1 \
590+ | prefix_stdout "${_Blue}PYENV ${_creset}[check] "
592+ local err=${PIPESTATUS[1]}
593+ if [ "$err" -ne "0" ]; then
594+ build_msg PYENV "[check] python test failed"
598+ build_msg PYENV "OK"
605+ build_msg PYENV "[virtualenv] drop ${PY_ENV}"
613+ # Prompts a python script with additional checks. Used by pyenv.OK to check
614+ # if virtualenv is ready to install python objects. This function should be
615+ # overwritten by the application script.
619+ for i in "${PIP_BOILERPLATE[@]}"; do
632+ py.clean > /dev/null
634+ if ! pyenv.install.OK > /dev/null; then
635+ build_msg PYENV "[install] ${PYOBJECTS}"
636+ if ! pyenv.OK >/dev/null; then
639+ for i in ${PYOBJECTS}; do
640+ build_msg PYENV "[install] pip install -e '$i${PY_SETUP_EXTRAS}'"
641+ "${PY_ENV_BIN}/python" -m pip install -e "$i${PY_SETUP_EXTRAS}"
650+ [ "$_pyenv_install_OK" == "OK" ] && return 0
655+ if [ "." = "${PYOBJECTS}" ]; then
656+ imp="import $(basename "$(pwd)")"
658+ # shellcheck disable=SC2086
659+ for i in ${PYOBJECTS}; do imp="$imp, $i"; done
660+ imp="import ${imp#,*} "
663+ [ "$VERBOSE" = "1" ] && set -x
664+ "${PY_ENV_BIN}/python" -c "import sys; sys.path.pop(0); $imp;" 2>/dev/null
668+ if [ "$err" -ne "0" ]; then
669+ build_msg PYENV "[install] python installation test failed"
673+ build_msg PYENV "[install] OK"
674+ _pyenv_install_OK="OK"
680+ build_msg PYENV "[uninstall] ${PYOBJECTS}"
682+ if [ "." = "${PYOBJECTS}" ]; then
683+ pyenv.cmd python setup.py develop --uninstall 2>&1 \
684+ | prefix_stdout "${_Blue}PYENV ${_creset}[pyenv.uninstall] "
686+ pyenv.cmd python -m pip uninstall --yes ${PYOBJECTS} 2>&1 \
687+ | prefix_stdout "${_Blue}PYENV ${_creset}[pyenv.uninstall] "
695+ # shellcheck source=/dev/null
696+ source "${PY_ENV_BIN}/activate"
697+ [ "$VERBOSE" = "1" ] && set -x
@@ -1250,7 +1453,7 @@ pkg_install() {
1251 # shellcheck disable=SC2068
1454 # shellcheck disable=SC2068
1252 yum install -y $@
1455 yum install -y $@
@@ -1382,6 +1585,10 @@ LXC_ENV_FOLDER=
1382if in_container; then
1585if in_container; then
1383 # shellcheck disable=SC2034
1586 # shellcheck disable=SC2034
1384 LXC_ENV_FOLDER="lxc-env/$(hostname)/"
1587 LXC_ENV_FOLDER="lxc-env/$(hostname)/"
1588+ PY_ENV="${LXC_ENV_FOLDER}${PY_ENV}"
1589+ PY_ENV_BIN="${LXC_ENV_FOLDER}${PY_ENV_BIN}"
1590+ PYDIST="${LXC_ENV_FOLDER}${PYDIST}"
1591+ PYBUILD="${LXC_ENV_FOLDER}${PYBUILD}"
1387lxc_init_container_env() {
1594lxc_init_container_env() {