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