Ensure functions have docstrings for documentation
def platform_is_win32():
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()
Same data, no extra tab — call code_get_file + code_get_findings over MCP from Claude/Cursor/Copilot.