blob: 9918477789136a08859a34388cdd08a71f18ca2c [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
David James58e0c092012-03-04 20:31:12 -080015import multiprocessing
Brian Harring3fec5a82012-03-01 05:57:03 -080016import optparse
17import os
18import pprint
19import sys
20
21from chromite.buildbot import builderstage as bs
22from chromite.buildbot import cbuildbot_background as background
23from chromite.buildbot import cbuildbot_config
24from chromite.buildbot import cbuildbot_stages as stages
25from chromite.buildbot import cbuildbot_results as results_lib
Brian Harring3fec5a82012-03-01 05:57:03 -080026from chromite.buildbot import constants
27from chromite.buildbot import gerrit_helper
28from chromite.buildbot import patch as cros_patch
29from chromite.buildbot import remote_try
30from chromite.buildbot import repository
31from chromite.buildbot import tee
32
Brian Harringc92a7012012-02-29 10:11:34 -080033from chromite.lib import cgroups
Brian Harringa184efa2012-03-04 11:51:25 -080034from chromite.lib import cleanup
Brian Harring3fec5a82012-03-01 05:57:03 -080035from chromite.lib import cros_build_lib as cros_lib
36from chromite.lib import sudo
37
Ryan Cuiadd49122012-03-21 22:19:58 -070038
Brian Harring3fec5a82012-03-01 05:57:03 -080039cros_lib.STRICT_SUDO = True
40
41_DEFAULT_LOG_DIR = 'cbuildbot_logs'
42_BUILDBOT_LOG_FILE = 'cbuildbot.log'
43_DEFAULT_EXT_BUILDROOT = 'trybot'
44_DEFAULT_INT_BUILDROOT = 'trybot-internal'
45_PATH_TO_CBUILDBOT = 'chromite/bin/cbuildbot'
46_DISTRIBUTED_TYPES = [constants.COMMIT_QUEUE_TYPE, constants.PFQ_TYPE,
47 constants.CANARY_TYPE, constants.CHROME_PFQ_TYPE,
48 constants.PALADIN_TYPE]
Brian Harring351ce442012-03-09 16:38:14 -080049_BUILDBOT_REQUIRED_BINARIES = ('pbzip2',)
Brian Harring3fec5a82012-03-01 05:57:03 -080050
51
52def _PrintValidConfigs(trybot_only=True):
53 """Print a list of valid buildbot configs.
54
55 Arguments:
56 trybot_only: Only print selected trybot configs, as specified by the
57 'trybot_list' config setting.
58 """
59 COLUMN_WIDTH = 45
60 print 'config'.ljust(COLUMN_WIDTH), 'description'
61 print '------'.ljust(COLUMN_WIDTH), '-----------'
62 config_names = cbuildbot_config.config.keys()
63 config_names.sort()
64 for name in config_names:
65 if not trybot_only or cbuildbot_config.config[name]['trybot_list']:
66 desc = ''
67 if cbuildbot_config.config[name]['description']:
68 desc = cbuildbot_config.config[name]['description']
69
70 print name.ljust(COLUMN_WIDTH), desc
71
72
73def _GetConfig(config_name):
74 """Gets the configuration for the build"""
75 if not cbuildbot_config.config.has_key(config_name):
76 print 'Non-existent configuration %s specified.' % config_name
77 print 'Please specify one of:'
78 _PrintValidConfigs()
79 sys.exit(1)
80
81 result = cbuildbot_config.config[config_name]
82
83 return result
84
85
86def _GetChromiteTrackingBranch():
David James66009462012-03-25 10:08:38 -070087 """Returns the remote branch associated with chromite."""
Brian Harring3fec5a82012-03-01 05:57:03 -080088 cwd = os.path.dirname(os.path.realpath(__file__))
David James66009462012-03-25 10:08:38 -070089 branch = cros_lib.GetCurrentBranch(cwd)
90 if branch:
91 tracking_branch = cros_lib.GetTrackingBranch(branch, cwd)[1]
92 if tracking_branch.startswith('refs/heads/'):
93 return tracking_branch.replace('refs/heads/', '')
94 # If we are not on a branch, or if the tracking branch is a revision,
David James8b3c1bf2012-03-28 09:10:16 -070095 # use the push branch. For repo repositories, this will be the manifest
96 # branch configured for this project. For other repositories, we'll just
97 # guess 'master', since there's no easy way to find out what branch
98 # we're on.
99 return cros_lib.GetPushBranch(cwd)[1]
Brian Harring3fec5a82012-03-01 05:57:03 -0800100
101
102def _CheckBuildRootBranch(buildroot, tracking_branch):
103 """Make sure buildroot branch is the same as Chromite branch."""
104 manifest_branch = cros_lib.GetManifestDefaultBranch(buildroot)
105 if manifest_branch != tracking_branch:
106 cros_lib.Die('Chromite is not on same branch as buildroot checkout\n' +
107 'Chromite is on branch %s.\n' % tracking_branch +
108 'Buildroot checked out to %s\n' % manifest_branch)
109
110
111def _PreProcessPatches(gerrit_patches, local_patches):
112 """Validate patches ASAP to catch user errors. Also generate patch info.
113
114 Args:
115 gerrit_patches: List of gerrit CL ID's passed in by user.
116 local_patches: List of local project branches to generate patches from.
117
118 Returns:
119 A tuple containing a list of cros_patch.GerritPatch and a list of
120 cros_patch.LocalPatch objects.
121 """
122 gerrit_patch_info = []
123 local_patch_info = []
124
125 try:
126 if gerrit_patches:
127 gerrit_patch_info = gerrit_helper.GetGerritPatchInfo(gerrit_patches)
128 for patch in gerrit_patch_info:
129 if patch.IsAlreadyMerged():
130 cros_lib.Warning('Patch %s has already been merged.' % str(patch))
131 except gerrit_helper.GerritException as e:
132 cros_lib.Die(str(e))
133
134 try:
135 if local_patches:
136 local_patch_info = cros_patch.PrepareLocalPatches(
137 local_patches,
138 _GetChromiteTrackingBranch())
139
140 except cros_patch.PatchException as e:
141 cros_lib.Die(str(e))
142
143 return gerrit_patch_info, local_patch_info
144
145
146def _IsIncrementalBuild(buildroot, clobber):
147 """Returns True if we are reusing an existing buildroot."""
148 repo_dir = os.path.join(buildroot, '.repo')
149 return not clobber and os.path.isdir(repo_dir)
150
151
152class Builder(object):
153 """Parent class for all builder types.
154
155 This class functions as a parent class for various build types. It's intended
156 use is builder_instance.Run().
157
158 Vars:
Brian Harring3fec5a82012-03-01 05:57:03 -0800159 build_config: The configuration dictionary from cbuildbot_config.
160 options: The options provided from optparse in main().
161 completed_stages_file: Where we store resume state.
162 archive_url: Where our artifacts for this builder will be archived.
163 tracking_branch: The tracking branch for this build.
164 release_tag: The associated "chrome os version" of this build.
165 gerrit_patches: Gerrit patches to be included in build.
166 local_patches: Local patches to be included in build.
167 """
168
David James944a48e2012-03-07 12:19:03 -0800169 def __init__(self, options, build_config):
Brian Harring3fec5a82012-03-01 05:57:03 -0800170 """Initializes instance variables. Must be called by all subclasses."""
Brian Harring3fec5a82012-03-01 05:57:03 -0800171 self.build_config = build_config
172 self.options = options
173
174 # TODO, Remove here and in config after bug chromium-os:14649 is fixed.
175 if self.build_config['chromeos_official']:
176 os.environ['CHROMEOS_OFFICIAL'] = '1'
177
178 self.completed_stages_file = os.path.join(options.buildroot,
179 '.completed_stages')
David James58e0c092012-03-04 20:31:12 -0800180 self.archive_stages = {}
Brian Harring3fec5a82012-03-01 05:57:03 -0800181 self.archive_urls = {}
182 self.release_tag = None
183 self.tracking_branch = _GetChromiteTrackingBranch()
184 self.gerrit_patches = None
185 self.local_patches = None
186
187 def Initialize(self):
188 """Runs through the initialization steps of an actual build."""
189 if self.options.resume and os.path.exists(self.completed_stages_file):
190 with open(self.completed_stages_file, 'r') as load_file:
191 results_lib.Results.RestoreCompletedStages(load_file)
192
193 # We only want to do this if we need to patch changes.
194 if not results_lib.Results.GetPrevious().get(
195 self._GetStageInstance(stages.PatchChangesStage, None, None).name):
196 self.gerrit_patches, self.local_patches = _PreProcessPatches(
197 self.options.gerrit_patches, self.options.local_patches)
198
199 bs.BuilderStage.SetTrackingBranch(self.tracking_branch)
200
201 # Check branch matching early.
202 if _IsIncrementalBuild(self.options.buildroot, self.options.clobber):
203 _CheckBuildRootBranch(self.options.buildroot, self.tracking_branch)
204
205 self._RunStage(stages.CleanUpStage)
206
207 def _GetStageInstance(self, stage, *args, **kwargs):
208 """Helper function to get an instance given the args.
209
David James944a48e2012-03-07 12:19:03 -0800210 Useful as almost all stages just take in options and build_config.
Brian Harring3fec5a82012-03-01 05:57:03 -0800211 """
David James944a48e2012-03-07 12:19:03 -0800212 config = kwargs.pop('config', self.build_config)
213 return stage(self.options, config, *args, **kwargs)
Brian Harring3fec5a82012-03-01 05:57:03 -0800214
215 def _SetReleaseTag(self):
216 """Sets the release tag from the manifest_manager.
217
218 Must be run after sync stage as syncing enables us to have a release tag.
219 """
220 # Extract version we have decided to build into self.release_tag.
221 manifest_manager = stages.ManifestVersionedSyncStage.manifest_manager
222 if manifest_manager:
223 self.release_tag = manifest_manager.current_version
224
225 def _RunStage(self, stage, *args, **kwargs):
226 """Wrapper to run a stage."""
227 stage_instance = self._GetStageInstance(stage, *args, **kwargs)
228 return stage_instance.Run()
229
230 def GetSyncInstance(self):
231 """Returns an instance of a SyncStage that should be run.
232
233 Subclasses must override this method.
234 """
235 raise NotImplementedError()
236
237 def RunStages(self):
238 """Subclasses must override this method. Runs the appropriate code."""
239 raise NotImplementedError()
240
241 def _WriteCheckpoint(self):
242 """Drops a completed stages file with current state."""
243 with open(self.completed_stages_file, 'w+') as save_file:
244 results_lib.Results.SaveCompletedStages(save_file)
245
246 def _ShouldReExecuteInBuildRoot(self):
247 """Returns True if this build should be re-executed in the buildroot."""
248 abs_buildroot = os.path.abspath(self.options.buildroot)
249 return not os.path.abspath(__file__).startswith(abs_buildroot)
250
251 def _ReExecuteInBuildroot(self, sync_instance):
252 """Reexecutes self in buildroot and returns True if build succeeds.
253
254 This allows the buildbot code to test itself when changes are patched for
255 buildbot-related code. This is a no-op if the buildroot == buildroot
256 of the running chromite checkout.
257
258 Args:
259 sync_instance: Instance of the sync stage that was run to sync.
260
261 Returns:
262 True if the Build succeeded.
263 """
264 # If we are resuming, use last checkpoint.
265 if not self.options.resume:
266 self._WriteCheckpoint()
267
268 # Re-write paths to use absolute paths.
269 # Suppress any timeout options given from the commandline in the
270 # invoked cbuildbot; our timeout will enforce it instead.
271 args_to_append = ['--resume', '--timeout', '0', '--buildroot',
272 os.path.abspath(self.options.buildroot)]
273
274 if self.options.chrome_root:
275 args_to_append += ['--chrome_root',
276 os.path.abspath(self.options.chrome_root)]
277
278 if stages.ManifestVersionedSyncStage.manifest_manager:
279 ver = stages.ManifestVersionedSyncStage.manifest_manager.current_version
280 args_to_append += ['--version', ver]
281
282 if isinstance(sync_instance, stages.CommitQueueSyncStage):
283 vp_file = sync_instance.SaveValidationPool()
284 args_to_append += ['--validation_pool', vp_file]
285
286 # Re-run the command in the buildroot.
287 # Finally, be generous and give the invoked cbuildbot 30s to shutdown
288 # when something occurs. It should exit quicker, but the sigterm may
289 # hit while the system is particularly busy.
290 return_obj = cros_lib.RunCommand(
291 [_PATH_TO_CBUILDBOT] + sys.argv[1:] + args_to_append,
292 cwd=self.options.buildroot, error_code_ok=True, kill_timeout=30)
293 return return_obj.returncode == 0
294
295 def Run(self):
296 """Main runner for this builder class. Runs build and prints summary."""
297 print_report = True
298 success = True
299 try:
300 self.Initialize()
301 sync_instance = self.GetSyncInstance()
302 sync_instance.Run()
303 self._SetReleaseTag()
304
Ryan Cuicedd8a52012-03-22 02:28:35 -0700305 if (self.gerrit_patches or self.local_patches
306 or self.options.remote_patches):
Brian Harring3fec5a82012-03-01 05:57:03 -0800307 self._RunStage(stages.PatchChangesStage,
308 self.gerrit_patches, self.local_patches)
309
310 if self._ShouldReExecuteInBuildRoot():
311 print_report = False
312 success = self._ReExecuteInBuildroot(sync_instance)
313 else:
314 self.RunStages()
315
316 finally:
317 if print_report:
318 self._WriteCheckpoint()
319 print '\n\n\n@@@BUILD_STEP Report@@@\n'
320 results_lib.Results.Report(sys.stdout, self.archive_urls,
321 self.release_tag)
322 success = results_lib.Results.BuildSucceededSoFar()
323
324 return success
325
326
327class SimpleBuilder(Builder):
328 """Builder that performs basic vetting operations."""
329
330 def GetSyncInstance(self):
331 """Sync to lkgm or TOT as necessary.
332
333 Returns: the instance of the sync stage that was run.
334 """
335 if self.options.lkgm or self.build_config['use_lkgm']:
336 sync_stage = self._GetStageInstance(stages.LKGMSyncStage)
337 else:
338 sync_stage = self._GetStageInstance(stages.SyncStage)
339
340 return sync_stage
341
David James58e0c092012-03-04 20:31:12 -0800342 def _RunBackgroundStagesForBoard(self, board):
343 """Run background board-specific stages for the specified board."""
David James58e0c092012-03-04 20:31:12 -0800344 archive_stage = self.archive_stages[board]
David James944a48e2012-03-07 12:19:03 -0800345 configs = self.build_config['board_specific_configs']
346 config = configs.get(board, self.build_config)
347 stage_list = [[stages.VMTestStage, board, archive_stage],
348 [stages.ChromeTestStage, board, archive_stage],
349 [stages.UnitTestStage, board],
350 [stages.UploadPrebuiltsStage, board]]
Brian Harring3fec5a82012-03-01 05:57:03 -0800351
David James58e0c092012-03-04 20:31:12 -0800352 # We can not run hw tests without archiving the payloads.
353 if self.options.archive:
David James944a48e2012-03-07 12:19:03 -0800354 for suite in config['hw_tests']:
355 stage_list.append([stages.HWTestStage, board, archive_stage, suite])
Chris Sosab50dc932012-03-01 14:00:58 -0800356
David James944a48e2012-03-07 12:19:03 -0800357 steps = [self._GetStageInstance(*x, config=config).Run for x in stage_list]
358 background.RunParallelSteps(steps + [archive_stage.Run])
Brian Harring3fec5a82012-03-01 05:57:03 -0800359
360 def RunStages(self):
361 """Runs through build process."""
362 self._RunStage(stages.BuildBoardStage)
363
364 # TODO(sosa): Split these out into classes.
Brian Harring3fec5a82012-03-01 05:57:03 -0800365 if self.build_config['build_type'] == constants.CHROOT_BUILDER_TYPE:
366 self._RunStage(stages.SDKTestStage)
367 self._RunStage(stages.UploadPrebuiltsStage,
368 constants.CHROOT_BUILDER_BOARD)
369 elif self.build_config['build_type'] == constants.REFRESH_PACKAGES_TYPE:
370 self._RunStage(stages.RefreshPackageStatusStage)
371 else:
372 self._RunStage(stages.UprevStage)
Brian Harring3fec5a82012-03-01 05:57:03 -0800373
David James944a48e2012-03-07 12:19:03 -0800374 configs = self.build_config['board_specific_configs']
David James58e0c092012-03-04 20:31:12 -0800375 for board in self.build_config['boards']:
David James944a48e2012-03-07 12:19:03 -0800376 config = configs.get(board, self.build_config)
377 archive_stage = self._GetStageInstance(stages.ArchiveStage, board,
378 config=config)
David James58e0c092012-03-04 20:31:12 -0800379 self.archive_stages[board] = archive_stage
380
David James944a48e2012-03-07 12:19:03 -0800381 # Set up a process pool to run test/archive stages in the background.
382 # This process runs task(board) for each board added to the queue.
David James58e0c092012-03-04 20:31:12 -0800383 queue = multiprocessing.Queue()
384 task = self._RunBackgroundStagesForBoard
385 with background.BackgroundTaskRunner(queue, task):
David James944a48e2012-03-07 12:19:03 -0800386 for board in self.build_config['boards']:
David James58e0c092012-03-04 20:31:12 -0800387 # Run BuildTarget in the foreground.
David James944a48e2012-03-07 12:19:03 -0800388 archive_stage = self.archive_stages[board]
389 config = configs.get(board, self.build_config)
390 self._RunStage(stages.BuildTargetStage, board, archive_stage,
391 config=config)
David James58e0c092012-03-04 20:31:12 -0800392 self.archive_urls[board] = archive_stage.GetDownloadUrl()
393
David James944a48e2012-03-07 12:19:03 -0800394 # Kick off task(board) in the background.
David James58e0c092012-03-04 20:31:12 -0800395 queue.put([board])
396
Brian Harring3fec5a82012-03-01 05:57:03 -0800397
398class DistributedBuilder(SimpleBuilder):
399 """Build class that has special logic to handle distributed builds.
400
401 These builds sync using git/manifest logic in manifest_versions. In general
402 they use a non-distributed builder code for the bulk of the work.
403 """
David James944a48e2012-03-07 12:19:03 -0800404 def __init__(self, options, build_config):
Brian Harring3fec5a82012-03-01 05:57:03 -0800405 """Initializes a buildbot builder.
406
407 Extra variables:
408 completion_stage_class: Stage used to complete a build. Set in the Sync
409 stage.
410 """
David James944a48e2012-03-07 12:19:03 -0800411 super(DistributedBuilder, self).__init__(options, build_config)
Brian Harring3fec5a82012-03-01 05:57:03 -0800412 self.completion_stage_class = None
413
414 def GetSyncInstance(self):
415 """Syncs the tree using one of the distributed sync logic paths.
416
417 Returns: the instance of the sync stage that was run.
418 """
419 # Determine sync class to use. CQ overrides PFQ bits so should check it
420 # first.
421 if cbuildbot_config.IsCQType(self.build_config['build_type']):
422 sync_stage = self._GetStageInstance(stages.CommitQueueSyncStage)
423 self.completion_stage_class = stages.CommitQueueCompletionStage
424 elif cbuildbot_config.IsPFQType(self.build_config['build_type']):
425 sync_stage = self._GetStageInstance(stages.LKGMCandidateSyncStage)
426 self.completion_stage_class = stages.LKGMCandidateSyncCompletionStage
427 else:
428 sync_stage = self._GetStageInstance(stages.ManifestVersionedSyncStage)
429 self.completion_stage_class = stages.ManifestVersionedSyncCompletionStage
430
431 return sync_stage
432
433 def Publish(self, was_build_successful):
434 """Completes build by publishing any required information."""
435 completion_stage = self._GetStageInstance(self.completion_stage_class,
436 was_build_successful)
437 completion_stage.Run()
438 name = completion_stage.name
439 if not results_lib.Results.WasStageSuccessful(name):
440 should_publish_changes = False
441 else:
442 should_publish_changes = (self.build_config['master'] and
443 was_build_successful)
444
445 if should_publish_changes:
446 self._RunStage(stages.PublishUprevChangesStage)
447
448 def RunStages(self):
449 """Runs simple builder logic and publishes information to overlays."""
450 was_build_successful = False
451 try:
David Jamesf55709e2012-03-13 09:10:15 -0700452 super(DistributedBuilder, self).RunStages()
453 was_build_successful = results_lib.Results.BuildSucceededSoFar()
Brian Harring3fec5a82012-03-01 05:57:03 -0800454 except SystemExit as ex:
455 # If a stage calls sys.exit(0), it's exiting with success, so that means
456 # we should mark ourselves as successful.
457 if ex.code == 0:
458 was_build_successful = True
459 raise
460 finally:
461 self.Publish(was_build_successful)
462
Brian Harring3fec5a82012-03-01 05:57:03 -0800463
464def _ConfirmBuildRoot(buildroot):
465 """Confirm with user the inferred buildroot, and mark it as confirmed."""
466 warning = 'Using default directory %s as buildroot' % buildroot
467 response = cros_lib.YesNoPrompt(default=cros_lib.NO, warning=warning,
468 full=True)
469 if response == cros_lib.NO:
470 print('Please specify a buildroot with the --buildroot option.')
471 sys.exit(0)
472
473 if not os.path.exists(buildroot):
474 os.mkdir(buildroot)
475
476 repository.CreateTrybotMarker(buildroot)
477
478
479def _DetermineDefaultBuildRoot(internal_build):
480 """Default buildroot to be under the directory that contains current checkout.
481
482 Arguments:
483 internal_build: Whether the build is an internal build
484 """
485 repo_dir = cros_lib.FindRepoDir()
486 if not repo_dir:
487 cros_lib.Die('Could not find root of local checkout. Please specify'
488 'using --buildroot option.')
489
490 # Place trybot buildroot under the directory containing current checkout.
491 top_level = os.path.dirname(os.path.realpath(os.path.dirname(repo_dir)))
492 if internal_build:
493 buildroot = os.path.join(top_level, _DEFAULT_INT_BUILDROOT)
494 else:
495 buildroot = os.path.join(top_level, _DEFAULT_EXT_BUILDROOT)
496
497 return buildroot
498
499
500def _BackupPreviousLog(log_file, backup_limit=25):
501 """Rename previous log.
502
503 Args:
504 log_file: The absolute path to the previous log.
505 """
506 if os.path.exists(log_file):
507 old_logs = sorted(glob.glob(log_file + '.*'),
508 key=distutils.version.LooseVersion)
509
510 if len(old_logs) >= backup_limit:
511 os.remove(old_logs[0])
512
513 last = 0
514 if old_logs:
515 last = int(old_logs.pop().rpartition('.')[2])
516
517 os.rename(log_file, log_file + '.' + str(last + 1))
518
519
David James944a48e2012-03-07 12:19:03 -0800520def _RunBuildStagesWrapper(options, build_config):
Brian Harring3fec5a82012-03-01 05:57:03 -0800521 """Helper function that wraps RunBuildStages()."""
522 def IsDistributedBuilder():
523 """Determines whether the build_config should be a DistributedBuilder."""
524 if not options.buildbot:
525 return False
526 elif build_config['build_type'] in _DISTRIBUTED_TYPES:
527 chrome_rev = build_config['chrome_rev']
528 if options.chrome_rev: chrome_rev = options.chrome_rev
529 # We don't do distributed logic to TOT Chrome PFQ's, nor local
530 # chrome roots (e.g. chrome try bots)
531 if chrome_rev not in [constants.CHROME_REV_TOT,
532 constants.CHROME_REV_LOCAL,
533 constants.CHROME_REV_SPEC]:
534 return True
535
536 return False
537
538 # Start tee-ing output to file.
539 log_file = None
540 if options.tee:
541 default_dir = os.path.join(options.buildroot, _DEFAULT_LOG_DIR)
542 dirname = options.log_dir or default_dir
543 log_file = os.path.join(dirname, _BUILDBOT_LOG_FILE)
544
545 cros_lib.SafeMakedirs(dirname)
546 _BackupPreviousLog(log_file)
547
548 try:
549 with cros_lib.AllowDisabling(options.tee, tee.Tee, log_file):
550 cros_lib.Info("cbuildbot executed with args %s"
551 % ' '.join(map(repr, sys.argv)))
552 if IsDistributedBuilder():
David James944a48e2012-03-07 12:19:03 -0800553 buildbot = DistributedBuilder(options, build_config)
Brian Harring3fec5a82012-03-01 05:57:03 -0800554 else:
David James944a48e2012-03-07 12:19:03 -0800555 buildbot = SimpleBuilder(options, build_config)
Brian Harring3fec5a82012-03-01 05:57:03 -0800556
557 if not buildbot.Run():
558 sys.exit(1)
559 finally:
560 if options.tee:
561 cros_lib.Info('Output should be saved to %s' % log_file)
562
563
564# Parser related functions
Ryan Cuicedd8a52012-03-22 02:28:35 -0700565def _CheckLocalPatches(local_patches):
Brian Harring3fec5a82012-03-01 05:57:03 -0800566 """Do an early quick check of the passed-in patches.
567
568 If the branch of a project is not specified we append the current branch the
569 project is on.
570 """
Ryan Cuicedd8a52012-03-22 02:28:35 -0700571 verified_patches = []
572 for patch in local_patches:
Brian Harring3fec5a82012-03-01 05:57:03 -0800573 components = patch.split(':')
574 if len(components) > 2:
575 msg = 'Specify local patches in project[:branch] format.'
576 raise optparse.OptionValueError(msg)
577
578 # validate project
579 project = components[0]
580 if not cros_lib.DoesProjectExist('.', project):
581 raise optparse.OptionValueError('Project %s does not exist.' % project)
582
583 project_dir = cros_lib.GetProjectDir('.', project)
584
585 # If no branch was specified, we use the project's current branch.
586 if len(components) == 1:
587 branch = cros_lib.GetCurrentBranch(project_dir)
588 if not branch:
589 raise optparse.OptionValueError('project %s is not on a branch!'
590 % project)
591 # Append branch information to patch
592 patch = '%s:%s' % (project, branch)
593 else:
594 branch = components[1]
595 if not cros_lib.DoesLocalBranchExist(project_dir, branch):
596 raise optparse.OptionValueError('Project %s does not have branch %s'
597 % (project, branch))
598
Ryan Cuicedd8a52012-03-22 02:28:35 -0700599 verified_patches.append(patch)
Brian Harring3fec5a82012-03-01 05:57:03 -0800600
Ryan Cuicedd8a52012-03-22 02:28:35 -0700601 return verified_patches
Brian Harring3fec5a82012-03-01 05:57:03 -0800602
603
604def _CheckBuildRootOption(_option, _opt_str, value, parser):
605 """Validate and convert buildroot to full-path form."""
606 value = value.strip()
607 if not value or value == '/':
608 raise optparse.OptionValueError('Invalid buildroot specified')
609
610 parser.values.buildroot = os.path.realpath(os.path.expanduser(value))
611
612
613def _CheckLogDirOption(_option, _opt_str, value, parser):
614 """Validate and convert buildroot to full-path form."""
615 parser.values.log_dir = os.path.abspath(os.path.expanduser(value))
616
617
618def _CheckChromeVersionOption(_option, _opt_str, value, parser):
619 """Upgrade other options based on chrome_version being passed."""
620 value = value.strip()
621
622 if parser.values.chrome_rev is None and value:
623 parser.values.chrome_rev = constants.CHROME_REV_SPEC
624
625 parser.values.chrome_version = value
626
627
628def _CheckChromeRootOption(_option, _opt_str, value, parser):
629 """Validate and convert chrome_root to full-path form."""
630 value = value.strip()
631 if not value or value == '/':
632 raise optparse.OptionValueError('Invalid chrome_root specified')
633
634 if parser.values.chrome_rev is None:
635 parser.values.chrome_rev = constants.CHROME_REV_LOCAL
636
637 parser.values.chrome_root = os.path.realpath(os.path.expanduser(value))
638
639
640def _CheckChromeRevOption(_option, _opt_str, value, parser):
641 """Validate the chrome_rev option."""
642 value = value.strip()
643 if value not in constants.VALID_CHROME_REVISIONS:
644 raise optparse.OptionValueError('Invalid chrome rev specified')
645
646 parser.values.chrome_rev = value
647
648
649def _CreateParser():
650 """Generate and return the parser with all the options."""
651 # Parse options
652 usage = "usage: %prog [options] buildbot_config"
653 parser = optparse.OptionParser(usage=usage)
654
655 # Main options
656 parser.add_option('-a', '--all', action='store_true', dest='print_all',
657 default=False,
658 help=('List all of the buildbot configs available. Use '
659 'with the --list option'))
660 parser.add_option('-r', '--buildroot', action='callback', dest='buildroot',
661 type='string', callback=_CheckBuildRootOption,
662 help=('Root directory where source is checked out to, and '
663 'where the build occurs. For external build configs, '
664 "defaults to 'trybot' directory at top level of your "
665 'repo-managed checkout.'))
666 parser.add_option('--chrome_rev', default=None, type='string',
667 action='callback', dest='chrome_rev',
668 callback=_CheckChromeRevOption,
669 help=('Revision of Chrome to use, of type '
670 '[%s]' % '|'.join(constants.VALID_CHROME_REVISIONS)))
Ryan Cuicedd8a52012-03-22 02:28:35 -0700671 parser.add_option('-g', '--gerrit-patches', action='append', default=[],
672 type='string', metavar="'Id1 *int_Id2...IdN'",
Brian Harring3fec5a82012-03-01 05:57:03 -0800673 help=("Space-separated list of short-form Gerrit "
674 "Change-Id's or change numbers to patch. Please "
675 "prepend '*' to internal Change-Id's"))
676 parser.add_option('-l', '--list', action='store_true', dest='list',
677 default=False,
678 help=('List the suggested trybot configs to use. Use '
679 '--all to list all of the available configs.'))
Ryan Cuicedd8a52012-03-22 02:28:35 -0700680 parser.add_option('-p', '--local-patches', action='append', default=[],
Brian Harring3fec5a82012-03-01 05:57:03 -0800681 metavar="'<project1>[:<branch1>]...<projectN>[:<branchN>]'",
682 help=('Space-separated list of project branches with '
683 'patches to apply. Projects are specified by name. '
684 'If no branch is specified the current branch of the '
685 'project will be used.'))
686 parser.add_option('--profile', default=None, type='string', action='store',
687 dest='profile',
688 help=('Name of profile to sub-specify board variant.'))
689 parser.add_option('--remote', default=False, action='store_true',
Brian Harring3fec5a82012-03-01 05:57:03 -0800690 help=('Specifies that this tryjob should be run remotely.'))
691
692 # Advanced options
693 group = optparse.OptionGroup(
694 parser,
695 'Advanced Options',
696 'Caution: use these options at your own risk.')
697
698 group.add_option('--buildbot', dest='buildbot', action='store_true',
699 default=False, help='This is running on a buildbot')
700 group.add_option('--buildnumber',
701 help='build number', type='int', default=0)
702 group.add_option('--chrome_root', default=None, type='string',
703 action='callback', dest='chrome_root',
704 callback=_CheckChromeRootOption,
705 help='Local checkout of Chrome to use.')
706 group.add_option('--chrome_version', default=None, type='string',
707 action='callback', dest='chrome_version',
708 callback=_CheckChromeVersionOption,
709 help='Used with SPEC logic to force a particular SVN '
710 'revision of chrome rather than the latest.')
711 group.add_option('--clobber', action='store_true', dest='clobber',
712 default=False,
713 help='Clears an old checkout before syncing')
714 group.add_option('--lkgm', action='store_true', dest='lkgm', default=False,
715 help='Sync to last known good manifest blessed by PFQ')
716 parser.add_option('--log_dir', action='callback', dest='log_dir',
717 type='string', callback=_CheckLogDirOption,
718 help=('Directory where logs are stored.'))
719 group.add_option('--maxarchives', dest='max_archive_builds',
720 default=3, type='int',
721 help="Change the local saved build count limit.")
722 group.add_option('--noarchive', action='store_false', dest='archive',
723 default=True,
724 help="Don't run archive stage.")
725 group.add_option('--nobuild', action='store_false', dest='build',
726 default=True,
727 help="Don't actually build (for cbuildbot dev")
728 group.add_option('--noclean', action='store_false', dest='clean',
729 default=True,
730 help="Don't clean the buildroot")
731 group.add_option('--noprebuilts', action='store_false', dest='prebuilts',
732 default=True,
733 help="Don't upload prebuilts.")
734 group.add_option('--nosync', action='store_false', dest='sync',
735 default=True,
736 help="Don't sync before building.")
737 group.add_option('--nocgroups', action='store_false', dest='cgroups',
738 default=True,
739 help='Disable cbuildbots usage of cgroups.')
Ryan Cuiadd49122012-03-21 22:19:58 -0700740 group.add_option('--notests', action='store_false', dest='tests',
741 default=True,
742 help='Override values from buildconfig and run no tests.')
743 group.add_option('--nouprev', action='store_false', dest='uprev',
744 default=True,
745 help='Override values from buildconfig and never uprev.')
Brian Harring3fec5a82012-03-01 05:57:03 -0800746 group.add_option('--reference-repo', action='store', default=None,
747 dest='reference_repo',
748 help='Reuse git data stored in an existing repo '
749 'checkout. This can drastically reduce the network '
750 'time spent setting up the trybot checkout. By '
751 "default, if this option isn't given but cbuildbot "
752 'is invoked from a repo checkout, cbuildbot will '
753 'use the repo root.')
Ryan Cuicedd8a52012-03-22 02:28:35 -0700754 # Indicates this is running on a remote trybot machine. '
Ryan Cuiba41ad32012-03-08 17:15:29 -0800755 group.add_option('--remote-trybot', dest='remote_trybot', action='store_true',
Ryan Cuicedd8a52012-03-22 02:28:35 -0700756 default=False, help=optparse.SUPPRESS_HELP)
757 # Patches uploaded by trybot client when run using the -p option.
758 group.add_option('--remote-patches', action='append', default=[],
759 help=optparse.SUPPRESS_HELP)
760 group.add_option('--resume', action='store_true', default=False,
Ryan Cuiadd49122012-03-21 22:19:58 -0700761 help='Skip stages already successfully completed.')
Brian Harring3fec5a82012-03-01 05:57:03 -0800762 group.add_option('--timeout', action='store', type='int', default=0,
763 help="Specify the maximum amount of time this job can run "
764 "for, at which point the build will be aborted. If "
765 "set to zero, then there is no timeout")
Brian Harring3fec5a82012-03-01 05:57:03 -0800766 group.add_option('--validation_pool', default=None,
767 help='Path to a pickled validation pool. Intended for use '
768 'only with the commit queue.')
769 group.add_option('--version', dest='force_version', default=None,
770 help='Used with manifest logic. Forces use of this version '
771 'rather than create or get latest.')
772
773 parser.add_option_group(group)
774
775 # Debug options
776 group = optparse.OptionGroup(parser, "Debug Options")
777
Ryan Cui85867972012-02-23 18:21:49 -0800778 group.add_option('--debug', action='store_true', default=None,
Brian Harring3fec5a82012-03-01 05:57:03 -0800779 help='Override some options to run as a developer.')
780 group.add_option('--dump_config', action='store_true', dest='dump_config',
781 default=False,
782 help='Dump out build config options, and exit.')
783 group.add_option('--notee', action='store_false', dest='tee', default=True,
784 help="Disable logging and internal tee process. Primarily "
785 "used for debugging cbuildbot itself.")
786 parser.add_option_group(group)
787 return parser
788
789
Ryan Cui85867972012-02-23 18:21:49 -0800790def _FinishParsing(options, args):
791 """Perform some parsing tasks that need to take place after optparse.
792
793 This function needs to be easily testable! Keep it free of
794 environment-dependent code. Put more detailed usage validation in
795 _PostParseCheck().
Brian Harring3fec5a82012-03-01 05:57:03 -0800796
797 Args:
Ryan Cui85867972012-02-23 18:21:49 -0800798 options, args: The options/args object returned by optparse
Brian Harring3fec5a82012-03-01 05:57:03 -0800799 """
Brian Harring3fec5a82012-03-01 05:57:03 -0800800 if options.chrome_root:
801 if options.chrome_rev != constants.CHROME_REV_LOCAL:
802 cros_lib.Die('Chrome rev must be %s if chrome_root is set.' %
803 constants.CHROME_REV_LOCAL)
804 else:
805 if options.chrome_rev == constants.CHROME_REV_LOCAL:
806 cros_lib.Die('Chrome root must be set if chrome_rev is %s.' %
807 constants.CHROME_REV_LOCAL)
808
809 if options.chrome_version:
810 if options.chrome_rev != constants.CHROME_REV_SPEC:
811 cros_lib.Die('Chrome rev must be %s if chrome_version is set.' %
812 constants.CHROME_REV_SPEC)
813 else:
814 if options.chrome_rev == constants.CHROME_REV_SPEC:
815 cros_lib.Die('Chrome rev must not be %s if chrome_version is not set.' %
816 constants.CHROME_REV_SPEC)
817
Ryan Cuicedd8a52012-03-22 02:28:35 -0700818 if options.remote and not (options.gerrit_patches or options.local_patches):
Brian Harring3fec5a82012-03-01 05:57:03 -0800819 cros_lib.Die('Must provide patches when running with --remote.')
820
821 if len(args) > 1 and not options.remote:
822 cros_lib.Die('Multiple configs not supported if not running with --remote.')
823
Ryan Cuiba41ad32012-03-08 17:15:29 -0800824 if options.buildbot and options.remote_trybot:
825 cros_lib.Die('--buildbot and --remote-trybot cannot be used together.')
826
Ryan Cui85867972012-02-23 18:21:49 -0800827 # Record whether --debug was set explicitly vs. it was inferred.
828 options.debug_forced = False
829 if options.debug:
830 options.debug_forced = True
831 else:
Ryan Cui16ca5812012-03-08 20:34:27 -0800832 # We don't set debug by default for
833 # 1. --buildbot invocations.
834 # 2. --remote invocations, because it needs to push changes to the tryjob
835 # repo.
836 options.debug = not options.buildbot and not options.remote
Brian Harring3fec5a82012-03-01 05:57:03 -0800837
Brian Harring3fec5a82012-03-01 05:57:03 -0800838
Ryan Cuicedd8a52012-03-22 02:28:35 -0700839def _SplitAndFlatten(appended_items):
840 """Given a list of space-separated items, split into flattened list.
841
842 Given ['abc def', 'hij'] return ['abc', 'def', 'hij'].
843 Arguments:
844 appended_items: List of delimiter-separated items.
845
846 Returns: Flattened list.
847 """
848 new_list = []
849 for item in appended_items:
Mike Frysinger4bd23892012-03-26 15:08:52 -0400850 new_list.extend(item.split())
Ryan Cuicedd8a52012-03-22 02:28:35 -0700851 return new_list
852
853
Ryan Cui85867972012-02-23 18:21:49 -0800854def _PostParseCheck(options, args):
855 """Perform some usage validation after we've parsed the arguments
Brian Harring3fec5a82012-03-01 05:57:03 -0800856
Ryan Cui85867972012-02-23 18:21:49 -0800857 Args:
858 options/args: The options/args object returned by optparse
859 """
860 if not options.resume:
Ryan Cuicedd8a52012-03-22 02:28:35 -0700861 options.gerrit_patches = _SplitAndFlatten(options.gerrit_patches)
862 options.remote_patches = _SplitAndFlatten(options.remote_patches)
Ryan Cui85867972012-02-23 18:21:49 -0800863 try:
864 # TODO(rcui): Split this into two stages, one that parses, another that
865 # validates. Parsing step will be called by _FinishParsing().
Ryan Cuicedd8a52012-03-22 02:28:35 -0700866 options.local_patches = _CheckLocalPatches(
867 _SplitAndFlatten(options.local_patches))
Ryan Cui85867972012-02-23 18:21:49 -0800868 except optparse.OptionValueError as e:
869 cros_lib.Die(str(e))
870
871
872def _ParseCommandLine(parser, argv):
873 """Completely parse the commandline arguments"""
Brian Harring3fec5a82012-03-01 05:57:03 -0800874 (options, args) = parser.parse_args(argv)
875 # Strip out null arguments.
876 # TODO(rcui): Remove when buildbot is fixed
877 args = [arg for arg in args if arg]
Ryan Cui85867972012-02-23 18:21:49 -0800878 _FinishParsing(options, args)
879 return options, args
880
881
882def main(argv):
883 # Set umask to 022 so files created by buildbot are readable.
884 os.umask(022)
885
886 if cros_lib.IsInsideChroot():
887 cros_lib.Die('Please run cbuildbot from outside the chroot.')
888
889 parser = _CreateParser()
890 (options, args) = _ParseCommandLine(parser, argv)
Brian Harring3fec5a82012-03-01 05:57:03 -0800891
892 if options.list:
893 _PrintValidConfigs(not options.print_all)
894 sys.exit(0)
895
896 _PostParseCheck(options, args)
897
898 if options.remote:
Ryan Cui16ca5812012-03-08 20:34:27 -0800899 cros_lib.DebugLevel.SetDebugLevel(cros_lib.DebugLevel.WARNING)
900
Brian Harring3fec5a82012-03-01 05:57:03 -0800901 # Verify configs are valid.
902 for bot in args:
903 _GetConfig(bot)
904
905 # Verify gerrit patches are valid.
Ryan Cui16ca5812012-03-08 20:34:27 -0800906 print 'Verifying patches...'
Ryan Cuicedd8a52012-03-22 02:28:35 -0700907 _, local_patches = _PreProcessPatches(options.gerrit_patches,
908 options.local_patches)
Ryan Cui16ca5812012-03-08 20:34:27 -0800909 print 'Submitting tryjob...'
Ryan Cuicedd8a52012-03-22 02:28:35 -0700910 tryjob = remote_try.RemoteTryJob(options, args, local_patches)
Ryan Cui16ca5812012-03-08 20:34:27 -0800911 tryjob.Submit(dryrun=options.debug)
912 print 'Tryjob submitted!'
913 print ('Go to %s to view the status of your job.'
Ryan Cui4906e1c2012-04-03 20:09:34 -0700914 % tryjob.GetTrybotWaterfallLink())
Brian Harring3fec5a82012-03-01 05:57:03 -0800915 sys.exit(0)
916
917 if args:
918 # Only expecting one config
919 bot_id = args[-1]
920 build_config = _GetConfig(bot_id)
921 else:
922 parser.error('Invalid usage. Use -h to see usage.')
923
924 if options.reference_repo is None:
925 repo_path = os.path.join(constants.SOURCE_ROOT, '.repo')
926 # If we're being run from a repo checkout, reuse the repo's git pool to
927 # cut down on sync time.
928 if os.path.exists(repo_path):
929 options.reference_repo = constants.SOURCE_ROOT
930 elif options.reference_repo:
931 if not os.path.exists(options.reference_repo):
932 parser.error('Reference path %s does not exist'
933 % (options.reference_repo,))
934 elif not os.path.exists(os.path.join(options.reference_repo, '.repo')):
935 parser.error('Reference path %s does not look to be the base of a '
936 'repo checkout; no .repo exists in the root.'
937 % (options.reference_repo,))
Brian Harring470f6112012-03-02 11:47:10 -0800938 if options.buildbot:
939 if not options.cgroups:
940 parser.error('Options --buildbot and --nocgroups cannot be used '
941 'together. Cgroup support is required for buildbot mode.')
942 if not cgroups.Cgroup.CgroupsSupported():
943 parser.error('Option --buildbot was given, but this system does not '
944 'support cgroups. Failing.')
Brian Harring3fec5a82012-03-01 05:57:03 -0800945
Brian Harring351ce442012-03-09 16:38:14 -0800946 missing = []
947 for program in _BUILDBOT_REQUIRED_BINARIES:
948 ret = cros_lib.RunCommand('which %s' % program, shell=True,
949 redirect_stderr=True, redirect_stdout=True,
950 error_code_ok=True, print_cmd=False)
951 if ret.returncode != 0:
952 missing.append(program)
953
954 if missing:
955 parser.error("Option --buildbot requires the following binaries which "
956 "couldn't be found in $PATH: %s"
957 % (', '.join(missing)))
958
Brian Harring3fec5a82012-03-01 05:57:03 -0800959 if options.reference_repo:
960 options.reference_repo = os.path.abspath(options.reference_repo)
961
962 if options.dump_config:
963 # This works, but option ordering is bad...
964 print 'Configuration %s:' % bot_id
965 pretty_printer = pprint.PrettyPrinter(indent=2)
966 pretty_printer.pprint(build_config)
967 sys.exit(0)
968
969 if not options.buildroot:
970 if options.buildbot:
971 parser.error('Please specify a buildroot with the --buildroot option.')
Brian Harring470f6112012-03-02 11:47:10 -0800972 options.buildroot = _DetermineDefaultBuildRoot(build_config['internal'])
973 # We use a marker file in the buildroot to indicate the user has
974 # consented to using this directory.
975 if not os.path.exists(repository.GetTrybotMarkerPath(options.buildroot)):
976 _ConfirmBuildRoot(options.buildroot)
Brian Harring3fec5a82012-03-01 05:57:03 -0800977
978 # Sanity check of buildroot- specifically that it's not pointing into the
979 # midst of an existing repo since git-repo doesn't support nesting.
Brian Harring3fec5a82012-03-01 05:57:03 -0800980 if (not repository.IsARepoRoot(options.buildroot) and
David James6b80dc62012-02-29 15:34:40 -0800981 repository.InARepoRepository(options.buildroot)):
Brian Harring3fec5a82012-03-01 05:57:03 -0800982 parser.error('Configured buildroot %s points into a repository checkout, '
983 'rather than the root of it. This is not supported.'
984 % options.buildroot)
985
Brian Harringa184efa2012-03-04 11:51:25 -0800986 with cleanup.EnforcedCleanupSection() as critical_section:
987 with sudo.SudoKeepAlive():
988 with cros_lib.AllowDisabling(options.cgroups,
Brian Harring4e6412d2012-03-09 20:54:02 -0800989 cgroups.SimpleContainChildren, 'cbuildbot'):
Brian Harringa184efa2012-03-04 11:51:25 -0800990 # Mark everything between EnforcedCleanupSection and here as having to
991 # be rolled back via the contextmanager cleanup handlers. This ensures
992 # that sudo bits cannot outlive cbuildbot, that anything cgroups
993 # would kill gets killed, etc.
994 critical_section.ForkWatchdog()
995
996 with cros_lib.AllowDisabling(options.timeout > 0,
997 cros_lib.Timeout, options.timeout):
998 if not options.buildbot:
999 build_config = cbuildbot_config.OverrideConfigForTrybot(
Ryan Cui3d6b4742012-03-14 11:42:24 -07001000 build_config,
1001 options.remote_trybot)
Brian Harringa184efa2012-03-04 11:51:25 -08001002
1003 _RunBuildStagesWrapper(options, build_config)