Bug 1760185 [wpt PR 33176] - [FedCM] Add hint argument to revoke, a=testonly
[gecko.git] / build / moz.configure / util.configure
blob9a14029c030196a97df3a7976e96f389a3b77178
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/.
8 @imports("sys")
9 def die(*args):
10     "Print an error and terminate configure."
11     log.error(*args)
12     sys.exit(1)
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).
25 @imports("os")
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(
32         args,
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,
40         errors="replace",
41         **kwargs
42     )
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
50 # does not.
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)
58         if retcode == 0:
59             return stdout
61         log.debug("The command returned non-zero exit status %d.", retcode)
62         for out, desc in ((stdout, "output"), (stderr, "error output")):
63             if out:
64                 log.debug("Its %s was:", desc)
65                 with LineIO(lambda l: log.debug("| %s", l)) as o:
66                     o.write(out)
67         if onerror:
68             return onerror()
69         die("Command `%s` failed with exit status %d." % (quote(*args), retcode))
72 @imports("os")
73 def is_absolute_or_relative(path):
74     if os.altsep and os.altsep in path:
75         return True
76     return os.sep in path
79 @imports(_import="mozpack.path", _as="mozpath")
80 def normsep(path):
81     return mozpath.normsep(path)
84 @imports("ctypes")
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.
91     """
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))
98     if not res:
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.
106     else:
107         die("unsupported binary type on %s: %s" % (path, bin_type))
110 @imports("ctypes")
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
119 @template
120 @imports("ctypes")
121 @imports("platform")
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.
126     # c:\PROGRA~1...).
127     if platform.system() == "Windows":
128         GetShortPathNameW = get_GetShortPathNameW()
130         def normalize_path(path):
131             path = normsep(path)
132             if quote(path) == path:
133                 return path
134             size = 0
135             while True:
136                 out = ctypes.create_unicode_buffer(size)
137                 needed = GetShortPathNameW(path, out, size)
138                 if size >= needed:
139                     if " " in out.value:
140                         die(
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 "
145                             "that have spaces.",
146                             out.value,
147                         )
148                     return normsep(out.value)
149                 size = needed
151     else:
153         def normalize_path(path):
154             return normsep(path)
156     return normalize_path
159 normalize_path = normalize_path()
162 # Locates the given program using which, or returns the given path if it
163 # exists.
164 # The `paths` parameter may be passed to search the given paths instead of
165 # $PATH.
166 @imports("sys")
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"]
182     else:
183         exts = None
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
189     if paths:
190         if not isinstance(paths, (list, tuple)):
191             die(
192                 "Paths provided to find_program must be a list of strings, " "not %r",
193                 paths,
194             )
195         paths = pathsep.join(paths)
197     path = which(file, path=paths, exts=exts)
198     return normalize_path(path) if path else None
201 @imports("os")
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):
205     flags = flags or []
207     if not isinstance(flags, (list, tuple)):
208         die("Flags provided to try_compile must be a list of strings, " "not %r", flags)
210     suffix = {
211         "C": ".c",
212         "C++": ".cpp",
213     }[language]
215     fd, path = mkstemp(prefix="conftest.", suffix=suffix, text=True)
216     try:
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:
221             out.write(source)
223         os.write(fd, source)
224         os.close(fd)
225         cmd = compiler + [path] + list(flags)
226         kwargs = {"onerror": onerror}
227         return check_cmd_output(*cmd, **kwargs)
228     finally:
229         os.remove(path)
232 def unique_list(l):
233     result = []
234     for i in l:
235         if l not in result:
236             result.append(i)
237     return result
240 # Get values out of the Windows registry. This function can only be called on
241 # Windows.
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.
253 # Examples:
254 #   get_registry_values(r'HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\'
255 #                       r'Windows Kits\Installed Roots\KitsRoot*')
256 #   yields e.g.:
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')
262 #   yields e.g.:
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)
268 #   yields e.g.:
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*')
274 #   yields e.g.:
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')
282 #   yields e.g.:
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):
291         i = 0
292         while True:
293             try:
294                 yield func(key, i)
295             except WindowsError:
296                 break
297             i += 1
299     def get_keys(key, pattern, access_mask):
300         try:
301             s = winreg.OpenKey(key, "\\".join(pattern[:-1]), 0, access_mask)
302         except WindowsError:
303             return
304         for k in enum_helper(winreg.EnumKey, s):
305             if fnmatch(k, pattern[-1]):
306                 try:
307                     yield k, winreg.OpenKey(s, k, 0, access_mask)
308                 except WindowsError:
309                     pass
311     def get_values(key, pattern, access_mask):
312         try:
313             s = winreg.OpenKey(key, "\\".join(pattern[:-1]), 0, access_mask)
314         except WindowsError:
315             return
316         for k, v, t in enum_helper(winreg.EnumValue, s):
317             if fnmatch(k, pattern[-1]):
318                 yield k, v
320     def split_pattern(pattern):
321         subpattern = []
322         for p in pattern:
323             subpattern.append(p)
324             if "*" in p:
325                 yield subpattern
326                 subpattern = []
327         if subpattern:
328             yield subpattern
330     def get_all_values(keys, pattern, access_mask):
331         for i, p in enumerate(pattern):
332             next_keys = []
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,))
340                 else:
341                     for name, k in get_keys(base_key, p, access_mask):
342                         next_keys.append(matches + (name, k))
343             keys = next_keys
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
352         ):
353             yield match
354         for match in get_all_values(
355             keys, pattern, winreg.KEY_READ | winreg.KEY_WOW64_64KEY
356         ):
357             yield match
358     else:
359         for match in get_all_values(keys, pattern, winreg.KEY_READ):
360             yield match
363 @imports(_from="mozbuild.configure.util", _import="Version", _as="_Version")
364 def Version(v):
365     "A version number that can be compared usefully."
366     return _Version(v)
369 # Denotes a deprecated option. Combines option() and @depends:
370 # @deprecated_option('--option')
371 # def option(value):
372 #     ...
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.
377 @template
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"}
384     def decorator(func):
385         @depends(opt.option, **kwargs)
386         def deprecated(value):
387             if value.origin != "default":
388                 return func(value)
390         return deprecated
392     return decorator
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.
398 @template
399 @imports(_from="inspect", _import="isfunction")
400 @imports(_from="mozbuild.configure", _import="SandboxDependsFunction")
401 def dependable(obj):
402     if isinstance(obj, SandboxDependsFunction):
403         return obj
404     if isfunction(obj):
405         return depends(when=True)(obj)
406     # Depend on --help to make lint happy if the dependable is used as an input
407     # to an option().
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
417 # arguments.
418 @template
419 def depends_tmpl(eval_args_fn, *args, **kwargs):
420     if kwargs:
421         assert len(kwargs) == 1
422         when = kwargs["when"]
423     else:
424         when = None
426     def decorator(func):
427         @depends(*args, when=when)
428         def wrapper(*args):
429             if eval_args_fn(args):
430                 return func(*args)
432         return wrapper
434     return decorator
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)
439 @template
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.
446 @template
447 def depends_all(*args, **kwargs):
448     return depends_tmpl(all, *args, **kwargs)
451 # Hacks related to old-configure
452 # ==============================
455 @dependable
456 def old_configure_assignments():
457     return []
460 @template
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:
469             return
470         if value is True:
471             assignments.append((var, "1"))
472         elif value is False:
473             assignments.append((var, ""))
474         else:
475             if isinstance(value, (list, tuple)):
476                 value = quote(*value)
477             assignments.append((var, str(value)))