blob: 4229c68e8c599e57a0380d8df82cbc356e4d6d6f [file] [log] [blame]
Philipp Wollermanndcaddd92018-02-21 14:13:43 +01001#!/usr/bin/env python3
2#
Jakob Buchgraber0b6a39d2018-02-17 00:45:14 +01003# Copyright 2018 The Bazel Authors. All rights reserved.
4#
5# Licensed under the Apache License, Version 2.0 (the "License");
6# you may not use this file except in compliance with the License.
7# You may obtain a copy of the License at
8#
9# http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS,
13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14# See the License for the specific language governing permissions and
15# limitations under the License.
16
Jakob Buchgraber0b6a39d2018-02-17 00:45:14 +010017import argparse
Jakob Buchgraberd20ffeb2018-02-18 03:16:43 +010018import base64
Jakob Buchgraber0b6a39d2018-02-17 00:45:14 +010019import codecs
Jakob Buchgraberd20ffeb2018-02-18 03:16:43 +010020import hashlib
Jakob Buchgraber0b6a39d2018-02-17 00:45:14 +010021import json
Jakob Buchgraber6db0f262018-02-17 15:45:54 +010022import multiprocessing
Philipp Wollermann0a04cf32018-02-21 17:07:22 +010023import os
Philipp Wollermanndcaddd92018-02-21 14:13:43 +010024import os.path
Jakob Buchgraber257693b2018-02-20 00:03:56 +010025import random
Jakob Buchgraber0b6a39d2018-02-17 00:45:14 +010026import re
Philipp Wollermanndcaddd92018-02-21 14:13:43 +010027from shutil import copyfile
Jakob Buchgraber0b6a39d2018-02-17 00:45:14 +010028import shutil
Philipp Wollermanndcaddd92018-02-21 14:13:43 +010029import stat
Jakob Buchgraber0b6a39d2018-02-17 00:45:14 +010030import subprocess
31import sys
Jakob Buchgraber0b6a39d2018-02-17 00:45:14 +010032import tempfile
33import urllib.request
Philipp Wollermannc030f2e2018-02-21 17:02:19 +010034from urllib.request import url2pathname
Jakob Buchgraber0b6a39d2018-02-17 00:45:14 +010035from urllib.parse import urlparse
Philipp Wollermanndcaddd92018-02-21 14:13:43 +010036
37# Initialize the random number generator.
38random.seed()
39
Jakob Buchgraber95e3d572018-02-21 18:48:49 +010040
Philipp Wollermanndcaddd92018-02-21 14:13:43 +010041class BuildkiteException(Exception):
42 """
43 Raised whenever something goes wrong and we should exit with an error.
44 """
45 pass
46
47
48class BinaryUploadRaceException(Exception):
49 """
50 Raised when try_publish_binaries wasn't able to publish a set of binaries,
51 because the generation of the current file didn't match the expected value.
52 """
53 pass
54
55
56class BazelTestFailedException(Exception):
57 """
58 Raised when a Bazel test fails.
59 """
60 pass
61
62
Jakob Buchgraber95e3d572018-02-21 18:48:49 +010063class BazelTestFailedException(Exception):
64 """
65 Raised when a Bazel build fails.
66 """
67 pass
68
Philipp Wollermanndcaddd92018-02-21 14:13:43 +010069def eprint(*args, **kwargs):
70 """
71 Print to stderr and flush (just in case).
72 """
73 print(*args, flush=True, file=sys.stderr, **kwargs)
Jakob Buchgraber0b6a39d2018-02-17 00:45:14 +010074
75
76def downstream_projects():
Philipp Wollermann598b4a42018-02-19 17:03:36 +010077 return {
Jakob Buchgraberf462ff92018-02-20 17:34:09 +010078 "Bazel Remote Execution": {
79 "git_repository": "https://github.com/bazelbuild/bazel.git",
80 "http_config": "https://raw.githubusercontent.com/bazelbuild/continuous-integration/master/buildkite/pipelines/bazel-remote-execution-postsubmit.json"
81 },
Philipp Wollermann598b4a42018-02-19 17:03:36 +010082 "BUILD_file_generator": {
83 "git_repository": "https://github.com/bazelbuild/BUILD_file_generator.git",
84 "http_config": "https://raw.githubusercontent.com/bazelbuild/continuous-integration/master/buildkite/pipelines/BUILD_file_generator-postsubmit.json"
85 },
86 "bazel-toolchains": {
87 "git_repository": "https://github.com/bazelbuild/bazel-toolchains.git",
88 "http_config": "https://raw.githubusercontent.com/bazelbuild/continuous-integration/master/buildkite/pipelines/bazel-toolchains-postsubmit.json"
89 },
90 "buildtools": {
91 "git_repository": "https://github.com/bazelbuild/buildtools.git",
92 "http_config": "https://raw.githubusercontent.com/bazelbuild/continuous-integration/master/buildkite/pipelines/buildtools-postsubmit.json"
93 },
94 "CLion Plugin": {
95 "git_repository": "https://github.com/bazelbuild/intellij.git",
96 "http_config": "https://raw.githubusercontent.com/bazelbuild/continuous-integration/master/buildkite/pipelines/clion-postsubmit.json"
97 },
98 "Eclipse Plugin": {
99 "git_repository": "https://github.com/bazelbuild/eclipse.git",
100 "http_config": "https://raw.githubusercontent.com/bazelbuild/continuous-integration/master/buildkite/pipelines/eclipse-postsubmit.json"
101 },
102 "Gerrit": {
103 "git_repository": "https://gerrit.googlesource.com/gerrit.git",
104 "http_config": "https://raw.githubusercontent.com/bazelbuild/continuous-integration/master/buildkite/pipelines/gerrit-postsubmit.json"
105 },
106 "Google Logging": {
107 "git_repository": "https://github.com/google/glog.git",
108 "http_config": "https://raw.githubusercontent.com/bazelbuild/continuous-integration/master/buildkite/pipelines/glog-postsubmit.json"
109 },
110 "IntelliJ Plugin": {
111 "git_repository": "https://github.com/bazelbuild/intellij.git",
112 "http_config": "https://raw.githubusercontent.com/bazelbuild/continuous-integration/master/buildkite/pipelines/intellij-postsubmit.json"
113 },
114 "migration-tooling": {
115 "git_repository": "https://github.com/bazelbuild/migration-tooling.git",
116 "http_config": "https://raw.githubusercontent.com/bazelbuild/continuous-integration/master/buildkite/pipelines/migration-tooling-postsubmit.json"
117 },
118 "protobuf": {
119 "git_repository": "https://github.com/google/protobuf.git",
120 "http_config": "https://raw.githubusercontent.com/bazelbuild/continuous-integration/master/buildkite/pipelines/protobuf-postsubmit.json"
121 },
122 "re2": {
123 "git_repository": "https://github.com/google/re2.git",
124 "http_config": "https://raw.githubusercontent.com/bazelbuild/continuous-integration/master/buildkite/pipelines/re2-postsubmit.json"
125 },
126 "rules_appengine": {
127 "git_repository": "https://github.com/bazelbuild/rules_appengine.git",
128 "http_config": "https://raw.githubusercontent.com/bazelbuild/continuous-integration/master/buildkite/pipelines/rules_appengine-postsubmit.json"
129 },
130 "rules_closure": {
131 "git_repository": "https://github.com/bazelbuild/rules_closure.git",
132 "http_config": "https://raw.githubusercontent.com/bazelbuild/continuous-integration/master/buildkite/pipelines/rules_closure-postsubmit.json"
133 },
134 "rules_d": {
135 "git_repository": "https://github.com/bazelbuild/rules_d.git",
136 "http_config": "https://raw.githubusercontent.com/bazelbuild/continuous-integration/master/buildkite/pipelines/rules_d-postsubmit.json"
137 },
138 "rules_go": {
139 "git_repository": "https://github.com/bazelbuild/rules_go.git",
140 "http_config": "https://raw.githubusercontent.com/bazelbuild/continuous-integration/master/buildkite/pipelines/rules_go-postsubmit.json"
141 },
142 "rules_groovy": {
143 "git_repository": "https://github.com/bazelbuild/rules_groovy.git",
144 "http_config": "https://raw.githubusercontent.com/bazelbuild/continuous-integration/master/buildkite/pipelines/rules_groovy-postsubmit.json"
145 },
146 "rules_gwt": {
147 "git_repository": "https://github.com/bazelbuild/rules_gwt.git",
148 "http_config": "https://raw.githubusercontent.com/bazelbuild/continuous-integration/master/buildkite/pipelines/rules_gwt-postsubmit.json"
149 },
150 "rules_jsonnet": {
151 "git_repository": "https://github.com/bazelbuild/rules_jsonnet.git",
152 "http_config": "https://raw.githubusercontent.com/bazelbuild/continuous-integration/master/buildkite/pipelines/rules_jsonnet-postsubmit.json"
153 },
154 "rules_k8s": {
155 "git_repository": "https://github.com/bazelbuild/rules_k8s.git",
156 "http_config": "https://raw.githubusercontent.com/bazelbuild/continuous-integration/master/buildkite/pipelines/rules_k8s-postsubmit.json"
157 },
158 "rules_nodejs": {
159 "git_repository": "https://github.com/bazelbuild/rules_nodejs.git",
160 "http_config": "https://raw.githubusercontent.com/bazelbuild/continuous-integration/master/buildkite/pipelines/rules_nodejs-postsubmit.json"
161 },
162 "rules_perl": {
163 "git_repository": "https://github.com/bazelbuild/rules_perl.git",
164 "http_config": "https://raw.githubusercontent.com/bazelbuild/continuous-integration/master/buildkite/pipelines/rules_perl-postsubmit.json"
165 },
166 "rules_python": {
167 "git_repository": "https://github.com/bazelbuild/rules_python.git",
168 "http_config": "https://raw.githubusercontent.com/bazelbuild/continuous-integration/master/buildkite/pipelines/rules_python-postsubmit.json"
169 },
170 "rules_rust": {
171 "git_repository": "https://github.com/bazelbuild/rules_rust.git",
172 "http_config": "https://raw.githubusercontent.com/bazelbuild/continuous-integration/master/buildkite/pipelines/rules_rust-postsubmit.json"
173 },
174 "rules_sass": {
175 "git_repository": "https://github.com/bazelbuild/rules_sass.git",
176 "http_config": "https://raw.githubusercontent.com/bazelbuild/continuous-integration/master/buildkite/pipelines/rules_sass-postsubmit.json"
177 },
178 "rules_scala": {
179 "git_repository": "https://github.com/bazelbuild/rules_scala.git",
180 "http_config": "https://raw.githubusercontent.com/bazelbuild/continuous-integration/master/buildkite/pipelines/rules_scala-postsubmit.json"
181 },
182 "rules_typescript": {
183 "git_repository": "https://github.com/bazelbuild/rules_typescript.git",
184 "http_config": "https://raw.githubusercontent.com/bazelbuild/continuous-integration/master/buildkite/pipelines/rules_typescript-postsubmit.json"
185 },
186 # Enable once is resolved: https://github.com/bazelbuild/continuous-integration/issues/191
187 # "rules_webtesting": {
188 # "git_repository": "https://github.com/bazelbuild/rules_webtesting.git",
189 # "http_config": "https://raw.githubusercontent.com/bazelbuild/continuous-integration/master/buildkite/pipelines/rules_webtesting-postsubmit.json"
190 # },
191 "skydoc": {
192 "git_repository": "https://github.com/bazelbuild/skydoc.git",
193 "http_config": "https://raw.githubusercontent.com/bazelbuild/continuous-integration/master/buildkite/pipelines/skydoc-postsubmit.json"
194 },
195 "subpar": {
196 "git_repository": "https://github.com/google/subpar.git",
197 "http_config": "https://raw.githubusercontent.com/bazelbuild/continuous-integration/master/buildkite/pipelines/subpar-postsubmit.json"
198 },
199 "TensorFlow": {
200 "git_repository": "https://github.com/tensorflow/tensorflow.git",
201 "http_config": "https://raw.githubusercontent.com/bazelbuild/continuous-integration/master/buildkite/pipelines/tensorflow-postsubmit.json"
202 },
203 "TensorFlow Serving": {
204 "git_repository": "https://github.com/tensorflow/serving.git",
205 "http_config": "https://raw.githubusercontent.com/bazelbuild/continuous-integration/master/buildkite/pipelines/tensorflow-serving-postsubmit.json"
206 }
207 }
Jakob Buchgraber0b6a39d2018-02-17 00:45:14 +0100208
209
Philipp Wollermannbcedf9b2018-02-19 18:07:44 +0100210def python_binary(platform=None):
211 if platform == "windows":
212 return "python.exe"
Philipp Wollermann598b4a42018-02-19 17:03:36 +0100213 return "python3.6"
Jakob Buchgraber0b6a39d2018-02-17 00:45:14 +0100214
215
216def bazelcipy_url():
Philipp Wollermanndb024862018-02-19 17:16:56 +0100217 """
Philipp Wollermann598b4a42018-02-19 17:03:36 +0100218 URL to the latest version of this script.
Philipp Wollermanndb024862018-02-19 17:16:56 +0100219 """
Philipp Wollermann598b4a42018-02-19 17:03:36 +0100220 return "https://raw.githubusercontent.com/bazelbuild/continuous-integration/master/buildkite/bazelci.py"
Jakob Buchgraber0b6a39d2018-02-17 00:45:14 +0100221
222
Jakob Buchgraber0b6a39d2018-02-17 00:45:14 +0100223def platforms_info():
Philipp Wollermanndb024862018-02-19 17:16:56 +0100224 """
Philipp Wollermann598b4a42018-02-19 17:03:36 +0100225 Returns a map containing all supported platform names as keys, with the
226 values being the platform name in a human readable format, and a the
227 buildkite-agent's working directory.
Philipp Wollermanndb024862018-02-19 17:16:56 +0100228 """
Philipp Wollermann598b4a42018-02-19 17:03:36 +0100229 return {
230 "ubuntu1404": {
231 "name": "Ubuntu 14.04",
Philipp Wollermann1c036362018-02-19 17:16:31 +0100232 "agent-directory": "/var/lib/buildkite-agent/builds/${BUILDKITE_AGENT_NAME}"
Philipp Wollermann598b4a42018-02-19 17:03:36 +0100233 },
234 "ubuntu1604": {
235 "name": "Ubuntu 16.04",
Philipp Wollermann1c036362018-02-19 17:16:31 +0100236 "agent-directory": "/var/lib/buildkite-agent/builds/${BUILDKITE_AGENT_NAME}"
Philipp Wollermann598b4a42018-02-19 17:03:36 +0100237 },
238 "macos": {
239 "name": "macOS",
Philipp Wollermann1c036362018-02-19 17:16:31 +0100240 "agent-directory": "/usr/local/var/buildkite-agent/builds/${BUILDKITE_AGENT_NAME}"
241 },
242 "windows": {
243 "name": "Windows",
244 "agent-directory": "d:/build/${BUILDKITE_AGENT_NAME}",
Philipp Wollermann598b4a42018-02-19 17:03:36 +0100245 }
246 }
Jakob Buchgraber0b6a39d2018-02-17 00:45:14 +0100247
Jakob Buchgraber6db0f262018-02-17 15:45:54 +0100248
Jakob Buchgraber257693b2018-02-20 00:03:56 +0100249def flaky_test_meme_url():
Jakob Buchgraberf9107462018-02-20 00:18:39 +0100250 urls = ["https://storage.googleapis.com/bazel-buildkite-memes/flaky_tests_1.jpg",
Jakob Buchgraberad6692e2018-02-20 11:12:00 +0100251 "https://storage.googleapis.com/bazel-buildkite-memes/flaky_tests_2.jpg"]
Jakob Buchgraber257693b2018-02-20 00:03:56 +0100252 return random.choice(urls)
253
254
Jakob Buchgraber0b6a39d2018-02-17 00:45:14 +0100255def downstream_projects_root(platform):
Philipp Wollermann598b4a42018-02-19 17:03:36 +0100256 downstream_projects_dir = os.path.expandvars(
257 "${BUILDKITE_ORGANIZATION_SLUG}-downstream-projects")
258 path = os.path.join(agent_directory(platform), downstream_projects_dir)
259 if not os.path.exists(path):
260 os.makedirs(path)
261 return path
Jakob Buchgraber0b6a39d2018-02-17 00:45:14 +0100262
263
264def agent_directory(platform):
Philipp Wollermann598b4a42018-02-19 17:03:36 +0100265 return os.path.expandvars(platforms_info()[platform]["agent-directory"])
Jakob Buchgraber0b6a39d2018-02-17 00:45:14 +0100266
267
268def supported_platforms():
Philipp Wollermann598b4a42018-02-19 17:03:36 +0100269 return set(platforms_info().keys())
Jakob Buchgraber0b6a39d2018-02-17 00:45:14 +0100270
271
Jakob Buchgraber0b6a39d2018-02-17 00:45:14 +0100272def fetch_configs(http_url):
Philipp Wollermanndb024862018-02-19 17:16:56 +0100273 """
Philipp Wollermann598b4a42018-02-19 17:03:36 +0100274 If specified fetches the build configuration from http_url, else tries to
275 read it from .bazelci/config.json.
276 Returns the json configuration as a python data structure.
Philipp Wollermanndb024862018-02-19 17:16:56 +0100277 """
Philipp Wollermann598b4a42018-02-19 17:03:36 +0100278 if http_url is None:
279 with open(".bazelci/config.json", "r") as fd:
280 return json.load(fd)
281 with urllib.request.urlopen(http_url) as resp:
282 reader = codecs.getreader("utf-8")
283 return json.load(reader(resp))
Jakob Buchgraber0b6a39d2018-02-17 00:45:14 +0100284
Jakob Buchgraber9c83de72018-02-18 15:32:44 +0100285
Jakob Buchgraber3120f7a2018-02-18 13:28:02 +0100286def print_collapsed_group(name):
Philipp Wollermanndcaddd92018-02-21 14:13:43 +0100287 eprint("\n--- {0}\n".format(name))
Jakob Buchgraber3120f7a2018-02-18 13:28:02 +0100288
Jakob Buchgraber9c83de72018-02-18 15:32:44 +0100289
Jakob Buchgraber3120f7a2018-02-18 13:28:02 +0100290def print_expanded_group(name):
Philipp Wollermanndcaddd92018-02-21 14:13:43 +0100291 eprint("\n+++ {0}\n".format(name))
Jakob Buchgraber0b6a39d2018-02-17 00:45:14 +0100292
Jakob Buchgraber9c83de72018-02-18 15:32:44 +0100293
Jakob Buchgraber0b6a39d2018-02-17 00:45:14 +0100294def execute_commands(config, platform, git_repository, use_but, save_but,
295 build_only, test_only):
Philipp Wollermanndcaddd92018-02-21 14:13:43 +0100296 fail_pipeline = False
Philipp Wollermann598b4a42018-02-19 17:03:36 +0100297 tmpdir = None
298 bazel_binary = "bazel"
Jakob Buchgraber95e3d572018-02-21 18:48:49 +0100299 commit = os.getenv("BUILDKITE_COMMIT")
Philipp Wollermann598b4a42018-02-19 17:03:36 +0100300 try:
301 if git_repository:
302 clone_git_repository(git_repository, platform)
Philipp Wollermannff39ef52018-02-21 14:18:52 +0100303 cleanup()
Philipp Wollermann598b4a42018-02-19 17:03:36 +0100304 tmpdir = tempfile.mkdtemp()
305 if use_but:
306 print_collapsed_group("Downloading Bazel under test")
307 bazel_binary = download_bazel_binary(tmpdir, platform)
308 print_bazel_version_info(bazel_binary)
309 execute_shell_commands(config.get("shell_commands", None))
310 execute_bazel_run(bazel_binary, config.get("run_targets", None))
311 if not test_only:
Jakob Buchgraber95e3d572018-02-21 18:48:49 +0100312 build_bep_file = os.path.join(tmpdir, "build_bep.json")
313 if is_pull_request():
314 update_pull_request_build_status(git_repository, commit, "pending", None)
Philipp Wollermanndcaddd92018-02-21 14:13:43 +0100315 try:
Jakob Buchgraber95e3d572018-02-21 18:48:49 +0100316 execute_bazel_build(bazel_binary, platform, config.get("build_flags", []),
317 config.get("build_targets", None), build_bep_file)
318 if is_pull_request():
319 invocation_id = bes_invocation_id(build_bep_file)
320 update_pull_request_build_status(git_repository, commit, "success", invocation_id)
321 if save_but:
322 upload_bazel_binary()
323 except BazelBuildFailedException:
324 if is_pull_request()
325 invocation_id = bes_invocation_id(build_bep_file)
326 update_pull_request_build_status(git_repository, commit, "failure", invocation_id)
Philipp Wollermanndcaddd92018-02-21 14:13:43 +0100327 fail_pipeline = True
Jakob Buchgraber95e3d572018-02-21 18:48:49 +0100328 if not fail_pipeline and not build_only:
329 test_bep_file = os.path.join(tmpdir, "test_bep.json")
330 try:
331 if is_pull_request():
332 update_pull_request_test_status(git_repository, commit, "pending", None, 0, 0, 0)
333 execute_bazel_test(bazel_binary, platform, config.get("test_flags", []),
334 config.get("test_targets", None), test_bep_file)
335 if is_pull_request():
336 invocation_id = bes_invocation_id(test_bep_file)
337 update_pull_request_test_status(git_repository, commit, "success", invocation_id, 0, 0, 0)
338 except BazelTestFailedException:
339 if is_pull_request():
340 invocation_id = bes_invocation_id(test_bep_file)
341 update_pull_request_test_status(git_repository, commit, "success", invocation_id, 1, 1, 1)
342 fail_pipeline = True
343 print_test_summary(test_bep_file)
Philipp Wollermanndcaddd92018-02-21 14:13:43 +0100344
345 # Fail the pipeline if there were any flaky tests.
Jakob Buchgraber95e3d572018-02-21 18:48:49 +0100346 if has_flaky_tests(test_bep_file):
Philipp Wollermanndcaddd92018-02-21 14:13:43 +0100347 fail_pipeline = True
348
Jakob Buchgraber95e3d572018-02-21 18:48:49 +0100349 upload_test_logs(test_bep_file, tmpdir)
Philipp Wollermann598b4a42018-02-19 17:03:36 +0100350 finally:
351 if tmpdir:
352 shutil.rmtree(tmpdir)
Philipp Wollermannff39ef52018-02-21 14:18:52 +0100353 cleanup()
Philipp Wollermanndcaddd92018-02-21 14:13:43 +0100354
355 if fail_pipeline:
356 raise BuildkiteException("At least one test failed or was flaky.")
Jakob Buchgraber0b6a39d2018-02-17 00:45:14 +0100357
Jakob Buchgraber257693b2018-02-20 00:03:56 +0100358
Jakob Buchgraber95e3d572018-02-21 18:48:49 +0100359def fetch_github_token():
360 execute_command(
361 ["gsutil", "cp", "gs://bazel-encrypted-secrets/github-token.enc", "github-token.enc"])
362 return subprocess.check_output(["gcloud", "kms", "decrypt", "--location", "global", "--keyring", "buildkite",
363 "--key", "github-token", "--ciphertext-file", "github-token.enc",
364 "--plaintext-file", "-"]).decode("utf-8").strip()
365
366
367def owner_repository_from_url(git_repository):
368 m = re.search(r"/([^/]+)/([^/]+)\.git$", repository_url)
369 owner = m.group(1)
370 repository = m.group(2)
371 return (owner, repository)
372
373
374def update_pull_request_status(git_repository, commit, state, invocation_id, description, context):
375 gh = login(token=fetch_github_token())
376 owner, repo = owner_repository_from_url(git_repository)
377 repo = gh.repository(owner=owner, repository=repo)
378 results_url = "https://source.cloud.google.com/results/invocations/" + invocation_id
379 repo.create_status(sha=commit, state=state, target_url=results_url, description=description, context=context)
380
381
382def update_pull_request_build_status(git_repository, commit, state, invocation_id):
383 description = ""
384 if state == "pending":
385 description = "Running ..."
386 elif state == "failure":
387 description = "Failed"
388 elif state == "success":
389 description = "Succeeded"
390 update_pull_request_status(git_repository, commit, state, invocation_id, description, "bazel build")
391
392
393def update_pull_request_test_status(repository_url, commit, state, invocation_id, failed, timed_out,
394 flaky):
395 description = ""
396 if failed == 0 and timed_out == 0 and flaky == 0:
397 description = "All Tests Passed"
398 else:
399 description = "{0} tests failed, {1} tests timed out, {2} tests are flaky".format(failed, timed_out, flaky)
400 update_pull_request_status(repository_url, commit, state, invocation_id, description, "bazel test")
401
402
403def is_pull_request():
404 third_party_repo = pos.getenv("BUILDKITE_PULL_REQUEST_REPO", "")
405 return len(third_party_repo) > 0
406
407
408def bes_invocation_id(bep_file):
409 targets = []
410 raw_data = ""
411 with open(bep_file) as f:
412 raw_data = f.read()
413 decoder = json.JSONDecoder()
414
415 pos = 0
416 while pos < len(raw_data):
417 bep_obj, size = decoder.raw_decode(raw_data[pos:])
418 if "started" in bep_obj:
419 return bep_obj["started"]["uuid"]
420 pos += size + 1
421 return None
422
423
Jakob Buchgraber257693b2018-02-20 00:03:56 +0100424def show_image(url, alt):
Philipp Wollermanndcaddd92018-02-21 14:13:43 +0100425 eprint("\033]1338;url='\"{0}\"';alt='\"{1}\"'\a\n".format(url, alt))
Jakob Buchgraber257693b2018-02-20 00:03:56 +0100426
427
Jakob Buchgraberca19f782018-02-19 22:40:43 +0100428def print_test_summary(bep_file):
429 failed = test_logs_for_status(bep_file, status="FAILED")
430 if failed:
431 print_expanded_group("Failed Tests")
432 for label, _ in failed:
Philipp Wollermanndcaddd92018-02-21 14:13:43 +0100433 eprint(label)
Jakob Buchgraberca19f782018-02-19 22:40:43 +0100434 timed_out = test_logs_for_status(bep_file, status="TIMEOUT")
Jakob Buchgraber039d9ed2018-02-20 15:30:05 +0100435 if timed_out:
Jakob Buchgraberca19f782018-02-19 22:40:43 +0100436 print_expanded_group("Timed out Tests")
Jakob Buchgraber622d6522018-02-20 00:05:53 +0100437 for label, _ in timed_out:
Philipp Wollermanndcaddd92018-02-21 14:13:43 +0100438 eprint(label)
Jakob Buchgraberca19f782018-02-19 22:40:43 +0100439 flaky = test_logs_for_status(bep_file, status="FLAKY")
440 if flaky:
441 print_expanded_group("Flaky Tests")
Jakob Buchgraber257693b2018-02-20 00:03:56 +0100442 show_image(flaky_test_meme_url(), "Flaky Tests")
Jakob Buchgraber622d6522018-02-20 00:05:53 +0100443 for label, _ in flaky:
Philipp Wollermanndcaddd92018-02-21 14:13:43 +0100444 eprint(label)
Jakob Buchgraber0b6a39d2018-02-17 00:45:14 +0100445
Jakob Buchgraber257693b2018-02-20 00:03:56 +0100446
Jakob Buchgraber02e07222018-02-19 15:05:56 +0100447def has_flaky_tests(bep_file):
Philipp Wollermann598b4a42018-02-19 17:03:36 +0100448 return len(test_logs_for_status(bep_file, status="FLAKY")) > 0
Jakob Buchgraber02e07222018-02-19 15:05:56 +0100449
450
Jakob Buchgraber7e690a72018-02-18 13:22:15 +0100451def print_bazel_version_info(bazel_binary):
Philipp Wollermann598b4a42018-02-19 17:03:36 +0100452 print_collapsed_group("Bazel Info")
Philipp Wollermann3e1a7712018-02-19 17:34:24 +0100453 execute_command([bazel_binary, "version"])
454 execute_command([bazel_binary, "info"])
Jakob Buchgraber7e690a72018-02-18 13:22:15 +0100455
456
Jakob Buchgraber0b6a39d2018-02-17 00:45:14 +0100457def upload_bazel_binary():
Philipp Wollermann598b4a42018-02-19 17:03:36 +0100458 print_collapsed_group("Uploading Bazel under test")
Philipp Wollermann3e1a7712018-02-19 17:34:24 +0100459 execute_command(["buildkite-agent", "artifact", "upload", "bazel-bin/src/bazel"])
Jakob Buchgraber0b6a39d2018-02-17 00:45:14 +0100460
461
Jakob Buchgraberd20ffeb2018-02-18 03:16:43 +0100462def download_bazel_binary(dest_dir, platform):
Philipp Wollermannf6be4662018-02-21 14:48:28 +0100463 source_step = create_label(platform, "Bazel", build_only=True)
Philipp Wollermann3e1a7712018-02-19 17:34:24 +0100464 execute_command(["buildkite-agent", "artifact", "download",
465 "bazel-bin/src/bazel", dest_dir, "--step", source_step])
Philipp Wollermann598b4a42018-02-19 17:03:36 +0100466 bazel_binary_path = os.path.join(dest_dir, "bazel-bin/src/bazel")
467 st = os.stat(bazel_binary_path)
468 os.chmod(bazel_binary_path, st.st_mode | stat.S_IEXEC)
469 return bazel_binary_path
Jakob Buchgraber0b6a39d2018-02-17 00:45:14 +0100470
471
472def clone_git_repository(git_repository, platform):
Philipp Wollermann598b4a42018-02-19 17:03:36 +0100473 root = downstream_projects_root(platform)
Philipp Wollermannff39ef52018-02-21 14:18:52 +0100474 project_name = re.search(r"/([^/]+)\.git$", git_repository).group(1)
Philipp Wollermann598b4a42018-02-19 17:03:36 +0100475 clone_path = os.path.join(root, project_name)
476 print_collapsed_group("Fetching " + project_name + " sources")
477 if os.path.exists(clone_path):
478 os.chdir(clone_path)
Philipp Wollermann3e1a7712018-02-19 17:34:24 +0100479 execute_command(["git", "remote", "set-url", "origin", git_repository])
480 execute_command(["git", "clean", "-fdqx"])
481 execute_command(["git", "submodule", "foreach", "--recursive", "git", "clean", "-fdqx"])
Philipp Wollermann598b4a42018-02-19 17:03:36 +0100482 # sync to the latest commit of HEAD. Unlikely git pull this also works after
483 # a force push.
Philipp Wollermann3e1a7712018-02-19 17:34:24 +0100484 execute_command(["git", "fetch", "origin"])
485 remote_head = subprocess.check_output(["git", "symbolic-ref", "refs/remotes/origin/HEAD"])
Philipp Wollermann598b4a42018-02-19 17:03:36 +0100486 remote_head = remote_head.decode("utf-8")
487 remote_head = remote_head.rstrip()
Philipp Wollermann3e1a7712018-02-19 17:34:24 +0100488 execute_command(["git", "reset", remote_head, "--hard"])
489 execute_command(["git", "submodule", "sync", "--recursive"])
490 execute_command(["git", "submodule", "update", "--init", "--recursive", "--force"])
491 execute_command(["git", "submodule", "foreach", "--recursive", "git", "reset", "--hard"])
492 execute_command(["git", "clean", "-fdqx"])
493 execute_command(["git", "submodule", "foreach", "--recursive", "git", "clean", "-fdqx"])
Philipp Wollermann598b4a42018-02-19 17:03:36 +0100494 else:
Philipp Wollermann3e1a7712018-02-19 17:34:24 +0100495 execute_command(["git", "clone", "--recurse-submodules", git_repository, clone_path])
Philipp Wollermann598b4a42018-02-19 17:03:36 +0100496 os.chdir(clone_path)
Jakob Buchgraber0b6a39d2018-02-17 00:45:14 +0100497
498
Philipp Wollermannff39ef52018-02-21 14:18:52 +0100499def cleanup():
Philipp Wollermann598b4a42018-02-19 17:03:36 +0100500 print_collapsed_group("Cleanup")
501 if os.path.exists("WORKSPACE"):
Philipp Wollermann3e1a7712018-02-19 17:34:24 +0100502 execute_command(["bazel", "clean", "--expunge"])
Jakob Buchgraber0b6a39d2018-02-17 00:45:14 +0100503
Jakob Buchgraberfd1eaca2018-02-17 17:27:14 +0100504
Jakob Buchgraber0b6a39d2018-02-17 00:45:14 +0100505def execute_shell_commands(commands):
Philipp Wollermann598b4a42018-02-19 17:03:36 +0100506 if not commands:
507 return
508 print_collapsed_group("Setup (Shell Commands)")
509 shell_command = "\n".join(commands)
Philipp Wollermann3e1a7712018-02-19 17:34:24 +0100510 execute_command([shell_command], shell=True)
Jakob Buchgraber0b6a39d2018-02-17 00:45:14 +0100511
512
513def execute_bazel_run(bazel_binary, targets):
Philipp Wollermann598b4a42018-02-19 17:03:36 +0100514 if not targets:
515 return
516 print_collapsed_group("Setup (Run Targets)")
517 for target in targets:
Philipp Wollermann3e1a7712018-02-19 17:34:24 +0100518 execute_command([bazel_binary, "run", "--curses=yes",
Jakob Buchgraber99659532018-02-19 17:38:50 +0100519 "--color=yes", "--verbose_failures", target])
Jakob Buchgraber0b6a39d2018-02-17 00:45:14 +0100520
521
Jakob Buchgraber4f1d2712018-02-20 10:22:47 +0100522def remote_caching_flags(platform):
Jakob Buchgraber3af52932018-02-20 20:28:24 +0100523 common_flags = ["--bes_backend=buildeventservice.googleapis.com", "--bes_best_effort=false",
Jakob Buchgrabercfb03e42018-02-20 20:32:36 +0100524 "--bes_timeout=10s", "--tls_enabled", "--project_id=bazel-public",
Jakob Buchgraber3af52932018-02-20 20:28:24 +0100525 "--remote_instance_name=projects/bazel-public",
Jakob Buchgraber039d9ed2018-02-20 15:30:05 +0100526 "--experimental_remote_spawn_cache",
Jakob Buchgraber848b88e2018-02-21 12:57:45 +0100527 "--remote_timeout=10", "--remote_cache=remotebuildexecution.googleapis.com",
Jakob Buchgraber3af52932018-02-20 20:28:24 +0100528 "--experimental_remote_platform_override=properties:{name:\"platform\" value:\"" + platform + "\"}"]
Jakob Buchgraberc38a7192018-02-20 12:56:52 +0100529 if platform in ["ubuntu1404", "ubuntu1604"]:
530 return common_flags + ["--google_default_credentials"]
531 elif platform == "macos":
532 return common_flags + ["--google_credentials=/Users/ci/GoogleDrive/bazel-public-e29b1f995cb1.json"]
Jakob Buchgraber4f1d2712018-02-20 10:22:47 +0100533 return []
534
535
Jakob Buchgraberb4342cd2018-02-20 16:35:07 +0100536def remote_enabled(flags):
537 # Detect if the project configuration enabled its own remote caching / execution.
538 remote_flags = ["--remote_executor", "--remote_cache", "--remote_http_cache"]
539 for flag in flags:
540 for remote_flag in remote_flags:
541 if flag.startswith(remote_flag):
542 return True
543 return False
544
Jakob Buchgraber95e3d572018-02-21 18:48:49 +0100545
546def execute_bazel_build(bazel_binary, platform, flags, targets, bep_file):
Philipp Wollermann598b4a42018-02-19 17:03:36 +0100547 if not targets:
548 return
549 print_expanded_group("Build")
550 num_jobs = str(multiprocessing.cpu_count())
Jakob Buchgraberad6692e2018-02-20 11:12:00 +0100551 common_flags = ["--show_progress_rate_limit=5", "--curses=yes", "--color=yes", "--keep_going",
Jakob Buchgraber95e3d572018-02-21 18:48:49 +0100552 "--jobs=" + num_jobs], "--build_event_json_file=" + bep_file,
553 "--experimental_build_event_json_file_path_conversion=false"]
Jakob Buchgraberb4342cd2018-02-20 16:35:07 +0100554 caching_flags = []
555 if not remote_enabled(flags):
556 caching_flags = remote_caching_flags(platform)
Jakob Buchgraber95e3d572018-02-21 18:48:49 +0100557 try:
558 execute_command([bazel_binary, "build"] + common_flags + caching_flags + flags + targets)
559 except subprocess.CalledProcessError as e:
560 raise BazelBuildFailedException("bazel build failed with exit code {}".format(e.returncode))
Jakob Buchgraber0b6a39d2018-02-17 00:45:14 +0100561
562
Jakob Buchgraberd2204292018-02-20 10:25:40 +0100563def execute_bazel_test(bazel_binary, platform, flags, targets, bep_file):
Philipp Wollermann598b4a42018-02-19 17:03:36 +0100564 if not targets:
Philipp Wollermann556cf1e2018-02-21 15:02:34 +0100565 return
Philipp Wollermann598b4a42018-02-19 17:03:36 +0100566 print_expanded_group("Test")
567 num_jobs = str(multiprocessing.cpu_count())
Jakob Buchgraber77175c72018-02-20 15:32:07 +0100568 common_flags = ["--show_progress_rate_limit=5", "--curses=yes", "--color=yes", "--keep_going",
Philipp Wollermann598b4a42018-02-19 17:03:36 +0100569 "--flaky_test_attempts=3", "--build_tests_only",
570 "--jobs=" + num_jobs, "--local_test_jobs=" + num_jobs,
Jakob Buchgraber77595f12018-02-21 11:44:18 +0100571 "--build_event_json_file=" + bep_file,
572 "--experimental_build_event_json_file_path_conversion=false"]
Jakob Buchgraberb4342cd2018-02-20 16:35:07 +0100573 caching_flags = []
574 if not remote_enabled(flags):
575 caching_flags = remote_caching_flags(platform)
Philipp Wollermanndcaddd92018-02-21 14:13:43 +0100576 try:
577 execute_command([bazel_binary, "test"] + common_flags + caching_flags + flags + targets)
578 except subprocess.CalledProcessError as e:
579 raise BazelTestFailedException("bazel test failed with exit code {}".format(e.returncode))
Jakob Buchgraber0b6a39d2018-02-17 00:45:14 +0100580
581
Jakob Buchgraber02e07222018-02-19 15:05:56 +0100582def upload_test_logs(bep_file, tmpdir):
Philipp Wollermann598b4a42018-02-19 17:03:36 +0100583 if not os.path.exists(bep_file):
584 return
585 test_logs = test_logs_to_upload(bep_file, tmpdir)
586 if test_logs:
587 cwd = os.getcwd()
588 try:
589 os.chdir(tmpdir)
590 print_collapsed_group("Uploading test logs")
Jakob Buchgraber175b5212018-02-19 21:18:21 +0100591 execute_command(["buildkite-agent", "artifact", "upload", "*/**/*.log"])
Philipp Wollermann598b4a42018-02-19 17:03:36 +0100592 finally:
593 os.chdir(cwd)
Jakob Buchgraber0b6a39d2018-02-17 00:45:14 +0100594
595
Jakob Buchgraber02e07222018-02-19 15:05:56 +0100596def test_logs_to_upload(bep_file, tmpdir):
Philipp Wollermann598b4a42018-02-19 17:03:36 +0100597 failed = test_logs_for_status(bep_file, status="FAILED")
598 timed_out = test_logs_for_status(bep_file, status="TIMEOUT")
599 flaky = test_logs_for_status(bep_file, status="FLAKY")
600 # Rename the test.log files to the target that created them
601 # so that it's easy to associate test.log and target.
602 new_paths = []
Philipp Wollermannff39ef52018-02-21 14:18:52 +0100603 for label, test_logs in failed + timed_out + flaky:
Philipp Wollermann598b4a42018-02-19 17:03:36 +0100604 attempt = 0
605 if len(test_logs) > 1:
606 attempt = 1
607 for test_log in test_logs:
Jakob Buchgraber070ef5f2018-02-20 17:57:14 +0100608 try:
609 new_path = test_label_to_path(tmpdir, label, attempt)
Philipp Wollermannf6be4662018-02-21 14:48:28 +0100610 eprint("new_path: " + new_path)
Jakob Buchgraber070ef5f2018-02-20 17:57:14 +0100611 os.makedirs(os.path.dirname(new_path), exist_ok=True)
Jakob Buchgraber070ef5f2018-02-20 17:57:14 +0100612 copyfile(test_log, new_path)
Jakob Buchgraber070ef5f2018-02-20 17:57:14 +0100613 new_paths.append(new_path)
Philipp Wollermannc030f2e2018-02-21 17:02:19 +0100614 attempt += 1
Jakob Buchgraber070ef5f2018-02-20 17:57:14 +0100615 except IOError as err:
Philipp Wollermanndcaddd92018-02-21 14:13:43 +0100616 # Log error and ignore.
617 eprint(err)
Philipp Wollermann598b4a42018-02-19 17:03:36 +0100618 return new_paths
Jakob Buchgraber699aece2018-02-19 12:49:30 +0100619
620
Jakob Buchgraber02e07222018-02-19 15:05:56 +0100621def test_label_to_path(tmpdir, label, attempt):
Philipp Wollermann598b4a42018-02-19 17:03:36 +0100622 # remove leading //
623 path = label[2:]
Philipp Wollermannc030f2e2018-02-21 17:02:19 +0100624 path = path.replace("/", os.sep)
625 path = path.replace(":", os.sep)
Philipp Wollermann598b4a42018-02-19 17:03:36 +0100626 if attempt == 0:
627 path = os.path.join(path, "test.log")
628 else:
629 path = os.path.join(path, "attempt_" + str(attempt) + ".log")
630 return os.path.join(tmpdir, path)
Jakob Buchgraber699aece2018-02-19 12:49:30 +0100631
632
Jakob Buchgraber02e07222018-02-19 15:05:56 +0100633def test_logs_for_status(bep_file, status):
Philipp Wollermann598b4a42018-02-19 17:03:36 +0100634 targets = []
635 raw_data = ""
636 with open(bep_file) as f:
637 raw_data = f.read()
638 decoder = json.JSONDecoder()
Jakob Buchgraber0b6a39d2018-02-17 00:45:14 +0100639
Philipp Wollermann598b4a42018-02-19 17:03:36 +0100640 pos = 0
641 while pos < len(raw_data):
642 bep_obj, size = decoder.raw_decode(raw_data[pos:])
Jakob Buchgraber45e38632018-02-19 17:27:08 +0100643 if "testSummary" in bep_obj:
644 test_target = bep_obj["id"]["testSummary"]["label"]
645 test_status = bep_obj["testSummary"]["overallStatus"]
646 if test_status == status:
647 outputs = bep_obj["testSummary"]["failed"]
Philipp Wollermann598b4a42018-02-19 17:03:36 +0100648 test_logs = []
649 for output in outputs:
Philipp Wollermannc030f2e2018-02-21 17:02:19 +0100650 test_logs.append(url2pathname(urlparse(output["uri"]).path))
Jakob Buchgraber45e38632018-02-19 17:27:08 +0100651 targets.append((test_target, test_logs))
Philipp Wollermann598b4a42018-02-19 17:03:36 +0100652 pos += size + 1
653 return targets
Jakob Buchgraber0b6a39d2018-02-17 00:45:14 +0100654
655
Philipp Wollermann3e1a7712018-02-19 17:34:24 +0100656def execute_command(args, shell=False, fail_if_nonzero=True):
Philipp Wollermanndcaddd92018-02-21 14:13:43 +0100657 eprint(" ".join(args))
658 return subprocess.run(args, shell=shell, check=fail_if_nonzero).returncode
Jakob Buchgraber0b6a39d2018-02-17 00:45:14 +0100659
660
661def print_project_pipeline(platform_configs, project_name, http_config,
662 git_repository, use_but):
Philipp Wollermann598b4a42018-02-19 17:03:36 +0100663 pipeline_steps = []
Philipp Wollermannff39ef52018-02-21 14:18:52 +0100664 for platform, _ in platform_configs.items():
Philipp Wollermannf6be4662018-02-21 14:48:28 +0100665 step = runner_step(platform, project_name, http_config, git_repository, use_but)
Philipp Wollermann598b4a42018-02-19 17:03:36 +0100666 pipeline_steps.append(step)
Jakob Buchgraber0b6a39d2018-02-17 00:45:14 +0100667
Philipp Wollermann598b4a42018-02-19 17:03:36 +0100668 print_pipeline(pipeline_steps)
Jakob Buchgraber0b6a39d2018-02-17 00:45:14 +0100669
670
671def runner_step(platform, project_name=None, http_config=None,
Philipp Wollermannf6be4662018-02-21 14:48:28 +0100672 git_repository=None, use_but=False):
Philipp Wollermannbcedf9b2018-02-19 18:07:44 +0100673 command = python_binary(platform) + " bazelci.py runner --platform=" + platform
Philipp Wollermann598b4a42018-02-19 17:03:36 +0100674 if http_config:
Philipp Wollermannf6be4662018-02-21 14:48:28 +0100675 command += " --http_config=" + http_config
Philipp Wollermann598b4a42018-02-19 17:03:36 +0100676 if git_repository:
Philipp Wollermannf6be4662018-02-21 14:48:28 +0100677 command += " --git_repository=" + git_repository
Philipp Wollermann598b4a42018-02-19 17:03:36 +0100678 if use_but:
Philipp Wollermannf6be4662018-02-21 14:48:28 +0100679 command += " --use_but"
680 label = create_label(platform, project_name)
Philipp Wollermann598b4a42018-02-19 17:03:36 +0100681 return """
Jakob Buchgraber0b6a39d2018-02-17 00:45:14 +0100682 - label: \"{0}\"
683 command: \"{1}\\n{2}\"
684 agents:
685 - \"os={3}\"""".format(label, fetch_bazelcipy_command(), command, platform)
686
687
688def print_pipeline(steps):
Philipp Wollermann598b4a42018-02-19 17:03:36 +0100689 print("steps:")
690 for step in steps:
691 print(step)
Jakob Buchgraber0b6a39d2018-02-17 00:45:14 +0100692
693
694def wait_step():
Philipp Wollermann598b4a42018-02-19 17:03:36 +0100695 return """
Jakob Buchgraber0b6a39d2018-02-17 00:45:14 +0100696 - wait"""
697
698
699def http_config_flag(http_config):
Philipp Wollermann598b4a42018-02-19 17:03:36 +0100700 if http_config is not None:
701 return "--http_config=" + http_config
702 return ""
Jakob Buchgraber0b6a39d2018-02-17 00:45:14 +0100703
704
705def fetch_bazelcipy_command():
Philipp Wollermann598b4a42018-02-19 17:03:36 +0100706 return "curl -s {0} -o bazelci.py".format(bazelcipy_url())
Jakob Buchgraber0b6a39d2018-02-17 00:45:14 +0100707
708
709def upload_project_pipeline_step(project_name, git_repository, http_config):
Philipp Wollermann598b4a42018-02-19 17:03:36 +0100710 pipeline_command = ("{0} bazelci.py project_pipeline --project_name=\\\"{1}\\\" " +
711 "--use_but --git_repository={2}").format(python_binary(), project_name,
712 git_repository)
713 if http_config:
Philipp Wollermannf6be4662018-02-21 14:48:28 +0100714 pipeline_command += " --http_config=" + http_config
715 pipeline_command += " | buildkite-agent pipeline upload"
Jakob Buchgraber0b6a39d2018-02-17 00:45:14 +0100716
Philipp Wollermann598b4a42018-02-19 17:03:36 +0100717 return """
Jakob Buchgraber0b6a39d2018-02-17 00:45:14 +0100718 - label: \"Setup {0}\"
719 command: \"{1}\\n{2}\"
720 agents:
721 - \"pipeline=true\"""".format(project_name, fetch_bazelcipy_command(),
722 pipeline_command)
723
724
Philipp Wollermannf6be4662018-02-21 14:48:28 +0100725def create_label(platform, project_name, build_only=False, test_only=False):
726 if build_only and test_only:
727 raise BuildkiteException("build_only and test_only cannot be true at the same time")
728 platform_name = platforms_info()[platform]["name"]
729
Philipp Wollermann598b4a42018-02-19 17:03:36 +0100730 if build_only:
731 label = "Build "
Philipp Wollermannf6be4662018-02-21 14:48:28 +0100732 elif test_only:
Philipp Wollermann598b4a42018-02-19 17:03:36 +0100733 label = "Test "
Philipp Wollermann598b4a42018-02-19 17:03:36 +0100734 else:
Philipp Wollermannf6be4662018-02-21 14:48:28 +0100735 label = ""
736
737 if project_name:
738 label += "{0} ({1})".format(project_name, platform_name)
739 else:
740 label += platform_name
741
Philipp Wollermann598b4a42018-02-19 17:03:36 +0100742 return label
Jakob Buchgraber0b6a39d2018-02-17 00:45:14 +0100743
744
Philipp Wollermannf6be4662018-02-21 14:48:28 +0100745def bazel_build_step(platform, project_name, http_config=None, build_only=False, test_only=False):
Philipp Wollermannbcedf9b2018-02-19 18:07:44 +0100746 pipeline_command = python_binary(platform) + " bazelci.py runner"
Philipp Wollermann598b4a42018-02-19 17:03:36 +0100747 if build_only:
Philipp Wollermannf6be4662018-02-21 14:48:28 +0100748 pipeline_command += " --build_only --save_but"
Philipp Wollermann598b4a42018-02-19 17:03:36 +0100749 if test_only:
Philipp Wollermannf6be4662018-02-21 14:48:28 +0100750 pipeline_command += " --test_only"
Philipp Wollermann598b4a42018-02-19 17:03:36 +0100751 if http_config:
Philipp Wollermannf6be4662018-02-21 14:48:28 +0100752 pipeline_command += " --http_config=" + http_config
753 label = create_label(platform, project_name, build_only, test_only)
754 pipeline_command += " --platform=" + platform
Jakob Buchgraber0b6a39d2018-02-17 00:45:14 +0100755
Philipp Wollermann598b4a42018-02-19 17:03:36 +0100756 return """
Jakob Buchgraber0b6a39d2018-02-17 00:45:14 +0100757 - label: \"{0}\"
758 command: \"{1}\\n{2}\"
759 agents:
760 - \"os={3}\"""".format(label, fetch_bazelcipy_command(),
761 pipeline_command, platform)
762
763
Jakob Buchgraber76381e02018-02-19 16:19:56 +0100764def publish_bazel_binaries_step():
Philipp Wollermann598b4a42018-02-19 17:03:36 +0100765 command = python_binary() + " bazelci.py publish_binaries"
766 return """
Jakob Buchgraber76381e02018-02-19 16:19:56 +0100767 - label: \"Publish Bazel Binaries\"
768 command: \"{0}\\n{1}\"
Jakob Buchgraberd20ffeb2018-02-18 03:16:43 +0100769 agents:
Jakob Buchgraber76381e02018-02-19 16:19:56 +0100770 - \"pipeline=true\"""".format(fetch_bazelcipy_command(), command)
Jakob Buchgraberd20ffeb2018-02-18 03:16:43 +0100771
772
Jakob Buchgraber0b6a39d2018-02-17 00:45:14 +0100773def print_bazel_postsubmit_pipeline(configs, http_config):
Philipp Wollermann598b4a42018-02-19 17:03:36 +0100774 if not configs:
Philipp Wollermanndcaddd92018-02-21 14:13:43 +0100775 raise BuildkiteException("Bazel postsubmit pipeline configuration is empty.")
Philipp Wollermann598b4a42018-02-19 17:03:36 +0100776 if set(configs.keys()) != set(supported_platforms()):
Philipp Wollermanndcaddd92018-02-21 14:13:43 +0100777 raise BuildkiteException("Bazel postsubmit pipeline needs to build Bazel on all supported platforms.")
Jakob Buchgraber0b6a39d2018-02-17 00:45:14 +0100778
Philipp Wollermann598b4a42018-02-19 17:03:36 +0100779 pipeline_steps = []
780 for platform, config in configs.items():
Philipp Wollermannf6be4662018-02-21 14:48:28 +0100781 pipeline_steps.append(bazel_build_step(platform, "Bazel", http_config, build_only=True))
Philipp Wollermann598b4a42018-02-19 17:03:36 +0100782 pipeline_steps.append(wait_step())
Jakob Buchgraberd20ffeb2018-02-18 03:16:43 +0100783
Philipp Wollermann598b4a42018-02-19 17:03:36 +0100784 # todo move this to the end with a wait step.
785 pipeline_steps.append(publish_bazel_binaries_step())
Jakob Buchgraberd20ffeb2018-02-18 03:16:43 +0100786
Philipp Wollermann598b4a42018-02-19 17:03:36 +0100787 for platform, config in configs.items():
Philipp Wollermannf6be4662018-02-21 14:48:28 +0100788 pipeline_steps.append(bazel_build_step(platform, "Bazel", http_config, test_only=True))
789
Philipp Wollermann598b4a42018-02-19 17:03:36 +0100790 for project, config in downstream_projects().items():
791 git_repository = config["git_repository"]
792 http_config = config.get("http_config", None)
793 pipeline_steps.append(upload_project_pipeline_step(project,
794 git_repository, http_config))
Jakob Buchgraber0b6a39d2018-02-17 00:45:14 +0100795
Philipp Wollermann598b4a42018-02-19 17:03:36 +0100796 print_pipeline(pipeline_steps)
Jakob Buchgraber0b6a39d2018-02-17 00:45:14 +0100797
798
Jakob Buchgraberd20ffeb2018-02-18 03:16:43 +0100799def bazelci_builds_download_url(platform, build_number):
Philipp Wollermann598b4a42018-02-19 17:03:36 +0100800 return "https://storage.googleapis.com/bazel-builds/artifacts/{0}/{1}/bazel".format(platform, build_number)
Jakob Buchgraberd20ffeb2018-02-18 03:16:43 +0100801
802
803def bazelci_builds_upload_url(platform, build_number):
Jakob Buchgraber2a2304f2018-02-19 21:29:44 +0100804 return "gs://bazel-builds/artifacts/{0}/{1}/bazel".format(platform, build_number)
Jakob Buchgraberd20ffeb2018-02-18 03:16:43 +0100805
806
Jakob Buchgraber76381e02018-02-19 16:19:56 +0100807def bazelci_builds_metadata_url():
Philipp Wollermann598b4a42018-02-19 17:03:36 +0100808 return "gs://bazel-builds/metadata/latest_fully_tested.json"
Jakob Buchgraberd20ffeb2018-02-18 03:16:43 +0100809
810
Jakob Buchgraber76381e02018-02-19 16:19:56 +0100811def latest_generation_and_build_number():
Philipp Wollermann598b4a42018-02-19 17:03:36 +0100812 output = None
813 attempt = 0
814 while attempt < 5:
815 output = subprocess.check_output(
816 ["gsutil", "stat", bazelci_builds_metadata_url()])
817 match = re.search("Generation:[ ]*([0-9]+)", output.decode("utf-8"))
818 if not match:
Philipp Wollermanndcaddd92018-02-21 14:13:43 +0100819 raise BuildkiteException("Couldn't parse generation. gsutil output format changed?")
Philipp Wollermann598b4a42018-02-19 17:03:36 +0100820 generation = match.group(1)
Jakob Buchgraberd20ffeb2018-02-18 03:16:43 +0100821
Philipp Wollermannff39ef52018-02-21 14:18:52 +0100822 match = re.search(r"Hash \(md5\):[ ]*([^\s]+)", output.decode("utf-8"))
Philipp Wollermann598b4a42018-02-19 17:03:36 +0100823 if not match:
Philipp Wollermanndcaddd92018-02-21 14:13:43 +0100824 raise BuildkiteException("Couldn't parse md5 hash. gsutil output format changed?")
Philipp Wollermann598b4a42018-02-19 17:03:36 +0100825 expected_md5hash = base64.b64decode(match.group(1))
Jakob Buchgraberd20ffeb2018-02-18 03:16:43 +0100826
Philipp Wollermann598b4a42018-02-19 17:03:36 +0100827 output = subprocess.check_output(
828 ["gsutil", "cat", bazelci_builds_metadata_url()])
829 hasher = hashlib.md5()
830 hasher.update(output)
831 actual_md5hash = hasher.digest()
Jakob Buchgraberd20ffeb2018-02-18 03:16:43 +0100832
Philipp Wollermann598b4a42018-02-19 17:03:36 +0100833 if expected_md5hash == actual_md5hash:
834 break
Philipp Wollermannf6be4662018-02-21 14:48:28 +0100835 attempt += 1
Philipp Wollermann598b4a42018-02-19 17:03:36 +0100836 info = json.loads(output.decode("utf-8"))
837 return (generation, info["build_number"])
Jakob Buchgraberd20ffeb2018-02-18 03:16:43 +0100838
Jakob Buchgraber699aece2018-02-19 12:49:30 +0100839
Jakob Buchgraber88083fd2018-02-18 17:23:35 +0100840def sha256_hexdigest(filename):
Philipp Wollermann598b4a42018-02-19 17:03:36 +0100841 sha256 = hashlib.sha256()
842 with open(filename, 'rb') as f:
843 for block in iter(lambda: f.read(65536), b''):
844 sha256.update(block)
845 return sha256.hexdigest()
Jakob Buchgraber699aece2018-02-19 12:49:30 +0100846
Jakob Buchgraberd20ffeb2018-02-18 03:16:43 +0100847
Jakob Buchgraber76381e02018-02-19 16:19:56 +0100848def try_publish_binaries(build_number, expected_generation):
Philipp Wollermann3e1a7712018-02-19 17:34:24 +0100849 tmpdir = tempfile.mkdtemp()
Philipp Wollermann598b4a42018-02-19 17:03:36 +0100850 try:
Philipp Wollermann598b4a42018-02-19 17:03:36 +0100851 info = {
852 "build_number": build_number,
853 "git_commit": os.environ["BUILDKITE_COMMIT"],
854 "platforms": {}
855 }
856 for platform in supported_platforms():
857 bazel_binary_path = download_bazel_binary(tmpdir, platform)
Philipp Wollermann3e1a7712018-02-19 17:34:24 +0100858 execute_command(["gsutil", "cp", "-a", "public-read", bazel_binary_path,
859 bazelci_builds_upload_url(platform, build_number)])
Philipp Wollermann598b4a42018-02-19 17:03:36 +0100860 info["platforms"][platform] = {
861 "url": bazelci_builds_download_url(platform, build_number),
862 "sha256": sha256_hexdigest(bazel_binary_path),
863 }
Jakob Buchgraber76381e02018-02-19 16:19:56 +0100864
Philipp Wollermann598b4a42018-02-19 17:03:36 +0100865 info_file = os.path.join(tmpdir, "info.json")
866 with open(info_file, mode="w", encoding="utf-8") as fp:
867 json.dump(info, fp)
Philipp Wollermanndcaddd92018-02-21 14:13:43 +0100868
869 try:
870 execute_command([
871 "gsutil",
872 "-h", "x-goog-if-generation-match:" + expected_generation,
873 "-h", "Content-Type:application/json",
874 "cp", "-a", "public-read",
875 info_file, bazelci_builds_metadata_url()])
876 except subprocess.CalledProcessError:
877 raise BinaryUploadRaceException()
Philipp Wollermann598b4a42018-02-19 17:03:36 +0100878 finally:
Philipp Wollermann3e1a7712018-02-19 17:34:24 +0100879 shutil.rmtree(tmpdir)
Jakob Buchgraberd20ffeb2018-02-18 03:16:43 +0100880
881
Jakob Buchgraber76381e02018-02-19 16:19:56 +0100882def publish_binaries():
Philipp Wollermanndb024862018-02-19 17:16:56 +0100883 """
Philipp Wollermann598b4a42018-02-19 17:03:36 +0100884 Publish Bazel binaries to GCS.
Philipp Wollermanndb024862018-02-19 17:16:56 +0100885 """
Philipp Wollermanndcaddd92018-02-21 14:13:43 +0100886 current_build_number = os.environ.get("BUILDKITE_BUILD_NUMBER", None)
887 if not current_build_number:
888 raise BuildkiteException("Not running inside Buildkite")
889 current_build_number = int(current_build_number)
890
891 for _ in range(5):
Philipp Wollermann598b4a42018-02-19 17:03:36 +0100892 latest_generation, latest_build_number = latest_generation_and_build_number()
Jakob Buchgraberd20ffeb2018-02-18 03:16:43 +0100893
Philipp Wollermann598b4a42018-02-19 17:03:36 +0100894 if current_build_number <= latest_build_number:
Philipp Wollermanndcaddd92018-02-21 14:13:43 +0100895 eprint(("Current build '{0}' is not newer than latest published '{1}'. " +
896 "Skipping publishing of binaries.").format(current_build_number,
897 latest_build_number))
Philipp Wollermann598b4a42018-02-19 17:03:36 +0100898 break
Jakob Buchgraberd20ffeb2018-02-18 03:16:43 +0100899
Philipp Wollermanndcaddd92018-02-21 14:13:43 +0100900 try:
901 try_publish_binaries(current_build_number, latest_generation)
902 except BinaryUploadRaceException:
903 # Retry.
904 continue
905
906 eprint("Successfully updated '{0}' to binaries from build {1}."
907 .format(bazelci_builds_metadata_url(), current_build_number))
908 break
909 else:
910 raise BuildkiteException("Could not publish binaries, ran out of attempts.")
Jakob Buchgraberd20ffeb2018-02-18 03:16:43 +0100911
912
Philipp Wollermanndcaddd92018-02-21 14:13:43 +0100913def main(argv=None):
914 if argv is None:
915 argv = sys.argv
916
Philipp Wollermann3e1a7712018-02-19 17:34:24 +0100917 parser = argparse.ArgumentParser(description='Bazel Continuous Integration Script')
Jakob Buchgraber0b6a39d2018-02-17 00:45:14 +0100918
Philipp Wollermann598b4a42018-02-19 17:03:36 +0100919 subparsers = parser.add_subparsers(dest="subparsers_name")
Philipp Wollermanndcaddd92018-02-21 14:13:43 +0100920
Philipp Wollermann3e1a7712018-02-19 17:34:24 +0100921 bazel_postsubmit_pipeline = subparsers.add_parser("bazel_postsubmit_pipeline")
Philipp Wollermann598b4a42018-02-19 17:03:36 +0100922 bazel_postsubmit_pipeline.add_argument("--http_config", type=str)
923 bazel_postsubmit_pipeline.add_argument("--git_repository", type=str)
Jakob Buchgraber0b6a39d2018-02-17 00:45:14 +0100924
Philipp Wollermann598b4a42018-02-19 17:03:36 +0100925 project_pipeline = subparsers.add_parser("project_pipeline")
926 project_pipeline.add_argument("--project_name", type=str)
927 project_pipeline.add_argument("--http_config", type=str)
928 project_pipeline.add_argument("--git_repository", type=str)
Philipp Wollermann3e1a7712018-02-19 17:34:24 +0100929 project_pipeline.add_argument("--use_but", type=bool, nargs="?", const=True)
Jakob Buchgraber0b6a39d2018-02-17 00:45:14 +0100930
Philipp Wollermann598b4a42018-02-19 17:03:36 +0100931 runner = subparsers.add_parser("runner")
Philipp Wollermann3e1a7712018-02-19 17:34:24 +0100932 runner.add_argument("--platform", action="store", choices=list(supported_platforms()))
Philipp Wollermann598b4a42018-02-19 17:03:36 +0100933 runner.add_argument("--http_config", type=str)
934 runner.add_argument("--git_repository", type=str)
935 runner.add_argument("--use_but", type=bool, nargs="?", const=True)
936 runner.add_argument("--save_but", type=bool, nargs="?", const=True)
937 runner.add_argument("--build_only", type=bool, nargs="?", const=True)
938 runner.add_argument("--test_only", type=bool, nargs="?", const=True)
Jakob Buchgraber0b6a39d2018-02-17 00:45:14 +0100939
Philipp Wollermann598b4a42018-02-19 17:03:36 +0100940 runner = subparsers.add_parser("publish_binaries")
Jakob Buchgraberd20ffeb2018-02-18 03:16:43 +0100941
Philipp Wollermann598b4a42018-02-19 17:03:36 +0100942 args = parser.parse_args()
Jakob Buchgraber0b6a39d2018-02-17 00:45:14 +0100943
Philipp Wollermanndcaddd92018-02-21 14:13:43 +0100944 try:
945 if args.subparsers_name == "bazel_postsubmit_pipeline":
946 configs = fetch_configs(args.http_config)
947 print_bazel_postsubmit_pipeline(configs.get("platforms", None), args.http_config)
948 elif args.subparsers_name == "project_pipeline":
949 configs = fetch_configs(args.http_config)
950 print_project_pipeline(configs.get("platforms", None), args.project_name,
951 args.http_config, args.git_repository, args.use_but)
952 elif args.subparsers_name == "runner":
953 configs = fetch_configs(args.http_config)
954 execute_commands(configs.get("platforms", None)[args.platform],
955 args.platform, args.git_repository, args.use_but, args.save_but,
956 args.build_only, args.test_only)
957 elif args.subparsers_name == "publish_binaries":
958 publish_binaries()
959 else:
960 parser.print_help()
961 return 2
962 except BuildkiteException as e:
963 eprint(str(e))
964 return 1
965 return 0
966
Jakob Buchgraber95e3d572018-02-21 18:48:49 +0100967
Philipp Wollermanndcaddd92018-02-21 14:13:43 +0100968if __name__ == "__main__":
969 sys.exit(main())