因果応報

忘れないうちに書き留める

Jan 21, 2019 - 26 minute read - Comments - rust

[Memo] Distribute Rustc Flow

Reading rust building flow.

Entry point

rust-lang/rust

  • .travis.yml
  • x.py
  • bootstrap.py

.travis.yml

language: shell
sudo: required
dist: xenial
services:
  - docker
addons:
  apt:
    packages:
      - gdb

git:
  depth: 2
  submodules: false

matrix:
  fast_finish: true
  include:
    # Images used in testing PR and try-build should be run first.
    - env: IMAGE=x86_64-gnu-llvm-6.0 RUST_BACKTRACE=1
      if: type = pull_request OR branch = auto

    - env: IMAGE=dist-x86_64-linux DEPLOY=1
      if: branch = try OR branch = auto

    # "alternate" deployments, these are "nightlies" but have LLVM assertions
    # turned on, they're deployed to a different location primarily for
    # additional testing.
    - env: IMAGE=dist-x86_64-linux DEPLOY_ALT=1 CI_JOB_NAME=dist-x86_64-linux-alt
      if: branch = try OR branch = auto

    - env: >
        RUST_CHECK_TARGET=dist
        RUST_CONFIGURE_ARGS="--enable-extended --enable-profiler --enable-lldb --set rust.jemalloc"
        SRC=.
        DEPLOY_ALT=1
        RUSTC_RETRY_LINKER_ON_SEGFAULT=1
        MACOSX_DEPLOYMENT_TARGET=10.7
        NO_LLVM_ASSERTIONS=1
        NO_DEBUG_ASSERTIONS=1
        CI_JOB_NAME=dist-x86_64-apple-alt        
      os: osx
      osx_image: xcode9.3-moar
      if: branch = auto

    # macOS builders. These are placed near the beginning because they are very
    # slow to run.

    # OSX builders running tests, these run the full test suite.
    # NO_DEBUG_ASSERTIONS=1 to make them go faster, but also do have some
    # runners that run `//ignore-debug` tests.
    #
    # Note that the compiler is compiled to target 10.8 here because the Xcode
    # version that we're using, 8.2, cannot compile LLVM for OSX 10.7.
    - env: >
        RUST_CHECK_TARGET=check
        RUST_CONFIGURE_ARGS="--build=x86_64-apple-darwin --enable-sanitizers --enable-profiler --set rust.jemalloc"
        SRC=.
        RUSTC_RETRY_LINKER_ON_SEGFAULT=1
        MACOSX_DEPLOYMENT_TARGET=10.8
        MACOSX_STD_DEPLOYMENT_TARGET=10.7
        NO_LLVM_ASSERTIONS=1
        NO_DEBUG_ASSERTIONS=1
        CI_JOB_NAME=x86_64-apple        
      os: osx
      osx_image: xcode9.3-moar
      if: branch = auto

    - env: >
        RUST_CHECK_TARGET=check
        RUST_CONFIGURE_ARGS="--build=i686-apple-darwin --set rust.jemalloc"
        SRC=.
        RUSTC_RETRY_LINKER_ON_SEGFAULT=1
        MACOSX_DEPLOYMENT_TARGET=10.8
        MACOSX_STD_DEPLOYMENT_TARGET=10.7
        NO_LLVM_ASSERTIONS=1
        NO_DEBUG_ASSERTIONS=1
        CI_JOB_NAME=i686-apple        
      os: osx
      osx_image: xcode9.3-moar
      if: branch = auto

    # OSX builders producing releases. These do not run the full test suite and
    # just produce a bunch of artifacts.
    #
    # Note that these are running in the `xcode7` image instead of the
    # `xcode8.2` image as above. That's because we want to build releases for
    # OSX 10.7 and `xcode7` is the latest Xcode able to compile LLVM for 10.7.
    - env: >
        RUST_CHECK_TARGET=dist
        RUST_CONFIGURE_ARGS="--build=i686-apple-darwin --enable-full-tools --enable-profiler --enable-lldb --set rust.jemalloc"
        SRC=.
        DEPLOY=1
        RUSTC_RETRY_LINKER_ON_SEGFAULT=1
        MACOSX_DEPLOYMENT_TARGET=10.7
        NO_LLVM_ASSERTIONS=1
        NO_DEBUG_ASSERTIONS=1
        DIST_REQUIRE_ALL_TOOLS=1
        CI_JOB_NAME=dist-i686-apple        
      os: osx
      osx_image: xcode9.3-moar
      if: branch = auto

    - env: >
        RUST_CHECK_TARGET=dist
        RUST_CONFIGURE_ARGS="--target=aarch64-apple-ios,armv7-apple-ios,armv7s-apple-ios,i386-apple-ios,x86_64-apple-ios --enable-full-tools --enable-sanitizers --enable-profiler --enable-lldb --set rust.jemalloc"
        SRC=.
        DEPLOY=1
        RUSTC_RETRY_LINKER_ON_SEGFAULT=1
        MACOSX_DEPLOYMENT_TARGET=10.7
        NO_LLVM_ASSERTIONS=1
        NO_DEBUG_ASSERTIONS=1
        DIST_REQUIRE_ALL_TOOLS=1
        CI_JOB_NAME=dist-x86_64-apple        
      os: osx
      osx_image: xcode9.3-moar
      if: branch = auto

    # Linux builders, remaining docker images
    - env: IMAGE=x86_64-gnu
      if: branch = auto
    - env: IMAGE=x86_64-gnu-full-bootstrap
      if: branch = auto
    - env: IMAGE=x86_64-gnu-aux
      if: branch = auto
    - env: IMAGE=x86_64-gnu-tools
      if: branch = auto OR (type = pull_request AND commit_message =~ /(?i:^update.*\b(rls|rustfmt|clippy|miri|cargo)\b)/)
    - env: IMAGE=x86_64-gnu-debug
      if: branch = auto
    - env: IMAGE=x86_64-gnu-nopt
      if: branch = auto
    - env: IMAGE=x86_64-gnu-distcheck
      if: branch = auto
    - env: IMAGE=mingw-check
      if: type = pull_request OR branch = auto

    - stage: publish toolstate
      if: branch = master AND type = push
      before_install: []
      install: []
      sudo: false
      script:
        MESSAGE_FILE=$(mktemp -t msg.XXXXXX);
        . src/ci/docker/x86_64-gnu-tools/repo.sh;
        commit_toolstate_change "$MESSAGE_FILE" "$TRAVIS_BUILD_DIR/src/tools/publish_toolstate.py" "$(git rev-parse HEAD)" "$(git log --format=%s -n1 HEAD)" "$MESSAGE_FILE" "$TOOLSTATE_REPO_ACCESS_TOKEN";

before_install:
  # We'll use the AWS cli to download/upload cached docker layers as well as
  # push our deployments, so download that here.
  - pip install --user awscli; export PATH=$PATH:$HOME/.local/bin:$HOME/Library/Python/2.7/bin/
  - mkdir -p $HOME/rustsrc
  # FIXME(#46924): these two commands are required to enable IPv6,
  # they shouldn't exist, please revert once more official solutions appeared.
  # see https://github.com/travis-ci/travis-ci/issues/8891#issuecomment-353403729
  - if [ "$TRAVIS_OS_NAME" = linux ]; then
      echo '{"ipv6":true,"fixed-cidr-v6":"fd9a:8454:6789:13f7::/64"}' | sudo tee /etc/docker/daemon.json;
      sudo service docker restart;
    fi

install:
  - case "$TRAVIS_OS_NAME" in
        linux)
          travis_retry curl -fo $HOME/stamp https://s3-us-west-1.amazonaws.com/rust-lang-ci2/rust-ci-mirror/2017-03-17-stamp-x86_64-unknown-linux-musl &&
            chmod +x $HOME/stamp &&
            export PATH=$PATH:$HOME
          ;;
        osx)
          if [[ "$RUST_CHECK_TARGET" == dist ]]; then
            travis_retry brew update &&
            travis_retry brew install xz &&
            travis_retry brew install swig;
          fi &&
          travis_retry curl -fo /usr/local/bin/sccache https://s3-us-west-1.amazonaws.com/rust-lang-ci2/rust-ci-mirror/2018-04-02-sccache-x86_64-apple-darwin &&
            chmod +x /usr/local/bin/sccache &&
          travis_retry curl -fo /usr/local/bin/stamp https://s3-us-west-1.amazonaws.com/rust-lang-ci2/rust-ci-mirror/2017-03-17-stamp-x86_64-apple-darwin &&
            chmod +x /usr/local/bin/stamp &&
          travis_retry curl -f http://releases.llvm.org/7.0.0/clang+llvm-7.0.0-x86_64-apple-darwin.tar.xz | tar xJf - &&
            export CC=`pwd`/clang+llvm-7.0.0-x86_64-apple-darwin/bin/clang &&
            export CXX=`pwd`/clang+llvm-7.0.0-x86_64-apple-darwin/bin/clang++ &&
            export AR=ar
          ;;
    esac

before_script:
  - >
      echo "#### Disk usage before running script:";
      df -h;
      du . | sort -nr | head -n100      
  - >
      RUN_SCRIPT="src/ci/init_repo.sh . $HOME/rustsrc";
      if [ "$TRAVIS_OS_NAME" = "osx" ]; then
          export RUN_SCRIPT="$RUN_SCRIPT && src/ci/run.sh";
      else
          export RUN_SCRIPT="$RUN_SCRIPT && src/ci/docker/run.sh $IMAGE";
          # Enable core dump on Linux.
          sudo sh -c 'echo "/checkout/obj/cores/core.%p.%E" > /proc/sys/kernel/core_pattern';
      fi      
  - >
      if [ "$IMAGE" = mingw-check ]; then
        # verify the publish_toolstate script works.
        git clone --depth=1 https://github.com/rust-lang-nursery/rust-toolstate.git;
        cd rust-toolstate;
        python2.7 "$TRAVIS_BUILD_DIR/src/tools/publish_toolstate.py" "$(git rev-parse HEAD)" "$(git log --format=%s -n1 HEAD)" "" "";
        cd ..;
        rm -rf rust-toolstate;
      fi      

# Log time information from this machine and an external machine for insight into possible
# clock drift. Timezones don't matter since relative deltas give all the necessary info.
script:
  - >
            date && (curl -fs --head https://google.com | grep ^Date: | sed 's/Date: //g' || true)
  - stamp sh -x -c "$RUN_SCRIPT"
  - >
            date && (curl -fs --head https://google.com | grep ^Date: | sed 's/Date: //g' || true)

after_success:
  - >
      echo "#### Build successful; Disk usage after running script:";
      df -h;
      du . | sort -nr | head -n100      
  - >
      if [ "$DEPLOY$DEPLOY_ALT" == "1" ]; then
        mkdir -p deploy/$TRAVIS_COMMIT;
        if [ "$TRAVIS_OS_NAME" == "osx" ]; then
            rm -rf build/dist/doc &&
            cp -r build/dist/* deploy/$TRAVIS_COMMIT;
        else
            rm -rf obj/build/dist/doc &&
            cp -r obj/build/dist/* deploy/$TRAVIS_COMMIT;
        fi;
        ls -la deploy/$TRAVIS_COMMIT;
        deploy_dir=rustc-builds;
        if [ "$DEPLOY_ALT" == "1" ]; then
            deploy_dir=rustc-builds-alt;
        fi;
        travis_retry aws s3 cp --no-progress --recursive --acl public-read ./deploy s3://rust-lang-ci2/$deploy_dir
      fi      

after_failure:
  - >
      echo "#### Build failed; Disk usage after running script:";
      df -h;
      du . | sort -nr | head -n100      

  # Random attempt at debugging currently. Just poking around in here to see if
  # anything shows up.

  # Dump backtrace for macOS
  - ls -lat $HOME/Library/Logs/DiagnosticReports/
  - find $HOME/Library/Logs/DiagnosticReports
      -type f
      -name '*.crash'
      -not -name '*.stage2-*.crash'
      -not -name 'com.apple.CoreSimulator.CoreSimulatorService-*.crash'
      -exec printf travis_fold":start:crashlog\n\033[31;1m%s\033[0m\n" {} \;
      -exec head -750 {} \;
      -exec echo travis_fold":"end:crashlog \; || true

  # Dump backtrace for Linux
  - ln -s . checkout &&
    for CORE in obj/cores/core.*; do
      EXE=$(echo $CORE | sed 's|obj/cores/core\.[0-9]*\.!checkout!\(.*\)|\1|;y|!|/|');
      if [ -f "$EXE" ]; then
        printf travis_fold":start:crashlog\n\033[31;1m%s\033[0m\n" "$CORE";
        gdb --batch -q -c "$CORE" "$EXE"
          -iex 'set auto-load off'
          -iex 'dir src/'
          -iex 'set sysroot .'
          -ex bt
          -ex q;
        echo travis_fold":"end:crashlog;
      fi;
    done || true

  # see #50887
  - cat ./obj/build/x86_64-unknown-linux-gnu/native/asan/build/lib/asan/clang_rt.asan-dynamic-i386.vers || true

  # attempt to debug anything killed by the oom killer on linux, just to see if
  # it happened
  - dmesg | grep -i kill

notifications:
  email: false

src/bootstrap/bootstrap.py

from __future__ import absolute_import, division, print_function
import argparse
import contextlib
import datetime
import hashlib
import os
import re
import shutil
import subprocess
import sys
import tarfile
import tempfile

from time import time


def get(url, path, verbose=False):
    suffix = '.sha256'
    sha_url = url + suffix
    with tempfile.NamedTemporaryFile(delete=False) as temp_file:
        temp_path = temp_file.name
    with tempfile.NamedTemporaryFile(suffix=suffix, delete=False) as sha_file:
        sha_path = sha_file.name

    try:
        download(sha_path, sha_url, False, verbose)
        if os.path.exists(path):
            if verify(path, sha_path, False):
                if verbose:
                    print("using already-download file", path)
                return
            else:
                if verbose:
                    print("ignoring already-download file",
                          path, "due to failed verification")
                os.unlink(path)
        download(temp_path, url, True, verbose)
        if not verify(temp_path, sha_path, verbose):
            raise RuntimeError("failed verification")
        if verbose:
            print("moving {} to {}".format(temp_path, path))
        shutil.move(temp_path, path)
    finally:
        delete_if_present(sha_path, verbose)
        delete_if_present(temp_path, verbose)


def delete_if_present(path, verbose):
    """Remove the given file if present"""
    if os.path.isfile(path):
        if verbose:
            print("removing", path)
        os.unlink(path)


def download(path, url, probably_big, verbose):
    for _ in range(0, 4):
        try:
            _download(path, url, probably_big, verbose, True)
            return
        except RuntimeError:
            print("\nspurious failure, trying again")
    _download(path, url, probably_big, verbose, False)


def _download(path, url, probably_big, verbose, exception):
    if probably_big or verbose:
        print("downloading {}".format(url))
    # see http://serverfault.com/questions/301128/how-to-download
    if sys.platform == 'win32':
        run(["PowerShell.exe", "/nologo", "-Command",
             "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12;",
             "(New-Object System.Net.WebClient).DownloadFile('{}', '{}')".format(url, path)],
            verbose=verbose,
            exception=exception)
    else:
        if probably_big or verbose:
            option = "-#"
        else:
            option = "-s"
        run(["curl", option,
             "-y", "30", "-Y", "10",    # timeout if speed is < 10 bytes/sec for > 30 seconds
             "--connect-timeout", "30", # timeout if cannot connect within 30 seconds
             "--retry", "3", "-Sf", "-o", path, url],
            verbose=verbose,
            exception=exception)


def verify(path, sha_path, verbose):
    """Check if the sha256 sum of the given path is valid"""
    if verbose:
        print("verifying", path)
    with open(path, "rb") as source:
        found = hashlib.sha256(source.read()).hexdigest()
    with open(sha_path, "r") as sha256sum:
        expected = sha256sum.readline().split()[0]
    verified = found == expected
    if not verified:
        print("invalid checksum:\n"
              "    found:    {}\n"
              "    expected: {}".format(found, expected))
    return verified


def unpack(tarball, dst, verbose=False, match=None):
    """Unpack the given tarball file"""
    print("extracting", tarball)
    fname = os.path.basename(tarball).replace(".tar.gz", "")
    with contextlib.closing(tarfile.open(tarball)) as tar:
        for member in tar.getnames():
            if "/" not in member:
                continue
            name = member.replace(fname + "/", "", 1)
            if match is not None and not name.startswith(match):
                continue
            name = name[len(match) + 1:]

            dst_path = os.path.join(dst, name)
            if verbose:
                print("  extracting", member)
            tar.extract(member, dst)
            src_path = os.path.join(dst, member)
            if os.path.isdir(src_path) and os.path.exists(dst_path):
                continue
            shutil.move(src_path, dst_path)
    shutil.rmtree(os.path.join(dst, fname))


def run(args, verbose=False, exception=False, **kwargs):
    """Run a child program in a new process"""
    if verbose:
        print("running: " + ' '.join(args))
    sys.stdout.flush()
    # Use Popen here instead of call() as it apparently allows powershell on
    # Windows to not lock up waiting for input presumably.
    ret = subprocess.Popen(args, **kwargs)
    code = ret.wait()
    if code != 0:
        err = "failed to run: " + ' '.join(args)
        if verbose or exception:
            raise RuntimeError(err)
        sys.exit(err)


def stage0_data(rust_root):
    """Build a dictionary from stage0.txt"""
    nightlies = os.path.join(rust_root, "src/stage0.txt")
    with open(nightlies, 'r') as nightlies:
        lines = [line.rstrip() for line in nightlies
                 if not line.startswith("#")]
        return dict([line.split(": ", 1) for line in lines if line])


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)))


def default_build_triple():
    """Build triple as in LLVM"""
    default_encoding = sys.getdefaultencoding()
    try:
        ostype = subprocess.check_output(
            ['uname', '-s']).strip().decode(default_encoding)
        cputype = subprocess.check_output(
            ['uname', '-m']).strip().decode(default_encoding)
    except (subprocess.CalledProcessError, OSError):
        if sys.platform == 'win32':
            return 'x86_64-pc-windows-msvc'
        err = "uname not found"
        sys.exit(err)

    # 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.
    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(
            ['uname', '-o']).strip().decode(default_encoding)
        if os_from_sp == 'Android':
            ostype = 'linux-android'
        else:
            ostype = 'unknown-linux-gnu'
    elif ostype == 'SunOS':
        ostype = 'sun-solaris'
        # On Solaris, uname -m will return a machine classification instead
        # of a cpu type, so uname -p is recommended instead.  However, the
        # output from that option is too generic for our purposes (it will
        # always emit 'i386' on x86/amd64 systems).  As such, isainfo -k
        # must be used instead.
        try:
            cputype = subprocess.check_output(
                ['isainfo', '-k']).strip().decode(default_encoding)
        except (subprocess.CalledProcessError, OSError):
            err = "isainfo not found"
            sys.exit(err)
    elif ostype.startswith('MINGW'):
        # msys' `uname` does not print gcc configuration, but prints msys
        # configuration. so we cannot believe `uname -m`:
        # msys1 is always i686 and msys2 is always x86_64.
        # instead, msys defines $MSYSTEM which is MINGW32 on i686 and
        # MINGW64 on x86_64.
        ostype = 'pc-windows-gnu'
        cputype = 'i686'
        if os.environ.get('MSYSTEM') == 'MINGW64':
            cputype = 'x86_64'
    elif ostype.startswith('MSYS'):
        ostype = 'pc-windows-gnu'
    elif ostype.startswith('CYGWIN_NT'):
        cputype = 'i686'
        if ostype.endswith('WOW64'):
            cputype = 'x86_64'
        ostype = 'pc-windows-gnu'
    else:
        err = "unknown OS type: {}".format(ostype)
        sys.exit(err)

    if cputype == 'powerpc' and ostype == 'unknown-freebsd':
        cputype = subprocess.check_output(
              ['uname', '-p']).strip().decode(default_encoding)
    cputype_mapper = {
        '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'}:
        cputype = 'arm'
        if ostype == 'linux-android':
            ostype = 'linux-androideabi'
    elif cputype == 'armv6l':
        cputype = 'arm'
        if ostype == 'linux-android':
            ostype = 'linux-androideabi'
        else:
            ostype += 'eabihf'
    elif cputype in {'armv7l', 'armv8l'}:
        cputype = 'armv7'
        if ostype == 'linux-android':
            ostype = 'linux-androideabi'
        else:
            ostype += 'eabihf'
    elif cputype == 'mips':
        if sys.byteorder == 'big':
            cputype = 'mips'
        elif sys.byteorder == 'little':
            cputype = 'mipsel'
        else:
            raise ValueError("unknown byteorder: {}".format(sys.byteorder))
    elif cputype == 'mips64':
        if sys.byteorder == 'big':
            cputype = 'mips64'
        elif sys.byteorder == 'little':
            cputype = 'mips64el'
        else:
            raise ValueError('unknown byteorder: {}'.format(sys.byteorder))
        # only the n64 ABI is supported, indicate it
        ostype += 'abi64'
    elif cputype == 'sparc' or cputype == 'sparcv9' or cputype == 'sparc64':
        pass
    else:
        err = "unknown cpu type: {}".format(cputype)
        sys.exit(err)

    return "{}-{}".format(cputype, ostype)


@contextlib.contextmanager
def output(filepath):
    tmp = filepath + '.tmp'
    with open(tmp, 'w') as f:
        yield f
    try:
        os.remove(filepath)  # PermissionError/OSError on Win32 if in use
        os.rename(tmp, filepath)
    except OSError:
        shutil.copy2(tmp, filepath)
        os.remove(tmp)


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_toml = ''
        self.rust_root = ''
        self.use_locked_deps = ''
        self.use_vendored_sources = ''
        self.verbose = False

    def download_stage0(self):
        """Fetch the build system for Rust, written in Rust

        This method will build a cache directory, then it will fetch the
        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 \
                (not os.path.exists(self.rustc()) or
                 self.program_out_of_date(self.rustc_stamp())):
            if os.path.exists(self.bin_root()):
                shutil.rmtree(self.bin_root())
            filename = "rust-std-{}-{}.tar.gz".format(
                rustc_channel, self.build)
            pattern = "rust-std-{}".format(self.build)
            self._download_stage0_helper(filename, pattern)

            filename = "rustc-{}-{}.tar.gz".format(rustc_channel, self.build)
            self._download_stage0_helper(filename, "rustc")
            self.fix_executable("{}/bin/rustc".format(self.bin_root()))
            self.fix_executable("{}/bin/rustdoc".format(self.bin_root()))
            with output(self.rustc_stamp()) as rust_stamp:
                rust_stamp.write(self.date)

            # This is required so that we don't mix incompatible MinGW
            # libraries/binaries that are included in rust-std with
            # the system MinGW ones.
            if "pc-windows-gnu" in self.build:
                filename = "rust-mingw-{}-{}.tar.gz".format(
                    rustc_channel, self.build)
                self._download_stage0_helper(filename, "rust-mingw")

        if self.cargo().startswith(self.bin_root()) and \
                (not os.path.exists(self.cargo()) or
                 self.program_out_of_date(self.cargo_stamp())):
            filename = "cargo-{}-{}.tar.gz".format(cargo_channel, self.build)
            self._download_stage0_helper(filename, "cargo")
            self.fix_executable("{}/bin/cargo".format(self.bin_root()))
            with output(self.cargo_stamp()) as cargo_stamp:
                cargo_stamp.write(self.date)

    def _download_stage0_helper(self, filename, pattern):
        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()
        try:
            ostype = subprocess.check_output(
                ['uname', '-s']).strip().decode(default_encoding)
        except subprocess.CalledProcessError:
            return
        except OSError as reason:
            if getattr(reason, 'winerror', None) is not None:
                return
            raise reason

        if ostype != "Linux":
            return

        if not os.path.exists("/etc/NIXOS"):
            return
        if os.path.exists("/lib"):
            return

        # At this point we're pretty sure the user is running NixOS
        nix_os_msg = "info: you seem to be running NixOS. Attempting to patch"
        print(nix_os_msg, fname)

        try:
            interpreter = subprocess.check_output(
                ["patchelf", "--print-interpreter", fname])
            interpreter = interpreter.strip().decode(default_encoding)
        except subprocess.CalledProcessError as reason:
            print("warning: failed to call patchelf:", reason)
            return

        loader = interpreter.split("/")[-1]

        try:
            ldd_output = subprocess.check_output(
                ['ldd', '/run/current-system/sw/bin/sh'])
            ldd_output = ldd_output.strip().decode(default_encoding)
        except subprocess.CalledProcessError as reason:
            print("warning: unable to call ldd:", reason)
            return

        for line in ldd_output.splitlines():
            libname = line.split()[0]
            if libname.endswith(loader):
                loader_path = libname[:len(libname) - len(loader)]
                break
        else:
            print("warning: unable to find the path to the dynamic linker")
            return

        correct_interpreter = loader_path + loader

        try:
            subprocess.check_output(
                ["patchelf", "--set-interpreter", correct_interpreter, fname])
        except subprocess.CalledProcessError as reason:
            print("warning: failed to call patchelf:", reason)
            return

    def rustc_stamp(self):
        """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')

    def cargo_stamp(self):
        """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')

    def program_out_of_date(self, stamp_path):
        """Check if the given program stamp is out of date"""
        if not os.path.exists(stamp_path) or self.clean:
            return True
        with open(stamp_path, 'r') as stamp:
            return self.date != stamp.read()

    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")

    def get_toml(self, key, section=None):
        """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") is None
        True

        Optionally also matches the section the key appears in

        >>> rb.config_toml = '[a]\\nkey = "value1"\\n[b]\\nkey = "value2"'
        >>> rb.get_toml('key', 'a')
        'value1'
        >>> rb.get_toml('key', 'b')
        'value2'
        >>> rb.get_toml('key', 'c') is None
        True
        """

        cur_section = None
        for line in self.config_toml.splitlines():
            section_match = re.match(r'^\s*\[(.*)\]\s*$', line)
            if section_match is not None:
                cur_section = section_match.group(1)

            match = re.match(r'^{}\s*=(.*)$'.format(key), line)
            if match is not None:
                value = match.group(1)
                if section is None or section == cur_section:
                    return self.get_string(value) or value.strip()
        return None

    def cargo(self):
        """Return config path for cargo"""
        return self.program_config('cargo')

    def rustc(self):
        """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.program_config('rustc')
        'rustc'
        >>> rb.config_toml = ''
        >>> 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:
            return os.path.expanduser(config)
        return os.path.join(self.bin_root(), "bin", "{}{}".format(
            program, self.exe_suffix()))

    @staticmethod
    def get_string(line):
        """Return the value between double quotes

        >>> RustBuild.get_string('    "devel"   ')
        'devel'
        """
        start = line.find('"')
        if start != -1:
            end = start + 1 + line[start + 1:].find('"')
            return line[start + 1:end]
        start = line.find('\'')
        if start != -1:
            end = start + 1 + line[start + 1:].find('\'')
            return line[start + 1:end]
        return None

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

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

        >>> 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):
        """Build bootstrap"""
        build_dir = os.path.join(self.build_dir, "bootstrap")
        if self.clean and os.path.exists(build_dir):
            shutil.rmtree(build_dir)
        env = os.environ.copy()
        env["RUSTC_BOOTSTRAP"] = '1'
        env["CARGO_TARGET_DIR"] = build_dir
        env["RUSTC"] = self.rustc()
        env["LD_LIBRARY_PATH"] = os.path.join(self.bin_root(), "lib") + \
            (os.pathsep + env["LD_LIBRARY_PATH"]) \
            if "LD_LIBRARY_PATH" in env else ""
        env["DYLD_LIBRARY_PATH"] = os.path.join(self.bin_root(), "lib") + \
            (os.pathsep + env["DYLD_LIBRARY_PATH"]) \
            if "DYLD_LIBRARY_PATH" in env else ""
        env["LIBRARY_PATH"] = os.path.join(self.bin_root(), "lib") + \
            (os.pathsep + env["LIBRARY_PATH"]) \
            if "LIBRARY_PATH" in env else ""
        env["RUSTFLAGS"] = "-Cdebuginfo=2 "

        build_section = "target.{}".format(self.build_triple())
        target_features = []
        if self.get_toml("crt-static", build_section) == "true":
            target_features += ["+crt-static"]
        elif self.get_toml("crt-static", build_section) == "false":
            target_features += ["-crt-static"]
        if target_features:
            env["RUSTFLAGS"] += "-C target-feature=" + (",".join(target_features)) + " "
        target_linker = self.get_toml("linker", build_section)
        if target_linker is not None:
            env["RUSTFLAGS"] += "-C linker=" + target_linker + " "

        env["PATH"] = os.path.join(self.bin_root(), "bin") + \
            os.pathsep + env["PATH"]
        if not os.path.isfile(self.cargo()):
            raise Exception("no cargo executable found at `{}`".format(
                self.cargo()))
        args = [self.cargo(), "build", "--manifest-path",
                os.path.join(self.rust_root, "src/bootstrap/Cargo.toml")]
        for _ in range(1, self.verbose):
            args.append("--verbose")
        if self.use_locked_deps:
            args.append("--locked")
        if self.use_vendored_sources:
            args.append("--frozen")
        run(args, env=env, verbose=self.verbose)

    def build_triple(self):
        """Build triple as in LLVM"""
        config = self.get_toml('build')
        if config:
            return config
        return default_build_triple()

    def check_submodule(self, module, slow_submodules):
        if not slow_submodules:
            checked_out = subprocess.Popen(["git", "rev-parse", "HEAD"],
                                           cwd=os.path.join(self.rust_root, module),
                                           stdout=subprocess.PIPE)
            return checked_out
        else:
            return None

    def update_submodule(self, module, checked_out, recorded_submodules):
        module_path = os.path.join(self.rust_root, module)

        if checked_out != None:
            default_encoding = sys.getdefaultencoding()
            checked_out = checked_out.communicate()[0].decode(default_encoding).strip()
            if recorded_submodules[module] == checked_out:
                return

        print("Updating submodule", module)

        run(["git", "submodule", "-q", "sync", module],
            cwd=self.rust_root, verbose=self.verbose)
        run(["git", "submodule", "update",
            "--init", "--recursive", "--progress", module],
            cwd=self.rust_root, verbose=self.verbose)
        run(["git", "reset", "-q", "--hard"],
            cwd=module_path, verbose=self.verbose)
        run(["git", "clean", "-qdfx"],
            cwd=module_path, verbose=self.verbose)

    def update_submodules(self):
        """Update submodules"""
        if (not os.path.exists(os.path.join(self.rust_root, ".git"))) or \
                self.get_toml('submodules') == "false":
            return
        slow_submodules = self.get_toml('fast-submodules') == "false"
        start_time = time()
        if slow_submodules:
            print('Unconditionally updating all submodules')
        else:
            print('Updating only changed submodules')
        default_encoding = sys.getdefaultencoding()
        submodules = [s.split(' ', 1)[1] for s in subprocess.check_output(
            ["git", "config", "--file",
             os.path.join(self.rust_root, ".gitmodules"),
             "--get-regexp", "path"]
        ).decode(default_encoding).splitlines()]
        filtered_submodules = []
        submodules_names = []
        for module in submodules:
            if module.endswith("llvm"):
                if self.get_toml('llvm-config'):
                    continue
            if module.endswith("llvm-emscripten"):
                backends = self.get_toml('codegen-backends')
                if backends is None or not 'emscripten' in backends:
                    continue
            if module.endswith("lld"):
                config = self.get_toml('lld')
                if config is None or config == 'false':
                    continue
            if module.endswith("lldb") or module.endswith("clang"):
                config = self.get_toml('lldb')
                if config is None or config == 'false':
                    continue
            check = self.check_submodule(module, slow_submodules)
            filtered_submodules.append((module, check))
            submodules_names.append(module)
        recorded = subprocess.Popen(["git", "ls-tree", "HEAD"] + submodules_names,
                                    cwd=self.rust_root, stdout=subprocess.PIPE)
        recorded = recorded.communicate()[0].decode(default_encoding).strip().splitlines()
        recorded_submodules = {}
        for data in recorded:
            data = data.split()
            recorded_submodules[data[3]] = data[2]
        for module in filtered_submodules:
            self.update_submodule(module[0], module[1], recorded_submodules)
        print("Submodules updated in %.2f seconds" % (time() - start_time))

    def set_dev_environment(self):
        """Set download URL for development environment"""
        self._download_url = 'https://dev-static.rust-lang.org'


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

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

    parser = argparse.ArgumentParser(description='Build rust')
    parser.add_argument('--config')
    parser.add_argument('--build')
    parser.add_argument('--src')
    parser.add_argument('--clean', action='store_true')
    parser.add_argument('-v', '--verbose', action='count', default=0)

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

    # Configure initial bootstrap
    build = RustBuild()
    build.rust_root = args.src or os.path.abspath(os.path.join(__file__, '../../..'))
    build.verbose = args.verbose
    build.clean = args.clean

    try:
        with open(args.config or 'config.toml') as config:
            build.config_toml = config.read()
    except (OSError, IOError):
        pass

    match = re.search(r'\nverbose = (\d+)', build.config_toml)
    if match is not None:
        build.verbose = max(build.verbose, int(match.group(1)))

    build.use_vendored_sources = '\nvendor = true' in build.config_toml

    build.use_locked_deps = '\nlocked-deps = true' in build.config_toml

    if 'SUDO_USER' in os.environ and not build.use_vendored_sources:
        if os.environ.get('USER') != os.environ['SUDO_USER']:
            build.use_vendored_sources = True
            print('info: looks like you are running this command under `sudo`')
            print('      and so in order to preserve your $HOME this will now')
            print('      use vendored sources by default. Note that if this')
            print('      does not work you should run a normal build first')
            print('      before running a command like `sudo ./x.py install`')

    if build.use_vendored_sources:
        if not os.path.exists('.cargo'):
            os.makedirs('.cargo')
        with output('.cargo/config') as cargo_config:
            cargo_config.write("""
                [source.crates-io]
                replace-with = 'vendored-sources'
                registry = 'https://example.com'

                [source.vendored-sources]
                directory = '{}/vendor'
            """.format(build.rust_root))
    else:
        if os.path.exists('.cargo'):
            shutil.rmtree('.cargo')

    data = stage0_data(build.rust_root)
    build.date = data['date']
    build.rustc_channel = data['rustc']
    build.cargo_channel = data['cargo']

    if 'dev' in data:
        build.set_dev_environment()

    build.update_submodules()

    # Fetch/build the bootstrap
    build.build = args.build or build.build_triple()
    build.download_stage0()
    sys.stdout.flush()
    build.build_bootstrap()
    sys.stdout.flush()

    # Run the bootstrap
    args = [build.bootstrap_binary()]
    args.extend(sys.argv[1:])
    env = os.environ.copy()
    env["BUILD"] = build.build
    env["SRC"] = build.rust_root
    env["BOOTSTRAP_PARENT_ID"] = str(os.getpid())
    env["BOOTSTRAP_PYTHON"] = sys.executable
    env["BUILD_DIR"] = build.build_dir
    env["RUSTC_BOOTSTRAP"] = '1'
    env["CARGO"] = build.cargo()
    env["RUSTC"] = build.rustc()
    run(args, env=env, verbose=build.verbose)


def main():
    """Entry point for the bootstrap process"""
    start_time = time()

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

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


if __name__ == '__main__':
    main()

init_repo.sh

#!/usr/bin/env bash

set -o errexit
set -o pipefail
set -o nounset

ci_dir=$(cd $(dirname $0) && pwd)
. "$ci_dir/shared.sh"

travis_fold start init_repo
travis_time_start

REPO_DIR="$1"
CACHE_DIR="$2"

cache_src_dir="$CACHE_DIR/src"

if [ ! -d "$REPO_DIR" -o ! -d "$REPO_DIR/.git" ]; then
    echo "Error: $REPO_DIR does not exist or is not a git repo"
    exit 1
fi
cd $REPO_DIR
if [ ! -d "$CACHE_DIR" ]; then
    echo "Error: $CACHE_DIR does not exist or is not an absolute path"
    exit 1
fi

rm -rf "$CACHE_DIR"
mkdir "$CACHE_DIR"

# On the beta channel we'll be automatically calculating the prerelease version
# via the git history, so unshallow our shallow clone from CI.
if grep -q RUST_RELEASE_CHANNEL=beta src/ci/run.sh; then
  git fetch origin --unshallow beta master
fi

function fetch_submodule {
    local module=$1
    local cached="download-${module//\//-}.tar.gz"
    retry sh -c "rm -f $cached && \
        curl -sSL -o $cached $2"
    mkdir $module
    touch "$module/.git"
    tar -C $module --strip-components=1 -xf $cached
    rm $cached
}

included="src/llvm src/llvm-emscripten src/doc/book src/doc/rust-by-example"
included="$included src/tools/lld src/tools/clang src/tools/lldb"
modules="$(git config --file .gitmodules --get-regexp '\.path$' | cut -d' ' -f2)"
modules=($modules)
use_git=""
urls="$(git config --file .gitmodules --get-regexp '\.url$' | cut -d' ' -f2)"
urls=($urls)
for i in ${!modules[@]}; do
    module=${modules[$i]}
    if [[ " $included " = *" $module "* ]]; then
        commit="$(git ls-tree HEAD $module | awk '{print $3}')"
        git rm $module
        url=${urls[$i]}
        url=${url/\.git/}
        fetch_submodule $module "$url/archive/$commit.tar.gz" &
        continue
    else
        use_git="$use_git $module"
    fi
done
retry sh -c "git submodule deinit -f $use_git && \
    git submodule sync && \
    git submodule update -j 16 --init --recursive $use_git"
wait
travis_fold end init_repo
travis_time_finish

src/ci/run.sh

#!/usr/bin/env bash

set -e

if [ -n "$CI_JOB_NAME" ]; then
  echo "[CI_JOB_NAME=$CI_JOB_NAME]"
fi

if [ "$NO_CHANGE_USER" = "" ]; then
  if [ "$LOCAL_USER_ID" != "" ]; then
    useradd --shell /bin/bash -u $LOCAL_USER_ID -o -c "" -m user
    export HOME=/home/user
    unset LOCAL_USER_ID
    exec su --preserve-environment -c "env PATH=$PATH \"$0\"" user
  fi
fi

# only enable core dump on Linux
if [ -f /proc/sys/kernel/core_pattern ]; then
  ulimit -c unlimited
fi

ci_dir=`cd $(dirname $0) && pwd`
source "$ci_dir/shared.sh"

if [ "$TRAVIS" != "true" ] || [ "$TRAVIS_BRANCH" == "auto" ]; then
    RUST_CONFIGURE_ARGS="$RUST_CONFIGURE_ARGS --set build.print-step-timings --enable-verbose-tests"
fi

RUST_CONFIGURE_ARGS="$RUST_CONFIGURE_ARGS --enable-sccache"
RUST_CONFIGURE_ARGS="$RUST_CONFIGURE_ARGS --disable-manage-submodules"
RUST_CONFIGURE_ARGS="$RUST_CONFIGURE_ARGS --enable-locked-deps"
RUST_CONFIGURE_ARGS="$RUST_CONFIGURE_ARGS --enable-cargo-native-static"
RUST_CONFIGURE_ARGS="$RUST_CONFIGURE_ARGS --set rust.codegen-units-std=1"

if [ "$DIST_SRC" = "" ]; then
  RUST_CONFIGURE_ARGS="$RUST_CONFIGURE_ARGS --disable-dist-src"
fi

# If we're deploying artifacts then we set the release channel, otherwise if
# we're not deploying then we want to be sure to enable all assertions because
# we'll be running tests
#
# FIXME: need a scheme for changing this `nightly` value to `beta` and `stable`
#        either automatically or manually.
export RUST_RELEASE_CHANNEL=nightly
if [ "$DEPLOY$DEPLOY_ALT" != "" ]; then
  RUST_CONFIGURE_ARGS="$RUST_CONFIGURE_ARGS --release-channel=$RUST_RELEASE_CHANNEL"
  RUST_CONFIGURE_ARGS="$RUST_CONFIGURE_ARGS --enable-llvm-static-stdcpp"
  RUST_CONFIGURE_ARGS="$RUST_CONFIGURE_ARGS --set rust.remap-debuginfo"

  if [ "$NO_LLVM_ASSERTIONS" = "1" ]; then
    RUST_CONFIGURE_ARGS="$RUST_CONFIGURE_ARGS --disable-llvm-assertions"
  elif [ "$DEPLOY_ALT" != "" ]; then
    RUST_CONFIGURE_ARGS="$RUST_CONFIGURE_ARGS --enable-llvm-assertions"
    RUST_CONFIGURE_ARGS="$RUST_CONFIGURE_ARGS --set rust.verify-llvm-ir"
  fi
else
  # We almost always want debug assertions enabled, but sometimes this takes too
  # long for too little benefit, so we just turn them off.
  if [ "$NO_DEBUG_ASSERTIONS" = "" ]; then
    RUST_CONFIGURE_ARGS="$RUST_CONFIGURE_ARGS --enable-debug-assertions"
  fi

  # In general we always want to run tests with LLVM assertions enabled, but not
  # all platforms currently support that, so we have an option to disable.
  if [ "$NO_LLVM_ASSERTIONS" = "" ]; then
    RUST_CONFIGURE_ARGS="$RUST_CONFIGURE_ARGS --enable-llvm-assertions"
  fi

  RUST_CONFIGURE_ARGS="$RUST_CONFIGURE_ARGS --set rust.verify-llvm-ir"
fi

if [ "$RUST_RELEASE_CHANNEL" = "nightly" ] || [ "$DIST_REQUIRE_ALL_TOOLS" = "" ]; then
    RUST_CONFIGURE_ARGS="$RUST_CONFIGURE_ARGS --enable-missing-tools"
fi

# We've had problems in the past of shell scripts leaking fds into the sccache
# server (#48192) which causes Cargo to erroneously think that a build script
# hasn't finished yet. Try to solve that problem by starting a very long-lived
# sccache server at the start of the build, but no need to worry if this fails.
SCCACHE_IDLE_TIMEOUT=10800 sccache --start-server || true

if [ "$RUN_CHECK_WITH_PARALLEL_QUERIES" != "" ]; then
  $SRC/configure --enable-experimental-parallel-queries
  CARGO_INCREMENTAL=0 python2.7 ../x.py check
  rm -f config.toml
  rm -rf build
fi

travis_fold start configure
travis_time_start
$SRC/configure $RUST_CONFIGURE_ARGS
travis_fold end configure
travis_time_finish

travis_fold start make-prepare
travis_time_start
retry make prepare
travis_fold end make-prepare
travis_time_finish

travis_fold start check-bootstrap
travis_time_start
make check-bootstrap
travis_fold end check-bootstrap
travis_time_finish

# Display the CPU and memory information. This helps us know why the CI timing
# is fluctuating.
travis_fold start log-system-info
if [ "$TRAVIS_OS_NAME" = "osx" ]; then
    system_profiler SPHardwareDataType || true
    sysctl hw || true
    ncpus=$(sysctl -n hw.ncpu)
else
    cat /proc/cpuinfo || true
    cat /proc/meminfo || true
    ncpus=$(grep processor /proc/cpuinfo | wc -l)
fi
travis_fold end log-system-info

if [ ! -z "$SCRIPT" ]; then
  sh -x -c "$SCRIPT"
else
  do_make() {
    travis_fold start "make-$1"
    travis_time_start
    echo "make -j $ncpus $1"
    make -j $ncpus $1
    local retval=$?
    travis_fold end "make-$1"
    travis_time_finish
    return $retval
  }

  do_make tidy
  do_make all
  do_make "$RUST_CHECK_TARGET"
fi

src/ci/shared.sh

#!/bin/false

# This file is intended to be sourced with `. shared.sh` or
# `source shared.sh`, hence the invalid shebang and not being
# marked as an executable file in git.

# See http://unix.stackexchange.com/questions/82598
function retry {
  echo "Attempting with retry:" "$@"
  local n=1
  local max=5
  while true; do
    "$@" && break || {
      if [[ $n -lt $max ]]; then
        sleep $n  # don't retry immediately
        ((n++))
        echo "Command failed. Attempt $n/$max:"
      else
        echo "The command has failed after $n attempts."
        return 1
      fi
    }
  done
}

if ! declare -F travis_fold; then
  if [ "${TRAVIS-false}" = 'true' ]; then
    # This is a trimmed down copy of
    # https://github.com/travis-ci/travis-build/blob/master/lib/travis/build/templates/header.sh
    travis_fold() {
      echo -en "travis_fold:$1:$2\r\033[0K"
    }
    travis_time_start() {
      travis_timer_id=$(printf %08x $(( RANDOM * RANDOM )))
      travis_start_time=$(travis_nanoseconds)
      echo -en "travis_time:start:$travis_timer_id\r\033[0K"
    }
    travis_time_finish() {
      travis_end_time=$(travis_nanoseconds)
      local duration=$(($travis_end_time-$travis_start_time))
      local msg="travis_time🔚$travis_timer_id"
      echo -en "\n$msg:start=$travis_start_time,finish=$travis_end_time,duration=$duration\r\033[0K"
    }
    if [ $(uname) = 'Darwin' ]; then
      travis_nanoseconds() {
        date -u '+%s000000000'
      }
    else
      travis_nanoseconds() {
        date -u '+%s%N'
      }
    fi
  else
    travis_fold() { return 0; }
    travis_time_start() { return 0; }
    travis_time_finish() { return 0; }
  fi
fi

rust-lang/rust-central-station

crontab

PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/root/.cargo/bin

# renewing ssl certs
24 * * * * root letsencrypt renew 2>&1 | logger --tag letsencrypt-renew

# signing/hashing/promoting releases
0 0 * * * root promote-release /tmp/nightly nightly /data/secrets.toml 2>&1 | logger --tag release-nightly
20 3 * * * root promote-release /tmp/beta beta /data/secrets.toml 2>&1 | logger --tag release-beta
40 * * * * root promote-release /tmp/stable stable /data/secrets-dev.toml 2>&1 | logger --tag release-stable

# cancelling appveyor/travis builds if we don't need them
*/2 * * * * root /src/bin/cancelbot-rust.sh 2>&1 | logger --tag cancelbot-rust

promote-release/src/main.rs

extern crate curl;
extern crate flate2;
extern crate fs2;
extern crate rand;
#[macro_use]
extern crate serde_json;
extern crate tar;
extern crate toml;
extern crate xz2;

use std::env;
use std::fs::{self, File, OpenOptions};
use std::io::{self, Read, Write};
use std::path::{PathBuf, Path};
use std::process::Command;

use curl::easy::Easy;
use fs2::FileExt;

macro_rules! t {
    ($e:expr) => (match $e {
        Ok(e) => e,
        Err(e) => panic!("{} failed with {:?}", stringify!($e), e),
    })
}

struct Context {
    work: PathBuf,
    release: String,
    handle: Easy,
	secrets: toml::Value,
    date: String,
    current_version: Option<String>,
}

// Called as:
//
//  $prog work/dir release-channel path/to/secrets.toml
fn main() {
    let mut secrets = String::new();
    t!(t!(File::open(env::args().nth(3).unwrap())).read_to_string(&mut secrets));

    Context {
        work: t!(env::current_dir()).join(env::args_os().nth(1).unwrap()),
        release: env::args().nth(2).unwrap(),
        secrets: t!(secrets.parse()),
        handle: Easy::new(),
        date: output(Command::new("date").arg("+%Y-%m-%d")).trim().to_string(),
        current_version: None,
    }.run()
}

impl Context {
    fn run(&mut self) {
        let _lock = self.lock();
        self.update_repo();
        let branch = match &self.release[..] {
            "nightly" => "master",
            "beta" => "beta",
            "stable" => "stable",
            _ => panic!("unknown release: {}", self.release),
        };
        self.do_release(branch);
    }

    /// Locks execution of concurrent invocations of this script in case one
    /// takes a long time to run. The call to `try_lock_exclusive` will fail if
    /// the lock is held already
    fn lock(&mut self) -> File {
        t!(fs::create_dir_all(&self.work));
        let file = t!(OpenOptions::new()
                            .read(true)
                            .write(true)
                            .create(true)
                            .open(self.work.join(".lock")));
        t!(file.try_lock_exclusive());
        file
    }

    /// Update the rust repository we have cached, either cloning a fresh one or
    /// fetching remote references
    fn update_repo(&mut self) {
        // Clone/update the repo
        let dir = self.rust_dir();
        if dir.is_dir() {
            println!("fetching");
            run(Command::new("git")
                        .arg("fetch")
                        .arg("origin")
                        .current_dir(&dir));
        } else {
            println!("cloning");
            run(Command::new("git")
                        .arg("clone")
                        .arg("https://github.com/rust-lang/rust")
                        .arg(&dir));
        }
    }

    /// Does a release for the `branch` specified.
    fn do_release(&mut self, branch: &str) {
        // Learn the precise rev of the remote branch, this'll guide what we
        // download.
        let rev = output(Command::new("git")
                                 .arg("rev-parse")
                                 .arg(format!("origin/{}", branch))
                                 .current_dir(&self.rust_dir()));
        let rev = rev.trim();
        println!("{} rev is {}", self.release, rev);

        // Download the current live manifest for the channel we're releasing.
        // Through that we learn the current version of the release.
        let manifest = self.download_manifest();
        let previous_version = manifest["pkg"]["rust"]["version"]
                                       .as_str()
                                       .expect("rust version not a string");
        println!("previous version: {}", previous_version);

        // If the previously released version is the same rev, then there's
        // nothing for us to do, nothing has changed.
        if previous_version.contains(&rev[..7]) {
            return println!("found rev in previous version, skipping");
        }

        // We may still not do a release if the version number hasn't changed.
        // To learn about the current branch's version number we download
        // artifacts and look inside.
        //
        // If revisions of the current release and the current branch are
        // different and the versions are the same then there's nothing for us
        // to do. This represents a scenario where changes have been merged to
        // the stable/beta branch but the version bump hasn't happened yet.
        self.download_artifacts(&rev);
        if self.current_version_same(&previous_version) {
            return println!("version hasn't changed, skipping");
        }

        self.assert_all_components_present();

        // Ok we've now determined that a release needs to be done. Let's
        // configure rust, sign the artifacts we just downloaded, and upload the
        // signatures to the CI bucket.
        self.configure_rust(rev);
        self.sign_artifacts();
        self.upload_signatures(&rev);

        // Merge all the signatures with the download files, and then sync that
        // whole dir up to the release archives
        for file in t!(self.build_dir().join("build/dist/").read_dir()) {
            let file = t!(file);
            t!(fs::copy(file.path(), self.dl_dir().join(file.file_name())));
        }
        self.publish_archive();
        self.publish_docs();
        self.publish_release();

        self.invalidate_cloudfront();
    }

    fn configure_rust(&mut self, rev: &str) {
        let build = self.build_dir();
        drop(fs::remove_dir_all(&build));
        t!(fs::create_dir_all(&build));
        let rust = self.rust_dir();

        run(Command::new("git")
                    .arg("reset")
                    .arg("--hard")
                    .arg(rev)
                    .current_dir(&rust));

        run(Command::new(rust.join("configure"))
                    .current_dir(&build)
                    .arg(format!("--release-channel={}", self.release)));
        let mut config = String::new();
        let path = build.join("config.toml");
        drop(File::open(&path).and_then(|mut f| f.read_to_string(&mut config)));
        let lines = config.lines().filter(|l| !l.starts_with("[dist]"));
        let mut new_config = String::new();
        for line in lines {
            new_config.push_str(line);
            new_config.push_str("\n");
        }
        new_config.push_str(&format!("
[dist]
sign-folder = \"{}\"
gpg-password-file = \"{}\"
upload-addr = \"{}/{}\"
",
            self.dl_dir().display(),
            self.secrets["dist"]["gpg-password-file"].as_str().unwrap(),
            self.secrets["dist"]["upload-addr"].as_str().unwrap(),
            self.secrets["dist"]["upload-dir"].as_str().unwrap()));
        t!(t!(File::create(&path)).write_all(new_config.as_bytes()));
    }

    fn current_version_same(&mut self, prev: &str) -> bool {
        // nightly's always changing
        if self.release == "nightly" {
            return false
        }
        let prev_version = prev.split(' ').next().unwrap();

        let current = t!(self.dl_dir().read_dir()).filter_map(|e| {
            let e = t!(e);
            let filename = e.file_name().into_string().unwrap();
            if !filename.starts_with("rustc-") || !filename.ends_with(".tar.gz") {
                return None
            }
            println!("looking inside {} for a version", filename);

            let file = t!(File::open(&e.path()));
            let reader = flate2::read::GzDecoder::new(file);
            let mut archive = tar::Archive::new(reader);

            let entry = t!(archive.entries()).map(|e| t!(e)).filter(|e| {
                let path = t!(e.path());
                match path.iter().skip(1).next() {
                    Some(path) => path == Path::new("version"),
                    None => false,
                }
            }).next();
            let mut entry = match entry {
                Some(e) => e,
                None => return None,
            };
            let mut contents = String::new();
            t!(entry.read_to_string(&mut contents));
            Some(contents)
        }).next().expect("no archives with a version");

        println!("current version: {}", current);

        let current_version = current.split(' ').next().unwrap();
        self.current_version = Some(current_version.to_string());

        // The release process for beta looks like so:
        //
        // * Force push master branch to beta branch
        // * Send a PR to beta, updating release channel
        //
        // In the window between these two steps we don't actually have release
        // artifacts but this script may be run. Try to detect that case here if
        // the versions mismatch and panic. We'll try again later once that PR
        // has merged and everything should look good.
        if (current.contains("nightly") && !prev.contains("nightly")) ||
           (current.contains("beta") && !prev.contains("beta")) {
            panic!("looks like channels are being switched -- was this branch \
                    just created and has a pending PR to change the release \
                    channel?");
        }

        prev_version == current_version
    }

    /// An emergency fix for the current situation where the RLS or clippy often
    /// aren't available. Don't produce nightlies if a component is missing.
    ///
    /// Note that we already don't merge PRs in rust-lang/rust that don't
    /// build cargo
    fn assert_all_components_present(&self) {
        if self.release != "nightly" {
            return
        }
        let components = t!(self.dl_dir().read_dir())
            .map(|e| t!(e))
            .map(|e| e.file_name().into_string().unwrap())
            .filter(|s| s.contains("x86_64-unknown-linux-gnu"))
            .collect::<Vec<_>>();
        println!("components in this nightly {:?}", components);
        assert!(components.iter().any(|s| s.starts_with("rustc-")));
        assert!(components.iter().any(|s| s.starts_with("rust-std-")));
        assert!(components.iter().any(|s| s.starts_with("cargo-")));
        // assert!(components.iter().any(|s| s.starts_with("rustfmt-")));
        // assert!(components.iter().any(|s| s.starts_with("rls-")));
        // assert!(components.iter().any(|s| s.starts_with("clippy-")));
    }

    fn download_artifacts(&mut self, rev: &str) {
        let dl = self.dl_dir();
        drop(fs::remove_dir_all(&dl));
        t!(fs::create_dir_all(&dl));

        let src = format!("s3://rust-lang-ci2/rustc-builds/{}/", rev);
        run(self.aws_s3()
                .arg("cp")
                .arg("--recursive")
                .arg("--only-show-errors")
                .arg(&src)
                .arg(format!("{}/", dl.display())));

        let mut files = t!(dl.read_dir());
        if files.next().is_none() {
            panic!("appears that this rev doesn't have any artifacts, \
                    is this a stable/beta branch awaiting a PR?");
        }

        // Delete residue signature/hash files. These may come around for a few
        // reasons:
        //
        // 1. We died halfway through before uploading the manifest, in which
        //    case we want to re-upload everything but we don't want to sign
        //    signatures.
        //
        // 2. We're making a stable release. The stable release is first signed
        //    with the dev key and then it's signed with the prod key later. We
        //    want the prod key to overwrite the dev key signatures.
        //
        // Also, generate *.gz from *.xz if the former is missing. Since the gz
        // and xz tarballs have the same content, we did not deploy the gz files
        // from the CI. But rustup users may still expect to get gz files, so we
        // are recompressing the xz files as gz here.
        for file in t!(dl.read_dir()) {
            let file = t!(file);
            let path = file.path();
            match path.extension().and_then(|s| s.to_str()) {
                // Delete signature/hash files...
                Some("asc") |
                Some("sha256") => {
                    t!(fs::remove_file(&path));
                }
                // Generate *.gz from *.xz...
                Some("xz") => {
                    let gz_path = path.with_extension("gz");
                    if !gz_path.is_file() {
                        println!("recompressing {}...", gz_path.display());
                        let xz = t!(File::open(path));
                        let mut xz = xz2::read::XzDecoder::new(xz);
                        let gz = t!(File::create(gz_path));
                        let mut gz = flate2::write::GzEncoder::new(gz, flate2::Compression::best());
                        t!(io::copy(&mut xz, &mut gz));
                    }
                }
                _ => {}
            }
        }
    }

    fn sign_artifacts(&mut self) {
        let build = self.build_dir();
        run(Command::new(self.rust_dir().join("x.py"))
                    .current_dir(&build)
                    .arg("dist")
                    .arg("hash-and-sign"));
    }

    fn upload_signatures(&mut self, rev: &str) {
        let dst = format!("s3://rust-lang-ci2/rustc-builds/{}/", rev);
        run(self.aws_s3()
                .arg("cp")
                .arg("--recursive")
                .arg("--only-show-errors")
                .arg(self.build_dir().join("build/dist/"))
                .arg(&dst));
    }

    fn publish_archive(&mut self) {
        let bucket = self.secrets["dist"]["upload-bucket"].as_str().unwrap();
        let dir = self.secrets["dist"]["upload-dir"].as_str().unwrap();
        let dst = format!("s3://{}/{}/{}/", bucket, dir, self.date);
        run(self.aws_s3()
                .arg("cp")
                .arg("--recursive")
                .arg("--only-show-errors")
                .arg("--metadata-directive")
                .arg("REPLACE")
                .arg("--cache-control")
                .arg("public")
                .arg(format!("{}/", self.dl_dir().display()))
                .arg(&dst));
    }

    fn publish_docs(&mut self) {
        let (version, upload_dir) = match &self.release[..] {
            "stable" => {
                let vers = &self.current_version.as_ref().unwrap()[..];
                (vers, "stable")
            }
            "beta" => ("beta", "beta"),
            "nightly" => ("nightly", "nightly"),
            _ => panic!(),
        };

        // Pull out HTML documentation from one of the `rust-docs-*` tarballs.
        // For now we just arbitrarily pick x86_64-unknown-linux-gnu.
        let docs = self.work.join("docs");
        drop(fs::remove_dir_all(&docs));
        t!(fs::create_dir_all(&docs));
        let target = "x86_64-unknown-linux-gnu";

        // Unpack the regular documentation tarball.
        let tarball_prefix = format!("rust-docs-{}-{}", version, target);
        let tarball = format!("{}.tar.gz", self.dl_dir().join(&tarball_prefix).display());
        let tarball_dir = format!("{}/rust-docs/share/doc/rust/html", tarball_prefix);
        run(Command::new("tar")
                    .arg("xf")
                    .arg(&tarball)
                    .arg("--strip-components=6")
                    .arg(&tarball_dir)
                    .current_dir(&docs));

        // Construct path to rustc documentation.
        let tarball_prefix = format!("rustc-docs-{}-{}", version, target);
        let tarball = format!("{}.tar.gz", self.dl_dir().join(&tarball_prefix).display());
        // Construct the path that contains the documentation inside the tarball.
        let tarball_dir = format!("{}/rustc-docs/share/doc/rust/html", tarball_prefix);

        // Only create and unpack rustc docs if artefacts include tarball.
        if Path::new(&tarball).exists() {
            let rustc_docs = docs.join("nightly-rustc");
            t!(fs::create_dir_all(&rustc_docs));

            // Unpack the rustc documentation into the new directory.
            run(Command::new("tar")
                        .arg("xf")
                        .arg(&tarball)
                        .arg("--strip-components=6")
                        .arg(&tarball_dir)
                        .current_dir(&rustc_docs));
        }

        // Upload this to `/doc/$channel`
        let bucket = self.secrets["dist"]["upload-bucket"].as_str().unwrap();
        let dst = format!("s3://{}/doc/{}/", bucket, upload_dir);
        run(self.aws_s3()
                .arg("sync")
                .arg("--delete")
                .arg("--only-show-errors")
                .arg(format!("{}/", docs.display()))
                .arg(&dst));
        self.invalidate_docs(upload_dir);

        // Stable artifacts also go to `/doc/$version/
        if upload_dir == "stable" {
            let dst = format!("s3://{}/doc/{}/", bucket, version);
            run(self.aws_s3()
                    .arg("sync")
                    .arg("--delete")
                    .arg("--only-show-errors")
                    .arg(format!("{}/", docs.display()))
                    .arg(&dst));
            self.invalidate_docs(&version);
        }
    }

    fn invalidate_docs(&self, dir: &str) {
        let distribution_id = self.secrets["dist"]["rustdoc-cf-distribution-id"]
                                          .as_str().unwrap();
        let mut cmd = Command::new("aws");
        self.aws_creds(&mut cmd);
        cmd.arg("cloudfront")
            .arg("create-invalidation")
            .arg("--distribution-id").arg(distribution_id);
        if dir == "stable" {
            cmd.arg("--paths").arg("/*");
        } else {
            cmd.arg("--paths").arg(format!("/{0}/*", dir));
        }
        run(&mut cmd);
    }

    fn publish_release(&mut self) {
        let bucket = self.secrets["dist"]["upload-bucket"].as_str().unwrap();
        let dir = self.secrets["dist"]["upload-dir"].as_str().unwrap();
        let dst = format!("s3://{}/{}/", bucket, dir);
        run(self.aws_s3()
                .arg("cp")
                .arg("--recursive")
                .arg("--only-show-errors")
                .arg(format!("{}/", self.dl_dir().display()))
                .arg(&dst));
    }

    fn invalidate_cloudfront(&mut self) {
        let json = json!({
            "Paths": {
                "Items": [
                    "/dist/channel*",
                    "/dist/rust*",
                    "/dist/index*",
                    "/dist/",
                ],
                "Quantity": 4,
            },
            "CallerReference": format!("rct-{}", rand::random::<usize>()),
        }).to_string();
        let dst = self.work.join("payload.json");
        t!(t!(File::create(&dst)).write_all(json.as_bytes()));

        let distribution_id = self.secrets["dist"]["cloudfront-distribution-id"]
                                          .as_str().unwrap();
        let mut cmd = Command::new("aws");
        self.aws_creds(&mut cmd);
        run(cmd.arg("cloudfront")
               .arg("create-invalidation")
               .arg("--invalidation-batch").arg(format!("file://{}", dst.display()))
               .arg("--distribution-id").arg(distribution_id));
    }

    fn rust_dir(&self) -> PathBuf {
        self.work.join("rust")
    }

    fn dl_dir(&self) -> PathBuf {
        self.work.join("dl")
    }

    fn build_dir(&self) -> PathBuf {
        self.work.join("build")
    }

    fn aws_s3(&self) -> Command {
        let mut cmd = Command::new("aws");
        cmd.arg("s3");
        self.aws_creds(&mut cmd);
        return cmd
    }

    fn aws_creds(&self, cmd: &mut Command) {
        let access = self.secrets["dist"]["aws-access-key-id"].as_str().unwrap();
        let secret = self.secrets["dist"]["aws-secret-key"].as_str().unwrap();
        cmd.env("AWS_ACCESS_KEY_ID", &access)
           .env("AWS_SECRET_ACCESS_KEY", &secret);
    }

    fn download_manifest(&mut self) -> toml::Value {
        t!(self.handle.get(true));
        let addr = self.secrets["dist"]["upload-addr"].as_str().unwrap();
        let upload_dir = self.secrets["dist"]["upload-dir"].as_str().unwrap();
        let url = format!("{}/{}/channel-rust-{}.toml",
                          addr,
                          upload_dir,
                          self.release);
        println!("downloading manifest from: {}", url);
        t!(self.handle.url(&url));
        let mut result = Vec::new();
        {
            let mut t = self.handle.transfer();

            t!(t.write_function(|data| {
                result.extend_from_slice(data);
                Ok(data.len())
            }));
            t!(t.perform());
        }
        assert_eq!(t!(self.handle.response_code()), 200);
        t!(t!(String::from_utf8(result)).parse())
    }
}

fn run(cmd: &mut Command) {
    println!("running {:?}", cmd);
    let status = t!(cmd.status());
    if !status.success() {
        panic!("failed command:{:?}\n:{}", cmd, status);
    }
}

fn output(cmd: &mut Command) -> String {
    println!("running {:?}", cmd);
    let output = t!(cmd.output());
    if !output.status.success() {
        panic!("failed command:{:?}\n:{}\n\n{}\n\n{}", cmd, output.status,
               String::from_utf8_lossy(&output.stdout),
               String::from_utf8_lossy(&output.stderr),);
    }

    String::from_utf8(output.stdout).unwrap()
}

rust-lang-nursery/tool-state

rust-lang/rustup.rs

Tags: rust memo

First Post [Memo] Rustc Overview - librustc_driver

comments powered by Disqus