1from __future__ import absolute_import, division, print_function
2import argparse
3import contextlib
4import datetime
5import distutils.version
6import hashlib
7import json
8import os
9import re
10import shutil
11import subprocess
12import sys
13import tarfile
14import tempfile
15
16from time import time
17
18def support_xz():
19    try:
20        with tempfile.NamedTemporaryFile(delete=False) as temp_file:
21            temp_path = temp_file.name
22        with tarfile.open(temp_path, "w:xz"):
23            pass
24        return True
25    except tarfile.CompressionError:
26        return False
27
28def get(base, url, path, checksums, verbose=False, do_verify=True):
29    with tempfile.NamedTemporaryFile(delete=False) as temp_file:
30        temp_path = temp_file.name
31
32    try:
33        if do_verify:
34            if url not in checksums:
35                raise RuntimeError("src/stage0.json doesn't contain a checksum for {}".format(url))
36            sha256 = checksums[url]
37            if os.path.exists(path):
38                if verify(path, sha256, False):
39                    if verbose:
40                        print("using already-download file", path)
41                    return
42                else:
43                    if verbose:
44                        print("ignoring already-download file",
45                            path, "due to failed verification")
46                    os.unlink(path)
47        download(temp_path, "{}/{}".format(base, url), True, verbose)
48        if do_verify and not verify(temp_path, sha256, verbose):
49            raise RuntimeError("failed verification")
50        if verbose:
51            print("moving {} to {}".format(temp_path, path))
52        shutil.move(temp_path, path)
53    finally:
54        if os.path.isfile(temp_path):
55            if verbose:
56                print("removing", temp_path)
57            os.unlink(temp_path)
58
59
60def download(path, url, probably_big, verbose):
61    for _ in range(0, 4):
62        try:
63            _download(path, url, probably_big, verbose, True)
64            return
65        except RuntimeError:
66            print("\nspurious failure, trying again")
67    _download(path, url, probably_big, verbose, False)
68
69
70def _download(path, url, probably_big, verbose, exception):
71    if probably_big or verbose:
72        print("downloading {}".format(url))
73    # see https://serverfault.com/questions/301128/how-to-download
74    if sys.platform == 'win32':
75        run(["PowerShell.exe", "/nologo", "-Command",
76             "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12;",
77             "(New-Object System.Net.WebClient).DownloadFile('{}', '{}')".format(url, path)],
78            verbose=verbose,
79            exception=exception)
80    else:
81        if probably_big or verbose:
82            option = "-#"
83        else:
84            option = "-s"
85        require(["curl", "--version"])
86        run(["curl", option,
87             "-y", "30", "-Y", "10",    # timeout if speed is < 10 bytes/sec for > 30 seconds
88             "--connect-timeout", "30",  # timeout if cannot connect within 30 seconds
89             "--retry", "3", "-Sf", "-o", path, url],
90            verbose=verbose,
91            exception=exception)
92
93
94def verify(path, expected, verbose):
95    """Check if the sha256 sum of the given path is valid"""
96    if verbose:
97        print("verifying", path)
98    with open(path, "rb") as source:
99        found = hashlib.sha256(source.read()).hexdigest()
100    verified = found == expected
101    if not verified:
102        print("invalid checksum:\n"
103              "    found:    {}\n"
104              "    expected: {}".format(found, expected))
105    return verified
106
107
108def unpack(tarball, tarball_suffix, dst, verbose=False, match=None):
109    """Unpack the given tarball file"""
110    print("extracting", tarball)
111    fname = os.path.basename(tarball).replace(tarball_suffix, "")
112    with contextlib.closing(tarfile.open(tarball)) as tar:
113        for member in tar.getnames():
114            if "/" not in member:
115                continue
116            name = member.replace(fname + "/", "", 1)
117            if match is not None and not name.startswith(match):
118                continue
119            name = name[len(match) + 1:]
120
121            dst_path = os.path.join(dst, name)
122            if verbose:
123                print("  extracting", member)
124            tar.extract(member, dst)
125            src_path = os.path.join(dst, member)
126            if os.path.isdir(src_path) and os.path.exists(dst_path):
127                continue
128            shutil.move(src_path, dst_path)
129    shutil.rmtree(os.path.join(dst, fname))
130
131
132def run(args, verbose=False, exception=False, is_bootstrap=False, **kwargs):
133    """Run a child program in a new process"""
134    if verbose:
135        print("running: " + ' '.join(args))
136    sys.stdout.flush()
137    # Use Popen here instead of call() as it apparently allows powershell on
138    # Windows to not lock up waiting for input presumably.
139    ret = subprocess.Popen(args, **kwargs)
140    code = ret.wait()
141    if code != 0:
142        err = "failed to run: " + ' '.join(args)
143        if verbose or exception:
144            raise RuntimeError(err)
145        # For most failures, we definitely do want to print this error, or the user will have no
146        # idea what went wrong. But when we've successfully built bootstrap and it failed, it will
147        # have already printed an error above, so there's no need to print the exact command we're
148        # running.
149        if is_bootstrap:
150            sys.exit(1)
151        else:
152            sys.exit(err)
153
154
155def require(cmd, exit=True):
156    '''Run a command, returning its output.
157    On error,
158        If `exit` is `True`, exit the process.
159        Otherwise, return None.'''
160    try:
161        return subprocess.check_output(cmd).strip()
162    except (subprocess.CalledProcessError, OSError) as exc:
163        if not exit:
164            return None
165        print("error: unable to run `{}`: {}".format(' '.join(cmd), exc))
166        print("Please make sure it's installed and in the path.")
167        sys.exit(1)
168
169
170def format_build_time(duration):
171    """Return a nicer format for build time
172
173    >>> format_build_time('300')
174    '0:05:00'
175    """
176    return str(datetime.timedelta(seconds=int(duration)))
177
178
179def default_build_triple(verbose):
180    """Build triple as in LLVM"""
181    # If the user already has a host build triple with an existing `rustc`
182    # install, use their preference. This fixes most issues with Windows builds
183    # being detected as GNU instead of MSVC.
184    default_encoding = sys.getdefaultencoding()
185    try:
186        version = subprocess.check_output(["rustc", "--version", "--verbose"],
187                stderr=subprocess.DEVNULL)
188        version = version.decode(default_encoding)
189        host = next(x for x in version.split('\n') if x.startswith("host: "))
190        triple = host.split("host: ")[1]
191        if verbose:
192            print("detected default triple {}".format(triple))
193        return triple
194    except Exception as e:
195        if verbose:
196            print("rustup not detected: {}".format(e))
197            print("falling back to auto-detect")
198
199    required = sys.platform != 'win32'
200    ostype = require(["uname", "-s"], exit=required)
201    cputype = require(['uname', '-m'], exit=required)
202
203    # If we do not have `uname`, assume Windows.
204    if ostype is None or cputype is None:
205        return 'x86_64-pc-windows-msvc'
206
207    ostype = ostype.decode(default_encoding)
208    cputype = cputype.decode(default_encoding)
209
210    # The goal here is to come up with the same triple as LLVM would,
211    # at least for the subset of platforms we're willing to target.
212    ostype_mapper = {
213        'Darwin': 'apple-darwin',
214        'DragonFly': 'unknown-dragonfly',
215        'FreeBSD': 'unknown-freebsd',
216        'Haiku': 'unknown-haiku',
217        'NetBSD': 'unknown-netbsd',
218        'OpenBSD': 'unknown-openbsd'
219    }
220
221    # Consider the direct transformation first and then the special cases
222    if ostype in ostype_mapper:
223        ostype = ostype_mapper[ostype]
224    elif ostype == 'Linux':
225        os_from_sp = subprocess.check_output(
226            ['uname', '-o']).strip().decode(default_encoding)
227        if os_from_sp == 'Android':
228            ostype = 'linux-android'
229        else:
230            ostype = 'unknown-linux-gnu'
231    elif ostype == 'SunOS':
232        ostype = 'pc-solaris'
233        # On Solaris, uname -m will return a machine classification instead
234        # of a cpu type, so uname -p is recommended instead.  However, the
235        # output from that option is too generic for our purposes (it will
236        # always emit 'i386' on x86/amd64 systems).  As such, isainfo -k
237        # must be used instead.
238        cputype = require(['isainfo', '-k']).decode(default_encoding)
239        # sparc cpus have sun as a target vendor
240        if 'sparc' in cputype:
241            ostype = 'sun-solaris'
242    elif ostype.startswith('MINGW'):
243        # msys' `uname` does not print gcc configuration, but prints msys
244        # configuration. so we cannot believe `uname -m`:
245        # msys1 is always i686 and msys2 is always x86_64.
246        # instead, msys defines $MSYSTEM which is MINGW32 on i686 and
247        # MINGW64 on x86_64.
248        ostype = 'pc-windows-gnu'
249        cputype = 'i686'
250        if os.environ.get('MSYSTEM') == 'MINGW64':
251            cputype = 'x86_64'
252    elif ostype.startswith('MSYS'):
253        ostype = 'pc-windows-gnu'
254    elif ostype.startswith('CYGWIN_NT'):
255        cputype = 'i686'
256        if ostype.endswith('WOW64'):
257            cputype = 'x86_64'
258        ostype = 'pc-windows-gnu'
259    elif sys.platform == 'win32':
260        # Some Windows platforms might have a `uname` command that returns a
261        # non-standard string (e.g. gnuwin32 tools returns `windows32`). In
262        # these cases, fall back to using sys.platform.
263        return 'x86_64-pc-windows-msvc'
264    else:
265        err = "unknown OS type: {}".format(ostype)
266        sys.exit(err)
267
268    if cputype == 'powerpc' and ostype == 'unknown-freebsd':
269        cputype = subprocess.check_output(
270              ['uname', '-p']).strip().decode(default_encoding)
271    cputype_mapper = {
272        'BePC': 'i686',
273        'aarch64': 'aarch64',
274        'amd64': 'x86_64',
275        'arm64': 'aarch64',
276        'i386': 'i686',
277        'i486': 'i686',
278        'i686': 'i686',
279        'i786': 'i686',
280        'm68k': 'm68k',
281        'powerpc': 'powerpc',
282        'powerpc64': 'powerpc64',
283        'powerpc64le': 'powerpc64le',
284        'ppc': 'powerpc',
285        'ppc64': 'powerpc64',
286        'ppc64le': 'powerpc64le',
287        'riscv64': 'riscv64gc',
288        's390x': 's390x',
289        'x64': 'x86_64',
290        'x86': 'i686',
291        'x86-64': 'x86_64',
292        'x86_64': 'x86_64'
293    }
294
295    # Consider the direct transformation first and then the special cases
296    if cputype in cputype_mapper:
297        cputype = cputype_mapper[cputype]
298    elif cputype in {'xscale', 'arm'}:
299        cputype = 'arm'
300        if ostype == 'linux-android':
301            ostype = 'linux-androideabi'
302        elif ostype == 'unknown-freebsd':
303            cputype = subprocess.check_output(
304                ['uname', '-p']).strip().decode(default_encoding)
305            ostype = 'unknown-freebsd'
306    elif cputype == 'armv6l':
307        cputype = 'arm'
308        if ostype == 'linux-android':
309            ostype = 'linux-androideabi'
310        else:
311            ostype += 'eabihf'
312    elif cputype in {'armv7l', 'armv8l'}:
313        cputype = 'armv7'
314        if ostype == 'linux-android':
315            ostype = 'linux-androideabi'
316        else:
317            ostype += 'eabihf'
318    elif cputype == 'mips':
319        if sys.byteorder == 'big':
320            cputype = 'mips'
321        elif sys.byteorder == 'little':
322            cputype = 'mipsel'
323        else:
324            raise ValueError("unknown byteorder: {}".format(sys.byteorder))
325    elif cputype == 'mips64':
326        if sys.byteorder == 'big':
327            cputype = 'mips64'
328        elif sys.byteorder == 'little':
329            cputype = 'mips64el'
330        else:
331            raise ValueError('unknown byteorder: {}'.format(sys.byteorder))
332        # only the n64 ABI is supported, indicate it
333        ostype += 'abi64'
334    elif cputype == 'sparc' or cputype == 'sparcv9' or cputype == 'sparc64':
335        pass
336    else:
337        err = "unknown cpu type: {}".format(cputype)
338        sys.exit(err)
339
340    return "{}-{}".format(cputype, ostype)
341
342
343@contextlib.contextmanager
344def output(filepath):
345    tmp = filepath + '.tmp'
346    with open(tmp, 'w') as f:
347        yield f
348    try:
349        if os.path.exists(filepath):
350            os.remove(filepath)  # PermissionError/OSError on Win32 if in use
351    except OSError:
352        shutil.copy2(tmp, filepath)
353        os.remove(tmp)
354        return
355    os.rename(tmp, filepath)
356
357
358class Stage0Toolchain:
359    def __init__(self, stage0_payload):
360        self.date = stage0_payload["date"]
361        self.version = stage0_payload["version"]
362
363    def channel(self):
364        return self.version + "-" + self.date
365
366
367class RustBuild(object):
368    """Provide all the methods required to build Rust"""
369    def __init__(self):
370        self.checksums_sha256 = {}
371        self.stage0_compiler = None
372        self.stage0_rustfmt = None
373        self._download_url = ''
374        self.build = ''
375        self.build_dir = ''
376        self.clean = False
377        self.config_toml = ''
378        self.rust_root = ''
379        self.use_locked_deps = ''
380        self.use_vendored_sources = ''
381        self.verbose = False
382        self.git_version = None
383        self.nix_deps_dir = None
384        self.rustc_commit = None
385
386    def download_toolchain(self, stage0=True, rustc_channel=None):
387        """Fetch the build system for Rust, written in Rust
388
389        This method will build a cache directory, then it will fetch the
390        tarball which has the stage0 compiler used to then bootstrap the Rust
391        compiler itself.
392
393        Each downloaded tarball is extracted, after that, the script
394        will move all the content to the right place.
395        """
396        if rustc_channel is None:
397            rustc_channel = self.stage0_compiler.version
398        bin_root = self.bin_root(stage0)
399
400        key = self.stage0_compiler.date
401        if not stage0:
402            key += str(self.rustc_commit)
403        if self.rustc(stage0).startswith(bin_root) and \
404                (not os.path.exists(self.rustc(stage0)) or
405                 self.program_out_of_date(self.rustc_stamp(stage0), key)):
406            if os.path.exists(bin_root):
407                shutil.rmtree(bin_root)
408            tarball_suffix = '.tar.xz' if support_xz() else '.tar.gz'
409            filename = "rust-std-{}-{}{}".format(
410                rustc_channel, self.build, tarball_suffix)
411            pattern = "rust-std-{}".format(self.build)
412            self._download_component_helper(filename, pattern, tarball_suffix, stage0)
413            filename = "rustc-{}-{}{}".format(rustc_channel, self.build,
414                                              tarball_suffix)
415            self._download_component_helper(filename, "rustc", tarball_suffix, stage0)
416            # download-rustc doesn't need its own cargo, it can just use beta's.
417            if stage0:
418                filename = "cargo-{}-{}{}".format(rustc_channel, self.build,
419                                                tarball_suffix)
420                self._download_component_helper(filename, "cargo", tarball_suffix)
421                self.fix_bin_or_dylib("{}/bin/cargo".format(bin_root))
422            else:
423                filename = "rustc-dev-{}-{}{}".format(rustc_channel, self.build, tarball_suffix)
424                self._download_component_helper(
425                    filename, "rustc-dev", tarball_suffix, stage0
426                )
427
428            self.fix_bin_or_dylib("{}/bin/rustc".format(bin_root))
429            self.fix_bin_or_dylib("{}/bin/rustdoc".format(bin_root))
430            lib_dir = "{}/lib".format(bin_root)
431            for lib in os.listdir(lib_dir):
432                if lib.endswith(".so"):
433                    self.fix_bin_or_dylib(os.path.join(lib_dir, lib))
434            with output(self.rustc_stamp(stage0)) as rust_stamp:
435                rust_stamp.write(key)
436
437        if self.rustfmt() and self.rustfmt().startswith(bin_root) and (
438            not os.path.exists(self.rustfmt())
439            or self.program_out_of_date(
440                self.rustfmt_stamp(),
441                "" if self.stage0_rustfmt is None else self.stage0_rustfmt.channel()
442            )
443        ):
444            if self.stage0_rustfmt is not None:
445                tarball_suffix = '.tar.xz' if support_xz() else '.tar.gz'
446                filename = "rustfmt-{}-{}{}".format(
447                    self.stage0_rustfmt.version, self.build, tarball_suffix,
448                )
449                self._download_component_helper(
450                    filename, "rustfmt-preview", tarball_suffix, key=self.stage0_rustfmt.date
451                )
452                self.fix_bin_or_dylib("{}/bin/rustfmt".format(bin_root))
453                self.fix_bin_or_dylib("{}/bin/cargo-fmt".format(bin_root))
454                with output(self.rustfmt_stamp()) as rustfmt_stamp:
455                    rustfmt_stamp.write(self.stage0_rustfmt.channel())
456
457        # Avoid downloading LLVM twice (once for stage0 and once for the master rustc)
458        if self.downloading_llvm() and stage0:
459            # We want the most recent LLVM submodule update to avoid downloading
460            # LLVM more often than necessary.
461            #
462            # This git command finds that commit SHA, looking for bors-authored
463            # commits that modified src/llvm-project or other relevant version
464            # stamp files.
465            #
466            # This works even in a repository that has not yet initialized
467            # submodules.
468            top_level = subprocess.check_output([
469                "git", "rev-parse", "--show-toplevel",
470            ]).decode(sys.getdefaultencoding()).strip()
471            llvm_sha = subprocess.check_output([
472                "git", "rev-list", "--author=bors@rust-lang.org", "-n1",
473                "--first-parent", "HEAD",
474                "--",
475                "{}/src/llvm-project".format(top_level),
476                "{}/src/bootstrap/download-ci-llvm-stamp".format(top_level),
477                # the LLVM shared object file is named `LLVM-12-rust-{version}-nightly`
478                "{}/src/version".format(top_level)
479            ]).decode(sys.getdefaultencoding()).strip()
480            llvm_assertions = self.get_toml('assertions', 'llvm') == 'true'
481            llvm_root = self.llvm_root()
482            llvm_lib = os.path.join(llvm_root, "lib")
483            if self.program_out_of_date(self.llvm_stamp(), llvm_sha + str(llvm_assertions)):
484                self._download_ci_llvm(llvm_sha, llvm_assertions)
485                for binary in ["llvm-config", "FileCheck"]:
486                    self.fix_bin_or_dylib(os.path.join(llvm_root, "bin", binary))
487                for lib in os.listdir(llvm_lib):
488                    if lib.endswith(".so"):
489                        self.fix_bin_or_dylib(os.path.join(llvm_lib, lib))
490                with output(self.llvm_stamp()) as llvm_stamp:
491                    llvm_stamp.write(llvm_sha + str(llvm_assertions))
492
493    def downloading_llvm(self):
494        opt = self.get_toml('download-ci-llvm', 'llvm')
495        # This is currently all tier 1 targets and tier 2 targets with host tools
496        # (since others may not have CI artifacts)
497        # https://doc.rust-lang.org/rustc/platform-support.html#tier-1
498        supported_platforms = [
499            # tier 1
500            "aarch64-unknown-linux-gnu",
501            "i686-pc-windows-gnu",
502            "i686-pc-windows-msvc",
503            "i686-unknown-linux-gnu",
504            "x86_64-unknown-linux-gnu",
505            "x86_64-apple-darwin",
506            "x86_64-pc-windows-gnu",
507            "x86_64-pc-windows-msvc",
508            # tier 2 with host tools
509            "aarch64-apple-darwin",
510            "aarch64-pc-windows-msvc",
511            "aarch64-unknown-linux-musl",
512            "arm-unknown-linux-gnueabi",
513            "arm-unknown-linux-gnueabihf",
514            "armv7-unknown-linux-gnueabihf",
515            "mips-unknown-linux-gnu",
516            "mips64-unknown-linux-gnuabi64",
517            "mips64el-unknown-linux-gnuabi64",
518            "mipsel-unknown-linux-gnu",
519            "powerpc-unknown-linux-gnu",
520            "powerpc64-unknown-linux-gnu",
521            "powerpc64le-unknown-linux-gnu",
522            "riscv64gc-unknown-linux-gnu",
523            "s390x-unknown-linux-gnu",
524            "x86_64-unknown-freebsd",
525            "x86_64-unknown-illumos",
526            "x86_64-unknown-linux-musl",
527            "x86_64-unknown-netbsd",
528        ]
529        return opt == "true" \
530            or (opt == "if-available" and self.build in supported_platforms)
531
532    def _download_component_helper(
533        self, filename, pattern, tarball_suffix, stage0=True, key=None
534    ):
535        if key is None:
536            if stage0:
537                key = self.stage0_compiler.date
538            else:
539                key = self.rustc_commit
540        cache_dst = os.path.join(self.build_dir, "cache")
541        rustc_cache = os.path.join(cache_dst, key)
542        if not os.path.exists(rustc_cache):
543            os.makedirs(rustc_cache)
544
545        if stage0:
546            base = self._download_url
547            url = "dist/{}".format(key)
548        else:
549            base = "https://ci-artifacts.rust-lang.org"
550            url = "rustc-builds/{}".format(self.rustc_commit)
551        tarball = os.path.join(rustc_cache, filename)
552        if not os.path.exists(tarball):
553            get(
554                base,
555                "{}/{}".format(url, filename),
556                tarball,
557                self.checksums_sha256,
558                verbose=self.verbose,
559                do_verify=stage0,
560            )
561        unpack(tarball, tarball_suffix, self.bin_root(stage0), match=pattern, verbose=self.verbose)
562
563    def _download_ci_llvm(self, llvm_sha, llvm_assertions):
564        if not llvm_sha:
565            print("error: could not find commit hash for downloading LLVM")
566            print("help: maybe your repository history is too shallow?")
567            print("help: consider disabling `download-ci-llvm`")
568            print("help: or fetch enough history to include one upstream commit")
569            exit(1)
570        cache_prefix = "llvm-{}-{}".format(llvm_sha, llvm_assertions)
571        cache_dst = os.path.join(self.build_dir, "cache")
572        rustc_cache = os.path.join(cache_dst, cache_prefix)
573        if not os.path.exists(rustc_cache):
574            os.makedirs(rustc_cache)
575
576        base = "https://ci-artifacts.rust-lang.org"
577        url = "rustc-builds/{}".format(llvm_sha)
578        if llvm_assertions:
579            url = url.replace('rustc-builds', 'rustc-builds-alt')
580        # ci-artifacts are only stored as .xz, not .gz
581        if not support_xz():
582            print("error: XZ support is required to download LLVM")
583            print("help: consider disabling `download-ci-llvm` or using python3")
584            exit(1)
585        tarball_suffix = '.tar.xz'
586        filename = "rust-dev-nightly-" + self.build + tarball_suffix
587        tarball = os.path.join(rustc_cache, filename)
588        if not os.path.exists(tarball):
589            get(
590                base,
591                "{}/{}".format(url, filename),
592                tarball,
593                self.checksums_sha256,
594                verbose=self.verbose,
595                do_verify=False,
596            )
597        unpack(tarball, tarball_suffix, self.llvm_root(),
598                match="rust-dev",
599                verbose=self.verbose)
600
601    def fix_bin_or_dylib(self, fname):
602        """Modifies the interpreter section of 'fname' to fix the dynamic linker,
603        or the RPATH section, to fix the dynamic library search path
604
605        This method is only required on NixOS and uses the PatchELF utility to
606        change the interpreter/RPATH of ELF executables.
607
608        Please see https://nixos.org/patchelf.html for more information
609        """
610        default_encoding = sys.getdefaultencoding()
611        try:
612            ostype = subprocess.check_output(
613                ['uname', '-s']).strip().decode(default_encoding)
614        except subprocess.CalledProcessError:
615            return
616        except OSError as reason:
617            if getattr(reason, 'winerror', None) is not None:
618                return
619            raise reason
620
621        if ostype != "Linux":
622            return
623
624        # If the user has asked binaries to be patched for Nix, then
625        # don't check for NixOS or `/lib`, just continue to the patching.
626        if self.get_toml('patch-binaries-for-nix', 'build') != 'true':
627            # Use `/etc/os-release` instead of `/etc/NIXOS`.
628            # The latter one does not exist on NixOS when using tmpfs as root.
629            try:
630                with open("/etc/os-release", "r") as f:
631                    if not any(line.strip() == "ID=nixos" for line in f):
632                        return
633            except FileNotFoundError:
634                return
635            if os.path.exists("/lib"):
636                return
637
638        # At this point we're pretty sure the user is running NixOS or
639        # using Nix
640        nix_os_msg = "info: you seem to be using Nix. Attempting to patch"
641        print(nix_os_msg, fname)
642
643        # Only build `.nix-deps` once.
644        nix_deps_dir = self.nix_deps_dir
645        if not nix_deps_dir:
646            # Run `nix-build` to "build" each dependency (which will likely reuse
647            # the existing `/nix/store` copy, or at most download a pre-built copy).
648            #
649            # Importantly, we create a gc-root called `.nix-deps` in the `build/`
650            # directory, but still reference the actual `/nix/store` path in the rpath
651            # as it makes it significantly more robust against changes to the location of
652            # the `.nix-deps` location.
653            #
654            # bintools: Needed for the path of `ld-linux.so` (via `nix-support/dynamic-linker`).
655            # zlib: Needed as a system dependency of `libLLVM-*.so`.
656            # patchelf: Needed for patching ELF binaries (see doc comment above).
657            nix_deps_dir = "{}/{}".format(self.build_dir, ".nix-deps")
658            nix_expr = '''
659            with (import <nixpkgs> {});
660            symlinkJoin {
661              name = "rust-stage0-dependencies";
662              paths = [
663                zlib
664                patchelf
665                stdenv.cc.bintools
666              ];
667            }
668            '''
669            try:
670                subprocess.check_output([
671                    "nix-build", "-E", nix_expr, "-o", nix_deps_dir,
672                ])
673            except subprocess.CalledProcessError as reason:
674                print("warning: failed to call nix-build:", reason)
675                return
676            self.nix_deps_dir = nix_deps_dir
677
678        patchelf = "{}/bin/patchelf".format(nix_deps_dir)
679        rpath_entries = [
680            # Relative default, all binary and dynamic libraries we ship
681            # appear to have this (even when `../lib` is redundant).
682            "$ORIGIN/../lib",
683            os.path.join(os.path.realpath(nix_deps_dir), "lib")
684        ]
685        patchelf_args = ["--set-rpath", ":".join(rpath_entries)]
686        if not fname.endswith(".so"):
687            # Finally, set the corret .interp for binaries
688            with open("{}/nix-support/dynamic-linker".format(nix_deps_dir)) as dynamic_linker:
689                patchelf_args += ["--set-interpreter", dynamic_linker.read().rstrip()]
690
691        try:
692            subprocess.check_output([patchelf] + patchelf_args + [fname])
693        except subprocess.CalledProcessError as reason:
694            print("warning: failed to call patchelf:", reason)
695            return
696
697    # If `download-rustc` is set, download the most recent commit with CI artifacts
698    def maybe_download_ci_toolchain(self):
699        # If `download-rustc` is not set, default to rebuilding.
700        download_rustc = self.get_toml("download-rustc", section="rust")
701        if download_rustc is None or download_rustc == "false":
702            return None
703        assert download_rustc == "true" or download_rustc == "if-unchanged", download_rustc
704
705        # Handle running from a directory other than the top level
706        rev_parse = ["git", "rev-parse", "--show-toplevel"]
707        top_level = subprocess.check_output(rev_parse, universal_newlines=True).strip()
708        compiler = "{}/compiler/".format(top_level)
709        library = "{}/library/".format(top_level)
710
711        # Look for a version to compare to based on the current commit.
712        # Only commits merged by bors will have CI artifacts.
713        merge_base = [
714            "git", "rev-list", "--author=bors@rust-lang.org", "-n1",
715            "--first-parent", "HEAD"
716        ]
717        commit = subprocess.check_output(merge_base, universal_newlines=True).strip()
718        if not commit:
719            print("error: could not find commit hash for downloading rustc")
720            print("help: maybe your repository history is too shallow?")
721            print("help: consider disabling `download-rustc`")
722            print("help: or fetch enough history to include one upstream commit")
723            exit(1)
724
725        # Warn if there were changes to the compiler or standard library since the ancestor commit.
726        status = subprocess.call(["git", "diff-index", "--quiet", commit, "--", compiler, library])
727        if status != 0:
728            if download_rustc == "if-unchanged":
729                return None
730            print("warning: `download-rustc` is enabled, but there are changes to \
731                   compiler/ or library/")
732
733        if self.verbose:
734            print("using downloaded stage1 artifacts from CI (commit {})".format(commit))
735        self.rustc_commit = commit
736        # FIXME: support downloading artifacts from the beta channel
737        self.download_toolchain(False, "nightly")
738
739    def rustc_stamp(self, stage0):
740        """Return the path for .rustc-stamp at the given stage
741
742        >>> rb = RustBuild()
743        >>> rb.build_dir = "build"
744        >>> rb.rustc_stamp(True) == os.path.join("build", "stage0", ".rustc-stamp")
745        True
746        >>> rb.rustc_stamp(False) == os.path.join("build", "ci-rustc", ".rustc-stamp")
747        True
748        """
749        return os.path.join(self.bin_root(stage0), '.rustc-stamp')
750
751    def rustfmt_stamp(self):
752        """Return the path for .rustfmt-stamp
753
754        >>> rb = RustBuild()
755        >>> rb.build_dir = "build"
756        >>> rb.rustfmt_stamp() == os.path.join("build", "stage0", ".rustfmt-stamp")
757        True
758        """
759        return os.path.join(self.bin_root(True), '.rustfmt-stamp')
760
761    def llvm_stamp(self):
762        """Return the path for .rustfmt-stamp
763
764        >>> rb = RustBuild()
765        >>> rb.build_dir = "build"
766        >>> rb.llvm_stamp() == os.path.join("build", "ci-llvm", ".llvm-stamp")
767        True
768        """
769        return os.path.join(self.llvm_root(), '.llvm-stamp')
770
771
772    def program_out_of_date(self, stamp_path, key):
773        """Check if the given program stamp is out of date"""
774        if not os.path.exists(stamp_path) or self.clean:
775            return True
776        with open(stamp_path, 'r') as stamp:
777            return key != stamp.read()
778
779    def bin_root(self, stage0):
780        """Return the binary root directory for the given stage
781
782        >>> rb = RustBuild()
783        >>> rb.build_dir = "build"
784        >>> rb.bin_root(True) == os.path.join("build", "stage0")
785        True
786        >>> rb.bin_root(False) == os.path.join("build", "ci-rustc")
787        True
788
789        When the 'build' property is given should be a nested directory:
790
791        >>> rb.build = "devel"
792        >>> rb.bin_root(True) == os.path.join("build", "devel", "stage0")
793        True
794        """
795        if stage0:
796            subdir = "stage0"
797        else:
798            subdir = "ci-rustc"
799        return os.path.join(self.build_dir, self.build, subdir)
800
801    def llvm_root(self):
802        """Return the CI LLVM root directory
803
804        >>> rb = RustBuild()
805        >>> rb.build_dir = "build"
806        >>> rb.llvm_root() == os.path.join("build", "ci-llvm")
807        True
808
809        When the 'build' property is given should be a nested directory:
810
811        >>> rb.build = "devel"
812        >>> rb.llvm_root() == os.path.join("build", "devel", "ci-llvm")
813        True
814        """
815        return os.path.join(self.build_dir, self.build, "ci-llvm")
816
817    def get_toml(self, key, section=None):
818        """Returns the value of the given key in config.toml, otherwise returns None
819
820        >>> rb = RustBuild()
821        >>> rb.config_toml = 'key1 = "value1"\\nkey2 = "value2"'
822        >>> rb.get_toml("key2")
823        'value2'
824
825        If the key does not exists, the result is None:
826
827        >>> rb.get_toml("key3") is None
828        True
829
830        Optionally also matches the section the key appears in
831
832        >>> rb.config_toml = '[a]\\nkey = "value1"\\n[b]\\nkey = "value2"'
833        >>> rb.get_toml('key', 'a')
834        'value1'
835        >>> rb.get_toml('key', 'b')
836        'value2'
837        >>> rb.get_toml('key', 'c') is None
838        True
839
840        >>> rb.config_toml = 'key1 = true'
841        >>> rb.get_toml("key1")
842        'true'
843        """
844
845        cur_section = None
846        for line in self.config_toml.splitlines():
847            section_match = re.match(r'^\s*\[(.*)\]\s*$', line)
848            if section_match is not None:
849                cur_section = section_match.group(1)
850
851            match = re.match(r'^{}\s*=(.*)$'.format(key), line)
852            if match is not None:
853                value = match.group(1)
854                if section is None or section == cur_section:
855                    return self.get_string(value) or value.strip()
856        return None
857
858    def cargo(self):
859        """Return config path for cargo"""
860        return self.program_config('cargo')
861
862    def rustc(self, stage0):
863        """Return config path for rustc"""
864        return self.program_config('rustc', stage0)
865
866    def rustfmt(self):
867        """Return config path for rustfmt"""
868        if self.stage0_rustfmt is None:
869            return None
870        return self.program_config('rustfmt')
871
872    def program_config(self, program, stage0=True):
873        """Return config path for the given program at the given stage
874
875        >>> rb = RustBuild()
876        >>> rb.config_toml = 'rustc = "rustc"\\n'
877        >>> rb.program_config('rustc')
878        'rustc'
879        >>> rb.config_toml = ''
880        >>> cargo_path = rb.program_config('cargo', True)
881        >>> cargo_path.rstrip(".exe") == os.path.join(rb.bin_root(True),
882        ... "bin", "cargo")
883        True
884        >>> cargo_path = rb.program_config('cargo', False)
885        >>> cargo_path.rstrip(".exe") == os.path.join(rb.bin_root(False),
886        ... "bin", "cargo")
887        True
888        """
889        config = self.get_toml(program)
890        if config:
891            return os.path.expanduser(config)
892        return os.path.join(self.bin_root(stage0), "bin", "{}{}".format(
893            program, self.exe_suffix()))
894
895    @staticmethod
896    def get_string(line):
897        """Return the value between double quotes
898
899        >>> RustBuild.get_string('    "devel"   ')
900        'devel'
901        >>> RustBuild.get_string("    'devel'   ")
902        'devel'
903        >>> RustBuild.get_string('devel') is None
904        True
905        >>> RustBuild.get_string('    "devel   ')
906        ''
907        """
908        start = line.find('"')
909        if start != -1:
910            end = start + 1 + line[start + 1:].find('"')
911            return line[start + 1:end]
912        start = line.find('\'')
913        if start != -1:
914            end = start + 1 + line[start + 1:].find('\'')
915            return line[start + 1:end]
916        return None
917
918    @staticmethod
919    def exe_suffix():
920        """Return a suffix for executables"""
921        if sys.platform == 'win32':
922            return '.exe'
923        return ''
924
925    def bootstrap_binary(self):
926        """Return the path of the bootstrap binary
927
928        >>> rb = RustBuild()
929        >>> rb.build_dir = "build"
930        >>> rb.bootstrap_binary() == os.path.join("build", "bootstrap",
931        ... "debug", "bootstrap")
932        True
933        """
934        return os.path.join(self.build_dir, "bootstrap", "debug", "bootstrap")
935
936    def build_bootstrap(self):
937        """Build bootstrap"""
938        build_dir = os.path.join(self.build_dir, "bootstrap")
939        if self.clean and os.path.exists(build_dir):
940            shutil.rmtree(build_dir)
941        env = os.environ.copy()
942        # `CARGO_BUILD_TARGET` breaks bootstrap build.
943        # See also: <https://github.com/rust-lang/rust/issues/70208>.
944        if "CARGO_BUILD_TARGET" in env:
945            del env["CARGO_BUILD_TARGET"]
946        env["CARGO_TARGET_DIR"] = build_dir
947        env["RUSTC"] = self.rustc(True)
948        env["LD_LIBRARY_PATH"] = os.path.join(self.bin_root(True), "lib") + \
949            (os.pathsep + env["LD_LIBRARY_PATH"]) \
950            if "LD_LIBRARY_PATH" in env else ""
951        env["DYLD_LIBRARY_PATH"] = os.path.join(self.bin_root(True), "lib") + \
952            (os.pathsep + env["DYLD_LIBRARY_PATH"]) \
953            if "DYLD_LIBRARY_PATH" in env else ""
954        env["LIBRARY_PATH"] = os.path.join(self.bin_root(True), "lib") + \
955            (os.pathsep + env["LIBRARY_PATH"]) \
956            if "LIBRARY_PATH" in env else ""
957
958        # preserve existing RUSTFLAGS
959        env.setdefault("RUSTFLAGS", "")
960        build_section = "target.{}".format(self.build)
961        target_features = []
962        if self.get_toml("crt-static", build_section) == "true":
963            target_features += ["+crt-static"]
964        elif self.get_toml("crt-static", build_section) == "false":
965            target_features += ["-crt-static"]
966        if target_features:
967            env["RUSTFLAGS"] += " -C target-feature=" + (",".join(target_features))
968        target_linker = self.get_toml("linker", build_section)
969        if target_linker is not None:
970            env["RUSTFLAGS"] += " -C linker=" + target_linker
971        env["RUSTFLAGS"] += " -Wrust_2018_idioms -Wunused_lifetimes"
972        env["RUSTFLAGS"] += " -Wsemicolon_in_expressions_from_macros"
973        if self.get_toml("deny-warnings", "rust") != "false":
974            env["RUSTFLAGS"] += " -Dwarnings"
975
976        env["PATH"] = os.path.join(self.bin_root(True), "bin") + \
977            os.pathsep + env["PATH"]
978        if not os.path.isfile(self.cargo()):
979            raise Exception("no cargo executable found at `{}`".format(
980                self.cargo()))
981        args = [self.cargo(), "build", "--manifest-path",
982                os.path.join(self.rust_root, "src/bootstrap/Cargo.toml")]
983        for _ in range(0, self.verbose):
984            args.append("--verbose")
985        if self.use_locked_deps:
986            args.append("--locked")
987        if self.use_vendored_sources:
988            args.append("--frozen")
989        run(args, env=env, verbose=self.verbose)
990
991    def build_triple(self):
992        """Build triple as in LLVM
993
994        Note that `default_build_triple` is moderately expensive,
995        so use `self.build` where possible.
996        """
997        config = self.get_toml('build')
998        if config:
999            return config
1000        return default_build_triple(self.verbose)
1001
1002    def check_submodule(self, module, slow_submodules):
1003        if not slow_submodules:
1004            checked_out = subprocess.Popen(["git", "rev-parse", "HEAD"],
1005                                           cwd=os.path.join(self.rust_root, module),
1006                                           stdout=subprocess.PIPE)
1007            return checked_out
1008        else:
1009            return None
1010
1011    def update_submodule(self, module, checked_out, recorded_submodules):
1012        module_path = os.path.join(self.rust_root, module)
1013
1014        if checked_out is not None:
1015            default_encoding = sys.getdefaultencoding()
1016            checked_out = checked_out.communicate()[0].decode(default_encoding).strip()
1017            if recorded_submodules[module] == checked_out:
1018                return
1019
1020        print("Updating submodule", module)
1021
1022        run(["git", "submodule", "-q", "sync", module],
1023            cwd=self.rust_root, verbose=self.verbose)
1024
1025        update_args = ["git", "submodule", "update", "--init", "--recursive", "--depth=1"]
1026        if self.git_version >= distutils.version.LooseVersion("2.11.0"):
1027            update_args.append("--progress")
1028        update_args.append(module)
1029        try:
1030            run(update_args, cwd=self.rust_root, verbose=self.verbose, exception=True)
1031        except RuntimeError:
1032            print("Failed updating submodule. This is probably due to uncommitted local changes.")
1033            print('Either stash the changes by running "git stash" within the submodule\'s')
1034            print('directory, reset them by running "git reset --hard", or commit them.')
1035            print("To reset all submodules' changes run", end=" ")
1036            print('"git submodule foreach --recursive git reset --hard".')
1037            raise SystemExit(1)
1038
1039        run(["git", "reset", "-q", "--hard"],
1040            cwd=module_path, verbose=self.verbose)
1041        run(["git", "clean", "-qdfx"],
1042            cwd=module_path, verbose=self.verbose)
1043
1044    def update_submodules(self):
1045        """Update submodules"""
1046        if (not os.path.exists(os.path.join(self.rust_root, ".git"))) or \
1047                self.get_toml('submodules') == "false":
1048            return
1049
1050        default_encoding = sys.getdefaultencoding()
1051
1052        # check the existence and version of 'git' command
1053        git_version_str = require(['git', '--version']).split()[2].decode(default_encoding)
1054        self.git_version = distutils.version.LooseVersion(git_version_str)
1055
1056        slow_submodules = self.get_toml('fast-submodules') == "false"
1057        start_time = time()
1058        if slow_submodules:
1059            print('Unconditionally updating submodules')
1060        else:
1061            print('Updating only changed submodules')
1062        default_encoding = sys.getdefaultencoding()
1063        # Only update submodules that are needed to build bootstrap.  These are needed because Cargo
1064        # currently requires everything in a workspace to be "locally present" when starting a
1065        # build, and will give a hard error if any Cargo.toml files are missing.
1066        # FIXME: Is there a way to avoid cloning these eagerly? Bootstrap itself doesn't need to
1067        #   share a workspace with any tools - maybe it could be excluded from the workspace?
1068        #   That will still require cloning the submodules the second you check the standard
1069        #   library, though...
1070        # FIXME: Is there a way to avoid hard-coding the submodules required?
1071        # WARNING: keep this in sync with the submodules hard-coded in bootstrap/lib.rs
1072        submodules = [
1073            "src/tools/rust-installer",
1074            "src/tools/cargo",
1075            "src/tools/rls",
1076            "src/tools/miri",
1077            "library/backtrace",
1078            "library/stdarch"
1079        ]
1080        filtered_submodules = []
1081        submodules_names = []
1082        for module in submodules:
1083            check = self.check_submodule(module, slow_submodules)
1084            filtered_submodules.append((module, check))
1085            submodules_names.append(module)
1086        recorded = subprocess.Popen(["git", "ls-tree", "HEAD"] + submodules_names,
1087                                    cwd=self.rust_root, stdout=subprocess.PIPE)
1088        recorded = recorded.communicate()[0].decode(default_encoding).strip().splitlines()
1089        # { filename: hash }
1090        recorded_submodules = {}
1091        for data in recorded:
1092            # [mode, kind, hash, filename]
1093            data = data.split()
1094            recorded_submodules[data[3]] = data[2]
1095        for module in filtered_submodules:
1096            self.update_submodule(module[0], module[1], recorded_submodules)
1097        print("Submodules updated in %.2f seconds" % (time() - start_time))
1098
1099    def set_dist_environment(self, url):
1100        """Set download URL for normal environment"""
1101        if 'RUSTUP_DIST_SERVER' in os.environ:
1102            self._download_url = os.environ['RUSTUP_DIST_SERVER']
1103        else:
1104            self._download_url = url
1105
1106    def check_vendored_status(self):
1107        """Check that vendoring is configured properly"""
1108        vendor_dir = os.path.join(self.rust_root, 'vendor')
1109        if 'SUDO_USER' in os.environ and not self.use_vendored_sources:
1110            if os.environ.get('USER') != os.environ['SUDO_USER']:
1111                self.use_vendored_sources = True
1112                print('info: looks like you are running this command under `sudo`')
1113                print('      and so in order to preserve your $HOME this will now')
1114                print('      use vendored sources by default.')
1115                if not os.path.exists(vendor_dir):
1116                    print('error: vendoring required, but vendor directory does not exist.')
1117                    print('       Run `cargo vendor` without sudo to initialize the '
1118                          'vendor directory.')
1119                    raise Exception("{} not found".format(vendor_dir))
1120
1121        if self.use_vendored_sources:
1122            config = ("[source.crates-io]\n"
1123                      "replace-with = 'vendored-sources'\n"
1124                      "registry = 'https://example.com'\n"
1125                      "\n"
1126                      "[source.vendored-sources]\n"
1127                      "directory = '{}/vendor'\n"
1128                      .format(self.rust_root))
1129            if not os.path.exists('.cargo'):
1130                os.makedirs('.cargo')
1131                with output('.cargo/config') as cargo_config:
1132                    cargo_config.write(config)
1133            else:
1134                print('info: using vendored source, but .cargo/config is already present.')
1135                print('      Reusing the current configuration file. But you may want to '
1136                      'configure vendoring like this:')
1137                print(config)
1138        else:
1139            if os.path.exists('.cargo'):
1140                shutil.rmtree('.cargo')
1141
1142    def ensure_vendored(self):
1143        """Ensure that the vendored sources are available if needed"""
1144        vendor_dir = os.path.join(self.rust_root, 'vendor')
1145        # Note that this does not handle updating the vendored dependencies if
1146        # the rust git repository is updated. Normal development usually does
1147        # not use vendoring, so hopefully this isn't too much of a problem.
1148        if self.use_vendored_sources and not os.path.exists(vendor_dir):
1149            run([
1150                self.cargo(),
1151                "vendor",
1152                "--sync=./src/tools/rust-analyzer/Cargo.toml",
1153                "--sync=./compiler/rustc_codegen_cranelift/Cargo.toml",
1154            ], verbose=self.verbose, cwd=self.rust_root)
1155
1156
1157def bootstrap(help_triggered):
1158    """Configure, fetch, build and run the initial bootstrap"""
1159
1160    # If the user is asking for help, let them know that the whole download-and-build
1161    # process has to happen before anything is printed out.
1162    if help_triggered:
1163        print("info: Downloading and building bootstrap before processing --help")
1164        print("      command. See src/bootstrap/README.md for help with common")
1165        print("      commands.")
1166
1167    parser = argparse.ArgumentParser(description='Build rust')
1168    parser.add_argument('--config')
1169    parser.add_argument('--build')
1170    parser.add_argument('--clean', action='store_true')
1171    parser.add_argument('-v', '--verbose', action='count', default=0)
1172
1173    args = [a for a in sys.argv if a != '-h' and a != '--help']
1174    args, _ = parser.parse_known_args(args)
1175
1176    # Configure initial bootstrap
1177    build = RustBuild()
1178    build.rust_root = os.path.abspath(os.path.join(__file__, '../../..'))
1179    build.verbose = args.verbose
1180    build.clean = args.clean
1181
1182    # Read from `RUST_BOOTSTRAP_CONFIG`, then `--config`, then fallback to `config.toml` (if it
1183    # exists).
1184    toml_path = os.getenv('RUST_BOOTSTRAP_CONFIG') or args.config
1185    if not toml_path and os.path.exists('config.toml'):
1186        toml_path = 'config.toml'
1187
1188    if toml_path:
1189        if not os.path.exists(toml_path):
1190            toml_path = os.path.join(build.rust_root, toml_path)
1191
1192        with open(toml_path) as config:
1193            build.config_toml = config.read()
1194
1195    profile = build.get_toml('profile')
1196    if profile is not None:
1197        include_file = 'config.{}.toml'.format(profile)
1198        include_dir = os.path.join(build.rust_root, 'src', 'bootstrap', 'defaults')
1199        include_path = os.path.join(include_dir, include_file)
1200        # HACK: This works because `build.get_toml()` returns the first match it finds for a
1201        # specific key, so appending our defaults at the end allows the user to override them
1202        with open(include_path) as included_toml:
1203            build.config_toml += os.linesep + included_toml.read()
1204
1205    config_verbose = build.get_toml('verbose', 'build')
1206    if config_verbose is not None:
1207        build.verbose = max(build.verbose, int(config_verbose))
1208
1209    build.use_vendored_sources = build.get_toml('vendor', 'build') == 'true'
1210
1211    build.use_locked_deps = build.get_toml('locked-deps', 'build') == 'true'
1212
1213    build.check_vendored_status()
1214
1215    build_dir = build.get_toml('build-dir', 'build') or 'build'
1216    build.build_dir = os.path.abspath(build_dir.replace("$ROOT", build.rust_root))
1217
1218    with open(os.path.join(build.rust_root, "src", "stage0.json")) as f:
1219        data = json.load(f)
1220    build.checksums_sha256 = data["checksums_sha256"]
1221    build.stage0_compiler = Stage0Toolchain(data["compiler"])
1222    if data.get("rustfmt") is not None:
1223        build.stage0_rustfmt = Stage0Toolchain(data["rustfmt"])
1224
1225    build.set_dist_environment(data["dist_server"])
1226
1227    build.build = args.build or build.build_triple()
1228    build.update_submodules()
1229
1230    # Fetch/build the bootstrap
1231    build.download_toolchain()
1232    # Download the master compiler if `download-rustc` is set
1233    build.maybe_download_ci_toolchain()
1234    sys.stdout.flush()
1235    build.ensure_vendored()
1236    build.build_bootstrap()
1237    sys.stdout.flush()
1238
1239    # Run the bootstrap
1240    args = [build.bootstrap_binary()]
1241    args.extend(sys.argv[1:])
1242    env = os.environ.copy()
1243    env["BOOTSTRAP_PARENT_ID"] = str(os.getpid())
1244    env["BOOTSTRAP_PYTHON"] = sys.executable
1245    env["BUILD_DIR"] = build.build_dir
1246    env["RUSTC_BOOTSTRAP"] = '1'
1247    if toml_path:
1248        env["BOOTSTRAP_CONFIG"] = toml_path
1249    if build.rustc_commit is not None:
1250        env["BOOTSTRAP_DOWNLOAD_RUSTC"] = '1'
1251    run(args, env=env, verbose=build.verbose, is_bootstrap=True)
1252
1253
1254def main():
1255    """Entry point for the bootstrap process"""
1256    start_time = time()
1257
1258    # x.py help <cmd> ...
1259    if len(sys.argv) > 1 and sys.argv[1] == 'help':
1260        sys.argv = [sys.argv[0], '-h'] + sys.argv[2:]
1261
1262    help_triggered = (
1263        '-h' in sys.argv) or ('--help' in sys.argv) or (len(sys.argv) == 1)
1264    try:
1265        bootstrap(help_triggered)
1266        if not help_triggered:
1267            print("Build completed successfully in {}".format(
1268                format_build_time(time() - start_time)))
1269    except (SystemExit, KeyboardInterrupt) as error:
1270        if hasattr(error, 'code') and isinstance(error.code, int):
1271            exit_code = error.code
1272        else:
1273            exit_code = 1
1274            print(error)
1275        if not help_triggered:
1276            print("Build completed unsuccessfully in {}".format(
1277                format_build_time(time() - start_time)))
1278        sys.exit(exit_code)
1279
1280
1281if __name__ == '__main__':
1282    main()
1283