diff options
| author | Alexandre Flament <alex@al-f.net> | 2021-04-24 07:14:35 +0200 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2021-04-24 07:14:35 +0200 |
| commit | a7b9eca98a196052bed8168ff11d13456851b04f (patch) | |
| tree | fcafca4b1c2b95f5e789275d7fd64b9d578c13fb /utils/lib.sh | |
| parent | fe064a5c390f7b85aa0e7b207b38129cca2ccc17 (diff) | |
| parent | abd423cbf8fd221c855eeabc5f3a61b7954e3961 (diff) | |
Merge pull request #8 from return42/manage-script
Replace Makefile boilerplate by shell scripts
Diffstat (limited to 'utils/lib.sh')
| -rwxr-xr-x | utils/lib.sh | 319 |
1 files changed, 315 insertions, 4 deletions
diff --git a/utils/lib.sh b/utils/lib.sh index 8ae6bdd44..f2a879743 100755 --- a/utils/lib.sh +++ b/utils/lib.sh @@ -86,7 +86,7 @@ set_terminal_colors() { _Red='\e[0;31m' _Green='\e[0;32m' _Yellow='\e[0;33m' - _Blue='\e[0;34m' + _Blue='\e[0;94m' _Violet='\e[0;35m' _Cyan='\e[0;36m' @@ -95,12 +95,12 @@ set_terminal_colors() { _BRed='\e[1;31m' _BGreen='\e[1;32m' _BYellow='\e[1;33m' - _BBlue='\e[1;34m' + _BBlue='\e[1;94m' _BPurple='\e[1;35m' _BCyan='\e[1;36m' } -if [ ! -p /dev/stdout ]; then +if [ ! -p /dev/stdout ] && [ ! "$TERM" = 'dumb' ] && [ ! "$TERM" = 'unknown' ]; then set_terminal_colors fi @@ -152,6 +152,22 @@ err_msg() { echo -e "${_BRed}ERROR:${_creset} $*" >&2; } warn_msg() { echo -e "${_BBlue}WARN:${_creset} $*" >&2; } info_msg() { echo -e "${_BYellow}INFO:${_creset} $*" >&2; } +build_msg() { + local tag="$1 " + shift + echo -e "${_Blue}${tag:0:10}${_creset}$*" +} + +dump_return() { + + # Use this as last command in your function to prompt an ERROR message if + # the exit code is not zero. + + local err=$1 + [ "$err" -ne "0" ] && err_msg "${FUNCNAME[1]} exit with error ($err)" + return "$err" +} + clean_stdin() { if [[ $(uname -s) != 'Darwin' ]]; then while read -r -n1 -t 0.1; do : ; done @@ -496,6 +512,295 @@ service_is_available() { return "$exit_val" } +# python +# ------ + +PY="${PY:=3}" +PYTHON="${PYTHON:=python$PY}" +PY_ENV="${PY_ENV:=local/py${PY}}" +PY_ENV_BIN="${PY_ENV}/bin" +PY_ENV_REQ="${PY_ENV_REQ:=${REPO_ROOT}/requirements*.txt}" + +# List of python packages (folders) or modules (files) installed by command: +# pyenv.install +PYOBJECTS="${PYOBJECTS:=.}" + +# folder where the python distribution takes place +PYDIST="${PYDIST:=dist}" + +# folder where the intermediate build files take place +PYBUILD="${PYBUILD:=build/py${PY}}" + +# https://www.python.org/dev/peps/pep-0508/#extras +#PY_SETUP_EXTRAS='[develop,test]' +PY_SETUP_EXTRAS="${PY_SETUP_EXTRAS:=[develop,test]}" + +PIP_BOILERPLATE=( pip wheel setuptools ) + +# shellcheck disable=SC2120 +pyenv() { + + # usage: pyenv [vtenv_opts ...] + # + # vtenv_opts: see 'pip install --help' + # + # Builds virtualenv with 'requirements*.txt' (PY_ENV_REQ) installed. The + # virtualenv will be reused by validating sha256sum of the requirement + # files. + + required_commands \ + sha256sum "${PYTHON}" \ + || exit + + local pip_req=() + + if ! pyenv.OK > /dev/null; then + rm -f "${PY_ENV}/${PY_ENV_REQ}.sha256" + pyenv.drop > /dev/null + build_msg PYENV "[virtualenv] installing ${PY_ENV_REQ} into ${PY_ENV}" + + "${PYTHON}" -m venv "$@" "${PY_ENV}" + "${PY_ENV_BIN}/python" -m pip install -U "${PIP_BOILERPLATE[@]}" + + for i in ${PY_ENV_REQ}; do + pip_req=( "${pip_req[@]}" "-r" "$i" ) + done + + ( + [ "$VERBOSE" = "1" ] && set -x + # shellcheck disable=SC2086 + "${PY_ENV_BIN}/python" -m pip install "${pip_req[@]}" \ + && sha256sum ${PY_ENV_REQ} > "${PY_ENV}/requirements.sha256" + ) + fi + pyenv.OK +} + +_pyenv_OK='' +pyenv.OK() { + + # probes if pyenv exists and runs the script from pyenv.check + + [ "$_pyenv_OK" == "OK" ] && return 0 + + if [ ! -f "${PY_ENV_BIN}/python" ]; then + build_msg PYENV "[virtualenv] missing ${PY_ENV_BIN}/python" + return 1 + fi + + if [ ! -f "${PY_ENV}/requirements.sha256" ] \ + || ! sha256sum --check --status <"${PY_ENV}/requirements.sha256" 2>/dev/null; then + build_msg PYENV "[virtualenv] requirements.sha256 failed" + sed 's/^/ [virtualenv] - /' <"${PY_ENV}/requirements.sha256" + return 1 + fi + + pyenv.check \ + | "${PY_ENV_BIN}/python" 2>&1 \ + | prefix_stdout "${_Blue}PYENV ${_creset}[check] " + + local err=${PIPESTATUS[1]} + if [ "$err" -ne "0" ]; then + build_msg PYENV "[check] python test failed" + return "$err" + fi + + build_msg PYENV "OK" + _pyenv_OK="OK" + return 0 +} + +pyenv.drop() { + + build_msg PYENV "[virtualenv] drop ${PY_ENV}" + rm -rf "${PY_ENV}" + _pyenv_OK='' + +} + +pyenv.check() { + + # Prompts a python script with additional checks. Used by pyenv.OK to check + # if virtualenv is ready to install python objects. This function should be + # overwritten by the application script. + + local imp="" + + for i in "${PIP_BOILERPLATE[@]}"; do + imp="$imp, $i" + done + + cat <<EOF +import ${imp#,*} + +EOF +} + +pyenv.install() { + + if ! pyenv.OK; then + py.clean > /dev/null + fi + if ! pyenv.install.OK > /dev/null; then + build_msg PYENV "[install] ${PYOBJECTS}" + if ! pyenv.OK >/dev/null; then + pyenv + fi + for i in ${PYOBJECTS}; do + build_msg PYENV "[install] pip install -e '$i${PY_SETUP_EXTRAS}'" + "${PY_ENV_BIN}/python" -m pip install -e "$i${PY_SETUP_EXTRAS}" + done + fi + pyenv.install.OK +} + +_pyenv_install_OK='' +pyenv.install.OK() { + + [ "$_pyenv_install_OK" == "OK" ] && return 0 + + local imp="" + local err="" + + if [ "." = "${PYOBJECTS}" ]; then + imp="import $(basename "$(pwd)")" + else + # shellcheck disable=SC2086 + for i in ${PYOBJECTS}; do imp="$imp, $i"; done + imp="import ${imp#,*} " + fi + ( + [ "$VERBOSE" = "1" ] && set -x + "${PY_ENV_BIN}/python" -c "import sys; sys.path.pop(0); $imp;" 2>/dev/null + ) + + err=$? + if [ "$err" -ne "0" ]; then + build_msg PYENV "[install] python installation test failed" + return "$err" + fi + + build_msg PYENV "[install] OK" + _pyenv_install_OK="OK" + return 0 +} + +pyenv.uninstall() { + + build_msg PYENV "[uninstall] ${PYOBJECTS}" + + if [ "." = "${PYOBJECTS}" ]; then + pyenv.cmd python setup.py develop --uninstall 2>&1 \ + | prefix_stdout "${_Blue}PYENV ${_creset}[pyenv.uninstall] " + else + pyenv.cmd python -m pip uninstall --yes ${PYOBJECTS} 2>&1 \ + | prefix_stdout "${_Blue}PYENV ${_creset}[pyenv.uninstall] " + fi +} + + +pyenv.cmd() { + pyenv.install + ( set -e + # shellcheck source=/dev/null + source "${PY_ENV_BIN}/activate" + [ "$VERBOSE" = "1" ] && set -x + "$@" + ) +} + +# Sphinx doc +# ---------- + +GH_PAGES="build/gh-pages" +DOCS_DIST="${DOCS_DIST:=dist/docs}" +DOCS_BUILD="${DOCS_BUILD:=build/docs}" + +docs.html() { + build_msg SPHINX "HTML ./docs --> file://$(readlink -e "$(pwd)/$DOCS_DIST")" + pyenv.install + docs.prebuild + # shellcheck disable=SC2086 + PATH="${PY_ENV_BIN}:${PATH}" pyenv.cmd sphinx-build \ + ${SPHINX_VERBOSE} ${SPHINXOPTS} \ + -b html -c ./docs -d "${DOCS_BUILD}/.doctrees" ./docs "${DOCS_DIST}" + dump_return $? +} + +docs.live() { + build_msg SPHINX "autobuild ./docs --> file://$(readlink -e "$(pwd)/$DOCS_DIST")" + pyenv.install + docs.prebuild + # shellcheck disable=SC2086 + PATH="${PY_ENV_BIN}:${PATH}" pyenv.cmd sphinx-autobuild \ + ${SPHINX_VERBOSE} ${SPHINXOPTS} --open-browser --host 0.0.0.0 \ + -b html -c ./docs -d "${DOCS_BUILD}/.doctrees" ./docs "${DOCS_DIST}" + dump_return $? +} + +docs.clean() { + build_msg CLEAN "docs -- ${DOCS_BUILD} ${DOCS_DIST}" + # shellcheck disable=SC2115 + rm -rf "${GH_PAGES}" "${DOCS_BUILD}" "${DOCS_DIST}" + dump_return $? +} + +docs.prebuild() { + # Dummy function to run some actions before sphinx-doc build gets started. + # This finction needs to be overwritten by the application script. + true + dump_return $? +} + +# shellcheck disable=SC2155 +docs.gh-pages() { + + # The commit history in the gh-pages branch makes no sense, the history only + # inflates the repository unnecessarily. Therefore a *new orphan* branch + # is created each time we deploy on the gh-pages branch. + + docs.clean + docs.prebuild + docs.html + + [ "$VERBOSE" = "1" ] && set -x + local head="$(git rev-parse HEAD)" + local branch="$(git name-rev --name-only HEAD)" + local remote="$(git config branch."${branch}".remote)" + local remote_url="$(git config remote."${remote}".url)" + + build_msg GH-PAGES "prepare folder: ${GH_PAGES}" + build_msg GH-PAGES "remote of the gh-pages branch: ${remote} / ${remote_url}" + build_msg GH-PAGES "current branch: ${branch}" + + # prepare the *orphan* gh-pages working tree + ( + git worktree remove -f "${GH_PAGES}" + git branch -D gh-pages + ) &> /dev/null || true + git worktree add --no-checkout "${GH_PAGES}" "${remote}/master" + + pushd "${GH_PAGES}" &> /dev/null + git checkout --orphan gh-pages + git rm -rfq . + popd &> /dev/null + + cp -r "${DOCS_DIST}"/* "${GH_PAGES}"/ + touch "${GH_PAGES}/.nojekyll" + cat > "${GH_PAGES}/404.html" <<EOF +<html><head><META http-equiv='refresh' content='0;URL=index.html'></head></html> +EOF + + pushd "${GH_PAGES}" &> /dev/null + git add --all . + git commit -q -m "gh-pages build from: ${branch}@${head} (${remote_url})" + git push -f "${remote}" gh-pages + popd &> /dev/null + + set +x + build_msg GH-PAGES "deployed" +} + # golang # ------ @@ -1250,7 +1555,7 @@ pkg_install() { centos) # shellcheck disable=SC2068 yum install -y $@ - ;; + ;; esac } @@ -1382,6 +1687,12 @@ LXC_ENV_FOLDER= if in_container; then # shellcheck disable=SC2034 LXC_ENV_FOLDER="lxc-env/$(hostname)/" + PY_ENV="${LXC_ENV_FOLDER}${PY_ENV}" + PY_ENV_BIN="${LXC_ENV_FOLDER}${PY_ENV_BIN}" + PYDIST="${LXC_ENV_FOLDER}${PYDIST}" + PYBUILD="${LXC_ENV_FOLDER}${PYBUILD}" + DOCS_DIST="${LXC_ENV_FOLDER}${DOCS_DIST}" + DOCS_BUILD="${LXC_ENV_FOLDER}${DOCS_BUILD}" fi lxc_init_container_env() { |