зеркало из https://github.com/mozilla/gecko-dev.git
553 строки
19 KiB
Python
553 строки
19 KiB
Python
# -*- 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 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),
|
|
)
|
|
|
|
|
|
@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, build_project)
|
|
@imports(_from="mozboot.util", _import="MINIMUM_RUST_VERSION")
|
|
@imports(_from="textwrap", _import="dedent")
|
|
def rust_compiler(rustc_info, cargo_info, build_project):
|
|
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/
|
|
"""
|
|
)
|
|
)
|
|
if build_project == "tools/crashreporter":
|
|
rustc_min_version = Version("1.47.0")
|
|
else:
|
|
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 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)
|
|
except ValueError:
|
|
if t.startswith("thumb"):
|
|
cpu, rest = t.split("-", 1)
|
|
retry = "-".join(("arm", rest))
|
|
elif t.endswith("-windows-msvc"):
|
|
retry = t[: -len("windows-msvc")] + "mingw32"
|
|
elif t.endswith("-windows-gnu"):
|
|
retry = t[: -len("windows-gnu")] + "mingw32"
|
|
else:
|
|
continue
|
|
try:
|
|
info = split_triplet(retry)
|
|
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 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
|
|
narrowed = [c for c in candidates if c.target.alias == host_or_target.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="six", _import="ensure_binary")
|
|
@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 = '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, ensure_binary(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)
|