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)
91 "-L", # Follow redirect.
92 "-y", "30", "-Y", "10", # timeout if speed is < 10 bytes/sec for > 30 seconds
93 "--connect-timeout", "30", # timeout if cannot connect within 30 seconds
94 "--retry", "3", "-Sf", "-o", path, url],
96 exception=True, # Will raise RuntimeError on failure
98 except (subprocess.CalledProcessError, OSError, RuntimeError):
99 # see http://serverfault.com/questions/301128/how-to-download
100 if platform_is_win32:
101 run(["PowerShell.exe", "/nologo", "-Command",
102 "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12;",
103 "(New-Object System.Net.WebClient).DownloadFile('{}', '{}')".format(url, path)],
106 # Check if the RuntimeError raised by run(curl) should be silenced
107 elif verbose or exception:
111 def verify(path, expected, verbose):
112 """Check if the sha256 sum of the given path is valid"""
114 print("verifying", path)
115 with open(path, "rb") as source:
116 found = hashlib.sha256(source.read()).hexdigest()
117 verified = found == expected
119 print("invalid checksum:\n"
121 " expected: {}".format(found, expected))
125 def unpack(tarball, tarball_suffix, dst, verbose=False, match=None):
126 """Unpack the given tarball file"""
127 print("extracting", tarball)
128 fname = os.path.basename(tarball).replace(tarball_suffix, "")
129 with contextlib.closing(tarfile.open(tarball)) as tar:
130 for member in tar.getnames():
131 if "/" not in member:
133 name = member.replace(fname + "/", "", 1)
134 if match is not None and not name.startswith(match):
136 name = name[len(match) + 1:]
138 dst_path = os.path.join(dst, name)
140 print(" extracting", member)
141 tar.extract(member, dst)
142 src_path = os.path.join(dst, member)
143 if os.path.isdir(src_path) and os.path.exists(dst_path):
145 shutil.move(src_path, dst_path)
146 shutil.rmtree(os.path.join(dst, fname))
149 def run(args, verbose=False, exception=False, is_bootstrap=False, **kwargs):
150 """Run a child program in a new process"""
152 print("running: " + ' '.join(args))
154 # Ensure that the .exe is used on Windows just in case a Linux ELF has been
155 # compiled in the same directory.
156 if os.name == 'nt' and not args[0].endswith('.exe'):
158 # Use Popen here instead of call() as it apparently allows powershell on
159 # Windows to not lock up waiting for input presumably.
160 ret = subprocess.Popen(args, **kwargs)
163 err = "failed to run: " + ' '.join(args)
164 if verbose or exception:
165 raise RuntimeError(err)
166 # For most failures, we definitely do want to print this error, or the user will have no
167 # idea what went wrong. But when we've successfully built bootstrap and it failed, it will
168 # have already printed an error above, so there's no need to print the exact command we're
176 def require(cmd, exit=True, exception=False):
177 '''Run a command, returning its output.
179 If `exception` is `True`, raise the error
180 Otherwise If `exit` is `True`, exit the process
183 return subprocess.check_output(cmd).strip()
184 except (subprocess.CalledProcessError, OSError) as exc:
188 print("error: unable to run `{}`: {}".format(' '.join(cmd), exc))
189 print("Please make sure it's installed and in the path.")
195 def format_build_time(duration):
196 """Return a nicer format for build time
198 >>> format_build_time('300')
201 return str(datetime.timedelta(seconds=int(duration)))
204 def default_build_triple(verbose):
205 """Build triple as in LLVM"""
206 # If the user already has a host build triple with an existing `rustc`
207 # install, use their preference. This fixes most issues with Windows builds
208 # being detected as GNU instead of MSVC.
209 default_encoding = sys.getdefaultencoding()
211 version = subprocess.check_output(["rustc", "--version", "--verbose"],
212 stderr=subprocess.DEVNULL)
213 version = version.decode(default_encoding)
214 host = next(x for x in version.split('\n') if x.startswith("host: "))
215 triple = host.split("host: ")[1]
217 print("detected default triple {} from pre-installed rustc".format(triple))
219 except Exception as e:
221 print("pre-installed rustc not detected: {}".format(e))
222 print("falling back to auto-detect")
224 required = sys.platform != 'win32'
225 ostype = require(["uname", "-s"], exit=required)
226 cputype = require(['uname', '-m'], exit=required)
228 # If we do not have `uname`, assume Windows.
229 if ostype is None or cputype is None:
230 return 'x86_64-pc-windows-msvc'
232 ostype = ostype.decode(default_encoding)
233 cputype = cputype.decode(default_encoding)
235 # The goal here is to come up with the same triple as LLVM would,
236 # at least for the subset of platforms we're willing to target.
238 'Darwin': 'apple-darwin',
239 'DragonFly': 'unknown-dragonfly',
240 'FreeBSD': 'unknown-freebsd',
241 'Haiku': 'unknown-haiku',
242 'NetBSD': 'unknown-netbsd',
243 'OpenBSD': 'unknown-openbsd'
246 # Consider the direct transformation first and then the special cases
247 if ostype in ostype_mapper:
248 ostype = ostype_mapper[ostype]
249 elif ostype == 'Linux':
250 os_from_sp = subprocess.check_output(
251 ['uname', '-o']).strip().decode(default_encoding)
252 if os_from_sp == 'Android':
253 ostype = 'linux-android'
255 ostype = 'unknown-linux-gnu'
256 elif ostype == 'SunOS':
257 ostype = 'pc-solaris'
258 # On Solaris, uname -m will return a machine classification instead
259 # of a cpu type, so uname -p is recommended instead. However, the
260 # output from that option is too generic for our purposes (it will
261 # always emit 'i386' on x86/amd64 systems). As such, isainfo -k
262 # must be used instead.
263 cputype = require(['isainfo', '-k']).decode(default_encoding)
264 # sparc cpus have sun as a target vendor
265 if 'sparc' in cputype:
266 ostype = 'sun-solaris'
267 elif ostype.startswith('MINGW'):
268 # msys' `uname` does not print gcc configuration, but prints msys
269 # configuration. so we cannot believe `uname -m`:
270 # msys1 is always i686 and msys2 is always x86_64.
271 # instead, msys defines $MSYSTEM which is MINGW32 on i686 and
273 ostype = 'pc-windows-gnu'
275 if os.environ.get('MSYSTEM') == 'MINGW64':
277 elif ostype.startswith('MSYS'):
278 ostype = 'pc-windows-gnu'
279 elif ostype.startswith('CYGWIN_NT'):
281 if ostype.endswith('WOW64'):
283 ostype = 'pc-windows-gnu'
284 elif sys.platform == 'win32':
285 # Some Windows platforms might have a `uname` command that returns a
286 # non-standard string (e.g. gnuwin32 tools returns `windows32`). In
287 # these cases, fall back to using sys.platform.
288 return 'x86_64-pc-windows-msvc'
290 err = "unknown OS type: {}".format(ostype)
293 if cputype in ['powerpc', 'riscv'] and ostype == 'unknown-freebsd':
294 cputype = subprocess.check_output(
295 ['uname', '-p']).strip().decode(default_encoding)
298 'aarch64': 'aarch64',
306 'powerpc': 'powerpc',
307 'powerpc64': 'powerpc64',
308 'powerpc64le': 'powerpc64le',
310 'ppc64': 'powerpc64',
311 'ppc64le': 'powerpc64le',
312 'riscv64': 'riscv64gc',
320 # Consider the direct transformation first and then the special cases
321 if cputype in cputype_mapper:
322 cputype = cputype_mapper[cputype]
323 elif cputype in {'xscale', 'arm'}:
325 if ostype == 'linux-android':
326 ostype = 'linux-androideabi'
327 elif ostype == 'unknown-freebsd':
328 cputype = subprocess.check_output(
329 ['uname', '-p']).strip().decode(default_encoding)
330 ostype = 'unknown-freebsd'
331 elif cputype == 'armv6l':
333 if ostype == 'linux-android':
334 ostype = 'linux-androideabi'
337 elif cputype in {'armv7l', 'armv8l'}:
339 if ostype == 'linux-android':
340 ostype = 'linux-androideabi'
343 elif cputype == 'mips':
344 if sys.byteorder == 'big':
346 elif sys.byteorder == 'little':
349 raise ValueError("unknown byteorder: {}".format(sys.byteorder))
350 elif cputype == 'mips64':
351 if sys.byteorder == 'big':
353 elif sys.byteorder == 'little':
356 raise ValueError('unknown byteorder: {}'.format(sys.byteorder))
357 # only the n64 ABI is supported, indicate it
359 elif cputype == 'sparc' or cputype == 'sparcv9' or cputype == 'sparc64':
362 err = "unknown cpu type: {}".format(cputype)
365 return "{}-{}".format(cputype, ostype)
368 @contextlib.contextmanager
369 def output(filepath):
370 tmp = filepath + '.tmp'
371 with open(tmp, 'w') as f:
374 if os.path.exists(filepath):
375 os.remove(filepath) # PermissionError/OSError on Win32 if in use
377 shutil.copy2(tmp, filepath)
380 os.rename(tmp, filepath)
383 class Stage0Toolchain:
384 def __init__(self, stage0_payload):
385 self.date = stage0_payload["date"]
386 self.version = stage0_payload["version"]
389 return self.version + "-" + self.date
392 class RustBuild(object):
393 """Provide all the methods required to build Rust"""
395 self.checksums_sha256 = {}
396 self.stage0_compiler = None
397 self.download_url = ''
401 self.config_toml = ''
403 self.use_locked_deps = False
404 self.use_vendored_sources = False
406 self.git_version = None
407 self.nix_deps_dir = None
408 self._should_fix_bins_and_dylibs = 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.gz' if lzma is None else '.tar.xz'
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 if self.should_fix_bins_and_dylibs():
441 self.fix_bin_or_dylib("{}/bin/cargo".format(bin_root))
443 self.fix_bin_or_dylib("{}/bin/rustc".format(bin_root))
444 self.fix_bin_or_dylib("{}/bin/rustdoc".format(bin_root))
445 self.fix_bin_or_dylib("{}/libexec/rust-analyzer-proc-macro-srv".format(bin_root))
446 lib_dir = "{}/lib".format(bin_root)
447 for lib in os.listdir(lib_dir):
448 if lib.endswith(".so"):
449 self.fix_bin_or_dylib(os.path.join(lib_dir, lib))
451 with output(self.rustc_stamp()) as rust_stamp:
452 rust_stamp.write(key)
454 def _download_component_helper(
455 self, filename, pattern, tarball_suffix,
457 key = self.stage0_compiler.date
458 cache_dst = os.path.join(self.build_dir, "cache")
459 rustc_cache = os.path.join(cache_dst, key)
460 if not os.path.exists(rustc_cache):
461 os.makedirs(rustc_cache)
463 tarball = os.path.join(rustc_cache, filename)
464 if not os.path.exists(tarball):
467 "dist/{}/{}".format(key, filename),
469 self.checksums_sha256,
470 verbose=self.verbose,
472 unpack(tarball, tarball_suffix, self.bin_root(), match=pattern, verbose=self.verbose)
474 def should_fix_bins_and_dylibs(self):
475 """Whether or not `fix_bin_or_dylib` needs to be run; can only be True
478 if self._should_fix_bins_and_dylibs is not None:
479 return self._should_fix_bins_and_dylibs
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`.
498 if self.get_toml("patch-binaries-for-nix", "build") == "true":
501 # Use `/etc/os-release` instead of `/etc/NIXOS`.
502 # The latter one does not exist on NixOS when using tmpfs as root.
504 with open("/etc/os-release", "r") as f:
505 if not any(l.strip() in ("ID=nixos", "ID='nixos'", 'ID="nixos"') for l in f):
507 except FileNotFoundError:
509 if os.path.exists("/lib"):
514 answer = self._should_fix_bins_and_dylibs = get_answer()
516 print("info: You seem to be using Nix.")
519 def fix_bin_or_dylib(self, fname):
520 """Modifies the interpreter section of 'fname' to fix the dynamic linker,
521 or the RPATH section, to fix the dynamic library search path
523 This method is only required on NixOS and uses the PatchELF utility to
524 change the interpreter/RPATH of ELF executables.
526 Please see https://nixos.org/patchelf.html for more information
528 assert self._should_fix_bins_and_dylibs is True
529 print("attempting to patch", fname)
531 # Only build `.nix-deps` once.
532 nix_deps_dir = self.nix_deps_dir
534 # Run `nix-build` to "build" each dependency (which will likely reuse
535 # the existing `/nix/store` copy, or at most download a pre-built copy).
537 # Importantly, we create a gc-root called `.nix-deps` in the `build/`
538 # directory, but still reference the actual `/nix/store` path in the rpath
539 # as it makes it significantly more robust against changes to the location of
540 # the `.nix-deps` location.
542 # bintools: Needed for the path of `ld-linux.so` (via `nix-support/dynamic-linker`).
543 # zlib: Needed as a system dependency of `libLLVM-*.so`.
544 # patchelf: Needed for patching ELF binaries (see doc comment above).
545 nix_deps_dir = "{}/{}".format(self.build_dir, ".nix-deps")
547 with (import <nixpkgs> {});
549 name = "rust-stage0-dependencies";
558 subprocess.check_output([
559 "nix-build", "-E", nix_expr, "-o", nix_deps_dir,
561 except subprocess.CalledProcessError as reason:
562 print("warning: failed to call nix-build:", reason)
564 self.nix_deps_dir = nix_deps_dir
566 patchelf = "{}/bin/patchelf".format(nix_deps_dir)
568 # Relative default, all binary and dynamic libraries we ship
569 # appear to have this (even when `../lib` is redundant).
571 os.path.join(os.path.realpath(nix_deps_dir), "lib")
573 patchelf_args = ["--set-rpath", ":".join(rpath_entries)]
574 if not fname.endswith(".so"):
575 # Finally, set the corret .interp for binaries
576 with open("{}/nix-support/dynamic-linker".format(nix_deps_dir)) as dynamic_linker:
577 patchelf_args += ["--set-interpreter", dynamic_linker.read().rstrip()]
580 subprocess.check_output([patchelf] + patchelf_args + [fname])
581 except subprocess.CalledProcessError as reason:
582 print("warning: failed to call patchelf:", reason)
585 def rustc_stamp(self):
586 """Return the path for .rustc-stamp at the given stage
589 >>> rb.build_dir = "build"
590 >>> rb.rustc_stamp() == os.path.join("build", "stage0", ".rustc-stamp")
593 return os.path.join(self.bin_root(), '.rustc-stamp')
595 def program_out_of_date(self, stamp_path, key):
596 """Check if the given program stamp is out of date"""
597 if not os.path.exists(stamp_path) or self.clean:
599 with open(stamp_path, 'r') as stamp:
600 return key != stamp.read()
603 """Return the binary root directory for the given stage
606 >>> rb.build_dir = "build"
607 >>> rb.bin_root() == os.path.join("build", "stage0")
610 When the 'build' property is given should be a nested directory:
612 >>> rb.build = "devel"
613 >>> rb.bin_root() == os.path.join("build", "devel", "stage0")
617 return os.path.join(self.build_dir, self.build, subdir)
619 def get_toml(self, key, section=None):
620 """Returns the value of the given key in config.toml, otherwise returns None
623 >>> rb.config_toml = 'key1 = "value1"\\nkey2 = "value2"'
624 >>> rb.get_toml("key2")
627 If the key does not exist, the result is None:
629 >>> rb.get_toml("key3") is None
632 Optionally also matches the section the key appears in
634 >>> rb.config_toml = '[a]\\nkey = "value1"\\n[b]\\nkey = "value2"'
635 >>> rb.get_toml('key', 'a')
637 >>> rb.get_toml('key', 'b')
639 >>> rb.get_toml('key', 'c') is None
642 >>> rb.config_toml = 'key1 = true'
643 >>> rb.get_toml("key1")
648 for line in self.config_toml.splitlines():
649 section_match = re.match(r'^\s*\[(.*)\]\s*$', line)
650 if section_match is not None:
651 cur_section = section_match.group(1)
653 match = re.match(r'^{}\s*=(.*)$'.format(key), line)
654 if match is not None:
655 value = match.group(1)
656 if section is None or section == cur_section:
657 return self.get_string(value) or value.strip()
661 """Return config path for cargo"""
662 return self.program_config('cargo')
665 """Return config path for rustc"""
666 return self.program_config('rustc')
668 def program_config(self, program):
669 """Return config path for the given program at the given stage
672 >>> rb.config_toml = 'rustc = "rustc"\\n'
673 >>> rb.program_config('rustc')
675 >>> rb.config_toml = ''
676 >>> cargo_path = rb.program_config('cargo')
677 >>> cargo_path.rstrip(".exe") == os.path.join(rb.bin_root(),
681 config = self.get_toml(program)
683 return os.path.expanduser(config)
684 return os.path.join(self.bin_root(), "bin", "{}{}".format(program, EXE_SUFFIX))
687 def get_string(line):
688 """Return the value between double quotes
690 >>> RustBuild.get_string(' "devel" ')
692 >>> RustBuild.get_string(" 'devel' ")
694 >>> RustBuild.get_string('devel') is None
696 >>> RustBuild.get_string(' "devel ')
699 start = line.find('"')
701 end = start + 1 + line[start + 1:].find('"')
702 return line[start + 1:end]
703 start = line.find('\'')
705 end = start + 1 + line[start + 1:].find('\'')
706 return line[start + 1:end]
709 def bootstrap_binary(self):
710 """Return the path of the bootstrap binary
713 >>> rb.build_dir = "build"
714 >>> rb.bootstrap_binary() == os.path.join("build", "bootstrap",
715 ... "debug", "bootstrap")
718 return os.path.join(self.build_dir, "bootstrap", "debug", "bootstrap")
720 def build_bootstrap(self, color, verbose_count):
721 """Build bootstrap"""
722 print("Building bootstrap")
723 build_dir = os.path.join(self.build_dir, "bootstrap")
724 if self.clean and os.path.exists(build_dir):
725 shutil.rmtree(build_dir)
726 env = os.environ.copy()
727 # `CARGO_BUILD_TARGET` breaks bootstrap build.
728 # See also: <https://github.com/rust-lang/rust/issues/70208>.
729 if "CARGO_BUILD_TARGET" in env:
730 del env["CARGO_BUILD_TARGET"]
731 env["CARGO_TARGET_DIR"] = build_dir
732 env["RUSTC"] = self.rustc()
733 env["LD_LIBRARY_PATH"] = os.path.join(self.bin_root(), "lib") + \
734 (os.pathsep + env["LD_LIBRARY_PATH"]) \
735 if "LD_LIBRARY_PATH" in env else ""
736 env["DYLD_LIBRARY_PATH"] = os.path.join(self.bin_root(), "lib") + \
737 (os.pathsep + env["DYLD_LIBRARY_PATH"]) \
738 if "DYLD_LIBRARY_PATH" in env else ""
739 env["LIBRARY_PATH"] = os.path.join(self.bin_root(), "lib") + \
740 (os.pathsep + env["LIBRARY_PATH"]) \
741 if "LIBRARY_PATH" in env else ""
743 # Export Stage0 snapshot compiler related env variables
744 build_section = "target.{}".format(self.build)
745 host_triple_sanitized = self.build.replace("-", "_")
747 "CC": "cc", "CXX": "cxx", "LD": "linker", "AR": "ar", "RANLIB": "ranlib"
749 for var_name, toml_key in var_data.items():
750 toml_val = self.get_toml(toml_key, build_section)
752 env["{}_{}".format(var_name, host_triple_sanitized)] = toml_val
754 # preserve existing RUSTFLAGS
755 env.setdefault("RUSTFLAGS", "")
757 if self.get_toml("crt-static", build_section) == "true":
758 target_features += ["+crt-static"]
759 elif self.get_toml("crt-static", build_section) == "false":
760 target_features += ["-crt-static"]
762 env["RUSTFLAGS"] += " -C target-feature=" + (",".join(target_features))
763 target_linker = self.get_toml("linker", build_section)
764 if target_linker is not None:
765 env["RUSTFLAGS"] += " -C linker=" + target_linker
766 env["RUSTFLAGS"] += " -Wrust_2018_idioms -Wunused_lifetimes"
767 if self.get_toml("deny-warnings", "rust") != "false":
768 env["RUSTFLAGS"] += " -Dwarnings"
770 env["PATH"] = os.path.join(self.bin_root(), "bin") + \
771 os.pathsep + env["PATH"]
772 if not os.path.isfile(self.cargo()):
773 raise Exception("no cargo executable found at `{}`".format(
775 args = [self.cargo(), "build", "--manifest-path",
776 os.path.join(self.rust_root, "src/bootstrap/Cargo.toml")]
777 args.extend("--verbose" for _ in range(verbose_count))
778 if self.use_locked_deps:
779 args.append("--locked")
780 if self.use_vendored_sources:
781 args.append("--frozen")
782 if self.get_toml("metrics", "build"):
783 args.append("--features")
784 args.append("build-metrics")
785 if color == "always":
786 args.append("--color=always")
787 elif color == "never":
788 args.append("--color=never")
790 # Run this from the source directory so cargo finds .cargo/config
791 run(args, env=env, verbose=self.verbose, cwd=self.rust_root)
793 def build_triple(self):
794 """Build triple as in LLVM
796 Note that `default_build_triple` is moderately expensive,
797 so use `self.build` where possible.
799 config = self.get_toml('build')
800 return config or default_build_triple(self.verbose)
802 def check_vendored_status(self):
803 """Check that vendoring is configured properly"""
804 # keep this consistent with the equivalent check in rustbuild:
805 # https://github.com/rust-lang/rust/blob/a8a33cf27166d3eabaffc58ed3799e054af3b0c6/src/bootstrap/lib.rs#L399-L405
806 if 'SUDO_USER' in os.environ and not self.use_vendored_sources:
808 self.use_vendored_sources = True
809 print('info: looks like you\'re trying to run this command as root')
810 print(' and so in order to preserve your $HOME this will now')
811 print(' use vendored sources by default.')
813 cargo_dir = os.path.join(self.rust_root, '.cargo')
814 if self.use_vendored_sources:
815 vendor_dir = os.path.join(self.rust_root, 'vendor')
816 if not os.path.exists(vendor_dir):
817 sync_dirs = "--sync ./src/tools/rust-analyzer/Cargo.toml " \
818 "--sync ./compiler/rustc_codegen_cranelift/Cargo.toml " \
819 "--sync ./src/bootstrap/Cargo.toml "
820 print('error: vendoring required, but vendor directory does not exist.')
821 print(' Run `cargo vendor {}` to initialize the '
822 'vendor directory.'.format(sync_dirs))
823 print('Alternatively, use the pre-vendored `rustc-src` dist component.')
824 raise Exception("{} not found".format(vendor_dir))
826 if not os.path.exists(cargo_dir):
827 print('error: vendoring required, but .cargo/config does not exist.')
828 raise Exception("{} not found".format(cargo_dir))
830 if os.path.exists(cargo_dir):
831 shutil.rmtree(cargo_dir)
834 """Parse the command line arguments that the python script needs."""
835 parser = argparse.ArgumentParser(add_help=False)
836 parser.add_argument('-h', '--help', action='store_true')
837 parser.add_argument('--config')
838 parser.add_argument('--build-dir')
839 parser.add_argument('--build')
840 parser.add_argument('--color', choices=['always', 'never', 'auto'])
841 parser.add_argument('--clean', action='store_true')
842 parser.add_argument('-v', '--verbose', action='count', default=0)
844 return parser.parse_known_args(sys.argv)[0]
847 """Configure, fetch, build and run the initial bootstrap"""
848 # Configure initial bootstrap
850 build.rust_root = os.path.abspath(os.path.join(__file__, '../../..'))
851 build.verbose = args.verbose != 0
852 build.clean = args.clean
854 # Read from `--config`, then `RUST_BOOTSTRAP_CONFIG`, then `./config.toml`,
855 # then `config.toml` in the root directory.
856 toml_path = args.config or os.getenv('RUST_BOOTSTRAP_CONFIG')
857 using_default_path = toml_path is None
858 if using_default_path:
859 toml_path = 'config.toml'
860 if not os.path.exists(toml_path):
861 toml_path = os.path.join(build.rust_root, toml_path)
863 # Give a hard error if `--config` or `RUST_BOOTSTRAP_CONFIG` are set to a missing path,
864 # but not if `config.toml` hasn't been created.
865 if not using_default_path or os.path.exists(toml_path):
866 with open(toml_path) as config:
867 build.config_toml = config.read()
869 profile = build.get_toml('profile')
870 if profile is not None:
871 include_file = 'config.{}.toml'.format(profile)
872 include_dir = os.path.join(build.rust_root, 'src', 'bootstrap', 'defaults')
873 include_path = os.path.join(include_dir, include_file)
874 # HACK: This works because `build.get_toml()` returns the first match it finds for a
875 # specific key, so appending our defaults at the end allows the user to override them
876 with open(include_path) as included_toml:
877 build.config_toml += os.linesep + included_toml.read()
879 verbose_count = args.verbose
880 config_verbose_count = build.get_toml('verbose', 'build')
881 if config_verbose_count is not None:
882 verbose_count = max(args.verbose, int(config_verbose_count))
884 build.use_vendored_sources = build.get_toml('vendor', 'build') == 'true'
885 build.use_locked_deps = build.get_toml('locked-deps', 'build') == 'true'
887 build.check_vendored_status()
889 build_dir = args.build_dir or build.get_toml('build-dir', 'build') or 'build'
890 build.build_dir = os.path.abspath(build_dir)
892 with open(os.path.join(build.rust_root, "src", "stage0.json")) as f:
894 build.checksums_sha256 = data["checksums_sha256"]
895 build.stage0_compiler = Stage0Toolchain(data["compiler"])
896 build.download_url = os.getenv("RUSTUP_DIST_SERVER") or data["config"]["dist_server"]
898 build.build = args.build or build.build_triple()
900 if not os.path.exists(build.build_dir):
901 os.makedirs(build.build_dir)
903 # Fetch/build the bootstrap
904 build.download_toolchain()
906 build.build_bootstrap(args.color, verbose_count)
910 args = [build.bootstrap_binary()]
911 args.extend(sys.argv[1:])
912 env = os.environ.copy()
913 env["BOOTSTRAP_PARENT_ID"] = str(os.getpid())
914 env["BOOTSTRAP_PYTHON"] = sys.executable
915 run(args, env=env, verbose=build.verbose, is_bootstrap=True)
919 """Entry point for the bootstrap process"""
922 # x.py help <cmd> ...
923 if len(sys.argv) > 1 and sys.argv[1] == 'help':
927 help_triggered = args.help or len(sys.argv) == 1
929 # If the user is asking for help, let them know that the whole download-and-build
930 # process has to happen before anything is printed out.
933 "info: Downloading and building bootstrap before processing --help command.\n"
934 " See src/bootstrap/README.md for help with common commands."
940 except (SystemExit, KeyboardInterrupt) as error:
941 if hasattr(error, 'code') and isinstance(error.code, int):
942 exit_code = error.code
947 if not help_triggered:
948 print("Build completed successfully in", format_build_time(time() - start_time))
952 if __name__ == '__main__':