src/bootstrap/bootstrap.py PYTHON 1,478 lines View on github.com → Search inside
1from __future__ import absolute_import, division, print_function2import argparse3import contextlib4import datetime5import hashlib6import os7import re8import shutil9import subprocess10import sys11import sysconfig12import tarfile13import tempfile1415from time import time16from multiprocessing import Pool, cpu_count1718try:19    import lzma20except ImportError:21    lzma = None222324def platform_is_win32():25    return sys.platform == "win32"262728if platform_is_win32():29    EXE_SUFFIX = ".exe"30else:31    EXE_SUFFIX = ""323334def get_cpus():35    if hasattr(os, "sched_getaffinity"):36        return len(os.sched_getaffinity(0))37    if hasattr(os, "cpu_count"):38        cpus = os.cpu_count()39        if cpus is not None:40            return cpus41    try:42        return cpu_count()43    except NotImplementedError:44        return 1454647def eprint(*args, **kwargs):48    kwargs["file"] = sys.stderr49    print(*args, **kwargs)505152def get(base, url, path, checksums, verbose=0):53    with tempfile.NamedTemporaryFile(delete=False) as temp_file:54        temp_path = temp_file.name5556    try:57        if url not in checksums:58            raise RuntimeError(59                (60                    "src/stage0 doesn't contain a checksum for {}. "61                    "Pre-built artifacts might not be available for this "62                    "target at this time, see https://doc.rust-lang.org/nightly"63                    "/rustc/platform-support.html for more information."64                ).format(url)65            )66        sha256 = checksums[url]67        if os.path.exists(path):68            if verify(path, sha256, False):69                if verbose > 0:70                    eprint("using already-download file", path)71                return72            else:73                if verbose > 0:74                    eprint(75                        "ignoring already-download file",76                        path,77                        "due to failed verification",78                    )79                os.unlink(path)80        download(temp_path, "{}/{}".format(base, url), True, verbose)81        if not verify(temp_path, sha256, verbose):82            raise RuntimeError("failed verification")83        if verbose > 0:84            eprint("moving {} to {}".format(temp_path, path))85        shutil.move(temp_path, path)86    finally:87        if os.path.isfile(temp_path):88            if verbose > 0:89                eprint("removing", temp_path)90            os.unlink(temp_path)919293def curl_version():94    m = re.match(bytes("^curl ([0-9]+)\\.([0-9]+)", "utf8"), require(["curl", "-V"]))95    if m is None:96        return (0, 0)97    return (int(m[1]), int(m[2]))9899100def download(path, url, probably_big, verbose):101    for _ in range(4):102        try:103            _download(path, url, probably_big, verbose, True)104            return105        except RuntimeError:106            eprint("\nspurious failure, trying again")107    _download(path, url, probably_big, verbose, False)108109110def _download(path, url, probably_big, verbose, exception):111    # Try to use curl (potentially available on win32112    #    https://devblogs.microsoft.com/commandline/tar-and-curl-come-to-windows/)113    # If an error occurs:114    #  - If we are on win32 fallback to powershell115    #  - Otherwise raise the error if appropriate116    if probably_big or verbose > 0:117        eprint("downloading {}".format(url))118119    try:120        if (probably_big or verbose > 0) and "GITHUB_ACTIONS" not in os.environ:121            option = "--progress-bar"122        else:123            option = "--silent"124        # If curl is not present on Win32, we should not sys.exit125        #   but raise `CalledProcessError` or `OSError` instead126        require(["curl", "--version"], exception=platform_is_win32())127        extra_flags = []128        if curl_version() > (7, 70):129            extra_flags = ["--retry-all-errors"]130        # options should be kept in sync with131        # src/bootstrap/src/core/download.rs132        # for consistency.133        # they are also more compreprensivly explained in that file.134        run(135            ["curl", option]136            + extra_flags137            + [138                # Follow redirect.139                "--location",140                # timeout if speed is < 10 bytes/sec for > 30 seconds141                "--speed-time",142                "30",143                "--speed-limit",144                "10",145                # timeout if cannot connect within 30 seconds146                "--connect-timeout",147                "30",148                "--output",149                path,150                "--continue-at",151                "-",152                "--retry",153                "3",154                "--show-error",155                "--remote-time",156                "--fail",157                url,158            ],159            verbose=verbose,160            exception=True,  # Will raise RuntimeError on failure161        )162    except (subprocess.CalledProcessError, OSError, RuntimeError):163        # see http://serverfault.com/questions/301128/how-to-download164        script = "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12;"165        if platform_is_win32():166            run_powershell(167                [168                    script,169                    "(New-Object System.Net.WebClient).DownloadFile('{}', '{}')".format(170                        url, path171                    ),172                ],173                verbose=verbose,174                exception=exception,175            )176        # Check if the RuntimeError raised by run(curl) should be silenced177        elif verbose or exception:178            raise179180181def verify(path, expected, verbose):182    """Check if the sha256 sum of the given path is valid"""183    if verbose > 0:184        eprint("verifying", path)185    with open(path, "rb") as source:186        found = hashlib.sha256(source.read()).hexdigest()187    verified = found == expected188    if not verified:189        eprint(190            "invalid checksum:\n" "    found:    {}\n" "    expected: {}".format(191                found, expected192            )193        )194    return verified195196197def unpack(tarball, tarball_suffix, dst, verbose=0, match=None):198    """Unpack the given tarball file"""199    eprint("extracting", tarball)200    fname = os.path.basename(tarball).replace(tarball_suffix, "")201    with contextlib.closing(tarfile.open(tarball)) as tar:202        for member in tar.getnames():203            if "/" not in member:204                continue205            name = member.replace(fname + "/", "", 1)206            if match is not None and not name.startswith(match):207                continue208            name = name[len(match) + 1 :]209210            dst_path = os.path.join(dst, name)211            if verbose > 0:212                eprint("  extracting", member)213            tar.extract(member, dst)214            src_path = os.path.join(dst, member)215            if os.path.isdir(src_path) and os.path.exists(dst_path):216                continue217            shutil.move(src_path, dst_path)218    shutil.rmtree(os.path.join(dst, fname))219220221def run(args, verbose=0, exception=False, is_bootstrap=False, **kwargs):222    """Run a child program in a new process"""223    if verbose > 0:224        eprint("running: " + " ".join(args))225    sys.stdout.flush()226    # Ensure that the .exe is used on Windows just in case a Linux ELF has been227    # compiled in the same directory.228    if os.name == "nt" and not args[0].endswith(".exe"):229        args[0] += ".exe"230    # Use Popen here instead of call() as it apparently allows powershell on231    # Windows to not lock up waiting for input presumably.232    ret = subprocess.Popen(args, **kwargs)233    code = ret.wait()234    if code != 0:235        err = "failed to run: " + " ".join(args)236        if verbose > 0 or exception:237            raise RuntimeError(err)238        # For most failures, we definitely do want to print this error, or the user will have no239        # idea what went wrong. But when we've successfully built bootstrap and it failed, it will240        # have already printed an error above, so there's no need to print the exact command we're241        # running.242        if is_bootstrap:243            sys.exit(1)244        else:245            sys.exit(err)246247248def run_powershell(script, *args, **kwargs):249    """Run a powershell script"""250    run(["PowerShell.exe", "/nologo", "-Command"] + script, *args, **kwargs)251252253def require(cmd, exit=True, exception=False):254    """Run a command, returning its output.255    On error,256        If `exception` is `True`, raise the error257        Otherwise If `exit` is `True`, exit the process258        Else return None."""259    try:260        return subprocess.check_output(cmd).strip()261    except (subprocess.CalledProcessError, OSError) as exc:262        if exception:263            raise264        elif exit:265            eprint("ERROR: unable to run `{}`: {}".format(" ".join(cmd), exc))266            eprint("Please make sure it's installed and in the path.")267            sys.exit(1)268        return None269270271def format_build_time(duration):272    """Return a nicer format for build time273274    >>> format_build_time('300')275    '0:05:00'276    """277    return str(datetime.timedelta(seconds=int(duration)))278279280def default_build_triple(verbose):281    """Build triple as in LLVM"""282    # If we're on Windows and have an existing `rustc` toolchain, use `rustc --version --verbose`283    # to find our host target triple. This fixes an issue with Windows builds being detected284    # as GNU instead of MSVC.285    # Otherwise, detect it via `uname`286    default_encoding = sys.getdefaultencoding()287288    if platform_is_win32():289        try:290            version = subprocess.check_output(291                ["rustc", "--version", "--verbose"], stderr=subprocess.DEVNULL292            )293            version = version.decode(default_encoding)294            host = next(x for x in version.split("\n") if x.startswith("host: "))295            triple = host.split("host: ")[1]296            if verbose > 0:297                eprint(298                    "detected default triple {} from pre-installed rustc".format(triple)299                )300            return triple301        except Exception as e:302            if verbose > 0:303                eprint("pre-installed rustc not detected: {}".format(e))304                eprint("falling back to auto-detect")305306    required = not platform_is_win32()307    uname = require(["uname", "-smp"], exit=required)308309    # If we do not have `uname`, assume Windows.310    if uname is None:311        return "x86_64-pc-windows-msvc"312313    kernel, cputype, processor = uname.decode(default_encoding).split(maxsplit=2)314315    # ON NetBSD, use `uname -p` to set the CPU type316    if kernel == "NetBSD":317        cputype = (318            subprocess.check_output(["uname", "-p"]).strip().decode(default_encoding)319        )320321    # The goal here is to come up with the same triple as LLVM would,322    # at least for the subset of platforms we're willing to target.323    kerneltype_mapper = {324        "Darwin": "apple-darwin",325        "DragonFly": "unknown-dragonfly",326        "FreeBSD": "unknown-freebsd",327        "Haiku": "unknown-haiku",328        "NetBSD": "unknown-netbsd",329        "OpenBSD": "unknown-openbsd",330        "GNU": "unknown-hurd",331    }332333    # Consider the direct transformation first and then the special cases334    if kernel in kerneltype_mapper:335        kernel = kerneltype_mapper[kernel]336    elif kernel == "Linux":337        # Apple doesn't support `-o` so this can't be used in the combined338        # uname invocation above339        ostype = require(["uname", "-o"], exit=required).decode(default_encoding)340        if ostype == "Android":341            kernel = "linux-android"342        else:343            python_soabi = sysconfig.get_config_var("SOABI")344            if python_soabi is not None and "musl" in python_soabi:345                kernel = "unknown-linux-musl"346            else:347                kernel = "unknown-linux-gnu"348    elif kernel == "SunOS":349        kernel = "pc-solaris"350        # On Solaris, uname -m will return a machine classification instead351        # of a cpu type, so uname -p is recommended instead.  However, the352        # output from that option is too generic for our purposes (it will353        # always emit 'i386' on x86/amd64 systems).  As such, isainfo -k354        # must be used instead.355        cputype = require(["isainfo", "-k"]).decode(default_encoding)356        # sparc cpus have sun as a target vendor357        if "sparc" in cputype:358            kernel = "sun-solaris"359    elif kernel.startswith("MINGW"):360        # msys' `uname` does not print gcc configuration, but prints msys361        # configuration. so we cannot believe `uname -m`:362        # msys1 is always i686 and msys2 is always x86_64.363        # instead, msys defines $MSYSTEM which is MINGW32 on i686 and364        # MINGW64 on x86_64.365        kernel = "pc-windows-gnu"366        cputype = "i686"367        if os.environ.get("MSYSTEM") == "MINGW64":368            cputype = "x86_64"369    elif kernel.startswith("MSYS"):370        kernel = "pc-windows-gnu"371    elif kernel.startswith("CYGWIN_NT"):372        cputype = "i686"373        if kernel.endswith("WOW64"):374            cputype = "x86_64"375        kernel = "pc-windows-gnu"376    elif platform_is_win32():377        # Some Windows platforms might have a `uname` command that returns a378        # non-standard string (e.g. gnuwin32 tools returns `windows32`). In379        # these cases, fall back to using sys.platform.380        return "x86_64-pc-windows-msvc"381    elif kernel == "AIX":382        # `uname -m` returns the machine ID rather than machine hardware on AIX,383        # so we are unable to use cputype to form triple. AIX 7.2 and384        # above supports 32-bit and 64-bit mode simultaneously and `uname -p`385        # returns `powerpc`, however we only supports `powerpc64-ibm-aix` in386        # rust on AIX. For above reasons, kerneltype_mapper and cputype_mapper387        # are not used to infer AIX's triple.388        return "powerpc64-ibm-aix"389    else:390        err = "unknown OS type: {}".format(kernel)391        sys.exit(err)392393    if cputype in ["powerpc", "riscv"] and kernel == "unknown-freebsd":394        cputype = (395            subprocess.check_output(["uname", "-p"]).strip().decode(default_encoding)396        )397    cputype_mapper = {398        "BePC": "i686",399        "aarch64": "aarch64",400        "aarch64eb": "aarch64",401        "amd64": "x86_64",402        "arm64": "aarch64",403        "i386": "i686",404        "i486": "i686",405        "i686": "i686",406        "i686-AT386": "i686",407        "i786": "i686",408        "loongarch32": "loongarch32",409        "loongarch64": "loongarch64",410        "m68k": "m68k",411        "csky": "csky",412        "powerpc": "powerpc",413        "powerpc64": "powerpc64",414        "powerpc64le": "powerpc64le",415        "ppc": "powerpc",416        "ppc64": "powerpc64",417        "ppc64le": "powerpc64le",418        "riscv64": "riscv64gc",419        "s390x": "s390x",420        "x64": "x86_64",421        "x86": "i686",422        "x86-64": "x86_64",423        "x86_64": "x86_64",424    }425426    # Consider the direct transformation first and then the special cases427    if cputype in cputype_mapper:428        cputype = cputype_mapper[cputype]429    elif cputype in {"xscale", "arm"}:430        cputype = "arm"431        if kernel == "linux-android":432            kernel = "linux-androideabi"433        elif kernel == "unknown-freebsd":434            cputype = processor435            kernel = "unknown-freebsd"436    elif cputype == "armv6l":437        cputype = "arm"438        if kernel == "linux-android":439            kernel = "linux-androideabi"440        else:441            kernel += "eabihf"442    elif cputype in {"armv6hf", "earmv6hf"}:443        cputype = "armv6"444        if kernel == "unknown-netbsd":445            kernel += "-eabihf"446    elif cputype in {"armv7l", "earmv7hf", "armv8l"}:447        cputype = "armv7"448        if kernel == "linux-android":449            kernel = "linux-androideabi"450        elif kernel == "unknown-netbsd":451            kernel += "-eabihf"452        else:453            kernel += "eabihf"454    elif cputype == "mips":455        if sys.byteorder == "big":456            cputype = "mips"457        elif sys.byteorder == "little":458            cputype = "mipsel"459        else:460            raise ValueError("unknown byteorder: {}".format(sys.byteorder))461    elif cputype == "mips64":462        if sys.byteorder == "big":463            cputype = "mips64"464        elif sys.byteorder == "little":465            cputype = "mips64el"466        else:467            raise ValueError("unknown byteorder: {}".format(sys.byteorder))468        # only the n64 ABI is supported, indicate it469        kernel += "abi64"470    elif cputype == "sparc" or cputype == "sparcv9" or cputype == "sparc64":471        pass472    else:473        err = "unknown cpu type: {}".format(cputype)474        sys.exit(err)475476    return "{}-{}".format(cputype, kernel)477478479@contextlib.contextmanager480def output(filepath):481    tmp = filepath + ".tmp"482    with open(tmp, "w", encoding="utf-8") as f:483        yield f484    try:485        if os.path.exists(filepath):486            os.remove(filepath)  # PermissionError/OSError on Win32 if in use487    except OSError:488        shutil.copy2(tmp, filepath)489        os.remove(tmp)490        return491    os.rename(tmp, filepath)492493494class Stage0Toolchain:495    def __init__(self, date, version):496        self.date = date497        self.version = version498499    def channel(self):500        return self.version + "-" + self.date501502503class DownloadInfo:504    """A helper class that can be pickled into a parallel subprocess"""505506    def __init__(507        self,508        base_download_url,509        download_path,510        bin_root,511        tarball_path,512        tarball_suffix,513        stage0_data,514        pattern,515        verbose,516    ):517        self.base_download_url = base_download_url518        self.download_path = download_path519        self.bin_root = bin_root520        self.tarball_path = tarball_path521        self.tarball_suffix = tarball_suffix522        self.stage0_data = stage0_data523        self.pattern = pattern524        self.verbose = verbose525526527def download_component(download_info):528    if not os.path.exists(download_info.tarball_path):529        get(530            download_info.base_download_url,531            download_info.download_path,532            download_info.tarball_path,533            download_info.stage0_data,534            verbose=download_info.verbose,535        )536537538def unpack_component(download_info):539    unpack(540        download_info.tarball_path,541        download_info.tarball_suffix,542        download_info.bin_root,543        match=download_info.pattern,544        verbose=download_info.verbose,545    )546547548class FakeArgs:549    """Used for unit tests to avoid updating all call sites"""550551    def __init__(self):552        self.build = ""553        self.build_dir = ""554        self.clean = False555        self.verbose = False556        self.json_output = False557        self.color = "auto"558        self.warnings = "default"559560561class RustBuild(object):562    """Provide all the methods required to build Rust"""563564    def __init__(self, config_toml="", args=None):565        if args is None:566            args = FakeArgs()567        self.git_version = None568        self.nix_deps_dir = None569        self._should_fix_bins_and_dylibs = None570        self.rust_root = os.path.abspath(os.path.join(__file__, "../../.."))571572        self.config_toml = config_toml573574        self.clean = args.clean575        self.json_output = args.json_output576        self.verbose = args.verbose577        self.color = args.color578        self.warnings = args.warnings579580        config_verbose_count = self.get_toml("verbose", "build")581        if config_verbose_count is not None:582            self.verbose = max(self.verbose, int(config_verbose_count))583584        self.use_vendored_sources = self.get_toml("vendor", "build") == "true"585        self.use_locked_deps = self.get_toml("locked-deps", "build") == "true"586587        build_dir = args.build_dir or self.get_toml("build-dir", "build") or "build"588        self.build_dir = os.path.abspath(build_dir)589590        self.stage0_data = parse_stage0_file(591            os.path.join(self.rust_root, "src", "stage0")592        )593        self.stage0_compiler = Stage0Toolchain(594            self.stage0_data["compiler_date"], self.stage0_data["compiler_version"]595        )596        self.download_url = (597            os.getenv("RUSTUP_DIST_SERVER") or self.stage0_data["dist_server"]598        )599        self.jobs = self.get_toml("jobs", "build") or "default"600601        self.build = args.build or self.build_triple()602603    def download_toolchain(self):604        """Fetch the build system for Rust, written in Rust605606        This method will build a cache directory, then it will fetch the607        tarball which has the stage0 compiler used to then bootstrap the Rust608        compiler itself.609610        Each downloaded tarball is extracted, after that, the script611        will move all the content to the right place.612        """613        rustc_channel = self.stage0_compiler.version614        bin_root = self.bin_root()615616        key = self.stage0_compiler.date617        is_outdated = self.program_out_of_date(self.rustc_stamp(), key)618        need_rustc = self.rustc().startswith(bin_root) and (619            not os.path.exists(self.rustc()) or is_outdated620        )621        need_cargo = self.cargo().startswith(bin_root) and (622            not os.path.exists(self.cargo()) or is_outdated623        )624625        if need_rustc or need_cargo:626            if os.path.exists(bin_root):627                # HACK: On Windows, we can't delete rust-analyzer-proc-macro-server while it's628                # running. Kill it.629                if platform_is_win32():630                    print(631                        "Killing rust-analyzer-proc-macro-srv before deleting stage0 toolchain"632                    )633                    regex = "{}\\\\(host|{})\\\\stage0\\\\libexec".format(634                        os.path.basename(self.build_dir), self.build635                    )636                    script = (637                        # NOTE: can't use `taskkill` or `Get-Process -Name` because they error if638                        # the server isn't running.639                        "Get-Process | "640                        + 'Where-Object {$_.Name -eq "rust-analyzer-proc-macro-srv"} |'641                        + 'Where-Object {{$_.Path -match "{}"}} |'.format(regex)642                        + "Stop-Process"643                    )644                    run_powershell([script])645                shutil.rmtree(bin_root)646647            cache_dst = self.get_toml("bootstrap-cache-path", "build") or os.path.join(648                self.build_dir, "cache"649            )650651            rustc_cache = os.path.join(cache_dst, key)652            if not os.path.exists(rustc_cache):653                os.makedirs(rustc_cache)654655            tarball_suffix = ".tar.gz" if lzma is None else ".tar.xz"656657            toolchain_suffix = "{}-{}{}".format(658                rustc_channel, self.build, tarball_suffix659            )660661            tarballs_to_download = []662663            if need_rustc:664                tarballs_to_download.append(665                    (666                        "rust-std-{}".format(toolchain_suffix),667                        "rust-std-{}".format(self.build),668                    )669                )670                tarballs_to_download.append(671                    ("rustc-{}".format(toolchain_suffix), "rustc")672                )673674            if need_cargo:675                tarballs_to_download.append(676                    ("cargo-{}".format(toolchain_suffix), "cargo")677                )678679            tarballs_download_info = [680                DownloadInfo(681                    base_download_url=self.download_url,682                    download_path="dist/{}/{}".format(683                        self.stage0_compiler.date, filename684                    ),685                    bin_root=self.bin_root(),686                    tarball_path=os.path.join(rustc_cache, filename),687                    tarball_suffix=tarball_suffix,688                    stage0_data=self.stage0_data,689                    pattern=pattern,690                    verbose=self.verbose,691                )692                for filename, pattern in tarballs_to_download693            ]694695            # Download the components serially to show the progress bars properly.696            for download_info in tarballs_download_info:697                download_component(download_info)698699            # Unpack the tarballs in parallel.700            # In Python 2.7, Pool cannot be used as a context manager.701            pool_size = min(len(tarballs_download_info), get_cpus())702            if self.verbose > 0:703                print(704                    "Choosing a pool size of",705                    pool_size,706                    "for the unpacking of the tarballs",707                )708            p = Pool(pool_size)709            try:710                # FIXME: A cheap workaround for https://github.com/rust-lang/rust/issues/125578,711                # remove this once the issue is closed.712                bootstrap_build_artifacts = os.path.join(self.bootstrap_out(), "debug")713                if os.path.exists(bootstrap_build_artifacts):714                    shutil.rmtree(bootstrap_build_artifacts)715716                p.map(unpack_component, tarballs_download_info)717            finally:718                p.close()719            p.join()720721            if self.should_fix_bins_and_dylibs():722                self.fix_bin_or_dylib("{}/bin/cargo".format(bin_root))723724                self.fix_bin_or_dylib("{}/bin/rustc".format(bin_root))725                self.fix_bin_or_dylib("{}/bin/rustdoc".format(bin_root))726                self.fix_bin_or_dylib(727                    "{}/libexec/rust-analyzer-proc-macro-srv".format(bin_root)728                )729                lib_dir = "{}/lib".format(bin_root)730                rustlib_bin_dir = "{}/rustlib/{}/bin".format(lib_dir, self.build)731                self.fix_bin_or_dylib("{}/rust-lld".format(rustlib_bin_dir))732                self.fix_bin_or_dylib("{}/gcc-ld/ld.lld".format(rustlib_bin_dir))733                for lib in os.listdir(lib_dir):734                    # .so is not necessarily the suffix, there can be version numbers afterwards.735                    if ".so" in lib:736                        elf_path = os.path.join(lib_dir, lib)737                        with open(elf_path, "rb") as f:738                            magic = f.read(4)739                            # Patchelf will skip non-ELF files, but issue a warning.740                            if magic == b"\x7fELF":741                                self.fix_bin_or_dylib(elf_path)742743            with output(self.rustc_stamp()) as rust_stamp:744                rust_stamp.write(key)745746    def should_fix_bins_and_dylibs(self):747        """Whether or not `fix_bin_or_dylib` needs to be run; can only be True748        on NixOS or if bootstrap.toml has `build.patch-binaries-for-nix` set.749        """750        if self._should_fix_bins_and_dylibs is not None:751            return self._should_fix_bins_and_dylibs752753        def get_answer():754            default_encoding = sys.getdefaultencoding()755            try:756                ostype = (757                    subprocess.check_output(["uname", "-s"])758                    .strip()759                    .decode(default_encoding)760                )761            except subprocess.CalledProcessError:762                return False763            except OSError as reason:764                if getattr(reason, "winerror", None) is not None:765                    return False766                raise reason767768            if ostype != "Linux":769                return False770771            # If the user has explicitly indicated whether binaries should be772            # patched for Nix, then don't check for NixOS.773            if self.get_toml("patch-binaries-for-nix", "build") == "true":774                return True775            if self.get_toml("patch-binaries-for-nix", "build") == "false":776                return False777778            # Use `/etc/os-release` instead of `/etc/NIXOS`.779            # The latter one does not exist on NixOS when using tmpfs as root.780            try:781                with open("/etc/os-release", "r", encoding="utf-8") as f:782                    is_nixos = any(783                        ln.strip() in ("ID=nixos", "ID='nixos'", 'ID="nixos"')784                        for ln in f785                    )786            except FileNotFoundError:787                is_nixos = False788789            # If not on NixOS, then warn if user seems to be atop Nix shell790            if not is_nixos:791                in_nix_shell = os.getenv("IN_NIX_SHELL")792                if in_nix_shell:793                    eprint(794                        "The IN_NIX_SHELL environment variable is `{}`;".format(795                            in_nix_shell796                        ),797                        "you may need to set `patch-binaries-for-nix=true` in bootstrap.toml",798                    )799800            return is_nixos801802        answer = self._should_fix_bins_and_dylibs = get_answer()803        if answer:804            eprint("INFO: You seem to be using Nix.")805        return answer806807    def fix_bin_or_dylib(self, fname):808        """Modifies the interpreter section of 'fname' to fix the dynamic linker,809        or the RPATH section, to fix the dynamic library search path810811        This method is only required on NixOS and uses the PatchELF utility to812        change the interpreter/RPATH of ELF executables.813814        Please see https://nixos.org/patchelf.html for more information815        """816        assert self._should_fix_bins_and_dylibs is True817        eprint("attempting to patch", fname)818819        # Only build `.nix-deps` once.820        nix_deps_dir = self.nix_deps_dir821        if not nix_deps_dir:822            # Run `nix-build` to "build" each dependency (which will likely reuse823            # the existing `/nix/store` copy, or at most download a pre-built copy).824            #825            # Importantly, we create a gc-root called `.nix-deps` in the `build/`826            # directory, but still reference the actual `/nix/store` path in the rpath827            # as it makes it significantly more robust against changes to the location of828            # the `.nix-deps` location.829            #830            # bintools: Needed for the path of `ld-linux.so` (via `nix-support/dynamic-linker`).831            # zlib: Needed as a system dependency of `libLLVM-*.so`.832            # patchelf: Needed for patching ELF binaries (see doc comment above).833            nix_deps_dir = "{}/{}".format(self.build_dir, ".nix-deps")834            nix_expr = """835            with (import <nixpkgs> {});836            symlinkJoin {837              name = "rust-stage0-dependencies";838              paths = [839                zlib840                patchelf841                stdenv.cc.bintools842              ];843            }844            """845            try:846                subprocess.check_output(847                    [848                        "nix-build",849                        "-E",850                        nix_expr,851                        "-o",852                        nix_deps_dir,853                    ]854                )855            except subprocess.CalledProcessError as reason:856                eprint("WARNING: failed to call nix-build:", reason)857                return858            self.nix_deps_dir = nix_deps_dir859860        patchelf = "{}/bin/patchelf".format(nix_deps_dir)861        rpath_entries = [os.path.join(os.path.realpath(nix_deps_dir), "lib")]862        patchelf_args = ["--add-rpath", ":".join(rpath_entries)]863        if ".so" not in fname:864            # Finally, set the correct .interp for binaries865            with open(866                "{}/nix-support/dynamic-linker".format(nix_deps_dir),867                encoding="utf-8",868            ) as dynamic_linker:869                patchelf_args += ["--set-interpreter", dynamic_linker.read().rstrip()]870871        try:872            subprocess.check_output([patchelf] + patchelf_args + [fname])873        except subprocess.CalledProcessError as reason:874            eprint("WARNING: failed to call patchelf:", reason)875            return876877    def rustc_stamp(self):878        """Return the path for .rustc-stamp at the given stage879880        >>> rb = RustBuild()881        >>> rb.build = "host"882        >>> rb.build_dir = "build"883        >>> expected = os.path.join("build", "host", "stage0", ".rustc-stamp")884        >>> assert rb.rustc_stamp() == expected, rb.rustc_stamp()885        """886        return os.path.join(self.bin_root(), ".rustc-stamp")887888    def program_out_of_date(self, stamp_path, key):889        """Check if the given program stamp is out of date"""890        if not os.path.exists(stamp_path) or self.clean:891            return True892        with open(stamp_path, "r", encoding="utf-8") as stamp:893            return key != stamp.read()894895    def bin_root(self):896        """Return the binary root directory for the given stage897898        >>> rb = RustBuild()899        >>> rb.build = "devel"900        >>> expected = os.path.abspath(os.path.join("build", "devel", "stage0"))901        >>> assert rb.bin_root() == expected, rb.bin_root()902        """903        subdir = "stage0"904        return os.path.join(self.build_dir, self.build, subdir)905906    def get_toml(self, key, section=None):907        """Returns the value of the given key in bootstrap.toml, otherwise returns None908909        >>> rb = RustBuild()910        >>> rb.config_toml = 'key1 = "value1"\\nkey2 = "value2"'911        >>> rb.get_toml("key2")912        'value2'913914        If the key does not exist, the result is None:915916        >>> rb.get_toml("key3") is None917        True918919        Optionally also matches the section the key appears in920921        >>> rb.config_toml = '[a]\\nkey = "value1"\\n[b]\\nkey = "value2"'922        >>> rb.get_toml('key', 'a')923        'value1'924        >>> rb.get_toml('key', 'b')925        'value2'926        >>> rb.get_toml('key', 'c') is None927        True928929        A dotted key names a table relative to its enclosing section, so the930        full table path must match for the key to be found:931932        >>> rb.config_toml = 'build.cargo = "/path/to/cargo"'933        >>> rb.get_toml('cargo', 'build')934        '/path/to/cargo'935        >>> rb.get_toml('cargo', 'other') is None936        True937938        A dotted key inside a section composes with that section's name:939940        >>> rb.config_toml = '[target]\\nx86_64-unknown-linux-gnu.cc = "gcc"'941        >>> rb.get_toml('cc', 'target.x86_64-unknown-linux-gnu')942        'gcc'943944        >>> rb.config_toml = 'key1 = true'945        >>> rb.get_toml("key1")946        'true'947        """948        return RustBuild.get_toml_static(self.config_toml, key, section)949950    @staticmethod951    def get_toml_static(config_toml, key, section=None):952        cur_section = None953        for line in config_toml.splitlines():954            section_match = re.match(r"^\s*\[(.*)\]\s*$", line)955            if section_match is not None:956                cur_section = section_match.group(1)957958            # Match the key, optionally preceded by a dotted-table prefix (the959            # `build.` in `build.cargo`), which names a table relative to the960            # current `[section]` and is appended to `cur_section`. This is a961            # subset parser, not full TOML: quoted names (e.g. the `'a.b'` that962            # configure.py emits for dotted targets) are not matched here.963            match = re.match(964                r"^\s*(?:([\w.-]+)\.)?{}\s*=(.*)$".format(re.escape(key)), line965            )966            if match is not None:967                prefix = match.group(1)968                if prefix is None:969                    line_section = cur_section970                elif cur_section is None:971                    line_section = prefix972                else:973                    line_section = "{}.{}".format(cur_section, prefix)974                value = match.group(2)975                if section is None or section == line_section:976                    return RustBuild.get_string(value) or value.strip()977        return None978979    def cargo(self):980        """Return config path for cargo"""981        return self.program_config("cargo")982983    def rustc(self):984        """Return config path for rustc"""985        return self.program_config("rustc")986987    def program_config(self, program):988        """Return config path for the given program at the given stage989990        >>> rb = RustBuild()991        >>> rb.config_toml = 'build.rustc = "rustc"\\n'992        >>> rb.program_config('rustc')993        'rustc'994        >>> rb.config_toml = '[build]\\nrustc = "rustc"\\n'995        >>> rb.program_config('rustc')996        'rustc'997        >>> rb.config_toml = ''998        >>> cargo_path = rb.program_config('cargo')999        >>> cargo_path.rstrip(".exe") == os.path.join(rb.bin_root(),1000        ... "bin", "cargo")1001        True1002        """1003        config = self.get_toml(program, "build")1004        if config:1005            return os.path.expanduser(config)1006        return os.path.join(self.bin_root(), "bin", "{}{}".format(program, EXE_SUFFIX))10071008    @staticmethod1009    def get_string(line):1010        """Return the value between double quotes10111012        >>> RustBuild.get_string('    "devel"   ')1013        'devel'1014        >>> RustBuild.get_string("    'devel'   ")1015        'devel'1016        >>> RustBuild.get_string('devel') is None1017        True1018        >>> RustBuild.get_string('    "devel   ')1019        ''1020        """1021        start = line.find('"')1022        if start != -1:1023            end = start + 1 + line[start + 1 :].find('"')1024            return line[start + 1 : end]1025        start = line.find("'")1026        if start != -1:1027            end = start + 1 + line[start + 1 :].find("'")1028            return line[start + 1 : end]1029        return None10301031    def bootstrap_out(self):1032        """Return the path of the bootstrap build artifacts10331034        >>> rb = RustBuild()1035        >>> rb.build_dir = "build"1036        >>> rb.bootstrap_binary() == os.path.join("build", "bootstrap")1037        True1038        """1039        return os.path.join(self.build_dir, "bootstrap")10401041    def bootstrap_binary(self):1042        """Return the path of the bootstrap binary10431044        >>> rb = RustBuild()1045        >>> rb.build_dir = "build"1046        >>> rb.bootstrap_binary() == os.path.join("build", "bootstrap",1047        ... "debug", "bootstrap")1048        True1049        """1050        return os.path.join(self.bootstrap_out(), "debug", "bootstrap")10511052    def build_bootstrap(self):1053        """Build bootstrap"""1054        env = os.environ.copy()1055        if "GITHUB_ACTIONS" in env:1056            print("::group::Building bootstrap")1057        else:1058            eprint("Building bootstrap")10591060        args = self.build_bootstrap_cmd(env)1061        # Run this from the source directory so cargo finds .cargo/config1062        run(args, env=env, verbose=self.verbose, cwd=self.rust_root)10631064        if "GITHUB_ACTIONS" in env:1065            print("::endgroup::")10661067    def build_bootstrap_cmd(self, env):1068        """For tests."""1069        build_dir = os.path.join(self.build_dir, "bootstrap")1070        if self.clean and os.path.exists(build_dir):1071            shutil.rmtree(build_dir)1072        # `CARGO_BUILD_TARGET` breaks bootstrap build.1073        # See also: <https://github.com/rust-lang/rust/issues/70208>.1074        if "CARGO_BUILD_TARGET" in env:1075            del env["CARGO_BUILD_TARGET"]1076        # if in CI, don't use incremental build when building bootstrap.1077        if "GITHUB_ACTIONS" in env:1078            env["CARGO_INCREMENTAL"] = "0"1079        env["CARGO_TARGET_DIR"] = build_dir1080        env["RUSTC"] = self.rustc()1081        env["LD_LIBRARY_PATH"] = (1082            os.path.join(self.bin_root(), "lib") + (os.pathsep + env["LD_LIBRARY_PATH"])1083            if "LD_LIBRARY_PATH" in env1084            else ""1085        )1086        env["DYLD_LIBRARY_PATH"] = (1087            os.path.join(self.bin_root(), "lib")1088            + (os.pathsep + env["DYLD_LIBRARY_PATH"])1089            if "DYLD_LIBRARY_PATH" in env1090            else ""1091        )1092        env["LIBRARY_PATH"] = (1093            os.path.join(self.bin_root(), "lib") + (os.pathsep + env["LIBRARY_PATH"])1094            if "LIBRARY_PATH" in env1095            else ""1096        )1097        env["LIBPATH"] = (1098            os.path.join(self.bin_root(), "lib") + (os.pathsep + env["LIBPATH"])1099            if "LIBPATH" in env1100            else ""1101        )11021103        # Export Stage0 snapshot compiler related env variables1104        build_section = "target.{}".format(self.build)1105        host_triple_sanitized = self.build.replace("-", "_")1106        var_data = {1107            "CC": "cc",1108            "CXX": "cxx",1109            "LD": "linker",1110            "AR": "ar",1111            "RANLIB": "ranlib",1112        }1113        for var_name, toml_key in var_data.items():1114            toml_val = self.get_toml(toml_key, build_section)1115            if toml_val is not None:1116                env["{}_{}".format(var_name, host_triple_sanitized)] = toml_val11171118        # In src/etc/rust_analyzer_settings.json, we configure rust-analyzer to1119        # pass RUSTC_BOOTSTRAP=1 to all cargo invocations because the standard1120        # library uses unstable Cargo features. Without RUSTC_BOOTSTRAP,1121        # rust-analyzer would fail to fetch workspace layout when the system's1122        # default toolchain is not nightly.1123        #1124        # But that setting has the collateral effect of rust-analyzer also1125        # passing RUSTC_BOOTSTRAP=1 to all x.py invocations too (the various1126        # overrideCommand).1127        #1128        # Set a consistent RUSTC_BOOTSTRAP=1 here to prevent spurious rebuilds1129        # of bootstrap when rust-analyzer x.py invocations are interleaved with1130        # handwritten ones on the command line.1131        env["RUSTC_BOOTSTRAP"] = "1"11321133        # If any of RUSTFLAGS or RUSTFLAGS_BOOTSTRAP are present and nonempty,1134        # we allow arbitrary compiler flags in there, including unstable ones1135        # such as `-Zthreads=8`.1136        #1137        # But if there aren't custom flags being passed to bootstrap, then we1138        # cancel the RUSTC_BOOTSTRAP=1 from above by passing `-Zallow-features=`1139        # to ensure unstable language or library features do not accidentally1140        # get introduced into bootstrap over time. Distros rely on being able to1141        # compile bootstrap with a variety of their toolchains, not necessarily1142        # the same as Rust's CI uses.1143        if env.get("RUSTFLAGS", "") or env.get("RUSTFLAGS_BOOTSTRAP", ""):1144            # Preserve existing RUSTFLAGS.1145            env.setdefault("RUSTFLAGS", "")1146        else:1147            env["RUSTFLAGS"] = "-Zallow-features="11481149        if not os.path.isfile(self.cargo()):1150            raise Exception("no cargo executable found at `{}`".format(self.cargo()))1151        args = [1152            self.cargo(),1153            "build",1154            "--jobs=" + self.jobs,1155            "--manifest-path",1156            os.path.join(self.rust_root, "src/bootstrap/Cargo.toml"),1157            "-Zroot-dir=" + self.rust_root,1158        ]1159        # verbose cargo output is very noisy, so only enable it with -vv1160        args.extend("--verbose" for _ in range(self.verbose - 1))1161        if self.verbose < 0:1162            args.append("--quiet")11631164        target_features = []1165        if self.get_toml("crt-static", build_section) == "true":1166            target_features += ["+crt-static"]1167        elif self.get_toml("crt-static", build_section) == "false":1168            target_features += ["-crt-static"]1169        if target_features:1170            env["RUSTFLAGS"] += " -C target-feature=" + (",".join(target_features))1171        target_linker = self.get_toml("linker", build_section)1172        if target_linker is not None:1173            env["RUSTFLAGS"] += " -C linker=" + target_linker1174        # When changing this list, also update the corresponding list in `Builder::cargo`1175        # in `src/bootstrap/src/core/builder.rs`.1176        env["RUSTFLAGS"] += " -Wrust_2018_idioms -Wunused_lifetimes"1177        if self.warnings == "default":1178            deny_warnings = self.get_toml("deny-warnings", "rust") != "false"1179        else:1180            deny_warnings = self.warnings == "deny"1181        if deny_warnings:1182            env["CARGO_BUILD_WARNINGS"] = "deny"11831184        # Add RUSTFLAGS_BOOTSTRAP to RUSTFLAGS for bootstrap compilation.1185        # Note that RUSTFLAGS_BOOTSTRAP should always be added to the end of1186        # RUSTFLAGS, since that causes RUSTFLAGS_BOOTSTRAP to override RUSTFLAGS.1187        if "RUSTFLAGS_BOOTSTRAP" in env:1188            env["RUSTFLAGS"] += " " + env["RUSTFLAGS_BOOTSTRAP"]11891190        if "BOOTSTRAP_TRACING" in env:1191            args.append("--features=tracing")11921193        if self.use_locked_deps:1194            args.append("--locked")1195        if self.use_vendored_sources:1196            args.append("--frozen")1197        if self.get_toml("metrics", "build"):1198            args.append("--features")1199            args.append("build-metrics")1200        if self.json_output:1201            args.append("--message-format=json")1202        if self.color == "always":1203            args.append("--color=always")1204        elif self.color == "never":1205            args.append("--color=never")1206        try:1207            args += env["CARGOFLAGS"].split()1208        except KeyError:1209            pass12101211        return args12121213    def build_triple(self):1214        """Build triple as in LLVM12151216        Note that `default_build_triple` is moderately expensive,1217        so use `self.build` where possible.1218        """1219        config = self.get_toml("build")1220        return config or default_build_triple(self.verbose)12211222    def is_git_repository(self, repo_path):1223        return os.path.isdir(os.path.join(repo_path, ".git"))12241225    def get_latest_commit(self):1226        repo_path = self.rust_root1227        author_email = self.stage0_data.get("git_merge_commit_email")1228        if not self.is_git_repository(repo_path):1229            return "<commit>"1230        cmd = [1231            "git",1232            "rev-list",1233            "--author",1234            author_email,1235            "-n1",1236            "HEAD",1237        ]1238        try:1239            commit = subprocess.check_output(1240                cmd, universal_newlines=True, cwd=repo_path1241            ).strip()1242            return commit or "<commit>"1243        except subprocess.CalledProcessError:1244            return "<commit>"12451246    def check_vendored_status(self):1247        """Check that vendoring is configured properly"""1248        # keep this consistent with the equivalent check in bootstrap:1249        # https://github.com/rust-lang/rust/blob/a8a33cf27166d3eabaffc58ed3799e054af3b0c6/src/bootstrap/lib.rs#L399-L4051250        if "SUDO_USER" in os.environ and not self.use_vendored_sources:1251            if os.getuid() == 0:1252                self.use_vendored_sources = True1253                eprint("INFO: looks like you're trying to run this command as root")1254                eprint("      and so in order to preserve your $HOME this will now")1255                eprint("      use vendored sources by default.")12561257        cargo_dir = os.path.join(self.rust_root, ".cargo")1258        commit = self.get_latest_commit()1259        url = f"https://ci-artifacts.rust-lang.org/rustc-builds/{commit}/rustc-nightly-src.tar.xz"1260        if self.use_vendored_sources:1261            vendor_dir = os.path.join(self.rust_root, "vendor")1262            if not os.path.exists(vendor_dir):1263                eprint(1264                    "ERROR: vendoring required, but vendor directory does not exist."1265                )1266                eprint("       Run `x.py vendor` to initialize the vendor directory.")1267                eprint(1268                    "       Alternatively, use the pre-vendored `rustc-src` dist component."1269                )1270                eprint(1271                    "       To get a stable/beta/nightly version, download it from: "1272                )1273                eprint(1274                    "       "1275                    "https://forge.rust-lang.org/infra/other-installation-methods.html#source-code"1276                )1277                eprint(1278                    "       To get a specific commit version, download it using the below URL,"1279                )1280                eprint("       replacing <commit> with a specific commit checksum: ")1281                eprint("       ", url)1282                eprint(1283                    "       Once you have the source downloaded, place the vendor directory"1284                )1285                eprint("       from the archive in the root of the rust project.")1286                raise Exception("{} not found".format(vendor_dir))12871288            if not os.path.exists(cargo_dir):1289                eprint("ERROR: vendoring required, but .cargo/config does not exist.")1290                raise Exception("{} not found".format(cargo_dir))129112921293def parse_args(args):1294    """Parse the command line arguments that the python script needs."""12951296    # Pass allow_abbrev=False to remove support for inexact matches (e.g.,1297    # `--json` turning on `--json-output`). The argument list here is partial,1298    # most flags are matched in the Rust bootstrap code. This prevents the1299    # default ambiguity checks in argparse from functioning correctly.1300    parser = argparse.ArgumentParser(add_help=False, allow_abbrev=False)1301    parser.add_argument("-h", "--help", action="store_true")1302    parser.add_argument("--config")1303    parser.add_argument("--build-dir")1304    parser.add_argument("--build")1305    parser.add_argument("--color", choices=["always", "never", "auto"])1306    parser.add_argument("--clean", action="store_true")1307    parser.add_argument("--json-output", action="store_true")1308    parser.add_argument(1309        "--warnings", choices=["deny", "warn", "default"], default="default"1310    )1311    group = parser.add_mutually_exclusive_group()1312    group.add_argument("-v", "--verbose", action="count", default=0)1313    # Note that we're storing the `--quiet` value in `verbose`. That way we don't need to thread1314    # `self.quiet` throughout the code. That could be error prone, which could let some output1315    # through that should have been suppressed.1316    group.add_argument("-q", "--quiet", action="store_const", const=-1, dest="verbose")13171318    return parser.parse_known_args(args)[0]131913201321def parse_stage0_file(path):1322    result = {}1323    with open(path, "r", encoding="utf-8") as file:1324        for line in file:1325            line = line.strip()1326            if line and not line.startswith("#"):1327                key, value = line.split("=", 1)1328                result[key.strip()] = value.strip()1329    return result133013311332def bootstrap(args):1333    """Configure, fetch, build and run the initial bootstrap"""1334    rust_root = os.path.abspath(os.path.join(__file__, "../../.."))13351336    if not os.path.exists(os.path.join(rust_root, ".git")) and os.path.exists(1337        os.path.join(rust_root, ".github")1338    ):1339        eprint(1340            "warn: Looks like you are trying to bootstrap Rust from a source that is neither a "1341            "git clone nor distributed tarball.\nThis build may fail due to missing submodules "1342            "unless you put them in place manually."1343        )13441345    # Read from `--config` first, followed by `RUST_BOOTSTRAP_CONFIG`.1346    # If neither is set, check `./bootstrap.toml`, then `bootstrap.toml` in the root directory.1347    # If those are unavailable, fall back to `./config.toml`, then `config.toml` for1348    # backward compatibility.1349    toml_path = args.config or os.getenv("RUST_BOOTSTRAP_CONFIG")1350    using_default_path = toml_path is None1351    if using_default_path:1352        toml_path = "bootstrap.toml"1353        if not os.path.exists(toml_path):1354            toml_path = os.path.join(rust_root, "bootstrap.toml")1355            if not os.path.exists(toml_path):1356                toml_path = "config.toml"1357                if not os.path.exists(toml_path):1358                    toml_path = os.path.join(rust_root, "config.toml")13591360    # Give a hard error if `--config` or `RUST_BOOTSTRAP_CONFIG` are set to a missing path,1361    # but not if `bootstrap.toml` hasn't been created.1362    if not using_default_path or os.path.exists(toml_path):1363        with open(toml_path, encoding="utf-8") as config:1364            config_toml = config.read()1365    else:1366        config_toml = ""13671368    profile = RustBuild.get_toml_static(config_toml, "profile")1369    is_non_git_source = not os.path.exists(os.path.join(rust_root, ".git"))13701371    if profile is None and is_non_git_source:1372        profile = "dist"13731374    if profile is not None:1375        # Allows creating alias for profile names, allowing1376        # profiles to be renamed while maintaining back compatibility1377        # Keep in sync with `profile_aliases` in config.rs1378        profile_aliases = {"user": "dist"}1379        include_file = "bootstrap.{}.toml".format(1380            profile_aliases.get(profile) or profile1381        )1382        include_dir = os.path.join(rust_root, "src", "bootstrap", "defaults")1383        include_path = os.path.join(include_dir, include_file)13841385        if not os.path.exists(include_path):1386            raise Exception(1387                "Unrecognized config profile '{}'. Check src/bootstrap/defaults"1388                " for available options.".format(profile)1389            )13901391        # HACK: This works because `self.get_toml()` returns the first match it finds for a1392        # specific key, so appending our defaults at the end allows the user to override them1393        with open(include_path, encoding="utf-8") as included_toml:1394            config_toml += os.linesep + included_toml.read()13951396    # Configure initial bootstrap1397    build = RustBuild(config_toml, args)1398    build.check_vendored_status()13991400    if not os.path.exists(build.build_dir):1401        os.makedirs(os.path.realpath(build.build_dir))14021403    # Fetch/build the bootstrap1404    build.download_toolchain()1405    sys.stdout.flush()1406    build.build_bootstrap()1407    sys.stdout.flush()14081409    # Run the bootstrap1410    args = [build.bootstrap_binary()]1411    args.extend(sys.argv[1:])1412    env = os.environ.copy()1413    env["BOOTSTRAP_PYTHON"] = sys.executable1414    run(args, env=env, verbose=build.verbose, is_bootstrap=True)141514161417def main():1418    """Entry point for the bootstrap process"""1419    start_time = time()14201421    # x.py help <cmd> ...1422    if len(sys.argv) > 1 and sys.argv[1] == "help":1423        sys.argv[1] = "-h"14241425    args = parse_args(sys.argv)14261427    # Root help (e.g., x.py --help) prints help from the saved file to save the time1428    if len(sys.argv) == 1 or sys.argv[1] in ["-h", "--help"]:1429        try:1430            with open(1431                os.path.join(os.path.dirname(__file__), "../etc/xhelp"),1432                "r",1433                encoding="utf-8",1434            ) as f:1435                # The file from bootstrap func already has newline.1436                print(f.read(), end="")1437                sys.exit(0)1438        except Exception as error:1439            eprint(1440                f"ERROR: unable to run help: {error}\n",1441                "x.py run generate-help may solve the problem.",1442            )1443            sys.exit(1)14441445    # If the user is asking for other helps, let them know that the whole download-and-build1446    # process has to happen before anything is printed out.1447    if args.help:1448        eprint(1449            "INFO: Downloading and building bootstrap before processing --help command.\n"1450            "      See src/bootstrap/README.md for help with common commands."1451        )14521453    exit_code = 01454    success_word = "successfully"1455    try:1456        bootstrap(args)1457    except (SystemExit, KeyboardInterrupt) as error:1458        if hasattr(error, "code") and isinstance(error.code, int):1459            exit_code = error.code1460        else:1461            exit_code = 11462            eprint(error)1463        success_word = "unsuccessfully"14641465    if not args.help:1466        eprint(1467            "Build completed",1468            success_word,1469            "in",1470            format_build_time(time() - start_time),1471        )14721473    sys.exit(exit_code)147414751476if __name__ == "__main__":1477    main()

Code quality findings 72

Ensure functions have docstrings for documentation
missing-docstring
def platform_is_win32():
Ensure functions have docstrings for documentation
missing-docstring
def get_cpus():
Use logging module for better control and configurability
print-statement
def eprint(*args, **kwargs):
Ensure functions have docstrings for documentation
missing-docstring
def eprint(*args, **kwargs):
Use logging module for better control and configurability
print-statement
print(*args, **kwargs)
Ensure functions have docstrings for documentation
missing-docstring
def get(base, url, path, checksums, verbose=0):
Ensure try blocks have corresponding except or finally blocks
try-without-except
try:
Use logging module for better control and configurability
print-statement
eprint("using already-download file", path)
Use logging module for better control and configurability
print-statement
eprint(
Use logging module for better control and configurability
print-statement
eprint("moving {} to {}".format(temp_path, path))
Use logging module for better control and configurability
print-statement
eprint("removing", temp_path)
Ensure functions have docstrings for documentation
missing-docstring
def curl_version():
Ensure functions have docstrings for documentation
missing-docstring
def download(path, url, probably_big, verbose):
Use logging module for better control and configurability
print-statement
eprint("\nspurious failure, trying again")
Use logging module for better control and configurability
print-statement
eprint("downloading {}".format(url))
Ensure try blocks have corresponding except or finally blocks
try-without-except
try:
Use logging module for better control and configurability
print-statement
eprint("verifying", path)
Use logging module for better control and configurability
print-statement
eprint(
Use logging module for better control and configurability
print-statement
eprint("extracting", tarball)
Use logging module for better control and configurability
print-statement
eprint(" extracting", member)
Use logging module for better control and configurability
print-statement
eprint("running: " + " ".join(args))
Use logging module for better control and configurability
print-statement
eprint("ERROR: unable to run `{}`: {}".format(" ".join(cmd), exc))
Use logging module for better control and configurability
print-statement
eprint("Please make sure it's installed and in the path.")
Ensure try blocks have corresponding except or finally blocks
try-without-except
try:
Use logging module for better control and configurability
print-statement
eprint(
Use logging module for better control and configurability
print-statement
eprint("pre-installed rustc not detected: {}".format(e))
Use logging module for better control and configurability
print-statement
eprint("falling back to auto-detect")
Ensure functions have docstrings for documentation
missing-docstring
def output(filepath):
Ensure functions have docstrings for documentation
missing-docstring
def channel(self):
Ensure functions have docstrings for documentation
missing-docstring
def download_component(download_info):
Ensure functions have docstrings for documentation
missing-docstring
def unpack_component(download_info):
Use logging module for better control and configurability
print-statement
print(
Use logging module for better control and configurability
print-statement
print(
Ensure functions have docstrings for documentation
missing-docstring
def get_answer():
Use logging module for better control and configurability
print-statement
eprint(
Use logging module for better control and configurability
print-statement
eprint("INFO: You seem to be using Nix.")
Use logging module for better control and configurability
print-statement
eprint("attempting to patch", fname)
Use logging module for better control and configurability
print-statement
eprint("WARNING: failed to call nix-build:", reason)
Use logging module for better control and configurability
print-statement
eprint("WARNING: failed to call patchelf:", reason)
Ensure functions have docstrings for documentation
missing-docstring
def get_toml_static(config_toml, key, section=None):
Use logging module for better control and configurability
print-statement
print("::group::Building bootstrap")
Use logging module for better control and configurability
print-statement
eprint("Building bootstrap")
Use logging module for better control and configurability
print-statement
print("::endgroup::")
Avoid unless necessary; Python's garbage collector typically handles object deletion
unnecessary-del
del env["CARGO_BUILD_TARGET"]
Raise specific exception types for better error handling
generic-raise
raise Exception("no cargo executable found at `{}`".format(self.cargo()))
Ensure functions have docstrings for documentation
missing-docstring
def is_git_repository(self, repo_path):
Ensure functions have docstrings for documentation
missing-docstring
def get_latest_commit(self):
Use logging module for better control and configurability
print-statement
eprint("INFO: looks like you're trying to run this command as root")
Use logging module for better control and configurability
print-statement
eprint(" and so in order to preserve your $HOME this will now")
Use logging module for better control and configurability
print-statement
eprint(" use vendored sources by default.")
Use logging module for better control and configurability
print-statement
eprint(
Use logging module for better control and configurability
print-statement
eprint(" Run `x.py vendor` to initialize the vendor directory.")
Use logging module for better control and configurability
print-statement
eprint(
Use logging module for better control and configurability
print-statement
eprint(
Use logging module for better control and configurability
print-statement
eprint(
Use logging module for better control and configurability
print-statement
eprint(
Use logging module for better control and configurability
print-statement
eprint(" replacing <commit> with a specific commit checksum: ")
Use logging module for better control and configurability
print-statement
eprint(" ", url)
Use logging module for better control and configurability
print-statement
eprint(
Use logging module for better control and configurability
print-statement
eprint(" from the archive in the root of the rust project.")
Raise specific exception types for better error handling
generic-raise
raise Exception("{} not found".format(vendor_dir))
Use logging module for better control and configurability
print-statement
eprint("ERROR: vendoring required, but .cargo/config does not exist.")
Raise specific exception types for better error handling
generic-raise
raise Exception("{} not found".format(cargo_dir))
Ensure functions have docstrings for documentation
missing-docstring
def parse_stage0_file(path):
Use logging module for better control and configurability
print-statement
eprint(
Raise specific exception types for better error handling
generic-raise
raise Exception(
Use logging module for better control and configurability
print-statement
print(f.read(), end="")
Use logging module for better control and configurability
print-statement
eprint(
Use logging module for better control and configurability
print-statement
eprint(
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
if hasattr(error, "code") and isinstance(error.code, int):
Use logging module for better control and configurability
print-statement
eprint(error)
Use logging module for better control and configurability
print-statement
eprint(

Security findings 1

Potential decompression bomb vulnerability in Python code if input is untrusted; ensure to limit the number of bytes read.
security decompression-bomb
with contextlib.closing(tarfile.open(tarball)) as tar:

Get this view in your editor

Same data, no extra tab — call code_get_file + code_get_findings over MCP from Claude/Cursor/Copilot.