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/.
6
7include("util.configure")
8include("checks.configure")
9
10# Make `toolkit` available when toolkit/moz.configure is not included.
11toolkit = dependable(None)
12# Likewise with `bindgen_config_paths` when
13# build/moz.configure/bindgen.configure is not included.
14bindgen_config_paths = dependable(None)
15
16option(env="DIST", nargs=1, help="DIST directory")
17
18
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")
25def check_build_environment(help, dist):
26    topobjdir = os.path.realpath(".")
27    topsrcdir = os.path.realpath(os.path.join(os.path.dirname(__file__), "..", ".."))
28
29    if dist:
30        dist = normsep(dist[0])
31    else:
32        dist = os.path.join(topobjdir, "dist")
33
34    result = namespace(
35        topsrcdir=topsrcdir,
36        topobjdir=topobjdir,
37        dist=dist,
38    )
39
40    if help:
41        return result
42
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)
55
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        )
68
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            )
93
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        )
113
114    return result
115
116
117set_config("TOPSRCDIR", check_build_environment.topsrcdir)
118set_config("TOPOBJDIR", check_build_environment.topobjdir)
119set_config("DIST", check_build_environment.dist)
120
121add_old_configure_assignment("_topsrcdir", check_build_environment.topsrcdir)
122add_old_configure_assignment("_objdir", check_build_environment.topobjdir)
123add_old_configure_assignment("DIST", check_build_environment.dist)
124
125option(env="MOZ_AUTOMATION", help="Enable options for automated builds")
126set_config("MOZ_AUTOMATION", depends_if("MOZ_AUTOMATION")(lambda x: True))
127
128
129option(env="OLD_CONFIGURE", nargs=1, help="Path to the old configure script")
130
131option(env="MOZCONFIG", nargs=1, help="Mozconfig location")
132
133
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.
139
140
141@depends("MOZCONFIG", "OLD_CONFIGURE", check_build_environment, "--help")
142@imports(_from="mozbuild.mozconfig", _import="MozconfigLoader")
143@imports(_from="mozboot.mozconfig", _import="find_mozconfig")
144def 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")
147
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}
168
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)
174
175    return mozconfig
176
177
178set_config("MOZCONFIG", depends(mozconfig)(lambda m: m["path"]))
179
180
181# Mozilla-Build
182# ==============================================================
183option(env="MOZILLABUILD", nargs=1, help="Path to Mozilla Build (Windows-only)")
184
185option(env="CONFIG_SHELL", nargs=1, help="Path to a POSIX shell")
186
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...
189
190
191@depends("CONFIG_SHELL", "MOZILLABUILD")
192@checking("for a shell")
193@imports("sys")
194def 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)
203
204
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")
208def help_shell(help, shell):
209    if help and not shell:
210        return "sh"
211
212
213shell = help_shell | shell
214
215
216# Python 3
217# ========
218
219option(env="PYTHON3", nargs=1, help="Python 3 interpreter (3.6 or later)")
220
221
222@depends("PYTHON3", check_build_environment, mozconfig, "--help")
223@imports(_from="__builtin__", _import="Exception")
224@imports("os")
225@imports("sys")
226@imports("subprocess")
227@imports("distutils.sysconfig")
228@imports(_from="mozbuild.configure.util", _import="LineIO")
229@imports(_from="mozbuild.virtualenv", _import="VirtualenvManager")
230@imports(_from="mozbuild.virtualenv", _import="verify_python_version")
231@imports(_from="mozbuild.pythonutil", _import="find_python3_executable")
232@imports(_from="mozbuild.pythonutil", _import="python_executable_version")
233@imports(_from="six", _import="ensure_text")
234@imports(_from="__builtin__", _import="KeyError")
235def virtualenv_python3(env_python, build_env, mozconfig, help):
236    # Avoid re-executing python when running configure --help.
237    if help:
238        return
239
240    # NOTE: We cannot assume the Python we are calling this code with is the
241    # Python we want to set up a virtualenv for.
242    #
243    # We also cannot assume that the Python the caller is configuring meets our
244    # build requirements.
245    #
246    # Because of this the code is written to re-execute itself with the correct
247    # interpreter if required.
248
249    log.debug("python3: running with pid %r" % os.getpid())
250    log.debug("python3: sys.executable: %r" % sys.executable)
251
252    python = env_python[0] if env_python else None
253
254    # Did our python come from mozconfig? Overrides environment setting.
255    # Ideally we'd rely on the mozconfig injection from mozconfig_options,
256    # but we'd rather avoid the verbosity when we need to reexecute with
257    # a different python.
258    if mozconfig["path"]:
259        if "PYTHON3" in mozconfig["env"]["added"]:
260            python = mozconfig["env"]["added"]["PYTHON3"]
261        elif "PYTHON3" in mozconfig["env"]["modified"]:
262            python = mozconfig["env"]["modified"]["PYTHON3"][1]
263        elif "PYTHON3" in mozconfig["vars"]["added"]:
264            python = mozconfig["vars"]["added"]["PYTHON3"]
265        elif "PYTHON3" in mozconfig["vars"]["modified"]:
266            python = mozconfig["vars"]["modified"]["PYTHON3"][1]
267        for i in ("env", "vars"):
268            for j in ("added", "modified"):
269                try:
270                    del mozconfig[i][j]["PYTHON3"]
271                except KeyError:
272                    pass
273
274    log.debug("python3: executable from configuration: %r" % python)
275
276    # Verify that the Python version we executed this code with is the minimum
277    # required version to handle all project code.
278    with LineIO(lambda l: log.error(l)) as out:
279        verify_python_version(out)
280
281    # If this is a mozilla-central build, we'll find the virtualenv in the top
282    # source directory. If this is a SpiderMonkey build, we assume we're at
283    # js/src and try to find the virtualenv from the mozilla-central root.
284    # See mozilla-central changeset d2cce982a7c809815d86d5daecefe2e7a563ecca
285    # Bug 784841
286    topsrcdir, topobjdir = build_env.topsrcdir, build_env.topobjdir
287    if topobjdir.endswith("/js/src"):
288        topobjdir = topobjdir[:-7]
289
290    test_python = os.environ.get("MOZ_TEST_PYTHON")
291    if test_python:
292        manager = namespace(
293            up_to_date=lambda python: True,
294            python_path=test_python,
295        )
296    else:
297        with LineIO(lambda l: log.info(l), "replace") as out:
298            manager = VirtualenvManager(
299                topsrcdir,
300                os.path.join(topobjdir, "_virtualenvs", "common"),
301                out,
302                os.path.join(topsrcdir, "build", "build_virtualenv_packages.txt"),
303            )
304
305    # Update the path to include some necessary modules for find_program.
306    sys.path.insert(0, os.path.join(topsrcdir, "testing", "mozbase", "mozfile"))
307
308    # If we know the Python executable the caller is asking for then verify its
309    # version. If the caller did not ask for a specific executable then find
310    # a reasonable default.
311    if python:
312        found_python = find_program(python)
313        if not found_python:
314            die(
315                "The PYTHON3 environment variable does not contain "
316                "a valid path. Cannot find %s",
317                python,
318            )
319        python = found_python
320        try:
321            version = python_executable_version(python).version
322        except Exception as e:
323            raise FatalCheckError(
324                "could not determine version of PYTHON3 " "(%s): %s" % (python, e)
325            )
326    else:
327        # Fall back to the search routine.
328        python, version = find_python3_executable(min_version="3.6.0")
329
330        # The API returns a bytes whereas everything in configure is unicode.
331        if python:
332            python = ensure_text(python)
333
334    if not python:
335        raise FatalCheckError(
336            "Python 3.6 or newer is required to build. "
337            "Ensure a `python3.x` executable is in your "
338            "PATH or define PYTHON3 to point to a Python "
339            "3.6 executable."
340        )
341
342    if version < (3, 6, 0):
343        raise FatalCheckError(
344            "Python 3.6 or newer is required to build; "
345            "%s is Python %d.%d" % (python, version[0], version[1])
346        )
347
348    log.debug("python3: found executable: %r" % python)
349
350    if not manager.up_to_date(python):
351        log.info("Creating Python 3 environment")
352        manager.build(python)
353    else:
354        log.debug("python3: venv is up to date")
355
356    python = normsep(manager.python_path)
357
358    if python != normsep(sys.executable):
359        log.debug(
360            "python3: executing as %s, should be running as %s"
361            % (sys.executable, manager.python_path)
362        )
363        log.info("Re-executing in the virtualenv")
364        if env_python:
365                try:
366                    del os.environ["PYTHON3"]
367                except KeyError:
368                    pass
369        # Another quirk on macOS, with the system python, the virtualenv is
370        # not fully operational (missing entries in sys.path) if
371        # __PYVENV_LAUNCHER__ is set.
372        os.environ.pop("__PYVENV_LAUNCHER__", None)
373        # One would prefer to use os.execl, but that's completely borked on
374        # Windows.
375        sys.exit(subprocess.call([python] + sys.argv))
376
377    # We are now in the virtualenv
378    if not distutils.sysconfig.get_python_lib():
379        die("Could not determine python site packages directory")
380
381    str_version = ".".join(str(v) for v in version)
382
383    return namespace(
384        path=python,
385        version=version,
386        str_version=str_version,
387    )
388
389
390@depends(virtualenv_python3)
391@checking("for Python 3", callback=lambda x: "%s (%s)" % (x.path, x.str_version))
392def virtualenv_python3(venv):
393    return venv
394
395
396set_config("PYTHON3", virtualenv_python3.path)
397set_config("PYTHON3_VERSION", virtualenv_python3.str_version)
398add_old_configure_assignment("PYTHON3", virtualenv_python3.path)
399
400
401# Inject mozconfig options
402# ==============================================================
403# All options defined above this point can't be injected in mozconfig_options
404# below, so collect them.
405
406
407@template
408def early_options():
409    @depends("--help")
410    @imports("__sandbox__")
411    @imports(_from="six", _import="itervalues")
412    def early_options(_):
413        return set(
414            option.env for option in itervalues(__sandbox__._options) if option.env
415        )
416
417    return early_options
418
419
420early_options = early_options()
421
422
423@depends(mozconfig, early_options, "MOZ_AUTOMATION", "--help")
424# This gives access to the sandbox. Don't copy this blindly.
425@imports("__sandbox__")
426@imports("os")
427@imports("six")
428def mozconfig_options(mozconfig, early_options, automation, help):
429    if mozconfig["path"]:
430        if "MOZ_AUTOMATION_MOZCONFIG" in mozconfig["env"]["added"]:
431            if not automation:
432                log.error(
433                    "%s directly or indirectly includes an in-tree " "mozconfig.",
434                    mozconfig["path"],
435                )
436                log.error(
437                    "In-tree mozconfigs make strong assumptions about "
438                    "and are only meant to be used by Mozilla "
439                    "automation."
440                )
441                die("Please don't use them.")
442        helper = __sandbox__._helper
443        log.info("Adding configure options from %s" % mozconfig["path"])
444        for arg in mozconfig["configure_args"]:
445            log.info("  %s" % arg)
446            # We could be using imply_option() here, but it has other
447            # contraints that don't really apply to the command-line
448            # emulation that mozconfig provides.
449            helper.add(arg, origin="mozconfig", args=helper._args)
450
451        def add(key, value):
452            if key.isupper():
453                arg = "%s=%s" % (key, value)
454                log.info("  %s" % arg)
455                if key not in early_options:
456                    helper.add(arg, origin="mozconfig", args=helper._args)
457
458        for key, value in six.iteritems(mozconfig["env"]["added"]):
459            add(key, value)
460            os.environ[key] = value
461        for key, (_, value) in six.iteritems(mozconfig["env"]["modified"]):
462            add(key, value)
463            os.environ[key] = value
464        for key, value in six.iteritems(mozconfig["vars"]["added"]):
465            add(key, value)
466        for key, (_, value) in six.iteritems(mozconfig["vars"]["modified"]):
467            add(key, value)
468
469
470@depends(check_build_environment, "--help")
471@imports(_from="os.path", _import="exists")
472def js_package(build_env, help):
473    return not exists(os.path.join(build_env.topsrcdir, "browser"))
474
475
476# Source checkout and version control integration.
477# ================================================
478
479
480@depends(check_build_environment, "MOZ_AUTOMATION", js_package, "--help")
481@checking("for vcs source checkout")
482@imports("os")
483def vcs_checkout_type(build_env, automation, js_package, help):
484    if os.path.exists(os.path.join(build_env.topsrcdir, ".hg")):
485        return "hg"
486    elif os.path.exists(os.path.join(build_env.topsrcdir, ".git")):
487        return "git"
488    elif automation and not js_package and not help:
489        raise FatalCheckError(
490            "unable to resolve VCS type; must run "
491            "from a source checkout when MOZ_AUTOMATION "
492            "is set"
493        )
494
495
496# Resolve VCS binary for detected repository type.
497
498
499# TODO remove hg.exe once bug 1382940 addresses ambiguous executables case.
500hg = check_prog(
501    "HG",
502    (
503        "hg.exe",
504        "hg",
505    ),
506    allow_missing=True,
507    when=depends(vcs_checkout_type)(lambda x: x == "hg"),
508)
509git = check_prog(
510    "GIT",
511    ("git",),
512    allow_missing=True,
513    when=depends(vcs_checkout_type)(lambda x: x == "git"),
514)
515
516
517@depends_if(hg)
518@checking("for Mercurial version")
519@imports("os")
520@imports("re")
521def hg_version(hg):
522    # HGPLAIN in Mercurial 1.5+ forces stable output, regardless of set
523    # locale or encoding.
524    env = dict(os.environ)
525    env["HGPLAIN"] = "1"
526
527    out = check_cmd_output(hg, "--version", env=env)
528
529    match = re.search(r"Mercurial Distributed SCM \(version ([^\)]+)", out)
530
531    if not match:
532        raise FatalCheckError("unable to determine Mercurial version: %s" % out)
533
534    # The version string may be "unknown" for Mercurial run out of its own
535    # source checkout or for bad builds. But LooseVersion handles it.
536
537    return Version(match.group(1))
538
539
540# Resolve Mercurial config items so other checks have easy access.
541# Do NOT set this in the config because it may contain sensitive data
542# like API keys.
543
544
545@depends_all(check_build_environment, hg, hg_version)
546@imports("os")
547def hg_config(build_env, hg, version):
548    env = dict(os.environ)
549    env["HGPLAIN"] = "1"
550
551    # Warnings may get sent to stderr. But check_cmd_output() ignores
552    # stderr if exit code is 0. And the command should always succeed if
553    # `hg version` worked.
554    out = check_cmd_output(hg, "config", env=env, cwd=build_env.topsrcdir)
555
556    config = {}
557
558    for line in out.strip().splitlines():
559        key, value = [s.strip() for s in line.split("=", 1)]
560        config[key] = value
561
562    return config
563
564
565@depends_if(git)
566@checking("for Git version")
567@imports("re")
568def git_version(git):
569    out = check_cmd_output(git, "--version").rstrip()
570
571    match = re.search("git version (.*)$", out)
572
573    if not match:
574        raise FatalCheckError("unable to determine Git version: %s" % out)
575
576    return Version(match.group(1))
577
578
579# Only set VCS_CHECKOUT_TYPE if we resolved the VCS binary.
580# Require resolved VCS info when running in automation so automation's
581# environment is more well-defined.
582
583
584@depends(vcs_checkout_type, hg_version, git_version, "MOZ_AUTOMATION")
585def exposed_vcs_checkout_type(vcs_checkout_type, hg, git, automation):
586    if vcs_checkout_type == "hg":
587        if hg:
588            return "hg"
589
590        if automation:
591            raise FatalCheckError("could not resolve Mercurial binary info")
592
593    elif vcs_checkout_type == "git":
594        if git:
595            return "git"
596
597        if automation:
598            raise FatalCheckError("could not resolve Git binary info")
599    elif vcs_checkout_type:
600        raise FatalCheckError("unhandled VCS type: %s" % vcs_checkout_type)
601
602
603set_config("VCS_CHECKOUT_TYPE", exposed_vcs_checkout_type)
604
605# Obtain a Repository interface for the current VCS repository.
606
607
608@depends(check_build_environment, exposed_vcs_checkout_type, hg, git)
609@imports(_from="mozversioncontrol", _import="get_repository_object")
610def vcs_repository(build_env, vcs_checkout_type, hg, git):
611    if vcs_checkout_type == "hg":
612        return get_repository_object(build_env.topsrcdir, hg=hg)
613    elif vcs_checkout_type == "git":
614        return get_repository_object(build_env.topsrcdir, git=git)
615    elif vcs_checkout_type:
616        raise FatalCheckError("unhandled VCS type: %s" % vcs_checkout_type)
617
618
619@depends_if(vcs_repository)
620@checking("for sparse checkout")
621def vcs_sparse_checkout(repo):
622    return repo.sparse_checkout_present()
623
624
625set_config("VCS_SPARSE_CHECKOUT", vcs_sparse_checkout)
626
627# The application/project to build
628# ==============================================================
629option(
630    "--enable-application",
631    nargs=1,
632    env="MOZ_BUILD_APP",
633    help="Application to build. Same as --enable-project.",
634)
635
636
637@depends("--enable-application")
638def application(app):
639    if app:
640        return app
641
642
643imply_option("--enable-project", application)
644
645
646@depends(check_build_environment, js_package)
647def default_project(build_env, js_package):
648    if js_package or build_env.topobjdir.endswith("/js/src"):
649        return "js"
650    return "browser"
651
652
653option("--enable-project", nargs=1, default=default_project, help="Project to build")
654
655
656# Host and target systems
657# ==============================================================
658option("--host", nargs=1, help="Define the system type performing the build")
659
660option(
661    "--target",
662    nargs=1,
663    help="Define the system type where the resulting executables will be " "used",
664)
665
666
667@imports(_from="mozbuild.configure.constants", _import="CPU")
668@imports(_from="mozbuild.configure.constants", _import="CPU_bitness")
669@imports(_from="mozbuild.configure.constants", _import="Endianness")
670@imports(_from="mozbuild.configure.constants", _import="Kernel")
671@imports(_from="mozbuild.configure.constants", _import="OS")
672@imports(_from="__builtin__", _import="ValueError")
673def split_triplet(triplet, allow_msvc=False, allow_wasi=False):
674    # The standard triplet is defined as
675    #   CPU_TYPE-VENDOR-OPERATING_SYSTEM
676    # There is also a quartet form:
677    #   CPU_TYPE-VENDOR-KERNEL-OPERATING_SYSTEM
678    # But we can consider the "KERNEL-OPERATING_SYSTEM" as one.
679    # Additionally, some may omit "unknown" when the vendor
680    # is not specified and emit
681    #   CPU_TYPE-OPERATING_SYSTEM
682    vendor = "unknown"
683    parts = triplet.split("-", 2)
684    if len(parts) == 3:
685        cpu, vendor, os = parts
686    elif len(parts) == 2:
687        cpu, os = parts
688    else:
689        raise ValueError("Unexpected triplet string: %s" % triplet)
690
691    # Autoconf uses config.sub to validate and canonicalize those triplets,
692    # but the granularity of its results has never been satisfying to our
693    # use, so we've had our own, different, canonicalization. We've also
694    # historically not been very consistent with how we use the canonicalized
695    # values. Hopefully, this will help us make things better.
696    # The tests are inherited from our decades-old autoconf-based configure,
697    # which can probably be improved/cleaned up because they are based on a
698    # mix of uname and config.guess output, while we now only use the latter,
699    # which presumably has a cleaner and leaner output. Let's refine later.
700    os = os.replace("/", "_")
701    if "android" in os:
702        canonical_os = "Android"
703        canonical_kernel = "Linux"
704    elif os.startswith("linux"):
705        canonical_os = "GNU"
706        canonical_kernel = "Linux"
707    elif os.startswith("kfreebsd") and os.endswith("-gnu"):
708        canonical_os = "GNU"
709        canonical_kernel = "kFreeBSD"
710    elif os.startswith("gnu"):
711        canonical_os = canonical_kernel = "GNU"
712    elif os.startswith("mingw") or (allow_msvc and os == "windows-msvc"):
713        # windows-msvc is only opt-in for the caller of this function until
714        # full support in bug 1617793.
715        canonical_os = canonical_kernel = "WINNT"
716    elif os.startswith("darwin"):
717        canonical_kernel = "Darwin"
718        canonical_os = "OSX"
719    elif os.startswith("dragonfly"):
720        canonical_os = canonical_kernel = "DragonFly"
721    elif os.startswith("freebsd"):
722        canonical_os = canonical_kernel = "FreeBSD"
723    elif os.startswith("netbsd"):
724        canonical_os = canonical_kernel = "NetBSD"
725    elif os.startswith("openbsd"):
726        canonical_os = canonical_kernel = "OpenBSD"
727    elif os.startswith("solaris"):
728        canonical_os = canonical_kernel = "SunOS"
729    elif os.startswith("wasi") and allow_wasi:
730        canonical_os = canonical_kernel = "WASI"
731    else:
732        raise ValueError("Unknown OS: %s" % os)
733
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    elif cpu == "wasm32" and allow_wasi:
785        canonical_cpu = "wasm32"
786        endianness = "little"
787    else:
788        raise ValueError("Unknown CPU type: %s" % cpu)
789
790    # Toolchains, most notably for cross compilation may use cpu-os
791    # prefixes. We need to be more specific about the LLVM target on Mac
792    # so cross-language LTO will work correctly.
793
794    if os.startswith("darwin"):
795        toolchain = "%s-apple-%s" % (cpu, os)
796    elif canonical_cpu == "aarch64" and canonical_os == "WINNT":
797        toolchain = "aarch64-windows-msvc"
798    else:
799        toolchain = "%s-%s" % (cpu, os)
800
801    return namespace(
802        alias=triplet,
803        cpu=CPU(canonical_cpu),
804        bitness=CPU_bitness[canonical_cpu],
805        kernel=Kernel(canonical_kernel),
806        os=OS(canonical_os),
807        endianness=Endianness(endianness),
808        raw_cpu=cpu,
809        raw_os=os,
810        toolchain=toolchain,
811        vendor=vendor,
812    )
813
814
815# This defines a fake target/host namespace for when running with --help
816# If either --host or --target is passed on the command line, then fall
817# back to the real deal.
818@depends("--help", "--host", "--target")
819def help_host_target(help, host, target):
820    if help and not host and not target:
821        return namespace(
822            alias="unknown-unknown-unknown",
823            cpu="unknown",
824            bitness="unknown",
825            kernel="unknown",
826            os="unknown",
827            endianness="unknown",
828            raw_cpu="unknown",
829            raw_os="unknown",
830            toolchain="unknown-unknown",
831        )
832
833
834def config_sub(shell, triplet):
835    config_sub = os.path.join(os.path.dirname(__file__), "..", "autoconf", "config.sub")
836    return check_cmd_output(shell, config_sub, triplet).strip()
837
838
839@depends("--host", shell)
840@checking("for host system type", lambda h: h.alias)
841@imports("os")
842@imports("sys")
843@imports(_from="__builtin__", _import="ValueError")
844def real_host(value, shell):
845    if not value and sys.platform == "win32":
846        arch = os.environ.get("PROCESSOR_ARCHITEW6432") or os.environ.get(
847            "PROCESSOR_ARCHITECTURE"
848        )
849        if arch == "AMD64":
850            return split_triplet("x86_64-pc-mingw32")
851        elif arch == "x86":
852            return split_triplet("i686-pc-mingw32")
853
854    if not value:
855        config_guess = os.path.join(
856            os.path.dirname(__file__), "..", "autoconf", "config.guess"
857        )
858
859        # Ensure that config.guess is determining the host triplet, not the target
860        # triplet
861        env = os.environ.copy()
862        env.pop("CC_FOR_BUILD", None)
863        env.pop("HOST_CC", None)
864        env.pop("CC", None)
865
866        host = check_cmd_output(shell, config_guess, env=env).strip()
867        try:
868            return split_triplet(host)
869        except ValueError:
870            pass
871    else:
872        host = value[0]
873
874    host = config_sub(shell, host)
875
876    try:
877        return split_triplet(host)
878    except ValueError as e:
879        die(e)
880
881
882host = help_host_target | real_host
883
884
885@depends("--target", real_host, shell, "--enable-project", "--enable-application")
886@checking("for target system type", lambda t: t.alias)
887@imports(_from="__builtin__", _import="ValueError")
888def real_target(value, host, shell, project, application):
889    # Because --enable-project is implied by --enable-application, and
890    # implied options are not currently handled during --help, which is
891    # used get the build target in mozbuild.base, we manually check
892    # whether --enable-application was given, and fall back to
893    # --enable-project if not. Both can't be given contradictory values
894    # under normal circumstances, so it's fine.
895    if application:
896        project = application[0]
897    elif project:
898        project = project[0]
899    if not value:
900        if project == "mobile/android":
901            if host.raw_os == "mingw32":
902                log.warning(
903                    "Building Firefox for Android on Windows is not fully "
904                    "supported. See https://bugzilla.mozilla.org/show_bug.cgi?"
905                    "id=1169873 for details."
906                )
907            return split_triplet("arm-unknown-linux-androideabi")
908        return host
909    # If --target was only given a cpu arch, expand it with the
910    # non-cpu part of the host. For mobile/android, expand it with
911    # unknown-linux-android.
912    target = value[0]
913    if "-" not in target:
914        if project == "mobile/android":
915            rest = "unknown-linux-android"
916            if target.startswith("arm"):
917                rest += "eabi"
918        else:
919            cpu, rest = host.alias.split("-", 1)
920        target = "-".join((target, rest))
921        try:
922            return split_triplet(target)
923        except ValueError:
924            pass
925
926    try:
927        return split_triplet(config_sub(shell, target), allow_wasi=(project == "js"))
928    except ValueError as e:
929        die(e)
930
931
932target = help_host_target | real_target
933
934
935@depends(host, target)
936@checking("whether cross compiling")
937def cross_compiling(host, target):
938    return host != target
939
940
941set_config("CROSS_COMPILE", cross_compiling)
942set_define("CROSS_COMPILE", cross_compiling)
943add_old_configure_assignment("CROSS_COMPILE", cross_compiling)
944
945
946@depends(target)
947def have_64_bit(target):
948    if target.bitness == 64:
949        return True
950
951
952set_config("HAVE_64BIT_BUILD", have_64_bit)
953set_define("HAVE_64BIT_BUILD", have_64_bit)
954add_old_configure_assignment("HAVE_64BIT_BUILD", have_64_bit)
955
956
957@depends(host)
958def host_os_kernel_major_version(host):
959    versions = host.raw_os.split(".")
960    version = "".join(x for x in versions[0] if x.isdigit())
961    return version
962
963
964set_config("HOST_MAJOR_VERSION", host_os_kernel_major_version)
965
966# Autoconf needs these set
967
968
969@depends(host)
970def host_for_sub_configure(host):
971    return "--host=%s" % host.alias
972
973
974@depends(target)
975def target_for_sub_configure(target):
976    target_alias = target.alias
977    return "--target=%s" % target_alias
978
979
980# These variables are for compatibility with the current moz.builds and
981# old-configure. Eventually, we'll want to canonicalize better.
982@depends(target)
983def target_variables(target):
984    if target.kernel == "kFreeBSD":
985        os_target = "GNU/kFreeBSD"
986        os_arch = "GNU_kFreeBSD"
987    elif target.kernel == "Darwin" or (target.kernel == "Linux" and target.os == "GNU"):
988        os_target = target.kernel
989        os_arch = target.kernel
990    else:
991        os_target = target.os
992        os_arch = target.kernel
993
994    return namespace(
995        OS_TARGET=os_target,
996        OS_ARCH=os_arch,
997        INTEL_ARCHITECTURE=target.cpu in ("x86", "x86_64") or None,
998    )
999
1000
1001set_config("OS_TARGET", target_variables.OS_TARGET)
1002add_old_configure_assignment("OS_TARGET", target_variables.OS_TARGET)
1003set_config("OS_ARCH", target_variables.OS_ARCH)
1004add_old_configure_assignment("OS_ARCH", target_variables.OS_ARCH)
1005set_config("CPU_ARCH", target.cpu)
1006add_old_configure_assignment("CPU_ARCH", target.cpu)
1007set_config("INTEL_ARCHITECTURE", target_variables.INTEL_ARCHITECTURE)
1008set_config("TARGET_CPU", target.raw_cpu)
1009set_config("TARGET_OS", target.raw_os)
1010set_config("TARGET_ENDIANNESS", target.endianness)
1011
1012
1013@depends(host)
1014def host_variables(host):
1015    if host.kernel == "kFreeBSD":
1016        os_arch = "GNU_kFreeBSD"
1017    else:
1018        os_arch = host.kernel
1019    return namespace(
1020        HOST_OS_ARCH=os_arch,
1021    )
1022
1023
1024set_config("HOST_CPU_ARCH", host.cpu)
1025set_config("HOST_OS_ARCH", host_variables.HOST_OS_ARCH)
1026add_old_configure_assignment("HOST_OS_ARCH", host_variables.HOST_OS_ARCH)
1027
1028
1029@depends(target)
1030def target_is_windows(target):
1031    if target.kernel == "WINNT":
1032        return True
1033
1034
1035set_define("_WINDOWS", target_is_windows)
1036set_define("WIN32", target_is_windows)
1037set_define("XP_WIN", target_is_windows)
1038
1039
1040@depends(target)
1041def target_is_unix(target):
1042    if target.kernel != "WINNT":
1043        return True
1044
1045
1046set_define("XP_UNIX", target_is_unix)
1047
1048
1049@depends(target)
1050def target_is_darwin(target):
1051    if target.kernel == "Darwin":
1052        return True
1053
1054
1055set_define("XP_DARWIN", target_is_darwin)
1056
1057
1058@depends(target)
1059def target_is_osx(target):
1060    if target.kernel == "Darwin" and target.os == "OSX":
1061        return True
1062
1063
1064set_define("XP_MACOSX", target_is_osx)
1065
1066
1067@depends(target)
1068def target_is_linux(target):
1069    if target.kernel == "Linux":
1070        return True
1071
1072
1073set_define("XP_LINUX", target_is_linux)
1074
1075
1076@depends(target)
1077def target_is_linux_or_wasi(target):
1078    if target.kernel in ("Linux", "WASI"):
1079        return True
1080
1081
1082@depends(target)
1083def target_is_android(target):
1084    if target.os == "Android":
1085        return True
1086
1087
1088set_define("ANDROID", target_is_android)
1089
1090
1091@depends(target)
1092def target_is_openbsd(target):
1093    if target.kernel == "OpenBSD":
1094        return True
1095
1096
1097set_define("XP_OPENBSD", target_is_openbsd)
1098
1099
1100@depends(target)
1101def target_is_netbsd(target):
1102    if target.kernel == "NetBSD":
1103        return True
1104
1105
1106set_define("XP_NETBSD", target_is_netbsd)
1107
1108
1109@depends(target)
1110def target_is_freebsd(target):
1111    if target.kernel == "FreeBSD":
1112        return True
1113
1114
1115set_define("XP_FREEBSD", target_is_freebsd)
1116
1117
1118@depends(target)
1119def target_is_solaris(target):
1120    if target.kernel == "SunOS":
1121        return True
1122
1123
1124set_define("XP_SOLARIS", target_is_solaris)
1125
1126
1127@depends(target)
1128def target_is_sparc(target):
1129    if target.cpu == "sparc64":
1130        return True
1131
1132
1133set_define("SPARC64", target_is_sparc)
1134
1135
1136@depends("--enable-project", check_build_environment, "--help")
1137@imports(_from="os.path", _import="exists")
1138def include_project_configure(project, build_env, help):
1139    if not project:
1140        die("--enable-project is required.")
1141
1142    base_dir = build_env.topsrcdir
1143    path = os.path.join(base_dir, project[0], "moz.configure")
1144    if not exists(path):
1145        die("Cannot find project %s", project[0])
1146    return path
1147
1148
1149@depends("--enable-project")
1150def build_project(project):
1151    return project[0]
1152
1153
1154set_config("MOZ_BUILD_APP", build_project)
1155set_define("MOZ_BUILD_APP", build_project)
1156add_old_configure_assignment("MOZ_BUILD_APP", build_project)
1157
1158
1159option(env="MOZILLA_OFFICIAL", help="Build an official release")
1160
1161
1162@depends("MOZILLA_OFFICIAL")
1163def mozilla_official(official):
1164    if official:
1165        return True
1166
1167
1168set_config("MOZILLA_OFFICIAL", mozilla_official)
1169set_define("MOZILLA_OFFICIAL", mozilla_official)
1170add_old_configure_assignment("MOZILLA_OFFICIAL", mozilla_official)
1171
1172
1173# Allow specifying custom paths to the version files used by the milestone() function below.
1174option(
1175    "--with-version-file-path",
1176    nargs=1,
1177    help="Specify a custom path to app version files instead of auto-detecting",
1178    default=None,
1179)
1180
1181
1182@depends("--with-version-file-path")
1183def version_path(path):
1184    return path
1185
1186
1187# set RELEASE_OR_BETA and NIGHTLY_BUILD variables depending on the cycle we're in
1188# The logic works like this:
1189# - if we have "a1" in GRE_MILESTONE, we're building Nightly (define NIGHTLY_BUILD)
1190# - otherwise, if we have "a" in GRE_MILESTONE, we're building Nightly or Aurora
1191# - otherwise, we're building Release/Beta (define RELEASE_OR_BETA)
1192@depends(check_build_environment, build_project, version_path, "--help")
1193@imports(_from="__builtin__", _import="open")
1194@imports("os")
1195@imports("re")
1196def milestone(build_env, build_project, version_path, _):
1197    versions = []
1198    paths = ["config/milestone.txt"]
1199    if build_project == "js":
1200        paths = paths * 3
1201    else:
1202        paths += [
1203            "browser/config/version.txt",
1204            "browser/config/version_display.txt",
1205        ]
1206    if version_path:
1207        version_path = version_path[0]
1208    else:
1209        version_path = os.path.join(build_project, "config")
1210    for f in ("version.txt", "version_display.txt"):
1211        f = os.path.join(version_path, f)
1212        if not os.path.exists(os.path.join(build_env.topsrcdir, f)):
1213            break
1214        paths.append(f)
1215
1216    for p in paths:
1217        with open(os.path.join(build_env.topsrcdir, p), "r") as fh:
1218            content = fh.read().splitlines()
1219            if not content:
1220                die("Could not find a version number in {}".format(p))
1221            versions.append(content[-1])
1222
1223    milestone, firefox_version, firefox_version_display = versions[:3]
1224
1225    # version.txt content from the project directory if there is one, otherwise
1226    # the firefox version.
1227    app_version = versions[3] if len(versions) > 3 else firefox_version
1228    # version_display.txt content from the project directory if there is one,
1229    # otherwise version.txt content from the project directory, otherwise the
1230    # firefox version for display.
1231    app_version_display = versions[-1] if len(versions) > 3 else firefox_version_display
1232
1233    is_nightly = is_release_or_beta = is_early_beta_or_earlier = None
1234
1235    if "a1" in milestone:
1236        is_nightly = True
1237    elif "a" not in milestone:
1238        is_release_or_beta = True
1239
1240    major_version = milestone.split(".")[0]
1241    m = re.search(r"([ab]\d+)", milestone)
1242    ab_patch = m.group(1) if m else ""
1243
1244    defines = os.path.join(build_env.topsrcdir, "build", "defines.sh")
1245    with open(defines, "r") as fh:
1246        for line in fh.read().splitlines():
1247            line = line.strip()
1248            if not line or line.startswith("#"):
1249                continue
1250            name, _, value = line.partition("=")
1251            name = name.strip()
1252            value = value.strip()
1253            if name != "EARLY_BETA_OR_EARLIER":
1254                die(
1255                    "Only the EARLY_BETA_OR_EARLIER variable can be set in build/defines.sh"
1256                )
1257            if value:
1258                is_early_beta_or_earlier = True
1259
1260    # Only expose the major version milestone in the UA string and hide the
1261    # patch leve (bugs 572659 and 870868).
1262    #
1263    # Only expose major milestone and alpha version in the symbolversion
1264    # string; as the name suggests, we use it for symbol versioning on Linux.
1265    return namespace(
1266        version=milestone,
1267        uaversion="%s.0" % major_version,
1268        symbolversion="%s%s" % (major_version, ab_patch),
1269        is_nightly=is_nightly,
1270        is_release_or_beta=is_release_or_beta,
1271        is_early_beta_or_earlier=is_early_beta_or_earlier,
1272        is_esr=app_version_display.endswith("esr") or None,
1273        app_version=app_version,
1274        app_version_display=app_version_display,
1275    )
1276
1277
1278set_config("GRE_MILESTONE", milestone.version)
1279set_config("NIGHTLY_BUILD", milestone.is_nightly)
1280set_define("NIGHTLY_BUILD", milestone.is_nightly)
1281set_config("RELEASE_OR_BETA", milestone.is_release_or_beta)
1282set_define("RELEASE_OR_BETA", milestone.is_release_or_beta)
1283add_old_configure_assignment("RELEASE_OR_BETA", milestone.is_release_or_beta)
1284set_config("MOZ_ESR", milestone.is_esr)
1285set_define("MOZ_ESR", milestone.is_esr)
1286set_config("EARLY_BETA_OR_EARLIER", milestone.is_early_beta_or_earlier)
1287set_define("EARLY_BETA_OR_EARLIER", milestone.is_early_beta_or_earlier)
1288add_old_configure_assignment(
1289    "EARLY_BETA_OR_EARLIER", milestone.is_early_beta_or_earlier
1290)
1291set_define("MOZILLA_VERSION", depends(milestone)(lambda m: '"%s"' % m.version))
1292set_config("MOZILLA_VERSION", milestone.version)
1293set_define("MOZILLA_VERSION_U", milestone.version)
1294set_define("MOZILLA_UAVERSION", depends(milestone)(lambda m: '"%s"' % m.uaversion))
1295set_config("MOZILLA_SYMBOLVERSION", milestone.symbolversion)
1296# JS configure still wants to look at these.
1297add_old_configure_assignment("MOZILLA_VERSION", milestone.version)
1298add_old_configure_assignment("MOZILLA_SYMBOLVERSION", milestone.symbolversion)
1299
1300set_config("MOZ_APP_VERSION", milestone.app_version)
1301set_config("MOZ_APP_VERSION_DISPLAY", milestone.app_version_display)
1302add_old_configure_assignment("MOZ_APP_VERSION", milestone.app_version)
1303
1304
1305# The app update channel is 'default' when not supplied. The value is used in
1306# the application's confvars.sh (and is made available to a project specific
1307# moz.configure).
1308option(
1309    "--enable-update-channel",
1310    nargs=1,
1311    help="Select application update channel",
1312    default="default",
1313)
1314
1315
1316@depends("--enable-update-channel")
1317def update_channel(channel):
1318    if not channel or channel[0] == "":
1319        return "default"
1320    return channel[0].lower()
1321
1322
1323set_config("MOZ_UPDATE_CHANNEL", update_channel)
1324set_define("MOZ_UPDATE_CHANNEL", update_channel)
1325add_old_configure_assignment("MOZ_UPDATE_CHANNEL", update_channel)
1326
1327
1328option(
1329    env="MOZBUILD_STATE_PATH",
1330    nargs=1,
1331    help="Path to a persistent state directory for the build system "
1332    "and related tools",
1333)
1334
1335
1336@depends("MOZBUILD_STATE_PATH", "--help")
1337@imports("os")
1338def mozbuild_state_path(path, _):
1339    if path:
1340        return path[0]
1341    return os.path.expanduser(os.path.join("~", ".mozbuild"))
1342
1343
1344# A template providing a shorthand for setting a variable. The created
1345# option will only be settable with imply_option.
1346# It is expected that a project-specific moz.configure will call imply_option
1347# to set a value other than the default.
1348# If required, the set_as_define and set_for_old_configure arguments
1349# will additionally cause the variable to be set using set_define and
1350# add_old_configure_assignment. util.configure would be an appropriate place for
1351# this, but it uses add_old_configure_assignment, which is defined in this file.
1352@template
1353def project_flag(env=None, set_for_old_configure=False, set_as_define=False, **kwargs):
1354
1355    if not env:
1356        configure_error("A project_flag must be passed a variable name to set.")
1357
1358    opt = option(env=env, possible_origins=("implied",), **kwargs)
1359
1360    @depends(opt.option)
1361    def option_implementation(value):
1362        if value:
1363            if len(value):
1364                return value
1365            return bool(value)
1366
1367    set_config(env, option_implementation)
1368    if set_as_define:
1369        set_define(env, option_implementation)
1370    if set_for_old_configure:
1371        add_old_configure_assignment(env, option_implementation)
1372
1373
1374# milestone.is_nightly corresponds to cases NIGHTLY_BUILD is set.
1375
1376
1377@depends(milestone)
1378def enabled_in_nightly(milestone):
1379    return milestone.is_nightly
1380
1381
1382# Branding
1383# ==============================================================
1384option(
1385    "--with-app-basename",
1386    env="MOZ_APP_BASENAME",
1387    nargs=1,
1388    help="Typically stays consistent for multiple branded versions of a "
1389    'given application (e.g. Aurora and Firefox both use "Firefox"), but '
1390    "may vary for full rebrandings (e.g. Iceweasel). Used for "
1391    'application.ini\'s "Name" field, which controls profile location in '
1392    'the absence of a "Profile" field (see below), and various system '
1393    "integration hooks (Unix remoting, Windows MessageWindow name, etc.",
1394)
1395
1396
1397@depends("--with-app-basename", target_is_android)
1398def moz_app_basename(value, target_is_android):
1399    if value:
1400        return value[0]
1401    if target_is_android:
1402        return "Fennec"
1403    return "Firefox"
1404
1405
1406set_config(
1407    "MOZ_APP_BASENAME",
1408    moz_app_basename,
1409    when=depends(build_project)(lambda p: p != "js"),
1410)
1411