-
Notifications
You must be signed in to change notification settings - Fork 7
/
setup-env
executable file
·316 lines (265 loc) · 9.73 KB
/
setup-env
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
#!/usr/bin/env bash
set -o nounset
set -o errexit
set -o pipefail
USAGE=$(
cat << 'END_OF_LINE'
Configure a development environment for this repository.
It does the following:
- Allows the user to specify the Python version to use for the virtual environment.
- Allows the user to specify a name for the virtual environment.
- Verifies pyenv and pyenv-virtualenv are installed.
- Creates the Python virtual environment.
- Configures the activation of the virtual enviroment for the repo directory.
- Installs the requirements needed for development.
- Installs git pre-commit hooks.
- Configures git remotes for upstream "lineage" repositories.
Usage:
setup-env [--venv-name venv_name] [--python-version python_version]
setup-env (-h | --help)
Options:
-f | --force Delete virtual enviroment if it already exists.
-h | --help Show this message.
-i | --install-hooks Install hook environments for all environments in the
pre-commit config file.
-l | --list-versions List available Python versions and select one interactively.
-v | --venv-name Specify the name of the virtual environment.
-p | --python-version Specify the Python version for the virtual environment.
END_OF_LINE
)
# Display pyenv's installed Python versions
python_versions() {
pyenv versions --bare --skip-aliases --skip-envs
}
check_python_version() {
local version=$1
# This is a valid regex for semantically correct Python version strings.
# For more information see here:
# https://semver.org/#is-there-a-suggested-regular-expression-regex-to-check-a-semver-string
# Break down the regex into readable parts major.minor.patch
local major="0|[1-9]\d*"
local minor="0|[1-9]\d*"
local patch="0|[1-9]\d*"
# Splitting the prerelease part for readability
# Start of the prerelease
local prerelease="(?:-"
# Numeric or alphanumeric identifiers
local prerelease+="(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)"
# Additional dot-separated identifiers
local prerelease+="(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*"
# End of the prerelease, making it optional
local prerelease+=")?"
# Optional build metadata
local build="(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?"
# Final regex composed of parts
local regex="^($major)\.($minor)\.($patch)$prerelease$build$"
# This checks if the Python version does not match the regex pattern specified in $regex,
# using Perl for regex matching. If the pattern is not found, then prompt the user with
# the invalid version message.
if ! echo "$version" | perl -ne "exit(!/$regex/)"; then
echo "Invalid version of Python: Python follows semantic versioning," \
"so any version string that is not a valid semantic version is an" \
"invalid version of Python."
exit 1
# Else if the Python version isn't installed then notify the user.
# grep -E is used for searching through text lines that match the specific verison.
elif ! python_versions | grep -E "^${version}$" > /dev/null; then
echo "Error: Python version $version is not installed."
echo "Installed Python versions are:"
python_versions
exit 1
else
echo "Using Python version $version"
fi
}
# Flag to force deletion and creation of virtual environment
FORCE=0
# Initialize the other flags
INSTALL_HOOKS=0
LIST_VERSIONS=0
PYTHON_VERSION=""
VENV_NAME=""
# Define long options
LONGOPTS="force,help,install-hooks,list-versions,python-version:,venv-name:"
# Define short options for getopt
SHORTOPTS="fhilp:v:"
# Check for GNU getopt by matching a specific pattern ("getopt from util-linux")
# in its version output. This approach presumes the output format remains stable.
# Be aware that format changes could invalidate this check.
if [[ $(getopt --version 2> /dev/null) != *"getopt from util-linux"* ]]; then
cat << 'END_OF_LINE'
Please note, this script requires GNU getopt due to its enhanced
functionality and compatibility with certain script features that
are not supported by the POSIX getopt found in some systems, particularly
those with a non-GNU version of getopt. This distinction is crucial
as a system might have a non-GNU version of getopt installed by default,
which could lead to unexpected behavior.
On macOS, we recommend installing brew (https://brew.sh/). Then installation
is as simple as `brew install gnu-getopt` and adding this to your
profile:
export PATH="$(brew --prefix)/opt/gnu-getopt/bin:$PATH"
GNU getopt must be explicitly added to the PATH since it
is keg-only (https://docs.brew.sh/FAQ#what-does-keg-only-mean).
END_OF_LINE
exit 1
fi
# Check to see if pyenv is installed
if [ -z "$(command -v pyenv)" ] || { [ -z "$(command -v pyenv-virtualenv)" ] && [ ! -f "$(pyenv root)/plugins/pyenv-virtualenv/bin/pyenv-virtualenv" ]; }; then
echo "pyenv and pyenv-virtualenv are required."
if [[ "$OSTYPE" == "darwin"* ]]; then
cat << 'END_OF_LINE'
On macOS, we recommend installing brew, https://brew.sh/. Then installation
is as simple as `brew install pyenv pyenv-virtualenv` and adding this to your
profile:
eval "$(pyenv init -)"
eval "$(pyenv virtualenv-init -)"
END_OF_LINE
fi
cat << 'END_OF_LINE'
For Linux, Windows Subsystem for Linux (WSL), or macOS (if you don't want
to use "brew") you can use https://github.com/pyenv/pyenv-installer to install
the necessary tools. Before running this ensure that you have installed the
prerequisites for your platform according to the pyenv wiki page,
https://github.com/pyenv/pyenv/wiki/common-build-problems.
On WSL you should treat your platform as whatever Linux distribution you've
chosen to install.
Once you have installed "pyenv" you will need to add the following lines to
your ".bashrc":
export PATH="$PATH:$HOME/.pyenv/bin"
eval "$(pyenv init -)"
eval "$(pyenv virtualenv-init -)"
END_OF_LINE
exit 1
fi
# Use GNU getopt to parse options
if ! PARSED=$(getopt --options $SHORTOPTS --longoptions $LONGOPTS --name "$0" -- "$@"); then
echo "Error parsing options"
exit 1
fi
eval set -- "$PARSED"
while true; do
case "$1" in
-f | --force)
FORCE=1
shift
;;
-h | --help)
echo "$USAGE"
exit 0
;;
-i | --install-hooks)
INSTALL_HOOKS=1
shift
;;
-l | --list-versions)
LIST_VERSIONS=1
shift
;;
-p | --python-version)
PYTHON_VERSION="$2"
shift 2
# Check the Python version being passed in.
check_python_version "$PYTHON_VERSION"
;;
-v | --venv-name)
VENV_NAME="$2"
shift 2
;;
--)
shift
break
;;
*)
# Unreachable due to GNU getopt handling all options
echo "Programming error"
exit 64
;;
esac
done
# Determine the virtual environment name
if [ -n "$VENV_NAME" ]; then
# Use the user-provided environment name
env_name="$VENV_NAME"
else
# Set the environment name to the last part of the working directory.
env_name=${PWD##*/}
fi
# List Python versions and select one interactively.
if [ $LIST_VERSIONS -ne 0 ]; then
echo Available Python versions:
python_versions
# Read the user's desired Python version.
# -r: treat backslashes as literal, -p: display prompt before input.
read -r -p "Enter the desired Python version: " PYTHON_VERSION
# Check the Python version being passed in.
check_python_version "$PYTHON_VERSION"
fi
# Remove any lingering local configuration.
if [ $FORCE -ne 0 ]; then
rm -f .python-version
pyenv virtualenv-delete --force "${env_name}" || true
elif [[ -f .python-version ]]; then
cat << 'END_OF_LINE'
An existing .python-version file was found. Either remove this file yourself
or re-run with the --force option to have it deleted along with the associated
virtual environment.
rm .python-version
END_OF_LINE
exit 1
fi
# Create a new virtual environment for this project
#
# If $PYTHON_VERSION is undefined then the current pyenv Python version will be used.
#
# We can't quote ${PYTHON_VERSION:=} below since if the variable is
# undefined then we want nothing to appear; this is the reason for the
# "shellcheck disable" line below.
#
# shellcheck disable=SC2086
if ! pyenv virtualenv ${PYTHON_VERSION:=} "${env_name}"; then
cat << END_OF_LINE
An existing virtual environment named $env_name was found. Either delete this
environment yourself or re-run with the --force option to have it deleted.
pyenv virtualenv-delete ${env_name}
END_OF_LINE
exit 1
fi
# Set the local application-specific Python version(s) by writing the
# version name to a file named `.python-version'.
pyenv local "${env_name}"
# Upgrade pip and friends
python3 -m pip install --upgrade pip setuptools wheel
# Find a requirements file (if possible) and install
for req_file in "requirements-dev.txt" "requirements-test.txt" "requirements.txt"; do
if [[ -f $req_file ]]; then
pip install --requirement $req_file
break
fi
done
# Install git pre-commit hooks now or later.
pre-commit install ${INSTALL_HOOKS:+"--install-hooks"}
# Setup git remotes from lineage configuration
# This could fail if the remotes are already setup, but that is ok.
set +o errexit
eval "$(
python3 << 'END_OF_LINE'
from pathlib import Path
import yaml
import sys
LINEAGE_CONFIG = Path(".github/lineage.yml")
if not LINEAGE_CONFIG.exists():
print("No lineage configuration found.", file=sys.stderr)
sys.exit(0)
with LINEAGE_CONFIG.open("r") as f:
lineage = yaml.safe_load(stream=f)
if lineage["version"] == "1":
for parent_name, v in lineage["lineage"].items():
remote_url = v["remote-url"]
print(f"git remote add {parent_name} {remote_url};")
print(f"git remote set-url --push {parent_name} no_push;")
else:
print(f'Unsupported lineage version: {lineage["version"]}', file=sys.stderr)
END_OF_LINE
)"
# Qapla'
echo "Success!"