]> git.lizzy.rs Git - rust.git/blob - src/bootstrap/bootstrap.py
Add #[track_caller] for some function in core::mem.
[rust.git] / src / bootstrap / bootstrap.py
1 from __future__ import absolute_import, division, print_function
2 import argparse
3 import contextlib
4 import datetime
5 import distutils.version
6 import hashlib
7 import os
8 import re
9 import shutil
10 import subprocess
11 import sys
12 import tarfile
13 import tempfile
14
15 from time import time
16
17 def support_xz():
18     try:
19         with tempfile.NamedTemporaryFile(delete=False) as temp_file:
20             temp_path = temp_file.name
21         with tarfile.open(temp_path, "w:xz"):
22             pass
23         return True
24     except tarfile.CompressionError:
25         return False
26
27 def get(url, path, verbose=False, do_verify=True):
28     suffix = '.sha256'
29     sha_url = url + suffix
30     with tempfile.NamedTemporaryFile(delete=False) as temp_file:
31         temp_path = temp_file.name
32     with tempfile.NamedTemporaryFile(suffix=suffix, delete=False) as sha_file:
33         sha_path = sha_file.name
34
35     try:
36         if do_verify:
37             download(sha_path, sha_url, False, verbose)
38             if os.path.exists(path):
39                 if verify(path, sha_path, False):
40                     if verbose:
41                         print("using already-download file", path)
42                     return
43                 else:
44                     if verbose:
45                         print("ignoring already-download file",
46                             path, "due to failed verification")
47                     os.unlink(path)
48         download(temp_path, url, True, verbose)
49         if do_verify and not verify(temp_path, sha_path, verbose):
50             raise RuntimeError("failed verification")
51         if verbose:
52             print("moving {} to {}".format(temp_path, path))
53         shutil.move(temp_path, path)
54     finally:
55         delete_if_present(sha_path, verbose)
56         delete_if_present(temp_path, verbose)
57
58
59 def delete_if_present(path, verbose):
60     """Remove the given file if present"""
61     if os.path.isfile(path):
62         if verbose:
63             print("removing", path)
64         os.unlink(path)
65
66
67 def download(path, url, probably_big, verbose):
68     for _ in range(0, 4):
69         try:
70             _download(path, url, probably_big, verbose, True)
71             return
72         except RuntimeError:
73             print("\nspurious failure, trying again")
74     _download(path, url, probably_big, verbose, False)
75
76
77 def _download(path, url, probably_big, verbose, exception):
78     if probably_big or verbose:
79         print("downloading {}".format(url))
80     # see https://serverfault.com/questions/301128/how-to-download
81     if sys.platform == 'win32':
82         run(["PowerShell.exe", "/nologo", "-Command",
83              "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12;",
84              "(New-Object System.Net.WebClient).DownloadFile('{}', '{}')".format(url, path)],
85             verbose=verbose,
86             exception=exception)
87     else:
88         if probably_big or verbose:
89             option = "-#"
90         else:
91             option = "-s"
92         require(["curl", "--version"])
93         run(["curl", option,
94              "-y", "30", "-Y", "10",    # timeout if speed is < 10 bytes/sec for > 30 seconds
95              "--connect-timeout", "30",  # timeout if cannot connect within 30 seconds
96              "--retry", "3", "-Sf", "-o", path, url],
97             verbose=verbose,
98             exception=exception)
99
100
101 def verify(path, sha_path, verbose):
102     """Check if the sha256 sum of the given path is valid"""
103     if verbose:
104         print("verifying", path)
105     with open(path, "rb") as source:
106         found = hashlib.sha256(source.read()).hexdigest()
107     with open(sha_path, "r") as sha256sum:
108         expected = sha256sum.readline().split()[0]
109     verified = found == expected
110     if not verified:
111         print("invalid checksum:\n"
112               "    found:    {}\n"
113               "    expected: {}".format(found, expected))
114     return verified
115
116
117 def unpack(tarball, tarball_suffix, dst, verbose=False, match=None):
118     """Unpack the given tarball file"""
119     print("extracting", tarball)
120     fname = os.path.basename(tarball).replace(tarball_suffix, "")
121     with contextlib.closing(tarfile.open(tarball)) as tar:
122         for member in tar.getnames():
123             if "/" not in member:
124                 continue
125             name = member.replace(fname + "/", "", 1)
126             if match is not None and not name.startswith(match):
127                 continue
128             name = name[len(match) + 1:]
129
130             dst_path = os.path.join(dst, name)
131             if verbose:
132                 print("  extracting", member)
133             tar.extract(member, dst)
134             src_path = os.path.join(dst, member)
135             if os.path.isdir(src_path) and os.path.exists(dst_path):
136                 continue
137             shutil.move(src_path, dst_path)
138     shutil.rmtree(os.path.join(dst, fname))
139
140
141 def run(args, verbose=False, exception=False, is_bootstrap=False, **kwargs):
142     """Run a child program in a new process"""
143     if verbose:
144         print("running: " + ' '.join(args))
145     sys.stdout.flush()
146     # Use Popen here instead of call() as it apparently allows powershell on
147     # Windows to not lock up waiting for input presumably.
148     ret = subprocess.Popen(args, **kwargs)
149     code = ret.wait()
150     if code != 0:
151         err = "failed to run: " + ' '.join(args)
152         if verbose or exception:
153             raise RuntimeError(err)
154         # For most failures, we definitely do want to print this error, or the user will have no
155         # idea what went wrong. But when we've successfully built bootstrap and it failed, it will
156         # have already printed an error above, so there's no need to print the exact command we're
157         # running.
158         if is_bootstrap:
159             sys.exit(1)
160         else:
161             sys.exit(err)
162
163
164 def require(cmd, exit=True):
165     '''Run a command, returning its output.
166     On error,
167         If `exit` is `True`, exit the process.
168         Otherwise, return None.'''
169     try:
170         return subprocess.check_output(cmd).strip()
171     except (subprocess.CalledProcessError, OSError) as exc:
172         if not exit:
173             return None
174         print("error: unable to run `{}`: {}".format(' '.join(cmd), exc))
175         print("Please make sure it's installed and in the path.")
176         sys.exit(1)
177
178
179 def stage0_data(rust_root):
180     """Build a dictionary from stage0.txt"""
181     nightlies = os.path.join(rust_root, "src/stage0.txt")
182     with open(nightlies, 'r') as nightlies:
183         lines = [line.rstrip() for line in nightlies
184                  if not line.startswith("#")]
185         return dict([line.split(": ", 1) for line in lines if line])
186
187
188 def format_build_time(duration):
189     """Return a nicer format for build time
190
191     >>> format_build_time('300')
192     '0:05:00'
193     """
194     return str(datetime.timedelta(seconds=int(duration)))
195
196
197 def default_build_triple(verbose):
198     """Build triple as in LLVM"""
199     # If the user already has a host build triple with an existing `rustc`
200     # install, use their preference. This fixes most issues with Windows builds
201     # being detected as GNU instead of MSVC.
202     default_encoding = sys.getdefaultencoding()
203     try:
204         version = subprocess.check_output(["rustc", "--version", "--verbose"],
205                 stderr=subprocess.DEVNULL)
206         version = version.decode(default_encoding)
207         host = next(x for x in version.split('\n') if x.startswith("host: "))
208         triple = host.split("host: ")[1]
209         if verbose:
210             print("detected default triple {}".format(triple))
211         return triple
212     except Exception as e:
213         if verbose:
214             print("rustup not detected: {}".format(e))
215             print("falling back to auto-detect")
216
217     required = sys.platform != 'win32'
218     ostype = require(["uname", "-s"], exit=required)
219     cputype = require(['uname', '-m'], exit=required)
220
221     # If we do not have `uname`, assume Windows.
222     if ostype is None or cputype is None:
223         return 'x86_64-pc-windows-msvc'
224
225     ostype = ostype.decode(default_encoding)
226     cputype = cputype.decode(default_encoding)
227
228     # The goal here is to come up with the same triple as LLVM would,
229     # at least for the subset of platforms we're willing to target.
230     ostype_mapper = {
231         'Darwin': 'apple-darwin',
232         'DragonFly': 'unknown-dragonfly',
233         'FreeBSD': 'unknown-freebsd',
234         'Haiku': 'unknown-haiku',
235         'NetBSD': 'unknown-netbsd',
236         'OpenBSD': 'unknown-openbsd'
237     }
238
239     # Consider the direct transformation first and then the special cases
240     if ostype in ostype_mapper:
241         ostype = ostype_mapper[ostype]
242     elif ostype == 'Linux':
243         os_from_sp = subprocess.check_output(
244             ['uname', '-o']).strip().decode(default_encoding)
245         if os_from_sp == 'Android':
246             ostype = 'linux-android'
247         else:
248             ostype = 'unknown-linux-gnu'
249     elif ostype == 'SunOS':
250         ostype = 'pc-solaris'
251         # On Solaris, uname -m will return a machine classification instead
252         # of a cpu type, so uname -p is recommended instead.  However, the
253         # output from that option is too generic for our purposes (it will
254         # always emit 'i386' on x86/amd64 systems).  As such, isainfo -k
255         # must be used instead.
256         cputype = require(['isainfo', '-k']).decode(default_encoding)
257         # sparc cpus have sun as a target vendor
258         if 'sparc' in cputype:
259             ostype = 'sun-solaris'
260     elif ostype.startswith('MINGW'):
261         # msys' `uname` does not print gcc configuration, but prints msys
262         # configuration. so we cannot believe `uname -m`:
263         # msys1 is always i686 and msys2 is always x86_64.
264         # instead, msys defines $MSYSTEM which is MINGW32 on i686 and
265         # MINGW64 on x86_64.
266         ostype = 'pc-windows-gnu'
267         cputype = 'i686'
268         if os.environ.get('MSYSTEM') == 'MINGW64':
269             cputype = 'x86_64'
270     elif ostype.startswith('MSYS'):
271         ostype = 'pc-windows-gnu'
272     elif ostype.startswith('CYGWIN_NT'):
273         cputype = 'i686'
274         if ostype.endswith('WOW64'):
275             cputype = 'x86_64'
276         ostype = 'pc-windows-gnu'
277     elif sys.platform == 'win32':
278         # Some Windows platforms might have a `uname` command that returns a
279         # non-standard string (e.g. gnuwin32 tools returns `windows32`). In
280         # these cases, fall back to using sys.platform.
281         return 'x86_64-pc-windows-msvc'
282     else:
283         err = "unknown OS type: {}".format(ostype)
284         sys.exit(err)
285
286     if cputype == 'powerpc' and ostype == 'unknown-freebsd':
287         cputype = subprocess.check_output(
288               ['uname', '-p']).strip().decode(default_encoding)
289     cputype_mapper = {
290         'BePC': 'i686',
291         'aarch64': 'aarch64',
292         'amd64': 'x86_64',
293         'arm64': 'aarch64',
294         'i386': 'i686',
295         'i486': 'i686',
296         'i686': 'i686',
297         'i786': 'i686',
298         'powerpc': 'powerpc',
299         'powerpc64': 'powerpc64',
300         'powerpc64le': 'powerpc64le',
301         'ppc': 'powerpc',
302         'ppc64': 'powerpc64',
303         'ppc64le': 'powerpc64le',
304         's390x': 's390x',
305         'x64': 'x86_64',
306         'x86': 'i686',
307         'x86-64': 'x86_64',
308         'x86_64': 'x86_64'
309     }
310
311     # Consider the direct transformation first and then the special cases
312     if cputype in cputype_mapper:
313         cputype = cputype_mapper[cputype]
314     elif cputype in {'xscale', 'arm'}:
315         cputype = 'arm'
316         if ostype == 'linux-android':
317             ostype = 'linux-androideabi'
318         elif ostype == 'unknown-freebsd':
319             cputype = subprocess.check_output(
320                 ['uname', '-p']).strip().decode(default_encoding)
321             ostype = 'unknown-freebsd'
322     elif cputype == 'armv6l':
323         cputype = 'arm'
324         if ostype == 'linux-android':
325             ostype = 'linux-androideabi'
326         else:
327             ostype += 'eabihf'
328     elif cputype in {'armv7l', 'armv8l'}:
329         cputype = 'armv7'
330         if ostype == 'linux-android':
331             ostype = 'linux-androideabi'
332         else:
333             ostype += 'eabihf'
334     elif cputype == 'mips':
335         if sys.byteorder == 'big':
336             cputype = 'mips'
337         elif sys.byteorder == 'little':
338             cputype = 'mipsel'
339         else:
340             raise ValueError("unknown byteorder: {}".format(sys.byteorder))
341     elif cputype == 'mips64':
342         if sys.byteorder == 'big':
343             cputype = 'mips64'
344         elif sys.byteorder == 'little':
345             cputype = 'mips64el'
346         else:
347             raise ValueError('unknown byteorder: {}'.format(sys.byteorder))
348         # only the n64 ABI is supported, indicate it
349         ostype += 'abi64'
350     elif cputype == 'sparc' or cputype == 'sparcv9' or cputype == 'sparc64':
351         pass
352     else:
353         err = "unknown cpu type: {}".format(cputype)
354         sys.exit(err)
355
356     return "{}-{}".format(cputype, ostype)
357
358
359 @contextlib.contextmanager
360 def output(filepath):
361     tmp = filepath + '.tmp'
362     with open(tmp, 'w') as f:
363         yield f
364     try:
365         if os.path.exists(filepath):
366             os.remove(filepath)  # PermissionError/OSError on Win32 if in use
367     except OSError:
368         shutil.copy2(tmp, filepath)
369         os.remove(tmp)
370         return
371     os.rename(tmp, filepath)
372
373
374 class RustBuild(object):
375     """Provide all the methods required to build Rust"""
376     def __init__(self):
377         self.date = ''
378         self._download_url = ''
379         self.rustc_channel = ''
380         self.rustfmt_channel = ''
381         self.build = ''
382         self.build_dir = ''
383         self.clean = False
384         self.config_toml = ''
385         self.rust_root = ''
386         self.use_locked_deps = ''
387         self.use_vendored_sources = ''
388         self.verbose = False
389         self.git_version = None
390         self.nix_deps_dir = None
391         self.rustc_commit = None
392
393     def download_toolchain(self, stage0=True, rustc_channel=None):
394         """Fetch the build system for Rust, written in Rust
395
396         This method will build a cache directory, then it will fetch the
397         tarball which has the stage0 compiler used to then bootstrap the Rust
398         compiler itself.
399
400         Each downloaded tarball is extracted, after that, the script
401         will move all the content to the right place.
402         """
403         if rustc_channel is None:
404             rustc_channel = self.rustc_channel
405         rustfmt_channel = self.rustfmt_channel
406         bin_root = self.bin_root(stage0)
407
408         key = self.date
409         if not stage0:
410             key += str(self.rustc_commit)
411         if self.rustc(stage0).startswith(bin_root) and \
412                 (not os.path.exists(self.rustc(stage0)) or
413                  self.program_out_of_date(self.rustc_stamp(stage0), key)):
414             if os.path.exists(bin_root):
415                 shutil.rmtree(bin_root)
416             tarball_suffix = '.tar.xz' if support_xz() else '.tar.gz'
417             filename = "rust-std-{}-{}{}".format(
418                 rustc_channel, self.build, tarball_suffix)
419             pattern = "rust-std-{}".format(self.build)
420             self._download_component_helper(filename, pattern, tarball_suffix, stage0)
421             filename = "rustc-{}-{}{}".format(rustc_channel, self.build,
422                                               tarball_suffix)
423             self._download_component_helper(filename, "rustc", tarball_suffix, stage0)
424             # download-rustc doesn't need its own cargo, it can just use beta's.
425             if stage0:
426                 filename = "cargo-{}-{}{}".format(rustc_channel, self.build,
427                                                 tarball_suffix)
428                 self._download_component_helper(filename, "cargo", tarball_suffix)
429                 self.fix_bin_or_dylib("{}/bin/cargo".format(bin_root))
430             else:
431                 filename = "rustc-dev-{}-{}{}".format(rustc_channel, self.build, tarball_suffix)
432                 self._download_component_helper(
433                     filename, "rustc-dev", tarball_suffix, stage0
434                 )
435
436             self.fix_bin_or_dylib("{}/bin/rustc".format(bin_root))
437             self.fix_bin_or_dylib("{}/bin/rustdoc".format(bin_root))
438             lib_dir = "{}/lib".format(bin_root)
439             for lib in os.listdir(lib_dir):
440                 if lib.endswith(".so"):
441                     self.fix_bin_or_dylib(os.path.join(lib_dir, lib))
442             with output(self.rustc_stamp(stage0)) as rust_stamp:
443                 rust_stamp.write(key)
444
445         if self.rustfmt() and self.rustfmt().startswith(bin_root) and (
446             not os.path.exists(self.rustfmt())
447             or self.program_out_of_date(self.rustfmt_stamp(), self.rustfmt_channel)
448         ):
449             if rustfmt_channel:
450                 tarball_suffix = '.tar.xz' if support_xz() else '.tar.gz'
451                 [channel, date] = rustfmt_channel.split('-', 1)
452                 filename = "rustfmt-{}-{}{}".format(channel, self.build, tarball_suffix)
453                 self._download_component_helper(
454                     filename, "rustfmt-preview", tarball_suffix, key=date
455                 )
456                 self.fix_bin_or_dylib("{}/bin/rustfmt".format(bin_root))
457                 self.fix_bin_or_dylib("{}/bin/cargo-fmt".format(bin_root))
458                 with output(self.rustfmt_stamp()) as rustfmt_stamp:
459                     rustfmt_stamp.write(self.rustfmt_channel)
460
461         # Avoid downloading LLVM twice (once for stage0 and once for the master rustc)
462         if self.downloading_llvm() and stage0:
463             # We want the most recent LLVM submodule update to avoid downloading
464             # LLVM more often than necessary.
465             #
466             # This git command finds that commit SHA, looking for bors-authored
467             # merges that modified src/llvm-project.
468             #
469             # This works even in a repository that has not yet initialized
470             # submodules.
471             top_level = subprocess.check_output([
472                 "git", "rev-parse", "--show-toplevel",
473             ]).decode(sys.getdefaultencoding()).strip()
474             llvm_sha = subprocess.check_output([
475                 "git", "log", "--author=bors", "--format=%H", "-n1",
476                 "-m", "--first-parent",
477                 "--",
478                 "{}/src/llvm-project".format(top_level),
479                 "{}/src/bootstrap/download-ci-llvm-stamp".format(top_level),
480                 # the LLVM shared object file is named `LLVM-12-rust-{version}-nightly`
481                 "{}/src/version".format(top_level)
482             ]).decode(sys.getdefaultencoding()).strip()
483             llvm_assertions = self.get_toml('assertions', 'llvm') == 'true'
484             llvm_root = self.llvm_root()
485             llvm_lib = os.path.join(llvm_root, "lib")
486             if self.program_out_of_date(self.llvm_stamp(), llvm_sha + str(llvm_assertions)):
487                 self._download_ci_llvm(llvm_sha, llvm_assertions)
488                 for binary in ["llvm-config", "FileCheck"]:
489                     self.fix_bin_or_dylib(os.path.join(llvm_root, "bin", binary))
490                 for lib in os.listdir(llvm_lib):
491                     if lib.endswith(".so"):
492                         self.fix_bin_or_dylib(os.path.join(llvm_lib, lib))
493                 with output(self.llvm_stamp()) as llvm_stamp:
494                     llvm_stamp.write(llvm_sha + str(llvm_assertions))
495
496     def downloading_llvm(self):
497         opt = self.get_toml('download-ci-llvm', 'llvm')
498         # This is currently all tier 1 targets (since others may not have CI
499         # artifacts)
500         # https://doc.rust-lang.org/rustc/platform-support.html#tier-1
501         supported_platforms = [
502             "aarch64-unknown-linux-gnu",
503             "i686-pc-windows-gnu",
504             "i686-pc-windows-msvc",
505             "i686-unknown-linux-gnu",
506             "x86_64-unknown-linux-gnu",
507             "x86_64-apple-darwin",
508             "x86_64-pc-windows-gnu",
509             "x86_64-pc-windows-msvc",
510         ]
511         return opt == "true" \
512             or (opt == "if-available" and self.build in supported_platforms)
513
514     def _download_component_helper(
515         self, filename, pattern, tarball_suffix, stage0=True, key=None
516     ):
517         if key is None:
518             if stage0:
519                 key = self.date
520             else:
521                 key = self.rustc_commit
522         cache_dst = os.path.join(self.build_dir, "cache")
523         rustc_cache = os.path.join(cache_dst, key)
524         if not os.path.exists(rustc_cache):
525             os.makedirs(rustc_cache)
526
527         if stage0:
528             url = "{}/dist/{}".format(self._download_url, key)
529         else:
530             url = "https://ci-artifacts.rust-lang.org/rustc-builds/{}".format(self.rustc_commit)
531         tarball = os.path.join(rustc_cache, filename)
532         if not os.path.exists(tarball):
533             get("{}/{}".format(url, filename), tarball, verbose=self.verbose, do_verify=stage0)
534         unpack(tarball, tarball_suffix, self.bin_root(stage0), match=pattern, verbose=self.verbose)
535
536     def _download_ci_llvm(self, llvm_sha, llvm_assertions):
537         cache_prefix = "llvm-{}-{}".format(llvm_sha, llvm_assertions)
538         cache_dst = os.path.join(self.build_dir, "cache")
539         rustc_cache = os.path.join(cache_dst, cache_prefix)
540         if not os.path.exists(rustc_cache):
541             os.makedirs(rustc_cache)
542
543         url = "https://ci-artifacts.rust-lang.org/rustc-builds/{}".format(llvm_sha)
544         if llvm_assertions:
545             url = url.replace('rustc-builds', 'rustc-builds-alt')
546         # ci-artifacts are only stored as .xz, not .gz
547         if not support_xz():
548             print("error: XZ support is required to download LLVM")
549             print("help: consider disabling `download-ci-llvm` or using python3")
550             exit(1)
551         tarball_suffix = '.tar.xz'
552         filename = "rust-dev-nightly-" + self.build + tarball_suffix
553         tarball = os.path.join(rustc_cache, filename)
554         if not os.path.exists(tarball):
555             get("{}/{}".format(url, filename), tarball, verbose=self.verbose, do_verify=False)
556         unpack(tarball, tarball_suffix, self.llvm_root(),
557                 match="rust-dev",
558                 verbose=self.verbose)
559
560     def fix_bin_or_dylib(self, fname):
561         """Modifies the interpreter section of 'fname' to fix the dynamic linker,
562         or the RPATH section, to fix the dynamic library search path
563
564         This method is only required on NixOS and uses the PatchELF utility to
565         change the interpreter/RPATH of ELF executables.
566
567         Please see https://nixos.org/patchelf.html for more information
568         """
569         default_encoding = sys.getdefaultencoding()
570         try:
571             ostype = subprocess.check_output(
572                 ['uname', '-s']).strip().decode(default_encoding)
573         except subprocess.CalledProcessError:
574             return
575         except OSError as reason:
576             if getattr(reason, 'winerror', None) is not None:
577                 return
578             raise reason
579
580         if ostype != "Linux":
581             return
582
583         # Use `/etc/os-release` instead of `/etc/NIXOS`.
584         # The latter one does not exist on NixOS when using tmpfs as root.
585         try:
586             with open("/etc/os-release", "r") as f:
587                 if not any(line.strip() == "ID=nixos" for line in f):
588                     return
589         except FileNotFoundError:
590             return
591         if os.path.exists("/lib"):
592             return
593
594         # At this point we're pretty sure the user is running NixOS
595         nix_os_msg = "info: you seem to be running NixOS. Attempting to patch"
596         print(nix_os_msg, fname)
597
598         # Only build `.nix-deps` once.
599         nix_deps_dir = self.nix_deps_dir
600         if not nix_deps_dir:
601             # Run `nix-build` to "build" each dependency (which will likely reuse
602             # the existing `/nix/store` copy, or at most download a pre-built copy).
603             #
604             # Importantly, we create a gc-root called `.nix-deps` in the `build/`
605             # directory, but still reference the actual `/nix/store` path in the rpath
606             # as it makes it significantly more robust against changes to the location of
607             # the `.nix-deps` location.
608             #
609             # bintools: Needed for the path of `ld-linux.so` (via `nix-support/dynamic-linker`).
610             # zlib: Needed as a system dependency of `libLLVM-*.so`.
611             # patchelf: Needed for patching ELF binaries (see doc comment above).
612             nix_deps_dir = "{}/{}".format(self.build_dir, ".nix-deps")
613             nix_expr = '''
614             with (import <nixpkgs> {});
615             symlinkJoin {
616               name = "rust-stage0-dependencies";
617               paths = [
618                 zlib
619                 patchelf
620                 stdenv.cc.bintools
621               ];
622             }
623             '''
624             try:
625                 subprocess.check_output([
626                     "nix-build", "-E", nix_expr, "-o", nix_deps_dir,
627                 ])
628             except subprocess.CalledProcessError as reason:
629                 print("warning: failed to call nix-build:", reason)
630                 return
631             self.nix_deps_dir = nix_deps_dir
632
633         patchelf = "{}/bin/patchelf".format(nix_deps_dir)
634         rpath_entries = [
635             # Relative default, all binary and dynamic libraries we ship
636             # appear to have this (even when `../lib` is redundant).
637             "$ORIGIN/../lib",
638             os.path.join(os.path.realpath(nix_deps_dir), "lib")
639         ]
640         patchelf_args = ["--set-rpath", ":".join(rpath_entries)]
641         if not fname.endswith(".so"):
642             # Finally, set the corret .interp for binaries
643             with open("{}/nix-support/dynamic-linker".format(nix_deps_dir)) as dynamic_linker:
644                 patchelf_args += ["--set-interpreter", dynamic_linker.read().rstrip()]
645
646         try:
647             subprocess.check_output([patchelf] + patchelf_args + [fname])
648         except subprocess.CalledProcessError as reason:
649             print("warning: failed to call patchelf:", reason)
650             return
651
652     # If `download-rustc` is set, download the most recent commit with CI artifacts
653     def maybe_download_ci_toolchain(self):
654         # If `download-rustc` is not set, default to rebuilding.
655         download_rustc = self.get_toml("download-rustc", section="rust")
656         if download_rustc is None or download_rustc == "false":
657             return None
658         assert download_rustc == "true" or download_rustc == "if-unchanged", download_rustc
659
660         # Handle running from a directory other than the top level
661         rev_parse = ["git", "rev-parse", "--show-toplevel"]
662         top_level = subprocess.check_output(rev_parse, universal_newlines=True).strip()
663         compiler = "{}/compiler/".format(top_level)
664         library = "{}/library/".format(top_level)
665
666         # Look for a version to compare to based on the current commit.
667         # Only commits merged by bors will have CI artifacts.
668         merge_base = ["git", "log", "--author=bors", "--pretty=%H", "-n1"]
669         commit = subprocess.check_output(merge_base, universal_newlines=True).strip()
670
671         # Warn if there were changes to the compiler or standard library since the ancestor commit.
672         status = subprocess.call(["git", "diff-index", "--quiet", commit, "--", compiler, library])
673         if status != 0:
674             if download_rustc == "if-unchanged":
675                 return None
676             print("warning: `download-rustc` is enabled, but there are changes to \
677                    compiler/ or library/")
678
679         if self.verbose:
680             print("using downloaded stage1 artifacts from CI (commit {})".format(commit))
681         self.rustc_commit = commit
682         # FIXME: support downloading artifacts from the beta channel
683         self.download_toolchain(False, "nightly")
684
685     def rustc_stamp(self, stage0):
686         """Return the path for .rustc-stamp at the given stage
687
688         >>> rb = RustBuild()
689         >>> rb.build_dir = "build"
690         >>> rb.rustc_stamp(True) == os.path.join("build", "stage0", ".rustc-stamp")
691         True
692         >>> rb.rustc_stamp(False) == os.path.join("build", "ci-rustc", ".rustc-stamp")
693         True
694         """
695         return os.path.join(self.bin_root(stage0), '.rustc-stamp')
696
697     def rustfmt_stamp(self):
698         """Return the path for .rustfmt-stamp
699
700         >>> rb = RustBuild()
701         >>> rb.build_dir = "build"
702         >>> rb.rustfmt_stamp() == os.path.join("build", "stage0", ".rustfmt-stamp")
703         True
704         """
705         return os.path.join(self.bin_root(True), '.rustfmt-stamp')
706
707     def llvm_stamp(self):
708         """Return the path for .rustfmt-stamp
709
710         >>> rb = RustBuild()
711         >>> rb.build_dir = "build"
712         >>> rb.llvm_stamp() == os.path.join("build", "ci-llvm", ".llvm-stamp")
713         True
714         """
715         return os.path.join(self.llvm_root(), '.llvm-stamp')
716
717
718     def program_out_of_date(self, stamp_path, key):
719         """Check if the given program stamp is out of date"""
720         if not os.path.exists(stamp_path) or self.clean:
721             return True
722         with open(stamp_path, 'r') as stamp:
723             return key != stamp.read()
724
725     def bin_root(self, stage0):
726         """Return the binary root directory for the given stage
727
728         >>> rb = RustBuild()
729         >>> rb.build_dir = "build"
730         >>> rb.bin_root(True) == os.path.join("build", "stage0")
731         True
732         >>> rb.bin_root(False) == os.path.join("build", "ci-rustc")
733         True
734
735         When the 'build' property is given should be a nested directory:
736
737         >>> rb.build = "devel"
738         >>> rb.bin_root(True) == os.path.join("build", "devel", "stage0")
739         True
740         """
741         if stage0:
742             subdir = "stage0"
743         else:
744             subdir = "ci-rustc"
745         return os.path.join(self.build_dir, self.build, subdir)
746
747     def llvm_root(self):
748         """Return the CI LLVM root directory
749
750         >>> rb = RustBuild()
751         >>> rb.build_dir = "build"
752         >>> rb.llvm_root() == os.path.join("build", "ci-llvm")
753         True
754
755         When the 'build' property is given should be a nested directory:
756
757         >>> rb.build = "devel"
758         >>> rb.llvm_root() == os.path.join("build", "devel", "ci-llvm")
759         True
760         """
761         return os.path.join(self.build_dir, self.build, "ci-llvm")
762
763     def get_toml(self, key, section=None):
764         """Returns the value of the given key in config.toml, otherwise returns None
765
766         >>> rb = RustBuild()
767         >>> rb.config_toml = 'key1 = "value1"\\nkey2 = "value2"'
768         >>> rb.get_toml("key2")
769         'value2'
770
771         If the key does not exists, the result is None:
772
773         >>> rb.get_toml("key3") is None
774         True
775
776         Optionally also matches the section the key appears in
777
778         >>> rb.config_toml = '[a]\\nkey = "value1"\\n[b]\\nkey = "value2"'
779         >>> rb.get_toml('key', 'a')
780         'value1'
781         >>> rb.get_toml('key', 'b')
782         'value2'
783         >>> rb.get_toml('key', 'c') is None
784         True
785
786         >>> rb.config_toml = 'key1 = true'
787         >>> rb.get_toml("key1")
788         'true'
789         """
790
791         cur_section = None
792         for line in self.config_toml.splitlines():
793             section_match = re.match(r'^\s*\[(.*)\]\s*$', line)
794             if section_match is not None:
795                 cur_section = section_match.group(1)
796
797             match = re.match(r'^{}\s*=(.*)$'.format(key), line)
798             if match is not None:
799                 value = match.group(1)
800                 if section is None or section == cur_section:
801                     return self.get_string(value) or value.strip()
802         return None
803
804     def cargo(self):
805         """Return config path for cargo"""
806         return self.program_config('cargo')
807
808     def rustc(self, stage0):
809         """Return config path for rustc"""
810         return self.program_config('rustc', stage0)
811
812     def rustfmt(self):
813         """Return config path for rustfmt"""
814         if not self.rustfmt_channel:
815             return None
816         return self.program_config('rustfmt')
817
818     def program_config(self, program, stage0=True):
819         """Return config path for the given program at the given stage
820
821         >>> rb = RustBuild()
822         >>> rb.config_toml = 'rustc = "rustc"\\n'
823         >>> rb.program_config('rustc')
824         'rustc'
825         >>> rb.config_toml = ''
826         >>> cargo_path = rb.program_config('cargo', True)
827         >>> cargo_path.rstrip(".exe") == os.path.join(rb.bin_root(True),
828         ... "bin", "cargo")
829         True
830         >>> cargo_path = rb.program_config('cargo', False)
831         >>> cargo_path.rstrip(".exe") == os.path.join(rb.bin_root(False),
832         ... "bin", "cargo")
833         True
834         """
835         config = self.get_toml(program)
836         if config:
837             return os.path.expanduser(config)
838         return os.path.join(self.bin_root(stage0), "bin", "{}{}".format(
839             program, self.exe_suffix()))
840
841     @staticmethod
842     def get_string(line):
843         """Return the value between double quotes
844
845         >>> RustBuild.get_string('    "devel"   ')
846         'devel'
847         >>> RustBuild.get_string("    'devel'   ")
848         'devel'
849         >>> RustBuild.get_string('devel') is None
850         True
851         >>> RustBuild.get_string('    "devel   ')
852         ''
853         """
854         start = line.find('"')
855         if start != -1:
856             end = start + 1 + line[start + 1:].find('"')
857             return line[start + 1:end]
858         start = line.find('\'')
859         if start != -1:
860             end = start + 1 + line[start + 1:].find('\'')
861             return line[start + 1:end]
862         return None
863
864     @staticmethod
865     def exe_suffix():
866         """Return a suffix for executables"""
867         if sys.platform == 'win32':
868             return '.exe'
869         return ''
870
871     def bootstrap_binary(self):
872         """Return the path of the bootstrap binary
873
874         >>> rb = RustBuild()
875         >>> rb.build_dir = "build"
876         >>> rb.bootstrap_binary() == os.path.join("build", "bootstrap",
877         ... "debug", "bootstrap")
878         True
879         """
880         return os.path.join(self.build_dir, "bootstrap", "debug", "bootstrap")
881
882     def build_bootstrap(self):
883         """Build bootstrap"""
884         build_dir = os.path.join(self.build_dir, "bootstrap")
885         if self.clean and os.path.exists(build_dir):
886             shutil.rmtree(build_dir)
887         env = os.environ.copy()
888         # `CARGO_BUILD_TARGET` breaks bootstrap build.
889         # See also: <https://github.com/rust-lang/rust/issues/70208>.
890         if "CARGO_BUILD_TARGET" in env:
891             del env["CARGO_BUILD_TARGET"]
892         env["CARGO_TARGET_DIR"] = build_dir
893         env["RUSTC"] = self.rustc(True)
894         env["LD_LIBRARY_PATH"] = os.path.join(self.bin_root(True), "lib") + \
895             (os.pathsep + env["LD_LIBRARY_PATH"]) \
896             if "LD_LIBRARY_PATH" in env else ""
897         env["DYLD_LIBRARY_PATH"] = os.path.join(self.bin_root(True), "lib") + \
898             (os.pathsep + env["DYLD_LIBRARY_PATH"]) \
899             if "DYLD_LIBRARY_PATH" in env else ""
900         env["LIBRARY_PATH"] = os.path.join(self.bin_root(True), "lib") + \
901             (os.pathsep + env["LIBRARY_PATH"]) \
902             if "LIBRARY_PATH" in env else ""
903         # preserve existing RUSTFLAGS
904         env.setdefault("RUSTFLAGS", "")
905         env["RUSTFLAGS"] += " -Cdebuginfo=2"
906
907         build_section = "target.{}".format(self.build)
908         target_features = []
909         if self.get_toml("crt-static", build_section) == "true":
910             target_features += ["+crt-static"]
911         elif self.get_toml("crt-static", build_section) == "false":
912             target_features += ["-crt-static"]
913         if target_features:
914             env["RUSTFLAGS"] += " -C target-feature=" + (",".join(target_features))
915         target_linker = self.get_toml("linker", build_section)
916         if target_linker is not None:
917             env["RUSTFLAGS"] += " -C linker=" + target_linker
918         env["RUSTFLAGS"] += " -Wrust_2018_idioms -Wunused_lifetimes"
919         env["RUSTFLAGS"] += " -Wsemicolon_in_expressions_from_macros"
920         if self.get_toml("deny-warnings", "rust") != "false":
921             env["RUSTFLAGS"] += " -Dwarnings"
922
923         env["PATH"] = os.path.join(self.bin_root(True), "bin") + \
924             os.pathsep + env["PATH"]
925         if not os.path.isfile(self.cargo()):
926             raise Exception("no cargo executable found at `{}`".format(
927                 self.cargo()))
928         args = [self.cargo(), "build", "--manifest-path",
929                 os.path.join(self.rust_root, "src/bootstrap/Cargo.toml")]
930         for _ in range(1, self.verbose):
931             args.append("--verbose")
932         if self.use_locked_deps:
933             args.append("--locked")
934         if self.use_vendored_sources:
935             args.append("--frozen")
936         run(args, env=env, verbose=self.verbose)
937
938     def build_triple(self):
939         """Build triple as in LLVM
940
941         Note that `default_build_triple` is moderately expensive,
942         so use `self.build` where possible.
943         """
944         config = self.get_toml('build')
945         if config:
946             return config
947         return default_build_triple(self.verbose)
948
949     def check_submodule(self, module, slow_submodules):
950         if not slow_submodules:
951             checked_out = subprocess.Popen(["git", "rev-parse", "HEAD"],
952                                            cwd=os.path.join(self.rust_root, module),
953                                            stdout=subprocess.PIPE)
954             return checked_out
955         else:
956             return None
957
958     def update_submodule(self, module, checked_out, recorded_submodules):
959         module_path = os.path.join(self.rust_root, module)
960
961         if checked_out is not None:
962             default_encoding = sys.getdefaultencoding()
963             checked_out = checked_out.communicate()[0].decode(default_encoding).strip()
964             if recorded_submodules[module] == checked_out:
965                 return
966
967         print("Updating submodule", module)
968
969         run(["git", "submodule", "-q", "sync", module],
970             cwd=self.rust_root, verbose=self.verbose)
971
972         update_args = ["git", "submodule", "update", "--init", "--recursive"]
973         if self.git_version >= distutils.version.LooseVersion("2.11.0"):
974             update_args.append("--progress")
975         update_args.append(module)
976         run(update_args, cwd=self.rust_root, verbose=self.verbose, exception=True)
977
978         run(["git", "reset", "-q", "--hard"],
979             cwd=module_path, verbose=self.verbose)
980         run(["git", "clean", "-qdfx"],
981             cwd=module_path, verbose=self.verbose)
982
983     def update_submodules(self):
984         """Update submodules"""
985         if (not os.path.exists(os.path.join(self.rust_root, ".git"))) or \
986                 self.get_toml('submodules') == "false":
987             return
988
989         default_encoding = sys.getdefaultencoding()
990
991         # check the existence and version of 'git' command
992         git_version_str = require(['git', '--version']).split()[2].decode(default_encoding)
993         self.git_version = distutils.version.LooseVersion(git_version_str)
994
995         slow_submodules = self.get_toml('fast-submodules') == "false"
996         start_time = time()
997         if slow_submodules:
998             print('Unconditionally updating submodules')
999         else:
1000             print('Updating only changed submodules')
1001         default_encoding = sys.getdefaultencoding()
1002         # Only update submodules that are needed to build bootstrap.  These are needed because Cargo
1003         # currently requires everything in a workspace to be "locally present" when starting a
1004         # build, and will give a hard error if any Cargo.toml files are missing.
1005         # FIXME: Is there a way to avoid cloning these eagerly? Bootstrap itself doesn't need to
1006         #   share a workspace with any tools - maybe it could be excluded from the workspace?
1007         #   That will still require cloning the submodules the second you check the standard
1008         #   library, though...
1009         # FIXME: Is there a way to avoid hard-coding the submodules required?
1010         # WARNING: keep this in sync with the submodules hard-coded in bootstrap/lib.rs
1011         submodules = [
1012             "src/tools/rust-installer",
1013             "src/tools/cargo",
1014             "src/tools/rls",
1015             "src/tools/miri",
1016             "library/backtrace",
1017             "library/stdarch"
1018         ]
1019         filtered_submodules = []
1020         submodules_names = []
1021         for module in submodules:
1022             check = self.check_submodule(module, slow_submodules)
1023             filtered_submodules.append((module, check))
1024             submodules_names.append(module)
1025         recorded = subprocess.Popen(["git", "ls-tree", "HEAD"] + submodules_names,
1026                                     cwd=self.rust_root, stdout=subprocess.PIPE)
1027         recorded = recorded.communicate()[0].decode(default_encoding).strip().splitlines()
1028         # { filename: hash }
1029         recorded_submodules = {}
1030         for data in recorded:
1031             # [mode, kind, hash, filename]
1032             data = data.split()
1033             recorded_submodules[data[3]] = data[2]
1034         for module in filtered_submodules:
1035             self.update_submodule(module[0], module[1], recorded_submodules)
1036         print("Submodules updated in %.2f seconds" % (time() - start_time))
1037
1038     def set_normal_environment(self):
1039         """Set download URL for normal environment"""
1040         if 'RUSTUP_DIST_SERVER' in os.environ:
1041             self._download_url = os.environ['RUSTUP_DIST_SERVER']
1042         else:
1043             self._download_url = 'https://static.rust-lang.org'
1044
1045     def set_dev_environment(self):
1046         """Set download URL for development environment"""
1047         if 'RUSTUP_DEV_DIST_SERVER' in os.environ:
1048             self._download_url = os.environ['RUSTUP_DEV_DIST_SERVER']
1049         else:
1050             self._download_url = 'https://dev-static.rust-lang.org'
1051
1052     def check_vendored_status(self):
1053         """Check that vendoring is configured properly"""
1054         vendor_dir = os.path.join(self.rust_root, 'vendor')
1055         if 'SUDO_USER' in os.environ and not self.use_vendored_sources:
1056             if os.environ.get('USER') != os.environ['SUDO_USER']:
1057                 self.use_vendored_sources = True
1058                 print('info: looks like you are running this command under `sudo`')
1059                 print('      and so in order to preserve your $HOME this will now')
1060                 print('      use vendored sources by default.')
1061                 if not os.path.exists(vendor_dir):
1062                     print('error: vendoring required, but vendor directory does not exist.')
1063                     print('       Run `cargo vendor` without sudo to initialize the '
1064                           'vendor directory.')
1065                     raise Exception("{} not found".format(vendor_dir))
1066
1067         if self.use_vendored_sources:
1068             if not os.path.exists('.cargo'):
1069                 os.makedirs('.cargo')
1070             with output('.cargo/config') as cargo_config:
1071                 cargo_config.write(
1072                     "[source.crates-io]\n"
1073                     "replace-with = 'vendored-sources'\n"
1074                     "registry = 'https://example.com'\n"
1075                     "\n"
1076                     "[source.vendored-sources]\n"
1077                     "directory = '{}/vendor'\n"
1078                     .format(self.rust_root))
1079         else:
1080             if os.path.exists('.cargo'):
1081                 shutil.rmtree('.cargo')
1082
1083     def ensure_vendored(self):
1084         """Ensure that the vendored sources are available if needed"""
1085         vendor_dir = os.path.join(self.rust_root, 'vendor')
1086         # Note that this does not handle updating the vendored dependencies if
1087         # the rust git repository is updated. Normal development usually does
1088         # not use vendoring, so hopefully this isn't too much of a problem.
1089         if self.use_vendored_sources and not os.path.exists(vendor_dir):
1090             run([
1091                 self.cargo(),
1092                 "vendor",
1093                 "--sync=./src/tools/rust-analyzer/Cargo.toml",
1094                 "--sync=./compiler/rustc_codegen_cranelift/Cargo.toml",
1095             ], verbose=self.verbose, cwd=self.rust_root)
1096
1097
1098 def bootstrap(help_triggered):
1099     """Configure, fetch, build and run the initial bootstrap"""
1100
1101     # If the user is asking for help, let them know that the whole download-and-build
1102     # process has to happen before anything is printed out.
1103     if help_triggered:
1104         print("info: Downloading and building bootstrap before processing --help")
1105         print("      command. See src/bootstrap/README.md for help with common")
1106         print("      commands.")
1107
1108     parser = argparse.ArgumentParser(description='Build rust')
1109     parser.add_argument('--config')
1110     parser.add_argument('--build')
1111     parser.add_argument('--clean', action='store_true')
1112     parser.add_argument('-v', '--verbose', action='count', default=0)
1113
1114     args = [a for a in sys.argv if a != '-h' and a != '--help']
1115     args, _ = parser.parse_known_args(args)
1116
1117     # Configure initial bootstrap
1118     build = RustBuild()
1119     build.rust_root = os.path.abspath(os.path.join(__file__, '../../..'))
1120     build.verbose = args.verbose
1121     build.clean = args.clean
1122
1123     # Read from `RUST_BOOTSTRAP_CONFIG`, then `--config`, then fallback to `config.toml` (if it
1124     # exists).
1125     toml_path = os.getenv('RUST_BOOTSTRAP_CONFIG') or args.config
1126     if not toml_path and os.path.exists('config.toml'):
1127         toml_path = 'config.toml'
1128
1129     if toml_path:
1130         if not os.path.exists(toml_path):
1131             toml_path = os.path.join(build.rust_root, toml_path)
1132
1133         with open(toml_path) as config:
1134             build.config_toml = config.read()
1135
1136     profile = build.get_toml('profile')
1137     if profile is not None:
1138         include_file = 'config.{}.toml'.format(profile)
1139         include_dir = os.path.join(build.rust_root, 'src', 'bootstrap', 'defaults')
1140         include_path = os.path.join(include_dir, include_file)
1141         # HACK: This works because `build.get_toml()` returns the first match it finds for a
1142         # specific key, so appending our defaults at the end allows the user to override them
1143         with open(include_path) as included_toml:
1144             build.config_toml += os.linesep + included_toml.read()
1145
1146     config_verbose = build.get_toml('verbose', 'build')
1147     if config_verbose is not None:
1148         build.verbose = max(build.verbose, int(config_verbose))
1149
1150     build.use_vendored_sources = build.get_toml('vendor', 'build') == 'true'
1151
1152     build.use_locked_deps = build.get_toml('locked-deps', 'build') == 'true'
1153
1154     build.check_vendored_status()
1155
1156     build_dir = build.get_toml('build-dir', 'build') or 'build'
1157     build.build_dir = os.path.abspath(build_dir.replace("$ROOT", build.rust_root))
1158
1159     data = stage0_data(build.rust_root)
1160     build.date = data['date']
1161     build.rustc_channel = data['rustc']
1162
1163     if "rustfmt" in data:
1164         build.rustfmt_channel = data['rustfmt']
1165
1166     if 'dev' in data:
1167         build.set_dev_environment()
1168     else:
1169         build.set_normal_environment()
1170
1171     build.build = args.build or build.build_triple()
1172     build.update_submodules()
1173
1174     # Fetch/build the bootstrap
1175     build.download_toolchain()
1176     # Download the master compiler if `download-rustc` is set
1177     build.maybe_download_ci_toolchain()
1178     sys.stdout.flush()
1179     build.ensure_vendored()
1180     build.build_bootstrap()
1181     sys.stdout.flush()
1182
1183     # Run the bootstrap
1184     args = [build.bootstrap_binary()]
1185     args.extend(sys.argv[1:])
1186     env = os.environ.copy()
1187     env["BOOTSTRAP_PARENT_ID"] = str(os.getpid())
1188     env["BOOTSTRAP_PYTHON"] = sys.executable
1189     env["BUILD_DIR"] = build.build_dir
1190     env["RUSTC_BOOTSTRAP"] = '1'
1191     if toml_path:
1192         env["BOOTSTRAP_CONFIG"] = toml_path
1193     if build.rustc_commit is not None:
1194         env["BOOTSTRAP_DOWNLOAD_RUSTC"] = '1'
1195     run(args, env=env, verbose=build.verbose, is_bootstrap=True)
1196
1197
1198 def main():
1199     """Entry point for the bootstrap process"""
1200     start_time = time()
1201
1202     # x.py help <cmd> ...
1203     if len(sys.argv) > 1 and sys.argv[1] == 'help':
1204         sys.argv = [sys.argv[0], '-h'] + sys.argv[2:]
1205
1206     help_triggered = (
1207         '-h' in sys.argv) or ('--help' in sys.argv) or (len(sys.argv) == 1)
1208     try:
1209         bootstrap(help_triggered)
1210         if not help_triggered:
1211             print("Build completed successfully in {}".format(
1212                 format_build_time(time() - start_time)))
1213     except (SystemExit, KeyboardInterrupt) as error:
1214         if hasattr(error, 'code') and isinstance(error.code, int):
1215             exit_code = error.code
1216         else:
1217             exit_code = 1
1218             print(error)
1219         if not help_triggered:
1220             print("Build completed unsuccessfully in {}".format(
1221                 format_build_time(time() - start_time)))
1222         sys.exit(exit_code)
1223
1224
1225 if __name__ == '__main__':
1226     main()