Joey Armstrong | 2550e89 | 2024-08-16 13:41:08 -0400 | [diff] [blame^] | 1 | #!/bin/bash |
| 2 | ## ----------------------------------------------------------------------- |
| 3 | ## ----------------------------------------------------------------------- |
| 4 | |
| 5 | # set -euo pipefail |
| 6 | |
| 7 | ## ----------------------------------------------------------------------- |
| 8 | ## Intent: Dislpay an error message then exit |
| 9 | ## ----------------------------------------------------------------------- |
| 10 | function error() |
| 11 | { |
| 12 | cat <<ERR |
| 13 | |
| 14 | ** ----------------------------------------------------------------------- |
| 15 | ** IAM: ${BASH_SOURCE[0]} |
| 16 | ** IAM: ${FUNCNAME[1]} (LINENO:${BASH_LINENO[0]}) |
| 17 | ** ERR: $@ |
| 18 | ** ----------------------------------------------------------------------- |
| 19 | ERR |
| 20 | |
| 21 | exit 1 |
| 22 | } |
| 23 | |
| 24 | ## ----------------------------------------------------------------------- |
| 25 | ## Intent: Dislpay an error message then exit |
| 26 | ## ----------------------------------------------------------------------- |
| 27 | function banner() |
| 28 | { |
| 29 | cat <<MSG |
| 30 | |
| 31 | ** ----------------------------------------------------------------------- |
| 32 | ** $@ |
| 33 | ** ----------------------------------------------------------------------- |
| 34 | MSG |
| 35 | |
| 36 | return |
| 37 | } |
| 38 | |
| 39 | ## ----------------------------------------------------------------------- |
| 40 | ## Intent: Debug function, display minimalist script debugging |
| 41 | ## ----------------------------------------------------------------------- |
| 42 | function signpost() |
| 43 | { |
| 44 | echo |
| 45 | echo "** SIGNPOST[${FUNCNAME[1]}] (LINENO:${BASH_LINENO[1]}): $@" |
| 46 | return |
| 47 | } |
| 48 | |
| 49 | ## ----------------------------------------------------------------------- |
| 50 | ## Intent: Identify system type |
| 51 | ## https://stackoverflow.com/questions/394230/how-to-detect-the-os-from-a-bash-script |
| 52 | ## ----------------------------------------------------------------------- |
| 53 | function detect_arch() |
| 54 | { |
| 55 | local -n ref=$1; shift |
| 56 | |
| 57 | ref='' |
| 58 | if [[ "$OSTYPE" == "linux-gnu"* ]]; then |
| 59 | ref='linux' |
| 60 | elif [[ "$OSTYPE" == "darwin"* ]]; then |
| 61 | ref='darwin' |
| 62 | elif [[ "$OSTYPE" == "freebsd"* ]]; then |
| 63 | ref='freebsd' |
| 64 | else |
| 65 | error "Deteted unhandled OS_TYPE=[$OS_TYPE]" |
| 66 | fi |
| 67 | |
| 68 | return |
| 69 | } |
| 70 | |
| 71 | ## ----------------------------------------------------------------------- |
| 72 | ## Intent: Add more apt sources to install versioned packages |
| 73 | ## ----------------------------------------------------------------------- |
| 74 | function configure_apt() |
| 75 | { |
| 76 | |
| 77 | ## Perform once |
| 78 | [[ -v configure_apt_seen ]] && { return; } |
| 79 | declare -g -i configure_apt_seen=1 |
| 80 | |
| 81 | sandbox '[ENTER]' |
| 82 | sudo 'add-apt-repository' 'universe' |
| 83 | sudo apt update |
| 84 | sandbox '[LEAVE]' |
| 85 | |
| 86 | return |
| 87 | } |
| 88 | |
| 89 | ## ----------------------------------------------------------------------- |
| 90 | ## Intent: Ubuntu: apt install packages for a versioned python interpreter |
| 91 | ## ----------------------------------------------------------------------- |
| 92 | function install_apt_packages() |
| 93 | { |
| 94 | local ver="$1"; shift |
| 95 | |
| 96 | pkgs+=("libpython${ver}") # Shared Python runtime library (version ${ver}) |
| 97 | pkgs+=("libpython${ver}-dev") # Header files and a static library for Python (v${ver}) |
| 98 | pkgs+=("libpython${ver}-minimal") # Minimal subset of the Python language (version ${ver}) |
| 99 | pkgs+=("python${ver}") # - Interactive high-level object-oriented language (version ${ver}) |
| 100 | pkgs+=("python${ver}-dev") # - Header files and a static library for Python (v${ver}) |
| 101 | pkgs+=("python${ver}-full") # - Python Interpreter with complete class library (version ${ver}) |
| 102 | pkgs+=("python${ver}-venv") # - Interactive high-level object-oriented language (pyvenv binary, version ${ver}) |
| 103 | |
| 104 | pkgs+=('virtualenv') # Convenience wrapper for installation |
| 105 | |
| 106 | sudo apt-get install -y "${pkgs[@]}" |
| 107 | return |
| 108 | } |
| 109 | |
| 110 | ## ----------------------------------------------------------------------- |
| 111 | ## Intent: |
| 112 | ## o Install multiple interpreters for evaluation |
| 113 | ## o Installer is non-destructive |
| 114 | ## ----------------------------------------------------------------------- |
| 115 | ## [TODO] |
| 116 | ## o Configure update-alternatives to simplify version switching. |
| 117 | ## o Careful, modifying /usr/bin/python can break system dependencies |
| 118 | ## ----------------------------------------------------------------------- |
| 119 | function install_python() |
| 120 | { |
| 121 | local ver="$1"; shift |
| 122 | local path="/usr/bin/python${ver}" |
| 123 | |
| 124 | echo "INSTALL PYTHON" |
| 125 | local arch='' |
| 126 | detect_arch arch |
| 127 | |
| 128 | ## Configure package manager to include versioned python packages. |
| 129 | case "$arch" in |
| 130 | 'linux') configure_apt ;; |
| 131 | 'darwin') |
| 132 | ## Logic needed if installing with: brew, etc |
| 133 | error "Installer not yet configured for OSX (arch=[$arch])" |
| 134 | ;; |
| 135 | *) error "Installer not yet configured for arch=[$arch]" ;; |
| 136 | esac |
| 137 | |
| 138 | |
| 139 | ## ------------------------------- |
| 140 | ## If evaluating multiple versions |
| 141 | ## ------------------------------- |
| 142 | declare -a versions=() |
| 143 | # versions+=('3.8') |
| 144 | versions+=('3.10') |
| 145 | # versions+=('3.12') |
| 146 | # versions+=('3.13') |
| 147 | |
| 148 | local version |
| 149 | for version in "${versions[@]}"; |
| 150 | do |
| 151 | local path="/usr/bin/python${version}" |
| 152 | signpost "Interpreter: $(declare -p path)" |
| 153 | |
| 154 | if [[ -e "$path" ]]; then |
| 155 | echo "[${FUNCNAME[0]}:SKIP] interpreter already installed [$path]" |
| 156 | continue |
| 157 | fi |
| 158 | |
| 159 | case "$arch" in |
| 160 | 'linux') install_apt_packages "$version" ;; |
| 161 | |
| 162 | ## Commands needed for brew, etc |
| 163 | 'darwin') |
| 164 | error "Installer not yet configured for OSX (arch=[$arch])" |
| 165 | ;; |
| 166 | *) error "Installer not yet configured for arch=[$arch]" ;; |
| 167 | esac |
| 168 | done |
| 169 | |
| 170 | return |
| 171 | } |
| 172 | |
| 173 | ## ----------------------------------------------------------------------- |
| 174 | ## Intent: Install a versioned venv into a cloned sandbox |
| 175 | ## ----------------------------------------------------------------------- |
| 176 | ## [NOTE] Early creation of {sandbox}/.venv will short circuit a makefile |
| 177 | ## makefile target that normally installs venv. |
| 178 | ## ----------------------------------------------------------------------- |
| 179 | function install_sandbox_virtualenv() |
| 180 | { |
| 181 | local repo="$1"; shift |
| 182 | local ver="$1"; shift |
| 183 | local interpreter="/usr/bin/python${ver}" |
| 184 | |
| 185 | signpost '[ENTER]' |
| 186 | |
| 187 | pushd "$repo" >/dev/null || { error "pushd $repo failed"; } |
| 188 | |
| 189 | readarray -t cmd < <(which virtualenv 2>/dev/null) |
| 190 | if [[ "${#cmd[@]}" -gt 0 ]] && [[ -x "${cmd[0]}" ]]; then |
| 191 | ## Prefer tool:virtualenv for installation when available |
| 192 | banner "** Install python using tool virtualenv" |
| 193 | |
| 194 | declare -a args=() |
| 195 | [[ ! -v ARGV_no_quiet ]] && { args+=('--quiet'); } |
| 196 | virtualenv "${args[@]}" -p "$interpreter" '.venv' |
| 197 | |
| 198 | else |
| 199 | # fallback to dependency-less venv install |
| 200 | banner "** Install python using a versioned interpreter" |
| 201 | "$interpreter" -m venv '.venv' |
| 202 | fi |
| 203 | |
| 204 | |
| 205 | ## ------------------------------------------------------- |
| 206 | ## 1) Configure default interpreter version |
| 207 | ## 2) Upgrade core interpreter packages: pip, setuptools |
| 208 | ## 3) Install per-repository packages : requirements.txt |
| 209 | ## ------------------------------------------------------ |
| 210 | ## See Also: |
| 211 | ## % make venv |
| 212 | ## makefiles/virtualenv/include.mk |
| 213 | ## ------------------------------------------------------- |
| 214 | source .venv/bin/activate |
| 215 | |
| 216 | declare -a pip_install=() |
| 217 | pip_install+=('pip' 'install' '--no-cache-dir') |
| 218 | [[ ! -v ARGV_no_quiet ]] && { pip_install+=('--quiet'); } |
| 219 | # Upgrade core interpreter packages: pip, setuptools |
| 220 | # pip --upgrade has chicken-n-egg problems |
| 221 | python -m "${pip_install[@]}" --upgrade pip |
| 222 | |
| 223 | "${pip_install[@]}" --upgrade setuptools |
| 224 | |
| 225 | ## Install per-repository packages : requirements.txt |
| 226 | python -m "${pip_install[@]}" -r ./requirements.txt |
| 227 | |
| 228 | popd >/dev/null || { error "pushd $repo failed"; } |
| 229 | |
| 230 | signpost '[LEAVE]' |
| 231 | |
| 232 | return |
| 233 | } |
| 234 | |
| 235 | ## ----------------------------------------------------------------------- |
| 236 | ## Intent: |
| 237 | ## Determine if python interpreter is valid/current. |
| 238 | ## No need for installation if the OS is current with recent packages |
| 239 | ## ----------------------------------------------------------------------- |
| 240 | function verify_python_version() |
| 241 | { |
| 242 | local want="$1"; shift |
| 243 | |
| 244 | readarray -t version < <(/usr/bin/python3 --version) |
| 245 | |
| 246 | case "${version[*]}" in |
| 247 | *"$want"*) local -i is_valid=1 ;; |
| 248 | esac |
| 249 | |
| 250 | [[ -v is_valid ]] && { /bin/true; } || { /bin/false; } |
| 251 | return |
| 252 | } |
| 253 | |
| 254 | ## ----------------------------------------------------------------------- |
| 255 | ## Intent: |
| 256 | ## o Clone a repository and create a user branch |
| 257 | ## o Installer is non-destructive |
| 258 | ## ----------------------------------------------------------------------- |
| 259 | function clone_and_branch_repo() |
| 260 | { |
| 261 | local repo="$1"; shift |
| 262 | local path="$repo" |
| 263 | |
| 264 | sandbox '[ENTER]' |
| 265 | |
| 266 | if [[ -v ARGV_clean ]] && [[ -d "$path" ]]; then |
| 267 | echo "** Removing old sandbox: $repo" |
| 268 | rm -fr "$repo" |
| 269 | fi |
| 270 | |
| 271 | ## NOP if sandbox already checked out |
| 272 | if [[ -e "$path" ]]; then |
| 273 | echo "[${FUNCNAME[0]}:SKIP] repository already checked out" |
| 274 | return |
| 275 | fi |
| 276 | |
| 277 | banner "Clone and branch repo: ${repo}" |
| 278 | |
| 279 | local url="ssh://gerrit.opencord.org:29418/${repo}.git" |
| 280 | git clone "$url" |
| 281 | pushd "$repo" >/dev/null || { error "pushd $repo failed"; } |
| 282 | |
| 283 | local branch="dev-${USER}" |
| 284 | banner "git checkout -b \"$branch\"" |
| 285 | git checkout -b "$branch" |
| 286 | |
| 287 | banner "git remote -v show" |
| 288 | git remote -v show |
| 289 | |
| 290 | banner "git branch -a" |
| 291 | git branch -a |
| 292 | |
| 293 | popd >/dev/null || { error "popd $repo failed"; } |
| 294 | |
| 295 | sandbox '[LEAVE]' |
| 296 | |
| 297 | return |
| 298 | } |
| 299 | |
| 300 | ## ----------------------------------------------------------------------- |
| 301 | ## Intent: Alter requirements.txt to use latest package versions |
| 302 | ## ----------------------------------------------------------------------- |
| 303 | function upgrade_packages() |
| 304 | { |
| 305 | local repo="$1"; shift |
| 306 | |
| 307 | signpost '[ENTER]' |
| 308 | |
| 309 | ## NOP: Already modified |
| 310 | if ! grep '[=<>]' "$repo/requirements.txt"; then |
| 311 | return |
| 312 | fi |
| 313 | |
| 314 | pushd "$repo" >/dev/null || { error "pushd $repo failed"; } |
| 315 | |
| 316 | sed -i'' '\@zdw/robotframework-importresource@d' 'requirements.txt' |
| 317 | |
| 318 | # ------------------------------------------------- |
| 319 | # Disgusting but portable: prune version qualifiers |
| 320 | # Pip will install latest packages by name |
| 321 | # ------------------------------------------------- |
| 322 | cut -d'=' -f1 requirements.txt \ |
| 323 | | cut -d'<' -f1 \ |
| 324 | | cut -d'>' -f1 \ |
| 325 | > requirements.tmp |
| 326 | |
| 327 | echo "** Use git checkout requirements.txt to revert changes" |
| 328 | mv requirements.tmp requirements.txt |
| 329 | |
| 330 | popd >/dev/null || { error "popd $repo failed"; } |
| 331 | |
| 332 | signpost '[LEAVE]' |
| 333 | |
| 334 | return |
| 335 | } |
| 336 | |
| 337 | ## ----------------------------------------------------------------------- |
| 338 | ## Intent: If version strings |
| 339 | ## ----------------------------------------------------------------------- |
| 340 | function freeze_packages() |
| 341 | { |
| 342 | local repo="$1"; shift |
| 343 | |
| 344 | signpost '[ENTER]' |
| 345 | |
| 346 | pushd "$repo" >/dev/null || { error "pushd $repo failed"; } |
| 347 | source .venv/bin/activate |
| 348 | |
| 349 | declare -a pip_freeze=() |
| 350 | pip_freeze+=('pip' 'freeze') |
| 351 | pip_freeze+=('--local') # local directory |
| 352 | pip_freeze+=('--requirement' 'requirements.txt') # existing order |
| 353 | |
| 354 | "${pip_freeze[@]}" > 'requirements.txt.tmp' |
| 355 | mv 'requirements.txt.tmp' 'requirements.txt' |
| 356 | |
| 357 | popd >/dev/null || { error "popd $repo failed"; } |
| 358 | |
| 359 | signpost '[LEAVE]' |
| 360 | |
| 361 | return |
| 362 | } |
| 363 | |
| 364 | ## ----------------------------------------------------------------------- |
| 365 | ## Intent: Display misc information about the installation, known problems |
| 366 | ## and makefile targets used for building and testing. |
| 367 | ## ----------------------------------------------------------------------- |
| 368 | function show_readme() |
| 369 | { |
| 370 | local repo="$1"; shift |
| 371 | |
| 372 | /bin/pwd |
| 373 | pushd "$repo" >/dev/null || { error "pushd $repo failed"; } |
| 374 | |
| 375 | cat <<HOWTO |
| 376 | |
| 377 | ** ----------------------------------------------------------------------- |
| 378 | ** IAM: ${FUNCNAME[0]} |
| 379 | ** PWD: $(/bin/pwd) |
| 380 | ** ----------------------------------------------------------------------- |
| 381 | |
| 382 | % cd ${repo} |
| 383 | % source .venv/bin/activate |
| 384 | $(source .venv/bin/activate && python --version) |
| 385 | |
| 386 | % make help # Display available targets |
| 387 | % make all |
| 388 | % make lint |
| 389 | % make pre-commit |
| 390 | % make build |
| 391 | % make test |
| 392 | |
| 393 | HOWTO |
| 394 | |
| 395 | readarray -t zdw < <(grep '/zdw/' requirements.txt) |
| 396 | cat <<PROBLEMS |
| 397 | |
| 398 | ** ----------------------------------------------------------------------- |
| 399 | ** Known Problems: early failures that require attention |
| 400 | ** ----------------------------------------------------------------------- |
| 401 | [requirements.txt] |
| 402 | o Comment out package /zdw/robotframework-import in equirements.txt. |
| 403 | o Try --upgrade-packages to install latest versions |
| 404 | |
| 405 | [grpc] |
| 406 | o repo:voltha-protos and repo:voltha-system-tests both have |
| 407 | problems building packages. |
| 408 | o ERROR: Failed to build installable wheels for some pyproject.toml based projects (grpcio) |
| 409 | |
| 410 | PROBLEMS |
| 411 | |
| 412 | popd >/dev/null || { error "pushd $repo failed"; } |
| 413 | return |
| 414 | } |
| 415 | |
| 416 | ## ----------------------------------------------------------------------- |
| 417 | ## ----------------------------------------------------------------------- |
| 418 | function usage() |
| 419 | { |
| 420 | cat <<EOHELP |
| 421 | Usage: $0 |
| 422 | --repo [r] Repository name to checkout and configure. |
| 423 | --version [v] Install python virtualenv at version [v] |
| 424 | |
| 425 | --gerrit Browse a list of VOLTHA repositories in the gerrit UI |
| 426 | --upgrade-pacakges Remove frozen version information from requirements.txt |
| 427 | |
| 428 | --no-quiet Run commands in verbose mode (pip, virtualenv) |
| 429 | --verbose alias for --no-quiet |
| 430 | --clean Delete cloned sandbox prior to install |
| 431 | --help This message |
| 432 | EOHELP |
| 433 | |
| 434 | cat <<EOHELP |
| 435 | |
| 436 | [EXAMPLES] |
| 437 | % $0 --version 3.12 --repo voltha-system-tests |
| 438 | % $0 --version 3.12 --repo voltha-lib-go |
| 439 | |
| 440 | EOHELP |
| 441 | |
| 442 | return |
| 443 | } |
| 444 | |
| 445 | ##----------------## |
| 446 | ##---] MAIN [---## |
| 447 | ##----------------## |
| 448 | banner '[NOTE] If the status message [FIN] is displayed script ran successfully' |
| 449 | |
| 450 | |
| 451 | while [[ $# -gt 0 ]]; do |
| 452 | arg="$1"; shift |
| 453 | |
| 454 | case "$arg" in |
| 455 | '--no-quiet') declare -g -i ARGV_no_quiet=1; ;; |
| 456 | '--verbose') declare -g -i ARGV_no_quiet=1; ;; |
| 457 | |
| 458 | '--clean') declare -g -i ARGV_clean=1 ;; |
| 459 | '--upgr'*) declare -g ARGV_upgrade_packages=1 ;; |
| 460 | '--repo'*) declare -g ARGV_repo="$1"; shift ;; |
| 461 | '--ver'*) declare -g ARGV_version="$1"; shift ;; |
| 462 | '--help') usage; exit 0 ;; |
| 463 | '--gerrit') |
| 464 | "${BROWSER:-firefox}" 'https://gerrit.opencord.org/admin/repos/q/filter:voltha-' |
| 465 | exit 1 |
| 466 | ;; |
| 467 | '-'*) error "Detected unknown switch [$arg]" ;; |
| 468 | *) echo "[SKIP] unknown argument [$arg]" ;; |
| 469 | esac |
| 470 | done |
| 471 | |
| 472 | [[ ! -v ARGV_version ]] && { error '--version is required (default: 2.13)'; } |
| 473 | [[ ! -v ARGV_repo ]] && { error '--repo is required'; } |
| 474 | |
| 475 | ## ----------------------------------------------------------------------- |
| 476 | ## NOP if system interpreter is the latest |
| 477 | ## ----------------------------------------------------------------------- |
| 478 | if verify_python_version "$ARGV_version"; then |
| 479 | |
| 480 | readarray -t version < <(pyhon --version 2>/dev/null) |
| 481 | echo "** Python Version: ${version[*]}" |
| 482 | case "${version[*]}" in |
| 483 | |
| 484 | # v3.13 is in pre-release. |
| 485 | # may be pre-installed for new distribution installations. |
| 486 | # If unavailabler sources are available for download but |
| 487 | # will require building the interpreter. |
| 488 | |
| 489 | *'3.12'*|*'3.13'*) |
| 490 | banner \ |
| 491 | "[SKIP] Python interpreter already installed (ver=${ARGV_version})" \ |
| 492 | '[NOTE] This script is no longer needed, detected latest python version' |
| 493 | ;; |
| 494 | *) |
| 495 | banner "[SKIP] Python interpreter already installed (ver=${ARGV_version})" |
| 496 | ;; |
| 497 | esac |
| 498 | |
| 499 | # [ELSE] we need to install |
| 500 | else |
| 501 | |
| 502 | banner "Installing python version ${ARGV_version}" |
| 503 | |
| 504 | ## ------------------------------------------------------------ |
| 505 | # package manager: Configure and install versioned interpreters |
| 506 | ## ------------------------------------------------------------ |
| 507 | install_python "${ARGV_version}" # OS packages |
| 508 | |
| 509 | fi |
| 510 | |
| 511 | mkdir -p sandbox # git clone into sandbox/ for easy cleanup |
| 512 | pushd 'sandbox' >/dev/null || { error 'pushd sandbox/ failed'; } |
| 513 | |
| 514 | ## Checkout VOLTHA code base |
| 515 | clone_and_branch_repo "$ARGV_repo" |
| 516 | echo |
| 517 | |
| 518 | [[ -v ARGV_upgrade_packages ]] && { upgrade_packages "$ARGV_repo"; } |
| 519 | |
| 520 | ## Install upgraded python interpreter in the sandbox |
| 521 | install_sandbox_virtualenv "${ARGV_repo}" "${ARGV_version}" # sandbox/virtualenv/ |
| 522 | |
| 523 | freeze_packages "$ARGV_repo" # capture all upgraded package versions |
| 524 | show_readme "${ARGV_repo}" # display notes and caveats |
| 525 | |
| 526 | popd >/dev/null || { error 'popd sandbox/ failed'; } |
| 527 | |
| 528 | |
| 529 | echo '[FIN] - installer ran successfully' |
| 530 | |
| 531 | # [EOF] |