#!/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. ''':' py2commands=" analyze android android-emulator artifact awsy-test browsertime build build-backend buildsymbols cargo check-spidermonkey clang-format compileflags configure cppunittest cramtest crashtest devtools-css-db doctor empty-makefiles file-info firefox-ui-functional geckodriver geckodriver-test geckoview-junit gradle gtest hazards ide install jsapi-tests jsshell-bench jstestbrowser jstests marionette-test mochitest mozbuild-reference mozharness mozregression package package-multi-locale pastebin power prettier-format puppeteer-test python python-test raptor raptor-test reftest release release-history remote repackage resource-usage robocop run rusttests show-log static-analysis talos-test taskcluster-build-image taskcluster-load-image taskgraph telemetry-tests-client test test-info tps-build try valgrind-test visualmetrics warnings-list watch web-platform-tests web-platform-tests-update webidl-example webidl-parser-test webrtc-gtest wpt wpt-manifest-update wpt-metadata-merge wpt-metadata-summary wpt-serve wpt-test-paths wpt-unittest wpt-update xpcshell-test " run_py() { # Try to run a specific Python interpreter. Fall back to the system # default Python if the specific interpreter couldn't be found. py_executable="$1" shift if which "$py_executable" > /dev/null then exec "$py_executable" "$0" "$@" elif [ "$py_executable" = "python2.7" ]; then exec python "$0" "$@" else echo "This mach command requires $py_executable, which wasn't found on the system!" exit 1 fi } first_arg=$1 if [ "$first_arg" = "help" ]; then # When running `./mach help `, the correct Python for # needs to be used. first_arg=$2 elif [ "$first_arg" = "mach-completion" ]; then # When running `./mach mach-completion /path/to/mach `, the # correct Python for needs to be used. first_arg=$3 fi if [ -z "$first_arg" ]; then # User ran `./mach` or `./mach help`, use Python 3. run_py python3 "$@" fi case "${first_arg}" in "-"*) # We have global arguments which are tricky to parse from this shell # script. So invoke `mach` with a special --print-command argument to # return the name of the command. This adds extra overhead when using # global arguments, but global arguments are an edge case and this hack # is only needed temporarily for the Python 3 migration. We use Python # 2.7 because using Python 3 hits this error in build tasks: # https://searchfox.org/mozilla-central/rev/c7e8bc4996f9/build/moz.configure/init.configure#319 command=`run_py python2.7 --print-command "$@" | tail -n1` ;; *) # In the common case, the first argument is the command. command=${first_arg}; ;; 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 python2.7 "$@" ;; *) run_py python3 "$@" ;; esac # Run Python 3 for everything else. run_py python3 "$@" ''' 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:])