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).
26 @imports("subprocess")
27 @imports(_from="mozbuild.shellutil", _import="quote")
28 @imports(_from="mozbuild.util", _import="system_encoding")
29 def get_cmd_output(*args, **kwargs):
30 log.debug("Executing: `%s`", quote(*args))
31 proc = subprocess.Popen(
33 stdout=subprocess.PIPE,
34 stderr=subprocess.PIPE,
35 # On Python 2 on Windows, close_fds prevents the process from inheriting
36 # stdout/stderr. Elsewhere, it simply prevents it from inheriting extra
37 # file descriptors, which is what we want.
38 close_fds=os.name != "nt",
39 encoding=system_encoding,
43 stdout, stderr = proc.communicate()
44 return proc.wait(), stdout, stderr
47 # A wrapper to obtain a process' output that returns the output generated
48 # by running the given command if it exits normally, and streams that
49 # output to log.debug and calls die or the given error callback if it
51 @imports(_from="mozbuild.configure.util", _import="LineIO")
52 @imports(_from="mozbuild.shellutil", _import="quote")
53 def check_cmd_output(*args, **kwargs):
54 onerror = kwargs.pop("onerror", None)
56 with log.queue_debug():
57 retcode, stdout, stderr = get_cmd_output(*args, **kwargs)
61 log.debug("The command returned non-zero exit status %d.", retcode)
62 for out, desc in ((stdout, "output"), (stderr, "error output")):
64 log.debug("Its %s was:", desc)
65 with LineIO(lambda l: log.debug("| %s", l)) as o:
69 die("Command `%s` failed with exit status %d." % (quote(*args), retcode))
73 def is_absolute_or_relative(path):
74 if os.altsep and os.altsep in path:
79 @imports(_import="mozpack.path", _as="mozpath")
81 return mozpath.normsep(path)
85 @imports(_from="ctypes", _import="wintypes")
86 @imports(_from="mozbuild.configure.constants", _import="WindowsBinaryType")
87 def windows_binary_type(path):
88 """Obtain the type of a binary on Windows.
90 Returns WindowsBinaryType constant.
92 GetBinaryTypeW = ctypes.windll.kernel32.GetBinaryTypeW
93 GetBinaryTypeW.argtypes = [wintypes.LPWSTR, ctypes.POINTER(wintypes.DWORD)]
94 GetBinaryTypeW.restype = wintypes.BOOL
96 bin_type = wintypes.DWORD()
97 res = GetBinaryTypeW(path, ctypes.byref(bin_type))
99 die("could not obtain binary type of %s" % path)
101 if bin_type.value == 0:
102 return WindowsBinaryType("win32")
103 elif bin_type.value == 6:
104 return WindowsBinaryType("win64")
105 # If we see another binary type, something is likely horribly wrong.
107 die("unsupported binary type on %s: %s" % (path, bin_type))
111 @imports(_from="ctypes", _import="wintypes")
112 def get_GetShortPathNameW():
113 GetShortPathNameW = ctypes.windll.kernel32.GetShortPathNameW
114 GetShortPathNameW.argtypes = [wintypes.LPCWSTR, wintypes.LPWSTR, wintypes.DWORD]
115 GetShortPathNameW.restype = wintypes.DWORD
116 return GetShortPathNameW
122 @imports(_from="mozbuild.shellutil", _import="quote")
123 def normalize_path():
124 # Until the build system can properly handle programs that need quoting,
125 # transform those paths into their short version on Windows (e.g.
127 if platform.system() == "Windows":
128 GetShortPathNameW = get_GetShortPathNameW()
130 def normalize_path(path):
132 if quote(path) == path:
136 out = ctypes.create_unicode_buffer(size)
137 needed = GetShortPathNameW(path, out, size)
141 "GetShortPathName returned a long path name: `%s`. "
142 "Use `fsutil file setshortname' "
143 "to create a short name "
144 "for any components of this path "
148 return normsep(out.value)
153 def normalize_path(path):
156 return normalize_path
159 normalize_path = normalize_path()
162 # Locates the given program using which, or returns the given path if it
164 # The `paths` parameter may be passed to search the given paths instead of
167 @imports(_from="os", _import="pathsep")
168 @imports(_from="os", _import="environ")
169 @imports(_from="mozfile", _import="which")
170 def find_program(file, paths=None):
171 # The following snippet comes from `which` itself, with a slight
172 # modification to use lowercase extensions, because it's confusing rustup
173 # (on top of making results not really appealing to the eye).
175 # Windows has the concept of a list of extensions (PATHEXT env var).
176 if sys.platform.startswith("win"):
177 exts = [e.lower() for e in environ.get("PATHEXT", "").split(pathsep)]
178 # If '.exe' is not in exts then obviously this is Win9x and
179 # or a bogus PATHEXT, then use a reasonable default.
180 if ".exe" not in exts:
181 exts = [".com", ".exe", ".bat"]
185 if is_absolute_or_relative(file):
186 path = which(os.path.basename(file), path=os.path.dirname(file), exts=exts)
187 return normalize_path(path) if path else None
190 if not isinstance(paths, (list, tuple)):
192 "Paths provided to find_program must be a list of strings, " "not %r",
195 paths = pathsep.join(paths)
197 path = which(file, path=paths, exts=exts)
198 return normalize_path(path) if path else None
202 @imports(_from="mozbuild.configure.util", _import="LineIO")
203 @imports(_from="tempfile", _import="mkstemp")
204 def try_invoke_compiler(compiler, language, source, flags=None, onerror=None):
207 if not isinstance(flags, (list, tuple)):
208 die("Flags provided to try_compile must be a list of strings, " "not %r", flags)
215 fd, path = mkstemp(prefix="conftest.", suffix=suffix, text=True)
217 source = source.encode("ascii", "replace")
219 log.debug("Creating `%s` with content:", path)
220 with LineIO(lambda l: log.debug("| %s", l)) as out:
225 cmd = compiler + [path] + list(flags)
226 kwargs = {"onerror": onerror}
227 return check_cmd_output(*cmd, **kwargs)
240 # Get values out of the Windows registry. This function can only be called on
242 # The `pattern` argument is a string starting with HKEY_ and giving the full
243 # "path" of the registry key to get the value for, with backslash separators.
244 # The string can contains wildcards ('*').
245 # The result of this functions is an enumerator yielding tuples for each
246 # match. Each of these tuples contains the key name matching wildcards
247 # followed by the value.
249 # The `get_32_and_64_bit` argument is a boolean, if True then it will return the
250 # values from the 32-bit and 64-bit registry views. This defaults to False,
251 # which will return the view depending on the bitness of python.
254 # get_registry_values(r'HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\'
255 # r'Windows Kits\Installed Roots\KitsRoot*')
257 # ('KitsRoot81', r'C:\Program Files (x86)\Windows Kits\8.1\')
258 # ('KitsRoot10', r'C:\Program Files (x86)\Windows Kits\10\')
260 # get_registry_values(r'HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\'
261 # r'Windows Kits\Installed Roots\KitsRoot8.1')
263 # (r'C:\Program Files (x86)\Windows Kits\8.1\',)
265 # get_registry_values(r'HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\'
266 # r'Windows Kits\Installed Roots\KitsRoot8.1',
267 # get_32_and_64_bit=True)
269 # (r'C:\Program Files (x86)\Windows Kits\8.1\',)
270 # (r'C:\Program Files\Windows Kits\8.1\',)
272 # get_registry_values(r'HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\'
273 # r'Windows Kits\*\KitsRoot*')
275 # ('Installed Roots', 'KitsRoot81',
276 # r'C:\Program Files (x86)\Windows Kits\8.1\')
277 # ('Installed Roots', 'KitsRoot10',
278 # r'C:\Program Files (x86)\Windows Kits\10\')
280 # get_registry_values(r'HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\'
281 # r'VisualStudio\VC\*\x86\*\Compiler')
283 # ('19.0', 'arm', r'C:\...\amd64_arm\cl.exe')
284 # ('19.0', 'x64', r'C:\...\amd64\cl.exe')
285 # ('19.0', 'x86', r'C:\...\amd64_x86\cl.exe')
286 @imports(_import="winreg")
287 @imports(_from="__builtin__", _import="WindowsError")
288 @imports(_from="fnmatch", _import="fnmatch")
289 def get_registry_values(pattern, get_32_and_64_bit=False):
290 def enum_helper(func, key):
299 def get_keys(key, pattern, access_mask):
301 s = winreg.OpenKey(key, "\\".join(pattern[:-1]), 0, access_mask)
304 for k in enum_helper(winreg.EnumKey, s):
305 if fnmatch(k, pattern[-1]):
307 yield k, winreg.OpenKey(s, k, 0, access_mask)
311 def get_values(key, pattern, access_mask):
313 s = winreg.OpenKey(key, "\\".join(pattern[:-1]), 0, access_mask)
316 for k, v, t in enum_helper(winreg.EnumValue, s):
317 if fnmatch(k, pattern[-1]):
320 def split_pattern(pattern):
330 def get_all_values(keys, pattern, access_mask):
331 for i, p in enumerate(pattern):
333 for base_key in keys:
334 matches = base_key[:-1]
335 base_key = base_key[-1]
336 if i == len(pattern) - 1:
337 want_name = "*" in p[-1]
338 for name, value in get_values(base_key, p, access_mask):
339 yield matches + ((name, value) if want_name else (value,))
341 for name, k in get_keys(base_key, p, access_mask):
342 next_keys.append(matches + (name, k))
345 pattern = pattern.split("\\")
346 assert pattern[0].startswith("HKEY_")
347 keys = [(getattr(winreg, pattern[0]),)]
348 pattern = list(split_pattern(pattern[1:]))
349 if get_32_and_64_bit:
350 for match in get_all_values(
351 keys, pattern, winreg.KEY_READ | winreg.KEY_WOW64_32KEY
354 for match in get_all_values(
355 keys, pattern, winreg.KEY_READ | winreg.KEY_WOW64_64KEY
359 for match in get_all_values(keys, pattern, winreg.KEY_READ):
363 @imports(_from="mozbuild.configure.util", _import="Version", _as="_Version")
365 "A version number that can be compared usefully."
369 # Denotes a deprecated option. Combines option() and @depends:
370 # @deprecated_option('--option')
373 # @deprecated_option() takes the same arguments as option(), except `help`.
374 # The function may handle the option like a typical @depends function would,
375 # but it is recommended it emits a deprecation error message suggesting an
376 # alternative option to use if there is one.
378 def deprecated_option(*args, **kwargs):
379 assert "help" not in kwargs
380 kwargs["help"] = "Deprecated"
381 opt = option(*args, **kwargs)
382 kwargs = {k: v for k, v in kwargs.items() if k == "when"}
385 @depends(opt.option, **kwargs)
386 def deprecated(value):
387 if value.origin != "default":
395 # Turn an object into an object that can be used as an argument to @depends.
396 # The given object can be a literal value, a function that takes no argument,
397 # or, for convenience, a @depends function.
399 @imports(_from="inspect", _import="isfunction")
400 @imports(_from="mozbuild.configure", _import="SandboxDependsFunction")
402 if isinstance(obj, SandboxDependsFunction):
405 return depends(when=True)(obj)
406 # Depend on --help to make lint happy if the dependable is used as an input
408 return depends("--help", when=True)(lambda _: obj)
411 always = dependable(True)
412 never = dependable(False)
415 # Create a decorator that will only execute the body of a function
416 # if the passed function returns True when passed all positional
419 def depends_tmpl(eval_args_fn, *args, **kwargs):
421 assert len(kwargs) == 1
422 when = kwargs["when"]
427 @depends(*args, when=when)
429 if eval_args_fn(args):
437 # Like @depends, but the decorated function is only called if one of the
438 # arguments it would be called with has a positive value (bool(value) is True)
440 def depends_if(*args, **kwargs):
441 return depends_tmpl(any, *args, **kwargs)
444 # Like @depends, but the decorated function is only called if all of the
445 # arguments it would be called with have a positive value.
447 def depends_all(*args, **kwargs):
448 return depends_tmpl(all, *args, **kwargs)
451 # Hacks related to old-configure
452 # ==============================
456 def old_configure_assignments():
461 def add_old_configure_assignment(var, value, when=None):
462 var = dependable(var)
463 value = dependable(value)
465 @depends(old_configure_assignments, var, value, when=when)
466 @imports(_from="mozbuild.shellutil", _import="quote")
467 def add_assignment(assignments, var, value):
468 if var is None or value is None:
471 assignments.append((var, "1"))
473 assignments.append((var, ""))
475 if isinstance(value, (list, tuple)):
476 value = quote(*value)
477 assignments.append((var, str(value)))