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 # Use Popen here instead of call() as it apparently allows powershell on
156 # Windows to not lock up waiting for input presumably.
157 ret = subprocess.Popen(args, **kwargs)
160 err = "failed to run: " + ' '.join(args)
161 if verbose or exception:
162 raise RuntimeError(err)
163 # For most failures, we definitely do want to print this error, or the user will have no
164 # idea what went wrong. But when we've successfully built bootstrap and it failed, it will
165 # have already printed an error above, so there's no need to print the exact command we're
173 def require(cmd, exit=True, exception=False):
174 '''Run a command, returning its output.
176 If `exception` is `True`, raise the error
177 Otherwise If `exit` is `True`, exit the process
180 return subprocess.check_output(cmd).strip()
181 except (subprocess.CalledProcessError, OSError) as exc:
185 print("error: unable to run `{}`: {}".format(' '.join(cmd), exc))
186 print("Please make sure it's installed and in the path.")
192 def format_build_time(duration):
193 """Return a nicer format for build time
195 >>> format_build_time('300')
198 return str(datetime.timedelta(seconds=int(duration)))
201 def default_build_triple(verbose):
202 """Build triple as in LLVM"""
203 # If the user already has a host build triple with an existing `rustc`
204 # install, use their preference. This fixes most issues with Windows builds
205 # being detected as GNU instead of MSVC.
206 default_encoding = sys.getdefaultencoding()
208 version = subprocess.check_output(["rustc", "--version", "--verbose"],
209 stderr=subprocess.DEVNULL)
210 version = version.decode(default_encoding)
211 host = next(x for x in version.split('\n') if x.startswith("host: "))
212 triple = host.split("host: ")[1]
214 print("detected default triple {} from pre-installed rustc".format(triple))
216 except Exception as e:
218 print("pre-installed rustc not detected: {}".format(e))
219 print("falling back to auto-detect")
221 required = sys.platform != 'win32'
222 ostype = require(["uname", "-s"], exit=required)
223 cputype = require(['uname', '-m'], exit=required)
225 # If we do not have `uname`, assume Windows.
226 if ostype is None or cputype is None:
227 return 'x86_64-pc-windows-msvc'
229 ostype = ostype.decode(default_encoding)
230 cputype = cputype.decode(default_encoding)
232 # The goal here is to come up with the same triple as LLVM would,
233 # at least for the subset of platforms we're willing to target.
235 'Darwin': 'apple-darwin',
236 'DragonFly': 'unknown-dragonfly',
237 'FreeBSD': 'unknown-freebsd',
238 'Haiku': 'unknown-haiku',
239 'NetBSD': 'unknown-netbsd',
240 'OpenBSD': 'unknown-openbsd'
243 # Consider the direct transformation first and then the special cases
244 if ostype in ostype_mapper:
245 ostype = ostype_mapper[ostype]
246 elif ostype == 'Linux':
247 os_from_sp = subprocess.check_output(
248 ['uname', '-o']).strip().decode(default_encoding)
249 if os_from_sp == 'Android':
250 ostype = 'linux-android'
252 ostype = 'unknown-linux-gnu'
253 elif ostype == 'SunOS':
254 ostype = 'pc-solaris'
255 # On Solaris, uname -m will return a machine classification instead
256 # of a cpu type, so uname -p is recommended instead. However, the
257 # output from that option is too generic for our purposes (it will
258 # always emit 'i386' on x86/amd64 systems). As such, isainfo -k
259 # must be used instead.
260 cputype = require(['isainfo', '-k']).decode(default_encoding)
261 # sparc cpus have sun as a target vendor
262 if 'sparc' in cputype:
263 ostype = 'sun-solaris'
264 elif ostype.startswith('MINGW'):
265 # msys' `uname` does not print gcc configuration, but prints msys
266 # configuration. so we cannot believe `uname -m`:
267 # msys1 is always i686 and msys2 is always x86_64.
268 # instead, msys defines $MSYSTEM which is MINGW32 on i686 and
270 ostype = 'pc-windows-gnu'
272 if os.environ.get('MSYSTEM') == 'MINGW64':
274 elif ostype.startswith('MSYS'):
275 ostype = 'pc-windows-gnu'
276 elif ostype.startswith('CYGWIN_NT'):
278 if ostype.endswith('WOW64'):
280 ostype = 'pc-windows-gnu'
281 elif sys.platform == 'win32':
282 # Some Windows platforms might have a `uname` command that returns a
283 # non-standard string (e.g. gnuwin32 tools returns `windows32`). In
284 # these cases, fall back to using sys.platform.
285 return 'x86_64-pc-windows-msvc'
287 err = "unknown OS type: {}".format(ostype)
290 if cputype in ['powerpc', 'riscv'] and ostype == 'unknown-freebsd':
291 cputype = subprocess.check_output(
292 ['uname', '-p']).strip().decode(default_encoding)
295 'aarch64': 'aarch64',
303 'powerpc': 'powerpc',
304 'powerpc64': 'powerpc64',
305 'powerpc64le': 'powerpc64le',
307 'ppc64': 'powerpc64',
308 'ppc64le': 'powerpc64le',
309 'riscv64': 'riscv64gc',
317 # Consider the direct transformation first and then the special cases
318 if cputype in cputype_mapper:
319 cputype = cputype_mapper[cputype]
320 elif cputype in {'xscale', 'arm'}:
322 if ostype == 'linux-android':
323 ostype = 'linux-androideabi'
324 elif ostype == 'unknown-freebsd':
325 cputype = subprocess.check_output(
326 ['uname', '-p']).strip().decode(default_encoding)
327 ostype = 'unknown-freebsd'
328 elif cputype == 'armv6l':
330 if ostype == 'linux-android':
331 ostype = 'linux-androideabi'
334 elif cputype in {'armv7l', 'armv8l'}:
336 if ostype == 'linux-android':
337 ostype = 'linux-androideabi'
340 elif cputype == 'mips':
341 if sys.byteorder == 'big':
343 elif sys.byteorder == 'little':
346 raise ValueError("unknown byteorder: {}".format(sys.byteorder))
347 elif cputype == 'mips64':
348 if sys.byteorder == 'big':
350 elif sys.byteorder == 'little':
353 raise ValueError('unknown byteorder: {}'.format(sys.byteorder))
354 # only the n64 ABI is supported, indicate it
356 elif cputype == 'sparc' or cputype == 'sparcv9' or cputype == 'sparc64':
359 err = "unknown cpu type: {}".format(cputype)
362 return "{}-{}".format(cputype, ostype)
365 @contextlib.contextmanager
366 def output(filepath):
367 tmp = filepath + '.tmp'
368 with open(tmp, 'w') as f:
371 if os.path.exists(filepath):
372 os.remove(filepath) # PermissionError/OSError on Win32 if in use
374 shutil.copy2(tmp, filepath)
377 os.rename(tmp, filepath)
380 class Stage0Toolchain:
381 def __init__(self, stage0_payload):
382 self.date = stage0_payload["date"]
383 self.version = stage0_payload["version"]
386 return self.version + "-" + self.date
389 class RustBuild(object):
390 """Provide all the methods required to build Rust"""
392 self.checksums_sha256 = {}
393 self.stage0_compiler = None
394 self._download_url = ''
398 self.config_toml = ''
400 self.use_locked_deps = ''
401 self.use_vendored_sources = ''
403 self.git_version = None
404 self.nix_deps_dir = None
406 def download_toolchain(self):
407 """Fetch the build system for Rust, written in Rust
409 This method will build a cache directory, then it will fetch the
410 tarball which has the stage0 compiler used to then bootstrap the Rust
413 Each downloaded tarball is extracted, after that, the script
414 will move all the content to the right place.
416 rustc_channel = self.stage0_compiler.version
417 bin_root = self.bin_root()
419 key = self.stage0_compiler.date
420 if self.rustc().startswith(bin_root) and \
421 (not os.path.exists(self.rustc()) or
422 self.program_out_of_date(self.rustc_stamp(), key)):
423 if os.path.exists(bin_root):
424 shutil.rmtree(bin_root)
425 tarball_suffix = '.tar.xz' if support_xz() else '.tar.gz'
426 filename = "rust-std-{}-{}{}".format(
427 rustc_channel, self.build, tarball_suffix)
428 pattern = "rust-std-{}".format(self.build)
429 self._download_component_helper(filename, pattern, tarball_suffix)
430 filename = "rustc-{}-{}{}".format(rustc_channel, self.build,
432 self._download_component_helper(filename, "rustc", tarball_suffix)
433 filename = "cargo-{}-{}{}".format(rustc_channel, self.build,
435 self._download_component_helper(filename, "cargo", tarball_suffix)
436 self.fix_bin_or_dylib("{}/bin/cargo".format(bin_root))
438 self.fix_bin_or_dylib("{}/bin/rustc".format(bin_root))
439 self.fix_bin_or_dylib("{}/bin/rustdoc".format(bin_root))
440 lib_dir = "{}/lib".format(bin_root)
441 for lib in os.listdir(lib_dir):
442 if lib.endswith(".so"):
443 self.fix_bin_or_dylib(os.path.join(lib_dir, lib))
444 with output(self.rustc_stamp()) as rust_stamp:
445 rust_stamp.write(key)
447 def _download_component_helper(
448 self, filename, pattern, tarball_suffix,
450 key = self.stage0_compiler.date
451 cache_dst = os.path.join(self.build_dir, "cache")
452 rustc_cache = os.path.join(cache_dst, key)
453 if not os.path.exists(rustc_cache):
454 os.makedirs(rustc_cache)
456 base = self._download_url
457 url = "dist/{}".format(key)
458 tarball = os.path.join(rustc_cache, filename)
459 if not os.path.exists(tarball):
462 "{}/{}".format(url, filename),
464 self.checksums_sha256,
465 verbose=self.verbose,
467 unpack(tarball, tarball_suffix, self.bin_root(), match=pattern, verbose=self.verbose)
469 def fix_bin_or_dylib(self, fname):
470 """Modifies the interpreter section of 'fname' to fix the dynamic linker,
471 or the RPATH section, to fix the dynamic library search path
473 This method is only required on NixOS and uses the PatchELF utility to
474 change the interpreter/RPATH of ELF executables.
476 Please see https://nixos.org/patchelf.html for more information
478 default_encoding = sys.getdefaultencoding()
480 ostype = subprocess.check_output(
481 ['uname', '-s']).strip().decode(default_encoding)
482 except subprocess.CalledProcessError:
484 except OSError as reason:
485 if getattr(reason, 'winerror', None) is not None:
489 if ostype != "Linux":
492 # If the user has asked binaries to be patched for Nix, then
493 # don't check for NixOS or `/lib`, just continue to the patching.
494 if self.get_toml('patch-binaries-for-nix', 'build') != 'true':
495 # Use `/etc/os-release` instead of `/etc/NIXOS`.
496 # The latter one does not exist on NixOS when using tmpfs as root.
498 with open("/etc/os-release", "r") as f:
499 if not any(l.strip() in ["ID=nixos", "ID='nixos'", 'ID="nixos"'] for l in f):
501 except FileNotFoundError:
503 if os.path.exists("/lib"):
506 # At this point we're pretty sure the user is running NixOS or
508 nix_os_msg = "info: you seem to be using Nix. Attempting to patch"
509 print(nix_os_msg, fname)
511 # Only build `.nix-deps` once.
512 nix_deps_dir = self.nix_deps_dir
514 # Run `nix-build` to "build" each dependency (which will likely reuse
515 # the existing `/nix/store` copy, or at most download a pre-built copy).
517 # Importantly, we create a gc-root called `.nix-deps` in the `build/`
518 # directory, but still reference the actual `/nix/store` path in the rpath
519 # as it makes it significantly more robust against changes to the location of
520 # the `.nix-deps` location.
522 # bintools: Needed for the path of `ld-linux.so` (via `nix-support/dynamic-linker`).
523 # zlib: Needed as a system dependency of `libLLVM-*.so`.
524 # patchelf: Needed for patching ELF binaries (see doc comment above).
525 nix_deps_dir = "{}/{}".format(self.build_dir, ".nix-deps")
527 with (import <nixpkgs> {});
529 name = "rust-stage0-dependencies";
538 subprocess.check_output([
539 "nix-build", "-E", nix_expr, "-o", nix_deps_dir,
541 except subprocess.CalledProcessError as reason:
542 print("warning: failed to call nix-build:", reason)
544 self.nix_deps_dir = nix_deps_dir
546 patchelf = "{}/bin/patchelf".format(nix_deps_dir)
548 # Relative default, all binary and dynamic libraries we ship
549 # appear to have this (even when `../lib` is redundant).
551 os.path.join(os.path.realpath(nix_deps_dir), "lib")
553 patchelf_args = ["--set-rpath", ":".join(rpath_entries)]
554 if not fname.endswith(".so"):
555 # Finally, set the corret .interp for binaries
556 with open("{}/nix-support/dynamic-linker".format(nix_deps_dir)) as dynamic_linker:
557 patchelf_args += ["--set-interpreter", dynamic_linker.read().rstrip()]
560 subprocess.check_output([patchelf] + patchelf_args + [fname])
561 except subprocess.CalledProcessError as reason:
562 print("warning: failed to call patchelf:", reason)
565 def rustc_stamp(self):
566 """Return the path for .rustc-stamp at the given stage
569 >>> rb.build_dir = "build"
570 >>> rb.rustc_stamp() == os.path.join("build", "stage0", ".rustc-stamp")
573 return os.path.join(self.bin_root(), '.rustc-stamp')
575 def program_out_of_date(self, stamp_path, key):
576 """Check if the given program stamp is out of date"""
577 if not os.path.exists(stamp_path) or self.clean:
579 with open(stamp_path, 'r') as stamp:
580 return key != stamp.read()
583 """Return the binary root directory for the given stage
586 >>> rb.build_dir = "build"
587 >>> rb.bin_root() == os.path.join("build", "stage0")
590 When the 'build' property is given should be a nested directory:
592 >>> rb.build = "devel"
593 >>> rb.bin_root() == os.path.join("build", "devel", "stage0")
597 return os.path.join(self.build_dir, self.build, subdir)
599 def get_toml(self, key, section=None):
600 """Returns the value of the given key in config.toml, otherwise returns None
603 >>> rb.config_toml = 'key1 = "value1"\\nkey2 = "value2"'
604 >>> rb.get_toml("key2")
607 If the key does not exist, the result is None:
609 >>> rb.get_toml("key3") is None
612 Optionally also matches the section the key appears in
614 >>> rb.config_toml = '[a]\\nkey = "value1"\\n[b]\\nkey = "value2"'
615 >>> rb.get_toml('key', 'a')
617 >>> rb.get_toml('key', 'b')
619 >>> rb.get_toml('key', 'c') is None
622 >>> rb.config_toml = 'key1 = true'
623 >>> rb.get_toml("key1")
628 for line in self.config_toml.splitlines():
629 section_match = re.match(r'^\s*\[(.*)\]\s*$', line)
630 if section_match is not None:
631 cur_section = section_match.group(1)
633 match = re.match(r'^{}\s*=(.*)$'.format(key), line)
634 if match is not None:
635 value = match.group(1)
636 if section is None or section == cur_section:
637 return self.get_string(value) or value.strip()
641 """Return config path for cargo"""
642 return self.program_config('cargo')
645 """Return config path for rustc"""
646 return self.program_config('rustc')
648 def program_config(self, program):
649 """Return config path for the given program at the given stage
652 >>> rb.config_toml = 'rustc = "rustc"\\n'
653 >>> rb.program_config('rustc')
655 >>> rb.config_toml = ''
656 >>> cargo_path = rb.program_config('cargo')
657 >>> cargo_path.rstrip(".exe") == os.path.join(rb.bin_root(),
661 config = self.get_toml(program)
663 return os.path.expanduser(config)
664 return os.path.join(self.bin_root(), "bin", "{}{}".format(
665 program, self.exe_suffix()))
668 def get_string(line):
669 """Return the value between double quotes
671 >>> RustBuild.get_string(' "devel" ')
673 >>> RustBuild.get_string(" 'devel' ")
675 >>> RustBuild.get_string('devel') is None
677 >>> RustBuild.get_string(' "devel ')
680 start = line.find('"')
682 end = start + 1 + line[start + 1:].find('"')
683 return line[start + 1:end]
684 start = line.find('\'')
686 end = start + 1 + line[start + 1:].find('\'')
687 return line[start + 1:end]
692 """Return a suffix for executables"""
693 if sys.platform == 'win32':
697 def bootstrap_binary(self):
698 """Return the path of the bootstrap binary
701 >>> rb.build_dir = "build"
702 >>> rb.bootstrap_binary() == os.path.join("build", "bootstrap",
703 ... "debug", "bootstrap")
706 return os.path.join(self.build_dir, "bootstrap", "debug", "bootstrap")
708 def build_bootstrap(self, color):
709 """Build bootstrap"""
710 print("Building rustbuild")
711 build_dir = os.path.join(self.build_dir, "bootstrap")
712 if self.clean and os.path.exists(build_dir):
713 shutil.rmtree(build_dir)
714 env = os.environ.copy()
715 # `CARGO_BUILD_TARGET` breaks bootstrap build.
716 # See also: <https://github.com/rust-lang/rust/issues/70208>.
717 if "CARGO_BUILD_TARGET" in env:
718 del env["CARGO_BUILD_TARGET"]
719 env["CARGO_TARGET_DIR"] = build_dir
720 env["RUSTC"] = self.rustc()
721 env["LD_LIBRARY_PATH"] = os.path.join(self.bin_root(), "lib") + \
722 (os.pathsep + env["LD_LIBRARY_PATH"]) \
723 if "LD_LIBRARY_PATH" in env else ""
724 env["DYLD_LIBRARY_PATH"] = os.path.join(self.bin_root(), "lib") + \
725 (os.pathsep + env["DYLD_LIBRARY_PATH"]) \
726 if "DYLD_LIBRARY_PATH" in env else ""
727 env["LIBRARY_PATH"] = os.path.join(self.bin_root(), "lib") + \
728 (os.pathsep + env["LIBRARY_PATH"]) \
729 if "LIBRARY_PATH" in env else ""
731 # preserve existing RUSTFLAGS
732 env.setdefault("RUSTFLAGS", "")
733 build_section = "target.{}".format(self.build)
735 if self.get_toml("crt-static", build_section) == "true":
736 target_features += ["+crt-static"]
737 elif self.get_toml("crt-static", build_section) == "false":
738 target_features += ["-crt-static"]
740 env["RUSTFLAGS"] += " -C target-feature=" + (",".join(target_features))
741 target_linker = self.get_toml("linker", build_section)
742 if target_linker is not None:
743 env["RUSTFLAGS"] += " -C linker=" + target_linker
744 env["RUSTFLAGS"] += " -Wrust_2018_idioms -Wunused_lifetimes"
745 env["RUSTFLAGS"] += " -Wsemicolon_in_expressions_from_macros"
746 if self.get_toml("deny-warnings", "rust") != "false":
747 env["RUSTFLAGS"] += " -Dwarnings"
749 env["PATH"] = os.path.join(self.bin_root(), "bin") + \
750 os.pathsep + env["PATH"]
751 if not os.path.isfile(self.cargo()):
752 raise Exception("no cargo executable found at `{}`".format(
754 args = [self.cargo(), "build", "--manifest-path",
755 os.path.join(self.rust_root, "src/bootstrap/Cargo.toml")]
756 for _ in range(0, self.verbose):
757 args.append("--verbose")
758 if self.use_locked_deps:
759 args.append("--locked")
760 if self.use_vendored_sources:
761 args.append("--frozen")
762 if self.get_toml("metrics", "build"):
763 args.append("--features")
764 args.append("build-metrics")
765 if color == "always":
766 args.append("--color=always")
767 elif color == "never":
768 args.append("--color=never")
770 run(args, env=env, verbose=self.verbose)
772 def build_triple(self):
773 """Build triple as in LLVM
775 Note that `default_build_triple` is moderately expensive,
776 so use `self.build` where possible.
778 config = self.get_toml('build')
781 return default_build_triple(self.verbose)
783 def set_dist_environment(self, url):
784 """Set download URL for normal environment"""
785 if 'RUSTUP_DIST_SERVER' in os.environ:
786 self._download_url = os.environ['RUSTUP_DIST_SERVER']
788 self._download_url = url
790 def check_vendored_status(self):
791 """Check that vendoring is configured properly"""
792 if 'SUDO_USER' in os.environ and not self.use_vendored_sources:
794 self.use_vendored_sources = True
795 print('info: looks like you\'re trying to run this command as root')
796 print(' and so in order to preserve your $HOME this will now')
797 print(' use vendored sources by default.')
799 cargo_dir = os.path.join(self.rust_root, '.cargo')
800 if self.use_vendored_sources:
801 vendor_dir = os.path.join(self.rust_root, 'vendor')
802 if not os.path.exists(vendor_dir):
803 sync_dirs = "--sync ./src/tools/rust-analyzer/Cargo.toml " \
804 "--sync ./compiler/rustc_codegen_cranelift/Cargo.toml " \
805 "--sync ./src/bootstrap/Cargo.toml "
806 print('error: vendoring required, but vendor directory does not exist.')
807 print(' Run `cargo vendor {}` to initialize the '
808 'vendor directory.'.format(sync_dirs))
809 print('Alternatively, use the pre-vendored `rustc-src` dist component.')
810 raise Exception("{} not found".format(vendor_dir))
812 if not os.path.exists(cargo_dir):
813 print('error: vendoring required, but .cargo/config does not exist.')
814 raise Exception("{} not found".format(cargo_dir))
816 if os.path.exists(cargo_dir):
817 shutil.rmtree(cargo_dir)
819 def bootstrap(help_triggered):
820 """Configure, fetch, build and run the initial bootstrap"""
822 # If the user is asking for help, let them know that the whole download-and-build
823 # process has to happen before anything is printed out.
825 print("info: Downloading and building bootstrap before processing --help")
826 print(" command. See src/bootstrap/README.md for help with common")
829 parser = argparse.ArgumentParser(description='Build rust')
830 parser.add_argument('--config')
831 parser.add_argument('--build-dir')
832 parser.add_argument('--build')
833 parser.add_argument('--color', choices=['always', 'never', 'auto'])
834 parser.add_argument('--clean', action='store_true')
835 parser.add_argument('-v', '--verbose', action='count', default=0)
837 args = [a for a in sys.argv if a != '-h' and a != '--help']
838 args, _ = parser.parse_known_args(args)
840 # Configure initial bootstrap
842 build.rust_root = os.path.abspath(os.path.join(__file__, '../../..'))
843 build.verbose = args.verbose
844 build.clean = args.clean
846 # Read from `--config`, then `RUST_BOOTSTRAP_CONFIG`, then `./config.toml`,
847 # then `config.toml` in the root directory.
848 toml_path = args.config or os.getenv('RUST_BOOTSTRAP_CONFIG')
849 using_default_path = toml_path is None
850 if using_default_path:
851 toml_path = 'config.toml'
852 if not os.path.exists(toml_path):
853 toml_path = os.path.join(build.rust_root, toml_path)
855 # Give a hard error if `--config` or `RUST_BOOTSTRAP_CONFIG` are set to a missing path,
856 # but not if `config.toml` hasn't been created.
857 if not using_default_path or os.path.exists(toml_path):
858 with open(toml_path) as config:
859 build.config_toml = config.read()
861 profile = build.get_toml('profile')
862 if profile is not None:
863 include_file = 'config.{}.toml'.format(profile)
864 include_dir = os.path.join(build.rust_root, 'src', 'bootstrap', 'defaults')
865 include_path = os.path.join(include_dir, include_file)
866 # HACK: This works because `build.get_toml()` returns the first match it finds for a
867 # specific key, so appending our defaults at the end allows the user to override them
868 with open(include_path) as included_toml:
869 build.config_toml += os.linesep + included_toml.read()
871 config_verbose = build.get_toml('verbose', 'build')
872 if config_verbose is not None:
873 build.verbose = max(build.verbose, int(config_verbose))
875 build.use_vendored_sources = build.get_toml('vendor', 'build') == 'true'
877 build.use_locked_deps = build.get_toml('locked-deps', 'build') == 'true'
879 build.check_vendored_status()
881 build_dir = args.build_dir or build.get_toml('build-dir', 'build') or 'build'
882 build.build_dir = os.path.abspath(build_dir)
884 with open(os.path.join(build.rust_root, "src", "stage0.json")) as f:
886 build.checksums_sha256 = data["checksums_sha256"]
887 build.stage0_compiler = Stage0Toolchain(data["compiler"])
889 build.set_dist_environment(data["config"]["dist_server"])
891 build.build = args.build or build.build_triple()
893 if not os.path.exists(build.build_dir):
894 os.makedirs(build.build_dir)
896 # Fetch/build the bootstrap
897 build.download_toolchain()
899 build.build_bootstrap(args.color)
903 args = [build.bootstrap_binary()]
904 args.extend(sys.argv[1:])
905 env = os.environ.copy()
906 env["BOOTSTRAP_PARENT_ID"] = str(os.getpid())
907 env["BOOTSTRAP_PYTHON"] = sys.executable
908 run(args, env=env, verbose=build.verbose, is_bootstrap=True)
912 """Entry point for the bootstrap process"""
915 # x.py help <cmd> ...
916 if len(sys.argv) > 1 and sys.argv[1] == 'help':
917 sys.argv = [sys.argv[0], '-h'] + sys.argv[2:]
920 '-h' in sys.argv) or ('--help' in sys.argv) or (len(sys.argv) == 1)
922 bootstrap(help_triggered)
923 if not help_triggered:
924 print("Build completed successfully in {}".format(
925 format_build_time(time() - start_time)))
926 except (SystemExit, KeyboardInterrupt) as error:
927 if hasattr(error, 'code') and isinstance(error.code, int):
928 exit_code = error.code
932 if not help_triggered:
933 print("Build completed unsuccessfully in {}".format(
934 format_build_time(time() - start_time)))
938 if __name__ == '__main__':