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 target_linker = self.get_toml("linker", build_section)
757 if target_linker is not None:
758 env["RUSTFLAGS"] += " -C linker=" + target_linker
759 env["RUSTFLAGS"] += " -Wrust_2018_idioms -Wunused_lifetimes"
760 env["RUSTFLAGS"] += " -Wsemicolon_in_expressions_from_macros"
761 if self.get_toml("deny-warnings", "rust") != "false":
762 env["RUSTFLAGS"] += " -Dwarnings"
764 env["PATH"] = os.path.join(self.bin_root(), "bin") + \
765 os.pathsep + env["PATH"]
766 if not os.path.isfile(self.cargo()):
767 raise Exception("no cargo executable found at `{}`".format(
769 args = [self.cargo(), "build", "--manifest-path",
770 os.path.join(self.rust_root, "src/bootstrap/Cargo.toml")]
771 for _ in range(0, self.verbose):
772 args.append("--verbose")
773 if self.use_locked_deps:
774 args.append("--locked")
775 if self.use_vendored_sources:
776 args.append("--frozen")
777 if self.get_toml("metrics", "build"):
778 args.append("--features")
779 args.append("build-metrics")
780 if color == "always":
781 args.append("--color=always")
782 elif color == "never":
783 args.append("--color=never")
785 # Run this from the source directory so cargo finds .cargo/config
786 run(args, env=env, verbose=self.verbose, cwd=self.rust_root)
788 def build_triple(self):
789 """Build triple as in LLVM
791 Note that `default_build_triple` is moderately expensive,
792 so use `self.build` where possible.
794 config = self.get_toml('build')
797 return default_build_triple(self.verbose)
799 def set_dist_environment(self, url):
800 """Set download URL for normal environment"""
801 if 'RUSTUP_DIST_SERVER' in os.environ:
802 self._download_url = os.environ['RUSTUP_DIST_SERVER']
804 self._download_url = url
806 def check_vendored_status(self):
807 """Check that vendoring is configured properly"""
808 # keep this consistent with the equivalent check in rustbuild:
809 # https://github.com/rust-lang/rust/blob/a8a33cf27166d3eabaffc58ed3799e054af3b0c6/src/bootstrap/lib.rs#L399-L405
810 if 'SUDO_USER' in os.environ and not self.use_vendored_sources:
812 self.use_vendored_sources = True
813 print('info: looks like you\'re trying to run this command as root')
814 print(' and so in order to preserve your $HOME this will now')
815 print(' use vendored sources by default.')
817 cargo_dir = os.path.join(self.rust_root, '.cargo')
818 if self.use_vendored_sources:
819 vendor_dir = os.path.join(self.rust_root, 'vendor')
820 if not os.path.exists(vendor_dir):
821 sync_dirs = "--sync ./src/tools/rust-analyzer/Cargo.toml " \
822 "--sync ./compiler/rustc_codegen_cranelift/Cargo.toml " \
823 "--sync ./src/bootstrap/Cargo.toml "
824 print('error: vendoring required, but vendor directory does not exist.')
825 print(' Run `cargo vendor {}` to initialize the '
826 'vendor directory.'.format(sync_dirs))
827 print('Alternatively, use the pre-vendored `rustc-src` dist component.')
828 raise Exception("{} not found".format(vendor_dir))
830 if not os.path.exists(cargo_dir):
831 print('error: vendoring required, but .cargo/config does not exist.')
832 raise Exception("{} not found".format(cargo_dir))
834 if os.path.exists(cargo_dir):
835 shutil.rmtree(cargo_dir)
837 def bootstrap(help_triggered):
838 """Configure, fetch, build and run the initial bootstrap"""
840 # If the user is asking for help, let them know that the whole download-and-build
841 # process has to happen before anything is printed out.
843 print("info: Downloading and building bootstrap before processing --help")
844 print(" command. See src/bootstrap/README.md for help with common")
847 parser = argparse.ArgumentParser(description='Build rust')
848 parser.add_argument('--config')
849 parser.add_argument('--build-dir')
850 parser.add_argument('--build')
851 parser.add_argument('--color', choices=['always', 'never', 'auto'])
852 parser.add_argument('--clean', action='store_true')
853 parser.add_argument('-v', '--verbose', action='count', default=0)
855 args = [a for a in sys.argv if a != '-h' and a != '--help']
856 args, _ = parser.parse_known_args(args)
858 # Configure initial bootstrap
860 build.rust_root = os.path.abspath(os.path.join(__file__, '../../..'))
861 build.verbose = args.verbose
862 build.clean = args.clean
864 # Read from `--config`, then `RUST_BOOTSTRAP_CONFIG`, then `./config.toml`,
865 # then `config.toml` in the root directory.
866 toml_path = args.config or os.getenv('RUST_BOOTSTRAP_CONFIG')
867 using_default_path = toml_path is None
868 if using_default_path:
869 toml_path = 'config.toml'
870 if not os.path.exists(toml_path):
871 toml_path = os.path.join(build.rust_root, toml_path)
873 # Give a hard error if `--config` or `RUST_BOOTSTRAP_CONFIG` are set to a missing path,
874 # but not if `config.toml` hasn't been created.
875 if not using_default_path or os.path.exists(toml_path):
876 with open(toml_path) as config:
877 build.config_toml = config.read()
879 profile = build.get_toml('profile')
880 if profile is not None:
881 include_file = 'config.{}.toml'.format(profile)
882 include_dir = os.path.join(build.rust_root, 'src', 'bootstrap', 'defaults')
883 include_path = os.path.join(include_dir, include_file)
884 # HACK: This works because `build.get_toml()` returns the first match it finds for a
885 # specific key, so appending our defaults at the end allows the user to override them
886 with open(include_path) as included_toml:
887 build.config_toml += os.linesep + included_toml.read()
889 config_verbose = build.get_toml('verbose', 'build')
890 if config_verbose is not None:
891 build.verbose = max(build.verbose, int(config_verbose))
893 build.use_vendored_sources = build.get_toml('vendor', 'build') == 'true'
895 build.use_locked_deps = build.get_toml('locked-deps', 'build') == 'true'
897 build.check_vendored_status()
899 build_dir = args.build_dir or build.get_toml('build-dir', 'build') or 'build'
900 build.build_dir = os.path.abspath(build_dir)
902 with open(os.path.join(build.rust_root, "src", "stage0.json")) as f:
904 build.checksums_sha256 = data["checksums_sha256"]
905 build.stage0_compiler = Stage0Toolchain(data["compiler"])
907 build.set_dist_environment(data["config"]["dist_server"])
909 build.build = args.build or build.build_triple()
911 if not os.path.exists(build.build_dir):
912 os.makedirs(build.build_dir)
914 # Fetch/build the bootstrap
915 build.download_toolchain()
917 build.build_bootstrap(args.color)
921 args = [build.bootstrap_binary()]
922 args.extend(sys.argv[1:])
923 env = os.environ.copy()
924 env["BOOTSTRAP_PARENT_ID"] = str(os.getpid())
925 env["BOOTSTRAP_PYTHON"] = sys.executable
926 run(args, env=env, verbose=build.verbose, is_bootstrap=True)
930 """Entry point for the bootstrap process"""
933 # x.py help <cmd> ...
934 if len(sys.argv) > 1 and sys.argv[1] == 'help':
935 sys.argv = [sys.argv[0], '-h'] + sys.argv[2:]
938 '-h' in sys.argv) or ('--help' in sys.argv) or (len(sys.argv) == 1)
940 bootstrap(help_triggered)
941 if not help_triggered:
942 print("Build completed successfully in {}".format(
943 format_build_time(time() - start_time)))
944 except (SystemExit, KeyboardInterrupt) as error:
945 if hasattr(error, 'code') and isinstance(error.code, int):
946 exit_code = error.code
950 if not help_triggered:
951 print("Build completed unsuccessfully in {}".format(
952 format_build_time(time() - start_time)))
956 if __name__ == '__main__':