blob: eacb69b946213a6d041086cbdb3958127df33a6d [file] [log] [blame]
Joey Armstrong2550e892024-08-16 13:41:08 -04001#!/bin/bash
2## -----------------------------------------------------------------------
3## -----------------------------------------------------------------------
4
5# set -euo pipefail
6
7## -----------------------------------------------------------------------
8## Intent: Dislpay an error message then exit
9## -----------------------------------------------------------------------
10function error()
11{
12 cat <<ERR
13
14** -----------------------------------------------------------------------
15** IAM: ${BASH_SOURCE[0]}
16** IAM: ${FUNCNAME[1]} (LINENO:${BASH_LINENO[0]})
17** ERR: $@
18** -----------------------------------------------------------------------
19ERR
20
21 exit 1
22}
23
24## -----------------------------------------------------------------------
25## Intent: Dislpay an error message then exit
26## -----------------------------------------------------------------------
27function banner()
28{
29 cat <<MSG
30
31** -----------------------------------------------------------------------
32** $@
33** -----------------------------------------------------------------------
34MSG
35
36 return
37}
38
39## -----------------------------------------------------------------------
40## Intent: Debug function, display minimalist script debugging
41## -----------------------------------------------------------------------
42function 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## -----------------------------------------------------------------------
53function 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## -----------------------------------------------------------------------
74function 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## -----------------------------------------------------------------------
92function 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## -----------------------------------------------------------------------
119function 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## -----------------------------------------------------------------------
179function 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## -----------------------------------------------------------------------
240function 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## -----------------------------------------------------------------------
259function 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## -----------------------------------------------------------------------
303function 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## -----------------------------------------------------------------------
340function 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## -----------------------------------------------------------------------
368function 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
393HOWTO
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
410PROBLEMS
411
412 popd >/dev/null || { error "pushd $repo failed"; }
413 return
414}
415
416## -----------------------------------------------------------------------
417## -----------------------------------------------------------------------
418function usage()
419{
420 cat <<EOHELP
421Usage: $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
432EOHELP
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
440EOHELP
441
442 return
443}
444
445##----------------##
446##---] MAIN [---##
447##----------------##
448banner '[NOTE] If the status message [FIN] is displayed script ran successfully'
449
450
451while [[ $# -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
470done
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## -----------------------------------------------------------------------
478if 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
500else
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
509fi
510
511mkdir -p sandbox # git clone into sandbox/ for easy cleanup
512pushd 'sandbox' >/dev/null || { error 'pushd sandbox/ failed'; }
513
514## Checkout VOLTHA code base
515clone_and_branch_repo "$ARGV_repo"
516echo
517
518[[ -v ARGV_upgrade_packages ]] && { upgrade_packages "$ARGV_repo"; }
519
520## Install upgraded python interpreter in the sandbox
521install_sandbox_virtualenv "${ARGV_repo}" "${ARGV_version}" # sandbox/virtualenv/
522
523freeze_packages "$ARGV_repo" # capture all upgraded package versions
524show_readme "${ARGV_repo}" # display notes and caveats
525
526popd >/dev/null || { error 'popd sandbox/ failed'; }
527
528
529echo '[FIN] - installer ran successfully'
530
531# [EOF]