blob: cb132c409ce71e56a22a660865286307e06457ea [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 Garrett90c9da22017-09-12 16:45:40 -0700253def _InstallSystemCrcmodIfNeeded():
254 """Install Crcmod binary extension if needed.
255
256 If the build host has python-crcmod installed without the binary extension,
257 that breaks some versions of gsutil. In that case, this function installs
258 a new copy of the module with the extension.
259
260 Newer branches workaround this in gs.py, and hopefully future versions of
261 gsutil will work around this in gsutil, however, older branches will always
262 have this problem.
263
264 So... this method will always be required unless/until we chose to solve this
265 through configuration management of hosts.
266
267 http://crbug.com/763438 covers this in detail.
268 """
269 try:
270 import crcmod
271 # If crcmod exists on the system, but doesn't have the binary extension,
272 # gsutil behaves badly. Override the system module with one that contains
273 # the binary extension. This depends on /usr/local/lib overridding /usr/lib.
274 if not (getattr(crcmod, 'crcmod', None) and
275 getattr(crcmod.crcmod, '_usingExtension', None)):
276 logging.warn('FORCING CRCMOD INSTALL to workaround crbug.com/763438.')
277 cmd = ['pip', 'install', '--ignore-installed', 'crcmod']
278 result = cros_build_lib.SudoRunCommand(
279 cmd, error_code_ok=True, mute_output=True)
280 logging.warn('CRCMOD INSTALL RETURNED :%d', result.returncode)
281 except ImportError:
282 # If crcmod doesn't exist on the system, gs.py will properly use its
283 # version in the cache.
284 pass
285
286
Don Garrettf15d65b2017-04-12 12:39:55 -0700287def ConfigureGlobalEnvironment():
288 """Setup process wide environmental changes."""
Don Garrettf15d65b2017-04-12 12:39:55 -0700289 # Set umask to 022 so files created by buildbot are readable.
290 os.umask(0o22)
291
Don Garrett86fec482017-05-17 18:13:33 -0700292 # These variables can interfere with LANG / locale behavior.
293 unwanted_local_vars = [
294 'LC_ALL', 'LC_CTYPE', 'LC_COLLATE', 'LC_TIME', 'LC_NUMERIC',
295 'LC_MONETARY', 'LC_MESSAGES', 'LC_PAPER', 'LC_NAME', 'LC_ADDRESS',
296 'LC_TELEPHONE', 'LC_MEASUREMENT', 'LC_IDENTIFICATION',
297 ]
298 for v in unwanted_local_vars:
299 os.environ.pop(v, None)
300
301 # This variable is required for repo sync's to work in all cases.
302 os.environ['LANG'] = 'en_US.UTF-8'
303
Don Garrett90c9da22017-09-12 16:45:40 -0700304 _InstallSystemCrcmodIfNeeded()
Don Garrett90f5e7d2017-09-08 17:42:46 -0700305
Don Garrettc4114cc2016-11-01 20:04:06 -0700306
Don Garrettacbb2392017-05-11 18:27:41 -0700307def _main(argv):
Don Garrettc4114cc2016-11-01 20:04:06 -0700308 """main method of script.
309
310 Args:
311 argv: All command line arguments to pass as list of strings.
312
313 Returns:
314 Return code of cbuildbot as an integer.
315 """
Don Garrettd1d90dd2017-06-13 17:35:52 -0700316 options = PreParseArguments(argv)
317
318 branchname = options.branch or 'master'
319 root = options.buildroot
320 buildroot = os.path.join(root, 'repository')
321 build_config = options.build_targets[0]
322
323 metrics_fields = {
324 'branch_name': branchname,
325 'build_config': build_config,
326 'tryjob': options.remote_trybot,
327 }
Don Garrettf15d65b2017-04-12 12:39:55 -0700328
Don Garrettacbb2392017-05-11 18:27:41 -0700329 # Does the entire build pass or fail.
Don Garrett45e77412017-06-14 16:57:55 -0700330 with metrics.Presence(METRIC_ACTIVE, metrics_fields), \
331 metrics.SuccessCounter(METRIC_COMPLETED, metrics_fields) as s_fields:
Don Garrettc4114cc2016-11-01 20:04:06 -0700332
Don Garrettacbb2392017-05-11 18:27:41 -0700333 # Preliminary set, mostly command line parsing.
Don Garrettd1d90dd2017-06-13 17:35:52 -0700334 with metrics.SuccessCounter(METRIC_INVOKED, metrics_fields):
Don Garrettacbb2392017-05-11 18:27:41 -0700335 logging.EnableBuildbotMarkers()
336 ConfigureGlobalEnvironment()
Don Garrett86881cb2017-02-15 15:41:55 -0800337
Don Garrettacbb2392017-05-11 18:27:41 -0700338 # Prepare the buildroot with source for the build.
339 with metrics.SuccessCounter(METRIC_PREP, metrics_fields):
340 site_config = config_lib.GetConfig()
341 manifest_url = site_config.params['MANIFEST_INT_URL']
342 repo = repository.RepoRepository(manifest_url, buildroot,
343 branch=branchname,
Don Garrettd1d90dd2017-06-13 17:35:52 -0700344 git_cache_dir=options.git_cache_dir)
Don Garrett86881cb2017-02-15 15:41:55 -0800345
Don Garrettacbb2392017-05-11 18:27:41 -0700346 # Clean up the buildroot to a safe state.
347 with metrics.SecondsTimer(METRIC_CLEAN, fields=metrics_fields):
Don Garrettbf90cdf2017-05-19 15:54:02 -0700348 CleanBuildRoot(root, repo, metrics_fields)
Don Garrettacbb2392017-05-11 18:27:41 -0700349
350 # Get a checkout close enough the branched cbuildbot can handle it.
351 with metrics.SecondsTimer(METRIC_INITIAL, fields=metrics_fields):
352 InitialCheckout(repo)
353
354 # Run cbuildbot inside the full ChromeOS checkout, on the specified branch.
355 with metrics.SecondsTimer(METRIC_CBUILDBOT, fields=metrics_fields):
Don Garrettd1d90dd2017-06-13 17:35:52 -0700356 result = RunCbuildbot(buildroot, argv)
357 s_fields['success'] = (result == 0)
Don Garrettacbb2392017-05-11 18:27:41 -0700358 return result
359
360
361def main(argv):
362 # Enable Monarch metrics gathering.
363 with ts_mon_config.SetupTsMonGlobalState('cbuildbot_launch', indirect=True):
364 return _main(argv)