blob: 5a0db36bb3ea77d9b3527f98021d149a3c2ac860 [file] [log] [blame]
Brian Harring3fec5a82012-03-01 05:57:03 -08001#!/usr/bin/python
2
3# Copyright (c) 2011-2012 The Chromium OS Authors. All rights reserved.
4# Use of this source code is governed by a BSD-style license that can be
5# found in the LICENSE file.
6
7"""Main builder code for Chromium OS.
8
9Used by Chromium OS buildbot configuration for all Chromium OS builds including
10full and pre-flight-queue builds.
11"""
12
13import distutils.version
14import glob
Chris Sosa4f6ffaf2012-05-01 17:05:44 -070015import logging
David James58e0c092012-03-04 20:31:12 -080016import multiprocessing
Brian Harring3fec5a82012-03-01 05:57:03 -080017import optparse
18import os
19import pprint
20import sys
Ryan Cui54da0702012-04-19 18:38:08 -070021import time
Brian Harring3fec5a82012-03-01 05:57:03 -080022
23from chromite.buildbot import builderstage as bs
24from chromite.buildbot import cbuildbot_background as background
25from chromite.buildbot import cbuildbot_config
26from chromite.buildbot import cbuildbot_stages as stages
27from chromite.buildbot import cbuildbot_results as results_lib
Brian Harring3fec5a82012-03-01 05:57:03 -080028from chromite.buildbot import constants
29from chromite.buildbot import gerrit_helper
30from chromite.buildbot import patch as cros_patch
31from chromite.buildbot import remote_try
32from chromite.buildbot import repository
33from chromite.buildbot import tee
Ryan Cui16d9e1f2012-05-11 10:50:18 -070034from chromite.buildbot import trybot_patch_pool
Brian Harring3fec5a82012-03-01 05:57:03 -080035
Brian Harringc92a7012012-02-29 10:11:34 -080036from chromite.lib import cgroups
Brian Harringa184efa2012-03-04 11:51:25 -080037from chromite.lib import cleanup
Brian Harring3fec5a82012-03-01 05:57:03 -080038from chromite.lib import cros_build_lib as cros_lib
Brian Harringaf019fb2012-05-10 15:06:13 -070039from chromite.lib import osutils
Brian Harring3fec5a82012-03-01 05:57:03 -080040from chromite.lib import sudo
41
Ryan Cuiadd49122012-03-21 22:19:58 -070042
Brian Harring3fec5a82012-03-01 05:57:03 -080043cros_lib.STRICT_SUDO = True
44
45_DEFAULT_LOG_DIR = 'cbuildbot_logs'
46_BUILDBOT_LOG_FILE = 'cbuildbot.log'
47_DEFAULT_EXT_BUILDROOT = 'trybot'
48_DEFAULT_INT_BUILDROOT = 'trybot-internal'
Brian Harring3fec5a82012-03-01 05:57:03 -080049_DISTRIBUTED_TYPES = [constants.COMMIT_QUEUE_TYPE, constants.PFQ_TYPE,
50 constants.CANARY_TYPE, constants.CHROME_PFQ_TYPE,
51 constants.PALADIN_TYPE]
Brian Harring351ce442012-03-09 16:38:14 -080052_BUILDBOT_REQUIRED_BINARIES = ('pbzip2',)
Brian Harring3fec5a82012-03-01 05:57:03 -080053
Brian Harring37e559b2012-05-22 20:47:32 -070054# Used by --resume and --bootstrap to decipher which options they
55# can pass to the target cbuildbot (since it may not have that
56# option).
57# Format is Major:Minor. Minor is used for tracking new options added
58# that aren't critical to the older version if it's not ran.
59# Major is used for tracking heavy API breakage- for example, no longer
60# supporting the --resume option.
61_REEXEC_API_MAJOR = 0
62_REEXEC_API_MINOR = 1
63_REEXEC_API_VERSION = '%i.%i' % (_REEXEC_API_MAJOR, _REEXEC_API_MINOR)
64
Brian Harring3fec5a82012-03-01 05:57:03 -080065
Ryan Cui4f6cf7e2012-04-18 16:12:27 -070066def _PrintValidConfigs(display_all=False):
Brian Harring3fec5a82012-03-01 05:57:03 -080067 """Print a list of valid buildbot configs.
68
69 Arguments:
Ryan Cui4f6cf7e2012-04-18 16:12:27 -070070 display_all: Print all configs. Otherwise, prints only configs with
71 trybot_list=True.
Brian Harring3fec5a82012-03-01 05:57:03 -080072 """
Ryan Cui4f6cf7e2012-04-18 16:12:27 -070073 def _GetSortKey(config_name):
74 config_dict = cbuildbot_config.config[config_name]
75 return (not config_dict['trybot_list'], config_dict['description'],
76 config_name)
77
Brian Harring3fec5a82012-03-01 05:57:03 -080078 COLUMN_WIDTH = 45
79 print 'config'.ljust(COLUMN_WIDTH), 'description'
80 print '------'.ljust(COLUMN_WIDTH), '-----------'
81 config_names = cbuildbot_config.config.keys()
Ryan Cui4f6cf7e2012-04-18 16:12:27 -070082 config_names.sort(key=_GetSortKey)
Brian Harring3fec5a82012-03-01 05:57:03 -080083 for name in config_names:
Ryan Cui4f6cf7e2012-04-18 16:12:27 -070084 if display_all or cbuildbot_config.config[name]['trybot_list']:
85 desc = cbuildbot_config.config[name].get('description')
86 desc = desc if desc else ''
Brian Harring3fec5a82012-03-01 05:57:03 -080087 print name.ljust(COLUMN_WIDTH), desc
88
89
90def _GetConfig(config_name):
91 """Gets the configuration for the build"""
92 if not cbuildbot_config.config.has_key(config_name):
93 print 'Non-existent configuration %s specified.' % config_name
94 print 'Please specify one of:'
95 _PrintValidConfigs()
96 sys.exit(1)
97
98 result = cbuildbot_config.config[config_name]
99
100 return result
101
102
103def _GetChromiteTrackingBranch():
David James66009462012-03-25 10:08:38 -0700104 """Returns the remote branch associated with chromite."""
Brian Harring3fec5a82012-03-01 05:57:03 -0800105 cwd = os.path.dirname(os.path.realpath(__file__))
Brian Harring609dc4e2012-05-07 02:17:44 -0700106 result = cros_lib.GetTrackingBranch(cwd, for_checkout=False, fallback=False)
107 if result is not None:
108 remote, branch = result
109 if branch.startswith("refs/heads/"):
110 # Normal scenario.
111 return cros_lib.StripLeadingRefsHeads(branch)
112 # Reaching here means it was refs/remotes/m/blah, or just plain invalid,
113 # or that we're on a detached head in a repo not managed by chromite.
114
115 # Manually try the manifest next.
116 try:
117 manifest = cros_lib.ManifestCheckout.Cached(cwd)
118 # Ensure the manifest knows of this checkout.
119 if manifest.FindProjectFromPath(cwd) is not None:
120 return manifest.manifest_branch
121 except EnvironmentError, e:
122 if e.errno != errno.ENOENT:
123 raise
124 # Not a manifest checkout.
125 cros_lib.Warning(
126 "Chromite checkout at %s isn't controlled by repo, nor is it on a "
127 "branch (or if it is, the tracking configuration is missing or broken). "
128 "Falling back to assuming the chromite checkout is derived from "
129 "'master'; this *may* result in breakage." % cwd)
130 return 'master'
Brian Harring3fec5a82012-03-01 05:57:03 -0800131
132
Ryan Cuie1e4e662012-05-21 16:39:46 -0700133def AcquirePoolFromOptions(options):
Ryan Cui16d9e1f2012-05-11 10:50:18 -0700134 """Generate patch objects from passed in options.
Brian Harring3fec5a82012-03-01 05:57:03 -0800135
136 Args:
Ryan Cui16d9e1f2012-05-11 10:50:18 -0700137 options: The options object generated by optparse.
Brian Harring3fec5a82012-03-01 05:57:03 -0800138
Ryan Cuif7f24692012-05-18 16:35:33 -0700139 Returns:
140 trybot_patch_pool.TrybotPatchPool object.
141
Ryan Cui16d9e1f2012-05-11 10:50:18 -0700142 Raises:
143 gerrit_helper.GerritException, cros_patch.PatchException
Brian Harring3fec5a82012-03-01 05:57:03 -0800144 """
Ryan Cui16d9e1f2012-05-11 10:50:18 -0700145 gerrit_patches = []
146 local_patches = []
147 remote_patches = []
Brian Harring3fec5a82012-03-01 05:57:03 -0800148
Ryan Cui16d9e1f2012-05-11 10:50:18 -0700149 if options.gerrit_patches:
150 gerrit_patches = gerrit_helper.GetGerritPatchInfo(
151 options.gerrit_patches)
152 for patch in gerrit_patches:
153 if patch.IsAlreadyMerged():
154 cros_lib.Warning('Patch %s has already been merged.' % str(patch))
Brian Harring3fec5a82012-03-01 05:57:03 -0800155
Ryan Cui16d9e1f2012-05-11 10:50:18 -0700156 if options.local_patches:
Brian Harring609dc4e2012-05-07 02:17:44 -0700157 manifest = cros_lib.ManifestCheckout.Cached(options.sourceroot)
158 local_patches = cros_patch.PrepareLocalPatches(manifest,
159 options.local_patches)
Brian Harring3fec5a82012-03-01 05:57:03 -0800160
Ryan Cui16d9e1f2012-05-11 10:50:18 -0700161 if options.remote_patches:
162 remote_patches = cros_patch.PrepareRemotePatches(
163 options.remote_patches)
Brian Harring3fec5a82012-03-01 05:57:03 -0800164
Ryan Cui16d9e1f2012-05-11 10:50:18 -0700165 return trybot_patch_pool.TrybotPatchPool(gerrit_patches, local_patches,
166 remote_patches)
Brian Harring3fec5a82012-03-01 05:57:03 -0800167
168
Brian Harring3fec5a82012-03-01 05:57:03 -0800169class Builder(object):
170 """Parent class for all builder types.
171
172 This class functions as a parent class for various build types. It's intended
173 use is builder_instance.Run().
174
175 Vars:
Brian Harring3fec5a82012-03-01 05:57:03 -0800176 build_config: The configuration dictionary from cbuildbot_config.
177 options: The options provided from optparse in main().
Brian Harring3fec5a82012-03-01 05:57:03 -0800178 archive_url: Where our artifacts for this builder will be archived.
179 tracking_branch: The tracking branch for this build.
180 release_tag: The associated "chrome os version" of this build.
Brian Harring3fec5a82012-03-01 05:57:03 -0800181 """
182
Ryan Cuie1e4e662012-05-21 16:39:46 -0700183 def __init__(self, options, build_config):
Brian Harring3fec5a82012-03-01 05:57:03 -0800184 """Initializes instance variables. Must be called by all subclasses."""
Brian Harring3fec5a82012-03-01 05:57:03 -0800185 self.build_config = build_config
186 self.options = options
187
188 # TODO, Remove here and in config after bug chromium-os:14649 is fixed.
189 if self.build_config['chromeos_official']:
190 os.environ['CHROMEOS_OFFICIAL'] = '1'
191
David James58e0c092012-03-04 20:31:12 -0800192 self.archive_stages = {}
Brian Harring3fec5a82012-03-01 05:57:03 -0800193 self.archive_urls = {}
194 self.release_tag = None
Ryan Cui16d9e1f2012-05-11 10:50:18 -0700195 self.patch_pool = trybot_patch_pool.GetEmptyPool()
Brian Harring3fec5a82012-03-01 05:57:03 -0800196
Ryan Cuie1e4e662012-05-21 16:39:46 -0700197 bs.BuilderStage.SetManifestBranch(self.options.branch)
Ryan Cuif7f24692012-05-18 16:35:33 -0700198
Brian Harring3fec5a82012-03-01 05:57:03 -0800199 def Initialize(self):
200 """Runs through the initialization steps of an actual build."""
Ryan Cuif7f24692012-05-18 16:35:33 -0700201 if self.options.resume:
202 results_lib.LoadCheckpoint(self.options.buildroot)
Brian Harring3fec5a82012-03-01 05:57:03 -0800203
Brian Harring3fec5a82012-03-01 05:57:03 -0800204 self._RunStage(stages.CleanUpStage)
205
206 def _GetStageInstance(self, stage, *args, **kwargs):
207 """Helper function to get an instance given the args.
208
David James944a48e2012-03-07 12:19:03 -0800209 Useful as almost all stages just take in options and build_config.
Brian Harring3fec5a82012-03-01 05:57:03 -0800210 """
David James944a48e2012-03-07 12:19:03 -0800211 config = kwargs.pop('config', self.build_config)
212 return stage(self.options, config, *args, **kwargs)
Brian Harring3fec5a82012-03-01 05:57:03 -0800213
214 def _SetReleaseTag(self):
215 """Sets the release tag from the manifest_manager.
216
217 Must be run after sync stage as syncing enables us to have a release tag.
218 """
219 # Extract version we have decided to build into self.release_tag.
220 manifest_manager = stages.ManifestVersionedSyncStage.manifest_manager
221 if manifest_manager:
222 self.release_tag = manifest_manager.current_version
223
224 def _RunStage(self, stage, *args, **kwargs):
225 """Wrapper to run a stage."""
226 stage_instance = self._GetStageInstance(stage, *args, **kwargs)
227 return stage_instance.Run()
228
229 def GetSyncInstance(self):
230 """Returns an instance of a SyncStage that should be run.
231
232 Subclasses must override this method.
233 """
234 raise NotImplementedError()
235
236 def RunStages(self):
237 """Subclasses must override this method. Runs the appropriate code."""
238 raise NotImplementedError()
239
Brian Harring3fec5a82012-03-01 05:57:03 -0800240 def _ShouldReExecuteInBuildRoot(self):
241 """Returns True if this build should be re-executed in the buildroot."""
242 abs_buildroot = os.path.abspath(self.options.buildroot)
243 return not os.path.abspath(__file__).startswith(abs_buildroot)
244
245 def _ReExecuteInBuildroot(self, sync_instance):
246 """Reexecutes self in buildroot and returns True if build succeeds.
247
248 This allows the buildbot code to test itself when changes are patched for
249 buildbot-related code. This is a no-op if the buildroot == buildroot
250 of the running chromite checkout.
251
252 Args:
253 sync_instance: Instance of the sync stage that was run to sync.
254
255 Returns:
256 True if the Build succeeded.
257 """
Brian Harring3fec5a82012-03-01 05:57:03 -0800258 if not self.options.resume:
Ryan Cuif7f24692012-05-18 16:35:33 -0700259 results_lib.WriteCheckpoint(self.options.buildroot)
Brian Harring3fec5a82012-03-01 05:57:03 -0800260
Brian Harring37e559b2012-05-22 20:47:32 -0700261 # Get the re-exec API version of the target chromite; if it's incompatible
262 # with us, bail now.
263 api = cros_lib.RunCommandCaptureOutput(
264 [constants.PATH_TO_CBUILDBOT] + ['--reexec-api-version'],
265 cwd=self.options.buildroot, error_code_ok=True)
266 # If the command failed, then we're targeting a cbuildbot that lacks the
267 # option; assume 0:0 (ie, initial state).
268 major, minor = 0, 0
269 if api.returncode == 0:
270 major, minor = map(int, api.output.strip().split('.', 1))
271
272 if major != _REEXEC_API_MAJOR:
273 cros_lib.Die(
274 'The targeted version of chromite in buildroot %s requires '
275 'api version %i, but we are api version %i. We cannot proceed.'
276 % (self.options.buildroot, major, _REEXEC_API_MAJOR))
277
Brian Harring3fec5a82012-03-01 05:57:03 -0800278 # Re-write paths to use absolute paths.
279 # Suppress any timeout options given from the commandline in the
280 # invoked cbuildbot; our timeout will enforce it instead.
Brian Harringf11bf682012-05-14 15:53:43 -0700281 args_to_append = ['--resume', '--timeout', '0', '--notee', '--nocgroups',
282 '--buildroot', os.path.abspath(self.options.buildroot)]
Brian Harring3fec5a82012-03-01 05:57:03 -0800283
284 if self.options.chrome_root:
285 args_to_append += ['--chrome_root',
286 os.path.abspath(self.options.chrome_root)]
287
288 if stages.ManifestVersionedSyncStage.manifest_manager:
289 ver = stages.ManifestVersionedSyncStage.manifest_manager.current_version
290 args_to_append += ['--version', ver]
291
292 if isinstance(sync_instance, stages.CommitQueueSyncStage):
293 vp_file = sync_instance.SaveValidationPool()
294 args_to_append += ['--validation_pool', vp_file]
295
296 # Re-run the command in the buildroot.
297 # Finally, be generous and give the invoked cbuildbot 30s to shutdown
298 # when something occurs. It should exit quicker, but the sigterm may
299 # hit while the system is particularly busy.
300 return_obj = cros_lib.RunCommand(
Ryan Cuif7f24692012-05-18 16:35:33 -0700301 [constants.PATH_TO_CBUILDBOT] + sys.argv[1:] + args_to_append,
Brian Harring3fec5a82012-03-01 05:57:03 -0800302 cwd=self.options.buildroot, error_code_ok=True, kill_timeout=30)
303 return return_obj.returncode == 0
304
Ryan Cuif7f24692012-05-18 16:35:33 -0700305 def _InitializeTrybotPatchPool(self):
306 """Generate patch pool from patches specified on the command line.
307
308 Do this only if we need to patch changes later on.
309 """
310 changes_stage = stages.PatchChangesStage.StageNamePrefix()
311 check_func = results_lib.Results.PreviouslyCompletedRecord
312 if not check_func(changes_stage) or self.options.bootstrap:
Ryan Cuie1e4e662012-05-21 16:39:46 -0700313 self.patch_pool = AcquirePoolFromOptions(self.options)
Ryan Cuif7f24692012-05-18 16:35:33 -0700314
315 def _GetBootstrapStage(self):
316 """Constructs and returns the BootStrapStage object.
317
318 We return None when there are no chromite patches to test, and
319 --test-bootstrap wasn't passed in.
320 """
321 stage = None
322 chromite_pool = self.patch_pool.Filter(project=constants.CHROMITE_PROJECT)
Ryan Cuie1e4e662012-05-21 16:39:46 -0700323 chromite_branch = _GetChromiteTrackingBranch()
324 if (chromite_pool or self.options.test_bootstrap
325 or chromite_branch != self.options.branch):
Ryan Cuif7f24692012-05-18 16:35:33 -0700326 stage = stages.BootstrapStage(self.options, self.build_config,
327 chromite_pool)
328 return stage
329
Brian Harring3fec5a82012-03-01 05:57:03 -0800330 def Run(self):
Ryan Cuif7f24692012-05-18 16:35:33 -0700331 """Main runner for this builder class. Runs build and prints summary.
332
333 Returns:
334 Whether the build succeeded.
335 """
336 self._InitializeTrybotPatchPool()
337
338 if self.options.bootstrap:
339 bootstrap_stage = self._GetBootstrapStage()
340 if bootstrap_stage:
341 # BootstrapStage blocks on re-execution of cbuildbot.
342 bootstrap_stage.Run()
343 return bootstrap_stage.returncode == 0
344
Brian Harring3fec5a82012-03-01 05:57:03 -0800345 print_report = True
David James3d4d3502012-04-09 15:12:06 -0700346 exception_thrown = False
Brian Harring3fec5a82012-03-01 05:57:03 -0800347 success = True
348 try:
349 self.Initialize()
350 sync_instance = self.GetSyncInstance()
351 sync_instance.Run()
352 self._SetReleaseTag()
353
Ryan Cui16d9e1f2012-05-11 10:50:18 -0700354 if self.patch_pool:
355 self._RunStage(stages.PatchChangesStage, self.patch_pool)
Brian Harring3fec5a82012-03-01 05:57:03 -0800356
357 if self._ShouldReExecuteInBuildRoot():
358 print_report = False
359 success = self._ReExecuteInBuildroot(sync_instance)
360 else:
361 self.RunStages()
David James3d4d3502012-04-09 15:12:06 -0700362 except Exception:
363 exception_thrown = True
364 raise
Brian Harring3fec5a82012-03-01 05:57:03 -0800365 finally:
366 if print_report:
Ryan Cuif7f24692012-05-18 16:35:33 -0700367 results_lib.WriteCheckpoint(self.options.buildroot)
Brian Harring3fec5a82012-03-01 05:57:03 -0800368 print '\n\n\n@@@BUILD_STEP Report@@@\n'
369 results_lib.Results.Report(sys.stdout, self.archive_urls,
370 self.release_tag)
371 success = results_lib.Results.BuildSucceededSoFar()
David James3d4d3502012-04-09 15:12:06 -0700372 if exception_thrown and success:
373 success = False
374 print >> sys.stderr, """
375@@@STEP_FAILURE@@@
376Exception thrown, but all stages marked successful. This is an internal error,
377because the stage that threw the exception should be marked as failing."""
Brian Harring3fec5a82012-03-01 05:57:03 -0800378
379 return success
380
381
382class SimpleBuilder(Builder):
383 """Builder that performs basic vetting operations."""
384
385 def GetSyncInstance(self):
386 """Sync to lkgm or TOT as necessary.
387
388 Returns: the instance of the sync stage that was run.
389 """
390 if self.options.lkgm or self.build_config['use_lkgm']:
391 sync_stage = self._GetStageInstance(stages.LKGMSyncStage)
392 else:
393 sync_stage = self._GetStageInstance(stages.SyncStage)
394
395 return sync_stage
396
David James58e0c092012-03-04 20:31:12 -0800397 def _RunBackgroundStagesForBoard(self, board):
398 """Run background board-specific stages for the specified board."""
David James58e0c092012-03-04 20:31:12 -0800399 archive_stage = self.archive_stages[board]
David James944a48e2012-03-07 12:19:03 -0800400 configs = self.build_config['board_specific_configs']
401 config = configs.get(board, self.build_config)
402 stage_list = [[stages.VMTestStage, board, archive_stage],
403 [stages.ChromeTestStage, board, archive_stage],
404 [stages.UnitTestStage, board],
Chris Sosa6a5dceb2012-05-14 13:48:56 -0700405 [stages.UploadPrebuiltsStage, board, archive_stage]]
Brian Harring3fec5a82012-03-01 05:57:03 -0800406
David James58e0c092012-03-04 20:31:12 -0800407 # We can not run hw tests without archiving the payloads.
408 if self.options.archive:
David James944a48e2012-03-07 12:19:03 -0800409 for suite in config['hw_tests']:
410 stage_list.append([stages.HWTestStage, board, archive_stage, suite])
Chris Sosab50dc932012-03-01 14:00:58 -0800411
David James944a48e2012-03-07 12:19:03 -0800412 steps = [self._GetStageInstance(*x, config=config).Run for x in stage_list]
413 background.RunParallelSteps(steps + [archive_stage.Run])
Brian Harring3fec5a82012-03-01 05:57:03 -0800414
415 def RunStages(self):
416 """Runs through build process."""
417 self._RunStage(stages.BuildBoardStage)
418
419 # TODO(sosa): Split these out into classes.
Brian Harring3fec5a82012-03-01 05:57:03 -0800420 if self.build_config['build_type'] == constants.CHROOT_BUILDER_TYPE:
421 self._RunStage(stages.SDKTestStage)
422 self._RunStage(stages.UploadPrebuiltsStage,
Chris Sosa6a5dceb2012-05-14 13:48:56 -0700423 constants.CHROOT_BUILDER_BOARD, None)
Brian Harring3fec5a82012-03-01 05:57:03 -0800424 elif self.build_config['build_type'] == constants.REFRESH_PACKAGES_TYPE:
425 self._RunStage(stages.RefreshPackageStatusStage)
426 else:
427 self._RunStage(stages.UprevStage)
Brian Harring3fec5a82012-03-01 05:57:03 -0800428
David James944a48e2012-03-07 12:19:03 -0800429 configs = self.build_config['board_specific_configs']
David James58e0c092012-03-04 20:31:12 -0800430 for board in self.build_config['boards']:
David James944a48e2012-03-07 12:19:03 -0800431 config = configs.get(board, self.build_config)
432 archive_stage = self._GetStageInstance(stages.ArchiveStage, board,
433 config=config)
David James58e0c092012-03-04 20:31:12 -0800434 self.archive_stages[board] = archive_stage
435
David James944a48e2012-03-07 12:19:03 -0800436 # Set up a process pool to run test/archive stages in the background.
437 # This process runs task(board) for each board added to the queue.
David James58e0c092012-03-04 20:31:12 -0800438 queue = multiprocessing.Queue()
439 task = self._RunBackgroundStagesForBoard
440 with background.BackgroundTaskRunner(queue, task):
David James944a48e2012-03-07 12:19:03 -0800441 for board in self.build_config['boards']:
David James58e0c092012-03-04 20:31:12 -0800442 # Run BuildTarget in the foreground.
David James944a48e2012-03-07 12:19:03 -0800443 archive_stage = self.archive_stages[board]
444 config = configs.get(board, self.build_config)
445 self._RunStage(stages.BuildTargetStage, board, archive_stage,
Chris Sosa1a87b3e2012-04-12 13:20:42 -0700446 self.release_tag, config=config)
David James58e0c092012-03-04 20:31:12 -0800447 self.archive_urls[board] = archive_stage.GetDownloadUrl()
448
David James944a48e2012-03-07 12:19:03 -0800449 # Kick off task(board) in the background.
David James58e0c092012-03-04 20:31:12 -0800450 queue.put([board])
451
Brian Harring3fec5a82012-03-01 05:57:03 -0800452
453class DistributedBuilder(SimpleBuilder):
454 """Build class that has special logic to handle distributed builds.
455
456 These builds sync using git/manifest logic in manifest_versions. In general
457 they use a non-distributed builder code for the bulk of the work.
458 """
Ryan Cuif7f24692012-05-18 16:35:33 -0700459 def __init__(self, *args, **kwargs):
Brian Harring3fec5a82012-03-01 05:57:03 -0800460 """Initializes a buildbot builder.
461
462 Extra variables:
463 completion_stage_class: Stage used to complete a build. Set in the Sync
464 stage.
465 """
Ryan Cuif7f24692012-05-18 16:35:33 -0700466 super(DistributedBuilder, self).__init__(*args, **kwargs)
Brian Harring3fec5a82012-03-01 05:57:03 -0800467 self.completion_stage_class = None
468
469 def GetSyncInstance(self):
470 """Syncs the tree using one of the distributed sync logic paths.
471
472 Returns: the instance of the sync stage that was run.
473 """
474 # Determine sync class to use. CQ overrides PFQ bits so should check it
475 # first.
476 if cbuildbot_config.IsCQType(self.build_config['build_type']):
477 sync_stage = self._GetStageInstance(stages.CommitQueueSyncStage)
478 self.completion_stage_class = stages.CommitQueueCompletionStage
479 elif cbuildbot_config.IsPFQType(self.build_config['build_type']):
480 sync_stage = self._GetStageInstance(stages.LKGMCandidateSyncStage)
481 self.completion_stage_class = stages.LKGMCandidateSyncCompletionStage
482 else:
483 sync_stage = self._GetStageInstance(stages.ManifestVersionedSyncStage)
484 self.completion_stage_class = stages.ManifestVersionedSyncCompletionStage
485
486 return sync_stage
487
488 def Publish(self, was_build_successful):
489 """Completes build by publishing any required information."""
490 completion_stage = self._GetStageInstance(self.completion_stage_class,
491 was_build_successful)
492 completion_stage.Run()
493 name = completion_stage.name
494 if not results_lib.Results.WasStageSuccessful(name):
495 should_publish_changes = False
496 else:
497 should_publish_changes = (self.build_config['master'] and
498 was_build_successful)
499
500 if should_publish_changes:
501 self._RunStage(stages.PublishUprevChangesStage)
502
503 def RunStages(self):
504 """Runs simple builder logic and publishes information to overlays."""
505 was_build_successful = False
506 try:
David Jamesf55709e2012-03-13 09:10:15 -0700507 super(DistributedBuilder, self).RunStages()
508 was_build_successful = results_lib.Results.BuildSucceededSoFar()
Brian Harring3fec5a82012-03-01 05:57:03 -0800509 except SystemExit as ex:
510 # If a stage calls sys.exit(0), it's exiting with success, so that means
511 # we should mark ourselves as successful.
512 if ex.code == 0:
513 was_build_successful = True
514 raise
515 finally:
516 self.Publish(was_build_successful)
517
Brian Harring3fec5a82012-03-01 05:57:03 -0800518
519def _ConfirmBuildRoot(buildroot):
520 """Confirm with user the inferred buildroot, and mark it as confirmed."""
521 warning = 'Using default directory %s as buildroot' % buildroot
522 response = cros_lib.YesNoPrompt(default=cros_lib.NO, warning=warning,
523 full=True)
524 if response == cros_lib.NO:
525 print('Please specify a buildroot with the --buildroot option.')
526 sys.exit(0)
527
528 if not os.path.exists(buildroot):
529 os.mkdir(buildroot)
530
531 repository.CreateTrybotMarker(buildroot)
532
533
Ryan Cuieaa9efd2012-04-25 17:56:45 -0700534def _ConfirmRemoteBuildbotRun():
535 """Confirm user wants to run with --buildbot --remote."""
536 warning = ('You are about to launch a PRODUCTION job! This is *NOT* a '
537 'trybot run! Are you sure?')
538 response = cros_lib.YesNoPrompt(default=cros_lib.NO, warning=warning,
539 full=True)
540
541 if response == cros_lib.NO:
542 print('Please specify --pass-through="--debug".')
543 sys.exit(0)
544
545
Ryan Cui5ba7e152012-05-10 14:36:52 -0700546def _DetermineDefaultBuildRoot(sourceroot, internal_build):
Brian Harring3fec5a82012-03-01 05:57:03 -0800547 """Default buildroot to be under the directory that contains current checkout.
548
549 Arguments:
550 internal_build: Whether the build is an internal build
Ryan Cui5ba7e152012-05-10 14:36:52 -0700551 sourceroot: Use specified sourceroot.
Brian Harring3fec5a82012-03-01 05:57:03 -0800552 """
Ryan Cui5ba7e152012-05-10 14:36:52 -0700553 if not repository.IsARepoRoot(sourceroot):
554 cros_lib.Die('Could not find root of local checkout at %s. Please specify '
555 'using the --sourceroot option.' % sourceroot)
Brian Harring3fec5a82012-03-01 05:57:03 -0800556
557 # Place trybot buildroot under the directory containing current checkout.
Ryan Cui5ba7e152012-05-10 14:36:52 -0700558 top_level = os.path.dirname(os.path.realpath(sourceroot))
Brian Harring3fec5a82012-03-01 05:57:03 -0800559 if internal_build:
560 buildroot = os.path.join(top_level, _DEFAULT_INT_BUILDROOT)
561 else:
562 buildroot = os.path.join(top_level, _DEFAULT_EXT_BUILDROOT)
563
564 return buildroot
565
566
567def _BackupPreviousLog(log_file, backup_limit=25):
568 """Rename previous log.
569
570 Args:
571 log_file: The absolute path to the previous log.
572 """
573 if os.path.exists(log_file):
574 old_logs = sorted(glob.glob(log_file + '.*'),
575 key=distutils.version.LooseVersion)
576
577 if len(old_logs) >= backup_limit:
578 os.remove(old_logs[0])
579
580 last = 0
581 if old_logs:
582 last = int(old_logs.pop().rpartition('.')[2])
583
584 os.rename(log_file, log_file + '.' + str(last + 1))
585
David James944a48e2012-03-07 12:19:03 -0800586def _RunBuildStagesWrapper(options, build_config):
Brian Harring3fec5a82012-03-01 05:57:03 -0800587 """Helper function that wraps RunBuildStages()."""
588 def IsDistributedBuilder():
589 """Determines whether the build_config should be a DistributedBuilder."""
590 if not options.buildbot:
591 return False
592 elif build_config['build_type'] in _DISTRIBUTED_TYPES:
593 chrome_rev = build_config['chrome_rev']
594 if options.chrome_rev: chrome_rev = options.chrome_rev
595 # We don't do distributed logic to TOT Chrome PFQ's, nor local
596 # chrome roots (e.g. chrome try bots)
597 if chrome_rev not in [constants.CHROME_REV_TOT,
598 constants.CHROME_REV_LOCAL,
599 constants.CHROME_REV_SPEC]:
600 return True
601
602 return False
603
Brian Harringd166aaf2012-05-14 18:31:53 -0700604 cros_lib.Info("cbuildbot executed with args %s"
605 % ' '.join(map(repr, sys.argv)))
Brian Harring3fec5a82012-03-01 05:57:03 -0800606
Ryan Cuif7f24692012-05-18 16:35:33 -0700607 target = DistributedBuilder if IsDistributedBuilder() else SimpleBuilder
Ryan Cuie1e4e662012-05-21 16:39:46 -0700608 buildbot = target(options, build_config)
Brian Harringd166aaf2012-05-14 18:31:53 -0700609 if not buildbot.Run():
610 sys.exit(1)
Brian Harring3fec5a82012-03-01 05:57:03 -0800611
612
613# Parser related functions
Ryan Cui5ba7e152012-05-10 14:36:52 -0700614def _CheckLocalPatches(sourceroot, local_patches):
Brian Harring3fec5a82012-03-01 05:57:03 -0800615 """Do an early quick check of the passed-in patches.
616
617 If the branch of a project is not specified we append the current branch the
618 project is on.
Ryan Cui5ba7e152012-05-10 14:36:52 -0700619
620 Args:
621 sourceroot: The checkout where patches are coming from.
Brian Harring3fec5a82012-03-01 05:57:03 -0800622 """
Ryan Cuicedd8a52012-03-22 02:28:35 -0700623 verified_patches = []
Brian Harring609dc4e2012-05-07 02:17:44 -0700624 manifest = cros_lib.ManifestCheckout.Cached(sourceroot)
Ryan Cuicedd8a52012-03-22 02:28:35 -0700625 for patch in local_patches:
Brian Harring3fec5a82012-03-01 05:57:03 -0800626 components = patch.split(':')
627 if len(components) > 2:
Brian Harring609dc4e2012-05-07 02:17:44 -0700628 cros_lib.Die('Specify local patches in project[:branch] format. Got %s'
629 % patch)
Brian Harring3fec5a82012-03-01 05:57:03 -0800630
631 # validate project
632 project = components[0]
Brian Harring3fec5a82012-03-01 05:57:03 -0800633
Brian Harring609dc4e2012-05-07 02:17:44 -0700634 try:
635 project_dir = manifest.GetProjectPath(project, True)
636 except KeyError:
637 cros_lib.Die('Project %s does not exist.' % project)
Brian Harring3fec5a82012-03-01 05:57:03 -0800638
639 # If no branch was specified, we use the project's current branch.
640 if len(components) == 1:
641 branch = cros_lib.GetCurrentBranch(project_dir)
642 if not branch:
Brian Harring609dc4e2012-05-07 02:17:44 -0700643 cros_lib.Die('Project %s is not on a branch!' % project)
Brian Harring3fec5a82012-03-01 05:57:03 -0800644 else:
645 branch = components[1]
646 if not cros_lib.DoesLocalBranchExist(project_dir, branch):
Brian Harring609dc4e2012-05-07 02:17:44 -0700647 cros_lib.Die('Project %s does not have branch %s' % (project, branch))
Brian Harring3fec5a82012-03-01 05:57:03 -0800648
Brian Harring609dc4e2012-05-07 02:17:44 -0700649 verified_patches.append('%s:%s' % (project, branch))
Brian Harring3fec5a82012-03-01 05:57:03 -0800650
Ryan Cuicedd8a52012-03-22 02:28:35 -0700651 return verified_patches
Brian Harring3fec5a82012-03-01 05:57:03 -0800652
653
Brian Harring3fec5a82012-03-01 05:57:03 -0800654def _CheckChromeVersionOption(_option, _opt_str, value, parser):
655 """Upgrade other options based on chrome_version being passed."""
656 value = value.strip()
657
658 if parser.values.chrome_rev is None and value:
659 parser.values.chrome_rev = constants.CHROME_REV_SPEC
660
661 parser.values.chrome_version = value
662
663
664def _CheckChromeRootOption(_option, _opt_str, value, parser):
665 """Validate and convert chrome_root to full-path form."""
Brian Harring3fec5a82012-03-01 05:57:03 -0800666 if parser.values.chrome_rev is None:
667 parser.values.chrome_rev = constants.CHROME_REV_LOCAL
668
Ryan Cui5ba7e152012-05-10 14:36:52 -0700669 parser.values.chrome_root = value
Brian Harring3fec5a82012-03-01 05:57:03 -0800670
671
672def _CheckChromeRevOption(_option, _opt_str, value, parser):
673 """Validate the chrome_rev option."""
674 value = value.strip()
675 if value not in constants.VALID_CHROME_REVISIONS:
676 raise optparse.OptionValueError('Invalid chrome rev specified')
677
678 parser.values.chrome_rev = value
679
680
Ryan Cui5ba7e152012-05-10 14:36:52 -0700681class CustomParser(optparse.OptionParser):
682 def add_remote_option(self, *args, **kwargs):
683 """For arguments that are passed-through to remote trybot."""
684 return optparse.OptionParser.add_option(self, *args,
685 remote_pass_through=True,
686 **kwargs)
687
688
689class CustomGroup(optparse.OptionGroup):
690 def add_remote_option(self, *args, **kwargs):
691 """For arguments that are passed-through to remote trybot."""
692 return optparse.OptionGroup.add_option(self, *args,
693 remote_pass_through=True,
694 **kwargs)
695
696
Ryan Cuif7f24692012-05-18 16:35:33 -0700697# pylint: disable=W0613
Ryan Cui5ba7e152012-05-10 14:36:52 -0700698def check_path(option, opt, value):
699 """Expand paths and make them absolute."""
700 expanded = osutils.ExpandPath(value)
701 if expanded == '/':
702 raise optparse.OptionValueError('Invalid path %s specified for %s'
703 % (expanded, opt))
704
705 return expanded
706
707
708class CustomOption(optparse.Option):
709 """Subclass Option class to implement pass-through and path evaluation."""
710 TYPES = optparse.Option.TYPES + ('path',)
711 TYPE_CHECKER = optparse.Option.TYPE_CHECKER.copy()
712 TYPE_CHECKER['path'] = check_path
713
Ryan Cui79319ab2012-05-21 12:59:18 -0700714 ACTIONS = optparse.Option.ACTIONS + ('extend',)
715 STORE_ACTIONS = optparse.Option.STORE_ACTIONS + ('extend',)
716 TYPED_ACTIONS = optparse.Option.TYPED_ACTIONS + ('extend',)
717 ALWAYS_TYPED_ACTIONS = optparse.Option.ALWAYS_TYPED_ACTIONS + ('extend',)
718
Ryan Cui5ba7e152012-05-10 14:36:52 -0700719 def __init__(self, *args, **kwargs):
720 # The remote_pass_through argument specifies whether we should directly
721 # pass the argument (with its value) onto the remote trybot.
722 self.pass_through = kwargs.pop('remote_pass_through', False)
723 optparse.Option.__init__(self, *args, **kwargs)
724
725 def take_action(self, action, dest, opt, value, values, parser):
Ryan Cui79319ab2012-05-21 12:59:18 -0700726 if action == 'extend':
727 lvalue = value.split(' ')
728 values.ensure_value(dest, []).extend(lvalue)
729 else:
730 optparse.Option.take_action(self, action, dest, opt, value, values,
731 parser)
732
Ryan Cui5ba7e152012-05-10 14:36:52 -0700733 if self.pass_through:
734 parser.values.pass_through_args.append(opt)
735 if self.nargs and self.nargs > 1:
736 # value is a tuple if nargs > 1
737 string_list = [str(val) for val in list(value)]
738 parser.values.pass_through_args.extend(string_list)
739 elif value:
740 parser.values.pass_through_args.append(str(value))
741
742
Brian Harring3fec5a82012-03-01 05:57:03 -0800743def _CreateParser():
Ryan Cui16d9e1f2012-05-11 10:50:18 -0700744 """Generate and return the parser with all the options."""
Brian Harring3fec5a82012-03-01 05:57:03 -0800745 # Parse options
746 usage = "usage: %prog [options] buildbot_config"
Ryan Cuieaa9efd2012-04-25 17:56:45 -0700747 parser = CustomParser(usage=usage, option_class=CustomOption)
Brian Harring3fec5a82012-03-01 05:57:03 -0800748
749 # Main options
Ryan Cuieaa9efd2012-04-25 17:56:45 -0700750 # The remote_pass_through parameter to add_option is implemented by the
751 # CustomOption class. See CustomOption for more information.
Brian Harring3fec5a82012-03-01 05:57:03 -0800752 parser.add_option('-a', '--all', action='store_true', dest='print_all',
753 default=False,
754 help=('List all of the buildbot configs available. Use '
755 'with the --list option'))
Ryan Cuie1e4e662012-05-21 16:39:46 -0700756 parser.add_remote_option('-b', '--branch',
757 help='The manifest branch to test. The branch to '
758 'check the buildroot out to.')
Ryan Cui5ba7e152012-05-10 14:36:52 -0700759 parser.add_option('-r', '--buildroot', dest='buildroot', type='path',
Ryan Cuieaa9efd2012-04-25 17:56:45 -0700760 help='Root directory where source is checked out to, and '
761 'where the build occurs. For external build configs, '
762 "defaults to 'trybot' directory at top level of your "
763 'repo-managed checkout.')
764 parser.add_remote_option('--chrome_rev', default=None, type='string',
765 action='callback', dest='chrome_rev',
766 callback=_CheckChromeRevOption,
767 help=('Revision of Chrome to use, of type [%s]'
768 % '|'.join(constants.VALID_CHROME_REVISIONS)))
Ryan Cui79319ab2012-05-21 12:59:18 -0700769 parser.add_remote_option('-g', '--gerrit-patches', action='extend',
Ryan Cuieaa9efd2012-04-25 17:56:45 -0700770 default=[], type='string',
771 metavar="'Id1 *int_Id2...IdN'",
772 help=("Space-separated list of short-form Gerrit "
773 "Change-Id's or change numbers to patch. "
774 "Please prepend '*' to internal Change-Id's"))
Brian Harring3fec5a82012-03-01 05:57:03 -0800775 parser.add_option('-l', '--list', action='store_true', dest='list',
776 default=False,
777 help=('List the suggested trybot configs to use. Use '
778 '--all to list all of the available configs.'))
Ryan Cui54da0702012-04-19 18:38:08 -0700779 parser.add_option('--local', default=False, action='store_true',
780 help=('Specifies that this tryjob should be run locally.'))
Ryan Cui79319ab2012-05-21 12:59:18 -0700781 parser.add_option('-p', '--local-patches', action='extend', default=[],
Brian Harring3fec5a82012-03-01 05:57:03 -0800782 metavar="'<project1>[:<branch1>]...<projectN>[:<branchN>]'",
783 help=('Space-separated list of project branches with '
784 'patches to apply. Projects are specified by name. '
785 'If no branch is specified the current branch of the '
786 'project will be used.'))
Ryan Cuieaa9efd2012-04-25 17:56:45 -0700787 parser.add_remote_option('--profile', default=None, type='string',
788 action='store', dest='profile',
789 help='Name of profile to sub-specify board variant.')
Brian Harring3fec5a82012-03-01 05:57:03 -0800790 parser.add_option('--remote', default=False, action='store_true',
Brian Harring3fec5a82012-03-01 05:57:03 -0800791 help=('Specifies that this tryjob should be run remotely.'))
792
793 # Advanced options
Ryan Cuieaa9efd2012-04-25 17:56:45 -0700794 group = CustomGroup(
Brian Harring3fec5a82012-03-01 05:57:03 -0800795 parser,
796 'Advanced Options',
797 'Caution: use these options at your own risk.')
798
Ryan Cui42aeae32012-05-21 17:09:09 -0700799 # bootstrap-args are not verified by the bootstrap code. It gets passed
800 # direcly to the bootstrap re-execution.
801 group.add_remote_option('--bootstrap-args', action='append',
802 default=[], help=optparse.SUPPRESS_HELP)
Ryan Cuieaa9efd2012-04-25 17:56:45 -0700803 group.add_remote_option('--buildbot', dest='buildbot', action='store_true',
804 default=False, help='This is running on a buildbot')
805 group.add_remote_option('--buildnumber', help='build number', type='int',
806 default=0)
Ryan Cui5ba7e152012-05-10 14:36:52 -0700807 group.add_option('--chrome_root', default=None, type='path',
808 action='callback', callback=_CheckChromeRootOption,
809 dest='chrome_root', help='Local checkout of Chrome to use.')
Ryan Cuieaa9efd2012-04-25 17:56:45 -0700810 group.add_remote_option('--chrome_version', default=None, type='string',
811 action='callback', dest='chrome_version',
812 callback=_CheckChromeVersionOption,
813 help='Used with SPEC logic to force a particular SVN '
814 'revision of chrome rather than the latest.')
815 group.add_remote_option('--clobber', action='store_true', dest='clobber',
816 default=False,
817 help='Clears an old checkout before syncing')
818 group.add_remote_option('--lkgm', action='store_true', dest='lkgm',
819 default=False,
820 help='Sync to last known good manifest blessed by '
821 'PFQ')
Ryan Cui5ba7e152012-05-10 14:36:52 -0700822 parser.add_option('--log_dir', dest='log_dir', type='path',
Brian Harring3fec5a82012-03-01 05:57:03 -0800823 help=('Directory where logs are stored.'))
Ryan Cuieaa9efd2012-04-25 17:56:45 -0700824 group.add_remote_option('--maxarchives', dest='max_archive_builds',
825 default=3, type='int',
826 help="Change the local saved build count limit.")
827 group.add_remote_option('--noarchive', action='store_false', dest='archive',
828 default=True, help="Don't run archive stage.")
Ryan Cuif7f24692012-05-18 16:35:33 -0700829 group.add_remote_option('--nobootstrap', action='store_false',
830 dest='bootstrap', default=True,
831 help="Don't checkout and run from a standalone "
832 "chromite repo.")
Ryan Cuieaa9efd2012-04-25 17:56:45 -0700833 group.add_remote_option('--nobuild', action='store_false', dest='build',
834 default=True,
835 help="Don't actually build (for cbuildbot dev)")
836 group.add_remote_option('--noclean', action='store_false', dest='clean',
837 default=True, help="Don't clean the buildroot")
Ryan Cuif7f24692012-05-18 16:35:33 -0700838 group.add_remote_option('--nocgroups', action='store_false', dest='cgroups',
839 default=True,
840 help='Disable cbuildbots usage of cgroups.')
Ryan Cuieaa9efd2012-04-25 17:56:45 -0700841 group.add_remote_option('--noprebuilts', action='store_false',
842 dest='prebuilts', default=True,
843 help="Don't upload prebuilts.")
844 group.add_remote_option('--nosync', action='store_false', dest='sync',
845 default=True, help="Don't sync before building.")
Ryan Cuieaa9efd2012-04-25 17:56:45 -0700846 group.add_remote_option('--notests', action='store_false', dest='tests',
847 default=True,
848 help='Override values from buildconfig and run no '
849 'tests.')
850 group.add_remote_option('--nouprev', action='store_false', dest='uprev',
851 default=True,
852 help='Override values from buildconfig and never '
853 'uprev.')
854 group.add_option('--pass-through', dest='pass_through_args', action='append',
855 type='string', default=[], help=optparse.SUPPRESS_HELP)
Brian Harring3fec5a82012-03-01 05:57:03 -0800856 group.add_option('--reference-repo', action='store', default=None,
Ryan Cuieaa9efd2012-04-25 17:56:45 -0700857 dest='reference_repo',
858 help='Reuse git data stored in an existing repo '
859 'checkout. This can drastically reduce the network '
860 'time spent setting up the trybot checkout. By '
861 "default, if this option isn't given but cbuildbot "
862 'is invoked from a repo checkout, cbuildbot will '
863 'use the repo root.')
Brian Harring37e559b2012-05-22 20:47:32 -0700864 # Used for handling forwards/backwards compatibility for --resume and
865 # --bootstrap.
866 group.add_option('--reexec-api-version', dest='output_api_version',
867 action='store_true', default=False,
868 help=optparse.SUPPRESS_HELP)
Ryan Cuieaa9efd2012-04-25 17:56:45 -0700869 # Indicates this is running on a remote trybot machine.
Ryan Cuiba41ad32012-03-08 17:15:29 -0800870 group.add_option('--remote-trybot', dest='remote_trybot', action='store_true',
Ryan Cuieaa9efd2012-04-25 17:56:45 -0700871 default=False, help=optparse.SUPPRESS_HELP)
Ryan Cuicedd8a52012-03-22 02:28:35 -0700872 # Patches uploaded by trybot client when run using the -p option.
Ryan Cui79319ab2012-05-21 12:59:18 -0700873 group.add_remote_option('--remote-patches', action='extend', default=[],
Ryan Cuieaa9efd2012-04-25 17:56:45 -0700874 help=optparse.SUPPRESS_HELP)
Ryan Cuicedd8a52012-03-22 02:28:35 -0700875 group.add_option('--resume', action='store_true', default=False,
Ryan Cuieaa9efd2012-04-25 17:56:45 -0700876 help='Skip stages already successfully completed.')
877 group.add_remote_option('--timeout', action='store', type='int', default=0,
878 help='Specify the maximum amount of time this job '
879 'can run for, at which point the build will be '
880 'aborted. If set to zero, then there is no '
881 'timeout.')
Ryan Cui79319ab2012-05-21 12:59:18 -0700882 # Specify specific remote tryslaves to run on.
883 group.add_option('--slaves', action='extend', default=[],
884 help=optparse.SUPPRESS_HELP)
Ryan Cui5ba7e152012-05-10 14:36:52 -0700885 group.add_option('--sourceroot', type='path', default=constants.SOURCE_ROOT,
886 help=optparse.SUPPRESS_HELP)
Ryan Cuif7f24692012-05-18 16:35:33 -0700887 # Causes cbuildbot to bootstrap itself twice, in the sequence A->B->C.
888 # A(unpatched) patches and bootstraps B. B patches and bootstraps C.
889 group.add_remote_option('--test-bootstrap', action='store_true',
890 default=False, help=optparse.SUPPRESS_HELP)
Ryan Cui39bdbbf2012-02-29 16:15:39 -0800891 group.add_option('--test-tryjob', action='store_true',
892 default=False,
893 help='Submit a tryjob to the test repository. Will not '
894 'show up on the production trybot waterfall.')
Ryan Cuieaa9efd2012-04-25 17:56:45 -0700895 group.add_remote_option('--validation_pool', default=None,
896 help='Path to a pickled validation pool. Intended '
897 'for use only with the commit queue.')
898 group.add_remote_option('--version', dest='force_version', default=None,
899 help='Used with manifest logic. Forces use of this '
900 'version rather than create or get latest.')
Brian Harring3fec5a82012-03-01 05:57:03 -0800901
902 parser.add_option_group(group)
903
904 # Debug options
Ryan Cuieaa9efd2012-04-25 17:56:45 -0700905 group = CustomGroup(parser, "Debug Options")
Brian Harring3fec5a82012-03-01 05:57:03 -0800906
Ryan Cui85867972012-02-23 18:21:49 -0800907 group.add_option('--debug', action='store_true', default=None,
Brian Harring3fec5a82012-03-01 05:57:03 -0800908 help='Override some options to run as a developer.')
909 group.add_option('--dump_config', action='store_true', dest='dump_config',
910 default=False,
911 help='Dump out build config options, and exit.')
912 group.add_option('--notee', action='store_false', dest='tee', default=True,
913 help="Disable logging and internal tee process. Primarily "
914 "used for debugging cbuildbot itself.")
915 parser.add_option_group(group)
916 return parser
917
918
Ryan Cui85867972012-02-23 18:21:49 -0800919def _FinishParsing(options, args):
920 """Perform some parsing tasks that need to take place after optparse.
921
922 This function needs to be easily testable! Keep it free of
923 environment-dependent code. Put more detailed usage validation in
924 _PostParseCheck().
Brian Harring3fec5a82012-03-01 05:57:03 -0800925
926 Args:
Ryan Cui85867972012-02-23 18:21:49 -0800927 options, args: The options/args object returned by optparse
Brian Harring3fec5a82012-03-01 05:57:03 -0800928 """
Brian Harring07039b52012-05-13 17:56:47 -0700929 # Setup logging levels first so any parsing triggered log messages
930 # are appropriately filtered.
931 logging.getLogger().setLevel(
932 logging.DEBUG if options.debug else logging.INFO)
933
Brian Harring3fec5a82012-03-01 05:57:03 -0800934 if options.chrome_root:
935 if options.chrome_rev != constants.CHROME_REV_LOCAL:
936 cros_lib.Die('Chrome rev must be %s if chrome_root is set.' %
937 constants.CHROME_REV_LOCAL)
938 else:
939 if options.chrome_rev == constants.CHROME_REV_LOCAL:
940 cros_lib.Die('Chrome root must be set if chrome_rev is %s.' %
941 constants.CHROME_REV_LOCAL)
942
943 if options.chrome_version:
944 if options.chrome_rev != constants.CHROME_REV_SPEC:
945 cros_lib.Die('Chrome rev must be %s if chrome_version is set.' %
946 constants.CHROME_REV_SPEC)
947 else:
948 if options.chrome_rev == constants.CHROME_REV_SPEC:
949 cros_lib.Die('Chrome rev must not be %s if chrome_version is not set.' %
950 constants.CHROME_REV_SPEC)
951
Ryan Cuieaa9efd2012-04-25 17:56:45 -0700952 patches = bool(options.gerrit_patches or options.local_patches)
953 if options.remote:
954 if options.local:
955 cros_lib.Die('Cannot specify both --remote and --local')
Ryan Cui54da0702012-04-19 18:38:08 -0700956
Ryan Cuieaa9efd2012-04-25 17:56:45 -0700957 if not options.buildbot and not patches:
958 cros_lib.Die('Must provide patches when running with --remote.')
Brian Harring3fec5a82012-03-01 05:57:03 -0800959
Ryan Cuieaa9efd2012-04-25 17:56:45 -0700960 # --debug needs to be explicitly passed through for remote invocations.
961 release_mode_with_patches = (options.buildbot and patches and
962 '--debug' not in options.pass_through_args)
963 else:
964 if len(args) > 1:
965 cros_lib.Die('Multiple configs not supported if not running with '
966 '--remote.')
967
Ryan Cui79319ab2012-05-21 12:59:18 -0700968 if options.slaves:
969 cros_lib.Die('Cannot use --slaves if not running with --remote.')
970
Ryan Cuieaa9efd2012-04-25 17:56:45 -0700971 release_mode_with_patches = (options.buildbot and patches and
972 not options.debug)
973
974 # When running in release mode, make sure we are running with checked-in code.
975 # We want checked-in cbuildbot/scripts to prevent errors, and we want to build
976 # a release image with checked-in code for CrOS packages.
977 if release_mode_with_patches:
978 cros_lib.Die('Cannot provide patches when running with --buildbot!')
Brian Harring3fec5a82012-03-01 05:57:03 -0800979
Ryan Cuiba41ad32012-03-08 17:15:29 -0800980 if options.buildbot and options.remote_trybot:
981 cros_lib.Die('--buildbot and --remote-trybot cannot be used together.')
982
Ryan Cui85867972012-02-23 18:21:49 -0800983 # Record whether --debug was set explicitly vs. it was inferred.
984 options.debug_forced = False
985 if options.debug:
986 options.debug_forced = True
987 else:
Ryan Cui16ca5812012-03-08 20:34:27 -0800988 # We don't set debug by default for
989 # 1. --buildbot invocations.
990 # 2. --remote invocations, because it needs to push changes to the tryjob
991 # repo.
992 options.debug = not options.buildbot and not options.remote
Brian Harring3fec5a82012-03-01 05:57:03 -0800993
Brian Harring3fec5a82012-03-01 05:57:03 -0800994
Brian Harring1d7ba942012-04-24 06:37:18 -0700995# pylint: disable=W0613
Ryan Cui85867972012-02-23 18:21:49 -0800996def _PostParseCheck(options, args):
997 """Perform some usage validation after we've parsed the arguments
Brian Harring3fec5a82012-03-01 05:57:03 -0800998
Ryan Cui85867972012-02-23 18:21:49 -0800999 Args:
1000 options/args: The options/args object returned by optparse
1001 """
Ryan Cuie1e4e662012-05-21 16:39:46 -07001002 if not options.branch:
1003 options.branch = _GetChromiteTrackingBranch()
1004
Ryan Cui5ba7e152012-05-10 14:36:52 -07001005 if options.local_patches and not repository.IsARepoRoot(options.sourceroot):
1006 raise Exception('Could not find repo checkout at %s!'
1007 % options.sourceroot)
1008
Brian Harring609dc4e2012-05-07 02:17:44 -07001009 if options.local_patches:
Brian Harring1d7ba942012-04-24 06:37:18 -07001010 options.local_patches = _CheckLocalPatches(
Brian Harring609dc4e2012-05-07 02:17:44 -07001011 options.sourceroot, options.local_patches)
Brian Harring1d7ba942012-04-24 06:37:18 -07001012
1013 default = os.environ.get('CBUILDBOT_DEFAULT_MODE')
1014 if (default and not any([options.local, options.buildbot,
1015 options.remote, options.remote_trybot])):
1016 cros_lib.Info("CBUILDBOT_DEFAULT_MODE=%s env var detected, using it."
1017 % default)
1018 default = default.lower()
1019 if default == 'local':
1020 options.local = True
1021 elif default == 'remote':
1022 options.remote = True
1023 elif default == 'buildbot':
1024 options.buildbot = True
1025 else:
1026 cros_lib.Die("CBUILDBOT_DEFAULT_MODE value %s isn't supported. "
1027 % default)
Ryan Cui85867972012-02-23 18:21:49 -08001028
1029
1030def _ParseCommandLine(parser, argv):
1031 """Completely parse the commandline arguments"""
Brian Harring3fec5a82012-03-01 05:57:03 -08001032 (options, args) = parser.parse_args(argv)
Brian Harring37e559b2012-05-22 20:47:32 -07001033
1034 if options.output_api_version:
1035 print _REEXEC_API_VERSION
1036 sys.exit(0)
1037
Ryan Cui54da0702012-04-19 18:38:08 -07001038 if options.list:
1039 _PrintValidConfigs(options.print_all)
1040 sys.exit(0)
1041
Ryan Cui8be16062012-04-24 12:05:26 -07001042 # Strip out null arguments.
1043 # TODO(rcui): Remove when buildbot is fixed
1044 args = [arg for arg in args if arg]
1045 if not args:
1046 parser.error('Invalid usage. Use -h to see usage. Use -l to list '
1047 'supported configs.')
1048
Ryan Cui85867972012-02-23 18:21:49 -08001049 _FinishParsing(options, args)
1050 return options, args
1051
1052
1053def main(argv):
1054 # Set umask to 022 so files created by buildbot are readable.
1055 os.umask(022)
1056
1057 if cros_lib.IsInsideChroot():
1058 cros_lib.Die('Please run cbuildbot from outside the chroot.')
1059
1060 parser = _CreateParser()
1061 (options, args) = _ParseCommandLine(parser, argv)
Brian Harring3fec5a82012-03-01 05:57:03 -08001062
Brian Harring3fec5a82012-03-01 05:57:03 -08001063 _PostParseCheck(options, args)
1064
1065 if options.remote:
Chris Sosa4f6ffaf2012-05-01 17:05:44 -07001066 cros_lib.logger.setLevel(logging.WARNING)
Ryan Cui16ca5812012-03-08 20:34:27 -08001067
Brian Harring3fec5a82012-03-01 05:57:03 -08001068 # Verify configs are valid.
1069 for bot in args:
1070 _GetConfig(bot)
1071
1072 # Verify gerrit patches are valid.
Ryan Cui16ca5812012-03-08 20:34:27 -08001073 print 'Verifying patches...'
Ryan Cuie1e4e662012-05-21 16:39:46 -07001074 patch_pool = AcquirePoolFromOptions(options)
Ryan Cui16d9e1f2012-05-11 10:50:18 -07001075
Ryan Cuieaa9efd2012-04-25 17:56:45 -07001076 # --debug need to be explicitly passed through for remote invocations.
1077 if options.buildbot and '--debug' not in options.pass_through_args:
1078 _ConfirmRemoteBuildbotRun()
1079
Ryan Cui16ca5812012-03-08 20:34:27 -08001080 print 'Submitting tryjob...'
Ryan Cui16d9e1f2012-05-11 10:50:18 -07001081 tryjob = remote_try.RemoteTryJob(options, args, patch_pool.local_patches)
Ryan Cui39bdbbf2012-02-29 16:15:39 -08001082 tryjob.Submit(testjob=options.test_tryjob, dryrun=options.debug)
Ryan Cui16ca5812012-03-08 20:34:27 -08001083 print 'Tryjob submitted!'
1084 print ('Go to %s to view the status of your job.'
Ryan Cui4906e1c2012-04-03 20:09:34 -07001085 % tryjob.GetTrybotWaterfallLink())
Brian Harringe2078b92012-05-24 03:21:38 -07001086 if options.debug:
1087 print
1088 print "Keep in mind that you had --debug enabled, thus nothing was"
1089 print "actually submitted."
Brian Harring3fec5a82012-03-01 05:57:03 -08001090 sys.exit(0)
Ryan Cui54da0702012-04-19 18:38:08 -07001091 elif (not options.buildbot and not options.remote_trybot
1092 and not options.resume and not options.local):
1093 cros_lib.Warning('Running in LOCAL TRYBOT mode! Use --remote to submit '
1094 'REMOTE tryjobs. Use --local to suppress this message.')
1095 cros_lib.Warning('Starting April 30th, --local will be required to run the '
1096 'local trybot.')
1097 time.sleep(5)
Brian Harring3fec5a82012-03-01 05:57:03 -08001098
Ryan Cui8be16062012-04-24 12:05:26 -07001099 # Only expecting one config
1100 bot_id = args[-1]
1101 build_config = _GetConfig(bot_id)
Brian Harring3fec5a82012-03-01 05:57:03 -08001102
1103 if options.reference_repo is None:
Ryan Cui5ba7e152012-05-10 14:36:52 -07001104 repo_path = os.path.join(options.sourceroot, '.repo')
Brian Harring3fec5a82012-03-01 05:57:03 -08001105 # If we're being run from a repo checkout, reuse the repo's git pool to
1106 # cut down on sync time.
1107 if os.path.exists(repo_path):
Ryan Cui5ba7e152012-05-10 14:36:52 -07001108 options.reference_repo = options.sourceroot
Brian Harring3fec5a82012-03-01 05:57:03 -08001109 elif options.reference_repo:
1110 if not os.path.exists(options.reference_repo):
1111 parser.error('Reference path %s does not exist'
1112 % (options.reference_repo,))
1113 elif not os.path.exists(os.path.join(options.reference_repo, '.repo')):
1114 parser.error('Reference path %s does not look to be the base of a '
1115 'repo checkout; no .repo exists in the root.'
1116 % (options.reference_repo,))
Ryan Cuid4a24212012-04-04 18:08:12 -07001117
Brian Harringf11bf682012-05-14 15:53:43 -07001118 if (options.buildbot or options.remote_trybot) and not options.resume:
Brian Harring470f6112012-03-02 11:47:10 -08001119 if not options.cgroups:
Ryan Cuid4a24212012-04-04 18:08:12 -07001120 parser.error('Options --buildbot/--remote-trybot and --nocgroups cannot '
1121 'be used together. Cgroup support is required for '
1122 'buildbot/remote-trybot mode.')
Brian Harring470f6112012-03-02 11:47:10 -08001123 if not cgroups.Cgroup.CgroupsSupported():
Ryan Cuid4a24212012-04-04 18:08:12 -07001124 parser.error('Option --buildbot/--remote-trybot was given, but this '
1125 'system does not support cgroups. Failing.')
Brian Harring3fec5a82012-03-01 05:57:03 -08001126
Brian Harring351ce442012-03-09 16:38:14 -08001127 missing = []
1128 for program in _BUILDBOT_REQUIRED_BINARIES:
1129 ret = cros_lib.RunCommand('which %s' % program, shell=True,
1130 redirect_stderr=True, redirect_stdout=True,
1131 error_code_ok=True, print_cmd=False)
1132 if ret.returncode != 0:
1133 missing.append(program)
1134
1135 if missing:
Ryan Cuid4a24212012-04-04 18:08:12 -07001136 parser.error("Option --buildbot/--remote-trybot requires the following "
1137 "binaries which couldn't be found in $PATH: %s"
Brian Harring351ce442012-03-09 16:38:14 -08001138 % (', '.join(missing)))
1139
Brian Harring3fec5a82012-03-01 05:57:03 -08001140 if options.reference_repo:
1141 options.reference_repo = os.path.abspath(options.reference_repo)
1142
1143 if options.dump_config:
1144 # This works, but option ordering is bad...
1145 print 'Configuration %s:' % bot_id
1146 pretty_printer = pprint.PrettyPrinter(indent=2)
1147 pretty_printer.pprint(build_config)
1148 sys.exit(0)
1149
1150 if not options.buildroot:
1151 if options.buildbot:
1152 parser.error('Please specify a buildroot with the --buildroot option.')
Matt Tennantd55b1f42012-04-13 14:15:01 -07001153
Ryan Cui5ba7e152012-05-10 14:36:52 -07001154 options.buildroot = _DetermineDefaultBuildRoot(options.sourceroot,
1155 build_config['internal'])
Brian Harring470f6112012-03-02 11:47:10 -08001156 # We use a marker file in the buildroot to indicate the user has
1157 # consented to using this directory.
1158 if not os.path.exists(repository.GetTrybotMarkerPath(options.buildroot)):
1159 _ConfirmBuildRoot(options.buildroot)
Brian Harring3fec5a82012-03-01 05:57:03 -08001160
1161 # Sanity check of buildroot- specifically that it's not pointing into the
1162 # midst of an existing repo since git-repo doesn't support nesting.
Brian Harring3fec5a82012-03-01 05:57:03 -08001163 if (not repository.IsARepoRoot(options.buildroot) and
David James6b80dc62012-02-29 15:34:40 -08001164 repository.InARepoRepository(options.buildroot)):
Brian Harring3fec5a82012-03-01 05:57:03 -08001165 parser.error('Configured buildroot %s points into a repository checkout, '
1166 'rather than the root of it. This is not supported.'
1167 % options.buildroot)
1168
Brian Harringd166aaf2012-05-14 18:31:53 -07001169 log_file = None
1170 if options.tee:
1171 default_dir = os.path.join(options.buildroot, _DEFAULT_LOG_DIR)
1172 dirname = options.log_dir or default_dir
1173 log_file = os.path.join(dirname, _BUILDBOT_LOG_FILE)
1174
1175 osutils.SafeMakedirs(dirname)
1176 _BackupPreviousLog(log_file)
1177
Brian Harringc2d09d92012-05-13 22:03:15 -07001178 with cros_lib.ContextManagerStack() as stack:
1179 critical_section = stack.Add(cleanup.EnforcedCleanupSection)
1180 stack.Add(sudo.SudoKeepAlive)
Brian Harringd166aaf2012-05-14 18:31:53 -07001181
Brian Harringc2d09d92012-05-13 22:03:15 -07001182 if not options.resume:
Brian Harring2bf55e12012-05-13 21:31:55 -07001183 # If we're in resume mode, use our parents tempdir rather than
1184 # nesting another layer.
Brian Harringc2d09d92012-05-13 22:03:15 -07001185 stack.Add(osutils.TempDirContextManager, 'cbuildbot-tmp')
1186 logging.debug("Cbuildbot tempdir is %r.", os.environ.get('TMP'))
Brian Harringd166aaf2012-05-14 18:31:53 -07001187
1188 if log_file is not None:
1189 stack.Add(tee.Tee, log_file)
1190 options.preserve_paths = set([_DEFAULT_LOG_DIR])
1191
Brian Harringc2d09d92012-05-13 22:03:15 -07001192 if options.cgroups:
1193 stack.Add(cgroups.SimpleContainChildren, 'cbuildbot')
Brian Harringa184efa2012-03-04 11:51:25 -08001194
Brian Harringc2d09d92012-05-13 22:03:15 -07001195 # Mark everything between EnforcedCleanupSection and here as having to
1196 # be rolled back via the contextmanager cleanup handlers. This
1197 # ensures that sudo bits cannot outlive cbuildbot, that anything
1198 # cgroups would kill gets killed, etc.
1199 critical_section.ForkWatchdog()
Brian Harringd166aaf2012-05-14 18:31:53 -07001200
Brian Harringc2d09d92012-05-13 22:03:15 -07001201 if options.timeout > 0:
1202 stack.Add(cros_lib.Timeout, options.timeout)
Brian Harringa184efa2012-03-04 11:51:25 -08001203
Brian Harringc2d09d92012-05-13 22:03:15 -07001204 if not options.buildbot:
1205 build_config = cbuildbot_config.OverrideConfigForTrybot(
1206 build_config,
1207 options.remote_trybot)
1208
1209 _RunBuildStagesWrapper(options, build_config)