1 from __future__ import absolute_import, division, print_function
22 if sys.platform == 'win32':
27 def get(base, url, path, checksums, verbose=False):
28 with tempfile.NamedTemporaryFile(delete=False) as temp_file:
29 temp_path = temp_file.name
32 if url not in checksums:
33 raise RuntimeError(("src/stage0.json doesn't contain a checksum for {}. "
34 "Pre-built artifacts might not be available for this "
35 "target at this time, see https://doc.rust-lang.org/nightly"
36 "/rustc/platform-support.html for more information.")
38 sha256 = checksums[url]
39 if os.path.exists(path):
40 if verify(path, sha256, False):
42 print("using already-download file", path)
46 print("ignoring already-download file",
47 path, "due to failed verification")
49 download(temp_path, "{}/{}".format(base, url), True, verbose)
50 if not verify(temp_path, sha256, verbose):
51 raise RuntimeError("failed verification")
53 print("moving {} to {}".format(temp_path, path))
54 shutil.move(temp_path, path)
56 if os.path.isfile(temp_path):
58 print("removing", temp_path)
62 def download(path, url, probably_big, verbose):
65 _download(path, url, probably_big, verbose, True)
68 print("\nspurious failure, trying again")
69 _download(path, url, probably_big, verbose, False)
72 def _download(path, url, probably_big, verbose, exception):
73 # Try to use curl (potentially available on win32
74 # https://devblogs.microsoft.com/commandline/tar-and-curl-come-to-windows/)
76 # - If we are on win32 fallback to powershell
77 # - Otherwise raise the error if appropriate
78 if probably_big or verbose:
79 print("downloading {}".format(url))
81 platform_is_win32 = sys.platform == 'win32'
83 if probably_big or verbose:
87 # If curl is not present on Win32, we should not sys.exit
88 # but raise `CalledProcessError` or `OSError` instead
89 require(["curl", "--version"], exception=platform_is_win32)
90 with open(path, "wb") as outfile:
92 "-L", # Follow redirect.
93 "-y", "30", "-Y", "10", # timeout if speed is < 10 bytes/sec for > 30 seconds
94 "--connect-timeout", "30", # timeout if cannot connect within 30 seconds
95 "--retry", "3", "-Sf", url],
96 stdout=outfile, #Implements cli redirect operator '>'
98 exception=True, # Will raise RuntimeError on failure
100 except (subprocess.CalledProcessError, OSError, RuntimeError):
101 # see http://serverfault.com/questions/301128/how-to-download
102 if platform_is_win32:
103 run(["PowerShell.exe", "/nologo", "-Command",
104 "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12;",
105 "(New-Object System.Net.WebClient).DownloadFile('{}', '{}')".format(url, path)],
108 # Check if the RuntimeError raised by run(curl) should be silenced
109 elif verbose or exception:
113 def verify(path, expected, verbose):
114 """Check if the sha256 sum of the given path is valid"""
116 print("verifying", path)
117 with open(path, "rb") as source:
118 found = hashlib.sha256(source.read()).hexdigest()
119 verified = found == expected
121 print("invalid checksum:\n"
123 " expected: {}".format(found, expected))
127 def unpack(tarball, tarball_suffix, dst, verbose=False, match=None):
128 """Unpack the given tarball file"""
129 print("extracting", tarball)
130 fname = os.path.basename(tarball).replace(tarball_suffix, "")
131 with contextlib.closing(tarfile.open(tarball)) as tar:
132 for member in tar.getnames():
133 if "/" not in member:
135 name = member.replace(fname + "/", "", 1)
136 if match is not None and not name.startswith(match):
138 name = name[len(match) + 1:]
140 dst_path = os.path.join(dst, name)
142 print(" extracting", member)
143 tar.extract(member, dst)
144 src_path = os.path.join(dst, member)
145 if os.path.isdir(src_path) and os.path.exists(dst_path):
147 shutil.move(src_path, dst_path)
148 shutil.rmtree(os.path.join(dst, fname))
151 def run(args, verbose=False, exception=False, is_bootstrap=False, **kwargs):
152 """Run a child program in a new process"""
154 print("running: " + ' '.join(args))
156 # Ensure that the .exe is used on Windows just in case a Linux ELF has been
157 # compiled in the same directory.
158 if os.name == 'nt' and not args[0].endswith('.exe'):
160 # Use Popen here instead of call() as it apparently allows powershell on
161 # Windows to not lock up waiting for input presumably.
162 ret = subprocess.Popen(args, **kwargs)
165 err = "failed to run: " + ' '.join(args)
166 if verbose or exception:
167 raise RuntimeError(err)
168 # For most failures, we definitely do want to print this error, or the user will have no
169 # idea what went wrong. But when we've successfully built bootstrap and it failed, it will
170 # have already printed an error above, so there's no need to print the exact command we're
178 def require(cmd, exit=True, exception=False):
179 '''Run a command, returning its output.
181 If `exception` is `True`, raise the error
182 Otherwise If `exit` is `True`, exit the process
185 return subprocess.check_output(cmd).strip()
186 except (subprocess.CalledProcessError, OSError) as exc:
190 print("error: unable to run `{}`: {}".format(' '.join(cmd), exc))
191 print("Please make sure it's installed and in the path.")
197 def format_build_time(duration):
198 """Return a nicer format for build time
200 >>> format_build_time('300')
203 return str(datetime.timedelta(seconds=int(duration)))
206 def default_build_triple(verbose):
207 """Build triple as in LLVM"""
208 # If the user already has a host build triple with an existing `rustc`
209 # install, use their preference. This fixes most issues with Windows builds
210 # being detected as GNU instead of MSVC.
211 default_encoding = sys.getdefaultencoding()
213 version = subprocess.check_output(["rustc", "--version", "--verbose"],
214 stderr=subprocess.DEVNULL)
215 version = version.decode(default_encoding)
216 host = next(x for x in version.split('\n') if x.startswith("host: "))
217 triple = host.split("host: ")[1]
219 print("detected default triple {} from pre-installed rustc".format(triple))
221 except Exception as e:
223 print("pre-installed rustc not detected: {}".format(e))
224 print("falling back to auto-detect")
226 required = sys.platform != 'win32'
227 ostype = require(["uname", "-s"], exit=required)
228 cputype = require(['uname', '-m'], exit=required)
230 # If we do not have `uname`, assume Windows.
231 if ostype is None or cputype is None:
232 return 'x86_64-pc-windows-msvc'
234 ostype = ostype.decode(default_encoding)
235 cputype = cputype.decode(default_encoding)
237 # The goal here is to come up with the same triple as LLVM would,
238 # at least for the subset of platforms we're willing to target.
240 'Darwin': 'apple-darwin',
241 'DragonFly': 'unknown-dragonfly',
242 'FreeBSD': 'unknown-freebsd',
243 'Haiku': 'unknown-haiku',
244 'NetBSD': 'unknown-netbsd',
245 'OpenBSD': 'unknown-openbsd'
248 # Consider the direct transformation first and then the special cases
249 if ostype in ostype_mapper:
250 ostype = ostype_mapper[ostype]
251 elif ostype == 'Linux':
252 os_from_sp = subprocess.check_output(
253 ['uname', '-o']).strip().decode(default_encoding)
254 if os_from_sp == 'Android':
255 ostype = 'linux-android'
257 ostype = 'unknown-linux-gnu'
258 elif ostype == 'SunOS':
259 ostype = 'pc-solaris'
260 # On Solaris, uname -m will return a machine classification instead
261 # of a cpu type, so uname -p is recommended instead. However, the
262 # output from that option is too generic for our purposes (it will
263 # always emit 'i386' on x86/amd64 systems). As such, isainfo -k
264 # must be used instead.
265 cputype = require(['isainfo', '-k']).decode(default_encoding)
266 # sparc cpus have sun as a target vendor
267 if 'sparc' in cputype:
268 ostype = 'sun-solaris'
269 elif ostype.startswith('MINGW'):
270 # msys' `uname` does not print gcc configuration, but prints msys
271 # configuration. so we cannot believe `uname -m`:
272 # msys1 is always i686 and msys2 is always x86_64.
273 # instead, msys defines $MSYSTEM which is MINGW32 on i686 and
275 ostype = 'pc-windows-gnu'
277 if os.environ.get('MSYSTEM') == 'MINGW64':
279 elif ostype.startswith('MSYS'):
280 ostype = 'pc-windows-gnu'
281 elif ostype.startswith('CYGWIN_NT'):
283 if ostype.endswith('WOW64'):
285 ostype = 'pc-windows-gnu'
286 elif sys.platform == 'win32':
287 # Some Windows platforms might have a `uname` command that returns a
288 # non-standard string (e.g. gnuwin32 tools returns `windows32`). In
289 # these cases, fall back to using sys.platform.
290 return 'x86_64-pc-windows-msvc'
292 err = "unknown OS type: {}".format(ostype)
295 if cputype in ['powerpc', 'riscv'] and ostype == 'unknown-freebsd':
296 cputype = subprocess.check_output(
297 ['uname', '-p']).strip().decode(default_encoding)
300 'aarch64': 'aarch64',
308 'powerpc': 'powerpc',
309 'powerpc64': 'powerpc64',
310 'powerpc64le': 'powerpc64le',
312 'ppc64': 'powerpc64',
313 'ppc64le': 'powerpc64le',
314 'riscv64': 'riscv64gc',
322 # Consider the direct transformation first and then the special cases
323 if cputype in cputype_mapper:
324 cputype = cputype_mapper[cputype]
325 elif cputype in {'xscale', 'arm'}:
327 if ostype == 'linux-android':
328 ostype = 'linux-androideabi'
329 elif ostype == 'unknown-freebsd':
330 cputype = subprocess.check_output(
331 ['uname', '-p']).strip().decode(default_encoding)
332 ostype = 'unknown-freebsd'
333 elif cputype == 'armv6l':
335 if ostype == 'linux-android':
336 ostype = 'linux-androideabi'
339 elif cputype in {'armv7l', 'armv8l'}:
341 if ostype == 'linux-android':
342 ostype = 'linux-androideabi'
345 elif cputype == 'mips':
346 if sys.byteorder == 'big':
348 elif sys.byteorder == 'little':
351 raise ValueError("unknown byteorder: {}".format(sys.byteorder))
352 elif cputype == 'mips64':
353 if sys.byteorder == 'big':
355 elif sys.byteorder == 'little':
358 raise ValueError('unknown byteorder: {}'.format(sys.byteorder))
359 # only the n64 ABI is supported, indicate it
361 elif cputype == 'sparc' or cputype == 'sparcv9' or cputype == 'sparc64':
364 err = "unknown cpu type: {}".format(cputype)
367 return "{}-{}".format(cputype, ostype)
370 @contextlib.contextmanager
371 def output(filepath):
372 tmp = filepath + '.tmp'
373 with open(tmp, 'w') as f:
376 if os.path.exists(filepath):
377 os.remove(filepath) # PermissionError/OSError on Win32 if in use
379 shutil.copy2(tmp, filepath)
382 os.rename(tmp, filepath)
385 class Stage0Toolchain:
386 def __init__(self, stage0_payload):
387 self.date = stage0_payload["date"]
388 self.version = stage0_payload["version"]
391 return self.version + "-" + self.date
394 class RustBuild(object):
395 """Provide all the methods required to build Rust"""
397 self.checksums_sha256 = {}
398 self.stage0_compiler = None
399 self.download_url = ''
403 self.config_toml = ''
405 self.use_locked_deps = False
406 self.use_vendored_sources = False
408 self.git_version = None
409 self.nix_deps_dir = None
410 self._should_fix_bins_and_dylibs = None
412 def download_toolchain(self):
413 """Fetch the build system for Rust, written in Rust
415 This method will build a cache directory, then it will fetch the
416 tarball which has the stage0 compiler used to then bootstrap the Rust
419 Each downloaded tarball is extracted, after that, the script
420 will move all the content to the right place.
422 rustc_channel = self.stage0_compiler.version
423 bin_root = self.bin_root()
425 key = self.stage0_compiler.date
426 if self.rustc().startswith(bin_root) and \
427 (not os.path.exists(self.rustc()) or
428 self.program_out_of_date(self.rustc_stamp(), key)):
429 if os.path.exists(bin_root):
430 shutil.rmtree(bin_root)
431 tarball_suffix = '.tar.gz' if lzma is None else '.tar.xz'
432 filename = "rust-std-{}-{}{}".format(
433 rustc_channel, self.build, tarball_suffix)
434 pattern = "rust-std-{}".format(self.build)
435 self._download_component_helper(filename, pattern, tarball_suffix)
436 filename = "rustc-{}-{}{}".format(rustc_channel, self.build,
438 self._download_component_helper(filename, "rustc", tarball_suffix)
439 filename = "cargo-{}-{}{}".format(rustc_channel, self.build,
441 self._download_component_helper(filename, "cargo", tarball_suffix)
442 if self.should_fix_bins_and_dylibs():
443 self.fix_bin_or_dylib("{}/bin/cargo".format(bin_root))
445 self.fix_bin_or_dylib("{}/bin/rustc".format(bin_root))
446 self.fix_bin_or_dylib("{}/bin/rustdoc".format(bin_root))
447 self.fix_bin_or_dylib("{}/libexec/rust-analyzer-proc-macro-srv".format(bin_root))
448 lib_dir = "{}/lib".format(bin_root)
449 for lib in os.listdir(lib_dir):
450 if lib.endswith(".so"):
451 self.fix_bin_or_dylib(os.path.join(lib_dir, lib))
453 with output(self.rustc_stamp()) as rust_stamp:
454 rust_stamp.write(key)
456 def _download_component_helper(
457 self, filename, pattern, tarball_suffix,
459 key = self.stage0_compiler.date
460 cache_dst = os.path.join(self.build_dir, "cache")
461 rustc_cache = os.path.join(cache_dst, key)
462 if not os.path.exists(rustc_cache):
463 os.makedirs(rustc_cache)
465 tarball = os.path.join(rustc_cache, filename)
466 if not os.path.exists(tarball):
469 "dist/{}/{}".format(key, filename),
471 self.checksums_sha256,
472 verbose=self.verbose,
474 unpack(tarball, tarball_suffix, self.bin_root(), match=pattern, verbose=self.verbose)
476 def should_fix_bins_and_dylibs(self):
477 """Whether or not `fix_bin_or_dylib` needs to be run; can only be True
480 if self._should_fix_bins_and_dylibs is not None:
481 return self._should_fix_bins_and_dylibs
484 default_encoding = sys.getdefaultencoding()
486 ostype = subprocess.check_output(
487 ['uname', '-s']).strip().decode(default_encoding)
488 except subprocess.CalledProcessError:
490 except OSError as reason:
491 if getattr(reason, 'winerror', None) is not None:
495 if ostype != "Linux":
498 # If the user has asked binaries to be patched for Nix, then
499 # don't check for NixOS or `/lib`.
500 if self.get_toml("patch-binaries-for-nix", "build") == "true":
503 # Use `/etc/os-release` instead of `/etc/NIXOS`.
504 # The latter one does not exist on NixOS when using tmpfs as root.
506 with open("/etc/os-release", "r") as f:
507 if not any(l.strip() in ("ID=nixos", "ID='nixos'", 'ID="nixos"') for l in f):
509 except FileNotFoundError:
511 if os.path.exists("/lib"):
516 answer = self._should_fix_bins_and_dylibs = get_answer()
518 print("info: You seem to be using Nix.")
521 def fix_bin_or_dylib(self, fname):
522 """Modifies the interpreter section of 'fname' to fix the dynamic linker,
523 or the RPATH section, to fix the dynamic library search path
525 This method is only required on NixOS and uses the PatchELF utility to
526 change the interpreter/RPATH of ELF executables.
528 Please see https://nixos.org/patchelf.html for more information
530 assert self._should_fix_bins_and_dylibs is True
531 print("attempting to patch", fname)
533 # Only build `.nix-deps` once.
534 nix_deps_dir = self.nix_deps_dir
536 # Run `nix-build` to "build" each dependency (which will likely reuse
537 # the existing `/nix/store` copy, or at most download a pre-built copy).
539 # Importantly, we create a gc-root called `.nix-deps` in the `build/`
540 # directory, but still reference the actual `/nix/store` path in the rpath
541 # as it makes it significantly more robust against changes to the location of
542 # the `.nix-deps` location.
544 # bintools: Needed for the path of `ld-linux.so` (via `nix-support/dynamic-linker`).
545 # zlib: Needed as a system dependency of `libLLVM-*.so`.
546 # patchelf: Needed for patching ELF binaries (see doc comment above).
547 nix_deps_dir = "{}/{}".format(self.build_dir, ".nix-deps")
549 with (import <nixpkgs> {});
551 name = "rust-stage0-dependencies";
560 subprocess.check_output([
561 "nix-build", "-E", nix_expr, "-o", nix_deps_dir,
563 except subprocess.CalledProcessError as reason:
564 print("warning: failed to call nix-build:", reason)
566 self.nix_deps_dir = nix_deps_dir
568 patchelf = "{}/bin/patchelf".format(nix_deps_dir)
570 # Relative default, all binary and dynamic libraries we ship
571 # appear to have this (even when `../lib` is redundant).
573 os.path.join(os.path.realpath(nix_deps_dir), "lib")
575 patchelf_args = ["--set-rpath", ":".join(rpath_entries)]
576 if not fname.endswith(".so"):
577 # Finally, set the corret .interp for binaries
578 with open("{}/nix-support/dynamic-linker".format(nix_deps_dir)) as dynamic_linker:
579 patchelf_args += ["--set-interpreter", dynamic_linker.read().rstrip()]
582 subprocess.check_output([patchelf] + patchelf_args + [fname])
583 except subprocess.CalledProcessError as reason:
584 print("warning: failed to call patchelf:", reason)
587 def rustc_stamp(self):
588 """Return the path for .rustc-stamp at the given stage
591 >>> rb.build_dir = "build"
592 >>> rb.rustc_stamp() == os.path.join("build", "stage0", ".rustc-stamp")
595 return os.path.join(self.bin_root(), '.rustc-stamp')
597 def program_out_of_date(self, stamp_path, key):
598 """Check if the given program stamp is out of date"""
599 if not os.path.exists(stamp_path) or self.clean:
601 with open(stamp_path, 'r') as stamp:
602 return key != stamp.read()
605 """Return the binary root directory for the given stage
608 >>> rb.build_dir = "build"
609 >>> rb.bin_root() == os.path.join("build", "stage0")
612 When the 'build' property is given should be a nested directory:
614 >>> rb.build = "devel"
615 >>> rb.bin_root() == os.path.join("build", "devel", "stage0")
619 return os.path.join(self.build_dir, self.build, subdir)
621 def get_toml(self, key, section=None):
622 """Returns the value of the given key in config.toml, otherwise returns None
625 >>> rb.config_toml = 'key1 = "value1"\\nkey2 = "value2"'
626 >>> rb.get_toml("key2")
629 If the key does not exist, the result is None:
631 >>> rb.get_toml("key3") is None
634 Optionally also matches the section the key appears in
636 >>> rb.config_toml = '[a]\\nkey = "value1"\\n[b]\\nkey = "value2"'
637 >>> rb.get_toml('key', 'a')
639 >>> rb.get_toml('key', 'b')
641 >>> rb.get_toml('key', 'c') is None
644 >>> rb.config_toml = 'key1 = true'
645 >>> rb.get_toml("key1")
650 for line in self.config_toml.splitlines():
651 section_match = re.match(r'^\s*\[(.*)\]\s*$', line)
652 if section_match is not None:
653 cur_section = section_match.group(1)
655 match = re.match(r'^{}\s*=(.*)$'.format(key), line)
656 if match is not None:
657 value = match.group(1)
658 if section is None or section == cur_section:
659 return self.get_string(value) or value.strip()
663 """Return config path for cargo"""
664 return self.program_config('cargo')
667 """Return config path for rustc"""
668 return self.program_config('rustc')
670 def program_config(self, program):
671 """Return config path for the given program at the given stage
674 >>> rb.config_toml = 'rustc = "rustc"\\n'
675 >>> rb.program_config('rustc')
677 >>> rb.config_toml = ''
678 >>> cargo_path = rb.program_config('cargo')
679 >>> cargo_path.rstrip(".exe") == os.path.join(rb.bin_root(),
683 config = self.get_toml(program)
685 return os.path.expanduser(config)
686 return os.path.join(self.bin_root(), "bin", "{}{}".format(program, EXE_SUFFIX))
689 def get_string(line):
690 """Return the value between double quotes
692 >>> RustBuild.get_string(' "devel" ')
694 >>> RustBuild.get_string(" 'devel' ")
696 >>> RustBuild.get_string('devel') is None
698 >>> RustBuild.get_string(' "devel ')
701 start = line.find('"')
703 end = start + 1 + line[start + 1:].find('"')
704 return line[start + 1:end]
705 start = line.find('\'')
707 end = start + 1 + line[start + 1:].find('\'')
708 return line[start + 1:end]
711 def bootstrap_binary(self):
712 """Return the path of the bootstrap binary
715 >>> rb.build_dir = "build"
716 >>> rb.bootstrap_binary() == os.path.join("build", "bootstrap",
717 ... "debug", "bootstrap")
720 return os.path.join(self.build_dir, "bootstrap", "debug", "bootstrap")
722 def build_bootstrap(self, color, verbose_count):
723 """Build bootstrap"""
724 print("Building bootstrap")
725 build_dir = os.path.join(self.build_dir, "bootstrap")
726 if self.clean and os.path.exists(build_dir):
727 shutil.rmtree(build_dir)
728 env = os.environ.copy()
729 # `CARGO_BUILD_TARGET` breaks bootstrap build.
730 # See also: <https://github.com/rust-lang/rust/issues/70208>.
731 if "CARGO_BUILD_TARGET" in env:
732 del env["CARGO_BUILD_TARGET"]
733 env["CARGO_TARGET_DIR"] = build_dir
734 env["RUSTC"] = self.rustc()
735 env["LD_LIBRARY_PATH"] = os.path.join(self.bin_root(), "lib") + \
736 (os.pathsep + env["LD_LIBRARY_PATH"]) \
737 if "LD_LIBRARY_PATH" in env else ""
738 env["DYLD_LIBRARY_PATH"] = os.path.join(self.bin_root(), "lib") + \
739 (os.pathsep + env["DYLD_LIBRARY_PATH"]) \
740 if "DYLD_LIBRARY_PATH" in env else ""
741 env["LIBRARY_PATH"] = os.path.join(self.bin_root(), "lib") + \
742 (os.pathsep + env["LIBRARY_PATH"]) \
743 if "LIBRARY_PATH" in env else ""
745 # Export Stage0 snapshot compiler related env variables
746 build_section = "target.{}".format(self.build)
747 host_triple_sanitized = self.build.replace("-", "_")
749 "CC": "cc", "CXX": "cxx", "LD": "linker", "AR": "ar", "RANLIB": "ranlib"
751 for var_name, toml_key in var_data.items():
752 toml_val = self.get_toml(toml_key, build_section)
754 env["{}_{}".format(var_name, host_triple_sanitized)] = toml_val
756 # preserve existing RUSTFLAGS
757 env.setdefault("RUSTFLAGS", "")
759 if self.get_toml("crt-static", build_section) == "true":
760 target_features += ["+crt-static"]
761 elif self.get_toml("crt-static", build_section) == "false":
762 target_features += ["-crt-static"]
764 env["RUSTFLAGS"] += " -C target-feature=" + (",".join(target_features))
765 target_linker = self.get_toml("linker", build_section)
766 if target_linker is not None:
767 env["RUSTFLAGS"] += " -C linker=" + target_linker
768 env["RUSTFLAGS"] += " -Wrust_2018_idioms -Wunused_lifetimes"
769 if self.get_toml("deny-warnings", "rust") != "false":
770 env["RUSTFLAGS"] += " -Dwarnings"
772 env["PATH"] = os.path.join(self.bin_root(), "bin") + \
773 os.pathsep + env["PATH"]
774 if not os.path.isfile(self.cargo()):
775 raise Exception("no cargo executable found at `{}`".format(
777 args = [self.cargo(), "build", "--manifest-path",
778 os.path.join(self.rust_root, "src/bootstrap/Cargo.toml")]
779 args.extend("--verbose" for _ in range(verbose_count))
780 if self.use_locked_deps:
781 args.append("--locked")
782 if self.use_vendored_sources:
783 args.append("--frozen")
784 if self.get_toml("metrics", "build"):
785 args.append("--features")
786 args.append("build-metrics")
787 if color == "always":
788 args.append("--color=always")
789 elif color == "never":
790 args.append("--color=never")
792 # Run this from the source directory so cargo finds .cargo/config
793 run(args, env=env, verbose=self.verbose, cwd=self.rust_root)
795 def build_triple(self):
796 """Build triple as in LLVM
798 Note that `default_build_triple` is moderately expensive,
799 so use `self.build` where possible.
801 config = self.get_toml('build')
802 return config or default_build_triple(self.verbose)
804 def check_vendored_status(self):
805 """Check that vendoring is configured properly"""
806 # keep this consistent with the equivalent check in rustbuild:
807 # https://github.com/rust-lang/rust/blob/a8a33cf27166d3eabaffc58ed3799e054af3b0c6/src/bootstrap/lib.rs#L399-L405
808 if 'SUDO_USER' in os.environ and not self.use_vendored_sources:
810 self.use_vendored_sources = True
811 print('info: looks like you\'re trying to run this command as root')
812 print(' and so in order to preserve your $HOME this will now')
813 print(' use vendored sources by default.')
815 cargo_dir = os.path.join(self.rust_root, '.cargo')
816 if self.use_vendored_sources:
817 vendor_dir = os.path.join(self.rust_root, 'vendor')
818 if not os.path.exists(vendor_dir):
819 sync_dirs = "--sync ./src/tools/rust-analyzer/Cargo.toml " \
820 "--sync ./compiler/rustc_codegen_cranelift/Cargo.toml " \
821 "--sync ./src/bootstrap/Cargo.toml "
822 print('error: vendoring required, but vendor directory does not exist.')
823 print(' Run `cargo vendor {}` to initialize the '
824 'vendor directory.'.format(sync_dirs))
825 print('Alternatively, use the pre-vendored `rustc-src` dist component.')
826 raise Exception("{} not found".format(vendor_dir))
828 if not os.path.exists(cargo_dir):
829 print('error: vendoring required, but .cargo/config does not exist.')
830 raise Exception("{} not found".format(cargo_dir))
832 if os.path.exists(cargo_dir):
833 shutil.rmtree(cargo_dir)
836 """Parse the command line arguments that the python script needs."""
837 parser = argparse.ArgumentParser(add_help=False)
838 parser.add_argument('-h', '--help', action='store_true')
839 parser.add_argument('--config')
840 parser.add_argument('--build-dir')
841 parser.add_argument('--build')
842 parser.add_argument('--color', choices=['always', 'never', 'auto'])
843 parser.add_argument('--clean', action='store_true')
844 parser.add_argument('-v', '--verbose', action='count', default=0)
846 return parser.parse_known_args(sys.argv)[0]
849 """Configure, fetch, build and run the initial bootstrap"""
850 # Configure initial bootstrap
852 build.rust_root = os.path.abspath(os.path.join(__file__, '../../..'))
853 build.verbose = args.verbose != 0
854 build.clean = args.clean
856 # Read from `--config`, then `RUST_BOOTSTRAP_CONFIG`, then `./config.toml`,
857 # then `config.toml` in the root directory.
858 toml_path = args.config or os.getenv('RUST_BOOTSTRAP_CONFIG')
859 using_default_path = toml_path is None
860 if using_default_path:
861 toml_path = 'config.toml'
862 if not os.path.exists(toml_path):
863 toml_path = os.path.join(build.rust_root, toml_path)
865 # Give a hard error if `--config` or `RUST_BOOTSTRAP_CONFIG` are set to a missing path,
866 # but not if `config.toml` hasn't been created.
867 if not using_default_path or os.path.exists(toml_path):
868 with open(toml_path) as config:
869 build.config_toml = config.read()
871 profile = build.get_toml('profile')
872 if profile is not None:
873 include_file = 'config.{}.toml'.format(profile)
874 include_dir = os.path.join(build.rust_root, 'src', 'bootstrap', 'defaults')
875 include_path = os.path.join(include_dir, include_file)
876 # HACK: This works because `build.get_toml()` returns the first match it finds for a
877 # specific key, so appending our defaults at the end allows the user to override them
878 with open(include_path) as included_toml:
879 build.config_toml += os.linesep + included_toml.read()
881 verbose_count = args.verbose
882 config_verbose_count = build.get_toml('verbose', 'build')
883 if config_verbose_count is not None:
884 verbose_count = max(args.verbose, int(config_verbose_count))
886 build.use_vendored_sources = build.get_toml('vendor', 'build') == 'true'
887 build.use_locked_deps = build.get_toml('locked-deps', 'build') == 'true'
889 build.check_vendored_status()
891 build_dir = args.build_dir or build.get_toml('build-dir', 'build') or 'build'
892 build.build_dir = os.path.abspath(build_dir)
894 with open(os.path.join(build.rust_root, "src", "stage0.json")) as f:
896 build.checksums_sha256 = data["checksums_sha256"]
897 build.stage0_compiler = Stage0Toolchain(data["compiler"])
898 build.download_url = os.getenv("RUSTUP_DIST_SERVER") or data["config"]["dist_server"]
900 build.build = args.build or build.build_triple()
902 if not os.path.exists(build.build_dir):
903 os.makedirs(build.build_dir)
905 # Fetch/build the bootstrap
906 build.download_toolchain()
908 build.build_bootstrap(args.color, verbose_count)
912 args = [build.bootstrap_binary()]
913 args.extend(sys.argv[1:])
914 env = os.environ.copy()
915 env["BOOTSTRAP_PARENT_ID"] = str(os.getpid())
916 env["BOOTSTRAP_PYTHON"] = sys.executable
917 run(args, env=env, verbose=build.verbose, is_bootstrap=True)
921 """Entry point for the bootstrap process"""
924 # x.py help <cmd> ...
925 if len(sys.argv) > 1 and sys.argv[1] == 'help':
929 help_triggered = args.help or len(sys.argv) == 1
931 # If the user is asking for help, let them know that the whole download-and-build
932 # process has to happen before anything is printed out.
935 "info: Downloading and building bootstrap before processing --help command.\n"
936 " See src/bootstrap/README.md for help with common commands."
942 except (SystemExit, KeyboardInterrupt) as error:
943 if hasattr(error, 'code') and isinstance(error.code, int):
944 exit_code = error.code
949 if not help_triggered:
950 print("Build completed successfully in", format_build_time(time() - start_time))
954 if __name__ == '__main__':