]>
git.proxmox.com Git - rustc.git/blob - src/bootstrap/bootstrap.py
1 from __future__
import absolute_import
, division
, print_function
16 from multiprocessing
import Pool
, cpu_count
23 def platform_is_win32():
24 return sys
.platform
== 'win32'
26 if platform_is_win32():
32 if hasattr(os
, "sched_getaffinity"):
33 return len(os
.sched_getaffinity(0))
34 if hasattr(os
, "cpu_count"):
40 except NotImplementedError:
44 def eprint(*args
, **kwargs
):
45 kwargs
["file"] = sys
.stderr
46 print(*args
, **kwargs
)
49 def get(base
, url
, path
, checksums
, verbose
=False):
50 with tempfile
.NamedTemporaryFile(delete
=False) as temp_file
:
51 temp_path
= temp_file
.name
54 if url
not in checksums
:
55 raise RuntimeError(("src/stage0.json doesn't contain a checksum for {}. "
56 "Pre-built artifacts might not be available for this "
57 "target at this time, see https://doc.rust-lang.org/nightly"
58 "/rustc/platform-support.html for more information.")
60 sha256
= checksums
[url
]
61 if os
.path
.exists(path
):
62 if verify(path
, sha256
, False):
64 eprint("using already-download file", path
)
68 eprint("ignoring already-download file",
69 path
, "due to failed verification")
71 download(temp_path
, "{}/{}".format(base
, url
), True, verbose
)
72 if not verify(temp_path
, sha256
, verbose
):
73 raise RuntimeError("failed verification")
75 eprint("moving {} to {}".format(temp_path
, path
))
76 shutil
.move(temp_path
, path
)
78 if os
.path
.isfile(temp_path
):
80 eprint("removing", temp_path
)
84 def download(path
, url
, probably_big
, verbose
):
87 _download(path
, url
, probably_big
, verbose
, True)
90 eprint("\nspurious failure, trying again")
91 _download(path
, url
, probably_big
, verbose
, False)
94 def _download(path
, url
, probably_big
, verbose
, exception
):
95 # Try to use curl (potentially available on win32
96 # https://devblogs.microsoft.com/commandline/tar-and-curl-come-to-windows/)
98 # - If we are on win32 fallback to powershell
99 # - Otherwise raise the error if appropriate
100 if probably_big
or verbose
:
101 eprint("downloading {}".format(url
))
104 if (probably_big
or verbose
) and "GITHUB_ACTIONS" not in os
.environ
:
108 # If curl is not present on Win32, we should not sys.exit
109 # but raise `CalledProcessError` or `OSError` instead
110 require(["curl", "--version"], exception
=platform_is_win32())
112 "-L", # Follow redirect.
113 "-y", "30", "-Y", "10", # timeout if speed is < 10 bytes/sec for > 30 seconds
114 "--connect-timeout", "30", # timeout if cannot connect within 30 seconds
116 "--retry", "3", "-SRf", url
],
118 exception
=True, # Will raise RuntimeError on failure
120 except (subprocess
.CalledProcessError
, OSError, RuntimeError):
121 # see http://serverfault.com/questions/301128/how-to-download
122 if platform_is_win32():
124 "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12;",
125 "(New-Object System.Net.WebClient).DownloadFile('{}', '{}')".format(url
, path
)],
128 # Check if the RuntimeError raised by run(curl) should be silenced
129 elif verbose
or exception
:
133 def verify(path
, expected
, verbose
):
134 """Check if the sha256 sum of the given path is valid"""
136 eprint("verifying", path
)
137 with
open(path
, "rb") as source
:
138 found
= hashlib
.sha256(source
.read()).hexdigest()
139 verified
= found
== expected
141 eprint("invalid checksum:\n"
143 " expected: {}".format(found
, expected
))
147 def unpack(tarball
, tarball_suffix
, dst
, verbose
=False, match
=None):
148 """Unpack the given tarball file"""
149 eprint("extracting", tarball
)
150 fname
= os
.path
.basename(tarball
).replace(tarball_suffix
, "")
151 with contextlib
.closing(tarfile
.open(tarball
)) as tar
:
152 for member
in tar
.getnames():
153 if "/" not in member
:
155 name
= member
.replace(fname
+ "/", "", 1)
156 if match
is not None and not name
.startswith(match
):
158 name
= name
[len(match
) + 1:]
160 dst_path
= os
.path
.join(dst
, name
)
162 eprint(" extracting", member
)
163 tar
.extract(member
, dst
)
164 src_path
= os
.path
.join(dst
, member
)
165 if os
.path
.isdir(src_path
) and os
.path
.exists(dst_path
):
167 shutil
.move(src_path
, dst_path
)
168 shutil
.rmtree(os
.path
.join(dst
, fname
))
171 def run(args
, verbose
=False, exception
=False, is_bootstrap
=False, **kwargs
):
172 """Run a child program in a new process"""
174 eprint("running: " + ' '.join(args
))
176 # Ensure that the .exe is used on Windows just in case a Linux ELF has been
177 # compiled in the same directory.
178 if os
.name
== 'nt' and not args
[0].endswith('.exe'):
180 # Use Popen here instead of call() as it apparently allows powershell on
181 # Windows to not lock up waiting for input presumably.
182 ret
= subprocess
.Popen(args
, **kwargs
)
185 err
= "failed to run: " + ' '.join(args
)
186 if verbose
or exception
:
187 raise RuntimeError(err
)
188 # For most failures, we definitely do want to print this error, or the user will have no
189 # idea what went wrong. But when we've successfully built bootstrap and it failed, it will
190 # have already printed an error above, so there's no need to print the exact command we're
197 def run_powershell(script
, *args
, **kwargs
):
198 """Run a powershell script"""
199 run(["PowerShell.exe", "/nologo", "-Command"] + script
, *args
, **kwargs
)
202 def require(cmd
, exit
=True, exception
=False):
203 '''Run a command, returning its output.
205 If `exception` is `True`, raise the error
206 Otherwise If `exit` is `True`, exit the process
209 return subprocess
.check_output(cmd
).strip()
210 except (subprocess
.CalledProcessError
, OSError) as exc
:
214 eprint("ERROR: unable to run `{}`: {}".format(' '.join(cmd
), exc
))
215 eprint("Please make sure it's installed and in the path.")
221 def format_build_time(duration
):
222 """Return a nicer format for build time
224 >>> format_build_time('300')
227 return str(datetime
.timedelta(seconds
=int(duration
)))
230 def default_build_triple(verbose
):
231 """Build triple as in LLVM"""
232 # If we're on Windows and have an existing `rustc` toolchain, use `rustc --version --verbose`
233 # to find our host target triple. This fixes an issue with Windows builds being detected
234 # as GNU instead of MSVC.
235 # Otherwise, detect it via `uname`
236 default_encoding
= sys
.getdefaultencoding()
238 if platform_is_win32():
240 version
= subprocess
.check_output(["rustc", "--version", "--verbose"],
241 stderr
=subprocess
.DEVNULL
)
242 version
= version
.decode(default_encoding
)
243 host
= next(x
for x
in version
.split('\n') if x
.startswith("host: "))
244 triple
= host
.split("host: ")[1]
246 eprint("detected default triple {} from pre-installed rustc".format(triple
))
248 except Exception as e
:
250 eprint("pre-installed rustc not detected: {}".format(e
))
251 eprint("falling back to auto-detect")
253 required
= not platform_is_win32()
254 uname
= require(["uname", "-smp"], exit
=required
)
256 # If we do not have `uname`, assume Windows.
258 return 'x86_64-pc-windows-msvc'
260 kernel
, cputype
, processor
= uname
.decode(default_encoding
).split(maxsplit
=2)
262 # The goal here is to come up with the same triple as LLVM would,
263 # at least for the subset of platforms we're willing to target.
264 kerneltype_mapper
= {
265 'Darwin': 'apple-darwin',
266 'DragonFly': 'unknown-dragonfly',
267 'FreeBSD': 'unknown-freebsd',
268 'Haiku': 'unknown-haiku',
269 'NetBSD': 'unknown-netbsd',
270 'OpenBSD': 'unknown-openbsd',
271 'GNU': 'unknown-hurd',
274 # Consider the direct transformation first and then the special cases
275 if kernel
in kerneltype_mapper
:
276 kernel
= kerneltype_mapper
[kernel
]
277 elif kernel
== 'Linux':
278 # Apple doesn't support `-o` so this can't be used in the combined
279 # uname invocation above
280 ostype
= require(["uname", "-o"], exit
=required
).decode(default_encoding
)
281 if ostype
== 'Android':
282 kernel
= 'linux-android'
284 kernel
= 'unknown-linux-gnu'
285 elif kernel
== 'SunOS':
286 kernel
= 'pc-solaris'
287 # On Solaris, uname -m will return a machine classification instead
288 # of a cpu type, so uname -p is recommended instead. However, the
289 # output from that option is too generic for our purposes (it will
290 # always emit 'i386' on x86/amd64 systems). As such, isainfo -k
291 # must be used instead.
292 cputype
= require(['isainfo', '-k']).decode(default_encoding
)
293 # sparc cpus have sun as a target vendor
294 if 'sparc' in cputype
:
295 kernel
= 'sun-solaris'
296 elif kernel
.startswith('MINGW'):
297 # msys' `uname` does not print gcc configuration, but prints msys
298 # configuration. so we cannot believe `uname -m`:
299 # msys1 is always i686 and msys2 is always x86_64.
300 # instead, msys defines $MSYSTEM which is MINGW32 on i686 and
302 kernel
= 'pc-windows-gnu'
304 if os
.environ
.get('MSYSTEM') == 'MINGW64':
306 elif kernel
.startswith('MSYS'):
307 kernel
= 'pc-windows-gnu'
308 elif kernel
.startswith('CYGWIN_NT'):
310 if kernel
.endswith('WOW64'):
312 kernel
= 'pc-windows-gnu'
313 elif platform_is_win32():
314 # Some Windows platforms might have a `uname` command that returns a
315 # non-standard string (e.g. gnuwin32 tools returns `windows32`). In
316 # these cases, fall back to using sys.platform.
317 return 'x86_64-pc-windows-msvc'
318 elif kernel
== 'AIX':
319 # `uname -m` returns the machine ID rather than machine hardware on AIX,
320 # so we are unable to use cputype to form triple. AIX 7.2 and
321 # above supports 32-bit and 64-bit mode simultaneously and `uname -p`
322 # returns `powerpc`, however we only supports `powerpc64-ibm-aix` in
323 # rust on AIX. For above reasons, kerneltype_mapper and cputype_mapper
324 # are not used to infer AIX's triple.
325 return 'powerpc64-ibm-aix'
327 err
= "unknown OS type: {}".format(kernel
)
330 if cputype
in ['powerpc', 'riscv'] and kernel
== 'unknown-freebsd':
331 cputype
= subprocess
.check_output(
332 ['uname', '-p']).strip().decode(default_encoding
)
335 'aarch64': 'aarch64',
336 'aarch64eb': 'aarch64',
342 'i686-AT386': 'i686',
344 'loongarch64': 'loongarch64',
347 'powerpc': 'powerpc',
348 'powerpc64': 'powerpc64',
349 'powerpc64le': 'powerpc64le',
351 'ppc64': 'powerpc64',
352 'ppc64le': 'powerpc64le',
353 'riscv64': 'riscv64gc',
361 # Consider the direct transformation first and then the special cases
362 if cputype
in cputype_mapper
:
363 cputype
= cputype_mapper
[cputype
]
364 elif cputype
in {'xscale', 'arm'}:
366 if kernel
== 'linux-android':
367 kernel
= 'linux-androideabi'
368 elif kernel
== 'unknown-freebsd':
370 kernel
= 'unknown-freebsd'
371 elif cputype
== 'armv6l':
373 if kernel
== 'linux-android':
374 kernel
= 'linux-androideabi'
377 elif cputype
in {'armv7l', 'armv8l'}:
379 if kernel
== 'linux-android':
380 kernel
= 'linux-androideabi'
383 elif cputype
== 'mips':
384 if sys
.byteorder
== 'big':
386 elif sys
.byteorder
== 'little':
389 raise ValueError("unknown byteorder: {}".format(sys
.byteorder
))
390 elif cputype
== 'mips64':
391 if sys
.byteorder
== 'big':
393 elif sys
.byteorder
== 'little':
396 raise ValueError('unknown byteorder: {}'.format(sys
.byteorder
))
397 # only the n64 ABI is supported, indicate it
399 elif cputype
== 'sparc' or cputype
== 'sparcv9' or cputype
== 'sparc64':
402 err
= "unknown cpu type: {}".format(cputype
)
405 return "{}-{}".format(cputype
, kernel
)
408 @contextlib.contextmanager
409 def output(filepath
):
410 tmp
= filepath
+ '.tmp'
411 with
open(tmp
, 'w') as f
:
414 if os
.path
.exists(filepath
):
415 os
.remove(filepath
) # PermissionError/OSError on Win32 if in use
417 shutil
.copy2(tmp
, filepath
)
420 os
.rename(tmp
, filepath
)
423 class Stage0Toolchain
:
424 def __init__(self
, stage0_payload
):
425 self
.date
= stage0_payload
["date"]
426 self
.version
= stage0_payload
["version"]
429 return self
.version
+ "-" + self
.date
433 """A helper class that can be pickled into a parallel subprocess"""
446 self
.base_download_url
= base_download_url
447 self
.download_path
= download_path
448 self
.bin_root
= bin_root
449 self
.tarball_path
= tarball_path
450 self
.tarball_suffix
= tarball_suffix
451 self
.checksums_sha256
= checksums_sha256
452 self
.pattern
= pattern
453 self
.verbose
= verbose
455 def download_component(download_info
):
456 if not os
.path
.exists(download_info
.tarball_path
):
458 download_info
.base_download_url
,
459 download_info
.download_path
,
460 download_info
.tarball_path
,
461 download_info
.checksums_sha256
,
462 verbose
=download_info
.verbose
,
465 def unpack_component(download_info
):
467 download_info
.tarball_path
,
468 download_info
.tarball_suffix
,
469 download_info
.bin_root
,
470 match
=download_info
.pattern
,
471 verbose
=download_info
.verbose
,
475 """Used for unit tests to avoid updating all call sites"""
481 self
.json_output
= False
483 self
.warnings
= 'default'
485 class RustBuild(object):
486 """Provide all the methods required to build Rust"""
487 def __init__(self
, config_toml
="", args
=None):
490 self
.git_version
= None
491 self
.nix_deps_dir
= None
492 self
._should
_fix
_bins
_and
_dylibs
= None
493 self
.rust_root
= os
.path
.abspath(os
.path
.join(__file__
, '../../..'))
495 self
.config_toml
= config_toml
497 self
.clean
= args
.clean
498 self
.json_output
= args
.json_output
499 self
.verbose
= args
.verbose
500 self
.color
= args
.color
501 self
.warnings
= args
.warnings
503 config_verbose_count
= self
.get_toml('verbose', 'build')
504 if config_verbose_count
is not None:
505 self
.verbose
= max(self
.verbose
, int(config_verbose_count
))
507 self
.use_vendored_sources
= self
.get_toml('vendor', 'build') == 'true'
508 self
.use_locked_deps
= self
.get_toml('locked-deps', 'build') == 'true'
510 build_dir
= args
.build_dir
or self
.get_toml('build-dir', 'build') or 'build'
511 self
.build_dir
= os
.path
.abspath(build_dir
)
513 with
open(os
.path
.join(self
.rust_root
, "src", "stage0.json")) as f
:
515 self
.checksums_sha256
= data
["checksums_sha256"]
516 self
.stage0_compiler
= Stage0Toolchain(data
["compiler"])
517 self
.download_url
= os
.getenv("RUSTUP_DIST_SERVER") or data
["config"]["dist_server"]
519 self
.build
= args
.build
or self
.build_triple()
522 def download_toolchain(self
):
523 """Fetch the build system for Rust, written in Rust
525 This method will build a cache directory, then it will fetch the
526 tarball which has the stage0 compiler used to then bootstrap the Rust
529 Each downloaded tarball is extracted, after that, the script
530 will move all the content to the right place.
532 rustc_channel
= self
.stage0_compiler
.version
533 bin_root
= self
.bin_root()
535 key
= self
.stage0_compiler
.date
536 if self
.rustc().startswith(bin_root
) and \
537 (not os
.path
.exists(self
.rustc()) or
538 self
.program_out_of_date(self
.rustc_stamp(), key
)):
539 if os
.path
.exists(bin_root
):
540 # HACK: On Windows, we can't delete rust-analyzer-proc-macro-server while it's
542 if platform_is_win32():
543 print("Killing rust-analyzer-proc-macro-srv before deleting stage0 toolchain")
544 regex
= '{}\\\\(host|{})\\\\stage0\\\\libexec'.format(
545 os
.path
.basename(self
.build_dir
),
549 # NOTE: can't use `taskkill` or `Get-Process -Name` because they error if
550 # the server isn't running.
552 'Where-Object {$_.Name -eq "rust-analyzer-proc-macro-srv"} |' +
553 'Where-Object {{$_.Path -match "{}"}} |'.format(regex
) +
556 run_powershell([script
])
557 shutil
.rmtree(bin_root
)
559 key
= self
.stage0_compiler
.date
560 cache_dst
= os
.path
.join(self
.build_dir
, "cache")
561 rustc_cache
= os
.path
.join(cache_dst
, key
)
562 if not os
.path
.exists(rustc_cache
):
563 os
.makedirs(rustc_cache
)
565 tarball_suffix
= '.tar.gz' if lzma
is None else '.tar.xz'
567 toolchain_suffix
= "{}-{}{}".format(rustc_channel
, self
.build
, tarball_suffix
)
569 tarballs_to_download
= [
570 ("rust-std-{}".format(toolchain_suffix
), "rust-std-{}".format(self
.build
)),
571 ("rustc-{}".format(toolchain_suffix
), "rustc"),
572 ("cargo-{}".format(toolchain_suffix
), "cargo"),
575 tarballs_download_info
= [
577 base_download_url
=self
.download_url
,
578 download_path
="dist/{}/{}".format(self
.stage0_compiler
.date
, filename
),
579 bin_root
=self
.bin_root(),
580 tarball_path
=os
.path
.join(rustc_cache
, filename
),
581 tarball_suffix
=tarball_suffix
,
582 checksums_sha256
=self
.checksums_sha256
,
584 verbose
=self
.verbose
,
586 for filename
, pattern
in tarballs_to_download
589 # Download the components serially to show the progress bars properly.
590 for download_info
in tarballs_download_info
:
591 download_component(download_info
)
593 # Unpack the tarballs in parallle.
594 # In Python 2.7, Pool cannot be used as a context manager.
595 pool_size
= min(len(tarballs_download_info
), get_cpus())
597 print('Choosing a pool size of', pool_size
, 'for the unpacking of the tarballs')
600 p
.map(unpack_component
, tarballs_download_info
)
605 if self
.should_fix_bins_and_dylibs():
606 self
.fix_bin_or_dylib("{}/bin/cargo".format(bin_root
))
608 self
.fix_bin_or_dylib("{}/bin/rustc".format(bin_root
))
609 self
.fix_bin_or_dylib("{}/bin/rustdoc".format(bin_root
))
610 self
.fix_bin_or_dylib("{}/libexec/rust-analyzer-proc-macro-srv".format(bin_root
))
611 lib_dir
= "{}/lib".format(bin_root
)
612 for lib
in os
.listdir(lib_dir
):
613 if lib
.endswith(".so"):
614 self
.fix_bin_or_dylib(os
.path
.join(lib_dir
, lib
))
616 with
output(self
.rustc_stamp()) as rust_stamp
:
617 rust_stamp
.write(key
)
619 def should_fix_bins_and_dylibs(self
):
620 """Whether or not `fix_bin_or_dylib` needs to be run; can only be True
621 on NixOS or if config.toml has `build.patch-binaries-for-nix` set.
623 if self
._should
_fix
_bins
_and
_dylibs
is not None:
624 return self
._should
_fix
_bins
_and
_dylibs
627 default_encoding
= sys
.getdefaultencoding()
629 ostype
= subprocess
.check_output(
630 ['uname', '-s']).strip().decode(default_encoding
)
631 except subprocess
.CalledProcessError
:
633 except OSError as reason
:
634 if getattr(reason
, 'winerror', None) is not None:
638 if ostype
!= "Linux":
641 # If the user has explicitly indicated whether binaries should be
642 # patched for Nix, then don't check for NixOS.
643 if self
.get_toml("patch-binaries-for-nix", "build") == "true":
645 if self
.get_toml("patch-binaries-for-nix", "build") == "false":
648 # Use `/etc/os-release` instead of `/etc/NIXOS`.
649 # The latter one does not exist on NixOS when using tmpfs as root.
651 with
open("/etc/os-release", "r") as f
:
652 is_nixos
= any(ln
.strip() in ("ID=nixos", "ID='nixos'", 'ID="nixos"')
654 except FileNotFoundError
:
657 # If not on NixOS, then warn if user seems to be atop Nix shell
659 in_nix_shell
= os
.getenv('IN_NIX_SHELL')
661 eprint("The IN_NIX_SHELL environment variable is `{}`;".format(in_nix_shell
),
662 "you may need to set `patch-binaries-for-nix=true` in config.toml")
666 answer
= self
._should
_fix
_bins
_and
_dylibs
= get_answer()
668 eprint("INFO: You seem to be using Nix.")
671 def fix_bin_or_dylib(self
, fname
):
672 """Modifies the interpreter section of 'fname' to fix the dynamic linker,
673 or the RPATH section, to fix the dynamic library search path
675 This method is only required on NixOS and uses the PatchELF utility to
676 change the interpreter/RPATH of ELF executables.
678 Please see https://nixos.org/patchelf.html for more information
680 assert self
._should
_fix
_bins
_and
_dylibs
is True
681 eprint("attempting to patch", fname
)
683 # Only build `.nix-deps` once.
684 nix_deps_dir
= self
.nix_deps_dir
686 # Run `nix-build` to "build" each dependency (which will likely reuse
687 # the existing `/nix/store` copy, or at most download a pre-built copy).
689 # Importantly, we create a gc-root called `.nix-deps` in the `build/`
690 # directory, but still reference the actual `/nix/store` path in the rpath
691 # as it makes it significantly more robust against changes to the location of
692 # the `.nix-deps` location.
694 # bintools: Needed for the path of `ld-linux.so` (via `nix-support/dynamic-linker`).
695 # zlib: Needed as a system dependency of `libLLVM-*.so`.
696 # patchelf: Needed for patching ELF binaries (see doc comment above).
697 nix_deps_dir
= "{}/{}".format(self
.build_dir
, ".nix-deps")
699 with (import <nixpkgs> {});
701 name = "rust-stage0-dependencies";
710 subprocess
.check_output([
711 "nix-build", "-E", nix_expr
, "-o", nix_deps_dir
,
713 except subprocess
.CalledProcessError
as reason
:
714 eprint("WARNING: failed to call nix-build:", reason
)
716 self
.nix_deps_dir
= nix_deps_dir
718 patchelf
= "{}/bin/patchelf".format(nix_deps_dir
)
720 # Relative default, all binary and dynamic libraries we ship
721 # appear to have this (even when `../lib` is redundant).
723 os
.path
.join(os
.path
.realpath(nix_deps_dir
), "lib")
725 patchelf_args
= ["--set-rpath", ":".join(rpath_entries
)]
726 if not fname
.endswith(".so"):
727 # Finally, set the correct .interp for binaries
728 with
open("{}/nix-support/dynamic-linker".format(nix_deps_dir
)) as dynamic_linker
:
729 patchelf_args
+= ["--set-interpreter", dynamic_linker
.read().rstrip()]
732 subprocess
.check_output([patchelf
] + patchelf_args
+ [fname
])
733 except subprocess
.CalledProcessError
as reason
:
734 eprint("WARNING: failed to call patchelf:", reason
)
737 def rustc_stamp(self
):
738 """Return the path for .rustc-stamp at the given stage
741 >>> rb.build = "host"
742 >>> rb.build_dir = "build"
743 >>> expected = os.path.join("build", "host", "stage0", ".rustc-stamp")
744 >>> assert rb.rustc_stamp() == expected, rb.rustc_stamp()
746 return os
.path
.join(self
.bin_root(), '.rustc-stamp')
748 def program_out_of_date(self
, stamp_path
, key
):
749 """Check if the given program stamp is out of date"""
750 if not os
.path
.exists(stamp_path
) or self
.clean
:
752 with
open(stamp_path
, 'r') as stamp
:
753 return key
!= stamp
.read()
756 """Return the binary root directory for the given stage
759 >>> rb.build = "devel"
760 >>> expected = os.path.abspath(os.path.join("build", "devel", "stage0"))
761 >>> assert rb.bin_root() == expected, rb.bin_root()
764 return os
.path
.join(self
.build_dir
, self
.build
, subdir
)
766 def get_toml(self
, key
, section
=None):
767 """Returns the value of the given key in config.toml, otherwise returns None
770 >>> rb.config_toml = 'key1 = "value1"\\nkey2 = "value2"'
771 >>> rb.get_toml("key2")
774 If the key does not exist, the result is None:
776 >>> rb.get_toml("key3") is None
779 Optionally also matches the section the key appears in
781 >>> rb.config_toml = '[a]\\nkey = "value1"\\n[b]\\nkey = "value2"'
782 >>> rb.get_toml('key', 'a')
784 >>> rb.get_toml('key', 'b')
786 >>> rb.get_toml('key', 'c') is None
789 >>> rb.config_toml = 'key1 = true'
790 >>> rb.get_toml("key1")
793 return RustBuild
.get_toml_static(self
.config_toml
, key
, section
)
796 def get_toml_static(config_toml
, key
, section
=None):
798 for line
in config_toml
.splitlines():
799 section_match
= re
.match(r
'^\s*\[(.*)\]\s*$', line
)
800 if section_match
is not None:
801 cur_section
= section_match
.group(1)
803 match
= re
.match(r
'^{}\s*=(.*)$'.format(key
), line
)
804 if match
is not None:
805 value
= match
.group(1)
806 if section
is None or section
== cur_section
:
807 return RustBuild
.get_string(value
) or value
.strip()
811 """Return config path for cargo"""
812 return self
.program_config('cargo')
815 """Return config path for rustc"""
816 return self
.program_config('rustc')
818 def program_config(self
, program
):
819 """Return config path for the given program at the given stage
822 >>> rb.config_toml = 'rustc = "rustc"\\n'
823 >>> rb.program_config('rustc')
825 >>> rb.config_toml = ''
826 >>> cargo_path = rb.program_config('cargo')
827 >>> cargo_path.rstrip(".exe") == os.path.join(rb.bin_root(),
831 config
= self
.get_toml(program
)
833 return os
.path
.expanduser(config
)
834 return os
.path
.join(self
.bin_root(), "bin", "{}{}".format(program
, EXE_SUFFIX
))
837 def get_string(line
):
838 """Return the value between double quotes
840 >>> RustBuild.get_string(' "devel" ')
842 >>> RustBuild.get_string(" 'devel' ")
844 >>> RustBuild.get_string('devel') is None
846 >>> RustBuild.get_string(' "devel ')
849 start
= line
.find('"')
851 end
= start
+ 1 + line
[start
+ 1:].find('"')
852 return line
[start
+ 1:end
]
853 start
= line
.find('\'')
855 end
= start
+ 1 + line
[start
+ 1:].find('\'')
856 return line
[start
+ 1:end
]
859 def bootstrap_binary(self
):
860 """Return the path of the bootstrap binary
863 >>> rb.build_dir = "build"
864 >>> rb.bootstrap_binary() == os.path.join("build", "bootstrap",
865 ... "debug", "bootstrap")
868 return os
.path
.join(self
.build_dir
, "bootstrap", "debug", "bootstrap")
870 def build_bootstrap(self
):
871 """Build bootstrap"""
872 env
= os
.environ
.copy()
873 if "GITHUB_ACTIONS" in env
:
874 print("::group::Building bootstrap")
876 eprint("Building bootstrap")
878 args
= self
.build_bootstrap_cmd(env
)
879 # Run this from the source directory so cargo finds .cargo/config
880 run(args
, env
=env
, verbose
=self
.verbose
, cwd
=self
.rust_root
)
882 if "GITHUB_ACTIONS" in env
:
883 print("::endgroup::")
885 def build_bootstrap_cmd(self
, env
):
887 build_dir
= os
.path
.join(self
.build_dir
, "bootstrap")
888 if self
.clean
and os
.path
.exists(build_dir
):
889 shutil
.rmtree(build_dir
)
890 # `CARGO_BUILD_TARGET` breaks bootstrap build.
891 # See also: <https://github.com/rust-lang/rust/issues/70208>.
892 if "CARGO_BUILD_TARGET" in env
:
893 del env
["CARGO_BUILD_TARGET"]
894 env
["CARGO_TARGET_DIR"] = build_dir
895 env
["RUSTC"] = self
.rustc()
896 env
["LD_LIBRARY_PATH"] = os
.path
.join(self
.bin_root(), "lib") + \
897 (os
.pathsep
+ env
["LD_LIBRARY_PATH"]) \
898 if "LD_LIBRARY_PATH" in env
else ""
899 env
["DYLD_LIBRARY_PATH"] = os
.path
.join(self
.bin_root(), "lib") + \
900 (os
.pathsep
+ env
["DYLD_LIBRARY_PATH"]) \
901 if "DYLD_LIBRARY_PATH" in env
else ""
902 env
["LIBRARY_PATH"] = os
.path
.join(self
.bin_root(), "lib") + \
903 (os
.pathsep
+ env
["LIBRARY_PATH"]) \
904 if "LIBRARY_PATH" in env
else ""
905 env
["LIBPATH"] = os
.path
.join(self
.bin_root(), "lib") + \
906 (os
.pathsep
+ env
["LIBPATH"]) \
907 if "LIBPATH" in env
else ""
909 # Export Stage0 snapshot compiler related env variables
910 build_section
= "target.{}".format(self
.build
)
911 host_triple_sanitized
= self
.build
.replace("-", "_")
913 "CC": "cc", "CXX": "cxx", "LD": "linker", "AR": "ar", "RANLIB": "ranlib"
915 for var_name
, toml_key
in var_data
.items():
916 toml_val
= self
.get_toml(toml_key
, build_section
)
917 if toml_val
is not None:
918 env
["{}_{}".format(var_name
, host_triple_sanitized
)] = toml_val
920 # preserve existing RUSTFLAGS
921 env
.setdefault("RUSTFLAGS", "")
924 if self
.get_toml("crt-static", build_section
) == "true":
925 target_features
+= ["+crt-static"]
926 elif self
.get_toml("crt-static", build_section
) == "false":
927 target_features
+= ["-crt-static"]
929 env
["RUSTFLAGS"] += " -C target-feature=" + (",".join(target_features
))
930 target_linker
= self
.get_toml("linker", build_section
)
931 if target_linker
is not None:
932 env
["RUSTFLAGS"] += " -C linker=" + target_linker
933 # When changing this list, also update the corresponding list in `Builder::cargo`
934 # in `src/bootstrap/src/core/builder.rs`.
935 env
["RUSTFLAGS"] += " -Wrust_2018_idioms -Wunused_lifetimes"
936 if self
.warnings
== "default":
937 deny_warnings
= self
.get_toml("deny-warnings", "rust") != "false"
939 deny_warnings
= self
.warnings
== "deny"
941 env
["RUSTFLAGS"] += " -Dwarnings"
943 # Add RUSTFLAGS_BOOTSTRAP to RUSTFLAGS for bootstrap compilation.
944 # Note that RUSTFLAGS_BOOTSTRAP should always be added to the end of
945 # RUSTFLAGS to be actually effective (e.g., if we have `-Dwarnings` in
946 # RUSTFLAGS, passing `-Awarnings` from RUSTFLAGS_BOOTSTRAP should override it).
947 if "RUSTFLAGS_BOOTSTRAP" in env
:
948 env
["RUSTFLAGS"] += " " + env
["RUSTFLAGS_BOOTSTRAP"]
950 env
["PATH"] = os
.path
.join(self
.bin_root(), "bin") + \
951 os
.pathsep
+ env
["PATH"]
952 if not os
.path
.isfile(self
.cargo()):
953 raise Exception("no cargo executable found at `{}`".format(
955 args
= [self
.cargo(), "build", "--manifest-path",
956 os
.path
.join(self
.rust_root
, "src/bootstrap/Cargo.toml")]
957 args
.extend("--verbose" for _
in range(self
.verbose
))
958 if self
.use_locked_deps
:
959 args
.append("--locked")
960 if self
.use_vendored_sources
:
961 args
.append("--frozen")
962 if self
.get_toml("metrics", "build"):
963 args
.append("--features")
964 args
.append("build-metrics")
966 args
.append("--message-format=json")
967 if self
.color
== "always":
968 args
.append("--color=always")
969 elif self
.color
== "never":
970 args
.append("--color=never")
972 args
+= env
["CARGOFLAGS"].split()
978 def build_triple(self
):
979 """Build triple as in LLVM
981 Note that `default_build_triple` is moderately expensive,
982 so use `self.build` where possible.
984 config
= self
.get_toml('build')
985 return config
or default_build_triple(self
.verbose
)
987 def check_vendored_status(self
):
988 """Check that vendoring is configured properly"""
989 # keep this consistent with the equivalent check in rustbuild:
990 # https://github.com/rust-lang/rust/blob/a8a33cf27166d3eabaffc58ed3799e054af3b0c6/src/bootstrap/lib.rs#L399-L405
991 if 'SUDO_USER' in os
.environ
and not self
.use_vendored_sources
:
993 self
.use_vendored_sources
= True
994 eprint('INFO: looks like you\'re trying to run this command as root')
995 eprint(' and so in order to preserve your $HOME this will now')
996 eprint(' use vendored sources by default.')
998 cargo_dir
= os
.path
.join(self
.rust_root
, '.cargo')
999 if self
.use_vendored_sources
:
1000 vendor_dir
= os
.path
.join(self
.rust_root
, 'vendor')
1001 if not os
.path
.exists(vendor_dir
):
1002 sync_dirs
= "--sync ./src/tools/cargo/Cargo.toml " \
1003 "--sync ./src/tools/rust-analyzer/Cargo.toml " \
1004 "--sync ./compiler/rustc_codegen_cranelift/Cargo.toml " \
1005 "--sync ./src/bootstrap/Cargo.toml "
1006 eprint('ERROR: vendoring required, but vendor directory does not exist.')
1007 eprint(' Run `cargo vendor {}` to initialize the '
1008 'vendor directory.'.format(sync_dirs
))
1009 eprint('Alternatively, use the pre-vendored `rustc-src` dist component.')
1010 raise Exception("{} not found".format(vendor_dir
))
1012 if not os
.path
.exists(cargo_dir
):
1013 eprint('ERROR: vendoring required, but .cargo/config does not exist.')
1014 raise Exception("{} not found".format(cargo_dir
))
1016 if os
.path
.exists(cargo_dir
):
1017 shutil
.rmtree(cargo_dir
)
1019 def parse_args(args
):
1020 """Parse the command line arguments that the python script needs."""
1021 parser
= argparse
.ArgumentParser(add_help
=False)
1022 parser
.add_argument('-h', '--help', action
='store_true')
1023 parser
.add_argument('--config')
1024 parser
.add_argument('--build-dir')
1025 parser
.add_argument('--build')
1026 parser
.add_argument('--color', choices
=['always', 'never', 'auto'])
1027 parser
.add_argument('--clean', action
='store_true')
1028 parser
.add_argument('--json-output', action
='store_true')
1029 parser
.add_argument('--warnings', choices
=['deny', 'warn', 'default'], default
='default')
1030 parser
.add_argument('-v', '--verbose', action
='count', default
=0)
1032 return parser
.parse_known_args(args
)[0]
1034 def bootstrap(args
):
1035 """Configure, fetch, build and run the initial bootstrap"""
1036 rust_root
= os
.path
.abspath(os
.path
.join(__file__
, '../../..'))
1038 if not os
.path
.exists(os
.path
.join(rust_root
, '.git')) and \
1039 os
.path
.exists(os
.path
.join(rust_root
, '.github')):
1040 eprint("warn: Looks like you are trying to bootstrap Rust from a source that is neither a "
1041 "git clone nor distributed tarball.\nThis build may fail due to missing submodules "
1042 "unless you put them in place manually.")
1044 # Read from `--config`, then `RUST_BOOTSTRAP_CONFIG`, then `./config.toml`,
1045 # then `config.toml` in the root directory.
1046 toml_path
= args
.config
or os
.getenv('RUST_BOOTSTRAP_CONFIG')
1047 using_default_path
= toml_path
is None
1048 if using_default_path
:
1049 toml_path
= 'config.toml'
1050 if not os
.path
.exists(toml_path
):
1051 toml_path
= os
.path
.join(rust_root
, toml_path
)
1053 # Give a hard error if `--config` or `RUST_BOOTSTRAP_CONFIG` are set to a missing path,
1054 # but not if `config.toml` hasn't been created.
1055 if not using_default_path
or os
.path
.exists(toml_path
):
1056 with
open(toml_path
) as config
:
1057 config_toml
= config
.read()
1061 profile
= RustBuild
.get_toml_static(config_toml
, 'profile')
1062 if profile
is not None:
1063 # Allows creating alias for profile names, allowing
1064 # profiles to be renamed while maintaining back compatibility
1065 # Keep in sync with `profile_aliases` in config.rs
1069 include_file
= 'config.{}.toml'.format(profile_aliases
.get(profile
) or profile
)
1070 include_dir
= os
.path
.join(rust_root
, 'src', 'bootstrap', 'defaults')
1071 include_path
= os
.path
.join(include_dir
, include_file
)
1073 if not os
.path
.exists(include_path
):
1074 raise Exception("Unrecognized config profile '{}'. Check src/bootstrap/defaults"
1075 " for available options.".format(profile
))
1077 # HACK: This works because `self.get_toml()` returns the first match it finds for a
1078 # specific key, so appending our defaults at the end allows the user to override them
1079 with
open(include_path
) as included_toml
:
1080 config_toml
+= os
.linesep
+ included_toml
.read()
1082 # Configure initial bootstrap
1083 build
= RustBuild(config_toml
, args
)
1084 build
.check_vendored_status()
1086 if not os
.path
.exists(build
.build_dir
):
1087 os
.makedirs(build
.build_dir
)
1089 # Fetch/build the bootstrap
1090 build
.download_toolchain()
1092 build
.build_bootstrap()
1096 args
= [build
.bootstrap_binary()]
1097 args
.extend(sys
.argv
[1:])
1098 env
= os
.environ
.copy()
1099 env
["BOOTSTRAP_PARENT_ID"] = str(os
.getpid())
1100 env
["BOOTSTRAP_PYTHON"] = sys
.executable
1101 run(args
, env
=env
, verbose
=build
.verbose
, is_bootstrap
=True)
1105 """Entry point for the bootstrap process"""
1108 # x.py help <cmd> ...
1109 if len(sys
.argv
) > 1 and sys
.argv
[1] == 'help':
1112 args
= parse_args(sys
.argv
)
1113 help_triggered
= args
.help or len(sys
.argv
) == 1
1115 # If the user is asking for help, let them know that the whole download-and-build
1116 # process has to happen before anything is printed out.
1119 "INFO: Downloading and building bootstrap before processing --help command.\n"
1120 " See src/bootstrap/README.md for help with common commands.")
1123 success_word
= "successfully"
1126 except (SystemExit, KeyboardInterrupt) as error
:
1127 if hasattr(error
, 'code') and isinstance(error
.code
, int):
1128 exit_code
= error
.code
1132 success_word
= "unsuccessfully"
1134 if not help_triggered
:
1135 eprint("Build completed", success_word
, "in", format_build_time(time() - start_time
))
1139 if __name__
== '__main__':