Bug 1686610 [wpt PR 27178] - Update <link> pseudo selector WPTs, a=testonly
[gecko.git] / build / moz.configure / init.configure
blob7435bdeaad03bf781bb6d2cc66ebf1cbae25f0db
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/.
7 include("util.configure")
8 include("checks.configure")
10 # Make `toolkit` available when toolkit/moz.configure is not included.
11 toolkit = dependable(None)
12 # Likewise with `bindgen_config_paths` when
13 # build/moz.configure/bindgen.configure is not included.
14 bindgen_config_paths = dependable(None)
16 option(env="DIST", nargs=1, help="DIST directory")
19 # Do not allow objdir == srcdir builds.
20 # ==============================================================
21 @depends("--help", "DIST")
22 @imports(_from="__builtin__", _import="open")
23 @imports(_from="os.path", _import="exists")
24 @imports(_from="six", _import="ensure_text")
25 def check_build_environment(help, dist):
26     topobjdir = os.path.realpath(".")
27     topsrcdir = os.path.realpath(os.path.join(os.path.dirname(__file__), "..", ".."))
29     if dist:
30         dist = normsep(dist[0])
31     else:
32         dist = os.path.join(topobjdir, "dist")
34     result = namespace(
35         topsrcdir=topsrcdir,
36         topobjdir=topobjdir,
37         dist=dist,
38     )
40     if help:
41         return result
43     # This limitation has mostly to do with GNU make. Since make can't represent
44     # variables with spaces without correct quoting and many paths are used
45     # without proper quoting, using paths with spaces commonly results in
46     # targets or dependencies being treated as multiple paths. This, of course,
47     # undermines the ability for make to perform up-to-date checks and makes
48     # the build system not work very efficiently. In theory, a non-make build
49     # backend will make this limitation go away. But there is likely a long tail
50     # of things that will need fixing due to e.g. lack of proper path quoting.
51     if len(topsrcdir.split()) > 1:
52         die("Source directory cannot be located in a path with spaces: %s" % topsrcdir)
53     if len(topobjdir.split()) > 1:
54         die("Object directory cannot be located in a path with spaces: %s" % topobjdir)
56     if topsrcdir == topobjdir:
57         die(
58             "  ***\n"
59             "  * Building directly in the main source directory is not allowed.\n"
60             "  *\n"
61             "  * To build, you must run configure from a separate directory\n"
62             "  * (referred to as an object directory).\n"
63             "  *\n"
64             "  * If you are building with a mozconfig, you will need to change your\n"
65             "  * mozconfig to point to a different object directory.\n"
66             "  ***"
67         )
69     # Check for CRLF line endings.
70     with open(os.path.join(topsrcdir, "configure.py"), "r") as fh:
71         data = ensure_text(fh.read())
72         if "\r" in data:
73             die(
74                 "\n ***\n"
75                 " * The source tree appears to have Windows-style line endings.\n"
76                 " *\n"
77                 " * If using Git, Git is likely configured to use Windows-style\n"
78                 " * line endings.\n"
79                 " *\n"
80                 " * To convert the working copy to UNIX-style line endings, run\n"
81                 " * the following:\n"
82                 " *\n"
83                 " * $ git config core.autocrlf false\n"
84                 " * $ git config core.eof lf\n"
85                 " * $ git rm --cached -r .\n"
86                 " * $ git reset --hard\n"
87                 " *\n"
88                 " * If not using Git, the tool you used to obtain the source\n"
89                 " * code likely converted files to Windows line endings. See\n"
90                 " * usage information for that tool for more.\n"
91                 " ***"
92             )
94     # Check for a couple representative files in the source tree
95     conflict_files = [
96         "*         %s" % f
97         for f in ("Makefile", "config/autoconf.mk")
98         if exists(os.path.join(topsrcdir, f))
99     ]
100     if conflict_files:
101         die(
102             "  ***\n"
103             "  *   Your source tree contains these files:\n"
104             "  %s\n"
105             "  *   This indicates that you previously built in the source tree.\n"
106             "  *   A source tree build can confuse the separate objdir build.\n"
107             "  *\n"
108             "  *   To clean up the source tree:\n"
109             "  *     1. cd %s\n"
110             "  *     2. gmake distclean\n"
111             "  ***" % ("\n  ".join(conflict_files), topsrcdir)
112         )
114     return result
117 set_config("TOPSRCDIR", check_build_environment.topsrcdir)
118 set_config("TOPOBJDIR", check_build_environment.topobjdir)
119 set_config("DIST", check_build_environment.dist)
121 add_old_configure_assignment("_topsrcdir", check_build_environment.topsrcdir)
122 add_old_configure_assignment("_objdir", check_build_environment.topobjdir)
123 add_old_configure_assignment("DIST", check_build_environment.dist)
125 option(env="MOZ_AUTOMATION", help="Enable options for automated builds")
126 set_config("MOZ_AUTOMATION", depends_if("MOZ_AUTOMATION")(lambda x: True))
129 option(env="OLD_CONFIGURE", nargs=1, help="Path to the old configure script")
131 option(env="MOZCONFIG", nargs=1, help="Mozconfig location")
134 # Read user mozconfig
135 # ==============================================================
136 # Note: the dependency on --help is only there to always read the mozconfig,
137 # even when --help is passed. Without this dependency, the function wouldn't
138 # be called when --help is passed, and the mozconfig wouldn't be read.
141 @depends("MOZCONFIG", "OLD_CONFIGURE", check_build_environment, "--help")
142 @imports(_from="mozbuild.mozconfig", _import="MozconfigLoader")
143 @imports(_from="mozboot.mozconfig", _import="find_mozconfig")
144 def mozconfig(mozconfig, old_configure, build_env, help):
145     if not old_configure and not help:
146         die("The OLD_CONFIGURE environment variable must be set")
148     # Don't read the mozconfig for the js configure (yay backwards
149     # compatibility)
150     # While the long term goal is that js and top-level use the same configure
151     # and the same overall setup, including the possibility to use mozconfigs,
152     # figuring out what we want to do wrt mozconfig vs. command line and
153     # environment variable is not a clear-cut case, and it's more important to
154     # fix the immediate problem mozconfig causes to js developers by
155     # "temporarily" returning to the previous behavior of not loading the
156     # mozconfig for the js configure.
157     # Separately to the immediate problem for js developers, there is also the
158     # need to not load a mozconfig when running js configure as a subconfigure.
159     # Unfortunately, there is no direct way to tell whether the running
160     # configure is the js configure. The indirect way is to look at the
161     # OLD_CONFIGURE path, which points to js/src/old-configure.
162     # I expect we'll have figured things out for mozconfigs well before
163     # old-configure dies.
164     if old_configure and os.path.dirname(os.path.abspath(old_configure[0])).endswith(
165         "/js/src"
166     ):
167         return {"path": None}
169     topsrcdir = build_env.topsrcdir
170     loader = MozconfigLoader(topsrcdir)
171     mozconfig = mozconfig[0] if mozconfig else None
172     mozconfig = find_mozconfig(topsrcdir, env={"MOZCONFIG": mozconfig})
173     mozconfig = loader.read_mozconfig(mozconfig)
175     return mozconfig
178 set_config("MOZCONFIG", depends(mozconfig)(lambda m: m["path"]))
181 # Mozilla-Build
182 # ==============================================================
183 option(env="MOZILLABUILD", nargs=1, help="Path to Mozilla Build (Windows-only)")
185 option(env="CONFIG_SHELL", nargs=1, help="Path to a POSIX shell")
187 # It feels dirty replicating this from python/mozbuild/mozbuild/mozconfig.py,
188 # but the end goal being that the configure script would go away...
191 @depends("CONFIG_SHELL", "MOZILLABUILD")
192 @checking("for a shell")
193 @imports("sys")
194 def shell(value, mozillabuild):
195     if value:
196         return find_program(value[0])
197     shell = "sh"
198     if mozillabuild:
199         shell = mozillabuild[0] + "/msys/bin/sh"
200     if sys.platform == "win32":
201         shell = shell + ".exe"
202     return find_program(shell)
205 # This defines a reasonable shell for when running with --help.
206 # If one was passed in the environment, though, fall back to that.
207 @depends("--help", "CONFIG_SHELL")
208 def help_shell(help, shell):
209     if help and not shell:
210         return "sh"
213 shell = help_shell | shell
216 # Python 3
217 # ========
219 option(env="PYTHON3", nargs=1, help="Python 3 interpreter (3.6 or later)")
221 option(
222     env="VIRTUALENV_NAME",
223     nargs=1,
224     default="init_py3",
225     help="Name of the in-objdir virtualenv",
229 @depends("PYTHON3", "VIRTUALENV_NAME", check_build_environment, mozconfig, "--help")
230 @imports(_from="__builtin__", _import="Exception")
231 @imports("os")
232 @imports("sys")
233 @imports("subprocess")
234 @imports("distutils.sysconfig")
235 @imports(_from="mozbuild.configure.util", _import="LineIO")
236 @imports(_from="mozbuild.virtualenv", _import="VirtualenvManager")
237 @imports(_from="mozbuild.virtualenv", _import="verify_python_version")
238 @imports(_from="mozbuild.pythonutil", _import="find_python3_executable")
239 @imports(_from="mozbuild.pythonutil", _import="python_executable_version")
240 @imports(_from="six", _import="ensure_text")
241 def virtualenv_python3(env_python, virtualenv_name, build_env, mozconfig, help):
242     # Avoid re-executing python when running configure --help.
243     if help:
244         return
246     # NOTE: We cannot assume the Python we are calling this code with is the
247     # Python we want to set up a virtualenv for.
248     #
249     # We also cannot assume that the Python the caller is configuring meets our
250     # build requirements.
251     #
252     # Because of this the code is written to re-execute itself with the correct
253     # interpreter if required.
255     log.debug("python3: running with pid %r" % os.getpid())
256     log.debug("python3: sys.executable: %r" % sys.executable)
258     python = env_python[0] if env_python else None
259     virtualenv_name = virtualenv_name[0]
261     # Did our python come from mozconfig? Overrides environment setting.
262     # Ideally we'd rely on the mozconfig injection from mozconfig_options,
263     # but we'd rather avoid the verbosity when we need to reexecute with
264     # a different python.
265     if mozconfig["path"]:
266         if "PYTHON3" in mozconfig["env"]["added"]:
267             python = mozconfig["env"]["added"]["PYTHON3"]
268         elif "PYTHON3" in mozconfig["env"]["modified"]:
269             python = mozconfig["env"]["modified"]["PYTHON3"][1]
270         elif "PYTHON3" in mozconfig["vars"]["added"]:
271             python = mozconfig["vars"]["added"]["PYTHON3"]
272         elif "PYTHON3" in mozconfig["vars"]["modified"]:
273             python = mozconfig["vars"]["modified"]["PYTHON3"][1]
275     log.debug("python3: executable from configuration: %r" % python)
277     # Verify that the Python version we executed this code with is the minimum
278     # required version to handle all project code.
279     with LineIO(lambda l: log.error(l)) as out:
280         verify_python_version(out)
282     # If this is a mozilla-central build, we'll find the virtualenv in the top
283     # source directory. If this is a SpiderMonkey build, we assume we're at
284     # js/src and try to find the virtualenv from the mozilla-central root.
285     # See mozilla-central changeset d2cce982a7c809815d86d5daecefe2e7a563ecca
286     # Bug 784841
287     topsrcdir, topobjdir = build_env.topsrcdir, build_env.topobjdir
288     if topobjdir.endswith("/js/src"):
289         topobjdir = topobjdir[:-7]
291     virtualenvs_root = os.path.join(topobjdir, "_virtualenvs")
292     with LineIO(lambda l: log.info(l), "replace") as out:
293         manager = VirtualenvManager(
294             topsrcdir,
295             os.path.join(virtualenvs_root, virtualenv_name),
296             out,
297             os.path.join(topsrcdir, "build", "build_virtualenv_packages.txt"),
298         )
300     # If we're not in the virtualenv, we need to update the path to include some
301     # necessary modules for find_program.
302     if "MOZBUILD_VIRTUALENV" in os.environ:
303         python = sys.executable
304     else:
305         sys.path.insert(0, os.path.join(topsrcdir, "testing", "mozbase", "mozfile"))
306         sys.path.insert(
307             0, os.path.join(topsrcdir, "third_party", "python", "backports")
308         )
310     # If we know the Python executable the caller is asking for then verify its
311     # version. If the caller did not ask for a specific executable then find
312     # a reasonable default.
313     if python:
314         found_python = find_program(python)
315         if not found_python:
316             die(
317                 "The PYTHON3 environment variable does not contain "
318                 "a valid path. Cannot find %s",
319                 python,
320             )
321         python = found_python
322         try:
323             version = python_executable_version(python).version
324         except Exception as e:
325             raise FatalCheckError(
326                 "could not determine version of PYTHON3 " "(%s): %s" % (python, e)
327             )
328     else:
329         # Fall back to the search routine.
330         python, version = find_python3_executable(min_version="3.6.0")
332         # The API returns a bytes whereas everything in configure is unicode.
333         if python:
334             python = ensure_text(python)
336     if not python:
337         raise FatalCheckError(
338             "Python 3.6 or newer is required to build. "
339             "Ensure a `python3.x` executable is in your "
340             "PATH or define PYTHON3 to point to a Python "
341             "3.6 executable."
342         )
344     if version < (3, 6, 0):
345         raise FatalCheckError(
346             "Python 3.6 or newer is required to build; "
347             "%s is Python %d.%d" % (python, version[0], version[1])
348         )
350     log.debug("python3: found executable: %r" % python)
352     if not manager.up_to_date(python):
353         log.info("Creating Python 3 environment")
354         manager.build(python)
355     else:
356         log.debug("python3: venv is up to date")
358     python = normsep(manager.python_path)
360     if not normsep(sys.executable).startswith(normsep(virtualenvs_root)):
361         log.debug(
362             "python3: executing as %s, should be running as %s"
363             % (sys.executable, manager.python_path)
364         )
365         log.info("Re-executing in the virtualenv")
366         if env_python:
367             del os.environ["PYTHON3"]
368         # Homebrew on macOS will change Python's sys.executable to a custom
369         # value which messes with mach's virtualenv handling code. Override
370         # Homebrew's changes with the correct sys.executable value.
371         os.environ["PYTHONEXECUTABLE"] = python
372         # Another quirk on macOS, with the system python, the virtualenv is
373         # not fully operational (missing entries in sys.path) if
374         # __PYVENV_LAUNCHER__ is set.
375         os.environ.pop("__PYVENV_LAUNCHER__", None)
376         # One would prefer to use os.execl, but that's completely borked on
377         # Windows.
378         sys.exit(subprocess.call([python] + sys.argv))
380     # We are now in the virtualenv
381     if not distutils.sysconfig.get_python_lib():
382         die("Could not determine python site packages directory")
384     # We may have set PYTHONEXECUTABLE above, and that affects python
385     # subprocesses we may invoke as part of configure (e.g. hg), so
386     # unset it.
387     os.environ.pop("PYTHONEXECUTABLE", None)
389     str_version = ".".join(str(v) for v in version)
391     return namespace(
392         path=python,
393         version=version,
394         str_version=str_version,
395     )
398 @depends(virtualenv_python3)
399 @checking("for Python 3", callback=lambda x: "%s (%s)" % (x.path, x.str_version))
400 def virtualenv_python3(venv):
401     return venv
404 set_config("PYTHON3", virtualenv_python3.path)
405 set_config("PYTHON3_VERSION", virtualenv_python3.str_version)
406 add_old_configure_assignment("PYTHON3", virtualenv_python3.path)
409 # Inject mozconfig options
410 # ==============================================================
411 # All options defined above this point can't be injected in mozconfig_options
412 # below, so collect them.
415 @template
416 def early_options():
417     @depends("--help")
418     @imports("__sandbox__")
419     @imports(_from="six", _import="itervalues")
420     def early_options(_):
421         return set(
422             option.env for option in itervalues(__sandbox__._options) if option.env
423         )
425     return early_options
428 early_options = early_options()
431 @depends(mozconfig, early_options, "MOZ_AUTOMATION", "--help")
432 # This gives access to the sandbox. Don't copy this blindly.
433 @imports("__sandbox__")
434 @imports("os")
435 @imports("six")
436 def mozconfig_options(mozconfig, early_options, automation, help):
437     if mozconfig["path"]:
438         if "MOZ_AUTOMATION_MOZCONFIG" in mozconfig["env"]["added"]:
439             if not automation:
440                 log.error(
441                     "%s directly or indirectly includes an in-tree " "mozconfig.",
442                     mozconfig["path"],
443                 )
444                 log.error(
445                     "In-tree mozconfigs make strong assumptions about "
446                     "and are only meant to be used by Mozilla "
447                     "automation."
448                 )
449                 die("Please don't use them.")
450         helper = __sandbox__._helper
451         log.info("Adding configure options from %s" % mozconfig["path"])
452         for arg in mozconfig["configure_args"]:
453             log.info("  %s" % arg)
454             # We could be using imply_option() here, but it has other
455             # contraints that don't really apply to the command-line
456             # emulation that mozconfig provides.
457             helper.add(arg, origin="mozconfig", args=helper._args)
459         def add(key, value):
460             if key.isupper():
461                 arg = "%s=%s" % (key, value)
462                 log.info("  %s" % arg)
463                 if key not in early_options:
464                     helper.add(arg, origin="mozconfig", args=helper._args)
466         for key, value in six.iteritems(mozconfig["env"]["added"]):
467             add(key, value)
468             os.environ[key] = value
469         for key, (_, value) in six.iteritems(mozconfig["env"]["modified"]):
470             add(key, value)
471             os.environ[key] = value
472         for key, value in six.iteritems(mozconfig["vars"]["added"]):
473             add(key, value)
474         for key, (_, value) in six.iteritems(mozconfig["vars"]["modified"]):
475             add(key, value)
478 # Source checkout and version control integration.
479 # ================================================
482 @depends(check_build_environment, "MOZ_AUTOMATION", "--help")
483 @checking("for vcs source checkout")
484 @imports("os")
485 def vcs_checkout_type(build_env, automation, help):
486     if os.path.exists(os.path.join(build_env.topsrcdir, ".hg")):
487         return "hg"
488     elif os.path.exists(os.path.join(build_env.topsrcdir, ".git")):
489         return "git"
490     elif automation and not help:
491         raise FatalCheckError(
492             "unable to resolve VCS type; must run "
493             "from a source checkout when MOZ_AUTOMATION "
494             "is set"
495         )
498 # Resolve VCS binary for detected repository type.
501 # TODO remove hg.exe once bug 1382940 addresses ambiguous executables case.
502 hg = check_prog(
503     "HG",
504     (
505         "hg.exe",
506         "hg",
507     ),
508     allow_missing=True,
509     when=depends(vcs_checkout_type)(lambda x: x == "hg"),
511 git = check_prog(
512     "GIT",
513     ("git",),
514     allow_missing=True,
515     when=depends(vcs_checkout_type)(lambda x: x == "git"),
519 @depends_if(hg)
520 @checking("for Mercurial version")
521 @imports("os")
522 @imports("re")
523 def hg_version(hg):
524     # HGPLAIN in Mercurial 1.5+ forces stable output, regardless of set
525     # locale or encoding.
526     env = dict(os.environ)
527     env["HGPLAIN"] = "1"
529     out = check_cmd_output(hg, "--version", env=env)
531     match = re.search(r"Mercurial Distributed SCM \(version ([^\)]+)", out)
533     if not match:
534         raise FatalCheckError("unable to determine Mercurial version: %s" % out)
536     # The version string may be "unknown" for Mercurial run out of its own
537     # source checkout or for bad builds. But LooseVersion handles it.
539     return Version(match.group(1))
542 # Resolve Mercurial config items so other checks have easy access.
543 # Do NOT set this in the config because it may contain sensitive data
544 # like API keys.
547 @depends_all(check_build_environment, hg, hg_version)
548 @imports("os")
549 def hg_config(build_env, hg, version):
550     env = dict(os.environ)
551     env["HGPLAIN"] = "1"
553     # Warnings may get sent to stderr. But check_cmd_output() ignores
554     # stderr if exit code is 0. And the command should always succeed if
555     # `hg version` worked.
556     out = check_cmd_output(hg, "config", env=env, cwd=build_env.topsrcdir)
558     config = {}
560     for line in out.strip().splitlines():
561         key, value = [s.strip() for s in line.split("=", 1)]
562         config[key] = value
564     return config
567 @depends_if(git)
568 @checking("for Git version")
569 @imports("re")
570 def git_version(git):
571     out = check_cmd_output(git, "--version").rstrip()
573     match = re.search("git version (.*)$", out)
575     if not match:
576         raise FatalCheckError("unable to determine Git version: %s" % out)
578     return Version(match.group(1))
581 # Only set VCS_CHECKOUT_TYPE if we resolved the VCS binary.
582 # Require resolved VCS info when running in automation so automation's
583 # environment is more well-defined.
586 @depends(vcs_checkout_type, hg_version, git_version, "MOZ_AUTOMATION")
587 def exposed_vcs_checkout_type(vcs_checkout_type, hg, git, automation):
588     if vcs_checkout_type == "hg":
589         if hg:
590             return "hg"
592         if automation:
593             raise FatalCheckError("could not resolve Mercurial binary info")
595     elif vcs_checkout_type == "git":
596         if git:
597             return "git"
599         if automation:
600             raise FatalCheckError("could not resolve Git binary info")
601     elif vcs_checkout_type:
602         raise FatalCheckError("unhandled VCS type: %s" % vcs_checkout_type)
605 set_config("VCS_CHECKOUT_TYPE", exposed_vcs_checkout_type)
607 # Obtain a Repository interface for the current VCS repository.
610 @depends(check_build_environment, exposed_vcs_checkout_type, hg, git)
611 @imports(_from="mozversioncontrol", _import="get_repository_object")
612 def vcs_repository(build_env, vcs_checkout_type, hg, git):
613     if vcs_checkout_type == "hg":
614         return get_repository_object(build_env.topsrcdir, hg=hg)
615     elif vcs_checkout_type == "git":
616         return get_repository_object(build_env.topsrcdir, git=git)
617     elif vcs_checkout_type:
618         raise FatalCheckError("unhandled VCS type: %s" % vcs_checkout_type)
621 @depends_if(vcs_repository)
622 @checking("for sparse checkout")
623 def vcs_sparse_checkout(repo):
624     return repo.sparse_checkout_present()
627 set_config("VCS_SPARSE_CHECKOUT", vcs_sparse_checkout)
629 # The application/project to build
630 # ==============================================================
631 option(
632     "--enable-application",
633     nargs=1,
634     env="MOZ_BUILD_APP",
635     help="Application to build. Same as --enable-project.",
639 @depends("--enable-application")
640 def application(app):
641     if app:
642         return app
645 imply_option("--enable-project", application)
648 @depends(check_build_environment)
649 def default_project(build_env):
650     if build_env.topobjdir.endswith("/js/src"):
651         return "js"
652     return "browser"
655 option("--enable-project", nargs=1, default=default_project, help="Project to build")
658 # Host and target systems
659 # ==============================================================
660 option("--host", nargs=1, help="Define the system type performing the build")
662 option(
663     "--target",
664     nargs=1,
665     help="Define the system type where the resulting executables will be " "used",
669 @imports(_from="mozbuild.configure.constants", _import="CPU")
670 @imports(_from="mozbuild.configure.constants", _import="CPU_bitness")
671 @imports(_from="mozbuild.configure.constants", _import="Endianness")
672 @imports(_from="mozbuild.configure.constants", _import="Kernel")
673 @imports(_from="mozbuild.configure.constants", _import="OS")
674 @imports(_from="__builtin__", _import="ValueError")
675 def split_triplet(triplet, allow_msvc=False):
676     # The standard triplet is defined as
677     #   CPU_TYPE-VENDOR-OPERATING_SYSTEM
678     # There is also a quartet form:
679     #   CPU_TYPE-VENDOR-KERNEL-OPERATING_SYSTEM
680     # But we can consider the "KERNEL-OPERATING_SYSTEM" as one.
681     # Additionally, some may omit "unknown" when the vendor
682     # is not specified and emit
683     #   CPU_TYPE-OPERATING_SYSTEM
684     vendor = "unknown"
685     parts = triplet.split("-", 2)
686     if len(parts) == 3:
687         cpu, vendor, os = parts
688     elif len(parts) == 2:
689         cpu, os = parts
690     else:
691         raise ValueError("Unexpected triplet string: %s" % triplet)
693     # Autoconf uses config.sub to validate and canonicalize those triplets,
694     # but the granularity of its results has never been satisfying to our
695     # use, so we've had our own, different, canonicalization. We've also
696     # historically not been very consistent with how we use the canonicalized
697     # values. Hopefully, this will help us make things better.
698     # The tests are inherited from our decades-old autoconf-based configure,
699     # which can probably be improved/cleaned up because they are based on a
700     # mix of uname and config.guess output, while we now only use the latter,
701     # which presumably has a cleaner and leaner output. Let's refine later.
702     os = os.replace("/", "_")
703     if "android" in os:
704         canonical_os = "Android"
705         canonical_kernel = "Linux"
706     elif os.startswith("linux"):
707         canonical_os = "GNU"
708         canonical_kernel = "Linux"
709     elif os.startswith("kfreebsd") and os.endswith("-gnu"):
710         canonical_os = "GNU"
711         canonical_kernel = "kFreeBSD"
712     elif os.startswith("gnu"):
713         canonical_os = canonical_kernel = "GNU"
714     elif os.startswith("mingw") or (allow_msvc and os == "windows-msvc"):
715         # windows-msvc is only opt-in for the caller of this function until
716         # full support in bug 1617793.
717         canonical_os = canonical_kernel = "WINNT"
718     elif os.startswith("darwin"):
719         canonical_kernel = "Darwin"
720         canonical_os = "OSX"
721     elif os.startswith("dragonfly"):
722         canonical_os = canonical_kernel = "DragonFly"
723     elif os.startswith("freebsd"):
724         canonical_os = canonical_kernel = "FreeBSD"
725     elif os.startswith("netbsd"):
726         canonical_os = canonical_kernel = "NetBSD"
727     elif os.startswith("openbsd"):
728         canonical_os = canonical_kernel = "OpenBSD"
729     elif os.startswith("solaris"):
730         canonical_os = canonical_kernel = "SunOS"
731     else:
732         raise ValueError("Unknown OS: %s" % os)
734     # The CPU granularity is probably not enough. Moving more things from
735     # old-configure will tell us if we need more
736     if cpu.endswith("86") or (cpu.startswith("i") and "86" in cpu):
737         canonical_cpu = "x86"
738         endianness = "little"
739     elif cpu in ("x86_64", "ia64"):
740         canonical_cpu = cpu
741         endianness = "little"
742     elif cpu in ("s390", "s390x"):
743         canonical_cpu = cpu
744         endianness = "big"
745     elif cpu in ("powerpc64", "ppc64", "powerpc64le", "ppc64le"):
746         canonical_cpu = "ppc64"
747         endianness = "little" if "le" in cpu else "big"
748     elif cpu in ("powerpc", "ppc", "rs6000") or cpu.startswith("powerpc"):
749         canonical_cpu = "ppc"
750         endianness = "big"
751     elif cpu in ("Alpha", "alpha", "ALPHA"):
752         canonical_cpu = "Alpha"
753         endianness = "little"
754     elif cpu.startswith("hppa") or cpu == "parisc":
755         canonical_cpu = "hppa"
756         endianness = "big"
757     elif cpu.startswith("sparc64") or cpu.startswith("sparcv9"):
758         canonical_cpu = "sparc64"
759         endianness = "big"
760     elif cpu.startswith("sparc") or cpu == "sun4u":
761         canonical_cpu = "sparc"
762         endianness = "big"
763     elif cpu.startswith("arm"):
764         canonical_cpu = "arm"
765         endianness = "big" if cpu.startswith(("armeb", "armbe")) else "little"
766     elif cpu in ("m68k"):
767         canonical_cpu = "m68k"
768         endianness = "big"
769     elif cpu in ("mips", "mipsel"):
770         canonical_cpu = "mips32"
771         endianness = "little" if "el" in cpu else "big"
772     elif cpu in ("mips64", "mips64el"):
773         canonical_cpu = "mips64"
774         endianness = "little" if "el" in cpu else "big"
775     elif cpu.startswith("aarch64"):
776         canonical_cpu = "aarch64"
777         endianness = "little"
778     elif cpu in ("riscv64", "riscv64gc"):
779         canonical_cpu = "riscv64"
780         endianness = "little"
781     elif cpu == "sh4":
782         canonical_cpu = "sh4"
783         endianness = "little"
784     else:
785         raise ValueError("Unknown CPU type: %s" % cpu)
787     # Toolchains, most notably for cross compilation may use cpu-os
788     # prefixes. We need to be more specific about the LLVM target on Mac
789     # so cross-language LTO will work correctly.
791     if os.startswith("darwin"):
792         toolchain = "%s-apple-%s" % (cpu, os)
793     elif canonical_cpu == "aarch64" and canonical_os == "WINNT":
794         toolchain = "aarch64-windows-msvc"
795     else:
796         toolchain = "%s-%s" % (cpu, os)
798     return namespace(
799         alias=triplet,
800         cpu=CPU(canonical_cpu),
801         bitness=CPU_bitness[canonical_cpu],
802         kernel=Kernel(canonical_kernel),
803         os=OS(canonical_os),
804         endianness=Endianness(endianness),
805         raw_cpu=cpu,
806         raw_os=os,
807         toolchain=toolchain,
808         vendor=vendor,
809     )
812 # This defines a fake target/host namespace for when running with --help
813 # If either --host or --target is passed on the command line, then fall
814 # back to the real deal.
815 @depends("--help", "--host", "--target")
816 def help_host_target(help, host, target):
817     if help and not host and not target:
818         return namespace(
819             alias="unknown-unknown-unknown",
820             cpu="unknown",
821             bitness="unknown",
822             kernel="unknown",
823             os="unknown",
824             endianness="unknown",
825             raw_cpu="unknown",
826             raw_os="unknown",
827             toolchain="unknown-unknown",
828         )
831 def config_sub(shell, triplet):
832     config_sub = os.path.join(os.path.dirname(__file__), "..", "autoconf", "config.sub")
833     return check_cmd_output(shell, config_sub, triplet).strip()
836 @depends("--host", shell)
837 @checking("for host system type", lambda h: h.alias)
838 @imports("os")
839 @imports("sys")
840 @imports(_from="__builtin__", _import="ValueError")
841 def real_host(value, shell):
842     if not value and sys.platform == "win32":
843         arch = os.environ.get("PROCESSOR_ARCHITEW6432") or os.environ.get(
844             "PROCESSOR_ARCHITECTURE"
845         )
846         if arch == "AMD64":
847             return split_triplet("x86_64-pc-mingw32")
848         elif arch == "x86":
849             return split_triplet("i686-pc-mingw32")
851     if not value:
852         config_guess = os.path.join(
853             os.path.dirname(__file__), "..", "autoconf", "config.guess"
854         )
856         # Ensure that config.guess is determining the host triplet, not the target
857         # triplet
858         env = os.environ.copy()
859         env.pop("CC_FOR_BUILD", None)
860         env.pop("HOST_CC", None)
861         env.pop("CC", None)
863         host = check_cmd_output(shell, config_guess, env=env).strip()
864         try:
865             return split_triplet(host)
866         except ValueError:
867             pass
868     else:
869         host = value[0]
871     host = config_sub(shell, host)
873     try:
874         return split_triplet(host)
875     except ValueError as e:
876         die(e)
879 host = help_host_target | real_host
882 @depends("--target", real_host, shell, "--enable-project", "--enable-application")
883 @checking("for target system type", lambda t: t.alias)
884 @imports(_from="__builtin__", _import="ValueError")
885 def real_target(value, host, shell, project, application):
886     # Because --enable-project is implied by --enable-application, and
887     # implied options are not currently handled during --help, which is
888     # used get the build target in mozbuild.base, we manually check
889     # whether --enable-application was given, and fall back to
890     # --enable-project if not. Both can't be given contradictory values
891     # under normal circumstances, so it's fine.
892     if application:
893         project = application[0]
894     elif project:
895         project = project[0]
896     if not value:
897         if project == "mobile/android":
898             if host.raw_os == "mingw32":
899                 log.warning(
900                     "Building Firefox for Android on Windows is not fully "
901                     "supported. See https://bugzilla.mozilla.org/show_bug.cgi?"
902                     "id=1169873 for details."
903                 )
904             return split_triplet("arm-unknown-linux-androideabi")
905         return host
906     # If --target was only given a cpu arch, expand it with the
907     # non-cpu part of the host. For mobile/android, expand it with
908     # unknown-linux-android.
909     target = value[0]
910     if "-" not in target:
911         if project == "mobile/android":
912             rest = "unknown-linux-android"
913             if target.startswith("arm"):
914                 rest += "eabi"
915         else:
916             cpu, rest = host.alias.split("-", 1)
917         target = "-".join((target, rest))
918         try:
919             return split_triplet(target)
920         except ValueError:
921             pass
923     try:
924         return split_triplet(config_sub(shell, target))
925     except ValueError as e:
926         die(e)
929 target = help_host_target | real_target
932 @depends(host, target)
933 @checking("whether cross compiling")
934 def cross_compiling(host, target):
935     return host != target
938 set_config("CROSS_COMPILE", cross_compiling)
939 set_define("CROSS_COMPILE", cross_compiling)
940 add_old_configure_assignment("CROSS_COMPILE", cross_compiling)
943 @depends(target)
944 def have_64_bit(target):
945     if target.bitness == 64:
946         return True
949 set_config("HAVE_64BIT_BUILD", have_64_bit)
950 set_define("HAVE_64BIT_BUILD", have_64_bit)
951 add_old_configure_assignment("HAVE_64BIT_BUILD", have_64_bit)
954 @depends(host)
955 def host_os_kernel_major_version(host):
956     versions = host.raw_os.split(".")
957     version = "".join(x for x in versions[0] if x.isdigit())
958     return version
961 set_config("HOST_MAJOR_VERSION", host_os_kernel_major_version)
963 # Autoconf needs these set
966 @depends(host)
967 def host_for_sub_configure(host):
968     return "--host=%s" % host.alias
971 @depends(target)
972 def target_for_sub_configure(target):
973     target_alias = target.alias
974     return "--target=%s" % target_alias
977 # These variables are for compatibility with the current moz.builds and
978 # old-configure. Eventually, we'll want to canonicalize better.
979 @depends(target)
980 def target_variables(target):
981     if target.kernel == "kFreeBSD":
982         os_target = "GNU/kFreeBSD"
983         os_arch = "GNU_kFreeBSD"
984     elif target.kernel == "Darwin" or (target.kernel == "Linux" and target.os == "GNU"):
985         os_target = target.kernel
986         os_arch = target.kernel
987     else:
988         os_target = target.os
989         os_arch = target.kernel
991     return namespace(
992         OS_TARGET=os_target,
993         OS_ARCH=os_arch,
994         INTEL_ARCHITECTURE=target.cpu in ("x86", "x86_64") or None,
995     )
998 set_config("OS_TARGET", target_variables.OS_TARGET)
999 add_old_configure_assignment("OS_TARGET", target_variables.OS_TARGET)
1000 set_config("OS_ARCH", target_variables.OS_ARCH)
1001 add_old_configure_assignment("OS_ARCH", target_variables.OS_ARCH)
1002 set_config("CPU_ARCH", target.cpu)
1003 add_old_configure_assignment("CPU_ARCH", target.cpu)
1004 set_config("INTEL_ARCHITECTURE", target_variables.INTEL_ARCHITECTURE)
1005 set_config("TARGET_CPU", target.raw_cpu)
1006 set_config("TARGET_OS", target.raw_os)
1007 set_config("TARGET_ENDIANNESS", target.endianness)
1010 @depends(host)
1011 def host_variables(host):
1012     if host.kernel == "kFreeBSD":
1013         os_arch = "GNU_kFreeBSD"
1014     else:
1015         os_arch = host.kernel
1016     return namespace(
1017         HOST_OS_ARCH=os_arch,
1018     )
1021 set_config("HOST_CPU_ARCH", host.cpu)
1022 set_config("HOST_OS_ARCH", host_variables.HOST_OS_ARCH)
1023 add_old_configure_assignment("HOST_OS_ARCH", host_variables.HOST_OS_ARCH)
1026 @depends(target)
1027 def target_is_windows(target):
1028     if target.kernel == "WINNT":
1029         return True
1032 set_define("_WINDOWS", target_is_windows)
1033 set_define("WIN32", target_is_windows)
1034 set_define("XP_WIN", target_is_windows)
1037 @depends(target)
1038 def target_is_unix(target):
1039     if target.kernel != "WINNT":
1040         return True
1043 set_define("XP_UNIX", target_is_unix)
1046 @depends(target)
1047 def target_is_darwin(target):
1048     if target.kernel == "Darwin":
1049         return True
1052 set_define("XP_DARWIN", target_is_darwin)
1055 @depends(target)
1056 def target_is_osx(target):
1057     if target.kernel == "Darwin" and target.os == "OSX":
1058         return True
1061 set_define("XP_MACOSX", target_is_osx)
1064 @depends(target)
1065 def target_is_linux(target):
1066     if target.kernel == "Linux":
1067         return True
1070 set_define("XP_LINUX", target_is_linux)
1073 @depends(target)
1074 def target_is_android(target):
1075     if target.os == "Android":
1076         return True
1079 set_define("ANDROID", target_is_android)
1082 @depends(target)
1083 def target_is_openbsd(target):
1084     if target.kernel == "OpenBSD":
1085         return True
1088 set_define("XP_OPENBSD", target_is_openbsd)
1091 @depends(target)
1092 def target_is_netbsd(target):
1093     if target.kernel == "NetBSD":
1094         return True
1097 set_define("XP_NETBSD", target_is_netbsd)
1100 @depends(target)
1101 def target_is_freebsd(target):
1102     if target.kernel == "FreeBSD":
1103         return True
1106 set_define("XP_FREEBSD", target_is_freebsd)
1109 @depends(target)
1110 def target_is_solaris(target):
1111     if target.kernel == "SunOS":
1112         return True
1115 set_define("XP_SOLARIS", target_is_solaris)
1118 @depends(target)
1119 def target_is_sparc(target):
1120     if target.cpu == "sparc64":
1121         return True
1124 set_define("SPARC64", target_is_sparc)
1127 @depends("--enable-project", check_build_environment, "--help")
1128 @imports(_from="os.path", _import="exists")
1129 def include_project_configure(project, build_env, help):
1130     if not project:
1131         die("--enable-project is required.")
1133     base_dir = build_env.topsrcdir
1134     path = os.path.join(base_dir, project[0], "moz.configure")
1135     if not exists(path):
1136         die("Cannot find project %s", project[0])
1137     return path
1140 @depends(include_project_configure, check_build_environment)
1141 def build_project(include_project_configure, build_env):
1142     ret = os.path.dirname(
1143         os.path.relpath(include_project_configure, build_env.topsrcdir)
1144     )
1145     return ret
1148 set_config("MOZ_BUILD_APP", build_project)
1149 set_define("MOZ_BUILD_APP", build_project)
1150 add_old_configure_assignment("MOZ_BUILD_APP", build_project)
1153 option(env="MOZILLA_OFFICIAL", help="Build an official release")
1156 @depends("MOZILLA_OFFICIAL")
1157 def mozilla_official(official):
1158     if official:
1159         return True
1162 set_config("MOZILLA_OFFICIAL", mozilla_official)
1163 set_define("MOZILLA_OFFICIAL", mozilla_official)
1164 add_old_configure_assignment("MOZILLA_OFFICIAL", mozilla_official)
1167 # Allow specifying custom paths to the version files used by the milestone() function below.
1168 option(
1169     "--with-version-file-path",
1170     nargs=1,
1171     help="Specify a custom path to app version files instead of auto-detecting",
1172     default=None,
1176 @depends("--with-version-file-path")
1177 def version_path(path):
1178     return path
1181 # set RELEASE_OR_BETA and NIGHTLY_BUILD variables depending on the cycle we're in
1182 # The logic works like this:
1183 # - if we have "a1" in GRE_MILESTONE, we're building Nightly (define NIGHTLY_BUILD)
1184 # - otherwise, if we have "a" in GRE_MILESTONE, we're building Nightly or Aurora
1185 # - otherwise, we're building Release/Beta (define RELEASE_OR_BETA)
1186 @depends(check_build_environment, build_project, version_path, "--help")
1187 @imports(_from="__builtin__", _import="open")
1188 @imports("os")
1189 @imports("re")
1190 def milestone(build_env, build_project, version_path, _):
1191     versions = []
1192     paths = ["config/milestone.txt"]
1193     if build_project == "js":
1194         paths = paths * 3
1195     else:
1196         paths += [
1197             "browser/config/version.txt",
1198             "browser/config/version_display.txt",
1199         ]
1200     if version_path:
1201         version_path = version_path[0]
1202     else:
1203         version_path = os.path.join(build_project, "config")
1204     for f in ("version.txt", "version_display.txt"):
1205         f = os.path.join(version_path, f)
1206         if not os.path.exists(os.path.join(build_env.topsrcdir, f)):
1207             break
1208         paths.append(f)
1210     for p in paths:
1211         with open(os.path.join(build_env.topsrcdir, p), "r") as fh:
1212             content = fh.read().splitlines()
1213             if not content:
1214                 die("Could not find a version number in {}".format(p))
1215             versions.append(content[-1])
1217     milestone, firefox_version, firefox_version_display = versions[:3]
1219     # version.txt content from the project directory if there is one, otherwise
1220     # the firefox version.
1221     app_version = versions[3] if len(versions) > 3 else firefox_version
1222     # version_display.txt content from the project directory if there is one,
1223     # otherwise version.txt content from the project directory, otherwise the
1224     # firefox version for display.
1225     app_version_display = versions[-1] if len(versions) > 3 else firefox_version_display
1227     is_nightly = is_release_or_beta = is_early_beta_or_earlier = None
1229     if "a1" in milestone:
1230         is_nightly = True
1231     elif "a" not in milestone:
1232         is_release_or_beta = True
1234     major_version = milestone.split(".")[0]
1235     m = re.search(r"([ab]\d+)", milestone)
1236     ab_patch = m.group(1) if m else ""
1238     defines = os.path.join(build_env.topsrcdir, "build", "defines.sh")
1239     with open(defines, "r") as fh:
1240         for line in fh.read().splitlines():
1241             line = line.strip()
1242             if not line or line.startswith("#"):
1243                 continue
1244             name, _, value = line.partition("=")
1245             name = name.strip()
1246             value = value.strip()
1247             if name != "EARLY_BETA_OR_EARLIER":
1248                 die(
1249                     "Only the EARLY_BETA_OR_EARLIER variable can be set in build/defines.sh"
1250                 )
1251             if value:
1252                 is_early_beta_or_earlier = True
1254     # Only expose the major version milestone in the UA string and hide the
1255     # patch leve (bugs 572659 and 870868).
1256     #
1257     # Only expose major milestone and alpha version in the symbolversion
1258     # string; as the name suggests, we use it for symbol versioning on Linux.
1259     return namespace(
1260         version=milestone,
1261         uaversion="%s.0" % major_version,
1262         symbolversion="%s%s" % (major_version, ab_patch),
1263         is_nightly=is_nightly,
1264         is_release_or_beta=is_release_or_beta,
1265         is_early_beta_or_earlier=is_early_beta_or_earlier,
1266         app_version=app_version,
1267         app_version_display=app_version_display,
1268     )
1271 set_config("GRE_MILESTONE", milestone.version)
1272 set_config("NIGHTLY_BUILD", milestone.is_nightly)
1273 set_define("NIGHTLY_BUILD", milestone.is_nightly)
1274 set_config("RELEASE_OR_BETA", milestone.is_release_or_beta)
1275 set_define("RELEASE_OR_BETA", milestone.is_release_or_beta)
1276 add_old_configure_assignment("RELEASE_OR_BETA", milestone.is_release_or_beta)
1277 set_config("EARLY_BETA_OR_EARLIER", milestone.is_early_beta_or_earlier)
1278 set_define("EARLY_BETA_OR_EARLIER", milestone.is_early_beta_or_earlier)
1279 add_old_configure_assignment(
1280     "EARLY_BETA_OR_EARLIER", milestone.is_early_beta_or_earlier
1282 set_define("MOZILLA_VERSION", depends(milestone)(lambda m: '"%s"' % m.version))
1283 set_config("MOZILLA_VERSION", milestone.version)
1284 set_define("MOZILLA_VERSION_U", milestone.version)
1285 set_define("MOZILLA_UAVERSION", depends(milestone)(lambda m: '"%s"' % m.uaversion))
1286 set_config("MOZILLA_SYMBOLVERSION", milestone.symbolversion)
1287 # JS configure still wants to look at these.
1288 add_old_configure_assignment("MOZILLA_VERSION", milestone.version)
1289 add_old_configure_assignment("MOZILLA_SYMBOLVERSION", milestone.symbolversion)
1291 set_config("MOZ_APP_VERSION", milestone.app_version)
1292 set_config("MOZ_APP_VERSION_DISPLAY", milestone.app_version_display)
1293 add_old_configure_assignment("MOZ_APP_VERSION", milestone.app_version)
1296 # Dummy function for availability in toolkit/moz.configure. Overridden in
1297 # mobile/android/moz.configure.
1298 @depends(milestone.is_nightly)
1299 def fennec_nightly(is_nightly):
1300     return is_nightly
1303 # The app update channel is 'default' when not supplied. The value is used in
1304 # the application's confvars.sh (and is made available to a project specific
1305 # moz.configure).
1306 option(
1307     "--enable-update-channel",
1308     nargs=1,
1309     help="Select application update channel",
1310     default="default",
1314 @depends("--enable-update-channel")
1315 def update_channel(channel):
1316     if not channel or channel[0] == "":
1317         return "default"
1318     return channel[0].lower()
1321 set_config("MOZ_UPDATE_CHANNEL", update_channel)
1322 set_define("MOZ_UPDATE_CHANNEL", update_channel)
1323 add_old_configure_assignment("MOZ_UPDATE_CHANNEL", update_channel)
1326 option(
1327     env="MOZBUILD_STATE_PATH",
1328     nargs=1,
1329     help="Path to a persistent state directory for the build system "
1330     "and related tools",
1334 @depends("MOZBUILD_STATE_PATH", "--help")
1335 @imports("os")
1336 def mozbuild_state_path(path, _):
1337     if path:
1338         return path[0]
1339     return os.path.expanduser(os.path.join("~", ".mozbuild"))
1342 # A template providing a shorthand for setting a variable. The created
1343 # option will only be settable with imply_option.
1344 # It is expected that a project-specific moz.configure will call imply_option
1345 # to set a value other than the default.
1346 # If required, the set_as_define and set_for_old_configure arguments
1347 # will additionally cause the variable to be set using set_define and
1348 # add_old_configure_assignment. util.configure would be an appropriate place for
1349 # this, but it uses add_old_configure_assignment, which is defined in this file.
1350 @template
1351 def project_flag(env=None, set_for_old_configure=False, set_as_define=False, **kwargs):
1353     if not env:
1354         configure_error("A project_flag must be passed a variable name to set.")
1356     opt = option(env=env, possible_origins=("implied",), **kwargs)
1358     @depends(opt.option)
1359     def option_implementation(value):
1360         if value:
1361             if len(value):
1362                 return value
1363             return bool(value)
1365     set_config(env, option_implementation)
1366     if set_as_define:
1367         set_define(env, option_implementation)
1368     if set_for_old_configure:
1369         add_old_configure_assignment(env, option_implementation)
1372 # milestone.is_nightly corresponds to cases NIGHTLY_BUILD is set.
1375 @depends(milestone)
1376 def enabled_in_nightly(milestone):
1377     return milestone.is_nightly
1380 # Branding
1381 # ==============================================================
1382 option(
1383     "--with-app-basename",
1384     env="MOZ_APP_BASENAME",
1385     nargs=1,
1386     help="Typically stays consistent for multiple branded versions of a "
1387     'given application (e.g. Aurora and Firefox both use "Firefox"), but '
1388     "may vary for full rebrandings (e.g. Iceweasel). Used for "
1389     'application.ini\'s "Name" field, which controls profile location in '
1390     'the absence of a "Profile" field (see below), and various system '
1391     "integration hooks (Unix remoting, Windows MessageWindow name, etc.",
1395 @depends("--with-app-basename", target_is_android)
1396 def moz_app_basename(value, target_is_android):
1397     if value:
1398         return value[0]
1399     if target_is_android:
1400         return "Fennec"
1401     return "Firefox"
1404 set_config(
1405     "MOZ_APP_BASENAME",
1406     moz_app_basename,
1407     when=depends(build_project)(lambda p: p != "js"),