Backed out changeset 2c48aa9b6764 (bug 1919488) because it was requested by developer.
[gecko.git] / mach
blob5ea05fb643c0cb53b17073b5c60a478b3c7a3e2c
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/.
6 import os
7 import platform
8 import sys
9 import subprocess
10 import traceback
11 from textwrap import dedent, fill
13 MIN_PYTHON_VERSION = (3, 8)
14 MAX_PYTHON_VERSION_TO_CONSIDER = (3, 11)
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
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)
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
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"
49         try:
50             out = subprocess.run(
51                 [potential_python_binary, "--version"],
52                 stdout=subprocess.PIPE,
53                 stderr=subprocess.PIPE,
54                 encoding="UTF-8",
55             )
57             binary_minor_version = int(out.stdout[9:11].strip("."))
59             if binary_minor_version >= MIN_PYTHON_VERSION[1]:
60                 yield potential_python_binary
62         except Exception:
63             pass
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
82 def main(args):
83     # Ensure we are running Python 3.8+. 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#installingpython
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)
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)
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             )
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
198 if __name__ == "__main__":
199     main(sys.argv[1:])