1 # -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
2 # vim: set filetype=python:
3 # This Source Code Form is subject to the terms of the Mozilla Public
4 # License, v. 2.0. If a copy of the MPL was not distributed with this
5 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
10 "Print an error and terminate configure."
15 @imports(_from="mozbuild.configure", _import="ConfigureError")
16 def configure_error(message):
17 """Raise a programming error and terminate configure.
18 Primarily for use in moz.configure templates to sanity check
19 their inputs from moz.configure usage."""
20 raise ConfigureError(message)
23 # A wrapper to obtain a process' output and return code.
24 # Returns a tuple (retcode, stdout, stderr).
27 @imports("subprocess")
28 @imports(_from="mozbuild.shellutil", _import="quote")
29 @imports(_from="mozbuild.util", _import="system_encoding")
30 def get_cmd_output(*args, **kwargs):
31 log.debug("Executing: `%s`", quote(*args))
32 proc = subprocess.Popen(
34 stdout=subprocess.PIPE,
35 stderr=subprocess.PIPE,
36 # On Python 2 on Windows, close_fds prevents the process from inheriting
37 # stdout/stderr. Elsewhere, it simply prevents it from inheriting extra
38 # file descriptors, which is what we want.
39 close_fds=os.name != "nt",
42 stdout, stderr = proc.communicate()
43 # Normally we would set the `encoding` and `errors` arguments in the
44 # constructor to subprocess.Popen, but those arguments were added in 3.6
45 # and we need to support back to 3.5, so instead we need to do this
47 stdout = six.ensure_text(
48 stdout, encoding=system_encoding, errors="replace"
49 ).replace("\r\n", "\n")
50 stderr = six.ensure_text(
51 stderr, encoding=system_encoding, errors="replace"
52 ).replace("\r\n", "\n")
53 return proc.wait(), stdout, stderr
56 # A wrapper to obtain a process' output that returns the output generated
57 # by running the given command if it exits normally, and streams that
58 # output to log.debug and calls die or the given error callback if it
60 @imports(_from="mozbuild.configure.util", _import="LineIO")
61 @imports(_from="mozbuild.shellutil", _import="quote")
62 def check_cmd_output(*args, **kwargs):
63 onerror = kwargs.pop("onerror", None)
65 with log.queue_debug():
66 retcode, stdout, stderr = get_cmd_output(*args, **kwargs)
70 log.debug("The command returned non-zero exit status %d.", retcode)
71 for out, desc in ((stdout, "output"), (stderr, "error output")):
73 log.debug("Its %s was:", desc)
74 with LineIO(lambda l: log.debug("| %s", l)) as o:
78 die("Command `%s` failed with exit status %d." % (quote(*args), retcode))
82 def is_absolute_or_relative(path):
83 if os.altsep and os.altsep in path:
88 @imports(_import="mozpack.path", _as="mozpath")
90 return mozpath.normsep(path)
94 @imports(_from="ctypes", _import="wintypes")
95 @imports(_from="mozbuild.configure.constants", _import="WindowsBinaryType")
96 def windows_binary_type(path):
97 """Obtain the type of a binary on Windows.
99 Returns WindowsBinaryType constant.
101 GetBinaryTypeW = ctypes.windll.kernel32.GetBinaryTypeW
102 GetBinaryTypeW.argtypes = [wintypes.LPWSTR, ctypes.POINTER(wintypes.DWORD)]
103 GetBinaryTypeW.restype = wintypes.BOOL
105 bin_type = wintypes.DWORD()
106 res = GetBinaryTypeW(path, ctypes.byref(bin_type))
108 die("could not obtain binary type of %s" % path)
110 if bin_type.value == 0:
111 return WindowsBinaryType("win32")
112 elif bin_type.value == 6:
113 return WindowsBinaryType("win64")
114 # If we see another binary type, something is likely horribly wrong.
116 die("unsupported binary type on %s: %s" % (path, bin_type))
120 @imports(_from="ctypes", _import="wintypes")
121 def get_GetShortPathNameW():
122 GetShortPathNameW = ctypes.windll.kernel32.GetShortPathNameW
123 GetShortPathNameW.argtypes = [wintypes.LPCWSTR, wintypes.LPWSTR, wintypes.DWORD]
124 GetShortPathNameW.restype = wintypes.DWORD
125 return GetShortPathNameW
131 @imports(_from="mozbuild.shellutil", _import="quote")
132 def normalize_path():
133 # Until the build system can properly handle programs that need quoting,
134 # transform those paths into their short version on Windows (e.g.
136 if platform.system() == "Windows":
137 GetShortPathNameW = get_GetShortPathNameW()
139 def normalize_path(path):
141 if quote(path) == path:
145 out = ctypes.create_unicode_buffer(size)
146 needed = GetShortPathNameW(path, out, size)
150 "GetShortPathName returned a long path name: `%s`. "
151 "Use `fsutil file setshortname' "
152 "to create a short name "
153 "for any components of this path "
157 return normsep(out.value)
162 def normalize_path(path):
165 return normalize_path
168 normalize_path = normalize_path()
171 # Locates the given program using which, or returns the given path if it
173 # The `paths` parameter may be passed to search the given paths instead of
176 @imports(_from="os", _import="pathsep")
177 @imports(_from="os", _import="environ")
178 @imports(_from="mozfile", _import="which")
179 def find_program(file, paths=None):
180 # The following snippet comes from `which` itself, with a slight
181 # modification to use lowercase extensions, because it's confusing rustup
182 # (on top of making results not really appealing to the eye).
184 # Windows has the concept of a list of extensions (PATHEXT env var).
185 if sys.platform.startswith("win"):
186 exts = [e.lower() for e in environ.get("PATHEXT", "").split(pathsep)]
187 # If '.exe' is not in exts then obviously this is Win9x and
188 # or a bogus PATHEXT, then use a reasonable default.
189 if ".exe" not in exts:
190 exts = [".com", ".exe", ".bat"]
194 if is_absolute_or_relative(file):
195 path = which(os.path.basename(file), path=os.path.dirname(file), exts=exts)
196 return normalize_path(path) if path else None
199 if not isinstance(paths, (list, tuple)):
201 "Paths provided to find_program must be a list of strings, " "not %r",
204 paths = pathsep.join(paths)
206 path = which(file, path=paths, exts=exts)
207 return normalize_path(path) if path else None
211 @imports(_from="mozbuild.configure.util", _import="LineIO")
212 @imports(_from="six", _import="ensure_binary")
213 @imports(_from="tempfile", _import="mkstemp")
214 def try_invoke_compiler(compiler, language, source, flags=None, onerror=None):
217 if not isinstance(flags, (list, tuple)):
218 die("Flags provided to try_compile must be a list of strings, " "not %r", flags)
225 fd, path = mkstemp(prefix="conftest.", suffix=suffix, text=True)
227 source = source.encode("ascii", "replace")
229 log.debug("Creating `%s` with content:", path)
230 with LineIO(lambda l: log.debug("| %s", l)) as out:
233 os.write(fd, ensure_binary(source))
235 cmd = compiler + [path] + list(flags)
236 kwargs = {"onerror": onerror}
237 return check_cmd_output(*cmd, **kwargs)
250 # Get values out of the Windows registry. This function can only be called on
252 # The `pattern` argument is a string starting with HKEY_ and giving the full
253 # "path" of the registry key to get the value for, with backslash separators.
254 # The string can contains wildcards ('*').
255 # The result of this functions is an enumerator yielding tuples for each
256 # match. Each of these tuples contains the key name matching wildcards
257 # followed by the value.
259 # The `get_32_and_64_bit` argument is a boolean, if True then it will return the
260 # values from the 32-bit and 64-bit registry views. This defaults to False,
261 # which will return the view depending on the bitness of python.
264 # get_registry_values(r'HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\'
265 # r'Windows Kits\Installed Roots\KitsRoot*')
267 # ('KitsRoot81', r'C:\Program Files (x86)\Windows Kits\8.1\')
268 # ('KitsRoot10', r'C:\Program Files (x86)\Windows Kits\10\')
270 # get_registry_values(r'HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\'
271 # r'Windows Kits\Installed Roots\KitsRoot8.1')
273 # (r'C:\Program Files (x86)\Windows Kits\8.1\',)
275 # get_registry_values(r'HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\'
276 # r'Windows Kits\Installed Roots\KitsRoot8.1',
277 # get_32_and_64_bit=True)
279 # (r'C:\Program Files (x86)\Windows Kits\8.1\',)
280 # (r'C:\Program Files\Windows Kits\8.1\',)
282 # get_registry_values(r'HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\'
283 # r'Windows Kits\*\KitsRoot*')
285 # ('Installed Roots', 'KitsRoot81',
286 # r'C:\Program Files (x86)\Windows Kits\8.1\')
287 # ('Installed Roots', 'KitsRoot10',
288 # r'C:\Program Files (x86)\Windows Kits\10\')
290 # get_registry_values(r'HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\'
291 # r'VisualStudio\VC\*\x86\*\Compiler')
293 # ('19.0', 'arm', r'C:\...\amd64_arm\cl.exe')
294 # ('19.0', 'x64', r'C:\...\amd64\cl.exe')
295 # ('19.0', 'x86', r'C:\...\amd64_x86\cl.exe')
296 @imports(_import="winreg")
297 @imports(_from="__builtin__", _import="WindowsError")
298 @imports(_from="fnmatch", _import="fnmatch")
299 def get_registry_values(pattern, get_32_and_64_bit=False):
300 def enum_helper(func, key):
309 def get_keys(key, pattern, access_mask):
311 s = winreg.OpenKey(key, "\\".join(pattern[:-1]), 0, access_mask)
314 for k in enum_helper(winreg.EnumKey, s):
315 if fnmatch(k, pattern[-1]):
317 yield k, winreg.OpenKey(s, k, 0, access_mask)
321 def get_values(key, pattern, access_mask):
323 s = winreg.OpenKey(key, "\\".join(pattern[:-1]), 0, access_mask)
326 for k, v, t in enum_helper(winreg.EnumValue, s):
327 if fnmatch(k, pattern[-1]):
330 def split_pattern(pattern):
340 def get_all_values(keys, pattern, access_mask):
341 for i, p in enumerate(pattern):
343 for base_key in keys:
344 matches = base_key[:-1]
345 base_key = base_key[-1]
346 if i == len(pattern) - 1:
347 want_name = "*" in p[-1]
348 for name, value in get_values(base_key, p, access_mask):
349 yield matches + ((name, value) if want_name else (value,))
351 for name, k in get_keys(base_key, p, access_mask):
352 next_keys.append(matches + (name, k))
355 pattern = pattern.split("\\")
356 assert pattern[0].startswith("HKEY_")
357 keys = [(getattr(winreg, pattern[0]),)]
358 pattern = list(split_pattern(pattern[1:]))
359 if get_32_and_64_bit:
360 for match in get_all_values(
361 keys, pattern, winreg.KEY_READ | winreg.KEY_WOW64_32KEY
364 for match in get_all_values(
365 keys, pattern, winreg.KEY_READ | winreg.KEY_WOW64_64KEY
369 for match in get_all_values(keys, pattern, winreg.KEY_READ):
373 @imports(_from="mozbuild.configure.util", _import="Version", _as="_Version")
375 "A version number that can be compared usefully."
379 # Denotes a deprecated option. Combines option() and @depends:
380 # @deprecated_option('--option')
383 # @deprecated_option() takes the same arguments as option(), except `help`.
384 # The function may handle the option like a typical @depends function would,
385 # but it is recommended it emits a deprecation error message suggesting an
386 # alternative option to use if there is one.
390 def deprecated_option(*args, **kwargs):
391 assert "help" not in kwargs
392 kwargs["help"] = "Deprecated"
393 opt = option(*args, **kwargs)
397 def deprecated(value):
398 if value.origin != "default":
406 # Turn an object into an object that can be used as an argument to @depends.
407 # The given object can be a literal value, a function that takes no argument,
408 # or, for convenience, a @depends function.
410 @imports(_from="inspect", _import="isfunction")
411 @imports(_from="mozbuild.configure", _import="SandboxDependsFunction")
413 if isinstance(obj, SandboxDependsFunction):
416 return depends(when=True)(obj)
417 # Depend on --help to make lint happy if the dependable is used as an input
419 return depends("--help", when=True)(lambda _: obj)
422 always = dependable(True)
423 never = dependable(False)
426 # Create a decorator that will only execute the body of a function
427 # if the passed function returns True when passed all positional
430 def depends_tmpl(eval_args_fn, *args, **kwargs):
432 assert len(kwargs) == 1
433 when = kwargs["when"]
438 @depends(*args, when=when)
440 if eval_args_fn(args):
448 # Like @depends, but the decorated function is only called if one of the
449 # arguments it would be called with has a positive value (bool(value) is True)
451 def depends_if(*args, **kwargs):
452 return depends_tmpl(any, *args, **kwargs)
455 # Like @depends, but the decorated function is only called if all of the
456 # arguments it would be called with have a positive value.
458 def depends_all(*args, **kwargs):
459 return depends_tmpl(all, *args, **kwargs)
462 # Hacks related to old-configure
463 # ==============================
467 def old_configure_assignments():
472 def add_old_configure_assignment(var, value, when=None):
473 var = dependable(var)
474 value = dependable(value)
476 @depends(old_configure_assignments, var, value, when=when)
477 @imports(_from="mozbuild.shellutil", _import="quote")
478 def add_assignment(assignments, var, value):
479 if var is None or value is None:
482 assignments.append((var, "1"))
484 assignments.append((var, ""))
486 if isinstance(value, (list, tuple)):
487 value = quote(*value)
488 assignments.append((var, str(value)))