#!/usr/bin/env python3 # 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/. import os import platform import sys import subprocess from textwrap import dedent MIN_PYTHON_VERSION = (3, 7) MAX_PYTHON_VERSION_TO_CONSIDER = (3, 11) def load_mach(dir_path, mach_path): # Defer import of "importlib.util" until after Python version check has happened # so that Python 2 usages fail gracefully. import importlib.util spec = importlib.util.spec_from_file_location('mach_initialize', mach_path) mach_initialize = importlib.util.module_from_spec(spec) spec.loader.exec_module(mach_initialize) return mach_initialize.initialize(dir_path) def check_and_get_mach(dir_path): initialize_paths = ( # Run Thunderbird's mach_initialize.py if it exists 'comm/build/mach_initialize.py', 'build/mach_initialize.py', # test package initialize 'tools/mach_initialize.py', ) for initialize_path in initialize_paths: mach_path = os.path.join(dir_path, initialize_path) if os.path.isfile(mach_path): return load_mach(dir_path, mach_path) return None def try_alternate_python3_executables(args): for i in range(MIN_PYTHON_VERSION[1], MAX_PYTHON_VERSION_TO_CONSIDER[1] + 1): try: potential_python_binary = f"python3.{i}" if os.name == 'nt': potential_python_binary += ".exe" out = subprocess.run( [potential_python_binary, "--version"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="UTF-8") binary_minor_version = int(out.stdout[9:11].strip(".")) if binary_minor_version >= MIN_PYTHON_VERSION[1]: print(f"We found '{potential_python_binary}' and will attempt to re-run Mach with it.") os.execvp(potential_python_binary, [potential_python_binary] + ["mach"] + args) except Exception: # We don't really care what goes wrong, just don't let it bubble up # If we can't successfully launch with a different python3 binary # we will just print the normal help messages. pass def main(args): # Ensure we are running Python 3.7+. We run this check as soon as # possible to avoid a cryptic import/usage error. if sys.version_info < MIN_PYTHON_VERSION: print(f"Python {MIN_PYTHON_VERSION[0]}.{MIN_PYTHON_VERSION[1]}+ is required to run mach.") print("You are running Mach with Python {0}".format(platform.python_version())) try_alternate_python3_executables(args) if sys.platform.startswith("linux"): print(dedent(""" See https://firefox-source-docs.mozilla.org/setup/linux_build.html#installingpython for guidance on how to install Python on your system. """).strip()) elif sys.platform.startswith("darwin"): print(dedent(""" See https://firefox-source-docs.mozilla.org/setup/macos_build.html for guidance on how to prepare your system to build Firefox. Perhaps you need to update Xcode, or install Python using brew? """).strip()) elif "MOZILLABUILD" in os.environ and os.environ.get("TERM"): print(dedent(""" Python is provided by MozillaBuild; ensure your MozillaBuild installation is up to date. See https://firefox-source-docs.mozilla.org/setup/windows_build.html#install-mozillabuild for details. """).strip()) elif sys.platform.startswith("win"): print(dedent(""" You probably want to be interacting with Mach from within MozillaBuild, see https://firefox-source-docs.mozilla.org/setup/windows_build.html for details. If you are deliberately using Mach from outside MozillaBuild, then see https://firefox-source-docs.mozilla.org/mach/windows-usage-outside-mozillabuild.html#install-python for guidance on installing native Python on your system. """).strip()) else: print(dedent(""" We do not have specific instructions for your platform on how to install Python. You may find Pyenv (https://github.com/pyenv/pyenv) helpful, if your system package manager does not provide a way to install a recent enough Python 3. """).strip()) sys.exit(1) # XCode python sets __PYVENV_LAUNCHER__, which overrides the executable # used when a python subprocess is created. This is an issue when we want # to run using our virtualenv python executables. # In future Python relases, __PYVENV_LAUNCHER__ will be cleared before # application code (mach) is started. # https://github.com/python/cpython/pull/9516 os.environ.pop("__PYVENV_LAUNCHER__", None) mach = check_and_get_mach(os.path.dirname(os.path.realpath(__file__))) 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:])