diff options
Diffstat (limited to '')
-rw-r--r-- | build/moz.configure/rust.configure | 786 |
1 files changed, 786 insertions, 0 deletions
diff --git a/build/moz.configure/rust.configure b/build/moz.configure/rust.configure new file mode 100644 index 0000000000..7a2fd1ae70 --- /dev/null +++ b/build/moz.configure/rust.configure @@ -0,0 +1,786 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + + +# Rust is required by `rust_compiler` below. We allow_missing here +# to propagate failures to the better error message there. +option(env="RUSTC", nargs=1, help="Path to the rust compiler") +option(env="CARGO", nargs=1, help="Path to the Cargo package manager") + +rustc = check_prog( + "_RUSTC", + ["rustc"], + what="rustc", + paths=rust_search_path, + input="RUSTC", + allow_missing=True, +) +cargo = check_prog( + "_CARGO", + ["cargo"], + what="cargo", + paths=rust_search_path, + input="CARGO", + allow_missing=True, +) + + +@template +def unwrap_rustup(prog, name): + # rustc and cargo can either be rustup wrappers, or they can be the actual, + # plain executables. For cargo, on OSX, rustup sets DYLD_LIBRARY_PATH (at + # least until https://github.com/rust-lang/rustup.rs/pull/1752 is merged + # and shipped) and that can wreak havoc (see bug 1536486). Similarly, for + # rustc, rustup silently honors toolchain overrides set by vendored crates + # (see bug 1547196). + # + # In either case, we need to find the plain executables. + # + # To achieve that, try to run `PROG +stable`. When the rustup wrapper is in + # use, it either prints PROG's help and exits with status 0, or prints + # an error message (error: toolchain 'stable' is not installed) and exits + # with status 1. In the cargo case, when plain cargo is in use, it exits + # with a different error message (e.g. "error: no such subcommand: + # `+stable`"), and exits with status 101. + # + # Unfortunately, in the rustc case, when plain rustc is in use, + # `rustc +stable` will exit with status 1, complaining about a missing + # "+stable" file. We'll examine the error output to try and distinguish + # between failing rustup and failing rustc. + @depends(prog, dependable(name)) + @imports(_from="__builtin__", _import="open") + @imports("os") + def unwrap(prog, name): + if not prog: + return + + def from_rustup_which(): + out = check_cmd_output("rustup", "which", name, executable=prog).rstrip() + # If for some reason the above failed to return something, keep the + # PROG we found originally. + if out: + log.info("Actually using '%s'", out) + return out + + log.info("No `rustup which` output, using '%s'", prog) + return prog + + (retcode, stdout, stderr) = get_cmd_output(prog, "+stable") + + if name == "cargo" and retcode != 101: + prog = from_rustup_which() + elif name == "rustc": + if retcode == 0: + prog = from_rustup_which() + elif "+stable" in stderr: + # PROG looks like plain `rustc`. + pass + else: + # Assume PROG looks like `rustup`. This case is a little weird, + # insofar as the user doesn't have the "stable" toolchain + # installed, but go ahead and unwrap anyway: the user might + # have only certain versions, beta, or nightly installed, and + # we'll catch invalid versions later. + prog = from_rustup_which() + + return normalize_path(prog) + + return unwrap + + +rustc = unwrap_rustup(rustc, "rustc") +cargo = unwrap_rustup(cargo, "cargo") + + +set_config("CARGO", cargo) +set_config("RUSTC", rustc) + + +@depends_if(rustc) +@checking("rustc version", lambda info: info.version) +def rustc_info(rustc): + if not rustc: + return + out = check_cmd_output(rustc, "--version", "--verbose").splitlines() + info = dict((s.strip() for s in line.split(":", 1)) for line in out[1:]) + return namespace( + version=Version(info.get("release", "0")), + commit=info.get("commit-hash", "unknown"), + host=info["host"], + llvm_version=Version(info.get("LLVM version", "0")), + ) + + +set_config( + "RUSTC_VERSION", + depends(rustc_info)(lambda info: str(info.version) if info else None), +) + + +set_config( + "RUSTC_LLVM_VERSION", + depends(rustc_info)(lambda info: str(info.llvm_version) if info else None), +) + +set_config( + "MOZ_CLANG_NEWER_THAN_RUSTC_LLVM", + depends(c_compiler, rustc_info)( + lambda c_compiler, rustc_info: rustc_info + and c_compiler.type == "clang" + and c_compiler.version.major > rustc_info.llvm_version.major + ), +) + + +@depends_if(cargo) +@checking("cargo version", lambda info: info.version) +@imports("re") +def cargo_info(cargo): + if not cargo: + return + out = check_cmd_output(cargo, "--version", "--verbose").splitlines() + info = dict((s.strip() for s in line.split(":", 1)) for line in out[1:]) + version = info.get("release") + # Older versions of cargo didn't support --verbose, in which case, they + # only output a not-really-pleasant-to-parse output. Fortunately, they + # don't error out, so we can just try some regexp matching on the output + # we already got. + if version is None: + VERSION_FORMAT = r"^cargo (\d\.\d+\.\d+).*" + + m = re.search(VERSION_FORMAT, out[0]) + # Fail fast if cargo changes its output on us. + if not m: + die("Could not determine cargo version from output: %s", out) + version = m.group(1) + + return namespace( + version=Version(version), + ) + + +@depends(rustc_info, cargo_info, target) +@imports(_from="mozboot.util", _import="MINIMUM_RUST_VERSION") +@imports(_from="textwrap", _import="dedent") +def rust_compiler(rustc_info, cargo_info, target): + if not rustc_info: + die( + dedent( + """\ + Rust compiler not found. + To compile rust language sources, you must have 'rustc' in your path. + See https://www.rust-lang.org/ for more information. + + You can install rust by running './mach bootstrap' + or by directly running the installer from https://rustup.rs/ + """ + ) + ) + rustc_min_version = Version(MINIMUM_RUST_VERSION) + cargo_min_version = rustc_min_version + + version = rustc_info.version + is_nightly = "nightly" in version.version + is_version_number_match = ( + version.major == rustc_min_version.major + and version.minor == rustc_min_version.minor + and version.patch == rustc_min_version.patch + ) + + if version < rustc_min_version or (is_version_number_match and is_nightly): + die( + dedent( + """\ + Rust compiler {} is too old. + + To compile Rust language sources please install at least + version {} of the 'rustc' toolchain (or, if using nightly, + at least one version newer than {}) and make sure it is + first in your path. + + You can verify this by typing 'rustc --version'. + + If you have the 'rustup' tool installed you can upgrade + to the latest release by typing 'rustup update'. The + installer is available from https://rustup.rs/ + """.format( + version, rustc_min_version, rustc_min_version + ) + ) + ) + + if target.kernel == "WINNT" and (version.major, version.minor) == (1, 56): + die( + dedent( + """\ + Rust compiler 1.56.* is not supported for Windows builds. + + Use a newer or an older version. + + See https://github.com/rust-lang/rust/issues/88576. + """ + ) + ) + + if not cargo_info: + die( + dedent( + """\ + Cargo package manager not found. + To compile Rust language sources, you must have 'cargo' in your path. + See https://www.rust-lang.org/ for more information. + + You can install cargo by running './mach bootstrap' + or by directly running the installer from https://rustup.rs/ + """ + ) + ) + + version = cargo_info.version + if version < cargo_min_version: + die( + dedent( + """\ + Cargo package manager {} is too old. + + To compile Rust language sources please install at least + version {} of 'cargo' and make sure it is first in your path. + + You can verify this by typing 'cargo --version'. + """ + ).format(version, cargo_min_version) + ) + + return True + + +@depends(rustc, when=rust_compiler) +@imports(_from="__builtin__", _import="ValueError") +def rust_supported_targets(rustc): + out = check_cmd_output(rustc, "--print", "target-list").splitlines() + data = {} + for t in out: + try: + info = split_triplet(t, allow_wasi=True) + except ValueError: + if t.startswith("thumb"): + cpu, rest = t.split("-", 1) + retry = "-".join(("arm", rest)) + else: + continue + try: + info = split_triplet(retry, allow_wasi=True) + except ValueError: + continue + key = (info.cpu, info.endianness, info.os) + data.setdefault(key, []).append(namespace(rust_target=t, target=info)) + return data + + +def detect_rustc_target( + host_or_target, compiler_info, arm_target, rust_supported_targets +): + # Rust's --target options are similar to, but not exactly the same + # as, the autoconf-derived targets we use. An example would be that + # Rust uses distinct target triples for targetting the GNU C++ ABI + # and the MSVC C++ ABI on Win32, whereas autoconf has a single + # triple and relies on the user to ensure that everything is + # compiled for the appropriate ABI. We need to perform appropriate + # munging to get the correct option to rustc. + # We correlate the autoconf-derived targets with the list of targets + # rustc gives us with --print target-list. + candidates = rust_supported_targets.get( + (host_or_target.cpu, host_or_target.endianness, host_or_target.os), [] + ) + + def find_candidate(candidates): + if len(candidates) == 1: + return candidates[0].rust_target + elif not candidates: + return None + + # We have multiple candidates. There are two cases where we can try to + # narrow further down using extra information from the build system. + # - For windows targets, correlate with the C compiler type + if host_or_target.kernel == "WINNT": + if host_or_target.abi: + if host_or_target.abi == "msvc": + suffix = "windows-msvc" + elif host_or_target.abi == "mingw": + suffix = "windows-gnu" + elif compiler_info.type in ("gcc", "clang"): + suffix = "windows-gnu" + else: + suffix = "windows-msvc" + narrowed = [ + c for c in candidates if c.rust_target.endswith("-{}".format(suffix)) + ] + if len(narrowed) == 1: + return narrowed[0].rust_target + elif narrowed: + candidates = narrowed + + vendor_aliases = {"pc": ("w64", "windows")} + narrowed = [ + c + for c in candidates + if host_or_target.vendor in vendor_aliases.get(c.target.vendor, ()) + ] + + if len(narrowed) == 1: + return narrowed[0].rust_target + + # - For arm targets, correlate with arm_target + # we could be more thorough with the supported rust targets, but they + # don't support OSes that are supported to build Gecko anyways. + # Also, sadly, the only interface to check the rust target cpu features + # is --print target-spec-json, and it's unstable, so we have to rely on + # our own knowledge of what each arm target means. + if host_or_target.cpu == "arm" and host_or_target.endianness == "little": + prefixes = [] + if arm_target.arm_arch >= 7: + if arm_target.thumb2 and arm_target.fpu == "neon": + prefixes.append("thumbv7neon") + if arm_target.thumb2: + prefixes.append("thumbv7a") + prefixes.append("armv7") + if arm_target.arm_arch >= 6: + prefixes.append("armv6") + if host_or_target.os != "Android": + # arm-* rust targets are armv6... except arm-linux-androideabi + prefixes.append("arm") + if arm_target.arm_arch >= 5: + prefixes.append("armv5te") + if host_or_target.os == "Android": + # arm-* rust targets are armv6... except arm-linux-androideabi + prefixes.append("arm") + if arm_target.arm_arch >= 4: + prefixes.append("armv4t") + # rust freebsd targets are the only ones that don't have a 'hf' suffix + # for hard-float. Technically, that means if the float abi ever is not + # hard-float, this will pick a wrong target, but since rust only + # supports hard-float, let's assume that means freebsd only support + # hard-float. + if arm_target.float_abi == "hard" and host_or_target.os != "FreeBSD": + suffix = "hf" + else: + suffix = "" + for p in prefixes: + for c in candidates: + if c.rust_target.startswith( + "{}-".format(p) + ) and c.rust_target.endswith(suffix): + return c.rust_target + + # See if we can narrow down on the exact alias. + # We use the sub_configure_alias to keep support mingw32 triplets as input. + narrowed = [ + c + for c in candidates + if c.target.sub_configure_alias == host_or_target.sub_configure_alias + ] + if len(narrowed) == 1: + return narrowed[0].rust_target + elif narrowed: + candidates = narrowed + + # See if we can narrow down with the raw OS + narrowed = [c for c in candidates if c.target.raw_os == host_or_target.raw_os] + if len(narrowed) == 1: + return narrowed[0].rust_target + elif narrowed: + candidates = narrowed + + # See if we can narrow down with the raw OS and raw CPU + narrowed = [ + c + for c in candidates + if c.target.raw_os == host_or_target.raw_os + and c.target.raw_cpu == host_or_target.raw_cpu + ] + if len(narrowed) == 1: + return narrowed[0].rust_target + + # Finally, see if the vendor can be used to disambiguate. + narrowed = [c for c in candidates if c.target.vendor == host_or_target.vendor] + if len(narrowed) == 1: + return narrowed[0].rust_target + + return None + + rustc_target = find_candidate(candidates) + + if rustc_target is None: + die("Don't know how to translate {} for rustc".format(host_or_target.alias)) + + return rustc_target + + +@imports("os") +@imports(_from="tempfile", _import="mkstemp") +@imports(_from="textwrap", _import="dedent") +@imports(_from="mozbuild.configure.util", _import="LineIO") +def assert_rust_compile(host_or_target, rustc_target, rustc): + # Check to see whether our rustc has a reasonably functional stdlib + # for our chosen target. + target_arg = "--target=" + rustc_target + in_fd, in_path = mkstemp(prefix="conftest", suffix=".rs", text=True) + out_fd, out_path = mkstemp(prefix="conftest", suffix=".rlib") + os.close(out_fd) + try: + source = b'pub extern fn hello() { println!("Hello world"); }' + log.debug("Creating `%s` with content:", in_path) + with LineIO(lambda l: log.debug("| %s", l)) as out: + out.write(source) + + os.write(in_fd, source) + os.close(in_fd) + + cmd = [ + rustc, + "--crate-type", + "staticlib", + target_arg, + "-o", + out_path, + in_path, + ] + + def failed(): + die( + dedent( + """\ + Cannot compile for {} with {} + The target may be unsupported, or you may not have + a rust std library for that target installed. Try: + + rustup target add {} + """.format( + host_or_target.alias, rustc, rustc_target + ) + ) + ) + + check_cmd_output(*cmd, onerror=failed) + if not os.path.exists(out_path) or os.path.getsize(out_path) == 0: + failed() + finally: + os.remove(in_path) + os.remove(out_path) + + +@depends( + rustc, + host, + host_c_compiler, + rustc_info.host, + rust_supported_targets, + arm_target, + when=rust_compiler, +) +@checking("for rust host triplet") +@imports(_from="textwrap", _import="dedent") +def rust_host_triple( + rustc, host, compiler_info, rustc_host, rust_supported_targets, arm_target +): + rustc_target = detect_rustc_target( + host, compiler_info, arm_target, rust_supported_targets + ) + if rustc_target != rustc_host: + if host.alias == rustc_target: + configure_host = host.alias + else: + configure_host = "{}/{}".format(host.alias, rustc_target) + die( + dedent( + """\ + The rust compiler host ({rustc}) is not suitable for the configure host ({configure}). + + You can solve this by: + * Set your configure host to match the rust compiler host by editing your + mozconfig and adding "ac_add_options --host={rustc}". + * Or, install the rust toolchain for {configure}, if supported, by running + "rustup default stable-{rustc_target}" + """.format( + rustc=rustc_host, + configure=configure_host, + rustc_target=rustc_target, + ) + ) + ) + assert_rust_compile(host, rustc_target, rustc) + return rustc_target + + +@depends( + rustc, target, c_compiler, rust_supported_targets, arm_target, when=rust_compiler +) +@checking("for rust target triplet") +def rust_target_triple( + rustc, target, compiler_info, rust_supported_targets, arm_target +): + rustc_target = detect_rustc_target( + target, compiler_info, arm_target, rust_supported_targets + ) + assert_rust_compile(target, rustc_target, rustc) + return rustc_target + + +set_config("RUST_TARGET", rust_target_triple) +set_config("RUST_HOST_TARGET", rust_host_triple) + + +# This is used for putting source info into symbol files. +set_config("RUSTC_COMMIT", depends(rustc_info)(lambda i: i.commit)) + +# Rustdoc is required by Rust tests below. +option(env="RUSTDOC", nargs=1, help="Path to the rustdoc program") + +rustdoc = check_prog( + "RUSTDOC", + ["rustdoc"], + paths=rust_search_path, + input="RUSTDOC", + allow_missing=True, +) + +# This option is separate from --enable-tests because Rust tests are particularly +# expensive in terms of compile time (especially for code in libxul). +option( + "--enable-rust-tests", + help="Enable building and running of Rust tests during `make check`", +) + + +@depends("--enable-rust-tests", rustdoc) +def rust_tests(enable_rust_tests, rustdoc): + if enable_rust_tests and not rustdoc: + die("--enable-rust-tests requires rustdoc") + return bool(enable_rust_tests) + + +set_config("MOZ_RUST_TESTS", rust_tests) + + +@depends(target, c_compiler, rustc) +@imports("os") +def rustc_natvis_ldflags(target, compiler_info, rustc): + if target.kernel == "WINNT" and compiler_info.type == "clang-cl": + sysroot = check_cmd_output(rustc, "--print", "sysroot").strip() + etc = os.path.join(sysroot, "lib/rustlib/etc") + ldflags = [] + if os.path.isdir(etc): + for f in os.listdir(etc): + if f.endswith(".natvis"): + ldflags.append("-NATVIS:" + normsep(os.path.join(etc, f))) + return ldflags + + +set_config("RUSTC_NATVIS_LDFLAGS", rustc_natvis_ldflags) + + +option( + "--enable-rust-debug", + default=depends(when="--enable-debug")(lambda: True), + help="{Build|Do not build} Rust code with debug assertions turned " "on.", +) + + +@depends(when="--enable-rust-debug") +def debug_rust(): + return True + + +set_config("MOZ_DEBUG_RUST", debug_rust) +set_define("MOZ_DEBUG_RUST", debug_rust) + +# ============================================================== + +option(env="RUSTFLAGS", nargs=1, help="Rust compiler flags") +set_config("RUSTFLAGS", depends("RUSTFLAGS")(lambda flags: flags)) + + +# Rust compiler flags +# ============================================================== + + +@depends(moz_optimize) +def rustc_opt_level_default(moz_optimize): + return "2" if moz_optimize.optimize else "0" + + +option( + env="RUSTC_OPT_LEVEL", + default=rustc_opt_level_default, + nargs=1, + help="Rust compiler optimization level (-C opt-level=%s)", +) + + +@depends("RUSTC_OPT_LEVEL") +def rustc_opt_level(opt_level_option): + return opt_level_option[0] + + +set_config("CARGO_PROFILE_RELEASE_OPT_LEVEL", rustc_opt_level) +set_config("CARGO_PROFILE_DEV_OPT_LEVEL", rustc_opt_level) + + +@depends( + rustc_opt_level, + debug_rust, + target, + "--enable-debug-symbols", + "--enable-frame-pointers", + path_remapping, + path_remappings, +) +def rust_compile_flags( + opt_level, + debug_rust, + target, + debug_symbols, + frame_pointers, + path_remapping, + path_remappings, +): + # Cargo currently supports only two interesting profiles for building: + # development and release. Those map (roughly) to --enable-debug and + # --disable-debug in Gecko, respectively. + # + # But we'd also like to support an additional axis of control for + # optimization level. Since Cargo only supports 2 profiles, we're in + # a bit of a bind. + # + # Code here derives various compiler options given other configure options. + # The options defined here effectively override defaults specified in + # Cargo.toml files. + + debug_assertions = None + debug_info = None + + # opt-level=0 implies -C debug-assertions, which may not be desired + # unless Rust debugging is enabled. + if opt_level == "0" and not debug_rust: + debug_assertions = False + + if debug_symbols: + debug_info = "2" + + opts = [] + + if debug_assertions is not None: + opts.append("debug-assertions=%s" % ("yes" if debug_assertions else "no")) + if debug_info is not None: + opts.append("debuginfo=%s" % debug_info) + if frame_pointers: + opts.append("force-frame-pointers=yes") + # CFG for arm64 is crashy, see `def security_hardening_cflags`. + if target.kernel == "WINNT" and target.cpu != "aarch64": + opts.append("control-flow-guard=yes") + + flags = [] + for opt in opts: + flags.extend(["-C", opt]) + + if "rust" in path_remapping: + # rustc has supported --remap-path-prefix since version 1.26, well + # before our required minimum Rust version, so there's no need to + # feature-detect or gate on versions. + for old, new in path_remappings: + flags.append(f"--remap-path-prefix={old}={new}") + + return flags + + +# Rust incremental compilation +# ============================================================== + + +option("--disable-cargo-incremental", help="Disable incremental rust compilation.") + + +@depends( + developer_options, + debug_rust, + "MOZ_AUTOMATION", + code_coverage, + "--disable-cargo-incremental", + using_sccache, + "RUSTC_WRAPPER", +) +@imports("os") +def cargo_incremental( + developer_options, + debug_rust, + automation, + code_coverage, + enabled, + using_sccache, + rustc_wrapper, +): + """Return a value for the CARGO_INCREMENTAL environment variable.""" + + if not enabled: + return "0" + elif enabled.origin != "default": + return "1" + + # We never want to use incremental compilation in automation. sccache + # handles our automation use case much better than incremental compilation + # would. + if automation: + return "0" + + # Coverage instrumentation doesn't play well with incremental compilation + # https://github.com/rust-lang/rust/issues/50203. + if code_coverage: + return "0" + + # Incremental compilation doesn't work as well as it should, and if we're + # using sccache, it's better to use sccache than incremental compilation. + if not using_sccache and rustc_wrapper: + rustc_wrapper = os.path.basename(rustc_wrapper[0]) + if os.path.splitext(rustc_wrapper)[0].lower() == "sccache": + using_sccache = True + if using_sccache: + return "0" + + # Incremental compilation is automatically turned on for debug builds, so + # we don't need to do anything special here. + if debug_rust: + return + + # Don't enable on --enable-release builds, because of the runtime + # performance cost. + if not developer_options: + return + + # We're clear to use incremental compilation! + return "1" + + +set_config("CARGO_INCREMENTAL", cargo_incremental) + + +@depends(rust_compile_flags, "--enable-warnings-as-errors", rustc_info) +def rust_flags(compile_flags, warnings_as_errors, rustc_info): + warning_flags = [] + + # Note that cargo passes --cap-lints warn to rustc for third-party code, so + # we don't need a very complicated setup. + if warnings_as_errors: + warning_flags.append("-Dwarnings") + # Work around https://github.com/rust-lang/rust/issues/84428 + if rustc_info.version >= "1.52": + warning_flags.append("-Aproc-macro-back-compat") + else: + warning_flags.extend(("--cap-lints", "warn")) + + return compile_flags + warning_flags + + +set_config("MOZ_RUST_DEFAULT_FLAGS", rust_flags) |