1 # -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
2 # vim: set filetype=python:
3 # This Source Code Form is subject to the terms of the Mozilla Public
4 # License, v. 2.0. If a copy of the MPL was not distributed with this
5 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
8 # Rust is required by `rust_compiler` below. We allow_missing here
9 # to propagate failures to the better error message there.
10 option(env="RUSTC", nargs=1, help="Path to the rust compiler")
11 option(env="CARGO", nargs=1, help="Path to the Cargo package manager")
17 paths=rust_search_path,
25 paths=rust_search_path,
32 def unwrap_rustup(prog, name):
33 # rustc and cargo can either be rustup wrappers, or they can be the actual,
34 # plain executables. For cargo, on OSX, rustup sets DYLD_LIBRARY_PATH (at
35 # least until https://github.com/rust-lang/rustup.rs/pull/1752 is merged
36 # and shipped) and that can wreak havoc (see bug 1536486). Similarly, for
37 # rustc, rustup silently honors toolchain overrides set by vendored crates
40 # In either case, we need to find the plain executables.
42 # To achieve that, try to run `PROG +stable`. When the rustup wrapper is in
43 # use, it either prints PROG's help and exits with status 0, or prints
44 # an error message (error: toolchain 'stable' is not installed) and exits
45 # with status 1. In the cargo case, when plain cargo is in use, it exits
46 # with a different error message (e.g. "error: no such subcommand:
47 # `+stable`"), and exits with status 101.
49 # Unfortunately, in the rustc case, when plain rustc is in use,
50 # `rustc +stable` will exit with status 1, complaining about a missing
51 # "+stable" file. We'll examine the error output to try and distinguish
52 # between failing rustup and failing rustc.
53 @depends(prog, dependable(name))
54 @imports(_from="__builtin__", _import="open")
56 def unwrap(prog, name):
60 def from_rustup_which():
61 out = check_cmd_output("rustup", "which", name, executable=prog).rstrip()
62 # If for some reason the above failed to return something, keep the
63 # PROG we found originally.
65 log.info("Actually using '%s'", out)
68 log.info("No `rustup which` output, using '%s'", prog)
71 (retcode, stdout, stderr) = get_cmd_output(prog, "+stable")
73 if name == "cargo" and retcode != 101:
74 prog = from_rustup_which()
77 prog = from_rustup_which()
78 elif "+stable" in stderr:
79 # PROG looks like plain `rustc`.
82 # Assume PROG looks like `rustup`. This case is a little weird,
83 # insofar as the user doesn't have the "stable" toolchain
84 # installed, but go ahead and unwrap anyway: the user might
85 # have only certain versions, beta, or nightly installed, and
86 # we'll catch invalid versions later.
87 prog = from_rustup_which()
89 return normalize_path(prog)
94 rustc = unwrap_rustup(rustc, "rustc")
95 cargo = unwrap_rustup(cargo, "cargo")
98 set_config("CARGO", cargo)
99 set_config("RUSTC", rustc)
103 @checking("rustc version", lambda info: info.version)
104 def rustc_info(rustc):
107 out = check_cmd_output(rustc, "--version", "--verbose").splitlines()
108 info = dict((s.strip() for s in line.split(":", 1)) for line in out[1:])
110 version=Version(info.get("release", "0")),
111 commit=info.get("commit-hash", "unknown"),
113 llvm_version=Version(info.get("LLVM version", "0")),
119 depends(rustc_info)(lambda info: str(info.version) if info else None),
124 "RUSTC_LLVM_VERSION",
125 depends(rustc_info)(lambda info: str(info.llvm_version) if info else None),
129 "MOZ_CLANG_NEWER_THAN_RUSTC_LLVM",
130 depends(c_compiler, rustc_info)(
131 lambda c_compiler, rustc_info: rustc_info
132 and c_compiler.type == "clang"
133 and c_compiler.version.major > rustc_info.llvm_version.major
139 @checking("cargo version", lambda info: info.version)
141 def cargo_info(cargo):
144 out = check_cmd_output(cargo, "--version", "--verbose").splitlines()
145 info = dict((s.strip() for s in line.split(":", 1)) for line in out[1:])
146 version = info.get("release")
147 # Older versions of cargo didn't support --verbose, in which case, they
148 # only output a not-really-pleasant-to-parse output. Fortunately, they
149 # don't error out, so we can just try some regexp matching on the output
152 VERSION_FORMAT = r"^cargo (\d\.\d+\.\d+).*"
154 m = re.search(VERSION_FORMAT, out[0])
155 # Fail fast if cargo changes its output on us.
157 die("Could not determine cargo version from output: %s", out)
161 version=Version(version),
165 @depends(rustc_info, cargo_info, target)
166 @imports(_from="mozboot.util", _import="MINIMUM_RUST_VERSION")
167 @imports(_from="textwrap", _import="dedent")
168 def rust_compiler(rustc_info, cargo_info, target):
173 Rust compiler not found.
174 To compile rust language sources, you must have 'rustc' in your path.
175 See https://www.rust-lang.org/ for more information.
177 You can install rust by running './mach bootstrap'
178 or by directly running the installer from https://rustup.rs/
182 rustc_min_version = Version(MINIMUM_RUST_VERSION)
183 cargo_min_version = rustc_min_version
185 version = rustc_info.version
186 is_nightly = "nightly" in version.version
187 is_version_number_match = (
188 version.major == rustc_min_version.major
189 and version.minor == rustc_min_version.minor
190 and version.patch == rustc_min_version.patch
193 if version < rustc_min_version or (is_version_number_match and is_nightly):
197 Rust compiler {} is too old.
199 To compile Rust language sources please install at least
200 version {} of the 'rustc' toolchain (or, if using nightly,
201 at least one version newer than {}) and make sure it is
204 You can verify this by typing 'rustc --version'.
206 If you have the 'rustup' tool installed you can upgrade
207 to the latest release by typing 'rustup update'. The
208 installer is available from https://rustup.rs/
210 version, rustc_min_version, rustc_min_version
215 if target.kernel == "WINNT" and (version.major, version.minor) == (1, 56):
219 Rust compiler 1.56.* is not supported for Windows builds.
221 Use a newer or an older version.
223 See https://github.com/rust-lang/rust/issues/88576.
232 Cargo package manager not found.
233 To compile Rust language sources, you must have 'cargo' in your path.
234 See https://www.rust-lang.org/ for more information.
236 You can install cargo by running './mach bootstrap'
237 or by directly running the installer from https://rustup.rs/
242 version = cargo_info.version
243 if version < cargo_min_version:
247 Cargo package manager {} is too old.
249 To compile Rust language sources please install at least
250 version {} of 'cargo' and make sure it is first in your path.
252 You can verify this by typing 'cargo --version'.
254 ).format(version, cargo_min_version)
260 @depends(rustc, when=rust_compiler)
261 @imports(_from="__builtin__", _import="ValueError")
262 def rust_supported_targets(rustc):
263 out = check_cmd_output(rustc, "--print", "target-list").splitlines()
267 info = split_triplet(t, allow_wasi=True)
269 if t.startswith("thumb"):
270 cpu, rest = t.split("-", 1)
271 retry = "-".join(("arm", rest))
275 info = split_triplet(retry, allow_wasi=True)
278 key = (info.cpu, info.endianness, info.os)
279 data.setdefault(key, []).append(namespace(rust_target=t, target=info))
283 def detect_rustc_target(
284 host_or_target, compiler_info, arm_target, rust_supported_targets
286 # Rust's --target options are similar to, but not exactly the same
287 # as, the autoconf-derived targets we use. An example would be that
288 # Rust uses distinct target triples for targetting the GNU C++ ABI
289 # and the MSVC C++ ABI on Win32, whereas autoconf has a single
290 # triple and relies on the user to ensure that everything is
291 # compiled for the appropriate ABI. We need to perform appropriate
292 # munging to get the correct option to rustc.
293 # We correlate the autoconf-derived targets with the list of targets
294 # rustc gives us with --print target-list.
295 candidates = rust_supported_targets.get(
296 (host_or_target.cpu, host_or_target.endianness, host_or_target.os), []
299 def find_candidate(candidates):
300 if len(candidates) == 1:
301 return candidates[0].rust_target
305 # We have multiple candidates. There are two cases where we can try to
306 # narrow further down using extra information from the build system.
307 # - For windows targets, correlate with the C compiler type
308 if host_or_target.kernel == "WINNT":
309 if host_or_target.abi:
310 if host_or_target.abi == "msvc":
311 suffix = "windows-msvc"
312 elif host_or_target.abi == "mingw":
313 suffix = "windows-gnu"
314 elif compiler_info.type in ("gcc", "clang"):
315 suffix = "windows-gnu"
317 suffix = "windows-msvc"
319 c for c in candidates if c.rust_target.endswith("-{}".format(suffix))
321 if len(narrowed) == 1:
322 return narrowed[0].rust_target
324 candidates = narrowed
326 vendor_aliases = {"pc": ("w64", "windows")}
330 if host_or_target.vendor in vendor_aliases.get(c.target.vendor, ())
333 if len(narrowed) == 1:
334 return narrowed[0].rust_target
336 # - For arm targets, correlate with arm_target
337 # we could be more thorough with the supported rust targets, but they
338 # don't support OSes that are supported to build Gecko anyways.
339 # Also, sadly, the only interface to check the rust target cpu features
340 # is --print target-spec-json, and it's unstable, so we have to rely on
341 # our own knowledge of what each arm target means.
342 if host_or_target.cpu == "arm" and host_or_target.endianness == "little":
344 if arm_target.arm_arch >= 7:
345 if arm_target.thumb2 and arm_target.fpu == "neon":
346 prefixes.append("thumbv7neon")
347 if arm_target.thumb2:
348 prefixes.append("thumbv7a")
349 prefixes.append("armv7")
350 if arm_target.arm_arch >= 6:
351 prefixes.append("armv6")
352 if host_or_target.os != "Android":
353 # arm-* rust targets are armv6... except arm-linux-androideabi
354 prefixes.append("arm")
355 if arm_target.arm_arch >= 5:
356 prefixes.append("armv5te")
357 if host_or_target.os == "Android":
358 # arm-* rust targets are armv6... except arm-linux-androideabi
359 prefixes.append("arm")
360 if arm_target.arm_arch >= 4:
361 prefixes.append("armv4t")
362 # rust freebsd targets are the only ones that don't have a 'hf' suffix
363 # for hard-float. Technically, that means if the float abi ever is not
364 # hard-float, this will pick a wrong target, but since rust only
365 # supports hard-float, let's assume that means freebsd only support
367 if arm_target.float_abi == "hard" and host_or_target.os != "FreeBSD":
373 if c.rust_target.startswith(
375 ) and c.rust_target.endswith(suffix):
378 # See if we can narrow down on the exact alias.
379 # We use the sub_configure_alias to keep support mingw32 triplets as input.
383 if c.target.sub_configure_alias == host_or_target.sub_configure_alias
385 if len(narrowed) == 1:
386 return narrowed[0].rust_target
388 candidates = narrowed
390 # See if we can narrow down with the raw OS
391 narrowed = [c for c in candidates if c.target.raw_os == host_or_target.raw_os]
392 if len(narrowed) == 1:
393 return narrowed[0].rust_target
395 candidates = narrowed
397 # See if we can narrow down with the raw OS and raw CPU
401 if c.target.raw_os == host_or_target.raw_os
402 and c.target.raw_cpu == host_or_target.raw_cpu
404 if len(narrowed) == 1:
405 return narrowed[0].rust_target
407 # Finally, see if the vendor can be used to disambiguate.
408 narrowed = [c for c in candidates if c.target.vendor == host_or_target.vendor]
409 if len(narrowed) == 1:
410 return narrowed[0].rust_target
414 rustc_target = find_candidate(candidates)
416 if rustc_target is None:
417 die("Don't know how to translate {} for rustc".format(host_or_target.alias))
423 @imports(_from="tempfile", _import="mkstemp")
424 @imports(_from="textwrap", _import="dedent")
425 @imports(_from="mozbuild.configure.util", _import="LineIO")
426 def assert_rust_compile(host_or_target, rustc_target, rustc):
427 # Check to see whether our rustc has a reasonably functional stdlib
428 # for our chosen target.
429 target_arg = "--target=" + rustc_target
430 in_fd, in_path = mkstemp(prefix="conftest", suffix=".rs", text=True)
431 out_fd, out_path = mkstemp(prefix="conftest", suffix=".rlib")
434 source = b'pub extern fn hello() { println!("Hello world"); }'
435 log.debug("Creating `%s` with content:", in_path)
436 with LineIO(lambda l: log.debug("| %s", l)) as out:
439 os.write(in_fd, source)
456 Cannot compile for {} with {}
457 The target may be unsupported, or you may not have
458 a rust std library for that target installed. Try:
462 host_or_target.alias, rustc, rustc_target
467 check_cmd_output(*cmd, onerror=failed)
468 if not os.path.exists(out_path) or os.path.getsize(out_path) == 0:
480 rust_supported_targets,
484 @checking("for rust host triplet")
485 @imports(_from="textwrap", _import="dedent")
486 def rust_host_triple(
487 rustc, host, compiler_info, rustc_host, rust_supported_targets, arm_target
489 rustc_target = detect_rustc_target(
490 host, compiler_info, arm_target, rust_supported_targets
492 if rustc_target != rustc_host:
493 if host.alias == rustc_target:
494 configure_host = host.alias
496 configure_host = "{}/{}".format(host.alias, rustc_target)
500 The rust compiler host ({rustc}) is not suitable for the configure host ({configure}).
502 You can solve this by:
503 * Set your configure host to match the rust compiler host by editing your
504 mozconfig and adding "ac_add_options --host={rustc}".
505 * Or, install the rust toolchain for {configure}, if supported, by running
506 "rustup default stable-{rustc_target}"
509 configure=configure_host,
510 rustc_target=rustc_target,
514 assert_rust_compile(host, rustc_target, rustc)
519 rustc, target, c_compiler, rust_supported_targets, arm_target, when=rust_compiler
521 @checking("for rust target triplet")
522 def rust_target_triple(
523 rustc, target, compiler_info, rust_supported_targets, arm_target
525 rustc_target = detect_rustc_target(
526 target, compiler_info, arm_target, rust_supported_targets
528 assert_rust_compile(target, rustc_target, rustc)
532 set_config("RUST_TARGET", rust_target_triple)
533 set_config("RUST_HOST_TARGET", rust_host_triple)
536 # This is used for putting source info into symbol files.
537 set_config("RUSTC_COMMIT", depends(rustc_info)(lambda i: i.commit))
539 # Rustdoc is required by Rust tests below.
540 option(env="RUSTDOC", nargs=1, help="Path to the rustdoc program")
542 rustdoc = check_prog(
545 paths=rust_search_path,
550 # This option is separate from --enable-tests because Rust tests are particularly
551 # expensive in terms of compile time (especially for code in libxul).
553 "--enable-rust-tests",
554 help="Enable building and running of Rust tests during `make check`",
558 @depends("--enable-rust-tests", rustdoc)
559 def rust_tests(enable_rust_tests, rustdoc):
560 if enable_rust_tests and not rustdoc:
561 die("--enable-rust-tests requires rustdoc")
562 return bool(enable_rust_tests)
565 set_config("MOZ_RUST_TESTS", rust_tests)
568 @depends(target, c_compiler, rustc)
570 def rustc_natvis_ldflags(target, compiler_info, rustc):
571 if target.kernel == "WINNT" and compiler_info.type == "clang-cl":
572 sysroot = check_cmd_output(rustc, "--print", "sysroot").strip()
573 etc = os.path.join(sysroot, "lib/rustlib/etc")
575 if os.path.isdir(etc):
576 for f in os.listdir(etc):
577 if f.endswith(".natvis"):
578 ldflags.append("-NATVIS:" + normsep(os.path.join(etc, f)))
582 set_config("RUSTC_NATVIS_LDFLAGS", rustc_natvis_ldflags)
586 "--enable-rust-debug",
587 default=depends(when="--enable-debug")(lambda: True),
588 help="{Build|Do not build} Rust code with debug assertions turned " "on.",
592 @depends(when="--enable-rust-debug")
597 set_config("MOZ_DEBUG_RUST", debug_rust)
598 set_define("MOZ_DEBUG_RUST", debug_rust)
600 # ==============================================================
602 option(env="RUSTFLAGS", nargs=1, help="Rust compiler flags")
603 set_config("RUSTFLAGS", depends("RUSTFLAGS")(lambda flags: flags))
606 # Rust compiler flags
607 # ==============================================================
610 @depends(moz_optimize)
611 def rustc_opt_level_default(moz_optimize):
612 return "2" if moz_optimize.optimize else "0"
616 env="RUSTC_OPT_LEVEL",
617 default=rustc_opt_level_default,
619 help="Rust compiler optimization level (-C opt-level=%s)",
623 @depends("RUSTC_OPT_LEVEL")
624 def rustc_opt_level(opt_level_option):
625 return opt_level_option[0]
628 set_config("CARGO_PROFILE_RELEASE_OPT_LEVEL", rustc_opt_level)
629 set_config("CARGO_PROFILE_DEV_OPT_LEVEL", rustc_opt_level)
636 "--enable-debug-symbols",
637 "--enable-frame-pointers",
641 def rust_compile_flags(
650 # Cargo currently supports only two interesting profiles for building:
651 # development and release. Those map (roughly) to --enable-debug and
652 # --disable-debug in Gecko, respectively.
654 # But we'd also like to support an additional axis of control for
655 # optimization level. Since Cargo only supports 2 profiles, we're in
658 # Code here derives various compiler options given other configure options.
659 # The options defined here effectively override defaults specified in
662 debug_assertions = None
665 # opt-level=0 implies -C debug-assertions, which may not be desired
666 # unless Rust debugging is enabled.
667 if opt_level == "0" and not debug_rust:
668 debug_assertions = False
675 if debug_assertions is not None:
676 opts.append("debug-assertions=%s" % ("yes" if debug_assertions else "no"))
677 if debug_info is not None:
678 opts.append("debuginfo=%s" % debug_info)
680 opts.append("force-frame-pointers=yes")
681 # CFG for arm64 is crashy, see `def security_hardening_cflags`.
682 if target.kernel == "WINNT" and target.cpu != "aarch64":
683 opts.append("control-flow-guard=yes")
687 flags.extend(["-C", opt])
689 if "rust" in path_remapping:
690 # rustc has supported --remap-path-prefix since version 1.26, well
691 # before our required minimum Rust version, so there's no need to
692 # feature-detect or gate on versions.
693 for old, new in path_remappings:
694 flags.append(f"--remap-path-prefix={old}={new}")
699 # Rust incremental compilation
700 # ==============================================================
703 option("--disable-cargo-incremental", help="Disable incremental rust compilation.")
711 "--disable-cargo-incremental",
716 def cargo_incremental(
725 """Return a value for the CARGO_INCREMENTAL environment variable."""
729 elif enabled.origin != "default":
732 # We never want to use incremental compilation in automation. sccache
733 # handles our automation use case much better than incremental compilation
738 # Coverage instrumentation doesn't play well with incremental compilation
739 # https://github.com/rust-lang/rust/issues/50203.
743 # Incremental compilation doesn't work as well as it should, and if we're
744 # using sccache, it's better to use sccache than incremental compilation.
745 if not using_sccache and rustc_wrapper:
746 rustc_wrapper = os.path.basename(rustc_wrapper[0])
747 if os.path.splitext(rustc_wrapper)[0].lower() == "sccache":
752 # Incremental compilation is automatically turned on for debug builds, so
753 # we don't need to do anything special here.
757 # Don't enable on --enable-release builds, because of the runtime
759 if not developer_options:
762 # We're clear to use incremental compilation!
766 set_config("CARGO_INCREMENTAL", cargo_incremental)
769 @depends(rust_compile_flags, "--enable-warnings-as-errors", rustc_info)
770 def rust_flags(compile_flags, warnings_as_errors, rustc_info):
773 # Note that cargo passes --cap-lints warn to rustc for third-party code, so
774 # we don't need a very complicated setup.
775 if warnings_as_errors:
776 warning_flags.append("-Dwarnings")
777 # Work around https://github.com/rust-lang/rust/issues/84428
778 if rustc_info.version >= "1.52":
779 warning_flags.append("-Aproc-macro-back-compat")
781 warning_flags.extend(("--cap-lints", "warn"))
783 return compile_flags + warning_flags
786 set_config("MOZ_RUST_DEFAULT_FLAGS", rust_flags)