blob: 89f44d3c31ad2b8931b5a3fd518a1a3b40c24c90 [file] [log] [blame]
Don Garrettc4114cc2016-11-01 20:04:06 -07001# Copyright 2016 The Chromium OS Authors. All rights reserved.
2# 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
10script will use 'master'.
11
12Among other things, this allows us to invoke build configs that exist on a given
13branch, but not on TOT.
14"""
15
16from __future__ import print_function
17
Don Garrett125d4dc2017-04-25 16:26:03 -070018import functools
Don Garrettc4114cc2016-11-01 20:04:06 -070019import os
20
21from chromite.cbuildbot import repository
Don Garrett597ddff2017-02-17 18:29:37 -080022from chromite.cbuildbot.stages import sync_stages
Don Garrett86881cb2017-02-15 15:41:55 -080023from chromite.lib import config_lib
Don Garretta50bf492017-09-28 18:33:02 -070024from chromite.lib import constants
Don Garrettc4114cc2016-11-01 20:04:06 -070025from chromite.lib import cros_build_lib
26from chromite.lib import cros_logging as logging
Don Garrettacbb2392017-05-11 18:27:41 -070027from chromite.lib import metrics
Don Garrettc4114cc2016-11-01 20:04:06 -070028from chromite.lib import osutils
Don Garrettacbb2392017-05-11 18:27:41 -070029from chromite.lib import ts_mon_config
Don Garrett86881cb2017-02-15 15:41:55 -080030from chromite.scripts import cbuildbot
Don Garrettc4114cc2016-11-01 20:04:06 -070031
Don Garrett597ddff2017-02-17 18:29:37 -080032
Don Garrett60967922017-04-12 18:51:44 -070033# This number should be incremented when we change the layout of the buildroot
34# in a non-backwards compatible way. This wipes all buildroots.
Don Garrettbf90cdf2017-05-19 15:54:02 -070035BUILDROOT_BUILDROOT_LAYOUT = 2
Don Garrett60967922017-04-12 18:51:44 -070036
Don Garrettacbb2392017-05-11 18:27:41 -070037# Metrics reported to Monarch.
Don Garrett45e77412017-06-14 16:57:55 -070038METRIC_ACTIVE = 'chromeos/chromite/cbuildbot_launch/active'
Don Garrettacbb2392017-05-11 18:27:41 -070039METRIC_INVOKED = 'chromeos/chromite/cbuildbot_launch/invoked'
40METRIC_COMPLETED = 'chromeos/chromite/cbuildbot_launch/completed'
41METRIC_PREP = 'chromeos/chromite/cbuildbot_launch/prep_completed'
42METRIC_CLEAN = 'chromeos/chromite/cbuildbot_launch/clean_buildroot_durations'
43METRIC_INITIAL = 'chromeos/chromite/cbuildbot_launch/initial_checkout_durations'
44METRIC_CBUILDBOT = 'chromeos/chromite/cbuildbot_launch/cbuildbot_durations'
45METRIC_CLOBBER = 'chromeos/chromite/cbuildbot_launch/clobber'
46METRIC_BRANCH_CLEANUP = 'chromeos/chromite/cbuildbot_launch/branch_cleanup'
47
Don Garrett60967922017-04-12 18:51:44 -070048
Don Garrett125d4dc2017-04-25 16:26:03 -070049def StageDecorator(functor):
50 """A Decorator that adds buildbot stage tags around a method.
51
Don Garrettacbb2392017-05-11 18:27:41 -070052 It uses the method name as the stage name, and assumes failure on a true
53 return value, or an exception.
Don Garrett125d4dc2017-04-25 16:26:03 -070054 """
55 @functools.wraps(functor)
56 def wrapped_functor(*args, **kwargs):
57 try:
58 logging.PrintBuildbotStepName(functor.__name__)
Don Garrettacbb2392017-05-11 18:27:41 -070059 result = functor(*args, **kwargs)
Don Garrett125d4dc2017-04-25 16:26:03 -070060 except Exception:
61 logging.PrintBuildbotStepFailure()
62 raise
63
Don Garrettacbb2392017-05-11 18:27:41 -070064 if result:
65 logging.PrintBuildbotStepFailure()
66 return result
67
Don Garrett125d4dc2017-04-25 16:26:03 -070068 return wrapped_functor
69
70
Don Garrettacbb2392017-05-11 18:27:41 -070071def field(fields, **kwargs):
72 """Helper for inserting more fields into a metrics fields dictionary.
73
74 Args:
75 fields: Dictionary of metrics fields.
76 kwargs: Each argument is a key/value pair to insert into dict.
77
78 Returns:
79 Copy of original dictionary with kwargs set as fields.
80 """
81 f = fields.copy()
82 f.update(kwargs)
83 return f
84
Don Garretta50bf492017-09-28 18:33:02 -070085
86def PrependPath(prepend):
87 """Generate path with new directory at the beginning.
88
89 Args:
90 prepend: Directory to add at the beginning of the path.
91
92 Returns:
93 Extended path as a string.
94 """
95 return os.pathsep.join([prepend, os.environ.get('PATH', os.defpath)])
96
97
Don Garrett86881cb2017-02-15 15:41:55 -080098def PreParseArguments(argv):
Don Garrettc4114cc2016-11-01 20:04:06 -070099 """Extract the branch name from cbuildbot command line arguments.
100
Don Garrettc4114cc2016-11-01 20:04:06 -0700101 Args:
102 argv: The command line arguments to parse.
103
104 Returns:
105 Branch as a string ('master' if nothing is specified).
106 """
Don Garrett86881cb2017-02-15 15:41:55 -0800107 parser = cbuildbot.CreateParser()
Mike Frysinger80bba8a2017-08-18 15:28:36 -0400108 options = cbuildbot.ParseCommandLine(parser, argv)
Don Garrettd1d90dd2017-06-13 17:35:52 -0700109 options.Freeze()
Don Garrett86881cb2017-02-15 15:41:55 -0800110
111 # This option isn't required for cbuildbot, but is for us.
112 if not options.buildroot:
113 cros_build_lib.Die('--buildroot is a required option.')
114
Don Garrettd1d90dd2017-06-13 17:35:52 -0700115 if len(options.build_targets) != 1:
116 cros_build_lib.Die('Exactly one build target required, got: %s',
117 ', '.join(options.build_targets) or 'None')
Don Garrett597ddff2017-02-17 18:29:37 -0800118
Don Garrett86881cb2017-02-15 15:41:55 -0800119 return options
Don Garrettc4114cc2016-11-01 20:04:06 -0700120
121
Don Garrettbf90cdf2017-05-19 15:54:02 -0700122def GetState(root):
123 """Fetch the current state of our working directory.
124
125 Will return with a default result if there is no known state.
126
127 Args:
128 root: Root of the working directory tree as a string.
129
130 Returns:
131 Layout version as an integer (0 for unknown).
132 Previous branch as a string ('' for unknown).
133 """
134 state_file = os.path.join(root, '.cbuildbot_launch_state')
Don Garrett60967922017-04-12 18:51:44 -0700135
136 try:
137 state = osutils.ReadFile(state_file)
138 buildroot_layout, branchname = state.split()
139 buildroot_layout = int(buildroot_layout)
140 return buildroot_layout, branchname
141 except (IOError, ValueError):
142 # If we are unable to either read or parse the state file, we get here.
143 return 0, ''
144
145
Don Garrettbf90cdf2017-05-19 15:54:02 -0700146def SetState(branchname, root):
147 """Save the current state of our working directory.
148
149 Args:
150 branchname: Name of branch we prepped for as a string.
151 root: Root of the working directory tree as a string.
152 """
Don Garrett60967922017-04-12 18:51:44 -0700153 assert branchname
Don Garrettbf90cdf2017-05-19 15:54:02 -0700154 state_file = os.path.join(root, '.cbuildbot_launch_state')
Don Garrett60967922017-04-12 18:51:44 -0700155 new_state = '%d %s' % (BUILDROOT_BUILDROOT_LAYOUT, branchname)
156 osutils.WriteFile(state_file, new_state)
157
158
Don Garrett125d4dc2017-04-25 16:26:03 -0700159@StageDecorator
Don Garrettbf90cdf2017-05-19 15:54:02 -0700160def CleanBuildRoot(root, repo, metrics_fields):
Don Garrett7ade05a2017-02-17 13:31:47 -0800161 """Some kinds of branch transitions break builds.
162
Don Garrettbf90cdf2017-05-19 15:54:02 -0700163 This method ensures that cbuildbot's buildroot is a clean checkout on the
164 given branch when it starts. If necessary (a branch transition) it will wipe
165 assorted state that cannot be safely reused from the previous build.
Don Garrett7ade05a2017-02-17 13:31:47 -0800166
Don Garrett7ade05a2017-02-17 13:31:47 -0800167 Args:
Don Garrettbf90cdf2017-05-19 15:54:02 -0700168 root: Root directory owned by cbuildbot_launch.
Don Garrettf324bc32017-05-23 14:00:53 -0700169 repo: repository.RepoRepository instance.
Don Garrettacbb2392017-05-11 18:27:41 -0700170 metrics_fields: Dictionary of fields to include in metrics.
Don Garrett7ade05a2017-02-17 13:31:47 -0800171 """
Don Garrettbf90cdf2017-05-19 15:54:02 -0700172 old_buildroot_layout, old_branch = GetState(root)
Don Garrette17e1d92017-04-12 15:28:19 -0700173
Don Garrett60967922017-04-12 18:51:44 -0700174 if old_buildroot_layout != BUILDROOT_BUILDROOT_LAYOUT:
Don Garrett125d4dc2017-04-25 16:26:03 -0700175 logging.PrintBuildbotStepText('Unknown layout: Wiping buildroot.')
Don Garrettacbb2392017-05-11 18:27:41 -0700176 metrics.Counter(METRIC_CLOBBER).increment(
177 field(metrics_fields, reason='layout_change'))
Benjamin Gordon59ba2f82017-08-28 15:31:06 -0600178 chroot_dir = os.path.join(root, 'chroot')
179 if os.path.exists(chroot_dir) or os.path.exists(chroot_dir + '.img'):
180 cros_build_lib.CleanupChrootMount(chroot_dir, delete_image=True)
Don Garrettbf90cdf2017-05-19 15:54:02 -0700181 osutils.RmDir(root, ignore_missing=True, sudo=True)
Don Garrettf324bc32017-05-23 14:00:53 -0700182 else:
183 if old_branch != repo.branch:
184 logging.PrintBuildbotStepText('Branch change: Cleaning buildroot.')
185 logging.info('Unmatched branch: %s -> %s', old_branch, repo.branch)
Don Garrettacbb2392017-05-11 18:27:41 -0700186 metrics.Counter(METRIC_BRANCH_CLEANUP).increment(
187 field(metrics_fields, old_branch=old_branch))
Don Garrett39963602017-02-27 14:41:58 -0800188
Don Garrettf324bc32017-05-23 14:00:53 -0700189 logging.info('Remove Chroot.')
Benjamin Gordon59ba2f82017-08-28 15:31:06 -0600190 chroot_dir = os.path.join(repo.directory, 'chroot')
191 if os.path.exists(chroot_dir) or os.path.exists(chroot_dir + '.img'):
192 cros_build_lib.CleanupChrootMount(chroot_dir, delete_image=True)
193 osutils.RmDir(chroot_dir, ignore_missing=True, sudo=True)
Don Garrett7ade05a2017-02-17 13:31:47 -0800194
Don Garrettf324bc32017-05-23 14:00:53 -0700195 logging.info('Remove Chrome checkout.')
Don Garrettbf90cdf2017-05-19 15:54:02 -0700196 osutils.RmDir(os.path.join(repo.directory, '.cache', 'distfiles'),
Don Garrettf324bc32017-05-23 14:00:53 -0700197 ignore_missing=True, sudo=True)
198
199 try:
200 # If there is any failure doing the cleanup, wipe everything.
201 repo.BuildRootGitCleanup(prune_all=True)
202 except Exception:
203 logging.info('Checkout cleanup failed, wiping buildroot:', exc_info=True)
Don Garrettacbb2392017-05-11 18:27:41 -0700204 metrics.Counter(METRIC_CLOBBER).increment(
205 field(metrics_fields, reason='repo_cleanup_failure'))
Don Garrettbf90cdf2017-05-19 15:54:02 -0700206 repository.ClearBuildRoot(repo.directory)
Don Garrett39963602017-02-27 14:41:58 -0800207
Don Garrettbf90cdf2017-05-19 15:54:02 -0700208 # Ensure buildroot exists. Save the state we are prepped for.
209 osutils.SafeMakedirs(repo.directory)
210 SetState(repo.branch, root)
Don Garrett7ade05a2017-02-17 13:31:47 -0800211
212
Don Garrett125d4dc2017-04-25 16:26:03 -0700213@StageDecorator
Don Garrettf324bc32017-05-23 14:00:53 -0700214def InitialCheckout(repo):
Don Garrett86881cb2017-02-15 15:41:55 -0800215 """Preliminary ChromeOS checkout.
216
217 Perform a complete checkout of ChromeOS on the specified branch. This does NOT
218 match what the build needs, but ensures the buildroot both has a 'hot'
219 checkout, and is close enough that the branched cbuildbot can successfully get
220 the right checkout.
221
222 This checks out full ChromeOS, even if a ChromiumOS build is going to be
223 performed. This is because we have no knowledge of the build config to be
224 used.
Don Garrettc4114cc2016-11-01 20:04:06 -0700225
226 Args:
Don Garrettf324bc32017-05-23 14:00:53 -0700227 repo: repository.RepoRepository instance.
Don Garrettc4114cc2016-11-01 20:04:06 -0700228 """
Don Garrettf324bc32017-05-23 14:00:53 -0700229 logging.PrintBuildbotStepText('Branch: %s' % repo.branch)
Don Garrett7ade05a2017-02-17 13:31:47 -0800230 logging.info('Bootstrap script starting initial sync on branch: %s',
Don Garrettf324bc32017-05-23 14:00:53 -0700231 repo.branch)
Don Garrett76496912017-05-11 16:59:11 -0700232 repo.Sync(detach=True)
Don Garrettc4114cc2016-11-01 20:04:06 -0700233
234
Don Garrett125d4dc2017-04-25 16:26:03 -0700235@StageDecorator
Don Garretta50bf492017-09-28 18:33:02 -0700236def RunCbuildbot(buildroot, depot_tools_path, argv):
Don Garrettc4114cc2016-11-01 20:04:06 -0700237 """Start cbuildbot in specified directory with all arguments.
238
239 Args:
Don Garrettbf90cdf2017-05-19 15:54:02 -0700240 buildroot: Directory to be passed to cbuildbot with --buildroot.
Don Garretta50bf492017-09-28 18:33:02 -0700241 depot_tools_path: Directory for depot_tools to be used by cbuildbot.
Don Garrettd1d90dd2017-06-13 17:35:52 -0700242 argv: Command line options passed to cbuildbot_launch.
Don Garrettc4114cc2016-11-01 20:04:06 -0700243
244 Returns:
245 Return code of cbuildbot as an integer.
246 """
Don Garrettbf90cdf2017-05-19 15:54:02 -0700247 logging.info('Bootstrap cbuildbot in: %s', buildroot)
Don Garrettbf90cdf2017-05-19 15:54:02 -0700248
Don Garrettd1d90dd2017-06-13 17:35:52 -0700249 # Fixup buildroot parameter.
250 argv = argv[:]
251 for i in xrange(len(argv)):
252 if argv[i] in ('-r', '--buildroot'):
253 argv[i+1] = buildroot
Don Garrett597ddff2017-02-17 18:29:37 -0800254
Don Garrettd1d90dd2017-06-13 17:35:52 -0700255 # This filters out command line arguments not supported by older versions
256 # of cbuildbot.
257 parser = cbuildbot.CreateParser()
Mike Frysinger80bba8a2017-08-18 15:28:36 -0400258 options = cbuildbot.ParseCommandLine(parser, argv)
Don Garrettd1d90dd2017-06-13 17:35:52 -0700259 cbuildbot_path = os.path.join(buildroot, 'chromite', 'bin', 'cbuildbot')
Don Garrett597ddff2017-02-17 18:29:37 -0800260 cmd = sync_stages.BootstrapStage.FilterArgsForTargetCbuildbot(
Don Garrettbf90cdf2017-05-19 15:54:02 -0700261 buildroot, cbuildbot_path, options)
Don Garrett597ddff2017-02-17 18:29:37 -0800262
Don Garretta50bf492017-09-28 18:33:02 -0700263 # We want cbuildbot to use branched depot_tools scripts from our manifest,
264 # so that depot_tools is branched to match cbuildbot.
265 logging.info('Adding depot_tools into PATH: %s', depot_tools_path)
266 extra_env = {'PATH': PrependPath(depot_tools_path)}
267
268 result = cros_build_lib.RunCommand(
269 cmd, extra_env=extra_env, error_code_ok=True, cwd=buildroot)
Don Garrettacbb2392017-05-11 18:27:41 -0700270 return result.returncode
Don Garrettc4114cc2016-11-01 20:04:06 -0700271
Don Garrett60967922017-04-12 18:51:44 -0700272
Don Garrett90c9da22017-09-12 16:45:40 -0700273def _InstallSystemCrcmodIfNeeded():
274 """Install Crcmod binary extension if needed.
275
276 If the build host has python-crcmod installed without the binary extension,
277 that breaks some versions of gsutil. In that case, this function installs
278 a new copy of the module with the extension.
279
280 Newer branches workaround this in gs.py, and hopefully future versions of
281 gsutil will work around this in gsutil, however, older branches will always
282 have this problem.
283
284 So... this method will always be required unless/until we chose to solve this
285 through configuration management of hosts.
286
287 http://crbug.com/763438 covers this in detail.
288 """
289 try:
290 import crcmod
291 # If crcmod exists on the system, but doesn't have the binary extension,
292 # gsutil behaves badly. Override the system module with one that contains
293 # the binary extension. This depends on /usr/local/lib overridding /usr/lib.
294 if not (getattr(crcmod, 'crcmod', None) and
295 getattr(crcmod.crcmod, '_usingExtension', None)):
296 logging.warn('FORCING CRCMOD INSTALL to workaround crbug.com/763438.')
297 cmd = ['pip', 'install', '--ignore-installed', 'crcmod']
298 result = cros_build_lib.SudoRunCommand(
299 cmd, error_code_ok=True, mute_output=True)
300 logging.warn('CRCMOD INSTALL RETURNED :%d', result.returncode)
301 except ImportError:
302 # If crcmod doesn't exist on the system, gs.py will properly use its
303 # version in the cache.
304 pass
305
306
Don Garrettf15d65b2017-04-12 12:39:55 -0700307def ConfigureGlobalEnvironment():
308 """Setup process wide environmental changes."""
Don Garrettf15d65b2017-04-12 12:39:55 -0700309 # Set umask to 022 so files created by buildbot are readable.
310 os.umask(0o22)
311
Don Garrett86fec482017-05-17 18:13:33 -0700312 # These variables can interfere with LANG / locale behavior.
313 unwanted_local_vars = [
314 'LC_ALL', 'LC_CTYPE', 'LC_COLLATE', 'LC_TIME', 'LC_NUMERIC',
315 'LC_MONETARY', 'LC_MESSAGES', 'LC_PAPER', 'LC_NAME', 'LC_ADDRESS',
316 'LC_TELEPHONE', 'LC_MEASUREMENT', 'LC_IDENTIFICATION',
317 ]
318 for v in unwanted_local_vars:
319 os.environ.pop(v, None)
320
321 # This variable is required for repo sync's to work in all cases.
322 os.environ['LANG'] = 'en_US.UTF-8'
323
Don Garrett90c9da22017-09-12 16:45:40 -0700324 _InstallSystemCrcmodIfNeeded()
Don Garrett90f5e7d2017-09-08 17:42:46 -0700325
Don Garrettc4114cc2016-11-01 20:04:06 -0700326
Don Garrettacbb2392017-05-11 18:27:41 -0700327def _main(argv):
Don Garrettc4114cc2016-11-01 20:04:06 -0700328 """main method of script.
329
330 Args:
331 argv: All command line arguments to pass as list of strings.
332
333 Returns:
334 Return code of cbuildbot as an integer.
335 """
Don Garrettd1d90dd2017-06-13 17:35:52 -0700336 options = PreParseArguments(argv)
337
338 branchname = options.branch or 'master'
339 root = options.buildroot
340 buildroot = os.path.join(root, 'repository')
Don Garretta50bf492017-09-28 18:33:02 -0700341 depot_tools_path = os.path.join(buildroot, constants.DEPOT_TOOLS_SUBPATH)
Don Garrettd1d90dd2017-06-13 17:35:52 -0700342 build_config = options.build_targets[0]
343
344 metrics_fields = {
345 'branch_name': branchname,
346 'build_config': build_config,
347 'tryjob': options.remote_trybot,
348 }
Don Garrettf15d65b2017-04-12 12:39:55 -0700349
Don Garrettacbb2392017-05-11 18:27:41 -0700350 # Does the entire build pass or fail.
Don Garrett45e77412017-06-14 16:57:55 -0700351 with metrics.Presence(METRIC_ACTIVE, metrics_fields), \
352 metrics.SuccessCounter(METRIC_COMPLETED, metrics_fields) as s_fields:
Don Garrettc4114cc2016-11-01 20:04:06 -0700353
Don Garrettacbb2392017-05-11 18:27:41 -0700354 # Preliminary set, mostly command line parsing.
Don Garrettd1d90dd2017-06-13 17:35:52 -0700355 with metrics.SuccessCounter(METRIC_INVOKED, metrics_fields):
Don Garrettfbbccec2017-09-20 14:04:48 -0700356 if options.enable_buildbot_tags:
357 logging.EnableBuildbotMarkers()
Don Garrettacbb2392017-05-11 18:27:41 -0700358 ConfigureGlobalEnvironment()
Don Garrett86881cb2017-02-15 15:41:55 -0800359
Don Garrettacbb2392017-05-11 18:27:41 -0700360 # Prepare the buildroot with source for the build.
361 with metrics.SuccessCounter(METRIC_PREP, metrics_fields):
362 site_config = config_lib.GetConfig()
363 manifest_url = site_config.params['MANIFEST_INT_URL']
364 repo = repository.RepoRepository(manifest_url, buildroot,
365 branch=branchname,
Don Garrettd1d90dd2017-06-13 17:35:52 -0700366 git_cache_dir=options.git_cache_dir)
Don Garrett86881cb2017-02-15 15:41:55 -0800367
Don Garrettacbb2392017-05-11 18:27:41 -0700368 # Clean up the buildroot to a safe state.
369 with metrics.SecondsTimer(METRIC_CLEAN, fields=metrics_fields):
Don Garrettbf90cdf2017-05-19 15:54:02 -0700370 CleanBuildRoot(root, repo, metrics_fields)
Don Garrettacbb2392017-05-11 18:27:41 -0700371
Don Garretta50bf492017-09-28 18:33:02 -0700372 # Get a checkout close enough to the branch that cbuildbot can handle it.
Don Garrettacbb2392017-05-11 18:27:41 -0700373 with metrics.SecondsTimer(METRIC_INITIAL, fields=metrics_fields):
374 InitialCheckout(repo)
375
376 # Run cbuildbot inside the full ChromeOS checkout, on the specified branch.
377 with metrics.SecondsTimer(METRIC_CBUILDBOT, fields=metrics_fields):
Don Garretta50bf492017-09-28 18:33:02 -0700378 result = RunCbuildbot(buildroot, depot_tools_path, argv)
Don Garrettd1d90dd2017-06-13 17:35:52 -0700379 s_fields['success'] = (result == 0)
Don Garrettacbb2392017-05-11 18:27:41 -0700380 return result
381
382
383def main(argv):
384 # Enable Monarch metrics gathering.
385 with ts_mon_config.SetupTsMonGlobalState('cbuildbot_launch', indirect=True):
386 return _main(argv)