osx_utils.sh 13.8 KB
Newer Older
Matthew Brett's avatar
Matthew Brett committed
1
2
3
4
#!/bin/bash
# Use with ``source osx_utils.sh``
set -e

5
6
7
8
# Get our own location on this filesystem, load common utils
MULTIBUILD_DIR=$(dirname "${BASH_SOURCE[0]}")
source $MULTIBUILD_DIR/common_utils.sh

Matthew Brett's avatar
Matthew Brett committed
9
10
MACPYTHON_URL=https://www.python.org/ftp/python
MACPYTHON_PY_PREFIX=/Library/Frameworks/Python.framework/Versions
11
MACPYTHON_DEFAULT_OSX="10.6"
12
MB_PYTHON_OSX_VER=${MB_PYTHON_OSX_VER:-$MACPYTHON_DEFAULT_OSX}
Matthew Brett's avatar
Matthew Brett committed
13
14
15
16
GET_PIP_URL=https://bootstrap.pypa.io/get-pip.py
DOWNLOADS_SDIR=downloads
WORKING_SDIR=working

mattip's avatar
mattip committed
17
# As of 14 Oct 2019 - latest Python of each version with binary download
Matthew Brett's avatar
Matthew Brett committed
18
# available.
19
# See: https://www.python.org/downloads/mac-osx/
Andrew Murray's avatar
Andrew Murray committed
20
LATEST_2p7=2.7.16
Andrew Murray's avatar
Andrew Murray committed
21
LATEST_3p5=3.5.4
Andrew Murray's avatar
Andrew Murray committed
22
LATEST_3p6=3.6.8
Andrew Murray's avatar
Andrew Murray committed
23
LATEST_3p7=3.7.4
mattip's avatar
mattip committed
24
LATEST_3p8=3.8.0
Matthew Brett's avatar
Matthew Brett committed
25

Matthew Brett's avatar
Matthew Brett committed
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

function check_python {
    if [ -z "$PYTHON_EXE" ]; then
        echo "PYTHON_EXE variable not defined"
        exit 1
    fi
}

function check_pip {
    if [ -z "$PIP_CMD" ]; then
        echo "PIP_CMD variable not defined"
        exit 1
    fi
}

function check_var {
    if [ -z "$1" ]; then
        echo "required variable not defined"
        exit 1
    fi
}

function get_py_digit {
    check_python
    $PYTHON_EXE -c "import sys; print(sys.version_info[0])"
}

function get_py_mm {
    check_python
    $PYTHON_EXE -c "import sys; print('{0}.{1}'.format(*sys.version_info[0:2]))"
}

function get_py_mm_nodot {
    check_python
    $PYTHON_EXE -c "import sys; print('{0}{1}'.format(*sys.version_info[0:2]))"
}

function get_py_prefix {
    check_python
    $PYTHON_EXE -c "import sys; print(sys.prefix)"
}

function fill_pyver {
    # Convert major or major.minor format to major.minor.micro
    #
    # Hence:
    # 2 -> 2.7.11  (depending on LATEST_2p7 value)
    # 2.7 -> 2.7.11  (depending on LATEST_2p7 value)
    local ver=$1
    check_var $ver
    if [[ $ver =~ [0-9]+\.[0-9]+\.[0-9]+ ]]; then
        # Major.minor.micro format already
        echo $ver
    elif [ $ver == 2 ] || [ $ver == "2.7" ]; then
        echo $LATEST_2p7
Andrew Murray's avatar
Andrew Murray committed
81
82
83
    elif [ $ver == 3 ] || [ $ver == "3.7" ]; then
        echo $LATEST_3p7
    elif [ $ver == "3.6" ]; then
Matthew Brett's avatar
Matthew Brett committed
84
85
        echo $LATEST_3p6
    elif [ $ver == "3.5" ]; then
Matthew Brett's avatar
Matthew Brett committed
86
        echo $LATEST_3p5
mattip's avatar
mattip committed
87
88
    elif [ $ver == "3.8" ]; then
        echo $LATEST_3p8
Matthew Brett's avatar
Matthew Brett committed
89
    else
90
        echo "Can't fill version $ver" 1>&2
Matthew Brett's avatar
Matthew Brett committed
91
92
93
94
95
96
97
98
99
100
101
102
        exit 1
    fi
}

function pyinst_ext_for_version {
    # echo "pkg" or "dmg" depending on the passed Python version
    # Parameters
    #   $py_version (python version in major.minor.extra format)
    #
    # Earlier Python installers are .dmg, later are .pkg.
    local py_version=$1
    check_var $py_version
103
    py_version=$(fill_pyver $py_version)
Matthew Brett's avatar
Matthew Brett committed
104
105
    local py_0=${py_version:0:1}
    if [ $py_0 -eq 2 ]; then
106
        if [ "$(lex_ver $py_version)" -ge "$(lex_ver 2.7.9)" ]; then
Matthew Brett's avatar
Matthew Brett committed
107
108
109
110
111
            echo "pkg"
        else
            echo "dmg"
        fi
    elif [ $py_0 -ge 3 ]; then
Andrew Murray's avatar
Andrew Murray committed
112
		echo "pkg"
Matthew Brett's avatar
Matthew Brett committed
113
114
115
    fi
}

116
function pyinst_fname_for_version {
117
    # echo filename for OSX installer file given Python and minimum
118
    # macOS versions
119
    # Parameters
120
    #   $py_version (Python version in major.minor.extra format)
121
    #   $py_osx_ver: {major.minor | not defined}
122
    #       if defined, the macOS version that Python is built for, e.g.
123
124
125
126
    #       "10.6" or "10.9", if not defined, uses the default
    #       MACPYTHON_DEFAULT_OSX
    #       Note: this is the version the Python is built for, and hence
    #       the min version supported, NOT the version of the current system
127
    local py_version=$1
128
    local py_osx_ver=${2:-$MACPYTHON_DEFAULT_OSX}
129
    local inst_ext=$(pyinst_ext_for_version $py_version)
130
    echo "python-${py_version}-macosx${py_osx_ver}.${inst_ext}"
131
132
}

133
function get_macpython_arch {
robbuckley's avatar
robbuckley committed
134
135
136
137
138
139
    # echo arch (e.g. intel or x86_64), extracted from the distutils platform tag
    # Parameters
    #   $distutils_plat   PEP425 style platform tag, or if not provided, calls
    #                       the function get_distutils_platform, provided by
    #                       common_utils.sh. Fails if this is not a mac platform
    #
Andrew Murray's avatar
Andrew Murray committed
140
    # Note: MUST only be called after the version of Python used to build the
robbuckley's avatar
robbuckley committed
141
142
    # target wheel has been installed and is on the path
    local distutils_plat=${1:-$(get_distutils_platform)}
143
144
145
    if [[ $distutils_plat =~ macosx-(10\.[0-9]+)-(.*) ]]; then
        echo ${BASH_REMATCH[2]}
    else
robbuckley's avatar
robbuckley committed
146
        echo "Error parsing macOS distutils platform '$distutils_plat'"
147
148
149
150
151
        exit 1
    fi
}

function get_macpython_osx_ver {
robbuckley's avatar
robbuckley committed
152
153
154
155
156
157
    # echo minimum macOS version (e.g. 10.9) from the distutils platform tag
    # Parameters
    #   $distutils_plat   PEP425 style platform tag, or if not provided, calls
    #                       the function get_distutils_platform, provided by
    #                       common_utils.sh. Fails if this is not a mac platform
    #
Andrew Murray's avatar
Andrew Murray committed
158
    # Note: MUST only be called after the version of Python used to build the
robbuckley's avatar
robbuckley committed
159
160
    # target wheel has been installed and is on the path
    local distutils_plat=${1:-$(get_distutils_platform)}
161
162
163
    if [[ $distutils_plat =~ macosx-(10\.[0-9]+)-(.*) ]]; then
        echo ${BASH_REMATCH[1]}
    else
robbuckley's avatar
robbuckley committed
164
        echo "Error parsing macOS distutils platform '$distutils_plat'"
165
166
167
        exit 1
    fi
}
168

169
function macpython_arch_for_version {
170
171
    # echo arch (intel or x86_64) that a version of Python is expected
    # to be built for
172
    # Parameters
173
174
    #   $py_ver     Python version, in the format (major.minor.patch) for
    #               CPython, or pypy-(major.minor) for PyPy
175
    #   $py_osx_ver minimum macOS version the target Python is built for
176
    #               (major.minor)
177
178
179
180
181
182
183
184
185
    local py_ver=$1
    local py_osx_ver=${2:-$MB_PYTHON_OSX_VER}
    check_var $1
    if [[ $(macpython_impl_for_version $py_ver) == "cp" ]]; then
        if [[ "$py_osx_ver" == "10.6" ]]; then
            echo "intel"
        elif [[ "$py_osx_ver" == "10.9" ]]; then
            echo "x86_64"
        else
186
            echo "Unexpected CPython macOS version: ${py_osx_ver}, supported values: 10.6 and 10.9"
187
188
189
            exit 1
        fi
    else
Rob Buckley's avatar
Rob Buckley committed
190
        echo "x86_64"
191
192
193
194
195
196
    fi
}

function macpython_impl_for_version {
    # echo Python implementation (cp for CPython, pp for PyPy) given a
    # suitably formatted version string
197
198
    # Parameters:
    #     $version : [implementation-]major[.minor[.patch]]
199
200
    #         Python implementation, e.g. "3.6" for CPython or
    #         "pypy-5.4" for PyPy
201
    local version=$1
202
    check_var $1
203
    if [[ "$version" =~ pypy-([0-9\.]+) ]]; then
204
        echo pp
205
    elif [[ "$version" =~ ([0-9\.]+) ]]; then
206
        echo cp
Kyle Stewart's avatar
Kyle Stewart committed
207
    else
Matt Bachmann's avatar
Matt Bachmann committed
208
        echo "config error: Issue parsing this implementation in install_python:"
209
        echo "    version=$version"
Kyle Stewart's avatar
Kyle Stewart committed
210
211
212
213
        exit 1
    fi
}

214
function strip_macpython_ver_prefix {
215
    # strip any implementation prefix from a Python version string
216
217
218
219
220
221
222
223
224
225
226
    # Parameters:
    #     $version : [implementation-]major[.minor[.patch]]
    #         Python implementation, e.g. "3.6" for CPython or
    #         "pypy-5.4" for PyPy
    local version=$1
    check_var $1
    if [[ "$version" =~ (pypy-)?([0-9\.]+) ]]; then
        echo ${BASH_REMATCH[2]}
    fi
}

227
228
229
230
231
function install_macpython {
    # Install Python and set $PYTHON_EXE to the installed executable
    # Parameters:
    #     $version : [implementation-]major[.minor[.patch]]
    #         The Python implementation to install, e.g. "3.6" or "pypy-5.4"
232
    #     $py_osx_ver: {major.minor | not defined}
233
234
    #       if defined, the macOS version that CPython is built for, e.g.
    #       "10.6" or "10.9". Ignored for PyPy
235
    local version=$1
236
    local py_osx_ver=$2
237
238
239
240
241
242
    local impl=$(macpython_impl_for_version $version)
    local stripped_ver=$(strip_macpython_ver_prefix $version)
    if [[ "$impl" == "pp" ]]; then
        install_mac_pypy $stripped_ver
    elif [[ "$impl" == "cp" ]]; then
        install_mac_cpython $stripped_ver $py_osx_ver
Kyle Stewart's avatar
Kyle Stewart committed
243
    else
244
        echo "Unexpected Python impl: ${impl}"
Kyle Stewart's avatar
Kyle Stewart committed
245
246
247
248
        exit 1
    fi
}

249
function install_mac_cpython {
Matthew Brett's avatar
Matthew Brett committed
250
    # Installs Python.org Python
251
252
253
    # Parameters
    #   $py_version
    #       Version given in major or major.minor or major.minor.micro e.g
Andrew Murray's avatar
Andrew Murray committed
254
    #       "3" or "3.7" or "3.7.1".
255
    #   $py_osx_ver
256
    #       {major.minor | not defined}
257
    #       if defined, the macOS version that Python is built for, e.g.
258
    #        "10.6" or "10.9"
259
    # sets $PYTHON_EXE variable to Python executable
Matthew Brett's avatar
Matthew Brett committed
260
    local py_version=$(fill_pyver $1)
Rob Buckley's avatar
Rob Buckley committed
261
    local py_osx_ver=$2
262
    local py_stripped=$(strip_ver_suffix $py_version)
263
    local py_inst=$(pyinst_fname_for_version $py_version $py_osx_ver)
Matthew Brett's avatar
Matthew Brett committed
264
265
    local inst_path=$DOWNLOADS_SDIR/$py_inst
    mkdir -p $DOWNLOADS_SDIR
266
    curl $MACPYTHON_URL/$py_stripped/${py_inst} > $inst_path
267
    if [ "${py_inst: -3}" == "dmg" ]; then
Matthew Brett's avatar
Matthew Brett committed
268
269
270
271
272
273
        hdiutil attach $inst_path -mountpoint /Volumes/Python
        inst_path=/Volumes/Python/Python.mpkg
    fi
    sudo installer -pkg $inst_path -target /
    local py_mm=${py_version:0:3}
    PYTHON_EXE=$MACPYTHON_PY_PREFIX/$py_mm/bin/python$py_mm
274
275
276
277
278
    # Install certificates for Python 3.6
    local inst_cmd="/Applications/Python ${py_mm}/Install Certificates.command"
    if [ -e "$inst_cmd" ]; then
        sh "$inst_cmd"
    fi
Matthew Brett's avatar
Matthew Brett committed
279
280
}

Kyle Stewart's avatar
Kyle Stewart committed
281
282
283
284
function install_mac_pypy {
    # Installs pypy.org PyPy
    # Parameter $version
    # Version given in major or major.minor or major.minor.micro e.g
Andrew Murray's avatar
Andrew Murray committed
285
    # "3" or "3.7" or "3.7.1".
Kyle Stewart's avatar
Kyle Stewart committed
286
287
288
289
290
291
292
293
294
295
296
    # sets $PYTHON_EXE variable to python executable
    local py_version=$(fill_pypy_ver $1)
    local py_build=$(get_pypy_build_prefix $py_version)$py_version-osx64
    local py_zip=$py_build.tar.bz2
    local zip_path=$DOWNLOADS_SDIR/$py_zip
    mkdir -p $DOWNLOADS_SDIR
    wget -nv $PYPY_URL/${py_zip} -P $DOWNLOADS_SDIR
    untar $zip_path
    PYTHON_EXE=$(realpath $py_build/bin/pypy)
}

Matthew Brett's avatar
Matthew Brett committed
297
298
299
300
301
302
303
304
function install_pip {
    # Generic install pip
    # Gets needed version from version implied by $PYTHON_EXE
    # Installs pip into python given by $PYTHON_EXE
    # Assumes pip will be installed into same directory as $PYTHON_EXE
    check_python
    mkdir -p $DOWNLOADS_SDIR
    local py_mm=`get_py_mm`
305
    local get_pip_path=$DOWNLOADS_SDIR/get-pip.py
Hugo's avatar
Hugo committed
306
    curl $GET_PIP_URL > $get_pip_path
307
308
    # Travis VMS now install pip for system python by default - force install
    # even if installed already.
309
    sudo $PYTHON_EXE $get_pip_path --ignore-installed $pip_args
310
311
312
313
314
315
    PIP_CMD="sudo $(dirname $PYTHON_EXE)/pip$py_mm"
    # Append pip_args if present (avoiding trailing space cf using variable
    # above).
    if [ -n "$pip_args" ]; then
        PIP_CMD="$PIP_CMD $pip_args"
    fi
Matthew Brett's avatar
Matthew Brett committed
316
317
318
319
320
321
322
323
324
325
326
}

function install_virtualenv {
    # Generic install of virtualenv
    # Installs virtualenv into python given by $PYTHON_EXE
    # Assumes virtualenv will be installed into same directory as $PYTHON_EXE
    check_pip
    # Travis VMS install virtualenv for system python by default - force
    # install even if installed already
    $PIP_CMD install virtualenv --ignore-installed
    check_python
327
    VIRTUALENV_CMD="$(dirname $PYTHON_EXE)/virtualenv"
Matthew Brett's avatar
Matthew Brett committed
328
329
330
331
332
333
334
335
336
337
338
339
340
}

function make_workon_venv {
    # Make a virtualenv in given directory ('venv' default)
    # Set $PYTHON_EXE, $PIP_CMD to virtualenv versions
    # Parameter $venv_dir
    #    directory for virtualenv
    local venv_dir=$1
    if [ -z "$venv_dir" ]; then
        venv_dir="venv"
    fi
    venv_dir=`abspath $venv_dir`
    check_python
xoviat's avatar
xoviat committed
341
    $PYTHON_EXE -m virtualenv $venv_dir
Matthew Brett's avatar
Matthew Brett committed
342
343
344
345
346
347
    PYTHON_EXE=$venv_dir/bin/python
    PIP_CMD=$venv_dir/bin/pip
}

function remove_travis_ve_pip {
    # Remove travis installs of virtualenv and pip
xoviat's avatar
xoviat committed
348
349
    # FIXME: What if virtualenv is installed but pip is not?
    if [ "$(sudo which virtualenv)" == /usr/local/bin/virtualenv ] && [ "$(sudo which pip)" == /usr/local/bin/pip ]; then
Matthew Brett's avatar
Matthew Brett committed
350
351
352
353
354
355
356
357
        sudo pip uninstall -y virtualenv;
    fi
    if [ "$(sudo which pip)" == /usr/local/bin/pip ]; then
        sudo pip uninstall -y pip;
    fi
}

function set_py_vars {
358
    # Used by terryfy project; left here for back-compatibility
Matthew Brett's avatar
Matthew Brett committed
359
360
361
362
    export PATH="`dirname $PYTHON_EXE`:$PATH"
    export PYTHON_EXE PIP_CMD
}

363
function get_macpython_environment {
Matthew Brett's avatar
Matthew Brett committed
364
365
    # Set up MacPython environment
    # Parameters:
366
367
    #     $version : [implementation-]major[.minor[.patch]]
    #         The Python implementation to install, e.g. "3.6" or "pypy-5.4"
Matthew Brett's avatar
Matthew Brett committed
368
369
370
    #     $venv_dir : {directory_name|not defined}
    #         If defined - make virtualenv in this directory, set python / pip
    #         commands accordingly
371
    #     $py_osx_ver: {major.minor | not defined}
372
373
    #         if defined, the macOS version that Python is built for, e.g.
    #         "10.6" or "10.9", if not defined, use the version from MB_PYTHON_OSX_VER
Matthew Brett's avatar
Matthew Brett committed
374
375
376
377
378
379
    #
    # Installs Python
    # Sets $PYTHON_EXE to path to Python executable
    # Sets $PIP_CMD to full command for pip (including sudo if necessary)
    # If $venv_dir defined, Sets $VIRTUALENV_CMD to virtualenv executable
    # Puts directory of $PYTHON_EXE on $PATH
380
381
    local version=$1
    local venv_dir=$2
382
    local py_osx_ver=${3:-$MB_PYTHON_OSX_VER}
xoviat's avatar
xoviat committed
383
384
385
386

    if [ "$USE_CCACHE" == "1" ]; then
        activate_ccache
    fi
387

Matthew Brett's avatar
Matthew Brett committed
388
    remove_travis_ve_pip
389
    install_macpython $version $py_osx_ver
Matthew Brett's avatar
Matthew Brett committed
390
    install_pip
xoviat's avatar
xoviat committed
391

Matthew Brett's avatar
Matthew Brett committed
392
393
394
    if [ -n "$venv_dir" ]; then
        install_virtualenv
        make_workon_venv $venv_dir
395
396
397
        source $venv_dir/bin/activate
    else
        export PATH="`dirname $PYTHON_EXE`:$PATH"
Matthew Brett's avatar
Matthew Brett committed
398
    fi
399
    export PYTHON_EXE PIP_CMD
Matthew Brett's avatar
Matthew Brett committed
400
}
401

402
function install_delocate {
403
404
    check_pip
    $PIP_CMD install delocate
405
406
407
408
409
}

function repair_wheelhouse {
    local wheelhouse=$1
    install_delocate
410
411
    delocate-wheel $wheelhouse/*.whl # copies library dependencies into wheel
}
412
413
414
415
416
417
418

function install_pkg_config {
    # Install pkg-config avoiding error from homebrew
    # See :
    # https://github.com/matthew-brett/multibuild/issues/24#issue-221951587
    command -v pkg-config > /dev/null 2>&1 || brew install pkg-config
}
xoviat's avatar
xoviat committed
419
420

function activate_ccache {
xoviat's avatar
xoviat committed
421

xoviat's avatar
xoviat committed
422
423
424
425
426
    brew install ccache
    export PATH=/usr/local/opt/ccache/libexec:$PATH
    export CCACHE_CPP2=1

    # Prove to the developer that ccache is activated
xoviat's avatar
xoviat committed
427
    echo "Using C compiler: $(which clang)"
xoviat's avatar
xoviat committed
428
}