gecko-dev/build/moz.configure/rust.configure

463 строки
18 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.
js_option(env='RUSTC', nargs=1, help='Path to the rust compiler')
js_option(env='CARGO', nargs=1, help='Path to the Cargo package manager')
rustc = check_prog('_RUSTC', ['rustc'], what='rustc',
paths=toolchain_search_path, input='RUSTC',
allow_missing=True)
cargo = check_prog('_CARGO', ['cargo'], what='cargo',
paths=toolchain_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='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.31.0')
else:
rustc_min_version = Version('1.41.0')
cargo_min_version = rustc_min_version
version = rustc_info.version
if version < rustc_min_version:
die(dedent('''\
Rust compiler {} is too old.
To compile Rust language sources please install at least
version {} of the 'rustc' toolchain 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)))
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
@template
def rust_triple_alias(host_or_target, host_or_target_c_compiler):
"""Template defining the alias used for rustc's --target flag.
`host_or_target` is either `host` or `target` (the @depends functions
from init.configure).
"""
assert host_or_target in {host, target}
host_or_target_str = {host: 'host', target: 'target'}[host_or_target]
@depends(rustc, host_or_target, host_or_target_c_compiler,
rust_supported_targets, arm_target, when=rust_compiler)
@checking('for rust %s triplet' % host_or_target_str)
@imports('os')
@imports(_from='mozbuild.configure.util', _import='LineIO')
@imports(_from='mozbuild.shellutil', _import='quote')
@imports(_from='six', _import='ensure_binary')
@imports(_from='tempfile', _import='mkstemp')
@imports(_from='textwrap', _import='dedent')
def rust_target(rustc, host_or_target, compiler_info,
rust_supported_targets, arm_target):
# 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))
# 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)
# This target is usable.
return rustc_target
return rust_target
rust_target_triple = rust_triple_alias(target, c_compiler)
rust_host_triple = rust_triple_alias(host, host_c_compiler)
@depends(host, rust_host_triple, rustc_info.host)
def validate_rust_host_triple(host, rust_host, rustc_host):
if rust_host != rustc_host:
if host.alias == rust_host:
configure_host = host.alias
else:
configure_host = '{}/{}'.format(host.alias, rust_host)
die("The rust compiler host ({}) is not suitable for the configure host ({})."
.format(rustc_host, configure_host))
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.
js_option(env='RUSTDOC', nargs=1, help='Path to the rustdoc program')
rustdoc = check_prog('RUSTDOC', ['rustdoc'], paths=toolchain_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 = []
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)