1# This Source Code Form is subject to the terms of the Mozilla Public
2# License, v. 2.0. If a copy of the MPL was not distributed with this
3# file, You can obtain one at http://mozilla.org/MPL/2.0/.
4
5from __future__ import absolute_import, print_function, unicode_literals
6
7import ctypes
8import os
9import sys
10import subprocess
11
12from mozboot.base import BaseBootstrapper
13
14
15def is_aarch64_host():
16    from ctypes import wintypes
17
18    kernel32 = ctypes.windll.kernel32
19    IMAGE_FILE_MACHINE_UNKNOWN = 0
20    IMAGE_FILE_MACHINE_ARM64 = 0xAA64
21
22    try:
23        iswow64process2 = kernel32.IsWow64Process2
24    except Exception:
25        # If we can't access the symbol, we know we're not on aarch64.
26        return False
27
28    currentProcess = kernel32.GetCurrentProcess()
29    processMachine = wintypes.USHORT(IMAGE_FILE_MACHINE_UNKNOWN)
30    nativeMachine = wintypes.USHORT(IMAGE_FILE_MACHINE_UNKNOWN)
31
32    gotValue = iswow64process2(
33        currentProcess, ctypes.byref(processMachine), ctypes.byref(nativeMachine)
34    )
35    # If this call fails, we have no idea.
36    if not gotValue:
37        return False
38
39    return nativeMachine.value == IMAGE_FILE_MACHINE_ARM64
40
41
42def get_is_windefender_disabled():
43    import winreg
44
45    try:
46        with winreg.OpenKeyEx(
47            winreg.HKEY_LOCAL_MACHINE, r"SOFTWARE\Microsoft\Windows Defender"
48        ) as windefender_key:
49            is_antivirus_disabled, _ = winreg.QueryValueEx(
50                windefender_key, "DisableAntiSpyware"
51            )
52            # is_antivirus_disabled is either 0 (False) or 1 (True)
53            return bool(is_antivirus_disabled)
54    except FileNotFoundError:
55        return True
56
57
58def get_windefender_exclusion_paths():
59    import winreg
60
61    paths = []
62    try:
63        with winreg.OpenKeyEx(
64            winreg.HKEY_LOCAL_MACHINE,
65            r"SOFTWARE\Microsoft\Windows Defender\Exclusions\Paths",
66        ) as exclusions_key:
67            _, values_count, __ = winreg.QueryInfoKey(exclusions_key)
68            for i in range(0, values_count):
69                path, _, __ = winreg.EnumValue(exclusions_key, i)
70                paths.append(path)
71    except FileNotFoundError:
72        pass
73
74    return paths
75
76
77def is_windefender_affecting_srcdir(srcdir):
78    if get_is_windefender_disabled():
79        return False
80
81    # When there's a match, but path cases aren't the same between srcdir and exclusion_path,
82    # commonpath will use the casing of the first path provided.
83    # To avoid surprises here, we normcase(...) so we don't get unexpected breakage if we change
84    # the path order.
85    srcdir = os.path.normcase(os.path.abspath(srcdir))
86
87    try:
88        exclusion_paths = get_windefender_exclusion_paths()
89    except OSError as e:
90        if e.winerror == 5:
91            # A version of Windows 10 released in 2021 raises an "Access is denied"
92            # error (ERROR_ACCESS_DENIED == 5) to un-elevated processes when they
93            # query Windows Defender's exclusions. Skip the exclusion path checking.
94            return
95        raise
96
97    for exclusion_path in exclusion_paths:
98        exclusion_path = os.path.normcase(os.path.abspath(exclusion_path))
99        try:
100            if os.path.commonpath([exclusion_path, srcdir]) == exclusion_path:
101                # exclusion_path is an ancestor of srcdir
102                return False
103        except ValueError:
104            # ValueError: Paths don't have the same drive - can't be ours
105            pass
106    return True
107
108
109class MozillaBuildBootstrapper(BaseBootstrapper):
110    """Bootstrapper for MozillaBuild to install rustup."""
111
112    INSTALL_PYTHON_GUIDANCE = (
113        "Python is provided by MozillaBuild; ensure your MozillaBuild "
114        "installation is up to date."
115    )
116
117    def __init__(self, no_interactive=False, no_system_changes=False):
118        BaseBootstrapper.__init__(
119            self, no_interactive=no_interactive, no_system_changes=no_system_changes
120        )
121
122    def validate_environment(self, srcdir):
123        if self.application.startswith("mobile_android"):
124            print(
125                "WARNING!!! Building Firefox for Android on Windows is not "
126                "fully supported. See https://bugzilla.mozilla.org/show_bug."
127                "cgi?id=1169873 for details.",
128                file=sys.stderr,
129            )
130
131        if is_windefender_affecting_srcdir(srcdir):
132            print(
133                "Warning: the Firefox checkout directory is currently not in the "
134                "Windows Defender exclusion list. This can cause the build process "
135                "to be dramatically slowed or broken. To resolve this, follow the "
136                "directions here: "
137                "https://firefox-source-docs.mozilla.org/setup/windows_build.html"
138                "#antivirus-performance",
139                file=sys.stderr,
140            )
141
142    def install_system_packages(self):
143        pass
144
145    def upgrade_mercurial(self, current):
146        # Mercurial upstream sometimes doesn't upload wheels, and building
147        # from source requires MS Visual C++ 9.0. So we force pip to install
148        # the last version that comes with wheels.
149        self.pip_install("mercurial", "--only-binary", "mercurial")
150
151    def install_browser_packages(self, mozconfig_builder):
152        pass
153
154    def install_browser_artifact_mode_packages(self, mozconfig_builder):
155        pass
156
157    def install_mobile_android_packages(self, mozconfig_builder):
158        self.ensure_mobile_android_packages(mozconfig_builder)
159
160    def install_mobile_android_artifact_mode_packages(self, mozconfig_builder):
161        self.ensure_mobile_android_packages(mozconfig_builder, artifact_mode=True)
162
163    def ensure_mobile_android_packages(self, mozconfig_builder, artifact_mode=False):
164        java_bin_dir = self.ensure_java(mozconfig_builder)
165        from mach.util import setenv
166
167        setenv("PATH", "{}{}{}".format(java_bin_dir, os.pathsep, os.environ["PATH"]))
168
169        from mozboot import android
170
171        android.ensure_android(
172            "windows", artifact_mode=artifact_mode, no_interactive=self.no_interactive
173        )
174
175    def generate_mobile_android_mozconfig(self, artifact_mode=False):
176        from mozboot import android
177
178        return android.generate_mozconfig("windows", artifact_mode=artifact_mode)
179
180    def generate_mobile_android_artifact_mode_mozconfig(self):
181        return self.generate_mobile_android_mozconfig(artifact_mode=True)
182
183    def ensure_clang_static_analysis_package(self, state_dir, checkout_root):
184        from mozboot import static_analysis
185
186        self.install_toolchain_static_analysis(
187            state_dir, checkout_root, static_analysis.WINDOWS_CLANG_TIDY
188        )
189
190    def ensure_sccache_packages(self, state_dir, checkout_root):
191        from mozboot import sccache
192
193        self.install_toolchain_artifact(state_dir, checkout_root, sccache.WIN64_SCCACHE)
194        self.install_toolchain_artifact(
195            state_dir, checkout_root, sccache.RUSTC_DIST_TOOLCHAIN, no_unpack=True
196        )
197        self.install_toolchain_artifact(
198            state_dir, checkout_root, sccache.CLANG_DIST_TOOLCHAIN, no_unpack=True
199        )
200
201    def ensure_stylo_packages(self, state_dir, checkout_root):
202        # On-device artifact builds are supported; on-device desktop builds are not.
203        if is_aarch64_host():
204            raise Exception(
205                "You should not be performing desktop builds on an "
206                "AArch64 device.  If you want to do artifact builds "
207                "instead, please choose the appropriate artifact build "
208                "option when beginning bootstrap."
209            )
210
211        from mozboot import stylo
212
213        self.install_toolchain_artifact(state_dir, checkout_root, stylo.WINDOWS_CLANG)
214        self.install_toolchain_artifact(
215            state_dir, checkout_root, stylo.WINDOWS_CBINDGEN
216        )
217
218    def ensure_nasm_packages(self, state_dir, checkout_root):
219        from mozboot import nasm
220
221        self.install_toolchain_artifact(state_dir, checkout_root, nasm.WINDOWS_NASM)
222
223    def ensure_node_packages(self, state_dir, checkout_root):
224        from mozboot import node
225
226        # We don't have native aarch64 node available, but aarch64 windows
227        # runs x86 binaries, so just use the x86 packages for such hosts.
228        node_artifact = node.WIN32 if is_aarch64_host() else node.WIN64
229        self.install_toolchain_artifact(state_dir, checkout_root, node_artifact)
230
231    def ensure_dump_syms_packages(self, state_dir, checkout_root):
232        from mozboot import dump_syms
233
234        self.install_toolchain_artifact(
235            state_dir, checkout_root, dump_syms.WIN64_DUMP_SYMS
236        )
237
238    def ensure_fix_stacks_packages(self, state_dir, checkout_root):
239        from mozboot import fix_stacks
240
241        self.install_toolchain_artifact(
242            state_dir, checkout_root, fix_stacks.WINDOWS_FIX_STACKS
243        )
244
245    def ensure_minidump_stackwalk_packages(self, state_dir, checkout_root):
246        from mozboot import minidump_stackwalk
247
248        self.install_toolchain_artifact(
249            state_dir, checkout_root, minidump_stackwalk.WINDOWS_MINIDUMP_STACKWALK
250        )
251
252    def _update_package_manager(self):
253        pass
254
255    def run(self, command):
256        subprocess.check_call(command, stdin=sys.stdin)
257
258    def pip_install(self, *packages):
259        pip_dir = os.path.join(
260            os.environ["MOZILLABUILD"], "python", "Scripts", "pip.exe"
261        )
262        command = [pip_dir, "install", "--upgrade"]
263        command.extend(packages)
264        self.run(command)
265