blob: eacb69b946213a6d041086cbdb3958127df33a6d [file] [log] [blame]
#!/bin/bash
## -----------------------------------------------------------------------
## -----------------------------------------------------------------------
# set -euo pipefail
## -----------------------------------------------------------------------
## Intent: Dislpay an error message then exit
## -----------------------------------------------------------------------
function error()
{
cat <<ERR
** -----------------------------------------------------------------------
** IAM: ${BASH_SOURCE[0]}
** IAM: ${FUNCNAME[1]} (LINENO:${BASH_LINENO[0]})
** ERR: $@
** -----------------------------------------------------------------------
ERR
exit 1
}
## -----------------------------------------------------------------------
## Intent: Dislpay an error message then exit
## -----------------------------------------------------------------------
function banner()
{
cat <<MSG
** -----------------------------------------------------------------------
** $@
** -----------------------------------------------------------------------
MSG
return
}
## -----------------------------------------------------------------------
## Intent: Debug function, display minimalist script debugging
## -----------------------------------------------------------------------
function signpost()
{
echo
echo "** SIGNPOST[${FUNCNAME[1]}] (LINENO:${BASH_LINENO[1]}): $@"
return
}
## -----------------------------------------------------------------------
## Intent: Identify system type
## https://stackoverflow.com/questions/394230/how-to-detect-the-os-from-a-bash-script
## -----------------------------------------------------------------------
function detect_arch()
{
local -n ref=$1; shift
ref=''
if [[ "$OSTYPE" == "linux-gnu"* ]]; then
ref='linux'
elif [[ "$OSTYPE" == "darwin"* ]]; then
ref='darwin'
elif [[ "$OSTYPE" == "freebsd"* ]]; then
ref='freebsd'
else
error "Deteted unhandled OS_TYPE=[$OS_TYPE]"
fi
return
}
## -----------------------------------------------------------------------
## Intent: Add more apt sources to install versioned packages
## -----------------------------------------------------------------------
function configure_apt()
{
## Perform once
[[ -v configure_apt_seen ]] && { return; }
declare -g -i configure_apt_seen=1
sandbox '[ENTER]'
sudo 'add-apt-repository' 'universe'
sudo apt update
sandbox '[LEAVE]'
return
}
## -----------------------------------------------------------------------
## Intent: Ubuntu: apt install packages for a versioned python interpreter
## -----------------------------------------------------------------------
function install_apt_packages()
{
local ver="$1"; shift
pkgs+=("libpython${ver}") # Shared Python runtime library (version ${ver})
pkgs+=("libpython${ver}-dev") # Header files and a static library for Python (v${ver})
pkgs+=("libpython${ver}-minimal") # Minimal subset of the Python language (version ${ver})
pkgs+=("python${ver}") # - Interactive high-level object-oriented language (version ${ver})
pkgs+=("python${ver}-dev") # - Header files and a static library for Python (v${ver})
pkgs+=("python${ver}-full") # - Python Interpreter with complete class library (version ${ver})
pkgs+=("python${ver}-venv") # - Interactive high-level object-oriented language (pyvenv binary, version ${ver})
pkgs+=('virtualenv') # Convenience wrapper for installation
sudo apt-get install -y "${pkgs[@]}"
return
}
## -----------------------------------------------------------------------
## Intent:
## o Install multiple interpreters for evaluation
## o Installer is non-destructive
## -----------------------------------------------------------------------
## [TODO]
## o Configure update-alternatives to simplify version switching.
## o Careful, modifying /usr/bin/python can break system dependencies
## -----------------------------------------------------------------------
function install_python()
{
local ver="$1"; shift
local path="/usr/bin/python${ver}"
echo "INSTALL PYTHON"
local arch=''
detect_arch arch
## Configure package manager to include versioned python packages.
case "$arch" in
'linux') configure_apt ;;
'darwin')
## Logic needed if installing with: brew, etc
error "Installer not yet configured for OSX (arch=[$arch])"
;;
*) error "Installer not yet configured for arch=[$arch]" ;;
esac
## -------------------------------
## If evaluating multiple versions
## -------------------------------
declare -a versions=()
# versions+=('3.8')
versions+=('3.10')
# versions+=('3.12')
# versions+=('3.13')
local version
for version in "${versions[@]}";
do
local path="/usr/bin/python${version}"
signpost "Interpreter: $(declare -p path)"
if [[ -e "$path" ]]; then
echo "[${FUNCNAME[0]}:SKIP] interpreter already installed [$path]"
continue
fi
case "$arch" in
'linux') install_apt_packages "$version" ;;
## Commands needed for brew, etc
'darwin')
error "Installer not yet configured for OSX (arch=[$arch])"
;;
*) error "Installer not yet configured for arch=[$arch]" ;;
esac
done
return
}
## -----------------------------------------------------------------------
## Intent: Install a versioned venv into a cloned sandbox
## -----------------------------------------------------------------------
## [NOTE] Early creation of {sandbox}/.venv will short circuit a makefile
## makefile target that normally installs venv.
## -----------------------------------------------------------------------
function install_sandbox_virtualenv()
{
local repo="$1"; shift
local ver="$1"; shift
local interpreter="/usr/bin/python${ver}"
signpost '[ENTER]'
pushd "$repo" >/dev/null || { error "pushd $repo failed"; }
readarray -t cmd < <(which virtualenv 2>/dev/null)
if [[ "${#cmd[@]}" -gt 0 ]] && [[ -x "${cmd[0]}" ]]; then
## Prefer tool:virtualenv for installation when available
banner "** Install python using tool virtualenv"
declare -a args=()
[[ ! -v ARGV_no_quiet ]] && { args+=('--quiet'); }
virtualenv "${args[@]}" -p "$interpreter" '.venv'
else
# fallback to dependency-less venv install
banner "** Install python using a versioned interpreter"
"$interpreter" -m venv '.venv'
fi
## -------------------------------------------------------
## 1) Configure default interpreter version
## 2) Upgrade core interpreter packages: pip, setuptools
## 3) Install per-repository packages : requirements.txt
## ------------------------------------------------------
## See Also:
## % make venv
## makefiles/virtualenv/include.mk
## -------------------------------------------------------
source .venv/bin/activate
declare -a pip_install=()
pip_install+=('pip' 'install' '--no-cache-dir')
[[ ! -v ARGV_no_quiet ]] && { pip_install+=('--quiet'); }
# Upgrade core interpreter packages: pip, setuptools
# pip --upgrade has chicken-n-egg problems
python -m "${pip_install[@]}" --upgrade pip
"${pip_install[@]}" --upgrade setuptools
## Install per-repository packages : requirements.txt
python -m "${pip_install[@]}" -r ./requirements.txt
popd >/dev/null || { error "pushd $repo failed"; }
signpost '[LEAVE]'
return
}
## -----------------------------------------------------------------------
## Intent:
## Determine if python interpreter is valid/current.
## No need for installation if the OS is current with recent packages
## -----------------------------------------------------------------------
function verify_python_version()
{
local want="$1"; shift
readarray -t version < <(/usr/bin/python3 --version)
case "${version[*]}" in
*"$want"*) local -i is_valid=1 ;;
esac
[[ -v is_valid ]] && { /bin/true; } || { /bin/false; }
return
}
## -----------------------------------------------------------------------
## Intent:
## o Clone a repository and create a user branch
## o Installer is non-destructive
## -----------------------------------------------------------------------
function clone_and_branch_repo()
{
local repo="$1"; shift
local path="$repo"
sandbox '[ENTER]'
if [[ -v ARGV_clean ]] && [[ -d "$path" ]]; then
echo "** Removing old sandbox: $repo"
rm -fr "$repo"
fi
## NOP if sandbox already checked out
if [[ -e "$path" ]]; then
echo "[${FUNCNAME[0]}:SKIP] repository already checked out"
return
fi
banner "Clone and branch repo: ${repo}"
local url="ssh://gerrit.opencord.org:29418/${repo}.git"
git clone "$url"
pushd "$repo" >/dev/null || { error "pushd $repo failed"; }
local branch="dev-${USER}"
banner "git checkout -b \"$branch\""
git checkout -b "$branch"
banner "git remote -v show"
git remote -v show
banner "git branch -a"
git branch -a
popd >/dev/null || { error "popd $repo failed"; }
sandbox '[LEAVE]'
return
}
## -----------------------------------------------------------------------
## Intent: Alter requirements.txt to use latest package versions
## -----------------------------------------------------------------------
function upgrade_packages()
{
local repo="$1"; shift
signpost '[ENTER]'
## NOP: Already modified
if ! grep '[=<>]' "$repo/requirements.txt"; then
return
fi
pushd "$repo" >/dev/null || { error "pushd $repo failed"; }
sed -i'' '\@zdw/robotframework-importresource@d' 'requirements.txt'
# -------------------------------------------------
# Disgusting but portable: prune version qualifiers
# Pip will install latest packages by name
# -------------------------------------------------
cut -d'=' -f1 requirements.txt \
| cut -d'<' -f1 \
| cut -d'>' -f1 \
> requirements.tmp
echo "** Use git checkout requirements.txt to revert changes"
mv requirements.tmp requirements.txt
popd >/dev/null || { error "popd $repo failed"; }
signpost '[LEAVE]'
return
}
## -----------------------------------------------------------------------
## Intent: If version strings
## -----------------------------------------------------------------------
function freeze_packages()
{
local repo="$1"; shift
signpost '[ENTER]'
pushd "$repo" >/dev/null || { error "pushd $repo failed"; }
source .venv/bin/activate
declare -a pip_freeze=()
pip_freeze+=('pip' 'freeze')
pip_freeze+=('--local') # local directory
pip_freeze+=('--requirement' 'requirements.txt') # existing order
"${pip_freeze[@]}" > 'requirements.txt.tmp'
mv 'requirements.txt.tmp' 'requirements.txt'
popd >/dev/null || { error "popd $repo failed"; }
signpost '[LEAVE]'
return
}
## -----------------------------------------------------------------------
## Intent: Display misc information about the installation, known problems
## and makefile targets used for building and testing.
## -----------------------------------------------------------------------
function show_readme()
{
local repo="$1"; shift
/bin/pwd
pushd "$repo" >/dev/null || { error "pushd $repo failed"; }
cat <<HOWTO
** -----------------------------------------------------------------------
** IAM: ${FUNCNAME[0]}
** PWD: $(/bin/pwd)
** -----------------------------------------------------------------------
% cd ${repo}
% source .venv/bin/activate
$(source .venv/bin/activate && python --version)
% make help # Display available targets
% make all
% make lint
% make pre-commit
% make build
% make test
HOWTO
readarray -t zdw < <(grep '/zdw/' requirements.txt)
cat <<PROBLEMS
** -----------------------------------------------------------------------
** Known Problems: early failures that require attention
** -----------------------------------------------------------------------
[requirements.txt]
o Comment out package /zdw/robotframework-import in equirements.txt.
o Try --upgrade-packages to install latest versions
[grpc]
o repo:voltha-protos and repo:voltha-system-tests both have
problems building packages.
o ERROR: Failed to build installable wheels for some pyproject.toml based projects (grpcio)
PROBLEMS
popd >/dev/null || { error "pushd $repo failed"; }
return
}
## -----------------------------------------------------------------------
## -----------------------------------------------------------------------
function usage()
{
cat <<EOHELP
Usage: $0
--repo [r] Repository name to checkout and configure.
--version [v] Install python virtualenv at version [v]
--gerrit Browse a list of VOLTHA repositories in the gerrit UI
--upgrade-pacakges Remove frozen version information from requirements.txt
--no-quiet Run commands in verbose mode (pip, virtualenv)
--verbose alias for --no-quiet
--clean Delete cloned sandbox prior to install
--help This message
EOHELP
cat <<EOHELP
[EXAMPLES]
% $0 --version 3.12 --repo voltha-system-tests
% $0 --version 3.12 --repo voltha-lib-go
EOHELP
return
}
##----------------##
##---] MAIN [---##
##----------------##
banner '[NOTE] If the status message [FIN] is displayed script ran successfully'
while [[ $# -gt 0 ]]; do
arg="$1"; shift
case "$arg" in
'--no-quiet') declare -g -i ARGV_no_quiet=1; ;;
'--verbose') declare -g -i ARGV_no_quiet=1; ;;
'--clean') declare -g -i ARGV_clean=1 ;;
'--upgr'*) declare -g ARGV_upgrade_packages=1 ;;
'--repo'*) declare -g ARGV_repo="$1"; shift ;;
'--ver'*) declare -g ARGV_version="$1"; shift ;;
'--help') usage; exit 0 ;;
'--gerrit')
"${BROWSER:-firefox}" 'https://gerrit.opencord.org/admin/repos/q/filter:voltha-'
exit 1
;;
'-'*) error "Detected unknown switch [$arg]" ;;
*) echo "[SKIP] unknown argument [$arg]" ;;
esac
done
[[ ! -v ARGV_version ]] && { error '--version is required (default: 2.13)'; }
[[ ! -v ARGV_repo ]] && { error '--repo is required'; }
## -----------------------------------------------------------------------
## NOP if system interpreter is the latest
## -----------------------------------------------------------------------
if verify_python_version "$ARGV_version"; then
readarray -t version < <(pyhon --version 2>/dev/null)
echo "** Python Version: ${version[*]}"
case "${version[*]}" in
# v3.13 is in pre-release.
# may be pre-installed for new distribution installations.
# If unavailabler sources are available for download but
# will require building the interpreter.
*'3.12'*|*'3.13'*)
banner \
"[SKIP] Python interpreter already installed (ver=${ARGV_version})" \
'[NOTE] This script is no longer needed, detected latest python version'
;;
*)
banner "[SKIP] Python interpreter already installed (ver=${ARGV_version})"
;;
esac
# [ELSE] we need to install
else
banner "Installing python version ${ARGV_version}"
## ------------------------------------------------------------
# package manager: Configure and install versioned interpreters
## ------------------------------------------------------------
install_python "${ARGV_version}" # OS packages
fi
mkdir -p sandbox # git clone into sandbox/ for easy cleanup
pushd 'sandbox' >/dev/null || { error 'pushd sandbox/ failed'; }
## Checkout VOLTHA code base
clone_and_branch_repo "$ARGV_repo"
echo
[[ -v ARGV_upgrade_packages ]] && { upgrade_packages "$ARGV_repo"; }
## Install upgraded python interpreter in the sandbox
install_sandbox_virtualenv "${ARGV_repo}" "${ARGV_version}" # sandbox/virtualenv/
freeze_packages "$ARGV_repo" # capture all upgraded package versions
show_readme "${ARGV_repo}" # display notes and caveats
popd >/dev/null || { error 'popd sandbox/ failed'; }
echo '[FIN] - installer ran successfully'
# [EOF]