summaryrefslogtreecommitdiff
path: root/utils/lib.sh
diff options
context:
space:
mode:
authorAlexandre Flament <alex@al-f.net>2021-04-24 07:14:35 +0200
committerGitHub <noreply@github.com>2021-04-24 07:14:35 +0200
commita7b9eca98a196052bed8168ff11d13456851b04f (patch)
treefcafca4b1c2b95f5e789275d7fd64b9d578c13fb /utils/lib.sh
parentfe064a5c390f7b85aa0e7b207b38129cca2ccc17 (diff)
parentabd423cbf8fd221c855eeabc5f3a61b7954e3961 (diff)
Merge pull request #8 from return42/manage-script
Replace Makefile boilerplate by shell scripts
Diffstat (limited to 'utils/lib.sh')
-rwxr-xr-xutils/lib.sh319
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() {