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 should 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 self.fix_bin_or_dylib("{}/libexec/rust-analyzer-proc-macro-srv".format(bin_root))
445 lib_dir = "{}/lib".format(bin_root)
446 for lib in os.listdir(lib_dir):
447 if lib.endswith(".so"):
448 self.fix_bin_or_dylib(os.path.join(lib_dir, lib))
449 with output(self.rustc_stamp()) as rust_stamp:
450 rust_stamp.write(key)
452 def _download_component_helper(
453 self, filename, pattern, tarball_suffix,
455 key = self.stage0_compiler.date
456 cache_dst = os.path.join(self.build_dir, "cache")
457 rustc_cache = os.path.join(cache_dst, key)
458 if not os.path.exists(rustc_cache):
459 os.makedirs(rustc_cache)
461 base = self._download_url
462 url = "dist/{}".format(key)
463 tarball = os.path.join(rustc_cache, filename)
464 if not os.path.exists(tarball):
467 "{}/{}".format(url, filename),
469 self.checksums_sha256,
470 verbose=self.verbose,
472 unpack(tarball, tarball_suffix, self.bin_root(), match=pattern, verbose=self.verbose)
474 def fix_bin_or_dylib(self, fname):
475 """Modifies the interpreter section of 'fname' to fix the dynamic linker,
476 or the RPATH section, to fix the dynamic library search path
478 This method is only required on NixOS and uses the PatchELF utility to
479 change the interpreter/RPATH of ELF executables.
481 Please see https://nixos.org/patchelf.html for more information
483 default_encoding = sys.getdefaultencoding()
485 ostype = subprocess.check_output(
486 ['uname', '-s']).strip().decode(default_encoding)
487 except subprocess.CalledProcessError:
489 except OSError as reason:
490 if getattr(reason, 'winerror', None) is not None:
494 if ostype != "Linux":
497 # If the user has asked binaries to be patched for Nix, then
498 # don't check for NixOS or `/lib`, just continue to the patching.
499 if self.get_toml('patch-binaries-for-nix', 'build') != 'true':
500 # Use `/etc/os-release` instead of `/etc/NIXOS`.
501 # The latter one does not exist on NixOS when using tmpfs as root.
503 with open("/etc/os-release", "r") as f:
504 if not any(l.strip() in ["ID=nixos", "ID='nixos'", 'ID="nixos"'] for l in f):
506 except FileNotFoundError:
508 if os.path.exists("/lib"):
511 # At this point we're pretty sure the user is running NixOS or
513 nix_os_msg = "info: you seem to be using Nix. Attempting to patch"
514 print(nix_os_msg, fname)
516 # Only build `.nix-deps` once.
517 nix_deps_dir = self.nix_deps_dir
519 # Run `nix-build` to "build" each dependency (which will likely reuse
520 # the existing `/nix/store` copy, or at most download a pre-built copy).
522 # Importantly, we create a gc-root called `.nix-deps` in the `build/`
523 # directory, but still reference the actual `/nix/store` path in the rpath
524 # as it makes it significantly more robust against changes to the location of
525 # the `.nix-deps` location.
527 # bintools: Needed for the path of `ld-linux.so` (via `nix-support/dynamic-linker`).
528 # zlib: Needed as a system dependency of `libLLVM-*.so`.
529 # patchelf: Needed for patching ELF binaries (see doc comment above).
530 nix_deps_dir = "{}/{}".format(self.build_dir, ".nix-deps")
532 with (import <nixpkgs> {});
534 name = "rust-stage0-dependencies";
543 subprocess.check_output([
544 "nix-build", "-E", nix_expr, "-o", nix_deps_dir,
546 except subprocess.CalledProcessError as reason:
547 print("warning: failed to call nix-build:", reason)
549 self.nix_deps_dir = nix_deps_dir
551 patchelf = "{}/bin/patchelf".format(nix_deps_dir)
553 # Relative default, all binary and dynamic libraries we ship
554 # appear to have this (even when `../lib` is redundant).
556 os.path.join(os.path.realpath(nix_deps_dir), "lib")
558 patchelf_args = ["--set-rpath", ":".join(rpath_entries)]
559 if not fname.endswith(".so"):
560 # Finally, set the corret .interp for binaries
561 with open("{}/nix-support/dynamic-linker".format(nix_deps_dir)) as dynamic_linker:
562 patchelf_args += ["--set-interpreter", dynamic_linker.read().rstrip()]
565 subprocess.check_output([patchelf] + patchelf_args + [fname])
566 except subprocess.CalledProcessError as reason:
567 print("warning: failed to call patchelf:", reason)
570 def rustc_stamp(self):
571 """Return the path for .rustc-stamp at the given stage
574 >>> rb.build_dir = "build"
575 >>> rb.rustc_stamp() == os.path.join("build", "stage0", ".rustc-stamp")
578 return os.path.join(self.bin_root(), '.rustc-stamp')
580 def program_out_of_date(self, stamp_path, key):
581 """Check if the given program stamp is out of date"""
582 if not os.path.exists(stamp_path) or self.clean:
584 with open(stamp_path, 'r') as stamp:
585 return key != stamp.read()
588 """Return the binary root directory for the given stage
591 >>> rb.build_dir = "build"
592 >>> rb.bin_root() == os.path.join("build", "stage0")
595 When the 'build' property is given should be a nested directory:
597 >>> rb.build = "devel"
598 >>> rb.bin_root() == os.path.join("build", "devel", "stage0")
602 return os.path.join(self.build_dir, self.build, subdir)
604 def get_toml(self, key, section=None):
605 """Returns the value of the given key in config.toml, otherwise returns None
608 >>> rb.config_toml = 'key1 = "value1"\\nkey2 = "value2"'
609 >>> rb.get_toml("key2")
612 If the key does not exist, the result is None:
614 >>> rb.get_toml("key3") is None
617 Optionally also matches the section the key appears in
619 >>> rb.config_toml = '[a]\\nkey = "value1"\\n[b]\\nkey = "value2"'
620 >>> rb.get_toml('key', 'a')
622 >>> rb.get_toml('key', 'b')
624 >>> rb.get_toml('key', 'c') is None
627 >>> rb.config_toml = 'key1 = true'
628 >>> rb.get_toml("key1")
633 for line in self.config_toml.splitlines():
634 section_match = re.match(r'^\s*\[(.*)\]\s*$', line)
635 if section_match is not None:
636 cur_section = section_match.group(1)
638 match = re.match(r'^{}\s*=(.*)$'.format(key), line)
639 if match is not None:
640 value = match.group(1)
641 if section is None or section == cur_section:
642 return self.get_string(value) or value.strip()
646 """Return config path for cargo"""
647 return self.program_config('cargo')
650 """Return config path for rustc"""
651 return self.program_config('rustc')
653 def program_config(self, program):
654 """Return config path for the given program at the given stage
657 >>> rb.config_toml = 'rustc = "rustc"\\n'
658 >>> rb.program_config('rustc')
660 >>> rb.config_toml = ''
661 >>> cargo_path = rb.program_config('cargo')
662 >>> cargo_path.rstrip(".exe") == os.path.join(rb.bin_root(),
666 config = self.get_toml(program)
668 return os.path.expanduser(config)
669 return os.path.join(self.bin_root(), "bin", "{}{}".format(
670 program, self.exe_suffix()))
673 def get_string(line):
674 """Return the value between double quotes
676 >>> RustBuild.get_string(' "devel" ')
678 >>> RustBuild.get_string(" 'devel' ")
680 >>> RustBuild.get_string('devel') is None
682 >>> RustBuild.get_string(' "devel ')
685 start = line.find('"')
687 end = start + 1 + line[start + 1:].find('"')
688 return line[start + 1:end]
689 start = line.find('\'')
691 end = start + 1 + line[start + 1:].find('\'')
692 return line[start + 1:end]
697 """Return a suffix for executables"""
698 if sys.platform == 'win32':
702 def bootstrap_binary(self):
703 """Return the path of the bootstrap binary
706 >>> rb.build_dir = "build"
707 >>> rb.bootstrap_binary() == os.path.join("build", "bootstrap",
708 ... "debug", "bootstrap")
711 return os.path.join(self.build_dir, "bootstrap", "debug", "bootstrap")
713 def build_bootstrap(self, color):
714 """Build bootstrap"""
715 print("Building rustbuild")
716 build_dir = os.path.join(self.build_dir, "bootstrap")
717 if self.clean and os.path.exists(build_dir):
718 shutil.rmtree(build_dir)
719 env = os.environ.copy()
720 # `CARGO_BUILD_TARGET` breaks bootstrap build.
721 # See also: <https://github.com/rust-lang/rust/issues/70208>.
722 if "CARGO_BUILD_TARGET" in env:
723 del env["CARGO_BUILD_TARGET"]
724 env["CARGO_TARGET_DIR"] = build_dir
725 env["RUSTC"] = self.rustc()
726 env["LD_LIBRARY_PATH"] = os.path.join(self.bin_root(), "lib") + \
727 (os.pathsep + env["LD_LIBRARY_PATH"]) \
728 if "LD_LIBRARY_PATH" in env else ""
729 env["DYLD_LIBRARY_PATH"] = os.path.join(self.bin_root(), "lib") + \
730 (os.pathsep + env["DYLD_LIBRARY_PATH"]) \
731 if "DYLD_LIBRARY_PATH" in env else ""
732 env["LIBRARY_PATH"] = os.path.join(self.bin_root(), "lib") + \
733 (os.pathsep + env["LIBRARY_PATH"]) \
734 if "LIBRARY_PATH" in env else ""
736 # Export Stage0 snapshot compiler related env variables
737 build_section = "target.{}".format(self.build)
738 host_triple_sanitized = self.build.replace("-", "_")
740 "CC": "cc", "CXX": "cxx", "LD": "linker", "AR": "ar", "RANLIB": "ranlib"
742 for var_name, toml_key in var_data.items():
743 toml_val = self.get_toml(toml_key, build_section)
745 env["{}_{}".format(var_name, host_triple_sanitized)] = toml_val
747 # preserve existing RUSTFLAGS
748 env.setdefault("RUSTFLAGS", "")
750 if self.get_toml("crt-static", build_section) == "true":
751 target_features += ["+crt-static"]
752 elif self.get_toml("crt-static", build_section) == "false":
753 target_features += ["-crt-static"]
755 env["RUSTFLAGS"] += " -C target-feature=" + (",".join(target_features))
756 env["RUSTFLAGS"] += " -Wrust_2018_idioms -Wunused_lifetimes"
757 env["RUSTFLAGS"] += " -Wsemicolon_in_expressions_from_macros"
758 if self.get_toml("deny-warnings", "rust") != "false":
759 env["RUSTFLAGS"] += " -Dwarnings"
761 env["PATH"] = os.path.join(self.bin_root(), "bin") + \
762 os.pathsep + env["PATH"]
763 if not os.path.isfile(self.cargo()):
764 raise Exception("no cargo executable found at `{}`".format(
766 args = [self.cargo(), "build", "--manifest-path",
767 os.path.join(self.rust_root, "src/bootstrap/Cargo.toml")]
768 for _ in range(0, self.verbose):
769 args.append("--verbose")
770 if self.use_locked_deps:
771 args.append("--locked")
772 if self.use_vendored_sources:
773 args.append("--frozen")
774 if self.get_toml("metrics", "build"):
775 args.append("--features")
776 args.append("build-metrics")
777 if color == "always":
778 args.append("--color=always")
779 elif color == "never":
780 args.append("--color=never")
782 # Run this from the source directory so cargo finds .cargo/config
783 run(args, env=env, verbose=self.verbose, cwd=self.rust_root)
785 def build_triple(self):
786 """Build triple as in LLVM
788 Note that `default_build_triple` is moderately expensive,
789 so use `self.build` where possible.
791 config = self.get_toml('build')
794 return default_build_triple(self.verbose)
796 def set_dist_environment(self, url):
797 """Set download URL for normal environment"""
798 if 'RUSTUP_DIST_SERVER' in os.environ:
799 self._download_url = os.environ['RUSTUP_DIST_SERVER']
801 self._download_url = url
803 def check_vendored_status(self):
804 """Check that vendoring is configured properly"""
805 # keep this consistent with the equivalent check in rustbuild:
806 # https://github.com/rust-lang/rust/blob/a8a33cf27166d3eabaffc58ed3799e054af3b0c6/src/bootstrap/lib.rs#L399-L405
807 if 'SUDO_USER' in os.environ and not self.use_vendored_sources:
809 self.use_vendored_sources = True
810 print('info: looks like you\'re trying to run this command as root')
811 print(' and so in order to preserve your $HOME this will now')
812 print(' use vendored sources by default.')
814 cargo_dir = os.path.join(self.rust_root, '.cargo')
815 if self.use_vendored_sources:
816 vendor_dir = os.path.join(self.rust_root, 'vendor')
817 if not os.path.exists(vendor_dir):
818 sync_dirs = "--sync ./src/tools/rust-analyzer/Cargo.toml " \
819 "--sync ./compiler/rustc_codegen_cranelift/Cargo.toml " \
820 "--sync ./src/bootstrap/Cargo.toml "
821 print('error: vendoring required, but vendor directory does not exist.')
822 print(' Run `cargo vendor {}` to initialize the '
823 'vendor directory.'.format(sync_dirs))
824 print('Alternatively, use the pre-vendored `rustc-src` dist component.')
825 raise Exception("{} not found".format(vendor_dir))
827 if not os.path.exists(cargo_dir):
828 print('error: vendoring required, but .cargo/config does not exist.')
829 raise Exception("{} not found".format(cargo_dir))
831 if os.path.exists(cargo_dir):
832 shutil.rmtree(cargo_dir)
834 def bootstrap(help_triggered):
835 """Configure, fetch, build and run the initial bootstrap"""
837 # If the user is asking for help, let them know that the whole download-and-build
838 # process has to happen before anything is printed out.
840 print("info: Downloading and building bootstrap before processing --help")
841 print(" command. See src/bootstrap/README.md for help with common")
844 parser = argparse.ArgumentParser(description='Build rust')
845 parser.add_argument('--config')
846 parser.add_argument('--build-dir')
847 parser.add_argument('--build')
848 parser.add_argument('--color', choices=['always', 'never', 'auto'])
849 parser.add_argument('--clean', action='store_true')
850 parser.add_argument('-v', '--verbose', action='count', default=0)
852 args = [a for a in sys.argv if a != '-h' and a != '--help']
853 args, _ = parser.parse_known_args(args)
855 # Configure initial bootstrap
857 build.rust_root = os.path.abspath(os.path.join(__file__, '../../..'))
858 build.verbose = args.verbose
859 build.clean = args.clean
861 # Read from `--config`, then `RUST_BOOTSTRAP_CONFIG`, then `./config.toml`,
862 # then `config.toml` in the root directory.
863 toml_path = args.config or os.getenv('RUST_BOOTSTRAP_CONFIG')
864 using_default_path = toml_path is None
865 if using_default_path:
866 toml_path = 'config.toml'
867 if not os.path.exists(toml_path):
868 toml_path = os.path.join(build.rust_root, toml_path)
870 # Give a hard error if `--config` or `RUST_BOOTSTRAP_CONFIG` are set to a missing path,
871 # but not if `config.toml` hasn't been created.
872 if not using_default_path or os.path.exists(toml_path):
873 with open(toml_path) as config:
874 build.config_toml = config.read()
876 profile = build.get_toml('profile')
877 if profile is not None:
878 include_file = 'config.{}.toml'.format(profile)
879 include_dir = os.path.join(build.rust_root, 'src', 'bootstrap', 'defaults')
880 include_path = os.path.join(include_dir, include_file)
881 # HACK: This works because `build.get_toml()` returns the first match it finds for a
882 # specific key, so appending our defaults at the end allows the user to override them
883 with open(include_path) as included_toml:
884 build.config_toml += os.linesep + included_toml.read()
886 config_verbose = build.get_toml('verbose', 'build')
887 if config_verbose is not None:
888 build.verbose = max(build.verbose, int(config_verbose))
890 build.use_vendored_sources = build.get_toml('vendor', 'build') == 'true'
892 build.use_locked_deps = build.get_toml('locked-deps', 'build') == 'true'
894 build.check_vendored_status()
896 build_dir = args.build_dir or build.get_toml('build-dir', 'build') or 'build'
897 build.build_dir = os.path.abspath(build_dir)
899 with open(os.path.join(build.rust_root, "src", "stage0.json")) as f:
901 build.checksums_sha256 = data["checksums_sha256"]
902 build.stage0_compiler = Stage0Toolchain(data["compiler"])
904 build.set_dist_environment(data["config"]["dist_server"])
906 build.build = args.build or build.build_triple()
908 if not os.path.exists(build.build_dir):
909 os.makedirs(build.build_dir)
911 # Fetch/build the bootstrap
912 build.download_toolchain()
914 build.build_bootstrap(args.color)
918 args = [build.bootstrap_binary()]
919 args.extend(sys.argv[1:])
920 env = os.environ.copy()
921 env["BOOTSTRAP_PARENT_ID"] = str(os.getpid())
922 env["BOOTSTRAP_PYTHON"] = sys.executable
923 run(args, env=env, verbose=build.verbose, is_bootstrap=True)
927 """Entry point for the bootstrap process"""
930 # x.py help <cmd> ...
931 if len(sys.argv) > 1 and sys.argv[1] == 'help':
932 sys.argv = [sys.argv[0], '-h'] + sys.argv[2:]
935 '-h' in sys.argv) or ('--help' in sys.argv) or (len(sys.argv) == 1)
937 bootstrap(help_triggered)
938 if not help_triggered:
939 print("Build completed successfully in {}".format(
940 format_build_time(time() - start_time)))
941 except (SystemExit, KeyboardInterrupt) as error:
942 if hasattr(error, 'code') and isinstance(error.code, int):
943 exit_code = error.code
947 if not help_triggered:
948 print("Build completed unsuccessfully in {}".format(
949 format_build_time(time() - start_time)))
953 if __name__ == '__main__':