Bug 1286799 - mozboot: Install or upgrade rust. r?gps draft
authorRalph Giles <giles@mozilla.com>
Thu, 17 Nov 2016 12:06:29 -0800
changeset 442188 75cf121d2dff55c21de7a4ee5e08efecbb6c2c1a
parent 442122 7e3b93919bbd859d6c6ca63aaf659c8f1f4844ba
child 442189 1b7793724c6f8044abcb4cee74e3a4964eb61ef1
push id36622
push userbmo:giles@thaumas.net
push dateTue, 22 Nov 2016 01:00:21 +0000
reviewersgps
bugs1286799
milestone53.0a1
Bug 1286799 - mozboot: Install or upgrade rust. r?gps Download and run a known-good rustup-init installer for the host system. Once installed, use it to upgrade the latest toolchain. NB: I expect the MozillaBuildBootstrapper to run its installer first, but this will take care of Mac, Linux, and FreeBSD. MozReview-Commit-ID: BKDm1UcLxQS
python/mozboot/mozboot/base.py
python/mozboot/mozboot/rust.py
--- a/python/mozboot/mozboot/base.py
+++ b/python/mozboot/mozboot/base.py
@@ -7,17 +7,17 @@ from __future__ import print_function, u
 import hashlib
 import os
 import re
 import subprocess
 import sys
 import urllib2
 
 from distutils.version import LooseVersion
-
+from mozboot import rust
 
 NO_MERCURIAL = '''
 Could not find Mercurial (hg) in the current shell's path. Try starting a new
 shell and running the bootstrapper again.
 '''
 
 MERCURIAL_UNABLE_UPGRADE = '''
 You are currently running Mercurial %s. Running %s or newer is
@@ -92,16 +92,34 @@ and upgrade Rust programming language su
 its output. It may be an old version, or not be the installer from
 https://rustup.rs/
 
 Please move it out of the way and run the bootstrap script again.
 Or if you prefer and know how, use the current rustup to install
 a compatible version of the Rust programming language yourself.
 '''
 
+RUST_UPGRADE_FAILED = '''
+We attempted to upgrade Rust to a modern version (%s or newer).
+However, you appear to still have version %s.
+
+It's possible rustup failed. It's also possible the new Rust is not being
+installed in the search path for this shell. Try creating a new shell and
+run this bootstrapper again.
+
+If this continues to fail and you are sure you have a modern Rust on your
+system, ensure it is on the $PATH and try again. If that fails, you'll need to
+install Rust manually and ensure the path with the rustc and cargo  binaries
+are listed in the $PATH environment variable.
+
+We recommend the installer from https://rustup.rs/ for installing Rust,
+but you may be able to get a recent enough version from a software install
+tool or package manager on your system, or directly from https://rust-lang.org/
+'''
+
 BROWSER_ARTIFACT_MODE_MOZCONFIG = '''
 Paste the lines between the chevrons (>>> and <<<) into your mozconfig file:
 
 <<<
 # Automatically download and use compiled C++ components:
 ac_add_options --enable-artifact-builds
 >>>
 '''
@@ -476,19 +494,19 @@ class BaseBootstrapper(object):
     def upgrade_python(self, current):
         """Upgrade Python.
 
         Child classes should reimplement this.
         """
         print(PYTHON_UNABLE_UPGRADE % (current, MODERN_PYTHON_VERSION))
 
     def is_rust_modern(self):
-        rust = self.which('rustc')
-        if not rust:
-            print('Could not find rust compiler.')
+        rustc = self.which('rustc')
+        if not rustc:
+            print('Could not find a Rust compiler.')
             return False, None
 
         cargo = self.which('cargo')
 
         our = self._parse_version(rust)
         if not our:
             return False, None
 
@@ -497,46 +515,91 @@ class BaseBootstrapper(object):
     def ensure_rust_modern(self):
         modern, version = self.is_rust_modern()
 
         if modern:
             print('Your version of Rust (%s) is new enough.' % version)
             return
 
         if not version:
-            '''Rust wasn't in PATH. Try a few things.'''
+            # Rust wasn't in PATH. Try the standard path.
             cargo_home = os.environ.get('CARGO_HOME',
                     os.path.expanduser(os.path.join('~', '.cargo')))
             cargo_bin = os.path.join(cargo_home, 'bin')
             have_rustc = os.path.exists(os.path.join(cargo_bin, 'rustc'))
             have_cargo = os.path.exists(os.path.join(cargo_bin, 'cargo'))
             if have_rustc or have_cargo:
                 print(RUST_NOT_IN_PATH % { 'cargo_bin': cargo_bin,
                                            'cargo_home': cargo_home })
                 sys.exit(1)
 
-            rustup = self.which('rustup')
-            if rustup:
-                print('Found rustup.')
-                version = self._parse_version(rustup)
-                if not version:
-                    print(RUSTUP_OLD)
-                    sys.exit(1)
-
-            print('Please download and run the installer from https://rustup.rs/')
-            sys.exit(1)
-
-        # TODO: Upgrade rust.
+        rustup = self.which('rustup')
+        if rustup:
+            rustup_version = self._parse_version(rustup)
+            if not rustup_version:
+                print(RUSTUP_OLD)
+                sys.exit(1)
+            if not version:
+                # We have rustup but no rustc.
+                # Try running rustup; maybe it will fix things.
+                print('Found rustup. Will try to upgrade.')
+            else:
+                # We have both rustup and rustc.
+                print('Your version of Rust (%s) is too old. Will try to upgrade.' %
+                  version)
+            self.upgrade_rust(rustup)
+        else:
+            # No rustc or rustup.
+            print('Will try to install Rust.')
+            self.install_rust()
 
         modern, after = self.is_rust_modern()
 
         if not modern:
             print(RUST_UPGRADE_FAILED % (MODERN_RUST_VERSION, after))
             sys.exit(1)
 
+    def upgrade_rust(self, rustup):
+        """Upgrade Rust.
+
+        Invoke rustup from the given path to update the rust install."""
+        subprocess.check_call([rustup, 'update'])
+
+    def install_rust(self):
+        """Download and run the rustup installer."""
+        import errno
+        import stat
+        import tempfile
+        platform = rust.platform()
+        url = rust.rustup_url(platform)
+        checksum = rust.rustup_hash(platform)
+        if not url or not checksum:
+            print('ERROR: Could not download installer.')
+            sys.exit(1)
+        print('Downloading rustup-init... ', end='')
+        fd, rustup_init = tempfile.mkstemp(prefix=os.path.basename(url))
+        os.close(fd)
+        try:
+            self.http_download_and_save(url, rustup_init, checksum)
+            mode = os.stat(rustup_init).st_mode
+            os.chmod(rustup_init, mode | stat.S_IRWXU)
+            print('Ok')
+            print('Running rustup-init...')
+            subprocess.check_call([rustup_init, '-y',
+                '--default-toolchain', 'stable',
+                '--default-host', platform,
+            ])
+            print('Rust installation complete.')
+        finally:
+            try:
+                os.remove(rustup_init)
+            except OSError as e:
+                if e.errno != errno.ENOENT:
+                    raise
+
     def http_download_and_save(self, url, dest, sha256hexhash):
         f = urllib2.urlopen(url)
         h = hashlib.sha256()
         with open(dest, 'wb') as out:
             while True:
                 data = f.read(4096)
                 if data:
                     out.write(data)
new file mode 100644
--- /dev/null
+++ b/python/mozboot/mozboot/rust.py
@@ -0,0 +1,69 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this,
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+# If we add unicode_literals, Python 2.6.1 (required for OS X 10.6) breaks.
+from __future__ import print_function
+
+import errno
+import os
+import stat
+import subprocess
+import sys
+
+# Base url for pulling the rustup installer.
+RUSTUP_URL_BASE = 'https://static.rust-lang.org/rustup'
+
+# Pull this to get the lastest stable version number.
+RUSTUP_MANIFEST = os.path.join(RUSTUP_URL_BASE, 'release-stable.toml')
+
+# We bake in a known version number so we can verify a checksum.
+RUSTUP_VERSION = '0.6.5'
+
+# SHA-256 checksums of the installers, per platform.
+RUSTUP_HASHES = {
+    'x86_64-apple-darwin':
+        '6404ab0a92c1559bac279a20d31be9166c91434f8e7ff8d1a97bcbe4dbd3cadc',
+    'x86_64-pc-windows-msvc':
+        '772579edcbc9a480a61fb19ace49527839e7f919e1041bcc2dee2a4ff82d3ca2',
+    'x86_64-unknown-linux-gnu':
+        'e901e23ee48c3a24470d997c4376d8835cecca51bf2636dcd419821d4056d823',
+    'x86_64-unknown-freebsd':
+        '63b7c0f35a811993c94af85b96abdd3dcca847d260af284f888e91c2ffdb374e',
+}
+
+NO_PLATFORM = '''
+Sorry, we have no installer configured for your platform.
+
+Please try installing rust for your system from https://rustup.rs/
+or from https://rust-lang.org/ or from your package manager.
+'''
+
+def rustup_url(host, version=RUSTUP_VERSION):
+    '''Download url for a particular version of the installer.'''
+    ext = ''
+    if 'windows' in host:
+        ext = '.exe'
+    return os.path.join(RUSTUP_URL_BASE,
+        'archive/%(version)s/%(host)s/rustup-init%(ext)s') % {
+                'version': version,
+                'host': host,
+                'ext': ext}
+
+def rustup_hash(host):
+    '''Look up the checksum for the given installer.'''
+    return RUSTUP_HASHES.get(host, None)
+
+def platform():
+    '''Determine the appropriate rust platform string for the current host'''
+    if sys.platform.startswith('darwin'):
+        return 'x86_64-apple-darwin'
+    elif sys.platform.startswith(('win32', 'msys')):
+        # Bravely assume we'll be building 64-bit Firefox.
+        return 'x86_64-pc-windows-msvc'
+    elif sys.platform.startswith('linux'):
+        return 'x86_64-unknown-linux-gnu'
+    elif sys.platform.startswith('freebsd'):
+        return 'x86_64-unknown-freebsd'
+
+    return None