blob: a23bf1a96bc7779de0097cdcfc3e2632b4051f40 [file] [log] [blame]
Jakob Buchgraber0b6a39d2018-02-17 00:45:14 +01001# Copyright 2018 The Bazel Authors. All rights reserved.
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14
15from __future__ import print_function
16import argparse
Jakob Buchgraberd20ffeb2018-02-18 03:16:43 +010017import base64
Jakob Buchgraber0b6a39d2018-02-17 00:45:14 +010018import codecs
Jakob Buchgraberd20ffeb2018-02-18 03:16:43 +010019import hashlib
Jakob Buchgraber0b6a39d2018-02-17 00:45:14 +010020import json
21import os.path
Jakob Buchgraber6db0f262018-02-17 15:45:54 +010022import multiprocessing
Jakob Buchgraber0b6a39d2018-02-17 00:45:14 +010023import re
24import shutil
25import subprocess
26import sys
27import stat
28import tempfile
29import urllib.request
30from shutil import copyfile
31from urllib.parse import urlparse
32
33
34def downstream_projects():
35 return {
36 "BUILD_file_generator": {
37 "git_repository": "https://github.com/bazelbuild/BUILD_file_generator.git",
38 "http_config": "https://raw.githubusercontent.com/bazelbuild/continuous-integration/master/buildkite/pipelines/BUILD_file_generator-postsubmit.json"
39 },
40 "bazel-toolchains": {
41 "git_repository": "https://github.com/bazelbuild/bazel-toolchains.git",
42 "http_config": "https://raw.githubusercontent.com/bazelbuild/continuous-integration/master/buildkite/pipelines/bazel-toolchains-postsubmit.json"
43 },
44 "buildtools": {
45 "git_repository": "https://github.com/bazelbuild/buildtools.git",
46 "http_config": "https://raw.githubusercontent.com/bazelbuild/continuous-integration/master/buildkite/pipelines/buildtools-postsubmit.json"
47 },
48 "CLion Plugin": {
49 "git_repository": "https://github.com/bazelbuild/intellij.git",
50 "http_config": "https://raw.githubusercontent.com/bazelbuild/continuous-integration/master/buildkite/pipelines/clion-postsubmit.json"
51 },
52 "Eclipse Plugin": {
53 "git_repository": "https://github.com/bazelbuild/eclipse.git",
54 "http_config": "https://raw.githubusercontent.com/bazelbuild/continuous-integration/master/buildkite/pipelines/eclipse-postsubmit.json"
55 },
56 "Gerrit": {
57 "git_repository": "https://gerrit.googlesource.com/gerrit.git",
58 "http_config": "https://raw.githubusercontent.com/bazelbuild/continuous-integration/master/buildkite/pipelines/gerrit-postsubmit.json"
59 },
60 "Google Logging": {
61 "git_repository": "https://github.com/google/glog.git",
62 "http_config": "https://raw.githubusercontent.com/bazelbuild/continuous-integration/master/buildkite/pipelines/glog-postsubmit.json"
63 },
64 "IntelliJ Plugin": {
65 "git_repository": "https://github.com/bazelbuild/intellij.git",
66 "http_config": "https://raw.githubusercontent.com/bazelbuild/continuous-integration/master/buildkite/pipelines/intellij-postsubmit.json"
67 },
68 "migration-tooling": {
69 "git_repository": "https://github.com/bazelbuild/migration-tooling.git",
70 "http_config": "https://raw.githubusercontent.com/bazelbuild/continuous-integration/master/buildkite/pipelines/migration-tooling-postsubmit.json"
71 },
72 "protobuf": {
73 "git_repository": "https://github.com/google/protobuf.git",
74 "http_config": "https://raw.githubusercontent.com/bazelbuild/continuous-integration/master/buildkite/pipelines/protobuf-postsubmit.json"
75 },
76 "re2": {
77 "git_repository": "https://github.com/google/re2.git",
78 "http_config": "https://raw.githubusercontent.com/bazelbuild/continuous-integration/master/buildkite/pipelines/re2-postsubmit.json"
79 },
80 "rules_appengine": {
81 "git_repository": "https://github.com/bazelbuild/rules_appengine.git",
82 "http_config": "https://raw.githubusercontent.com/bazelbuild/continuous-integration/master/buildkite/pipelines/rules_appengine-postsubmit.json"
83 },
84 "rules_closure": {
85 "git_repository": "https://github.com/bazelbuild/rules_closure.git",
86 "http_config": "https://raw.githubusercontent.com/bazelbuild/continuous-integration/master/buildkite/pipelines/rules_closure-postsubmit.json"
87 },
88 "rules_d": {
89 "git_repository": "https://github.com/bazelbuild/rules_d.git",
90 "http_config": "https://raw.githubusercontent.com/bazelbuild/continuous-integration/master/buildkite/pipelines/rules_d-postsubmit.json"
91 },
92 "rules_go": {
93 "git_repository": "https://github.com/bazelbuild/rules_go.git",
94 "http_config": "https://raw.githubusercontent.com/bazelbuild/continuous-integration/master/buildkite/pipelines/rules_go-postsubmit.json"
95 },
96 "rules_groovy": {
97 "git_repository": "https://github.com/bazelbuild/rules_groovy.git",
98 "http_config": "https://raw.githubusercontent.com/bazelbuild/continuous-integration/master/buildkite/pipelines/rules_groovy-postsubmit.json"
99 },
100 "rules_gwt": {
101 "git_repository": "https://github.com/bazelbuild/rules_gwt.git",
102 "http_config": "https://raw.githubusercontent.com/bazelbuild/continuous-integration/master/buildkite/pipelines/rules_gwt-postsubmit.json"
103 },
104 "rules_jsonnet": {
105 "git_repository": "https://github.com/bazelbuild/rules_jsonnet.git",
106 "http_config": "https://raw.githubusercontent.com/bazelbuild/continuous-integration/master/buildkite/pipelines/rules_jsonnet-postsubmit.json"
107 },
108 "rules_k8s": {
109 "git_repository": "https://github.com/bazelbuild/rules_k8s.git",
110 "http_config": "https://raw.githubusercontent.com/bazelbuild/continuous-integration/master/buildkite/pipelines/rules_k8s-postsubmit.json"
111 },
112 "rules_nodejs": {
113 "git_repository": "https://github.com/bazelbuild/rules_nodejs.git",
114 "http_config": "https://raw.githubusercontent.com/bazelbuild/continuous-integration/master/buildkite/pipelines/rules_nodejs-postsubmit.json"
115 },
116 "rules_perl": {
117 "git_repository": "https://github.com/bazelbuild/rules_perl.git",
118 "http_config": "https://raw.githubusercontent.com/bazelbuild/continuous-integration/master/buildkite/pipelines/rules_perl-postsubmit.json"
119 },
120 "rules_python": {
121 "git_repository": "https://github.com/bazelbuild/rules_python.git",
122 "http_config": "https://raw.githubusercontent.com/bazelbuild/continuous-integration/master/buildkite/pipelines/rules_python-postsubmit.json"
123 },
124 "rules_rust": {
125 "git_repository": "https://github.com/bazelbuild/rules_rust.git",
126 "http_config": "https://raw.githubusercontent.com/bazelbuild/continuous-integration/master/buildkite/pipelines/rules_rust-postsubmit.json"
127 },
128 "rules_sass": {
129 "git_repository": "https://github.com/bazelbuild/rules_sass.git",
130 "http_config": "https://raw.githubusercontent.com/bazelbuild/continuous-integration/master/buildkite/pipelines/rules_sass-postsubmit.json"
131 },
132 "rules_scala": {
133 "git_repository": "https://github.com/bazelbuild/rules_scala.git",
134 "http_config": "https://raw.githubusercontent.com/bazelbuild/continuous-integration/master/buildkite/pipelines/rules_scala-postsubmit.json"
135 },
136 "rules_typescript": {
137 "git_repository": "https://github.com/bazelbuild/rules_typescript.git",
138 "http_config": "https://raw.githubusercontent.com/bazelbuild/continuous-integration/master/buildkite/pipelines/rules_typescript-postsubmit.json"
139 },
Jakob Buchgraberd2522ec2018-02-18 17:56:36 +0100140 # Enable once is resolved: https://github.com/bazelbuild/continuous-integration/issues/191
141 # "rules_webtesting": {
142 # "git_repository": "https://github.com/bazelbuild/rules_webtesting.git",
143 # "http_config": "https://raw.githubusercontent.com/bazelbuild/continuous-integration/master/buildkite/pipelines/rules_webtesting-postsubmit.json"
144 # },
Jakob Buchgraber0b6a39d2018-02-17 00:45:14 +0100145 "skydoc": {
146 "git_repository": "https://github.com/bazelbuild/skydoc.git",
147 "http_config": "https://raw.githubusercontent.com/bazelbuild/continuous-integration/master/buildkite/pipelines/skydoc-postsubmit.json"
148 },
149 "subpar": {
150 "git_repository": "https://github.com/google/subpar.git",
151 "http_config": "https://raw.githubusercontent.com/bazelbuild/continuous-integration/master/buildkite/pipelines/subpar-postsubmit.json"
152 },
153 "TensorFlow": {
154 "git_repository": "https://github.com/tensorflow/tensorflow.git",
155 "http_config": "https://raw.githubusercontent.com/bazelbuild/continuous-integration/master/buildkite/pipelines/tensorflow-postsubmit.json"
156 },
157 "TensorFlow Serving": {
158 "git_repository": "https://github.com/tensorflow/serving.git",
159 "http_config": "https://raw.githubusercontent.com/bazelbuild/continuous-integration/master/buildkite/pipelines/tensorflow-serving-postsubmit.json"
160 }
161 }
162
163
164def python_binary():
165 return "python3.6"
166
167
168def bazelcipy_url():
169 '''
170 URL to the latest version of this script.
171 '''
Jakob Buchgraber371d6222018-02-17 00:56:13 +0100172 return "https://raw.githubusercontent.com/bazelbuild/continuous-integration/master/buildkite/bazelci.py"
Jakob Buchgraber0b6a39d2018-02-17 00:45:14 +0100173
174
175def eprint(*args, **kwargs):
176 '''
177 Print to stderr and exit the process.
178 '''
179 print(*args, file=sys.stderr, **kwargs)
180 exit(1)
181
182
183def platforms_info():
184 '''
185 Returns a map containing all supported platform names as keys, with the
186 values being the platform name in a human readable format, and a the
187 buildkite-agent's working directory.
188 '''
189 return {
190 "ubuntu1404":
191 {
192 "name": "Ubuntu 14.04",
Jakob Buchgraber9c83de72018-02-18 15:32:44 +0100193 "agent-directory": "/var/lib/buildkite-agent/builds/${BUILDKITE_AGENT_NAME}/"
Jakob Buchgraber0b6a39d2018-02-17 00:45:14 +0100194 },
195 "ubuntu1604":
196 {
197 "name": "Ubuntu 16.04",
Jakob Buchgraber9c83de72018-02-18 15:32:44 +0100198 "agent-directory": "/var/lib/buildkite-agent/builds/${BUILDKITE_AGENT_NAME}/"
Jakob Buchgraber0b6a39d2018-02-17 00:45:14 +0100199 },
200 "macos":
201 {
202 "name": "macOS",
Jakob Buchgraber9c83de72018-02-18 15:32:44 +0100203 "agent-directory": "/usr/local/var/buildkite-agent/builds/${BUILDKITE_AGENT_NAME}/"
Jakob Buchgraber0b6a39d2018-02-17 00:45:14 +0100204 }
205 }
206
Jakob Buchgraber6db0f262018-02-17 15:45:54 +0100207
Jakob Buchgraber0b6a39d2018-02-17 00:45:14 +0100208def downstream_projects_root(platform):
209 downstream_projects_dir = os.path.expandvars(
210 "${BUILDKITE_ORGANIZATION_SLUG}-downstream-projects")
211 path = os.path.join(agent_directory(platform), downstream_projects_dir)
212 if not os.path.exists(path):
213 os.makedirs(path)
214 return path
215
216
217def agent_directory(platform):
218 return os.path.expandvars(platforms_info()[platform]["agent-directory"])
219
220
221def supported_platforms():
222 return set(platforms_info().keys())
223
224
225def platform_name(platform):
226 return platforms_info()[platform]["name"]
227
228
229def fetch_configs(http_url):
230 '''
231 If specified fetches the build configuration from http_url, else tries to
232 read it from .bazelci/config.json.
233 Returns the json configuration as a python data structure.
234 '''
235 if http_url is None:
236 with open(".bazelci/config.json", "r") as fd:
237 return json.load(fd)
238 with urllib.request.urlopen(http_url) as resp:
239 reader = codecs.getreader("utf-8")
240 return json.load(reader(resp))
241
Jakob Buchgraber9c83de72018-02-18 15:32:44 +0100242
Jakob Buchgraber3120f7a2018-02-18 13:28:02 +0100243def print_collapsed_group(name):
244 print("\n--- {0}\n".format(name))
245
Jakob Buchgraber9c83de72018-02-18 15:32:44 +0100246
Jakob Buchgraber3120f7a2018-02-18 13:28:02 +0100247def print_expanded_group(name):
248 print("\n+++ {0}\n".format(name))
Jakob Buchgraber0b6a39d2018-02-17 00:45:14 +0100249
Jakob Buchgraber9c83de72018-02-18 15:32:44 +0100250
Jakob Buchgraber0b6a39d2018-02-17 00:45:14 +0100251def execute_commands(config, platform, git_repository, use_but, save_but,
252 build_only, test_only):
253 exit_code = -1
254 tmpdir = None
255 bazel_binary = "bazel"
256 try:
Jakob Buchgraberdb20a862018-02-17 17:40:27 +0100257 if git_repository:
258 clone_git_repository(git_repository, platform)
259 cleanup(platform)
Jakob Buchgraber0b6a39d2018-02-17 00:45:14 +0100260 tmpdir = tempfile.mkdtemp()
261 if use_but:
Jakob Buchgraber3120f7a2018-02-18 13:28:02 +0100262 print_collapsed_group("Downloading Bazel under test")
Jakob Buchgraberd20ffeb2018-02-18 03:16:43 +0100263 bazel_binary = download_bazel_binary(tmpdir, platform)
Jakob Buchgraber7e690a72018-02-18 13:22:15 +0100264 print_bazel_version_info(bazel_binary)
Jakob Buchgraber0b6a39d2018-02-17 00:45:14 +0100265 execute_shell_commands(config.get("shell_commands", None))
266 execute_bazel_run(bazel_binary, config.get("run_targets", None))
267 if not test_only:
268 execute_bazel_build(bazel_binary, config.get("build_flags", []),
269 config.get("build_targets", None))
270 if save_but:
271 upload_bazel_binary()
272 if not build_only:
273 bep_file = os.path.join(tmpdir, "build_event_json_file.json")
274 exit_code = execute_bazel_test(bazel_binary, config.get("test_flags", []),
275 config.get("test_targets", None), bep_file)
Jakob Buchgraber02e07222018-02-19 15:05:56 +0100276 # Fail the pipeline if there were any flaky tests.
277 if has_flaky_tests() and exit_code == 0:
278 exit_code = 1
279 upload_test_logs(bep_file, tmpdir)
Jakob Buchgraber0b6a39d2018-02-17 00:45:14 +0100280 finally:
281 if tmpdir:
282 shutil.rmtree(tmpdir)
Jakob Buchgraber1554bfb2018-02-17 17:36:36 +0100283 cleanup(platform)
Jakob Buchgraber0b6a39d2018-02-17 00:45:14 +0100284 if exit_code > -1:
285 exit(exit_code)
286
287
Jakob Buchgraber02e07222018-02-19 15:05:56 +0100288def has_flaky_tests(bep_file):
289 return len(test_logs_for_status(bep_file, status="FLAKY")) > 0
290
291
Jakob Buchgraber7e690a72018-02-18 13:22:15 +0100292def print_bazel_version_info(bazel_binary):
Jakob Buchgraber3120f7a2018-02-18 13:28:02 +0100293 print_collapsed_group("Bazel Info")
Jakob Buchgraber7e690a72018-02-18 13:22:15 +0100294 fail_if_nonzero(execute_command([bazel_binary, "version"]))
295 fail_if_nonzero(execute_command([bazel_binary, "info"]))
296
297
Jakob Buchgraber0b6a39d2018-02-17 00:45:14 +0100298def upload_bazel_binary():
Jakob Buchgraber3120f7a2018-02-18 13:28:02 +0100299 print_collapsed_group("Uploading Bazel under test")
Jakob Buchgraber0b6a39d2018-02-17 00:45:14 +0100300 fail_if_nonzero(execute_command(["buildkite-agent", "artifact", "upload",
301 "bazel-bin/src/bazel"]))
302
303
Jakob Buchgraberd20ffeb2018-02-18 03:16:43 +0100304def download_bazel_binary(dest_dir, platform):
305 source_step = create_label(platform_name(platform), "Bazel", build_only=True,
306 test_only=False)
Jakob Buchgraber0b6a39d2018-02-17 00:45:14 +0100307 fail_if_nonzero(execute_command(["buildkite-agent", "artifact", "download",
308 "bazel-bin/src/bazel", dest_dir, "--step", source_step]))
309 bazel_binary_path = os.path.join(dest_dir, "bazel-bin/src/bazel")
310 st = os.stat(bazel_binary_path)
311 os.chmod(bazel_binary_path, st.st_mode | stat.S_IEXEC)
312 return bazel_binary_path
313
314
315def clone_git_repository(git_repository, platform):
316 root = downstream_projects_root(platform)
317 project_name = re.search("/([^/]+)\.git$", git_repository).group(1)
318 clone_path = os.path.join(root, project_name)
Jakob Buchgraber288d87f2018-02-18 13:30:33 +0100319 print_collapsed_group("Fetching " + project_name + " sources")
Jakob Buchgraber0b6a39d2018-02-17 00:45:14 +0100320 if os.path.exists(clone_path):
321 os.chdir(clone_path)
322 fail_if_nonzero(execute_command(
323 ["git", "remote", "set-url", "origin", git_repository]))
324 fail_if_nonzero(execute_command(["git", "clean", "-fdqx"]))
325 fail_if_nonzero(execute_command(
326 ["git", "submodule", "foreach", "--recursive", "git", "clean", "-fdqx"]))
327 # sync to the latest commit of HEAD. Unlikely git pull this also works after
328 # a force push.
329 fail_if_nonzero(execute_command(["git", "fetch", "origin"]))
330 remote_head = subprocess.check_output(
331 ["git", "symbolic-ref", "refs/remotes/origin/HEAD"])
332 remote_head = remote_head.decode("utf-8")
333 remote_head = remote_head.rstrip()
Jakob Buchgraber7e690a72018-02-18 13:22:15 +0100334 fail_if_nonzero(execute_command(
335 ["git", "reset", remote_head, "--hard"]))
Jakob Buchgraber0b6a39d2018-02-17 00:45:14 +0100336 fail_if_nonzero(execute_command(
337 ["git", "submodule", "sync", "--recursive"]))
338 fail_if_nonzero(execute_command(
339 ["git", "submodule", "update", "--init", "--recursive", "--force"]))
340 fail_if_nonzero(execute_command(
341 ["git", "submodule", "foreach", "--recursive", "git", "reset", "--hard"]))
342 fail_if_nonzero(execute_command(["git", "clean", "-fdqx"]))
343 fail_if_nonzero(execute_command(
344 ["git", "submodule", "foreach", "--recursive", "git", "clean", "-fdqx"]))
345 else:
346 fail_if_nonzero(execute_command(
347 ["git", "clone", "--recurse-submodules", git_repository, clone_path]))
348 os.chdir(clone_path)
349
350
Jakob Buchgraber9f53b3a2018-02-17 17:30:00 +0100351def cleanup(platform):
Jakob Buchgraber3120f7a2018-02-18 13:28:02 +0100352 print_collapsed_group("Cleanup")
Jakob Buchgraber0b6a39d2018-02-17 00:45:14 +0100353 if os.path.exists("WORKSPACE"):
Jakob Buchgraber9f53b3a2018-02-17 17:30:00 +0100354 fail_if_nonzero(execute_command(["bazel", "clean", "--expunge"]))
Jakob Buchgraber0b6a39d2018-02-17 00:45:14 +0100355
Jakob Buchgraberfd1eaca2018-02-17 17:27:14 +0100356
Jakob Buchgraber0b6a39d2018-02-17 00:45:14 +0100357def execute_shell_commands(commands):
358 if not commands:
359 return
Jakob Buchgraber3120f7a2018-02-18 13:28:02 +0100360 print_collapsed_group("Setup (Shell Commands)")
Jakob Buchgraber0b6a39d2018-02-17 00:45:14 +0100361 shell_command = "\n".join(commands)
362 fail_if_nonzero(execute_command([shell_command], shell=True))
363
364
365def execute_bazel_run(bazel_binary, targets):
366 if not targets:
367 return
Jakob Buchgraber3120f7a2018-02-18 13:28:02 +0100368 print_collapsed_group("Setup (Run Targets)")
Jakob Buchgraber0b6a39d2018-02-17 00:45:14 +0100369 for target in targets:
Jakob Buchgraberd332b3f2018-02-19 15:18:44 +0100370 fail_if_nonzero(execute_command([bazel_binary, "run", "--curses=yes",
371 "--color=yes", "--verbose_failurs", target]))
Jakob Buchgraber0b6a39d2018-02-17 00:45:14 +0100372
373
374def execute_bazel_build(bazel_binary, flags, targets):
375 if not targets:
376 return
Jakob Buchgraber3120f7a2018-02-18 13:28:02 +0100377 print_expanded_group("Build")
Jakob Buchgraber7b685c02018-02-17 16:05:22 +0100378 num_jobs = str(multiprocessing.cpu_count())
Jakob Buchgraberd332b3f2018-02-19 15:18:44 +0100379 common_flags = ["--curses=yes", "--color=yes", "--keep_going",
380 "--verbose_failures", "--jobs=" + num_jobs]
Jakob Buchgraber6db0f262018-02-17 15:45:54 +0100381 fail_if_nonzero(execute_command(
382 [bazel_binary, "build"] + common_flags + flags + targets))
Jakob Buchgraber0b6a39d2018-02-17 00:45:14 +0100383
384
385def execute_bazel_test(bazel_binary, flags, targets, bep_file):
386 if not targets:
387 return 0
Jakob Buchgraber3120f7a2018-02-18 13:28:02 +0100388 print_expanded_group("Test")
Jakob Buchgraber7b685c02018-02-17 16:05:22 +0100389 num_jobs = str(multiprocessing.cpu_count())
Jakob Buchgraberd332b3f2018-02-19 15:18:44 +0100390 common_flags = ["--curses=yes", "--color=yes", "--keep_going", "--verbose_failures",
Jakob Buchgraber02e07222018-02-19 15:05:56 +0100391 "--flaky_test_attempts=3", "--build_tests_only",
392 "--jobs=" + num_jobs, "--local_test_jobs=" + num_jobs,
393 "--build_event_json_file=" + bep_file]
Jakob Buchgraber6db0f262018-02-17 15:45:54 +0100394 return execute_command([bazel_binary, "test"] + common_flags + flags + targets)
Jakob Buchgraber0b6a39d2018-02-17 00:45:14 +0100395
396
397def fail_if_nonzero(exitcode):
398 if exitcode is not 0:
399 exit(exitcode)
400
401
Jakob Buchgraber02e07222018-02-19 15:05:56 +0100402def upload_test_logs(bep_file, tmpdir):
Jakob Buchgraber0b6a39d2018-02-17 00:45:14 +0100403 if not os.path.exists(bep_file):
404 return
Jakob Buchgraber02e07222018-02-19 15:05:56 +0100405 test_logs = test_logs_to_upload(bep_file, tmpdir)
406 if test_logs:
Jakob Buchgraber699aece2018-02-19 12:49:30 +0100407 cwd = os.getcwd()
408 try:
409 os.chdir(tmpdir)
Jakob Buchgraber02e07222018-02-19 15:05:56 +0100410 print_collapsed_group("Uploading test logs")
411 for logfile in test_logs:
Jakob Buchgraberc0d736c2018-02-19 14:21:43 +0100412 relative_path = os.path.relpath(logfile, tmpdir)
Jakob Buchgraber699aece2018-02-19 12:49:30 +0100413 fail_if_nonzero(execute_command(["buildkite-agent", "artifact", "upload",
Jakob Buchgraberc0d736c2018-02-19 14:21:43 +0100414 relative_path]))
Jakob Buchgraber699aece2018-02-19 12:49:30 +0100415 finally:
416 os.chdir(cwd)
Jakob Buchgraber0b6a39d2018-02-17 00:45:14 +0100417
418
Jakob Buchgraber02e07222018-02-19 15:05:56 +0100419def test_logs_to_upload(bep_file, tmpdir):
420 failed = test_logs_for_status(bep_file, status="FAILED")
421 timed_out = test_logs_for_status(bep_file, status="TIMEOUT")
422 flaky = test_logs_for_status(bep_file, status="FLAKY")
423 # Rename the test.log files to the target that created them
424 # so that it's easy to associate test.log and target.
425 new_paths = []
426 for label, test_logs in (failed + timed_out + flaky):
427 attempt = 0
428 if len(test_logs) > 1:
429 attempt = 1
430 for test_log in test_logs:
431 new_path = test_label_to_path(tmpdir, label, attempt)
432 os.makedirs(os.path.dirname(new_path), exist_ok=True)
433 copyfile(test_logs, new_path)
434 new_paths.append(new_path)
435 attempt = attempt + 1
436 return new_paths
Jakob Buchgraber699aece2018-02-19 12:49:30 +0100437
438
Jakob Buchgraber02e07222018-02-19 15:05:56 +0100439def test_label_to_path(tmpdir, label, attempt):
Jakob Buchgraber699aece2018-02-19 12:49:30 +0100440 # remove leading //
441 path = label[2:]
442 path = path.replace(":", "/")
Jakob Buchgraber02e07222018-02-19 15:05:56 +0100443 if attempt == 0:
444 path = os.path.join(path, "test.log")
445 else:
446 path = os.path.join(path, "attempt_" + str(attempt) + ".log")
Jakob Buchgraber699aece2018-02-19 12:49:30 +0100447 return os.path.join(tmpdir, path)
448
449
Jakob Buchgraber02e07222018-02-19 15:05:56 +0100450def test_logs_for_status(bep_file, status):
Jakob Buchgraber699aece2018-02-19 12:49:30 +0100451 targets = []
Jakob Buchgraber0b6a39d2018-02-17 00:45:14 +0100452 raw_data = ""
453 with open(bep_file) as f:
454 raw_data = f.read()
455 decoder = json.JSONDecoder()
456
457 pos = 0
458 while pos < len(raw_data):
Jakob Buchgraber699aece2018-02-19 12:49:30 +0100459 bep_obj, size = decoder.raw_decode(raw_data[pos:])
460 if "testResult" in bep_obj:
Jakob Buchgraber02e07222018-02-19 15:05:56 +0100461 test_target = bep_obj["id"]["testResult"]["label"]
Jakob Buchgraber699aece2018-02-19 12:49:30 +0100462 test_result = bep_obj["testResult"]
463 if test_result["status"] == status:
Jakob Buchgraber0b6a39d2018-02-17 00:45:14 +0100464 outputs = test_result["testActionOutput"]
Jakob Buchgraber02e07222018-02-19 15:05:56 +0100465 test_logs = []
Jakob Buchgraber0b6a39d2018-02-17 00:45:14 +0100466 for output in outputs:
467 if output["name"] == "test.log":
Jakob Buchgraber02e07222018-02-19 15:05:56 +0100468 test_logs.append(urlparse(output["uri"]).path)
469 if test_logs:
470 targets.append((test_target, test_logs))
Jakob Buchgraber0b6a39d2018-02-17 00:45:14 +0100471 pos += size + 1
Jakob Buchgraber699aece2018-02-19 12:49:30 +0100472 return targets
Jakob Buchgraber0b6a39d2018-02-17 00:45:14 +0100473
474
475def execute_command(args, shell=False):
476 print(" ".join(args))
477 res = subprocess.run(args, shell=shell)
478 return res.returncode
479
480
481def print_project_pipeline(platform_configs, project_name, http_config,
482 git_repository, use_but):
483 pipeline_steps = []
484 for platform, config in platform_configs.items():
485 step = runner_step(platform, project_name, http_config, git_repository,
486 use_but)
487 pipeline_steps.append(step)
488
489 print_pipeline(pipeline_steps)
490
491
492def runner_step(platform, project_name=None, http_config=None,
493 git_repository=None, use_but=False, save_but=False, build_only=False,
494 test_only=False):
495 command = python_binary() + " bazelci.py runner --platform=" + platform
496 if http_config:
497 command = command + " --http_config=" + http_config
498 if git_repository:
499 command = command + " --git_repository=" + git_repository
500 if use_but:
501 command = command + " --use_but"
502 if save_but:
503 command = command + " --save_but"
504 if build_only:
505 command = command + " --build_only"
506 if test_only:
507 command = command + " --test_only"
508 label = create_label(platform_name(platform),
509 project_name, build_only, test_only)
510 return """
511 - label: \"{0}\"
512 command: \"{1}\\n{2}\"
513 agents:
514 - \"os={3}\"""".format(label, fetch_bazelcipy_command(), command, platform)
515
516
517def print_pipeline(steps):
518 print("steps:")
519 for step in steps:
520 print(step)
521
522
523def wait_step():
524 return """
525 - wait"""
526
527
528def http_config_flag(http_config):
529 if http_config is not None:
530 return "--http_config=" + http_config
531 return ""
532
533
534def fetch_bazelcipy_command():
Jakob Buchgraber1f926112018-02-19 15:51:54 +0100535 return "curl -s {0} -o bazelci.py".format(bazelcipy_url())
Jakob Buchgraber0b6a39d2018-02-17 00:45:14 +0100536
537
538def upload_project_pipeline_step(project_name, git_repository, http_config):
539 pipeline_command = ("{0} bazelci.py project_pipeline --project_name=\\\"{1}\\\" " +
540 "--use_but --git_repository={2}").format(python_binary(), project_name,
541 git_repository)
542 if http_config:
543 pipeline_command = pipeline_command + " --http_config=" + http_config
544 pipeline_command = pipeline_command + " | buildkite-agent pipeline upload"
545
546 return """
547 - label: \"Setup {0}\"
548 command: \"{1}\\n{2}\"
549 agents:
550 - \"pipeline=true\"""".format(project_name, fetch_bazelcipy_command(),
551 pipeline_command)
552
553
554def create_label(platform_name, project_name=None, build_only=False,
555 test_only=False):
556 label = ""
557 if build_only:
558 label = "Build "
559 if test_only:
560 label = "Test "
561 if project_name:
562 label = label + "{0} ({1})".format(project_name, platform_name)
563 else:
564 label = label + platform_name
565 return label
566
567
568def bazel_build_step(platform, project_name, http_config=None,
569 build_only=False, test_only=False):
570 pipeline_command = python_binary() + " bazelci.py runner"
571 if build_only:
572 pipeline_command = pipeline_command + " --build_only --save_but"
573 if test_only:
574 pipeline_command = pipeline_command + " --test_only"
575 if http_config:
576 pipeline_command = pipeline_command + " --http_config=" + http_config
577 label = create_label(platform_name(platform), project_name, build_only=build_only,
578 test_only=test_only)
579 pipeline_command = pipeline_command + " --platform=" + platform
580
581 return """
582 - label: \"{0}\"
583 command: \"{1}\\n{2}\"
584 agents:
585 - \"os={3}\"""".format(label, fetch_bazelcipy_command(),
586 pipeline_command, platform)
587
588
Jakob Buchgraber76381e02018-02-19 16:19:56 +0100589def publish_bazel_binaries_step():
590 command = python_binary() + " bazelci.py publish_binaries"
Jakob Buchgraberd20ffeb2018-02-18 03:16:43 +0100591 return """
Jakob Buchgraber76381e02018-02-19 16:19:56 +0100592 - label: \"Publish Bazel Binaries\"
593 command: \"{0}\\n{1}\"
Jakob Buchgraberd20ffeb2018-02-18 03:16:43 +0100594 agents:
Jakob Buchgraber76381e02018-02-19 16:19:56 +0100595 - \"pipeline=true\"""".format(fetch_bazelcipy_command(), command)
Jakob Buchgraberd20ffeb2018-02-18 03:16:43 +0100596
597
Jakob Buchgraber0b6a39d2018-02-17 00:45:14 +0100598def print_bazel_postsubmit_pipeline(configs, http_config):
599 if not configs:
600 eprint("Bazel postsubmit pipeline configuration is empty.")
601 if set(configs.keys()) != set(supported_platforms()):
602 eprint("Bazel postsubmit pipeline needs to build Bazel on all " +
603 "supported platforms.")
604
605 pipeline_steps = []
606 for platform, config in configs.items():
607 pipeline_steps.append(bazel_build_step(platform, "Bazel",
608 http_config, build_only=True))
609 pipeline_steps.append(wait_step())
Jakob Buchgraberd20ffeb2018-02-18 03:16:43 +0100610
Jakob Buchgraberc0ccfbb2018-02-18 04:39:20 +0100611 # todo move this to the end with a wait step.
Jakob Buchgraber76381e02018-02-19 16:19:56 +0100612 pipeline_steps.append(publish_bazel_binaries_step())
Jakob Buchgraberd20ffeb2018-02-18 03:16:43 +0100613
Jakob Buchgraberc0ccfbb2018-02-18 04:39:20 +0100614 for platform, config in configs.items():
615 pipeline_steps.append(bazel_build_step(platform, "Bazel",
616 http_config, test_only=True))
617 for project, config in downstream_projects().items():
618 git_repository = config["git_repository"]
619 http_config = config.get("http_config", None)
620 pipeline_steps.append(upload_project_pipeline_step(project,
621 git_repository, http_config))
Jakob Buchgraber0b6a39d2018-02-17 00:45:14 +0100622
623 print_pipeline(pipeline_steps)
624
625
Jakob Buchgraberd20ffeb2018-02-18 03:16:43 +0100626def bazelci_builds_download_url(platform, build_number):
Jakob Buchgraber76381e02018-02-19 16:19:56 +0100627 return "https://storage.googleapis.com/bazel-builds/artifacts/{0}/{1}/bazel".format(platform, build_number)
Jakob Buchgraberd20ffeb2018-02-18 03:16:43 +0100628
629
630def bazelci_builds_upload_url(platform, build_number):
Jakob Buchgraber455dd182018-02-18 13:39:31 +0100631 return "gs://bazel-builds/artifacts/{0}/{1}/bazel".format(build_number, platform)
Jakob Buchgraberd20ffeb2018-02-18 03:16:43 +0100632
633
Jakob Buchgraber76381e02018-02-19 16:19:56 +0100634def bazelci_builds_metadata_url():
635 return "gs://bazel-builds/metadata/latest_fully_tested.json"
Jakob Buchgraberd20ffeb2018-02-18 03:16:43 +0100636
637
Jakob Buchgraber76381e02018-02-19 16:19:56 +0100638def latest_generation_and_build_number():
Jakob Buchgraberd20ffeb2018-02-18 03:16:43 +0100639 output = None
640 attempt = 0
641 while attempt < 5:
642 output = subprocess.check_output(
Jakob Buchgraber76381e02018-02-19 16:19:56 +0100643 ["gsutil", "stat", bazelci_builds_metadata_url()])
Jakob Buchgraberd20ffeb2018-02-18 03:16:43 +0100644 match = re.search("Generation:[ ]*([0-9]+)", output.decode("utf-8"))
645 if not match:
646 eprint("Couldn't parse generation. gsutil output format changed?")
647 generation = match.group(1)
648
649 match = re.search("Hash \(md5\):[ ]*([^\s]+)", output.decode("utf-8"))
650 if not match:
651 eprint("Couldn't parse md5 hash. gsutil output format changed?")
652 expected_md5hash = base64.b64decode(match.group(1))
653
654 output = subprocess.check_output(
Jakob Buchgraber76381e02018-02-19 16:19:56 +0100655 ["gsutil", "cat", bazelci_builds_metadata_url()])
Jakob Buchgraberd20ffeb2018-02-18 03:16:43 +0100656 hasher = hashlib.md5()
657 hasher.update(output)
658 actual_md5hash = hasher.digest()
659
660 if expected_md5hash == actual_md5hash:
661 break
662 attempt = attempt + 1
663 info = json.loads(output.decode("utf-8"))
664 return (generation, info["build_number"])
665
Jakob Buchgraber699aece2018-02-19 12:49:30 +0100666
Jakob Buchgraber88083fd2018-02-18 17:23:35 +0100667def sha256_hexdigest(filename):
Jakob Buchgraber699aece2018-02-19 12:49:30 +0100668 sha256 = hashlib.sha256()
669 with open(filename, 'rb') as f:
670 for block in iter(lambda: f.read(65536), b''):
671 sha256.update(block)
672 return sha256.hexdigest()
673
Jakob Buchgraberd20ffeb2018-02-18 03:16:43 +0100674
Jakob Buchgraber76381e02018-02-19 16:19:56 +0100675def try_publish_binaries(build_number, expected_generation):
Jakob Buchgraberd20ffeb2018-02-18 03:16:43 +0100676 tmpdir = None
677 try:
678 tmpdir = tempfile.mkdtemp()
Jakob Buchgraberd20ffeb2018-02-18 03:16:43 +0100679 info = {
680 "build_number": build_number,
Jakob Buchgraber88083fd2018-02-18 17:23:35 +0100681 "git_commit": os.environ["BUILDKITE_COMMIT"],
Jakob Buchgraber76381e02018-02-19 16:19:56 +0100682 "platforms": {}
Jakob Buchgraberd20ffeb2018-02-18 03:16:43 +0100683 }
Jakob Buchgraber76381e02018-02-19 16:19:56 +0100684 for platform in supported_platforms():
685 bazel_binary_path = download_bazel_binary(tmpdir, platform)
686 fail_if_nonzero(execute_command(["gsutil", "cp", "-a", "public-read", bazel_binary_path,
687 bazelci_builds_upload_url(platform, build_number)]))
688 info["platforms"][platform] = {
689 "url": bazelci_builds_download_url(platform, build_number),
690 "sha256": sha256_hexdigest(bazel_binary_path),
691 }
692
Jakob Buchgraberd20ffeb2018-02-18 03:16:43 +0100693 info_file = os.path.join(tmpdir, "info.json")
Jakob Buchgraber7e690a72018-02-18 13:22:15 +0100694 with open(info_file, mode="w", encoding="utf-8") as fp:
695 json.dump(info, fp)
Jakob Buchgraber4680fdf2018-02-18 17:04:27 +0100696 exitcode = execute_command(["gsutil", "-h", "x-goog-if-generation-match:" + expected_generation,
697 "-h", "Content-Type:application/json", "cp", "-a",
698 "public-read", info_file, bazelci_builds_metadata_url(platform)])
Jakob Buchgraberd20ffeb2018-02-18 03:16:43 +0100699 return exitcode == 0
700 finally:
701 if tmpdir:
Jakob Buchgraber24bf36d2018-02-18 03:50:25 +0100702 shutil.rmtree(tmpdir)
Jakob Buchgraberd20ffeb2018-02-18 03:16:43 +0100703
704
Jakob Buchgraber76381e02018-02-19 16:19:56 +0100705def publish_binaries():
Jakob Buchgraberd20ffeb2018-02-18 03:16:43 +0100706 '''
Jakob Buchgraber76381e02018-02-19 16:19:56 +0100707 Publish Bazel binaries to GCS.
Jakob Buchgraberd20ffeb2018-02-18 03:16:43 +0100708 '''
709 attempt = 0
710 while attempt < 5:
Jakob Buchgraber76381e02018-02-19 16:19:56 +0100711 latest_generation, latest_build_number = latest_generation_and_build_number()
Jakob Buchgraberd20ffeb2018-02-18 03:16:43 +0100712
713 current_build_number = os.environ.get("BUILDKITE_BUILD_NUMBER", None)
714 if not current_build_number:
715 eprint("Not running inside Buildkite")
716 current_build_number = int(current_build_number)
717 if current_build_number <= latest_build_number:
718 print(("Current build '{0}' is not newer than latest published '{1}'. " +
719 "Skipping publishing of binaries.").format(current_build_number,
720 latest_build_number))
721 break
722
Jakob Buchgraber76381e02018-02-19 16:19:56 +0100723 if try_publish_binaries(current_build_number, latest_generation):
724 print("Successfully updated '{0}' to binaries from build {1}."
725 .format(bazelci_builds_metadata_url(), current_build_number))
Jakob Buchgraberd20ffeb2018-02-18 03:16:43 +0100726 break
727 attempt = attempt + 1
728
729
Jakob Buchgraber0b6a39d2018-02-17 00:45:14 +0100730if __name__ == "__main__":
731 parser = argparse.ArgumentParser(
732 description='Bazel Continuous Integration Script')
733
734 subparsers = parser.add_subparsers(dest="subparsers_name")
735 bazel_postsubmit_pipeline = subparsers.add_parser(
736 "bazel_postsubmit_pipeline")
737 bazel_postsubmit_pipeline.add_argument("--http_config", type=str)
738 bazel_postsubmit_pipeline.add_argument("--git_repository", type=str)
739
740 project_pipeline = subparsers.add_parser("project_pipeline")
741 project_pipeline.add_argument("--project_name", type=str)
742 project_pipeline.add_argument("--http_config", type=str)
743 project_pipeline.add_argument("--git_repository", type=str)
744 project_pipeline.add_argument(
745 "--use_but", type=bool, nargs="?", const=True)
746
747 runner = subparsers.add_parser("runner")
748 runner.add_argument("--platform", action="store",
749 choices=list(supported_platforms()))
750 runner.add_argument("--http_config", type=str)
751 runner.add_argument("--git_repository", type=str)
752 runner.add_argument("--use_but", type=bool, nargs="?", const=True)
753 runner.add_argument("--save_but", type=bool, nargs="?", const=True)
754 runner.add_argument("--build_only", type=bool, nargs="?", const=True)
755 runner.add_argument("--test_only", type=bool, nargs="?", const=True)
756
Jakob Buchgraber76381e02018-02-19 16:19:56 +0100757 runner = subparsers.add_parser("publish_binaries")
Jakob Buchgraberd20ffeb2018-02-18 03:16:43 +0100758
Jakob Buchgraber0b6a39d2018-02-17 00:45:14 +0100759 args = parser.parse_args()
760
761 if args.subparsers_name == "bazel_postsubmit_pipeline":
762 configs = fetch_configs(args.http_config)
763 print_bazel_postsubmit_pipeline(configs.get("platforms", None),
764 args.http_config)
765 elif args.subparsers_name == "project_pipeline":
766 configs = fetch_configs(args.http_config)
767 print_project_pipeline(configs.get("platforms", None), args.project_name,
768 args.http_config, args.git_repository, args.use_but)
769 elif args.subparsers_name == "runner":
770 configs = fetch_configs(args.http_config)
771 execute_commands(configs.get("platforms", None)[args.platform],
772 args.platform, args.git_repository, args.use_but, args.save_but,
773 args.build_only, args.test_only)
Jakob Buchgraber76381e02018-02-19 16:19:56 +0100774 elif args.subparsers_name == "publish_binaries":
775 publish_binaries()
Jakob Buchgraber0b6a39d2018-02-17 00:45:14 +0100776 else:
777 parser.print_help()