]> git.proxmox.com Git - rustc.git/blame - src/bootstrap/bootstrap.py
New upstream version 1.46.0~beta.2+dfsg1
[rustc.git] / src / bootstrap / bootstrap.py
CommitLineData
abe05a73 1from __future__ import absolute_import, division, print_function
7453a54e
SL
2import argparse
3import contextlib
3157f602 4import datetime
f9f354fc 5import distutils.version
a7813a04 6import hashlib
7453a54e 7import os
7cac9316 8import re
7453a54e
SL
9import shutil
10import subprocess
11import sys
12import tarfile
a7813a04
XL
13import tempfile
14
3157f602
XL
15from time import time
16
7453a54e
SL
17
18def get(url, path, verbose=False):
041b39d2
XL
19 suffix = '.sha256'
20 sha_url = url + suffix
a7813a04
XL
21 with tempfile.NamedTemporaryFile(delete=False) as temp_file:
22 temp_path = temp_file.name
041b39d2 23 with tempfile.NamedTemporaryFile(suffix=suffix, delete=False) as sha_file:
a7813a04
XL
24 sha_path = sha_file.name
25
26 try:
476ff2be 27 download(sha_path, sha_url, False, verbose)
5bcae85e
SL
28 if os.path.exists(path):
29 if verify(path, sha_path, False):
476ff2be 30 if verbose:
3b2f2976 31 print("using already-download file", path)
5bcae85e
SL
32 return
33 else:
476ff2be 34 if verbose:
3b2f2976
XL
35 print("ignoring already-download file",
36 path, "due to failed verification")
5bcae85e 37 os.unlink(path)
476ff2be
SL
38 download(temp_path, url, True, verbose)
39 if not verify(temp_path, sha_path, verbose):
5bcae85e 40 raise RuntimeError("failed verification")
476ff2be
SL
41 if verbose:
42 print("moving {} to {}".format(temp_path, path))
a7813a04
XL
43 shutil.move(temp_path, path)
44 finally:
476ff2be
SL
45 delete_if_present(sha_path, verbose)
46 delete_if_present(temp_path, verbose)
a7813a04
XL
47
48
476ff2be 49def delete_if_present(path, verbose):
041b39d2 50 """Remove the given file if present"""
a7813a04 51 if os.path.isfile(path):
476ff2be 52 if verbose:
3b2f2976 53 print("removing", path)
a7813a04
XL
54 os.unlink(path)
55
56
476ff2be 57def download(path, url, probably_big, verbose):
3b2f2976 58 for _ in range(0, 4):
8bb4bdeb
XL
59 try:
60 _download(path, url, probably_big, verbose, True)
61 return
62 except RuntimeError:
63 print("\nspurious failure, trying again")
64 _download(path, url, probably_big, verbose, False)
65
66
67def _download(path, url, probably_big, verbose, exception):
476ff2be
SL
68 if probably_big or verbose:
69 print("downloading {}".format(url))
7453a54e
SL
70 # see http://serverfault.com/questions/301128/how-to-download
71 if sys.platform == 'win32':
72 run(["PowerShell.exe", "/nologo", "-Command",
a1dfa0c6
XL
73 "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12;",
74 "(New-Object System.Net.WebClient).DownloadFile('{}', '{}')".format(url, path)],
8bb4bdeb
XL
75 verbose=verbose,
76 exception=exception)
7453a54e 77 else:
476ff2be
SL
78 if probably_big or verbose:
79 option = "-#"
80 else:
81 option = "-s"
f9f354fc 82 require(["curl", "--version"])
b7449926
XL
83 run(["curl", option,
84 "-y", "30", "-Y", "10", # timeout if speed is < 10 bytes/sec for > 30 seconds
74b04a01 85 "--connect-timeout", "30", # timeout if cannot connect within 30 seconds
b7449926 86 "--retry", "3", "-Sf", "-o", path, url],
8bb4bdeb
XL
87 verbose=verbose,
88 exception=exception)
7453a54e 89
a7813a04
XL
90
91def verify(path, sha_path, verbose):
041b39d2 92 """Check if the sha256 sum of the given path is valid"""
476ff2be 93 if verbose:
3b2f2976 94 print("verifying", path)
041b39d2
XL
95 with open(path, "rb") as source:
96 found = hashlib.sha256(source.read()).hexdigest()
97 with open(sha_path, "r") as sha256sum:
98 expected = sha256sum.readline().split()[0]
5bcae85e 99 verified = found == expected
476ff2be 100 if not verified:
5bcae85e 101 print("invalid checksum:\n"
7cac9316
XL
102 " found: {}\n"
103 " expected: {}".format(found, expected))
5bcae85e 104 return verified
a7813a04
XL
105
106
60c5eb7d 107def unpack(tarball, tarball_suffix, dst, verbose=False, match=None):
041b39d2 108 """Unpack the given tarball file"""
3b2f2976 109 print("extracting", tarball)
60c5eb7d 110 fname = os.path.basename(tarball).replace(tarball_suffix, "")
7453a54e 111 with contextlib.closing(tarfile.open(tarball)) as tar:
3b2f2976
XL
112 for member in tar.getnames():
113 if "/" not in member:
7453a54e 114 continue
3b2f2976 115 name = member.replace(fname + "/", "", 1)
7453a54e
SL
116 if match is not None and not name.startswith(match):
117 continue
118 name = name[len(match) + 1:]
119
3b2f2976 120 dst_path = os.path.join(dst, name)
7453a54e 121 if verbose:
3b2f2976
XL
122 print(" extracting", member)
123 tar.extract(member, dst)
124 src_path = os.path.join(dst, member)
125 if os.path.isdir(src_path) and os.path.exists(dst_path):
7453a54e 126 continue
3b2f2976 127 shutil.move(src_path, dst_path)
7453a54e
SL
128 shutil.rmtree(os.path.join(dst, fname))
129
041b39d2 130
7cac9316 131def run(args, verbose=False, exception=False, **kwargs):
3b2f2976 132 """Run a child program in a new process"""
7453a54e
SL
133 if verbose:
134 print("running: " + ' '.join(args))
135 sys.stdout.flush()
136 # Use Popen here instead of call() as it apparently allows powershell on
137 # Windows to not lock up waiting for input presumably.
7cac9316 138 ret = subprocess.Popen(args, **kwargs)
7453a54e
SL
139 code = ret.wait()
140 if code != 0:
a7813a04 141 err = "failed to run: " + ' '.join(args)
8bb4bdeb 142 if verbose or exception:
a7813a04
XL
143 raise RuntimeError(err)
144 sys.exit(err)
145
7cac9316 146
f9f354fc
XL
147def require(cmd, exit=True):
148 '''Run a command, returning its output.
149 On error,
150 If `exit` is `True`, exit the process.
151 Otherwise, return None.'''
152 try:
153 return subprocess.check_output(cmd).strip()
154 except (subprocess.CalledProcessError, OSError) as exc:
155 if not exit:
156 return None
157 print("error: unable to run `{}`: {}".format(' '.join(cmd), exc))
158 print("Please make sure it's installed and in the path.")
159 sys.exit(1)
160
161
a7813a04 162def stage0_data(rust_root):
3b2f2976 163 """Build a dictionary from stage0.txt"""
a7813a04
XL
164 nightlies = os.path.join(rust_root, "src/stage0.txt")
165 with open(nightlies, 'r') as nightlies:
3b2f2976
XL
166 lines = [line.rstrip() for line in nightlies
167 if not line.startswith("#")]
168 return dict([line.split(": ", 1) for line in lines if line])
3157f602 169
7cac9316 170
3157f602 171def format_build_time(duration):
3b2f2976
XL
172 """Return a nicer format for build time
173
174 >>> format_build_time('300')
175 '0:05:00'
176 """
3157f602 177 return str(datetime.timedelta(seconds=int(duration)))
7453a54e 178
9e0c209e 179
ea8adc8c
XL
180def default_build_triple():
181 """Build triple as in LLVM"""
182 default_encoding = sys.getdefaultencoding()
f9f354fc
XL
183 required = sys.platform != 'win32'
184 ostype = require(["uname", "-s"], exit=required)
185 cputype = require(['uname', '-m'], exit=required)
186
f035d41b 187 # If we do not have `uname`, assume Windows.
f9f354fc
XL
188 if ostype is None or cputype is None:
189 return 'x86_64-pc-windows-msvc'
190
191 ostype = ostype.decode(default_encoding)
192 cputype = cputype.decode(default_encoding)
ea8adc8c
XL
193
194 # The goal here is to come up with the same triple as LLVM would,
195 # at least for the subset of platforms we're willing to target.
196 ostype_mapper = {
ea8adc8c
XL
197 'Darwin': 'apple-darwin',
198 'DragonFly': 'unknown-dragonfly',
199 'FreeBSD': 'unknown-freebsd',
200 'Haiku': 'unknown-haiku',
201 'NetBSD': 'unknown-netbsd',
202 'OpenBSD': 'unknown-openbsd'
203 }
204
205 # Consider the direct transformation first and then the special cases
206 if ostype in ostype_mapper:
207 ostype = ostype_mapper[ostype]
208 elif ostype == 'Linux':
209 os_from_sp = subprocess.check_output(
210 ['uname', '-o']).strip().decode(default_encoding)
211 if os_from_sp == 'Android':
212 ostype = 'linux-android'
213 else:
214 ostype = 'unknown-linux-gnu'
215 elif ostype == 'SunOS':
216 ostype = 'sun-solaris'
217 # On Solaris, uname -m will return a machine classification instead
218 # of a cpu type, so uname -p is recommended instead. However, the
219 # output from that option is too generic for our purposes (it will
220 # always emit 'i386' on x86/amd64 systems). As such, isainfo -k
221 # must be used instead.
f9f354fc 222 cputype = require(['isainfo', '-k']).decode(default_encoding)
ea8adc8c
XL
223 elif ostype.startswith('MINGW'):
224 # msys' `uname` does not print gcc configuration, but prints msys
225 # configuration. so we cannot believe `uname -m`:
226 # msys1 is always i686 and msys2 is always x86_64.
227 # instead, msys defines $MSYSTEM which is MINGW32 on i686 and
228 # MINGW64 on x86_64.
229 ostype = 'pc-windows-gnu'
230 cputype = 'i686'
231 if os.environ.get('MSYSTEM') == 'MINGW64':
232 cputype = 'x86_64'
233 elif ostype.startswith('MSYS'):
234 ostype = 'pc-windows-gnu'
235 elif ostype.startswith('CYGWIN_NT'):
236 cputype = 'i686'
237 if ostype.endswith('WOW64'):
238 cputype = 'x86_64'
239 ostype = 'pc-windows-gnu'
f035d41b
XL
240 elif sys.platform == 'win32':
241 # Some Windows platforms might have a `uname` command that returns a
242 # non-standard string (e.g. gnuwin32 tools returns `windows32`). In
243 # these cases, fall back to using sys.platform.
244 return 'x86_64-pc-windows-msvc'
ea8adc8c
XL
245 else:
246 err = "unknown OS type: {}".format(ostype)
247 sys.exit(err)
248
9fa01778
XL
249 if cputype == 'powerpc' and ostype == 'unknown-freebsd':
250 cputype = subprocess.check_output(
251 ['uname', '-p']).strip().decode(default_encoding)
ea8adc8c
XL
252 cputype_mapper = {
253 'BePC': 'i686',
254 'aarch64': 'aarch64',
255 'amd64': 'x86_64',
256 'arm64': 'aarch64',
257 'i386': 'i686',
258 'i486': 'i686',
259 'i686': 'i686',
260 'i786': 'i686',
261 'powerpc': 'powerpc',
262 'powerpc64': 'powerpc64',
263 'powerpc64le': 'powerpc64le',
264 'ppc': 'powerpc',
265 'ppc64': 'powerpc64',
266 'ppc64le': 'powerpc64le',
267 's390x': 's390x',
268 'x64': 'x86_64',
269 'x86': 'i686',
270 'x86-64': 'x86_64',
271 'x86_64': 'x86_64'
272 }
273
274 # Consider the direct transformation first and then the special cases
275 if cputype in cputype_mapper:
276 cputype = cputype_mapper[cputype]
277 elif cputype in {'xscale', 'arm'}:
278 cputype = 'arm'
279 if ostype == 'linux-android':
280 ostype = 'linux-androideabi'
532ac7d7
XL
281 elif ostype == 'unknown-freebsd':
282 cputype = subprocess.check_output(
283 ['uname', '-p']).strip().decode(default_encoding)
284 ostype = 'unknown-freebsd'
ea8adc8c
XL
285 elif cputype == 'armv6l':
286 cputype = 'arm'
287 if ostype == 'linux-android':
288 ostype = 'linux-androideabi'
289 else:
290 ostype += 'eabihf'
291 elif cputype in {'armv7l', 'armv8l'}:
292 cputype = 'armv7'
293 if ostype == 'linux-android':
294 ostype = 'linux-androideabi'
295 else:
296 ostype += 'eabihf'
297 elif cputype == 'mips':
298 if sys.byteorder == 'big':
299 cputype = 'mips'
300 elif sys.byteorder == 'little':
301 cputype = 'mipsel'
302 else:
303 raise ValueError("unknown byteorder: {}".format(sys.byteorder))
304 elif cputype == 'mips64':
305 if sys.byteorder == 'big':
306 cputype = 'mips64'
307 elif sys.byteorder == 'little':
308 cputype = 'mips64el'
309 else:
310 raise ValueError('unknown byteorder: {}'.format(sys.byteorder))
311 # only the n64 ABI is supported, indicate it
312 ostype += 'abi64'
0531ce1d 313 elif cputype == 'sparc' or cputype == 'sparcv9' or cputype == 'sparc64':
ea8adc8c
XL
314 pass
315 else:
316 err = "unknown cpu type: {}".format(cputype)
317 sys.exit(err)
318
319 return "{}-{}".format(cputype, ostype)
320
abe05a73 321
8faf50e0
XL
322@contextlib.contextmanager
323def output(filepath):
324 tmp = filepath + '.tmp'
325 with open(tmp, 'w') as f:
326 yield f
327 try:
328 os.remove(filepath) # PermissionError/OSError on Win32 if in use
329 os.rename(tmp, filepath)
330 except OSError:
331 shutil.copy2(tmp, filepath)
332 os.remove(tmp)
333
334
9e0c209e 335class RustBuild(object):
3b2f2976
XL
336 """Provide all the methods required to build Rust"""
337 def __init__(self):
338 self.cargo_channel = ''
339 self.date = ''
e1599b0c 340 self._download_url = ''
3b2f2976 341 self.rustc_channel = ''
dfeec247 342 self.rustfmt_channel = ''
3b2f2976 343 self.build = ''
f9f354fc 344 self.build_dir = ''
3b2f2976 345 self.clean = False
3b2f2976 346 self.config_toml = ''
83c7162d 347 self.rust_root = ''
3b2f2976
XL
348 self.use_locked_deps = ''
349 self.use_vendored_sources = ''
350 self.verbose = False
f9f354fc 351 self.git_version = None
7cac9316 352
a7813a04 353 def download_stage0(self):
3b2f2976
XL
354 """Fetch the build system for Rust, written in Rust
355
356 This method will build a cache directory, then it will fetch the
357 tarball which has the stage0 compiler used to then bootstrap the Rust
358 compiler itself.
cc61c64b 359
3b2f2976
XL
360 Each downloaded tarball is extracted, after that, the script
361 will move all the content to the right place.
362 """
363 rustc_channel = self.rustc_channel
364 cargo_channel = self.cargo_channel
dfeec247 365 rustfmt_channel = self.rustfmt_channel
7453a54e 366
60c5eb7d
XL
367 def support_xz():
368 try:
369 with tempfile.NamedTemporaryFile(delete=False) as temp_file:
370 temp_path = temp_file.name
74b04a01 371 with tarfile.open(temp_path, "w:xz"):
60c5eb7d
XL
372 pass
373 return True
374 except tarfile.CompressionError:
375 return False
376
7453a54e 377 if self.rustc().startswith(self.bin_root()) and \
3b2f2976
XL
378 (not os.path.exists(self.rustc()) or
379 self.program_out_of_date(self.rustc_stamp())):
54a0048b
SL
380 if os.path.exists(self.bin_root()):
381 shutil.rmtree(self.bin_root())
60c5eb7d
XL
382 tarball_suffix = '.tar.xz' if support_xz() else '.tar.gz'
383 filename = "rust-std-{}-{}{}".format(
384 rustc_channel, self.build, tarball_suffix)
3b2f2976 385 pattern = "rust-std-{}".format(self.build)
60c5eb7d 386 self._download_stage0_helper(filename, pattern, tarball_suffix)
7453a54e 387
60c5eb7d
XL
388 filename = "rustc-{}-{}{}".format(rustc_channel, self.build,
389 tarball_suffix)
390 self._download_stage0_helper(filename, "rustc", tarball_suffix)
3b2f2976
XL
391 self.fix_executable("{}/bin/rustc".format(self.bin_root()))
392 self.fix_executable("{}/bin/rustdoc".format(self.bin_root()))
8faf50e0 393 with output(self.rustc_stamp()) as rust_stamp:
3b2f2976 394 rust_stamp.write(self.date)
7453a54e 395
0531ce1d
XL
396 # This is required so that we don't mix incompatible MinGW
397 # libraries/binaries that are included in rust-std with
398 # the system MinGW ones.
399 if "pc-windows-gnu" in self.build:
60c5eb7d
XL
400 filename = "rust-mingw-{}-{}{}".format(
401 rustc_channel, self.build, tarball_suffix)
402 self._download_stage0_helper(filename, "rust-mingw", tarball_suffix)
0531ce1d 403
7453a54e 404 if self.cargo().startswith(self.bin_root()) and \
3b2f2976
XL
405 (not os.path.exists(self.cargo()) or
406 self.program_out_of_date(self.cargo_stamp())):
60c5eb7d
XL
407 tarball_suffix = '.tar.xz' if support_xz() else '.tar.gz'
408 filename = "cargo-{}-{}{}".format(cargo_channel, self.build,
409 tarball_suffix)
410 self._download_stage0_helper(filename, "cargo", tarball_suffix)
3b2f2976 411 self.fix_executable("{}/bin/cargo".format(self.bin_root()))
8faf50e0 412 with output(self.cargo_stamp()) as cargo_stamp:
3b2f2976
XL
413 cargo_stamp.write(self.date)
414
dfeec247
XL
415 if self.rustfmt() and self.rustfmt().startswith(self.bin_root()) and (
416 not os.path.exists(self.rustfmt())
74b04a01 417 or self.program_out_of_date(self.rustfmt_stamp(), self.rustfmt_channel)
dfeec247
XL
418 ):
419 if rustfmt_channel:
420 tarball_suffix = '.tar.xz' if support_xz() else '.tar.gz'
421 [channel, date] = rustfmt_channel.split('-', 1)
422 filename = "rustfmt-{}-{}{}".format(channel, self.build, tarball_suffix)
423 self._download_stage0_helper(filename, "rustfmt-preview", tarball_suffix, date)
424 self.fix_executable("{}/bin/rustfmt".format(self.bin_root()))
425 self.fix_executable("{}/bin/cargo-fmt".format(self.bin_root()))
426 with output(self.rustfmt_stamp()) as rustfmt_stamp:
74b04a01 427 rustfmt_stamp.write(self.date + self.rustfmt_channel)
dfeec247
XL
428
429 def _download_stage0_helper(self, filename, pattern, tarball_suffix, date=None):
430 if date is None:
431 date = self.date
3b2f2976 432 cache_dst = os.path.join(self.build_dir, "cache")
dfeec247 433 rustc_cache = os.path.join(cache_dst, date)
3b2f2976
XL
434 if not os.path.exists(rustc_cache):
435 os.makedirs(rustc_cache)
436
dfeec247 437 url = "{}/dist/{}".format(self._download_url, date)
3b2f2976
XL
438 tarball = os.path.join(rustc_cache, filename)
439 if not os.path.exists(tarball):
440 get("{}/{}".format(url, filename), tarball, verbose=self.verbose)
60c5eb7d 441 unpack(tarball, tarball_suffix, self.bin_root(), match=pattern, verbose=self.verbose)
8bb4bdeb 442
3b2f2976
XL
443 @staticmethod
444 def fix_executable(fname):
445 """Modifies the interpreter section of 'fname' to fix the dynamic linker
446
447 This method is only required on NixOS and uses the PatchELF utility to
448 change the dynamic linker of ELF executables.
449
450 Please see https://nixos.org/patchelf.html for more information
451 """
8bb4bdeb
XL
452 default_encoding = sys.getdefaultencoding()
453 try:
7cac9316
XL
454 ostype = subprocess.check_output(
455 ['uname', '-s']).strip().decode(default_encoding)
3b2f2976 456 except subprocess.CalledProcessError:
8bb4bdeb 457 return
3b2f2976
XL
458 except OSError as reason:
459 if getattr(reason, 'winerror', None) is not None:
460 return
461 raise reason
8bb4bdeb
XL
462
463 if ostype != "Linux":
464 return
465
466 if not os.path.exists("/etc/NIXOS"):
467 return
468 if os.path.exists("/lib"):
469 return
470
471 # At this point we're pretty sure the user is running NixOS
041b39d2
XL
472 nix_os_msg = "info: you seem to be running NixOS. Attempting to patch"
473 print(nix_os_msg, fname)
8bb4bdeb
XL
474
475 try:
7cac9316
XL
476 interpreter = subprocess.check_output(
477 ["patchelf", "--print-interpreter", fname])
8bb4bdeb 478 interpreter = interpreter.strip().decode(default_encoding)
3b2f2976
XL
479 except subprocess.CalledProcessError as reason:
480 print("warning: failed to call patchelf:", reason)
8bb4bdeb
XL
481 return
482
483 loader = interpreter.split("/")[-1]
484
485 try:
7cac9316
XL
486 ldd_output = subprocess.check_output(
487 ['ldd', '/run/current-system/sw/bin/sh'])
8bb4bdeb 488 ldd_output = ldd_output.strip().decode(default_encoding)
3b2f2976
XL
489 except subprocess.CalledProcessError as reason:
490 print("warning: unable to call ldd:", reason)
8bb4bdeb
XL
491 return
492
493 for line in ldd_output.splitlines():
494 libname = line.split()[0]
495 if libname.endswith(loader):
496 loader_path = libname[:len(libname) - len(loader)]
497 break
498 else:
499 print("warning: unable to find the path to the dynamic linker")
500 return
501
502 correct_interpreter = loader_path + loader
503
504 try:
7cac9316
XL
505 subprocess.check_output(
506 ["patchelf", "--set-interpreter", correct_interpreter, fname])
3b2f2976
XL
507 except subprocess.CalledProcessError as reason:
508 print("warning: failed to call patchelf:", reason)
8bb4bdeb
XL
509 return
510
7453a54e 511 def rustc_stamp(self):
3b2f2976
XL
512 """Return the path for .rustc-stamp
513
514 >>> rb = RustBuild()
515 >>> rb.build_dir = "build"
516 >>> rb.rustc_stamp() == os.path.join("build", "stage0", ".rustc-stamp")
517 True
518 """
7453a54e
SL
519 return os.path.join(self.bin_root(), '.rustc-stamp')
520
521 def cargo_stamp(self):
3b2f2976 522 """Return the path for .cargo-stamp
7453a54e 523
3b2f2976
XL
524 >>> rb = RustBuild()
525 >>> rb.build_dir = "build"
526 >>> rb.cargo_stamp() == os.path.join("build", "stage0", ".cargo-stamp")
527 True
528 """
529 return os.path.join(self.bin_root(), '.cargo-stamp')
7453a54e 530
dfeec247
XL
531 def rustfmt_stamp(self):
532 """Return the path for .rustfmt-stamp
533
534 >>> rb = RustBuild()
535 >>> rb.build_dir = "build"
536 >>> rb.rustfmt_stamp() == os.path.join("build", "stage0", ".rustfmt-stamp")
537 True
538 """
539 return os.path.join(self.bin_root(), '.rustfmt-stamp')
540
74b04a01 541 def program_out_of_date(self, stamp_path, extra=""):
3b2f2976
XL
542 """Check if the given program stamp is out of date"""
543 if not os.path.exists(stamp_path) or self.clean:
7453a54e 544 return True
3b2f2976 545 with open(stamp_path, 'r') as stamp:
74b04a01 546 return (self.date + extra) != stamp.read()
7453a54e
SL
547
548 def bin_root(self):
3b2f2976
XL
549 """Return the binary root directory
550
551 >>> rb = RustBuild()
552 >>> rb.build_dir = "build"
553 >>> rb.bin_root() == os.path.join("build", "stage0")
554 True
555
556 When the 'build' property is given should be a nested directory:
557
558 >>> rb.build = "devel"
559 >>> rb.bin_root() == os.path.join("build", "devel", "stage0")
560 True
561 """
7453a54e
SL
562 return os.path.join(self.build_dir, self.build, "stage0")
563
94b46f34 564 def get_toml(self, key, section=None):
3b2f2976
XL
565 """Returns the value of the given key in config.toml, otherwise returns None
566
567 >>> rb = RustBuild()
568 >>> rb.config_toml = 'key1 = "value1"\\nkey2 = "value2"'
569 >>> rb.get_toml("key2")
570 'value2'
571
572 If the key does not exists, the result is None:
573
abe05a73 574 >>> rb.get_toml("key3") is None
3b2f2976 575 True
94b46f34
XL
576
577 Optionally also matches the section the key appears in
578
579 >>> rb.config_toml = '[a]\\nkey = "value1"\\n[b]\\nkey = "value2"'
580 >>> rb.get_toml('key', 'a')
581 'value1'
582 >>> rb.get_toml('key', 'b')
583 'value2'
584 >>> rb.get_toml('key', 'c') is None
585 True
e1599b0c
XL
586
587 >>> rb.config_toml = 'key1 = true'
588 >>> rb.get_toml("key1")
589 'true'
3b2f2976 590 """
94b46f34
XL
591
592 cur_section = None
7453a54e 593 for line in self.config_toml.splitlines():
94b46f34
XL
594 section_match = re.match(r'^\s*\[(.*)\]\s*$', line)
595 if section_match is not None:
596 cur_section = section_match.group(1)
597
7cac9316
XL
598 match = re.match(r'^{}\s*=(.*)$'.format(key), line)
599 if match is not None:
600 value = match.group(1)
94b46f34
XL
601 if section is None or section == cur_section:
602 return self.get_string(value) or value.strip()
7453a54e
SL
603 return None
604
7453a54e 605 def cargo(self):
3b2f2976
XL
606 """Return config path for cargo"""
607 return self.program_config('cargo')
7453a54e
SL
608
609 def rustc(self):
3b2f2976
XL
610 """Return config path for rustc"""
611 return self.program_config('rustc')
612
dfeec247
XL
613 def rustfmt(self):
614 """Return config path for rustfmt"""
615 if not self.rustfmt_channel:
616 return None
617 return self.program_config('rustfmt')
618
3b2f2976
XL
619 def program_config(self, program):
620 """Return config path for the given program
621
622 >>> rb = RustBuild()
623 >>> rb.config_toml = 'rustc = "rustc"\\n'
3b2f2976
XL
624 >>> rb.program_config('rustc')
625 'rustc'
3b2f2976 626 >>> rb.config_toml = ''
3b2f2976
XL
627 >>> cargo_path = rb.program_config('cargo')
628 >>> cargo_path.rstrip(".exe") == os.path.join(rb.bin_root(),
629 ... "bin", "cargo")
630 True
631 """
632 config = self.get_toml(program)
7453a54e 633 if config:
abe05a73 634 return os.path.expanduser(config)
3b2f2976
XL
635 return os.path.join(self.bin_root(), "bin", "{}{}".format(
636 program, self.exe_suffix()))
637
638 @staticmethod
639 def get_string(line):
640 """Return the value between double quotes
641
642 >>> RustBuild.get_string(' "devel" ')
643 'devel'
e1599b0c
XL
644 >>> RustBuild.get_string(" 'devel' ")
645 'devel'
646 >>> RustBuild.get_string('devel') is None
647 True
648 >>> RustBuild.get_string(' "devel ')
649 ''
3b2f2976 650 """
7453a54e 651 start = line.find('"')
ea8adc8c
XL
652 if start != -1:
653 end = start + 1 + line[start + 1:].find('"')
654 return line[start + 1:end]
655 start = line.find('\'')
656 if start != -1:
657 end = start + 1 + line[start + 1:].find('\'')
658 return line[start + 1:end]
659 return None
7453a54e 660
3b2f2976
XL
661 @staticmethod
662 def exe_suffix():
663 """Return a suffix for executables"""
7453a54e
SL
664 if sys.platform == 'win32':
665 return '.exe'
041b39d2 666 return ''
7453a54e 667
476ff2be 668 def bootstrap_binary(self):
0bf4aa26 669 """Return the path of the bootstrap binary
3b2f2976
XL
670
671 >>> rb = RustBuild()
672 >>> rb.build_dir = "build"
673 >>> rb.bootstrap_binary() == os.path.join("build", "bootstrap",
674 ... "debug", "bootstrap")
675 True
676 """
677 return os.path.join(self.build_dir, "bootstrap", "debug", "bootstrap")
476ff2be 678
7453a54e 679 def build_bootstrap(self):
3b2f2976 680 """Build bootstrap"""
3157f602
XL
681 build_dir = os.path.join(self.build_dir, "bootstrap")
682 if self.clean and os.path.exists(build_dir):
683 shutil.rmtree(build_dir)
7453a54e 684 env = os.environ.copy()
ba9703b0
XL
685 # `CARGO_BUILD_TARGET` breaks bootstrap build.
686 # See also: <https://github.com/rust-lang/rust/issues/70208>.
687 if "CARGO_BUILD_TARGET" in env:
688 del env["CARGO_BUILD_TARGET"]
041b39d2 689 env["RUSTC_BOOTSTRAP"] = '1'
3157f602 690 env["CARGO_TARGET_DIR"] = build_dir
7453a54e 691 env["RUSTC"] = self.rustc()
8bb4bdeb 692 env["LD_LIBRARY_PATH"] = os.path.join(self.bin_root(), "lib") + \
7cac9316
XL
693 (os.pathsep + env["LD_LIBRARY_PATH"]) \
694 if "LD_LIBRARY_PATH" in env else ""
8bb4bdeb 695 env["DYLD_LIBRARY_PATH"] = os.path.join(self.bin_root(), "lib") + \
7cac9316
XL
696 (os.pathsep + env["DYLD_LIBRARY_PATH"]) \
697 if "DYLD_LIBRARY_PATH" in env else ""
698 env["LIBRARY_PATH"] = os.path.join(self.bin_root(), "lib") + \
699 (os.pathsep + env["LIBRARY_PATH"]) \
700 if "LIBRARY_PATH" in env else ""
60c5eb7d
XL
701 # preserve existing RUSTFLAGS
702 env.setdefault("RUSTFLAGS", "")
703 env["RUSTFLAGS"] += " -Cdebuginfo=2"
94b46f34
XL
704
705 build_section = "target.{}".format(self.build_triple())
706 target_features = []
707 if self.get_toml("crt-static", build_section) == "true":
708 target_features += ["+crt-static"]
709 elif self.get_toml("crt-static", build_section) == "false":
710 target_features += ["-crt-static"]
711 if target_features:
60c5eb7d 712 env["RUSTFLAGS"] += " -C target-feature=" + (",".join(target_features))
13cf67c4
XL
713 target_linker = self.get_toml("linker", build_section)
714 if target_linker is not None:
60c5eb7d
XL
715 env["RUSTFLAGS"] += " -C linker=" + target_linker
716 env["RUSTFLAGS"] += " -Wrust_2018_idioms -Wunused_lifetimes"
e1599b0c 717 if self.get_toml("deny-warnings", "rust") != "false":
60c5eb7d 718 env["RUSTFLAGS"] += " -Dwarnings"
94b46f34 719
7453a54e 720 env["PATH"] = os.path.join(self.bin_root(), "bin") + \
7cac9316 721 os.pathsep + env["PATH"]
32a655c1 722 if not os.path.isfile(self.cargo()):
3b2f2976
XL
723 raise Exception("no cargo executable found at `{}`".format(
724 self.cargo()))
476ff2be
SL
725 args = [self.cargo(), "build", "--manifest-path",
726 os.path.join(self.rust_root, "src/bootstrap/Cargo.toml")]
0531ce1d 727 for _ in range(1, self.verbose):
7cac9316 728 args.append("--verbose")
8bb4bdeb
XL
729 if self.use_locked_deps:
730 args.append("--locked")
476ff2be
SL
731 if self.use_vendored_sources:
732 args.append("--frozen")
7cac9316 733 run(args, env=env, verbose=self.verbose)
7453a54e
SL
734
735 def build_triple(self):
3b2f2976 736 """Build triple as in LLVM"""
7453a54e
SL
737 config = self.get_toml('build')
738 if config:
739 return config
ea8adc8c 740 return default_build_triple()
7453a54e 741
0531ce1d
XL
742 def check_submodule(self, module, slow_submodules):
743 if not slow_submodules:
744 checked_out = subprocess.Popen(["git", "rev-parse", "HEAD"],
745 cwd=os.path.join(self.rust_root, module),
746 stdout=subprocess.PIPE)
747 return checked_out
748 else:
749 return None
750
751 def update_submodule(self, module, checked_out, recorded_submodules):
752 module_path = os.path.join(self.rust_root, module)
753
e1599b0c 754 if checked_out is not None:
0531ce1d
XL
755 default_encoding = sys.getdefaultencoding()
756 checked_out = checked_out.communicate()[0].decode(default_encoding).strip()
757 if recorded_submodules[module] == checked_out:
758 return
759
760 print("Updating submodule", module)
761
762 run(["git", "submodule", "-q", "sync", module],
763 cwd=self.rust_root, verbose=self.verbose)
f9f354fc
XL
764
765 update_args = ["git", "submodule", "update", "--init", "--recursive"]
766 if self.git_version >= distutils.version.LooseVersion("2.11.0"):
767 update_args.append("--progress")
768 update_args.append(module)
769 run(update_args, cwd=self.rust_root, verbose=self.verbose, exception=True)
770
0531ce1d
XL
771 run(["git", "reset", "-q", "--hard"],
772 cwd=module_path, verbose=self.verbose)
773 run(["git", "clean", "-qdfx"],
774 cwd=module_path, verbose=self.verbose)
775
7cac9316 776 def update_submodules(self):
3b2f2976 777 """Update submodules"""
7cac9316 778 if (not os.path.exists(os.path.join(self.rust_root, ".git"))) or \
ea8adc8c 779 self.get_toml('submodules') == "false":
7cac9316 780 return
e1599b0c 781
f9f354fc
XL
782 default_encoding = sys.getdefaultencoding()
783
784 # check the existence and version of 'git' command
785 git_version_str = require(['git', '--version']).split()[2].decode(default_encoding)
786 self.git_version = distutils.version.LooseVersion(git_version_str)
e1599b0c 787
0531ce1d
XL
788 slow_submodules = self.get_toml('fast-submodules') == "false"
789 start_time = time()
790 if slow_submodules:
791 print('Unconditionally updating all submodules')
792 else:
793 print('Updating only changed submodules')
7cac9316 794 default_encoding = sys.getdefaultencoding()
7cac9316 795 submodules = [s.split(' ', 1)[1] for s in subprocess.check_output(
041b39d2
XL
796 ["git", "config", "--file",
797 os.path.join(self.rust_root, ".gitmodules"),
7cac9316
XL
798 "--get-regexp", "path"]
799 ).decode(default_encoding).splitlines()]
2c00a5a8 800 filtered_submodules = []
0531ce1d 801 submodules_names = []
2c00a5a8 802 for module in submodules:
9fa01778
XL
803 if module.endswith("llvm-project"):
804 if self.get_toml('llvm-config') and self.get_toml('lld') != 'true':
2c00a5a8 805 continue
0531ce1d
XL
806 check = self.check_submodule(module, slow_submodules)
807 filtered_submodules.append((module, check))
808 submodules_names.append(module)
809 recorded = subprocess.Popen(["git", "ls-tree", "HEAD"] + submodules_names,
810 cwd=self.rust_root, stdout=subprocess.PIPE)
811 recorded = recorded.communicate()[0].decode(default_encoding).strip().splitlines()
812 recorded_submodules = {}
813 for data in recorded:
814 data = data.split()
815 recorded_submodules[data[3]] = data[2]
816 for module in filtered_submodules:
817 self.update_submodule(module[0], module[1], recorded_submodules)
818 print("Submodules updated in %.2f seconds" % (time() - start_time))
7cac9316 819
e1599b0c
XL
820 def set_normal_environment(self):
821 """Set download URL for normal environment"""
822 if 'RUSTUP_DIST_SERVER' in os.environ:
823 self._download_url = os.environ['RUSTUP_DIST_SERVER']
824 else:
825 self._download_url = 'https://static.rust-lang.org'
826
3b2f2976
XL
827 def set_dev_environment(self):
828 """Set download URL for development environment"""
e1599b0c
XL
829 if 'RUSTUP_DEV_DIST_SERVER' in os.environ:
830 self._download_url = os.environ['RUSTUP_DEV_DIST_SERVER']
831 else:
832 self._download_url = 'https://dev-static.rust-lang.org'
3b2f2976 833
416331ca
XL
834 def check_vendored_status(self):
835 """Check that vendoring is configured properly"""
836 vendor_dir = os.path.join(self.rust_root, 'vendor')
837 if 'SUDO_USER' in os.environ and not self.use_vendored_sources:
838 if os.environ.get('USER') != os.environ['SUDO_USER']:
839 self.use_vendored_sources = True
840 print('info: looks like you are running this command under `sudo`')
841 print(' and so in order to preserve your $HOME this will now')
842 print(' use vendored sources by default.')
843 if not os.path.exists(vendor_dir):
844 print('error: vendoring required, but vendor directory does not exist.')
845 print(' Run `cargo vendor` without sudo to initialize the '
74b04a01 846 'vendor directory.')
416331ca
XL
847 raise Exception("{} not found".format(vendor_dir))
848
849 if self.use_vendored_sources:
850 if not os.path.exists('.cargo'):
851 os.makedirs('.cargo')
852 with output('.cargo/config') as cargo_config:
853 cargo_config.write(
854 "[source.crates-io]\n"
855 "replace-with = 'vendored-sources'\n"
856 "registry = 'https://example.com'\n"
857 "\n"
858 "[source.vendored-sources]\n"
859 "directory = '{}/vendor'\n"
74b04a01 860 .format(self.rust_root))
416331ca
XL
861 else:
862 if os.path.exists('.cargo'):
863 shutil.rmtree('.cargo')
864
865 def ensure_vendored(self):
866 """Ensure that the vendored sources are available if needed"""
867 vendor_dir = os.path.join(self.rust_root, 'vendor')
868 # Note that this does not handle updating the vendored dependencies if
869 # the rust git repository is updated. Normal development usually does
870 # not use vendoring, so hopefully this isn't too much of a problem.
871 if self.use_vendored_sources and not os.path.exists(vendor_dir):
f035d41b 872 run([self.cargo(), "vendor", "--sync=./src/tools/rust-analyzer/Cargo.toml"],
416331ca
XL
873 verbose=self.verbose, cwd=self.rust_root)
874
7cac9316 875
0531ce1d 876def bootstrap(help_triggered):
3b2f2976 877 """Configure, fetch, build and run the initial bootstrap"""
0531ce1d
XL
878
879 # If the user is asking for help, let them know that the whole download-and-build
880 # process has to happen before anything is printed out.
881 if help_triggered:
882 print("info: Downloading and building bootstrap before processing --help")
883 print(" command. See src/bootstrap/README.md for help with common")
884 print(" commands.")
885
a7813a04
XL
886 parser = argparse.ArgumentParser(description='Build rust')
887 parser.add_argument('--config')
3b2f2976 888 parser.add_argument('--build')
83c7162d 889 parser.add_argument('--src')
3157f602 890 parser.add_argument('--clean', action='store_true')
0531ce1d 891 parser.add_argument('-v', '--verbose', action='count', default=0)
a7813a04 892
5bcae85e 893 args = [a for a in sys.argv if a != '-h' and a != '--help']
a7813a04
XL
894 args, _ = parser.parse_known_args(args)
895
896 # Configure initial bootstrap
3b2f2976 897 build = RustBuild()
83c7162d 898 build.rust_root = args.src or os.path.abspath(os.path.join(__file__, '../../..'))
3b2f2976
XL
899 build.verbose = args.verbose
900 build.clean = args.clean
a7813a04 901
f035d41b
XL
902 # Read from `RUST_BOOTSTRAP_CONFIG`, then `--config`, then fallback to `config.toml` (if it
903 # exists).
904 toml_path = os.getenv('RUST_BOOTSTRAP_CONFIG') or args.config
905 if not toml_path and os.path.exists('config.toml'):
906 toml_path = 'config.toml'
907
908 if toml_path:
f9f354fc
XL
909 if not os.path.exists(toml_path):
910 toml_path = os.path.join(build.rust_root, toml_path)
911
912 with open(toml_path) as config:
3b2f2976 913 build.config_toml = config.read()
a7813a04 914
e1599b0c
XL
915 config_verbose = build.get_toml('verbose', 'build')
916 if config_verbose is not None:
917 build.verbose = max(build.verbose, int(config_verbose))
7cac9316 918
e1599b0c 919 build.use_vendored_sources = build.get_toml('vendor', 'build') == 'true'
476ff2be 920
e1599b0c 921 build.use_locked_deps = build.get_toml('locked-deps', 'build') == 'true'
8bb4bdeb 922
416331ca 923 build.check_vendored_status()
476ff2be 924
f9f354fc
XL
925 build_dir = build.get_toml('build-dir', 'build') or 'build'
926 build.build_dir = os.path.abspath(build_dir.replace("$ROOT", build.rust_root))
927
3b2f2976
XL
928 data = stage0_data(build.rust_root)
929 build.date = data['date']
930 build.rustc_channel = data['rustc']
931 build.cargo_channel = data['cargo']
932
dfeec247
XL
933 if "rustfmt" in data:
934 build.rustfmt_channel = data['rustfmt']
935
7cac9316 936 if 'dev' in data:
3b2f2976 937 build.set_dev_environment()
e1599b0c
XL
938 else:
939 build.set_normal_environment()
7cac9316 940
83c7162d 941 build.update_submodules()
a7813a04
XL
942
943 # Fetch/build the bootstrap
3b2f2976
XL
944 build.build = args.build or build.build_triple()
945 build.download_stage0()
a7813a04 946 sys.stdout.flush()
416331ca 947 build.ensure_vendored()
3b2f2976 948 build.build_bootstrap()
a7813a04
XL
949 sys.stdout.flush()
950
951 # Run the bootstrap
3b2f2976 952 args = [build.bootstrap_binary()]
a7813a04
XL
953 args.extend(sys.argv[1:])
954 env = os.environ.copy()
3b2f2976
XL
955 env["BUILD"] = build.build
956 env["SRC"] = build.rust_root
a7813a04 957 env["BOOTSTRAP_PARENT_ID"] = str(os.getpid())
041b39d2 958 env["BOOTSTRAP_PYTHON"] = sys.executable
83c7162d 959 env["BUILD_DIR"] = build.build_dir
8faf50e0
XL
960 env["RUSTC_BOOTSTRAP"] = '1'
961 env["CARGO"] = build.cargo()
962 env["RUSTC"] = build.rustc()
f035d41b
XL
963 if toml_path:
964 env["BOOTSTRAP_CONFIG"] = toml_path
dfeec247
XL
965 if build.rustfmt():
966 env["RUSTFMT"] = build.rustfmt()
3b2f2976 967 run(args, env=env, verbose=build.verbose)
7cac9316 968
a7813a04 969
8bb4bdeb 970def main():
3b2f2976 971 """Entry point for the bootstrap process"""
8bb4bdeb 972 start_time = time()
0bf4aa26
XL
973
974 # x.py help <cmd> ...
975 if len(sys.argv) > 1 and sys.argv[1] == 'help':
532ac7d7 976 sys.argv = [sys.argv[0], '-h'] + sys.argv[2:]
0bf4aa26 977
7cac9316
XL
978 help_triggered = (
979 '-h' in sys.argv) or ('--help' in sys.argv) or (len(sys.argv) == 1)
8bb4bdeb 980 try:
0531ce1d 981 bootstrap(help_triggered)
cc61c64b 982 if not help_triggered:
3b2f2976
XL
983 print("Build completed successfully in {}".format(
984 format_build_time(time() - start_time)))
985 except (SystemExit, KeyboardInterrupt) as error:
986 if hasattr(error, 'code') and isinstance(error.code, int):
987 exit_code = error.code
8bb4bdeb
XL
988 else:
989 exit_code = 1
3b2f2976 990 print(error)
cc61c64b 991 if not help_triggered:
3b2f2976
XL
992 print("Build completed unsuccessfully in {}".format(
993 format_build_time(time() - start_time)))
8bb4bdeb 994 sys.exit(exit_code)
3157f602 995
041b39d2 996
a7813a04
XL
997if __name__ == '__main__':
998 main()