Replace Bazel-Install by a pipeline job

Simplifies the installation code by reducing the code specific
to each platform and removing having 2 jobs for it.

This new job stop installing HEAD Bazel which is no longer
generated from the new pipeline.

Change-Id: I2b03076cca22d1b0aeb0a3c60451abffe169d4f2
diff --git a/jenkins/jobs/BUILD b/jenkins/jobs/BUILD
index 3f43535..0a6bde9 100644
--- a/jenkins/jobs/BUILD
+++ b/jenkins/jobs/BUILD
@@ -27,7 +27,8 @@
     project_url = "http://bazel.io",
     substitutions = JOBS_SUBSTITUTIONS,
     deps = glob(["%s.*.tpl" % job]),
-) for job in BAZEL_JOBS.keys() if job != "Global/pipeline" and job != "CR/global-verifier"]
+) for job in BAZEL_JOBS.keys()
+  if job not in ["install-bazel", "Global/pipeline", "CR/global-verifier"]]
 
 jenkins_job(
     name = "Global/pipeline",
@@ -47,6 +48,12 @@
     ],
 )
 
+jenkins_job(
+    name = "install-bazel",
+    config = "install-bazel.xml.tpl",
+    deps = [":install-bazel.groovy"],
+)
+
 # TODO(dmarting): activate Tensorflow on mac (missing dependencies)
 bazel_github_job(
     name = "TensorFlow",
diff --git a/jenkins/jobs/Bazel-Install.bat.tpl b/jenkins/jobs/Bazel-Install.bat.tpl
deleted file mode 100644
index c18af9d..0000000
--- a/jenkins/jobs/Bazel-Install.bat.tpl
+++ /dev/null
@@ -1,61 +0,0 @@
-:: 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.
-
-:: Batch script to install bazel on the windows host
-@echo on
-
-:: Get the latest bazel version number
-echo $url = "https://github.com/bazelbuild/bazel/releases/latest" > get_latest_bazel_version.ps1
-echo $req=[system.Net.HttpWebRequest]::Create($url); >> get_latest_bazel_version.ps1
-echo $res = $req.getresponse(); >> get_latest_bazel_version.ps1
-echo $res.Close(); >> get_latest_bazel_version.ps1
-echo $res.ResponseUri.AbsolutePath.TrimStart("/bazelbuild/bazel/releases/tag/") >> get_latest_bazel_version.ps1
-for /F %%i in ('powershell -file get_latest_bazel_version.ps1') do set BAZEL_VERSION=%%i
-del get_latest_bazel_version.ps1
-
-echo BAZEL_VERSION=(%BAZEL_VERSION%)
-if "%BAZEL_VERSION%" == "" (
-  echo ERROR: Could not parse BAZEL_VERSION from https://github.com/bazelbuild/bazel/releases/latest
-  exit 1
-)
-
-:: Download the latest bazel release
-set folder=c:\bazel_ci\installs\%BAZEL_VERSION%
-
-
-:: Download MSVC version Bazel.
-:: TODO(pcloudy): Use the following URL after default Bazel release is MSVC version
-:: set url='https://releases.bazel.build/%BAZEL_VERSION%/release/bazel-%BAZEL_VERSION%-windows-x86_64.exe'
-set url='https://releases.bazel.build/%BAZEL_VERSION%/release/bazel-msvc-%BAZEL_VERSION%-windows-msvc-x86_64.exe'
-
-if not exist %folder%\bazel.exe (
-  md %folder%
-  powershell -Command "(New-Object Net.WebClient).DownloadFile(%url%, '%folder%\bazel.exe')"
-)
-
-:: Create a junction to the latest release
-rmdir /q c:\bazel_ci\installs\latest
-mklink /J c:\bazel_ci\installs\latest %folder%
-
-:: Also update bootstrap Bazel to the latest release
-rmdir /q c:\bazel_ci\installs\bootstrap
-mklink /J c:\bazel_ci\installs\bootstrap %folder%
-
-:: Install Bazel built at HEAD
-md c:\bazel_ci\installs\HEAD
-echo F | xcopy /y "bazel-installer\PLATFORM_NAME=windows-x86_64\output\ci\bazel*.exe" c:\bazel_ci\installs\HEAD\bazel.exe
-
-if not exist c:\bazel_ci\installs\HEAD\bazel.exe (
-  exit 1
-)
diff --git a/jenkins/jobs/Bazel-Install.sh.tpl b/jenkins/jobs/Bazel-Install.sh.tpl
deleted file mode 100644
index b72228c..0000000
--- a/jenkins/jobs/Bazel-Install.sh.tpl
+++ /dev/null
@@ -1,77 +0,0 @@
-#!/usr/bin/env 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.
-
-# Shell script to install bazel on the host
-
-set -eux
-
-# Get the platform we are running on
-PLATFORM=$(uname -s | tr '[:upper:]' '[:lower:]')-$(uname -m)
-
-# The test suite has not yet been fully adapted to work on FreeBSD. Additonally,
-# the installation on FreeBSD is different, as we do not provide binary packages
-# with installer scripts (but the devel/bazel port and the respective packages
-# built by FreeBSD can be used). So skip it for now.
-if [[ "${PLATFORM_NAME}" =~ "freebsd" ]] ; then
-  echo "Not installing on FreeBSD; only testing the build"
-  exit 0
-fi
-
-
-# Create the URL to download a specific version of bazel for current platform
-create_url() {
-  local version="$1"
-  local flavour="${2-}"
-  if [ -n "${flavour}" ]; then
-    flavour="-${2}"
-  fi
-  echo "https://releases.bazel.build/${version}/release/bazel-${version}${flavour}-installer-${PLATFORM}.sh"
-}
-
-# Install bazel using the specified installer
-install_bazel() {
-  local installer="$1"
-  local destination="$2"
-  mkdir -p "${destination}"
-  if [[ "${installer}" =~ ^https?:// ]]; then
-    curl -L -o install.sh "${installer}"
-    installer="${PWD}/install.sh"
-  fi
-  chmod 0755 "${installer}"
-  rm -fr "${destination}"
-  "${installer}" \
-    --base="${destination}" \
-    --bin="${destination}/binary"
-}
-
-# Get the version of latest BAZEL
-if [ -z "${BAZEL_VERSION:-}" ]; then
-  BAZEL_VERSION=$(curl -I https://github.com/bazelbuild/bazel/releases/latest | grep '^Location: ' | sed 's|.*/||' | sed $'s/\r//')
-fi
-
-# Install bazel from HEAD
-install_bazel "$(find $PWD/bazel-installer -name '*.sh' | \
-  grep -F "PLATFORM_NAME=${PLATFORM}" | grep -Fv jdk7 | grep -F without-jdk | head -1)" \
-  ~/.bazel/HEAD
-
-# Install latest Bazel if not yet installed
-if [ ! -d ~/.bazel/${BAZEL_VERSION} ]; then
-  install_bazel "$(create_url ${BAZEL_VERSION} without-jdk)" \
-    ~/.bazel/${BAZEL_VERSION}
-fi
-
-# Recreate symlinks to the latest version for Bazel
-rm -f ~/.bazel/latest ~/.bazel/latest-jdk7
-ln -s ~/.bazel/${BAZEL_VERSION} ~/.bazel/latest
diff --git a/jenkins/jobs/global.groovy b/jenkins/jobs/global.groovy
index ff82eb4..9429b04 100644
--- a/jenkins/jobs/global.groovy
+++ b/jenkins/jobs/global.groovy
@@ -75,7 +75,11 @@
                       configuration: json_config,
                       restrict_configuration: restrict_configuration,
                       excludes: ["**/*.bazel.build.tar*", "**/bazel", "**/bazel.exe"])
-          // TODO(dmarting): trigger bazel install everywhere in case of release.
+          if (is_release) {
+            stage("Install new release on all nodes") {
+              build(job: "install-bazel", wait: false, propagate: false)
+            }
+          }
         }
       }
     }
diff --git a/jenkins/jobs/install-bazel.groovy b/jenkins/jobs/install-bazel.groovy
new file mode 100644
index 0000000..90b6ee5
--- /dev/null
+++ b/jenkins/jobs/install-bazel.groovy
@@ -0,0 +1,48 @@
+// Copyright (C) 2017 The Bazel Authors
+//
+// 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 the bazel release on all the machine
+
+import build.bazel.ci.JenkinsUtils
+
+jobs = [:]
+nodes = JenkinsUtils.nodeNames("install-bazel")
+latest = "latest"
+
+stage("Get latest version of Bazel") {
+  latest = installBazel.getLatestBazelVersion()
+  echo "Latest bazel version is ${latest}"
+}
+
+for (int k = 0; k < nodes.size(); k++) {
+  def node = nodes[k]
+  if (!node.startsWith("freebsd")) {
+    // Skip freebsd who is installed from the port
+    jobs[node] = {
+      stage("Install Bazel on ${node}") {
+        installBazel(node: node,
+                     version: latest,
+                     flavours: node.startsWith("windows") ? [""] : ["", "-jdk7"],
+                     alias: "latest")
+      }
+    }
+  }
+}
+
+stage("Install on all nodes") {
+  // We fail after 4h in case a node is offline and not comming back online
+  timeout(240) {
+    parallel jobs
+  }
+}
diff --git a/jenkins/jobs/install-bazel.xml.tpl b/jenkins/jobs/install-bazel.xml.tpl
new file mode 100644
index 0000000..8ad10b0
--- /dev/null
+++ b/jenkins/jobs/install-bazel.xml.tpl
@@ -0,0 +1,14 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<flow-definition>
+  <actions/>
+  <description>Job to install Bazel on all nodes</description>
+  <keepDependencies>false</keepDependencies>
+  <properties>
+    <org.jenkinsci.plugins.workflow.job.properties.DisableConcurrentBuildsJobProperty/>
+  </properties>
+  <definition class="org.jenkinsci.plugins.workflow.cps.CpsFlowDefinition">
+    <script>{{ imports['//jenkins/jobs:install-bazel.groovy'] }}</script>
+    <sandbox>true</sandbox>
+  </definition>
+  <triggers/>
+</flow-definition>
diff --git a/jenkins/jobs/jobs-Bazel-Install-Trigger.xml.tpl b/jenkins/jobs/jobs-Bazel-Install-Trigger.xml.tpl
deleted file mode 100644
index b1238f2..0000000
--- a/jenkins/jobs/jobs-Bazel-Install-Trigger.xml.tpl
+++ /dev/null
@@ -1,105 +0,0 @@
-<?xml version='1.0' encoding='UTF-8'?>
-<!--
-  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.
--->
-<project>
-  <actions/>
-  <description>Just trigger Bazel-Install to run it on all slaves</description>
-  <keepDependencies>false</keepDependencies>
-  <properties/>
-  <assignedNode>deploy</assignedNode>
-  <canRoam>false</canRoam>
-  <disabled>false</disabled>
-  <blockBuildWhenDownstreamBuilding>false</blockBuildWhenDownstreamBuilding>
-  <blockBuildWhenUpstreamBuilding>false</blockBuildWhenUpstreamBuilding>
-  <triggers/>
-  <concurrentBuild>false</concurrentBuild>
-  <builders>
-    <hudson.plugins.parameterizedtrigger.TriggerBuilder plugin="{{ variables.JENKINS_parameterized_trigger }}">
-      <configs>
-        <hudson.plugins.parameterizedtrigger.BlockableBuildTriggerConfig>
-          <configs>
-            <hudson.plugins.parameterizedtrigger.CurrentBuildParameters/>
-          </configs>
-          <configFactories>
-            <org.jvnet.jenkins.plugins.nodelabelparameter.parameterizedtrigger.AllNodesForLabelBuildParameterFactory plugin="{{ variables.JENKINS_nodelabelparameter }}">
-              <name>PLATFORM_NAME</name>
-              <nodeLabel>install-bazel</nodeLabel>
-              <ignoreOfflineNodes>true</ignoreOfflineNodes>
-            </org.jvnet.jenkins.plugins.nodelabelparameter.parameterizedtrigger.AllNodesForLabelBuildParameterFactory>
-          </configFactories>
-          <projects>Bazel-Install</projects>
-          <condition>ALWAYS</condition>
-          <triggerWithNoParameters>false</triggerWithNoParameters>
-          <block>
-            <buildStepFailureThreshold>
-              <name>FAILURE</name>
-              <ordinal>2</ordinal>
-              <color>RED</color>
-              <completeBuild>true</completeBuild>
-            </buildStepFailureThreshold>
-            <unstableThreshold>
-              <name>UNSTABLE</name>
-              <ordinal>1</ordinal>
-              <color>YELLOW</color>
-              <completeBuild>true</completeBuild>
-            </unstableThreshold>
-            <failureThreshold>
-              <name>FAILURE</name>
-              <ordinal>2</ordinal>
-              <color>RED</color>
-              <completeBuild>true</completeBuild>
-            </failureThreshold>
-          </block>
-          <buildAllNodesWithLabel>false</buildAllNodesWithLabel>
-        </hudson.plugins.parameterizedtrigger.BlockableBuildTriggerConfig>
-      </configs>
-    </hudson.plugins.parameterizedtrigger.TriggerBuilder>
-  </builders>
-  <publishers>
-    {% if variables.SEND_EMAIL == "1" %}
-    <hudson.tasks.Mailer>
-      <recipients>{{ variables.BAZEL_BUILD_RECIPIENT }}</recipients>
-      <dontNotifyEveryUnstableBuild>false</dontNotifyEveryUnstableBuild>
-      <sendToIndividuals>false</sendToIndividuals>
-    </hudson.tasks.Mailer>
-    {% endif %}
-    <hudson.plugins.parameterizedtrigger.BuildTrigger>
-      <configs>
-        <hudson.plugins.parameterizedtrigger.BuildTriggerConfig>
-          <configs>
-            <hudson.plugins.parameterizedtrigger.CurrentBuildParameters/>
-          </configs>
-          <projects>Tutorial, {{ variables.GITHUB_JOBS }}</projects>
-          <condition>UNSTABLE_OR_BETTER</condition>
-          <triggerWithNoParameters>false</triggerWithNoParameters>
-        </hudson.plugins.parameterizedtrigger.BuildTriggerConfig>
-      </configs>
-    </hudson.plugins.parameterizedtrigger.BuildTrigger>
-  </publishers>
-  <buildWrappers>
-    <hudson.plugins.build__timeout.BuildTimeoutWrapper>
-      <strategy class="hudson.plugins.build_timeout.impl.AbsoluteTimeOutStrategy">
-        <timeoutMinutes>240</timeoutMinutes>
-      </strategy>
-      <operationList>
-        <hudson.plugins.build__timeout.operations.FailOperation/>
-        <hudson.plugins.build__timeout.operations.WriteDescriptionOperation>
-          <description>Timed out</description>
-        </hudson.plugins.build__timeout.operations.WriteDescriptionOperation>
-      </operationList>
-    </hudson.plugins.build__timeout.BuildTimeoutWrapper>
-  </buildWrappers>
-</project>
diff --git a/jenkins/jobs/jobs-Bazel-Install.xml.tpl b/jenkins/jobs/jobs-Bazel-Install.xml.tpl
deleted file mode 100644
index c303859..0000000
--- a/jenkins/jobs/jobs-Bazel-Install.xml.tpl
+++ /dev/null
@@ -1,107 +0,0 @@
-<?xml version='1.0' encoding='UTF-8'?>
-<!--
-  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.
--->
-<project>
-  <actions/>
-  <description>Install Bazel on all the slaves</description>
-  <keepDependencies>false</keepDependencies>
-  <properties>
-    <hudson.model.ParametersDefinitionProperty>
-      <parameterDefinitions>
-        <hudson.model.StringParameterDefinition>
-          <name>PLATFORM_NAME</name>
-          <description></description>
-          <defaultValue></defaultValue>
-        </hudson.model.StringParameterDefinition>
-      </parameterDefinitions>
-    </hudson.model.ParametersDefinitionProperty>
-  </properties>
-  <quietPeriod>0</quietPeriod>
-  <canRoam>true</canRoam>
-  <disabled>false</disabled>
-  <blockBuildWhenDownstreamBuilding>false</blockBuildWhenDownstreamBuilding>
-  <blockBuildWhenUpstreamBuilding>false</blockBuildWhenUpstreamBuilding>
-  <concurrentBuild>true</concurrentBuild>
-  <builders>
-    <org.jenkinsci.plugins.conditionalbuildstep.singlestep.SingleConditionalBuilder>
-      <condition class="org.jenkins_ci.plugins.run_condition.core.ExpressionCondition">
-        <expression>^((?!windows).)*$</expression>
-        <label>${PLATFORM_NAME}</label>
-      </condition>
-      <buildStep class="hudson.tasks.Shell">
-        <!-- Files copied are read-only, and jobs might fails because of that, clean-up. -->
-        <command>rm -fr bazel-installer</command>
-      </buildStep>
-      <runner class="org.jenkins_ci.plugins.run_condition.BuildStepRunner$Fail"/>
-    </org.jenkinsci.plugins.conditionalbuildstep.singlestep.SingleConditionalBuilder>
-    <org.jenkinsci.plugins.conditionalbuildstep.singlestep.SingleConditionalBuilder>
-      <condition class="org.jenkins_ci.plugins.run_condition.core.ExpressionCondition">
-        <expression>windows.*</expression>
-        <label>${PLATFORM_NAME}</label>
-      </condition>
-      <buildStep class="hudson.tasks.BatchFile">
-        <command>rmdir /q /s bazel-installer</command>
-      </buildStep>
-      <runner class="org.jenkins_ci.plugins.run_condition.BuildStepRunner$Fail"/>
-    </org.jenkinsci.plugins.conditionalbuildstep.singlestep.SingleConditionalBuilder>
-    <hudson.plugins.copyartifact.CopyArtifact>
-      <project>Bazel</project>
-      <filter>**/ci/*installer*.sh,**/ci/bazel*.exe</filter>
-      <target>bazel-installer</target>
-      <excludes></excludes>
-      <selector class="hudson.plugins.copyartifact.TriggeredBuildSelector">
-        <fallbackToLastSuccessful>true</fallbackToLastSuccessful>
-        <upstreamFilterStrategy>UseGlobalSetting</upstreamFilterStrategy>
-      </selector>
-      <doNotFingerprintArtifacts>false</doNotFingerprintArtifacts>
-    </hudson.plugins.copyartifact.CopyArtifact>
-    <org.jenkinsci.plugins.conditionalbuildstep.singlestep.SingleConditionalBuilder>
-      <condition class="org.jenkins_ci.plugins.run_condition.core.ExpressionCondition">
-        <expression>^((?!windows).)*$</expression>
-        <label>${PLATFORM_NAME}</label>
-      </condition>
-      <buildStep class="hudson.tasks.Shell">
-	<command>{{ imports['//jenkins/jobs:Bazel-Install.sh.tpl'] }}</command>
-      </buildStep>
-      <runner class="org.jenkins_ci.plugins.run_condition.BuildStepRunner$Fail"/>
-    </org.jenkinsci.plugins.conditionalbuildstep.singlestep.SingleConditionalBuilder>
-    <org.jenkinsci.plugins.conditionalbuildstep.singlestep.SingleConditionalBuilder>
-      <condition class="org.jenkins_ci.plugins.run_condition.core.ExpressionCondition">
-        <expression>windows.*</expression>
-        <label>${PLATFORM_NAME}</label>
-      </condition>
-      <buildStep class="hudson.tasks.BatchFile">
-        <command>{{ imports['//jenkins/jobs:Bazel-Install.bat.tpl'] }}</command>
-      </buildStep>
-      <runner class="org.jenkins_ci.plugins.run_condition.BuildStepRunner$Fail"/>
-    </org.jenkinsci.plugins.conditionalbuildstep.singlestep.SingleConditionalBuilder>
-  </builders>
-  <publishers/>
-  <buildWrappers>
-    <hudson.plugins.build__timeout.BuildTimeoutWrapper>
-      <strategy class="hudson.plugins.build_timeout.impl.AbsoluteTimeOutStrategy">
-        <timeoutMinutes>240</timeoutMinutes>
-      </strategy>
-      <operationList>
-        <hudson.plugins.build__timeout.operations.FailOperation/>
-        <hudson.plugins.build__timeout.operations.WriteDescriptionOperation>
-          <description>Timed out</description>
-        </hudson.plugins.build__timeout.operations.WriteDescriptionOperation>
-      </operationList>
-    </hudson.plugins.build__timeout.BuildTimeoutWrapper>
-  </buildWrappers>
-</project>
-
diff --git a/jenkins/jobs/jobs.bzl b/jenkins/jobs/jobs.bzl
index fa09a2d..f94b118 100644
--- a/jenkins/jobs/jobs.bzl
+++ b/jenkins/jobs/jobs.bzl
@@ -64,8 +64,7 @@
     "Github-Trigger": UNIX_PLATFORMS,
     "Global/pipeline": [],
     "CR/global-verifier": [],
-    "Bazel-Install": [],
-    "Bazel-Install-Trigger": [],
+    "install-bazel": [],
 }
 
 BAZEL_JOBS = BAZEL_STAGING_JOBS + {
diff --git a/jenkins/lib/src/build/bazel/ci/JenkinsUtils.groovy b/jenkins/lib/src/build/bazel/ci/JenkinsUtils.groovy
index 6f9fdc4..643cfcc 100644
--- a/jenkins/lib/src/build/bazel/ci/JenkinsUtils.groovy
+++ b/jenkins/lib/src/build/bazel/ci/JenkinsUtils.groovy
@@ -41,10 +41,14 @@
     return node_label
   }
 
-  /** Returns the list of all slave' names */
+  /** Returns the list of all slave' names (optionally filtering by label) */
   @NonCPS
-  static def nodeNames() {
-    return jenkins.model.Jenkins.instance.nodes.collect { node -> node.name }
+  static def nodeNames(label = null) {
+    def nodes = jenkins.model.Jenkins.instance.nodes
+    if (label != null) {
+      nodes = nodes.findAll { node -> label in node.labelString.split() }
+    }
+    return nodes.collect { node -> node.name }
   }
 
   /** Returns the list of job in the folder `folderName` (empty or null for top folder). */
diff --git a/jenkins/lib/vars/installBazel.groovy b/jenkins/lib/vars/installBazel.groovy
new file mode 100644
index 0000000..4a19dc5
--- /dev/null
+++ b/jenkins/lib/vars/installBazel.groovy
@@ -0,0 +1,99 @@
+// Copyright (C) 2017 The Bazel Authors.
+//
+// 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.
+
+import java.nio.channels.Channels
+
+// Returns the latest Bazel version available on github
+@NonCPS
+def getLatestBazelVersion() {
+  def url = new URL("https://github.com/bazelbuild/bazel/releases/latest")
+  def conn = url.openConnection()
+  conn.setInstanceFollowRedirects(false)
+  String location = conn.getHeaderField("Location").trim()
+  return location.replaceAll("^.*/", "")  // Last component of the location is the release tag
+}
+
+// Install bazel version X from the web
+private def installBazel(script, version, flavour, platform) {
+  def release_url = "https://releases.bazel.build/${version}/release"
+  if (platform.startsWith("windows")) {
+    // TODO(dmarting): this should be included in the flavour rather than special casing here
+    def msvc = ""
+    if (platform.startsWith("windows-msvc")) {
+      msvc = "-msvc"
+    }
+    def url = "${release_url}/bazel${msvc}-${version}-windows${msvc}-x86_64.exe"
+    def to = "c:\\bazel_ci\\installs\\${version}\\bazel.exe"
+    script.bat "powershell -Command \"(New-Object Net.WebClient).DownloadFile('${url}', '${to}')\""
+  } else {
+    def destination ="${env.HOME}/.bazel/${version}${flavour}"
+    // TODO(dmarting): this is kind of a hack, can we select -without-jdk in a better way?
+    def jdk = flavour.isEmpty() ? "-without-jdk" : ""
+    script.sh """#!/bin/bash -x
+curl -L -o install.sh '${release_url}/bazel-${version}${flavour}${jdk}-installer-${platform}.sh'
+chmod 0755 install.sh
+./install.sh --base='${destination}' --bin='${destination}/binary'
+"""
+  }
+}
+
+@NonCPS
+private def getPlatformFromNodeName(node) {
+  def platforms = ["windows-msvc": "windows-msvc-x86_64",
+                   "windows": "windows-x86_64",
+                   "darwin": "darwin-x86_64",
+                   "": "linux-x86_64"]
+  return platforms.find { e -> node.startsWith(e.key) }.value
+}
+
+// A step to install a released version of Bazel
+// parameters:
+//   version: the version to install, default to latest
+//   alias: alias of the version, if version is latest, this is set to "latest" too
+//   flavour: flavours to install, default [""]
+def call(params = [:]) {
+  params["version"] = params.get("version", "latest")
+  params["flavours"] = params.get("flavours", [""])
+  params["alias"] = params.get("alias", params.version == "latest" ? "latest" : "")
+
+  if (params.version == "latest") {
+    params.version = getLatestBazelVersion()
+  }
+
+  machine(params.node) {
+    // Determine the platform
+    def platform = getPlatformFromNodeName(params.node)
+
+    // install bazel
+    for (flavour in params.flavours) {
+      installBazel(this, params.version, flavour, platform)
+    }
+
+    // Symlinks
+    if (!params.alias.isEmpty()) {
+      for (flavour in params.flavours) {
+        def from = "${params.version}${flavour}"
+        def to = "${params.alias}${flavour}"
+        if (platform.startsWith("windows")) {
+          bat """
+rmdir /q c:\\bazel_ci\\installs\\${to}
+mklink /J c:\\bazel_ci\\installs\\${to} c:\\bazel_ci\\installs\\${from}
+"""
+        } else {
+          sh "rm -f ~/.bazel/${to}; ln -s ~/.bazel/${from} ~/.bazel/${to}"
+        }
+      }
+    }
+  }
+}