1 from __future__ import absolute_import, division, print_function
5 import distutils.version
18 def get(url, path, verbose=False):
20 sha_url = url + suffix
21 with tempfile.NamedTemporaryFile(delete=False) as temp_file:
22 temp_path = temp_file.name
23 with tempfile.NamedTemporaryFile(suffix=suffix, delete=False) as sha_file:
24 sha_path = sha_file.name
27 download(sha_path, sha_url, False, verbose)
28 if os.path.exists(path):
29 if verify(path, sha_path, False):
31 print("using already-download file", path)
35 print("ignoring already-download file",
36 path, "due to failed verification")
38 download(temp_path, url, True, verbose)
39 if not verify(temp_path, sha_path, verbose):
40 raise RuntimeError("failed verification")
42 print("moving {} to {}".format(temp_path, path))
43 shutil.move(temp_path, path)
45 delete_if_present(sha_path, verbose)
46 delete_if_present(temp_path, verbose)
49 def delete_if_present(path, verbose):
50 """Remove the given file if present"""
51 if os.path.isfile(path):
53 print("removing", path)
57 def download(path, url, probably_big, verbose):
60 _download(path, url, probably_big, verbose, True)
63 print("\nspurious failure, trying again")
64 _download(path, url, probably_big, verbose, False)
67 def _download(path, url, probably_big, verbose, exception):
68 if probably_big or verbose:
69 print("downloading {}".format(url))
70 # see http://serverfault.com/questions/301128/how-to-download
71 if sys.platform == 'win32':
72 run(["PowerShell.exe", "/nologo", "-Command",
73 "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12;",
74 "(New-Object System.Net.WebClient).DownloadFile('{}', '{}')".format(url, path)],
78 if probably_big or verbose:
82 require(["curl", "--version"])
84 "-y", "30", "-Y", "10", # timeout if speed is < 10 bytes/sec for > 30 seconds
85 "--connect-timeout", "30", # timeout if cannot connect within 30 seconds
86 "--retry", "3", "-Sf", "-o", path, url],
91 def verify(path, sha_path, verbose):
92 """Check if the sha256 sum of the given path is valid"""
94 print("verifying", path)
95 with open(path, "rb") as source:
96 found = hashlib.sha256(source.read()).hexdigest()
97 with open(sha_path, "r") as sha256sum:
98 expected = sha256sum.readline().split()[0]
99 verified = found == expected
101 print("invalid checksum:\n"
103 " expected: {}".format(found, expected))
107 def unpack(tarball, tarball_suffix, dst, verbose=False, match=None):
108 """Unpack the given tarball file"""
109 print("extracting", tarball)
110 fname = os.path.basename(tarball).replace(tarball_suffix, "")
111 with contextlib.closing(tarfile.open(tarball)) as tar:
112 for member in tar.getnames():
113 if "/" not in member:
115 name = member.replace(fname + "/", "", 1)
116 if match is not None and not name.startswith(match):
118 name = name[len(match) + 1:]
120 dst_path = os.path.join(dst, name)
122 print(" extracting", member)
123 tar.extract(member, dst)
124 src_path = os.path.join(dst, member)
125 if os.path.isdir(src_path) and os.path.exists(dst_path):
127 shutil.move(src_path, dst_path)
128 shutil.rmtree(os.path.join(dst, fname))
131 def run(args, verbose=False, exception=False, **kwargs):
132 """Run a child program in a new process"""
134 print("running: " + ' '.join(args))
136 # Use Popen here instead of call() as it apparently allows powershell on
137 # Windows to not lock up waiting for input presumably.
138 ret = subprocess.Popen(args, **kwargs)
141 err = "failed to run: " + ' '.join(args)
142 if verbose or exception:
143 raise RuntimeError(err)
147 def require(cmd, exit=True):
148 '''Run a command, returning its output.
150 If `exit` is `True`, exit the process.
151 Otherwise, return None.'''
153 return subprocess.check_output(cmd).strip()
154 except (subprocess.CalledProcessError, OSError) as exc:
157 print("error: unable to run `{}`: {}".format(' '.join(cmd), exc))
158 print("Please make sure it's installed and in the path.")
162 def stage0_data(rust_root):
163 """Build a dictionary from stage0.txt"""
164 nightlies = os.path.join(rust_root, "src/stage0.txt")
165 with open(nightlies, 'r') as nightlies:
166 lines = [line.rstrip() for line in nightlies
167 if not line.startswith("#")]
168 return dict([line.split(": ", 1) for line in lines if line])
171 def format_build_time(duration):
172 """Return a nicer format for build time
174 >>> format_build_time('300')
177 return str(datetime.timedelta(seconds=int(duration)))
180 def default_build_triple():
181 """Build triple as in LLVM"""
182 default_encoding = sys.getdefaultencoding()
183 required = sys.platform != 'win32'
184 ostype = require(["uname", "-s"], exit=required)
185 cputype = require(['uname', '-m'], exit=required)
187 # If we do not have `uname`, assume Windows.
188 if ostype is None or cputype is None:
189 return 'x86_64-pc-windows-msvc'
191 ostype = ostype.decode(default_encoding)
192 cputype = cputype.decode(default_encoding)
194 # The goal here is to come up with the same triple as LLVM would,
195 # at least for the subset of platforms we're willing to target.
197 'Darwin': 'apple-darwin',
198 'DragonFly': 'unknown-dragonfly',
199 'FreeBSD': 'unknown-freebsd',
200 'Haiku': 'unknown-haiku',
201 'NetBSD': 'unknown-netbsd',
202 'OpenBSD': 'unknown-openbsd'
205 # Consider the direct transformation first and then the special cases
206 if ostype in ostype_mapper:
207 ostype = ostype_mapper[ostype]
208 elif ostype == 'Linux':
209 os_from_sp = subprocess.check_output(
210 ['uname', '-o']).strip().decode(default_encoding)
211 if os_from_sp == 'Android':
212 ostype = 'linux-android'
214 ostype = 'unknown-linux-gnu'
215 elif ostype == 'SunOS':
216 ostype = 'sun-solaris'
217 # On Solaris, uname -m will return a machine classification instead
218 # of a cpu type, so uname -p is recommended instead. However, the
219 # output from that option is too generic for our purposes (it will
220 # always emit 'i386' on x86/amd64 systems). As such, isainfo -k
221 # must be used instead.
222 cputype = require(['isainfo', '-k']).decode(default_encoding)
223 elif ostype.startswith('MINGW'):
224 # msys' `uname` does not print gcc configuration, but prints msys
225 # configuration. so we cannot believe `uname -m`:
226 # msys1 is always i686 and msys2 is always x86_64.
227 # instead, msys defines $MSYSTEM which is MINGW32 on i686 and
229 ostype = 'pc-windows-gnu'
231 if os.environ.get('MSYSTEM') == 'MINGW64':
233 elif ostype.startswith('MSYS'):
234 ostype = 'pc-windows-gnu'
235 elif ostype.startswith('CYGWIN_NT'):
237 if ostype.endswith('WOW64'):
239 ostype = 'pc-windows-gnu'
240 elif sys.platform == 'win32':
241 # Some Windows platforms might have a `uname` command that returns a
242 # non-standard string (e.g. gnuwin32 tools returns `windows32`). In
243 # these cases, fall back to using sys.platform.
244 return 'x86_64-pc-windows-msvc'
246 err = "unknown OS type: {}".format(ostype)
249 if cputype == 'powerpc' and ostype == 'unknown-freebsd':
250 cputype = subprocess.check_output(
251 ['uname', '-p']).strip().decode(default_encoding)
254 'aarch64': 'aarch64',
261 'powerpc': 'powerpc',
262 'powerpc64': 'powerpc64',
263 'powerpc64le': 'powerpc64le',
265 'ppc64': 'powerpc64',
266 'ppc64le': 'powerpc64le',
274 # Consider the direct transformation first and then the special cases
275 if cputype in cputype_mapper:
276 cputype = cputype_mapper[cputype]
277 elif cputype in {'xscale', 'arm'}:
279 if ostype == 'linux-android':
280 ostype = 'linux-androideabi'
281 elif ostype == 'unknown-freebsd':
282 cputype = subprocess.check_output(
283 ['uname', '-p']).strip().decode(default_encoding)
284 ostype = 'unknown-freebsd'
285 elif cputype == 'armv6l':
287 if ostype == 'linux-android':
288 ostype = 'linux-androideabi'
291 elif cputype in {'armv7l', 'armv8l'}:
293 if ostype == 'linux-android':
294 ostype = 'linux-androideabi'
297 elif cputype == 'mips':
298 if sys.byteorder == 'big':
300 elif sys.byteorder == 'little':
303 raise ValueError("unknown byteorder: {}".format(sys.byteorder))
304 elif cputype == 'mips64':
305 if sys.byteorder == 'big':
307 elif sys.byteorder == 'little':
310 raise ValueError('unknown byteorder: {}'.format(sys.byteorder))
311 # only the n64 ABI is supported, indicate it
313 elif cputype == 'sparc' or cputype == 'sparcv9' or cputype == 'sparc64':
316 err = "unknown cpu type: {}".format(cputype)
319 return "{}-{}".format(cputype, ostype)
322 @contextlib.contextmanager
323 def output(filepath):
324 tmp = filepath + '.tmp'
325 with open(tmp, 'w') as f:
328 os.remove(filepath) # PermissionError/OSError on Win32 if in use
329 os.rename(tmp, filepath)
331 shutil.copy2(tmp, filepath)
335 class RustBuild(object):
336 """Provide all the methods required to build Rust"""
338 self.cargo_channel = ''
340 self._download_url = ''
341 self.rustc_channel = ''
342 self.rustfmt_channel = ''
346 self.config_toml = ''
348 self.use_locked_deps = ''
349 self.use_vendored_sources = ''
351 self.git_version = None
352 self.nix_deps_dir = None
354 def download_stage0(self):
355 """Fetch the build system for Rust, written in Rust
357 This method will build a cache directory, then it will fetch the
358 tarball which has the stage0 compiler used to then bootstrap the Rust
361 Each downloaded tarball is extracted, after that, the script
362 will move all the content to the right place.
364 rustc_channel = self.rustc_channel
365 cargo_channel = self.cargo_channel
366 rustfmt_channel = self.rustfmt_channel
370 with tempfile.NamedTemporaryFile(delete=False) as temp_file:
371 temp_path = temp_file.name
372 with tarfile.open(temp_path, "w:xz"):
375 except tarfile.CompressionError:
378 if self.rustc().startswith(self.bin_root()) and \
379 (not os.path.exists(self.rustc()) or
380 self.program_out_of_date(self.rustc_stamp())):
381 if os.path.exists(self.bin_root()):
382 shutil.rmtree(self.bin_root())
383 tarball_suffix = '.tar.xz' if support_xz() else '.tar.gz'
384 filename = "rust-std-{}-{}{}".format(
385 rustc_channel, self.build, tarball_suffix)
386 pattern = "rust-std-{}".format(self.build)
387 self._download_stage0_helper(filename, pattern, tarball_suffix)
389 filename = "rustc-{}-{}{}".format(rustc_channel, self.build,
391 self._download_stage0_helper(filename, "rustc", tarball_suffix)
392 self.fix_bin_or_dylib("{}/bin/rustc".format(self.bin_root()))
393 self.fix_bin_or_dylib("{}/bin/rustdoc".format(self.bin_root()))
394 lib_dir = "{}/lib".format(self.bin_root())
395 for lib in os.listdir(lib_dir):
396 if lib.endswith(".so"):
397 self.fix_bin_or_dylib("{}/{}".format(lib_dir, lib))
398 with output(self.rustc_stamp()) as rust_stamp:
399 rust_stamp.write(self.date)
401 if self.cargo().startswith(self.bin_root()) and \
402 (not os.path.exists(self.cargo()) or
403 self.program_out_of_date(self.cargo_stamp())):
404 tarball_suffix = '.tar.xz' if support_xz() else '.tar.gz'
405 filename = "cargo-{}-{}{}".format(cargo_channel, self.build,
407 self._download_stage0_helper(filename, "cargo", tarball_suffix)
408 self.fix_bin_or_dylib("{}/bin/cargo".format(self.bin_root()))
409 with output(self.cargo_stamp()) as cargo_stamp:
410 cargo_stamp.write(self.date)
412 if self.rustfmt() and self.rustfmt().startswith(self.bin_root()) and (
413 not os.path.exists(self.rustfmt())
414 or self.program_out_of_date(self.rustfmt_stamp(), self.rustfmt_channel)
417 tarball_suffix = '.tar.xz' if support_xz() else '.tar.gz'
418 [channel, date] = rustfmt_channel.split('-', 1)
419 filename = "rustfmt-{}-{}{}".format(channel, self.build, tarball_suffix)
420 self._download_stage0_helper(filename, "rustfmt-preview", tarball_suffix, date)
421 self.fix_bin_or_dylib("{}/bin/rustfmt".format(self.bin_root()))
422 self.fix_bin_or_dylib("{}/bin/cargo-fmt".format(self.bin_root()))
423 with output(self.rustfmt_stamp()) as rustfmt_stamp:
424 rustfmt_stamp.write(self.date + self.rustfmt_channel)
426 def _download_stage0_helper(self, filename, pattern, tarball_suffix, date=None):
429 cache_dst = os.path.join(self.build_dir, "cache")
430 rustc_cache = os.path.join(cache_dst, date)
431 if not os.path.exists(rustc_cache):
432 os.makedirs(rustc_cache)
434 url = "{}/dist/{}".format(self._download_url, date)
435 tarball = os.path.join(rustc_cache, filename)
436 if not os.path.exists(tarball):
437 get("{}/{}".format(url, filename), tarball, verbose=self.verbose)
438 unpack(tarball, tarball_suffix, self.bin_root(), match=pattern, verbose=self.verbose)
440 def fix_bin_or_dylib(self, fname):
441 """Modifies the interpreter section of 'fname' to fix the dynamic linker,
442 or the RPATH section, to fix the dynamic library search path
444 This method is only required on NixOS and uses the PatchELF utility to
445 change the interpreter/RPATH of ELF executables.
447 Please see https://nixos.org/patchelf.html for more information
449 default_encoding = sys.getdefaultencoding()
451 ostype = subprocess.check_output(
452 ['uname', '-s']).strip().decode(default_encoding)
453 except subprocess.CalledProcessError:
455 except OSError as reason:
456 if getattr(reason, 'winerror', None) is not None:
460 if ostype != "Linux":
463 if not os.path.exists("/etc/NIXOS"):
465 if os.path.exists("/lib"):
468 # At this point we're pretty sure the user is running NixOS
469 nix_os_msg = "info: you seem to be running NixOS. Attempting to patch"
470 print(nix_os_msg, fname)
472 # Only build `stage0/.nix-deps` once.
473 nix_deps_dir = self.nix_deps_dir
475 nix_deps_dir = "{}/.nix-deps".format(self.bin_root())
476 if not os.path.exists(nix_deps_dir):
477 os.makedirs(nix_deps_dir)
480 # Needed for the path of `ld-linux.so` (via `nix-support/dynamic-linker`).
481 "stdenv.cc.bintools",
483 # Needed as a system dependency of `libLLVM-*.so`.
486 # Needed for patching ELF binaries (see doc comment above).
490 # Run `nix-build` to "build" each dependency (which will likely reuse
491 # the existing `/nix/store` copy, or at most download a pre-built copy).
492 # Importantly, we don't rely on `nix-build` printing the `/nix/store`
493 # path on stdout, but use `-o` to symlink it into `stage0/.nix-deps/$dep`,
494 # ensuring garbage collection will never remove the `/nix/store` path
495 # (which would break our patched binaries that hardcode those paths).
498 subprocess.check_output([
499 "nix-build", "<nixpkgs>",
501 "-o", "{}/{}".format(nix_deps_dir, dep),
503 except subprocess.CalledProcessError as reason:
504 print("warning: failed to call nix-build:", reason)
507 self.nix_deps_dir = nix_deps_dir
509 patchelf = "{}/patchelf/bin/patchelf".format(nix_deps_dir)
511 if fname.endswith(".so"):
512 # Dynamic library, patch RPATH to point to system dependencies.
513 dylib_deps = ["zlib"]
515 # Relative default, all binary and dynamic libraries we ship
516 # appear to have this (even when `../lib` is redundant).
518 ] + ["{}/{}/lib".format(nix_deps_dir, dep) for dep in dylib_deps]
519 patchelf_args = ["--set-rpath", ":".join(rpath_entries)]
521 bintools_dir = "{}/stdenv.cc.bintools".format(nix_deps_dir)
522 with open("{}/nix-support/dynamic-linker".format(bintools_dir)) as dynamic_linker:
523 patchelf_args = ["--set-interpreter", dynamic_linker.read().rstrip()]
526 subprocess.check_output([patchelf] + patchelf_args + [fname])
527 except subprocess.CalledProcessError as reason:
528 print("warning: failed to call patchelf:", reason)
531 def rustc_stamp(self):
532 """Return the path for .rustc-stamp
535 >>> rb.build_dir = "build"
536 >>> rb.rustc_stamp() == os.path.join("build", "stage0", ".rustc-stamp")
539 return os.path.join(self.bin_root(), '.rustc-stamp')
541 def cargo_stamp(self):
542 """Return the path for .cargo-stamp
545 >>> rb.build_dir = "build"
546 >>> rb.cargo_stamp() == os.path.join("build", "stage0", ".cargo-stamp")
549 return os.path.join(self.bin_root(), '.cargo-stamp')
551 def rustfmt_stamp(self):
552 """Return the path for .rustfmt-stamp
555 >>> rb.build_dir = "build"
556 >>> rb.rustfmt_stamp() == os.path.join("build", "stage0", ".rustfmt-stamp")
559 return os.path.join(self.bin_root(), '.rustfmt-stamp')
561 def program_out_of_date(self, stamp_path, extra=""):
562 """Check if the given program stamp is out of date"""
563 if not os.path.exists(stamp_path) or self.clean:
565 with open(stamp_path, 'r') as stamp:
566 return (self.date + extra) != stamp.read()
569 """Return the binary root directory
572 >>> rb.build_dir = "build"
573 >>> rb.bin_root() == os.path.join("build", "stage0")
576 When the 'build' property is given should be a nested directory:
578 >>> rb.build = "devel"
579 >>> rb.bin_root() == os.path.join("build", "devel", "stage0")
582 return os.path.join(self.build_dir, self.build, "stage0")
584 def get_toml(self, key, section=None):
585 """Returns the value of the given key in config.toml, otherwise returns None
588 >>> rb.config_toml = 'key1 = "value1"\\nkey2 = "value2"'
589 >>> rb.get_toml("key2")
592 If the key does not exists, the result is None:
594 >>> rb.get_toml("key3") is None
597 Optionally also matches the section the key appears in
599 >>> rb.config_toml = '[a]\\nkey = "value1"\\n[b]\\nkey = "value2"'
600 >>> rb.get_toml('key', 'a')
602 >>> rb.get_toml('key', 'b')
604 >>> rb.get_toml('key', 'c') is None
607 >>> rb.config_toml = 'key1 = true'
608 >>> rb.get_toml("key1")
613 for line in self.config_toml.splitlines():
614 section_match = re.match(r'^\s*\[(.*)\]\s*$', line)
615 if section_match is not None:
616 cur_section = section_match.group(1)
618 match = re.match(r'^{}\s*=(.*)$'.format(key), line)
619 if match is not None:
620 value = match.group(1)
621 if section is None or section == cur_section:
622 return self.get_string(value) or value.strip()
626 """Return config path for cargo"""
627 return self.program_config('cargo')
630 """Return config path for rustc"""
631 return self.program_config('rustc')
634 """Return config path for rustfmt"""
635 if not self.rustfmt_channel:
637 return self.program_config('rustfmt')
639 def program_config(self, program):
640 """Return config path for the given program
643 >>> rb.config_toml = 'rustc = "rustc"\\n'
644 >>> rb.program_config('rustc')
646 >>> rb.config_toml = ''
647 >>> cargo_path = rb.program_config('cargo')
648 >>> cargo_path.rstrip(".exe") == os.path.join(rb.bin_root(),
652 config = self.get_toml(program)
654 return os.path.expanduser(config)
655 return os.path.join(self.bin_root(), "bin", "{}{}".format(
656 program, self.exe_suffix()))
659 def get_string(line):
660 """Return the value between double quotes
662 >>> RustBuild.get_string(' "devel" ')
664 >>> RustBuild.get_string(" 'devel' ")
666 >>> RustBuild.get_string('devel') is None
668 >>> RustBuild.get_string(' "devel ')
671 start = line.find('"')
673 end = start + 1 + line[start + 1:].find('"')
674 return line[start + 1:end]
675 start = line.find('\'')
677 end = start + 1 + line[start + 1:].find('\'')
678 return line[start + 1:end]
683 """Return a suffix for executables"""
684 if sys.platform == 'win32':
688 def bootstrap_binary(self):
689 """Return the path of the bootstrap binary
692 >>> rb.build_dir = "build"
693 >>> rb.bootstrap_binary() == os.path.join("build", "bootstrap",
694 ... "debug", "bootstrap")
697 return os.path.join(self.build_dir, "bootstrap", "debug", "bootstrap")
699 def build_bootstrap(self):
700 """Build bootstrap"""
701 build_dir = os.path.join(self.build_dir, "bootstrap")
702 if self.clean and os.path.exists(build_dir):
703 shutil.rmtree(build_dir)
704 env = os.environ.copy()
705 # `CARGO_BUILD_TARGET` breaks bootstrap build.
706 # See also: <https://github.com/rust-lang/rust/issues/70208>.
707 if "CARGO_BUILD_TARGET" in env:
708 del env["CARGO_BUILD_TARGET"]
709 env["CARGO_TARGET_DIR"] = build_dir
710 env["RUSTC"] = self.rustc()
711 env["LD_LIBRARY_PATH"] = os.path.join(self.bin_root(), "lib") + \
712 (os.pathsep + env["LD_LIBRARY_PATH"]) \
713 if "LD_LIBRARY_PATH" in env else ""
714 env["DYLD_LIBRARY_PATH"] = os.path.join(self.bin_root(), "lib") + \
715 (os.pathsep + env["DYLD_LIBRARY_PATH"]) \
716 if "DYLD_LIBRARY_PATH" in env else ""
717 env["LIBRARY_PATH"] = os.path.join(self.bin_root(), "lib") + \
718 (os.pathsep + env["LIBRARY_PATH"]) \
719 if "LIBRARY_PATH" in env else ""
720 # preserve existing RUSTFLAGS
721 env.setdefault("RUSTFLAGS", "")
722 env["RUSTFLAGS"] += " -Cdebuginfo=2"
724 build_section = "target.{}".format(self.build_triple())
726 if self.get_toml("crt-static", build_section) == "true":
727 target_features += ["+crt-static"]
728 elif self.get_toml("crt-static", build_section) == "false":
729 target_features += ["-crt-static"]
731 env["RUSTFLAGS"] += " -C target-feature=" + (",".join(target_features))
732 target_linker = self.get_toml("linker", build_section)
733 if target_linker is not None:
734 env["RUSTFLAGS"] += " -C linker=" + target_linker
735 env["RUSTFLAGS"] += " -Wrust_2018_idioms -Wunused_lifetimes"
736 if self.get_toml("deny-warnings", "rust") != "false":
737 env["RUSTFLAGS"] += " -Dwarnings"
739 env["PATH"] = os.path.join(self.bin_root(), "bin") + \
740 os.pathsep + env["PATH"]
741 if not os.path.isfile(self.cargo()):
742 raise Exception("no cargo executable found at `{}`".format(
744 args = [self.cargo(), "build", "--manifest-path",
745 os.path.join(self.rust_root, "src/bootstrap/Cargo.toml")]
746 for _ in range(1, self.verbose):
747 args.append("--verbose")
748 if self.use_locked_deps:
749 args.append("--locked")
750 if self.use_vendored_sources:
751 args.append("--frozen")
752 run(args, env=env, verbose=self.verbose)
754 def build_triple(self):
755 """Build triple as in LLVM"""
756 config = self.get_toml('build')
759 return default_build_triple()
761 def check_submodule(self, module, slow_submodules):
762 if not slow_submodules:
763 checked_out = subprocess.Popen(["git", "rev-parse", "HEAD"],
764 cwd=os.path.join(self.rust_root, module),
765 stdout=subprocess.PIPE)
770 def update_submodule(self, module, checked_out, recorded_submodules):
771 module_path = os.path.join(self.rust_root, module)
773 if checked_out is not None:
774 default_encoding = sys.getdefaultencoding()
775 checked_out = checked_out.communicate()[0].decode(default_encoding).strip()
776 if recorded_submodules[module] == checked_out:
779 print("Updating submodule", module)
781 run(["git", "submodule", "-q", "sync", module],
782 cwd=self.rust_root, verbose=self.verbose)
784 update_args = ["git", "submodule", "update", "--init", "--recursive"]
785 if self.git_version >= distutils.version.LooseVersion("2.11.0"):
786 update_args.append("--progress")
787 update_args.append(module)
788 run(update_args, cwd=self.rust_root, verbose=self.verbose, exception=True)
790 run(["git", "reset", "-q", "--hard"],
791 cwd=module_path, verbose=self.verbose)
792 run(["git", "clean", "-qdfx"],
793 cwd=module_path, verbose=self.verbose)
795 def update_submodules(self):
796 """Update submodules"""
797 if (not os.path.exists(os.path.join(self.rust_root, ".git"))) or \
798 self.get_toml('submodules') == "false":
801 default_encoding = sys.getdefaultencoding()
803 # check the existence and version of 'git' command
804 git_version_str = require(['git', '--version']).split()[2].decode(default_encoding)
805 self.git_version = distutils.version.LooseVersion(git_version_str)
807 slow_submodules = self.get_toml('fast-submodules') == "false"
810 print('Unconditionally updating all submodules')
812 print('Updating only changed submodules')
813 default_encoding = sys.getdefaultencoding()
814 submodules = [s.split(' ', 1)[1] for s in subprocess.check_output(
815 ["git", "config", "--file",
816 os.path.join(self.rust_root, ".gitmodules"),
817 "--get-regexp", "path"]
818 ).decode(default_encoding).splitlines()]
819 filtered_submodules = []
820 submodules_names = []
821 for module in submodules:
822 if module.endswith("llvm-project"):
823 if self.get_toml('llvm-config') and self.get_toml('lld') != 'true':
825 check = self.check_submodule(module, slow_submodules)
826 filtered_submodules.append((module, check))
827 submodules_names.append(module)
828 recorded = subprocess.Popen(["git", "ls-tree", "HEAD"] + submodules_names,
829 cwd=self.rust_root, stdout=subprocess.PIPE)
830 recorded = recorded.communicate()[0].decode(default_encoding).strip().splitlines()
831 recorded_submodules = {}
832 for data in recorded:
834 recorded_submodules[data[3]] = data[2]
835 for module in filtered_submodules:
836 self.update_submodule(module[0], module[1], recorded_submodules)
837 print("Submodules updated in %.2f seconds" % (time() - start_time))
839 def set_normal_environment(self):
840 """Set download URL for normal environment"""
841 if 'RUSTUP_DIST_SERVER' in os.environ:
842 self._download_url = os.environ['RUSTUP_DIST_SERVER']
844 self._download_url = 'https://static.rust-lang.org'
846 def set_dev_environment(self):
847 """Set download URL for development environment"""
848 if 'RUSTUP_DEV_DIST_SERVER' in os.environ:
849 self._download_url = os.environ['RUSTUP_DEV_DIST_SERVER']
851 self._download_url = 'https://dev-static.rust-lang.org'
853 def check_vendored_status(self):
854 """Check that vendoring is configured properly"""
855 vendor_dir = os.path.join(self.rust_root, 'vendor')
856 if 'SUDO_USER' in os.environ and not self.use_vendored_sources:
857 if os.environ.get('USER') != os.environ['SUDO_USER']:
858 self.use_vendored_sources = True
859 print('info: looks like you are running this command under `sudo`')
860 print(' and so in order to preserve your $HOME this will now')
861 print(' use vendored sources by default.')
862 if not os.path.exists(vendor_dir):
863 print('error: vendoring required, but vendor directory does not exist.')
864 print(' Run `cargo vendor` without sudo to initialize the '
866 raise Exception("{} not found".format(vendor_dir))
868 if self.use_vendored_sources:
869 if not os.path.exists('.cargo'):
870 os.makedirs('.cargo')
871 with output('.cargo/config') as cargo_config:
873 "[source.crates-io]\n"
874 "replace-with = 'vendored-sources'\n"
875 "registry = 'https://example.com'\n"
877 "[source.vendored-sources]\n"
878 "directory = '{}/vendor'\n"
879 .format(self.rust_root))
881 if os.path.exists('.cargo'):
882 shutil.rmtree('.cargo')
884 def ensure_vendored(self):
885 """Ensure that the vendored sources are available if needed"""
886 vendor_dir = os.path.join(self.rust_root, 'vendor')
887 # Note that this does not handle updating the vendored dependencies if
888 # the rust git repository is updated. Normal development usually does
889 # not use vendoring, so hopefully this isn't too much of a problem.
890 if self.use_vendored_sources and not os.path.exists(vendor_dir):
891 run([self.cargo(), "vendor", "--sync=./src/tools/rust-analyzer/Cargo.toml"],
892 verbose=self.verbose, cwd=self.rust_root)
895 def bootstrap(help_triggered):
896 """Configure, fetch, build and run the initial bootstrap"""
898 # If the user is asking for help, let them know that the whole download-and-build
899 # process has to happen before anything is printed out.
901 print("info: Downloading and building bootstrap before processing --help")
902 print(" command. See src/bootstrap/README.md for help with common")
905 parser = argparse.ArgumentParser(description='Build rust')
906 parser.add_argument('--config')
907 parser.add_argument('--build')
908 parser.add_argument('--src')
909 parser.add_argument('--clean', action='store_true')
910 parser.add_argument('-v', '--verbose', action='count', default=0)
912 args = [a for a in sys.argv if a != '-h' and a != '--help']
913 args, _ = parser.parse_known_args(args)
915 # Configure initial bootstrap
917 build.rust_root = args.src or os.path.abspath(os.path.join(__file__, '../../..'))
918 build.verbose = args.verbose
919 build.clean = args.clean
921 # Read from `RUST_BOOTSTRAP_CONFIG`, then `--config`, then fallback to `config.toml` (if it
923 toml_path = os.getenv('RUST_BOOTSTRAP_CONFIG') or args.config
924 if not toml_path and os.path.exists('config.toml'):
925 toml_path = 'config.toml'
928 if not os.path.exists(toml_path):
929 toml_path = os.path.join(build.rust_root, toml_path)
931 with open(toml_path) as config:
932 build.config_toml = config.read()
934 config_verbose = build.get_toml('verbose', 'build')
935 if config_verbose is not None:
936 build.verbose = max(build.verbose, int(config_verbose))
938 build.use_vendored_sources = build.get_toml('vendor', 'build') == 'true'
940 build.use_locked_deps = build.get_toml('locked-deps', 'build') == 'true'
942 build.check_vendored_status()
944 build_dir = build.get_toml('build-dir', 'build') or 'build'
945 build.build_dir = os.path.abspath(build_dir.replace("$ROOT", build.rust_root))
947 data = stage0_data(build.rust_root)
948 build.date = data['date']
949 build.rustc_channel = data['rustc']
950 build.cargo_channel = data['cargo']
952 if "rustfmt" in data:
953 build.rustfmt_channel = data['rustfmt']
956 build.set_dev_environment()
958 build.set_normal_environment()
960 build.update_submodules()
962 # Fetch/build the bootstrap
963 build.build = args.build or build.build_triple()
964 build.download_stage0()
966 build.ensure_vendored()
967 build.build_bootstrap()
971 args = [build.bootstrap_binary()]
972 args.extend(sys.argv[1:])
973 env = os.environ.copy()
974 env["BUILD"] = build.build
975 env["SRC"] = build.rust_root
976 env["BOOTSTRAP_PARENT_ID"] = str(os.getpid())
977 env["BOOTSTRAP_PYTHON"] = sys.executable
978 env["BUILD_DIR"] = build.build_dir
979 env["RUSTC_BOOTSTRAP"] = '1'
980 env["CARGO"] = build.cargo()
981 env["RUSTC"] = build.rustc()
983 env["BOOTSTRAP_CONFIG"] = toml_path
985 env["RUSTFMT"] = build.rustfmt()
986 run(args, env=env, verbose=build.verbose)
990 """Entry point for the bootstrap process"""
993 # x.py help <cmd> ...
994 if len(sys.argv) > 1 and sys.argv[1] == 'help':
995 sys.argv = [sys.argv[0], '-h'] + sys.argv[2:]
998 '-h' in sys.argv) or ('--help' in sys.argv) or (len(sys.argv) == 1)
1000 bootstrap(help_triggered)
1001 if not help_triggered:
1002 print("Build completed successfully in {}".format(
1003 format_build_time(time() - start_time)))
1004 except (SystemExit, KeyboardInterrupt) as error:
1005 if hasattr(error, 'code') and isinstance(error.code, int):
1006 exit_code = error.code
1010 if not help_triggered:
1011 print("Build completed unsuccessfully in {}".format(
1012 format_build_time(time() - start_time)))
1016 if __name__ == '__main__':