blob: 5334de9d1ce97c608fc90a0a3afb8c796177969b [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 Garrettc4114cc2016-11-01 20:04:06 -070024from chromite.lib import cros_build_lib
25from chromite.lib import cros_logging as logging
Don Garrettacbb2392017-05-11 18:27:41 -070026from chromite.lib import metrics
Don Garrettc4114cc2016-11-01 20:04:06 -070027from chromite.lib import osutils
Don Garrettacbb2392017-05-11 18:27:41 -070028from chromite.lib import ts_mon_config
Don Garrett86881cb2017-02-15 15:41:55 -080029from chromite.scripts import cbuildbot
Don Garrettc4114cc2016-11-01 20:04:06 -070030
Don Garrett597ddff2017-02-17 18:29:37 -080031
Don Garrett60967922017-04-12 18:51:44 -070032# This number should be incremented when we change the layout of the buildroot
33# in a non-backwards compatible way. This wipes all buildroots.
Don Garrettbf90cdf2017-05-19 15:54:02 -070034BUILDROOT_BUILDROOT_LAYOUT = 2
Don Garrett60967922017-04-12 18:51:44 -070035
Don Garrettacbb2392017-05-11 18:27:41 -070036# Metrics reported to Monarch.
37METRIC_INVOKED = 'chromeos/chromite/cbuildbot_launch/invoked'
38METRIC_COMPLETED = 'chromeos/chromite/cbuildbot_launch/completed'
39METRIC_PREP = 'chromeos/chromite/cbuildbot_launch/prep_completed'
40METRIC_CLEAN = 'chromeos/chromite/cbuildbot_launch/clean_buildroot_durations'
41METRIC_INITIAL = 'chromeos/chromite/cbuildbot_launch/initial_checkout_durations'
42METRIC_CBUILDBOT = 'chromeos/chromite/cbuildbot_launch/cbuildbot_durations'
43METRIC_CLOBBER = 'chromeos/chromite/cbuildbot_launch/clobber'
44METRIC_BRANCH_CLEANUP = 'chromeos/chromite/cbuildbot_launch/branch_cleanup'
45
Don Garrett60967922017-04-12 18:51:44 -070046
Don Garrett125d4dc2017-04-25 16:26:03 -070047def StageDecorator(functor):
48 """A Decorator that adds buildbot stage tags around a method.
49
Don Garrettacbb2392017-05-11 18:27:41 -070050 It uses the method name as the stage name, and assumes failure on a true
51 return value, or an exception.
Don Garrett125d4dc2017-04-25 16:26:03 -070052 """
53 @functools.wraps(functor)
54 def wrapped_functor(*args, **kwargs):
55 try:
56 logging.PrintBuildbotStepName(functor.__name__)
Don Garrettacbb2392017-05-11 18:27:41 -070057 result = functor(*args, **kwargs)
Don Garrett125d4dc2017-04-25 16:26:03 -070058 except Exception:
59 logging.PrintBuildbotStepFailure()
60 raise
61
Don Garrettacbb2392017-05-11 18:27:41 -070062 if result:
63 logging.PrintBuildbotStepFailure()
64 return result
65
Don Garrett125d4dc2017-04-25 16:26:03 -070066 return wrapped_functor
67
68
Don Garrettacbb2392017-05-11 18:27:41 -070069def field(fields, **kwargs):
70 """Helper for inserting more fields into a metrics fields dictionary.
71
72 Args:
73 fields: Dictionary of metrics fields.
74 kwargs: Each argument is a key/value pair to insert into dict.
75
76 Returns:
77 Copy of original dictionary with kwargs set as fields.
78 """
79 f = fields.copy()
80 f.update(kwargs)
81 return f
82
Don Garrett86881cb2017-02-15 15:41:55 -080083def PreParseArguments(argv):
Don Garrettc4114cc2016-11-01 20:04:06 -070084 """Extract the branch name from cbuildbot command line arguments.
85
Don Garrettc4114cc2016-11-01 20:04:06 -070086 Args:
87 argv: The command line arguments to parse.
88
89 Returns:
90 Branch as a string ('master' if nothing is specified).
91 """
Don Garrett86881cb2017-02-15 15:41:55 -080092 parser = cbuildbot.CreateParser()
Don Garrettd1d90dd2017-06-13 17:35:52 -070093 options, _ = cbuildbot.ParseCommandLine(parser, argv)
94 options.Freeze()
Don Garrett86881cb2017-02-15 15:41:55 -080095
96 # This option isn't required for cbuildbot, but is for us.
97 if not options.buildroot:
98 cros_build_lib.Die('--buildroot is a required option.')
99
Don Garrettd1d90dd2017-06-13 17:35:52 -0700100 if len(options.build_targets) != 1:
101 cros_build_lib.Die('Exactly one build target required, got: %s',
102 ', '.join(options.build_targets) or 'None')
Don Garrett597ddff2017-02-17 18:29:37 -0800103
Don Garrett86881cb2017-02-15 15:41:55 -0800104 return options
Don Garrettc4114cc2016-11-01 20:04:06 -0700105
106
Don Garrettbf90cdf2017-05-19 15:54:02 -0700107def GetState(root):
108 """Fetch the current state of our working directory.
109
110 Will return with a default result if there is no known state.
111
112 Args:
113 root: Root of the working directory tree as a string.
114
115 Returns:
116 Layout version as an integer (0 for unknown).
117 Previous branch as a string ('' for unknown).
118 """
119 state_file = os.path.join(root, '.cbuildbot_launch_state')
Don Garrett60967922017-04-12 18:51:44 -0700120
121 try:
122 state = osutils.ReadFile(state_file)
123 buildroot_layout, branchname = state.split()
124 buildroot_layout = int(buildroot_layout)
125 return buildroot_layout, branchname
126 except (IOError, ValueError):
127 # If we are unable to either read or parse the state file, we get here.
128 return 0, ''
129
130
Don Garrettbf90cdf2017-05-19 15:54:02 -0700131def SetState(branchname, root):
132 """Save the current state of our working directory.
133
134 Args:
135 branchname: Name of branch we prepped for as a string.
136 root: Root of the working directory tree as a string.
137 """
Don Garrett60967922017-04-12 18:51:44 -0700138 assert branchname
Don Garrettbf90cdf2017-05-19 15:54:02 -0700139 state_file = os.path.join(root, '.cbuildbot_launch_state')
Don Garrett60967922017-04-12 18:51:44 -0700140 new_state = '%d %s' % (BUILDROOT_BUILDROOT_LAYOUT, branchname)
141 osutils.WriteFile(state_file, new_state)
142
143
Don Garrett125d4dc2017-04-25 16:26:03 -0700144@StageDecorator
Don Garrettbf90cdf2017-05-19 15:54:02 -0700145def CleanBuildRoot(root, repo, metrics_fields):
Don Garrett7ade05a2017-02-17 13:31:47 -0800146 """Some kinds of branch transitions break builds.
147
Don Garrettbf90cdf2017-05-19 15:54:02 -0700148 This method ensures that cbuildbot's buildroot is a clean checkout on the
149 given branch when it starts. If necessary (a branch transition) it will wipe
150 assorted state that cannot be safely reused from the previous build.
Don Garrett7ade05a2017-02-17 13:31:47 -0800151
Don Garrett7ade05a2017-02-17 13:31:47 -0800152 Args:
Don Garrettbf90cdf2017-05-19 15:54:02 -0700153 root: Root directory owned by cbuildbot_launch.
Don Garrettf324bc32017-05-23 14:00:53 -0700154 repo: repository.RepoRepository instance.
Don Garrettacbb2392017-05-11 18:27:41 -0700155 metrics_fields: Dictionary of fields to include in metrics.
Don Garrett7ade05a2017-02-17 13:31:47 -0800156 """
Don Garrettbf90cdf2017-05-19 15:54:02 -0700157 old_buildroot_layout, old_branch = GetState(root)
Don Garrette17e1d92017-04-12 15:28:19 -0700158
Don Garrett60967922017-04-12 18:51:44 -0700159 if old_buildroot_layout != BUILDROOT_BUILDROOT_LAYOUT:
Don Garrett125d4dc2017-04-25 16:26:03 -0700160 logging.PrintBuildbotStepText('Unknown layout: Wiping buildroot.')
Don Garrettacbb2392017-05-11 18:27:41 -0700161 metrics.Counter(METRIC_CLOBBER).increment(
162 field(metrics_fields, reason='layout_change'))
Don Garrettbf90cdf2017-05-19 15:54:02 -0700163 osutils.RmDir(root, ignore_missing=True, sudo=True)
Don Garrettf324bc32017-05-23 14:00:53 -0700164 else:
165 if old_branch != repo.branch:
166 logging.PrintBuildbotStepText('Branch change: Cleaning buildroot.')
167 logging.info('Unmatched branch: %s -> %s', old_branch, repo.branch)
Don Garrettacbb2392017-05-11 18:27:41 -0700168 metrics.Counter(METRIC_BRANCH_CLEANUP).increment(
169 field(metrics_fields, old_branch=old_branch))
Don Garrett39963602017-02-27 14:41:58 -0800170
Don Garrettf324bc32017-05-23 14:00:53 -0700171 logging.info('Remove Chroot.')
Don Garrettbf90cdf2017-05-19 15:54:02 -0700172 osutils.RmDir(os.path.join(repo.directory, 'chroot'),
Don Garrettf324bc32017-05-23 14:00:53 -0700173 ignore_missing=True, sudo=True)
Don Garrett7ade05a2017-02-17 13:31:47 -0800174
Don Garrettf324bc32017-05-23 14:00:53 -0700175 logging.info('Remove Chrome checkout.')
Don Garrettbf90cdf2017-05-19 15:54:02 -0700176 osutils.RmDir(os.path.join(repo.directory, '.cache', 'distfiles'),
Don Garrettf324bc32017-05-23 14:00:53 -0700177 ignore_missing=True, sudo=True)
178
179 try:
180 # If there is any failure doing the cleanup, wipe everything.
181 repo.BuildRootGitCleanup(prune_all=True)
182 except Exception:
183 logging.info('Checkout cleanup failed, wiping buildroot:', exc_info=True)
Don Garrettacbb2392017-05-11 18:27:41 -0700184 metrics.Counter(METRIC_CLOBBER).increment(
185 field(metrics_fields, reason='repo_cleanup_failure'))
Don Garrettbf90cdf2017-05-19 15:54:02 -0700186 repository.ClearBuildRoot(repo.directory)
Don Garrett39963602017-02-27 14:41:58 -0800187
Don Garrettbf90cdf2017-05-19 15:54:02 -0700188 # Ensure buildroot exists. Save the state we are prepped for.
189 osutils.SafeMakedirs(repo.directory)
190 SetState(repo.branch, root)
Don Garrett7ade05a2017-02-17 13:31:47 -0800191
192
Don Garrett125d4dc2017-04-25 16:26:03 -0700193@StageDecorator
Don Garrettf324bc32017-05-23 14:00:53 -0700194def InitialCheckout(repo):
Don Garrett86881cb2017-02-15 15:41:55 -0800195 """Preliminary ChromeOS checkout.
196
197 Perform a complete checkout of ChromeOS on the specified branch. This does NOT
198 match what the build needs, but ensures the buildroot both has a 'hot'
199 checkout, and is close enough that the branched cbuildbot can successfully get
200 the right checkout.
201
202 This checks out full ChromeOS, even if a ChromiumOS build is going to be
203 performed. This is because we have no knowledge of the build config to be
204 used.
Don Garrettc4114cc2016-11-01 20:04:06 -0700205
206 Args:
Don Garrettf324bc32017-05-23 14:00:53 -0700207 repo: repository.RepoRepository instance.
Don Garrettc4114cc2016-11-01 20:04:06 -0700208 """
Don Garrettf324bc32017-05-23 14:00:53 -0700209 logging.PrintBuildbotStepText('Branch: %s' % repo.branch)
Don Garrett7ade05a2017-02-17 13:31:47 -0800210 logging.info('Bootstrap script starting initial sync on branch: %s',
Don Garrettf324bc32017-05-23 14:00:53 -0700211 repo.branch)
Don Garrett76496912017-05-11 16:59:11 -0700212 repo.Sync(detach=True)
Don Garrettc4114cc2016-11-01 20:04:06 -0700213
214
Don Garrett125d4dc2017-04-25 16:26:03 -0700215@StageDecorator
Don Garrettd1d90dd2017-06-13 17:35:52 -0700216def RunCbuildbot(buildroot, argv):
Don Garrettc4114cc2016-11-01 20:04:06 -0700217 """Start cbuildbot in specified directory with all arguments.
218
219 Args:
Don Garrettbf90cdf2017-05-19 15:54:02 -0700220 buildroot: Directory to be passed to cbuildbot with --buildroot.
Don Garrettd1d90dd2017-06-13 17:35:52 -0700221 argv: Command line options passed to cbuildbot_launch.
Don Garrettc4114cc2016-11-01 20:04:06 -0700222
223 Returns:
224 Return code of cbuildbot as an integer.
225 """
Don Garrettbf90cdf2017-05-19 15:54:02 -0700226 logging.info('Bootstrap cbuildbot in: %s', buildroot)
Don Garrettbf90cdf2017-05-19 15:54:02 -0700227
Don Garrettd1d90dd2017-06-13 17:35:52 -0700228 # Fixup buildroot parameter.
229 argv = argv[:]
230 for i in xrange(len(argv)):
231 if argv[i] in ('-r', '--buildroot'):
232 argv[i+1] = buildroot
Don Garrett597ddff2017-02-17 18:29:37 -0800233
Don Garrettd1d90dd2017-06-13 17:35:52 -0700234 # This filters out command line arguments not supported by older versions
235 # of cbuildbot.
236 parser = cbuildbot.CreateParser()
237 options, _ = cbuildbot.ParseCommandLine(parser, argv)
238 cbuildbot_path = os.path.join(buildroot, 'chromite', 'bin', 'cbuildbot')
Don Garrett597ddff2017-02-17 18:29:37 -0800239 cmd = sync_stages.BootstrapStage.FilterArgsForTargetCbuildbot(
Don Garrettbf90cdf2017-05-19 15:54:02 -0700240 buildroot, cbuildbot_path, options)
Don Garrett597ddff2017-02-17 18:29:37 -0800241
Don Garrettd1d90dd2017-06-13 17:35:52 -0700242 # Actually run cbuildbot with the fixed up command line options.
243 result = cros_build_lib.RunCommand(cmd, error_code_ok=True, cwd=buildroot)
Don Garrettacbb2392017-05-11 18:27:41 -0700244 return result.returncode
Don Garrettc4114cc2016-11-01 20:04:06 -0700245
Don Garrett60967922017-04-12 18:51:44 -0700246
Don Garrettf15d65b2017-04-12 12:39:55 -0700247def ConfigureGlobalEnvironment():
248 """Setup process wide environmental changes."""
Don Garrettf15d65b2017-04-12 12:39:55 -0700249 # Set umask to 022 so files created by buildbot are readable.
250 os.umask(0o22)
251
Don Garrett86fec482017-05-17 18:13:33 -0700252 # These variables can interfere with LANG / locale behavior.
253 unwanted_local_vars = [
254 'LC_ALL', 'LC_CTYPE', 'LC_COLLATE', 'LC_TIME', 'LC_NUMERIC',
255 'LC_MONETARY', 'LC_MESSAGES', 'LC_PAPER', 'LC_NAME', 'LC_ADDRESS',
256 'LC_TELEPHONE', 'LC_MEASUREMENT', 'LC_IDENTIFICATION',
257 ]
258 for v in unwanted_local_vars:
259 os.environ.pop(v, None)
260
261 # This variable is required for repo sync's to work in all cases.
262 os.environ['LANG'] = 'en_US.UTF-8'
263
Don Garrettc4114cc2016-11-01 20:04:06 -0700264
Don Garrettacbb2392017-05-11 18:27:41 -0700265def _main(argv):
Don Garrettc4114cc2016-11-01 20:04:06 -0700266 """main method of script.
267
268 Args:
269 argv: All command line arguments to pass as list of strings.
270
271 Returns:
272 Return code of cbuildbot as an integer.
273 """
Don Garrettd1d90dd2017-06-13 17:35:52 -0700274 options = PreParseArguments(argv)
275
276 branchname = options.branch or 'master'
277 root = options.buildroot
278 buildroot = os.path.join(root, 'repository')
279 build_config = options.build_targets[0]
280
281 metrics_fields = {
282 'branch_name': branchname,
283 'build_config': build_config,
284 'tryjob': options.remote_trybot,
285 }
Don Garrettf15d65b2017-04-12 12:39:55 -0700286
Don Garrettacbb2392017-05-11 18:27:41 -0700287 # Does the entire build pass or fail.
Don Garrettd1d90dd2017-06-13 17:35:52 -0700288 with metrics.SuccessCounter(METRIC_COMPLETED, metrics_fields) as s_fields:
Don Garrettc4114cc2016-11-01 20:04:06 -0700289
Don Garrettacbb2392017-05-11 18:27:41 -0700290 # Preliminary set, mostly command line parsing.
Don Garrettd1d90dd2017-06-13 17:35:52 -0700291 with metrics.SuccessCounter(METRIC_INVOKED, metrics_fields):
Don Garrettacbb2392017-05-11 18:27:41 -0700292 logging.EnableBuildbotMarkers()
293 ConfigureGlobalEnvironment()
Don Garrett86881cb2017-02-15 15:41:55 -0800294
Don Garrettacbb2392017-05-11 18:27:41 -0700295 # Prepare the buildroot with source for the build.
296 with metrics.SuccessCounter(METRIC_PREP, metrics_fields):
297 site_config = config_lib.GetConfig()
298 manifest_url = site_config.params['MANIFEST_INT_URL']
299 repo = repository.RepoRepository(manifest_url, buildroot,
300 branch=branchname,
Don Garrettd1d90dd2017-06-13 17:35:52 -0700301 git_cache_dir=options.git_cache_dir)
Don Garrett86881cb2017-02-15 15:41:55 -0800302
Don Garrettacbb2392017-05-11 18:27:41 -0700303 # Clean up the buildroot to a safe state.
304 with metrics.SecondsTimer(METRIC_CLEAN, fields=metrics_fields):
Don Garrettbf90cdf2017-05-19 15:54:02 -0700305 CleanBuildRoot(root, repo, metrics_fields)
Don Garrettacbb2392017-05-11 18:27:41 -0700306
307 # Get a checkout close enough the branched cbuildbot can handle it.
308 with metrics.SecondsTimer(METRIC_INITIAL, fields=metrics_fields):
309 InitialCheckout(repo)
310
311 # Run cbuildbot inside the full ChromeOS checkout, on the specified branch.
312 with metrics.SecondsTimer(METRIC_CBUILDBOT, fields=metrics_fields):
Don Garrettd1d90dd2017-06-13 17:35:52 -0700313 result = RunCbuildbot(buildroot, argv)
314 s_fields['success'] = (result == 0)
Don Garrettacbb2392017-05-11 18:27:41 -0700315 return result
316
317
318def main(argv):
319 # Enable Monarch metrics gathering.
320 with ts_mon_config.SetupTsMonGlobalState('cbuildbot_launch', indirect=True):
321 return _main(argv)