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