Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Small bootstrap improvements #107470

Merged
merged 3 commits into from
Feb 1, 2023
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
203 changes: 94 additions & 109 deletions src/bootstrap/bootstrap.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
import argparse
import contextlib
import datetime
import distutils.version
import hashlib
import json
import os
Expand All @@ -13,17 +12,17 @@
import tarfile
import tempfile

from time import time, sleep
from time import time

def support_xz():
try:
with tempfile.NamedTemporaryFile(delete=False) as temp_file:
temp_path = temp_file.name
with tarfile.open(temp_path, "w:xz"):
pass
return True
except tarfile.CompressionError:
return False
try:
import lzma
except ImportError:
lzma = None

if sys.platform == 'win32':
EXE_SUFFIX = ".exe"
else:
EXE_SUFFIX = ""

def get(base, url, path, checksums, verbose=False):
with tempfile.NamedTemporaryFile(delete=False) as temp_file:
Expand Down Expand Up @@ -61,7 +60,7 @@ def get(base, url, path, checksums, verbose=False):


def download(path, url, probably_big, verbose):
for _ in range(0, 4):
for _ in range(4):
try:
_download(path, url, probably_big, verbose, True)
return
Expand Down Expand Up @@ -395,15 +394,15 @@ class RustBuild(object):
def __init__(self):
self.checksums_sha256 = {}
self.stage0_compiler = None
self._download_url = ''
self.download_url = ''
self.build = ''
self.build_dir = ''
self.clean = False
self.config_toml = ''
self.rust_root = ''
self.use_locked_deps = ''
self.use_vendored_sources = ''
self.verbose = False
self.use_locked_deps = False
self.use_vendored_sources = False
self.verbose = 0
kadiwa4 marked this conversation as resolved.
Show resolved Hide resolved
self.git_version = None
self.nix_deps_dir = None

Expand All @@ -426,7 +425,7 @@ def download_toolchain(self):
self.program_out_of_date(self.rustc_stamp(), key)):
if os.path.exists(bin_root):
shutil.rmtree(bin_root)
tarball_suffix = '.tar.xz' if support_xz() else '.tar.gz'
tarball_suffix = '.tar.gz' if lzma is None else '.tar.xz'
filename = "rust-std-{}-{}{}".format(
rustc_channel, self.build, tarball_suffix)
pattern = "rust-std-{}".format(self.build)
Expand All @@ -437,15 +436,17 @@ def download_toolchain(self):
filename = "cargo-{}-{}{}".format(rustc_channel, self.build,
tarball_suffix)
self._download_component_helper(filename, "cargo", tarball_suffix)
self.fix_bin_or_dylib("{}/bin/cargo".format(bin_root))

self.fix_bin_or_dylib("{}/bin/rustc".format(bin_root))
self.fix_bin_or_dylib("{}/bin/rustdoc".format(bin_root))
self.fix_bin_or_dylib("{}/libexec/rust-analyzer-proc-macro-srv".format(bin_root))
lib_dir = "{}/lib".format(bin_root)
for lib in os.listdir(lib_dir):
if lib.endswith(".so"):
self.fix_bin_or_dylib(os.path.join(lib_dir, lib))
if self.should_fix_bins_and_dylibs():
self.fix_bin_or_dylib("{}/bin/cargo".format(bin_root))

self.fix_bin_or_dylib("{}/bin/rustc".format(bin_root))
self.fix_bin_or_dylib("{}/bin/rustdoc".format(bin_root))
self.fix_bin_or_dylib("{}/libexec/rust-analyzer-proc-macro-srv".format(bin_root))
lib_dir = "{}/lib".format(bin_root)
for lib in os.listdir(lib_dir):
if lib.endswith(".so"):
self.fix_bin_or_dylib(os.path.join(lib_dir, lib))

with output(self.rustc_stamp()) as rust_stamp:
rust_stamp.write(key)

Expand All @@ -458,60 +459,64 @@ def _download_component_helper(
if not os.path.exists(rustc_cache):
os.makedirs(rustc_cache)

base = self._download_url
url = "dist/{}".format(key)
tarball = os.path.join(rustc_cache, filename)
if not os.path.exists(tarball):
get(
base,
"{}/{}".format(url, filename),
self.download_url,
"dist/{}/{}".format(key, filename),
tarball,
self.checksums_sha256,
verbose=self.verbose,
verbose=self.verbose != 0,
)
unpack(tarball, tarball_suffix, self.bin_root(), match=pattern, verbose=self.verbose)

def fix_bin_or_dylib(self, fname):
"""Modifies the interpreter section of 'fname' to fix the dynamic linker,
or the RPATH section, to fix the dynamic library search path
unpack(tarball, tarball_suffix, self.bin_root(), match=pattern, verbose=self.verbose != 0)

This method is only required on NixOS and uses the PatchELF utility to
change the interpreter/RPATH of ELF executables.

Please see https://nixos.org/patchelf.html for more information
def should_fix_bins_and_dylibs(self):
"""Whether or not `fix_bin_or_dylib` needs to be run; can only be True
on NixOS.
"""
default_encoding = sys.getdefaultencoding()
try:
ostype = subprocess.check_output(
['uname', '-s']).strip().decode(default_encoding)
except subprocess.CalledProcessError:
return
return False
except OSError as reason:
if getattr(reason, 'winerror', None) is not None:
return
return False
raise reason

if ostype != "Linux":
return
return False

# If the user has asked binaries to be patched for Nix, then
# don't check for NixOS or `/lib`, just continue to the patching.
if self.get_toml('patch-binaries-for-nix', 'build') != 'true':
# Use `/etc/os-release` instead of `/etc/NIXOS`.
# The latter one does not exist on NixOS when using tmpfs as root.
try:
with open("/etc/os-release", "r") as f:
if not any(l.strip() in ["ID=nixos", "ID='nixos'", 'ID="nixos"'] for l in f):
return
except FileNotFoundError:
return
if os.path.exists("/lib"):
return
# don't check for NixOS or `/lib`.
if self.get_toml("patch-binaries-for-nix", "build") == "true":
return True

# At this point we're pretty sure the user is running NixOS or
# using Nix
nix_os_msg = "info: you seem to be using Nix. Attempting to patch"
print(nix_os_msg, fname)
# Use `/etc/os-release` instead of `/etc/NIXOS`.
# The latter one does not exist on NixOS when using tmpfs as root.
try:
with open("/etc/os-release", "r") as f:
if not any(l.strip() in ("ID=nixos", "ID='nixos'", 'ID="nixos"') for l in f):
return False
except FileNotFoundError:
return False
if os.path.exists("/lib"):
return False

print("info: You seem to be using Nix.")
return True

def fix_bin_or_dylib(self, fname):
"""Modifies the interpreter section of 'fname' to fix the dynamic linker,
or the RPATH section, to fix the dynamic library search path

This method is only required on NixOS and uses the PatchELF utility to
change the interpreter/RPATH of ELF executables.

Please see https://nixos.org/patchelf.html for more information
"""
print("attempting to patch", fname)

# Only build `.nix-deps` once.
nix_deps_dir = self.nix_deps_dir
Expand Down Expand Up @@ -666,8 +671,7 @@ def program_config(self, program):
config = self.get_toml(program)
if config:
return os.path.expanduser(config)
return os.path.join(self.bin_root(), "bin", "{}{}".format(
program, self.exe_suffix()))
return os.path.join(self.bin_root(), "bin", "{}{}".format(program, EXE_SUFFIX))

@staticmethod
def get_string(line):
Expand All @@ -692,13 +696,6 @@ def get_string(line):
return line[start + 1:end]
return None

@staticmethod
def exe_suffix():
"""Return a suffix for executables"""
if sys.platform == 'win32':
return '.exe'
return ''

def bootstrap_binary(self):
"""Return the path of the bootstrap binary

Expand Down Expand Up @@ -757,7 +754,6 @@ def build_bootstrap(self, color):
if target_linker is not None:
env["RUSTFLAGS"] += " -C linker=" + target_linker
env["RUSTFLAGS"] += " -Wrust_2018_idioms -Wunused_lifetimes"
env["RUSTFLAGS"] += " -Wsemicolon_in_expressions_from_macros"
if self.get_toml("deny-warnings", "rust") != "false":
env["RUSTFLAGS"] += " -Dwarnings"

Expand All @@ -768,8 +764,7 @@ def build_bootstrap(self, color):
self.cargo()))
args = [self.cargo(), "build", "--manifest-path",
os.path.join(self.rust_root, "src/bootstrap/Cargo.toml")]
for _ in range(0, self.verbose):
args.append("--verbose")
args.extend("--verbose" for _ in range(self.verbose))
if self.use_locked_deps:
args.append("--locked")
if self.use_vendored_sources:
Expand All @@ -783,7 +778,7 @@ def build_bootstrap(self, color):
args.append("--color=never")

# Run this from the source directory so cargo finds .cargo/config
run(args, env=env, verbose=self.verbose, cwd=self.rust_root)
run(args, env=env, verbose=self.verbose != 0, cwd=self.rust_root)

def build_triple(self):
"""Build triple as in LLVM
Expand All @@ -792,16 +787,7 @@ def build_triple(self):
so use `self.build` where possible.
"""
config = self.get_toml('build')
if config:
return config
return default_build_triple(self.verbose)

def set_dist_environment(self, url):
"""Set download URL for normal environment"""
if 'RUSTUP_DIST_SERVER' in os.environ:
self._download_url = os.environ['RUSTUP_DIST_SERVER']
else:
self._download_url = url
return config or default_build_triple(self.verbose != 0)

def check_vendored_status(self):
"""Check that vendoring is configured properly"""
Expand Down Expand Up @@ -834,27 +820,21 @@ def check_vendored_status(self):
if os.path.exists(cargo_dir):
shutil.rmtree(cargo_dir)

def bootstrap(help_triggered):
"""Configure, fetch, build and run the initial bootstrap"""

# If the user is asking for help, let them know that the whole download-and-build
# process has to happen before anything is printed out.
if help_triggered:
print("info: Downloading and building bootstrap before processing --help")
print(" command. See src/bootstrap/README.md for help with common")
print(" commands.")

parser = argparse.ArgumentParser(description='Build rust')
def parse_args():
"""Parse the command line arguments that the python script needs."""
parser = argparse.ArgumentParser(add_help=False)
parser.add_argument('-h', '--help', action='store_true')
parser.add_argument('--config')
parser.add_argument('--build-dir')
parser.add_argument('--build')
parser.add_argument('--color', choices=['always', 'never', 'auto'])
parser.add_argument('--clean', action='store_true')
parser.add_argument('-v', '--verbose', action='count', default=0)

args = [a for a in sys.argv if a != '-h' and a != '--help']
args, _ = parser.parse_known_args(args)
return parser.parse_known_args(sys.argv)[0]

def bootstrap(args):
"""Configure, fetch, build and run the initial bootstrap"""
# Configure initial bootstrap
build = RustBuild()
build.rust_root = os.path.abspath(os.path.join(__file__, '../../..'))
Expand Down Expand Up @@ -891,7 +871,6 @@ def bootstrap(help_triggered):
build.verbose = max(build.verbose, int(config_verbose))

build.use_vendored_sources = build.get_toml('vendor', 'build') == 'true'

build.use_locked_deps = build.get_toml('locked-deps', 'build') == 'true'

build.check_vendored_status()
Expand All @@ -903,8 +882,7 @@ def bootstrap(help_triggered):
data = json.load(f)
build.checksums_sha256 = data["checksums_sha256"]
build.stage0_compiler = Stage0Toolchain(data["compiler"])

build.set_dist_environment(data["config"]["dist_server"])
build.download_url = os.getenv("RUSTUP_DIST_SERVER") or data["config"]["dist_server"]

build.build = args.build or build.build_triple()

Expand Down Expand Up @@ -932,25 +910,32 @@ def main():

# x.py help <cmd> ...
if len(sys.argv) > 1 and sys.argv[1] == 'help':
sys.argv = [sys.argv[0], '-h'] + sys.argv[2:]
sys.argv[1] = '-h'

args = parse_args()
help_triggered = args.help or len(sys.argv) == 1

help_triggered = (
'-h' in sys.argv) or ('--help' in sys.argv) or (len(sys.argv) == 1)
# If the user is asking for help, let them know that the whole download-and-build
# process has to happen before anything is printed out.
if help_triggered:
print(
"info: Downloading and building bootstrap before processing --help command.\n"
" See src/bootstrap/README.md for help with common commands."
)

exit_code = 0
try:
bootstrap(help_triggered)
if not help_triggered:
print("Build completed successfully in {}".format(
format_build_time(time() - start_time)))
bootstrap(args)
except (SystemExit, KeyboardInterrupt) as error:
if hasattr(error, 'code') and isinstance(error.code, int):
exit_code = error.code
else:
exit_code = 1
print(error)
if not help_triggered:
print("Build completed unsuccessfully in {}".format(
format_build_time(time() - start_time)))
sys.exit(exit_code)

if not help_triggered:
print("Build completed successfully in", format_build_time(time() - start_time))
kadiwa4 marked this conversation as resolved.
Show resolved Hide resolved
sys.exit(exit_code)


if __name__ == '__main__':
Expand Down
Loading