blob: 000dff0fd9f17d659474d62eae265802fe42ed2d [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
Florian Weikert78e67de2024-02-06 12:58:41 +010020import collections
21import concurrent.futures
Yun Peng5a1a9442022-01-11 14:26:48 +010022import copy
Jakob Buchgraber12807052018-02-25 17:04:56 +010023import datetime
Philipp Wollermann2b4ee9f2021-02-11 16:32:35 +010024from glob import glob
Jakob Buchgraberd20ffeb2018-02-18 03:16:43 +010025import hashlib
Yun Peng5a1a9442022-01-11 14:26:48 +010026import itertools
Jakob Buchgraber0b6a39d2018-02-17 00:45:14 +010027import json
Jakob Buchgraber6db0f262018-02-17 15:45:54 +010028import multiprocessing
Philipp Wollermann0a04cf32018-02-21 17:07:22 +010029import os
Philipp Wollermanndcaddd92018-02-21 14:13:43 +010030import os.path
Florian Weikertda94a102022-10-21 12:24:37 +020031import platform as platform_module
Jakob Buchgraber257693b2018-02-20 00:03:56 +010032import random
Jakob Buchgraber0b6a39d2018-02-17 00:45:14 +010033import re
Yun Peng9337bb32020-02-28 13:31:29 +010034import requests
Jakob Buchgraber0b6a39d2018-02-17 00:45:14 +010035import shutil
Philipp Wollermanndcaddd92018-02-21 14:13:43 +010036import stat
Jakob Buchgraber0b6a39d2018-02-17 00:45:14 +010037import subprocess
38import sys
Florian Weikert5e70d9d2023-05-08 19:20:23 +020039import tarfile
Jakob Buchgraber0b6a39d2018-02-17 00:45:14 +010040import tempfile
Philipp Wollermanne1318eb2018-08-13 15:08:01 +020041import time
Philipp Wollermannce986af2019-07-18 14:46:05 +020042import urllib.error
Jakob Buchgraber0b6a39d2018-02-17 00:45:14 +010043import urllib.request
Jakob Buchgraber25bb50f2018-02-22 18:06:21 +010044import yaml
Philipp Wollermanndcaddd92018-02-21 14:13:43 +010045
46# Initialize the random number generator.
47random.seed()
48
Yun Pengc12a4c92025-01-10 13:46:54 +010049BUILDKITE_ORG = os.environ.get("BUILDKITE_ORGANIZATION_SLUG", "bazel")
Florian Weikertc2745512021-02-17 16:13:55 +010050THIS_IS_PRODUCTION = BUILDKITE_ORG == "bazel"
Philipp Wollermanne67eec42019-05-24 15:18:20 +020051THIS_IS_TESTING = BUILDKITE_ORG == "bazel-testing"
52THIS_IS_TRUSTED = BUILDKITE_ORG == "bazel-trusted"
53THIS_IS_SPARTA = True
54
55CLOUD_PROJECT = "bazel-public" if THIS_IS_TRUSTED else "bazel-untrusted"
56
57GITHUB_BRANCH = {"bazel": "master", "bazel-trusted": "master", "bazel-testing": "testing"}[
58 BUILDKITE_ORG
59]
60
Florian Weikerte1ca35d2024-01-25 13:46:16 +010061SCRIPT_URL = "https://raw.githubusercontent.com/bazelbuild/continuous-integration/{}/buildkite/bazelci.py".format(
62 GITHUB_BRANCH
Philipp Wollermannc05ac682019-01-19 12:37:28 +010063)
Jakob Buchgraber95e3d572018-02-21 18:48:49 +010064
Philipp Wollermanne67eec42019-05-24 15:18:20 +020065AGGREGATE_INCOMPATIBLE_TEST_RESULT_URL = "https://raw.githubusercontent.com/bazelbuild/continuous-integration/{}/buildkite/aggregate_incompatible_flags_test_result.py?{}".format(
66 GITHUB_BRANCH, int(time.time())
67)
68
69EMERGENCY_FILE_URL = "https://raw.githubusercontent.com/bazelbuild/continuous-integration/{}/buildkite/emergency.yml?{}".format(
70 GITHUB_BRANCH, int(time.time())
71)
72
73FLAKY_TESTS_BUCKET = {
74 "bazel-testing": "gs://bazel-testing-buildkite-stats/flaky-tests-bep/",
75 "bazel-trusted": "gs://bazel-buildkite-stats/flaky-tests-bep/",
76 "bazel": "gs://bazel-buildkite-stats/flaky-tests-bep/",
77}[BUILDKITE_ORG]
78
Chi Wangb2b65682020-08-27 10:36:15 +080079KZIPS_BUCKET = {
80 "bazel-testing": "gs://bazel-kzips-testing/",
81 "bazel-trusted": "gs://bazel-kzips/",
82 "bazel": "gs://bazel-kzips/",
83}[BUILDKITE_ORG]
84
Florian Weikert78e67de2024-02-06 12:58:41 +010085# We don't collect logs in the trusted org
86LOG_BUCKET = {
87 "bazel-testing": "https://storage.googleapis.com/bazel-testing-buildkite-artifacts",
Florian Weikert1bd9c122024-02-06 16:29:25 +010088 "bazel-trusted": "",
Florian Weikert78e67de2024-02-06 12:58:41 +010089 "bazel": "https://storage.googleapis.com/bazel-untrusted-buildkite-artifacts",
90}[BUILDKITE_ORG]
91
Florian Weikert797787b2019-12-19 15:33:07 +010092# Projects can opt out of receiving GitHub issues from --notify by adding `"do_not_notify": True` to their respective downstream entry.
Philipp Wollermanne67eec42019-05-24 15:18:20 +020093DOWNSTREAM_PROJECTS_PRODUCTION = {
Mai Hussien0731b4d2022-06-28 11:16:07 -070094 "Android Studio Plugin Google": {
95 "git_repository": "https://github.com/bazelbuild/intellij.git",
Yun Pengfb86f672023-11-10 16:18:55 +010096 "file_config": ".bazelci/android-studio.yml",
Mai Hussien0731b4d2022-06-28 11:16:07 -070097 "pipeline_slug": "android-studio-plugin-google",
Gowroji Sunil58777df2025-01-28 19:46:12 +053098 "disabled_reason": "https://github.com/bazelbuild/intellij/issues/7232#issuecomment-2617127267",
Mai Hussien0731b4d2022-06-28 11:16:07 -070099 },
Yun Peng996efad2018-11-27 17:19:44 +0100100 "Android Testing": {
101 "git_repository": "https://github.com/googlesamples/android-testing.git",
Yun Pengfb86f672023-11-10 16:18:55 +0100102 "file_config": "bazelci/buildkite-pipeline.yml",
Philipp Wollermanncd5694c2019-01-03 14:53:04 +0100103 "pipeline_slug": "android-testing",
Yun Peng9b1d3432021-12-07 10:40:45 +0100104 "disabled_reason": "https://github.com/android/testing-samples/issues/417",
Yun Peng996efad2018-11-27 17:19:44 +0100105 },
Yun Peng8910fa32019-01-03 08:58:16 +0100106 "Bazel": {
107 "git_repository": "https://github.com/bazelbuild/bazel.git",
Yun Peng19ebf1c2024-07-24 12:22:18 +0200108 "file_config": ".bazelci/postsubmit.yml",
Philipp Wollermanncd5694c2019-01-03 14:53:04 +0100109 "pipeline_slug": "bazel-bazel",
Yun Peng8910fa32019-01-03 08:58:16 +0100110 },
Tobias Werthd848eca2019-05-14 15:08:35 +0200111 "Bazel Bench": {
112 "git_repository": "https://github.com/bazelbuild/bazel-bench.git",
Yun Pengfb86f672023-11-10 16:18:55 +0100113 "file_config": ".bazelci/postsubmit.yml",
Tobias Werthd848eca2019-05-14 15:08:35 +0200114 "pipeline_slug": "bazel-bench",
115 },
Jinfce9b302019-08-08 15:18:26 -0400116 "Bazel Examples": {
117 "git_repository": "https://github.com/bazelbuild/examples.git",
Jinfce9b302019-08-08 15:18:26 -0400118 "pipeline_slug": "bazel-bazel-examples",
119 },
Philipp Wollermann6dd7aa32019-02-05 22:42:15 +0100120 "Bazel Remote Cache": {
121 "git_repository": "https://github.com/buchgr/bazel-remote.git",
Philipp Wollermann6dd7aa32019-02-05 22:42:15 +0100122 "pipeline_slug": "bazel-remote-cache",
Yun Peng996efad2018-11-27 17:19:44 +0100123 },
Philipp Wollermann6dd7aa32019-02-05 22:42:15 +0100124 "Bazel skylib": {
Philipp Wollermann2409c6e2018-08-07 07:37:54 +0200125 "git_repository": "https://github.com/bazelbuild/bazel-skylib.git",
Philipp Wollermanncd5694c2019-01-03 14:53:04 +0100126 "pipeline_slug": "bazel-skylib",
Yun Peng667750b2020-02-20 14:06:43 +0100127 "owned_by_bazel": True,
Philipp Wollermann2409c6e2018-08-07 07:37:54 +0200128 },
Philipp Wollermann6dd7aa32019-02-05 22:42:15 +0100129 "Bazel toolchains": {
130 "git_repository": "https://github.com/bazelbuild/bazel-toolchains.git",
Philipp Wollermann6dd7aa32019-02-05 22:42:15 +0100131 "pipeline_slug": "bazel-toolchains",
132 },
Philipp Wollermann1dc76992019-05-28 16:42:51 +0200133 "Bazelisk": {
134 "git_repository": "https://github.com/bazelbuild/bazelisk.git",
Yun Pengfb86f672023-11-10 16:18:55 +0100135 "file_config": ".bazelci/config.yml",
Philipp Wollermann1dc76992019-05-28 16:42:51 +0200136 "pipeline_slug": "bazelisk",
137 },
Philipp Wollermann6dd7aa32019-02-05 22:42:15 +0100138 "Buildfarm": {
Yun Penga423d662024-11-05 10:22:42 +0100139 "git_repository": "https://github.com/buildfarm/buildfarm.git",
Philipp Wollermann89e5d882021-09-09 12:44:29 +0200140 "pipeline_slug": "buildfarm-farmer",
Philipp Wollermann6dd7aa32019-02-05 22:42:15 +0100141 },
Philipp Wollermann6dd7aa32019-02-05 22:42:15 +0100142 "Buildtools": {
Philipp Wollermann2409c6e2018-08-07 07:37:54 +0200143 "git_repository": "https://github.com/bazelbuild/buildtools.git",
Philipp Wollermanncd5694c2019-01-03 14:53:04 +0100144 "pipeline_slug": "buildtools",
Philipp Wollermann2409c6e2018-08-07 07:37:54 +0200145 },
Yun Peng39a42582018-11-09 10:59:47 +0100146 "CLion Plugin": {
147 "git_repository": "https://github.com/bazelbuild/intellij.git",
Yun Pengfb86f672023-11-10 16:18:55 +0100148 "file_config": ".bazelci/clion.yml",
Philipp Wollermanncd5694c2019-01-03 14:53:04 +0100149 "pipeline_slug": "clion-plugin",
Gowroji Sunil58777df2025-01-28 19:46:12 +0530150 "disabled_reason": "https://github.com/bazelbuild/intellij/issues/7232#issuecomment-2617127267",
Yun Peng39a42582018-11-09 10:59:47 +0100151 },
Mai Hussien0731b4d2022-06-28 11:16:07 -0700152 "CLion Plugin Google": {
153 "git_repository": "https://github.com/bazelbuild/intellij.git",
Yun Pengfb86f672023-11-10 16:18:55 +0100154 "file_config": ".bazelci/clion.yml",
Mai Hussien0731b4d2022-06-28 11:16:07 -0700155 "pipeline_slug": "clion-plugin-google",
Gowroji Sunil58777df2025-01-28 19:46:12 +0530156 "disabled_reason": "https://github.com/bazelbuild/intellij/issues/7232#issuecomment-2617127267",
Mai Hussien0731b4d2022-06-28 11:16:07 -0700157 },
Philipp Wollermannee850782019-02-05 22:56:04 +0100158 "Cloud Robotics Core": {
Stefan Sauerb4dd3f92019-02-05 22:44:28 +0100159 "git_repository": "https://github.com/googlecloudrobotics/core.git",
Philipp Wollermanndd8cf922021-10-01 15:43:33 +0200160 "http_config": "https://raw.githubusercontent.com/bazelbuild/continuous-integration/master/pipelines/cloud-robotics.yml",
Stefan Sauerb4dd3f92019-02-05 22:44:28 +0100161 "pipeline_slug": "cloud-robotics-core",
162 },
Philipp Wollermannf3750fa2019-05-21 17:11:59 +0200163 "Flogger": {
164 "git_repository": "https://github.com/google/flogger.git",
Philipp Wollermanndd8cf922021-10-01 15:43:33 +0200165 "http_config": "https://raw.githubusercontent.com/bazelbuild/continuous-integration/master/pipelines/flogger.yml",
Philipp Wollermannf3750fa2019-05-21 17:11:59 +0200166 "pipeline_slug": "flogger",
167 },
Yun Peng9586db52018-11-02 10:48:40 +0100168 "IntelliJ Plugin": {
169 "git_repository": "https://github.com/bazelbuild/intellij.git",
Yun Pengfb86f672023-11-10 16:18:55 +0100170 "file_config": ".bazelci/intellij.yml",
Philipp Wollermanncd5694c2019-01-03 14:53:04 +0100171 "pipeline_slug": "intellij-plugin",
Yun Peng9586db52018-11-02 10:48:40 +0100172 },
Mai Hussien0731b4d2022-06-28 11:16:07 -0700173 "IntelliJ Plugin Google": {
174 "git_repository": "https://github.com/bazelbuild/intellij.git",
Yun Pengfb86f672023-11-10 16:18:55 +0100175 "file_config": ".bazelci/intellij.yml",
Mai Hussien0731b4d2022-06-28 11:16:07 -0700176 "pipeline_slug": "intellij-plugin-google",
Gowroji Sunil58777df2025-01-28 19:46:12 +0530177 "disabled_reason": "https://github.com/bazelbuild/intellij/issues/7232#issuecomment-2617127267",
Mai Hussien0731b4d2022-06-28 11:16:07 -0700178 },
179 "IntelliJ UE Plugin": {
180 "git_repository": "https://github.com/bazelbuild/intellij.git",
Yun Pengfb86f672023-11-10 16:18:55 +0100181 "file_config": ".bazelci/intellij-ue.yml",
Mai Hussien0731b4d2022-06-28 11:16:07 -0700182 "pipeline_slug": "intellij-ue-plugin",
183 },
184 "IntelliJ UE Plugin Google": {
185 "git_repository": "https://github.com/bazelbuild/intellij.git",
Yun Pengfb86f672023-11-10 16:18:55 +0100186 "file_config": ".bazelci/intellij-ue.yml",
Mai Hussien0731b4d2022-06-28 11:16:07 -0700187 "pipeline_slug": "intellij-ue-plugin-google",
Gowroji Sunil58777df2025-01-28 19:46:12 +0530188 "disabled_reason": "https://github.com/bazelbuild/intellij/issues/7232#issuecomment-2617127267",
Mai Hussien0731b4d2022-06-28 11:16:07 -0700189 },
Philipp Wollermann6dd7aa32019-02-05 22:42:15 +0100190 "IntelliJ Plugin Aspect": {
191 "git_repository": "https://github.com/bazelbuild/intellij.git",
Yun Pengfb86f672023-11-10 16:18:55 +0100192 "file_config": ".bazelci/aspect.yml",
Philipp Wollermann6dd7aa32019-02-05 22:42:15 +0100193 "pipeline_slug": "intellij-plugin-aspect",
Gowroji Sunil58777df2025-01-28 19:46:12 +0530194 "disabled_reason": "https://github.com/bazelbuild/intellij/issues/7232#issuecomment-2617127267",
Philipp Wollermann6dd7aa32019-02-05 22:42:15 +0100195 },
Mai Hussien0731b4d2022-06-28 11:16:07 -0700196 "IntelliJ Plugin Aspect Google": {
197 "git_repository": "https://github.com/bazelbuild/intellij.git",
Yun Pengfb86f672023-11-10 16:18:55 +0100198 "file_config": ".bazelci/aspect.yml",
Mai Hussien0731b4d2022-06-28 11:16:07 -0700199 "pipeline_slug": "intellij-plugin-aspect-google",
Gowroji Sunil58777df2025-01-28 19:46:12 +0530200 "disabled_reason": "https://github.com/bazelbuild/intellij/issues/7232#issuecomment-2617127267",
Mai Hussien0731b4d2022-06-28 11:16:07 -0700201 },
Laurent Le Brunf6326d62020-07-28 18:24:10 +0200202 "Stardoc": {
203 "git_repository": "https://github.com/bazelbuild/stardoc.git",
Laurent Le Brunf6326d62020-07-28 18:24:10 +0200204 "pipeline_slug": "stardoc",
Yun Peng667750b2020-02-20 14:06:43 +0100205 "owned_by_bazel": True,
Philipp Wollermann1dc76992019-05-28 16:42:51 +0200206 },
Philipp Wollermann1dc76992019-05-28 16:42:51 +0200207 "TensorFlow": {
208 "git_repository": "https://github.com/tensorflow/tensorflow.git",
Philipp Wollermanndd8cf922021-10-01 15:43:33 +0200209 "http_config": "https://raw.githubusercontent.com/bazelbuild/continuous-integration/master/pipelines/tensorflow.yml",
Philipp Wollermann1dc76992019-05-28 16:42:51 +0200210 "pipeline_slug": "tensorflow",
Yun Pengab700d52023-05-08 13:16:44 +0200211 "disabled_reason": "https://github.com/tensorflow/tensorflow/issues/60508",
Philipp Wollermann1dc76992019-05-28 16:42:51 +0200212 },
Philipp Wollermann6dd7aa32019-02-05 22:42:15 +0100213 "rules_android": {
214 "git_repository": "https://github.com/bazelbuild/rules_android.git",
Philipp Wollermann6dd7aa32019-02-05 22:42:15 +0100215 "pipeline_slug": "rules-android",
Yun Penga423d662024-11-05 10:22:42 +0100216 "owned_by_bazel": True,
Gowroji Sunil085dc862025-01-27 16:03:59 +0530217 },
Keith Smiley2e3d7c82022-09-09 00:47:51 -0700218 "rules_android_ndk": {
Yun Peng687ad6b2022-09-15 11:00:02 +0200219 "git_repository": "https://github.com/bazelbuild/rules_android_ndk.git",
Keith Smiley2e3d7c82022-09-09 00:47:51 -0700220 "pipeline_slug": "rules-android-ndk",
Yun Penga423d662024-11-05 10:22:42 +0100221 "owned_by_bazel": True,
Gowroji Sunilf90effe2025-01-23 14:42:15 +0530222 "disabled_reason": "https://github.com/bazelbuild/rules_android_ndk/issues/83, https://github.com/bazelbuild/rules_android_ndk/issues/86, https://github.com/bazelbuild/rules_android_ndk/issues/88",
Yun Peng809f27b2018-11-13 10:15:39 +0100223 },
Philipp Wollermann6dd7aa32019-02-05 22:42:15 +0100224 "rules_cc": {
225 "git_repository": "https://github.com/bazelbuild/rules_cc.git",
Philipp Wollermann6dd7aa32019-02-05 22:42:15 +0100226 "pipeline_slug": "rules-cc",
Yun Peng667750b2020-02-20 14:06:43 +0100227 "owned_by_bazel": True,
Gowroji Sunil3358b922024-01-25 18:17:17 +0530228 "disabled_reason": "https://github.com/bazelbuild/rules_cc/issues/190",
Philipp Wollermann6dd7aa32019-02-05 22:42:15 +0100229 },
Yun Pengea6e4392024-02-14 11:52:14 +0100230 "rules_platform": {
231 "git_repository": "https://github.com/bazelbuild/rules_platform.git",
232 "pipeline_slug": "rules-platform",
Yun Peng667750b2020-02-20 14:06:43 +0100233 "owned_by_bazel": True,
Yannic6110b3c2019-08-12 15:09:37 +0000234 },
Yun Peng36134702024-11-07 15:25:00 +0100235 "rules_python": {
236 "git_repository": "https://github.com/bazelbuild/rules_python.git",
237 "pipeline_slug": "rules-python-python",
238 },
Richard Levasseura9690c62023-03-06 08:39:36 -0800239 "rules_testing": {
240 "git_repository": "https://github.com/bazelbuild/rules_testing.git",
Richard Levasseura9690c62023-03-06 08:39:36 -0800241 "pipeline_slug": "rules-testing",
242 "owned_by_bazel": True,
243 },
Philipp Wollermann2409c6e2018-08-07 07:37:54 +0200244}
245
Philipp Wollermanne67eec42019-05-24 15:18:20 +0200246DOWNSTREAM_PROJECTS_TESTING = {
Philipp Wollermannbed211d2019-06-07 11:38:59 +0200247 "Bazel": DOWNSTREAM_PROJECTS_PRODUCTION["Bazel"],
248 "Bazelisk": DOWNSTREAM_PROJECTS_PRODUCTION["Bazelisk"],
Yun Penga423d662024-11-05 10:22:42 +0100249 "rules_android": DOWNSTREAM_PROJECTS_PRODUCTION["rules_android"],
250 "rules_cc": DOWNSTREAM_PROJECTS_PRODUCTION["rules_cc"],
251 "rules_testing": DOWNSTREAM_PROJECTS_PRODUCTION["rules_testing"],
Philipp Wollermanne67eec42019-05-24 15:18:20 +0200252}
253
254DOWNSTREAM_PROJECTS = {
255 "bazel-testing": DOWNSTREAM_PROJECTS_TESTING,
256 "bazel-trusted": {},
257 "bazel": DOWNSTREAM_PROJECTS_PRODUCTION,
258}[BUILDKITE_ORG]
Philipp Wollermann6dd7aa32019-02-05 22:42:15 +0100259
Philipp Wollermann81a88412019-07-12 10:34:33 +0200260DOCKER_REGISTRY_PREFIX = {
261 "bazel-testing": "bazel-public/testing",
262 "bazel-trusted": "bazel-public",
263 "bazel": "bazel-public",
264}[BUILDKITE_ORG]
265
Philipp Wollermann2409c6e2018-08-07 07:37:54 +0200266# A map containing all supported platform names as keys, with the values being
267# the platform name in a human readable format, and a the buildkite-agent's
268# working directory.
269PLATFORMS = {
Philipp Wollermanneffcd6e2019-06-21 18:30:34 +0200270 "centos7": {
Chi Wangcae34ca2024-03-06 12:09:55 +0100271 "name": "CentOS 7",
Yun Peng89ea8912024-03-07 16:33:44 +0100272 "emoji-name": ":centos: CentOS 7",
Yun Pengcebc3292024-03-12 11:59:00 +0100273 "publish_binary": ["ubuntu1404", "centos7", "linux"],
Chi Wangcae34ca2024-03-06 12:09:55 +0100274 "docker-image": f"gcr.io/{DOCKER_REGISTRY_PREFIX}/centos7",
Philipp Wollermanneffcd6e2019-06-21 18:30:34 +0200275 "python": "python3.6",
276 },
Philipp Wollermann9af2b432021-10-20 22:37:17 +0200277 "centos7_java11": {
278 "name": "CentOS 7 (OpenJDK 11, gcc 4.8.5)",
Yun Peng89ea8912024-03-07 16:33:44 +0100279 "emoji-name": ":centos: CentOS 7 (OpenJDK 11, gcc 4.8.5)",
Philipp Wollermann9af2b432021-10-20 22:37:17 +0200280 "publish_binary": [],
281 "docker-image": f"gcr.io/{DOCKER_REGISTRY_PREFIX}/centos7-java11",
282 "python": "python3.6",
283 },
284 "centos7_java11_devtoolset10": {
285 "name": "CentOS 7 (OpenJDK 11, gcc 10.2.1)",
Yun Peng89ea8912024-03-07 16:33:44 +0100286 "emoji-name": ":centos: CentOS 7 (OpenJDK 11, gcc 10.2.1)",
Yun Pengc7812662024-03-11 13:28:02 +0100287 "publish_binary": [],
Philipp Wollermann9af2b432021-10-20 22:37:17 +0200288 "docker-image": f"gcr.io/{DOCKER_REGISTRY_PREFIX}/centos7-java11-devtoolset10",
289 "python": "python3.6",
290 },
Philipp Wollermann67fc3712019-06-12 15:39:21 +0200291 "debian10": {
Philipp Wollermann9af2b432021-10-20 22:37:17 +0200292 "name": "Debian 10 Buster (OpenJDK 11, gcc 8.3.0)",
Yun Peng89ea8912024-03-07 16:33:44 +0100293 "emoji-name": ":debian: Debian 10 Buster (OpenJDK 11, gcc 8.3.0)",
Philipp Wollermann67fc3712019-06-12 15:39:21 +0200294 "publish_binary": [],
Philipp Wollermann5d6765d2020-02-17 17:12:02 +0100295 "docker-image": f"gcr.io/{DOCKER_REGISTRY_PREFIX}/debian10-java11",
Philipp Wollermann67fc3712019-06-12 15:39:21 +0200296 "python": "python3.7",
297 },
Philipp Wollermann9af2b432021-10-20 22:37:17 +0200298 "debian11": {
299 "name": "Debian 11 Bullseye (OpenJDK 17, gcc 10.2.1)",
Yun Peng89ea8912024-03-07 16:33:44 +0100300 "emoji-name": ":debian: Debian 11 Bullseye (OpenJDK 17, gcc 10.2.1)",
Philipp Wollermann9af2b432021-10-20 22:37:17 +0200301 "publish_binary": [],
302 "docker-image": f"gcr.io/{DOCKER_REGISTRY_PREFIX}/debian11-java17",
303 "python": "python3.9",
304 },
Philipp Wollermann2409c6e2018-08-07 07:37:54 +0200305 "ubuntu1604": {
Philipp Wollermann9af2b432021-10-20 22:37:17 +0200306 "name": "Ubuntu 16.04 LTS (OpenJDK 8, gcc 5.4.0)",
Yun Peng89ea8912024-03-07 16:33:44 +0100307 "emoji-name": ":ubuntu: Ubuntu 16.04 LTS (OpenJDK 8, gcc 5.4.0)",
Philipp Wollermann64047082021-10-21 21:26:25 +0200308 "publish_binary": [],
Philipp Wollermann5d6765d2020-02-17 17:12:02 +0100309 "docker-image": f"gcr.io/{DOCKER_REGISTRY_PREFIX}/ubuntu1604-java8",
Philipp Wollermann57b32682019-05-18 22:09:27 +0200310 "python": "python3.6",
Philipp Wollermann438ec242018-09-05 14:39:24 +0200311 },
312 "ubuntu1804": {
Florian Weikertd02ea362022-09-15 13:51:17 +0200313 "name": "Ubuntu 18.04 LTS (OpenJDK 11, gcc 7.5.0)",
Yun Peng89ea8912024-03-07 16:33:44 +0100314 "emoji-name": ":ubuntu: Ubuntu 18.04 LTS (OpenJDK 11, gcc 7.5.0)",
Yun Peng539b17f2023-10-18 10:36:58 +0200315 "publish_binary": [],
Philipp Wollermann5d6765d2020-02-17 17:12:02 +0100316 "docker-image": f"gcr.io/{DOCKER_REGISTRY_PREFIX}/ubuntu1804-java11",
Philipp Wollermann57b32682019-05-18 22:09:27 +0200317 "python": "python3.6",
Philipp Wollermann438ec242018-09-05 14:39:24 +0200318 },
Chi Wangcae34ca2024-03-06 12:09:55 +0100319 "ubuntu2004_java11": {
Florian Weikertd02ea362022-09-15 13:51:17 +0200320 "name": "Ubuntu 20.04 LTS (OpenJDK 11, gcc 9.4.0)",
Yun Peng89ea8912024-03-07 16:33:44 +0100321 "emoji-name": ":ubuntu: Ubuntu 20.04 LTS (OpenJDK 11, gcc 9.4.0)",
Philipp Wollermann55f72ac2020-09-21 22:22:05 +0200322 "publish_binary": [],
Mostyn Bramley-Mooreab4599e2020-06-23 20:31:01 +0200323 "docker-image": f"gcr.io/{DOCKER_REGISTRY_PREFIX}/ubuntu2004-java11",
324 "python": "python3.8",
325 },
Chi Wangcae34ca2024-03-06 12:09:55 +0100326 "ubuntu2004": {
327 "name": "Ubuntu 20.04 LTS",
Yun Peng89ea8912024-03-07 16:33:44 +0100328 "emoji-name": ":ubuntu: Ubuntu 20.04 LTS",
Chi Wangcae34ca2024-03-06 12:09:55 +0100329 "publish_binary": [],
330 "docker-image": f"gcr.io/{DOCKER_REGISTRY_PREFIX}/ubuntu2004",
331 "python": "python3.8",
332 },
Yun Peng07dafc52022-03-16 13:23:35 +0100333 "ubuntu2004_arm64": {
Yun Peng4f196e72024-03-11 12:01:06 +0100334 "name": "Ubuntu 20.04 LTS ARM64",
335 "emoji-name": ":ubuntu: Ubuntu 20.04 LTS ARM64",
Yun Peng9588f082024-08-21 12:31:36 +0200336 "publish_binary": ["linux_arm64"],
Yun Peng4f196e72024-03-11 12:01:06 +0100337 "docker-image": f"gcr.io/{DOCKER_REGISTRY_PREFIX}/ubuntu2004",
Yun Peng07dafc52022-03-16 13:23:35 +0100338 "python": "python3.8",
339 "queue": "arm64",
340 # TODO: Re-enable always-pull if we also publish docker containers for Linux ARM64
341 "always-pull": False,
342 },
Chi Wang6357efe2020-08-25 16:23:38 +0800343 "kythe_ubuntu2004": {
Chi Wang4ca83852024-03-14 15:28:45 +0100344 "name": "Kythe (Ubuntu 20.04 LTS)",
345 "emoji-name": "Kythe (:ubuntu: Ubuntu 20.04 LTS)",
Chi Wang6357efe2020-08-25 16:23:38 +0800346 "publish_binary": [],
Chi Wang4ca83852024-03-14 15:28:45 +0100347 "docker-image": f"gcr.io/{DOCKER_REGISTRY_PREFIX}/ubuntu2004-kythe",
Chi Wang6357efe2020-08-25 16:23:38 +0800348 "python": "python3.8",
349 },
Chi Wange1934582024-03-15 14:10:32 +0100350 "kythe_ubuntu2204": {
351 "name": "Kythe (Ubuntu 22.04 LTS)",
352 "emoji-name": "Kythe (:ubuntu: Ubuntu 22.04 LTS)",
353 "publish_binary": [],
354 "docker-image": f"gcr.io/{DOCKER_REGISTRY_PREFIX}/ubuntu2204-kythe",
355 "python": "python3",
356 },
Chi Wangb8da1b82025-01-10 14:15:12 +0100357 "kythe_ubuntu2404": {
358 "name": "Kythe (Ubuntu 24.04 LTS)",
359 "emoji-name": "Kythe (:ubuntu: Ubuntu 24.04 LTS)",
360 "publish_binary": [],
361 "docker-image": f"gcr.io/{DOCKER_REGISTRY_PREFIX}/ubuntu2404-kythe",
362 "python": "python3",
363 },
Chi Wangcae34ca2024-03-06 12:09:55 +0100364 "ubuntu2204_java17": {
Florian Weikertdcc75202022-09-06 16:34:04 +0200365 "name": "Ubuntu 22.04 (OpenJDK 17, gcc 11.2.0)",
Yun Peng89ea8912024-03-07 16:33:44 +0100366 "emoji-name": ":ubuntu: Ubuntu 22.04 (OpenJDK 17, gcc 11.2.0)",
Philipp Wollermann9af2b432021-10-20 22:37:17 +0200367 "publish_binary": [],
Florian Weikertdcc75202022-09-06 16:34:04 +0200368 "docker-image": f"gcr.io/{DOCKER_REGISTRY_PREFIX}/ubuntu2204-java17",
Philipp Wollermannb6a399a2021-10-22 07:57:26 +0200369 "python": "python3",
Philipp Wollermann9af2b432021-10-20 22:37:17 +0200370 },
Chi Wangcae34ca2024-03-06 12:09:55 +0100371 "ubuntu2204": {
372 "name": "Ubuntu 22.04",
Yun Peng89ea8912024-03-07 16:33:44 +0100373 "emoji-name": ":ubuntu: Ubuntu 22.04 LTS",
Chi Wangcae34ca2024-03-06 12:09:55 +0100374 "publish_binary": [],
375 "docker-image": f"gcr.io/{DOCKER_REGISTRY_PREFIX}/ubuntu2204",
376 "python": "python3",
377 },
Yun Peng7fe019f2024-10-16 14:00:46 +0200378 "ubuntu2404": {
379 "name": "Ubuntu 24.04",
380 "emoji-name": ":ubuntu: Ubuntu 24.04 LTS",
381 "publish_binary": [],
382 "docker-image": f"gcr.io/{DOCKER_REGISTRY_PREFIX}/ubuntu2404",
383 "python": "python3",
384 },
David Ostrovskyba4478f2023-05-08 16:24:04 +0200385 "fedora39": {
386 "name": "Fedora 39 (OpenJDK 17, gcc 13.1.1)",
Yun Peng89ea8912024-03-07 16:33:44 +0100387 "emoji-name": ":fedora: Fedora 39 (OpenJDK 17, gcc 13.1.1)",
David Ostrovskyba4478f2023-05-08 16:24:04 +0200388 "publish_binary": [],
389 "docker-image": f"gcr.io/{DOCKER_REGISTRY_PREFIX}/fedora39-java17",
390 "python": "python3",
391 },
David Ostrovsky9d79fb42024-06-03 13:10:33 +0200392 "fedora40": {
393 "name": "Fedora 40 (OpenJDK 21, gcc 14.1.1)",
394 "emoji-name": ":fedora: Fedora 40 (OpenJDK 21, gcc 14.1.1)",
395 "publish_binary": [],
396 "docker-image": f"gcr.io/{DOCKER_REGISTRY_PREFIX}/fedora40-java21",
397 "python": "python3",
398 },
Philipp Wollermann438ec242018-09-05 14:39:24 +0200399 "macos": {
Yun Peng89ea8912024-03-07 16:33:44 +0100400 "name": "macOS",
401 "emoji-name": ":darwin: macOS",
Philipp Wollermann783d1672019-06-06 13:35:30 +0200402 "publish_binary": ["macos"],
Philipp Wollermann7a185322019-05-18 22:15:48 +0200403 "queue": "macos",
Philipp Wollermann89d36492021-02-16 11:59:09 +0100404 "python": "python3",
Philipp Wollermann438ec242018-09-05 14:39:24 +0200405 },
Yun Peng46d43912021-04-21 09:49:53 +0200406 "macos_arm64": {
Yun Peng89ea8912024-03-07 16:33:44 +0100407 "name": "macOS arm64",
408 "emoji-name": ":darwin: macOS arm64",
Yun Peng83f32772021-04-21 11:22:35 +0200409 "publish_binary": ["macos_arm64"],
Yun Pengab922132022-11-18 20:28:02 +0100410 "queue": "macos_arm64",
Yun Peng46d43912021-04-21 09:49:53 +0200411 "python": "python3",
412 },
Philipp Wollermann438ec242018-09-05 14:39:24 +0200413 "windows": {
Yun Peng89ea8912024-03-07 16:33:44 +0100414 "name": "Windows",
415 "emoji-name": ":windows: Windows",
Philipp Wollermann783d1672019-06-06 13:35:30 +0200416 "publish_binary": ["windows"],
Philipp Wollermann7a185322019-05-18 22:15:48 +0200417 "queue": "windows",
Philipp Wollermann57b32682019-05-18 22:09:27 +0200418 "python": "python.exe",
Philipp Wollermann438ec242018-09-05 14:39:24 +0200419 },
Yun Peng6a7a6702022-02-02 15:02:47 +0100420 "windows_arm64": {
Yun Peng89ea8912024-03-07 16:33:44 +0100421 "name": "Windows ARM64",
422 "emoji-name": ":windows: Windows arm64",
Yun Penge024eb72023-07-25 10:18:39 +0200423 "publish_binary": ["windows_arm64"],
Yun Peng6a7a6702022-02-02 15:02:47 +0100424 # TODO(pcloudy): Switch to windows_arm64 queue when Windows ARM64 machines are available,
425 # current we just use x86_64 machines to do cross compile.
426 "queue": "windows",
427 "python": "python.exe",
Florian Weikert7f21ca42022-02-02 17:35:23 +0100428 },
Philipp Wollermann2409c6e2018-08-07 07:37:54 +0200429}
430
Chi Wang5a88d962023-10-19 14:44:04 +0200431# Generate rbe_ubuntu* platforms based on ubuntu* platforms.
432for platform, platform_dict in PLATFORMS.copy().items():
433 if platform.startswith("ubuntu"):
434 rbe_platform_dict = copy.deepcopy(platform_dict)
435 rbe_platform_dict["name"] = "RBE {}".format(platform_dict["name"])
436 rbe_platform_dict["emoji-name"] = "RBE {}".format(platform_dict["emoji-name"])
Yun Penge1b8edb2024-08-21 12:56:36 +0200437 rbe_platform_dict["publish_binary"] = []
Chi Wang5a88d962023-10-19 14:44:04 +0200438 PLATFORMS["rbe_{}".format(platform)] = rbe_platform_dict
439
Philipp Wollermannfce92bf2019-05-22 15:14:32 +0200440BUILDIFIER_DOCKER_IMAGE = "gcr.io/bazel-public/buildifier"
Florian Weikertf20ae6f2019-01-16 14:32:09 +0100441
Philipp Wollermann1403d2c2019-01-10 13:15:51 +0100442# The platform used for various steps (e.g. stuff that formerly ran on the "pipeline" workers).
443DEFAULT_PLATFORM = "ubuntu1804"
444
Philipp Wollermannf4aabb72019-06-25 15:59:00 +0200445# In order to test that "the one Linux binary" that we build for our official releases actually
446# works on all Linux distributions that we test on, we use the Linux binary built on our official
447# release platform for all Linux downstream tests.
Yun Pengea700652024-03-12 11:37:21 +0100448LINUX_BINARY_PLATFORM = "centos7"
Philipp Wollermannf4aabb72019-06-25 15:59:00 +0200449
Philipp Wollermann380f1e62019-04-12 16:45:27 +0200450XCODE_VERSION_REGEX = re.compile(r"^\d+\.\d+(\.\d+)?$")
Florian Weikertdb832a02020-11-19 19:14:48 +0100451XCODE_VERSION_OVERRIDES = {"10.2.1": "10.3", "11.2": "11.2.1", "11.3": "11.3.1"}
Philipp Wollermann380f1e62019-04-12 16:45:27 +0200452
Florian Weikertc8642af2019-02-03 23:58:51 +0100453BUILD_LABEL_PATTERN = re.compile(r"^Build label: (\S+)$", re.MULTILINE)
454
Florian Weikert5f5d3cb2019-04-15 15:36:27 +0200455SKIP_TASKS_ENV_VAR = "CI_SKIP_TASKS"
456
Florian Weikert42fbf792024-10-01 13:24:55 +0200457RUNNER_CMD = "bazelci.py runner"
458
Florian Weikertecf091c2023-04-28 10:22:23 +0200459# TODO: change to USE_BAZEL_DIFF once the feature has been tested in QA
Florian Weikert7012a522023-05-04 16:49:40 +0200460USE_BAZEL_DIFF_ENV_VAR = "USE_BAZEL_DIFF"
461
Florian Weikert77d20062023-05-08 16:32:48 +0200462BAZEL_DIFF_ANNOTATION_CTX = "'diff'"
463
Florian Weikert7012a522023-05-04 16:49:40 +0200464# TODO(fweikert): Install bazel-diff on the Docker images and on the Mac machines
465BAZEL_DIFF_URL = (
Florian Weikert6829be22024-10-28 18:56:04 +0100466 "https://github.com/Tinder/bazel-diff/releases/download/8.1.1/bazel-diff_deploy.jar"
Florian Weikert7012a522023-05-04 16:49:40 +0200467)
Florian Weikertecf091c2023-04-28 10:22:23 +0200468
469AUTO_DIFFBASE_VALUES = frozenset(["1", "true", "auto"])
470
Florian Weikerte417f9f2023-05-05 17:33:46 +0200471# Always run all test targets if any of the paths here are modified by the current commit.
472# Values can be directory paths (with a trailing slash) or file paths.
Florian Weikert8ec9f982024-09-19 17:11:15 +0200473DISABLE_BAZEL_DIFF_IF_MODIFIED = (".bazelci/", ".bazelversion", "MODULE.bazel", "repositories.bzl")
Florian Weikerte417f9f2023-05-05 17:33:46 +0200474
Florian Weikertecf091c2023-04-28 10:22:23 +0200475COMMIT_RE = re.compile(r"[0-9a-z]{40}")
476
Philipp Wollermannce986af2019-07-18 14:46:05 +0200477CONFIG_FILE_EXTENSIONS = {".yml", ".yaml"}
Florian Weikert778251c2019-04-25 15:14:44 +0200478
Chi Wang6357efe2020-08-25 16:23:38 +0800479KYTHE_DIR = "/usr/local/kythe"
480
481INDEX_UPLOAD_POLICY_ALWAYS = "Always"
482
483INDEX_UPLOAD_POLICY_IF_BUILD_SUCCESS = "IfBuildSuccess"
484
485INDEX_UPLOAD_POLICY_NEVER = "Never"
Florian Weikert13215a82019-05-10 12:42:21 +0200486
Florian Weikertee5ea982024-06-21 19:59:31 +0200487ESCAPED_BACKSLASH = "%5C"
488
Yun Peng686f4592022-01-17 15:38:48 +0100489# The maximum number of tasks allowed in one pipeline yaml config file.
490# This is to prevent accidentally creating too many tasks with the martix testing feature.
UebelAndre3c0e9822024-11-26 09:00:17 -0800491MAX_TASK_NUMBER = 128
Yun Peng686f4592022-01-17 15:38:48 +0100492
Florian Weikert78e67de2024-02-06 12:58:41 +0100493_TEST_BEP_FILE = "test_bep.json"
494_SHARD_RE = re.compile(r"(.+) \(shard (\d+)\)")
Florian Weikert0f3deb92024-07-09 12:35:01 +0200495_SLOWEST_N_TARGETS = 20
Florian Weikert78e67de2024-02-06 12:58:41 +0100496
Florian Weikertccd44572024-11-11 13:11:39 +0100497# Pipelines with elevated priority. Values are project slugs,
498# i.e. "org_slug/pipeline_slug".
499_PRIORITY_PIPELINES = frozenset(["bazel/google-bazel-presubmit"])
500
Florian Weikertdb832a02020-11-19 19:14:48 +0100501
Philipp Wollermanndcaddd92018-02-21 14:13:43 +0100502class BuildkiteException(Exception):
503 """
504 Raised whenever something goes wrong and we should exit with an error.
505 """
Philipp Wollermanncd5694c2019-01-03 14:53:04 +0100506
Philipp Wollermanndcaddd92018-02-21 14:13:43 +0100507 pass
508
509
Yun Pengd433d4f2022-12-09 10:53:15 +0100510class BuildkiteInfraException(Exception):
511 """
512 Raised whenever something goes wrong with the CI infra and we should immediately exit with an error.
513 """
514
515 pass
516
517
Philipp Wollermanndcaddd92018-02-21 14:13:43 +0100518class BinaryUploadRaceException(Exception):
519 """
520 Raised when try_publish_binaries wasn't able to publish a set of binaries,
521 because the generation of the current file didn't match the expected value.
522 """
Philipp Wollermanncd5694c2019-01-03 14:53:04 +0100523
Philipp Wollermanndcaddd92018-02-21 14:13:43 +0100524 pass
525
526
Florian Weikerta0e74592019-03-07 11:56:12 +0100527class BuildkiteClient(object):
Yun Pengc00c8152024-09-25 15:34:14 +0200528 _ENCRYPTED_BUILDKITE_UNTRUSTED_API_TOKEN = """
Florian Weikerta0e74592019-03-07 11:56:12 +0100529CiQA4DEB9ldzC+E39KomywtqXfaQ86hhulgeDsicds2BuvbCYzsSUAAqwcvXZPh9IMWlwWh94J2F
530exosKKaWB0tSRJiPKnv2NPDfEqGul0ZwVjtWeASpugwxxKeLhFhPMcgHMPfndH6j2GEIY6nkKRbP
531uwoRMCwe
532""".strip()
533
Yun Pengc00c8152024-09-25 15:34:14 +0200534 _ENCRYPTED_BUILDKITE_TESTING_API_TOKEN = """
Philipp Wollermanne67eec42019-05-24 15:18:20 +0200535CiQAMTBkWjL1C+F5oon3+cC1vmum5+c1y5+96WQY44p0Lxd0PeASUQAy7iU0c6E3W5EOSFYfD5fA
536MWy/SHaMno1NQSUa4xDOl5yc2kizrtxPPVkX4x9pLNuGUY/xwAn2n1DdiUdWZNWlY1bX2C4ex65e
537P9w8kNhEbw==
538""".strip()
539
Yun Pengc00c8152024-09-25 15:34:14 +0200540 _ENCRYPTED_BUILDKITE_TRUSTED_API_TOKEN = """
541CiQAeiOS8AkJ92+STSUmqW/jlR9DKDZdX5PZIWn30PtyKXWE/74SVwC7bbymSHneleAcgXtVJsMu
5422DEEVd/uEGIdiEJigmPAPTs4vtmX/7ZxTsMhJ+rxRYBGufw9LgT+G6Bjg0ETifavKWHGzw+NTgUa
543gwD6RBL0qz1PFfg7Zw==
544""".strip()
545
Florian Weikertde96a6f2019-03-07 14:57:50 +0100546 _BUILD_STATUS_URL_TEMPLATE = (
547 "https://api.buildkite.com/v2/organizations/{}/pipelines/{}/builds/{}"
548 )
Florian Weikerta0e74592019-03-07 11:56:12 +0100549
Florian Weikertdb832a02020-11-19 19:14:48 +0100550 _NEW_BUILD_URL_TEMPLATE = "https://api.buildkite.com/v2/organizations/{}/pipelines/{}/builds"
Yun Peng9337bb32020-02-28 13:31:29 +0100551
552 _RETRY_JOB_URL_TEMPLATE = (
553 "https://api.buildkite.com/v2/organizations/{}/pipelines/{}/builds/{}/jobs/{}/retry"
554 )
555
Florian Weikertb3439b32022-11-09 11:05:16 +0100556 _PIPELINE_INFO_URL_TEMPLATE = "https://api.buildkite.com/v2/organizations/{}/pipelines/{}"
Mai Hussienfe58c062022-07-07 01:29:46 -0700557
Florian Weikerta0e74592019-03-07 11:56:12 +0100558 def __init__(self, org, pipeline):
559 self._org = org
560 self._pipeline = pipeline
561 self._token = self._get_buildkite_token()
562
563 def _get_buildkite_token(self):
Florian Weikert849afb22019-12-14 12:22:29 -0800564 return decrypt_token(
Florian Weikertb345b652024-03-06 12:34:25 +0100565 encrypted_token=(
Florian Weikerte425b912024-10-02 00:22:41 +0200566 self._ENCRYPTED_BUILDKITE_TRUSTED_API_TOKEN
567 if THIS_IS_TRUSTED
568 else self._ENCRYPTED_BUILDKITE_TESTING_API_TOKEN
569 if THIS_IS_TESTING
570 else self._ENCRYPTED_BUILDKITE_UNTRUSTED_API_TOKEN
Florian Weikertb345b652024-03-06 12:34:25 +0100571 ),
572 kms_key=(
Florian Weikerte425b912024-10-02 00:22:41 +0200573 "buildkite-trusted-api-token"
574 if THIS_IS_TRUSTED
575 else "buildkite-testing-api-token"
576 if THIS_IS_TESTING
577 else "buildkite-untrusted-api-token"
Yun Pengc00c8152024-09-25 15:34:14 +0200578 ),
Florian Weikerte425b912024-10-02 00:22:41 +0200579 project=("bazel-public" if THIS_IS_TRUSTED else "bazel-untrusted"),
Florian Weikerta0e74592019-03-07 11:56:12 +0100580 )
581
Yun Peng2d33fc62024-10-23 11:43:04 +0200582 def _open_url(self, url, params=[], retries=5):
583 params_str = "".join("&{}={}".format(k, v) for k, v in params)
584 full_url = "{}?access_token={}{}".format(url, self._token, params_str)
585
586 for attempt in range(retries):
587 try:
588 response = urllib.request.urlopen(full_url)
589 return response.read().decode("utf-8", "ignore")
590 except urllib.error.HTTPError as ex:
591 # Handle specific error codes
592 if ex.code == 429: # Too Many Requests
593 retry_after = ex.headers.get("RateLimit-Reset")
594 if retry_after:
595 wait_time = int(retry_after)
596 else:
Florian Weikertccd44572024-11-11 13:11:39 +0100597 wait_time = 2**attempt # Exponential backoff if no RateLimit-Reset header
Yun Peng2d33fc62024-10-23 11:43:04 +0200598
599 time.sleep(wait_time)
600 else:
Florian Weikertccd44572024-11-11 13:11:39 +0100601 raise BuildkiteException(
602 "Failed to open {}: {} - {}".format(url, ex.code, ex.reason)
603 )
Yun Peng2d33fc62024-10-23 11:43:04 +0200604
605 raise BuildkiteException(f"Failed to open {url} after {retries} retries.")
Florian Weikerta0e74592019-03-07 11:56:12 +0100606
Mai Hussienfe58c062022-07-07 01:29:46 -0700607 def get_pipeline_info(self):
608 """Get details for a pipeline given its organization slug
609 and pipeline slug.
610 See https://buildkite.com/docs/apis/rest-api/pipelines#get-a-pipeline
611
612 Returns
613 -------
614 dict
615 the metadata for the pipeline
616 """
617 url = self._PIPELINE_INFO_URL_TEMPLATE.format(self._org, self._pipeline)
618 output = self._open_url(url)
619 return json.loads(output)
620
Florian Weikerta0e74592019-03-07 11:56:12 +0100621 def get_build_info(self, build_number):
Yun Peng9337bb32020-02-28 13:31:29 +0100622 """Get build info for a pipeline with a given build number
623 See https://buildkite.com/docs/apis/rest-api/builds#get-a-build
624
625 Parameters
626 ----------
627 build_number : the build number
628
629 Returns
630 -------
631 dict
632 the metadata for the build
633 """
Florian Weikerta0e74592019-03-07 11:56:12 +0100634 url = self._BUILD_STATUS_URL_TEMPLATE.format(self._org, self._pipeline, build_number)
635 output = self._open_url(url)
636 return json.loads(output)
637
Yun Peng9337bb32020-02-28 13:31:29 +0100638 def get_build_info_list(self, params):
639 """Get a list of build infos for this pipeline
640 See https://buildkite.com/docs/apis/rest-api/builds#list-builds-for-a-pipeline
641
642 Parameters
643 ----------
644 params : the parameters to filter the result
645
646 Returns
647 -------
648 list of dict
649 the metadata for a list of builds
650 """
651 url = self._BUILD_STATUS_URL_TEMPLATE.format(self._org, self._pipeline, "")
652 output = self._open_url(url, params)
653 return json.loads(output)
654
Florian Weikerta0e74592019-03-07 11:56:12 +0100655 def get_build_log(self, job):
656 return self._open_url(job["raw_log_url"])
657
Yun Peng9337bb32020-02-28 13:31:29 +0100658 @staticmethod
659 def _check_response(response, expected_status_code):
660 if response.status_code != expected_status_code:
661 eprint("Exit code:", response.status_code)
662 eprint("Response:\n", response.text)
663 response.raise_for_status()
664
Florian Weikertdb832a02020-11-19 19:14:48 +0100665 def trigger_new_build(self, commit, message=None, env={}):
Yun Peng9337bb32020-02-28 13:31:29 +0100666 """Trigger a new build at a given commit and return the build metadata.
667 See https://buildkite.com/docs/apis/rest-api/builds#create-a-build
668
669 Parameters
670 ----------
671 commit : the commit we want to build at
672 message : the message we should as the build titile
673 env : (optional) the environment variables to set
674
675 Returns
676 -------
677 dict
678 the metadata for the build
679 """
Salma Samyf54c75b2022-11-04 16:28:18 +0200680 pipeline_info = self.get_pipeline_info()
681 if not pipeline_info:
682 raise BuildkiteException(f"Cannot find pipeline info for pipeline {self._pipeline}.")
683
Yun Peng9337bb32020-02-28 13:31:29 +0100684 url = self._NEW_BUILD_URL_TEMPLATE.format(self._org, self._pipeline)
685 data = {
686 "commit": commit,
Salma Samyf54c75b2022-11-04 16:28:18 +0200687 "branch": pipeline_info.get("default_branch") or "master",
Yun Peng9337bb32020-02-28 13:31:29 +0100688 "message": message if message else f"Trigger build at {commit}",
689 "env": env,
Yun Peng867b5a52022-01-11 13:59:41 +0100690 "ignore_pipeline_branch_filters": "true",
Yun Peng9337bb32020-02-28 13:31:29 +0100691 }
Florian Weikertdb832a02020-11-19 19:14:48 +0100692 response = requests.post(url + "?access_token=" + self._token, json=data)
Yun Peng9337bb32020-02-28 13:31:29 +0100693 BuildkiteClient._check_response(response, requests.codes.created)
694 return json.loads(response.text)
695
Yun Peng9337bb32020-02-28 13:31:29 +0100696 def trigger_job_retry(self, build_number, job_id):
697 """Trigger a job retry and return the job metadata.
698 See https://buildkite.com/docs/apis/rest-api/jobs#retry-a-job
699
700 Parameters
701 ----------
702 build_number : the number of the build we want to retry
703 job_id : the id of the job we want to retry
704
705 Returns
706 -------
707 dict
708 the metadata for the job
709 """
710 url = self._RETRY_JOB_URL_TEMPLATE.format(self._org, self._pipeline, build_number, job_id)
711 response = requests.put(url + "?access_token=" + self._token)
712 BuildkiteClient._check_response(response, requests.codes.ok)
713 return json.loads(response.text)
714
Yun Peng9337bb32020-02-28 13:31:29 +0100715 def wait_job_to_finish(self, build_number, job_id, interval_time=30, logger=None):
716 """Wait a job to finish and return the job metadata
717
718 Parameters
719 ----------
720 build_number : the number of the build we want to wait
721 job_id : the id of the job we want to wait
722 interval_time : (optional) the interval time to check the build status, default to 30s
723 logger : (optional) a logger to report progress
724
725 Returns
726 -------
727 dict
728 the latest metadata for the job
729 """
730 t = 0
731 build_info = self.get_build_info(build_number)
732 while True:
733 for job in build_info["jobs"]:
734 if job["id"] == job_id:
735 state = job["state"]
736 if state != "scheduled" and state != "running" and state != "assigned":
737 return job
738 break
739 else:
Florian Weikertdb832a02020-11-19 19:14:48 +0100740 raise BuildkiteException(
741 f"job id {job_id} doesn't exist in build " + build_info["web_url"]
742 )
Yun Peng9337bb32020-02-28 13:31:29 +0100743 url = build_info["web_url"]
744 if logger:
745 logger.log(f"Waiting for {url}, waited {t} seconds...")
746 time.sleep(interval_time)
747 t += interval_time
748 build_info = self.get_build_info(build_number)
749
Yun Peng9337bb32020-02-28 13:31:29 +0100750 def wait_build_to_finish(self, build_number, interval_time=30, logger=None):
751 """Wait a build to finish and return the build metadata
752
753 Parameters
754 ----------
755 build_number : the number of the build we want to wait
756 interval_time : (optional) the interval time to check the build status, default to 30s
757 logger : (optional) a logger to report progress
758
759 Returns
760 -------
761 dict
762 the latest metadata for the build
763 """
764 t = 0
765 build_info = self.get_build_info(build_number)
766 while build_info["state"] == "scheduled" or build_info["state"] == "running":
767 url = build_info["web_url"]
768 if logger:
769 logger.log(f"Waiting for {url}, waited {t} seconds...")
770 time.sleep(interval_time)
771 t += interval_time
772 build_info = self.get_build_info(build_number)
773 return build_info
774
775
Chi Wang5e3998f2023-05-10 11:51:02 +0000776def decrypt_token(encrypted_token, kms_key, project="bazel-untrusted"):
Florian Weikert1e067912024-07-23 13:51:11 +0200777 try:
Florian Weikert24ab0862024-07-23 19:47:59 +0200778 result = subprocess.run(
779 [
780 gcloud_command(),
781 "kms",
782 "decrypt",
783 "--project",
784 project,
785 "--location",
786 "global",
787 "--keyring",
788 "buildkite",
789 "--key",
790 kms_key,
791 "--ciphertext-file",
792 "-",
793 "--plaintext-file",
794 "-",
795 ],
796 input=base64.b64decode(encrypted_token),
797 env=os.environ,
798 check=True,
799 stdout=subprocess.PIPE, # We cannot use capture_output since some workers run Python <3.7
800 stderr=subprocess.PIPE, # We cannot use capture_output since some workers run Python <3.7
Florian Weikert849afb22019-12-14 12:22:29 -0800801 )
Florian Weikert24ab0862024-07-23 19:47:59 +0200802 return result.stdout.decode("utf-8").strip()
Florian Weikert1e067912024-07-23 13:51:11 +0200803 except subprocess.CalledProcessError as ex:
804 cause = ex.stderr.decode("utf-8")
805 raise BuildkiteException(f"Failed to decrypt token:\n{cause}")
Florian Weikert849afb22019-12-14 12:22:29 -0800806
807
Philipp Wollermanndcaddd92018-02-21 14:13:43 +0100808def eprint(*args, **kwargs):
809 """
810 Print to stderr and flush (just in case).
811 """
812 print(*args, flush=True, file=sys.stderr, **kwargs)
Jakob Buchgraber0b6a39d2018-02-17 00:45:14 +0100813
814
Jakob Buchgraber9f153542018-02-27 10:56:04 +0100815def is_windows():
Jakob Buchgraber09048fa2018-02-27 11:39:39 +0100816 return os.name == "nt"
Jakob Buchgraber9f153542018-02-27 10:56:04 +0100817
Jakob Buchgrabere6de16b2018-02-28 12:42:12 +0100818
Florian Weikerta6110a92022-10-20 01:16:09 +0200819def is_mac():
Florian Weikertda94a102022-10-21 12:24:37 +0200820 return platform_module.system() == "Darwin"
Florian Weikerta6110a92022-10-20 01:16:09 +0200821
822
Jakob Buchgraber9f153542018-02-27 10:56:04 +0100823def gsutil_command():
Philipp Wollermann2409c6e2018-08-07 07:37:54 +0200824 return "gsutil.cmd" if is_windows() else "gsutil"
Jakob Buchgraber9f153542018-02-27 10:56:04 +0100825
Jakob Buchgrabere6de16b2018-02-28 12:42:12 +0100826
Jakob Buchgraber9f153542018-02-27 10:56:04 +0100827def gcloud_command():
Philipp Wollermann2409c6e2018-08-07 07:37:54 +0200828 return "gcloud.cmd" if is_windows() else "gcloud"
Jakob Buchgraber0b6a39d2018-02-17 00:45:14 +0100829
Jakob Buchgrabere6de16b2018-02-28 12:42:12 +0100830
Yun Peng5a1a9442022-01-11 14:26:48 +0100831def match_matrix_attr_pattern(s):
832 return re.match("^\${{\s*(\w+)\s*}}$", s)
833
834
835def get_matrix_attributes(task):
836 """Get unexpanded matrix attributes from the given task.
837
838 If a value of field matches "${{<name>}}", then <name> is a wanted matrix attribute.
839 eg. platform: ${{ platform }}
840 """
841 attributes = []
842 for key, value in task.items():
843 if type(value) is str:
844 res = match_matrix_attr_pattern(value)
845 if res:
846 attributes.append(res.groups()[0])
847 return list(set(attributes))
848
849
850def get_combinations(matrix, attributes):
851 """Given a matrix and the wanted attributes, return all possible combinations.
852
853 eg.
854 With matrix = {'a': [1, 2], 'b': [1], 'c': [1]},
855 if attributes = ['a', 'b'], then returns [[('a', 1), ('b', 1)], [('a', 2), ('b', 1)]]
856 if attributes = ['b', 'c'], then returns [[('b', 1), ('c', 1)]]
857 if attributes = ['c'], then returns [[('c', 1)]]
858 """
Yun Peng2b59cde2023-05-08 15:44:38 +0200859 # Sort the attributes to make the output deterministic.
860 attributes.sort()
Yun Peng5a1a9442022-01-11 14:26:48 +0100861 for attr in attributes:
862 if attr not in matrix:
863 raise BuildkiteException("${{ %s }} is not defined in `matrix` section." % attr)
864 pairs = [[(attr, value) for value in matrix[attr]] for attr in attributes]
Yun Peng237309f2023-04-25 15:30:43 +0200865 return sorted(itertools.product(*pairs))
Yun Peng5a1a9442022-01-11 14:26:48 +0100866
867
868def get_expanded_task(task, combination):
869 """Expand a task with the given combination of values of attributes."""
870 combination = dict(combination)
871 expanded_task = copy.deepcopy(task)
872 for key, value in task.items():
873 if type(value) is str:
874 res = match_matrix_attr_pattern(value)
875 if res:
876 attr = res.groups()[0]
877 expanded_task[key] = combination[attr]
878 return expanded_task
879
880
Yun Peng2d33fc62024-10-23 11:43:04 +0200881def fetch_configs(http_url, file_config, bazel_version=None):
Philipp Wollermanndb024862018-02-19 17:16:56 +0100882 """
Jakob Buchgraberc57d4ad2018-03-22 12:33:17 +0100883 If specified fetches the build configuration from file_config or http_url, else tries to
Jakob Buchgraber25bb50f2018-02-22 18:06:21 +0100884 read it from .bazelci/presubmit.yml.
Philipp Wollermann598b4a42018-02-19 17:03:36 +0100885 Returns the json configuration as a python data structure.
Philipp Wollermanndb024862018-02-19 17:16:56 +0100886 """
Jakob Buchgraberc57d4ad2018-03-22 12:33:17 +0100887 if file_config is not None and http_url is not None:
888 raise BuildkiteException("file_config and http_url cannot be set at the same time")
889
Yun Peng2d33fc62024-10-23 11:43:04 +0200890 return load_config(http_url, file_config, bazel_version=bazel_version)
Florian Weikertc8b3ed22019-05-31 16:14:12 +0200891
892
Yun Pengfde48582022-01-11 14:37:47 +0100893def expand_task_config(config):
Yun Peng5a1a9442022-01-11 14:26:48 +0100894 # Expand tasks that uses attributes defined in the matrix section.
895 # The original task definition expands to multiple tasks for each possible combination.
896 tasks_to_expand = []
897 expanded_tasks = {}
898 matrix = config.pop("matrix", {})
899 for key, value in matrix.items():
900 if type(key) is not str or type(value) is not list:
901 raise BuildkiteException("Expect `matrix` is a map of str -> list")
902
903 for task in config["tasks"]:
904 attributes = get_matrix_attributes(config["tasks"][task])
905 if attributes:
906 tasks_to_expand.append(task)
907 count = 1
908 for combination in get_combinations(matrix, attributes):
909 expanded_task_name = "%s_config_%.2d" % (task, count)
910 count += 1
Florian Weikert7f21ca42022-02-02 17:35:23 +0100911 expanded_tasks[expanded_task_name] = get_expanded_task(
912 config["tasks"][task], combination
913 )
Yun Peng5a1a9442022-01-11 14:26:48 +0100914
915 for task in tasks_to_expand:
916 config["tasks"].pop(task)
917 config["tasks"].update(expanded_tasks)
918
Yun Pengfde48582022-01-11 14:37:47 +0100919
Yun Peng2d33fc62024-10-23 11:43:04 +0200920def maybe_overwrite_bazel_version(bazel_version, config):
921 if not bazel_version:
922 return
923 for task in config.get("tasks", {}):
924 config["tasks"][task]["old_bazel"] = config["tasks"][task].get("bazel")
925 config["tasks"][task]["bazel"] = bazel_version
Yun Pengab3f2a62024-10-30 15:15:22 +0100926 matrix = config.get("matrix", {})
927 if "bazel" in matrix:
928 # This will only apply to "old_bazel" and avoid generating multiple tasks with the same config
929 matrix["bazel"] = [", ".join(matrix["bazel"])]
Yun Peng2d33fc62024-10-23 11:43:04 +0200930
931
932def load_config(http_url, file_config, allow_imports=True, bazel_version=None):
Yun Pengfde48582022-01-11 14:37:47 +0100933 if http_url:
934 config = load_remote_yaml_file(http_url)
935 else:
936 file_config = file_config or ".bazelci/presubmit.yml"
937 with open(file_config, "r") as fd:
938 config = yaml.safe_load(fd)
939
940 # Legacy mode means that there is exactly one task per platform (e.g. ubuntu1604_nojdk),
941 # which means that we can get away with using the platform name as task ID.
942 # No other updates are needed since get_platform_for_task() falls back to using the
943 # task ID as platform if there is no explicit "platforms" field.
944 if "platforms" in config:
945 config["tasks"] = config.pop("platforms")
946
947 if "tasks" not in config:
948 config["tasks"] = {}
949
Yun Peng2d33fc62024-10-23 11:43:04 +0200950 maybe_overwrite_bazel_version(bazel_version, config)
Yun Pengfde48582022-01-11 14:37:47 +0100951 expand_task_config(config)
952
Florian Weikertc8b3ed22019-05-31 16:14:12 +0200953 imports = config.pop("imports", None)
954 if imports:
955 if not allow_imports:
956 raise BuildkiteException("Nested imports are not allowed")
957
958 for i in imports:
Yun Peng2d33fc62024-10-23 11:43:04 +0200959 imported_tasks = load_imported_tasks(i, http_url, file_config, bazel_version)
Florian Weikertc8b3ed22019-05-31 16:14:12 +0200960 config["tasks"].update(imported_tasks)
961
Yun Peng686f4592022-01-17 15:38:48 +0100962 if len(config["tasks"]) > MAX_TASK_NUMBER:
Florian Weikert7f21ca42022-02-02 17:35:23 +0100963 raise BuildkiteException(
UebelAndre3c0e9822024-11-26 09:00:17 -0800964 "The number of tasks in one config file is limited to %s, found %s!"
965 % (MAX_TASK_NUMBER, len(config["tasks"])),
Florian Weikert7f21ca42022-02-02 17:35:23 +0100966 )
Yun Peng686f4592022-01-17 15:38:48 +0100967
Florian Weikert843d7a02019-02-03 17:24:50 +0100968 return config
969
970
Florian Weikert13215a82019-05-10 12:42:21 +0200971def load_remote_yaml_file(http_url):
972 with urllib.request.urlopen(http_url) as resp:
973 reader = codecs.getreader("utf-8")
Philipp Wollermannd00107e2019-05-18 23:50:59 +0200974 return yaml.safe_load(reader(resp))
Florian Weikert13215a82019-05-10 12:42:21 +0200975
976
Yun Peng2d33fc62024-10-23 11:43:04 +0200977def load_imported_tasks(import_name, http_url, file_config, bazel_version):
Florian Weikertc8b3ed22019-05-31 16:14:12 +0200978 if "/" in import_name:
979 raise BuildkiteException("Invalid import '%s'" % import_name)
980
981 old_path = http_url or file_config
982 new_path = "%s%s" % (old_path[: old_path.rfind("/") + 1], import_name)
983 if http_url:
984 http_url = new_path
985 else:
986 file_config = new_path
987
Florian Weikertccd44572024-11-11 13:11:39 +0100988 imported_config = load_config(
989 http_url=http_url, file_config=file_config, allow_imports=False, bazel_version=bazel_version
990 )
Florian Weikertc8b3ed22019-05-31 16:14:12 +0200991
992 namespace = import_name.partition(".")[0]
993 tasks = {}
994 for task_name, task_config in imported_config["tasks"].items():
Florian Weikert61f29b82019-08-12 16:56:56 +0200995 fix_imported_task_platform(task_name, task_config)
996 fix_imported_task_name(namespace, task_config)
997 fix_imported_task_working_directory(namespace, task_config)
Florian Weikertc8b3ed22019-05-31 16:14:12 +0200998 tasks["%s_%s" % (namespace, task_name)] = task_config
999
1000 return tasks
1001
1002
Florian Weikert61f29b82019-08-12 16:56:56 +02001003def fix_imported_task_platform(task_name, task_config):
1004 if "platform" not in task_config:
1005 task_config["platform"] = task_name
1006
1007
1008def fix_imported_task_name(namespace, task_config):
1009 old_name = task_config.get("name")
1010 task_config["name"] = "%s (%s)" % (namespace, old_name) if old_name else namespace
1011
1012
1013def fix_imported_task_working_directory(namespace, task_config):
1014 old_dir = task_config.get("working_directory")
1015 task_config["working_directory"] = os.path.join(namespace, old_dir) if old_dir else namespace
1016
1017
Jakob Buchgraber3120f7a2018-02-18 13:28:02 +01001018def print_collapsed_group(name):
Jakob Buchgraber5c9b13d2018-02-21 22:28:14 +01001019 eprint("\n\n--- {0}\n\n".format(name))
Jakob Buchgraber3120f7a2018-02-18 13:28:02 +01001020
Jakob Buchgraber9c83de72018-02-18 15:32:44 +01001021
Jakob Buchgraber3120f7a2018-02-18 13:28:02 +01001022def print_expanded_group(name):
Jakob Buchgraber5c9b13d2018-02-21 22:28:14 +01001023 eprint("\n\n+++ {0}\n\n".format(name))
Jakob Buchgraber0b6a39d2018-02-17 00:45:14 +01001024
Yun Peng9b1d3432021-12-07 10:40:45 +01001025
Philipp Wollermannc8b86ac2021-10-25 17:52:07 +02001026def is_trueish(s):
1027 return str(s).lower() in ["true", "1", "t", "y", "yes"]
1028
Jakob Buchgraber9c83de72018-02-18 15:32:44 +01001029
Yun Peng8975c6b2019-02-28 11:55:55 +01001030def use_bazelisk_migrate():
1031 """
1032 If USE_BAZELISK_MIGRATE is set, we use `bazelisk --migrate` to test incompatible flags.
1033 """
Philipp Wollermannc8b86ac2021-10-25 17:52:07 +02001034 return is_trueish(os.environ.get("USE_BAZELISK_MIGRATE"))
Yun Peng8975c6b2019-02-28 11:55:55 +01001035
Yun Peng6a6d29d2023-11-09 14:24:06 +01001036
1037def is_downstream_pipeline():
1038 """
1039 Return true if BAZELCI_DOWNSTREAM_PIPELINE is set
1040 """
1041 return is_trueish(os.environ.get("BAZELCI_DOWNSTREAM_PIPELINE"))
1042
1043
John Cater2f0aee22023-10-19 05:34:33 -04001044def local_run_only():
1045 """
1046 If BAZELCI_LOCAL_RUN is set, run bazelci in local-only mode, with no attempt
1047 to use remote cache/execution, and no attempt to upload test results. Only
1048 compatible with `bazelci.py runner`, not other subcommands.
1049 """
1050 return is_trueish(os.environ.get("BAZELCI_LOCAL_RUN", "false"))
1051
Yun Peng8975c6b2019-02-28 11:55:55 +01001052
1053def bazelisk_flags():
1054 return ["--migrate"] if use_bazelisk_migrate() else []
1055
1056
Chi Wang54595a22021-06-11 17:49:58 +08001057def calculate_flags(task_config, task_config_key, action_key, tmpdir, test_env_vars):
Chi Wang6357efe2020-08-25 16:23:38 +08001058 include_json_profile = task_config.get("include_json_profile", [])
Chi Wang54595a22021-06-11 17:49:58 +08001059 capture_corrupted_outputs = task_config.get("capture_corrupted_outputs", [])
Chi Wang6357efe2020-08-25 16:23:38 +08001060
1061 json_profile_flags = []
1062 json_profile_out = None
Chi Wang54595a22021-06-11 17:49:58 +08001063 if action_key in include_json_profile:
1064 json_profile_out = os.path.join(tmpdir, "{}.profile.gz".format(action_key))
Tobias Werthc22e4c42021-10-08 12:03:14 +02001065 json_profile_flags = ["--profile={}".format(json_profile_out)]
1066
Chi Wang54595a22021-06-11 17:49:58 +08001067 capture_corrupted_outputs_flags = []
1068 capture_corrupted_outputs_dir = None
1069 if action_key in capture_corrupted_outputs:
Philipp Wollermannf436e742021-08-11 11:06:55 +02001070 capture_corrupted_outputs_dir = os.path.join(
1071 tmpdir, "{}_corrupted_outputs".format(action_key)
1072 )
1073 capture_corrupted_outputs_flags = [
1074 "--experimental_remote_capture_corrupted_outputs={}".format(
1075 capture_corrupted_outputs_dir
1076 )
1077 ]
Chi Wang6357efe2020-08-25 16:23:38 +08001078
Alexandre Rostovtsev42bbeb52023-08-24 04:08:04 -04001079 flags = list(task_config.get(task_config_key, []))
Chi Wang6357efe2020-08-25 16:23:38 +08001080 flags += json_profile_flags
Chi Wang54595a22021-06-11 17:49:58 +08001081 flags += capture_corrupted_outputs_flags
Chi Wang6357efe2020-08-25 16:23:38 +08001082 # We have to add --test_env flags to `build`, too, otherwise Bazel
1083 # discards its analysis cache between `build` and `test`.
1084 if test_env_vars:
1085 flags += ["--test_env={}".format(v) for v in test_env_vars]
1086
Chi Wang54595a22021-06-11 17:49:58 +08001087 return flags, json_profile_out, capture_corrupted_outputs_dir
Chi Wang6357efe2020-08-25 16:23:38 +08001088
1089
Philipp Wollermanncd5694c2019-01-03 14:53:04 +01001090def execute_commands(
Florian Weikertc8642af2019-02-03 23:58:51 +01001091 task_config,
Philipp Wollermanncd5694c2019-01-03 14:53:04 +01001092 platform,
Philipp Wollermanncd5694c2019-01-03 14:53:04 +01001093 use_bazel_at_commit,
1094 use_but,
1095 save_but,
Yun Peng4d1d6542019-01-17 18:30:33 +01001096 needs_clean,
Philipp Wollermanncd5694c2019-01-03 14:53:04 +01001097 build_only,
1098 test_only,
1099 monitor_flaky_tests,
Florian Weikertc8642af2019-02-03 23:58:51 +01001100 bazel_version=None,
Philipp Wollermanncd5694c2019-01-03 14:53:04 +01001101):
Yun Pengc85cd0e2022-09-02 10:44:29 +02001102 if use_bazelisk_migrate():
1103 # If we are testing incompatible flags with Bazelisk,
1104 # use Bazel@last_green if USE_BAZEL_VERSION env var is not set explicitly.
1105 if "USE_BAZEL_VERSION" not in os.environ:
1106 bazel_version = "last_green"
1107
1108 # Override use_but in case we are in the downstream pipeline so that it doesn't try to
1109 # download Bazel built from previous jobs.
1110 use_but = False
1111
1112 # Set BAZELISK_INCOMPATIBLE_FLAGS to tell Bazelisk which flags to test.
1113 os.environ["BAZELISK_INCOMPATIBLE_FLAGS"] = ",".join(fetch_incompatible_flags().keys())
1114
Florian Weikert13215a82019-05-10 12:42:21 +02001115 if not bazel_version:
1116 # The last good version of Bazel can be specified in an emergency file.
1117 # However, we only use last_good_bazel for pipelines that do not
1118 # explicitly specify a version of Bazel.
1119 try:
1120 emergency_settings = load_remote_yaml_file(EMERGENCY_FILE_URL)
1121 bazel_version = emergency_settings.get("last_good_bazel")
1122 except urllib.error.HTTPError:
1123 # Ignore this error. The Setup step will have already complained about
1124 # it by showing an error message.
1125 pass
Yun Pengf50f7b72019-02-28 19:09:52 +01001126
Jakob Buchgraberfb95a2f2018-02-22 11:46:25 +01001127 if build_only and test_only:
1128 raise BuildkiteException("build_only and test_only cannot be true at the same time")
Philipp Wollermanne1318eb2018-08-13 15:08:01 +02001129
Florian Weikertd7cb83d2023-02-24 19:51:52 +01001130 if use_but:
1131 if use_bazel_at_commit:
1132 raise BuildkiteException("use_bazel_at_commit cannot be set when use_but is true")
1133
1134 print_collapsed_group(":printer: Printing task config for downstream job...")
Florian Weikert7d894782023-02-24 20:28:14 +01001135 eprint(json.dumps(task_config, indent=2))
Yun Peng20d45602018-10-18 13:27:05 +02001136
Philipp Wollermanne1318eb2018-08-13 15:08:01 +02001137 tmpdir = tempfile.mkdtemp()
Yun Peng2dc708b2024-11-14 14:28:31 +01001138 if is_mac():
1139 activate_xcode(task_config)
Philipp Wollermann380f1e62019-04-12 16:45:27 +02001140
Yun Peng2dc708b2024-11-14 14:28:31 +01001141 # If the CI worker runs Bazelisk, we need to forward all required env variables to the test.
1142 # Otherwise any integration test that invokes Bazel (=Bazelisk in this case) will fail.
1143 test_env_vars = ["LocalAppData"] if platform == "windows" else ["HOME"]
Florian Weikertc12580c2021-07-13 17:09:25 +02001144
Yun Peng2dc708b2024-11-14 14:28:31 +01001145 # CI should have its own user agent so that we can remove it from Bazel download statistics.
1146 os.environ["BAZELISK_USER_AGENT"] = "Bazelisk/BazelCI"
1147 test_env_vars.append("BAZELISK_USER_AGENT")
Florian Weikertc12580c2021-07-13 17:09:25 +02001148
Yun Peng2dc708b2024-11-14 14:28:31 +01001149 # We use one binary for all Linux platforms (because we also just release one binary for all
1150 # Linux versions and we have to ensure that it works on all of them).
1151 binary_platform = platform if is_mac() or is_windows() else LINUX_BINARY_PLATFORM
Philipp Wollermannf4aabb72019-06-25 15:59:00 +02001152
Yun Peng2dc708b2024-11-14 14:28:31 +01001153 bazel_binary = "bazel"
1154 if use_bazel_at_commit:
1155 print_collapsed_group(":gcloud: Downloading Bazel built at " + use_bazel_at_commit)
1156 # Linux binaries are published under platform name "centos7"
1157 if binary_platform == LINUX_BINARY_PLATFORM:
1158 binary_platform = "centos7"
1159 os.environ["USE_BAZEL_VERSION"] = download_bazel_binary_at_commit(
1160 tmpdir, binary_platform, use_bazel_at_commit
1161 )
1162 print_collapsed_group(":bazel: Using Bazel at " + os.environ["USE_BAZEL_VERSION"])
1163 elif use_but:
1164 print_collapsed_group(":gcloud: Downloading Bazel Under Test")
1165 os.environ["USE_BAZEL_VERSION"] = download_bazel_binary(tmpdir, binary_platform)
1166 print_collapsed_group(":bazel: Using Bazel at " + os.environ["USE_BAZEL_VERSION"])
1167 else:
1168 print_collapsed_group(":bazel: Using Bazel version " + bazel_version)
1169 if bazel_version:
1170 os.environ["USE_BAZEL_VERSION"] = bazel_version
1171 if "USE_BAZEL_VERSION" in os.environ and not task_config.get(
1172 "skip_use_bazel_version_for_test", False
1173 ):
1174 # This will only work if the bazel binary in $PATH is actually a bazelisk binary
1175 # (https://github.com/bazelbuild/bazelisk).
1176 test_env_vars.append("USE_BAZEL_VERSION")
1177
1178 for key, value in task_config.get("environment", {}).items():
1179 # We have to explicitly convert the value to a string, because sometimes YAML tries to
1180 # be smart and converts strings like "true" and "false" to booleans.
1181 os.environ[key] = os.path.expandvars(str(value))
1182
1183 # Avoid "Network is unreachable" errors in IPv6-only environments
1184 for e in ("COURSIER_OPTS", "JAVA_TOOL_OPTIONS", "SSL_CERT_FILE"):
1185 # If users overrode a variable with an empty value above, it won't be added here.
1186 if os.getenv(e):
1187 test_env_vars.append(e)
1188
1189 # Set BAZELISK_SHUTDOWN to 1 when we use bazelisk --migrate on Windows.
1190 # This is a workaround for https://github.com/bazelbuild/continuous-integration/issues/1012
1191 if use_bazelisk_migrate() and platform == "windows":
1192 os.environ["BAZELISK_SHUTDOWN"] = "1"
1193
1194 def PrepareRepoInCwd(print_cmd_groups, initial_setup=False):
1195 # Allow the config to override the current working directory.
1196 requested_working_dir = task_config.get("working_directory")
1197 if requested_working_dir:
1198 if os.path.isabs(requested_working_dir):
1199 raise BuildkiteException(
1200 f"working_directory must be relative to the repository root, "
1201 "but was {requested_working_dir}"
1202 )
1203
1204 full_requested_working_dir = os.path.abspath(requested_working_dir)
1205 if not os.path.isdir(full_requested_working_dir):
1206 raise BuildkiteException(
1207 f"{full_requested_working_dir} does not exist or is not a directory"
1208 )
1209
1210 os.chdir(full_requested_working_dir)
1211
1212 # Dirty workaround for #1660
1213 if initial_setup:
Yun Penge8838ad2025-01-02 11:06:14 +01001214 # Run `bazel --version` to make sure Bazel is fetched.
1215 run_bazel_version(bazel_binary)
1216
Yun Peng2dc708b2024-11-14 14:28:31 +01001217 # Set OUTPUT_BASE environment variable
1218 os.environ["OUTPUT_BASE"] = get_output_base(bazel_binary)
1219
1220 cmd_exec_func = (
1221 execute_batch_commands if platform == "windows" else execute_shell_commands
Philipp Wollermannf4aabb72019-06-25 15:59:00 +02001222 )
Yun Peng2dc708b2024-11-14 14:28:31 +01001223 cmd_exec_func(task_config.get("setup", None))
Chi Wang6357efe2020-08-25 16:23:38 +08001224
UebelAndre2e56c362023-06-13 04:38:15 -07001225 if platform == "windows":
Yun Peng2dc708b2024-11-14 14:28:31 +01001226 execute_batch_commands(task_config.get("batch_commands", None), print_cmd_groups)
UebelAndre2e56c362023-06-13 04:38:15 -07001227 else:
Yun Peng2dc708b2024-11-14 14:28:31 +01001228 execute_shell_commands(task_config.get("shell_commands", None), print_cmd_groups)
UebelAndre2e56c362023-06-13 04:38:15 -07001229
Yun Peng2dc708b2024-11-14 14:28:31 +01001230 PrepareRepoInCwd(True, initial_setup=True)
1231
1232 bazel_version = print_bazel_version_info(bazel_binary, platform)
1233
1234 print_environment_variables_info()
1235
1236 execute_bazel_run(bazel_binary, platform, task_config.get("run_targets", None))
1237
1238 if needs_clean:
1239 execute_bazel_clean(bazel_binary, platform)
1240
1241 # we need the commit here in order to calculate the correct targets.
1242 git_commit = os.getenv("BUILDKITE_COMMIT")
1243 if not git_commit:
1244 raise BuildkiteInfraException("Unable to determine Git commit for this build")
1245
1246 test_flags, json_profile_out_test, capture_corrupted_outputs_dir_test = calculate_flags(
1247 task_config, "test_flags", "test", tmpdir, test_env_vars
1248 )
1249
1250 build_targets, test_targets, coverage_targets, index_targets = calculate_targets(
1251 task_config,
1252 bazel_binary,
1253 build_only,
1254 test_only,
1255 os.getcwd(),
1256 PrepareRepoInCwd,
1257 git_commit,
1258 test_flags,
1259 )
1260
1261 if build_targets:
1262 (
1263 build_flags,
1264 json_profile_out_build,
1265 capture_corrupted_outputs_dir_build,
1266 ) = calculate_flags(task_config, "build_flags", "build", tmpdir, test_env_vars)
1267 try:
1268 release_name = get_release_name_from_branch_name()
1269 execute_bazel_build(
1270 bazel_version,
1271 bazel_binary,
1272 platform,
1273 build_flags
1274 + (
1275 ["--stamp", "--embed_label=%s" % release_name]
1276 if save_but and release_name
1277 else []
1278 ),
1279 build_targets,
1280 None,
1281 )
1282 if save_but:
1283 upload_bazel_binary(platform)
1284 finally:
1285 if json_profile_out_build:
1286 upload_json_profile(json_profile_out_build, tmpdir)
1287 if capture_corrupted_outputs_dir_build:
1288 upload_corrupted_outputs(capture_corrupted_outputs_dir_build, tmpdir)
1289
1290 if test_targets:
1291 if not is_windows():
1292 # On platforms that support sandboxing (Linux, MacOS) we have
1293 # to allow access to Bazelisk's cache directory.
1294 # However, the flag requires the directory to exist,
1295 # so we create it here in order to not crash when a test
1296 # does not invoke Bazelisk.
1297 bazelisk_cache_dir = get_bazelisk_cache_directory()
1298 os.makedirs(bazelisk_cache_dir, mode=0o755, exist_ok=True)
1299 test_flags.append("--sandbox_writable_path={}".format(bazelisk_cache_dir))
1300
1301 # Set BUILDKITE_ANALYTICS_TOKEN so that bazelci-agent can upload test results to Test Analytics
1302 if "ENCRYPTED_BUILDKITE_ANALYTICS_TOKEN" in os.environ:
1303 if THIS_IS_TESTING:
1304 kms_key = "buildkite-testing-api-token"
1305 project = "bazel-untrusted"
1306 elif THIS_IS_TRUSTED:
1307 kms_key = "buildkite-trusted-api-token"
1308 project = "bazel-public"
1309 else:
1310 kms_key = "buildkite-untrusted-api-token"
1311 project = "bazel-untrusted"
1312
1313 try:
1314 os.environ["BUILDKITE_ANALYTICS_TOKEN"] = decrypt_token(
1315 encrypted_token=os.environ["ENCRYPTED_BUILDKITE_ANALYTICS_TOKEN"],
1316 kms_key=kms_key,
1317 project=project,
1318 )
1319 except Exception as ex:
1320 print_collapsed_group(
1321 ":rotating_light: Test analytics disabled due to an error :warning:"
1322 )
1323 eprint(ex)
1324
1325 test_bep_file = os.path.join(tmpdir, _TEST_BEP_FILE)
1326 # Create an empty test_bep_file so that the bazelci-agent can start to follow the file right away. Otherwise,
1327 # there is a race between when bazelci-agent starts to read the file and when Bazel creates the file.
1328 open(test_bep_file, "w").close()
1329 with concurrent.futures.ThreadPoolExecutor() as executor:
1330 future = executor.submit(
1331 upload_test_logs_from_bep, test_bep_file, tmpdir, monitor_flaky_tests
1332 )
1333 try:
1334 execute_bazel_test(
1335 bazel_version,
1336 bazel_binary,
1337 platform,
1338 test_flags,
1339 test_targets,
1340 test_bep_file,
1341 monitor_flaky_tests,
1342 )
1343 finally:
1344 if json_profile_out_test:
1345 upload_json_profile(json_profile_out_test, tmpdir)
1346 if capture_corrupted_outputs_dir_test:
1347 upload_corrupted_outputs(capture_corrupted_outputs_dir_test, tmpdir)
1348
1349 _ = future.result()
1350 # TODO: print results
1351
1352 if coverage_targets:
1353 (
1354 coverage_flags,
1355 json_profile_out_coverage,
1356 capture_corrupted_outputs_dir_coverage,
1357 ) = calculate_flags(task_config, "coverage_flags", "coverage", tmpdir, test_env_vars)
1358 try:
1359 execute_bazel_coverage(
1360 bazel_version,
1361 bazel_binary,
1362 platform,
1363 coverage_flags,
1364 coverage_targets,
1365 )
1366 finally:
1367 if json_profile_out_coverage:
1368 upload_json_profile(json_profile_out_coverage, tmpdir)
1369 if capture_corrupted_outputs_dir_coverage:
1370 upload_corrupted_outputs(capture_corrupted_outputs_dir_coverage, tmpdir)
1371
1372 if index_targets:
1373 (
1374 index_flags,
1375 json_profile_out_index,
1376 capture_corrupted_outputs_dir_index,
1377 ) = calculate_flags(task_config, "index_flags", "index", tmpdir, test_env_vars)
1378 index_upload_policy = task_config.get("index_upload_policy", "IfBuildSuccess")
1379 index_upload_gcs = task_config.get("index_upload_gcs", False)
1380
1381 try:
1382 should_upload_kzip = (
1383 True if index_upload_policy == INDEX_UPLOAD_POLICY_ALWAYS else False
1384 )
1385 try:
1386 execute_bazel_build_with_kythe(
1387 bazel_version,
1388 bazel_binary,
1389 platform,
1390 index_flags,
1391 index_targets,
1392 None,
1393 )
1394
1395 if index_upload_policy == INDEX_UPLOAD_POLICY_IF_BUILD_SUCCESS:
1396 should_upload_kzip = True
1397 except subprocess.CalledProcessError as e:
1398 # If not running with Always policy, raise the build error.
1399 if index_upload_policy != INDEX_UPLOAD_POLICY_ALWAYS:
1400 handle_bazel_failure(e, "build")
1401
1402 if should_upload_kzip and not is_pull_request():
1403 try:
1404 merge_and_upload_kythe_kzip(platform, index_upload_gcs)
1405 except subprocess.CalledProcessError:
1406 raise BuildkiteException("Failed to upload kythe kzip")
1407 finally:
1408 if json_profile_out_index:
1409 upload_json_profile(json_profile_out_index, tmpdir)
1410 if capture_corrupted_outputs_dir_index:
1411 upload_corrupted_outputs(capture_corrupted_outputs_dir_index, tmpdir)
1412
1413 if platform == "windows":
1414 execute_batch_commands(
1415 task_config.get("post_batch_commands", None),
1416 True,
1417 ":batch: Post Processing (Batch Commands)",
1418 )
1419 else:
1420 execute_shell_commands(
1421 task_config.get("post_shell_commands", None),
1422 True,
1423 ":bash: Post Processing (Shell Commands)",
1424 )
Philipp Wollermanndcaddd92018-02-21 14:13:43 +01001425
Philipp Wollermann3c8b8512019-07-16 15:28:03 +02001426
Florian Weikertee84c5c2019-05-28 11:21:51 +02001427def activate_xcode(task_config):
Florian Weikertcee5d1d2023-05-17 00:23:03 +02001428 supported_versions = sorted(
1429 # Stripping "Xcode" prefix and ".app" suffix from e.g. "Xcode12.0.1.app" leaves just the version number.
1430 [os.path.basename(x)[5:-4] for x in glob("/Applications/Xcode*.app")],
Florian Weikertcee5d1d2023-05-17 00:23:03 +02001431 )
Florian Weikertb649c502023-11-20 18:00:35 +01001432 if not supported_versions:
1433 raise BuildkiteInfraException("Could not find a valid Xcode installation.")
Florian Weikertcee5d1d2023-05-17 00:23:03 +02001434
Florian Weikertb649c502023-11-20 18:00:35 +01001435 # Get the Xcode version from the config. Can be None.
1436 wanted_xcode_version = get_requested_xcode_version(task_config)
Florian Weikertee84c5c2019-05-28 11:21:51 +02001437
Philipp Wollermann06e56972020-01-29 14:46:41 +01001438 # This is used to replace e.g. 11.2 with 11.2.1 without having to update all configs.
Philipp Wollermann2b4ee9f2021-02-11 16:32:35 +01001439 xcode_version = XCODE_VERSION_OVERRIDES.get(wanted_xcode_version, wanted_xcode_version)
1440
Yun Peng310b6ca2024-03-06 13:50:10 +01001441 # Default to the latest installed version if no version was requested
Florian Weikertb649c502023-11-20 18:00:35 +01001442 # or the requested version is not installed.
Philipp Wollermann2b4ee9f2021-02-11 16:32:35 +01001443 if xcode_version not in supported_versions:
Yun Peng310b6ca2024-03-06 13:50:10 +01001444 xcode_version = supported_versions[-1]
James Leitch3ac0ac12023-12-04 04:25:26 -08001445
Florian Weikertb649c502023-11-20 18:00:35 +01001446 if not wanted_xcode_version or wanted_xcode_version == xcode_version:
1447 print_collapsed_group(":xcode: Activating Xcode {}...".format(xcode_version))
1448 else:
Philipp Wollermann2b4ee9f2021-02-11 16:32:35 +01001449 print_collapsed_group(
1450 ":xcode: Fixed Xcode version: {} -> {}...".format(wanted_xcode_version, xcode_version)
1451 )
1452 lines = [
Florian Weikert9d5e4c02021-05-27 15:10:20 +02001453 "Your selected Xcode version {} was not available on the machine.".format(
1454 wanted_xcode_version
1455 ),
Philipp Wollermann2b4ee9f2021-02-11 16:32:35 +01001456 "Bazel CI automatically picked a fallback version: {}.".format(xcode_version),
Florian Weikert57255fd2024-05-17 20:17:46 +02001457 "Available versions are: {}.".format(", ".join(supported_versions)),
Philipp Wollermann2b4ee9f2021-02-11 16:32:35 +01001458 ]
Florian Weikert9d5e4c02021-05-27 15:10:20 +02001459 execute_command(
1460 [
1461 "buildkite-agent",
1462 "annotate",
1463 "--style=warning",
1464 "\n".join(lines),
1465 "--context",
1466 "ctx-xcode_version_fixed",
1467 ]
1468 )
Philipp Wollermann06e56972020-01-29 14:46:41 +01001469
Florian Weikertee84c5c2019-05-28 11:21:51 +02001470 # Now activate the specified Xcode version and let it install its required components.
1471 # The CI machines have a sudoers config that allows the 'buildkite' user to run exactly
1472 # these two commands, so don't change them without also modifying the file there.
Florian Weikertb649c502023-11-20 18:00:35 +01001473 xcode_path = "/Applications/Xcode{}.app".format(xcode_version)
Florian Weikertee84c5c2019-05-28 11:21:51 +02001474 execute_command(["/usr/bin/sudo", "/usr/bin/xcode-select", "--switch", xcode_path])
1475 execute_command(["/usr/bin/sudo", "/usr/bin/xcodebuild", "-runFirstLaunch"])
1476
1477
Florian Weikertb649c502023-11-20 18:00:35 +01001478def get_requested_xcode_version(task_config):
1479 wanted_xcode_version = task_config.get("xcode_version")
1480 if not wanted_xcode_version:
1481 return None
1482
1483 # Ensure it's a valid version number.
1484 if not isinstance(wanted_xcode_version, str):
1485 raise BuildkiteException(
1486 "Version number '{}' is not a string. Did you forget to put it in quotes?".format(
1487 wanted_xcode_version
1488 )
1489 )
1490 if not XCODE_VERSION_REGEX.match(wanted_xcode_version):
1491 raise BuildkiteException(
1492 "Invalid Xcode version format '{}', must match the format X.Y[.Z].".format(
1493 wanted_xcode_version
1494 )
1495 )
1496
1497 return wanted_xcode_version
1498
1499
Florian Weikertda94a102022-10-21 12:24:37 +02001500def get_bazelisk_cache_directory():
Florian Weikert4901c662019-02-26 13:20:11 +01001501 # The path relies on the behavior of Go's os.UserCacheDir()
1502 # and of the Go version of Bazelisk.
Florian Weikertda94a102022-10-21 12:24:37 +02001503 cache_dir = "Library/Caches" if is_mac() else ".cache"
Philipp Wollermannce986af2019-07-18 14:46:05 +02001504 return os.path.join(os.environ.get("HOME"), cache_dir, "bazelisk")
Florian Weikert4901c662019-02-26 13:20:11 +01001505
Florian Weikert5b890332019-02-25 14:57:43 +01001506
Philipp Wollermann1b5ecdc2021-06-10 21:52:55 +02001507def current_branch_is_main_branch():
Mai Hussien0731b4d2022-06-28 11:16:07 -07001508 return os.getenv("BUILDKITE_BRANCH") in ("master", "stable", "main", "google")
Philipp Wollermann1b5ecdc2021-06-10 21:52:55 +02001509
1510
Yun Peng92c0ef82021-07-26 10:41:21 +02001511def get_release_name_from_branch_name():
Yun Peng98948362022-11-08 16:14:20 +01001512 # TODO(pcloudy): Find a better way to do this
1513 if os.getenv("BUILDKITE_PIPELINE_SLUG") == "publish-bazel-binaries":
1514 return None
Yun Peng26e01ff2021-07-26 12:05:53 +02001515 res = re.match(r"release-(\d+\.\d+\.\d+(rc\d+)?).*", os.getenv("BUILDKITE_BRANCH"))
Yun Peng98948362022-11-08 16:14:20 +01001516 return res.group(1) if res else None
Yun Peng92c0ef82021-07-26 10:41:21 +02001517
1518
Jakob Buchgraber95e3d572018-02-21 18:48:49 +01001519def is_pull_request():
Jakob Buchgraber67761d32018-02-21 19:00:21 +01001520 third_party_repo = os.getenv("BUILDKITE_PULL_REQUEST_REPO", "")
Jakob Buchgraber95e3d572018-02-21 18:48:49 +01001521 return len(third_party_repo) > 0
1522
1523
Yun Penge3cf12d2018-12-05 15:01:09 +01001524def print_bazel_version_info(bazel_binary, platform):
Jakob Buchgraber99c4bbb2018-02-22 11:59:31 +01001525 print_collapsed_group(":information_source: Bazel Info")
Philipp Wollermannf13804b2019-02-05 21:08:30 +01001526 version_output = execute_command_and_get_output(
Florian Weikertb3439b32022-11-09 11:05:16 +01001527 [bazel_binary] + common_startup_flags() + ["--nosystem_rc", "--nohome_rc", "version"]
Philipp Wollermanncd5694c2019-01-03 14:53:04 +01001528 )
1529 execute_command(
Florian Weikertb3439b32022-11-09 11:05:16 +01001530 [bazel_binary] + common_startup_flags() + ["--nosystem_rc", "--nohome_rc", "info"]
Philipp Wollermanncd5694c2019-01-03 14:53:04 +01001531 )
Jakob Buchgraber7e690a72018-02-18 13:22:15 +01001532
Florian Weikertc8642af2019-02-03 23:58:51 +01001533 match = BUILD_LABEL_PATTERN.search(version_output)
1534 return match.group(1) if match else "unreleased binary"
1535
Jakob Buchgraber7e690a72018-02-18 13:22:15 +01001536
Yun Penga5a1ee02018-12-05 15:00:58 +01001537def print_environment_variables_info():
1538 print_collapsed_group(":information_source: Environment Variables")
1539 for key, value in os.environ.items():
1540 eprint("%s=(%s)" % (key, value))
1541
1542
Jakob Buchgraber426399e2018-03-20 19:45:46 +01001543def upload_bazel_binary(platform):
John Cater2f0aee22023-10-19 05:34:33 -04001544 if local_run_only():
1545 return
Jakob Buchgraber7d1d3bb2018-02-21 22:38:22 +01001546 print_collapsed_group(":gcloud: Uploading Bazel Under Test")
Jakob Buchgraber426399e2018-03-20 19:45:46 +01001547 if platform == "windows":
Philipp Wollermann10183212020-02-04 21:54:14 +01001548 binary_dir = r"bazel-bin\src"
1549 binary_name = r"bazel.exe"
Rupert Shuttleworth7f3a91e2020-08-22 07:31:47 -04001550 binary_nojdk_name = r"bazel_nojdk.exe"
Philipp Wollermann10183212020-02-04 21:54:14 +01001551 else:
1552 binary_dir = "bazel-bin/src"
1553 binary_name = "bazel"
Rupert Shuttleworth7f3a91e2020-08-22 07:31:47 -04001554 binary_nojdk_name = "bazel_nojdk"
Philipp Wollermann10183212020-02-04 21:54:14 +01001555 execute_command(["buildkite-agent", "artifact", "upload", binary_name], cwd=binary_dir)
Rupert Shuttleworth7f3a91e2020-08-22 07:31:47 -04001556 execute_command(["buildkite-agent", "artifact", "upload", binary_nojdk_name], cwd=binary_dir)
Jakob Buchgraber0b6a39d2018-02-17 00:45:14 +01001557
1558
Chi Wangb2b65682020-08-27 10:36:15 +08001559def merge_and_upload_kythe_kzip(platform, index_upload_gcs):
John Cater2f0aee22023-10-19 05:34:33 -04001560 if local_run_only():
1561 return
Chi Wang6357efe2020-08-25 16:23:38 +08001562 print_collapsed_group(":gcloud: Uploading kythe kzip")
1563
Philipp Wollermann2b4ee9f2021-02-11 16:32:35 +01001564 kzips = glob("bazel-out/*/extra_actions/**/*.kzip", recursive=True)
Chi Wang6357efe2020-08-25 16:23:38 +08001565
Chi Wang6357efe2020-08-25 16:23:38 +08001566 build_number = os.getenv("BUILDKITE_BUILD_NUMBER")
Chi Wangb2b65682020-08-27 10:36:15 +08001567 git_commit = os.getenv("BUILDKITE_COMMIT")
1568 final_kzip_name = "{}-{}-{}.kzip".format(build_number, platform, git_commit)
Chi Wang6357efe2020-08-25 16:23:38 +08001569
Chi Wangb2b65682020-08-27 10:36:15 +08001570 execute_command([f"{KYTHE_DIR}/tools/kzip", "merge", "--output", final_kzip_name] + kzips)
Chi Wang6357efe2020-08-25 16:23:38 +08001571 execute_command(["buildkite-agent", "artifact", "upload", final_kzip_name])
1572
Chi Wangb2b65682020-08-27 10:36:15 +08001573 if index_upload_gcs:
1574 pipeline = os.getenv("BUILDKITE_PIPELINE_SLUG")
Chi Wang08aea1c2022-03-21 18:04:52 +08001575 branch = os.getenv("BUILDKITE_BRANCH")
1576 destination = KZIPS_BUCKET + pipeline + "/" + branch + "/" + final_kzip_name
Chi Wangb2b65682020-08-27 10:36:15 +08001577 print("Uploading to GCS {}".format(destination))
Florian Weikertdb832a02020-11-19 19:14:48 +01001578 execute_command([gsutil_command(), "cp", final_kzip_name, destination])
Chi Wangb2b65682020-08-27 10:36:15 +08001579
Chi Wang6357efe2020-08-25 16:23:38 +08001580
Rupert Shuttleworth7f3a91e2020-08-22 07:31:47 -04001581def download_binary(dest_dir, platform, binary_name):
Philipp Wollermannc52e26a2019-05-18 22:10:47 +02001582 source_step = create_label(platform, "Bazel", build_only=True)
Philipp Wollermanncd5694c2019-01-03 14:53:04 +01001583 execute_command(
Philipp Wollermann10183212020-02-04 21:54:14 +01001584 ["buildkite-agent", "artifact", "download", binary_name, dest_dir, "--step", source_step]
Philipp Wollermanncd5694c2019-01-03 14:53:04 +01001585 )
Philipp Wollermann10183212020-02-04 21:54:14 +01001586 bazel_binary_path = os.path.join(dest_dir, binary_name)
Philipp Wollermann598b4a42018-02-19 17:03:36 +01001587 st = os.stat(bazel_binary_path)
1588 os.chmod(bazel_binary_path, st.st_mode | stat.S_IEXEC)
1589 return bazel_binary_path
Jakob Buchgraber0b6a39d2018-02-17 00:45:14 +01001590
1591
Rupert Shuttleworth7f3a91e2020-08-22 07:31:47 -04001592def download_bazel_binary(dest_dir, platform):
1593 binary_name = "bazel.exe" if platform == "windows" else "bazel"
1594 return download_binary(dest_dir, platform, binary_name)
1595
1596
1597def download_bazel_nojdk_binary(dest_dir, platform):
1598 binary_name = "bazel_nojdk.exe" if platform == "windows" else "bazel_nojdk"
1599 return download_binary(dest_dir, platform, binary_name)
1600
1601
Yun Pengd433d4f2022-12-09 10:53:15 +01001602def download_binary_at_commit(bazel_git_commit, bazel_binary_url, bazel_binary_path):
Yun Peng02312732019-01-17 18:17:05 +01001603 try:
Florian Weikertdb832a02020-11-19 19:14:48 +01001604 execute_command([gsutil_command(), "cp", bazel_binary_url, bazel_binary_path])
Yun Peng02312732019-01-17 18:17:05 +01001605 except subprocess.CalledProcessError as e:
Yun Pengd433d4f2022-12-09 10:53:15 +01001606 raise BuildkiteInfraException(
Philipp Wollermannc05ac682019-01-19 12:37:28 +01001607 "Failed to download Bazel binary at %s, error message:\n%s" % (bazel_git_commit, str(e))
1608 )
Yun Peng20d45602018-10-18 13:27:05 +02001609 st = os.stat(bazel_binary_path)
1610 os.chmod(bazel_binary_path, st.st_mode | stat.S_IEXEC)
1611 return bazel_binary_path
1612
Philipp Wollermanncd5694c2019-01-03 14:53:04 +01001613
Rupert Shuttleworth7f3a91e2020-08-22 07:31:47 -04001614def download_bazel_binary_at_commit(dest_dir, platform, bazel_git_commit):
1615 url = bazelci_builds_gs_url(platform, bazel_git_commit)
1616 path = os.path.join(dest_dir, "bazel.exe" if platform == "windows" else "bazel")
Yun Pengd433d4f2022-12-09 10:53:15 +01001617 return download_binary_at_commit(bazel_git_commit, url, path)
Rupert Shuttleworth7f3a91e2020-08-22 07:31:47 -04001618
1619
1620def download_bazel_nojdk_binary_at_commit(dest_dir, platform, bazel_git_commit):
1621 url = bazelci_builds_nojdk_gs_url(platform, bazel_git_commit)
1622 path = os.path.join(dest_dir, "bazel_nojdk.exe" if platform == "windows" else "bazel_nojdk")
Yun Pengd433d4f2022-12-09 10:53:15 +01001623 return download_binary_at_commit(bazel_git_commit, url, path)
Rupert Shuttleworth7f3a91e2020-08-22 07:31:47 -04001624
Florian Weikert7f21ca42022-02-02 17:35:23 +01001625
Chi Wangd61eb972023-01-18 11:36:18 +01001626def download_bazelci_agent(dest_dir):
Chi Wangaf6d5b12023-05-10 11:48:05 +00001627 repo = "bazelbuild/continuous-integration"
Chi Wang4412ef12023-05-10 16:29:22 +00001628 if THIS_IS_TESTING and "BAZELCI_AGENT_REPO" in os.environ:
Chi Wangaf6d5b12023-05-10 11:48:05 +00001629 repo = os.environ["BAZELCI_AGENT_REPO"]
Chi Wanga7436282024-12-06 21:27:06 +08001630 version = "0.2.6"
Chi Wang4412ef12023-05-10 16:29:22 +00001631 if THIS_IS_TESTING and "BAZELCI_AGENT_VERSION" in os.environ:
Chi Wangaf6d5b12023-05-10 11:48:05 +00001632 version = os.environ["BAZELCI_AGENT_VERSION"]
Chi Wangd279d582021-09-29 10:59:06 +08001633 postfix = ""
Florian Weikertda94a102022-10-21 12:24:37 +02001634 if is_windows():
Chi Wangd279d582021-09-29 10:59:06 +08001635 postfix = "x86_64-pc-windows-msvc.exe"
Florian Weikertda94a102022-10-21 12:24:37 +02001636 elif is_mac():
Florian Weikertb3439b32022-11-09 11:05:16 +01001637 if platform_module.machine() == "arm64":
Chi Wang74ad6ca2022-10-21 11:20:36 +02001638 postfix = "aarch64-apple-darwin"
1639 else:
1640 postfix = "x86_64-apple-darwin"
Chi Wangd279d582021-09-29 10:59:06 +08001641 else:
Chi Wangaac1ead2023-01-17 11:10:48 +01001642 if platform_module.machine() == "aarch64":
1643 postfix = "aarch64-unknown-linux-musl"
1644 else:
1645 postfix = "x86_64-unknown-linux-musl"
Chi Wangd279d582021-09-29 10:59:06 +08001646
1647 name = "bazelci-agent-{}-{}".format(version, postfix)
Florian Weikert9b805ce2023-05-10 18:04:03 +02001648 url = "https://github.com/{}/releases/download/agent-{}/{}".format(repo, version, name)
Florian Weikertda94a102022-10-21 12:24:37 +02001649 path = os.path.join(dest_dir, "bazelci-agent.exe" if is_windows() else "bazelci-agent")
Chi Wangd279d582021-09-29 10:59:06 +08001650 execute_command(["curl", "-sSL", url, "-o", path])
1651 st = os.stat(path)
1652 os.chmod(path, st.st_mode | stat.S_IEXEC)
1653 return path
Rupert Shuttleworth7f3a91e2020-08-22 07:31:47 -04001654
Florian Weikert7f21ca42022-02-02 17:35:23 +01001655
Florian Weikertda94a102022-10-21 12:24:37 +02001656def get_mirror_root():
1657 if is_mac():
1658 return "/usr/local/var/bazelbuild/"
1659 elif is_windows():
1660 return "c:\\buildkite\\bazelbuild\\"
joeleba7050d842019-05-23 17:03:31 +02001661
Florian Weikertda94a102022-10-21 12:24:37 +02001662 return "/var/lib/bazelbuild/"
joeleba7050d842019-05-23 17:03:31 +02001663
Florian Weikertc89d6142023-11-20 14:50:35 +01001664
Yun Pengfb86f672023-11-10 16:18:55 +01001665def get_repositories_root():
1666 """A root directory for preparing the reposioty to be tested."""
1667 repo_root = "/var/lib/buildkite-agent/builds/${BUILDKITE_AGENT_NAME}/${BUILDKITE_ORGANIZATION_SLUG}-org-repo-root"
1668 if is_mac():
1669 repo_root = "/Users/buildkite/builds/${BUILDKITE_AGENT_NAME}/${BUILDKITE_ORGANIZATION_SLUG}-org-repo-root"
1670 elif is_windows():
1671 repo_root = "c:/b/${BUILDKITE_AGENT_NAME}/${BUILDKITE_ORGANIZATION_SLUG}-org-repo-root"
1672 # If this is a Windows machine with a local SSD, the build directory is on drive D.
1673 if os.path.exists("d:/b"):
1674 repo_root = repo_root.replace("c:/b/", "d:/b/")
joeleba7050d842019-05-23 17:03:31 +02001675
Yun Pengfb86f672023-11-10 16:18:55 +01001676 repo_root = os.path.expandvars(repo_root)
1677
1678 if not os.path.exists(repo_root):
1679 os.makedirs(repo_root)
1680 return repo_root
1681
Yun Pengfb86f672023-11-10 16:18:55 +01001682
Florian Weikertc89d6142023-11-20 14:50:35 +01001683def clone_git_repository(git_repository, git_commit=None, suppress_stdout=False):
Yun Pengfb86f672023-11-10 16:18:55 +01001684 def execute_git_command(args):
1685 execute_command(args, print_output=not suppress_stdout, suppress_stdout=suppress_stdout)
1686
1687 root = get_repositories_root()
Philipp Wollermannff39ef52018-02-21 14:18:52 +01001688 project_name = re.search(r"/([^/]+)\.git$", git_repository).group(1)
Philipp Wollermann598b4a42018-02-19 17:03:36 +01001689 clone_path = os.path.join(root, project_name)
Yun Pengfb86f672023-11-10 16:18:55 +01001690 if not suppress_stdout:
1691 print_collapsed_group(
1692 "Fetching %s sources at %s" % (project_name, git_commit if git_commit else "HEAD")
1693 )
Philipp Wollermann438ec242018-09-05 14:39:24 +02001694
Florian Weikertda94a102022-10-21 12:24:37 +02001695 mirror_path = get_mirror_root() + re.sub(r"[^0-9A-Za-z]", "-", git_repository)
Philipp Wollermannea128282019-05-08 11:56:14 +02001696
Philipp Wollermann414703d2018-08-28 16:40:38 +02001697 if not os.path.exists(clone_path):
Philipp Wollermann62f4a032019-05-08 17:44:14 +02001698 if os.path.exists(mirror_path):
Yun Pengfb86f672023-11-10 16:18:55 +01001699 execute_git_command(
Philipp Wollermann62f4a032019-05-08 17:44:14 +02001700 ["git", "clone", "-v", "--reference", mirror_path, git_repository, clone_path]
Philipp Wollermanncd5694c2019-01-03 14:53:04 +01001701 )
Philipp Wollermannd4cd0d82018-05-01 09:56:24 +02001702 else:
Yun Pengfb86f672023-11-10 16:18:55 +01001703 execute_git_command(["git", "clone", "-v", git_repository, clone_path])
Jakob Buchgraber0b6a39d2018-02-17 00:45:14 +01001704
Philipp Wollermann414703d2018-08-28 16:40:38 +02001705 os.chdir(clone_path)
Yun Pengfb86f672023-11-10 16:18:55 +01001706 execute_git_command(["git", "remote", "set-url", "origin", git_repository])
1707 execute_git_command(["git", "clean", "-fdqx"])
1708 execute_git_command(["git", "submodule", "foreach", "--recursive", "git clean -fdqx"])
1709 execute_git_command(["git", "fetch", "origin"])
Yun Peng376d2b32018-11-29 10:24:54 +01001710 if git_commit:
1711 # sync to a specific commit of this repository
Yun Pengfb86f672023-11-10 16:18:55 +01001712 execute_git_command(["git", "reset", git_commit, "--hard"])
Yun Peng376d2b32018-11-29 10:24:54 +01001713 else:
1714 # sync to the latest commit of HEAD. Unlikely git pull this also works after a force push.
Philipp Wollermanncd5694c2019-01-03 14:53:04 +01001715 remote_head = (
1716 subprocess.check_output(["git", "symbolic-ref", "refs/remotes/origin/HEAD"])
1717 .decode("utf-8")
1718 .rstrip()
1719 )
Yun Pengfb86f672023-11-10 16:18:55 +01001720 execute_git_command(["git", "reset", remote_head, "--hard"])
1721 execute_git_command(["git", "submodule", "sync", "--recursive"])
1722 execute_git_command(["git", "submodule", "update", "--init", "--recursive", "--force"])
1723 execute_git_command(["git", "submodule", "foreach", "--recursive", "git reset --hard"])
1724 execute_git_command(["git", "clean", "-fdqx"])
1725 execute_git_command(["git", "submodule", "foreach", "--recursive", "git clean -fdqx"])
Yun Peng20d45602018-10-18 13:27:05 +02001726 return clone_path
Jakob Buchgraber0b6a39d2018-02-17 00:45:14 +01001727
Philipp Wollermann438ec242018-09-05 14:39:24 +02001728
Florian Weikertc89d6142023-11-20 14:50:35 +01001729def execute_batch_commands(
1730 commands, print_group=True, group_message=":batch: Setup (Batch Commands)"
1731):
Yun Penga935a542018-05-18 15:08:53 +02001732 if not commands:
1733 return
Florian Weikert4141ce92023-05-09 09:45:55 +02001734
1735 if print_group:
UebelAndre2e56c362023-06-13 04:38:15 -07001736 print_collapsed_group(group_message)
Florian Weikert4141ce92023-05-09 09:45:55 +02001737
Yun Penga935a542018-05-18 15:08:53 +02001738 batch_commands = "&".join(commands)
Jakob Buchgraber4a824412018-06-22 12:56:10 +02001739 return subprocess.run(batch_commands, shell=True, check=True, env=os.environ).returncode
Yun Penga935a542018-05-18 15:08:53 +02001740
Philipp Wollermann414703d2018-08-28 16:40:38 +02001741
Florian Weikertc89d6142023-11-20 14:50:35 +01001742def execute_shell_commands(
1743 commands, print_group=True, group_message=":bash: Setup (Shell Commands)"
1744):
Philipp Wollermann598b4a42018-02-19 17:03:36 +01001745 if not commands:
1746 return
Florian Weikert4141ce92023-05-09 09:45:55 +02001747
1748 if print_group:
UebelAndre2e56c362023-06-13 04:38:15 -07001749 print_collapsed_group(group_message)
Florian Weikert4141ce92023-05-09 09:45:55 +02001750
mostynb14440912020-03-17 17:11:47 +01001751 shell_command = "\n".join(["set -e"] + commands)
Philipp Wollermann3e1a7712018-02-19 17:34:24 +01001752 execute_command([shell_command], shell=True)
Jakob Buchgraber0b6a39d2018-02-17 00:45:14 +01001753
1754
Yun Peng0a6a98a2019-03-06 13:07:54 +01001755def handle_bazel_failure(exception, action):
1756 msg = "bazel {0} failed with exit code {1}".format(action, exception.returncode)
1757 if use_bazelisk_migrate():
1758 print_collapsed_group(msg)
1759 else:
1760 raise BuildkiteException(msg)
1761
1762
Yun Pengc85cd0e2022-09-02 10:44:29 +02001763def execute_bazel_run(bazel_binary, platform, targets):
Philipp Wollermann598b4a42018-02-19 17:03:36 +01001764 if not targets:
1765 return
1766 print_collapsed_group("Setup (Run Targets)")
1767 for target in targets:
Yun Peng0a6a98a2019-03-06 13:07:54 +01001768 try:
1769 execute_command(
1770 [bazel_binary]
1771 + bazelisk_flags()
Florian Weikertda94a102022-10-21 12:24:37 +02001772 + common_startup_flags()
Yun Peng0a6a98a2019-03-06 13:07:54 +01001773 + ["run"]
1774 + common_build_flags(None, platform)
Yun Peng0a6a98a2019-03-06 13:07:54 +01001775 + [target]
1776 )
1777 except subprocess.CalledProcessError as e:
1778 handle_bazel_failure(e, "run")
Jakob Buchgraber0b6a39d2018-02-17 00:45:14 +01001779
1780
Philipp Wollermannc4752e42022-02-08 16:35:39 +01001781def remote_caching_flags(platform, accept_cached=True):
Florian Weikertf3626a82024-09-02 17:13:08 +02001782 # Only enable caching for untrusted and testing builds, except for trusted MacOS VMs.
Florian Weikert6a8dad12024-09-10 18:26:34 +02001783 if THIS_IS_TRUSTED and not is_mac():
Philipp Wollermann2409c6e2018-08-07 07:37:54 +02001784 return []
Yun Peng07dafc52022-03-16 13:23:35 +01001785 # We don't enable remote caching on the Linux ARM64 machine since it doesn't have access to GCS.
1786 if platform == "ubuntu2004_arm64":
1787 return []
1788
Florian Weikert09012952023-07-12 14:07:39 +02001789 platform_cache_key = [
1790 BUILDKITE_ORG.encode("utf-8"),
1791 # Whenever the remote cache was known to have been poisoned increase the number below
Xùdōng Yáng9db28ad2024-11-06 00:05:11 +01001792 "cache-poisoning-20241105".encode("utf-8"),
Florian Weikert09012952023-07-12 14:07:39 +02001793 platform.encode("utf-8"),
1794 ]
1795
Florian Weikertda94a102022-10-21 12:24:37 +02001796 if is_mac():
Philipp Wollermannfce92bf2019-05-22 15:14:32 +02001797 platform_cache_key += [
Philipp Wollermannef89d2f2019-04-18 15:52:24 +02001798 # macOS version:
1799 subprocess.check_output(["/usr/bin/sw_vers", "-productVersion"]),
1800 # Path to Xcode:
1801 subprocess.check_output(["/usr/bin/xcode-select", "-p"]),
1802 # Xcode version:
1803 subprocess.check_output(["/usr/bin/xcodebuild", "-version"]),
1804 ]
Yun Peng1ce55be2023-08-11 16:19:26 +02001805
Florian Weikert6a8dad12024-09-10 18:26:34 +02001806 # Use GCS for caching builds running on MacService.
1807 # Use RBE for caching builds running on GCE.
1808 remote_cache_flags = []
1809 if is_mac():
1810 bucket_id = "trusted" if THIS_IS_TRUSTED else "untrusted"
1811 remote_cache_flags = [
1812 f"--remote_cache=https://storage.googleapis.com/bazel-{bucket_id}-build-cache"
1813 ]
Philipp Wollermannef89d2f2019-04-18 15:52:24 +02001814 else:
Florian Weikert6a8dad12024-09-10 18:26:34 +02001815 remote_cache_flags = [
1816 "--remote_cache=remotebuildexecution.googleapis.com",
Florian Weikerte425b912024-10-02 00:22:41 +02001817 "--remote_instance_name=projects/{}/instances/default_instance".format(CLOUD_PROJECT),
Florian Weikert6a8dad12024-09-10 18:26:34 +02001818 ]
1819
1820 flags = (
1821 remote_cache_flags
1822 + [
1823 "--google_default_credentials",
1824 ]
1825 + (
1826 []
1827 if is_mac()
1828 else [ # Re-enable on macOS once b/346751326 is resolved.
1829 # Enable BES / Build Results reporting.
1830 "--bes_backend=buildeventservice.googleapis.com",
1831 "--bes_timeout=360s",
1832 "--project_id=bazel-untrusted",
Florian Weikertee5ea982024-06-21 19:59:31 +02001833 ]
Florian Weikertee5ea982024-06-21 19:59:31 +02001834 )
Florian Weikert6a8dad12024-09-10 18:26:34 +02001835 )
Philipp Wollermann380f1e62019-04-12 16:45:27 +02001836
1837 platform_cache_digest = hashlib.sha256()
1838 for key in platform_cache_key:
Philipp Wollermannbda4b7d2019-05-16 20:04:17 +02001839 eprint("Adding to platform cache key: {}".format(key))
Philipp Wollermann380f1e62019-04-12 16:45:27 +02001840 platform_cache_digest.update(key)
1841 platform_cache_digest.update(b":")
1842
Florian Weikert6a8dad12024-09-10 18:26:34 +02001843 remote_timeout = 3600 if is_mac() else 60
Philipp Wollermannbda4b7d2019-05-16 20:04:17 +02001844 flags += [
Florian Weikert04ac4292023-11-21 17:04:54 +01001845 f"--remote_timeout={remote_timeout}",
Philipp Wollermann639c0452019-01-03 11:23:54 +01001846 "--remote_max_connections=200",
Philipp Wollermannbda4b7d2019-05-16 20:04:17 +02001847 '--remote_default_platform_properties=properties:{name:"cache-silo-key" value:"%s"}'
1848 % platform_cache_digest.hexdigest(),
Yun Peng5985f742023-04-17 11:32:42 +02001849 "--remote_download_toplevel",
Philipp Wollermann639c0452019-01-03 11:23:54 +01001850 ]
Jakob Buchgraber4f1d2712018-02-20 10:22:47 +01001851
Yun Pengb2fbce32022-02-04 12:56:34 +01001852 if not accept_cached:
1853 flags += ["--noremote_accept_cached"]
1854
Philipp Wollermannd96d8fa2019-01-11 14:37:47 +01001855 return flags
1856
Jakob Buchgraber4f1d2712018-02-20 10:22:47 +01001857
Jakob Buchgraberb4342cd2018-02-20 16:35:07 +01001858def remote_enabled(flags):
1859 # Detect if the project configuration enabled its own remote caching / execution.
Philipp Wollermann2409c6e2018-08-07 07:37:54 +02001860 remote_flags = ["--remote_executor", "--remote_cache", "--remote_http_cache"]
Jakob Buchgraberb4342cd2018-02-20 16:35:07 +01001861 for flag in flags:
1862 for remote_flag in remote_flags:
1863 if flag.startswith(remote_flag):
1864 return True
1865 return False
1866
Jakob Buchgraber95e3d572018-02-21 18:48:49 +01001867
Philipp Wollermann2409c6e2018-08-07 07:37:54 +02001868def concurrent_jobs(platform):
1869 return "75" if platform.startswith("rbe_") else str(multiprocessing.cpu_count())
Jakob Buchgraber51a83662018-02-22 19:49:24 +01001870
1871
Philipp Wollermann3e28d3b2018-02-23 23:19:37 +01001872def concurrent_test_jobs(platform):
Philipp Wollermann2409c6e2018-08-07 07:37:54 +02001873 if platform.startswith("rbe_"):
1874 return "75"
Florian Weikertda94a102022-10-21 12:24:37 +02001875 elif is_windows():
Jakob Buchgrabere3ccda32018-06-22 23:29:48 +02001876 return "8"
Florian Weikertda94a102022-10-21 12:24:37 +02001877 elif is_mac() and THIS_IS_TESTING:
Philipp Wollermann85877cb2021-07-02 19:20:33 +02001878 return "4"
Florian Weikertda94a102022-10-21 12:24:37 +02001879 elif is_mac():
Florian Weikertecf091c2023-04-28 10:22:23 +02001880 # TODO(twerth): This is an experiment, remove.
1881 return str(int(multiprocessing.cpu_count() / 2))
Philipp Wollermann2409c6e2018-08-07 07:37:54 +02001882 return "12"
Philipp Wollermann3e28d3b2018-02-23 23:19:37 +01001883
Philipp Wollermann2409c6e2018-08-07 07:37:54 +02001884
Florian Weikertda94a102022-10-21 12:24:37 +02001885def common_startup_flags():
1886 if is_windows():
Philipp Wollermannd5ab3d92020-02-05 16:55:13 +01001887 if os.path.exists("D:/b"):
1888 # This machine has a local SSD mounted as drive D.
1889 return ["--output_user_root=D:/b"]
1890 else:
1891 # This machine uses its PD-SSD as the build directory.
1892 return ["--output_user_root=C:/b"]
Florian Weikert6a8dad12024-09-10 18:26:34 +02001893 elif is_mac():
Florian Weikert04ac4292023-11-21 17:04:54 +01001894 return ["--host_jvm_args=-Djava.net.preferIPv6Addresses=true"]
Philipp Wollermannd5ab3d92020-02-05 16:55:13 +01001895 return []
Yun Peng58977d62018-11-16 12:19:20 +01001896
1897
1898def common_build_flags(bep_file, platform):
Yun Peng088cc932018-11-16 12:11:46 +01001899 flags = [
Yun Pengf51e7842018-11-16 11:35:43 +01001900 "--show_progress_rate_limit=5",
1901 "--curses=yes",
1902 "--color=yes",
Philipp Wollermannd99414c2019-05-28 17:26:09 +02001903 "--terminal_columns=143",
Philipp Wollermann4c8391e2019-05-28 18:03:35 +02001904 "--show_timestamps",
Yun Pengf51e7842018-11-16 11:35:43 +01001905 "--verbose_failures",
Yun Pengf51e7842018-11-16 11:35:43 +01001906 "--jobs=" + concurrent_jobs(platform),
Yun Pengf51e7842018-11-16 11:35:43 +01001907 "--announce_rc",
Philipp Wollermannb97f9102019-04-16 18:05:56 +02001908 "--experimental_repository_cache_hardlinks",
Philipp Wollermannef89d2f2019-04-18 15:52:24 +02001909 # Some projects set --disk_cache in their project-specific bazelrc, which we never want on
1910 # CI, so let's just disable it explicitly.
1911 "--disk_cache=",
Yun Peng088cc932018-11-16 12:11:46 +01001912 ]
Philipp Wollermann639c0452019-01-03 11:23:54 +01001913
Florian Weikertda94a102022-10-21 12:24:37 +02001914 if is_windows():
Philipp Wollermannb97f9102019-04-16 18:05:56 +02001915 pass
Florian Weikertda94a102022-10-21 12:24:37 +02001916 elif is_mac():
Philipp Wollermannb97f9102019-04-16 18:05:56 +02001917 flags += [
1918 "--sandbox_writable_path=/var/tmp/_bazel_buildkite/cache/repos/v1",
1919 "--test_env=REPOSITORY_CACHE=/var/tmp/_bazel_buildkite/cache/repos/v1",
1920 ]
1921 else:
Philipp Wollermann639c0452019-01-03 11:23:54 +01001922 flags += ["--sandbox_tmpfs_path=/tmp"]
1923
Yun Peng088cc932018-11-16 12:11:46 +01001924 if bep_file:
Philipp Wollermann639c0452019-01-03 11:23:54 +01001925 flags += [
1926 "--experimental_build_event_json_file_path_conversion=false",
1927 "--build_event_json_file=" + bep_file,
1928 ]
1929
Florian Weikert6a8dad12024-09-10 18:26:34 +02001930 if is_mac():
Florian Weikert04ac4292023-11-21 17:04:54 +01001931 flags += ["--jvmopt=-Djava.net.preferIPv6Addresses"]
1932
Yun Peng088cc932018-11-16 12:11:46 +01001933 return flags
Philipp Wollermann94bd9e32018-04-30 15:32:28 +02001934
Philipp Wollermann2409c6e2018-08-07 07:37:54 +02001935
Yun Pengb7247ff2018-11-15 13:52:39 +01001936def rbe_flags(original_flags, accept_cached):
Philipp Wollermannbcfd9da2018-08-09 15:31:18 +02001937 # Enable remote execution via RBE.
Philipp Wollermann2409c6e2018-08-07 07:37:54 +02001938 flags = [
1939 "--remote_executor=remotebuildexecution.googleapis.com",
Philipp Wollermann1403d2c2019-01-10 13:15:51 +01001940 "--remote_instance_name=projects/bazel-untrusted/instances/default_instance",
Philipp Wollermann2df93482021-06-16 04:39:49 +02001941 "--remote_timeout=3600",
Philipp Wollermann57dadb82020-02-17 14:32:24 +01001942 "--incompatible_strict_action_env",
Philipp Wollermanncd5694c2019-01-03 14:53:04 +01001943 "--google_default_credentials",
Daniel Wagner-Hall1ef88892024-12-11 13:39:03 +00001944 "--toolchain_resolution_debug=.*",
Yun Peng5985f742023-04-17 11:32:42 +02001945 "--remote_download_toplevel",
Philipp Wollermann2409c6e2018-08-07 07:37:54 +02001946 ]
Philipp Wollermanne1318eb2018-08-13 15:08:01 +02001947
Philipp Wollermannbcfd9da2018-08-09 15:31:18 +02001948 # Enable BES / Build Results reporting.
1949 flags += [
1950 "--bes_backend=buildeventservice.googleapis.com",
Philipp Wollermannbcfd9da2018-08-09 15:31:18 +02001951 "--bes_timeout=360s",
Florian Weikert736073b2023-11-22 15:10:20 +01001952 "--project_id=bazel-untrusted",
Philipp Wollermannbcfd9da2018-08-09 15:31:18 +02001953 ]
Philipp Wollermanne1318eb2018-08-13 15:08:01 +02001954
Philipp Wollermann2409c6e2018-08-07 07:37:54 +02001955 if not accept_cached:
1956 flags += ["--noremote_accept_cached"]
Philipp Wollermannbcfd9da2018-08-09 15:31:18 +02001957
Nicolas Lopez36996222019-05-28 12:21:28 -04001958 # Adapted from https://github.com/bazelbuild/bazel-toolchains/blob/master/bazelrc/.bazelrc
Philipp Wollermann2409c6e2018-08-07 07:37:54 +02001959 flags += [
Nicolas Lopez36996222019-05-28 12:21:28 -04001960 # These should NOT longer need to be modified.
1961 # All that is needed is updating the @bazel_toolchains repo pin
1962 # in projects' WORKSPACE files.
Xindb02c012018-11-07 14:10:54 -05001963 #
Philipp Wollermann2409c6e2018-08-07 07:37:54 +02001964 # Toolchain related flags to append at the end of your .bazelrc file.
Nicolas Lopez36996222019-05-28 12:21:28 -04001965 "--host_javabase=@buildkite_config//java:jdk",
1966 "--javabase=@buildkite_config//java:jdk",
Philipp Wollermann2409c6e2018-08-07 07:37:54 +02001967 "--host_java_toolchain=@bazel_tools//tools/jdk:toolchain_hostjdk8",
1968 "--java_toolchain=@bazel_tools//tools/jdk:toolchain_hostjdk8",
Nicolas Lopez36996222019-05-28 12:21:28 -04001969 "--crosstool_top=@buildkite_config//cc:toolchain",
Philipp Wollermann2409c6e2018-08-07 07:37:54 +02001970 "--action_env=BAZEL_DO_NOT_DETECT_CPP_TOOLCHAIN=1",
Philipp Wollermann2409c6e2018-08-07 07:37:54 +02001971 ]
Philipp Wollermanne1318eb2018-08-13 15:08:01 +02001972
Yun Pengb7247ff2018-11-15 13:52:39 +01001973 # Platform flags:
1974 # The toolchain container used for execution is defined in the target indicated
1975 # by "extra_execution_platforms", "host_platform" and "platforms".
Xin03a88ab2019-03-11 19:18:50 -04001976 # If you are using your own toolchain container, you need to create a platform
1977 # target with "constraint_values" that allow for the toolchain specified with
1978 # "extra_toolchains" to be selected (given constraints defined in
1979 # "exec_compatible_with").
Yun Pengb7247ff2018-11-15 13:52:39 +01001980 # More about platforms: https://docs.bazel.build/versions/master/platforms.html
1981 # Don't add platform flags if they are specified already.
1982 platform_flags = {
Nicolas Lopez36996222019-05-28 12:21:28 -04001983 "--extra_toolchains": "@buildkite_config//config:cc-toolchain",
1984 "--extra_execution_platforms": "@buildkite_config//config:platform",
1985 "--host_platform": "@buildkite_config//config:platform",
1986 "--platforms": "@buildkite_config//config:platform",
Yun Pengb7247ff2018-11-15 13:52:39 +01001987 }
Yun Peng67ab5062018-11-15 13:58:15 +01001988 for platform_flag, value in list(platform_flags.items()):
Yun Pengb7247ff2018-11-15 13:52:39 +01001989 found = False
1990 for original_flag in original_flags:
1991 if original_flag.startswith(platform_flag):
1992 found = True
1993 break
1994 if not found:
1995 flags += [platform_flag + "=" + value]
1996
Philipp Wollermann2409c6e2018-08-07 07:37:54 +02001997 return flags
1998
1999
Yun Penge8838ad2025-01-02 11:06:14 +01002000def run_bazel_version(bazel_binary, max_retry=5):
2001 """Run `bazel --version` to make sure Bazel is fetched by Bazelisk."""
2002 # Retry to address https://github.com/bazelbuild/continuous-integration/issues/2123
2003 retry = 0
2004 while retry < max_retry:
2005 exit_code = execute_command([bazel_binary, "--version"], fail_if_nonzero=False)
2006 if exit_code == 0:
2007 return
2008 retry += 1
2009 time.sleep(1)
2010
2011
Yun Peng28a9a532023-05-10 14:19:46 +02002012def get_output_base(bazel_binary):
Yun Peng2b6de292022-02-10 14:28:06 +01002013 return execute_command_and_get_output(
Florian Weikertda94a102022-10-21 12:24:37 +02002014 [bazel_binary] + common_startup_flags() + ["info", "output_base"],
Yun Peng2b6de292022-02-10 14:28:06 +01002015 print_output=False,
2016 ).strip()
2017
2018
Florian Weikertb3439b32022-11-09 11:05:16 +01002019def compute_flags(platform, flags, bep_file, bazel_binary, enable_remote_cache=False):
Yun Peng58977d62018-11-16 12:19:20 +01002020 aggregated_flags = common_build_flags(bep_file, platform)
John Cater2f0aee22023-10-19 05:34:33 -04002021
2022 if not local_run_only():
2023 if not remote_enabled(flags):
2024 if platform.startswith("rbe_"):
2025 aggregated_flags += rbe_flags(flags, accept_cached=enable_remote_cache)
2026 else:
Florian Weikertc89d6142023-11-20 14:50:35 +01002027 aggregated_flags += remote_caching_flags(
2028 platform, accept_cached=enable_remote_cache
2029 )
Yun Peng75786552018-11-13 15:23:30 +01002030 aggregated_flags += flags
Yun Peng53598002018-12-03 10:42:02 +01002031
Philipp Wollermann87b45252020-01-22 12:43:42 +01002032 for i, flag in enumerate(aggregated_flags):
2033 if "$HOME" in flag:
Florian Weikerta6110a92022-10-20 01:16:09 +02002034 if is_windows():
Philipp Wollermannd5ab3d92020-02-05 16:55:13 +01002035 if os.path.exists("D:/"):
2036 home = "D:"
2037 else:
2038 home = "C:/b"
Florian Weikerta6110a92022-10-20 01:16:09 +02002039 elif is_mac():
Philipp Wollermannd5ab3d92020-02-05 16:55:13 +01002040 home = "/Users/buildkite"
2041 else:
2042 home = "/var/lib/buildkite-agent"
Philipp Wollermann87b45252020-01-22 12:43:42 +01002043 aggregated_flags[i] = flag.replace("$HOME", home)
2044 if "$OUTPUT_BASE" in flag:
Yun Peng28a9a532023-05-10 14:19:46 +02002045 output_base = get_output_base(bazel_binary)
Philipp Wollermann87b45252020-01-22 12:43:42 +01002046 aggregated_flags[i] = flag.replace("$OUTPUT_BASE", output_base)
2047
Yun Pengd6443e22023-11-13 12:56:29 +01002048 if is_downstream_pipeline():
2049 # If we are in a downstream pipeline, turn off the lockfile update since changing Bazel version could affect the lockfile.
2050 aggregated_flags.append("--lockfile_mode=off")
Yun Pengd6443e22023-11-13 12:56:29 +01002051
Florian Weikert24a4b482018-11-30 19:05:38 +01002052 return aggregated_flags
Yun Peng53598002018-12-03 10:42:02 +01002053
Philipp Wollermann2409c6e2018-08-07 07:37:54 +02002054
Yun Pengea0359e2019-01-17 15:37:47 +01002055def execute_bazel_clean(bazel_binary, platform):
2056 print_expanded_group(":bazel: Clean")
2057
2058 try:
Florian Weikertda94a102022-10-21 12:24:37 +02002059 execute_command([bazel_binary] + common_startup_flags() + ["clean", "--expunge"])
Yun Pengea0359e2019-01-17 15:37:47 +01002060 except subprocess.CalledProcessError as e:
2061 raise BuildkiteException("bazel clean failed with exit code {}".format(e.returncode))
2062
2063
Chi Wang6357efe2020-08-25 16:23:38 +08002064def kythe_startup_flags():
2065 return [f"--bazelrc={KYTHE_DIR}/extractors.bazelrc"]
2066
2067
2068def kythe_build_flags():
Philipp Wollermannf61a20b2021-05-05 22:04:20 +02002069 return [
2070 "--experimental_convenience_symlinks=normal",
Florian Weikert9d5e4c02021-05-27 15:10:20 +02002071 f"--override_repository=kythe_release={KYTHE_DIR}",
Philipp Wollermannf61a20b2021-05-05 22:04:20 +02002072 ]
Chi Wang6357efe2020-08-25 16:23:38 +08002073
2074
Florian Weikertb3439b32022-11-09 11:05:16 +01002075def execute_bazel_build(bazel_version, bazel_binary, platform, flags, targets, bep_file):
Philipp Wollermannbda4b7d2019-05-16 20:04:17 +02002076 print_collapsed_group(":bazel: Computing flags for build step")
Philipp Wollermanncd5694c2019-01-03 14:53:04 +01002077 aggregated_flags = compute_flags(
Yun Peng8975c6b2019-02-28 11:55:55 +01002078 platform,
2079 flags,
Yun Peng8975c6b2019-02-28 11:55:55 +01002080 bep_file,
Philipp Wollermann87b45252020-01-22 12:43:42 +01002081 bazel_binary,
Florian Weikert29cb7ec2019-03-07 14:52:18 +01002082 enable_remote_cache=True,
Philipp Wollermanncd5694c2019-01-03 14:53:04 +01002083 )
Philipp Wollermannbda4b7d2019-05-16 20:04:17 +02002084
2085 print_expanded_group(":bazel: Build ({})".format(bazel_version))
Jakob Buchgraber95e3d572018-02-21 18:48:49 +01002086 try:
Philipp Wollermanncd5694c2019-01-03 14:53:04 +01002087 execute_command(
Florian Weikert29cb7ec2019-03-07 14:52:18 +01002088 [bazel_binary]
2089 + bazelisk_flags()
Florian Weikertda94a102022-10-21 12:24:37 +02002090 + common_startup_flags()
Florian Weikert29cb7ec2019-03-07 14:52:18 +01002091 + ["build"]
2092 + aggregated_flags
Philipp Wollermann2a160432019-09-19 15:57:28 +02002093 + ["--"]
Florian Weikert29cb7ec2019-03-07 14:52:18 +01002094 + targets
Philipp Wollermanncd5694c2019-01-03 14:53:04 +01002095 )
Jakob Buchgraber95e3d572018-02-21 18:48:49 +01002096 except subprocess.CalledProcessError as e:
Yun Peng0a6a98a2019-03-06 13:07:54 +01002097 handle_bazel_failure(e, "build")
Jakob Buchgraber0b6a39d2018-02-17 00:45:14 +01002098
2099
Florian Weikertb3439b32022-11-09 11:05:16 +01002100def execute_bazel_build_with_kythe(bazel_version, bazel_binary, platform, flags, targets, bep_file):
Chi Wang6357efe2020-08-25 16:23:38 +08002101 print_collapsed_group(":bazel: Computing flags for build step")
2102 aggregated_flags = compute_flags(
2103 platform,
2104 flags,
Chi Wang6357efe2020-08-25 16:23:38 +08002105 bep_file,
2106 bazel_binary,
2107 enable_remote_cache=False,
2108 )
2109
2110 print_expanded_group(":bazel: Build ({})".format(bazel_version))
2111
2112 execute_command(
2113 [bazel_binary]
2114 + bazelisk_flags()
Florian Weikertda94a102022-10-21 12:24:37 +02002115 + common_startup_flags()
Chi Wang6357efe2020-08-25 16:23:38 +08002116 + kythe_startup_flags()
2117 + ["build"]
2118 + kythe_build_flags()
2119 + aggregated_flags
2120 + ["--"]
2121 + targets
2122 )
2123
2124
Florian Weikert7dd9f662023-05-04 15:05:42 +02002125def calculate_targets(
Florian Weikert5e70d9d2023-05-08 19:20:23 +02002126 task_config,
2127 bazel_binary,
2128 build_only,
2129 test_only,
2130 workspace_dir,
2131 ws_setup_func,
2132 git_commit,
2133 test_flags,
Florian Weikert7dd9f662023-05-04 15:05:42 +02002134):
Florian Weikerte417f9f2023-05-05 17:33:46 +02002135 print_collapsed_group(":dart: Calculating targets")
2136
Alexandre Rostovtsev42bbeb52023-08-24 04:08:04 -04002137 build_targets = [] if test_only else list(task_config.get("build_targets", []))
2138 test_targets = [] if build_only else list(task_config.get("test_targets", []))
Florian Weikertc89d6142023-11-20 14:50:35 +01002139 coverage_targets = (
2140 [] if (build_only or test_only) else list(task_config.get("coverage_targets", []))
2141 )
Alexandre Rostovtsev42bbeb52023-08-24 04:08:04 -04002142 index_targets = [] if (build_only or test_only) else list(task_config.get("index_targets", []))
Chi Wang6357efe2020-08-25 16:23:38 +08002143
Florian Weikertdb832a02020-11-19 19:14:48 +01002144 index_targets_query = (
2145 None if (build_only or test_only) else task_config.get("index_targets_query", None)
2146 )
Chi Wang6357efe2020-08-25 16:23:38 +08002147 if index_targets_query:
2148 output = execute_command_and_get_output(
2149 [bazel_binary]
Florian Weikertda94a102022-10-21 12:24:37 +02002150 + common_startup_flags()
Yun Pengeb435bb2023-11-17 10:37:42 +01002151 + ["--nosystem_rc", "--nohome_rc", "query", "--lockfile_mode=off", index_targets_query],
Chi Wang6357efe2020-08-25 16:23:38 +08002152 print_output=False,
2153 )
2154 index_targets += output.strip().split("\n")
Florian Weikert736d06e2019-05-08 13:16:42 +02002155
Philipp Wollermann2a160432019-09-19 15:57:28 +02002156 # Remove the "--" argument splitter from the list that some configs explicitly
2157 # include. We'll add it back again later where needed.
2158 build_targets = [x.strip() for x in build_targets if x.strip() != "--"]
2159 test_targets = [x.strip() for x in test_targets if x.strip() != "--"]
Xavier Bonaventura3755ea02022-02-02 13:56:54 +01002160 coverage_targets = [x.strip() for x in coverage_targets if x.strip() != "--"]
Chi Wang6357efe2020-08-25 16:23:38 +08002161 index_targets = [x.strip() for x in index_targets if x.strip() != "--"]
Philipp Wollermann2a160432019-09-19 15:57:28 +02002162
Florian Weikertdcbbdf32023-05-02 11:58:53 +02002163 diffbase = os.getenv(USE_BAZEL_DIFF_ENV_VAR, "").lower()
2164 shard_id = int(os.getenv("BUILDKITE_PARALLEL_JOB", "-1"))
2165 shard_count = int(os.getenv("BUILDKITE_PARALLEL_JOB_COUNT", "-1"))
2166 sharding_enabled = shard_id > -1 and shard_count > -1
2167
Yun Pengfb86f672023-11-10 16:18:55 +01002168 use_bazel_diff = diffbase and can_use_bazel_diff(git_commit)
Florian Weikerte417f9f2023-05-05 17:33:46 +02002169
Florian Weikertdcbbdf32023-05-02 11:58:53 +02002170 # Skip target expansion if we don't need to calculate test targets
Florian Weikerte417f9f2023-05-05 17:33:46 +02002171 if not use_bazel_diff and not sharding_enabled:
Florian Weikertdcbbdf32023-05-02 11:58:53 +02002172 return build_targets, test_targets, coverage_targets, index_targets
2173
Florian Weikert7dd9f662023-05-04 15:05:42 +02002174 expanded_test_targets = expand_test_target_patterns(bazel_binary, test_targets, test_flags)
Florian Weikertecf091c2023-04-28 10:22:23 +02002175
Florian Weikertecf091c2023-04-28 10:22:23 +02002176 actual_test_targets = (
2177 filter_unchanged_targets(
Florian Weikert5e70d9d2023-05-08 19:20:23 +02002178 expanded_test_targets,
2179 workspace_dir,
2180 ws_setup_func,
2181 bazel_binary,
2182 diffbase,
2183 git_commit,
Florian Weikertecf091c2023-04-28 10:22:23 +02002184 )
Florian Weikerte417f9f2023-05-05 17:33:46 +02002185 if use_bazel_diff
Florian Weikertecf091c2023-04-28 10:22:23 +02002186 else expanded_test_targets
2187 )
2188
Florian Weikertdcbbdf32023-05-02 11:58:53 +02002189 if sharding_enabled:
Florian Weikert736d06e2019-05-08 13:16:42 +02002190 print_collapsed_group(
2191 ":female-detective: Calculating targets for shard {}/{}".format(
2192 shard_id + 1, shard_count
2193 )
2194 )
Florian Weikertb6115442024-10-02 15:39:23 +02002195 sorted_test_targets = sorted(actual_test_targets)
2196 actual_test_targets = get_targets_for_shard(sorted_test_targets, shard_id, shard_count)
2197
2198 if shard_id == 0:
2199 upload_shard_distribution(sorted_test_targets, shard_count)
Florian Weikert736d06e2019-05-08 13:16:42 +02002200
Florian Weikertecf091c2023-04-28 10:22:23 +02002201 return build_targets, actual_test_targets, coverage_targets, index_targets
Florian Weikert736d06e2019-05-08 13:16:42 +02002202
2203
Yun Pengfb86f672023-11-10 16:18:55 +01002204def can_use_bazel_diff(git_commit):
Florian Weikerte417f9f2023-05-05 17:33:46 +02002205 matched_files = []
Yun Pengfb86f672023-11-10 16:18:55 +01002206 for f in get_modified_files(git_commit):
Florian Weikerte417f9f2023-05-05 17:33:46 +02002207 for d in DISABLE_BAZEL_DIFF_IF_MODIFIED:
2208 if d.endswith("/") and f.startswith(d) or f == d:
2209 matched_files.append(f)
2210
2211 if matched_files:
2212 eprint(
2213 "Cannot enable bazel-diff since the following files were modified:\n\t{}".format(
2214 "\n\t".join(sorted(matched_files))
2215 )
2216 )
2217
2218 return not matched_files
2219
2220
Florian Weikert7dd9f662023-05-04 15:05:42 +02002221def expand_test_target_patterns(bazel_binary, test_targets, test_flags):
2222 if not test_targets:
2223 return []
Florian Weikert736d06e2019-05-08 13:16:42 +02002224
Florian Weikert7dd9f662023-05-04 15:05:42 +02002225 print_collapsed_group(":ninja: Resolving test targets via bazel query")
Philipp Wollermann2a160432019-09-19 15:57:28 +02002226
Florian Weikert736d06e2019-05-08 13:16:42 +02002227 output = execute_command_and_get_output(
2228 [bazel_binary]
Florian Weikertda94a102022-10-21 12:24:37 +02002229 + common_startup_flags()
Florian Weikert736d06e2019-05-08 13:16:42 +02002230 + [
Yun Peng8e8cdcf2022-03-16 13:08:15 +01002231 "--nosystem_rc",
2232 "--nohome_rc",
Florian Weikert5242a592023-05-04 19:19:28 +02002233 "cquery" if os.getenv("EXP_USE_CQUERY") else "query",
Yun Pengeb435bb2023-11-17 10:37:42 +01002234 "--lockfile_mode=off",
Florian Weikert7dd9f662023-05-04 15:05:42 +02002235 get_test_query(test_targets, test_flags),
Florian Weikert736d06e2019-05-08 13:16:42 +02002236 ],
2237 print_output=False,
Philipp Wollermann54719fe2021-07-02 21:54:08 +02002238 ).strip()
2239 return output.split("\n") if output else []
Florian Weikert736d06e2019-05-08 13:16:42 +02002240
2241
Florian Weikert7dd9f662023-05-04 15:05:42 +02002242def get_test_query(test_targets, test_flags):
Yun Peng68193b32024-08-06 18:19:20 +02002243 included_targets, excluded_targets, added_back = partition_list(test_targets)
Florian Weikert7dd9f662023-05-04 15:05:42 +02002244
2245 def FormatTargetList(targets):
2246 return " ".join("'{}'".format(t) for t in targets)
2247
Florian Weikert38a99212023-05-04 15:12:41 +02002248 query = "let t = tests(set({})) in $t".format(FormatTargetList(included_targets))
Florian Weikert7dd9f662023-05-04 15:05:42 +02002249
2250 if excluded_targets:
2251 query += " except tests(set({}))".format(FormatTargetList(excluded_targets))
2252
Yun Peng68193b32024-08-06 18:19:20 +02002253 if added_back:
2254 query += " union tests(set({}))".format(FormatTargetList(added_back))
2255
Florian Weikert7dd9f662023-05-04 15:05:42 +02002256 included_tags, excluded_tags = get_test_tags(test_flags)
2257
2258 for t in excluded_tags:
Florian Weikert38a99212023-05-04 15:12:41 +02002259 query += " except attr('tags', '\\b{}\\b', $t)".format(t)
Florian Weikert7dd9f662023-05-04 15:05:42 +02002260
2261 if included_tags:
Florian Weikert38a99212023-05-04 15:12:41 +02002262 parts = ["attr('tags', '\\b{}\\b', $tt)".format(t) for t in included_tags]
Florian Weikert7dd9f662023-05-04 15:05:42 +02002263 query = "let tt = {} in {}".format(query, " union ".join(parts))
2264
2265 return query
2266
2267
2268def get_test_tags(test_flags):
2269 wanted_prefix = "--test_tag_filters="
2270
2271 for f in test_flags:
2272 if not f.startswith(wanted_prefix):
2273 continue
2274
Florian Weikertc833a7c2023-05-05 21:26:36 +02002275 tags = removeprefix(f, wanted_prefix).split(",")
Yun Pengc948ad62024-08-06 18:35:27 +02002276 include, exclude, _ = partition_list(tags)
Florian Weikert7dd9f662023-05-04 15:05:42 +02002277
2278 # Skip tests tagged as "manual" by default, unless explicitly requested
2279 manual_tag = "manual"
2280 if manual_tag not in include and manual_tag not in exclude:
2281 exclude.append(manual_tag)
2282
2283 return include, exclude
2284
Florian Weikert721d5772024-11-21 17:50:23 +01002285 exclude = ["manual"]
2286 # Workaround for --test_tag_filters in .bazelrc
2287 if is_mac():
2288 exclude.append("no_macos")
2289 elif is_windows():
2290 exclude.append("no_windows")
2291
2292 return [], exclude
Florian Weikert7dd9f662023-05-04 15:05:42 +02002293
2294
Florian Weikertc833a7c2023-05-05 21:26:36 +02002295def removeprefix(s, prefix):
2296 def rp(p):
2297 if s.startswith(p):
Florian Weikertb0fcf902023-05-05 22:01:50 +02002298 return s[len(p) :]
Florian Weikertc833a7c2023-05-05 21:26:36 +02002299 return s
2300
2301 func = getattr(s, "removeprefix", rp)
2302 return func(prefix)
2303
2304
Florian Weikertecf091c2023-04-28 10:22:23 +02002305def filter_unchanged_targets(
Florian Weikert5e70d9d2023-05-08 19:20:23 +02002306 expanded_test_targets, workspace_dir, ws_setup_func, bazel_binary, diffbase, git_commit
Florian Weikertecf091c2023-04-28 10:22:23 +02002307):
2308 print_collapsed_group(
2309 f":scissors: Filtering targets that haven't been affected since {diffbase}"
2310 )
2311
2312 tmpdir = tempfile.mkdtemp()
Florian Weikertecf091c2023-04-28 10:22:23 +02002313 try:
Florian Weikertb0fcf902023-05-05 22:01:50 +02002314 resolved_diffbase = resolve_diffbase(diffbase)
Florian Weikert5e70d9d2023-05-08 19:20:23 +02002315 eprint(f"Resolved diffbase to {resolved_diffbase}")
Florian Weikertb0fcf902023-05-05 22:01:50 +02002316
Florian Weikert5e70d9d2023-05-08 19:20:23 +02002317 eprint("Cloning comparison repository...")
2318 diffbase_archive_url = get_commit_archive_url(resolved_diffbase)
2319 local_archive_path = download_file(diffbase_archive_url, tmpdir, "repo.tar.gz")
2320 diffbase_repo_dir = os.path.join(tmpdir, resolved_diffbase)
Florian Weikertb345b652024-03-06 12:34:25 +01002321 extract_archive(
2322 local_archive_path,
2323 diffbase_repo_dir,
2324 strip_top_level_dir=not is_googlesource_repo(diffbase_archive_url),
2325 )
Florian Weikert5e70d9d2023-05-08 19:20:23 +02002326
2327 eprint("Setting up comparison repository...")
2328 os.chdir(diffbase_repo_dir)
Florian Weikert4141ce92023-05-09 09:45:55 +02002329 ws_setup_func(False)
Florian Weikert5e70d9d2023-05-08 19:20:23 +02002330
2331 eprint(f"Downloading bazel-diff to {tmpdir}")
2332 bazel_diff_path = download_file(BAZEL_DIFF_URL, tmpdir, "bazel-diff.jar")
Florian Weikertb0fcf902023-05-05 22:01:50 +02002333 eprint(f"Running bazel-diff for {resolved_diffbase} and {git_commit}")
2334
Florian Weikertecf091c2023-04-28 10:22:23 +02002335 affected_targets = run_bazel_diff(
Florian Weikert5e70d9d2023-05-08 19:20:23 +02002336 bazel_diff_path, diffbase_repo_dir, workspace_dir, bazel_binary, tmpdir
Florian Weikertecf091c2023-04-28 10:22:23 +02002337 )
Florian Weikertb0fcf902023-05-05 22:01:50 +02002338 except (BuildkiteException, BuildkiteInfraException) as ex:
2339 try:
2340 execute_command(
2341 [
2342 "buildkite-agent",
2343 "annotate",
2344 "--style=warning",
2345 "--context",
2346 "'diff_failed'",
2347 "This build runs all test targets even though `{}` is set "
Florian Weikert9029ccf2023-05-08 17:07:04 +02002348 "since bazel-diff failed with an error:\n```\n{}\n```".format(
Florian Weikertb0fcf902023-05-05 22:01:50 +02002349 USE_BAZEL_DIFF_ENV_VAR, ex
2350 ),
2351 ]
2352 )
Florian Weikert77d20062023-05-08 16:32:48 +02002353 execute_command(
2354 ["buildkite-agent", "annotation", "remove", "--context", BAZEL_DIFF_ANNOTATION_CTX]
2355 )
Florian Weikertb0fcf902023-05-05 22:01:50 +02002356 finally:
2357 return expanded_test_targets
Florian Weikertecf091c2023-04-28 10:22:23 +02002358 finally:
Florian Weikerte747d4f2023-05-09 09:43:47 +02002359 try:
2360 shutil.rmtree(tmpdir)
2361 except:
2362 pass
2363
Florian Weikert5e70d9d2023-05-08 19:20:23 +02002364 os.chdir(workspace_dir)
Florian Weikertecf091c2023-04-28 10:22:23 +02002365
Florian Weikertaa0488f2023-05-03 15:06:13 +02002366 config_target_set = set(expanded_test_targets)
2367 remaining_targets = list(config_target_set.intersection(affected_targets))
2368 if len(remaining_targets) < len(expanded_test_targets):
2369 print_collapsed_group(
2370 ":scissors: Successfully reduced test targets from {} to {}".format(
2371 len(expanded_test_targets), len(remaining_targets)
2372 )
2373 )
2374
2375 skipped_targets = sorted(config_target_set.difference(remaining_targets))
2376 eprint("Skipped targets:\n\t{}".format("\n\t".join(skipped_targets)))
2377
Florian Weikertecf091c2023-04-28 10:22:23 +02002378 execute_command(
2379 [
2380 "buildkite-agent",
2381 "annotate",
2382 "--style=info",
Florian Weikert20151ab2023-05-04 20:32:36 +02002383 "--context",
Florian Weikert77d20062023-05-08 16:32:48 +02002384 BAZEL_DIFF_ANNOTATION_CTX,
Florian Weikert76736e92023-04-28 11:23:27 +02002385 "This run only contains test targets that have been changed since "
Florian Weikertb0fcf902023-05-05 22:01:50 +02002386 "{} due to the `{}` env variable".format(resolved_diffbase, USE_BAZEL_DIFF_ENV_VAR),
Florian Weikertecf091c2023-04-28 10:22:23 +02002387 ]
2388 )
2389
Florian Weikertaa0488f2023-05-03 15:06:13 +02002390 return remaining_targets
Florian Weikertecf091c2023-04-28 10:22:23 +02002391
2392
Florian Weikertb6115442024-10-02 15:39:23 +02002393def upload_shard_distribution(sorted_test_targets, shard_count):
2394 tmpdir = tempfile.mkdtemp()
2395 try:
2396 data = {
2397 s: get_targets_for_shard(sorted_test_targets, s, shard_count)
2398 for s in range(shard_count)
2399 }
Florian Weikert513fa232024-10-04 18:32:14 +02002400 base = f"{os.getenv('BUILDKITE_PIPELINE_SLUG')}_{os.getenv('BUILDKITE_BUILD_NUMBER')}_shards.json"
2401 path = os.path.join(tmpdir, base)
Florian Weikertb6115442024-10-02 15:39:23 +02002402 with open(path, mode="w", encoding="utf-8") as fp:
2403 json.dump(data, fp, indent=2, sort_keys=True)
2404
2405 execute_command(["buildkite-agent", "artifact", "upload", path], cwd=tmpdir)
2406 finally:
2407 shutil.rmtree(tmpdir)
2408
2409
Yun Peng5e2c3ba2024-02-22 13:45:08 +01002410def fetch_base_branch():
2411 """Fetch the base branch for the current build, set FETCH_HEAD for git."""
2412 base_branch = os.getenv("BUILDKITE_PULL_REQUEST_BASE_BRANCH", "")
2413 # Fallback to the default branch for this repository if BUILDKITE_PULL_REQUEST_BASE_BRANCH is not set.
2414 if not base_branch:
2415 base_branch = os.getenv("BUILDKITE_PIPELINE_DEFAULT_BRANCH", "")
2416 execute_command(["git", "fetch", "origin", base_branch])
2417
2418
Florian Weikertecf091c2023-04-28 10:22:23 +02002419def resolve_diffbase(diffbase):
2420 if diffbase in AUTO_DIFFBASE_VALUES:
Yun Peng5e2c3ba2024-02-22 13:45:08 +01002421 fetch_base_branch()
Florian Weikertb345b652024-03-06 12:34:25 +01002422 return execute_command_and_get_output(["git", "merge-base", "HEAD", "FETCH_HEAD"]).strip()
Florian Weikertecf091c2023-04-28 10:22:23 +02002423 elif COMMIT_RE.fullmatch(diffbase):
2424 return diffbase
2425
2426 raise BuildkiteException(
Florian Weikertb0fcf902023-05-05 22:01:50 +02002427 "Invalid value '{}' for `{}` env variable. Must be a Git commit hash or one of {}".format(
Florian Weikertecf091c2023-04-28 10:22:23 +02002428 diffbase, ", ".join(AUTO_DIFFBASE_VALUES)
2429 )
2430 )
2431
2432
Yun Pengb2309892024-02-21 16:32:27 +01002433def is_googlesource_repo(repo_url):
2434 return "googlesource" in repo_url
2435
2436
Florian Weikert5e70d9d2023-05-08 19:20:23 +02002437def get_commit_archive_url(resolved_diffbase):
Yun Peng86b979c2024-02-21 15:47:32 +01002438 repo_url = os.getenv("BUILDKITE_REPO", "")
Yun Pengb2309892024-02-21 16:32:27 +01002439 prefix = "+" if is_googlesource_repo(repo_url) else ""
Florian Weikert5e70d9d2023-05-08 19:20:23 +02002440 return repo_url.replace(".git", "/{}archive/{}.tar.gz".format(prefix, resolved_diffbase))
2441
2442
Yun Pengb2309892024-02-21 16:32:27 +01002443def extract_archive(archive_path, dest_dir, strip_top_level_dir):
Florian Weikert5e70d9d2023-05-08 19:20:23 +02002444 if not os.path.isdir(dest_dir):
2445 os.mkdir(dest_dir)
2446
Florian Weikertecf091c2023-04-28 10:22:23 +02002447 try:
Yun Pengb2309892024-02-21 16:32:27 +01002448 with tarfile.open(archive_path, mode="r:gz") as archive:
2449 if strip_top_level_dir:
2450 for member in archive.getmembers():
2451 member.name = "/".join(member.name.split("/")[1:])
2452 archive.extract(member, dest_dir)
2453 else:
2454 archive.extractall(dest_dir)
Florian Weikert5e70d9d2023-05-08 19:20:23 +02002455 except tarfile.TarError as ex:
2456 raise BuildkiteInfraException("Failed to extract repository archive: {}".format(ex)) from ex
2457
2458
2459def download_file(url, dest_dir, dest_filename):
2460 local_path = os.path.join(dest_dir, dest_filename)
2461 try:
Florian Weikertdfd8db42023-05-09 15:41:30 +02002462 execute_command(["curl", "-sSL", url, "-o", local_path], capture_stderr=True)
Florian Weikertecf091c2023-04-28 10:22:23 +02002463 except subprocess.CalledProcessError as ex:
Florian Weikert5e70d9d2023-05-08 19:20:23 +02002464 raise BuildkiteInfraException("Failed to download {}: {}\n{}".format(url, ex, ex.stderr))
Florian Weikertecf091c2023-04-28 10:22:23 +02002465 return local_path
2466
2467
Florian Weikert5e70d9d2023-05-08 19:20:23 +02002468def run_bazel_diff(bazel_diff_path, old_workspace_dir, new_workspace_dir, bazel_binary, data_dir):
Florian Weikertecf091c2023-04-28 10:22:23 +02002469 before_json = os.path.join(data_dir, "before.json")
2470 after_json = os.path.join(data_dir, "after.json")
2471 targets_file = os.path.join(data_dir, "targets.txt")
2472
2473 try:
Florian Weikert5e70d9d2023-05-08 19:20:23 +02002474 for repo_dir, json_path in (
2475 (old_workspace_dir, before_json),
2476 (new_workspace_dir, after_json),
2477 ):
Florian Weikertecf091c2023-04-28 10:22:23 +02002478 execute_command(
2479 [
2480 "java",
2481 "-jar",
2482 bazel_diff_path,
2483 "generate-hashes",
Florian Weikert6829be22024-10-28 18:56:04 +01002484 "--excludeExternalTargets",
Florian Weikertecf091c2023-04-28 10:22:23 +02002485 "-w",
Florian Weikert5e70d9d2023-05-08 19:20:23 +02002486 repo_dir,
Florian Weikertecf091c2023-04-28 10:22:23 +02002487 "-b",
2488 bazel_binary,
Yun Peng83e4a7f2024-01-25 10:59:00 +01002489 "--bazelCommandOptions=--lockfile_mode=off",
Florian Weikertecf091c2023-04-28 10:22:23 +02002490 json_path,
Florian Weikertdfd8db42023-05-09 15:41:30 +02002491 ],
2492 capture_stderr=True,
Florian Weikertecf091c2023-04-28 10:22:23 +02002493 )
2494
2495 execute_command(
2496 [
2497 "java",
2498 "-jar",
2499 bazel_diff_path,
2500 "get-impacted-targets",
2501 "-sh",
2502 before_json,
2503 "-fh",
2504 after_json,
2505 "-o",
2506 targets_file,
Florian Weikertdfd8db42023-05-09 15:41:30 +02002507 ],
2508 capture_stderr=True,
Florian Weikertecf091c2023-04-28 10:22:23 +02002509 )
2510 except subprocess.CalledProcessError as ex:
Florian Weikertb0fcf902023-05-05 22:01:50 +02002511 raise BuildkiteInfraException("Failed to run bazel-diff: {}\n{}".format(ex, ex.stderr))
Florian Weikertecf091c2023-04-28 10:22:23 +02002512
2513 with open(targets_file, "rt") as f:
2514 contents = f.read()
2515
2516 return contents.split("\n")
2517
2518
Florian Weikert7dd9f662023-05-04 15:05:42 +02002519def partition_list(items):
Yun Peng68193b32024-08-06 18:19:20 +02002520 included, excluded, added_back = [], [], []
Florian Weikert7dd9f662023-05-04 15:05:42 +02002521 for i in items:
2522 if i.startswith("-"):
2523 excluded.append(i[1:])
Yun Peng68193b32024-08-06 18:19:20 +02002524 elif i.startswith("+"):
2525 added_back.append(i[1:])
Florian Weikert736d06e2019-05-08 13:16:42 +02002526 else:
Florian Weikert7dd9f662023-05-04 15:05:42 +02002527 included.append(i)
Florian Weikert736d06e2019-05-08 13:16:42 +02002528
Yun Peng68193b32024-08-06 18:19:20 +02002529 return included, excluded, added_back
Florian Weikert736d06e2019-05-08 13:16:42 +02002530
2531
Florian Weikertb6115442024-10-02 15:39:23 +02002532def get_targets_for_shard(sorted_test_targets, shard_id, shard_count):
Florian Weikert736d06e2019-05-08 13:16:42 +02002533 # TODO(fweikert): implement a more sophisticated algorithm
Florian Weikertb6115442024-10-02 15:39:23 +02002534 return sorted_test_targets[shard_id::shard_count]
Florian Weikert736d06e2019-05-08 13:16:42 +02002535
2536
Philipp Wollermanncd5694c2019-01-03 14:53:04 +01002537def execute_bazel_test(
Florian Weikertc8642af2019-02-03 23:58:51 +01002538 bazel_version,
2539 bazel_binary,
2540 platform,
2541 flags,
2542 targets,
2543 bep_file,
2544 monitor_flaky_tests,
Philipp Wollermanncd5694c2019-01-03 14:53:04 +01002545):
Philipp Wollermanncd5694c2019-01-03 14:53:04 +01002546 aggregated_flags = [
2547 "--flaky_test_attempts=3",
2548 "--build_tests_only",
Tobias Werth08a93832023-04-25 16:29:52 +02002549 "--local_test_jobs=" + concurrent_test_jobs(platform),
Philipp Wollermanncd5694c2019-01-03 14:53:04 +01002550 ]
Yun Peng6a6d29d2023-11-09 14:24:06 +01002551
Philipp Wollermann2409c6e2018-08-07 07:37:54 +02002552 # Don't enable remote caching if the user enabled remote execution / caching themselves
Jakob Buchgraberc340f582018-06-22 13:48:33 +02002553 # or flaky test monitoring is enabled, as remote caching makes tests look less flaky than
2554 # they are.
Philipp Wollermannbda4b7d2019-05-16 20:04:17 +02002555 print_collapsed_group(":bazel: Computing flags for test step")
Philipp Wollermanncd5694c2019-01-03 14:53:04 +01002556 aggregated_flags += compute_flags(
Yun Peng8975c6b2019-02-28 11:55:55 +01002557 platform,
2558 flags,
Yun Peng8975c6b2019-02-28 11:55:55 +01002559 bep_file,
Philipp Wollermann87b45252020-01-22 12:43:42 +01002560 bazel_binary,
Florian Weikert29cb7ec2019-03-07 14:52:18 +01002561 enable_remote_cache=not monitor_flaky_tests,
Philipp Wollermanncd5694c2019-01-03 14:53:04 +01002562 )
Philipp Wollermann2409c6e2018-08-07 07:37:54 +02002563
Philipp Wollermannbda4b7d2019-05-16 20:04:17 +02002564 print_expanded_group(":bazel: Test ({})".format(bazel_version))
Philipp Wollermanndcaddd92018-02-21 14:13:43 +01002565 try:
Philipp Wollermanncd5694c2019-01-03 14:53:04 +01002566 execute_command(
Florian Weikert29cb7ec2019-03-07 14:52:18 +01002567 [bazel_binary]
2568 + bazelisk_flags()
Florian Weikertda94a102022-10-21 12:24:37 +02002569 + common_startup_flags()
Florian Weikert29cb7ec2019-03-07 14:52:18 +01002570 + ["test"]
2571 + aggregated_flags
Philipp Wollermann2a160432019-09-19 15:57:28 +02002572 + ["--"]
Florian Weikert29cb7ec2019-03-07 14:52:18 +01002573 + targets
Philipp Wollermanncd5694c2019-01-03 14:53:04 +01002574 )
Philipp Wollermanndcaddd92018-02-21 14:13:43 +01002575 except subprocess.CalledProcessError as e:
Yun Peng0a6a98a2019-03-06 13:07:54 +01002576 handle_bazel_failure(e, "test")
Jakob Buchgraber0b6a39d2018-02-17 00:45:14 +01002577
Florian Weikert7f21ca42022-02-02 17:35:23 +01002578
Florian Weikertb3439b32022-11-09 11:05:16 +01002579def execute_bazel_coverage(bazel_version, bazel_binary, platform, flags, targets):
Xavier Bonaventura3755ea02022-02-02 13:56:54 +01002580 aggregated_flags = [
2581 "--build_tests_only",
Tobias Werth08a93832023-04-25 16:29:52 +02002582 "--local_test_jobs=" + concurrent_test_jobs(platform),
Xavier Bonaventura3755ea02022-02-02 13:56:54 +01002583 ]
2584 print_collapsed_group(":bazel: Computing flags for coverage step")
2585 aggregated_flags += compute_flags(
2586 platform,
2587 flags,
Xavier Bonaventura3755ea02022-02-02 13:56:54 +01002588 None,
2589 bazel_binary,
2590 enable_remote_cache=True,
2591 )
2592
2593 print_expanded_group(":bazel: Coverage ({})".format(bazel_version))
2594 try:
2595 execute_command(
2596 [bazel_binary]
2597 + bazelisk_flags()
Florian Weikertda94a102022-10-21 12:24:37 +02002598 + common_startup_flags()
Xavier Bonaventura3755ea02022-02-02 13:56:54 +01002599 + ["coverage"]
2600 + aggregated_flags
2601 + ["--"]
2602 + targets
2603 )
2604 except subprocess.CalledProcessError as e:
2605 handle_bazel_failure(e, "coverage")
Jakob Buchgraber0b6a39d2018-02-17 00:45:14 +01002606
Florian Weikert7f21ca42022-02-02 17:35:23 +01002607
Florian Weikertda94a102022-10-21 12:24:37 +02002608def upload_test_logs_from_bep(bep_file, tmpdir, monitor_flaky_tests):
John Cater2f0aee22023-10-19 05:34:33 -04002609 if local_run_only():
2610 return
Florian Weikert78e67de2024-02-06 12:58:41 +01002611
Chi Wangd61eb972023-01-18 11:36:18 +01002612 bazelci_agent_binary = download_bazelci_agent(tmpdir)
Chi Wangd279d582021-09-29 10:59:06 +08002613 execute_command(
Florian Weikert7f21ca42022-02-02 17:35:23 +01002614 [
2615 bazelci_agent_binary,
2616 "artifact",
2617 "upload",
Florian Weikert78e67de2024-02-06 12:58:41 +01002618 "--debug", # Force BEP upload for non-flaky failures
Florian Weikert7f21ca42022-02-02 17:35:23 +01002619 "--mode=buildkite",
2620 "--build_event_json_file={}".format(bep_file),
2621 ]
Chi Wangd279d582021-09-29 10:59:06 +08002622 + (["--monitor_flaky_tests"] if monitor_flaky_tests else [])
2623 )
Jakob Buchgraber0b6a39d2018-02-17 00:45:14 +01002624
Philipp Wollermann3c8b8512019-07-16 15:28:03 +02002625
joeleba76887952019-05-16 15:22:17 +02002626def upload_json_profile(json_profile_path, tmpdir):
John Cater2f0aee22023-10-19 05:34:33 -04002627 if local_run_only():
2628 return
joeleba76887952019-05-16 15:22:17 +02002629 if not os.path.exists(json_profile_path):
2630 return
2631 print_collapsed_group(":gcloud: Uploading JSON Profile")
Philipp Wollermann92cf51e2019-05-16 15:31:11 +02002632 execute_command(["buildkite-agent", "artifact", "upload", json_profile_path], cwd=tmpdir)
joeleba76887952019-05-16 15:22:17 +02002633
Philipp Wollermannf436e742021-08-11 11:06:55 +02002634
Chi Wang54595a22021-06-11 17:49:58 +08002635def upload_corrupted_outputs(capture_corrupted_outputs_dir, tmpdir):
John Cater2f0aee22023-10-19 05:34:33 -04002636 if local_run_only():
2637 return
Chi Wang54595a22021-06-11 17:49:58 +08002638 if not os.path.exists(capture_corrupted_outputs_dir):
2639 return
2640 print_collapsed_group(":gcloud: Uploading corrupted outputs")
Philipp Wollermannf436e742021-08-11 11:06:55 +02002641 execute_command(
2642 ["buildkite-agent", "artifact", "upload", "{}/**/*".format(capture_corrupted_outputs_dir)],
2643 cwd=tmpdir,
2644 )
2645
Philipp Wollermann5b00a702019-07-18 11:21:38 +02002646
Philipp Wollermannaf35abf2019-05-22 17:52:01 +02002647def execute_command_and_get_output(args, shell=False, fail_if_nonzero=True, print_output=True):
Philipp Wollermanndcaddd92018-02-21 14:13:43 +01002648 eprint(" ".join(args))
Florian Weikertc8642af2019-02-03 23:58:51 +01002649 process = subprocess.run(
2650 args,
2651 shell=shell,
2652 check=fail_if_nonzero,
2653 env=os.environ,
Florian Weikert9029ccf2023-05-08 17:07:04 +02002654 stdout=subprocess.PIPE, # We cannot use capture_output since some workers run Python <3.7
Philipp Wollermannf13804b2019-02-05 21:08:30 +01002655 errors="replace",
Florian Weikertc8642af2019-02-03 23:58:51 +01002656 universal_newlines=True,
2657 )
Florian Weikert736d06e2019-05-08 13:16:42 +02002658 if print_output:
2659 eprint(process.stdout)
2660
Florian Weikertc8642af2019-02-03 23:58:51 +01002661 return process.stdout
Jakob Buchgraber0b6a39d2018-02-17 00:45:14 +01002662
2663
Florian Weikertdfd8db42023-05-09 15:41:30 +02002664def execute_command(
Florian Weikertc89d6142023-11-20 14:50:35 +01002665 args,
2666 shell=False,
2667 fail_if_nonzero=True,
2668 cwd=None,
2669 print_output=True,
2670 capture_stderr=False,
2671 suppress_stdout=False,
Florian Weikertdfd8db42023-05-09 15:41:30 +02002672):
Yun Peng9337bb32020-02-28 13:31:29 +01002673 if print_output:
2674 eprint(" ".join(args))
Philipp Wollermann92cf51e2019-05-16 15:31:11 +02002675 return subprocess.run(
Florian Weikertb0fcf902023-05-05 22:01:50 +02002676 args,
2677 shell=shell,
2678 check=fail_if_nonzero,
2679 env=os.environ,
2680 cwd=cwd,
2681 errors="replace",
Florian Weikertb345b652024-03-06 12:34:25 +01002682 stdout=(
2683 subprocess.DEVNULL if suppress_stdout else None
2684 ), # suppress_stdout=True when we don't want the output to be printed
2685 stderr=(
2686 subprocess.PIPE if capture_stderr else None
2687 ), # capture_stderr=True when we want exceptions to contain stderr
Philipp Wollermann92cf51e2019-05-16 15:31:11 +02002688 ).returncode
Philipp Wollermannf13804b2019-02-05 21:08:30 +01002689
2690
Florian Weikertccd44572024-11-11 13:11:39 +01002691def create_step(
2692 label, commands, platform, shards=1, soft_fail=None, concurrency=None, concurrency_group=None
2693):
Philipp Wollermannc43d3cf2019-01-10 13:24:15 +01002694 if "docker-image" in PLATFORMS[platform]:
Florian Weikert736d06e2019-05-08 13:16:42 +02002695 step = create_docker_step(
Florian Weikertb3439b32022-11-09 11:05:16 +01002696 label,
2697 image=PLATFORMS[platform]["docker-image"],
2698 commands=commands,
2699 queue=PLATFORMS[platform].get("queue", "default"),
2700 always_pull=PLATFORMS[platform].get("always-pull", True),
Philipp Wollermannc05ac682019-01-19 12:37:28 +01002701 )
Philipp Wollermannc43d3cf2019-01-10 13:24:15 +01002702 else:
Philipp Wollermannf3750fa2019-05-21 17:11:59 +02002703 step = {
2704 "label": label,
2705 "command": commands,
2706 "agents": {"queue": PLATFORMS[platform]["queue"]},
2707 }
Philipp Wollermannc43d3cf2019-01-10 13:24:15 +01002708
Florian Weikertccd44572024-11-11 13:11:39 +01002709 project_slug = "{}/{}".format(
2710 os.getenv("BUILDKITE_ORGANIZATION_SLUG"), os.getenv("BUILDKITE_PIPELINE_SLUG")
2711 )
2712 if project_slug in _PRIORITY_PIPELINES:
2713 step["priority"] = 1
2714
Florian Weikert736d06e2019-05-08 13:16:42 +02002715 if shards > 1:
Florian Weikertb6115442024-10-02 15:39:23 +02002716 # %N means shard counting starts at 1, not 0
2717 step["label"] += " (shard %N)"
Florian Weikert736d06e2019-05-08 13:16:42 +02002718 step["parallelism"] = shards
2719
mai93b49bad72021-05-06 00:50:34 +02002720 if soft_fail is not None:
2721 step["soft_fail"] = soft_fail
2722
Philipp Wollermann5b2f3fc2019-05-18 22:36:17 +02002723 # Enforce a global 8 hour job timeout.
2724 step["timeout_in_minutes"] = 8 * 60
2725
2726 # Automatically retry when an agent got lost (usually due to an infra flake).
Philipp Wollermannf22bba32019-07-18 11:22:50 +02002727 step["retry"] = {
2728 "automatic": [
2729 {"exit_status": -1, "limit": 3}, # Buildkite internal "agent lost" exit code
2730 {"exit_status": 137, "limit": 3}, # SIGKILL
2731 {"exit_status": 143, "limit": 3}, # SIGTERM
2732 ]
2733 }
Philipp Wollermann5b2f3fc2019-05-18 22:36:17 +02002734
Yun Penge478ee52024-08-22 14:35:41 +02002735 # Retry on macos_arm64 due to
2736 # https://github.com/bazelbuild/continuous-integration/issues/2025
2737 if platform == "macos_arm64":
2738 step["retry"]["automatic"].append({"exit_status": 255, "limit": 1})
2739
Yun Peng47d1cea2024-08-08 11:22:56 +02002740 # Automatically retry on Intel Macs to work around flaky failures.
2741 if platform == "macos":
2742 step["retry"]["automatic"].append({"exit_status": 128, "limit": 1})
2743 step["retry"]["automatic"].append({"exit_status": 1, "limit": 1})
2744
Yun Peng2d33fc62024-10-23 11:43:04 +02002745 if concurrency and concurrency_group:
2746 step["concurrency"] = concurrency
2747 step["concurrency_group"] = concurrency_group
2748
Florian Weikert736d06e2019-05-08 13:16:42 +02002749 return step
2750
Philipp Wollermannc43d3cf2019-01-10 13:24:15 +01002751
Florian Weikertb3439b32022-11-09 11:05:16 +01002752def create_docker_step(
2753 label, image, commands=None, additional_env_vars=None, queue="default", always_pull=True
2754):
Philipp Wollermann0e051dd2019-05-16 11:37:52 +02002755 env = ["ANDROID_HOME", "ANDROID_NDK_HOME", "BUILDKITE_ARTIFACT_UPLOAD_DESTINATION"]
Yun Peng26b4ba82024-08-21 15:13:11 +02002756 if THIS_IS_TRUSTED:
2757 # For the trusted Linux arm64 machine to upload artifacts
2758 env += ["GOOGLE_APPLICATION_CREDENTIALS"]
Florian Weikert29cb7ec2019-03-07 14:52:18 +01002759 if additional_env_vars:
2760 env += ["{}={}".format(k, v) for k, v in additional_env_vars.items()]
2761
Philipp Wollermannc05ac682019-01-19 12:37:28 +01002762 step = {
Florian Weikertf20ae6f2019-01-16 14:32:09 +01002763 "label": label,
2764 "command": commands,
Yun Peng07dafc52022-03-16 13:23:35 +01002765 "agents": {"queue": queue},
Florian Weikertf20ae6f2019-01-16 14:32:09 +01002766 "plugins": {
Philipp Wollermann9fa03542021-07-01 23:27:53 +02002767 "docker#v3.8.0": {
Yun Peng07dafc52022-03-16 13:23:35 +01002768 "always-pull": always_pull,
Florian Weikert29cb7ec2019-03-07 14:52:18 +01002769 "environment": env,
Philipp Wollermannc05ac682019-01-19 12:37:28 +01002770 "image": image,
Florian Weikertf20ae6f2019-01-16 14:32:09 +01002771 "network": "host",
2772 "privileged": True,
2773 "propagate-environment": True,
Philipp Wollermann7aa95492019-05-18 22:03:24 +02002774 "propagate-uid-gid": True,
Florian Weikertf20ae6f2019-01-16 14:32:09 +01002775 "volumes": [
Philipp Wollermann7aa95492019-05-18 22:03:24 +02002776 "/etc/group:/etc/group:ro",
2777 "/etc/passwd:/etc/passwd:ro",
Philipp Wollermann3dc99b32021-10-12 00:34:30 +02002778 "/etc/shadow:/etc/shadow:ro",
Philipp Wollermann562ef1d2021-10-20 22:15:29 +02002779 "/opt/android-ndk-r15c:/opt/android-ndk-r15c:ro",
Yun Peng72874212022-09-13 19:08:03 +02002780 "/opt/android-ndk-r25b:/opt/android-ndk-r25b:ro",
Philipp Wollermann562ef1d2021-10-20 22:15:29 +02002781 "/opt/android-sdk-linux:/opt/android-sdk-linux:ro",
Philipp Wollermann7aa95492019-05-18 22:03:24 +02002782 "/var/lib/buildkite-agent:/var/lib/buildkite-agent",
Philipp Wollermann338db4a2019-05-18 11:21:04 +02002783 "/var/lib/gitmirrors:/var/lib/gitmirrors:ro",
Philipp Wollermanna65944a2020-02-03 12:45:22 +01002784 "/var/run/docker.sock:/var/run/docker.sock",
Florian Weikertf20ae6f2019-01-16 14:32:09 +01002785 ],
Florian Weikertf20ae6f2019-01-16 14:32:09 +01002786 }
2787 },
2788 }
Philipp Wollermannc05ac682019-01-19 12:37:28 +01002789 if not step["command"]:
2790 del step["command"]
2791 return step
Florian Weikertf20ae6f2019-01-16 14:32:09 +01002792
2793
Philipp Wollermanncd5694c2019-01-03 14:53:04 +01002794def print_project_pipeline(
Florian Weikertf20ae6f2019-01-16 14:32:09 +01002795 configs,
Philipp Wollermanncd5694c2019-01-03 14:53:04 +01002796 project_name,
2797 http_config,
2798 file_config,
2799 git_repository,
2800 monitor_flaky_tests,
2801 use_but,
Florian Weikert60661912019-12-18 15:17:10 +01002802 notify,
Florian Weikert78e67de2024-02-06 12:58:41 +01002803 print_shard_summary,
Philipp Wollermanncd5694c2019-01-03 14:53:04 +01002804):
Florian Weikert843d7a02019-02-03 17:24:50 +01002805 task_configs = configs.get("tasks", None)
2806 if not task_configs:
Philipp Wollermann2409c6e2018-08-07 07:37:54 +02002807 raise BuildkiteException("{0} pipeline configuration is empty.".format(project_name))
2808
Jakob Buchgraberaa2af382018-02-21 19:56:54 +01002809 pipeline_steps = []
mai93f2e116c2020-10-19 09:33:14 +02002810 # If the repository is hosted on Git-on-borg, we show the link to the commit Gerrit review
2811 buildkite_repo = os.getenv("BUILDKITE_REPO")
2812 if is_git_on_borg_repo(buildkite_repo):
2813 show_gerrit_review_link(buildkite_repo, pipeline_steps)
2814
Florian Weikert5f5d3cb2019-04-15 15:36:27 +02002815 task_configs = filter_tasks_that_should_be_skipped(task_configs, pipeline_steps)
Jakob Buchgraberff2bdad2018-02-25 13:06:30 +01002816
Florian Weikert85208912019-03-07 17:08:39 +01002817 buildifier_config = configs.get("buildifier")
Philipp Wollermanndac65512019-02-05 22:14:10 +01002818 # Skip Buildifier when we test downstream projects.
Yun Peng6a6d29d2023-11-09 14:24:06 +01002819 if buildifier_config and not is_downstream_pipeline():
Florian Weikert85208912019-03-07 17:08:39 +01002820 buildifier_env_vars = {}
2821 if isinstance(buildifier_config, str):
2822 # Simple format:
2823 # ---
2824 # buildifier: latest
Philipp Wollermann22538e72021-10-02 09:43:28 +02002825 buildifier_env_vars["BUILDIFIER_VERSION"] = buildifier_config
Florian Weikert85208912019-03-07 17:08:39 +01002826 else:
2827 # Advanced format:
2828 # ---
2829 # buildifier:
2830 # version: latest
2831 # warnings: all
Philipp Wollermann22538e72021-10-02 09:43:28 +02002832 if "version" in buildifier_config:
2833 buildifier_env_vars["BUILDIFIER_VERSION"] = buildifier_config["version"]
2834 if "warnings" in buildifier_config:
2835 buildifier_env_vars["BUILDIFIER_WARNINGS"] = buildifier_config["warnings"]
Florian Weikert85208912019-03-07 17:08:39 +01002836
2837 if not buildifier_env_vars:
2838 raise BuildkiteException(
2839 'Invalid buildifier configuration entry "{}"'.format(buildifier_config)
2840 )
2841
Florian Weikert29cb7ec2019-03-07 14:52:18 +01002842 pipeline_steps.append(
2843 create_docker_step(
Florian Weikert42fbf792024-10-01 13:24:55 +02002844 "Buildifier",
Florian Weikert29cb7ec2019-03-07 14:52:18 +01002845 image=BUILDIFIER_DOCKER_IMAGE,
Florian Weikert85208912019-03-07 17:08:39 +01002846 additional_env_vars=buildifier_env_vars,
Florian Weikert29cb7ec2019-03-07 14:52:18 +01002847 )
2848 )
Philipp Wollermannc05ac682019-01-19 12:37:28 +01002849
Philipp Wollermanndac65512019-02-05 22:14:10 +01002850 # In Bazel Downstream Project pipelines, we should test the project at the last green commit.
Yun Pengfb86f672023-11-10 16:18:55 +01002851 git_commit = get_last_green_commit(project_name) if is_downstream_pipeline() else None
Philipp Wollermanndac65512019-02-05 22:14:10 +01002852
Florian Weikert854fd852019-06-04 16:44:19 +02002853 config_hashes = set()
Yun Penge6b09902023-03-31 17:15:58 +02002854 skipped_downstream_tasks = []
Florian Weikert78e67de2024-02-06 12:58:41 +01002855 has_sharded_task = False
Florian Weikert843d7a02019-02-03 17:24:50 +01002856 for task, task_config in task_configs.items():
Florian Weikertdb832a02020-11-19 19:14:48 +01002857 platform = get_platform_for_task(task, task_config)
2858 task_name = task_config.get("name")
mai93b49bad72021-05-06 00:50:34 +02002859 soft_fail = task_config.get("soft_fail")
Florian Weikertdb832a02020-11-19 19:14:48 +01002860
Florian Weikert854fd852019-06-04 16:44:19 +02002861 # We override the Bazel version in downstream pipelines. This means that two tasks that
2862 # only differ in the value of their explicit "bazel" field will be identical in the
2863 # downstream pipeline, thus leading to duplicate work.
2864 # Consequently, we filter those duplicate tasks here.
Yun Peng6a6d29d2023-11-09 14:24:06 +01002865 if is_downstream_pipeline():
Florian Weikert854fd852019-06-04 16:44:19 +02002866 h = hash_task_config(task, task_config)
2867 if h in config_hashes:
Yun Penge6b09902023-03-31 17:15:58 +02002868 skipped_downstream_tasks.append(
Yun Peng9d905fb2023-04-24 14:13:26 +02002869 "{}: {}".format(
Florian Weikertb3439b32022-11-09 11:05:16 +01002870 create_label(platform, project_name, task_name=task_name),
Yun Penge6b09902023-03-31 17:15:58 +02002871 "The same task already exists after ignoring bazel version.",
Florian Weikertb3439b32022-11-09 11:05:16 +01002872 )
2873 )
Florian Weikert11d31432023-02-27 11:33:23 +01002874 continue
2875
Florian Weikert854fd852019-06-04 16:44:19 +02002876 config_hashes.add(h)
2877
Yun Penge6b09902023-03-31 17:15:58 +02002878 # Skip tasks with `skip_in_bazel_downstream_pipeline` specified.
2879 skipped_reason = task_config.get("skip_in_bazel_downstream_pipeline", "")
2880 if skipped_reason:
2881 skipped_downstream_tasks.append(
Yun Peng9d905fb2023-04-24 14:13:26 +02002882 "{}: {}".format(
Yun Penge6b09902023-03-31 17:15:58 +02002883 create_label(platform, project_name, task_name=task_name),
2884 skipped_reason,
2885 )
2886 )
2887 continue
2888
Florian Weikert736d06e2019-05-08 13:16:42 +02002889 shards = task_config.get("shards", "1")
2890 try:
2891 shards = int(shards)
2892 except ValueError:
2893 raise BuildkiteException("Task {} has invalid shard value '{}'".format(task, shards))
2894
Florian Weikert78e67de2024-02-06 12:58:41 +01002895 if shards > 1:
2896 has_sharded_task = True
2897
Florian Weikertd646a2d2023-12-20 18:29:17 +01002898 step = runner_step(
2899 platform=platform,
2900 task=task,
2901 task_name=task_name,
2902 project_name=project_name,
2903 http_config=http_config,
2904 file_config=file_config,
2905 git_repository=git_repository,
2906 git_commit=git_commit,
2907 monitor_flaky_tests=monitor_flaky_tests,
2908 use_but=use_but,
2909 shards=shards,
2910 soft_fail=soft_fail,
2911 )
2912 pipeline_steps.append(step)
Jakob Buchgraber0b6a39d2018-02-17 00:45:14 +01002913
Yun Penge6b09902023-03-31 17:15:58 +02002914 if skipped_downstream_tasks:
2915 lines = ["\n- {}".format(s) for s in skipped_downstream_tasks]
Florian Weikertdb832a02020-11-19 19:14:48 +01002916 commands = [
Yun Pengee010872023-04-24 14:48:19 +02002917 "buildkite-agent meta-data exists 'has-skipped-annotation' || buildkite-agent annotate --style=info 'The following tasks were skipped:\n' --context 'ctx-skipped_downstream_tasks'",
2918 "buildkite-agent meta-data set 'has-skipped-annotation' 'true'",
Yun Penge6b09902023-03-31 17:15:58 +02002919 "buildkite-agent annotate --style=info '{}' --append --context 'ctx-skipped_downstream_tasks'".format(
Xùdōng Yáng045c9812021-08-18 01:42:35 +10002920 "".join(lines)
2921 ),
Florian Weikertdb832a02020-11-19 19:14:48 +01002922 ]
2923 pipeline_steps.append(
2924 create_step(
Yun Penge6b09902023-03-31 17:15:58 +02002925 label=":pipeline: Print information about skipped tasks",
Florian Weikertdb832a02020-11-19 19:14:48 +01002926 commands=commands,
2927 platform=DEFAULT_PLATFORM,
2928 )
2929 )
2930
Yun Peng996efad2018-11-27 17:19:44 +01002931 pipeline_slug = os.getenv("BUILDKITE_PIPELINE_SLUG")
2932 all_downstream_pipeline_slugs = []
2933 for _, config in DOWNSTREAM_PROJECTS.items():
2934 all_downstream_pipeline_slugs.append(config["pipeline_slug"])
Florian Weikert78e67de2024-02-06 12:58:41 +01002935
Ivo List23ce48d2020-11-18 13:15:34 +01002936 # We update last green commit in the following cases:
2937 # 1. This job runs on master, stable or main branch (could be a custom build launched manually)
2938 # 2. We intend to run the same job in downstream with Bazel@HEAD (eg. google-bazel-presubmit)
2939 # 3. This job is not:
2940 # - a GitHub pull request
2941 # - uses a custom built Bazel binary (in Bazel Downstream Projects pipeline)
2942 # - testing incompatible flags
2943 # - running `bazelisk --migrate` in a non-downstream pipeline
Florian Weikert78e67de2024-02-06 12:58:41 +01002944 should_update_last_green = (
Philipp Wollermann1b5ecdc2021-06-10 21:52:55 +02002945 current_branch_is_main_branch()
Ivo List23ce48d2020-11-18 13:15:34 +01002946 and pipeline_slug in all_downstream_pipeline_slugs
Yun Pengc85cd0e2022-09-02 10:44:29 +02002947 and not (is_pull_request() or use_but or use_bazelisk_migrate())
Florian Weikert78e67de2024-02-06 12:58:41 +01002948 )
2949
2950 actually_print_shard_summary = has_sharded_task and print_shard_summary
2951
2952 if should_update_last_green or actually_print_shard_summary:
2953 pipeline_steps.append({"wait": None, "continue_on_failure": True})
2954
2955 if should_update_last_green:
Florian Weikertde96a6f2019-03-07 14:57:50 +01002956 # We need to call "Try Update Last Green Commit" even if there are failures,
2957 # since we don't want a failing Buildifier step to block the update of
2958 # the last green commit for this project.
2959 # try_update_last_green_commit() ensures that we don't update the commit
2960 # if any build or test steps fail.
Philipp Wollermanncd5694c2019-01-03 14:53:04 +01002961 pipeline_steps.append(
Philipp Wollermannc43d3cf2019-01-10 13:24:15 +01002962 create_step(
Philipp Wollermann1403d2c2019-01-10 13:15:51 +01002963 label="Try Update Last Green Commit",
2964 commands=[
Philipp Wollermanncd5694c2019-01-03 14:53:04 +01002965 fetch_bazelcipy_command(),
Philipp Wollermann57b32682019-05-18 22:09:27 +02002966 PLATFORMS[DEFAULT_PLATFORM]["python"]
2967 + " bazelci.py try_update_last_green_commit",
Philipp Wollermanncd5694c2019-01-03 14:53:04 +01002968 ],
Philipp Wollermann7a185322019-05-18 22:15:48 +02002969 platform=DEFAULT_PLATFORM,
Philipp Wollermann1403d2c2019-01-10 13:15:51 +01002970 )
Philipp Wollermanncd5694c2019-01-03 14:53:04 +01002971 )
Yun Peng43239b02018-11-23 13:57:34 +01002972
Yun Pengb3c65c22023-12-11 12:02:12 +01002973 if "validate_config" in configs and not is_downstream_pipeline():
Florian Weikertc89d6142023-11-20 14:50:35 +01002974 pipeline_steps += create_config_validation_steps(
2975 git_commit or os.getenv("BUILDKITE_COMMIT")
2976 )
Florian Weikert778251c2019-04-25 15:14:44 +02002977
Yun Peng6a6d29d2023-11-09 14:24:06 +01002978 if use_bazelisk_migrate() and not is_downstream_pipeline():
Florian Weikert09813a02019-10-26 19:34:33 +02002979 # Print results of bazelisk --migrate in project pipelines that explicitly set
2980 # the USE_BAZELISK_MIGRATE env var, but that are not being run as part of a
2981 # downstream pipeline.
2982 number = os.getenv("BUILDKITE_BUILD_NUMBER")
Florian Weikert60661912019-12-18 15:17:10 +01002983 pipeline_steps += get_steps_for_aggregating_migration_results(number, notify)
Florian Weikert09813a02019-10-26 19:34:33 +02002984
Florian Weikert78e67de2024-02-06 12:58:41 +01002985 if actually_print_shard_summary:
2986 pipeline_steps.append(
2987 create_step(
Florian Weikert42fbf792024-10-01 13:24:55 +02002988 label="Print Test Summary for Shards",
Florian Weikert78e67de2024-02-06 12:58:41 +01002989 commands=[
2990 fetch_bazelcipy_command(),
2991 PLATFORMS[DEFAULT_PLATFORM]["python"] + " bazelci.py print_shard_summary",
2992 ],
2993 platform=DEFAULT_PLATFORM,
2994 )
2995 )
2996
Yun Peng6a6d29d2023-11-09 14:24:06 +01002997 print_pipeline_steps(pipeline_steps, handle_emergencies=not is_downstream_pipeline())
Jakob Buchgraber0b6a39d2018-02-17 00:45:14 +01002998
Philipp Wollermanncd5694c2019-01-03 14:53:04 +01002999
mai93f2e116c2020-10-19 09:33:14 +02003000def show_gerrit_review_link(git_repository, pipeline_steps):
3001 host = re.search(r"https://(.+?)\.googlesource", git_repository).group(1)
3002 if not host:
3003 raise BuildkiteException("Couldn't get host name from %s" % git_repository)
Yun Peng95799f72024-07-08 12:32:26 +02003004 commit = os.getenv("BUILDKITE_COMMIT")
Yun Peng83e9cb52024-07-09 11:13:32 +02003005 line1 = f"The transformed code used in this pipeline can be found under https://{host}-review.googlesource.com/q/{commit}"
Yun Penga6afb692024-07-09 11:25:29 +02003006 line2 = f"\n\nFetch the source with `git fetch https://{host}.googlesource.com/bazel {commit} && git checkout FETCH_HEAD`"
Yun Pengf8bb2922024-07-09 10:46:04 +02003007 commands = [
Yun Penga6afb692024-07-09 11:25:29 +02003008 "buildkite-agent annotate --style=info '{}' --context 'gerrit'".format(line1),
3009 "buildkite-agent annotate --style=info '{}' --append --context 'gerrit'".format(line2),
Yun Pengf8bb2922024-07-09 10:46:04 +02003010 ]
mai93f2e116c2020-10-19 09:33:14 +02003011 pipeline_steps.append(
3012 create_step(
3013 label=":pipeline: Print information about Gerrit Review Link",
3014 commands=commands,
3015 platform=DEFAULT_PLATFORM,
3016 )
3017 )
3018
3019
3020def is_git_on_borg_repo(git_repository):
3021 return git_repository and "googlesource.com" in git_repository
3022
3023
Florian Weikert854fd852019-06-04 16:44:19 +02003024def hash_task_config(task_name, task_config):
3025 # Two task configs c1 and c2 have the same hash iff they lead to two functionally identical jobs
3026 # in the downstream pipeline. This function discards the "bazel" field (since it's being
Philipp Wollermannce986af2019-07-18 14:46:05 +02003027 # overridden) and the "name" field (since it has no effect on the actual work).
Florian Weikert854fd852019-06-04 16:44:19 +02003028 # Moreover, it adds an explicit "platform" field if that's missing.
3029 cpy = task_config.copy()
3030 cpy.pop("bazel", None)
3031 cpy.pop("name", None)
3032 if "platform" not in cpy:
3033 cpy["platform"] = task_name
3034
3035 m = hashlib.md5()
Florian Weikertb3439b32022-11-09 11:05:16 +01003036 # Technically we should sort cpy[key] if it's a list of entries
3037 # whose order does not matter (e.g. targets).
3038 # However, this seems to be overkill for the current use cases.
Florian Weikert854fd852019-06-04 16:44:19 +02003039 for key in sorted(cpy):
Florian Weikert8186c392019-06-05 12:53:39 +02003040 value = "%s:%s;" % (key, cpy[key])
3041 m.update(value.encode("utf-8"))
Florian Weikert854fd852019-06-04 16:44:19 +02003042
3043 return m.digest()
3044
3045
Florian Weikert843d7a02019-02-03 17:24:50 +01003046def get_platform_for_task(task, task_config):
3047 # Most pipeline configurations have exactly one task per platform, which makes it
3048 # convenient to use the platform name as task ID. Consequently, we use the
3049 # task ID as platform if there is no explicit "platform" field.
3050 return task_config.get("platform", task)
3051
3052
Yun Pengfb86f672023-11-10 16:18:55 +01003053def create_config_validation_steps(git_commit):
Florian Weikertf52f91a2019-05-08 15:19:30 +02003054 config_files = [
Philipp Wollermann67225ec2021-08-11 11:12:51 +02003055 path
Yun Pengfb86f672023-11-10 16:18:55 +01003056 for path in get_modified_files(git_commit)
Philipp Wollermann67225ec2021-08-11 11:12:51 +02003057 if path.startswith(".bazelci/") and os.path.splitext(path)[1] in CONFIG_FILE_EXTENSIONS
Florian Weikertf52f91a2019-05-08 15:19:30 +02003058 ]
Florian Weikertf52f91a2019-05-08 15:19:30 +02003059 return [
3060 create_step(
3061 label=":cop: Validate {}".format(f),
3062 commands=[
3063 fetch_bazelcipy_command(),
3064 "{} bazelci.py project_pipeline --file_config={}".format(
Philipp Wollermann57b32682019-05-18 22:09:27 +02003065 PLATFORMS[DEFAULT_PLATFORM]["python"], f
Florian Weikertf52f91a2019-05-08 15:19:30 +02003066 ),
3067 ],
Philipp Wollermann7a185322019-05-18 22:15:48 +02003068 platform=DEFAULT_PLATFORM,
Florian Weikertf52f91a2019-05-08 15:19:30 +02003069 )
3070 for f in config_files
3071 ]
3072
3073
Yun Pengfb86f672023-11-10 16:18:55 +01003074def get_modified_files(git_commit):
Yun Peng5e2c3ba2024-02-22 13:45:08 +01003075 fetch_base_branch()
Florian Weikertb345b652024-03-06 12:34:25 +01003076 merge_base_commit = execute_command_and_get_output(
3077 ["git", "merge-base", git_commit, "FETCH_HEAD"]
3078 ).strip()
Florian Weikerte417f9f2023-05-05 17:33:46 +02003079 output = execute_command_and_get_output(
Florian Weikertb345b652024-03-06 12:34:25 +01003080 [
3081 "git",
3082 "diff-tree",
3083 "--no-commit-id",
3084 "--name-only",
3085 "-r",
3086 "{}..{}".format(merge_base_commit, git_commit),
3087 ]
Florian Weikerte417f9f2023-05-05 17:33:46 +02003088 )
3089 return output.split("\n")
3090
3091
Florian Weikertd79dc502019-05-13 09:51:05 +02003092def print_pipeline_steps(pipeline_steps, handle_emergencies=True):
3093 if handle_emergencies:
3094 emergency_step = create_emergency_announcement_step_if_necessary()
3095 if emergency_step:
3096 pipeline_steps.insert(0, emergency_step)
Florian Weikert13215a82019-05-10 12:42:21 +02003097
3098 print(yaml.dump({"steps": pipeline_steps}))
3099
3100
3101def create_emergency_announcement_step_if_necessary():
3102 style = "error"
3103 message, issue_url, last_good_bazel = None, None, None
3104 try:
3105 emergency_settings = load_remote_yaml_file(EMERGENCY_FILE_URL)
3106 message = emergency_settings.get("message")
3107 issue_url = emergency_settings.get("issue_url")
3108 last_good_bazel = emergency_settings.get("last_good_bazel")
3109 except urllib.error.HTTPError as ex:
3110 message = str(ex)
3111 style = "warning"
3112
3113 if not any([message, issue_url, last_good_bazel]):
3114 return
3115
3116 text = '<span class="h1">:rotating_light: Emergency :rotating_light:</span>\n'
3117 if message:
3118 text += "- {}\n".format(message)
3119 if issue_url:
3120 text += '- Please check this <a href="{}">issue</a> for more details.\n'.format(issue_url)
3121 if last_good_bazel:
3122 text += (
3123 "- Default Bazel version is *{}*, "
3124 "unless the pipeline configuration specifies an explicit version."
3125 ).format(last_good_bazel)
3126
3127 return create_step(
3128 label=":rotating_light: Emergency :rotating_light:",
Philipp Wollermann7590b962019-05-16 11:35:03 +02003129 commands=[
3130 'buildkite-agent annotate --append --style={} --context "omg" "{}"'.format(style, text)
3131 ],
Philipp Wollermann7a185322019-05-18 22:15:48 +02003132 platform=DEFAULT_PLATFORM,
Florian Weikert13215a82019-05-10 12:42:21 +02003133 )
3134
3135
Philipp Wollermanncd5694c2019-01-03 14:53:04 +01003136def runner_step(
3137 platform,
Florian Weikert843d7a02019-02-03 17:24:50 +01003138 task,
3139 task_name=None,
Philipp Wollermanncd5694c2019-01-03 14:53:04 +01003140 project_name=None,
3141 http_config=None,
3142 file_config=None,
3143 git_repository=None,
3144 git_commit=None,
3145 monitor_flaky_tests=False,
3146 use_but=False,
Florian Weikert736d06e2019-05-08 13:16:42 +02003147 shards=1,
mai93b49bad72021-05-06 00:50:34 +02003148 soft_fail=None,
Philipp Wollermanncd5694c2019-01-03 14:53:04 +01003149):
Florian Weikert42fbf792024-10-01 13:24:55 +02003150 py = PLATFORMS[platform]["python"]
3151 command = f"{py} {RUNNER_CMD} --task={task}"
Philipp Wollermann598b4a42018-02-19 17:03:36 +01003152 if http_config:
Philipp Wollermannf6be4662018-02-21 14:48:28 +01003153 command += " --http_config=" + http_config
Jakob Buchgraberc57d4ad2018-03-22 12:33:17 +01003154 if file_config:
3155 command += " --file_config=" + file_config
Philipp Wollermann598b4a42018-02-19 17:03:36 +01003156 if git_repository:
Philipp Wollermannf6be4662018-02-21 14:48:28 +01003157 command += " --git_repository=" + git_repository
Yun Peng376d2b32018-11-29 10:24:54 +01003158 if git_commit:
3159 command += " --git_commit=" + git_commit
Jakob Buchgraberc340f582018-06-22 13:48:33 +02003160 if monitor_flaky_tests:
3161 command += " --monitor_flaky_tests"
Philipp Wollermann598b4a42018-02-19 17:03:36 +01003162 if use_but:
Philipp Wollermannf6be4662018-02-21 14:48:28 +01003163 command += " --use_but"
Florian Weikert843d7a02019-02-03 17:24:50 +01003164 label = create_label(platform, project_name, task_name=task_name)
Philipp Wollermannc43d3cf2019-01-10 13:24:15 +01003165 return create_step(
Florian Weikert9d5e4c02021-05-27 15:10:20 +02003166 label=label,
3167 commands=[fetch_bazelcipy_command(), command],
3168 platform=platform,
3169 shards=shards,
3170 soft_fail=soft_fail,
Philipp Wollermannc43d3cf2019-01-10 13:24:15 +01003171 )
Jakob Buchgraber0b6a39d2018-02-17 00:45:14 +01003172
3173
3174def fetch_bazelcipy_command():
Florian Weikerte1ca35d2024-01-25 13:46:16 +01003175 return "curl -sS {0}?{1} -o bazelci.py".format(SCRIPT_URL, int(time.time()))
Jakob Buchgraber0b6a39d2018-02-17 00:45:14 +01003176
Philipp Wollermanncd5694c2019-01-03 14:53:04 +01003177
Yun Peng8975c6b2019-02-28 11:55:55 +01003178def fetch_aggregate_incompatible_flags_test_result_command():
3179 return "curl -sS {0} -o aggregate_incompatible_flags_test_result.py".format(
Philipp Wollermanne67eec42019-05-24 15:18:20 +02003180 AGGREGATE_INCOMPATIBLE_TEST_RESULT_URL
Yun Peng8975c6b2019-02-28 11:55:55 +01003181 )
3182
3183
Florian Weikertb3439b32022-11-09 11:05:16 +01003184def upload_project_pipeline_step(project_name, git_repository, http_config, file_config):
Philipp Wollermanncd5694c2019-01-03 14:53:04 +01003185 pipeline_command = (
3186 '{0} bazelci.py project_pipeline --project_name="{1}" ' + "--git_repository={2}"
Philipp Wollermann57b32682019-05-18 22:09:27 +02003187 ).format(PLATFORMS[DEFAULT_PLATFORM]["python"], project_name, git_repository)
Yun Pengc85cd0e2022-09-02 10:44:29 +02003188 pipeline_command += " --use_but"
Philipp Wollermann598b4a42018-02-19 17:03:36 +01003189 if http_config:
Philipp Wollermannf6be4662018-02-21 14:48:28 +01003190 pipeline_command += " --http_config=" + http_config
Jakob Buchgraberc57d4ad2018-03-22 12:33:17 +01003191 if file_config:
3192 pipeline_command += " --file_config=" + file_config
Chi Wangf1f20362022-02-01 13:44:45 +01003193 pipeline_command += " | tee /dev/tty | buildkite-agent pipeline upload"
Jakob Buchgraber0b6a39d2018-02-17 00:45:14 +01003194
Philipp Wollermannc43d3cf2019-01-10 13:24:15 +01003195 return create_step(
Philipp Wollermann1403d2c2019-01-10 13:15:51 +01003196 label="Setup {0}".format(project_name),
3197 commands=[fetch_bazelcipy_command(), pipeline_command],
Philipp Wollermann7a185322019-05-18 22:15:48 +02003198 platform=DEFAULT_PLATFORM,
Philipp Wollermann1403d2c2019-01-10 13:15:51 +01003199 )
Jakob Buchgraber0b6a39d2018-02-17 00:45:14 +01003200
Philipp Wollermanncd5694c2019-01-03 14:53:04 +01003201
Florian Weikert843d7a02019-02-03 17:24:50 +01003202def create_label(platform, project_name, build_only=False, test_only=False, task_name=None):
Philipp Wollermannf6be4662018-02-21 14:48:28 +01003203 if build_only and test_only:
Philipp Wollermanncd5694c2019-01-03 14:53:04 +01003204 raise BuildkiteException("build_only and test_only cannot be true at the same time")
Florian Weikert843d7a02019-02-03 17:24:50 +01003205 platform_display_name = PLATFORMS[platform]["emoji-name"]
Philipp Wollermannf6be4662018-02-21 14:48:28 +01003206
Philipp Wollermann598b4a42018-02-19 17:03:36 +01003207 if build_only:
3208 label = "Build "
Philipp Wollermannf6be4662018-02-21 14:48:28 +01003209 elif test_only:
Philipp Wollermann598b4a42018-02-19 17:03:36 +01003210 label = "Test "
Philipp Wollermann598b4a42018-02-19 17:03:36 +01003211 else:
Philipp Wollermannf6be4662018-02-21 14:48:28 +01003212 label = ""
3213
Florian Weikert843d7a02019-02-03 17:24:50 +01003214 platform_label = (
3215 "{0} on {1}".format(task_name, platform_display_name)
3216 if task_name
3217 else platform_display_name
3218 )
3219
Philipp Wollermannf6be4662018-02-21 14:48:28 +01003220 if project_name:
Florian Weikert3808e8b2023-11-13 19:52:09 +01003221 # Update get_project_name_from_job in bazel_auto_sheriff.py if you change
3222 # the expected format of "Project Foo (Task bar on OS)"
Florian Weikert843d7a02019-02-03 17:24:50 +01003223 label += "{0} ({1})".format(project_name, platform_label)
Philipp Wollermannf6be4662018-02-21 14:48:28 +01003224 else:
Florian Weikert843d7a02019-02-03 17:24:50 +01003225 label += platform_label
Philipp Wollermannf6be4662018-02-21 14:48:28 +01003226
Philipp Wollermann598b4a42018-02-19 17:03:36 +01003227 return label
Jakob Buchgraber0b6a39d2018-02-17 00:45:14 +01003228
3229
Philipp Wollermanncd5694c2019-01-03 14:53:04 +01003230def bazel_build_step(
Florian Weikert843d7a02019-02-03 17:24:50 +01003231 task,
3232 platform,
3233 project_name,
3234 http_config=None,
3235 file_config=None,
3236 build_only=False,
3237 test_only=False,
Philipp Wollermanncd5694c2019-01-03 14:53:04 +01003238):
Florian Weikert42fbf792024-10-01 13:24:55 +02003239 py = PLATFORMS[platform]["python"]
3240 pipeline_command = f"{py} {RUNNER_CMD}"
Philipp Wollermann598b4a42018-02-19 17:03:36 +01003241 if build_only:
Philipp Wollermannc52e26a2019-05-18 22:10:47 +02003242 pipeline_command += " --build_only --save_but"
Philipp Wollermann598b4a42018-02-19 17:03:36 +01003243 if test_only:
Philipp Wollermannf6be4662018-02-21 14:48:28 +01003244 pipeline_command += " --test_only"
Philipp Wollermann598b4a42018-02-19 17:03:36 +01003245 if http_config:
Philipp Wollermannf6be4662018-02-21 14:48:28 +01003246 pipeline_command += " --http_config=" + http_config
Jakob Buchgraberc57d4ad2018-03-22 12:33:17 +01003247 if file_config:
3248 pipeline_command += " --file_config=" + file_config
Florian Weikert843d7a02019-02-03 17:24:50 +01003249 pipeline_command += " --task=" + task
Jakob Buchgraber0b6a39d2018-02-17 00:45:14 +01003250
Yun Pengbe4e2eb2022-09-16 11:42:50 +02003251 step = create_step(
Philipp Wollermann1403d2c2019-01-10 13:15:51 +01003252 label=create_label(platform, project_name, build_only, test_only),
3253 commands=[fetch_bazelcipy_command(), pipeline_command],
3254 platform=platform,
3255 )
Yun Pengbe4e2eb2022-09-16 11:42:50 +02003256 # Always try to automatically retry the bazel build step, this will make
3257 # the publish bazel binaries pipeline more reliable.
3258 step["retry"] = {
3259 "automatic": [
3260 {"exit_status": "*", "limit": 3},
3261 ]
3262 }
3263 return step
Jakob Buchgraberd20ffeb2018-02-18 03:16:43 +01003264
3265
Florian Weikert5f5d3cb2019-04-15 15:36:27 +02003266def filter_tasks_that_should_be_skipped(task_configs, pipeline_steps):
3267 skip_tasks = get_skip_tasks()
3268 if not skip_tasks:
Florian Weikert5f5d3cb2019-04-15 15:36:27 +02003269 return task_configs
3270
3271 actually_skipped = []
3272 skip_tasks = set(skip_tasks)
3273 for task in list(task_configs.keys()):
3274 if task in skip_tasks:
3275 actually_skipped.append(task)
3276 del task_configs[task]
3277 skip_tasks.remove(task)
3278
3279 if not task_configs:
3280 raise BuildkiteException(
3281 "Nothing to do since all tasks in the configuration should be skipped."
3282 )
3283
3284 annotations = []
3285 if actually_skipped:
3286 annotations.append(
3287 ("info", "Skipping the following task(s): {}".format(", ".join(actually_skipped)))
3288 )
3289
3290 if skip_tasks:
3291 annotations.append(
3292 (
3293 "warning",
3294 (
3295 "The following tasks should have been skipped, "
3296 "but were not part of the configuration: {}"
3297 ).format(", ".join(skip_tasks)),
3298 )
3299 )
3300
3301 if annotations:
3302 print_skip_task_annotations(annotations, pipeline_steps)
3303
3304 return task_configs
3305
3306
3307def get_skip_tasks():
3308 value = os.getenv(SKIP_TASKS_ENV_VAR, "")
3309 return [v for v in value.split(",") if v]
3310
3311
3312def print_skip_task_annotations(annotations, pipeline_steps):
3313 commands = [
3314 "buildkite-agent annotate --style={} '{}' --context 'ctx-{}'".format(s, t, hash(t))
3315 for s, t in annotations
3316 ]
3317 pipeline_steps.append(
Philipp Wollermann7a185322019-05-18 22:15:48 +02003318 create_step(
3319 label=":pipeline: Print information about skipped tasks",
3320 commands=commands,
3321 platform=DEFAULT_PLATFORM,
3322 )
Florian Weikert5f5d3cb2019-04-15 15:36:27 +02003323 )
3324
3325
Florian Weikert843d7a02019-02-03 17:24:50 +01003326def print_bazel_publish_binaries_pipeline(task_configs, http_config, file_config):
3327 if not task_configs:
Philipp Wollermann2409c6e2018-08-07 07:37:54 +02003328 raise BuildkiteException("Bazel publish binaries pipeline configuration is empty.")
3329
Florian Weikert5f5d3cb2019-04-15 15:36:27 +02003330 pipeline_steps = []
3331 task_configs = filter_tasks_that_should_be_skipped(task_configs, pipeline_steps)
3332
Florian Weikert843d7a02019-02-03 17:24:50 +01003333 platforms = [get_platform_for_task(t, tc) for t, tc in task_configs.items()]
Philipp Wollermann783d1672019-06-06 13:35:30 +02003334
3335 # These are the platforms that the bazel_publish_binaries.yml config is actually building.
3336 configured_platforms = set(filter(should_publish_binaries_for_platform, platforms))
Philipp Wollermanna2ea5d82018-08-27 14:12:10 +02003337
Philipp Wollermann783d1672019-06-06 13:35:30 +02003338 # These are the platforms that we want to build and publish according to this script.
3339 expected_platforms = set(filter(should_publish_binaries_for_platform, PLATFORMS))
Florian Weikert843d7a02019-02-03 17:24:50 +01003340
Philipp Wollermann30f314d2021-06-11 10:51:39 +02003341 # We can skip this check if we're not on the main branch, because then we're probably
3342 # building a one-off custom debugging binary anyway.
Florian Weikert7f21ca42022-02-02 17:35:23 +01003343 if current_branch_is_main_branch():
3344 missing = expected_platforms.difference(configured_platforms)
3345 if missing:
3346 raise BuildkiteException(
3347 (
3348 "Bazel publish binaries pipeline needs to build Bazel for every commit on all publish_binary-enabled platforms. "
3349 "Please add jobs for the missing platform(s) to the pipeline config: {}".format(
3350 ", ".join(missing)
3351 )
3352 )
3353 )
Jakob Buchgraber08e8e402018-03-20 19:22:07 +01003354
Yun Pengd352b6d2018-10-17 13:28:39 +02003355 # Build Bazel
Florian Weikert843d7a02019-02-03 17:24:50 +01003356 for task, task_config in task_configs.items():
Philipp Wollermanncd5694c2019-01-03 14:53:04 +01003357 pipeline_steps.append(
Florian Weikert843d7a02019-02-03 17:24:50 +01003358 bazel_build_step(
3359 task,
3360 get_platform_for_task(task, task_config),
3361 "Bazel",
3362 http_config,
3363 file_config,
3364 build_only=True,
3365 )
Philipp Wollermanncd5694c2019-01-03 14:53:04 +01003366 )
Jakob Buchgraber4631a032018-03-22 17:12:46 +01003367
Yun Peng1ce55be2023-08-11 16:19:26 +02003368 pipeline_steps.append({"wait": None, "continue_on_failure": True})
Jakob Buchgraber9d6ca8a2018-03-22 17:30:09 +01003369
Yun Pengc2dd6522018-10-17 12:58:35 +02003370 # If all builds succeed, publish the Bazel binaries to GCS.
Philipp Wollermanncd5694c2019-01-03 14:53:04 +01003371 pipeline_steps.append(
Philipp Wollermannc43d3cf2019-01-10 13:24:15 +01003372 create_step(
Philipp Wollermann1403d2c2019-01-10 13:15:51 +01003373 label="Publish Bazel Binaries",
Philipp Wollermann57b32682019-05-18 22:09:27 +02003374 commands=[
3375 fetch_bazelcipy_command(),
3376 PLATFORMS[DEFAULT_PLATFORM]["python"] + " bazelci.py publish_binaries",
3377 ],
Philipp Wollermann7a185322019-05-18 22:15:48 +02003378 platform=DEFAULT_PLATFORM,
Philipp Wollermann1403d2c2019-01-10 13:15:51 +01003379 )
Philipp Wollermanncd5694c2019-01-03 14:53:04 +01003380 )
Jakob Buchgraber08e8e402018-03-20 19:22:07 +01003381
Yun Peng312ab252024-09-25 16:40:52 +02003382 if current_branch_is_main_branch():
3383 pipeline_steps.append({"wait": None, "continue_on_failure": False})
Yun Peng5296abb2024-09-25 14:39:09 +02003384
Yun Peng312ab252024-09-25 16:40:52 +02003385 pipeline_steps.append(
3386 create_step(
3387 label="Update last green commit for Bazel",
3388 commands=[
3389 fetch_bazelcipy_command(),
3390 PLATFORMS[DEFAULT_PLATFORM]["python"]
3391 + " bazelci.py try_update_last_green_commit",
3392 ],
3393 platform=DEFAULT_PLATFORM,
3394 )
Yun Peng5296abb2024-09-25 14:39:09 +02003395 )
Yun Peng5296abb2024-09-25 14:39:09 +02003396
Florian Weikert13215a82019-05-10 12:42:21 +02003397 print_pipeline_steps(pipeline_steps)
Jakob Buchgraber08e8e402018-03-20 19:22:07 +01003398
3399
Florian Weikert843d7a02019-02-03 17:24:50 +01003400def should_publish_binaries_for_platform(platform):
3401 if platform not in PLATFORMS:
3402 raise BuildkiteException("Unknown platform '{}'".format(platform))
3403
3404 return PLATFORMS[platform]["publish_binary"]
3405
3406
Jakob Buchgraber9952a3b2018-12-06 15:38:51 +01003407def print_disabled_projects_info_box_step():
3408 info_text = ["Downstream testing is disabled for the following projects :sadpanda:"]
3409 for project, config in DOWNSTREAM_PROJECTS.items():
3410 disabled_reason = config.get("disabled_reason", None)
3411 if disabled_reason:
3412 info_text.append("* **%s**: %s" % (project, disabled_reason))
3413
3414 if len(info_text) == 1:
3415 return None
Philipp Wollermannc43d3cf2019-01-10 13:24:15 +01003416 return create_step(
Philipp Wollermann1403d2c2019-01-10 13:15:51 +01003417 label=":sadpanda:",
3418 commands=[
Philipp Wollermanncd5694c2019-01-03 14:53:04 +01003419 'buildkite-agent annotate --append --style=info "\n' + "\n".join(info_text) + '\n"'
Jakob Buchgraber9952a3b2018-12-06 15:38:51 +01003420 ],
Philipp Wollermann7a185322019-05-18 22:15:48 +02003421 platform=DEFAULT_PLATFORM,
Philipp Wollermann1403d2c2019-01-10 13:15:51 +01003422 )
Jakob Buchgraber9952a3b2018-12-06 15:38:51 +01003423
Yun Peng6528e652019-01-02 14:41:07 +01003424
3425def print_incompatible_flags_info_box_step(incompatible_flags_map):
3426 info_text = ["Build and test with the following incompatible flags:"]
3427
3428 for flag in incompatible_flags_map:
3429 info_text.append("* **%s**: %s" % (flag, incompatible_flags_map[flag]))
3430
3431 if len(info_text) == 1:
3432 return None
Philipp Wollermannc43d3cf2019-01-10 13:24:15 +01003433 return create_step(
Philipp Wollermann1403d2c2019-01-10 13:15:51 +01003434 label="Incompatible flags info",
3435 commands=[
Philipp Wollermanncd5694c2019-01-03 14:53:04 +01003436 'buildkite-agent annotate --append --style=info "\n' + "\n".join(info_text) + '\n"'
Yun Peng6528e652019-01-02 14:41:07 +01003437 ],
Philipp Wollermann7a185322019-05-18 22:15:48 +02003438 platform=DEFAULT_PLATFORM,
Philipp Wollermann1403d2c2019-01-10 13:15:51 +01003439 )
Yun Peng6528e652019-01-02 14:41:07 +01003440
3441
Yun Peng7d302f62019-01-10 16:56:15 +01003442def fetch_incompatible_flags():
Yun Peng6528e652019-01-02 14:41:07 +01003443 """
Yun Pengc85cd0e2022-09-02 10:44:29 +02003444 Return a list of incompatible flags to be tested. The key is the flag name and the value is its Github URL.
Yun Peng6528e652019-01-02 14:41:07 +01003445 """
Philipp Wollermanncd5694c2019-01-03 14:53:04 +01003446 output = subprocess.check_output(
3447 [
Yun Pengc85cd0e2022-09-02 10:44:29 +02003448 # Query for open issues with "incompatible-change" and "migration-ready" label.
Philipp Wollermanncd5694c2019-01-03 14:53:04 +01003449 "curl",
Yun Pengc85cd0e2022-09-02 10:44:29 +02003450 "https://api.github.com/search/issues?per_page=100&q=repo:bazelbuild/bazel+label:incompatible-change+label:migration-ready+state:open",
Philipp Wollermanncd5694c2019-01-03 14:53:04 +01003451 ]
3452 ).decode("utf-8")
Yun Peng6528e652019-01-02 14:41:07 +01003453 issue_info = json.loads(output)
3454
Yun Pengc85cd0e2022-09-02 10:44:29 +02003455 FLAG_PATTERN = re.compile(r"^--[a-z][a-z0-9_]*$")
3456 incompatible_flags = {}
Yun Peng6528e652019-01-02 14:41:07 +01003457 for issue in issue_info["items"]:
Yun Peng6528e652019-01-02 14:41:07 +01003458 name = "--" + issue["title"].split(":")[0]
3459 url = issue["html_url"]
Yun Pengc85cd0e2022-09-02 10:44:29 +02003460 if FLAG_PATTERN.match(name):
Yun Peng6528e652019-01-02 14:41:07 +01003461 incompatible_flags[name] = url
3462 else:
Philipp Wollermanncd5694c2019-01-03 14:53:04 +01003463 eprint(
Philipp Wollermann639c0452019-01-03 11:23:54 +01003464 f"{name} is not recognized as an incompatible flag, please modify the issue title "
3465 f'of {url} to "<incompatible flag name (without --)>:..."'
Philipp Wollermanncd5694c2019-01-03 14:53:04 +01003466 )
Yun Peng6528e652019-01-02 14:41:07 +01003467
Yun Pengc85cd0e2022-09-02 10:44:29 +02003468 # If INCOMPATIBLE_FLAGS is set manually, we test those flags, try to keep the URL info if possible.
3469 if "INCOMPATIBLE_FLAGS" in os.environ:
3470 given_incompatible_flags = {}
3471 for flag in os.environ["INCOMPATIBLE_FLAGS"].split(","):
3472 given_incompatible_flags[flag] = incompatible_flags.get(flag, "")
3473 return given_incompatible_flags
3474
Yun Peng6528e652019-01-02 14:41:07 +01003475 return incompatible_flags
3476
3477
Philipp Wollermanncd5694c2019-01-03 14:53:04 +01003478def print_bazel_downstream_pipeline(
Yun Pengc85cd0e2022-09-02 10:44:29 +02003479 task_configs, http_config, file_config, test_disabled_projects, notify
Philipp Wollermanncd5694c2019-01-03 14:53:04 +01003480):
Philipp Wollermann598b4a42018-02-19 17:03:36 +01003481 pipeline_steps = []
Philipp Wollermann2409c6e2018-08-07 07:37:54 +02003482
Jakob Buchgraber9952a3b2018-12-06 15:38:51 +01003483 info_box_step = print_disabled_projects_info_box_step()
Jakob Buchgraber9952a3b2018-12-06 15:38:51 +01003484 if info_box_step is not None:
3485 pipeline_steps.append(info_box_step)
3486
Yun Pengc85cd0e2022-09-02 10:44:29 +02003487 if not use_bazelisk_migrate():
3488 if not task_configs:
3489 raise BuildkiteException("Bazel downstream pipeline configuration is empty.")
Florian Weikert843d7a02019-02-03 17:24:50 +01003490 for task, task_config in task_configs.items():
Yun Peng5599ca22019-01-16 12:32:41 +01003491 pipeline_steps.append(
Florian Weikert843d7a02019-02-03 17:24:50 +01003492 bazel_build_step(
3493 task,
3494 get_platform_for_task(task, task_config),
3495 "Bazel",
3496 http_config,
3497 file_config,
3498 build_only=True,
3499 )
Yun Peng5599ca22019-01-16 12:32:41 +01003500 )
Philipp Wollermann2409c6e2018-08-07 07:37:54 +02003501
Yun Peng5599ca22019-01-16 12:32:41 +01003502 pipeline_steps.append("wait")
Yun Pengc85cd0e2022-09-02 10:44:29 +02003503 else:
Yun Peng7d302f62019-01-10 16:56:15 +01003504 incompatible_flags_map = fetch_incompatible_flags()
Yun Peng3c1d7d12020-06-30 14:58:34 +02003505 if not incompatible_flags_map:
Florian Weikert9d5e4c02021-05-27 15:10:20 +02003506 step = create_step(
3507 label="No Incompatible flags info",
3508 commands=[
Florian Weikert42738ac2021-05-27 15:54:14 +02003509 'buildkite-agent annotate --style=error "No incompatible flag issue is found on github for current version of Bazel." --context "noinc"'
Florian Weikert9d5e4c02021-05-27 15:10:20 +02003510 ],
3511 platform=DEFAULT_PLATFORM,
Florian Weikertdb832a02020-11-19 19:14:48 +01003512 )
Florian Weikert9d5e4c02021-05-27 15:10:20 +02003513 pipeline_steps.append(step)
3514 print_pipeline_steps(pipeline_steps)
3515 return
3516
Yun Peng6528e652019-01-02 14:41:07 +01003517 info_box_step = print_incompatible_flags_info_box_step(incompatible_flags_map)
3518 if info_box_step is not None:
3519 pipeline_steps.append(info_box_step)
Yun Peng7a539ef2018-11-30 15:07:24 +01003520
Philipp Wollermann2409c6e2018-08-07 07:37:54 +02003521 for project, config in DOWNSTREAM_PROJECTS.items():
Yun Peng996efad2018-11-27 17:19:44 +01003522 disabled_reason = config.get("disabled_reason", None)
Yun Pengfb759fa2018-12-13 11:35:39 +01003523 # If test_disabled_projects is true, we add configs for disabled projects.
Florian Weikert7b3f17e2019-03-14 13:52:42 +01003524 # If test_disabled_projects is false, we add configs for not disabled projects.
Philipp Wollermanncd5694c2019-01-03 14:53:04 +01003525 if (test_disabled_projects and disabled_reason) or (
3526 not test_disabled_projects and not disabled_reason
3527 ):
Yun Peng996efad2018-11-27 17:19:44 +01003528 pipeline_steps.append(
Philipp Wollermanncd5694c2019-01-03 14:53:04 +01003529 upload_project_pipeline_step(
3530 project_name=project,
3531 git_repository=config["git_repository"],
3532 http_config=config.get("http_config", None),
3533 file_config=config.get("file_config", None),
Philipp Wollermanncd5694c2019-01-03 14:53:04 +01003534 )
3535 )
Yun Peng71c2d9f2022-01-05 10:33:51 +01003536
Yun Pengc85cd0e2022-09-02 10:44:29 +02003537 if use_bazelisk_migrate():
Yun Peng002eab92018-12-17 18:28:14 +01003538 current_build_number = os.environ.get("BUILDKITE_BUILD_NUMBER", None)
3539 if not current_build_number:
3540 raise BuildkiteException("Not running inside Buildkite")
Yun Pengc85cd0e2022-09-02 10:44:29 +02003541
Florian Weikertb3439b32022-11-09 11:05:16 +01003542 pipeline_steps += get_steps_for_aggregating_migration_results(current_build_number, notify)
Yun Peng002eab92018-12-17 18:28:14 +01003543
Florian Weikert2896edb2019-04-04 16:12:47 +02003544 if (
3545 not test_disabled_projects
Yun Pengc85cd0e2022-09-02 10:44:29 +02003546 and not use_bazelisk_migrate()
Philipp Wollermann1b5ecdc2021-06-10 21:52:55 +02003547 and current_branch_is_main_branch()
Florian Weikert2896edb2019-04-04 16:12:47 +02003548 ):
Florian Weikert35906542019-04-01 11:53:53 +02003549 # Only update the last green downstream commit in the regular Bazel@HEAD + Downstream pipeline.
3550 pipeline_steps.append("wait")
3551 pipeline_steps.append(
3552 create_step(
3553 label="Try Update Last Green Downstream Commit",
3554 commands=[
3555 fetch_bazelcipy_command(),
Philipp Wollermann57b32682019-05-18 22:09:27 +02003556 PLATFORMS[DEFAULT_PLATFORM]["python"]
3557 + " bazelci.py try_update_last_green_downstream_commit",
Florian Weikert35906542019-04-01 11:53:53 +02003558 ],
Philipp Wollermann7a185322019-05-18 22:15:48 +02003559 platform=DEFAULT_PLATFORM,
Florian Weikert35906542019-04-01 11:53:53 +02003560 )
3561 )
3562
Florian Weikert13215a82019-05-10 12:42:21 +02003563 print_pipeline_steps(pipeline_steps)
Jakob Buchgraber0b6a39d2018-02-17 00:45:14 +01003564
3565
Florian Weikert60661912019-12-18 15:17:10 +01003566def get_steps_for_aggregating_migration_results(current_build_number, notify):
Florian Weikert09813a02019-10-26 19:34:33 +02003567 parts = [
3568 PLATFORMS[DEFAULT_PLATFORM]["python"],
3569 "aggregate_incompatible_flags_test_result.py",
3570 "--build_number=%s" % current_build_number,
Florian Weikert09813a02019-10-26 19:34:33 +02003571 ]
Florian Weikert60661912019-12-18 15:17:10 +01003572 if notify:
3573 parts.append("--notify")
Florian Weikert09813a02019-10-26 19:34:33 +02003574 return [
3575 {"wait": "~", "continue_on_failure": "true"},
3576 create_step(
3577 label="Aggregate incompatible flags test result",
3578 commands=[
3579 fetch_bazelcipy_command(),
3580 fetch_aggregate_incompatible_flags_test_result_command(),
3581 " ".join(parts),
3582 ],
3583 platform=DEFAULT_PLATFORM,
3584 ),
3585 ]
3586
3587
Yun Pengc2dd6522018-10-17 12:58:35 +02003588def bazelci_builds_download_url(platform, git_commit):
Philipp Wollermanne67eec42019-05-24 15:18:20 +02003589 bucket_name = "bazel-testing-builds" if THIS_IS_TESTING else "bazel-builds"
3590 return "https://storage.googleapis.com/{}/artifacts/{}/{}/bazel".format(
3591 bucket_name, platform, git_commit
Philipp Wollermanncd5694c2019-01-03 14:53:04 +01003592 )
Jakob Buchgraberd20ffeb2018-02-18 03:16:43 +01003593
3594
Rupert Shuttleworth7f3a91e2020-08-22 07:31:47 -04003595def bazelci_builds_nojdk_download_url(platform, git_commit):
3596 bucket_name = "bazel-testing-builds" if THIS_IS_TESTING else "bazel-builds"
3597 return "https://storage.googleapis.com/{}/artifacts/{}/{}/bazel_nojdk".format(
3598 bucket_name, platform, git_commit
3599 )
3600
3601
Yun Peng20d45602018-10-18 13:27:05 +02003602def bazelci_builds_gs_url(platform, git_commit):
Philipp Wollermanne67eec42019-05-24 15:18:20 +02003603 bucket_name = "bazel-testing-builds" if THIS_IS_TESTING else "bazel-builds"
3604 return "gs://{}/artifacts/{}/{}/bazel".format(bucket_name, platform, git_commit)
Jakob Buchgraberd20ffeb2018-02-18 03:16:43 +01003605
3606
Rupert Shuttleworth7f3a91e2020-08-22 07:31:47 -04003607def bazelci_builds_nojdk_gs_url(platform, git_commit):
3608 bucket_name = "bazel-testing-builds" if THIS_IS_TESTING else "bazel-builds"
3609 return "gs://{}/artifacts/{}/{}/bazel_nojdk".format(bucket_name, platform, git_commit)
3610
3611
mai93f04f9482020-10-20 17:22:30 +02003612def bazelci_latest_build_metadata_url():
Philipp Wollermanne67eec42019-05-24 15:18:20 +02003613 bucket_name = "bazel-testing-builds" if THIS_IS_TESTING else "bazel-builds"
3614 return "gs://{}/metadata/latest.json".format(bucket_name)
Jakob Buchgraberd20ffeb2018-02-18 03:16:43 +01003615
3616
mai93f04f9482020-10-20 17:22:30 +02003617def bazelci_builds_metadata_url(git_commit):
3618 bucket_name = "bazel-testing-builds" if THIS_IS_TESTING else "bazel-builds"
3619 return "gs://{}/metadata/{}.json".format(bucket_name, git_commit)
3620
3621
Yun Peng996efad2018-11-27 17:19:44 +01003622def bazelci_last_green_commit_url(git_repository, pipeline_slug):
Yun Peng5296abb2024-09-25 14:39:09 +02003623 bucket_name = (
Florian Weikerte425b912024-10-02 00:22:41 +02003624 "bazel-builds"
3625 if THIS_IS_TRUSTED
3626 else "bazel-testing-builds"
3627 if THIS_IS_TESTING
3628 else "bazel-untrusted-last-green-commits"
Yun Peng5296abb2024-09-25 14:39:09 +02003629 )
Philipp Wollermanne67eec42019-05-24 15:18:20 +02003630 return "gs://{}/last_green_commit/{}/{}".format(
3631 bucket_name, git_repository[len("https://") :], pipeline_slug
Philipp Wollermanncd5694c2019-01-03 14:53:04 +01003632 )
Yun Pengafe67d42018-11-23 17:06:43 +01003633
3634
Florian Weikert35906542019-04-01 11:53:53 +02003635def bazelci_last_green_downstream_commit_url():
Florian Weikerte425b912024-10-02 00:22:41 +02003636 bucket_name = (
3637 "bazel-testing-builds" if THIS_IS_TESTING else "bazel-untrusted-last-green-commits"
3638 )
Philipp Wollermanne67eec42019-05-24 15:18:20 +02003639 return "gs://{}/last_green_commit/downstream_pipeline".format(bucket_name)
Florian Weikert35906542019-04-01 11:53:53 +02003640
3641
Yun Pengfb86f672023-11-10 16:18:55 +01003642def get_last_green_commit_by_url(last_green_commit_url):
Yun Peng61a448f2018-11-23 17:11:46 +01003643 try:
Philipp Wollermanncd5694c2019-01-03 14:53:04 +01003644 return (
3645 subprocess.check_output(
3646 [gsutil_command(), "cat", last_green_commit_url], env=os.environ
3647 )
3648 .decode("utf-8")
3649 .strip()
3650 )
Yun Peng61a448f2018-11-23 17:11:46 +01003651 except subprocess.CalledProcessError:
3652 return None
Yun Peng43239b02018-11-23 13:57:34 +01003653
3654
Yun Pengfb86f672023-11-10 16:18:55 +01003655def get_last_green_commit(project_name):
3656 last_green_commit_url = bazelci_last_green_commit_url(
Florian Weikertc89d6142023-11-20 14:50:35 +01003657 DOWNSTREAM_PROJECTS[project_name]["git_repository"],
3658 DOWNSTREAM_PROJECTS[project_name]["pipeline_slug"],
Yun Pengfb86f672023-11-10 16:18:55 +01003659 )
3660 return get_last_green_commit_by_url(last_green_commit_url)
3661
3662
Yun Peng358cd882018-11-29 10:25:18 +01003663def try_update_last_green_commit():
Florian Weikertde96a6f2019-03-07 14:57:50 +01003664 org_slug = os.getenv("BUILDKITE_ORGANIZATION_SLUG")
Yun Peng358cd882018-11-29 10:25:18 +01003665 pipeline_slug = os.getenv("BUILDKITE_PIPELINE_SLUG")
Florian Weikertde96a6f2019-03-07 14:57:50 +01003666 build_number = os.getenv("BUILDKITE_BUILD_NUMBER")
3667 current_job_id = os.getenv("BUILDKITE_JOB_ID")
3668
3669 client = BuildkiteClient(org=org_slug, pipeline=pipeline_slug)
3670 build_info = client.get_build_info(build_number)
3671
mai9302a609c2021-05-20 10:36:46 +02003672 # Find any failing steps other than Buildifier and steps with soft_fail enabled then "try update last green".
Philipp Wollermannce986af2019-07-18 14:46:05 +02003673 def has_failed(job):
Florian Weikertbd40a272019-03-08 10:20:18 +01003674 state = job.get("state")
3675 # Ignore steps that don't have a state (like "wait").
Florian Weikertde96a6f2019-03-07 14:57:50 +01003676 return (
Florian Weikertcb00eea2024-07-09 16:54:00 +02003677 state
Florian Weikert35906542019-04-01 11:53:53 +02003678 and state != "passed"
mai9302a609c2021-05-20 10:36:46 +02003679 and not job.get("soft_failed")
Florian Weikertde96a6f2019-03-07 14:57:50 +01003680 and job["id"] != current_job_id
Florian Weikert42fbf792024-10-01 13:24:55 +02003681 and RUNNER_CMD in job.get("command", "") # Only look at test steps
Florian Weikertde96a6f2019-03-07 14:57:50 +01003682 )
3683
Philipp Wollermannce986af2019-07-18 14:46:05 +02003684 failing_jobs = [j["name"] for j in build_info["jobs"] if has_failed(j)]
Florian Weikertde96a6f2019-03-07 14:57:50 +01003685 if failing_jobs:
3686 raise BuildkiteException(
3687 "Cannot update last green commit due to {} failing step(s): {}".format(
3688 len(failing_jobs), ", ".join(failing_jobs)
3689 )
3690 )
3691
Yun Peng358cd882018-11-29 10:25:18 +01003692 git_repository = os.getenv("BUILDKITE_REPO")
Florian Weikert35906542019-04-01 11:53:53 +02003693 last_green_commit_url = bazelci_last_green_commit_url(git_repository, pipeline_slug)
3694 update_last_green_commit_if_newer(last_green_commit_url)
3695
3696
3697def update_last_green_commit_if_newer(last_green_commit_url):
Yun Pengfb86f672023-11-10 16:18:55 +01003698 last_green_commit = get_last_green_commit_by_url(last_green_commit_url)
Florian Weikert5e70d9d2023-05-08 19:20:23 +02003699 current_commit = resolve_revision("HEAD")
Yun Peng358cd882018-11-29 10:25:18 +01003700 if last_green_commit:
Jakob Buchgraber7c7ceee2019-10-28 10:28:58 +01003701 success = False
3702 try:
3703 execute_command(["git", "fetch", "-v", "origin", last_green_commit])
3704 success = True
3705 except subprocess.CalledProcessError:
3706 # If there was an error fetching the commit it typically means
3707 # that the commit does not exist anymore - due to a force push. In
3708 # order to recover from that assume that the current commit is the
3709 # newest commit.
3710 result = [current_commit]
3711 finally:
3712 if success:
3713 result = (
3714 subprocess.check_output(
3715 ["git", "rev-list", "%s..%s" % (last_green_commit, current_commit)]
3716 )
3717 .decode("utf-8")
3718 .strip()
3719 )
Philipp Wollermannce986af2019-07-18 14:46:05 +02003720 else:
3721 result = None
Yun Peng358cd882018-11-29 10:25:18 +01003722
Philipp Wollermann639c0452019-01-03 11:23:54 +01003723 # If current_commit is newer that last_green_commit, `git rev-list A..B` will output a bunch of
3724 # commits, otherwise the output should be empty.
Yun Peng358cd882018-11-29 10:25:18 +01003725 if not last_green_commit or result:
Philipp Wollermanncd5694c2019-01-03 14:53:04 +01003726 execute_command(
Philipp Wollermann76a7eac2020-02-17 18:29:52 +01003727 [
3728 "echo %s | %s -h 'Cache-Control: no-store' cp - %s"
3729 % (current_commit, gsutil_command(), last_green_commit_url)
3730 ],
Philipp Wollermanncd5694c2019-01-03 14:53:04 +01003731 shell=True,
3732 )
Yun Peng358cd882018-11-29 10:25:18 +01003733 else:
Philipp Wollermanncd5694c2019-01-03 14:53:04 +01003734 eprint(
3735 "Updating abandoned: last green commit (%s) is not older than current commit (%s)."
3736 % (last_green_commit, current_commit)
3737 )
3738
Yun Peng358cd882018-11-29 10:25:18 +01003739
Florian Weikert5e70d9d2023-05-08 19:20:23 +02003740def resolve_revision(rev):
3741 return subprocess.check_output(["git", "rev-parse", rev]).decode("utf-8").strip()
3742
3743
Florian Weikert35906542019-04-01 11:53:53 +02003744def try_update_last_green_downstream_commit():
3745 last_green_commit_url = bazelci_last_green_downstream_commit_url()
3746 update_last_green_commit_if_newer(last_green_commit_url)
3747
3748
Jakob Buchgraber76381e02018-02-19 16:19:56 +01003749def latest_generation_and_build_number():
Philipp Wollermannce986af2019-07-18 14:46:05 +02003750 generation = None
Philipp Wollermann598b4a42018-02-19 17:03:36 +01003751 output = None
Philipp Wollermannce986af2019-07-18 14:46:05 +02003752 for attempt in range(5):
Philipp Wollermann598b4a42018-02-19 17:03:36 +01003753 output = subprocess.check_output(
mai93f04f9482020-10-20 17:22:30 +02003754 [gsutil_command(), "stat", bazelci_latest_build_metadata_url()], env=os.environ
Philipp Wollermanncd5694c2019-01-03 14:53:04 +01003755 )
Philipp Wollermann598b4a42018-02-19 17:03:36 +01003756 match = re.search("Generation:[ ]*([0-9]+)", output.decode("utf-8"))
3757 if not match:
Philipp Wollermann2409c6e2018-08-07 07:37:54 +02003758 raise BuildkiteException("Couldn't parse generation. gsutil output format changed?")
Philipp Wollermann598b4a42018-02-19 17:03:36 +01003759 generation = match.group(1)
Jakob Buchgraberd20ffeb2018-02-18 03:16:43 +01003760
Philipp Wollermannff39ef52018-02-21 14:18:52 +01003761 match = re.search(r"Hash \(md5\):[ ]*([^\s]+)", output.decode("utf-8"))
Philipp Wollermann598b4a42018-02-19 17:03:36 +01003762 if not match:
Philipp Wollermann2409c6e2018-08-07 07:37:54 +02003763 raise BuildkiteException("Couldn't parse md5 hash. gsutil output format changed?")
Philipp Wollermann598b4a42018-02-19 17:03:36 +01003764 expected_md5hash = base64.b64decode(match.group(1))
Jakob Buchgraberd20ffeb2018-02-18 03:16:43 +01003765
Philipp Wollermann598b4a42018-02-19 17:03:36 +01003766 output = subprocess.check_output(
mai93f04f9482020-10-20 17:22:30 +02003767 [gsutil_command(), "cat", bazelci_latest_build_metadata_url()], env=os.environ
Philipp Wollermanncd5694c2019-01-03 14:53:04 +01003768 )
Philipp Wollermann598b4a42018-02-19 17:03:36 +01003769 hasher = hashlib.md5()
3770 hasher.update(output)
3771 actual_md5hash = hasher.digest()
Jakob Buchgraberd20ffeb2018-02-18 03:16:43 +01003772
Philipp Wollermann598b4a42018-02-19 17:03:36 +01003773 if expected_md5hash == actual_md5hash:
3774 break
Philipp Wollermann598b4a42018-02-19 17:03:36 +01003775 info = json.loads(output.decode("utf-8"))
Philipp Wollermannce986af2019-07-18 14:46:05 +02003776 return generation, info["build_number"]
Jakob Buchgraberd20ffeb2018-02-18 03:16:43 +01003777
Jakob Buchgraber699aece2018-02-19 12:49:30 +01003778
Jakob Buchgraber88083fd2018-02-18 17:23:35 +01003779def sha256_hexdigest(filename):
Philipp Wollermann598b4a42018-02-19 17:03:36 +01003780 sha256 = hashlib.sha256()
Philipp Wollermann2409c6e2018-08-07 07:37:54 +02003781 with open(filename, "rb") as f:
3782 for block in iter(lambda: f.read(65536), b""):
Philipp Wollermann598b4a42018-02-19 17:03:36 +01003783 sha256.update(block)
3784 return sha256.hexdigest()
Jakob Buchgraber699aece2018-02-19 12:49:30 +01003785
Jakob Buchgraberd20ffeb2018-02-18 03:16:43 +01003786
Florian Weikert78e67de2024-02-06 12:58:41 +01003787def print_shard_summary():
3788 tmpdir = tempfile.mkdtemp()
3789 try:
3790 print_collapsed_group("Fetching test artifacts...")
Florian Weikert0f3deb92024-07-09 12:35:01 +02003791 all_test_artifacts = get_test_artifacts()
Florian Weikertee5ea982024-06-21 19:59:31 +02003792 print_collapsed_group("Downloading & parsing BEP files...")
Florian Weikert0f3deb92024-07-09 12:35:01 +02003793 perf_stats = []
Florian Weikert78e67de2024-02-06 12:58:41 +01003794 for base_task, current_test_artifacts in all_test_artifacts.items():
3795 failures = []
3796 for test_artifact in current_test_artifacts:
3797 local_bep_path = test_artifact.download_bep(tmpdir)
3798 if not local_bep_path:
Florian Weikertb345b652024-03-06 12:34:25 +01003799 eprint(
3800 f"Skipping step {test_artifact.job_id} since "
3801 "{test_artifact.relative_bep_path} could not be downloaded..."
3802 )
Florian Weikert78e67de2024-02-06 12:58:41 +01003803 continue
3804
3805 for test_execution in parse_bep(local_bep_path):
Florian Weikert0f3deb92024-07-09 12:35:01 +02003806 for shard in test_execution.shards:
3807 perf_stats.append(shard.get_metrics(test_execution.label, test_artifact))
3808
Florian Weikert78e67de2024-02-06 12:58:41 +01003809 if test_execution.overall_status == "PASSED":
3810 continue
3811
Florian Weikertee5ea982024-06-21 19:59:31 +02003812 failures.append(
3813 test_execution.Format(test_artifact.job_id, test_artifact.is_windows)
3814 )
Florian Weikert78e67de2024-02-06 12:58:41 +01003815
3816 if failures:
3817 message = "\n".join(failures)
3818 execute_command(
3819 [
3820 "buildkite-agent",
3821 "annotate",
3822 "--style=error",
3823 f"**{base_task} Failures**\n\n{message}",
3824 "--context",
3825 f"{base_task}",
3826 ]
3827 )
Florian Weikert0f3deb92024-07-09 12:35:01 +02003828
3829 if perf_stats:
3830 slowest = sorted(perf_stats, key=lambda x: -x[0])
3831 message = "\n".join(
3832 f"{rank}. {tpl[1]}" for rank, tpl in enumerate(slowest[:_SLOWEST_N_TARGETS], 1)
3833 )
3834 execute_command(
3835 [
3836 "buildkite-agent",
3837 "annotate",
3838 "--style=warning",
3839 f"**Slowest {_SLOWEST_N_TARGETS} targets**\n\n{message}",
3840 "--context",
3841 "slowest_targets",
3842 ]
3843 )
Florian Weikertb345b652024-03-06 12:34:25 +01003844 except Exception as ex:
3845 eprint(f"Failed to print shard summary: {ex}")
Florian Weikert78e67de2024-02-06 12:58:41 +01003846 finally:
3847 shutil.rmtree(tmpdir)
3848
3849
Florian Weikertee5ea982024-06-21 19:59:31 +02003850def get_log_path_for_label(label, shard, total_shards, attempt, total_attempts, is_windows):
Florian Weikert78e67de2024-02-06 12:58:41 +01003851 parts = [label.lstrip("/").replace(":", "/")]
3852 if total_shards > 1:
3853 parts.append(f"shard_{shard}_of_{total_shards}")
3854 if total_attempts > 1:
3855 parts.append(f"test_attempts/attempt_{attempt}.log")
3856 else:
3857 parts.append("test.log")
3858
Florian Weikertee5ea982024-06-21 19:59:31 +02003859 path = "/".join(parts)
3860 return path.replace("/", ESCAPED_BACKSLASH) if is_windows else path
Florian Weikert78e67de2024-02-06 12:58:41 +01003861
3862
Florian Weikert0f3deb92024-07-09 12:35:01 +02003863def get_test_artifacts():
Florian Weikert78e67de2024-02-06 12:58:41 +01003864 org_slug = os.getenv("BUILDKITE_ORGANIZATION_SLUG")
3865 pipeline_slug = os.getenv("BUILDKITE_PIPELINE_SLUG")
3866 build_number = os.getenv("BUILDKITE_BUILD_NUMBER")
3867
3868 client = BuildkiteClient(org=org_slug, pipeline=pipeline_slug)
3869 build_info = client.get_build_info(build_number)
3870
3871 paths = collections.defaultdict(list)
3872 for job in build_info["jobs"]:
Florian Weikert0f3deb92024-07-09 12:35:01 +02003873 state = job.get("state")
3874 if not state: # Wait steps etc.
Florian Weikert78e67de2024-02-06 12:58:41 +01003875 continue
3876
Florian Weikert0f3deb92024-07-09 12:35:01 +02003877 job_name = job.get("name", "")
Florian Weikert78e67de2024-02-06 12:58:41 +01003878 # This is a bit hacky, but saves us one API request per job (to check for BUILDKITE_PARALLEL_JOB)
Florian Weikert0f3deb92024-07-09 12:35:01 +02003879 match = _SHARD_RE.search(job_name)
3880 base_task = match.group(1) if match else job_name
Florian Weikert78e67de2024-02-06 12:58:41 +01003881
3882 relative_bep_path, relative_log_paths = get_test_file_paths(job["id"])
Florian Weikert78e67de2024-02-06 12:58:41 +01003883 if not relative_bep_path:
3884 continue
3885
Florian Weikert78e67de2024-02-06 12:58:41 +01003886 ta = TestArtifacts(
3887 job_id=job["id"],
Florian Weikert0f3deb92024-07-09 12:35:01 +02003888 job_name=job_name,
3889 job_url=job.get("web_url"),
Florian Weikert78e67de2024-02-06 12:58:41 +01003890 relative_bep_path=relative_bep_path,
3891 relative_log_paths=relative_log_paths,
3892 )
3893 paths[base_task].append(ta)
3894
3895 return paths
3896
3897
3898class TestArtifacts:
Florian Weikert0f3deb92024-07-09 12:35:01 +02003899 def __init__(self, job_id, job_name, job_url, relative_bep_path, relative_log_paths) -> None:
Florian Weikert78e67de2024-02-06 12:58:41 +01003900 self.job_id = job_id
Florian Weikert0f3deb92024-07-09 12:35:01 +02003901 self.job_name = job_name
3902 self.job_url = job_url
Florian Weikert78e67de2024-02-06 12:58:41 +01003903 self.relative_bep_path = relative_bep_path
3904 self.relative_log_paths = relative_log_paths
3905
3906 def download_bep(self, dest_dir: str) -> str:
Florian Weikertb345b652024-03-06 12:34:25 +01003907 if not LOG_BUCKET:
3908 return None
3909
Florian Weikert78e67de2024-02-06 12:58:41 +01003910 job_dir = os.path.join(dest_dir, self.job_id)
3911 os.makedirs(job_dir)
3912
Florian Weikertb345b652024-03-06 12:34:25 +01003913 # We cannot use `buildkite agent download *` since it cannot handle backslashes in Windows artifact paths.
3914 # Consequently, we just escape all backslashes and download files directly from GCS.
Florian Weikertee5ea982024-06-21 19:59:31 +02003915 url = "/".join(
3916 [LOG_BUCKET, self.job_id, self.relative_bep_path.replace("\\", ESCAPED_BACKSLASH)]
3917 )
Florian Weikert78e67de2024-02-06 12:58:41 +01003918 try:
Florian Weikertb345b652024-03-06 12:34:25 +01003919 return download_file(url, job_dir, _TEST_BEP_FILE)
Florian Weikert78e67de2024-02-06 12:58:41 +01003920 except:
3921 # TODO: handle exception
3922 return None
3923
Florian Weikertee5ea982024-06-21 19:59:31 +02003924 @property
3925 def is_windows(self) -> bool:
3926 # Dirty hack
3927 return "\\" in self.relative_bep_path
3928
Florian Weikert78e67de2024-02-06 12:58:41 +01003929
3930def get_test_file_paths(job_id):
3931 bep_path = None
3932 log_paths = []
3933
3934 output = execute_command_and_get_output(
3935 [
3936 "buildkite-agent",
3937 "artifact",
3938 "search",
3939 "*",
3940 "--step",
3941 job_id,
3942 ],
3943 fail_if_nonzero=False,
3944 ).strip()
3945
3946 if not output or "no matches found" in output:
3947 return None, []
3948
3949 for line in output.split("\n"):
3950 parts = line.split(" ")
3951 # Expected format:
3952 # JOB_ID FILE_PATH TIMESTAMP
3953 if len(parts) != 3:
3954 continue
3955
3956 path = parts[1]
3957 if path.endswith(_TEST_BEP_FILE):
3958 bep_path = path
3959 elif path.endswith(".log"):
3960 log_paths.append(path)
3961
3962 return bep_path, log_paths
3963
3964
3965def format_millis(millis):
3966 def fmt(ms):
3967 return "{:.1f}s".format(ms / 1000)
3968
3969 if len(millis) == 1:
3970 return fmt(millis[0])
3971
3972 total = sum(millis)
3973 return f"{fmt(total)} ({' + '.join(fmt(ms) for ms in millis)})"
3974
3975
3976def format_test_status(status):
3977 cls = {"PASSED": "green", "FLAKY": "purple"}.get(status, "red")
3978 return f"<span class='{cls}'>{status}</span>"
3979
3980
3981# TODO here and below: use @dataclasses.dataclass(frozen=True) once Python has been updated on Docker machines
3982class TestAttempt:
Florian Weikertada80842024-05-21 16:27:56 +02003983 def __init__(self, number, status, millis, root_cause) -> None:
Florian Weikert78e67de2024-02-06 12:58:41 +01003984 self.number = number
3985 self.status = status
3986 self.millis = millis
Florian Weikertada80842024-05-21 16:27:56 +02003987 self.root_cause = root_cause
Florian Weikert78e67de2024-02-06 12:58:41 +01003988
3989
3990class TestShard:
Florian Weikert0f3deb92024-07-09 12:35:01 +02003991 def __init__(self, number, total_shards, attempts) -> None:
Florian Weikert78e67de2024-02-06 12:58:41 +01003992 self.number = number
Florian Weikert0f3deb92024-07-09 12:35:01 +02003993 self.total_shards = total_shards
Florian Weikert78e67de2024-02-06 12:58:41 +01003994 self.attempts = attempts
3995
3996 def _get_detailed_overall_status(self):
3997 counter = collections.Counter([a.status for a in self.attempts])
3998 passed = counter["PASSED"]
3999 no_attempts = len(self.attempts)
4000 if passed == no_attempts:
4001 return "PASSED", no_attempts, no_attempts
4002 elif passed and passed < no_attempts:
4003 return "FLAKY", no_attempts - passed, no_attempts
4004 elif counter["FAILED"]:
4005 return "FAILED", counter["FAILED"], no_attempts
4006
4007 [(status, count)] = counter.most_common(1)
4008 return status, count, no_attempts
4009
4010 def get_details(self):
4011 overall, bad_runs, total_runs = self._get_detailed_overall_status()
4012 qualifier = "" if not bad_runs else f"{bad_runs} out of "
Florian Weikert0f3deb92024-07-09 12:35:01 +02004013 runs = f" in {qualifier}{total_runs} runs" if total_runs > 1 else ""
4014 time = f" in {format_millis(self.attempt_millis)}" if self.attempt_millis else ""
Florian Weikertada80842024-05-21 16:27:56 +02004015 cause = f" because of {self.root_cause}" if self.root_cause else ""
Florian Weikert0f3deb92024-07-09 12:35:01 +02004016 return overall, f"{runs}{time}{cause}"
4017
4018 def get_metrics(self, label, test_artifact):
4019 total_time = sum(self.attempt_millis)
4020 shard_info = "" if self.total_shards == 1 else f" (shard {self.number}/{self.total_shards})"
4021 overall, stats = self.get_details()
4022 return (
4023 total_time,
4024 f"{label}{shard_info} on {test_artifact.job_name}: {format_test_status(overall)} {stats} ([log]({test_artifact.job_url}))",
4025 )
Florian Weikert78e67de2024-02-06 12:58:41 +01004026
4027 @property
4028 def overall_status(self):
4029 return self._get_detailed_overall_status()[0]
4030
4031 @property
4032 def attempt_millis(self):
4033 return [a.millis for a in self.attempts]
4034
Florian Weikertada80842024-05-21 16:27:56 +02004035 @property
4036 def root_cause(self):
4037 for a in self.attempts:
4038 if a.root_cause:
4039 return a.root_cause
4040
Florian Weikert78e67de2024-02-06 12:58:41 +01004041
4042class TestExecution:
4043 def __init__(self, label, shards) -> None:
4044 self.label = label
4045 self.shards = shards
4046
4047 @property
4048 def overall_status(self):
4049 status_set = set(s.overall_status for s in self.shards)
4050 if len(status_set) > 1:
4051 for status in (
4052 "FAILED",
4053 "TIMEOUT",
4054 "NO_STATUS",
4055 "INCOMPLETE",
4056 "REMOTE_FAILURE",
4057 "FAILED_TO_BUILD",
4058 "PASSED",
4059 ):
4060 if status in status_set:
4061 return status
4062
4063 return next(iter(status_set))
4064
4065 @property
4066 def critical_path(self):
4067 max_millis = 0
4068 path = None
4069
4070 for s in self.shards:
4071 duration_millis = sum(s.attempt_millis)
4072 if duration_millis > max_millis:
4073 max_millis = duration_millis
4074 path = s.attempt_millis
4075
4076 return format_millis(path)
4077
Florian Weikertee5ea982024-06-21 19:59:31 +02004078 def Format(self, job_id: str, is_windows: bool) -> str:
Florian Weikert78e67de2024-02-06 12:58:41 +01004079 def get_log_url_for_shard(s):
4080 local_log_path = get_log_path_for_label(
4081 self.label,
4082 s.number,
4083 len(self.shards),
4084 1,
4085 len(s.attempts),
Florian Weikertee5ea982024-06-21 19:59:31 +02004086 is_windows,
Florian Weikert78e67de2024-02-06 12:58:41 +01004087 )
4088 # TODO: check in relative_log_paths if log really exists?
4089 return os.path.join(LOG_BUCKET, job_id, local_log_path)
4090
4091 def format_shard(s):
Florian Weikert051b05f2024-02-06 13:08:29 +01004092 overall, statistics = s.get_details()
Florian Weikert9fe262a2024-05-21 14:32:26 +02004093 # There are no per-target log files for FAILED_TO_BUILD tests, so we simply
4094 # link to the step's test log in this case.
4095 log = f"#{job_id}" if overall == "FAILED_TO_BUILD" else get_log_url_for_shard(s)
4096 return f"{format_test_status(overall)} {statistics}: [log]({log})"
Florian Weikert78e67de2024-02-06 12:58:41 +01004097
4098 failing_shards = [s for s in self.shards if s.overall_status != "PASSED"]
4099 if len(failing_shards) == 1:
4100 [shard] = failing_shards
4101 # TODO: show log links for failing attempts > 1?
4102 return f"- {self.label} {format_shard(shard)}"
4103
Florian Weikert0c294322024-02-06 13:40:16 +01004104 shard_info = "\n".join(
Florian Weikert78e67de2024-02-06 12:58:41 +01004105 f" - Shard {s.number}/{len(self.shards)}: {format_shard(s)}" for s in failing_shards
4106 )
4107 return f"- {self.label}\n{shard_info}"
4108
4109
4110def parse_bep(path):
4111 data = collections.defaultdict(dict)
Florian Weikertada80842024-05-21 16:27:56 +02004112 for test, shard, attempt, status, millis, root_cause in get_test_results_from_bep(path):
4113 ta = TestAttempt(number=attempt, status=status, millis=millis, root_cause=root_cause)
Florian Weikert78e67de2024-02-06 12:58:41 +01004114 if shard not in data[test]:
4115 data[test][shard] = []
4116
4117 data[test][shard].append(ta)
4118
4119 tests = []
4120 for test, attempts_per_shard in data.items():
4121 shards = [
Florian Weikert0f3deb92024-07-09 12:35:01 +02004122 TestShard(
4123 number=shard, total_shards=len(data[test]), attempts=attempts_per_shard[shard]
4124 )
Florian Weikert78e67de2024-02-06 12:58:41 +01004125 for shard in sorted(attempts_per_shard.keys())
4126 ]
4127 tests.append(TestExecution(label=test, shards=shards))
4128
4129 return tests
4130
4131
4132def get_test_results_from_bep(path):
4133 with open(path, "rt") as f:
4134 for line in f:
Florian Weikert78e67de2024-02-06 12:58:41 +01004135 data = json.loads(line)
Florian Weikert9fe262a2024-05-21 14:32:26 +02004136 entry = data.get("id", {})
4137 test_result = entry.get("testResult")
4138 target_completed = entry.get("targetCompleted")
Florian Weikert78e67de2024-02-06 12:58:41 +01004139
Florian Weikert9fe262a2024-05-21 14:32:26 +02004140 if test_result:
4141 if "testResult" not in data:
Florian Weikert9fe262a2024-05-21 14:32:26 +02004142 continue
Florian Weikertce874542024-02-08 18:45:18 +01004143
Florian Weikert9fe262a2024-05-21 14:32:26 +02004144 yield (
4145 test_result["label"],
4146 test_result["shard"],
4147 test_result["attempt"],
4148 data["testResult"]["status"],
4149 int(data["testResult"]["testAttemptDurationMillis"]),
Florian Weikertada80842024-05-21 16:27:56 +02004150 None,
Florian Weikert9fe262a2024-05-21 14:32:26 +02004151 )
4152 elif target_completed:
4153 # There are no "testResult" events for targets that fail to build,
4154 # so we have to check for targetCompleted events that have a failureDetail
4155 # message.
4156 if data.get("completed", {}).get("failureDetail"):
4157 yield (
4158 target_completed["label"],
4159 1,
4160 1,
4161 "FAILED_TO_BUILD",
4162 0,
Florian Weikertada80842024-05-21 16:27:56 +02004163 get_root_cause(data),
Florian Weikert9fe262a2024-05-21 14:32:26 +02004164 )
Florian Weikert78e67de2024-02-06 12:58:41 +01004165
4166
Florian Weikertada80842024-05-21 16:27:56 +02004167def get_root_cause(bep_event):
4168 for c in bep_event.get("children", ()):
4169 for v in c.values():
4170 if "label" in v:
4171 return v["label"]
4172
4173
Philipp Wollermann02955272019-04-18 18:00:48 +02004174def upload_bazel_binaries():
4175 """
4176 Uploads all Bazel binaries to a deterministic URL based on the current Git commit.
4177
Rupert Shuttleworth7f3a91e2020-08-22 07:31:47 -04004178 Returns maps of platform names to sha256 hashes of the corresponding bazel and bazel_nojdk binaries.
Philipp Wollermann02955272019-04-18 18:00:48 +02004179 """
Rupert Shuttleworth7f3a91e2020-08-22 07:31:47 -04004180 bazel_hashes = {}
4181 bazel_nojdk_hashes = {}
Yun Peng1ce55be2023-08-11 16:19:26 +02004182 error = None
Philipp Wollermannbdd4bf92019-06-06 14:43:50 +02004183 for platform_name, platform in PLATFORMS.items():
Philipp Wollermann783d1672019-06-06 13:35:30 +02004184 if not should_publish_binaries_for_platform(platform_name):
4185 continue
Jakob Buchgraberb13a9a82018-03-27 18:37:09 +02004186 tmpdir = tempfile.mkdtemp()
4187 try:
Philipp Wollermann783d1672019-06-06 13:35:30 +02004188 bazel_binary_path = download_bazel_binary(tmpdir, platform_name)
4189 # One platform that we build on can generate binaries for multiple platforms, e.g.
Philipp Wollermannf4aabb72019-06-25 15:59:00 +02004190 # the centos7 platform generates binaries for the "centos7" platform, but also
Philipp Wollermann783d1672019-06-06 13:35:30 +02004191 # for the generic "linux" platform.
4192 for target_platform_name in platform["publish_binary"]:
4193 execute_command(
4194 [
4195 gsutil_command(),
4196 "cp",
4197 bazel_binary_path,
4198 bazelci_builds_gs_url(target_platform_name, os.environ["BUILDKITE_COMMIT"]),
4199 ]
4200 )
Rupert Shuttleworth7f3a91e2020-08-22 07:31:47 -04004201 bazel_hashes[target_platform_name] = sha256_hexdigest(bazel_binary_path)
4202
4203 # Also publish bazel_nojdk binaries.
4204 bazel_nojdk_binary_path = download_bazel_nojdk_binary(tmpdir, platform_name)
4205 for target_platform_name in platform["publish_binary"]:
4206 execute_command(
4207 [
4208 gsutil_command(),
4209 "cp",
4210 bazel_nojdk_binary_path,
Florian Weikertdb832a02020-11-19 19:14:48 +01004211 bazelci_builds_nojdk_gs_url(
4212 target_platform_name, os.environ["BUILDKITE_COMMIT"]
4213 ),
Rupert Shuttleworth7f3a91e2020-08-22 07:31:47 -04004214 ]
4215 )
4216 bazel_nojdk_hashes[target_platform_name] = sha256_hexdigest(bazel_nojdk_binary_path)
Philipp Wollermann30f314d2021-06-11 10:51:39 +02004217 except subprocess.CalledProcessError as e:
Yun Peng1ce55be2023-08-11 16:19:26 +02004218 eprint(
4219 "Failured to download and publish Bazel binary for platform {}: {}".format(
4220 platform_name, e
Philipp Wollermann30f314d2021-06-11 10:51:39 +02004221 )
Yun Peng1ce55be2023-08-11 16:19:26 +02004222 )
4223 error = e
Jakob Buchgraberb13a9a82018-03-27 18:37:09 +02004224 finally:
4225 shutil.rmtree(tmpdir)
Yun Peng1ce55be2023-08-11 16:19:26 +02004226 # If we're not on the main branch, we're probably building a custom one-off binary and
4227 # ignore failures for individual platforms (it's possible that we didn't build binaries
4228 # for all platforms).
4229 if error and current_branch_is_main_branch():
4230 raise error
Rupert Shuttleworth7f3a91e2020-08-22 07:31:47 -04004231 return bazel_hashes, bazel_nojdk_hashes
Philipp Wollermann02955272019-04-18 18:00:48 +02004232
4233
Rupert Shuttleworth7f3a91e2020-08-22 07:31:47 -04004234def try_publish_binaries(bazel_hashes, bazel_nojdk_hashes, build_number, expected_generation):
Philipp Wollermann02955272019-04-18 18:00:48 +02004235 """
4236 Uploads the info.json file that contains information about the latest Bazel commit that was
4237 successfully built on CI.
4238 """
4239 now = datetime.datetime.now()
4240 git_commit = os.environ["BUILDKITE_COMMIT"]
4241 info = {
4242 "build_number": build_number,
4243 "build_time": now.strftime("%d-%m-%Y %H:%M"),
4244 "git_commit": git_commit,
4245 "platforms": {},
4246 }
Rupert Shuttleworth7f3a91e2020-08-22 07:31:47 -04004247 for platform, sha256 in bazel_hashes.items():
Philipp Wollermann02955272019-04-18 18:00:48 +02004248 info["platforms"][platform] = {
4249 "url": bazelci_builds_download_url(platform, git_commit),
Philipp Wollermann783d1672019-06-06 13:35:30 +02004250 "sha256": sha256,
Rupert Shuttleworth7f3a91e2020-08-22 07:31:47 -04004251 "nojdk_url": bazelci_builds_nojdk_download_url(platform, git_commit),
4252 "nojdk_sha256": bazel_nojdk_hashes[platform],
Philipp Wollermann02955272019-04-18 18:00:48 +02004253 }
Jakob Buchgraberb13a9a82018-03-27 18:37:09 +02004254 tmpdir = tempfile.mkdtemp()
4255 try:
Philipp Wollermann598b4a42018-02-19 17:03:36 +01004256 info_file = os.path.join(tmpdir, "info.json")
4257 with open(info_file, mode="w", encoding="utf-8") as fp:
Jakob Buchgraber609a20e2018-02-25 17:06:51 +01004258 json.dump(info, fp, indent=2, sort_keys=True)
Philipp Wollermanndcaddd92018-02-21 14:13:43 +01004259
4260 try:
Philipp Wollermanncd5694c2019-01-03 14:53:04 +01004261 execute_command(
4262 [
4263 gsutil_command(),
4264 "-h",
4265 "x-goog-if-generation-match:" + expected_generation,
4266 "-h",
4267 "Content-Type:application/json",
4268 "cp",
Philipp Wollermanncd5694c2019-01-03 14:53:04 +01004269 info_file,
mai93f04f9482020-10-20 17:22:30 +02004270 bazelci_latest_build_metadata_url(),
Philipp Wollermanncd5694c2019-01-03 14:53:04 +01004271 ]
4272 )
Philipp Wollermanndcaddd92018-02-21 14:13:43 +01004273 except subprocess.CalledProcessError:
4274 raise BinaryUploadRaceException()
mai93f04f9482020-10-20 17:22:30 +02004275
4276 execute_command(
4277 [
4278 gsutil_command(),
4279 "cp",
4280 bazelci_latest_build_metadata_url(),
4281 bazelci_builds_metadata_url(git_commit),
4282 ]
4283 )
Philipp Wollermann598b4a42018-02-19 17:03:36 +01004284 finally:
Philipp Wollermann3e1a7712018-02-19 17:34:24 +01004285 shutil.rmtree(tmpdir)
Jakob Buchgraberd20ffeb2018-02-18 03:16:43 +01004286
4287
Jakob Buchgraber76381e02018-02-19 16:19:56 +01004288def publish_binaries():
Philipp Wollermanndb024862018-02-19 17:16:56 +01004289 """
Philipp Wollermann598b4a42018-02-19 17:03:36 +01004290 Publish Bazel binaries to GCS.
Philipp Wollermanndb024862018-02-19 17:16:56 +01004291 """
Philipp Wollermanndcaddd92018-02-21 14:13:43 +01004292 current_build_number = os.environ.get("BUILDKITE_BUILD_NUMBER", None)
4293 if not current_build_number:
4294 raise BuildkiteException("Not running inside Buildkite")
4295 current_build_number = int(current_build_number)
4296
Philipp Wollermann02955272019-04-18 18:00:48 +02004297 # Upload the Bazel binaries for this commit.
Rupert Shuttleworth7f3a91e2020-08-22 07:31:47 -04004298 bazel_hashes, bazel_nojdk_hashes = upload_bazel_binaries()
Philipp Wollermann02955272019-04-18 18:00:48 +02004299
4300 # Try to update the info.json with data about our build. This will fail (expectedly) if we're
Philipp Wollermann1b5ecdc2021-06-10 21:52:55 +02004301 # not the latest build. Only do this if we're building binaries from the main branch to avoid
4302 # accidentally publishing a custom debug build as the "latest" Bazel binary.
4303 if current_branch_is_main_branch():
4304 for _ in range(5):
4305 latest_generation, latest_build_number = latest_generation_and_build_number()
Jakob Buchgraberd20ffeb2018-02-18 03:16:43 +01004306
Philipp Wollermann1b5ecdc2021-06-10 21:52:55 +02004307 if current_build_number <= latest_build_number:
4308 eprint(
4309 (
4310 "Current build '{0}' is not newer than latest published '{1}'. "
4311 + "Skipping publishing of binaries."
4312 ).format(current_build_number, latest_build_number)
4313 )
4314 break
4315
4316 try:
4317 try_publish_binaries(
4318 bazel_hashes, bazel_nojdk_hashes, current_build_number, latest_generation
4319 )
4320 except BinaryUploadRaceException:
4321 # Retry.
4322 continue
4323
Philipp Wollermanncd5694c2019-01-03 14:53:04 +01004324 eprint(
Philipp Wollermann1b5ecdc2021-06-10 21:52:55 +02004325 "Successfully updated '{0}' to binaries from build {1}.".format(
4326 bazelci_latest_build_metadata_url(), current_build_number
4327 )
Philipp Wollermanncd5694c2019-01-03 14:53:04 +01004328 )
Philipp Wollermann598b4a42018-02-19 17:03:36 +01004329 break
Philipp Wollermann1b5ecdc2021-06-10 21:52:55 +02004330 else:
4331 raise BuildkiteException("Could not publish binaries, ran out of attempts.")
Philipp Wollermanncd5694c2019-01-03 14:53:04 +01004332
Philipp Wollermann3c8b8512019-07-16 15:28:03 +02004333
Philipp Wollermann639c0452019-01-03 11:23:54 +01004334# This is so that multiline python strings are represented as YAML
4335# block strings.
Jakob Buchgraber9952a3b2018-12-06 15:38:51 +01004336def str_presenter(dumper, data):
Philipp Wollermanncd5694c2019-01-03 14:53:04 +01004337 if len(data.splitlines()) > 1: # check for multiline string
4338 return dumper.represent_scalar("tag:yaml.org,2002:str", data, style="|")
4339 return dumper.represent_scalar("tag:yaml.org,2002:str", data)
4340
Jakob Buchgraberd20ffeb2018-02-18 03:16:43 +01004341
Philipp Wollermanndcaddd92018-02-21 14:13:43 +01004342def main(argv=None):
4343 if argv is None:
Yun Peng20d45602018-10-18 13:27:05 +02004344 argv = sys.argv[1:]
Philipp Wollermanndcaddd92018-02-21 14:13:43 +01004345
Jakob Buchgraber9952a3b2018-12-06 15:38:51 +01004346 yaml.add_representer(str, str_presenter)
4347
Philipp Wollermann2409c6e2018-08-07 07:37:54 +02004348 parser = argparse.ArgumentParser(description="Bazel Continuous Integration Script")
Florian Weikert944209b2019-05-10 12:41:48 +02004349 parser.add_argument("--script", type=str)
Jakob Buchgraber0b6a39d2018-02-17 00:45:14 +01004350
Philipp Wollermann598b4a42018-02-19 17:03:36 +01004351 subparsers = parser.add_subparsers(dest="subparsers_name")
Philipp Wollermanndcaddd92018-02-21 14:13:43 +01004352
Jakob Buchgraberc57d4ad2018-03-22 12:33:17 +01004353 bazel_publish_binaries_pipeline = subparsers.add_parser("bazel_publish_binaries_pipeline")
4354 bazel_publish_binaries_pipeline.add_argument("--file_config", type=str)
Jakob Buchgraber08e8e402018-03-20 19:22:07 +01004355 bazel_publish_binaries_pipeline.add_argument("--http_config", type=str)
4356 bazel_publish_binaries_pipeline.add_argument("--git_repository", type=str)
4357
Jakob Buchgraberc57d4ad2018-03-22 12:33:17 +01004358 bazel_downstream_pipeline = subparsers.add_parser("bazel_downstream_pipeline")
4359 bazel_downstream_pipeline.add_argument("--file_config", type=str)
4360 bazel_downstream_pipeline.add_argument("--http_config", type=str)
4361 bazel_downstream_pipeline.add_argument("--git_repository", type=str)
Philipp Wollermanncd5694c2019-01-03 14:53:04 +01004362 bazel_downstream_pipeline.add_argument(
Philipp Wollermanncd5694c2019-01-03 14:53:04 +01004363 "--test_disabled_projects", type=bool, nargs="?", const=True
4364 )
Florian Weikert60661912019-12-18 15:17:10 +01004365 bazel_downstream_pipeline.add_argument("--notify", type=bool, nargs="?", const=True)
Jakob Buchgraber0b6a39d2018-02-17 00:45:14 +01004366
Philipp Wollermann598b4a42018-02-19 17:03:36 +01004367 project_pipeline = subparsers.add_parser("project_pipeline")
4368 project_pipeline.add_argument("--project_name", type=str)
Jakob Buchgraberc57d4ad2018-03-22 12:33:17 +01004369 project_pipeline.add_argument("--file_config", type=str)
Philipp Wollermann598b4a42018-02-19 17:03:36 +01004370 project_pipeline.add_argument("--http_config", type=str)
4371 project_pipeline.add_argument("--git_repository", type=str)
Jakob Buchgraber66ba4fe2018-06-22 15:04:14 +02004372 project_pipeline.add_argument("--monitor_flaky_tests", type=bool, nargs="?", const=True)
Philipp Wollermann2409c6e2018-08-07 07:37:54 +02004373 project_pipeline.add_argument("--use_but", type=bool, nargs="?", const=True)
Florian Weikert60661912019-12-18 15:17:10 +01004374 project_pipeline.add_argument("--notify", type=bool, nargs="?", const=True)
Florian Weikert78e67de2024-02-06 12:58:41 +01004375 project_pipeline.add_argument("--print_shard_summary", type=bool, nargs="?", const=True)
Jakob Buchgraber0b6a39d2018-02-17 00:45:14 +01004376
Philipp Wollermann598b4a42018-02-19 17:03:36 +01004377 runner = subparsers.add_parser("runner")
Florian Weikert843d7a02019-02-03 17:24:50 +01004378 runner.add_argument("--task", action="store", type=str, default="")
Jakob Buchgraberc57d4ad2018-03-22 12:33:17 +01004379 runner.add_argument("--file_config", type=str)
Philipp Wollermann598b4a42018-02-19 17:03:36 +01004380 runner.add_argument("--http_config", type=str)
Florian Weikertccd44572024-11-11 13:11:39 +01004381 runner.add_argument(
4382 "--overwrite_bazel_version",
4383 type=str,
4384 help="Overwrite the bazel version in the config file.",
4385 )
Philipp Wollermann598b4a42018-02-19 17:03:36 +01004386 runner.add_argument("--git_repository", type=str)
Philipp Wollermanncd5694c2019-01-03 14:53:04 +01004387 runner.add_argument(
4388 "--git_commit", type=str, help="Reset the git repository to this commit after cloning it"
4389 )
4390 runner.add_argument(
Yun Peng5012a862021-09-16 16:35:43 +02004391 "--repo_location",
Philipp Wollermanncd5694c2019-01-03 14:53:04 +01004392 type=str,
4393 help="Use an existing repository instead of cloning from github",
4394 )
4395 runner.add_argument(
Dan Halperinefda1192019-01-16 00:34:09 -08004396 "--use_bazel_at_commit", type=str, help="Use Bazel binary built at a specific commit"
Philipp Wollermanncd5694c2019-01-03 14:53:04 +01004397 )
Philipp Wollermann598b4a42018-02-19 17:03:36 +01004398 runner.add_argument("--use_but", type=bool, nargs="?", const=True)
4399 runner.add_argument("--save_but", type=bool, nargs="?", const=True)
Yun Peng4d1d6542019-01-17 18:30:33 +01004400 runner.add_argument("--needs_clean", type=bool, nargs="?", const=True)
Philipp Wollermann598b4a42018-02-19 17:03:36 +01004401 runner.add_argument("--build_only", type=bool, nargs="?", const=True)
4402 runner.add_argument("--test_only", type=bool, nargs="?", const=True)
Jakob Buchgraber66ba4fe2018-06-22 15:04:14 +02004403 runner.add_argument("--monitor_flaky_tests", type=bool, nargs="?", const=True)
Jakob Buchgraberc340f582018-06-22 13:48:33 +02004404
Philipp Wollermannce986af2019-07-18 14:46:05 +02004405 subparsers.add_parser("publish_binaries")
4406 subparsers.add_parser("try_update_last_green_commit")
4407 subparsers.add_parser("try_update_last_green_downstream_commit")
Florian Weikert78e67de2024-02-06 12:58:41 +01004408 subparsers.add_parser("print_shard_summary")
Yun Peng358cd882018-11-29 10:25:18 +01004409
Yun Peng20d45602018-10-18 13:27:05 +02004410 args = parser.parse_args(argv)
Jakob Buchgraber0b6a39d2018-02-17 00:45:14 +01004411
Florian Weikert944209b2019-05-10 12:41:48 +02004412 if args.script:
4413 global SCRIPT_URL
4414 SCRIPT_URL = args.script
4415
Philipp Wollermanndcaddd92018-02-21 14:13:43 +01004416 try:
Jakob Buchgraber08e8e402018-03-20 19:22:07 +01004417 if args.subparsers_name == "bazel_publish_binaries_pipeline":
Jakob Buchgraberc57d4ad2018-03-22 12:33:17 +01004418 configs = fetch_configs(args.http_config, args.file_config)
Philipp Wollermanncd5694c2019-01-03 14:53:04 +01004419 print_bazel_publish_binaries_pipeline(
Florian Weikert843d7a02019-02-03 17:24:50 +01004420 task_configs=configs.get("tasks", None),
Philipp Wollermanncd5694c2019-01-03 14:53:04 +01004421 http_config=args.http_config,
4422 file_config=args.file_config,
4423 )
Jakob Buchgraberc57d4ad2018-03-22 12:33:17 +01004424 elif args.subparsers_name == "bazel_downstream_pipeline":
Yun Pengc85cd0e2022-09-02 10:44:29 +02004425 # If USE_BAZELISK_MIGRATE is true, we don't need to fetch task configs for Bazel
4426 # since we use Bazelisk to fetch Bazel binaries.
Florian Weikertb3439b32022-11-09 11:05:16 +01004427 configs = (
4428 {} if use_bazelisk_migrate() else fetch_configs(args.http_config, args.file_config)
4429 )
Philipp Wollermanncd5694c2019-01-03 14:53:04 +01004430 print_bazel_downstream_pipeline(
Florian Weikert843d7a02019-02-03 17:24:50 +01004431 task_configs=configs.get("tasks", None),
Philipp Wollermanncd5694c2019-01-03 14:53:04 +01004432 http_config=args.http_config,
4433 file_config=args.file_config,
Philipp Wollermanncd5694c2019-01-03 14:53:04 +01004434 test_disabled_projects=args.test_disabled_projects,
Florian Weikert60661912019-12-18 15:17:10 +01004435 notify=args.notify,
Philipp Wollermanncd5694c2019-01-03 14:53:04 +01004436 )
Philipp Wollermanndcaddd92018-02-21 14:13:43 +01004437 elif args.subparsers_name == "project_pipeline":
Yun Pengfb86f672023-11-10 16:18:55 +01004438 # Fetch the repo in case we need to use file_config.
4439 if args.git_repository:
Florian Weikertc89d6142023-11-20 14:50:35 +01004440 git_commit = (
4441 get_last_green_commit(args.project_name) if is_downstream_pipeline() else None
4442 )
Yun Pengfb86f672023-11-10 16:18:55 +01004443 clone_git_repository(args.git_repository, git_commit, suppress_stdout=True)
4444
Jakob Buchgraberc57d4ad2018-03-22 12:33:17 +01004445 configs = fetch_configs(args.http_config, args.file_config)
Philipp Wollermanncd5694c2019-01-03 14:53:04 +01004446 print_project_pipeline(
Florian Weikertf20ae6f2019-01-16 14:32:09 +01004447 configs=configs,
Philipp Wollermanncd5694c2019-01-03 14:53:04 +01004448 project_name=args.project_name,
4449 http_config=args.http_config,
4450 file_config=args.file_config,
4451 git_repository=args.git_repository,
4452 monitor_flaky_tests=args.monitor_flaky_tests,
4453 use_but=args.use_but,
Florian Weikert60661912019-12-18 15:17:10 +01004454 notify=args.notify,
Florian Weikert78e67de2024-02-06 12:58:41 +01004455 print_shard_summary=args.print_shard_summary,
Philipp Wollermanncd5694c2019-01-03 14:53:04 +01004456 )
Philipp Wollermanndcaddd92018-02-21 14:13:43 +01004457 elif args.subparsers_name == "runner":
Yun Pengfb86f672023-11-10 16:18:55 +01004458 # Fetch the repo in case we need to use file_config.
4459 if args.repo_location:
4460 os.chdir(args.repo_location)
4461 elif args.git_repository:
4462 clone_git_repository(args.git_repository, args.git_commit)
4463
Yun Peng2d33fc62024-10-23 11:43:04 +02004464 # Maybe overwrite the bazel version for each task, we have to do it before the config expansion.
4465 bazel_version = args.overwrite_bazel_version
4466 configs = fetch_configs(args.http_config, args.file_config, bazel_version)
Florian Weikert843d7a02019-02-03 17:24:50 +01004467 tasks = configs.get("tasks", {})
4468 task_config = tasks.get(args.task)
4469 if not task_config:
4470 raise BuildkiteException(
4471 "No such task '{}' in configuration. Available: {}".format(
4472 args.task, ", ".join(tasks)
4473 )
4474 )
4475
Chi Wangdb965412023-05-10 09:00:46 +00004476 os.environ["BAZELCI_TASK"] = args.task
4477
Florian Weikert843d7a02019-02-03 17:24:50 +01004478 platform = get_platform_for_task(args.task, task_config)
4479
Yun Pengdb76f842021-08-30 18:39:38 +02004480 # The value of `BUILDKITE_MESSAGE` defaults to the commit message, which can be too large
4481 # on Windows, therefore we truncate the value to 1000 characters.
4482 # See https://github.com/bazelbuild/continuous-integration/issues/1218
4483 if "BUILDKITE_MESSAGE" in os.environ:
4484 os.environ["BUILDKITE_MESSAGE"] = os.environ["BUILDKITE_MESSAGE"][:1000]
4485
Yun Peng2d33fc62024-10-23 11:43:04 +02004486 # Give user a warning that the bazel version in the config file has been overridden.
4487 old_bazel = task_config.get("old_bazel")
4488 if old_bazel:
4489 new_bazel = task_config.get("bazel")
Florian Weikertccd44572024-11-11 13:11:39 +01004490 print_collapsed_group(
4491 f":bazel: Bazel version overridden from {old_bazel} to {new_bazel}"
4492 )
Yun Peng2d33fc62024-10-23 11:43:04 +02004493
Philipp Wollermanncd5694c2019-01-03 14:53:04 +01004494 execute_commands(
Florian Weikertc8642af2019-02-03 23:58:51 +01004495 task_config=task_config,
Florian Weikert843d7a02019-02-03 17:24:50 +01004496 platform=platform,
Philipp Wollermanncd5694c2019-01-03 14:53:04 +01004497 use_bazel_at_commit=args.use_bazel_at_commit,
4498 use_but=args.use_but,
4499 save_but=args.save_but,
Yun Peng4d1d6542019-01-17 18:30:33 +01004500 needs_clean=args.needs_clean,
Philipp Wollermanncd5694c2019-01-03 14:53:04 +01004501 build_only=args.build_only,
4502 test_only=args.test_only,
4503 monitor_flaky_tests=args.monitor_flaky_tests,
Florian Weikertc8642af2019-02-03 23:58:51 +01004504 bazel_version=task_config.get("bazel") or configs.get("bazel"),
Philipp Wollermanncd5694c2019-01-03 14:53:04 +01004505 )
Philipp Wollermanndcaddd92018-02-21 14:13:43 +01004506 elif args.subparsers_name == "publish_binaries":
4507 publish_binaries()
Yun Peng358cd882018-11-29 10:25:18 +01004508 elif args.subparsers_name == "try_update_last_green_commit":
Florian Weikert35906542019-04-01 11:53:53 +02004509 # Update the last green commit of a project pipeline
Yun Peng358cd882018-11-29 10:25:18 +01004510 try_update_last_green_commit()
Florian Weikert35906542019-04-01 11:53:53 +02004511 elif args.subparsers_name == "try_update_last_green_downstream_commit":
4512 # Update the last green commit of the downstream pipeline
4513 try_update_last_green_downstream_commit()
Florian Weikert78e67de2024-02-06 12:58:41 +01004514 elif args.subparsers_name == "print_shard_summary":
4515 print_shard_summary()
Philipp Wollermanndcaddd92018-02-21 14:13:43 +01004516 else:
4517 parser.print_help()
4518 return 2
4519 except BuildkiteException as e:
4520 eprint(str(e))
4521 return 1
4522 return 0
4523
Jakob Buchgraber95e3d572018-02-21 18:48:49 +01004524
Philipp Wollermanndcaddd92018-02-21 14:13:43 +01004525if __name__ == "__main__":
4526 sys.exit(main())