build/moz.configure/rust.configure
author Mike Hommey <mh+mozilla@glandium.org>
Fri, 11 Jan 2019 15:42:07 +0000
changeset 453566 027d42f23d2d
parent 453565 82ed24e46ee4
child 454175 fad773445014
permissions -rw-r--r--
Bug 1519319 - Move cbindgen check to bindgen.configure. r=froydnj Depends on D16292 Differential Revision: https://phabricator.services.mozilla.com/D16293

# -*- 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'], paths=toolchain_search_path,
                   input='RUSTC', allow_missing=True)
cargo = check_prog('CARGO', ['cargo'], paths=toolchain_search_path,
                   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.31.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)

# 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)

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 in ('x86', 'aarch64') 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)