1 from __future__ import absolute_import, division, print_function
5 import distutils.version
16 from time import time, sleep
20 with tempfile.NamedTemporaryFile(delete=False) as temp_file:
21 temp_path = temp_file.name
22 with tarfile.open(temp_path, "w:xz"):
25 except tarfile.CompressionError:
28 def get(base, url, path, checksums, verbose=False):
29 with tempfile.NamedTemporaryFile(delete=False) as temp_file:
30 temp_path = temp_file.name
33 if url not in checksums:
34 raise RuntimeError(("src/stage0.json doesn't contain a checksum for {}. "
35 "Pre-built artifacts might not be available for this "
36 "target at this time, see https://doc.rust-lang.org/nightly"
37 "/rustc/platform-support.html for more information.")
39 sha256 = checksums[url]
40 if os.path.exists(path):
41 if verify(path, sha256, False):
43 print("using already-download file", path)
47 print("ignoring already-download file",
48 path, "due to failed verification")
50 download(temp_path, "{}/{}".format(base, url), True, verbose)
51 if not verify(temp_path, sha256, verbose):
52 raise RuntimeError("failed verification")
54 print("moving {} to {}".format(temp_path, path))
55 shutil.move(temp_path, path)
57 if os.path.isfile(temp_path):
59 print("removing", temp_path)
63 def download(path, url, probably_big, verbose):
66 _download(path, url, probably_big, verbose, True)
69 print("\nspurious failure, trying again")
70 _download(path, url, probably_big, verbose, False)
73 def _download(path, url, probably_big, verbose, exception):
74 # Try to use curl (potentially available on win32
75 # https://devblogs.microsoft.com/commandline/tar-and-curl-come-to-windows/)
77 # - If we are on win32 fallback to powershell
78 # - Otherwise raise the error if appropriate
79 if probably_big or verbose:
80 print("downloading {}".format(url))
82 platform_is_win32 = sys.platform == 'win32'
84 if probably_big or verbose:
88 # If curl is not present on Win32, we shoud not sys.exit
89 # but raise `CalledProcessError` or `OSError` instead
90 require(["curl", "--version"], exception=platform_is_win32)
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", "-o", path, url],
97 exception=True, # Will raise RuntimeError on failure
99 except (subprocess.CalledProcessError, OSError, RuntimeError):
100 # see http://serverfault.com/questions/301128/how-to-download
101 if platform_is_win32:
102 run(["PowerShell.exe", "/nologo", "-Command",
103 "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12;",
104 "(New-Object System.Net.WebClient).DownloadFile('{}', '{}')".format(url, path)],
107 # Check if the RuntimeError raised by run(curl) should be silenced
108 elif verbose or exception:
112 def verify(path, expected, verbose):
113 """Check if the sha256 sum of the given path is valid"""
115 print("verifying", path)
116 with open(path, "rb") as source:
117 found = hashlib.sha256(source.read()).hexdigest()
118 verified = found == expected
120 print("invalid checksum:\n"
122 " expected: {}".format(found, expected))
126 def unpack(tarball, tarball_suffix, dst, verbose=False, match=None):
127 """Unpack the given tarball file"""
128 print("extracting", tarball)
129 fname = os.path.basename(tarball).replace(tarball_suffix, "")
130 with contextlib.closing(tarfile.open(tarball)) as tar:
131 for member in tar.getnames():
132 if "/" not in member:
134 name = member.replace(fname + "/", "", 1)
135 if match is not None and not name.startswith(match):
137 name = name[len(match) + 1:]
139 dst_path = os.path.join(dst, name)
141 print(" extracting", member)
142 tar.extract(member, dst)
143 src_path = os.path.join(dst, member)
144 if os.path.isdir(src_path) and os.path.exists(dst_path):
146 shutil.move(src_path, dst_path)
147 shutil.rmtree(os.path.join(dst, fname))
150 def run(args, verbose=False, exception=False, is_bootstrap=False, **kwargs):
151 """Run a child program in a new process"""
153 print("running: " + ' '.join(args))
155 # Ensure that the .exe is used on Windows just in case a Linux ELF has been
156 # compiled in the same directory.
157 if os.name == 'nt' and not args[0].endswith('.exe'):
159 # Use Popen here instead of call() as it apparently allows powershell on
160 # Windows to not lock up waiting for input presumably.
161 ret = subprocess.Popen(args, **kwargs)
164 err = "failed to run: " + ' '.join(args)
165 if verbose or exception:
166 raise RuntimeError(err)
167 # For most failures, we definitely do want to print this error, or the user will have no
168 # idea what went wrong. But when we've successfully built bootstrap and it failed, it will
169 # have already printed an error above, so there's no need to print the exact command we're
177 def require(cmd, exit=True, exception=False):
178 '''Run a command, returning its output.
180 If `exception` is `True`, raise the error
181 Otherwise If `exit` is `True`, exit the process
184 return subprocess.check_output(cmd).strip()
185 except (subprocess.CalledProcessError, OSError) as exc:
189 print("error: unable to run `{}`: {}".format(' '.join(cmd), exc))
190 print("Please make sure it's installed and in the path.")
196 def format_build_time(duration):
197 """Return a nicer format for build time
199 >>> format_build_time('300')
202 return str(datetime.timedelta(seconds=int(duration)))
205 def default_build_triple(verbose):
206 """Build triple as in LLVM"""
207 # If the user already has a host build triple with an existing `rustc`
208 # install, use their preference. This fixes most issues with Windows builds
209 # being detected as GNU instead of MSVC.
210 default_encoding = sys.getdefaultencoding()
212 version = subprocess.check_output(["rustc", "--version", "--verbose"],
213 stderr=subprocess.DEVNULL)
214 version = version.decode(default_encoding)
215 host = next(x for x in version.split('\n') if x.startswith("host: "))
216 triple = host.split("host: ")[1]
218 print("detected default triple {} from pre-installed rustc".format(triple))
220 except Exception as e:
222 print("pre-installed rustc not detected: {}".format(e))
223 print("falling back to auto-detect")
225 required = sys.platform != 'win32'
226 ostype = require(["uname", "-s"], exit=required)
227 cputype = require(['uname', '-m'], exit=required)
229 # If we do not have `uname`, assume Windows.
230 if ostype is None or cputype is None:
231 return 'x86_64-pc-windows-msvc'
233 ostype = ostype.decode(default_encoding)
234 cputype = cputype.decode(default_encoding)
236 # The goal here is to come up with the same triple as LLVM would,
237 # at least for the subset of platforms we're willing to target.
239 'Darwin': 'apple-darwin',
240 'DragonFly': 'unknown-dragonfly',
241 'FreeBSD': 'unknown-freebsd',
242 'Haiku': 'unknown-haiku',
243 'NetBSD': 'unknown-netbsd',
244 'OpenBSD': 'unknown-openbsd'
247 # Consider the direct transformation first and then the special cases
248 if ostype in ostype_mapper:
249 ostype = ostype_mapper[ostype]
250 elif ostype == 'Linux':
251 os_from_sp = subprocess.check_output(
252 ['uname', '-o']).strip().decode(default_encoding)
253 if os_from_sp == 'Android':
254 ostype = 'linux-android'
256 ostype = 'unknown-linux-gnu'
257 elif ostype == 'SunOS':
258 ostype = 'pc-solaris'
259 # On Solaris, uname -m will return a machine classification instead
260 # of a cpu type, so uname -p is recommended instead. However, the
261 # output from that option is too generic for our purposes (it will
262 # always emit 'i386' on x86/amd64 systems). As such, isainfo -k
263 # must be used instead.
264 cputype = require(['isainfo', '-k']).decode(default_encoding)
265 # sparc cpus have sun as a target vendor
266 if 'sparc' in cputype:
267 ostype = 'sun-solaris'
268 elif ostype.startswith('MINGW'):
269 # msys' `uname` does not print gcc configuration, but prints msys
270 # configuration. so we cannot believe `uname -m`:
271 # msys1 is always i686 and msys2 is always x86_64.
272 # instead, msys defines $MSYSTEM which is MINGW32 on i686 and
274 ostype = 'pc-windows-gnu'
276 if os.environ.get('MSYSTEM') == 'MINGW64':
278 elif ostype.startswith('MSYS'):
279 ostype = 'pc-windows-gnu'
280 elif ostype.startswith('CYGWIN_NT'):
282 if ostype.endswith('WOW64'):
284 ostype = 'pc-windows-gnu'
285 elif sys.platform == 'win32':
286 # Some Windows platforms might have a `uname` command that returns a
287 # non-standard string (e.g. gnuwin32 tools returns `windows32`). In
288 # these cases, fall back to using sys.platform.
289 return 'x86_64-pc-windows-msvc'
291 err = "unknown OS type: {}".format(ostype)
294 if cputype in ['powerpc', 'riscv'] and ostype == 'unknown-freebsd':
295 cputype = subprocess.check_output(
296 ['uname', '-p']).strip().decode(default_encoding)
299 'aarch64': 'aarch64',
307 'powerpc': 'powerpc',
308 'powerpc64': 'powerpc64',
309 'powerpc64le': 'powerpc64le',
311 'ppc64': 'powerpc64',
312 'ppc64le': 'powerpc64le',
313 'riscv64': 'riscv64gc',
321 # Consider the direct transformation first and then the special cases
322 if cputype in cputype_mapper:
323 cputype = cputype_mapper[cputype]
324 elif cputype in {'xscale', 'arm'}:
326 if ostype == 'linux-android':
327 ostype = 'linux-androideabi'
328 elif ostype == 'unknown-freebsd':
329 cputype = subprocess.check_output(
330 ['uname', '-p']).strip().decode(default_encoding)
331 ostype = 'unknown-freebsd'
332 elif cputype == 'armv6l':
334 if ostype == 'linux-android':
335 ostype = 'linux-androideabi'
338 elif cputype in {'armv7l', 'armv8l'}:
340 if ostype == 'linux-android':
341 ostype = 'linux-androideabi'
344 elif cputype == 'mips':
345 if sys.byteorder == 'big':
347 elif sys.byteorder == 'little':
350 raise ValueError("unknown byteorder: {}".format(sys.byteorder))
351 elif cputype == 'mips64':
352 if sys.byteorder == 'big':
354 elif sys.byteorder == 'little':
357 raise ValueError('unknown byteorder: {}'.format(sys.byteorder))
358 # only the n64 ABI is supported, indicate it
360 elif cputype == 'sparc' or cputype == 'sparcv9' or cputype == 'sparc64':
363 err = "unknown cpu type: {}".format(cputype)
366 return "{}-{}".format(cputype, ostype)
369 @contextlib.contextmanager
370 def output(filepath):
371 tmp = filepath + '.tmp'
372 with open(tmp, 'w') as f:
375 if os.path.exists(filepath):
376 os.remove(filepath) # PermissionError/OSError on Win32 if in use
378 shutil.copy2(tmp, filepath)
381 os.rename(tmp, filepath)
384 class Stage0Toolchain:
385 def __init__(self, stage0_payload):
386 self.date = stage0_payload["date"]
387 self.version = stage0_payload["version"]
390 return self.version + "-" + self.date
393 class RustBuild(object):
394 """Provide all the methods required to build Rust"""
396 self.checksums_sha256 = {}
397 self.stage0_compiler = None
398 self._download_url = ''
402 self.config_toml = ''
404 self.use_locked_deps = ''
405 self.use_vendored_sources = ''
407 self.git_version = None
408 self.nix_deps_dir = None
410 def download_toolchain(self):
411 """Fetch the build system for Rust, written in Rust
413 This method will build a cache directory, then it will fetch the
414 tarball which has the stage0 compiler used to then bootstrap the Rust
417 Each downloaded tarball is extracted, after that, the script
418 will move all the content to the right place.
420 rustc_channel = self.stage0_compiler.version
421 bin_root = self.bin_root()
423 key = self.stage0_compiler.date
424 if self.rustc().startswith(bin_root) and \
425 (not os.path.exists(self.rustc()) or
426 self.program_out_of_date(self.rustc_stamp(), key)):
427 if os.path.exists(bin_root):
428 shutil.rmtree(bin_root)
429 tarball_suffix = '.tar.xz' if support_xz() else '.tar.gz'
430 filename = "rust-std-{}-{}{}".format(
431 rustc_channel, self.build, tarball_suffix)
432 pattern = "rust-std-{}".format(self.build)
433 self._download_component_helper(filename, pattern, tarball_suffix)
434 filename = "rustc-{}-{}{}".format(rustc_channel, self.build,
436 self._download_component_helper(filename, "rustc", tarball_suffix)
437 filename = "cargo-{}-{}{}".format(rustc_channel, self.build,
439 self._download_component_helper(filename, "cargo", tarball_suffix)
440 self.fix_bin_or_dylib("{}/bin/cargo".format(bin_root))
442 self.fix_bin_or_dylib("{}/bin/rustc".format(bin_root))
443 self.fix_bin_or_dylib("{}/bin/rustdoc".format(bin_root))
444 lib_dir = "{}/lib".format(bin_root)
445 for lib in os.listdir(lib_dir):
446 if lib.endswith(".so"):
447 self.fix_bin_or_dylib(os.path.join(lib_dir, lib))
448 with output(self.rustc_stamp()) as rust_stamp:
449 rust_stamp.write(key)
451 def _download_component_helper(
452 self, filename, pattern, tarball_suffix,
454 key = self.stage0_compiler.date
455 cache_dst = os.path.join(self.build_dir, "cache")
456 rustc_cache = os.path.join(cache_dst, key)
457 if not os.path.exists(rustc_cache):
458 os.makedirs(rustc_cache)
460 base = self._download_url
461 url = "dist/{}".format(key)
462 tarball = os.path.join(rustc_cache, filename)
463 if not os.path.exists(tarball):
466 "{}/{}".format(url, filename),
468 self.checksums_sha256,
469 verbose=self.verbose,
471 unpack(tarball, tarball_suffix, self.bin_root(), match=pattern, verbose=self.verbose)
473 def fix_bin_or_dylib(self, fname):
474 """Modifies the interpreter section of 'fname' to fix the dynamic linker,
475 or the RPATH section, to fix the dynamic library search path
477 This method is only required on NixOS and uses the PatchELF utility to
478 change the interpreter/RPATH of ELF executables.
480 Please see https://nixos.org/patchelf.html for more information
482 default_encoding = sys.getdefaultencoding()
484 ostype = subprocess.check_output(
485 ['uname', '-s']).strip().decode(default_encoding)
486 except subprocess.CalledProcessError:
488 except OSError as reason:
489 if getattr(reason, 'winerror', None) is not None:
493 if ostype != "Linux":
496 # If the user has asked binaries to be patched for Nix, then
497 # don't check for NixOS or `/lib`, just continue to the patching.
498 if self.get_toml('patch-binaries-for-nix', 'build') != 'true':
499 # Use `/etc/os-release` instead of `/etc/NIXOS`.
500 # The latter one does not exist on NixOS when using tmpfs as root.
502 with open("/etc/os-release", "r") as f:
503 if not any(l.strip() in ["ID=nixos", "ID='nixos'", 'ID="nixos"'] for l in f):
505 except FileNotFoundError:
507 if os.path.exists("/lib"):
510 # At this point we're pretty sure the user is running NixOS or
512 nix_os_msg = "info: you seem to be using Nix. Attempting to patch"
513 print(nix_os_msg, fname)
515 # Only build `.nix-deps` once.
516 nix_deps_dir = self.nix_deps_dir
518 # Run `nix-build` to "build" each dependency (which will likely reuse
519 # the existing `/nix/store` copy, or at most download a pre-built copy).
521 # Importantly, we create a gc-root called `.nix-deps` in the `build/`
522 # directory, but still reference the actual `/nix/store` path in the rpath
523 # as it makes it significantly more robust against changes to the location of
524 # the `.nix-deps` location.
526 # bintools: Needed for the path of `ld-linux.so` (via `nix-support/dynamic-linker`).
527 # zlib: Needed as a system dependency of `libLLVM-*.so`.
528 # patchelf: Needed for patching ELF binaries (see doc comment above).
529 nix_deps_dir = "{}/{}".format(self.build_dir, ".nix-deps")
531 with (import <nixpkgs> {});
533 name = "rust-stage0-dependencies";
542 subprocess.check_output([
543 "nix-build", "-E", nix_expr, "-o", nix_deps_dir,
545 except subprocess.CalledProcessError as reason:
546 print("warning: failed to call nix-build:", reason)
548 self.nix_deps_dir = nix_deps_dir
550 patchelf = "{}/bin/patchelf".format(nix_deps_dir)
552 # Relative default, all binary and dynamic libraries we ship
553 # appear to have this (even when `../lib` is redundant).
555 os.path.join(os.path.realpath(nix_deps_dir), "lib")
557 patchelf_args = ["--set-rpath", ":".join(rpath_entries)]
558 if not fname.endswith(".so"):
559 # Finally, set the corret .interp for binaries
560 with open("{}/nix-support/dynamic-linker".format(nix_deps_dir)) as dynamic_linker:
561 patchelf_args += ["--set-interpreter", dynamic_linker.read().rstrip()]
564 subprocess.check_output([patchelf] + patchelf_args + [fname])
565 except subprocess.CalledProcessError as reason:
566 print("warning: failed to call patchelf:", reason)
569 def rustc_stamp(self):
570 """Return the path for .rustc-stamp at the given stage
573 >>> rb.build_dir = "build"
574 >>> rb.rustc_stamp() == os.path.join("build", "stage0", ".rustc-stamp")
577 return os.path.join(self.bin_root(), '.rustc-stamp')
579 def program_out_of_date(self, stamp_path, key):
580 """Check if the given program stamp is out of date"""
581 if not os.path.exists(stamp_path) or self.clean:
583 with open(stamp_path, 'r') as stamp:
584 return key != stamp.read()
587 """Return the binary root directory for the given stage
590 >>> rb.build_dir = "build"
591 >>> rb.bin_root() == os.path.join("build", "stage0")
594 When the 'build' property is given should be a nested directory:
596 >>> rb.build = "devel"
597 >>> rb.bin_root() == os.path.join("build", "devel", "stage0")
601 return os.path.join(self.build_dir, self.build, subdir)
603 def get_toml(self, key, section=None):
604 """Returns the value of the given key in config.toml, otherwise returns None
607 >>> rb.config_toml = 'key1 = "value1"\\nkey2 = "value2"'
608 >>> rb.get_toml("key2")
611 If the key does not exist, the result is None:
613 >>> rb.get_toml("key3") is None
616 Optionally also matches the section the key appears in
618 >>> rb.config_toml = '[a]\\nkey = "value1"\\n[b]\\nkey = "value2"'
619 >>> rb.get_toml('key', 'a')
621 >>> rb.get_toml('key', 'b')
623 >>> rb.get_toml('key', 'c') is None
626 >>> rb.config_toml = 'key1 = true'
627 >>> rb.get_toml("key1")
632 for line in self.config_toml.splitlines():
633 section_match = re.match(r'^\s*\[(.*)\]\s*$', line)
634 if section_match is not None:
635 cur_section = section_match.group(1)
637 match = re.match(r'^{}\s*=(.*)$'.format(key), line)
638 if match is not None:
639 value = match.group(1)
640 if section is None or section == cur_section:
641 return self.get_string(value) or value.strip()
645 """Return config path for cargo"""
646 return self.program_config('cargo')
649 """Return config path for rustc"""
650 return self.program_config('rustc')
652 def program_config(self, program):
653 """Return config path for the given program at the given stage
656 >>> rb.config_toml = 'rustc = "rustc"\\n'
657 >>> rb.program_config('rustc')
659 >>> rb.config_toml = ''
660 >>> cargo_path = rb.program_config('cargo')
661 >>> cargo_path.rstrip(".exe") == os.path.join(rb.bin_root(),
665 config = self.get_toml(program)
667 return os.path.expanduser(config)
668 return os.path.join(self.bin_root(), "bin", "{}{}".format(
669 program, self.exe_suffix()))
672 def get_string(line):
673 """Return the value between double quotes
675 >>> RustBuild.get_string(' "devel" ')
677 >>> RustBuild.get_string(" 'devel' ")
679 >>> RustBuild.get_string('devel') is None
681 >>> RustBuild.get_string(' "devel ')
684 start = line.find('"')
686 end = start + 1 + line[start + 1:].find('"')
687 return line[start + 1:end]
688 start = line.find('\'')
690 end = start + 1 + line[start + 1:].find('\'')
691 return line[start + 1:end]
696 """Return a suffix for executables"""
697 if sys.platform == 'win32':
701 def bootstrap_binary(self):
702 """Return the path of the bootstrap binary
705 >>> rb.build_dir = "build"
706 >>> rb.bootstrap_binary() == os.path.join("build", "bootstrap",
707 ... "debug", "bootstrap")
710 return os.path.join(self.build_dir, "bootstrap", "debug", "bootstrap")
712 def build_bootstrap(self, color):
713 """Build bootstrap"""
714 print("Building rustbuild")
715 build_dir = os.path.join(self.build_dir, "bootstrap")
716 if self.clean and os.path.exists(build_dir):
717 shutil.rmtree(build_dir)
718 env = os.environ.copy()
719 # `CARGO_BUILD_TARGET` breaks bootstrap build.
720 # See also: <https://github.com/rust-lang/rust/issues/70208>.
721 if "CARGO_BUILD_TARGET" in env:
722 del env["CARGO_BUILD_TARGET"]
723 env["CARGO_TARGET_DIR"] = build_dir
724 env["RUSTC"] = self.rustc()
725 env["LD_LIBRARY_PATH"] = os.path.join(self.bin_root(), "lib") + \
726 (os.pathsep + env["LD_LIBRARY_PATH"]) \
727 if "LD_LIBRARY_PATH" in env else ""
728 env["DYLD_LIBRARY_PATH"] = os.path.join(self.bin_root(), "lib") + \
729 (os.pathsep + env["DYLD_LIBRARY_PATH"]) \
730 if "DYLD_LIBRARY_PATH" in env else ""
731 env["LIBRARY_PATH"] = os.path.join(self.bin_root(), "lib") + \
732 (os.pathsep + env["LIBRARY_PATH"]) \
733 if "LIBRARY_PATH" in env else ""
735 # preserve existing RUSTFLAGS
736 env.setdefault("RUSTFLAGS", "")
737 build_section = "target.{}".format(self.build)
739 if self.get_toml("crt-static", build_section) == "true":
740 target_features += ["+crt-static"]
741 elif self.get_toml("crt-static", build_section) == "false":
742 target_features += ["-crt-static"]
744 env["RUSTFLAGS"] += " -C target-feature=" + (",".join(target_features))
745 target_linker = self.get_toml("linker", build_section)
746 if target_linker is not None:
747 env["RUSTFLAGS"] += " -C linker=" + target_linker
748 env["RUSTFLAGS"] += " -Wrust_2018_idioms -Wunused_lifetimes"
749 env["RUSTFLAGS"] += " -Wsemicolon_in_expressions_from_macros"
750 if self.get_toml("deny-warnings", "rust") != "false":
751 env["RUSTFLAGS"] += " -Dwarnings"
753 env["PATH"] = os.path.join(self.bin_root(), "bin") + \
754 os.pathsep + env["PATH"]
755 if not os.path.isfile(self.cargo()):
756 raise Exception("no cargo executable found at `{}`".format(
758 args = [self.cargo(), "build", "--manifest-path",
759 os.path.join(self.rust_root, "src/bootstrap/Cargo.toml")]
760 for _ in range(0, self.verbose):
761 args.append("--verbose")
762 if self.use_locked_deps:
763 args.append("--locked")
764 if self.use_vendored_sources:
765 args.append("--frozen")
766 if self.get_toml("metrics", "build"):
767 args.append("--features")
768 args.append("build-metrics")
769 if color == "always":
770 args.append("--color=always")
771 elif color == "never":
772 args.append("--color=never")
774 run(args, env=env, verbose=self.verbose)
776 def build_triple(self):
777 """Build triple as in LLVM
779 Note that `default_build_triple` is moderately expensive,
780 so use `self.build` where possible.
782 config = self.get_toml('build')
785 return default_build_triple(self.verbose)
787 def set_dist_environment(self, url):
788 """Set download URL for normal environment"""
789 if 'RUSTUP_DIST_SERVER' in os.environ:
790 self._download_url = os.environ['RUSTUP_DIST_SERVER']
792 self._download_url = url
794 def check_vendored_status(self):
795 """Check that vendoring is configured properly"""
796 if 'SUDO_USER' in os.environ and not self.use_vendored_sources:
798 self.use_vendored_sources = True
799 print('info: looks like you\'re trying to run this command as root')
800 print(' and so in order to preserve your $HOME this will now')
801 print(' use vendored sources by default.')
803 cargo_dir = os.path.join(self.rust_root, '.cargo')
804 if self.use_vendored_sources:
805 vendor_dir = os.path.join(self.rust_root, 'vendor')
806 if not os.path.exists(vendor_dir):
807 sync_dirs = "--sync ./src/tools/rust-analyzer/Cargo.toml " \
808 "--sync ./compiler/rustc_codegen_cranelift/Cargo.toml " \
809 "--sync ./src/bootstrap/Cargo.toml "
810 print('error: vendoring required, but vendor directory does not exist.')
811 print(' Run `cargo vendor {}` to initialize the '
812 'vendor directory.'.format(sync_dirs))
813 print('Alternatively, use the pre-vendored `rustc-src` dist component.')
814 raise Exception("{} not found".format(vendor_dir))
816 if not os.path.exists(cargo_dir):
817 print('error: vendoring required, but .cargo/config does not exist.')
818 raise Exception("{} not found".format(cargo_dir))
820 if os.path.exists(cargo_dir):
821 shutil.rmtree(cargo_dir)
823 def bootstrap(help_triggered):
824 """Configure, fetch, build and run the initial bootstrap"""
826 # If the user is asking for help, let them know that the whole download-and-build
827 # process has to happen before anything is printed out.
829 print("info: Downloading and building bootstrap before processing --help")
830 print(" command. See src/bootstrap/README.md for help with common")
833 parser = argparse.ArgumentParser(description='Build rust')
834 parser.add_argument('--config')
835 parser.add_argument('--build-dir')
836 parser.add_argument('--build')
837 parser.add_argument('--color', choices=['always', 'never', 'auto'])
838 parser.add_argument('--clean', action='store_true')
839 parser.add_argument('-v', '--verbose', action='count', default=0)
841 args = [a for a in sys.argv if a != '-h' and a != '--help']
842 args, _ = parser.parse_known_args(args)
844 # Configure initial bootstrap
846 build.rust_root = os.path.abspath(os.path.join(__file__, '../../..'))
847 build.verbose = args.verbose
848 build.clean = args.clean
850 # Read from `--config`, then `RUST_BOOTSTRAP_CONFIG`, then `./config.toml`,
851 # then `config.toml` in the root directory.
852 toml_path = args.config or os.getenv('RUST_BOOTSTRAP_CONFIG')
853 using_default_path = toml_path is None
854 if using_default_path:
855 toml_path = 'config.toml'
856 if not os.path.exists(toml_path):
857 toml_path = os.path.join(build.rust_root, toml_path)
859 # Give a hard error if `--config` or `RUST_BOOTSTRAP_CONFIG` are set to a missing path,
860 # but not if `config.toml` hasn't been created.
861 if not using_default_path or os.path.exists(toml_path):
862 with open(toml_path) as config:
863 build.config_toml = config.read()
865 profile = build.get_toml('profile')
866 if profile is not None:
867 include_file = 'config.{}.toml'.format(profile)
868 include_dir = os.path.join(build.rust_root, 'src', 'bootstrap', 'defaults')
869 include_path = os.path.join(include_dir, include_file)
870 # HACK: This works because `build.get_toml()` returns the first match it finds for a
871 # specific key, so appending our defaults at the end allows the user to override them
872 with open(include_path) as included_toml:
873 build.config_toml += os.linesep + included_toml.read()
875 config_verbose = build.get_toml('verbose', 'build')
876 if config_verbose is not None:
877 build.verbose = max(build.verbose, int(config_verbose))
879 build.use_vendored_sources = build.get_toml('vendor', 'build') == 'true'
881 build.use_locked_deps = build.get_toml('locked-deps', 'build') == 'true'
883 build.check_vendored_status()
885 build_dir = args.build_dir or build.get_toml('build-dir', 'build') or 'build'
886 build.build_dir = os.path.abspath(build_dir)
888 with open(os.path.join(build.rust_root, "src", "stage0.json")) as f:
890 build.checksums_sha256 = data["checksums_sha256"]
891 build.stage0_compiler = Stage0Toolchain(data["compiler"])
893 build.set_dist_environment(data["config"]["dist_server"])
895 build.build = args.build or build.build_triple()
897 if not os.path.exists(build.build_dir):
898 os.makedirs(build.build_dir)
900 # Fetch/build the bootstrap
901 build.download_toolchain()
903 build.build_bootstrap(args.color)
907 args = [build.bootstrap_binary()]
908 args.extend(sys.argv[1:])
909 env = os.environ.copy()
910 env["BOOTSTRAP_PARENT_ID"] = str(os.getpid())
911 env["BOOTSTRAP_PYTHON"] = sys.executable
912 run(args, env=env, verbose=build.verbose, is_bootstrap=True)
916 """Entry point for the bootstrap process"""
919 # x.py help <cmd> ...
920 if len(sys.argv) > 1 and sys.argv[1] == 'help':
921 sys.argv = [sys.argv[0], '-h'] + sys.argv[2:]
924 '-h' in sys.argv) or ('--help' in sys.argv) or (len(sys.argv) == 1)
926 bootstrap(help_triggered)
927 if not help_triggered:
928 print("Build completed successfully in {}".format(
929 format_build_time(time() - start_time)))
930 except (SystemExit, KeyboardInterrupt) as error:
931 if hasattr(error, 'code') and isinstance(error.code, int):
932 exit_code = error.code
936 if not help_triggered:
937 print("Build completed unsuccessfully in {}".format(
938 format_build_time(time() - start_time)))
942 if __name__ == '__main__':