Add Buildkite infrastructure scripts.
diff --git a/buildkite/acceptance-test.sh b/buildkite/acceptance-test.sh
new file mode 100755
index 0000000..2b2bbe9
--- /dev/null
+++ b/buildkite/acceptance-test.sh
@@ -0,0 +1,29 @@
+#!/bin/bash
+#
+# Copyright 2017 The Bazel Authors. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#    http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# $BAZEL_VERSION is set in install-bazel.sh.
+mkdir -p /root/bazel
+cd /root/bazel
+curl -sSLo /root/bazel-dist.zip "https://releases.bazel.build/${BAZEL_VERSION}/release/bazel-${BAZEL_VERSION}-dist.zip"
+unzip /root/bazel-dist.zip > /dev/null
+rm /root/bazel-dist.zip
+
+source /etc/buildkite-agent/hooks/environment
+bazel build --spawn_strategy=linux-sandbox -- //src:bazel //src/test/... -//src/test/docker/...
+bazel clean --expunge
+
+cd /root
+rm -rf /root/bazel
diff --git a/buildkite/create_images.py b/buildkite/create_images.py
new file mode 100755
index 0000000..0a91725
--- /dev/null
+++ b/buildkite/create_images.py
@@ -0,0 +1,195 @@
+#!/usr/bin/env python3
+
+import json
+import os
+import re
+import subprocess
+import sys
+import tempfile
+import time
+from datetime import datetime
+
+DEBUG = False
+
+LOCATION = 'europe-west1-d'
+
+IMAGE_CREATION_VMS = {
+    # 'buildkite-freebsd11-image': {
+    #     'source_image': 'https://www.googleapis.com/compute/v1/projects/freebsd-org-cloud-dev/global/images/freebsd-11-1-stable-amd64-2017-12-28',
+    #     'target_image_family': 'bazel-freebsd11',
+    #     'scripts': [
+    #         'setup-freebsd.sh',
+    #         'install-buildkite-agent.sh'
+    #     ]
+    # },
+    'buildkite-ubuntu1404-image': {
+        'source_image_project': 'ubuntu-os-cloud',
+        'source_image_family': 'ubuntu-1404-lts',
+        'target_image_family': 'buildkite-ubuntu1404',
+        'scripts': [
+            'shell-utils.sh',
+            'setup-ubuntu.sh',
+            'install-azul-zulu.sh',
+            'install-bazel.sh',
+            'install-buildkite-agent.sh',
+            'install-docker.sh',
+            'install-nodejs.sh',
+            'install-android-sdk.sh',
+            'shutdown.sh'
+        ]
+    },
+    'buildkite-ubuntu1604-image': {
+        'source_image_project': 'ubuntu-os-cloud',
+        'source_image_family': 'ubuntu-1604-lts',
+        'target_image_family': 'buildkite-ubuntu1604',
+        'scripts': [
+            'shell-utils.sh',
+            'setup-ubuntu.sh',
+            'install-azul-zulu.sh',
+            'install-bazel.sh',
+            'install-buildkite-agent.sh',
+            'install-docker.sh',
+            'install-nodejs.sh',
+            'install-android-sdk.sh',
+            'shutdown.sh'
+        ]
+    },
+    # 'buildkite-windows2016-image': {
+    #     'source_image_project': 'windows-cloud',
+    #     'source_image_family': 'windows-2016',
+    #     'target_image_family': 'bazel-windows2016',
+    #     'scripts': [
+    #         'setup-windows2016.ps1'
+    #     ]
+    # }
+}
+
+
+def debug(*args, **kwargs):
+  if DEBUG:
+    print(*args, **kwargs)
+
+
+def run(args, **kwargs):
+  if DEBUG:
+    print('Running: %s' % ' '.join(args))
+  return subprocess.run(args, **kwargs)
+
+
+def wait_for_vm(vm, status):
+  while True:
+    result = run(['gcloud', 'compute', 'instances', 'describe', '--zone', LOCATION, '--format', 'json', vm], check=True, stdout=subprocess.PIPE)
+    current_status = json.loads(result.stdout)['status']
+    if current_status == status:
+      debug("wait_for_vm: VM %s reached status %s" % (vm, status))
+      break
+    else:
+      debug("wait_for_vm: Waiting for VM %s to transition from status %s -> %s" % (vm, current_status, status))
+    time.sleep(1)
+
+
+def print_pretty_logs(vm, log):
+  for line in log.splitlines():
+    # Skip empty lines.
+    if not line:
+      continue
+    if 'ubuntu' in vm:
+      match = re.match(r'.*INFO startup-script: (.*)', line)
+      if match:
+        print(match.group(1))
+    elif 'windows' in vm:
+      match = re.match(r'.*windows-startup-script-ps1: (.*)', line)
+      if match:
+        print(match.group(1))
+    else:
+      print(line)
+
+
+def tail_serial_console(vm):
+  next_start = '0'
+  while True:
+    result = run(['gcloud', 'compute', 'instances', 'get-serial-port-output', '--zone', LOCATION, '--start', next_start, vm], stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True)
+    if result.returncode != 0:
+      break
+    print_pretty_logs(vm, result.stdout)
+    next_start = re.search(r'--start=(\d*)', result.stderr).group(1)
+
+
+def merge_setup_scripts(scripts):
+  # Merge all setup scripts into one.
+  merged_script_path = tempfile.mkstemp()[1]
+  with open(merged_script_path, 'w') as merged_script_file:
+    for script in scripts:
+      with open(script, 'r') as script_file:
+        script_contents = script_file.read()
+        script_contents.replace('BUILDKITE_TOKEN="xxx"', 'BUILDKITE_TOKEN="%s"' % os.environ['BUILDKITE_TOKEN'])
+        merged_script_file.write(script_contents + '\n')
+  return merged_script_path
+
+
+def create_vm(vm, params):
+  merged_script_path = merge_setup_scripts(params['scripts'])
+  try:
+    cmd = ['gcloud', 'compute', 'instances', 'create', vm]
+    cmd.extend(['--zone', LOCATION])
+    cmd.extend(['--machine-type', 'n1-standard-32'])
+    cmd.extend(['--network', 'buildkite'])
+    if 'windows' in vm:
+      cmd.extend(['--metadata-from-file', 'windows-startup-script-ps1=' + merged_script_path])
+    else:
+      cmd.extend(['--metadata-from-file', 'startup-script=' + merged_script_path])
+    cmd.extend(['--min-cpu-platform', 'Intel Skylake'])
+    cmd.extend(['--boot-disk-type', 'pd-ssd'])
+    cmd.extend(['--boot-disk-size', '25GB'])
+    if 'source_image' in params:
+      cmd.extend(['--image', params['source_image']])
+    else:
+      cmd.extend(['--image-project', params['source_image_project']])
+      cmd.extend(['--image-family', params['source_image_family']])
+    run(cmd)
+  finally:
+    os.remove(merged_script_path)
+
+
+def delete_vm(vm):
+  run(['gcloud', 'compute', 'instances', 'delete', '--quiet', vm])
+
+
+def create_image(vm, target_image_family):
+  run(['gcloud', 'compute', 'images', 'create', vm, '--family', target_image_family, '--source-disk', vm, '--source-disk-zone', LOCATION])
+
+
+def main(argv=None):
+  if argv is None:
+    argv = sys.argv[1:]
+
+  if not 'BUILDKITE_TOKEN' in os.environ:
+    print("Please set the BUILDKITE_TOKEN environment variable.")
+    print("You can get the token from: https://buildkite.com/organizations/bazel/agents")
+    return 1
+
+  for vm, params in IMAGE_CREATION_VMS.items():
+    if argv and not vm in argv:
+        continue
+    vm = "%s-%s" % (vm, int(datetime.now().timestamp()))
+    try:
+      # Create the VM.
+      create_vm(vm, params)
+
+      # Wait for the VM to become ready.
+      wait_for_vm(vm, 'RUNNING')
+
+      # Continuously print the serial console.
+      tail_serial_console(vm)
+
+      # Wait for the VM to shutdown.
+      wait_for_vm(vm, 'TERMINATED')
+
+      # Create a new image from our VM.
+      create_image(vm, params['target_image_family'])
+    finally:
+      delete_vm(vm)
+  return 0
+
+if __name__ == '__main__':
+  sys.exit(main())
diff --git a/buildkite/install-android-sdk.sh b/buildkite/install-android-sdk.sh
new file mode 100755
index 0000000..399e7ef
--- /dev/null
+++ b/buildkite/install-android-sdk.sh
@@ -0,0 +1,48 @@
+#!/bin/bash
+#
+# Copyright 2015 The Bazel Authors. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#    http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Android NDK
+cd /opt
+curl -sSLo android-ndk.zip https://dl.google.com/android/repository/android-ndk-r14b-linux-x86_64.zip
+unzip android-ndk.zip > /dev/null
+rm android-ndk.zip
+
+# Android SDK
+mkdir -p /opt/android-sdk-linux
+cd /opt/android-sdk-linux
+curl -sSLo android-sdk.zip https://dl.google.com/android/repository/sdk-tools-linux-3859397.zip
+unzip android-sdk.zip > /dev/null
+rm android-sdk.zip
+expect -c '
+set timeout -1
+log_user 0
+spawn tools/bin/sdkmanager --update
+expect {
+    "Accept? (y/N)" { exp_send "y\r" ; exp_continue }
+    eof
+}
+'
+
+# This should be kept in sync with mac/mac-android.sh.
+tools/bin/sdkmanager \
+  "platform-tools" \
+  "build-tools;27.0.3" \
+  "platforms;android-24" \
+  "platforms;android-25" \
+  "platforms;android-26" \
+  "platforms;android-27" \
+  "extras;android;m2repository" > /dev/null
+chown -R root:root /opt/android*
diff --git a/buildkite/install-azul-zulu.sh b/buildkite/install-azul-zulu.sh
new file mode 100755
index 0000000..173bfa9
--- /dev/null
+++ b/buildkite/install-azul-zulu.sh
@@ -0,0 +1,20 @@
+#!/bin/bash
+#
+# Copyright 2017 The Bazel Authors. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#    http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv 0x219BD9C9
+apt-add-repository 'deb http://repos.azulsystems.com/ubuntu stable main'
+apt-get -qqy update
+apt-get -qqy install zulu-8 > /dev/null
diff --git a/buildkite/install-bazel.sh b/buildkite/install-bazel.sh
new file mode 100755
index 0000000..7626690
--- /dev/null
+++ b/buildkite/install-bazel.sh
@@ -0,0 +1,23 @@
+#!/bin/bash
+#
+# Copyright 2016 The Bazel Authors. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#    http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Install a bootstrap bazel; we use the latest released version
+PLATFORM=$(uname -s | tr '[:upper:]' '[:lower:]')-$(uname -m)
+BAZEL_VERSION=$(curl -sSI https://github.com/bazelbuild/bazel/releases/latest | grep '^Location: ' | sed 's|.*/||' | sed $'s/\r//')
+
+curl -sSLo install.sh "https://releases.bazel.build/${BAZEL_VERSION}/release/bazel-${BAZEL_VERSION}-without-jdk-installer-${PLATFORM}.sh"
+bash install.sh > /dev/null
+bazel version
diff --git a/buildkite/install-buildkite-agent.sh b/buildkite/install-buildkite-agent.sh
new file mode 100755
index 0000000..14ff9fc
--- /dev/null
+++ b/buildkite/install-buildkite-agent.sh
@@ -0,0 +1,75 @@
+#!/bin/bash
+#
+# Copyright 2017 The Bazel Authors. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#    http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys 32A37959C2FA5C3C99EFBC32A79206696452D198 &> /dev/null
+add-apt-repository -y "deb https://apt.buildkite.com/buildkite-agent stable main"
+
+apt-get -qqy update > /dev/null
+apt-get -qqy install buildkite-agent > /dev/null
+
+# Our very secret Buildkite token used to authenticate the agent.
+BUILDKITE_TOKEN="xxx"
+
+# Deduce the operating system from the hostname and put it into the metadata.
+case $(hostname) in
+  *ubuntu1404*)
+    osname="ubuntu1404"
+    ;;
+  *ubuntu1604*)
+    osname="ubuntu1604"
+    ;;
+  *freebsd11*)
+    osname="freebsd11"
+    ;;
+  default)
+    echo "Could not deduce operating system from hostname: $(hostname)!"
+    exit 1
+esac
+
+# Create configuration file for buildkite-agent.
+sed -i \
+  -e "s/^\(# \)*token=.*/token=\"${BUILDKITE_TOKEN}\"/g" \
+  -e "s/^\(# \)*name=.*/name=\"%hostname\"/g" \
+  -e "s/^\(# \)*meta-data=.*/meta-data=\"os=$osname\"/g" \
+  /etc/buildkite-agent/buildkite-agent.cfg
+
+cat > /etc/buildkite-agent/hooks/environment <<'EOF'
+#!/bin/bash
+
+# The `environment` hook will run before all other commands, and can be used
+# to set up secrets, data, etc. Anything exported in hooks will be available
+# to the build script.
+#
+# For example:
+#
+# export SECRET_VAR=token
+
+set -e
+
+export ANDROID_HOME="/opt/android-sdk-linux"
+export ANDROID_NDK_HOME="/opt/android-ndk-r14b"
+export BUILDKITE_ARTIFACT_UPLOAD_DESTINATION="gs://bazel-buildkite-artifacts/$BUILDKITE_JOB_ID"
+EOF
+
+chmod 0400 /etc/buildkite-agent/buildkite-agent.cfg
+chmod 0500 /etc/buildkite-agent/hooks/*
+chown -R buildkite-agent:buildkite-agent /etc/buildkite-agent
+
+# Do not start buildkite-agent automatically. The startup script will prepare
+# the local SSD first and then start it.
+if [ "$osname" = "ubuntu1404" ]; then
+  echo manual > /etc/init/buildkite-agent.override
+fi
diff --git a/buildkite/install-docker.sh b/buildkite/install-docker.sh
new file mode 100755
index 0000000..3dd8bb2
--- /dev/null
+++ b/buildkite/install-docker.sh
@@ -0,0 +1,27 @@
+#!/bin/bash
+#
+# Copyright 2017 The Bazel Authors. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#    http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+apt-get -qqy install apt-transport-https ca-certificates > /dev/null
+
+# From https://download.docker.com/linux/ubuntu/gpg
+curl -sSL https://download.docker.com/linux/ubuntu/gpg | apt-key add -
+add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable"
+
+apt-get -qqy update > /dev/null
+apt-get -qqy install docker-ce > /dev/null
+
+# Docker group
+usermod -aG docker buildkite-agent
diff --git a/buildkite/install-nodejs.sh b/buildkite/install-nodejs.sh
new file mode 100755
index 0000000..28d569c
--- /dev/null
+++ b/buildkite/install-nodejs.sh
@@ -0,0 +1,19 @@
+#!/bin/bash
+#
+# Copyright 2017 The Bazel Authors. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#    http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+curl -sSL https://deb.nodesource.com/setup_8.x | bash - > /dev/null
+apt-get -qqy install nodejs > /dev/null
+npm install -g typescript fried-twinkie
diff --git a/buildkite/install-oracle-java.sh b/buildkite/install-oracle-java.sh
new file mode 100755
index 0000000..88768e5
--- /dev/null
+++ b/buildkite/install-oracle-java.sh
@@ -0,0 +1,19 @@
+#!/bin/bash
+#
+# Copyright 2017 The Bazel Authors. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#    http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+add-apt-repository ppa:webupd8team/java
+echo oracle-java8-installer shared/accepted-oracle-license-v1-1 select true | /usr/bin/debconf-set-selections
+apt-get -qqy install oracle-java8-installer oracle-java8-set-default > /dev/null
diff --git a/buildkite/setup-freebsd.sh b/buildkite/setup-freebsd.sh
new file mode 100755
index 0000000..14754c5
--- /dev/null
+++ b/buildkite/setup-freebsd.sh
@@ -0,0 +1,33 @@
+#!/bin/sh
+#
+# Copyright 2017 The Bazel Authors. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#    http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Setup script for FreeBSD.
+set -eux
+
+## Install Bazel and its dependencies.
+pkg install -y \
+  bazel \
+  git \
+  wget \
+  zip
+
+## Mount procfs and fddescfs.
+mount -t fdescfs fdesc /dev/fd
+mount -t procfs proc /proc
+cat >> /etc/fstab <<EOF
+fdesc   /dev/fd         fdescfs         rw      0       0
+proc    /proc           procfs          rw      0       0
+EOF
diff --git a/buildkite/setup-ubuntu.sh b/buildkite/setup-ubuntu.sh
new file mode 100755
index 0000000..e30c046
--- /dev/null
+++ b/buildkite/setup-ubuntu.sh
@@ -0,0 +1,61 @@
+#!/bin/bash
+#
+# Copyright 2017 The Bazel Authors. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#    http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Setup script for Ubuntu 14.04 LTS and 16.04 LTS.
+
+# Prevent dpkg / apt-get / debconf from trying to access stdin.
+export DEBIAN_FRONTEND=noninteractive
+
+# Android SDK requires 32-bits libraries.
+dpkg --add-architecture i386
+
+apt-get -qqy update > /dev/null
+apt-get -qqy dist-upgrade > /dev/null
+
+packages=(
+  # Bazel dependencies.
+  build-essential
+  curl
+  git
+  python
+  python3
+  realpath
+  unzip
+  wget
+  xvfb
+  zip
+  zlib1g-dev
+
+  # Dependencies for Android SDK.
+  # https://developer.android.com/studio/troubleshoot.html#linux-libraries
+  # https://code.google.com/p/android/issues/detail?id=207212
+  expect
+  libbz2-1.0:i386
+  libncurses5:i386
+  libstdc++6:i386
+  libz1:i386
+
+  # Dependencies for TensorFlow.
+  libcurl3-dev
+  python-dev
+  python-numpy
+  python-pip
+  #python-wheel
+  swig
+)
+apt-get -qqy install "${packages[@]}" > /dev/null
+
+pip install mock
diff --git a/buildkite/setup-windows2016.ps1 b/buildkite/setup-windows2016.ps1
new file mode 100755
index 0000000..8290330
--- /dev/null
+++ b/buildkite/setup-windows2016.ps1
@@ -0,0 +1,106 @@
+# Stop on action error.
+$ErrorActionPreference = "Stop"
+
+# Install Chocolatey
+Invoke-Expression ((New-Object Net.WebClient).DownloadString("https://chocolatey.org/install.ps1"))
+
+if (-Not (Test-Path c:\bazel_ci)) {
+  New-Item c:\bazel_ci -type directory
+}
+Set-Location c:\bazel_ci
+
+# TODO(philwo) remove if it turns out we don't need Chocolatey.
+# Upgrade Chocolatey.
+# Write-Host "Updating Chocolatey..."
+# & "C:\ProgramData\chocolatey\choco.exe" upgrade chocolatey
+# & "C:\ProgramData\chocolatey\choco.exe" upgrade all
+
+# Update MSYS2 once.
+Write-Host "Updating MSYS2 packages (round 1)..."
+Start-Process -Wait "c:\msys64\msys2_shell.cmd" -ArgumentList "-c", "pacman --noconfirm -Syuu"
+
+# Update again, in case the first round only upgraded core packages.
+Write-Host "Updating MSYS2 packages (round 2)..."
+Start-Process -Wait "c:\msys64\msys2_shell.cmd" -ArgumentList "-c", "pacman --noconfirm -Syuu"
+
+# Install a couple of Python modules required by TensorFlow.
+Write-Host "Updating Python packages..."
+& "C:\Python3\Scripts\pip.exe" install --upgrade `
+    autograd `
+    numpy `
+    portpicker `
+    protobuf `
+    pyreadline `
+    six `
+    wheel
+
+# Fetch the instance ID from GCE.
+Write-Host "Fetching instance ID from GCE..."
+$webclient = (New-Object Net.WebClient)
+$webclient.Headers.Add("Metadata-Flavor", "Google")
+$jenkins_node = $webclient.DownloadString("http://metadata/computeMetadata/v1/instance/attributes/jenkins_node")
+
+# Get the latest release version number of Bazel.
+Write-Host "Grabbing latest Bazel version number from GitHub..."
+$url = "https://github.com/bazelbuild/bazel/releases/latest"
+$req = [system.Net.HttpWebRequest]::Create($url);
+$res = $req.getresponse();
+$res.Close();
+$bazel_version = $res.ResponseUri.AbsolutePath.TrimStart("/bazelbuild/bazel/releases/tag/")
+
+# Download the latest bazel.
+$folder = "c:\bazel_ci\installs\${bazel_version}"
+if (-Not (Test-Path "${folder}\bazel.exe")) {
+  Write-Host "Downloading Bazel ${bazel_version}..."
+  $url = "https://releases.bazel.build/${bazel_version}/release/bazel-${bazel_version}-without-jdk-windows-x86_64.exe"
+  New-Item $folder -type directory -force
+  (New-Object Net.WebClient).DownloadFile("${url}", "${folder}\bazel.exe")
+} else {
+  Write-Host "Bazel ${bazel_version} was already downloaded, skipping..."
+}
+
+# Create a junction to the latest release.
+Write-Host "Creating helper junctions..."
+$latest_folder = "C:\bazel_ci\installs\latest"
+if (Test-Path $latest_folder) {
+  Remove-Item -Force -Recurse $latest_folder
+}
+New-Item -ItemType Junction $latest_folder -Value $folder
+
+$bootstrap_folder = "C:\bazel_ci\installs\bootstrap"
+if (Test-Path $bootstrap_folder) {
+  Remove-Item -Force -Recurse $bootstrap_folder
+}
+New-Item -ItemType Junction $bootstrap_folder -Value $folder
+
+# Find the JDK. The path changes frequently, so hardcoding it is not enough.
+$java = Get-ChildItem "c:\Program Files\Java\jdk*" | Select-Object -Index 0 | foreach { $_.FullName }
+Write-Host "Found latest JDK at ${java}..."
+
+# Save the Jenkins slave.jar to a suitable location.
+Write-Host "Downloading https://ci.bazel.build/jnlpJars/slave.jar..."
+Invoke-WebRequest https://ci.bazel.build/jnlpJars/slave.jar -OutFile slave.jar
+
+# Create the service that runs the Jenkins slave
+# We can't execute Java directly because then it mysteriously fails with
+# "Sockets error: 10106: create", so we redirect through Powershell
+# The path change is needed because Jenkins cannot execute a different git
+# binary on different nodes, so we need to simply use "git"
+Write-Host "Creating Jenkins slave startup script..."
+$jnlpUrl = "https://ci.bazel.build/computer/${jenkins_node}/slave-agent.jnlp"
+$agent_script = @"
+`$env:path="c:\tools\msys64\usr\bin;`$env:path"
+cd c:\bazel_ci
+# A path name with c:\ in the JNLP URL makes Java hang. I don't know why.
+# Jenkins tries to reconnect to the wrong port if the server is restarted.
+# -noReconnect makes the agent die, and it is then subsequently restarted by
+# Windows because it is a service, and then all is well.
+& "${java}\bin\java" -jar c:\bazel_ci\slave.jar -jnlpUrl ${jnlpUrl} -noReconnect
+"@
+Write-Output $agent_script | Out-File -Encoding ascii agent_script.ps1
+
+Write-Host "Creating Jenkins slave service..."
+& nssm install bazel_ci powershell c:\bazel_ci\agent_script.ps1
+& nssm set bazel_ci AppStdout c:\bazel_ci\stdout.log
+& nssm set bazel_ci AppStderr c:\bazel_ci\stderr.log
+& nssm start bazel_ci
diff --git a/buildkite/shell-utils.sh b/buildkite/shell-utils.sh
new file mode 100755
index 0000000..4cf353b
--- /dev/null
+++ b/buildkite/shell-utils.sh
@@ -0,0 +1,21 @@
+#!/bin/bash
+#
+# Copyright 2017 The Bazel Authors. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#    http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Fail on errors.
+# Fail when using undefined variables.
+# Print all executed commands.
+# Fail when any command in a pipe fails.
+set -euxo pipefail
diff --git a/buildkite/shutdown.sh b/buildkite/shutdown.sh
new file mode 100755
index 0000000..f96e1a3
--- /dev/null
+++ b/buildkite/shutdown.sh
@@ -0,0 +1,17 @@
+#!/bin/bash
+#
+# Copyright 2017 The Bazel Authors. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#    http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+poweroff
diff --git a/buildkite/start_worker.py b/buildkite/start_worker.py
new file mode 100755
index 0000000..a78e209
--- /dev/null
+++ b/buildkite/start_worker.py
@@ -0,0 +1,120 @@
+#!/usr/bin/env python3
+
+import sys
+import subprocess
+import threading
+import queue
+
+DEBUG = True
+
+LOCATION = 'europe-west1-d'
+
+AGENTS = {
+    "buildkite-ubuntu1404": {
+        'count': 4,
+        'startup_script': 'startup-ubuntu1404.sh',
+        'machine_type': 'n1-standard-32',
+        'local_ssd': 'interface=nvme',
+    },
+    "buildkite-ubuntu1604": {
+        'count': 4,
+        'startup_script': 'startup-ubuntu1604.sh',
+        'machine_type': 'n1-standard-32',
+        'local_ssd': 'interface=nvme',
+    },
+}
+
+PRINT_LOCK = threading.Lock()
+WORK_QUEUE = queue.Queue()
+
+
+def debug(*args, **kwargs):
+  if DEBUG:
+    with PRINT_LOCK:
+      print(*args, **kwargs)
+
+
+def run(args, **kwargs):
+  debug('Running: %s' % ' '.join(args))
+  return subprocess.run(args, **kwargs)
+
+
+def delete_vm(vm):
+  return run(['gcloud', 'compute', 'instances', 'delete', '--quiet', vm])
+
+
+def create_vm(image_family, idx, params):
+  vm = '%s-%s' % (image_family, idx)
+  if delete_vm(vm).returncode == 0:
+    with PRINT_LOCK:
+      print("Deleted existing VM: %s" % vm)
+  cmd = ['gcloud', 'compute', 'instances', 'create', vm]
+  cmd.extend(['--zone', LOCATION])
+  cmd.extend(['--machine-type', params['machine_type']])
+  cmd.extend(['--network', 'buildkite'])
+  if 'windows' in vm:
+    cmd.extend(['--metadata-from-file', 'windows-startup-script-ps1=' + params['startup_script']])
+  else:
+    cmd.extend(['--metadata-from-file', 'startup-script=' + params['startup_script']])
+  cmd.extend(['--min-cpu-platform', 'Intel Skylake'])
+  cmd.extend(['--boot-disk-type', 'pd-ssd'])
+  cmd.extend(['--boot-disk-size', '25GB'])
+  if 'local_ssd' in params:
+    cmd.extend(['--local-ssd', params['local_ssd']])
+  cmd.extend(['--image-project', 'bazel-public'])
+  cmd.extend(['--image-family', image_family])
+  cmd.extend(['--service-account', 'remote-account@bazel-public.iam.gserviceaccount.com'])
+  cmd.extend(['--scopes', 'cloud-platform'])
+  run(cmd)
+
+
+def worker():
+  while True:
+    item = WORK_QUEUE.get()
+    if not item:
+      break
+    try:
+      create_vm(**item)
+    finally:
+      WORK_QUEUE.task_done()
+
+
+def main(argv=None):
+  if argv is None:
+    argv = sys.argv[1:]
+
+  # Put VM creation instructions into the work queue.
+  worker_count = 0
+  for image_family, params in AGENTS.items():
+    if argv and not image_family in argv:
+      continue
+    worker_count += params['count']
+    for idx in range(1, params['count'] + 1):
+      WORK_QUEUE.put({
+          'image_family': image_family,
+          'idx': idx,
+          'params': params
+      })
+
+  # Spawn worker threads that will create the VMs.
+  threads = []
+  for _ in range(worker_count):
+    t = threading.Thread(target=worker)
+    t.start()
+    threads.append(t)
+
+  # Wait for all VMs to be created.
+  WORK_QUEUE.join()
+
+  # Signal worker threads to exit.
+  for _ in range(worker_count):
+    WORK_QUEUE.put(None)
+
+  # Wait for worker threads to exit.
+  for t in threads:
+    t.join()
+
+  return 0
+
+if __name__ == '__main__':
+  sys.exit(main())
diff --git a/buildkite/startup-freebsd11.sh b/buildkite/startup-freebsd11.sh
new file mode 100755
index 0000000..05db1e5
--- /dev/null
+++ b/buildkite/startup-freebsd11.sh
@@ -0,0 +1,19 @@
+#!/bin/sh
+#
+# Copyright 2017 The Bazel Authors. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#    http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+set -eu
+
+exit 0
diff --git a/buildkite/startup-ubuntu1404.sh b/buildkite/startup-ubuntu1404.sh
new file mode 100755
index 0000000..d00c520
--- /dev/null
+++ b/buildkite/startup-ubuntu1404.sh
@@ -0,0 +1,31 @@
+#!/bin/bash
+#
+# Copyright 2017 The Bazel Authors. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#    http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+set -eu
+
+# Use a local SSD if available, otherwise use a RAM disk for our builds.
+if [ -e /dev/nvme0n1 ]; then
+  mkfs.ext4 -F /dev/nvme0n1
+  mount /dev/nvme0n1 /var/lib/buildkite-agent
+  chown -R buildkite-agent:buildkite-agent /var/lib/buildkite-agent
+  chmod 0755 /var/lib/buildkite-agent
+else
+  mount -t tmpfs -o mode=0755,uid=buildkite-agent,gid=buildkite-agent tmpfs /var/lib/buildkite-agent
+fi
+
+service buildkite-agent start
+
+exit 0
diff --git a/buildkite/startup-ubuntu1604.sh b/buildkite/startup-ubuntu1604.sh
new file mode 100755
index 0000000..05800f4
--- /dev/null
+++ b/buildkite/startup-ubuntu1604.sh
@@ -0,0 +1,31 @@
+#!/bin/bash
+#
+# Copyright 2017 The Bazel Authors. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#    http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+set -eu
+
+# Use a local SSD if available, otherwise use a RAM disk for our builds.
+if [ -e /dev/nvme0n1 ]; then
+  mkfs.ext4 -F /dev/nvme0n1
+  mount /dev/nvme0n1 /var/lib/buildkite-agent
+  chown -R buildkite-agent:buildkite-agent /var/lib/buildkite-agent
+  chmod 0755 /var/lib/buildkite-agent
+else
+  mount -t tmpfs -o mode=0755,uid=buildkite-agent,gid=buildkite-agent tmpfs /var/lib/buildkite-agent
+fi
+
+systemctl start buildkite-agent
+
+exit 0
diff --git a/buildkite/startup-windows2016.ps1 b/buildkite/startup-windows2016.ps1
new file mode 100755
index 0000000..e69de29
--- /dev/null
+++ b/buildkite/startup-windows2016.ps1