]> git.lizzy.rs Git - rust.git/blob - src/bootstrap/bootstrap.py
Merge commit '48d60ab7c505c6c1ebb042eacaafd8dc9f7a9267' into libgccjit-codegen
[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 or other relevant version
468             # stamp files.
469             #
470             # This works even in a repository that has not yet initialized
471             # submodules.
472             top_level = subprocess.check_output([
473                 "git", "rev-parse", "--show-toplevel",
474             ]).decode(sys.getdefaultencoding()).strip()
475             llvm_sha = subprocess.check_output([
476                 "git", "rev-list", "--author=bors@rust-lang.org", "-n1",
477                 "--merges", "--first-parent", "HEAD",
478                 "--",
479                 "{}/src/llvm-project".format(top_level),
480                 "{}/src/bootstrap/download-ci-llvm-stamp".format(top_level),
481                 # the LLVM shared object file is named `LLVM-12-rust-{version}-nightly`
482                 "{}/src/version".format(top_level)
483             ]).decode(sys.getdefaultencoding()).strip()
484             llvm_assertions = self.get_toml('assertions', 'llvm') == 'true'
485             llvm_root = self.llvm_root()
486             llvm_lib = os.path.join(llvm_root, "lib")
487             if self.program_out_of_date(self.llvm_stamp(), llvm_sha + str(llvm_assertions)):
488                 self._download_ci_llvm(llvm_sha, llvm_assertions)
489                 for binary in ["llvm-config", "FileCheck"]:
490                     self.fix_bin_or_dylib(os.path.join(llvm_root, "bin", binary))
491                 for lib in os.listdir(llvm_lib):
492                     if lib.endswith(".so"):
493                         self.fix_bin_or_dylib(os.path.join(llvm_lib, lib))
494                 with output(self.llvm_stamp()) as llvm_stamp:
495                     llvm_stamp.write(llvm_sha + str(llvm_assertions))
496
497     def downloading_llvm(self):
498         opt = self.get_toml('download-ci-llvm', 'llvm')
499         # This is currently all tier 1 targets (since others may not have CI
500         # artifacts)
501         # https://doc.rust-lang.org/rustc/platform-support.html#tier-1
502         supported_platforms = [
503             "aarch64-unknown-linux-gnu",
504             "i686-pc-windows-gnu",
505             "i686-pc-windows-msvc",
506             "i686-unknown-linux-gnu",
507             "x86_64-unknown-linux-gnu",
508             "x86_64-apple-darwin",
509             "x86_64-pc-windows-gnu",
510             "x86_64-pc-windows-msvc",
511         ]
512         return opt == "true" \
513             or (opt == "if-available" and self.build in supported_platforms)
514
515     def _download_component_helper(
516         self, filename, pattern, tarball_suffix, stage0=True, key=None
517     ):
518         if key is None:
519             if stage0:
520                 key = self.date
521             else:
522                 key = self.rustc_commit
523         cache_dst = os.path.join(self.build_dir, "cache")
524         rustc_cache = os.path.join(cache_dst, key)
525         if not os.path.exists(rustc_cache):
526             os.makedirs(rustc_cache)
527
528         if stage0:
529             url = "{}/dist/{}".format(self._download_url, key)
530         else:
531             url = "https://ci-artifacts.rust-lang.org/rustc-builds/{}".format(self.rustc_commit)
532         tarball = os.path.join(rustc_cache, filename)
533         if not os.path.exists(tarball):
534             get("{}/{}".format(url, filename), tarball, verbose=self.verbose, do_verify=stage0)
535         unpack(tarball, tarball_suffix, self.bin_root(stage0), match=pattern, verbose=self.verbose)
536
537     def _download_ci_llvm(self, llvm_sha, llvm_assertions):
538         cache_prefix = "llvm-{}-{}".format(llvm_sha, llvm_assertions)
539         cache_dst = os.path.join(self.build_dir, "cache")
540         rustc_cache = os.path.join(cache_dst, cache_prefix)
541         if not os.path.exists(rustc_cache):
542             os.makedirs(rustc_cache)
543
544         url = "https://ci-artifacts.rust-lang.org/rustc-builds/{}".format(llvm_sha)
545         if llvm_assertions:
546             url = url.replace('rustc-builds', 'rustc-builds-alt')
547         # ci-artifacts are only stored as .xz, not .gz
548         if not support_xz():
549             print("error: XZ support is required to download LLVM")
550             print("help: consider disabling `download-ci-llvm` or using python3")
551             exit(1)
552         tarball_suffix = '.tar.xz'
553         filename = "rust-dev-nightly-" + self.build + tarball_suffix
554         tarball = os.path.join(rustc_cache, filename)
555         if not os.path.exists(tarball):
556             get("{}/{}".format(url, filename), tarball, verbose=self.verbose, do_verify=False)
557         unpack(tarball, tarball_suffix, self.llvm_root(),
558                 match="rust-dev",
559                 verbose=self.verbose)
560
561     def fix_bin_or_dylib(self, fname):
562         """Modifies the interpreter section of 'fname' to fix the dynamic linker,
563         or the RPATH section, to fix the dynamic library search path
564
565         This method is only required on NixOS and uses the PatchELF utility to
566         change the interpreter/RPATH of ELF executables.
567
568         Please see https://nixos.org/patchelf.html for more information
569         """
570         default_encoding = sys.getdefaultencoding()
571         try:
572             ostype = subprocess.check_output(
573                 ['uname', '-s']).strip().decode(default_encoding)
574         except subprocess.CalledProcessError:
575             return
576         except OSError as reason:
577             if getattr(reason, 'winerror', None) is not None:
578                 return
579             raise reason
580
581         if ostype != "Linux":
582             return
583
584         # Use `/etc/os-release` instead of `/etc/NIXOS`.
585         # The latter one does not exist on NixOS when using tmpfs as root.
586         try:
587             with open("/etc/os-release", "r") as f:
588                 if not any(line.strip() == "ID=nixos" for line in f):
589                     return
590         except FileNotFoundError:
591             return
592         if os.path.exists("/lib"):
593             return
594
595         # At this point we're pretty sure the user is running NixOS
596         nix_os_msg = "info: you seem to be running NixOS. Attempting to patch"
597         print(nix_os_msg, fname)
598
599         # Only build `.nix-deps` once.
600         nix_deps_dir = self.nix_deps_dir
601         if not nix_deps_dir:
602             # Run `nix-build` to "build" each dependency (which will likely reuse
603             # the existing `/nix/store` copy, or at most download a pre-built copy).
604             #
605             # Importantly, we create a gc-root called `.nix-deps` in the `build/`
606             # directory, but still reference the actual `/nix/store` path in the rpath
607             # as it makes it significantly more robust against changes to the location of
608             # the `.nix-deps` location.
609             #
610             # bintools: Needed for the path of `ld-linux.so` (via `nix-support/dynamic-linker`).
611             # zlib: Needed as a system dependency of `libLLVM-*.so`.
612             # patchelf: Needed for patching ELF binaries (see doc comment above).
613             nix_deps_dir = "{}/{}".format(self.build_dir, ".nix-deps")
614             nix_expr = '''
615             with (import <nixpkgs> {});
616             symlinkJoin {
617               name = "rust-stage0-dependencies";
618               paths = [
619                 zlib
620                 patchelf
621                 stdenv.cc.bintools
622               ];
623             }
624             '''
625             try:
626                 subprocess.check_output([
627                     "nix-build", "-E", nix_expr, "-o", nix_deps_dir,
628                 ])
629             except subprocess.CalledProcessError as reason:
630                 print("warning: failed to call nix-build:", reason)
631                 return
632             self.nix_deps_dir = nix_deps_dir
633
634         patchelf = "{}/bin/patchelf".format(nix_deps_dir)
635         rpath_entries = [
636             # Relative default, all binary and dynamic libraries we ship
637             # appear to have this (even when `../lib` is redundant).
638             "$ORIGIN/../lib",
639             os.path.join(os.path.realpath(nix_deps_dir), "lib")
640         ]
641         patchelf_args = ["--set-rpath", ":".join(rpath_entries)]
642         if not fname.endswith(".so"):
643             # Finally, set the corret .interp for binaries
644             with open("{}/nix-support/dynamic-linker".format(nix_deps_dir)) as dynamic_linker:
645                 patchelf_args += ["--set-interpreter", dynamic_linker.read().rstrip()]
646
647         try:
648             subprocess.check_output([patchelf] + patchelf_args + [fname])
649         except subprocess.CalledProcessError as reason:
650             print("warning: failed to call patchelf:", reason)
651             return
652
653     # If `download-rustc` is set, download the most recent commit with CI artifacts
654     def maybe_download_ci_toolchain(self):
655         # If `download-rustc` is not set, default to rebuilding.
656         download_rustc = self.get_toml("download-rustc", section="rust")
657         if download_rustc is None or download_rustc == "false":
658             return None
659         assert download_rustc == "true" or download_rustc == "if-unchanged", download_rustc
660
661         # Handle running from a directory other than the top level
662         rev_parse = ["git", "rev-parse", "--show-toplevel"]
663         top_level = subprocess.check_output(rev_parse, universal_newlines=True).strip()
664         compiler = "{}/compiler/".format(top_level)
665         library = "{}/library/".format(top_level)
666
667         # Look for a version to compare to based on the current commit.
668         # Only commits merged by bors will have CI artifacts.
669         merge_base = [
670             "git", "rev-list", "--author=bors@rust-lang.org", "-n1",
671             "--merges", "--first-parent", "HEAD"
672         ]
673         commit = subprocess.check_output(merge_base, universal_newlines=True).strip()
674
675         # Warn if there were changes to the compiler or standard library since the ancestor commit.
676         status = subprocess.call(["git", "diff-index", "--quiet", commit, "--", compiler, library])
677         if status != 0:
678             if download_rustc == "if-unchanged":
679                 return None
680             print("warning: `download-rustc` is enabled, but there are changes to \
681                    compiler/ or library/")
682
683         if self.verbose:
684             print("using downloaded stage1 artifacts from CI (commit {})".format(commit))
685         self.rustc_commit = commit
686         # FIXME: support downloading artifacts from the beta channel
687         self.download_toolchain(False, "nightly")
688
689     def rustc_stamp(self, stage0):
690         """Return the path for .rustc-stamp at the given stage
691
692         >>> rb = RustBuild()
693         >>> rb.build_dir = "build"
694         >>> rb.rustc_stamp(True) == os.path.join("build", "stage0", ".rustc-stamp")
695         True
696         >>> rb.rustc_stamp(False) == os.path.join("build", "ci-rustc", ".rustc-stamp")
697         True
698         """
699         return os.path.join(self.bin_root(stage0), '.rustc-stamp')
700
701     def rustfmt_stamp(self):
702         """Return the path for .rustfmt-stamp
703
704         >>> rb = RustBuild()
705         >>> rb.build_dir = "build"
706         >>> rb.rustfmt_stamp() == os.path.join("build", "stage0", ".rustfmt-stamp")
707         True
708         """
709         return os.path.join(self.bin_root(True), '.rustfmt-stamp')
710
711     def llvm_stamp(self):
712         """Return the path for .rustfmt-stamp
713
714         >>> rb = RustBuild()
715         >>> rb.build_dir = "build"
716         >>> rb.llvm_stamp() == os.path.join("build", "ci-llvm", ".llvm-stamp")
717         True
718         """
719         return os.path.join(self.llvm_root(), '.llvm-stamp')
720
721
722     def program_out_of_date(self, stamp_path, key):
723         """Check if the given program stamp is out of date"""
724         if not os.path.exists(stamp_path) or self.clean:
725             return True
726         with open(stamp_path, 'r') as stamp:
727             return key != stamp.read()
728
729     def bin_root(self, stage0):
730         """Return the binary root directory for the given stage
731
732         >>> rb = RustBuild()
733         >>> rb.build_dir = "build"
734         >>> rb.bin_root(True) == os.path.join("build", "stage0")
735         True
736         >>> rb.bin_root(False) == os.path.join("build", "ci-rustc")
737         True
738
739         When the 'build' property is given should be a nested directory:
740
741         >>> rb.build = "devel"
742         >>> rb.bin_root(True) == os.path.join("build", "devel", "stage0")
743         True
744         """
745         if stage0:
746             subdir = "stage0"
747         else:
748             subdir = "ci-rustc"
749         return os.path.join(self.build_dir, self.build, subdir)
750
751     def llvm_root(self):
752         """Return the CI LLVM root directory
753
754         >>> rb = RustBuild()
755         >>> rb.build_dir = "build"
756         >>> rb.llvm_root() == os.path.join("build", "ci-llvm")
757         True
758
759         When the 'build' property is given should be a nested directory:
760
761         >>> rb.build = "devel"
762         >>> rb.llvm_root() == os.path.join("build", "devel", "ci-llvm")
763         True
764         """
765         return os.path.join(self.build_dir, self.build, "ci-llvm")
766
767     def get_toml(self, key, section=None):
768         """Returns the value of the given key in config.toml, otherwise returns None
769
770         >>> rb = RustBuild()
771         >>> rb.config_toml = 'key1 = "value1"\\nkey2 = "value2"'
772         >>> rb.get_toml("key2")
773         'value2'
774
775         If the key does not exists, the result is None:
776
777         >>> rb.get_toml("key3") is None
778         True
779
780         Optionally also matches the section the key appears in
781
782         >>> rb.config_toml = '[a]\\nkey = "value1"\\n[b]\\nkey = "value2"'
783         >>> rb.get_toml('key', 'a')
784         'value1'
785         >>> rb.get_toml('key', 'b')
786         'value2'
787         >>> rb.get_toml('key', 'c') is None
788         True
789
790         >>> rb.config_toml = 'key1 = true'
791         >>> rb.get_toml("key1")
792         'true'
793         """
794
795         cur_section = None
796         for line in self.config_toml.splitlines():
797             section_match = re.match(r'^\s*\[(.*)\]\s*$', line)
798             if section_match is not None:
799                 cur_section = section_match.group(1)
800
801             match = re.match(r'^{}\s*=(.*)$'.format(key), line)
802             if match is not None:
803                 value = match.group(1)
804                 if section is None or section == cur_section:
805                     return self.get_string(value) or value.strip()
806         return None
807
808     def cargo(self):
809         """Return config path for cargo"""
810         return self.program_config('cargo')
811
812     def rustc(self, stage0):
813         """Return config path for rustc"""
814         return self.program_config('rustc', stage0)
815
816     def rustfmt(self):
817         """Return config path for rustfmt"""
818         if not self.rustfmt_channel:
819             return None
820         return self.program_config('rustfmt')
821
822     def program_config(self, program, stage0=True):
823         """Return config path for the given program at the given stage
824
825         >>> rb = RustBuild()
826         >>> rb.config_toml = 'rustc = "rustc"\\n'
827         >>> rb.program_config('rustc')
828         'rustc'
829         >>> rb.config_toml = ''
830         >>> cargo_path = rb.program_config('cargo', True)
831         >>> cargo_path.rstrip(".exe") == os.path.join(rb.bin_root(True),
832         ... "bin", "cargo")
833         True
834         >>> cargo_path = rb.program_config('cargo', False)
835         >>> cargo_path.rstrip(".exe") == os.path.join(rb.bin_root(False),
836         ... "bin", "cargo")
837         True
838         """
839         config = self.get_toml(program)
840         if config:
841             return os.path.expanduser(config)
842         return os.path.join(self.bin_root(stage0), "bin", "{}{}".format(
843             program, self.exe_suffix()))
844
845     @staticmethod
846     def get_string(line):
847         """Return the value between double quotes
848
849         >>> RustBuild.get_string('    "devel"   ')
850         'devel'
851         >>> RustBuild.get_string("    'devel'   ")
852         'devel'
853         >>> RustBuild.get_string('devel') is None
854         True
855         >>> RustBuild.get_string('    "devel   ')
856         ''
857         """
858         start = line.find('"')
859         if start != -1:
860             end = start + 1 + line[start + 1:].find('"')
861             return line[start + 1:end]
862         start = line.find('\'')
863         if start != -1:
864             end = start + 1 + line[start + 1:].find('\'')
865             return line[start + 1:end]
866         return None
867
868     @staticmethod
869     def exe_suffix():
870         """Return a suffix for executables"""
871         if sys.platform == 'win32':
872             return '.exe'
873         return ''
874
875     def bootstrap_binary(self):
876         """Return the path of the bootstrap binary
877
878         >>> rb = RustBuild()
879         >>> rb.build_dir = "build"
880         >>> rb.bootstrap_binary() == os.path.join("build", "bootstrap",
881         ... "debug", "bootstrap")
882         True
883         """
884         return os.path.join(self.build_dir, "bootstrap", "debug", "bootstrap")
885
886     def build_bootstrap(self):
887         """Build bootstrap"""
888         build_dir = os.path.join(self.build_dir, "bootstrap")
889         if self.clean and os.path.exists(build_dir):
890             shutil.rmtree(build_dir)
891         env = os.environ.copy()
892         # `CARGO_BUILD_TARGET` breaks bootstrap build.
893         # See also: <https://github.com/rust-lang/rust/issues/70208>.
894         if "CARGO_BUILD_TARGET" in env:
895             del env["CARGO_BUILD_TARGET"]
896         env["CARGO_TARGET_DIR"] = build_dir
897         env["RUSTC"] = self.rustc(True)
898         env["LD_LIBRARY_PATH"] = os.path.join(self.bin_root(True), "lib") + \
899             (os.pathsep + env["LD_LIBRARY_PATH"]) \
900             if "LD_LIBRARY_PATH" in env else ""
901         env["DYLD_LIBRARY_PATH"] = os.path.join(self.bin_root(True), "lib") + \
902             (os.pathsep + env["DYLD_LIBRARY_PATH"]) \
903             if "DYLD_LIBRARY_PATH" in env else ""
904         env["LIBRARY_PATH"] = os.path.join(self.bin_root(True), "lib") + \
905             (os.pathsep + env["LIBRARY_PATH"]) \
906             if "LIBRARY_PATH" in env else ""
907         # preserve existing RUSTFLAGS
908         env.setdefault("RUSTFLAGS", "")
909         env["RUSTFLAGS"] += " -Cdebuginfo=2"
910
911         build_section = "target.{}".format(self.build)
912         target_features = []
913         if self.get_toml("crt-static", build_section) == "true":
914             target_features += ["+crt-static"]
915         elif self.get_toml("crt-static", build_section) == "false":
916             target_features += ["-crt-static"]
917         if target_features:
918             env["RUSTFLAGS"] += " -C target-feature=" + (",".join(target_features))
919         target_linker = self.get_toml("linker", build_section)
920         if target_linker is not None:
921             env["RUSTFLAGS"] += " -C linker=" + target_linker
922         env["RUSTFLAGS"] += " -Wrust_2018_idioms -Wunused_lifetimes"
923         env["RUSTFLAGS"] += " -Wsemicolon_in_expressions_from_macros"
924         if self.get_toml("deny-warnings", "rust") != "false":
925             env["RUSTFLAGS"] += " -Dwarnings"
926
927         env["PATH"] = os.path.join(self.bin_root(True), "bin") + \
928             os.pathsep + env["PATH"]
929         if not os.path.isfile(self.cargo()):
930             raise Exception("no cargo executable found at `{}`".format(
931                 self.cargo()))
932         args = [self.cargo(), "build", "--manifest-path",
933                 os.path.join(self.rust_root, "src/bootstrap/Cargo.toml")]
934         for _ in range(1, self.verbose):
935             args.append("--verbose")
936         if self.use_locked_deps:
937             args.append("--locked")
938         if self.use_vendored_sources:
939             args.append("--frozen")
940         run(args, env=env, verbose=self.verbose)
941
942     def build_triple(self):
943         """Build triple as in LLVM
944
945         Note that `default_build_triple` is moderately expensive,
946         so use `self.build` where possible.
947         """
948         config = self.get_toml('build')
949         if config:
950             return config
951         return default_build_triple(self.verbose)
952
953     def check_submodule(self, module, slow_submodules):
954         if not slow_submodules:
955             checked_out = subprocess.Popen(["git", "rev-parse", "HEAD"],
956                                            cwd=os.path.join(self.rust_root, module),
957                                            stdout=subprocess.PIPE)
958             return checked_out
959         else:
960             return None
961
962     def update_submodule(self, module, checked_out, recorded_submodules):
963         module_path = os.path.join(self.rust_root, module)
964
965         if checked_out is not None:
966             default_encoding = sys.getdefaultencoding()
967             checked_out = checked_out.communicate()[0].decode(default_encoding).strip()
968             if recorded_submodules[module] == checked_out:
969                 return
970
971         print("Updating submodule", module)
972
973         run(["git", "submodule", "-q", "sync", module],
974             cwd=self.rust_root, verbose=self.verbose)
975
976         update_args = ["git", "submodule", "update", "--init", "--recursive"]
977         if self.git_version >= distutils.version.LooseVersion("2.11.0"):
978             update_args.append("--progress")
979         update_args.append(module)
980         run(update_args, cwd=self.rust_root, verbose=self.verbose, exception=True)
981
982         run(["git", "reset", "-q", "--hard"],
983             cwd=module_path, verbose=self.verbose)
984         run(["git", "clean", "-qdfx"],
985             cwd=module_path, verbose=self.verbose)
986
987     def update_submodules(self):
988         """Update submodules"""
989         if (not os.path.exists(os.path.join(self.rust_root, ".git"))) or \
990                 self.get_toml('submodules') == "false":
991             return
992
993         default_encoding = sys.getdefaultencoding()
994
995         # check the existence and version of 'git' command
996         git_version_str = require(['git', '--version']).split()[2].decode(default_encoding)
997         self.git_version = distutils.version.LooseVersion(git_version_str)
998
999         slow_submodules = self.get_toml('fast-submodules') == "false"
1000         start_time = time()
1001         if slow_submodules:
1002             print('Unconditionally updating submodules')
1003         else:
1004             print('Updating only changed submodules')
1005         default_encoding = sys.getdefaultencoding()
1006         # Only update submodules that are needed to build bootstrap.  These are needed because Cargo
1007         # currently requires everything in a workspace to be "locally present" when starting a
1008         # build, and will give a hard error if any Cargo.toml files are missing.
1009         # FIXME: Is there a way to avoid cloning these eagerly? Bootstrap itself doesn't need to
1010         #   share a workspace with any tools - maybe it could be excluded from the workspace?
1011         #   That will still require cloning the submodules the second you check the standard
1012         #   library, though...
1013         # FIXME: Is there a way to avoid hard-coding the submodules required?
1014         # WARNING: keep this in sync with the submodules hard-coded in bootstrap/lib.rs
1015         submodules = [
1016             "src/tools/rust-installer",
1017             "src/tools/cargo",
1018             "src/tools/rls",
1019             "src/tools/miri",
1020             "library/backtrace",
1021             "library/stdarch"
1022         ]
1023         filtered_submodules = []
1024         submodules_names = []
1025         for module in submodules:
1026             check = self.check_submodule(module, slow_submodules)
1027             filtered_submodules.append((module, check))
1028             submodules_names.append(module)
1029         recorded = subprocess.Popen(["git", "ls-tree", "HEAD"] + submodules_names,
1030                                     cwd=self.rust_root, stdout=subprocess.PIPE)
1031         recorded = recorded.communicate()[0].decode(default_encoding).strip().splitlines()
1032         # { filename: hash }
1033         recorded_submodules = {}
1034         for data in recorded:
1035             # [mode, kind, hash, filename]
1036             data = data.split()
1037             recorded_submodules[data[3]] = data[2]
1038         for module in filtered_submodules:
1039             self.update_submodule(module[0], module[1], recorded_submodules)
1040         print("Submodules updated in %.2f seconds" % (time() - start_time))
1041
1042     def set_normal_environment(self):
1043         """Set download URL for normal environment"""
1044         if 'RUSTUP_DIST_SERVER' in os.environ:
1045             self._download_url = os.environ['RUSTUP_DIST_SERVER']
1046         else:
1047             self._download_url = 'https://static.rust-lang.org'
1048
1049     def set_dev_environment(self):
1050         """Set download URL for development environment"""
1051         if 'RUSTUP_DEV_DIST_SERVER' in os.environ:
1052             self._download_url = os.environ['RUSTUP_DEV_DIST_SERVER']
1053         else:
1054             self._download_url = 'https://dev-static.rust-lang.org'
1055
1056     def check_vendored_status(self):
1057         """Check that vendoring is configured properly"""
1058         vendor_dir = os.path.join(self.rust_root, 'vendor')
1059         if 'SUDO_USER' in os.environ and not self.use_vendored_sources:
1060             if os.environ.get('USER') != os.environ['SUDO_USER']:
1061                 self.use_vendored_sources = True
1062                 print('info: looks like you are running this command under `sudo`')
1063                 print('      and so in order to preserve your $HOME this will now')
1064                 print('      use vendored sources by default.')
1065                 if not os.path.exists(vendor_dir):
1066                     print('error: vendoring required, but vendor directory does not exist.')
1067                     print('       Run `cargo vendor` without sudo to initialize the '
1068                           'vendor directory.')
1069                     raise Exception("{} not found".format(vendor_dir))
1070
1071         if self.use_vendored_sources:
1072             if not os.path.exists('.cargo'):
1073                 os.makedirs('.cargo')
1074             with output('.cargo/config') as cargo_config:
1075                 cargo_config.write(
1076                     "[source.crates-io]\n"
1077                     "replace-with = 'vendored-sources'\n"
1078                     "registry = 'https://example.com'\n"
1079                     "\n"
1080                     "[source.vendored-sources]\n"
1081                     "directory = '{}/vendor'\n"
1082                     .format(self.rust_root))
1083         else:
1084             if os.path.exists('.cargo'):
1085                 shutil.rmtree('.cargo')
1086
1087     def ensure_vendored(self):
1088         """Ensure that the vendored sources are available if needed"""
1089         vendor_dir = os.path.join(self.rust_root, 'vendor')
1090         # Note that this does not handle updating the vendored dependencies if
1091         # the rust git repository is updated. Normal development usually does
1092         # not use vendoring, so hopefully this isn't too much of a problem.
1093         if self.use_vendored_sources and not os.path.exists(vendor_dir):
1094             run([
1095                 self.cargo(),
1096                 "vendor",
1097                 "--sync=./src/tools/rust-analyzer/Cargo.toml",
1098                 "--sync=./compiler/rustc_codegen_cranelift/Cargo.toml",
1099             ], verbose=self.verbose, cwd=self.rust_root)
1100
1101
1102 def bootstrap(help_triggered):
1103     """Configure, fetch, build and run the initial bootstrap"""
1104
1105     # If the user is asking for help, let them know that the whole download-and-build
1106     # process has to happen before anything is printed out.
1107     if help_triggered:
1108         print("info: Downloading and building bootstrap before processing --help")
1109         print("      command. See src/bootstrap/README.md for help with common")
1110         print("      commands.")
1111
1112     parser = argparse.ArgumentParser(description='Build rust')
1113     parser.add_argument('--config')
1114     parser.add_argument('--build')
1115     parser.add_argument('--clean', action='store_true')
1116     parser.add_argument('-v', '--verbose', action='count', default=0)
1117
1118     args = [a for a in sys.argv if a != '-h' and a != '--help']
1119     args, _ = parser.parse_known_args(args)
1120
1121     # Configure initial bootstrap
1122     build = RustBuild()
1123     build.rust_root = os.path.abspath(os.path.join(__file__, '../../..'))
1124     build.verbose = args.verbose
1125     build.clean = args.clean
1126
1127     # Read from `RUST_BOOTSTRAP_CONFIG`, then `--config`, then fallback to `config.toml` (if it
1128     # exists).
1129     toml_path = os.getenv('RUST_BOOTSTRAP_CONFIG') or args.config
1130     if not toml_path and os.path.exists('config.toml'):
1131         toml_path = 'config.toml'
1132
1133     if toml_path:
1134         if not os.path.exists(toml_path):
1135             toml_path = os.path.join(build.rust_root, toml_path)
1136
1137         with open(toml_path) as config:
1138             build.config_toml = config.read()
1139
1140     profile = build.get_toml('profile')
1141     if profile is not None:
1142         include_file = 'config.{}.toml'.format(profile)
1143         include_dir = os.path.join(build.rust_root, 'src', 'bootstrap', 'defaults')
1144         include_path = os.path.join(include_dir, include_file)
1145         # HACK: This works because `build.get_toml()` returns the first match it finds for a
1146         # specific key, so appending our defaults at the end allows the user to override them
1147         with open(include_path) as included_toml:
1148             build.config_toml += os.linesep + included_toml.read()
1149
1150     config_verbose = build.get_toml('verbose', 'build')
1151     if config_verbose is not None:
1152         build.verbose = max(build.verbose, int(config_verbose))
1153
1154     build.use_vendored_sources = build.get_toml('vendor', 'build') == 'true'
1155
1156     build.use_locked_deps = build.get_toml('locked-deps', 'build') == 'true'
1157
1158     build.check_vendored_status()
1159
1160     build_dir = build.get_toml('build-dir', 'build') or 'build'
1161     build.build_dir = os.path.abspath(build_dir.replace("$ROOT", build.rust_root))
1162
1163     data = stage0_data(build.rust_root)
1164     build.date = data['date']
1165     build.rustc_channel = data['rustc']
1166
1167     if "rustfmt" in data:
1168         build.rustfmt_channel = data['rustfmt']
1169
1170     if 'dev' in data:
1171         build.set_dev_environment()
1172     else:
1173         build.set_normal_environment()
1174
1175     build.build = args.build or build.build_triple()
1176     build.update_submodules()
1177
1178     # Fetch/build the bootstrap
1179     build.download_toolchain()
1180     # Download the master compiler if `download-rustc` is set
1181     build.maybe_download_ci_toolchain()
1182     sys.stdout.flush()
1183     build.ensure_vendored()
1184     build.build_bootstrap()
1185     sys.stdout.flush()
1186
1187     # Run the bootstrap
1188     args = [build.bootstrap_binary()]
1189     args.extend(sys.argv[1:])
1190     env = os.environ.copy()
1191     env["BOOTSTRAP_PARENT_ID"] = str(os.getpid())
1192     env["BOOTSTRAP_PYTHON"] = sys.executable
1193     env["BUILD_DIR"] = build.build_dir
1194     env["RUSTC_BOOTSTRAP"] = '1'
1195     if toml_path:
1196         env["BOOTSTRAP_CONFIG"] = toml_path
1197     if build.rustc_commit is not None:
1198         env["BOOTSTRAP_DOWNLOAD_RUSTC"] = '1'
1199     run(args, env=env, verbose=build.verbose, is_bootstrap=True)
1200
1201
1202 def main():
1203     """Entry point for the bootstrap process"""
1204     start_time = time()
1205
1206     # x.py help <cmd> ...
1207     if len(sys.argv) > 1 and sys.argv[1] == 'help':
1208         sys.argv = [sys.argv[0], '-h'] + sys.argv[2:]
1209
1210     help_triggered = (
1211         '-h' in sys.argv) or ('--help' in sys.argv) or (len(sys.argv) == 1)
1212     try:
1213         bootstrap(help_triggered)
1214         if not help_triggered:
1215             print("Build completed successfully in {}".format(
1216                 format_build_time(time() - start_time)))
1217     except (SystemExit, KeyboardInterrupt) as error:
1218         if hasattr(error, 'code') and isinstance(error.code, int):
1219             exit_code = error.code
1220         else:
1221             exit_code = 1
1222             print(error)
1223         if not help_triggered:
1224             print("Build completed unsuccessfully in {}".format(
1225                 format_build_time(time() - start_time)))
1226         sys.exit(exit_code)
1227
1228
1229 if __name__ == '__main__':
1230     main()