blob: 43c1dbbd5d4ed6151ac07bcd5fed4b64924b4d3a [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.
Ben Zhang30e5aac2017-05-31 15:02:12 +000034BUILDROOT_BUILDROOT_LAYOUT = 1
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
86 Ignores all arguments, other than the branch name.
87
88 Args:
89 argv: The command line arguments to parse.
90
91 Returns:
92 Branch as a string ('master' if nothing is specified).
93 """
Don Garrett86881cb2017-02-15 15:41:55 -080094 parser = cbuildbot.CreateParser()
Don Garrett597ddff2017-02-17 18:29:37 -080095 options, args = cbuildbot.ParseCommandLine(parser, argv)
Don Garrett86881cb2017-02-15 15:41:55 -080096
97 # This option isn't required for cbuildbot, but is for us.
98 if not options.buildroot:
99 cros_build_lib.Die('--buildroot is a required option.')
100
Don Garrett597ddff2017-02-17 18:29:37 -0800101 # Save off the build targets, in a mirror of cbuildbot code.
102 options.build_targets = args
Ben Zhang30e5aac2017-05-31 15:02:12 +0000103 options.Freeze()
Don Garrett597ddff2017-02-17 18:29:37 -0800104
Don Garrett86881cb2017-02-15 15:41:55 -0800105 return options
Don Garrettc4114cc2016-11-01 20:04:06 -0700106
107
Ben Zhang30e5aac2017-05-31 15:02:12 +0000108def GetBuildrootState(buildroot):
109 state_file = os.path.join(buildroot, '.cbuildbot_launch_state')
Don Garrett60967922017-04-12 18:51:44 -0700110
111 try:
112 state = osutils.ReadFile(state_file)
113 buildroot_layout, branchname = state.split()
114 buildroot_layout = int(buildroot_layout)
115 return buildroot_layout, branchname
116 except (IOError, ValueError):
117 # If we are unable to either read or parse the state file, we get here.
118 return 0, ''
119
120
Ben Zhang30e5aac2017-05-31 15:02:12 +0000121def SetBuildrootState(branchname, buildroot):
Don Garrett60967922017-04-12 18:51:44 -0700122 assert branchname
Ben Zhang30e5aac2017-05-31 15:02:12 +0000123 state_file = os.path.join(buildroot, '.cbuildbot_launch_state')
Don Garrett60967922017-04-12 18:51:44 -0700124 new_state = '%d %s' % (BUILDROOT_BUILDROOT_LAYOUT, branchname)
125 osutils.WriteFile(state_file, new_state)
126
127
Don Garrett125d4dc2017-04-25 16:26:03 -0700128@StageDecorator
Ben Zhang30e5aac2017-05-31 15:02:12 +0000129def CleanBuildroot(buildroot, repo, metrics_fields):
Don Garrett7ade05a2017-02-17 13:31:47 -0800130 """Some kinds of branch transitions break builds.
131
132 This method tries to detect cases where that can happen, and clobber what's
133 needed to succeed. However, the clobbers are costly, and should be avoided
134 if necessary.
135
Don Garrett7ade05a2017-02-17 13:31:47 -0800136 Args:
Ben Zhang30e5aac2017-05-31 15:02:12 +0000137 buildroot: Directory with old buildroot to clean as needed.
Don Garrettf324bc32017-05-23 14:00:53 -0700138 repo: repository.RepoRepository instance.
Don Garrettacbb2392017-05-11 18:27:41 -0700139 metrics_fields: Dictionary of fields to include in metrics.
Don Garrett7ade05a2017-02-17 13:31:47 -0800140 """
Ben Zhang30e5aac2017-05-31 15:02:12 +0000141 old_buildroot_layout, old_branch = GetBuildrootState(buildroot)
Don Garrette17e1d92017-04-12 15:28:19 -0700142
Don Garrett60967922017-04-12 18:51:44 -0700143 if old_buildroot_layout != BUILDROOT_BUILDROOT_LAYOUT:
Don Garrett125d4dc2017-04-25 16:26:03 -0700144 logging.PrintBuildbotStepText('Unknown layout: Wiping buildroot.')
Don Garrettacbb2392017-05-11 18:27:41 -0700145 metrics.Counter(METRIC_CLOBBER).increment(
146 field(metrics_fields, reason='layout_change'))
Ben Zhang30e5aac2017-05-31 15:02:12 +0000147 osutils.RmDir(buildroot, ignore_missing=True, sudo=True)
Don Garrettf324bc32017-05-23 14:00:53 -0700148 else:
149 if old_branch != repo.branch:
150 logging.PrintBuildbotStepText('Branch change: Cleaning buildroot.')
151 logging.info('Unmatched branch: %s -> %s', old_branch, repo.branch)
Don Garrettacbb2392017-05-11 18:27:41 -0700152 metrics.Counter(METRIC_BRANCH_CLEANUP).increment(
153 field(metrics_fields, old_branch=old_branch))
Don Garrett39963602017-02-27 14:41:58 -0800154
Don Garrettf324bc32017-05-23 14:00:53 -0700155 logging.info('Remove Chroot.')
Ben Zhang30e5aac2017-05-31 15:02:12 +0000156 osutils.RmDir(os.path.join(buildroot, 'chroot'),
Don Garrettf324bc32017-05-23 14:00:53 -0700157 ignore_missing=True, sudo=True)
Don Garrett7ade05a2017-02-17 13:31:47 -0800158
Don Garrettf324bc32017-05-23 14:00:53 -0700159 logging.info('Remove Chrome checkout.')
Ben Zhang30e5aac2017-05-31 15:02:12 +0000160 osutils.RmDir(os.path.join(buildroot, '.cache', 'distfiles'),
Don Garrettf324bc32017-05-23 14:00:53 -0700161 ignore_missing=True, sudo=True)
162
163 try:
164 # If there is any failure doing the cleanup, wipe everything.
165 repo.BuildRootGitCleanup(prune_all=True)
166 except Exception:
167 logging.info('Checkout cleanup failed, wiping buildroot:', exc_info=True)
Don Garrettacbb2392017-05-11 18:27:41 -0700168 metrics.Counter(METRIC_CLOBBER).increment(
169 field(metrics_fields, reason='repo_cleanup_failure'))
Ben Zhang30e5aac2017-05-31 15:02:12 +0000170 repository.ClearBuildRoot(buildroot)
Don Garrett39963602017-02-27 14:41:58 -0800171
Ben Zhang30e5aac2017-05-31 15:02:12 +0000172 # Ensure buildroot exists.
173 osutils.SafeMakedirs(buildroot)
174 SetBuildrootState(repo.branch, buildroot)
Don Garrett7ade05a2017-02-17 13:31:47 -0800175
176
Don Garrett125d4dc2017-04-25 16:26:03 -0700177@StageDecorator
Don Garrettf324bc32017-05-23 14:00:53 -0700178def InitialCheckout(repo):
Don Garrett86881cb2017-02-15 15:41:55 -0800179 """Preliminary ChromeOS checkout.
180
181 Perform a complete checkout of ChromeOS on the specified branch. This does NOT
182 match what the build needs, but ensures the buildroot both has a 'hot'
183 checkout, and is close enough that the branched cbuildbot can successfully get
184 the right checkout.
185
186 This checks out full ChromeOS, even if a ChromiumOS build is going to be
187 performed. This is because we have no knowledge of the build config to be
188 used.
Don Garrettc4114cc2016-11-01 20:04:06 -0700189
190 Args:
Don Garrettf324bc32017-05-23 14:00:53 -0700191 repo: repository.RepoRepository instance.
Don Garrettc4114cc2016-11-01 20:04:06 -0700192 """
Don Garrettf324bc32017-05-23 14:00:53 -0700193 logging.PrintBuildbotStepText('Branch: %s' % repo.branch)
Don Garrett7ade05a2017-02-17 13:31:47 -0800194 logging.info('Bootstrap script starting initial sync on branch: %s',
Don Garrettf324bc32017-05-23 14:00:53 -0700195 repo.branch)
Don Garrett76496912017-05-11 16:59:11 -0700196 repo.Sync(detach=True)
Don Garrettc4114cc2016-11-01 20:04:06 -0700197
198
Don Garrett125d4dc2017-04-25 16:26:03 -0700199@StageDecorator
Ben Zhang30e5aac2017-05-31 15:02:12 +0000200def RunCbuildbot(options):
Don Garrettc4114cc2016-11-01 20:04:06 -0700201 """Start cbuildbot in specified directory with all arguments.
202
203 Args:
Don Garrett597ddff2017-02-17 18:29:37 -0800204 options: Parse command line options.
Don Garrettc4114cc2016-11-01 20:04:06 -0700205
206 Returns:
207 Return code of cbuildbot as an integer.
208 """
Ben Zhang30e5aac2017-05-31 15:02:12 +0000209 logging.info('Bootstrap cbuildbot in: %s', options.buildroot)
Don Garrett597ddff2017-02-17 18:29:37 -0800210 cbuildbot_path = os.path.join(
Ben Zhang30e5aac2017-05-31 15:02:12 +0000211 options.buildroot, 'chromite', 'bin', 'cbuildbot')
Don Garrett597ddff2017-02-17 18:29:37 -0800212
213 cmd = sync_stages.BootstrapStage.FilterArgsForTargetCbuildbot(
Ben Zhang30e5aac2017-05-31 15:02:12 +0000214 options.buildroot, cbuildbot_path, options)
Don Garrett597ddff2017-02-17 18:29:37 -0800215
Don Garrettacbb2392017-05-11 18:27:41 -0700216 result = cros_build_lib.RunCommand(cmd, error_code_ok=True,
Ben Zhang30e5aac2017-05-31 15:02:12 +0000217 cwd=options.buildroot)
Don Garrettacbb2392017-05-11 18:27:41 -0700218 return result.returncode
Don Garrettc4114cc2016-11-01 20:04:06 -0700219
Don Garrett60967922017-04-12 18:51:44 -0700220
Don Garrettf15d65b2017-04-12 12:39:55 -0700221def ConfigureGlobalEnvironment():
222 """Setup process wide environmental changes."""
Don Garrettf15d65b2017-04-12 12:39:55 -0700223 # Set umask to 022 so files created by buildbot are readable.
224 os.umask(0o22)
225
Don Garrett86fec482017-05-17 18:13:33 -0700226 # These variables can interfere with LANG / locale behavior.
227 unwanted_local_vars = [
228 'LC_ALL', 'LC_CTYPE', 'LC_COLLATE', 'LC_TIME', 'LC_NUMERIC',
229 'LC_MONETARY', 'LC_MESSAGES', 'LC_PAPER', 'LC_NAME', 'LC_ADDRESS',
230 'LC_TELEPHONE', 'LC_MEASUREMENT', 'LC_IDENTIFICATION',
231 ]
232 for v in unwanted_local_vars:
233 os.environ.pop(v, None)
234
235 # This variable is required for repo sync's to work in all cases.
236 os.environ['LANG'] = 'en_US.UTF-8'
237
Don Garrettc4114cc2016-11-01 20:04:06 -0700238
Don Garrettacbb2392017-05-11 18:27:41 -0700239def _main(argv):
Don Garrettc4114cc2016-11-01 20:04:06 -0700240 """main method of script.
241
242 Args:
243 argv: All command line arguments to pass as list of strings.
244
245 Returns:
246 Return code of cbuildbot as an integer.
247 """
Don Garrettacbb2392017-05-11 18:27:41 -0700248 metrics_fields = {'branch_name': 'unknown'}
Don Garrettf15d65b2017-04-12 12:39:55 -0700249
Don Garrettacbb2392017-05-11 18:27:41 -0700250 # Does the entire build pass or fail.
251 with metrics.SuccessCounter(METRIC_COMPLETED, metrics_fields) as c_fields:
Don Garrettc4114cc2016-11-01 20:04:06 -0700252
Don Garrettacbb2392017-05-11 18:27:41 -0700253 # Preliminary set, mostly command line parsing.
254 with metrics.SuccessCounter(METRIC_INVOKED, metrics_fields) as i_fields:
255 logging.EnableBuildbotMarkers()
256 ConfigureGlobalEnvironment()
Don Garrett86881cb2017-02-15 15:41:55 -0800257
Don Garrettacbb2392017-05-11 18:27:41 -0700258 options = PreParseArguments(argv)
Don Garrettf324bc32017-05-23 14:00:53 -0700259
Don Garrettacbb2392017-05-11 18:27:41 -0700260 branchname = options.branch or 'master'
Ben Zhang30e5aac2017-05-31 15:02:12 +0000261 buildroot = options.buildroot
Don Garrettacbb2392017-05-11 18:27:41 -0700262 git_cache_dir = options.git_cache_dir
Don Garrettf324bc32017-05-23 14:00:53 -0700263
Don Garrettacbb2392017-05-11 18:27:41 -0700264 # Update metrics fields after parsing command line arguments.
265 metrics_fields['branch_name'] = branchname
266 i_fields.update(metrics_fields)
267 c_fields.update(metrics_fields)
Don Garrett7ade05a2017-02-17 13:31:47 -0800268
Don Garrettacbb2392017-05-11 18:27:41 -0700269 # Prepare the buildroot with source for the build.
270 with metrics.SuccessCounter(METRIC_PREP, metrics_fields):
271 site_config = config_lib.GetConfig()
272 manifest_url = site_config.params['MANIFEST_INT_URL']
273 repo = repository.RepoRepository(manifest_url, buildroot,
274 branch=branchname,
275 git_cache_dir=git_cache_dir)
Don Garrett86881cb2017-02-15 15:41:55 -0800276
Don Garrettacbb2392017-05-11 18:27:41 -0700277 # Clean up the buildroot to a safe state.
278 with metrics.SecondsTimer(METRIC_CLEAN, fields=metrics_fields):
Ben Zhang30e5aac2017-05-31 15:02:12 +0000279 CleanBuildroot(buildroot, repo, metrics_fields)
Don Garrettacbb2392017-05-11 18:27:41 -0700280
281 # Get a checkout close enough the branched cbuildbot can handle it.
282 with metrics.SecondsTimer(METRIC_INITIAL, fields=metrics_fields):
283 InitialCheckout(repo)
284
285 # Run cbuildbot inside the full ChromeOS checkout, on the specified branch.
286 with metrics.SecondsTimer(METRIC_CBUILDBOT, fields=metrics_fields):
Ben Zhang30e5aac2017-05-31 15:02:12 +0000287 result = RunCbuildbot(options)
Don Garrettacbb2392017-05-11 18:27:41 -0700288 c_fields['success'] = (result == 0)
289 return result
290
291
292def main(argv):
293 # Enable Monarch metrics gathering.
294 with ts_mon_config.SetupTsMonGlobalState('cbuildbot_launch', indirect=True):
295 return _main(argv)