blob: 186b1e02b81bbe2f5da93e446635a8907c35a389 [file] [log] [blame]
Mike Frysingerf1ba7ad2022-09-12 05:42:57 -04001# Copyright 2016 The ChromiumOS Authors
Don Garrettc4114cc2016-11-01 20:04:06 -07002# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4
5"""Bootstrap for cbuildbot.
6
7This script is intended to checkout chromite on the branch specified by -b or
8--branch (as normally accepted by cbuildbot), and then invoke cbuildbot. Most
9arguments are not parsed, only passed along. If a branch is not specified, this
Julio Hurtado9265c7e2021-04-19 23:04:44 +000010script will use 'main'.
Don Garrettc4114cc2016-11-01 20:04:06 -070011
12Among other things, this allows us to invoke build configs that exist on a given
13branch, but not on TOT.
14"""
15
Benjamin Gordon90b2dd92018-04-12 14:04:21 -060016import base64
Don Garrett125d4dc2017-04-25 16:26:03 -070017import functools
Chris McDonaldb55b7032021-06-17 16:41:32 -060018import logging
Don Garrettc4114cc2016-11-01 20:04:06 -070019import os
Brian Norris872684f2023-08-22 15:20:20 -070020from pathlib import Path
Mike Nicholsd0acc7f2021-05-21 17:18:24 +000021import time
Don Garrettc4114cc2016-11-01 20:04:06 -070022
Chris McDonaldb55b7032021-06-17 16:41:32 -060023from chromite.cbuildbot import cbuildbot_alerts
Don Garrettc4114cc2016-11-01 20:04:06 -070024from chromite.cbuildbot import repository
Don Garrett597ddff2017-02-17 18:29:37 -080025from chromite.cbuildbot.stages import sync_stages
Lann Martinebae73d2018-05-21 17:12:00 -060026from chromite.lib import boto_compat
Benjamin Gordon90b2dd92018-04-12 14:04:21 -060027from chromite.lib import build_summary
Brian Norris872684f2023-08-22 15:20:20 -070028from chromite.lib import chroot_lib
Don Garrett86881cb2017-02-15 15:41:55 -080029from chromite.lib import config_lib
Don Garretta50bf492017-09-28 18:33:02 -070030from chromite.lib import constants
Don Garrettc4114cc2016-11-01 20:04:06 -070031from chromite.lib import cros_build_lib
Benjamin Gordon74645232018-05-04 17:40:42 -060032from chromite.lib import cros_sdk_lib
Don Garrettacbb2392017-05-11 18:27:41 -070033from chromite.lib import metrics
Don Garrettc4114cc2016-11-01 20:04:06 -070034from chromite.lib import osutils
Mike Frysingerf0146252019-09-02 13:31:05 -040035from chromite.lib import timeout_util
Don Garrettacbb2392017-05-11 18:27:41 -070036from chromite.lib import ts_mon_config
Don Garrett86881cb2017-02-15 15:41:55 -080037from chromite.scripts import cbuildbot
Don Garrettc4114cc2016-11-01 20:04:06 -070038
Mike Frysinger3c831a72020-02-19 02:47:04 -050039
Don Garrett60967922017-04-12 18:51:44 -070040# This number should be incremented when we change the layout of the buildroot
41# in a non-backwards compatible way. This wipes all buildroots.
Don Garrettbf90cdf2017-05-19 15:54:02 -070042BUILDROOT_BUILDROOT_LAYOUT = 2
Prathmesh Prabhuc41a0f52018-04-03 13:26:52 -070043_DISTFILES_CACHE_EXPIRY_HOURS = 8 * 24
Don Garrett60967922017-04-12 18:51:44 -070044
Don Garrettacbb2392017-05-11 18:27:41 -070045# Metrics reported to Monarch.
Alex Klein1699fab2022-09-08 08:46:06 -060046METRIC_PREFIX = "chromeos/chromite/cbuildbot_launch/"
47METRIC_ACTIVE = METRIC_PREFIX + "active"
48METRIC_INVOKED = METRIC_PREFIX + "invoked"
49METRIC_COMPLETED = METRIC_PREFIX + "completed"
50METRIC_PREP = METRIC_PREFIX + "prep_completed"
51METRIC_CLEAN = METRIC_PREFIX + "clean_buildroot_durations"
52METRIC_INITIAL = METRIC_PREFIX + "initial_checkout_durations"
53METRIC_CBUILDBOT = METRIC_PREFIX + "cbuildbot_durations"
54METRIC_CBUILDBOT_INSTANCE = METRIC_PREFIX + "cbuildbot_instance_durations"
55METRIC_CLOBBER = METRIC_PREFIX + "clobber"
56METRIC_BRANCH_CLEANUP = METRIC_PREFIX + "branch_cleanup"
57METRIC_DISTFILES_CLEANUP = METRIC_PREFIX + "distfiles_cleanup"
58METRIC_CHROOT_CLEANUP = METRIC_PREFIX + "chroot_cleanup"
Don Garrettacbb2392017-05-11 18:27:41 -070059
Benjamin Gordon90b2dd92018-04-12 14:04:21 -060060# Builder state
Alex Klein1699fab2022-09-08 08:46:06 -060061BUILDER_STATE_FILENAME = ".cbuildbot_build_state.json"
Benjamin Gordon90b2dd92018-04-12 14:04:21 -060062
Don Garrett60967922017-04-12 18:51:44 -070063
Don Garrett125d4dc2017-04-25 16:26:03 -070064def StageDecorator(functor):
Alex Klein1699fab2022-09-08 08:46:06 -060065 """A Decorator that adds buildbot stage tags around a method.
Don Garrett125d4dc2017-04-25 16:26:03 -070066
Alex Klein1699fab2022-09-08 08:46:06 -060067 It uses the method name as the stage name, and assumes failure on a true
68 return value, or an exception.
69 """
Don Garrett125d4dc2017-04-25 16:26:03 -070070
Alex Klein1699fab2022-09-08 08:46:06 -060071 @functools.wraps(functor)
72 def wrapped_functor(*args, **kwargs):
73 try:
74 cbuildbot_alerts.PrintBuildbotStepName(functor.__name__)
75 result = functor(*args, **kwargs)
76 except Exception:
77 cbuildbot_alerts.PrintBuildbotStepFailure()
78 raise
Don Garrettacbb2392017-05-11 18:27:41 -070079
Alex Klein1699fab2022-09-08 08:46:06 -060080 if result:
81 cbuildbot_alerts.PrintBuildbotStepFailure()
82 return result
83
84 return wrapped_functor
Don Garrett125d4dc2017-04-25 16:26:03 -070085
86
Don Garrettb5fc08b2017-11-20 21:51:16 +000087def field(fields, **kwargs):
Alex Klein1699fab2022-09-08 08:46:06 -060088 """Helper for inserting more fields into a metrics fields dictionary.
Don Garrettacbb2392017-05-11 18:27:41 -070089
Alex Klein1699fab2022-09-08 08:46:06 -060090 Args:
Alex Klein361062b2023-04-05 09:45:28 -060091 fields: Dictionary of metrics fields.
92 **kwargs: Each argument is a key/value pair to insert into dict.
Don Garrettacbb2392017-05-11 18:27:41 -070093
Alex Klein1699fab2022-09-08 08:46:06 -060094 Returns:
Alex Klein361062b2023-04-05 09:45:28 -060095 Copy of original dictionary with kwargs set as fields.
Alex Klein1699fab2022-09-08 08:46:06 -060096 """
97 f = fields.copy()
98 f.update(kwargs)
99 return f
Don Garrettacbb2392017-05-11 18:27:41 -0700100
Don Garretta50bf492017-09-28 18:33:02 -0700101
102def PrependPath(prepend):
Alex Klein1699fab2022-09-08 08:46:06 -0600103 """Generate path with new directory at the beginning.
Don Garretta50bf492017-09-28 18:33:02 -0700104
Alex Klein1699fab2022-09-08 08:46:06 -0600105 Args:
Alex Klein9e7b29e2023-04-11 16:10:31 -0600106 prepend: Directory to add at the beginning of the path.
Don Garretta50bf492017-09-28 18:33:02 -0700107
Alex Klein1699fab2022-09-08 08:46:06 -0600108 Returns:
Alex Klein9e7b29e2023-04-11 16:10:31 -0600109 Extended path as a string.
Alex Klein1699fab2022-09-08 08:46:06 -0600110 """
111 return os.pathsep.join([prepend, os.environ.get("PATH", os.defpath)])
Don Garretta50bf492017-09-28 18:33:02 -0700112
Aviv Keshetc38eebe2018-09-27 23:19:58 +0000113
Don Garrett86881cb2017-02-15 15:41:55 -0800114def PreParseArguments(argv):
Alex Klein1699fab2022-09-08 08:46:06 -0600115 """Extract the branch name from cbuildbot command line arguments.
Don Garrettc4114cc2016-11-01 20:04:06 -0700116
Alex Klein1699fab2022-09-08 08:46:06 -0600117 Args:
Alex Klein9e7b29e2023-04-11 16:10:31 -0600118 argv: The command line arguments to parse.
Don Garrettc4114cc2016-11-01 20:04:06 -0700119
Alex Klein1699fab2022-09-08 08:46:06 -0600120 Returns:
Alex Klein9e7b29e2023-04-11 16:10:31 -0600121 Branch as a string ('main' if nothing is specified).
Alex Klein1699fab2022-09-08 08:46:06 -0600122 """
123 parser = cbuildbot.CreateParser()
124 options = cbuildbot.ParseCommandLine(parser, argv)
Don Garrett61ce1ee2019-02-26 16:20:25 -0800125
Alex Klein1699fab2022-09-08 08:46:06 -0600126 if not options.cache_dir:
127 options.cache_dir = os.path.join(
128 options.buildroot, "repository", ".cache"
129 )
Don Garrett61ce1ee2019-02-26 16:20:25 -0800130
Alex Klein1699fab2022-09-08 08:46:06 -0600131 options.Freeze()
Don Garrett86881cb2017-02-15 15:41:55 -0800132
Alex Klein1699fab2022-09-08 08:46:06 -0600133 # This option isn't required for cbuildbot, but is for us.
134 if not options.buildroot:
135 cros_build_lib.Die("--buildroot is a required option.")
Don Garrett86881cb2017-02-15 15:41:55 -0800136
Alex Klein1699fab2022-09-08 08:46:06 -0600137 return options
Don Garrettc4114cc2016-11-01 20:04:06 -0700138
Aviv Keshetc38eebe2018-09-27 23:19:58 +0000139
Benjamin Gordon8b6d4122018-04-26 13:38:39 -0600140def GetCurrentBuildState(options, branch):
Alex Klein1699fab2022-09-08 08:46:06 -0600141 """Extract information about the current build state from command-line args.
Benjamin Gordon90b2dd92018-04-12 14:04:21 -0600142
Alex Klein1699fab2022-09-08 08:46:06 -0600143 Args:
Alex Klein9e7b29e2023-04-11 16:10:31 -0600144 options: A parsed options object from a cbuildbot parser.
145 branch: The name of the branch this builder was called with.
Benjamin Gordon90b2dd92018-04-12 14:04:21 -0600146
Alex Klein1699fab2022-09-08 08:46:06 -0600147 Returns:
Alex Klein9e7b29e2023-04-11 16:10:31 -0600148 A BuildSummary object describing the current build.
Alex Klein1699fab2022-09-08 08:46:06 -0600149 """
150 build_state = build_summary.BuildSummary(
151 status=constants.BUILDER_STATUS_INFLIGHT,
152 buildroot_layout=BUILDROOT_BUILDROOT_LAYOUT,
153 branch=branch,
154 )
155 if options.buildnumber:
156 build_state.build_number = options.buildnumber
157 if options.buildbucket_id:
158 build_state.buildbucket_id = options.buildbucket_id
159 if options.master_build_id:
160 build_state.master_build_id = options.master_build_id
161 return build_state
Benjamin Gordon90b2dd92018-04-12 14:04:21 -0600162
163
164def GetLastBuildState(root):
Alex Klein1699fab2022-09-08 08:46:06 -0600165 """Fetch the state of the last build run from |root|.
Benjamin Gordon90b2dd92018-04-12 14:04:21 -0600166
Alex Klein9e7b29e2023-04-11 16:10:31 -0600167 If the saved state file can't be read or doesn't contain valid JSON, a
168 default state will be returned.
Benjamin Gordon90b2dd92018-04-12 14:04:21 -0600169
Alex Klein1699fab2022-09-08 08:46:06 -0600170 Args:
Alex Klein9e7b29e2023-04-11 16:10:31 -0600171 root: Root of the working directory tree as a string.
Benjamin Gordon90b2dd92018-04-12 14:04:21 -0600172
Alex Klein1699fab2022-09-08 08:46:06 -0600173 Returns:
Alex Klein9e7b29e2023-04-11 16:10:31 -0600174 A BuildSummary object representing the previous build.
Alex Klein1699fab2022-09-08 08:46:06 -0600175 """
176 state_file = os.path.join(root, BUILDER_STATE_FILENAME)
Benjamin Gordon90b2dd92018-04-12 14:04:21 -0600177
Benjamin Gordon8b6d4122018-04-26 13:38:39 -0600178 state = build_summary.BuildSummary()
Benjamin Gordon90b2dd92018-04-12 14:04:21 -0600179
Alex Klein1699fab2022-09-08 08:46:06 -0600180 try:
181 state_raw = osutils.ReadFile(state_file)
182 state.from_json(state_raw)
183 except IOError as e:
184 logging.info(
185 "Unable to read %s: %s. Expected for first task on bot.",
186 state_file,
187 e,
188 )
189 return state
190 except ValueError as e:
191 logging.warning(
192 "Saved state file %s is not valid JSON: %s", state_file, e
193 )
194 return state
195
196 if not state.is_valid():
197 logging.warning("Previous build state is not valid. Ignoring.")
198 state = build_summary.BuildSummary()
199
200 return state
Benjamin Gordon90b2dd92018-04-12 14:04:21 -0600201
202
203def SetLastBuildState(root, new_state):
Alex Klein1699fab2022-09-08 08:46:06 -0600204 """Save the state of the last build under |root|.
Benjamin Gordon90b2dd92018-04-12 14:04:21 -0600205
Alex Klein1699fab2022-09-08 08:46:06 -0600206 Args:
Alex Klein9e7b29e2023-04-11 16:10:31 -0600207 root: Root of the working directory tree as a string.
208 new_state: BuildSummary object containing the state to be saved.
Alex Klein1699fab2022-09-08 08:46:06 -0600209 """
210 state_file = os.path.join(root, BUILDER_STATE_FILENAME)
211 osutils.WriteFile(state_file, new_state.to_json())
Benjamin Gordon90b2dd92018-04-12 14:04:21 -0600212
Alex Klein1699fab2022-09-08 08:46:06 -0600213 # Remove old state file. Its contents have been migrated into the new file.
214 old_state_file = os.path.join(root, ".cbuildbot_launch_state")
215 osutils.SafeUnlink(old_state_file)
Benjamin Gordon8b6d4122018-04-26 13:38:39 -0600216
Benjamin Gordon90b2dd92018-04-12 14:04:21 -0600217
Mike Nicholsd0acc7f2021-05-21 17:18:24 +0000218def _MaybeCleanDistfiles(cache_dir, distfiles_ts):
Alex Klein1699fab2022-09-08 08:46:06 -0600219 """Cleans the distfiles directory if too old.
Mike Nicholsd0acc7f2021-05-21 17:18:24 +0000220
Alex Klein1699fab2022-09-08 08:46:06 -0600221 Args:
Alex Klein9e7b29e2023-04-11 16:10:31 -0600222 cache_dir: Directory of the cache, as a string.
223 distfiles_ts: A timestamp str for the last time distfiles was cleaned.
224 May be None.
Mike Nicholsd0acc7f2021-05-21 17:18:24 +0000225
Alex Klein1699fab2022-09-08 08:46:06 -0600226 Returns:
Alex Klein9e7b29e2023-04-11 16:10:31 -0600227 The new distfiles_ts to persist in state.
Alex Klein1699fab2022-09-08 08:46:06 -0600228 """
229 # distfiles_ts can be None for a fresh environment, which means clean.
230 if distfiles_ts is None:
231 return time.time()
232
233 distfiles_age = (time.time() - distfiles_ts) / 3600.0
234 if distfiles_age < _DISTFILES_CACHE_EXPIRY_HOURS:
235 return distfiles_ts
236
237 logging.info(
238 "Remove old distfiles cache (cache expiry %d hours)",
239 _DISTFILES_CACHE_EXPIRY_HOURS,
240 )
241 osutils.RmDir(
242 os.path.join(cache_dir, "distfiles"), ignore_missing=True, sudo=True
243 )
244 metrics.Counter(METRIC_DISTFILES_CLEANUP).increment(
245 fields=field({}, reason="cache_expired")
246 )
247
248 # Cleaned cache, so reset distfiles_ts
Mike Nicholsd0acc7f2021-05-21 17:18:24 +0000249 return time.time()
250
Mike Nicholsd0acc7f2021-05-21 17:18:24 +0000251
252def SanitizeCacheDir(cache_dir):
Alex Klein1699fab2022-09-08 08:46:06 -0600253 """Make certain the .cache directory is valid.
Mike Nicholsd0acc7f2021-05-21 17:18:24 +0000254
Alex Klein1699fab2022-09-08 08:46:06 -0600255 Args:
Alex Klein9e7b29e2023-04-11 16:10:31 -0600256 cache_dir: Directory of the cache, as a string.
Alex Klein1699fab2022-09-08 08:46:06 -0600257 """
258 logging.info("Cleaning up cache dir at %s", cache_dir)
259 # Verify that .cache is writable by the current user.
260 try:
261 osutils.Touch(
262 os.path.join(cache_dir, ".cbuildbot_launch"), makedirs=True
263 )
264 except IOError:
265 logging.info("Bad Permissions for cache dir, wiping: %s", cache_dir)
266 osutils.RmDir(cache_dir, sudo=True)
267 osutils.Touch(
268 os.path.join(cache_dir, ".cbuildbot_launch"), makedirs=True
269 )
Mike Nicholsd0acc7f2021-05-21 17:18:24 +0000270
Alex Klein1699fab2022-09-08 08:46:06 -0600271 osutils.RmDir(
272 os.path.join(cache_dir, "paygen_cache"), ignore_missing=True, sudo=True
273 )
274 logging.info("Finished cleaning cache_dir.")
Mike Nicholsd0acc7f2021-05-21 17:18:24 +0000275
276
277@StageDecorator
Mike Nicholsfe1a5e62021-09-03 13:11:17 -0400278def CleanBuildRoot(root, repo, cache_dir, build_state, source_cache=False):
Alex Klein1699fab2022-09-08 08:46:06 -0600279 """Some kinds of branch transitions break builds.
Mike Nicholsd0acc7f2021-05-21 17:18:24 +0000280
Alex Klein1699fab2022-09-08 08:46:06 -0600281 This method ensures that cbuildbot's buildroot is a clean checkout on the
282 given branch when it starts. If necessary (a branch transition) it will wipe
283 assorted state that cannot be safely reused from the previous build.
Mike Nicholsd0acc7f2021-05-21 17:18:24 +0000284
Alex Klein1699fab2022-09-08 08:46:06 -0600285 Args:
Alex Klein9e7b29e2023-04-11 16:10:31 -0600286 root: Root directory owned by cbuildbot_launch.
287 repo: repository.RepoRepository instance.
288 cache_dir: Cache directory.
289 build_state: BuildSummary object containing the current build state that
290 will be saved into the cleaned root. The distfiles_ts property will
291 be updated if the distfiles cache is cleaned.
292 source_cache: Bool whether to use source cache mounts.
Alex Klein1699fab2022-09-08 08:46:06 -0600293 """
294 previous_state = GetLastBuildState(root)
295 SetLastBuildState(root, build_state)
296 SanitizeCacheDir(cache_dir)
297 build_state.distfiles_ts = _MaybeCleanDistfiles(
298 cache_dir, previous_state.distfiles_ts
299 )
Mike Nicholsd0acc7f2021-05-21 17:18:24 +0000300
Mike Nicholsfe1a5e62021-09-03 13:11:17 -0400301 if not source_cache:
Alex Klein1699fab2022-09-08 08:46:06 -0600302 if previous_state.buildroot_layout != BUILDROOT_BUILDROOT_LAYOUT:
303 cbuildbot_alerts.PrintBuildbotStepText(
304 "Unknown layout: Wiping buildroot."
305 )
306 metrics.Counter(METRIC_CLOBBER).increment(
307 fields=field({}, reason="layout_change")
308 )
Brian Norris872684f2023-08-22 15:20:20 -0700309 chroot = chroot_lib.Chroot(
310 path=root / Path(constants.DEFAULT_CHROOT_DIR),
311 out_path=root / constants.DEFAULT_OUT_DIR,
312 )
313 if os.path.exists(chroot.path):
314 cros_sdk_lib.CleanupChrootMount(chroot, delete=True)
Alex Klein1699fab2022-09-08 08:46:06 -0600315 osutils.RmDir(root, ignore_missing=True, sudo=True)
316 osutils.RmDir(cache_dir, ignore_missing=True, sudo=True)
317 else:
318 if previous_state.branch != repo.branch:
319 cbuildbot_alerts.PrintBuildbotStepText(
320 "Branch change: Cleaning buildroot."
321 )
322 logging.info(
323 "Unmatched branch: %s -> %s",
324 previous_state.branch,
325 repo.branch,
326 )
327 metrics.Counter(METRIC_BRANCH_CLEANUP).increment(
328 fields=field({}, old_branch=previous_state.branch)
329 )
Mike Nicholsd0acc7f2021-05-21 17:18:24 +0000330
Alex Klein1699fab2022-09-08 08:46:06 -0600331 logging.info("Remove Chroot.")
Brian Norris872684f2023-08-22 15:20:20 -0700332 chroot = chroot_lib.Chroot(
333 path=repo.directory / Path(constants.DEFAULT_CHROOT_DIR),
334 out_path=repo.directory / constants.DEFAULT_OUT_DIR,
Alex Klein1699fab2022-09-08 08:46:06 -0600335 )
Brian Norris872684f2023-08-22 15:20:20 -0700336 if os.path.exists(chroot.path):
337 cros_sdk_lib.CleanupChrootMount(chroot, delete=True)
Alex Klein1699fab2022-09-08 08:46:06 -0600338
339 logging.info("Remove Chrome checkout.")
340 osutils.RmDir(
341 os.path.join(repo.directory, ".cache", "distfiles"),
342 ignore_missing=True,
343 sudo=True,
344 )
345
346 try:
Alex Klein9e7b29e2023-04-11 16:10:31 -0600347 # If there is any failure doing the cleanup, wipe everything. The
348 # previous run might have been killed in the middle leaving stale git
Alex Klein1699fab2022-09-08 08:46:06 -0600349 # locks. Clean those up, first.
350 if not source_cache:
351 repo.PreLoad()
352
353 # If the previous build didn't exit normally, run an expensive step to
Alex Klein9e7b29e2023-04-11 16:10:31 -0600354 # clean up abandoned git locks.
Alex Klein1699fab2022-09-08 08:46:06 -0600355 if previous_state.status not in (
356 constants.BUILDER_STATUS_FAILED,
357 constants.BUILDER_STATUS_PASSED,
358 ):
359 repo.CleanStaleLocks()
360
361 if not source_cache:
362 repo.BuildRootGitCleanup(prune_all=True)
363 except Exception:
364 logging.info(
365 "Checkout cleanup failed, wiping buildroot:", exc_info=True
366 )
367 metrics.Counter(METRIC_CLOBBER).increment(
368 fields=field({}, reason="repo_cleanup_failure")
369 )
370 repository.ClearBuildRoot(repo.directory)
371
372 if not source_cache:
373 # Ensure buildroot exists. Save the state we are prepped for.
374 osutils.SafeMakedirs(repo.directory)
375 SetLastBuildState(root, build_state)
Mike Nicholsd0acc7f2021-05-21 17:18:24 +0000376
377
Don Garrett125d4dc2017-04-25 16:26:03 -0700378@StageDecorator
Mike Nichols9fb48832021-01-29 14:47:15 -0700379def InitialCheckout(repo, options):
Alex Klein1699fab2022-09-08 08:46:06 -0600380 """Preliminary ChromeOS checkout.
Don Garrett86881cb2017-02-15 15:41:55 -0800381
Alex Klein9e7b29e2023-04-11 16:10:31 -0600382 Perform a complete checkout of ChromeOS on the specified branch. This does
383 NOT match what the build needs, but ensures the buildroot both has a 'hot'
384 checkout, and is close enough that the branched cbuildbot can successfully
385 get the right checkout.
Don Garrett86881cb2017-02-15 15:41:55 -0800386
Alex Klein1699fab2022-09-08 08:46:06 -0600387 This checks out full ChromeOS, even if a ChromiumOS build is going to be
388 performed. This is because we have no knowledge of the build config to be
389 used.
Don Garrettc4114cc2016-11-01 20:04:06 -0700390
Alex Klein1699fab2022-09-08 08:46:06 -0600391 Args:
Alex Klein9e7b29e2023-04-11 16:10:31 -0600392 repo: repository.RepoRepository instance.
393 options: A parsed options object from a cbuildbot parser.
Alex Klein1699fab2022-09-08 08:46:06 -0600394 """
395 cbuildbot_alerts.PrintBuildbotStepText("Branch: %s" % repo.branch)
396 if not options.source_cache:
397 logging.info(
398 "Bootstrap script starting initial sync on branch: %s", repo.branch
399 )
400 repo.PreLoad("/preload/chromeos")
401 repo.Sync(
402 jobs=32, detach=True, downgrade_repo=_ShouldDowngradeRepo(options)
403 )
Don Garrettc4114cc2016-11-01 20:04:06 -0700404
405
Lann Martin8c4c6802018-05-23 11:09:46 -0600406def ShouldFixBotoCerts(options):
Alex Klein1699fab2022-09-08 08:46:06 -0600407 """Decide if FixBotoCerts should be applied for this branch."""
408 try:
409 # Only apply to factory and firmware branches.
410 branch = options.branch or ""
411 prefix = branch.split("-")[0]
412 if prefix not in ("factory", "firmware"):
413 return False
Lann Martin8c4c6802018-05-23 11:09:46 -0600414
Alex Klein1699fab2022-09-08 08:46:06 -0600415 # Only apply to "old" branches.
416 if branch.endswith(".B"):
417 version = branch[:-2].split("-")[-1]
418 major = int(version.split(".")[0])
419 return major <= 9667 # This is the newest known to be failing.
Lann Martin8c4c6802018-05-23 11:09:46 -0600420
Alex Klein1699fab2022-09-08 08:46:06 -0600421 return False
422 except Exception as e:
423 logging.warning(" failed: %s", e)
424 # Conservatively continue without the fix.
425 return False
Lann Martin8c4c6802018-05-23 11:09:46 -0600426
427
Mike Nichols9fb48832021-01-29 14:47:15 -0700428def _ShouldDowngradeRepo(options):
Alex Klein1699fab2022-09-08 08:46:06 -0600429 """Determine which repo version to set for the branch.
Mike Nichols9fb48832021-01-29 14:47:15 -0700430
Alex Klein1699fab2022-09-08 08:46:06 -0600431 Repo version is set at cache creation time, in the nightly builder,
432 which means we are typically at the latest version. Older branches
433 are incompatible with newer version of ToT, therefore we downgrade
434 repo to a known working version.
Mike Nichols9fb48832021-01-29 14:47:15 -0700435
Alex Klein1699fab2022-09-08 08:46:06 -0600436 Args:
Alex Klein9e7b29e2023-04-11 16:10:31 -0600437 options: A parsed options object from a cbuildbot parser.
Mike Nichols9fb48832021-01-29 14:47:15 -0700438
Alex Klein1699fab2022-09-08 08:46:06 -0600439 Returns:
Alex Klein9e7b29e2023-04-11 16:10:31 -0600440 bool of whether to downgrade repo version based on branch.
Alex Klein1699fab2022-09-08 08:46:06 -0600441 """
442 try:
443 branch = options.branch or ""
444 # Only apply to "old" branches.
445 if branch.endswith(".B"):
446 branch_num = branch[:-2].split("-")[1][1:3]
447 return branch_num <= 79 # This is the newest known to be failing.
Mike Nichols9fb48832021-01-29 14:47:15 -0700448
Alex Klein1699fab2022-09-08 08:46:06 -0600449 return False
450 except Exception as e:
451 logging.warning(" failed: %s", e)
452 # Conservatively continue without the fix.
453 return False
Mike Nichols9fb48832021-01-29 14:47:15 -0700454
455
Don Garrett066e6f52017-09-28 19:14:01 -0700456@StageDecorator
Don Garrett6e5c6b92018-04-06 17:58:49 -0700457def Cbuildbot(buildroot, depot_tools_path, argv):
Alex Klein1699fab2022-09-08 08:46:06 -0600458 """Start cbuildbot in specified directory with all arguments.
Don Garrettc4114cc2016-11-01 20:04:06 -0700459
Alex Klein1699fab2022-09-08 08:46:06 -0600460 Args:
Alex Klein9e7b29e2023-04-11 16:10:31 -0600461 buildroot: Directory to be passed to cbuildbot with --buildroot.
462 depot_tools_path: Directory for depot_tools to be used by cbuildbot.
463 argv: Command line options passed to cbuildbot_launch.
Don Garrettc4114cc2016-11-01 20:04:06 -0700464
Alex Klein1699fab2022-09-08 08:46:06 -0600465 Returns:
Alex Klein9e7b29e2023-04-11 16:10:31 -0600466 Return code of cbuildbot as an integer.
Alex Klein1699fab2022-09-08 08:46:06 -0600467 """
468 logging.info("Bootstrap cbuildbot in: %s", buildroot)
Don Garrettbf90cdf2017-05-19 15:54:02 -0700469
Alex Klein1699fab2022-09-08 08:46:06 -0600470 # Fixup buildroot parameter.
471 argv = argv[:]
472 for i, arg in enumerate(argv):
473 if arg in ("-r", "--buildroot"):
474 argv[i + 1] = buildroot
Don Garrett597ddff2017-02-17 18:29:37 -0800475
Alex Klein1699fab2022-09-08 08:46:06 -0600476 # Source_cache flag is only used to indicate a transition to cache disks
477 # and doesn't need to be passed back to Cbuildbot.
478 if "--source_cache" in argv:
479 argv.remove("--source_cache")
Mike Nicholse333e402021-10-04 15:57:03 -0400480
Alex Klein1699fab2022-09-08 08:46:06 -0600481 logging.info("Cbuildbot Args: %s", argv)
Mike Nichols0b5555e2021-10-05 17:03:23 -0400482
Alex Klein1699fab2022-09-08 08:46:06 -0600483 # This filters out command line arguments not supported by older versions
484 # of cbuildbot.
485 parser = cbuildbot.CreateParser()
486 options = cbuildbot.ParseCommandLine(parser, argv)
487 cbuildbot_path = os.path.join(buildroot, "chromite", "bin", "cbuildbot")
488 cmd = sync_stages.BootstrapStage.FilterArgsForTargetCbuildbot(
489 buildroot, cbuildbot_path, options
490 )
Don Garrett597ddff2017-02-17 18:29:37 -0800491
Alex Klein1699fab2022-09-08 08:46:06 -0600492 # We want cbuildbot to use branched depot_tools scripts from our manifest,
493 # so that depot_tools is branched to match cbuildbot.
494 logging.info("Adding depot_tools into PATH: %s", depot_tools_path)
495 extra_env = {"PATH": PrependPath(depot_tools_path)}
Don Garretta50bf492017-09-28 18:33:02 -0700496
Alex Klein1699fab2022-09-08 08:46:06 -0600497 # TODO(crbug.com/845304): Remove once underlying boto issues are resolved.
498 fix_boto = ShouldFixBotoCerts(options)
Lann Martinebae73d2018-05-21 17:12:00 -0600499
Alex Klein1699fab2022-09-08 08:46:06 -0600500 with boto_compat.FixBotoCerts(activate=fix_boto):
501 result = cros_build_lib.run(
502 cmd, extra_env=extra_env, check=False, cwd=buildroot
503 )
Lann Martinebae73d2018-05-21 17:12:00 -0600504
Alex Klein1699fab2022-09-08 08:46:06 -0600505 return result.returncode
Don Garrettc4114cc2016-11-01 20:04:06 -0700506
Don Garrett60967922017-04-12 18:51:44 -0700507
Benjamin Gordonaee36b82018-02-05 14:25:26 -0700508@StageDecorator
509def CleanupChroot(buildroot):
Alex Klein9e7b29e2023-04-11 16:10:31 -0600510 """Unmount/cleanup an image-based chroot without deleting the backing image.
Benjamin Gordonaee36b82018-02-05 14:25:26 -0700511
Alex Klein1699fab2022-09-08 08:46:06 -0600512 Args:
Alex Klein9e7b29e2023-04-11 16:10:31 -0600513 buildroot: Directory containing the chroot to be cleaned up.
Alex Klein1699fab2022-09-08 08:46:06 -0600514 """
Brian Norris872684f2023-08-22 15:20:20 -0700515 chroot = chroot_lib.Chroot(
516 path=buildroot / Path(constants.DEFAULT_CHROOT_DIR),
517 out_path=buildroot / constants.DEFAULT_OUT_DIR,
518 )
519 logging.info("Cleaning up chroot at %s", chroot.path)
520 if os.path.exists(chroot.path):
Alex Klein1699fab2022-09-08 08:46:06 -0600521 try:
Brian Norris872684f2023-08-22 15:20:20 -0700522 cros_sdk_lib.CleanupChrootMount(chroot, delete=False)
Alex Klein1699fab2022-09-08 08:46:06 -0600523 except timeout_util.TimeoutError:
524 logging.exception("Cleaning up chroot timed out")
525 # Dump debug info to help https://crbug.com/1000034.
526 cros_build_lib.run(["mount"], check=True)
527 cros_build_lib.run(["uname", "-a"], check=True)
528 cros_build_lib.sudo_run(["losetup", "-a"], check=True)
529 cros_build_lib.run(["dmesg"], check=True)
530 logging.warning(
531 "Assuming the bot is going to reboot, so ignoring this "
532 "failure; see https://crbug.com/1000034"
533 )
Mike Frysingerf0146252019-09-02 13:31:05 -0400534
Alex Klein1699fab2022-09-08 08:46:06 -0600535 # NB: We ignore errors at this point because this stage runs last. If the
Alex Klein9e7b29e2023-04-11 16:10:31 -0600536 # chroot failed to unmount, we're going to reboot the system once we're
537 # done, and that will implicitly take care of cleaning things up. If the
538 # bots stop rebooting after every run, we'll need to make this fatal all the
539 # time.
Alex Klein1699fab2022-09-08 08:46:06 -0600540 #
541 # TODO(crbug.com/1000034): This should be fatal all the time.
Benjamin Gordonaee36b82018-02-05 14:25:26 -0700542
543
Don Garrettf15d65b2017-04-12 12:39:55 -0700544def ConfigureGlobalEnvironment():
Alex Klein1699fab2022-09-08 08:46:06 -0600545 """Setup process wide environmental changes."""
546 # Set umask to 022 so files created by buildbot are readable.
547 os.umask(0o22)
Don Garrettf15d65b2017-04-12 12:39:55 -0700548
Alex Klein1699fab2022-09-08 08:46:06 -0600549 # These variables can interfere with LANG / locale behavior.
550 unwanted_local_vars = [
551 "LC_ALL",
552 "LC_CTYPE",
553 "LC_COLLATE",
554 "LC_TIME",
555 "LC_NUMERIC",
556 "LC_MONETARY",
557 "LC_MESSAGES",
558 "LC_PAPER",
559 "LC_NAME",
560 "LC_ADDRESS",
561 "LC_TELEPHONE",
562 "LC_MEASUREMENT",
563 "LC_IDENTIFICATION",
564 ]
565 for v in unwanted_local_vars:
566 os.environ.pop(v, None)
Don Garrett86fec482017-05-17 18:13:33 -0700567
Alex Klein1699fab2022-09-08 08:46:06 -0600568 # This variable is required for repo sync's to work in all cases.
569 os.environ["LANG"] = "en_US.UTF-8"
Don Garrett86fec482017-05-17 18:13:33 -0700570
Aviv Keshetc38eebe2018-09-27 23:19:58 +0000571
Dhanya Ganesh95c5c152018-10-08 16:48:29 -0600572def _main(options, argv):
Alex Klein1699fab2022-09-08 08:46:06 -0600573 """main method of script.
Don Garrettc4114cc2016-11-01 20:04:06 -0700574
Alex Klein1699fab2022-09-08 08:46:06 -0600575 Args:
Alex Klein9e7b29e2023-04-11 16:10:31 -0600576 options: preparsed options object for the build.
577 argv: All command line arguments to pass as list of strings.
Don Garrettc4114cc2016-11-01 20:04:06 -0700578
Alex Klein1699fab2022-09-08 08:46:06 -0600579 Returns:
Alex Klein9e7b29e2023-04-11 16:10:31 -0600580 Return code of cbuildbot as an integer.
Alex Klein1699fab2022-09-08 08:46:06 -0600581 """
582 branchname = options.branch or "main"
583 root = options.buildroot
584 buildroot = os.path.join(root, "repository")
585 workspace = os.path.join(root, "workspace")
586 if options.source_cache:
587 buildroot = options.buildroot
588 if options.workspace:
589 workspace = options.workspace
590 depot_tools_path = os.path.join(buildroot, constants.DEPOT_TOOLS_SUBPATH)
Don Garrettd1d90dd2017-06-13 17:35:52 -0700591
Alex Klein1699fab2022-09-08 08:46:06 -0600592 # Does the entire build pass or fail.
593 with metrics.Presence(METRIC_ACTIVE), metrics.SuccessCounter(
594 METRIC_COMPLETED
595 ) as s_fields:
Alex Klein1699fab2022-09-08 08:46:06 -0600596 # Preliminary set, mostly command line parsing.
597 with metrics.SuccessCounter(METRIC_INVOKED):
598 if options.enable_buildbot_tags:
599 cbuildbot_alerts.EnableBuildbotMarkers()
600 ConfigureGlobalEnvironment()
Don Garrett86881cb2017-02-15 15:41:55 -0800601
Alex Klein1699fab2022-09-08 08:46:06 -0600602 # Prepare the buildroot with source for the build.
603 with metrics.SuccessCounter(METRIC_PREP):
604 manifest_url = config_lib.GetSiteParams().MANIFEST_INT_URL
605 repo = repository.RepoRepository(
606 manifest_url,
607 buildroot,
608 branch=branchname,
609 git_cache_dir=options.git_cache_dir,
610 )
611 previous_build_state = GetLastBuildState(root)
Don Garrett86881cb2017-02-15 15:41:55 -0800612
Alex Klein1699fab2022-09-08 08:46:06 -0600613 # Clean up the buildroot to a safe state.
614 with metrics.SecondsTimer(METRIC_CLEAN):
615 build_state = GetCurrentBuildState(options, branchname)
616 CleanBuildRoot(
617 root,
618 repo,
619 options.cache_dir,
620 build_state,
621 options.source_cache,
622 )
Don Garrettacbb2392017-05-11 18:27:41 -0700623
Alex Klein9e7b29e2023-04-11 16:10:31 -0600624 # Get a checkout close enough to the branch that cbuildbot can
625 # handle it.
Alex Klein1699fab2022-09-08 08:46:06 -0600626 if options.sync:
627 with metrics.SecondsTimer(METRIC_INITIAL):
628 InitialCheckout(repo, options)
Don Garrettacbb2392017-05-11 18:27:41 -0700629
Alex Klein9e7b29e2023-04-11 16:10:31 -0600630 # Run cbuildbot inside the full ChromeOS checkout, on the specified
631 # branch.
Alex Klein1699fab2022-09-08 08:46:06 -0600632 with metrics.SecondsTimer(
633 METRIC_CBUILDBOT
634 ), metrics.SecondsInstanceTimer(METRIC_CBUILDBOT_INSTANCE):
635 if previous_build_state.is_valid():
636 argv.append("--previous-build-state")
637 argv.append(
638 base64.b64encode(
639 previous_build_state.to_json().encode("utf-8")
640 ).decode("utf-8")
641 )
642 argv.extend(["--workspace", workspace])
Benjamin Gordon90b2dd92018-04-12 14:04:21 -0600643
Alex Klein1699fab2022-09-08 08:46:06 -0600644 if not options.cache_dir_specified:
645 argv.extend(["--cache-dir", options.cache_dir])
Don Garrett61ce1ee2019-02-26 16:20:25 -0800646
Alex Klein1699fab2022-09-08 08:46:06 -0600647 result = Cbuildbot(buildroot, depot_tools_path, argv)
648 s_fields["success"] = result == 0
Benjamin Gordon90b2dd92018-04-12 14:04:21 -0600649
Alex Klein1699fab2022-09-08 08:46:06 -0600650 build_state.status = (
651 constants.BUILDER_STATUS_PASSED
652 if result == 0
653 else constants.BUILDER_STATUS_FAILED
654 )
655 SetLastBuildState(root, build_state)
Benjamin Gordon90b2dd92018-04-12 14:04:21 -0600656
Alex Klein1699fab2022-09-08 08:46:06 -0600657 with metrics.SecondsTimer(METRIC_CHROOT_CLEANUP):
658 CleanupChroot(buildroot)
Don Garrett91a8cfc2018-06-22 15:39:36 -0700659
Alex Klein1699fab2022-09-08 08:46:06 -0600660 return result
Ben Pastenec8e4e792018-05-14 20:20:25 +0000661
Don Garrettacbb2392017-05-11 18:27:41 -0700662
663def main(argv):
Alex Klein1699fab2022-09-08 08:46:06 -0600664 options = PreParseArguments(argv)
665 metric_fields = {
666 "branch_name": options.branch or "main",
667 "build_config": options.build_config_name,
668 "tryjob": options.remote_trybot,
669 }
Dhanya Ganesh95c5c152018-10-08 16:48:29 -0600670
Alex Klein1699fab2022-09-08 08:46:06 -0600671 # Enable Monarch metrics gathering.
672 with ts_mon_config.SetupTsMonGlobalState(
673 "cbuildbot_launch", common_metric_fields=metric_fields, indirect=True
674 ):
675 return _main(options, argv)