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 (1aeaa33a64f9)

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
# -*- 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.
rustc = check_prog('RUSTC', add_rustup_path('rustc'), allow_missing=True)
cargo = check_prog('CARGO', add_rustup_path('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.24.0')
    cargo_min_version = Version('0.{}'.format(rustc_min_version.minor + 1))

    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)

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

set_config('MOZ_RUST_TESTS', depends('--enable-rust-tests')(lambda v: bool(v)))

option(env='WIN64_LINK', nargs=1, help='Path to link.exe that targets win64')
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)