@@ -86,7 +86,7 @@ set_terminal_colors() {
8686 _Red='\e[0;31m'
8787 _Green='\e[0;32m'
8888 _Yellow='\e[0;33m'
89− _Blue='\e[0;34m'
89+ _Blue='\e[0;94m'
9090 _Violet='\e[0;35m'
9191 _Cyan='\e[0;36m'
9292
@@ -95,12 +95,12 @@ set_terminal_colors() {
9595 _BRed='\e[1;31m'
9696 _BGreen='\e[1;32m'
9797 _BYellow='\e[1;33m'
98− _BBlue='\e[1;34m'
98+ _BBlue='\e[1;94m'
9999 _BPurple='\e[1;35m'
100100 _BCyan='\e[1;36m'
101101}
102102
103−if [ ! -p /dev/stdout ]; then
103+if [ ! -p /dev/stdout ] && [ ! "$TERM" = 'dumb' ] && [ ! "$TERM" = 'unknown' ]; then
104104 set_terminal_colors
105105fi
106106
@@ -152,6 +152,12 @@ err_msg() { echo -e "${_BRed}ERROR:${_creset} $*" >&2; }
152152warn_msg() { echo -e "${_BBlue}WARN:${_creset} $*" >&2; }
153153info_msg() { echo -e "${_BYellow}INFO:${_creset} $*" >&2; }
154154
155+build_msg() {
156+ local tag="$1 "
157+ shift
158+ echo -e "${_Blue}${tag:0:10}${_creset}$*"
159+}
160+
155161clean_stdin() {
156162 if [[ $(uname -s) != 'Darwin' ]]; then
157163 while read -r -n1 -t 0.1; do : ; done
@@ -496,6 +502,203 @@ service_is_available() {
496502 return "$exit_val"
497503}
498504
505+# python
506+# ------
507+
508+PY="${PY:=3}"
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}"
513+
514+# List of python packages (folders) or modules (files) installed by command:
515+# pyenv.install
516+PYOBJECTS="${PYOBJECTS:=.}"
517+
518+# folder where the python distribution takes place
519+PYDIST="${PYDIST:=dist}"
520+
521+# folder where the intermediate build files take place
522+PYBUILD="${PYBUILD:=build/py${PY}}"
523+
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]}"
527+
528+PIP_BOILERPLATE=( pip wheel setuptools )
529+
530+# shellcheck disable=SC2120
531+pyenv() {
532+
533+ # usage: pyenv [vtenv_opts ...]
534+ #
535+ # vtenv_opts: see 'pip install --help'
536+ #
537+ # Builds virtualenv with 'requirements*.txt' (PY_ENV_REQ) installed. The
538+ # virtualenv will be reused by validating sha256sum of the requirement
539+ # files.
540+
541+ required_commands \
542+ sha256sum "${PYTHON}" \
543+ || exit
544+
545+ local pip_req=()
546+
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}"
551+
552+ "${PYTHON}" -m venv "$@" "${PY_ENV}"
553+ "${PY_ENV_BIN}/python" -m pip install -U "${PIP_BOILERPLATE[@]}"
554+
555+ for i in ${PY_ENV_REQ}; do
556+ pip_req=( "${pip_req[@]}" "-r" "$i" )
557+ done
558+
559+ (
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"
564+ )
565+ fi
566+ pyenv.OK
567+}
568+
569+_pyenv_OK=''
570+pyenv.OK() {
571+
572+ # probes if pyenv exists and runs the script from pyenv.check
573+
574+ [ "$_pyenv_OK" == "OK" ] && return 0
575+
576+ if [ ! -f "${PY_ENV_BIN}/python" ]; then
577+ build_msg PYENV "[virtualenv] missing ${PY_ENV_BIN}/python"
578+ return 1
579+ fi
580+
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"
585+ return 1
586+ fi
587+
588+ pyenv.check \
589+ | "${PY_ENV_BIN}/python" 2>&1 \
590+ | prefix_stdout "${_Blue}PYENV ${_creset}[check] "
591+
592+ local err=${PIPESTATUS[1]}
593+ if [ "$err" -ne "0" ]; then
594+ build_msg PYENV "[check] python test failed"
595+ return "$err"
596+ fi
597+
598+ build_msg PYENV "OK"
599+ _pyenv_OK="OK"
600+ return 0
601+}
602+
603+pyenv.drop() {
604+
605+ build_msg PYENV "[virtualenv] drop ${PY_ENV}"
606+ rm -rf "${PY_ENV}"
607+ _pyenv_OK=''
608+
609+}
610+
611+pyenv.check() {
612+
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.
616+
617+ local imp=""
618+
619+ for i in "${PIP_BOILERPLATE[@]}"; do
620+ imp="$imp, $i"
621+ done
622+
623+ cat <<EOF
624+import ${imp#,*}
625+
626+EOF
627+}
628+
629+pyenv.install() {
630+
631+ if ! pyenv.OK; then
632+ py.clean > /dev/null
633+ fi
634+ if ! pyenv.install.OK > /dev/null; then
635+ build_msg PYENV "[install] ${PYOBJECTS}"
636+ if ! pyenv.OK >/dev/null; then
637+ pyenv
638+ fi
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}"
642+ done
643+ fi
644+ pyenv.install.OK
645+}
646+
647+_pyenv_install_OK=''
648+pyenv.install.OK() {
649+
650+ [ "$_pyenv_install_OK" == "OK" ] && return 0
651+
652+ local imp=""
653+ local err=""
654+
655+ if [ "." = "${PYOBJECTS}" ]; then
656+ imp="import $(basename "$(pwd)")"
657+ else
658+ # shellcheck disable=SC2086
659+ for i in ${PYOBJECTS}; do imp="$imp, $i"; done
660+ imp="import ${imp#,*} "
661+ fi
662+ (
663+ [ "$VERBOSE" = "1" ] && set -x
664+ "${PY_ENV_BIN}/python" -c "import sys; sys.path.pop(0); $imp;" 2>/dev/null
665+ )
666+
667+ err=$?
668+ if [ "$err" -ne "0" ]; then
669+ build_msg PYENV "[install] python installation test failed"
670+ return "$err"
671+ fi
672+
673+ build_msg PYENV "[install] OK"
674+ _pyenv_install_OK="OK"
675+ return 0
676+}
677+
678+pyenv.uninstall() {
679+
680+ build_msg PYENV "[uninstall] ${PYOBJECTS}"
681+
682+ if [ "." = "${PYOBJECTS}" ]; then
683+ pyenv.cmd python setup.py develop --uninstall 2>&1 \
684+ | prefix_stdout "${_Blue}PYENV ${_creset}[pyenv.uninstall] "
685+ else
686+ pyenv.cmd python -m pip uninstall --yes ${PYOBJECTS} 2>&1 \
687+ | prefix_stdout "${_Blue}PYENV ${_creset}[pyenv.uninstall] "
688+ fi
689+}
690+
691+
692+pyenv.cmd() {
693+ pyenv.install
694+ ( set -e
695+ # shellcheck source=/dev/null
696+ source "${PY_ENV_BIN}/activate"
697+ [ "$VERBOSE" = "1" ] && set -x
698+ "$@"
699+ )
700+}
701+
499702# golang
500703# ------
501704
@@ -1250,7 +1453,7 @@ pkg_install() {
12501453 centos)
12511454 # shellcheck disable=SC2068
12521455 yum install -y $@
1253− ;;
1456+ ;;
12541457 esac
12551458}
12561459
@@ -1382,6 +1585,10 @@ LXC_ENV_FOLDER=
13821585if in_container; then
13831586 # shellcheck disable=SC2034
13841587 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}"
13851592fi
13861593
13871594lxc_init_container_env() {