aboutsummaryrefslogtreecommitdiff
path: root/stenv_assist
blob: 36875c16d5fe14cb94534ff4c6eec0f6b68bd579 (plain) (blame)
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
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
#!/usr/bin/env bash
set -o pipefail

# Stores release URLs globally to avoid constantly hitting the API server
STENV_RELEASES=()

# Default conda program is "conda"
conda=conda

# Return a list of conda environments matching $1
have_conda_env() {
    if ! have_conda; then
        return 1
    fi
    $conda env list | sed '/^#.*/d' | cut -d ' ' -f 1 \
        | grep "^${1}$" &>/dev/null
}

# Return a comma delimited list of environments matching "stenv"
get_stenv() {
    if ! have_conda; then
        return 1
    fi

    local envs=($($conda env list | sed '/^#.*/d' | cut -d ' ' -f 1 \
        | grep "stenv" | sort -V))

    if (( ${#envs[@]} == 0)); then
        return 1
    fi

    for (( i = 0; i < ${#envs[@]}; i++ )); do
        echo -n "${envs[i]}"
        if (( i < ${#envs[@]} - 1 )); then
            echo -n ", "
        fi
    done
}

# Is conda available?
# return 1 on failure
# return 0 on success
have_conda() {
    type -P $conda &>/dev/null
}

# Get path to conda executable
# return 1 and empty string if conda is not available
# return 0 and path on success
get_conda() {
    if ! have_conda; then
        return 1
    fi
    echo $(type -P $conda)
    return 0
}

# Get CPU architecture
get_arch() {
    uname -m
}

# Convert host platform identifier to a conda-specific string
get_conda_platform() {
    local name=$(uname -s)
    case "$name" in
        Darwin)
            echo "macOSX"
            ;;
        *)
            echo "$name"
            ;;
    esac
}

# Convert host platform identifier to a stenv-specific string
get_stenv_platform() {
    local name=$(uname -s)
    case "$name" in
        Darwin)
            echo "macOS"
            ;;
        *)
            echo "$name"
            ;;
    esac
}

# Return stenv repository data (JSON)
stenv_api() {
    curl -s -X GET "https://api.github.com/repos/spacetelescope/stenv/$1"
}

# Extract release URLs from */stenv/releases
get_stenv_releases() {
    stenv_api releases \
        | grep -Eo '"browser_download_url": "(.*)"' \
        | while read line; do
        sed -e 's/.*: "\(.*\)"/\1/' <<< $line
    done
}

# Extract information from a stenv configuration file name
# returns string:
# name platform python_version stenv_version type
get_stenv_components() {
    local filename=$(basename $1)

    # Handle deprecated config naming convention
    if [[ $filename == "spacetelescope-env"* ]]; then
        filename=${filename/spacetelescope-env/stenv}
    fi
    read st_name st_platform st_python_version st_version st_type \
        <<< $(tr '-' ' ' <<< $filename | sed 's/\.yml//')

    # Immortalize deprecated latest/stable/dev naming convention
    if [[ -z "$st_type" ]]; then
        st_type="latest"
    fi

    echo "$st_name $st_platform ${st_python_version#py} $st_version $st_type"
}

# Main entry point
menu_main() {
    while true; do
        local envs

        echo "STENV Assist"
        echo "============"
        echo

        # Use mamba if its available
        if type -P mamba &>/dev/null; then
            conda=mamba
        fi

        /bin/echo -n "1) Install Conda  " 
        if have_conda; then
            echo -e "\t\t[found: $(get_conda)]"
        else
            echo -e "\t\t[not installed]"
        fi

        /bin/echo -n "2) Install release"
        envs=$(get_stenv)
        if [[ -n "$envs" ]]; then
            echo -e "\t\t[found: $(get_stenv)]"
        else
            echo -e "\t\t[not installed]"
        fi

        echo "q) quit"
        read -p "=> " choice
        case "$choice" in
            1)
                if have_conda; then
                    echo >&2
                    echo "Conda is already installed..." >&2
                    echo >&2
                    continue
                fi
                menu_conda_install
                ;;
            2)
                if ! have_conda; then
                    echo >&2
                    echo "Conda must be:" >&2
                    echo "  - Available on your PATH prior to running stenv_assit" >&2
                    echo "  - [or] Installed using option 1, 'Install Conda'" >&2
                    echo >&2
                    echo "To use an existing Conda installation:" >&2
                    echo "  1. Quit stenv_assit (option: q)" >&2
                    echo "  2. Activate your Conda base environment" >&2
                    echo "  3. Run stenv_assit again" >&2
                    echo >&2
                    continue
                fi
                menu_create_stenv
                ;;
            [Qq])
                exit 0
                ;;
            *)
                echo >&2
                echo "Invalid selection" >&2
                echo >&2
                ;;
        esac
    done
}

# Perform base conda installation
# arguments:
#   url: URL to remote conda installation script
# returns status of "$conda init"
task_conda_install() {
    local url=$1
    local filename=$(basename $1)
    local root

    if [[ "$filename" == "Mini"* ]]; then
        root=$HOME/miniconda3
    elif [[ "$filename" == "Mamba"* ]]; then
        root=$HOME/mambaforge
    else
        echo "Unsupported installer: $url" >&2
        return
    fi

    if [[ -d "$root" ]]; then
        echo
        echo "An existing installation was found:"
        echo "  $root"
        echo
        echo "Do the following:"
        echo "  1. Quit stenv_assist (option: q)"
        echo "  2. Activate $root"
        echo "     Run:"
        echo "       source $root/etc/profile.d/conda.sh"
        if [[ -f "$root"/etc/profile.d/mamba.sh ]]; then
            echo "       source $root/etc/profile.d/mamba.sh"
        fi
        echo "       conda activate base"
        echo "  3. Run stenv_assist again"
        echo
        return 0
    fi

    echo "Downloading... $url"
    curl -L -O "$url"

    echo "Installing... $filename"
    bash "$filename" -p "$root" -b
    rm -f "$filename"

    echo "Configuring shell environment..."
    source "$root"/etc/profile.d/conda.sh
    if [[ -f "$root"/etc/profile.d/mamba.sh ]]; then
        source "$root"/etc/profile.d/mamba.sh
    fi
    $conda init $(basename $SHELL)
}

menu_conda_install() {
    local arch=$(get_arch)
    local platform=$(get_conda_platform)
    local mambaforge=https://github.com/conda-forge/miniforge/releases/latest/download/Mambaforge-${platform}-${arch}.sh
    local miniconda=https://repo.anaconda.com/miniconda/Miniconda3-latest-${platform}-${arch}.sh

    echo
    echo "Choose an installer..."
    echo
    echo "1) mambaforge"
    echo "2) miniconda3"
    echo "b) go back"
    echo "q) quit"

    while true; do
        read -p "=> " choice
        case "$choice" in
            1)
                task_conda_install $mambaforge
                break
                ;;
            2)
                task_conda_install $miniconda
                break
                ;;
            b)
                break
                ;;
            [qQ])
                exit 0
                ;;
            *)
                echo >&2
                echo "Invalid selection" >&2
                echo >&2
                ;;
        esac
    done

}

menu_create_stenv() {
    echo
    echo "Choose a stenv release..."
    echo
    for (( i = 0; i < "${#STENV_RELEASES[@]}"; i++ )); do
        local name="${STENV_RELEASES[i]}"
        read st_name st_platform st_python_version st_version st_type \
            <<< $(get_stenv_components $name)

        printf "%4d) %-20s Python %-10s %-5s[%s]\n" \
            "$i" "$st_version" "$st_python_version" " " "$st_type"
    done
    echo "  b) go back"
    echo "  q) quit"

    while true; do
        local default=$(( ${#STENV_RELEASES[@]} - 1 ))
        read -p "(default: $default) => " choice
        if [[ -z "$choice" ]]; then
            choice=$default
        fi
        case "$choice" in
            [0-9]*)
                if [[ "$choice" =~ [a-Z]+ ]] || (( choice < 0 )) \
                    || (( choice > "${#STENV_RELEASES[@]}" - 1 )); then
                    echo "Index out of range" >&2
                    continue
                fi
                task_create_stenv ${STENV_RELEASES[choice]}
                break
                ;;
            [bB])
                break
                ;;
            [qQ])
                exit 0
                ;;
            *)
                echo >&2
                echo "Invalid selection" >&2
                echo >&2
                ;;
        esac
    done
}

# Perform stenv conda environment creation
# arguments:
#  url: URL of remote stenv YAML configuration
# returns status of "$conda env create"
task_create_stenv() {
    local choice
    local name
    local url="$1"
    read st_name st_platform st_python_version st_version st_type \
        <<< $(get_stenv_components $url)

    name="stenv_py${st_python_version/./}"

    if have_conda_env $name; then
        echo
        read -p "$name environment exists, do you wish to replace it ([y/N])? " choice
        case "$choice" in
            [yY])
                $conda env remove --yes --name "$name"
                # fall through to environment creation
                ;;
            *)
                echo
                return 1
                ;;
        esac
    fi

    echo "Using $url"
    $conda env create --name "$name" --file "$url"
}

# main()

# Populate release array for host system
for x in $(get_stenv_releases | sort -V); do
    if [[ "$x" =~ .*-"$(get_stenv_platform)-".* ]]; then
        STENV_RELEASES+=($x)
    fi
done

# Load the interactive menu system
menu_main