servo: Merge #19395 - Use rustup.rs instead of custom bootstrap (from servo:rustup.rs); r=jdm
authorSimon Sapin <simon.sapin@exyr.org>
Wed, 10 Jan 2018 12:00:59 -0600
changeset 450371 bebab8b62aee3f895e31a97e39a1b475452ad277
parent 450370 b6d240f90874d58ef2d339029dbf71d73af84555
child 450372 073f421c10ddbde154c6ca7eb9f644a85c8f36db
push id8527
push userCallek@gmail.com
push dateThu, 11 Jan 2018 21:05:50 +0000
treeherdermozilla-beta@95342d212a7a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjdm
milestone59.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
servo: Merge #19395 - Use rustup.rs instead of custom bootstrap (from servo:rustup.rs); r=jdm Use rustup.rs instead of custom bootstrap Fixes #11361, closes #18874, fixes #19365. Source-Repo: https://github.com/servo/servo Source-Revision: 2be49ac80659f58f2a39910dd79a3f3b5da98945
servo/.travis.yml
servo/README.md
servo/appveyor.yml
servo/etc/rustdoc-with-private
servo/geckolib-rust-toolchain
servo/python/servo/bootstrap_commands.py
servo/python/servo/build_commands.py
servo/python/servo/command_base.py
servo/python/servo/devenv_commands.py
servo/python/servo/post_build_commands.py
servo/python/servo/testing_commands.py
servo/python/servo/util.py
servo/rust-stable-version
servo/servobuild.example
--- a/servo/.travis.yml
+++ b/servo/.travis.yml
@@ -14,27 +14,30 @@ matrix:
       dist: trusty
       before_install:
         - sudo add-apt-repository ppa:ubuntu-toolchain-r/test -y
         - sudo add-apt-repository 'deb http://apt.llvm.org/precise/ llvm-toolchain-precise-3.9 main' -y
         - sudo apt-get update -q
         - sudo apt-get install clang-3.9 llvm-3.9 llvm-3.9-runtime -y
         - export LLVM_CONFIG=/usr/lib/llvm-3.9/bin/llvm-config
         - export CC=gcc-5 CXX=g++-5
+        - curl https://sh.rustup.rs -sSf | sh -s -- --default-toolchain none -y
+        - source ~/.profile
       script:
          - ./mach build -d --verbose
          - ./mach test-unit
          - ./mach clean
          - ./mach build-geckolib
          - ./mach test-stylo
          - bash etc/ci/lockfile_changed.sh
       cache:
         directories:
-          - .cargo
           - .servo
+          - $HOME/.cargo
+          - $HOME/.rustup
           - $HOME/.ccache
       before_cache:
         - ./mach clean-nightlies --keep 2 --force
         - ./mach clean-cargo-cache --keep 2 --force
       env:
         CCACHE=/usr/bin/ccache
         RUSTFLAGS=-Dwarnings
       addons:
--- a/servo/README.md
+++ b/servo/README.md
@@ -9,16 +9,42 @@ 64-bit OS X, 64-bit Linux, 64-bit Window
 Servo welcomes contribution from everyone.  See
 [`CONTRIBUTING.md`](CONTRIBUTING.md) and [`HACKING_QUICKSTART.md`](docs/HACKING_QUICKSTART.md)
 for help getting started.
 
 Visit the [Servo Project page](https://servo.org/) for news and guides.
 
 ## Setting up your environment
 
+### Rustup.rs
+
+Building servo requires [rustup](https://rustup.rs/), version 1.8.0 or more recent.
+If you have an older version, run `rustup self update`.
+
+To install on Windows, download and run [`rustup-init.exe`](https://win.rustup.rs/)
+then follow the onscreen instructions.
+
+To install on other systems, run:
+
+```sh
+curl https://sh.rustup.rs -sSf | sh
+```
+
+This will also download the current stable version of Rust, which Servo won’t use.
+To skip that step, run instead:
+
+```
+curl https://sh.rustup.rs -sSf | sh -s -- --default-toolchain none
+```
+
+See also [Other installation methods](
+https://github.com/rust-lang-nursery/rustup.rs/#other-installation-methods)
+
+### Other dependencies
+
 Please select your operating system:
 * [OS X](#os-x)
 * [Debian-based Linuxes](#on-debian-based-linuxes)
 * [Fedora](#on-fedora)
 * [Arch Linux](#on-arch-linux)
 * [openSUSE](#on-opensuse-linux)
 * [Gentoo Linux](#on-gentoo-linux)
 * [Microsoft Windows](#on-windows-msvc)
@@ -147,21 +173,19 @@ list of installed components. It is not 
 
 #### Cross-compilation for Android
 
 Pre-installed Android tools are needed. See wiki for
 [details](https://github.com/servo/servo/wiki/Building-for-Android)
 
 ## The Rust compiler
 
-Servo's build system automatically downloads a Rust compiler to build itself.
-This is normally a specific revision of Rust upstream, but sometimes has a
-backported patch or two.
-If you'd like to know which nightly build of Rust we use, see
-[`rust-toolchain`](https://github.com/servo/servo/blob/master/rust-toolchain).
+Servo's build system uses rustup.rs to automatically download a Rust compiler.
+This is a specific version of Rust Nightly determined by the
+[`rust-toolchain`](https://github.com/servo/servo/blob/master/rust-toolchain) file.
 
 ## Building
 
 Servo is built with [Cargo](https://crates.io/), the Rust package manager. We also use Mozilla's
 Mach tools to orchestrate the build and other tasks.
 
 ### Normal build
 
--- a/servo/appveyor.yml
+++ b/servo/appveyor.yml
@@ -32,20 +32,29 @@ environment:
   matrix:
   - TARGET: nightly-x86_64-pc-windows-msvc
 
 branches:
   only:
     - master
 
 cache:
+  - C:\Users\appveyor\.rustup -> rust-toolchain
+  - C:\Users\appveyor\.cargo -> rust-toolchain
   - .servo -> rust-toolchain
-  - .cargo -> rust-toolchain
   - .ccache
 
+install:
+  - appveyor-retry appveyor DownloadFile https://win.rustup.rs/ -FileName rustup-init.exe
+  - rustup-init.exe -y --default-host x86_64-pc-windows-msvc --default-toolchain none
+  - set PATH=%PATH%;C:\Users\appveyor\.cargo\bin
+  - rustup -V
+  - mach rustc --version
+  - mach cargo --version
+
 # Uncomment these lines to expose RDP access information to the build machine in the build log.
 #init:
 #  - ps: iex ((new-object net.webclient).DownloadString('https://raw.githubusercontent.com/appveyor/ci/master/scripts/enable-rdp.ps1'))
 #
 #on_finish:
 #  - ps: $blockRdp = $true; iex ((new-object net.webclient).DownloadString('https://raw.githubusercontent.com/appveyor/ci/master/scripts/enable-rdp.ps1'))
 
 build_script:
new file mode 100755
--- /dev/null
+++ b/servo/etc/rustdoc-with-private
@@ -0,0 +1,4 @@
+#!/bin/sh
+# Skip the strip-private and strip-hidden rustdoc passes
+# https://github.com/rust-lang/rust/issues/15347
+rustdoc --no-defaults --passes collapse-docs --passes unindent-comments --passes strip-priv-imports "$@"
rename from servo/rust-stable-version
rename to servo/geckolib-rust-toolchain
--- a/servo/python/servo/bootstrap_commands.py
+++ b/servo/python/servo/bootstrap_commands.py
@@ -9,211 +9,55 @@
 
 from __future__ import absolute_import, print_function, unicode_literals
 
 import base64
 import json
 import os
 import os.path as path
 import re
-import shutil
 import subprocess
 import sys
 import urllib2
 import glob
 
 from mach.decorators import (
     CommandArgument,
     CommandProvider,
     Command,
 )
 
 import servo.bootstrap as bootstrap
-from servo.command_base import CommandBase, BIN_SUFFIX, cd
-from servo.util import delete, download_bytes, download_file, extract, host_triple
-from servo.util import STATIC_RUST_LANG_ORG_DIST
+from servo.command_base import CommandBase, cd, check_call
+from servo.util import delete, download_bytes
 
 
 @CommandProvider
 class MachCommands(CommandBase):
     @Command('env',
              description='Print environment setup commands',
              category='bootstrap')
     def env(self):
         env = self.build_env()
+        print("export RUSTFLAGS=%s" % env["RUSTFLAGS"])
         print("export PATH=%s" % env["PATH"])
         if sys.platform == "darwin":
             print("export DYLD_LIBRARY_PATH=%s" % env["DYLD_LIBRARY_PATH"])
         else:
             print("export LD_LIBRARY_PATH=%s" % env["LD_LIBRARY_PATH"])
 
     @Command('bootstrap',
              description='Install required packages for building.',
              category='bootstrap')
     @CommandArgument('--force', '-f',
                      action='store_true',
                      help='Boostrap without confirmation')
     def bootstrap(self, force=False):
         return bootstrap.bootstrap(self.context, force=force)
 
-    @Command('bootstrap-rust',
-             description='Download the Rust compiler',
-             category='bootstrap')
-    @CommandArgument('--force', '-f',
-                     action='store_true',
-                     help='Force download even if a copy already exists')
-    @CommandArgument('--target',
-                     action='append',
-                     default=[],
-                     help='Download rust stdlib for specified target')
-    @CommandArgument('--stable',
-                     action='store_true',
-                     help='Use stable rustc version')
-    def bootstrap_rustc(self, force=False, target=[], stable=False):
-        self.set_use_stable_rust(stable)
-        rust_dir = path.join(self.context.sharedir, "rust", self.rust_path())
-        install_dir = path.join(self.context.sharedir, "rust", self.rust_install_dir())
-        version = self.rust_stable_version() if stable else "nightly"
-
-        nightly_dist = STATIC_RUST_LANG_ORG_DIST + "/" + self.rust_nightly_date()
-
-        if not force and path.exists(path.join(rust_dir, "rustc", "bin", "rustc" + BIN_SUFFIX)):
-            print("Rust compiler already downloaded.", end=" ")
-            print("Use |bootstrap-rust --force| to download again.")
-        else:
-            if path.isdir(rust_dir):
-                shutil.rmtree(rust_dir)
-            os.makedirs(rust_dir)
-
-            # The nightly Rust compiler is hosted on the nightly server under the date with a name
-            # rustc-nightly-HOST-TRIPLE.tar.gz, whereas the stable compiler is named
-            # rustc-VERSION-HOST-TRIPLE.tar.gz. We just need to pull down and extract it,
-            # giving a directory name that will be the same as the tarball name (rustc is
-            # in that directory).
-            if stable:
-                base_url = STATIC_RUST_LANG_ORG_DIST
-            else:
-                base_url = nightly_dist
-
-            rustc_url = base_url + "/rustc-%s-%s.tar.gz" % (version, host_triple())
-            tgz_file = rust_dir + '-rustc.tar.gz'
-            download_file("Rust compiler", rustc_url, tgz_file)
-
-            print("Extracting Rust compiler...")
-            extract(tgz_file, install_dir)
-            print("Rust compiler ready.")
-
-        # Each Rust stdlib has a name of the form `rust-std-nightly-TRIPLE.tar.gz` for the nightly
-        # releases, or rust-std-VERSION-TRIPLE.tar.gz for stable releases, with
-        # a directory of the name `rust-std-TRIPLE` inside and then a `lib` directory.
-        # This `lib` directory needs to be extracted and merged with the `rustc/lib`
-        # directory from the host compiler above.
-        lib_dir = path.join(install_dir,
-                            "rustc-%s-%s" % (version, host_triple()),
-                            "rustc", "lib", "rustlib")
-
-        # ensure that the libs for the host's target is downloaded
-        host_target = host_triple()
-        if host_target not in target:
-            target.append(host_target)
-
-        for target_triple in target:
-            target_lib_dir = path.join(lib_dir, target_triple)
-            if path.exists(target_lib_dir):
-                # No need to check for force. If --force the directory is already deleted
-                print("Rust lib for target {} already downloaded.".format(target_triple), end=" ")
-                print("Use |bootstrap-rust --force| to download again.")
-                continue
-
-            tarball = "rust-std-%s-%s.tar.gz" % (version, target_triple)
-            tgz_file = path.join(install_dir, tarball)
-            if self.use_stable_rust():
-                std_url = STATIC_RUST_LANG_ORG_DIST + "/" + tarball
-            else:
-                std_url = nightly_dist + "/" + tarball
-
-            download_file("Host rust library for target %s" % target_triple, std_url, tgz_file)
-            print("Extracting Rust stdlib for target %s..." % target_triple)
-            extract(tgz_file, install_dir)
-            shutil.copytree(path.join(install_dir,
-                                      "rust-std-%s-%s" % (version, target_triple),
-                                      "rust-std-%s" % target_triple,
-                                      "lib", "rustlib", target_triple),
-                            path.join(install_dir,
-                                      "rustc-%s-%s" % (version, host_triple()),
-                                      "rustc",
-                                      "lib", "rustlib", target_triple))
-            shutil.rmtree(path.join(install_dir, "rust-std-%s-%s" % (version, target_triple)))
-
-            print("Rust {} libs ready.".format(target_triple))
-
-    @Command('bootstrap-rust-docs',
-             description='Download the Rust documentation',
-             category='bootstrap')
-    @CommandArgument('--force', '-f',
-                     action='store_true',
-                     help='Force download even if docs already exist')
-    def bootstrap_rustc_docs(self, force=False):
-        self.ensure_bootstrapped()
-        rust_root = self.config["tools"]["rust-root"]
-        docs_dir = path.join(rust_root, "doc")
-        if not force and path.exists(docs_dir):
-            print("Rust docs already downloaded.", end=" ")
-            print("Use |bootstrap-rust-docs --force| to download again.")
-            return
-
-        if path.isdir(docs_dir):
-            shutil.rmtree(docs_dir)
-        docs_name = self.rust_path().replace("rustc-", "rust-docs-")
-        docs_url = "%s/%s/rust-docs-nightly-%s.tar.gz" % (
-            STATIC_RUST_LANG_ORG_DIST, self.rust_nightly_date(), host_triple()
-        )
-        tgz_file = path.join(rust_root, 'doc.tar.gz')
-
-        download_file("Rust docs", docs_url, tgz_file)
-
-        print("Extracting Rust docs...")
-        temp_dir = path.join(rust_root, "temp_docs")
-        if path.isdir(temp_dir):
-            shutil.rmtree(temp_dir)
-        extract(tgz_file, temp_dir)
-        shutil.move(path.join(temp_dir, docs_name.split("/")[1],
-                              "rust-docs", "share", "doc", "rust", "html"),
-                    docs_dir)
-        shutil.rmtree(temp_dir)
-        print("Rust docs ready.")
-
-    @Command('bootstrap-cargo',
-             description='Download the Cargo build tool',
-             category='bootstrap')
-    @CommandArgument('--force', '-f',
-                     action='store_true',
-                     help='Force download even if cargo already exists')
-    def bootstrap_cargo(self, force=False):
-        cargo_dir = path.join(self.context.sharedir, "cargo", self.rust_nightly_date())
-        if not force and path.exists(path.join(cargo_dir, "cargo", "bin", "cargo" + BIN_SUFFIX)):
-            print("Cargo already downloaded.", end=" ")
-            print("Use |bootstrap-cargo --force| to download again.")
-            return
-
-        if path.isdir(cargo_dir):
-            shutil.rmtree(cargo_dir)
-        os.makedirs(cargo_dir)
-
-        tgz_file = "cargo-nightly-%s.tar.gz" % host_triple()
-        nightly_url = "%s/%s/%s" % (STATIC_RUST_LANG_ORG_DIST, self.rust_nightly_date(), tgz_file)
-
-        download_file("Cargo nightly", nightly_url, tgz_file)
-
-        print("Extracting Cargo nightly...")
-        nightly_dir = path.join(cargo_dir,
-                                path.basename(tgz_file).replace(".tar.gz", ""))
-        extract(tgz_file, cargo_dir, movedir=nightly_dir)
-        print("Cargo ready.")
-
     @Command('update-hsts-preload',
              description='Download the HSTS preload list',
              category='bootstrap')
     def bootstrap_hsts_preload(self, force=False):
         preload_filename = "hsts_preload.json"
         preload_path = path.join(self.context.topdir, "resources")
 
         chromium_hsts_url = "https://chromium.googlesource.com/chromium/src" + \
@@ -277,61 +121,41 @@ class MachCommands(CommandBase):
              category='bootstrap')
     @CommandArgument('--force', '-f',
                      action='store_true',
                      help='Actually remove stuff')
     @CommandArgument('--keep',
                      default='1',
                      help='Keep up to this many most recent nightlies')
     def clean_nightlies(self, force=False, keep=None):
-        rust_current_nightly = self.rust_nightly_date()
-        rust_current_stable = self.rust_stable_version()
-        print("Current Rust nightly version: {}".format(rust_current_nightly))
-        print("Current Rust stable version: {}".format(rust_current_stable))
-        to_keep = set()
-        if int(keep) == 1:
-            # Optimize keep=1 case to not invoke git
-            to_keep.add(rust_current_nightly)
-            to_keep.add(rust_current_stable)
-        else:
-            for version_file in ['rust-toolchain', 'rust-stable-version']:
-                cmd = subprocess.Popen(
-                    ['git', 'log', '--oneline', '--no-color', '-n', keep, '--patch', version_file],
-                    stdout=subprocess.PIPE,
-                    universal_newlines=True
-                )
-                stdout, _ = cmd.communicate()
-                for line in stdout.splitlines():
-                    if line.startswith(b"+") and not line.startswith(b"+++"):
-                        line = line[len(b"+"):]
-                        if line.startswith(b"nightly-"):
-                            line = line[len(b"nightly-"):]
-                        to_keep.add(line)
+        default_toolchain = self.default_toolchain()
+        geckolib_toolchain = self.geckolib_toolchain()
+        print("Current Rust version for Servo: {}".format(default_toolchain))
+        print("Current Rust version for geckolib: {}".format(geckolib_toolchain))
+        old_toolchains = []
+        keep = int(keep)
+        for toolchain_file in ['rust-toolchain', 'geckolib-rust-toolchain']:
+            stdout = subprocess.check_output(['git', 'log', '--format=%H', toolchain_file])
+            for i, commit_hash in enumerate(stdout.split(), 1):
+                if i > keep:
+                    toolchain = subprocess.check_output(
+                        ['git', 'show', '%s:%s' % (commit_hash, toolchain_file)])
+                    old_toolchains.append(toolchain.strip())
 
         removing_anything = False
-        for tool in ["rust", "cargo"]:
-            base = path.join(self.context.sharedir, tool)
-            if not path.isdir(base):
-                continue
-            for name in os.listdir(base):
-                full_path = path.join(base, name)
-                if name.startswith("rust-"):
-                    name = name[len("rust-"):]
-                if name.endswith("-alt"):
-                    name = name[:-len("-alt")]
-                if name not in to_keep:
+        stdout = subprocess.check_output(['rustup', 'toolchain', 'list'])
+        for toolchain_with_host in stdout.split():
+            for old in old_toolchains:
+                if toolchain_with_host.startswith(old):
                     removing_anything = True
                     if force:
-                        print("Removing {}".format(full_path))
-                        try:
-                            delete(full_path)
-                        except OSError as e:
-                            print("Removal failed with error {}".format(e))
+                        print("Removing {}".format(toolchain_with_host))
+                        check_call(["rustup", "uninstall", toolchain_with_host])
                     else:
-                        print("Would remove {}".format(full_path))
+                        print("Would remove {}".format(toolchain_with_host))
         if not removing_anything:
             print("Nothing to remove.")
         elif not force:
             print("Nothing done. "
                   "Run `./mach clean-nightlies -f` to actually remove.")
 
     @Command('clean-cargo-cache',
              description='Clean unused Cargo packages',
--- a/servo/python/servo/build_commands.py
+++ b/servo/python/servo/build_commands.py
@@ -228,16 +228,23 @@ class MachCommands(CommandBase):
             opts += ["-v"]
         if very_verbose:
             opts += ["-vv"]
 
         if android:
             target = self.config["android"]["target"]
 
         if target:
+            if self.config["tools"]["use-rustup"]:
+                # 'rustup target add' fails if the toolchain is not installed at all.
+                self.call_rustup_run(["rustc", "--version"])
+
+                check_call(["rustup" + BIN_SUFFIX, "target", "add",
+                            "--toolchain", self.toolchain(), target])
+
             opts += ["--target", target]
             if not android:
                 android = self.handle_android_target(target)
 
         self.ensure_bootstrapped(target=target)
         self.ensure_clobbered()
 
         if debug_mozjs:
@@ -335,20 +342,17 @@ class MachCommands(CommandBase):
             env["CMAKE_ANDROID_ARCH_ABI"] = self.config["android"]["lib"]
             env["CMAKE_TOOLCHAIN_FILE"] = path.join(self.android_support_dir(), "toolchain.cmake")
             # Set output dir for gradle aar files
             aar_out_dir = self.android_aar_dir()
             if not os.path.exists(aar_out_dir):
                 os.makedirs(aar_out_dir)
             env["AAR_OUT_DIR"] = aar_out_dir
 
-        cargo_binary = "cargo" + BIN_SUFFIX
-
-        status = call(
-            [cargo_binary, "build"] + opts, env=env, verbose=verbose)
+        status = self.call_rustup_run(["cargo", "build"] + opts, env=env, verbose=verbose)
         elapsed = time() - build_start
 
         # Do some additional things if the build succeeded
         if status == 0:
             if sys.platform == "win32":
                 servo_exe_dir = path.join(base_path, "debug" if dev else "release")
                 # On windows, copy in our manifest
                 shutil.copy(path.join(self.get_top_dir(), "components", "servo", "servo.exe.manifest"),
@@ -425,19 +429,19 @@ class MachCommands(CommandBase):
 
         if with_debug_assertions:
             env["RUSTFLAGS"] = "-C debug_assertions"
 
         if is_macosx():
             # Unlike RUSTFLAGS, these are only passed in the final rustc invocation
             # so that `./mach build` followed by `./mach build-cef` both build
             # common dependencies with the same flags.
-            opts += ["--", "-C", "link-args=-Xlinker -undefined -Xlinker dynamic_lookup"]
+            opts += ["--", "-Clink-args=-Xlinker -undefined -Xlinker dynamic_lookup"]
 
-        ret = call(["cargo", "rustc"] + opts, env=env, verbose=verbose)
+        ret = self.call_rustup_run(["cargo", "rustc"] + opts, env=env, verbose=verbose)
         elapsed = time() - build_start
 
         # Generate Desktop Notification if elapsed-time > some threshold value
         notify_build_done(self.config, elapsed)
 
         print("CEF build completed in %s" % format_duration(elapsed))
 
         return ret
@@ -450,17 +454,17 @@ class MachCommands(CommandBase):
                      help='Number of jobs to run in parallel')
     @CommandArgument('--verbose', '-v',
                      action='store_true',
                      help='Print verbose output')
     @CommandArgument('--release', '-r',
                      action='store_true',
                      help='Build in release mode')
     def build_geckolib(self, jobs=None, verbose=False, release=False):
-        self.set_use_stable_rust()
+        self.set_use_geckolib_toolchain()
         self.ensure_bootstrapped()
         self.ensure_clobbered()
 
         env = self.build_env(is_build=True, geckolib=True)
 
         ret = None
         opts = ["-p", "geckoservo"]
         features = []
@@ -470,17 +474,17 @@ class MachCommands(CommandBase):
         if verbose:
             opts += ["-v"]
         if release:
             opts += ["--release"]
         if features:
             opts += ["--features", ' '.join(features)]
 
         build_start = time()
-        ret = call(["cargo", "build"] + opts, env=env, verbose=verbose)
+        ret = self.call_rustup_run(["cargo", "build"] + opts, env=env, verbose=verbose)
         elapsed = time() - build_start
 
         # Generate Desktop Notification if elapsed-time > some threshold value
         notify_build_done(self.config, elapsed)
 
         print("GeckoLib build completed in %s" % format_duration(elapsed))
 
         return ret
--- a/servo/python/servo/command_base.py
+++ b/servo/python/servo/command_base.py
@@ -2,22 +2,25 @@
 # file at the top-level directory of this distribution.
 #
 # 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.
 
+from errno import ENOENT as NO_SUCH_FILE_OR_DIRECTORY
 from glob import glob
 import gzip
 import itertools
 import locale
 import os
 from os import path
+import platform
+import re
 import contextlib
 import subprocess
 from subprocess import PIPE
 import sys
 import tarfile
 
 from mach.registrar import Registrar
 import toml
@@ -133,16 +136,28 @@ def call(*args, **kwargs):
         print(' '.join(args[0]))
     if 'env' in kwargs:
         kwargs['env'] = normalize_env(kwargs['env'])
     # we have to use shell=True in order to get PATH handling
     # when looking for the binary on Windows
     return subprocess.call(*args, shell=sys.platform == 'win32', **kwargs)
 
 
+def check_output(*args, **kwargs):
+    """Wrap `subprocess.call`, printing the command if verbose=True."""
+    verbose = kwargs.pop('verbose', False)
+    if verbose:
+        print(' '.join(args[0]))
+    if 'env' in kwargs:
+        kwargs['env'] = normalize_env(kwargs['env'])
+    # we have to use shell=True in order to get PATH handling
+    # when looking for the binary on Windows
+    return subprocess.check_output(*args, shell=sys.platform == 'win32', **kwargs)
+
+
 def check_call(*args, **kwargs):
     """Wrap `subprocess.check_call`, printing the command if verbose=True.
 
     Also fix any unicode-containing `env`, for subprocess """
     verbose = kwargs.pop('verbose', False)
 
     if 'env' in kwargs:
         kwargs['env'] = normalize_env(kwargs['env'])
@@ -249,20 +264,17 @@ class CommandBase(object):
 
         default_cargo_home = os.environ.get("CARGO_HOME",
                                             path.join(context.topdir, ".cargo"))
         self.config["tools"].setdefault("cargo-home-dir", default_cargo_home)
         resolverelative("tools", "cargo-home-dir")
 
         context.sharedir = self.config["tools"]["cache-dir"]
 
-        self.config["tools"].setdefault("system-rust", False)
-        self.config["tools"].setdefault("system-cargo", False)
-        self.config["tools"].setdefault("rust-root", "")
-        self.config["tools"].setdefault("cargo-root", "")
+        self.config["tools"].setdefault("use-rustup", True)
         self.config["tools"].setdefault("rustc-with-gold", get_env_bool("SERVO_RUSTC_WITH_GOLD", True))
 
         self.config.setdefault("build", {})
         self.config["build"].setdefault("android", False)
         self.config["build"].setdefault("mode", "")
         self.config["build"].setdefault("debug-mozjs", False)
         self.config["build"].setdefault("ccache", "")
         self.config["build"].setdefault("rustflags", "")
@@ -271,73 +283,73 @@ class CommandBase(object):
 
         self.config.setdefault("android", {})
         self.config["android"].setdefault("sdk", "")
         self.config["android"].setdefault("ndk", "")
         self.config["android"].setdefault("toolchain", "")
         # Set default android target
         self.handle_android_target("armv7-linux-androideabi")
 
-        self.set_cargo_root()
-        self.set_use_stable_rust(False)
-
-    _use_stable_rust = False
-    _rust_stable_version = None
-    _rust_nightly_date = None
+        self.set_use_geckolib_toolchain(False)
 
-    def set_cargo_root(self):
-        if not self.config["tools"]["system-cargo"]:
-            self.config["tools"]["cargo-root"] = path.join(
-                self.context.sharedir, "cargo", self.rust_nightly_date())
+    _use_geckolib_toolchain = False
+    _geckolib_toolchain = None
+    _default_toolchain = None
 
-    def set_use_stable_rust(self, use_stable_rust=True):
-        self._use_stable_rust = use_stable_rust
-        if not self.config["tools"]["system-rust"]:
-            self.config["tools"]["rust-root"] = path.join(
-                self.context.sharedir, "rust", self.rust_path())
-        if use_stable_rust:
+    def set_use_geckolib_toolchain(self, use_geckolib_toolchain=True):
+        self._use_geckolib_toolchain = use_geckolib_toolchain
+        if use_geckolib_toolchain:
             # Cargo maintainer's position is that CARGO_INCREMENTAL is a nightly-only feature
             # and should not be used on the stable channel.
             # https://github.com/rust-lang/cargo/issues/3835
             self.config["build"]["incremental"] = False
 
-    def use_stable_rust(self):
-        return self._use_stable_rust
-
-    def rust_install_dir(self):
-        if self._use_stable_rust:
-            return self.rust_stable_version()
-        else:
-            return self.rust_nightly_date()
-
-    def rust_path(self):
-        if self._use_stable_rust:
-            version = self.rust_stable_version()
+    def toolchain(self):
+        if self._use_geckolib_toolchain:
+            return self.geckolib_toolchain()
         else:
-            version = "nightly"
-
-        subdir = "rustc-%s-%s" % (version, host_triple())
-        return os.path.join(self.rust_install_dir(), subdir)
+            return self.default_toolchain()
 
-    def rust_stable_version(self):
-        if self._rust_stable_version is None:
-            filename = path.join("rust-stable-version")
+    def geckolib_toolchain(self):
+        if self._geckolib_toolchain is None:
+            filename = path.join(self.context.topdir, "geckolib-rust-toolchain")
             with open(filename) as f:
-                self._rust_stable_version = f.read().strip()
-        return self._rust_stable_version
+                self._geckolib_toolchain = f.read().strip()
+        return self._geckolib_toolchain
 
-    def rust_nightly_date(self):
-        if self._rust_nightly_date is None:
+    def default_toolchain(self):
+        if self._default_toolchain is None:
             filename = path.join(self.context.topdir, "rust-toolchain")
             with open(filename) as f:
-                toolchain = f.read().strip()
-                prefix = "nightly-"
-                assert toolchain.startswith(prefix)
-                self._rust_nightly_date = toolchain[len(prefix):]
-        return self._rust_nightly_date
+                self._default_toolchain = f.read().strip()
+        return self._default_toolchain
+
+    def call_rustup_run(self, args, **kwargs):
+        if self.config["tools"]["use-rustup"]:
+            try:
+                version_line = subprocess.check_output(["rustup" + BIN_SUFFIX, "--version"])
+            except OSError as e:
+                if e.errno == NO_SUCH_FILE_OR_DIRECTORY:
+                    print "It looks like rustup is not installed. See instructions at " \
+                          "https://github.com/servo/servo/#setting-up-your-environment"
+                    print
+                    return 1
+                raise
+            version = tuple(map(int, re.match("rustup (\d+)\.(\d+)\.(\d+)", version_line).groups()))
+            if version < (1, 8, 0):
+                print "rustup is at version %s.%s.%s, Servo requires 1.8.0 or more recent." % version
+                print "Try running 'rustup self update'."
+                return 1
+            toolchain = self.toolchain()
+            if platform.system() == "Windows":
+                toolchain += "-x86_64-pc-windows-msvc"
+            args = ["rustup" + BIN_SUFFIX, "run", "--install", toolchain] + args
+        else:
+            args[0] += BIN_SUFFIX
+        return call(args, **kwargs)
 
     def get_top_dir(self):
         return self.context.topdir
 
     def get_target_dir(self):
         if "CARGO_TARGET_DIR" in os.environ:
             return os.environ["CARGO_TARGET_DIR"]
         else:
@@ -383,17 +395,17 @@ class CommandBase(object):
             else:
                 return dev_path
 
         print("The %s profile is not built. Please run './mach build%s' "
               "and try again." % ("release" if release else "dev",
                                   " --release" if release else ""))
         sys.exit()
 
-    def build_env(self, hosts_file_path=None, target=None, is_build=False, geckolib=False):
+    def build_env(self, hosts_file_path=None, target=None, is_build=False, geckolib=False, test_unit=False):
         """Return an extended environment dictionary."""
         env = os.environ.copy()
         if sys.platform == "win32" and type(env['PATH']) == unicode:
             # On win32, the virtualenv's activate_this.py script sometimes ends up
             # turning os.environ['PATH'] into a unicode string.  This doesn't work
             # for passing env vars in to a process, so we force it back to ascii.
             # We don't use UTF8 since that won't be correct anyway; if you actually
             # have unicode stuff in your path, all this PATH munging would have broken
@@ -418,39 +430,19 @@ class CommandBase(object):
             env["MOZTOOLS_PATH"] = path.join(package_dir("moztools"), "bin")
 
         if is_windows():
             if not os.environ.get("NATIVE_WIN32_PYTHON"):
                 env["NATIVE_WIN32_PYTHON"] = sys.executable
             # Always build harfbuzz from source
             env["HARFBUZZ_SYS_NO_PKG_CONFIG"] = "true"
 
-        if not self.config["tools"]["system-rust"] \
-                or self.config["tools"]["rust-root"]:
-            env["RUST_ROOT"] = self.config["tools"]["rust-root"]
-            # These paths are for when rust-root points to an unpacked installer
-            extra_path += [path.join(self.config["tools"]["rust-root"], "rustc", "bin")]
-            extra_lib += [path.join(self.config["tools"]["rust-root"], "rustc", "lib")]
-            # These paths are for when rust-root points to a rustc sysroot
-            extra_path += [path.join(self.config["tools"]["rust-root"], "bin")]
-            extra_lib += [path.join(self.config["tools"]["rust-root"], "lib")]
-
-        if not self.config["tools"]["system-cargo"] \
-                or self.config["tools"]["cargo-root"]:
-            # This path is for when rust-root points to an unpacked installer
-            extra_path += [
-                path.join(self.config["tools"]["cargo-root"], "cargo", "bin")]
-            # This path is for when rust-root points to a rustc sysroot
-            extra_path += [
-                path.join(self.config["tools"]["cargo-root"], "bin")]
-
         if extra_path:
             env["PATH"] = "%s%s%s" % (os.pathsep.join(extra_path), os.pathsep, env["PATH"])
 
-        env["CARGO_HOME"] = self.config["tools"]["cargo-home-dir"]
         if self.config["build"]["incremental"]:
             env["CARGO_INCREMENTAL"] = "1"
 
         if extra_lib:
             if sys.platform == "darwin":
                 env["DYLD_LIBRARY_PATH"] = "%s%s%s" % \
                                            (os.pathsep.join(extra_lib),
                                             os.pathsep,
@@ -481,17 +473,20 @@ class CommandBase(object):
         if "ANDROID_NDK" in env:
             env["NDK_HOME"] = env["ANDROID_NDK"]
         if "ANDROID_TOOLCHAIN" in env:
             env["NDK_STANDALONE"] = env["ANDROID_TOOLCHAIN"]
 
         if hosts_file_path:
             env['HOST_FILE'] = hosts_file_path
 
-        env['RUSTDOCFLAGS'] = "--document-private-items"
+        if not test_unit:
+            # This wrapper script is in bash and doesn't work on Windows
+            # where we want to run doctests as part of `./mach test-unit`
+            env['RUSTDOC'] = path.join(self.context.topdir, 'etc', 'rustdoc-with-private')
 
         if self.config["build"]["rustflags"]:
             env['RUSTFLAGS'] = env.get('RUSTFLAGS', "") + " " + self.config["build"]["rustflags"]
 
         # Don't run the gold linker if on Windows https://github.com/servo/servo/issues/9499
         if self.config["tools"]["rustc-with-gold"] and sys.platform != "win32":
             if subprocess.call(['which', 'ld.gold'], stdout=PIPE, stderr=PIPE) == 0:
                 env['RUSTFLAGS'] = env.get('RUSTFLAGS', "") + " -C link-args=-fuse-ld=gold"
@@ -583,43 +578,20 @@ class CommandBase(object):
         return False
 
     def ensure_bootstrapped(self, target=None):
         if self.context.bootstrapped:
             return
 
         target_platform = target or host_triple()
 
-        rust_root = self.config["tools"]["rust-root"]
-        rustc_path = path.join(
-            rust_root, "rustc", "bin", "rustc" + BIN_SUFFIX
-        )
-        rustc_binary_exists = path.exists(rustc_path)
-
-        base_target_path = path.join(rust_root, "rustc", "lib", "rustlib")
-
-        target_path = path.join(base_target_path, target_platform)
-        target_exists = path.exists(target_path)
-
         # Always check if all needed MSVC dependencies are installed
         if "msvc" in target_platform:
             Registrar.dispatch("bootstrap", context=self.context)
 
-        if not (self.config['tools']['system-rust'] or (rustc_binary_exists and target_exists)):
-            print("Looking for rustc at %s" % (rustc_path))
-            Registrar.dispatch("bootstrap-rust", context=self.context, target=filter(None, [target]),
-                               stable=self._use_stable_rust)
-
-        cargo_path = path.join(self.config["tools"]["cargo-root"], "cargo", "bin",
-                               "cargo" + BIN_SUFFIX)
-        cargo_binary_exists = path.exists(cargo_path)
-
-        if not self.config["tools"]["system-cargo"] and not cargo_binary_exists:
-            Registrar.dispatch("bootstrap-cargo", context=self.context)
-
         self.context.bootstrapped = True
 
     def ensure_clobbered(self, target_dir=None):
         if target_dir is None:
             target_dir = self.get_target_dir()
         auto = True if os.environ.get('AUTOCLOBBER', False) else False
         src_clobber = os.path.join(self.context.topdir, 'CLOBBER')
         target_clobber = os.path.join(target_dir, 'CLOBBER')
--- a/servo/python/servo/devenv_commands.py
+++ b/servo/python/servo/devenv_commands.py
@@ -16,17 +16,17 @@ import urllib2
 import json
 
 from mach.decorators import (
     CommandArgument,
     CommandProvider,
     Command,
 )
 
-from servo.command_base import CommandBase, cd, call
+from servo.command_base import CommandBase, cd, call, BIN_SUFFIX
 from servo.build_commands import notify_build_done
 from servo.util import STATIC_RUST_LANG_ORG_DIST, URLOPEN_KWARGS
 
 
 @CommandProvider
 class MachCommands(CommandBase):
     def run_cargo(self, params, geckolib=False, check=False):
         if not params:
@@ -38,26 +38,26 @@ class MachCommands(CommandBase):
 
         if check:
             params = ['check'] + params
 
         if geckolib:
             # for c in $(cargo --list | tail -$(($(cargo --list | wc -l) - 1))); do
             #   (cargo help $c 2>&1 | grep "\\--package" >/dev/null 2>&1) && echo $c
             # done
-            if params[0] and params[0] in [
+            if params and params[0] in [
                 'bench', 'build', 'check', 'clean', 'doc', 'fmt', 'pkgid',
                 'run', 'rustc', 'rustdoc', 'test', 'update',
             ]:
                 params[1:1] = ['--package', 'geckoservo']
 
-            self.set_use_stable_rust()
+            self.set_use_geckolib_toolchain()
 
         build_start = time()
-        status = call(['cargo'] + params, env=env)
+        status = self.call_rustup_run(["cargo"] + params, env=env)
         elapsed = time() - build_start
 
         notify_build_done(self.config, elapsed, status == 0)
 
         if check and status == 0:
             print('Finished checking, binary NOT updated. Consider ./mach build before ./mach run')
 
         return status
@@ -192,54 +192,46 @@ class MachCommands(CommandBase):
             print("Please choose package to update with the --package (-p) ")
             print("flag or update all packages with --all-packages (-a) flag")
             sys.exit(1)
 
         if params or all_packages:
             self.ensure_bootstrapped()
 
             with cd(self.context.topdir):
-                call(["cargo", "update"] + params,
-                     env=self.build_env())
+                self.call_rustup_run(["cargo", "update"] + params, env=self.build_env())
 
     @Command('rustc',
              description='Run the Rust compiler',
              category='devenv')
     @CommandArgument(
         'params', default=None, nargs='...',
         help="Command-line arguments to be passed through to rustc")
     def rustc(self, params):
         if params is None:
             params = []
 
         self.ensure_bootstrapped()
-
-        return call(["rustc"] + params, env=self.build_env())
+        return self.call_rustup_run(["rustc"] + params, env=self.build_env())
 
     @Command('rustc-geckolib',
              description='Run the Rust compiler with the same compiler version and root crate as build-geckolib',
              category='devenv')
     @CommandArgument(
         'params', default=None, nargs='...',
         help="Command-line arguments to be passed through to rustc")
     def rustc_geckolib(self, params):
         if params is None:
             params = []
 
-        self.set_use_stable_rust()
+        self.set_use_geckolib_toolchain()
         self.ensure_bootstrapped()
         env = self.build_env(geckolib=True)
 
-        return call(["rustc"] + params, env=env)
-
-    @Command('rust-root',
-             description='Print the path to the root of the Rust compiler',
-             category='devenv')
-    def rust_root(self):
-        print(self.config["tools"]["rust-root"])
+        return self.call_rustup_run(["rustc"] + params, env=env)
 
     @Command('grep',
              description='`git grep` for selected directories.',
              category='devenv')
     @CommandArgument(
         'params', default=None, nargs='...',
         help="Command-line arguments to be passed through to `git grep`")
     def grep(self, params):
@@ -262,29 +254,22 @@ class MachCommands(CommandBase):
             env=self.build_env())
 
     @Command('rustup',
              description='Update the Rust version to latest Nightly',
              category='devenv')
     def rustup(self):
         url = STATIC_RUST_LANG_ORG_DIST + "/channel-rust-nightly-date.txt"
         nightly_date = urllib2.urlopen(url, **URLOPEN_KWARGS).read()
+        toolchain = "nightly-" + nightly_date
         filename = path.join(self.context.topdir, "rust-toolchain")
         with open(filename, "w") as f:
-            f.write("nightly-%s\n" % nightly_date)
-
-        # Reset self.config["tools"]["rust-root"] and self.config["tools"]["cargo-root"]
-        self._rust_nightly_date = None
-        self.set_use_stable_rust(False)
-        self.set_cargo_root()
-
-        self.fetch()
+            f.write(toolchain + "\n")
+        return call(["rustup" + BIN_SUFFIX, "install", toolchain])
 
     @Command('fetch',
              description='Fetch Rust, Cargo and Cargo dependencies',
              category='devenv')
     def fetch(self):
-        # Fetch Rust and Cargo
         self.ensure_bootstrapped()
 
-        # Fetch Cargo dependencies
         with cd(self.context.topdir):
-            call(["cargo", "fetch"], env=self.build_env())
+            return self.call_rustup_run(["cargo", "fetch"], env=self.build_env())
--- a/servo/python/servo/post_build_commands.py
+++ b/servo/python/servo/post_build_commands.py
@@ -9,27 +9,25 @@
 
 from __future__ import print_function, unicode_literals
 
 import os
 import os.path as path
 import subprocess
 from shutil import copytree, rmtree, copy2
 
-from mach.registrar import Registrar
-
 from mach.decorators import (
     CommandArgument,
     CommandProvider,
     Command,
 )
 
 from servo.command_base import (
     CommandBase,
-    call, check_call,
+    check_call, check_output, BIN_SUFFIX,
     is_linux, is_windows, is_macosx, set_osmesa_env,
     get_browserhtml_path,
 )
 
 
 def read_file(filename, if_exists=False):
     if if_exists and not path.exists(filename):
         return None
@@ -206,20 +204,24 @@ class PostBuildCommands(CommandBase):
 
     @Command('doc',
              description='Generate documentation',
              category='post-build')
     @CommandArgument(
         'params', nargs='...',
         help="Command-line arguments to be passed through to cargo doc")
     def doc(self, params):
+        env = os.environ.copy()
+        env["RUSTUP_TOOLCHAIN"] = self.toolchain()
+        rustc_path = check_output(["rustup" + BIN_SUFFIX, "which", "rustc"], env=env)
+        assert path.basename(path.dirname(rustc_path)) == "bin"
+        toolchain_path = path.dirname(path.dirname(rustc_path))
+        rust_docs = path.join(toolchain_path, "share", "doc", "rust", "html")
+
         self.ensure_bootstrapped()
-        if not path.exists(path.join(self.config["tools"]["rust-root"], "doc")):
-            Registrar.dispatch("bootstrap-rust-docs", context=self.context)
-        rust_docs = path.join(self.config["tools"]["rust-root"], "doc")
         docs = path.join(self.get_target_dir(), "doc")
         if not path.exists(docs):
             os.makedirs(docs)
 
         if read_file(path.join(docs, "version_info.html"), if_exists=True) != \
                 read_file(path.join(rust_docs, "version_info.html")):
             print("Copying Rust documentation.")
             # copytree doesn't like the destination already existing.
@@ -229,18 +231,20 @@ class PostBuildCommands(CommandBase):
                     destination = path.join(docs, name)
                     if path.isdir(full_name):
                         if path.exists(destination):
                             rmtree(destination)
                         copytree(full_name, destination)
                     else:
                         copy2(full_name, destination)
 
-        return call(["cargo", "doc"] + params,
-                    env=self.build_env(), cwd=self.servo_crate())
+        return self.call_rustup_run(
+            ["cargo", "doc", "--manifest-path", self.servo_manifest()] + params,
+            env=self.build_env()
+        )
 
     @Command('browse-doc',
              description='Generate documentation and open it in a web browser',
              category='post-build')
     def serve_docs(self):
         self.doc([])
         import webbrowser
         webbrowser.open("file://" + path.abspath(path.join(
--- a/servo/python/servo/testing_commands.py
+++ b/servo/python/servo/testing_commands.py
@@ -29,17 +29,17 @@ from mach.registrar import Registrar
 from mach.decorators import (
     CommandArgument,
     CommandProvider,
     Command,
 )
 
 from servo.command_base import (
     BuildNotFound, CommandBase,
-    call, cd, check_call, set_osmesa_env,
+    call, check_call, set_osmesa_env,
 )
 from servo.util import host_triple
 
 from wptrunner import wptcommandline
 from update import updatecommandline
 from servo_tidy import tidy
 from servo_tidy_tests import test_tidy
 
@@ -250,65 +250,64 @@ class MachCommands(CommandBase):
         try:
             packages.remove('selectors')
             in_crate_packages += ["selectors"]
         except KeyError:
             pass
 
         packages.discard('stylo')
 
-        env = self.build_env()
+        env = self.build_env(test_unit=True)
         env["RUST_BACKTRACE"] = "1"
 
-        # Work around https://github.com/rust-lang/cargo/issues/4790
-        del env["RUSTDOCFLAGS"]
-
         if "msvc" in host_triple():
             # on MSVC, we need some DLLs in the path. They were copied
             # in to the servo.exe build dir, so just point PATH to that.
             env["PATH"] = "%s%s%s" % (path.dirname(self.get_binary_path(False, False)), os.pathsep, env["PATH"])
 
         features = self.servo_features()
         if len(packages) > 0:
-            args = ["cargo", "bench" if bench else "test"]
+            args = ["cargo", "bench" if bench else "test", "--manifest-path", self.servo_manifest()]
             for crate in packages:
                 args += ["-p", "%s_tests" % crate]
             for crate in in_crate_packages:
                 args += ["-p", crate]
             args += test_patterns
 
             if features:
                 args += ["--features", "%s" % ' '.join(features)]
 
             if nocapture:
                 args += ["--", "--nocapture"]
 
-            err = call(args, env=env, cwd=self.servo_crate())
+            err = self.call_rustup_run(args, env=env)
             if err is not 0:
                 return err
 
     @Command('test-stylo',
              description='Run stylo unit tests',
              category='testing')
     @CommandArgument('test_name', nargs=argparse.REMAINDER,
                      help="Only run tests that match this pattern or file path")
     @CommandArgument('--release', default=False, action="store_true",
                      help="Run with a release build of servo")
     def test_stylo(self, release=False, test_name=None):
-        self.set_use_stable_rust()
+        self.set_use_geckolib_toolchain()
         self.ensure_bootstrapped()
 
         env = self.build_env()
         env["RUST_BACKTRACE"] = "1"
         env["CARGO_TARGET_DIR"] = path.join(self.context.topdir, "target", "geckolib").encode("UTF-8")
 
-        args = (["cargo", "test", "-p", "stylo_tests"] +
-                (["--release"] if release else []) + (test_name or []))
-        with cd(path.join("ports", "geckolib")):
-            return call(args, env=env)
+        args = (
+            ["cargo", "test", "--manifest-path", self.geckolib_manifest(), "-p", "stylo_tests"] +
+            (["--release"] if release else []) +
+            (test_name or [])
+        )
+        return self.call_rustup_run(args, env=env)
 
     @Command('test-content',
              description='Run the content tests',
              category='testing')
     def test_content(self):
         print("Content tests have been replaced by web-platform-tests under "
               "tests/wpt/mozilla/.")
         return 0
--- a/servo/python/servo/util.py
+++ b/servo/python/servo/util.py
@@ -11,17 +11,16 @@ from __future__ import absolute_import, 
 
 import os
 import os.path
 import platform
 import shutil
 from socket import error as socket_error
 import StringIO
 import sys
-import tarfile
 import zipfile
 import urllib2
 import certifi
 
 
 try:
     from ssl import HAS_SNI
 except ImportError:
@@ -143,20 +142,18 @@ def download_file(desc, src, dst):
             download(desc, src, fd, start_byte=start_byte)
     except os.error:
         with open(tmp_path, 'wb') as fd:
             download(desc, src, fd)
     os.rename(tmp_path, dst)
 
 
 def extract(src, dst, movedir=None):
-    if src.endswith(".zip"):
-        zipfile.ZipFile(src).extractall(dst)
-    else:
-        tarfile.open(src).extractall(dst)
+    assert src.endswith(".zip")
+    zipfile.ZipFile(src).extractall(dst)
 
     if movedir:
         for f in os.listdir(movedir):
             frm = os.path.join(movedir, f)
             to = os.path.join(dst, f)
             os.rename(frm, to)
         os.rmdir(movedir)
 
--- a/servo/servobuild.example
+++ b/servo/servobuild.example
@@ -1,32 +1,21 @@
 # Copy this file to .servobuild in the Servo root directory
 # Be sure to set the cache-dir correctly, otherwise extra
 # copies of the Rust compiler may get downloaded
 
 # Paths starting with "./" are relative to the repo root
 
 # Tool options
 [tools]
-# Where Rust compiler and other downloads will be stored.  Can be
-# shared by multiple Servo repositories.  Defaults to <servo-repo>/.servo
-cache-dir = "./.servo"
-
-# Where Cargo stores all of its clones  Defaults to <servo-repo>/.cargo
-cargo-home-dir = "./.cargo"
-
-# If system-rust is true, will use rustc/rustdoc from the path, or if
-# rust-root is specified, will make sure that rust-root is in the path
-# when building. Similarly for Cargo. This takes care of PATH as well as
-# [DY]LD_LIBRARY_PATH.
-# rust-root and cargo-root default to <servo-repo>/
-system-rust = false
-#rust-root = "/path/to/rust"
-system-cargo = false
-#cargo-root = "/path/to/cargo"
+# If use-rustup is set to false, mach will run for example "cargo build"
+# instead of "rustup run --install <toolchain> cargo build"
+# It is then the user’s responsibility to ensure that cargo and especially rustc
+# in $PATH are versions compatible with Servo.
+use-rustup = true
 
 # If rustc-with-gold is true, will try to find and use gold linker with rustc.
 # Defaults to true
 rustc-with-gold = true
 
 # If uncommented, this command is used instead of the platform’s default
 # to notify at the end of a compilation that took a long time.
 # This is the name or path of an executable called with two arguments: