blob: ff37ea498b2a2b225a2c3b77f7e3ed05bed50ac9 [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.
Don Garrett45e77412017-06-14 16:57:55 -070037METRIC_ACTIVE = 'chromeos/chromite/cbuildbot_launch/active'
Don Garrettacbb2392017-05-11 18:27:41 -070038METRIC_INVOKED = 'chromeos/chromite/cbuildbot_launch/invoked'
39METRIC_COMPLETED = 'chromeos/chromite/cbuildbot_launch/completed'
40METRIC_PREP = 'chromeos/chromite/cbuildbot_launch/prep_completed'
41METRIC_CLEAN = 'chromeos/chromite/cbuildbot_launch/clean_buildroot_durations'
42METRIC_INITIAL = 'chromeos/chromite/cbuildbot_launch/initial_checkout_durations'
43METRIC_CBUILDBOT = 'chromeos/chromite/cbuildbot_launch/cbuildbot_durations'
44METRIC_CLOBBER = 'chromeos/chromite/cbuildbot_launch/clobber'
45METRIC_BRANCH_CLEANUP = 'chromeos/chromite/cbuildbot_launch/branch_cleanup'
46
Don Garrett60967922017-04-12 18:51:44 -070047
Don Garrett125d4dc2017-04-25 16:26:03 -070048def StageDecorator(functor):
49 """A Decorator that adds buildbot stage tags around a method.
50
Don Garrettacbb2392017-05-11 18:27:41 -070051 It uses the method name as the stage name, and assumes failure on a true
52 return value, or an exception.
Don Garrett125d4dc2017-04-25 16:26:03 -070053 """
54 @functools.wraps(functor)
55 def wrapped_functor(*args, **kwargs):
56 try:
57 logging.PrintBuildbotStepName(functor.__name__)
Don Garrettacbb2392017-05-11 18:27:41 -070058 result = functor(*args, **kwargs)
Don Garrett125d4dc2017-04-25 16:26:03 -070059 except Exception:
60 logging.PrintBuildbotStepFailure()
61 raise
62
Don Garrettacbb2392017-05-11 18:27:41 -070063 if result:
64 logging.PrintBuildbotStepFailure()
65 return result
66
Don Garrett125d4dc2017-04-25 16:26:03 -070067 return wrapped_functor
68
69
Don Garrettacbb2392017-05-11 18:27:41 -070070def field(fields, **kwargs):
71 """Helper for inserting more fields into a metrics fields dictionary.
72
73 Args:
74 fields: Dictionary of metrics fields.
75 kwargs: Each argument is a key/value pair to insert into dict.
76
77 Returns:
78 Copy of original dictionary with kwargs set as fields.
79 """
80 f = fields.copy()
81 f.update(kwargs)
82 return f
83
Don Garrett86881cb2017-02-15 15:41:55 -080084def PreParseArguments(argv):
Don Garrettc4114cc2016-11-01 20:04:06 -070085 """Extract the branch name from cbuildbot command line arguments.
86
Don Garrettc4114cc2016-11-01 20:04:06 -070087 Args:
88 argv: The command line arguments to parse.
89
90 Returns:
91 Branch as a string ('master' if nothing is specified).
92 """
Don Garrett86881cb2017-02-15 15:41:55 -080093 parser = cbuildbot.CreateParser()
Mike Frysinger80bba8a2017-08-18 15:28:36 -040094 options = cbuildbot.ParseCommandLine(parser, argv)
Don Garrettd1d90dd2017-06-13 17:35:52 -070095 options.Freeze()
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 Garrettd1d90dd2017-06-13 17:35:52 -0700101 if len(options.build_targets) != 1:
102 cros_build_lib.Die('Exactly one build target required, got: %s',
103 ', '.join(options.build_targets) or 'None')
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
Don Garrettbf90cdf2017-05-19 15:54:02 -0700108def GetState(root):
109 """Fetch the current state of our working directory.
110
111 Will return with a default result if there is no known state.
112
113 Args:
114 root: Root of the working directory tree as a string.
115
116 Returns:
117 Layout version as an integer (0 for unknown).
118 Previous branch as a string ('' for unknown).
119 """
120 state_file = os.path.join(root, '.cbuildbot_launch_state')
Don Garrett60967922017-04-12 18:51:44 -0700121
122 try:
123 state = osutils.ReadFile(state_file)
124 buildroot_layout, branchname = state.split()
125 buildroot_layout = int(buildroot_layout)
126 return buildroot_layout, branchname
127 except (IOError, ValueError):
128 # If we are unable to either read or parse the state file, we get here.
129 return 0, ''
130
131
Don Garrettbf90cdf2017-05-19 15:54:02 -0700132def SetState(branchname, root):
133 """Save the current state of our working directory.
134
135 Args:
136 branchname: Name of branch we prepped for as a string.
137 root: Root of the working directory tree as a string.
138 """
Don Garrett60967922017-04-12 18:51:44 -0700139 assert branchname
Don Garrettbf90cdf2017-05-19 15:54:02 -0700140 state_file = os.path.join(root, '.cbuildbot_launch_state')
Don Garrett60967922017-04-12 18:51:44 -0700141 new_state = '%d %s' % (BUILDROOT_BUILDROOT_LAYOUT, branchname)
142 osutils.WriteFile(state_file, new_state)
143
144
Don Garrett125d4dc2017-04-25 16:26:03 -0700145@StageDecorator
Don Garrettbf90cdf2017-05-19 15:54:02 -0700146def CleanBuildRoot(root, repo, metrics_fields):
Don Garrett7ade05a2017-02-17 13:31:47 -0800147 """Some kinds of branch transitions break builds.
148
Don Garrettbf90cdf2017-05-19 15:54:02 -0700149 This method ensures that cbuildbot's buildroot is a clean checkout on the
150 given branch when it starts. If necessary (a branch transition) it will wipe
151 assorted state that cannot be safely reused from the previous build.
Don Garrett7ade05a2017-02-17 13:31:47 -0800152
Don Garrett7ade05a2017-02-17 13:31:47 -0800153 Args:
Don Garrettbf90cdf2017-05-19 15:54:02 -0700154 root: Root directory owned by cbuildbot_launch.
Don Garrettf324bc32017-05-23 14:00:53 -0700155 repo: repository.RepoRepository instance.
Don Garrettacbb2392017-05-11 18:27:41 -0700156 metrics_fields: Dictionary of fields to include in metrics.
Don Garrett7ade05a2017-02-17 13:31:47 -0800157 """
Don Garrettbf90cdf2017-05-19 15:54:02 -0700158 old_buildroot_layout, old_branch = GetState(root)
Don Garrette17e1d92017-04-12 15:28:19 -0700159
Don Garrett60967922017-04-12 18:51:44 -0700160 if old_buildroot_layout != BUILDROOT_BUILDROOT_LAYOUT:
Don Garrett125d4dc2017-04-25 16:26:03 -0700161 logging.PrintBuildbotStepText('Unknown layout: Wiping buildroot.')
Don Garrettacbb2392017-05-11 18:27:41 -0700162 metrics.Counter(METRIC_CLOBBER).increment(
163 field(metrics_fields, reason='layout_change'))
Benjamin Gordon59ba2f82017-08-28 15:31:06 -0600164 chroot_dir = os.path.join(root, 'chroot')
165 if os.path.exists(chroot_dir) or os.path.exists(chroot_dir + '.img'):
166 cros_build_lib.CleanupChrootMount(chroot_dir, delete_image=True)
Don Garrettbf90cdf2017-05-19 15:54:02 -0700167 osutils.RmDir(root, ignore_missing=True, sudo=True)
Don Garrettf324bc32017-05-23 14:00:53 -0700168 else:
169 if old_branch != repo.branch:
170 logging.PrintBuildbotStepText('Branch change: Cleaning buildroot.')
171 logging.info('Unmatched branch: %s -> %s', old_branch, repo.branch)
Don Garrettacbb2392017-05-11 18:27:41 -0700172 metrics.Counter(METRIC_BRANCH_CLEANUP).increment(
173 field(metrics_fields, old_branch=old_branch))
Don Garrett39963602017-02-27 14:41:58 -0800174
Don Garrettf324bc32017-05-23 14:00:53 -0700175 logging.info('Remove Chroot.')
Benjamin Gordon59ba2f82017-08-28 15:31:06 -0600176 chroot_dir = os.path.join(repo.directory, 'chroot')
177 if os.path.exists(chroot_dir) or os.path.exists(chroot_dir + '.img'):
178 cros_build_lib.CleanupChrootMount(chroot_dir, delete_image=True)
179 osutils.RmDir(chroot_dir, ignore_missing=True, sudo=True)
Don Garrett7ade05a2017-02-17 13:31:47 -0800180
Don Garrettf324bc32017-05-23 14:00:53 -0700181 logging.info('Remove Chrome checkout.')
Don Garrettbf90cdf2017-05-19 15:54:02 -0700182 osutils.RmDir(os.path.join(repo.directory, '.cache', 'distfiles'),
Don Garrettf324bc32017-05-23 14:00:53 -0700183 ignore_missing=True, sudo=True)
184
185 try:
186 # If there is any failure doing the cleanup, wipe everything.
187 repo.BuildRootGitCleanup(prune_all=True)
188 except Exception:
189 logging.info('Checkout cleanup failed, wiping buildroot:', exc_info=True)
Don Garrettacbb2392017-05-11 18:27:41 -0700190 metrics.Counter(METRIC_CLOBBER).increment(
191 field(metrics_fields, reason='repo_cleanup_failure'))
Don Garrettbf90cdf2017-05-19 15:54:02 -0700192 repository.ClearBuildRoot(repo.directory)
Don Garrett39963602017-02-27 14:41:58 -0800193
Don Garrettbf90cdf2017-05-19 15:54:02 -0700194 # Ensure buildroot exists. Save the state we are prepped for.
195 osutils.SafeMakedirs(repo.directory)
196 SetState(repo.branch, root)
Don Garrett7ade05a2017-02-17 13:31:47 -0800197
198
Don Garrett125d4dc2017-04-25 16:26:03 -0700199@StageDecorator
Don Garrettf324bc32017-05-23 14:00:53 -0700200def InitialCheckout(repo):
Don Garrett86881cb2017-02-15 15:41:55 -0800201 """Preliminary ChromeOS checkout.
202
203 Perform a complete checkout of ChromeOS on the specified branch. This does NOT
204 match what the build needs, but ensures the buildroot both has a 'hot'
205 checkout, and is close enough that the branched cbuildbot can successfully get
206 the right checkout.
207
208 This checks out full ChromeOS, even if a ChromiumOS build is going to be
209 performed. This is because we have no knowledge of the build config to be
210 used.
Don Garrettc4114cc2016-11-01 20:04:06 -0700211
212 Args:
Don Garrettf324bc32017-05-23 14:00:53 -0700213 repo: repository.RepoRepository instance.
Don Garrettc4114cc2016-11-01 20:04:06 -0700214 """
Don Garrettf324bc32017-05-23 14:00:53 -0700215 logging.PrintBuildbotStepText('Branch: %s' % repo.branch)
Don Garrett7ade05a2017-02-17 13:31:47 -0800216 logging.info('Bootstrap script starting initial sync on branch: %s',
Don Garrettf324bc32017-05-23 14:00:53 -0700217 repo.branch)
Don Garrett76496912017-05-11 16:59:11 -0700218 repo.Sync(detach=True)
Don Garrettc4114cc2016-11-01 20:04:06 -0700219
220
Don Garrett125d4dc2017-04-25 16:26:03 -0700221@StageDecorator
Don Garrettd1d90dd2017-06-13 17:35:52 -0700222def RunCbuildbot(buildroot, argv):
Don Garrettc4114cc2016-11-01 20:04:06 -0700223 """Start cbuildbot in specified directory with all arguments.
224
225 Args:
Don Garrettbf90cdf2017-05-19 15:54:02 -0700226 buildroot: Directory to be passed to cbuildbot with --buildroot.
Don Garrettd1d90dd2017-06-13 17:35:52 -0700227 argv: Command line options passed to cbuildbot_launch.
Don Garrettc4114cc2016-11-01 20:04:06 -0700228
229 Returns:
230 Return code of cbuildbot as an integer.
231 """
Don Garrettbf90cdf2017-05-19 15:54:02 -0700232 logging.info('Bootstrap cbuildbot in: %s', buildroot)
Don Garrettbf90cdf2017-05-19 15:54:02 -0700233
Don Garrettd1d90dd2017-06-13 17:35:52 -0700234 # Fixup buildroot parameter.
235 argv = argv[:]
236 for i in xrange(len(argv)):
237 if argv[i] in ('-r', '--buildroot'):
238 argv[i+1] = buildroot
Don Garrett597ddff2017-02-17 18:29:37 -0800239
Don Garrettd1d90dd2017-06-13 17:35:52 -0700240 # This filters out command line arguments not supported by older versions
241 # of cbuildbot.
242 parser = cbuildbot.CreateParser()
Mike Frysinger80bba8a2017-08-18 15:28:36 -0400243 options = cbuildbot.ParseCommandLine(parser, argv)
Don Garrettd1d90dd2017-06-13 17:35:52 -0700244 cbuildbot_path = os.path.join(buildroot, 'chromite', 'bin', 'cbuildbot')
Don Garrett597ddff2017-02-17 18:29:37 -0800245 cmd = sync_stages.BootstrapStage.FilterArgsForTargetCbuildbot(
Don Garrettbf90cdf2017-05-19 15:54:02 -0700246 buildroot, cbuildbot_path, options)
Don Garrett597ddff2017-02-17 18:29:37 -0800247
Don Garrettd1d90dd2017-06-13 17:35:52 -0700248 # Actually run cbuildbot with the fixed up command line options.
249 result = cros_build_lib.RunCommand(cmd, error_code_ok=True, cwd=buildroot)
Don Garrettacbb2392017-05-11 18:27:41 -0700250 return result.returncode
Don Garrettc4114cc2016-11-01 20:04:06 -0700251
Don Garrett60967922017-04-12 18:51:44 -0700252
Don Garrettf15d65b2017-04-12 12:39:55 -0700253def ConfigureGlobalEnvironment():
254 """Setup process wide environmental changes."""
Don Garrettf15d65b2017-04-12 12:39:55 -0700255 # Set umask to 022 so files created by buildbot are readable.
256 os.umask(0o22)
257
Don Garrett86fec482017-05-17 18:13:33 -0700258 # These variables can interfere with LANG / locale behavior.
259 unwanted_local_vars = [
260 'LC_ALL', 'LC_CTYPE', 'LC_COLLATE', 'LC_TIME', 'LC_NUMERIC',
261 'LC_MONETARY', 'LC_MESSAGES', 'LC_PAPER', 'LC_NAME', 'LC_ADDRESS',
262 'LC_TELEPHONE', 'LC_MEASUREMENT', 'LC_IDENTIFICATION',
263 ]
264 for v in unwanted_local_vars:
265 os.environ.pop(v, None)
266
267 # This variable is required for repo sync's to work in all cases.
268 os.environ['LANG'] = 'en_US.UTF-8'
269
Don Garrett90f5e7d2017-09-08 17:42:46 -0700270 # TODO(dgarrett): Super ugly, super temporary hack to fix release builds.
Don Garrettf25bc862017-09-12 12:38:27 -0700271 logging.warn('FORCING CRCMOD INSTALL to workaround crbug.com/763438.')
Don Garrett90f5e7d2017-09-08 17:42:46 -0700272 cmd = ['pip', 'install', '--ignore-installed', 'crcmod']
Don Garrettf25bc862017-09-12 12:38:27 -0700273 result = cros_build_lib.SudoRunCommand(
274 cmd, error_code_ok=True, mute_output=True)
275 logging.warn('CRCMOD INSTALL RETURNED: %d', result.returncode)
Don Garrett90f5e7d2017-09-08 17:42:46 -0700276
Don Garrettc4114cc2016-11-01 20:04:06 -0700277
Don Garrettacbb2392017-05-11 18:27:41 -0700278def _main(argv):
Don Garrettc4114cc2016-11-01 20:04:06 -0700279 """main method of script.
280
281 Args:
282 argv: All command line arguments to pass as list of strings.
283
284 Returns:
285 Return code of cbuildbot as an integer.
286 """
Don Garrettd1d90dd2017-06-13 17:35:52 -0700287 options = PreParseArguments(argv)
288
289 branchname = options.branch or 'master'
290 root = options.buildroot
291 buildroot = os.path.join(root, 'repository')
292 build_config = options.build_targets[0]
293
294 metrics_fields = {
295 'branch_name': branchname,
296 'build_config': build_config,
297 'tryjob': options.remote_trybot,
298 }
Don Garrettf15d65b2017-04-12 12:39:55 -0700299
Don Garrettacbb2392017-05-11 18:27:41 -0700300 # Does the entire build pass or fail.
Don Garrett45e77412017-06-14 16:57:55 -0700301 with metrics.Presence(METRIC_ACTIVE, metrics_fields), \
302 metrics.SuccessCounter(METRIC_COMPLETED, metrics_fields) as s_fields:
Don Garrettc4114cc2016-11-01 20:04:06 -0700303
Don Garrettacbb2392017-05-11 18:27:41 -0700304 # Preliminary set, mostly command line parsing.
Don Garrettd1d90dd2017-06-13 17:35:52 -0700305 with metrics.SuccessCounter(METRIC_INVOKED, metrics_fields):
Don Garrettacbb2392017-05-11 18:27:41 -0700306 logging.EnableBuildbotMarkers()
307 ConfigureGlobalEnvironment()
Don Garrett86881cb2017-02-15 15:41:55 -0800308
Don Garrettacbb2392017-05-11 18:27:41 -0700309 # Prepare the buildroot with source for the build.
310 with metrics.SuccessCounter(METRIC_PREP, metrics_fields):
311 site_config = config_lib.GetConfig()
312 manifest_url = site_config.params['MANIFEST_INT_URL']
313 repo = repository.RepoRepository(manifest_url, buildroot,
314 branch=branchname,
Don Garrettd1d90dd2017-06-13 17:35:52 -0700315 git_cache_dir=options.git_cache_dir)
Don Garrett86881cb2017-02-15 15:41:55 -0800316
Don Garrettacbb2392017-05-11 18:27:41 -0700317 # Clean up the buildroot to a safe state.
318 with metrics.SecondsTimer(METRIC_CLEAN, fields=metrics_fields):
Don Garrettbf90cdf2017-05-19 15:54:02 -0700319 CleanBuildRoot(root, repo, metrics_fields)
Don Garrettacbb2392017-05-11 18:27:41 -0700320
321 # Get a checkout close enough the branched cbuildbot can handle it.
322 with metrics.SecondsTimer(METRIC_INITIAL, fields=metrics_fields):
323 InitialCheckout(repo)
324
325 # Run cbuildbot inside the full ChromeOS checkout, on the specified branch.
326 with metrics.SecondsTimer(METRIC_CBUILDBOT, fields=metrics_fields):
Don Garrettd1d90dd2017-06-13 17:35:52 -0700327 result = RunCbuildbot(buildroot, argv)
328 s_fields['success'] = (result == 0)
Don Garrettacbb2392017-05-11 18:27:41 -0700329 return result
330
331
332def main(argv):
333 # Enable Monarch metrics gathering.
334 with ts_mon_config.SetupTsMonGlobalState('cbuildbot_launch', indirect=True):
335 return _main(argv)