DXR is a code search and navigation tool aimed at making sense of large projects. It supports full-text and regex searches as well as structural queries.

Mercurial (6863f516ba38)

VCS Links

Line Code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384
# -*- 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('subprocess')
    @imports(_from='__builtin__', _import='open')
    @imports('os')
    def unwrap(prog, name):
        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):
    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'],
    )

set_config('RUSTC_VERSION', depends(rustc_info)(lambda info: str(info.version)))

@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, 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.22.0')
        cargo_min_version = Version('0.23.0')
    else:
        rustc_min_version = Version('1.34.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)
        endianness = t.endianness
        if t.cpu.startswith('thumb') and endianness not in ('big', 'little'):
            endianness = 'little'
        key = (t.cpu, 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, 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}

    host_or_target_str = {host: 'host', target: 'target'}[host_or_target]

    @depends(rustc, 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('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, 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.
        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

        if host_or_target.cpu == 'arm' and arm_target.arm_arch == 7 and \
                arm_target.fpu == 'neon' and arm_target.thumb2:
            host_or_target_cpus = ('thumbv7neon', host_or_target.cpu)
        else:
            host_or_target_cpus = (host_or_target.cpu,)

        for host_or_target_cpu in host_or_target_cpus:
            rustc_target = rust_supported_targets.per_os.get(
                (host_or_target_cpu, host_or_target.endianness, host_or_target_os))
            if rustc_target:
                break

            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:
                break

        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)


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