]> git.lizzy.rs Git - rust.git/blob - src/bootstrap/bootstrap.py
Rollup merge of #98530 - davidkna:known-bug-ref, 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 json
8 import os
9 import re
10 import shutil
11 import subprocess
12 import sys
13 import tarfile
14 import tempfile
15
16 from time import time, sleep
17
18 # Acquire a lock on the build directory to make sure that
19 # we don't cause a race condition while building
20 # Lock is created in `build_dir/lock.db`
21 def acquire_lock(build_dir):
22     try:
23         import sqlite3
24
25         path = os.path.join(build_dir, "lock.db")
26         try:
27             con = sqlite3.Connection(path, timeout=0)
28             curs = con.cursor()
29             curs.execute("BEGIN EXCLUSIVE")
30             # The lock is released when the cursor is dropped
31             return curs
32         # If the database is busy then lock has already been acquired
33         # so we wait for the lock.
34         # We retry every quarter second so that execution is passed back to python
35         # so that it can handle signals
36         except sqlite3.OperationalError:
37             del con
38             del curs
39             print("Waiting for lock on build directory")
40             con = sqlite3.Connection(path, timeout=0.25)
41             curs = con.cursor()
42             while True:
43                 try:
44                     curs.execute("BEGIN EXCLUSIVE")
45                     break
46                 except sqlite3.OperationalError:
47                     pass
48                 sleep(0.25)
49             return curs
50     except ImportError:
51         print("warning: sqlite3 not available in python, skipping build directory lock")
52         print("please file an issue on rust-lang/rust")
53         print("this is not a problem for non-concurrent x.py invocations")
54         return None
55
56 def support_xz():
57     try:
58         with tempfile.NamedTemporaryFile(delete=False) as temp_file:
59             temp_path = temp_file.name
60         with tarfile.open(temp_path, "w:xz"):
61             pass
62         return True
63     except tarfile.CompressionError:
64         return False
65
66 def get(base, url, path, checksums, verbose=False):
67     with tempfile.NamedTemporaryFile(delete=False) as temp_file:
68         temp_path = temp_file.name
69
70     try:
71         if url not in checksums:
72             raise RuntimeError(("src/stage0.json doesn't contain a checksum for {}. "
73                                 "Pre-built artifacts might not be available for this "
74                                 "target at this time, see https://doc.rust-lang.org/nightly"
75                                 "/rustc/platform-support.html for more information.")
76                                 .format(url))
77         sha256 = checksums[url]
78         if os.path.exists(path):
79             if verify(path, sha256, False):
80                 if verbose:
81                     print("using already-download file", path)
82                 return
83             else:
84                 if verbose:
85                     print("ignoring already-download file",
86                         path, "due to failed verification")
87                 os.unlink(path)
88         download(temp_path, "{}/{}".format(base, url), True, verbose)
89         if not verify(temp_path, sha256, verbose):
90             raise RuntimeError("failed verification")
91         if verbose:
92             print("moving {} to {}".format(temp_path, path))
93         shutil.move(temp_path, path)
94     finally:
95         if os.path.isfile(temp_path):
96             if verbose:
97                 print("removing", temp_path)
98             os.unlink(temp_path)
99
100
101 def download(path, url, probably_big, verbose):
102     for _ in range(0, 4):
103         try:
104             _download(path, url, probably_big, verbose, True)
105             return
106         except RuntimeError:
107             print("\nspurious failure, trying again")
108     _download(path, url, probably_big, verbose, False)
109
110
111 def _download(path, url, probably_big, verbose, exception):
112     # Try to use curl (potentially available on win32
113     #    https://devblogs.microsoft.com/commandline/tar-and-curl-come-to-windows/)
114     # If an error occurs:
115     #  - If we are on win32 fallback to powershell
116     #  - Otherwise raise the error if appropriate
117     if probably_big or verbose:
118         print("downloading {}".format(url))
119
120     platform_is_win32 = sys.platform == 'win32'
121     try:
122         if probably_big or verbose:
123             option = "-#"
124         else:
125             option = "-s"
126         # If curl is not present on Win32, we shoud not sys.exit
127         #   but raise `CalledProcessError` or `OSError` instead
128         require(["curl", "--version"], exception=platform_is_win32)
129         run(["curl", option,
130              "-L", # Follow redirect.
131              "-y", "30", "-Y", "10",    # timeout if speed is < 10 bytes/sec for > 30 seconds
132              "--connect-timeout", "30",  # timeout if cannot connect within 30 seconds
133              "--retry", "3", "-Sf", "-o", path, url],
134             verbose=verbose,
135             exception=True, # Will raise RuntimeError on failure
136         )
137     except (subprocess.CalledProcessError, OSError, RuntimeError):
138         # see http://serverfault.com/questions/301128/how-to-download
139         if platform_is_win32:
140             run(["PowerShell.exe", "/nologo", "-Command",
141                  "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12;",
142                  "(New-Object System.Net.WebClient).DownloadFile('{}', '{}')".format(url, path)],
143                 verbose=verbose,
144                 exception=exception)
145         # Check if the RuntimeError raised by run(curl) should be silenced
146         elif verbose or exception:
147             raise
148
149
150 def verify(path, expected, verbose):
151     """Check if the sha256 sum of the given path is valid"""
152     if verbose:
153         print("verifying", path)
154     with open(path, "rb") as source:
155         found = hashlib.sha256(source.read()).hexdigest()
156     verified = found == expected
157     if not verified:
158         print("invalid checksum:\n"
159               "    found:    {}\n"
160               "    expected: {}".format(found, expected))
161     return verified
162
163
164 def unpack(tarball, tarball_suffix, dst, verbose=False, match=None):
165     """Unpack the given tarball file"""
166     print("extracting", tarball)
167     fname = os.path.basename(tarball).replace(tarball_suffix, "")
168     with contextlib.closing(tarfile.open(tarball)) as tar:
169         for member in tar.getnames():
170             if "/" not in member:
171                 continue
172             name = member.replace(fname + "/", "", 1)
173             if match is not None and not name.startswith(match):
174                 continue
175             name = name[len(match) + 1:]
176
177             dst_path = os.path.join(dst, name)
178             if verbose:
179                 print("  extracting", member)
180             tar.extract(member, dst)
181             src_path = os.path.join(dst, member)
182             if os.path.isdir(src_path) and os.path.exists(dst_path):
183                 continue
184             shutil.move(src_path, dst_path)
185     shutil.rmtree(os.path.join(dst, fname))
186
187
188 def run(args, verbose=False, exception=False, is_bootstrap=False, **kwargs):
189     """Run a child program in a new process"""
190     if verbose:
191         print("running: " + ' '.join(args))
192     sys.stdout.flush()
193     # Use Popen here instead of call() as it apparently allows powershell on
194     # Windows to not lock up waiting for input presumably.
195     ret = subprocess.Popen(args, **kwargs)
196     code = ret.wait()
197     if code != 0:
198         err = "failed to run: " + ' '.join(args)
199         if verbose or exception:
200             raise RuntimeError(err)
201         # For most failures, we definitely do want to print this error, or the user will have no
202         # idea what went wrong. But when we've successfully built bootstrap and it failed, it will
203         # have already printed an error above, so there's no need to print the exact command we're
204         # running.
205         if is_bootstrap:
206             sys.exit(1)
207         else:
208             sys.exit(err)
209
210
211 def require(cmd, exit=True, exception=False):
212     '''Run a command, returning its output.
213     On error,
214         If `exception` is `True`, raise the error
215         Otherwise If `exit` is `True`, exit the process
216         Else return None.'''
217     try:
218         return subprocess.check_output(cmd).strip()
219     except (subprocess.CalledProcessError, OSError) as exc:
220         if exception:
221             raise
222         elif exit:
223             print("error: unable to run `{}`: {}".format(' '.join(cmd), exc))
224             print("Please make sure it's installed and in the path.")
225             sys.exit(1)
226         return None
227
228
229
230 def format_build_time(duration):
231     """Return a nicer format for build time
232
233     >>> format_build_time('300')
234     '0:05:00'
235     """
236     return str(datetime.timedelta(seconds=int(duration)))
237
238
239 def default_build_triple(verbose):
240     """Build triple as in LLVM"""
241     # If the user already has a host build triple with an existing `rustc`
242     # install, use their preference. This fixes most issues with Windows builds
243     # being detected as GNU instead of MSVC.
244     default_encoding = sys.getdefaultencoding()
245     try:
246         version = subprocess.check_output(["rustc", "--version", "--verbose"],
247                 stderr=subprocess.DEVNULL)
248         version = version.decode(default_encoding)
249         host = next(x for x in version.split('\n') if x.startswith("host: "))
250         triple = host.split("host: ")[1]
251         if verbose:
252             print("detected default triple {} from pre-installed rustc".format(triple))
253         return triple
254     except Exception as e:
255         if verbose:
256             print("pre-installed rustc not detected: {}".format(e))
257             print("falling back to auto-detect")
258
259     required = sys.platform != 'win32'
260     ostype = require(["uname", "-s"], exit=required)
261     cputype = require(['uname', '-m'], exit=required)
262
263     # If we do not have `uname`, assume Windows.
264     if ostype is None or cputype is None:
265         return 'x86_64-pc-windows-msvc'
266
267     ostype = ostype.decode(default_encoding)
268     cputype = cputype.decode(default_encoding)
269
270     # The goal here is to come up with the same triple as LLVM would,
271     # at least for the subset of platforms we're willing to target.
272     ostype_mapper = {
273         'Darwin': 'apple-darwin',
274         'DragonFly': 'unknown-dragonfly',
275         'FreeBSD': 'unknown-freebsd',
276         'Haiku': 'unknown-haiku',
277         'NetBSD': 'unknown-netbsd',
278         'OpenBSD': 'unknown-openbsd'
279     }
280
281     # Consider the direct transformation first and then the special cases
282     if ostype in ostype_mapper:
283         ostype = ostype_mapper[ostype]
284     elif ostype == 'Linux':
285         os_from_sp = subprocess.check_output(
286             ['uname', '-o']).strip().decode(default_encoding)
287         if os_from_sp == 'Android':
288             ostype = 'linux-android'
289         else:
290             ostype = 'unknown-linux-gnu'
291     elif ostype == 'SunOS':
292         ostype = 'pc-solaris'
293         # On Solaris, uname -m will return a machine classification instead
294         # of a cpu type, so uname -p is recommended instead.  However, the
295         # output from that option is too generic for our purposes (it will
296         # always emit 'i386' on x86/amd64 systems).  As such, isainfo -k
297         # must be used instead.
298         cputype = require(['isainfo', '-k']).decode(default_encoding)
299         # sparc cpus have sun as a target vendor
300         if 'sparc' in cputype:
301             ostype = 'sun-solaris'
302     elif ostype.startswith('MINGW'):
303         # msys' `uname` does not print gcc configuration, but prints msys
304         # configuration. so we cannot believe `uname -m`:
305         # msys1 is always i686 and msys2 is always x86_64.
306         # instead, msys defines $MSYSTEM which is MINGW32 on i686 and
307         # MINGW64 on x86_64.
308         ostype = 'pc-windows-gnu'
309         cputype = 'i686'
310         if os.environ.get('MSYSTEM') == 'MINGW64':
311             cputype = 'x86_64'
312     elif ostype.startswith('MSYS'):
313         ostype = 'pc-windows-gnu'
314     elif ostype.startswith('CYGWIN_NT'):
315         cputype = 'i686'
316         if ostype.endswith('WOW64'):
317             cputype = 'x86_64'
318         ostype = 'pc-windows-gnu'
319     elif sys.platform == 'win32':
320         # Some Windows platforms might have a `uname` command that returns a
321         # non-standard string (e.g. gnuwin32 tools returns `windows32`). In
322         # these cases, fall back to using sys.platform.
323         return 'x86_64-pc-windows-msvc'
324     else:
325         err = "unknown OS type: {}".format(ostype)
326         sys.exit(err)
327
328     if cputype in ['powerpc', 'riscv'] and ostype == 'unknown-freebsd':
329         cputype = subprocess.check_output(
330               ['uname', '-p']).strip().decode(default_encoding)
331     cputype_mapper = {
332         'BePC': 'i686',
333         'aarch64': 'aarch64',
334         'amd64': 'x86_64',
335         'arm64': 'aarch64',
336         'i386': 'i686',
337         'i486': 'i686',
338         'i686': 'i686',
339         'i786': 'i686',
340         'm68k': 'm68k',
341         'powerpc': 'powerpc',
342         'powerpc64': 'powerpc64',
343         'powerpc64le': 'powerpc64le',
344         'ppc': 'powerpc',
345         'ppc64': 'powerpc64',
346         'ppc64le': 'powerpc64le',
347         'riscv64': 'riscv64gc',
348         's390x': 's390x',
349         'x64': 'x86_64',
350         'x86': 'i686',
351         'x86-64': 'x86_64',
352         'x86_64': 'x86_64'
353     }
354
355     # Consider the direct transformation first and then the special cases
356     if cputype in cputype_mapper:
357         cputype = cputype_mapper[cputype]
358     elif cputype in {'xscale', 'arm'}:
359         cputype = 'arm'
360         if ostype == 'linux-android':
361             ostype = 'linux-androideabi'
362         elif ostype == 'unknown-freebsd':
363             cputype = subprocess.check_output(
364                 ['uname', '-p']).strip().decode(default_encoding)
365             ostype = 'unknown-freebsd'
366     elif cputype == 'armv6l':
367         cputype = 'arm'
368         if ostype == 'linux-android':
369             ostype = 'linux-androideabi'
370         else:
371             ostype += 'eabihf'
372     elif cputype in {'armv7l', 'armv8l'}:
373         cputype = 'armv7'
374         if ostype == 'linux-android':
375             ostype = 'linux-androideabi'
376         else:
377             ostype += 'eabihf'
378     elif cputype == 'mips':
379         if sys.byteorder == 'big':
380             cputype = 'mips'
381         elif sys.byteorder == 'little':
382             cputype = 'mipsel'
383         else:
384             raise ValueError("unknown byteorder: {}".format(sys.byteorder))
385     elif cputype == 'mips64':
386         if sys.byteorder == 'big':
387             cputype = 'mips64'
388         elif sys.byteorder == 'little':
389             cputype = 'mips64el'
390         else:
391             raise ValueError('unknown byteorder: {}'.format(sys.byteorder))
392         # only the n64 ABI is supported, indicate it
393         ostype += 'abi64'
394     elif cputype == 'sparc' or cputype == 'sparcv9' or cputype == 'sparc64':
395         pass
396     else:
397         err = "unknown cpu type: {}".format(cputype)
398         sys.exit(err)
399
400     return "{}-{}".format(cputype, ostype)
401
402
403 @contextlib.contextmanager
404 def output(filepath):
405     tmp = filepath + '.tmp'
406     with open(tmp, 'w') as f:
407         yield f
408     try:
409         if os.path.exists(filepath):
410             os.remove(filepath)  # PermissionError/OSError on Win32 if in use
411     except OSError:
412         shutil.copy2(tmp, filepath)
413         os.remove(tmp)
414         return
415     os.rename(tmp, filepath)
416
417
418 class Stage0Toolchain:
419     def __init__(self, stage0_payload):
420         self.date = stage0_payload["date"]
421         self.version = stage0_payload["version"]
422
423     def channel(self):
424         return self.version + "-" + self.date
425
426
427 class RustBuild(object):
428     """Provide all the methods required to build Rust"""
429     def __init__(self):
430         self.checksums_sha256 = {}
431         self.stage0_compiler = None
432         self._download_url = ''
433         self.build = ''
434         self.build_dir = ''
435         self.clean = False
436         self.config_toml = ''
437         self.rust_root = ''
438         self.use_locked_deps = ''
439         self.use_vendored_sources = ''
440         self.verbose = False
441         self.git_version = None
442         self.nix_deps_dir = None
443
444     def download_toolchain(self):
445         """Fetch the build system for Rust, written in Rust
446
447         This method will build a cache directory, then it will fetch the
448         tarball which has the stage0 compiler used to then bootstrap the Rust
449         compiler itself.
450
451         Each downloaded tarball is extracted, after that, the script
452         will move all the content to the right place.
453         """
454         rustc_channel = self.stage0_compiler.version
455         bin_root = self.bin_root()
456
457         key = self.stage0_compiler.date
458         if self.rustc().startswith(bin_root) and \
459                 (not os.path.exists(self.rustc()) or
460                  self.program_out_of_date(self.rustc_stamp(), key)):
461             if os.path.exists(bin_root):
462                 shutil.rmtree(bin_root)
463             tarball_suffix = '.tar.xz' if support_xz() else '.tar.gz'
464             filename = "rust-std-{}-{}{}".format(
465                 rustc_channel, self.build, tarball_suffix)
466             pattern = "rust-std-{}".format(self.build)
467             self._download_component_helper(filename, pattern, tarball_suffix)
468             filename = "rustc-{}-{}{}".format(rustc_channel, self.build,
469                                               tarball_suffix)
470             self._download_component_helper(filename, "rustc", tarball_suffix)
471             filename = "cargo-{}-{}{}".format(rustc_channel, self.build,
472                                             tarball_suffix)
473             self._download_component_helper(filename, "cargo", tarball_suffix)
474             self.fix_bin_or_dylib("{}/bin/cargo".format(bin_root))
475
476             self.fix_bin_or_dylib("{}/bin/rustc".format(bin_root))
477             self.fix_bin_or_dylib("{}/bin/rustdoc".format(bin_root))
478             lib_dir = "{}/lib".format(bin_root)
479             for lib in os.listdir(lib_dir):
480                 if lib.endswith(".so"):
481                     self.fix_bin_or_dylib(os.path.join(lib_dir, lib))
482             with output(self.rustc_stamp()) as rust_stamp:
483                 rust_stamp.write(key)
484
485     def _download_component_helper(
486         self, filename, pattern, tarball_suffix,
487     ):
488         key = self.stage0_compiler.date
489         cache_dst = os.path.join(self.build_dir, "cache")
490         rustc_cache = os.path.join(cache_dst, key)
491         if not os.path.exists(rustc_cache):
492             os.makedirs(rustc_cache)
493
494         base = self._download_url
495         url = "dist/{}".format(key)
496         tarball = os.path.join(rustc_cache, filename)
497         if not os.path.exists(tarball):
498             get(
499                 base,
500                 "{}/{}".format(url, filename),
501                 tarball,
502                 self.checksums_sha256,
503                 verbose=self.verbose,
504             )
505         unpack(tarball, tarball_suffix, self.bin_root(), match=pattern, verbose=self.verbose)
506
507     def fix_bin_or_dylib(self, fname):
508         """Modifies the interpreter section of 'fname' to fix the dynamic linker,
509         or the RPATH section, to fix the dynamic library search path
510
511         This method is only required on NixOS and uses the PatchELF utility to
512         change the interpreter/RPATH of ELF executables.
513
514         Please see https://nixos.org/patchelf.html for more information
515         """
516         default_encoding = sys.getdefaultencoding()
517         try:
518             ostype = subprocess.check_output(
519                 ['uname', '-s']).strip().decode(default_encoding)
520         except subprocess.CalledProcessError:
521             return
522         except OSError as reason:
523             if getattr(reason, 'winerror', None) is not None:
524                 return
525             raise reason
526
527         if ostype != "Linux":
528             return
529
530         # If the user has asked binaries to be patched for Nix, then
531         # don't check for NixOS or `/lib`, just continue to the patching.
532         if self.get_toml('patch-binaries-for-nix', 'build') != 'true':
533             # Use `/etc/os-release` instead of `/etc/NIXOS`.
534             # The latter one does not exist on NixOS when using tmpfs as root.
535             try:
536                 with open("/etc/os-release", "r") as f:
537                     if not any(l.strip() in ["ID=nixos", "ID='nixos'", 'ID="nixos"'] for l in f):
538                         return
539             except FileNotFoundError:
540                 return
541             if os.path.exists("/lib"):
542                 return
543
544         # At this point we're pretty sure the user is running NixOS or
545         # using Nix
546         nix_os_msg = "info: you seem to be using Nix. Attempting to patch"
547         print(nix_os_msg, fname)
548
549         # Only build `.nix-deps` once.
550         nix_deps_dir = self.nix_deps_dir
551         if not nix_deps_dir:
552             # Run `nix-build` to "build" each dependency (which will likely reuse
553             # the existing `/nix/store` copy, or at most download a pre-built copy).
554             #
555             # Importantly, we create a gc-root called `.nix-deps` in the `build/`
556             # directory, but still reference the actual `/nix/store` path in the rpath
557             # as it makes it significantly more robust against changes to the location of
558             # the `.nix-deps` location.
559             #
560             # bintools: Needed for the path of `ld-linux.so` (via `nix-support/dynamic-linker`).
561             # zlib: Needed as a system dependency of `libLLVM-*.so`.
562             # patchelf: Needed for patching ELF binaries (see doc comment above).
563             nix_deps_dir = "{}/{}".format(self.build_dir, ".nix-deps")
564             nix_expr = '''
565             with (import <nixpkgs> {});
566             symlinkJoin {
567               name = "rust-stage0-dependencies";
568               paths = [
569                 zlib
570                 patchelf
571                 stdenv.cc.bintools
572               ];
573             }
574             '''
575             try:
576                 subprocess.check_output([
577                     "nix-build", "-E", nix_expr, "-o", nix_deps_dir,
578                 ])
579             except subprocess.CalledProcessError as reason:
580                 print("warning: failed to call nix-build:", reason)
581                 return
582             self.nix_deps_dir = nix_deps_dir
583
584         patchelf = "{}/bin/patchelf".format(nix_deps_dir)
585         rpath_entries = [
586             # Relative default, all binary and dynamic libraries we ship
587             # appear to have this (even when `../lib` is redundant).
588             "$ORIGIN/../lib",
589             os.path.join(os.path.realpath(nix_deps_dir), "lib")
590         ]
591         patchelf_args = ["--set-rpath", ":".join(rpath_entries)]
592         if not fname.endswith(".so"):
593             # Finally, set the corret .interp for binaries
594             with open("{}/nix-support/dynamic-linker".format(nix_deps_dir)) as dynamic_linker:
595                 patchelf_args += ["--set-interpreter", dynamic_linker.read().rstrip()]
596
597         try:
598             subprocess.check_output([patchelf] + patchelf_args + [fname])
599         except subprocess.CalledProcessError as reason:
600             print("warning: failed to call patchelf:", reason)
601             return
602
603     def rustc_stamp(self):
604         """Return the path for .rustc-stamp at the given stage
605
606         >>> rb = RustBuild()
607         >>> rb.build_dir = "build"
608         >>> rb.rustc_stamp() == os.path.join("build", "stage0", ".rustc-stamp")
609         True
610         """
611         return os.path.join(self.bin_root(), '.rustc-stamp')
612
613     def program_out_of_date(self, stamp_path, key):
614         """Check if the given program stamp is out of date"""
615         if not os.path.exists(stamp_path) or self.clean:
616             return True
617         with open(stamp_path, 'r') as stamp:
618             return key != stamp.read()
619
620     def bin_root(self):
621         """Return the binary root directory for the given stage
622
623         >>> rb = RustBuild()
624         >>> rb.build_dir = "build"
625         >>> rb.bin_root() == os.path.join("build", "stage0")
626         True
627
628         When the 'build' property is given should be a nested directory:
629
630         >>> rb.build = "devel"
631         >>> rb.bin_root() == os.path.join("build", "devel", "stage0")
632         True
633         """
634         subdir = "stage0"
635         return os.path.join(self.build_dir, self.build, subdir)
636
637     def get_toml(self, key, section=None):
638         """Returns the value of the given key in config.toml, otherwise returns None
639
640         >>> rb = RustBuild()
641         >>> rb.config_toml = 'key1 = "value1"\\nkey2 = "value2"'
642         >>> rb.get_toml("key2")
643         'value2'
644
645         If the key does not exist, the result is None:
646
647         >>> rb.get_toml("key3") is None
648         True
649
650         Optionally also matches the section the key appears in
651
652         >>> rb.config_toml = '[a]\\nkey = "value1"\\n[b]\\nkey = "value2"'
653         >>> rb.get_toml('key', 'a')
654         'value1'
655         >>> rb.get_toml('key', 'b')
656         'value2'
657         >>> rb.get_toml('key', 'c') is None
658         True
659
660         >>> rb.config_toml = 'key1 = true'
661         >>> rb.get_toml("key1")
662         'true'
663         """
664
665         cur_section = None
666         for line in self.config_toml.splitlines():
667             section_match = re.match(r'^\s*\[(.*)\]\s*$', line)
668             if section_match is not None:
669                 cur_section = section_match.group(1)
670
671             match = re.match(r'^{}\s*=(.*)$'.format(key), line)
672             if match is not None:
673                 value = match.group(1)
674                 if section is None or section == cur_section:
675                     return self.get_string(value) or value.strip()
676         return None
677
678     def cargo(self):
679         """Return config path for cargo"""
680         return self.program_config('cargo')
681
682     def rustc(self):
683         """Return config path for rustc"""
684         return self.program_config('rustc')
685
686     def program_config(self, program):
687         """Return config path for the given program at the given stage
688
689         >>> rb = RustBuild()
690         >>> rb.config_toml = 'rustc = "rustc"\\n'
691         >>> rb.program_config('rustc')
692         'rustc'
693         >>> rb.config_toml = ''
694         >>> cargo_path = rb.program_config('cargo')
695         >>> cargo_path.rstrip(".exe") == os.path.join(rb.bin_root(),
696         ... "bin", "cargo")
697         True
698         """
699         config = self.get_toml(program)
700         if config:
701             return os.path.expanduser(config)
702         return os.path.join(self.bin_root(), "bin", "{}{}".format(
703             program, self.exe_suffix()))
704
705     @staticmethod
706     def get_string(line):
707         """Return the value between double quotes
708
709         >>> RustBuild.get_string('    "devel"   ')
710         'devel'
711         >>> RustBuild.get_string("    'devel'   ")
712         'devel'
713         >>> RustBuild.get_string('devel') is None
714         True
715         >>> RustBuild.get_string('    "devel   ')
716         ''
717         """
718         start = line.find('"')
719         if start != -1:
720             end = start + 1 + line[start + 1:].find('"')
721             return line[start + 1:end]
722         start = line.find('\'')
723         if start != -1:
724             end = start + 1 + line[start + 1:].find('\'')
725             return line[start + 1:end]
726         return None
727
728     @staticmethod
729     def exe_suffix():
730         """Return a suffix for executables"""
731         if sys.platform == 'win32':
732             return '.exe'
733         return ''
734
735     def bootstrap_binary(self):
736         """Return the path of the bootstrap binary
737
738         >>> rb = RustBuild()
739         >>> rb.build_dir = "build"
740         >>> rb.bootstrap_binary() == os.path.join("build", "bootstrap",
741         ... "debug", "bootstrap")
742         True
743         """
744         return os.path.join(self.build_dir, "bootstrap", "debug", "bootstrap")
745
746     def build_bootstrap(self, color):
747         """Build bootstrap"""
748         print("Building rustbuild")
749         build_dir = os.path.join(self.build_dir, "bootstrap")
750         if self.clean and os.path.exists(build_dir):
751             shutil.rmtree(build_dir)
752         env = os.environ.copy()
753         # `CARGO_BUILD_TARGET` breaks bootstrap build.
754         # See also: <https://github.com/rust-lang/rust/issues/70208>.
755         if "CARGO_BUILD_TARGET" in env:
756             del env["CARGO_BUILD_TARGET"]
757         env["CARGO_TARGET_DIR"] = build_dir
758         env["RUSTC"] = self.rustc()
759         env["LD_LIBRARY_PATH"] = os.path.join(self.bin_root(), "lib") + \
760             (os.pathsep + env["LD_LIBRARY_PATH"]) \
761             if "LD_LIBRARY_PATH" in env else ""
762         env["DYLD_LIBRARY_PATH"] = os.path.join(self.bin_root(), "lib") + \
763             (os.pathsep + env["DYLD_LIBRARY_PATH"]) \
764             if "DYLD_LIBRARY_PATH" in env else ""
765         env["LIBRARY_PATH"] = os.path.join(self.bin_root(), "lib") + \
766             (os.pathsep + env["LIBRARY_PATH"]) \
767             if "LIBRARY_PATH" in env else ""
768
769         # preserve existing RUSTFLAGS
770         env.setdefault("RUSTFLAGS", "")
771         build_section = "target.{}".format(self.build)
772         target_features = []
773         if self.get_toml("crt-static", build_section) == "true":
774             target_features += ["+crt-static"]
775         elif self.get_toml("crt-static", build_section) == "false":
776             target_features += ["-crt-static"]
777         if target_features:
778             env["RUSTFLAGS"] += " -C target-feature=" + (",".join(target_features))
779         target_linker = self.get_toml("linker", build_section)
780         if target_linker is not None:
781             env["RUSTFLAGS"] += " -C linker=" + target_linker
782         env["RUSTFLAGS"] += " -Wrust_2018_idioms -Wunused_lifetimes"
783         env["RUSTFLAGS"] += " -Wsemicolon_in_expressions_from_macros"
784         if self.get_toml("deny-warnings", "rust") != "false":
785             env["RUSTFLAGS"] += " -Dwarnings"
786
787         env["PATH"] = os.path.join(self.bin_root(), "bin") + \
788             os.pathsep + env["PATH"]
789         if not os.path.isfile(self.cargo()):
790             raise Exception("no cargo executable found at `{}`".format(
791                 self.cargo()))
792         args = [self.cargo(), "build", "--manifest-path",
793                 os.path.join(self.rust_root, "src/bootstrap/Cargo.toml")]
794         for _ in range(0, self.verbose):
795             args.append("--verbose")
796         if self.use_locked_deps:
797             args.append("--locked")
798         if self.use_vendored_sources:
799             args.append("--frozen")
800         if self.get_toml("metrics", "build"):
801             args.append("--features")
802             args.append("build-metrics")
803         if color == "always":
804             args.append("--color=always")
805         elif color == "never":
806             args.append("--color=never")
807
808         run(args, env=env, verbose=self.verbose)
809
810     def build_triple(self):
811         """Build triple as in LLVM
812
813         Note that `default_build_triple` is moderately expensive,
814         so use `self.build` where possible.
815         """
816         config = self.get_toml('build')
817         if config:
818             return config
819         return default_build_triple(self.verbose)
820
821     def set_dist_environment(self, url):
822         """Set download URL for normal environment"""
823         if 'RUSTUP_DIST_SERVER' in os.environ:
824             self._download_url = os.environ['RUSTUP_DIST_SERVER']
825         else:
826             self._download_url = url
827
828     def check_vendored_status(self):
829         """Check that vendoring is configured properly"""
830         if 'SUDO_USER' in os.environ and not self.use_vendored_sources:
831             if os.getuid() == 0:
832                 self.use_vendored_sources = True
833                 print('info: looks like you\'re trying to run this command as root')
834                 print('      and so in order to preserve your $HOME this will now')
835                 print('      use vendored sources by default.')
836
837         cargo_dir = os.path.join(self.rust_root, '.cargo')
838         if self.use_vendored_sources:
839             vendor_dir = os.path.join(self.rust_root, 'vendor')
840             if not os.path.exists(vendor_dir):
841                 sync_dirs = "--sync ./src/tools/rust-analyzer/Cargo.toml " \
842                             "--sync ./compiler/rustc_codegen_cranelift/Cargo.toml " \
843                             "--sync ./src/bootstrap/Cargo.toml "
844                 print('error: vendoring required, but vendor directory does not exist.')
845                 print('       Run `cargo vendor {}` to initialize the '
846                       'vendor directory.'.format(sync_dirs))
847                 print('Alternatively, use the pre-vendored `rustc-src` dist component.')
848                 raise Exception("{} not found".format(vendor_dir))
849
850             if not os.path.exists(cargo_dir):
851                 print('error: vendoring required, but .cargo/config does not exist.')
852                 raise Exception("{} not found".format(cargo_dir))
853         else:
854             if os.path.exists(cargo_dir):
855                 shutil.rmtree(cargo_dir)
856
857 def bootstrap(help_triggered):
858     """Configure, fetch, build and run the initial bootstrap"""
859
860     # If the user is asking for help, let them know that the whole download-and-build
861     # process has to happen before anything is printed out.
862     if help_triggered:
863         print("info: Downloading and building bootstrap before processing --help")
864         print("      command. See src/bootstrap/README.md for help with common")
865         print("      commands.")
866
867     parser = argparse.ArgumentParser(description='Build rust')
868     parser.add_argument('--config')
869     parser.add_argument('--build')
870     parser.add_argument('--color', choices=['always', 'never', 'auto'])
871     parser.add_argument('--clean', action='store_true')
872     parser.add_argument('-v', '--verbose', action='count', default=0)
873
874     args = [a for a in sys.argv if a != '-h' and a != '--help']
875     args, _ = parser.parse_known_args(args)
876
877     # Configure initial bootstrap
878     build = RustBuild()
879     build.rust_root = os.path.abspath(os.path.join(__file__, '../../..'))
880     build.verbose = args.verbose
881     build.clean = args.clean
882
883     # Read from `--config`, then `RUST_BOOTSTRAP_CONFIG`, then `./config.toml`,
884     # then `config.toml` in the root directory.
885     toml_path = args.config or os.getenv('RUST_BOOTSTRAP_CONFIG')
886     using_default_path = toml_path is None
887     if using_default_path:
888         toml_path = 'config.toml'
889         if not os.path.exists(toml_path):
890             toml_path = os.path.join(build.rust_root, toml_path)
891
892     # Give a hard error if `--config` or `RUST_BOOTSTRAP_CONFIG` are set to a missing path,
893     # but not if `config.toml` hasn't been created.
894     if not using_default_path or os.path.exists(toml_path):
895         with open(toml_path) as config:
896             build.config_toml = config.read()
897
898     profile = build.get_toml('profile')
899     if profile is not None:
900         include_file = 'config.{}.toml'.format(profile)
901         include_dir = os.path.join(build.rust_root, 'src', 'bootstrap', 'defaults')
902         include_path = os.path.join(include_dir, include_file)
903         # HACK: This works because `build.get_toml()` returns the first match it finds for a
904         # specific key, so appending our defaults at the end allows the user to override them
905         with open(include_path) as included_toml:
906             build.config_toml += os.linesep + included_toml.read()
907
908     config_verbose = build.get_toml('verbose', 'build')
909     if config_verbose is not None:
910         build.verbose = max(build.verbose, int(config_verbose))
911
912     build.use_vendored_sources = build.get_toml('vendor', 'build') == 'true'
913
914     build.use_locked_deps = build.get_toml('locked-deps', 'build') == 'true'
915
916     build.check_vendored_status()
917
918     build_dir = build.get_toml('build-dir', 'build') or 'build'
919     build.build_dir = os.path.abspath(build_dir)
920
921     with open(os.path.join(build.rust_root, "src", "stage0.json")) as f:
922         data = json.load(f)
923     build.checksums_sha256 = data["checksums_sha256"]
924     build.stage0_compiler = Stage0Toolchain(data["compiler"])
925
926     build.set_dist_environment(data["config"]["dist_server"])
927
928     build.build = args.build or build.build_triple()
929
930     # Acquire the lock before doing any build actions
931     # The lock is released when `lock` is dropped
932     if not os.path.exists(build.build_dir):
933         os.makedirs(build.build_dir)
934     lock = acquire_lock(build.build_dir)
935
936     # Fetch/build the bootstrap
937     build.download_toolchain()
938     sys.stdout.flush()
939     build.build_bootstrap(args.color)
940     sys.stdout.flush()
941
942     # Run the bootstrap
943     args = [build.bootstrap_binary()]
944     args.extend(sys.argv[1:])
945     env = os.environ.copy()
946     env["BOOTSTRAP_PARENT_ID"] = str(os.getpid())
947     env["BOOTSTRAP_PYTHON"] = sys.executable
948     run(args, env=env, verbose=build.verbose, is_bootstrap=True)
949
950
951 def main():
952     """Entry point for the bootstrap process"""
953     start_time = time()
954
955     # x.py help <cmd> ...
956     if len(sys.argv) > 1 and sys.argv[1] == 'help':
957         sys.argv = [sys.argv[0], '-h'] + sys.argv[2:]
958
959     help_triggered = (
960         '-h' in sys.argv) or ('--help' in sys.argv) or (len(sys.argv) == 1)
961     try:
962         bootstrap(help_triggered)
963         if not help_triggered:
964             print("Build completed successfully in {}".format(
965                 format_build_time(time() - start_time)))
966     except (SystemExit, KeyboardInterrupt) as error:
967         if hasattr(error, 'code') and isinstance(error.code, int):
968             exit_code = error.code
969         else:
970             exit_code = 1
971             print(error)
972         if not help_triggered:
973             print("Build completed unsuccessfully in {}".format(
974                 format_build_time(time() - start_time)))
975         sys.exit(exit_code)
976
977
978 if __name__ == '__main__':
979     main()