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

355 строки
13 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/.
@imports(_from='os.path', _import='expanduser')
def add_rustup_path(what):
# rustup installs rustc/cargo into ~/.cargo/bin by default,
# so look there if the binaries aren't in $PATH.
return [what, os.path.join(expanduser('~/.cargo/bin'), what)]
# 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', add_rustup_path('rustc'),
input='RUSTC', allow_missing=True)
cargo = check_prog('CARGO', add_rustup_path('cargo'),
input='CARGO', allow_missing=True)
@depends_if(rustc)
@checking('rustc version', lambda info: info.version)
def rustc_info(rustc):
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'],
)
@depends_if(cargo)
@checking('cargo version', lambda info: info.version)
@imports('re')
def cargo_info(cargo):
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)
@imports(_from='textwrap', _import='dedent')
def rust_compiler(rustc_info, cargo_info):
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('1.28.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)
def rust_supported_targets(rustc):
out = check_cmd_output(rustc, '--print', 'target-list').splitlines()
# The os in the triplets used by rust may match the same OSes, in which
# case we need to check the raw_os instead.
per_os = {}
ambiguous = set()
per_raw_os = {}
for t in out:
t = split_triplet(t, allow_unknown=True)
key = (t.cpu, t.endianness, t.os)
if key in per_os:
previous = per_os[key]
per_raw_os[(previous.cpu, previous.endianness,
previous.raw_os)] = previous
del per_os[key]
ambiguous.add(key)
if key in ambiguous:
raw_os = t.raw_os
# split_triplet will return a raw_os of 'androideabi' for
# rust targets in the form cpu-linux-androideabi, but what
# we get from the build system is linux-androideabi, so
# normalize.
if raw_os == 'androideabi':
raw_os = 'linux-androideabi'
per_raw_os[(t.cpu, t.endianness, raw_os)] = t
else:
per_os[key] = t
return namespace(per_os=per_os, per_raw_os=per_raw_os)
@template
def rust_triple_alias(host_or_target):
"""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)
@depends(rustc, host_or_target, c_compiler, rust_supported_targets,
when=rust_compiler)
@imports('os')
@imports('subprocess')
@imports(_from='mozbuild.configure.util', _import='LineIO')
@imports(_from='mozbuild.shellutil', _import='quote')
@imports(_from='tempfile', _import='mkstemp')
@imports(_from='textwrap', _import='dedent')
def rust_target(rustc, host_or_target, compiler_info,
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.
if host_or_target.kernel == 'WINNT':
if compiler_info.type in ('gcc', 'clang'):
host_or_target_os = 'windows-gnu'
else:
host_or_target_os = 'windows-msvc'
host_or_target_raw_os = host_or_target_os
else:
host_or_target_os = host_or_target.os
host_or_target_raw_os = host_or_target.raw_os
rustc_target = rust_supported_targets.per_os.get(
(host_or_target.cpu, host_or_target.endianness, host_or_target_os))
if rustc_target is None:
rustc_target = rust_supported_targets.per_raw_os.get(
(host_or_target.cpu, host_or_target.endianness,
host_or_target_raw_os))
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.alias
in_fd, in_path = mkstemp(prefix='conftest', suffix='.rs')
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, 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.alias)))
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.alias
return rust_target
rust_target_triple = rust_triple_alias(target)
rust_host_triple = rust_triple_alias(host)
set_config('RUST_TARGET', rust_target_triple)
set_config('RUST_HOST_TARGET', rust_host_triple)
@depends(rust_target_triple)
def rust_target_env_name(triple):
return triple.upper().replace('-', '_')
# We need this to form various Cargo environment variables, as there is no
# uppercase function in make, and we don't want to shell out just for
# converting a string to uppercase.
set_config('RUST_TARGET_ENV_NAME', rust_target_env_name)
# This is used for putting source info into symbol files.
set_config('RUSTC_COMMIT', depends(rustc_info)(lambda i: i.commit))
# Until we remove all the other Rust checks in old-configure.
add_old_configure_assignment('RUSTC', rustc)
add_old_configure_assignment('RUST_TARGET', rust_target_triple)
# Rustdoc is required by Rust tests below.
js_option(env='RUSTDOC', nargs=1, help='Path to the rustdoc program')
rustdoc = check_prog('RUSTDOC', add_rustup_path('rustdoc'),
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 of Rust tests, and build-time execution of them')
@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)
# cbindgen is needed by the style system build.
cbindgen = check_prog('CBINDGEN', add_rustup_path('cbindgen'), paths=toolchain_search_path,
when=depends(build_project)
(lambda build_project: build_project != 'js'))
@depends_if(cbindgen)
@checking('cbindgen version')
@imports(_from='textwrap', _import='dedent')
def cbindgen_version(cbindgen):
cbindgen_min_version = Version('0.6.2')
# cbindgen x.y.z
version = Version(check_cmd_output(cbindgen, '--version').strip().split(" ")[1])
if version < cbindgen_min_version:
die(dedent('''\
cbindgen version {} is too old. At least version {} is required.
Please update using 'cargo install cbindgen --force' or running
'./mach bootstrap', after removing the existing executable located at
{}.
'''.format(version, cbindgen_min_version, cbindgen)))
return version
# Bindgen can use rustfmt to format Rust file, but it's not required.
js_option(env='RUSTFMT', nargs=1, help='Path to the rustfmt program')
rustfmt = check_prog('RUSTFMT', add_rustup_path('rustfmt'),
input='RUSTFMT', allow_missing=True)
js_option(env='WIN64_LINK', nargs=1, help='Path to link.exe that targets win64')
js_option(env='WIN64_LIB', nargs=1, help='Paths to libraries for the win64 linker')
set_config('WIN64_LINK', depends('WIN64_LINK')(lambda x: x))
set_config('WIN64_LIB', depends('WIN64_LIB')(lambda x: x))
@depends(target, rustc_info, c_compiler, 'WIN64_LINK', 'WIN64_LIB')
def win64_cargo_linker(target, rustc_info, compiler_info, link, lib):
# When we're building a 32-bit Windows build with a 64-bit rustc, we
# need to configure the linker it will use for host binaries (build scripts)
# specially because the compiler configuration we use for the build is for
# MSVC targeting 32-bit binaries.
if target.kernel == 'WINNT' and target.cpu == 'x86' and \
compiler_info.type in ('msvc', 'clang-cl') and \
rustc_info.host == 'x86_64-pc-windows-msvc' and link and lib:
return True
set_config('WIN64_CARGO_LINKER', win64_cargo_linker)
@depends(win64_cargo_linker, check_build_environment)
@imports(_from='textwrap', _import='dedent')
def win64_cargo_linker_config(linker, env):
if linker:
return dedent('''\
[target.x86_64-pc-windows-msvc]
linker = "{objdir}/build/win64/cargo-linker.bat"
'''.format(objdir=env.topobjdir))
# We want an empty string here so we don't leave the @ variable in the config file.
return ''
set_config('WIN64_CARGO_LINKER_CONFIG', win64_cargo_linker_config)