tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

mach (7311B)


      1 #!/usr/bin/env python3
      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 import os
      7 import platform
      8 import sys
      9 import subprocess
     10 import traceback
     11 from textwrap import dedent, fill
     12 
     13 MIN_PYTHON_VERSION = (3, 9)
     14 MAX_PYTHON_VERSION_TO_CONSIDER = (3, 12)
     15 
     16 
     17 def load_mach(dir_path, mach_path, args):
     18     # Defer import of "importlib.util" until after Python version check has happened
     19     # so that Python 2 usages fail gracefully.
     20     import importlib.util
     21 
     22     spec = importlib.util.spec_from_file_location("mach_initialize", mach_path)
     23     mach_initialize = importlib.util.module_from_spec(spec)
     24     spec.loader.exec_module(mach_initialize)
     25     return mach_initialize.initialize(dir_path, args)
     26 
     27 
     28 def check_and_get_mach(dir_path, args):
     29     initialize_paths = (
     30         # Run Thunderbird's mach_initialize.py if it exists
     31         "comm/build/mach_initialize.py",
     32         "build/mach_initialize.py",
     33         # test package initialize
     34         "tools/mach_initialize.py",
     35     )
     36     for initialize_path in initialize_paths:
     37         mach_path = os.path.join(dir_path, initialize_path)
     38         if os.path.isfile(mach_path):
     39             return load_mach(dir_path, mach_path, args)
     40     return None
     41 
     42 
     43 def find_alternate_python3_executables():
     44     for i in range(MIN_PYTHON_VERSION[1], MAX_PYTHON_VERSION_TO_CONSIDER[1] + 1):
     45         potential_python_binary = f"python3.{i}"
     46         if os.name == "nt":
     47             potential_python_binary += ".exe"
     48 
     49         try:
     50             out = subprocess.run(
     51                 [potential_python_binary, "--version"],
     52                 stdout=subprocess.PIPE,
     53                 stderr=subprocess.PIPE,
     54                 encoding="UTF-8",
     55             )
     56 
     57             binary_minor_version = int(out.stdout[9:11].strip("."))
     58 
     59             if binary_minor_version >= MIN_PYTHON_VERSION[1]:
     60                 yield potential_python_binary
     61 
     62         except Exception:
     63             pass
     64 
     65 
     66 def try_alternate_python3_executables(args):
     67     for potential_python_binary in find_alternate_python3_executables():
     68         try:
     69             print(
     70                 f"We found '{potential_python_binary}' and will attempt to re-run Mach with it."
     71             )
     72             os.execvp(
     73                 potential_python_binary, [potential_python_binary] + ["mach"] + args
     74             )
     75         except Exception:
     76             # We don't really care what goes wrong, just don't let it bubble up
     77             # If we can't successfully launch with a different python3 binary
     78             # we will just print the normal help messages.
     79             pass
     80 
     81 
     82 def main(args):
     83     # Ensure we are running Python 3.9+. We run this check as soon as
     84     # possible to avoid a cryptic import/usage error.
     85     if sys.version_info < MIN_PYTHON_VERSION:
     86         print(
     87             f"Python {MIN_PYTHON_VERSION[0]}.{MIN_PYTHON_VERSION[1]}+ is required to run mach."
     88         )
     89         print("You are running Mach with Python {0}".format(platform.python_version()))
     90         try_alternate_python3_executables(args)
     91         if sys.platform.startswith("linux"):
     92             print(
     93                 dedent(
     94                     """
     95             See https://firefox-source-docs.mozilla.org/setup/linux_build.html#install-python
     96             for guidance on how to install Python on your system.
     97             """
     98                 ).strip()
     99             )
    100         elif sys.platform.startswith("darwin"):
    101             print(
    102                 dedent(
    103                     """
    104             See https://firefox-source-docs.mozilla.org/setup/macos_build.html
    105             for guidance on how to prepare your system to build Firefox. Perhaps
    106             you need to update Xcode, or install Python using brew?
    107             """
    108                 ).strip()
    109             )
    110         elif "MOZILLABUILD" in os.environ and os.environ.get("TERM"):
    111             print(
    112                 dedent(
    113                     """
    114             Python is provided by MozillaBuild; ensure your MozillaBuild installation is
    115             up to date. See https://firefox-source-docs.mozilla.org/setup/windows_build.html#install-mozillabuild
    116             for details.
    117             """
    118                 ).strip()
    119             )
    120         elif sys.platform.startswith("win"):
    121             print(
    122                 dedent(
    123                     """
    124             You probably want to be interacting with Mach from within MozillaBuild, see
    125             https://firefox-source-docs.mozilla.org/setup/windows_build.html for details.
    126             
    127             If you are deliberately using Mach from outside MozillaBuild, then see
    128             https://firefox-source-docs.mozilla.org/mach/windows-usage-outside-mozillabuild.html#install-python
    129             for guidance on installing native Python on your system.
    130             """
    131                 ).strip()
    132             )
    133         else:
    134             print(
    135                 dedent(
    136                     """
    137             We do not have specific instructions for your platform on how to
    138             install Python. You may find Pyenv (https://github.com/pyenv/pyenv)
    139             helpful, if your system package manager does not provide a way to
    140             install a recent enough Python 3.
    141             """
    142                 ).strip()
    143             )
    144         sys.exit(1)
    145 
    146     # XCode python sets __PYVENV_LAUNCHER__, which overrides the executable
    147     # used when a python subprocess is created. This is an issue when we want
    148     # to run using our virtualenv python executables.
    149     # In future Python relases, __PYVENV_LAUNCHER__ will be cleared before
    150     # application code (mach) is started.
    151     # https://github.com/python/cpython/pull/9516
    152     os.environ.pop("__PYVENV_LAUNCHER__", None)
    153 
    154     try:
    155         mach = check_and_get_mach(os.path.dirname(os.path.realpath(__file__)), args)
    156         if not mach:
    157             print("Could not run mach: No mach source directory found.")
    158             sys.exit(1)
    159         sys.exit(mach.run(args))
    160     except (KeyboardInterrupt, SystemExit):
    161         raise
    162     except Exception as e:
    163         if sys.version_info >= (
    164             MAX_PYTHON_VERSION_TO_CONSIDER[0],
    165             MAX_PYTHON_VERSION_TO_CONSIDER[1] + 1,
    166         ):
    167             traceback.print_exc()
    168             print()
    169             print("---")
    170             print()
    171             print(
    172                 fill(
    173                     dedent(
    174                         f"""\
    175                 Note that you are running Mach with Python
    176                 {platform.python_version()}, which is higher than the highest
    177                 known working version of Python for Mach. Consider running Mach
    178                 with Python {MAX_PYTHON_VERSION_TO_CONSIDER[0]}.{MAX_PYTHON_VERSION_TO_CONSIDER[1]}
    179                 or lower."""
    180                     )
    181                 )
    182             )
    183 
    184             try:
    185                 alternative = next(find_alternate_python3_executables())
    186                 print()
    187                 print("Running the following command may solve your issue:")
    188                 print()
    189                 print(f"    {alternative} {sys.argv[0]} {' '.join(args)}")
    190                 print()
    191             except StopIteration:
    192                 pass
    193             sys.exit(1)
    194         else:
    195             raise
    196 
    197 
    198 if __name__ == "__main__":
    199     main(sys.argv[1:])