#!/bin/sh # This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this # file, You can obtain one at http://mozilla.org/MPL/2.0/. # The beginning of this script is both valid POSIX shell and valid Python, # such that the script starts with the shell and is reexecuted with # the right Python. # Embeds a shell script inside a Python triple quote. This pattern is valid # shell because `''':'`, `':'` and `:` are all equivalent, and `:` is a no-op. ''':' # Commands that are to be run with Python 2. py2commands=" awsy-test firefox-ui-functional geckodriver geckodriver-test jsshell-bench marionette-test jstests mozharness raptor raptor-test telemetry-tests-client test wpt-metadata-merge " # Commands that are to be run with the system Python 3 instead of the # virtualenv. nativecmds=" bootstrap create-mach-environment install-moz-phab " run_py() { # Try to run a specific Python interpreter. py_executable="$1" shift if command -v "$py_executable" > /dev/null then exec "$py_executable" "$0" "$@" else echo "This mach command requires $py_executable, which wasn't found on the system!" case "$py_executable" in python2.7|python3) ;; *) 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." ;; esac exit 1 fi } get_command() { # Parse the name of the mach command out of the arguments. This is necessary # in the presence of global mach arguments that come before the name of the # command, e.g. `mach -v build`. We dispatch to the correct Python # interpreter depending on the command. while true; do case $1 in -v|--verbose) shift;; -l|--log-file) if [ "$#" -lt 2 ] then echo break else shift 2 fi ;; --log-interval) shift;; --log-no-times) shift;; -h) shift;; --debug-command) shift;; --settings) if [ "$#" -lt 2 ] then echo break else shift 2 fi ;; # When running `./mach help `, the correct Python for # needs to be used. help) echo $2; break;; # When running `./mach mach-completion /path/to/mach `, the # correct Python for needs to be used. mach-completion) echo $3; break;; "") echo; break;; *) echo $1; break;; esac done } state_dir=${MOZBUILD_STATE_PATH:-~/.mozbuild} command=$(get_command "$@") # If MACH_USE_SYSTEM_PYTHON or MOZ_AUTOMATION are set, always use the # python{2.7,3} executables and not the virtualenv locations. if [ -z ${MACH_USE_SYSTEM_PYTHON} ] && [ -z ${MOZ_AUTOMATION} ] then case "$OSTYPE" in cygwin|msys|win32) bin_path=Scripts;; *) bin_path=bin;; esac py2executable=$state_dir/_virtualenvs/mach_py2/$bin_path/python py3executable=$state_dir/_virtualenvs/mach/$bin_path/python else py2executable=python2.7 py3executable=python3 fi # Check whether we need to run with the native Python 3 interpreter. case " $(echo $nativecmds) " in *\ $command\ *) run_py python3 "$@" ;; esac # Check for the mach subcommand in the Python 2 commands list and run it # with the correct interpreter. case " $(echo $py2commands) " in *\ $command\ *) run_py "$py2executable" "$@" ;; *) if [ -z ${MACH_PY2} ] then run_py "$py3executable" "$@" else if [ $command != "python-test" ] then echo "MACH_PY2 is only valid for mach python-test; please unset MACH_PY2 to continue." exit 1 fi run_py "$py2executable" "$@" fi ;; esac # Run Python 3 for everything else. run_py "$py3executable" "$@" ''' from __future__ import absolute_import, print_function, unicode_literals import os import sys def ancestors(path): while path: yield path (path, child) = os.path.split(path) if child == "": break def load_mach(dir_path, mach_path): if sys.version_info < (3, 5): import imp mach_bootstrap = imp.load_source('mach_bootstrap', mach_path) else: import importlib.util spec = importlib.util.spec_from_file_location('mach_bootstrap', mach_path) mach_bootstrap = importlib.util.module_from_spec(spec) spec.loader.exec_module(mach_bootstrap) return mach_bootstrap.bootstrap(dir_path) def check_and_get_mach(dir_path): bootstrap_paths = ( 'build/mach_bootstrap.py', # test package bootstrap 'tools/mach_bootstrap.py', ) for bootstrap_path in bootstrap_paths: mach_path = os.path.join(dir_path, bootstrap_path) if os.path.isfile(mach_path): return load_mach(dir_path, mach_path) return None def setdefaultenv(key, value): """Compatibility shim to ensure the proper string type is used with os.environ for the version of Python being used. """ encoding = "mbcs" if sys.platform == "win32" else "utf-8" if sys.version_info[0] == 2: if isinstance(key, unicode): key = key.encode(encoding) if isinstance(value, unicode): value = value.encode(encoding) else: if isinstance(key, bytes): key = key.decode(encoding) if isinstance(value, bytes): value = value.decode(encoding) os.environ.setdefault(key, value) def get_mach(): # Check whether the current directory is within a mach src or obj dir. for dir_path in ancestors(os.getcwd()): # If we find a "config.status" and "mozinfo.json" file, we are in the objdir. config_status_path = os.path.join(dir_path, 'config.status') mozinfo_path = os.path.join(dir_path, 'mozinfo.json') if os.path.isfile(config_status_path) and os.path.isfile(mozinfo_path): import json info = json.load(open(mozinfo_path)) if 'mozconfig' in info: # If the MOZCONFIG environment variable is not already set, set it # to the value from mozinfo.json. This will tell the build system # to look for a config file at the path in $MOZCONFIG rather than # its default locations. setdefaultenv('MOZCONFIG', info['mozconfig']) if 'topsrcdir' in info: # Continue searching for mach_bootstrap in the source directory. dir_path = info['topsrcdir'] mach = check_and_get_mach(dir_path) if mach: return mach # If we didn't find a source path by scanning for a mozinfo.json, check # whether the directory containing this script is a source directory. We # follow symlinks so mach can be run even if cwd is outside the srcdir. return check_and_get_mach(os.path.dirname(os.path.realpath(__file__))) def main(args): mach = get_mach() if not mach: print('Could not run mach: No mach source directory found.') sys.exit(1) sys.exit(mach.run(args)) if __name__ == '__main__': main(sys.argv[1:])