author Boris Chiou <>
Fri, 26 Oct 2018 18:03:33 +0000
changeset 443234 5b744b960fe0280854c3129aa26b7894083ef632
parent 440954 a232864e100291f7fe7e7b60745aae2094786b43
child 443588 5b0e4820c7fbd5c21451c9e72b103b5555664e3a
permissions -rw-r--r--
Bug 1496619 - Part 5: Bump cbindgen to 0.6.6 r=emilio So we can generate generic enum by cbindgen (for TimingFunction). Depends on D9845 Differential Revision:

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

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

@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'),

@checking('cargo version', lambda info: info.version)
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 =, 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 =

    return namespace(

@depends(rustc_info, cargo_info)
@imports(_from='textwrap', _import='dedent')
def rust_compiler(rustc_info, cargo_info):
    if not rustc_info:
        Rust compiler not found.
        To compile rust language sources, you must have 'rustc' in your path.
        See for more information.

        You can install rust by running './mach bootstrap'
        or by directly running the installer from
    rustc_min_version = Version('1.29.0')
    cargo_min_version = rustc_min_version

    version = rustc_info.version
    if version < rustc_min_version:
        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
        '''.format(version, rustc_min_version)))

    if not cargo_info:
        Cargo package manager not found.
        To compile Rust language sources, you must have 'cargo' in your path.
        See for more information.

        You can install cargo by running './mach bootstrap'
        or by directly running the installer from

    version = cargo_info.version
    if version < cargo_min_version:
        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]
        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
            per_os[key] = t
    return namespace(per_os=per_os, per_raw_os=per_raw_os)

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,
    @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'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'
                host_or_target_os = 'windows-msvc'
            host_or_target_raw_os = host_or_target_os
            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,

        if rustc_target is None:
            die("Don't know how to translate {} for rustc".format(

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

            os.write(in_fd, source)

            cmd = [
                '--crate-type', 'staticlib',
                '-o', out_path,

            def failed():
                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:

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

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).
       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,
                      (lambda build_project: build_project != 'js'))

@checking('cbindgen version')
@imports(_from='textwrap', _import='dedent')
def cbindgen_version(cbindgen):
    cbindgen_min_version = Version('0.6.6')

    # cbindgen x.y.z
    version = Version(check_cmd_output(cbindgen, '--version').strip().split(" ")[1])

    if version < cbindgen_min_version:
        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 \ == '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('''\
        linker = "{objdir}/build/win64/cargo-linker.bat"
    # 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)