osx_utils.sh 13.3 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
11
12
13
MACPYTHON_URL=https://www.python.org/ftp/python
MACPYTHON_PY_PREFIX=/Library/Frameworks/Python.framework/Versions
GET_PIP_URL=https://bootstrap.pypa.io/get-pip.py
WORKING_SDIR=working

Andrew Murray's avatar
Andrew Murray committed
14
# As of 24 September 2020 - latest Python of each version with binary download
Matthew Brett's avatar
Matthew Brett committed
15
# available.
16
# See: https://www.python.org/downloads/mac-osx/
robbuckley's avatar
robbuckley committed
17
LATEST_2p7=2.7.18
Andrew Murray's avatar
Andrew Murray committed
18
LATEST_3p5=3.5.4
Andrew Murray's avatar
Andrew Murray committed
19
LATEST_3p6=3.6.8
Andrew Murray's avatar
Andrew Murray committed
20
LATEST_3p7=3.7.9
Andrew Murray's avatar
Andrew Murray committed
21
LATEST_3p8=3.8.6
Matthew Brett's avatar
Matthew Brett committed
22

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

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
78
79
80
    elif [ $ver == 3 ] || [ $ver == "3.8" ]; then
        echo $LATEST_3p8
    elif [ $ver == "3.7" ]; then
Andrew Murray's avatar
Andrew Murray committed
81
82
        echo $LATEST_3p7
    elif [ $ver == "3.6" ]; then
Matthew Brett's avatar
Matthew Brett committed
83
84
        echo $LATEST_3p6
    elif [ $ver == "3.5" ]; then
Matthew Brett's avatar
Matthew Brett committed
85
86
        echo $LATEST_3p5
    else
87
        echo "Can't fill version $ver" 1>&2
Matthew Brett's avatar
Matthew Brett committed
88
89
90
91
        exit 1
    fi
}

92
93
94
95
96
97
98
99
100
101
102
103
function macpython_sdk_list_for_version {
    # return a list of SDK targets supported for a given CPython version
    # Parameters
    #   $py_version (python version in major.minor.extra format)
    # eg
    #  macpython_sdks_for_version 2.7.15
    #  >> 10.6 10.9
    local _ver=$(fill_pyver $1)
    local _major=${_ver%%.*}
    local _return

    if [ "$_major" -eq "2" ]; then
robbuckley's avatar
robbuckley committed
104
        [ $(lex_ver $_ver) -lt $(lex_ver 2.7.18) ] && _return="10.6"
105
106
107
108
109
110
111
112
113
114
115
116
117
        [ $(lex_ver $_ver) -ge $(lex_ver 2.7.15) ] && _return="$_return 10.9"
    elif [ "$_major" -eq "3" ]; then
        [ $(lex_ver $_ver) -lt $(lex_ver 3.8)    ] && _return="10.6"
        [ $(lex_ver $_ver) -ge $(lex_ver 3.6.5)  ] && _return="$_return 10.9"
    else
        echo "Error version=${_ver}, expecting 2.x or 3.x" 1>&2
        exit 1
    fi
    echo $_return
}

function macpython_sdk_for_version {
    # assumes the output of macpython_sdk_list_for_version is a list
Andrew Murray's avatar
Andrew Murray committed
118
    # of SDK versions XX.Y in sorted order, eg "10.6 10.9" or "10.9"
119
120
121
    echo $(macpython_sdk_list_for_version $1) | awk -F' ' '{print $NF}'
}

Matthew Brett's avatar
Matthew Brett committed
122
123
124
125
126
127
128
129
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
130
    py_version=$(fill_pyver $py_version)
Matthew Brett's avatar
Matthew Brett committed
131
132
    local py_0=${py_version:0:1}
    if [ $py_0 -eq 2 ]; then
133
        if [ "$(lex_ver $py_version)" -ge "$(lex_ver 2.7.9)" ]; then
Matthew Brett's avatar
Matthew Brett committed
134
135
136
137
138
            echo "pkg"
        else
            echo "dmg"
        fi
    elif [ $py_0 -ge 3 ]; then
Andrew Murray's avatar
Andrew Murray committed
139
		echo "pkg"
Matthew Brett's avatar
Matthew Brett committed
140
141
142
    fi
}

143
function pyinst_fname_for_version {
144
    # echo filename for OSX installer file given Python and minimum
145
    # macOS versions
146
    # Parameters
147
    #   $py_version (Python version in major.minor.extra format)
148
    #   $py_osx_ver: {major.minor | not defined}
149
150
151
    #       if defined, the minimum macOS SDK version that Python is
    #       built for, eg: "10.6" or "10.9", if not defined, infers
    #       this from $py_version using macpython_sdk_for_version
152
    local py_version=$1
153
    local py_osx_ver=${2:-$(macpython_sdk_for_version $py_version)}
154
    local inst_ext=$(pyinst_ext_for_version $py_version)
155
    echo "python-${py_version}-macosx${py_osx_ver}.${inst_ext}"
156
157
}

158
function get_macpython_arch {
robbuckley's avatar
robbuckley committed
159
160
161
162
163
164
    # 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
165
    # Note: MUST only be called after the version of Python used to build the
robbuckley's avatar
robbuckley committed
166
167
    # target wheel has been installed and is on the path
    local distutils_plat=${1:-$(get_distutils_platform)}
168
169
170
    if [[ $distutils_plat =~ macosx-(10\.[0-9]+)-(.*) ]]; then
        echo ${BASH_REMATCH[2]}
    else
robbuckley's avatar
robbuckley committed
171
        echo "Error parsing macOS distutils platform '$distutils_plat'"
172
173
174
175
176
        exit 1
    fi
}

function get_macpython_osx_ver {
robbuckley's avatar
robbuckley committed
177
178
179
180
181
182
    # 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
183
    # Note: MUST only be called after the version of Python used to build the
robbuckley's avatar
robbuckley committed
184
185
    # target wheel has been installed and is on the path
    local distutils_plat=${1:-$(get_distutils_platform)}
186
187
188
    if [[ $distutils_plat =~ macosx-(10\.[0-9]+)-(.*) ]]; then
        echo ${BASH_REMATCH[1]}
    else
robbuckley's avatar
robbuckley committed
189
        echo "Error parsing macOS distutils platform '$distutils_plat'"
190
191
192
        exit 1
    fi
}
193

194
function macpython_arch_for_version {
195
196
    # echo arch (intel or x86_64) that a version of Python is expected
    # to be built for
197
    # Parameters
198
199
    #   $py_ver     Python version, in the format (major.minor.patch) for
    #               CPython, or pypy-(major.minor) for PyPy
200
    #   $py_osx_ver minimum macOS version the target Python is built for
201
    #               (major.minor)
202
203
204
205
206
207
208
209
210
    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
211
            echo "Unexpected CPython macOS version: ${py_osx_ver}, supported values: 10.6 and 10.9"
212
213
214
            exit 1
        fi
    else
Rob Buckley's avatar
Rob Buckley committed
215
        echo "x86_64"
216
217
218
219
220
221
    fi
}

function macpython_impl_for_version {
    # echo Python implementation (cp for CPython, pp for PyPy) given a
    # suitably formatted version string
222
223
    # Parameters:
    #     $version : [implementation-]major[.minor[.patch]]
224
225
    #         Python implementation, e.g. "3.6" for CPython or
    #         "pypy-5.4" for PyPy
226
    local version=$1
227
    check_var $1
mattip's avatar
mattip committed
228
    if [[ "$version" =~ ^pypy ]]; then
229
        echo pp
230
    elif [[ "$version" =~ ([0-9\.]+) ]]; then
231
        echo cp
Kyle Stewart's avatar
Kyle Stewart committed
232
    else
Matt Bachmann's avatar
Matt Bachmann committed
233
        echo "config error: Issue parsing this implementation in install_python:"
234
        echo "    version=$version"
Kyle Stewart's avatar
Kyle Stewart committed
235
236
237
238
        exit 1
    fi
}

239
function strip_macpython_ver_prefix {
240
    # strip any implementation prefix from a Python version string
241
242
243
244
245
246
247
248
249
250
251
    # 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
}

252
253
254
255
function install_macpython {
    # Install Python and set $PYTHON_EXE to the installed executable
    # Parameters:
    #     $version : [implementation-]major[.minor[.patch]]
mattip's avatar
mattip committed
256
    #         The Python implementation to install, e.g. "3.6", "pypy-5.4" or "pypy3.6-7.2"
257
    #     $py_osx_ver: {major.minor | not defined}
258
259
    #       if defined, the macOS version that CPython is built for, e.g.
    #       "10.6" or "10.9". Ignored for PyPy
260
    local version=$1
261
    local py_osx_ver=$2
262
263
    local impl=$(macpython_impl_for_version $version)
    if [[ "$impl" == "pp" ]]; then
mattip's avatar
mattip committed
264
        install_pypy $version
265
    elif [[ "$impl" == "cp" ]]; then
266
        local stripped_ver=$(strip_macpython_ver_prefix $version)
267
        install_mac_cpython $stripped_ver $py_osx_ver
Kyle Stewart's avatar
Kyle Stewart committed
268
    else
269
        echo "Unexpected Python impl: ${impl}"
Kyle Stewart's avatar
Kyle Stewart committed
270
271
272
273
        exit 1
    fi
}

274
function install_mac_cpython {
Matthew Brett's avatar
Matthew Brett committed
275
    # Installs Python.org Python
276
277
278
    # Parameters
    #   $py_version
    #       Version given in major or major.minor or major.minor.micro e.g
Andrew Murray's avatar
Andrew Murray committed
279
    #       "3" or "3.7" or "3.7.1".
280
    #   $py_osx_ver
281
    #       {major.minor | not defined}
282
    #       if defined, the macOS version that Python is built for, e.g.
283
    #        "10.6" or "10.9"
284
    # sets $PYTHON_EXE variable to Python executable
Matthew Brett's avatar
Matthew Brett committed
285
    local py_version=$(fill_pyver $1)
Rob Buckley's avatar
Rob Buckley committed
286
    local py_osx_ver=$2
287
    local py_stripped=$(strip_ver_suffix $py_version)
288
    local py_inst=$(pyinst_fname_for_version $py_version $py_osx_ver)
Matthew Brett's avatar
Matthew Brett committed
289
290
    local inst_path=$DOWNLOADS_SDIR/$py_inst
    mkdir -p $DOWNLOADS_SDIR
291
    curl $MACPYTHON_URL/$py_stripped/${py_inst} > $inst_path
292
    if [ "${py_inst: -3}" == "dmg" ]; then
Matthew Brett's avatar
Matthew Brett committed
293
294
295
296
297
298
        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
299
300
301
302
303
    # 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
304
305
306
307
308
309
310
311
312
313
314
}

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
315
    VIRTUALENV_CMD="$(dirname $PYTHON_EXE)/virtualenv"
Matthew Brett's avatar
Matthew Brett committed
316
317
318
319
320
321
322
323
324
325
326
327
328
}

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
329
    $PYTHON_EXE -m virtualenv $venv_dir
Matthew Brett's avatar
Matthew Brett committed
330
331
332
333
334
335
    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
336
337
    # 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
338
339
340
341
342
343
344
345
        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 {
346
    # Used by terryfy project; left here for back-compatibility
Matthew Brett's avatar
Matthew Brett committed
347
348
349
350
    export PATH="`dirname $PYTHON_EXE`:$PATH"
    export PYTHON_EXE PIP_CMD
}

351
function get_macpython_environment {
Matthew Brett's avatar
Matthew Brett committed
352
353
    # Set up MacPython environment
    # Parameters:
354
355
    #     $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
356
357
358
    #     $venv_dir : {directory_name|not defined}
    #         If defined - make virtualenv in this directory, set python / pip
    #         commands accordingly
359
    #     $py_osx_ver: {major.minor | not defined}
360
361
    #         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
362
363
364
365
366
367
    #
    # 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
368
369
    local version=$1
    local venv_dir=$2
370
    local py_osx_ver=${3:-$MB_PYTHON_OSX_VER}
xoviat's avatar
xoviat committed
371
372
373
374

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

Matthew Brett's avatar
Matthew Brett committed
376
    remove_travis_ve_pip
377
    install_macpython $version $py_osx_ver
Matthew Brett's avatar
Matthew Brett committed
378
    install_pip
xoviat's avatar
xoviat committed
379

Matthew Brett's avatar
Matthew Brett committed
380
381
382
    if [ -n "$venv_dir" ]; then
        install_virtualenv
        make_workon_venv $venv_dir
383
384
385
        source $venv_dir/bin/activate
    else
        export PATH="`dirname $PYTHON_EXE`:$PATH"
Matthew Brett's avatar
Matthew Brett committed
386
    fi
387
    export PYTHON_EXE PIP_CMD
Matthew Brett's avatar
Matthew Brett committed
388
}
389

390
function install_delocate {
391
392
    check_pip
    $PIP_CMD install delocate
393
394
395
396
397
}

function repair_wheelhouse {
    local wheelhouse=$1
    install_delocate
398
399
    delocate-wheel $wheelhouse/*.whl # copies library dependencies into wheel
}
400
401
402
403
404
405
406

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
407
408

function activate_ccache {
xoviat's avatar
xoviat committed
409

xoviat's avatar
xoviat committed
410
411
412
413
414
    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
415
    echo "Using C compiler: $(which clang)"
xoviat's avatar
xoviat committed
416
}