1#!/bin/sh
2# This Source Code Form is subject to the terms of the Mozilla Public
3# License, v. 2.0. If a copy of the MPL was not distributed with this
4# file, You can obtain one at http://mozilla.org/MPL/2.0/.
5
6# The beginning of this script is both valid POSIX shell and valid Python,
7# such that the script starts with the shell and is reexecuted with
8# the right Python.
9
10# Embeds a shell script inside a Python triple quote. This pattern is valid
11# shell because `''':'`, `':'` and `:` are all equivalent, and `:` is a no-op.
12''':'
13
14# Commands that are to be run with the system Python 3 instead of the
15# virtualenv.
16nativecmds="
17    bootstrap
18    create-mach-environment
19    install-moz-phab
20"
21
22run_py() {
23    # Try to run a specific Python interpreter.
24    py_executable="$1"
25    shift
26    if command -v "$py_executable" > /dev/null
27    then
28        exec "$py_executable" $py_profile_command_args "$0" "$@"
29    else
30        echo "This mach command requires $py_executable, which wasn't found on the system!"
31        case "$py_executable" in
32            python3) ;;
33            *)
34                echo "Consider running 'mach bootstrap' or 'mach create-mach-environment' to create the mach virtualenvs, or set MACH_USE_SYSTEM_PYTHON to use the system Python installation over a virtualenv."
35                ;;
36        esac
37        exit 1
38    fi
39}
40
41get_command() {
42    # Parse the name of the mach command out of the arguments. This is necessary
43    # in the presence of global mach arguments that come before the name of the
44    # command, e.g. `mach -v build`. We dispatch to the correct Python
45    # interpreter depending on the command.
46    while true; do
47    case $1 in
48        -v|--verbose) shift;;
49        -l|--log-file)
50            if [ "$#" -lt 2 ]
51            then
52                echo
53                break
54            else
55                shift 2
56            fi
57            ;;
58        --no-interactive) shift;;
59        --log-interval) shift;;
60        --log-no-times) shift;;
61        -h) shift;;
62        --debug-command) shift;;
63        --profile-command)
64            py_profile_command="1"
65            shift;;
66        --settings)
67            if [ "$#" -lt 2 ]
68            then
69                echo
70                break
71            else
72                shift 2
73            fi
74            ;;
75        "") echo; break;;
76        *) echo $1; break;;
77    esac
78    done
79    return ${py_profile_command}
80}
81
82state_dir=${MOZBUILD_STATE_PATH:-~/.mozbuild}
83command=$(get_command "$@")
84py_profile_command=$?
85
86if [ ${py_profile_command} -eq 0 ]
87then
88    py_profile_command_args=""
89else
90    # We would prefer to use an array variable here, but we're limited to POSIX.
91    # None of our arguments have quoting or spaces so we can safely interpolate
92    # a string instead.
93    py_profile_command_args="-m cProfile -o mach_profile_${command}.cProfile"
94    echo "Running with --profile-command.  To visualize, use snakeviz:"
95    echo "$HOME/.mozbuild/_virtualenvs/mach/bin/python -m pip install snakeviz"
96    echo "$HOME/.mozbuild/_virtualenvs/mach/bin/python -m snakeviz mach_profile_${command}.cProfile"
97fi
98
99# If MACH_USE_SYSTEM_PYTHON or MOZ_AUTOMATION are set, always use the
100# python 3 executables and not the virtualenv locations.
101if [ -z ${MACH_USE_SYSTEM_PYTHON} ] && [ -z ${MOZ_AUTOMATION} ]
102then
103    case "$OSTYPE" in
104        cygwin|msys|win32) bin_path=Scripts;;
105        *) bin_path=bin;;
106    esac
107    py3executable=$state_dir/_virtualenvs/mach/$bin_path/python
108else
109    py3executable=python3
110fi
111
112# Check whether we need to run with the native Python 3 interpreter.
113case " $(echo $nativecmds) " in
114    *\ $command\ *)
115        run_py python3 "$@"
116        ;;
117esac
118
119# # Use the mach virtualenv's Python 3 for the rest of the commands.
120run_py "$py3executable" "$@"
121'''
122
123from __future__ import absolute_import, print_function, unicode_literals
124
125import os
126import sys
127
128def load_mach(dir_path, mach_path):
129    if sys.version_info < (3, 5):
130        import imp
131        mach_bootstrap = imp.load_source('mach_bootstrap', mach_path)
132    else:
133        import importlib.util
134        spec = importlib.util.spec_from_file_location('mach_bootstrap', mach_path)
135        mach_bootstrap = importlib.util.module_from_spec(spec)
136        spec.loader.exec_module(mach_bootstrap)
137
138    return mach_bootstrap.bootstrap(dir_path)
139
140
141def check_and_get_mach(dir_path):
142    bootstrap_paths = (
143        'build/mach_bootstrap.py',
144        # test package bootstrap
145        'tools/mach_bootstrap.py',
146    )
147    for bootstrap_path in bootstrap_paths:
148        mach_path = os.path.join(dir_path, bootstrap_path)
149        if os.path.isfile(mach_path):
150            return load_mach(dir_path, mach_path)
151    return None
152
153
154def main(args):
155    # XCode python sets __PYVENV_LAUNCHER__, which overrides the executable
156    # used when a python subprocess is created. This is an issue when we want
157    # to run using our virtualenv python executables.
158    # In future Python relases, __PYVENV_LAUNCHER__ will be cleared before
159    # application code (mach) is started.
160    # https://github.com/python/cpython/pull/9516
161    os.environ.pop("__PYVENV_LAUNCHER__", None)
162
163    mach = check_and_get_mach(os.path.dirname(os.path.realpath(__file__)))
164    if not mach:
165        print('Could not run mach: No mach source directory found.')
166        sys.exit(1)
167    sys.exit(mach.run(args))
168
169
170if __name__ == '__main__':
171    main(sys.argv[1:])
172