bootstrap: Major refactoring

This commit includes the following:

* Fix syntax errors in Python 3
* Include more docstrings in classes, methods, and functions
* Include unit tests using `unittest`
* Merge implementation of `{rustc,cargo}_out_of_date`
* Merge implementation of `RustBuild.{cargo,rustc}`
* Remove unnecessary source code
* Move all the attributes defined outside of `__init__`
* Remove remaining `%s` from print function
* Remove `WindowsError` reference on non-windows systems
* Rename some variables to be more explicit avoid their meaning
* Run bootstrap tests in the CI process
* Remove non-pythonic getters
* Remove duplicate code in `download_stage0` method
* Reduce the number of branches in `build_bootstrap` method
* Re-raise exception when we cannot execute uname in non-windows systems
* Avoid long lines
This commit is contained in:
Milton Mazzarri 2017-07-02 23:32:42 -05:00
parent 5c126bdee6
commit f516765b94
4 changed files with 426 additions and 200 deletions

View File

@ -37,12 +37,12 @@ def get(url, path, verbose=False):
if os.path.exists(path): if os.path.exists(path):
if verify(path, sha_path, False): if verify(path, sha_path, False):
if verbose: if verbose:
print("using already-download file " + path) print("using already-download file", path)
return return
else: else:
if verbose: if verbose:
print("ignoring already-download file " + print("ignoring already-download file",
path + " due to failed verification") path, "due to failed verification")
os.unlink(path) os.unlink(path)
download(temp_path, url, True, verbose) download(temp_path, url, True, verbose)
if not verify(temp_path, sha_path, verbose): if not verify(temp_path, sha_path, verbose):
@ -59,12 +59,12 @@ def delete_if_present(path, verbose):
"""Remove the given file if present""" """Remove the given file if present"""
if os.path.isfile(path): if os.path.isfile(path):
if verbose: if verbose:
print("removing " + path) print("removing", path)
os.unlink(path) os.unlink(path)
def download(path, url, probably_big, verbose): def download(path, url, probably_big, verbose):
for x in range(0, 4): for _ in range(0, 4):
try: try:
_download(path, url, probably_big, verbose, True) _download(path, url, probably_big, verbose, True)
return return
@ -96,7 +96,7 @@ def _download(path, url, probably_big, verbose, exception):
def verify(path, sha_path, verbose): def verify(path, sha_path, verbose):
"""Check if the sha256 sum of the given path is valid""" """Check if the sha256 sum of the given path is valid"""
if verbose: if verbose:
print("verifying " + path) print("verifying", path)
with open(path, "rb") as source: with open(path, "rb") as source:
found = hashlib.sha256(source.read()).hexdigest() found = hashlib.sha256(source.read()).hexdigest()
with open(sha_path, "r") as sha256sum: with open(sha_path, "r") as sha256sum:
@ -111,29 +111,30 @@ def verify(path, sha_path, verbose):
def unpack(tarball, dst, verbose=False, match=None): def unpack(tarball, dst, verbose=False, match=None):
"""Unpack the given tarball file""" """Unpack the given tarball file"""
print("extracting " + tarball) print("extracting", tarball)
fname = os.path.basename(tarball).replace(".tar.gz", "") fname = os.path.basename(tarball).replace(".tar.gz", "")
with contextlib.closing(tarfile.open(tarball)) as tar: with contextlib.closing(tarfile.open(tarball)) as tar:
for p in tar.getnames(): for member in tar.getnames():
if "/" not in p: if "/" not in member:
continue continue
name = p.replace(fname + "/", "", 1) name = member.replace(fname + "/", "", 1)
if match is not None and not name.startswith(match): if match is not None and not name.startswith(match):
continue continue
name = name[len(match) + 1:] name = name[len(match) + 1:]
fp = os.path.join(dst, name) dst_path = os.path.join(dst, name)
if verbose: if verbose:
print(" extracting " + p) print(" extracting", member)
tar.extract(p, dst) tar.extract(member, dst)
tp = os.path.join(dst, p) src_path = os.path.join(dst, member)
if os.path.isdir(tp) and os.path.exists(fp): if os.path.isdir(src_path) and os.path.exists(dst_path):
continue continue
shutil.move(tp, fp) shutil.move(src_path, dst_path)
shutil.rmtree(os.path.join(dst, fname)) shutil.rmtree(os.path.join(dst, fname))
def run(args, verbose=False, exception=False, **kwargs): def run(args, verbose=False, exception=False, **kwargs):
"""Run a child program in a new process"""
if verbose: if verbose:
print("running: " + ' '.join(args)) print("running: " + ' '.join(args))
sys.stdout.flush() sys.stdout.flush()
@ -149,97 +150,118 @@ def run(args, verbose=False, exception=False, **kwargs):
def stage0_data(rust_root): def stage0_data(rust_root):
"""Build a dictionary from stage0.txt"""
nightlies = os.path.join(rust_root, "src/stage0.txt") nightlies = os.path.join(rust_root, "src/stage0.txt")
data = {}
with open(nightlies, 'r') as nightlies: with open(nightlies, 'r') as nightlies:
for line in nightlies: lines = [line.rstrip() for line in nightlies
line = line.rstrip() # Strip newline character, '\n' if not line.startswith("#")]
if line.startswith("#") or line == '': return dict([line.split(": ", 1) for line in lines if line])
continue
a, b = line.split(": ", 1)
data[a] = b
return data
def format_build_time(duration): def format_build_time(duration):
"""Return a nicer format for build time
>>> format_build_time('300')
'0:05:00'
"""
return str(datetime.timedelta(seconds=int(duration))) return str(datetime.timedelta(seconds=int(duration)))
class RustBuild(object): class RustBuild(object):
"""Provide all the methods required to build Rust"""
def __init__(self):
self.cargo_channel = ''
self.date = ''
self._download_url = 'https://static.rust-lang.org'
self.rustc_channel = ''
self.build = ''
self.build_dir = os.path.join(os.getcwd(), "build")
self.clean = False
self.config_mk = ''
self.config_toml = ''
self.printed = False
self.rust_root = os.path.abspath(os.path.join(__file__, '../../..'))
self.use_locked_deps = ''
self.use_vendored_sources = ''
self.verbose = False
def download_stage0(self): def download_stage0(self):
cache_dst = os.path.join(self.build_dir, "cache") """Fetch the build system for Rust, written in Rust
rustc_cache = os.path.join(cache_dst, self.stage0_date())
if not os.path.exists(rustc_cache):
os.makedirs(rustc_cache)
rustc_channel = self.stage0_rustc_channel() This method will build a cache directory, then it will fetch the
cargo_channel = self.stage0_cargo_channel() tarball which has the stage0 compiler used to then bootstrap the Rust
compiler itself.
Each downloaded tarball is extracted, after that, the script
will move all the content to the right place.
"""
rustc_channel = self.rustc_channel
cargo_channel = self.cargo_channel
if self.rustc().startswith(self.bin_root()) and \ if self.rustc().startswith(self.bin_root()) and \
(not os.path.exists(self.rustc()) or self.rustc_out_of_date()): (not os.path.exists(self.rustc()) or
self.print_what_it_means_to_bootstrap() self.program_out_of_date(self.rustc_stamp())):
self.print_what_bootstrap_means()
if os.path.exists(self.bin_root()): if os.path.exists(self.bin_root()):
shutil.rmtree(self.bin_root()) shutil.rmtree(self.bin_root())
filename = "rust-std-{}-{}.tar.gz".format( filename = "rust-std-{}-{}.tar.gz".format(
rustc_channel, self.build) rustc_channel, self.build)
url = self._download_url + "/dist/" + self.stage0_date() pattern = "rust-std-{}".format(self.build)
tarball = os.path.join(rustc_cache, filename) self._download_stage0_helper(filename, pattern)
if not os.path.exists(tarball):
get("{}/{}".format(url, filename),
tarball, verbose=self.verbose)
unpack(tarball, self.bin_root(),
match="rust-std-" + self.build,
verbose=self.verbose)
filename = "rustc-{}-{}.tar.gz".format(rustc_channel, self.build) filename = "rustc-{}-{}.tar.gz".format(rustc_channel, self.build)
url = self._download_url + "/dist/" + self.stage0_date() self._download_stage0_helper(filename, "rustc")
tarball = os.path.join(rustc_cache, filename) self.fix_executable("{}/bin/rustc".format(self.bin_root()))
if not os.path.exists(tarball): self.fix_executable("{}/bin/rustdoc".format(self.bin_root()))
get("{}/{}".format(url, filename), with open(self.rustc_stamp(), 'w') as rust_stamp:
tarball, verbose=self.verbose) rust_stamp.write(self.date)
unpack(tarball, self.bin_root(),
match="rustc", verbose=self.verbose)
self.fix_executable(self.bin_root() + "/bin/rustc")
self.fix_executable(self.bin_root() + "/bin/rustdoc")
with open(self.rustc_stamp(), 'w') as f:
f.write(self.stage0_date())
if "pc-windows-gnu" in self.build: if "pc-windows-gnu" in self.build:
filename = "rust-mingw-{}-{}.tar.gz".format( filename = "rust-mingw-{}-{}.tar.gz".format(
rustc_channel, self.build) rustc_channel, self.build)
url = self._download_url + "/dist/" + self.stage0_date() self._download_stage0_helper(filename, "rust-mingw")
tarball = os.path.join(rustc_cache, filename)
if not os.path.exists(tarball):
get("{}/{}".format(url, filename),
tarball, verbose=self.verbose)
unpack(tarball, self.bin_root(),
match="rust-mingw", verbose=self.verbose)
if self.cargo().startswith(self.bin_root()) and \ if self.cargo().startswith(self.bin_root()) and \
(not os.path.exists(self.cargo()) or self.cargo_out_of_date()): (not os.path.exists(self.cargo()) or
self.print_what_it_means_to_bootstrap() self.program_out_of_date(self.cargo_stamp())):
self.print_what_bootstrap_means()
filename = "cargo-{}-{}.tar.gz".format(cargo_channel, self.build) filename = "cargo-{}-{}.tar.gz".format(cargo_channel, self.build)
url = self._download_url + "/dist/" + self.stage0_date() self._download_stage0_helper(filename, "cargo")
tarball = os.path.join(rustc_cache, filename) self.fix_executable("{}/bin/cargo".format(self.bin_root()))
if not os.path.exists(tarball): with open(self.cargo_stamp(), 'w') as cargo_stamp:
get("{}/{}".format(url, filename), cargo_stamp.write(self.date)
tarball, verbose=self.verbose)
unpack(tarball, self.bin_root(),
match="cargo", verbose=self.verbose)
self.fix_executable(self.bin_root() + "/bin/cargo")
with open(self.cargo_stamp(), 'w') as f:
f.write(self.stage0_date())
def fix_executable(self, fname): def _download_stage0_helper(self, filename, pattern):
# If we're on NixOS we need to change the path to the dynamic loader cache_dst = os.path.join(self.build_dir, "cache")
rustc_cache = os.path.join(cache_dst, self.date)
if not os.path.exists(rustc_cache):
os.makedirs(rustc_cache)
url = "{}/dist/{}".format(self._download_url, self.date)
tarball = os.path.join(rustc_cache, filename)
if not os.path.exists(tarball):
get("{}/{}".format(url, filename), tarball, verbose=self.verbose)
unpack(tarball, self.bin_root(), match=pattern, verbose=self.verbose)
@staticmethod
def fix_executable(fname):
"""Modifies the interpreter section of 'fname' to fix the dynamic linker
This method is only required on NixOS and uses the PatchELF utility to
change the dynamic linker of ELF executables.
Please see https://nixos.org/patchelf.html for more information
"""
default_encoding = sys.getdefaultencoding() default_encoding = sys.getdefaultencoding()
try: try:
ostype = subprocess.check_output( ostype = subprocess.check_output(
['uname', '-s']).strip().decode(default_encoding) ['uname', '-s']).strip().decode(default_encoding)
except (subprocess.CalledProcessError, WindowsError): except subprocess.CalledProcessError:
return return
except OSError as reason:
if getattr(reason, 'winerror', None) is not None:
return
raise reason
if ostype != "Linux": if ostype != "Linux":
return return
@ -257,8 +279,8 @@ class RustBuild(object):
interpreter = subprocess.check_output( interpreter = subprocess.check_output(
["patchelf", "--print-interpreter", fname]) ["patchelf", "--print-interpreter", fname])
interpreter = interpreter.strip().decode(default_encoding) interpreter = interpreter.strip().decode(default_encoding)
except subprocess.CalledProcessError as e: except subprocess.CalledProcessError as reason:
print("warning: failed to call patchelf: %s" % e) print("warning: failed to call patchelf:", reason)
return return
loader = interpreter.split("/")[-1] loader = interpreter.split("/")[-1]
@ -267,8 +289,8 @@ class RustBuild(object):
ldd_output = subprocess.check_output( ldd_output = subprocess.check_output(
['ldd', '/run/current-system/sw/bin/sh']) ['ldd', '/run/current-system/sw/bin/sh'])
ldd_output = ldd_output.strip().decode(default_encoding) ldd_output = ldd_output.strip().decode(default_encoding)
except subprocess.CalledProcessError as e: except subprocess.CalledProcessError as reason:
print("warning: unable to call ldd: %s" % e) print("warning: unable to call ldd:", reason)
return return
for line in ldd_output.splitlines(): for line in ldd_output.splitlines():
@ -285,45 +307,66 @@ class RustBuild(object):
try: try:
subprocess.check_output( subprocess.check_output(
["patchelf", "--set-interpreter", correct_interpreter, fname]) ["patchelf", "--set-interpreter", correct_interpreter, fname])
except subprocess.CalledProcessError as e: except subprocess.CalledProcessError as reason:
print("warning: failed to call patchelf: %s" % e) print("warning: failed to call patchelf:", reason)
return return
def stage0_date(self):
return self._date
def stage0_rustc_channel(self):
return self._rustc_channel
def stage0_cargo_channel(self):
return self._cargo_channel
def rustc_stamp(self): def rustc_stamp(self):
"""Return the path for .rustc-stamp""" """Return the path for .rustc-stamp
>>> rb = RustBuild()
>>> rb.build_dir = "build"
>>> rb.rustc_stamp() == os.path.join("build", "stage0", ".rustc-stamp")
True
"""
return os.path.join(self.bin_root(), '.rustc-stamp') return os.path.join(self.bin_root(), '.rustc-stamp')
def cargo_stamp(self): def cargo_stamp(self):
"""Return the path for .cargo-stamp""" """Return the path for .cargo-stamp
>>> rb = RustBuild()
>>> rb.build_dir = "build"
>>> rb.cargo_stamp() == os.path.join("build", "stage0", ".cargo-stamp")
True
"""
return os.path.join(self.bin_root(), '.cargo-stamp') return os.path.join(self.bin_root(), '.cargo-stamp')
def rustc_out_of_date(self): def program_out_of_date(self, stamp_path):
"""Check if rustc is out of date""" """Check if the given program stamp is out of date"""
if not os.path.exists(self.rustc_stamp()) or self.clean: if not os.path.exists(stamp_path) or self.clean:
return True return True
with open(self.rustc_stamp(), 'r') as f: with open(stamp_path, 'r') as stamp:
return self.stage0_date() != f.read() return self.date != stamp.read()
def cargo_out_of_date(self):
"""Check if cargo is out of date"""
if not os.path.exists(self.cargo_stamp()) or self.clean:
return True
with open(self.cargo_stamp(), 'r') as f:
return self.stage0_date() != f.read()
def bin_root(self): def bin_root(self):
"""Return the binary root directory
>>> rb = RustBuild()
>>> rb.build_dir = "build"
>>> rb.bin_root() == os.path.join("build", "stage0")
True
When the 'build' property is given should be a nested directory:
>>> rb.build = "devel"
>>> rb.bin_root() == os.path.join("build", "devel", "stage0")
True
"""
return os.path.join(self.build_dir, self.build, "stage0") return os.path.join(self.build_dir, self.build, "stage0")
def get_toml(self, key): def get_toml(self, key):
"""Returns the value of the given key in config.toml, otherwise returns None
>>> rb = RustBuild()
>>> rb.config_toml = 'key1 = "value1"\\nkey2 = "value2"'
>>> rb.get_toml("key2")
'value2'
If the key does not exists, the result is None:
>>> rb.get_toml("key3") == None
True
"""
for line in self.config_toml.splitlines(): for line in self.config_toml.splitlines():
match = re.match(r'^{}\s*=(.*)$'.format(key), line) match = re.match(r'^{}\s*=(.*)$'.format(key), line)
if match is not None: if match is not None:
@ -332,6 +375,18 @@ class RustBuild(object):
return None return None
def get_mk(self, key): def get_mk(self, key):
"""Returns the value of the given key in config.mk, otherwise returns None
>>> rb = RustBuild()
>>> rb.config_mk = 'key := value\\n'
>>> rb.get_mk('key')
'value'
If the key does not exists, the result is None:
>>> rb.get_mk('does_not_exists') == None
True
"""
for line in iter(self.config_mk.splitlines()): for line in iter(self.config_mk.splitlines()):
if line.startswith(key + ' '): if line.startswith(key + ' '):
var = line[line.find(':=') + 2:].strip() var = line[line.find(':=') + 2:].strip()
@ -340,36 +395,64 @@ class RustBuild(object):
return None return None
def cargo(self): def cargo(self):
config = self.get_toml('cargo') """Return config path for cargo"""
if config: return self.program_config('cargo')
return config
config = self.get_mk('CFG_LOCAL_RUST_ROOT')
if config:
return config + '/bin/cargo' + self.exe_suffix()
return os.path.join(self.bin_root(), "bin/cargo" + self.exe_suffix())
def rustc(self): def rustc(self):
config = self.get_toml('rustc') """Return config path for rustc"""
return self.program_config('rustc')
def program_config(self, program):
"""Return config path for the given program
>>> rb = RustBuild()
>>> rb.config_toml = 'rustc = "rustc"\\n'
>>> rb.config_mk = 'CFG_LOCAL_RUST_ROOT := /tmp/rust\\n'
>>> rb.program_config('rustc')
'rustc'
>>> cargo_path = rb.program_config('cargo')
>>> cargo_path.rstrip(".exe") == os.path.join("/tmp/rust",
... "bin", "cargo")
True
>>> rb.config_toml = ''
>>> rb.config_mk = ''
>>> cargo_path = rb.program_config('cargo')
>>> cargo_path.rstrip(".exe") == os.path.join(rb.bin_root(),
... "bin", "cargo")
True
"""
config = self.get_toml(program)
if config: if config:
return config return config
config = self.get_mk('CFG_LOCAL_RUST_ROOT') config = self.get_mk('CFG_LOCAL_RUST_ROOT')
if config: if config:
return config + '/bin/rustc' + self.exe_suffix() return os.path.join(config, "bin", "{}{}".format(
return os.path.join(self.bin_root(), "bin/rustc" + self.exe_suffix()) program, self.exe_suffix()))
return os.path.join(self.bin_root(), "bin", "{}{}".format(
program, self.exe_suffix()))
def get_string(self, line): @staticmethod
def get_string(line):
"""Return the value between double quotes
>>> RustBuild.get_string(' "devel" ')
'devel'
"""
start = line.find('"') start = line.find('"')
if start == -1: if start == -1:
return None return None
end = start + 1 + line[start + 1:].find('"') end = start + 1 + line[start + 1:].find('"')
return line[start + 1:end] return line[start + 1:end]
def exe_suffix(self): @staticmethod
def exe_suffix():
"""Return a suffix for executables"""
if sys.platform == 'win32': if sys.platform == 'win32':
return '.exe' return '.exe'
return '' return ''
def print_what_it_means_to_bootstrap(self): def print_what_bootstrap_means(self):
"""Prints more information about the build system"""
if hasattr(self, 'printed'): if hasattr(self, 'printed'):
return return
self.printed = True self.printed = True
@ -386,10 +469,19 @@ class RustBuild(object):
print(' src/bootstrap/README.md before the download finishes') print(' src/bootstrap/README.md before the download finishes')
def bootstrap_binary(self): def bootstrap_binary(self):
return os.path.join(self.build_dir, "bootstrap/debug/bootstrap") """Return the path of the boostrap binary
>>> rb = RustBuild()
>>> rb.build_dir = "build"
>>> rb.bootstrap_binary() == os.path.join("build", "bootstrap",
... "debug", "bootstrap")
True
"""
return os.path.join(self.build_dir, "bootstrap", "debug", "bootstrap")
def build_bootstrap(self): def build_bootstrap(self):
self.print_what_it_means_to_bootstrap() """Build bootstrap"""
self.print_what_bootstrap_means()
build_dir = os.path.join(self.build_dir, "bootstrap") build_dir = os.path.join(self.build_dir, "bootstrap")
if self.clean and os.path.exists(build_dir): if self.clean and os.path.exists(build_dir):
shutil.rmtree(build_dir) shutil.rmtree(build_dir)
@ -409,7 +501,8 @@ class RustBuild(object):
env["PATH"] = os.path.join(self.bin_root(), "bin") + \ env["PATH"] = os.path.join(self.bin_root(), "bin") + \
os.pathsep + env["PATH"] os.pathsep + env["PATH"]
if not os.path.isfile(self.cargo()): if not os.path.isfile(self.cargo()):
raise Exception("no cargo executable found at `%s`" % self.cargo()) raise Exception("no cargo executable found at `{}`".format(
self.cargo()))
args = [self.cargo(), "build", "--manifest-path", args = [self.cargo(), "build", "--manifest-path",
os.path.join(self.rust_root, "src/bootstrap/Cargo.toml")] os.path.join(self.rust_root, "src/bootstrap/Cargo.toml")]
if self.verbose: if self.verbose:
@ -423,6 +516,7 @@ class RustBuild(object):
run(args, env=env, verbose=self.verbose) run(args, env=env, verbose=self.verbose)
def build_triple(self): def build_triple(self):
"""Build triple as in LLVM"""
default_encoding = sys.getdefaultencoding() default_encoding = sys.getdefaultencoding()
config = self.get_toml('build') config = self.get_toml('build')
if config: if config:
@ -445,23 +539,26 @@ class RustBuild(object):
# The goal here is to come up with the same triple as LLVM would, # The goal here is to come up with the same triple as LLVM would,
# at least for the subset of platforms we're willing to target. # at least for the subset of platforms we're willing to target.
if ostype == 'Linux': ostype_mapper = {
'Bitrig': 'unknown-bitrig',
'Darwin': 'apple-darwin',
'DragonFly': 'unknown-dragonfly',
'FreeBSD': 'unknown-freebsd',
'Haiku': 'unknown-haiku',
'NetBSD': 'unknown-netbsd',
'OpenBSD': 'unknown-openbsd'
}
# Consider the direct transformation first and then the special cases
if ostype in ostype_mapper:
ostype = ostype_mapper[ostype]
elif ostype == 'Linux':
os_from_sp = subprocess.check_output( os_from_sp = subprocess.check_output(
['uname', '-o']).strip().decode(default_encoding) ['uname', '-o']).strip().decode(default_encoding)
if os_from_sp == 'Android': if os_from_sp == 'Android':
ostype = 'linux-android' ostype = 'linux-android'
else: else:
ostype = 'unknown-linux-gnu' ostype = 'unknown-linux-gnu'
elif ostype == 'FreeBSD':
ostype = 'unknown-freebsd'
elif ostype == 'DragonFly':
ostype = 'unknown-dragonfly'
elif ostype == 'Bitrig':
ostype = 'unknown-bitrig'
elif ostype == 'OpenBSD':
ostype = 'unknown-openbsd'
elif ostype == 'NetBSD':
ostype = 'unknown-netbsd'
elif ostype == 'SunOS': elif ostype == 'SunOS':
ostype = 'sun-solaris' ostype = 'sun-solaris'
# On Solaris, uname -m will return a machine classification instead # On Solaris, uname -m will return a machine classification instead
@ -477,10 +574,6 @@ class RustBuild(object):
if self.verbose: if self.verbose:
raise Exception(err) raise Exception(err)
sys.exit(err) sys.exit(err)
elif ostype == 'Darwin':
ostype = 'apple-darwin'
elif ostype == 'Haiku':
ostype = 'unknown-haiku'
elif ostype.startswith('MINGW'): elif ostype.startswith('MINGW'):
# msys' `uname` does not print gcc configuration, but prints msys # msys' `uname` does not print gcc configuration, but prints msys
# configuration. so we cannot believe `uname -m`: # configuration. so we cannot believe `uname -m`:
@ -499,13 +592,36 @@ class RustBuild(object):
cputype = 'x86_64' cputype = 'x86_64'
ostype = 'pc-windows-gnu' ostype = 'pc-windows-gnu'
else: else:
err = "unknown OS type: " + ostype err = "unknown OS type: {}".format(ostype)
if self.verbose: if self.verbose:
raise ValueError(err) raise ValueError(err)
sys.exit(err) sys.exit(err)
if cputype in {'i386', 'i486', 'i686', 'i786', 'x86'}: cputype_mapper = {
cputype = 'i686' 'BePC': 'i686',
'aarch64': 'aarch64',
'amd64': 'x86_64',
'arm64': 'aarch64',
'i386': 'i686',
'i486': 'i686',
'i686': 'i686',
'i786': 'i686',
'powerpc': 'powerpc',
'powerpc64': 'powerpc64',
'powerpc64le': 'powerpc64le',
'ppc': 'powerpc',
'ppc64': 'powerpc64',
'ppc64le': 'powerpc64le',
's390x': 's390x',
'x64': 'x86_64',
'x86': 'i686',
'x86-64': 'x86_64',
'x86_64': 'x86_64'
}
# Consider the direct transformation first and then the special cases
if cputype in cputype_mapper:
cputype = cputype_mapper[cputype]
elif cputype in {'xscale', 'arm'}: elif cputype in {'xscale', 'arm'}:
cputype = 'arm' cputype = 'arm'
if ostype == 'linux-android': if ostype == 'linux-android':
@ -522,40 +638,26 @@ class RustBuild(object):
ostype = 'linux-androideabi' ostype = 'linux-androideabi'
else: else:
ostype += 'eabihf' ostype += 'eabihf'
elif cputype in {'aarch64', 'arm64'}:
cputype = 'aarch64'
elif cputype == 'mips': elif cputype == 'mips':
if sys.byteorder == 'big': if sys.byteorder == 'big':
cputype = 'mips' cputype = 'mips'
elif sys.byteorder == 'little': elif sys.byteorder == 'little':
cputype = 'mipsel' cputype = 'mipsel'
else: else:
raise ValueError('unknown byteorder: ' + sys.byteorder) raise ValueError("unknown byteorder: {}".format(sys.byteorder))
elif cputype == 'mips64': elif cputype == 'mips64':
if sys.byteorder == 'big': if sys.byteorder == 'big':
cputype = 'mips64' cputype = 'mips64'
elif sys.byteorder == 'little': elif sys.byteorder == 'little':
cputype = 'mips64el' cputype = 'mips64el'
else: else:
raise ValueError('unknown byteorder: ' + sys.byteorder) raise ValueError('unknown byteorder: {}'.format(sys.byteorder))
# only the n64 ABI is supported, indicate it # only the n64 ABI is supported, indicate it
ostype += 'abi64' ostype += 'abi64'
elif cputype in {'powerpc', 'ppc'}:
cputype = 'powerpc'
elif cputype in {'powerpc64', 'ppc64'}:
cputype = 'powerpc64'
elif cputype in {'powerpc64le', 'ppc64le'}:
cputype = 'powerpc64le'
elif cputype == 'sparcv9': elif cputype == 'sparcv9':
pass pass
elif cputype in {'amd64', 'x86_64', 'x86-64', 'x64'}:
cputype = 'x86_64'
elif cputype == 's390x':
cputype = 's390x'
elif cputype == 'BePC':
cputype = 'i686'
else: else:
err = "unknown cpu type: " + cputype err = "unknown cpu type: {}".format(cputype)
if self.verbose: if self.verbose:
raise ValueError(err) raise ValueError(err)
sys.exit(err) sys.exit(err)
@ -563,6 +665,7 @@ class RustBuild(object):
return "{}-{}".format(cputype, ostype) return "{}-{}".format(cputype, ostype)
def update_submodules(self): def update_submodules(self):
"""Update submodules"""
if (not os.path.exists(os.path.join(self.rust_root, ".git"))) or \ if (not os.path.exists(os.path.join(self.rust_root, ".git"))) or \
self.get_toml('submodules') == "false" or \ self.get_toml('submodules') == "false" or \
self.get_mk('CFG_DISABLE_MANAGE_SUBMODULES') == "1": self.get_mk('CFG_DISABLE_MANAGE_SUBMODULES') == "1":
@ -592,8 +695,13 @@ class RustBuild(object):
"clean", "-qdfx"], "clean", "-qdfx"],
cwd=self.rust_root, verbose=self.verbose) cwd=self.rust_root, verbose=self.verbose)
def set_dev_environment(self):
"""Set download URL for development environment"""
self._download_url = 'https://dev-static.rust-lang.org'
def bootstrap(): def bootstrap():
"""Configure, fetch, build and run the initial bootstrap"""
parser = argparse.ArgumentParser(description='Build rust') parser = argparse.ArgumentParser(description='Build rust')
parser.add_argument('--config') parser.add_argument('--config')
parser.add_argument('--build') parser.add_argument('--build')
@ -604,107 +712,103 @@ def bootstrap():
args, _ = parser.parse_known_args(args) args, _ = parser.parse_known_args(args)
# Configure initial bootstrap # Configure initial bootstrap
rb = RustBuild() build = RustBuild()
rb.config_toml = '' build.verbose = args.verbose
rb.config_mk = '' build.clean = args.clean
rb.rust_root = os.path.abspath(os.path.join(__file__, '../../..'))
rb.build_dir = os.path.join(os.getcwd(), "build")
rb.verbose = args.verbose
rb.clean = args.clean
try: try:
with open(args.config or 'config.toml') as config: with open(args.config or 'config.toml') as config:
rb.config_toml = config.read() build.config_toml = config.read()
except: except:
pass pass
try: try:
rb.config_mk = open('config.mk').read() build.config_mk = open('config.mk').read()
except: except:
pass pass
if '\nverbose = 2' in rb.config_toml: if '\nverbose = 2' in build.config_toml:
rb.verbose = 2 build.verbose = 2
elif '\nverbose = 1' in rb.config_toml: elif '\nverbose = 1' in build.config_toml:
rb.verbose = 1 build.verbose = 1
rb.use_vendored_sources = '\nvendor = true' in rb.config_toml or \ build.use_vendored_sources = '\nvendor = true' in build.config_toml or \
'CFG_ENABLE_VENDOR' in rb.config_mk 'CFG_ENABLE_VENDOR' in build.config_mk
rb.use_locked_deps = '\nlocked-deps = true' in rb.config_toml or \ build.use_locked_deps = '\nlocked-deps = true' in build.config_toml or \
'CFG_ENABLE_LOCKED_DEPS' in rb.config_mk 'CFG_ENABLE_LOCKED_DEPS' in build.config_mk
if 'SUDO_USER' in os.environ and not rb.use_vendored_sources: if 'SUDO_USER' in os.environ and not build.use_vendored_sources:
if os.environ.get('USER') != os.environ['SUDO_USER']: if os.environ.get('USER') != os.environ['SUDO_USER']:
rb.use_vendored_sources = True build.use_vendored_sources = True
print('info: looks like you are running this command under `sudo`') print('info: looks like you are running this command under `sudo`')
print(' and so in order to preserve your $HOME this will now') print(' and so in order to preserve your $HOME this will now')
print(' use vendored sources by default. Note that if this') print(' use vendored sources by default. Note that if this')
print(' does not work you should run a normal build first') print(' does not work you should run a normal build first')
print(' before running a command like `sudo make install`') print(' before running a command like `sudo make install`')
if rb.use_vendored_sources: if build.use_vendored_sources:
if not os.path.exists('.cargo'): if not os.path.exists('.cargo'):
os.makedirs('.cargo') os.makedirs('.cargo')
with open('.cargo/config', 'w') as f: with open('.cargo/config', 'w') as cargo_config:
f.write(""" cargo_config.write("""
[source.crates-io] [source.crates-io]
replace-with = 'vendored-sources' replace-with = 'vendored-sources'
registry = 'https://example.com' registry = 'https://example.com'
[source.vendored-sources] [source.vendored-sources]
directory = '{}/src/vendor' directory = '{}/src/vendor'
""".format(rb.rust_root)) """.format(build.rust_root))
else: else:
if os.path.exists('.cargo'): if os.path.exists('.cargo'):
shutil.rmtree('.cargo') shutil.rmtree('.cargo')
data = stage0_data(rb.rust_root) data = stage0_data(build.rust_root)
rb._date = data['date'] build.date = data['date']
rb._rustc_channel = data['rustc'] build.rustc_channel = data['rustc']
rb._cargo_channel = data['cargo'] build.cargo_channel = data['cargo']
if 'dev' in data:
rb._download_url = 'https://dev-static.rust-lang.org'
else:
rb._download_url = 'https://static.rust-lang.org'
rb.update_submodules() if 'dev' in data:
build.set_dev_environment()
build.update_submodules()
# Fetch/build the bootstrap # Fetch/build the bootstrap
rb.build = args.build or rb.build_triple() build.build = args.build or build.build_triple()
rb.download_stage0() build.download_stage0()
sys.stdout.flush() sys.stdout.flush()
rb.build_bootstrap() build.build_bootstrap()
sys.stdout.flush() sys.stdout.flush()
# Run the bootstrap # Run the bootstrap
args = [rb.bootstrap_binary()] args = [build.bootstrap_binary()]
args.extend(sys.argv[1:]) args.extend(sys.argv[1:])
env = os.environ.copy() env = os.environ.copy()
env["BUILD"] = rb.build env["BUILD"] = build.build
env["SRC"] = rb.rust_root env["SRC"] = build.rust_root
env["BOOTSTRAP_PARENT_ID"] = str(os.getpid()) env["BOOTSTRAP_PARENT_ID"] = str(os.getpid())
env["BOOTSTRAP_PYTHON"] = sys.executable env["BOOTSTRAP_PYTHON"] = sys.executable
run(args, env=env, verbose=rb.verbose) run(args, env=env, verbose=build.verbose)
def main(): def main():
"""Entry point for the bootstrap process"""
start_time = time() start_time = time()
help_triggered = ( help_triggered = (
'-h' in sys.argv) or ('--help' in sys.argv) or (len(sys.argv) == 1) '-h' in sys.argv) or ('--help' in sys.argv) or (len(sys.argv) == 1)
try: try:
bootstrap() bootstrap()
if not help_triggered: if not help_triggered:
print("Build completed successfully in %s" % print("Build completed successfully in {}".format(
format_build_time(time() - start_time)) format_build_time(time() - start_time)))
except (SystemExit, KeyboardInterrupt) as e: except (SystemExit, KeyboardInterrupt) as error:
if hasattr(e, 'code') and isinstance(e.code, int): if hasattr(error, 'code') and isinstance(error.code, int):
exit_code = e.code exit_code = error.code
else: else:
exit_code = 1 exit_code = 1
print(e) print(error)
if not help_triggered: if not help_triggered:
print("Build completed unsuccessfully in %s" % print("Build completed unsuccessfully in {}".format(
format_build_time(time() - start_time)) format_build_time(time() - start_time)))
sys.exit(exit_code) sys.exit(exit_code)

View File

@ -0,0 +1,114 @@
# Copyright 2015-2016 The Rust Project Developers. See the COPYRIGHT
# file at the top-level directory of this distribution and at
# http://rust-lang.org/COPYRIGHT.
#
# Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
# http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
# <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
# option. This file may not be copied, modified, or distributed
# except according to those terms.
"""Bootstrap tests"""
import os
import doctest
import unittest
import tempfile
import hashlib
from shutil import rmtree
import bootstrap
class Stage0DataTestCase(unittest.TestCase):
"""Test Case for stage0_data"""
def setUp(self):
self.rust_root = tempfile.mkdtemp()
os.mkdir(os.path.join(self.rust_root, "src"))
with open(os.path.join(self.rust_root, "src",
"stage0.txt"), "w") as stage0:
stage0.write("#ignore\n\ndate: 2017-06-15\nrustc: beta\ncargo: beta")
def tearDown(self):
rmtree(self.rust_root)
def test_stage0_data(self):
"""Extract data from stage0.txt"""
expected = {"date": "2017-06-15", "rustc": "beta", "cargo": "beta"}
data = bootstrap.stage0_data(self.rust_root)
self.assertDictEqual(data, expected)
class VerifyTestCase(unittest.TestCase):
"""Test Case for verify"""
def setUp(self):
self.container = tempfile.mkdtemp()
self.src = os.path.join(self.container, "src.txt")
self.sums = os.path.join(self.container, "sums")
self.bad_src = os.path.join(self.container, "bad.txt")
content = "Hello world"
with open(self.src, "w") as src:
src.write(content)
with open(self.sums, "w") as sums:
sums.write(hashlib.sha256(content.encode("utf-8")).hexdigest())
with open(self.bad_src, "w") as bad:
bad.write("Hello!")
def tearDown(self):
rmtree(self.container)
def test_valid_file(self):
"""Check if the sha256 sum of the given file is valid"""
self.assertTrue(bootstrap.verify(self.src, self.sums, False))
def test_invalid_file(self):
"""Should verify that the file is invalid"""
self.assertFalse(bootstrap.verify(self.bad_src, self.sums, False))
class ProgramOutOfDate(unittest.TestCase):
"""Test if a program is out of date"""
def setUp(self):
self.container = tempfile.mkdtemp()
os.mkdir(os.path.join(self.container, "stage0"))
self.build = bootstrap.RustBuild()
self.build.date = "2017-06-15"
self.build.build_dir = self.container
self.rustc_stamp_path = os.path.join(self.container, "stage0",
".rustc-stamp")
def tearDown(self):
rmtree(self.container)
def test_stamp_path_does_not_exists(self):
"""Return True when the stamp file does not exists"""
if os.path.exists(self.rustc_stamp_path):
os.unlink(self.rustc_stamp_path)
self.assertTrue(self.build.program_out_of_date(self.rustc_stamp_path))
def test_dates_are_different(self):
"""Return True when the dates are different"""
with open(self.rustc_stamp_path, "w") as rustc_stamp:
rustc_stamp.write("2017-06-14")
self.assertTrue(self.build.program_out_of_date(self.rustc_stamp_path))
def test_same_dates(self):
"""Return False both dates match"""
with open(self.rustc_stamp_path, "w") as rustc_stamp:
rustc_stamp.write("2017-06-15")
self.assertFalse(self.build.program_out_of_date(self.rustc_stamp_path))
if __name__ == '__main__':
SUITE = unittest.TestSuite()
TEST_LOADER = unittest.TestLoader()
SUITE.addTest(doctest.DocTestSuite(bootstrap))
SUITE.addTests([
TEST_LOADER.loadTestsFromTestCase(Stage0DataTestCase),
TEST_LOADER.loadTestsFromTestCase(VerifyTestCase),
TEST_LOADER.loadTestsFromTestCase(ProgramOutOfDate)])
RUNNER = unittest.TextTestRunner(verbosity=2)
RUNNER.run(SUITE)

View File

@ -64,6 +64,8 @@ check-aux:
src/test/run-pass-fulldeps/pretty \ src/test/run-pass-fulldeps/pretty \
src/test/run-fail-fulldeps/pretty \ src/test/run-fail-fulldeps/pretty \
$(BOOTSTRAP_ARGS) $(BOOTSTRAP_ARGS)
check-bootstrap:
$(Q)$(CFG_PYTHON) $(CFG_SRC_DIR)src/bootstrap/bootstrap_test.py
dist: dist:
$(Q)$(BOOTSTRAP) dist $(BOOTSTRAP_ARGS) $(Q)$(BOOTSTRAP) dist $(BOOTSTRAP_ARGS)
distcheck: distcheck:

View File

@ -74,6 +74,12 @@ retry make prepare
travis_fold end make-prepare travis_fold end make-prepare
travis_time_finish travis_time_finish
travis_fold start check-bootstrap
travis_time_start
make check-bootstrap
travis_fold end check-bootstrap
travis_time_finish
if [ "$TRAVIS_OS_NAME" = "osx" ]; then if [ "$TRAVIS_OS_NAME" = "osx" ]; then
ncpus=$(sysctl -n hw.ncpu) ncpus=$(sysctl -n hw.ncpu)
else else