blob: 6c615d5ff76be3fd29cbaf425b69dd93585451b2 [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
15import optparse
16import os
17import pprint
18import sys
19
20from chromite.buildbot import builderstage as bs
21from chromite.buildbot import cbuildbot_background as background
22from chromite.buildbot import cbuildbot_config
23from chromite.buildbot import cbuildbot_stages as stages
24from chromite.buildbot import cbuildbot_results as results_lib
Brian Harring3fec5a82012-03-01 05:57:03 -080025from chromite.buildbot import constants
26from chromite.buildbot import gerrit_helper
27from chromite.buildbot import patch as cros_patch
28from chromite.buildbot import remote_try
29from chromite.buildbot import repository
30from chromite.buildbot import tee
31
Brian Harringc92a7012012-02-29 10:11:34 -080032from chromite.lib import cgroups
Brian Harring3fec5a82012-03-01 05:57:03 -080033from chromite.lib import cros_build_lib as cros_lib
34from chromite.lib import sudo
35
36cros_lib.STRICT_SUDO = True
37
38_DEFAULT_LOG_DIR = 'cbuildbot_logs'
39_BUILDBOT_LOG_FILE = 'cbuildbot.log'
40_DEFAULT_EXT_BUILDROOT = 'trybot'
41_DEFAULT_INT_BUILDROOT = 'trybot-internal'
42_PATH_TO_CBUILDBOT = 'chromite/bin/cbuildbot'
43_DISTRIBUTED_TYPES = [constants.COMMIT_QUEUE_TYPE, constants.PFQ_TYPE,
44 constants.CANARY_TYPE, constants.CHROME_PFQ_TYPE,
45 constants.PALADIN_TYPE]
46
47
48def _PrintValidConfigs(trybot_only=True):
49 """Print a list of valid buildbot configs.
50
51 Arguments:
52 trybot_only: Only print selected trybot configs, as specified by the
53 'trybot_list' config setting.
54 """
55 COLUMN_WIDTH = 45
56 print 'config'.ljust(COLUMN_WIDTH), 'description'
57 print '------'.ljust(COLUMN_WIDTH), '-----------'
58 config_names = cbuildbot_config.config.keys()
59 config_names.sort()
60 for name in config_names:
61 if not trybot_only or cbuildbot_config.config[name]['trybot_list']:
62 desc = ''
63 if cbuildbot_config.config[name]['description']:
64 desc = cbuildbot_config.config[name]['description']
65
66 print name.ljust(COLUMN_WIDTH), desc
67
68
69def _GetConfig(config_name):
70 """Gets the configuration for the build"""
71 if not cbuildbot_config.config.has_key(config_name):
72 print 'Non-existent configuration %s specified.' % config_name
73 print 'Please specify one of:'
74 _PrintValidConfigs()
75 sys.exit(1)
76
77 result = cbuildbot_config.config[config_name]
78
79 return result
80
81
82def _GetChromiteTrackingBranch():
83 """Returns the current Chromite tracking_branch.
84
85 If Chromite is on a detached HEAD, we assume it's the manifest branch.
86 """
87 cwd = os.path.dirname(os.path.realpath(__file__))
88 current_branch = cros_lib.GetCurrentBranch(cwd)
89 if current_branch:
90 (_, tracking_branch) = cros_lib.GetPushBranch(current_branch, cwd)
91 else:
92 tracking_branch = cros_lib.GetManifestDefaultBranch(cwd)
93
94 return tracking_branch
95
96
97def _CheckBuildRootBranch(buildroot, tracking_branch):
98 """Make sure buildroot branch is the same as Chromite branch."""
99 manifest_branch = cros_lib.GetManifestDefaultBranch(buildroot)
100 if manifest_branch != tracking_branch:
101 cros_lib.Die('Chromite is not on same branch as buildroot checkout\n' +
102 'Chromite is on branch %s.\n' % tracking_branch +
103 'Buildroot checked out to %s\n' % manifest_branch)
104
105
106def _PreProcessPatches(gerrit_patches, local_patches):
107 """Validate patches ASAP to catch user errors. Also generate patch info.
108
109 Args:
110 gerrit_patches: List of gerrit CL ID's passed in by user.
111 local_patches: List of local project branches to generate patches from.
112
113 Returns:
114 A tuple containing a list of cros_patch.GerritPatch and a list of
115 cros_patch.LocalPatch objects.
116 """
117 gerrit_patch_info = []
118 local_patch_info = []
119
120 try:
121 if gerrit_patches:
122 gerrit_patch_info = gerrit_helper.GetGerritPatchInfo(gerrit_patches)
123 for patch in gerrit_patch_info:
124 if patch.IsAlreadyMerged():
125 cros_lib.Warning('Patch %s has already been merged.' % str(patch))
126 except gerrit_helper.GerritException as e:
127 cros_lib.Die(str(e))
128
129 try:
130 if local_patches:
131 local_patch_info = cros_patch.PrepareLocalPatches(
132 local_patches,
133 _GetChromiteTrackingBranch())
134
135 except cros_patch.PatchException as e:
136 cros_lib.Die(str(e))
137
138 return gerrit_patch_info, local_patch_info
139
140
141def _IsIncrementalBuild(buildroot, clobber):
142 """Returns True if we are reusing an existing buildroot."""
143 repo_dir = os.path.join(buildroot, '.repo')
144 return not clobber and os.path.isdir(repo_dir)
145
146
147class Builder(object):
148 """Parent class for all builder types.
149
150 This class functions as a parent class for various build types. It's intended
151 use is builder_instance.Run().
152
153 Vars:
154 bot_id: Name of the build configuration.
155 build_config: The configuration dictionary from cbuildbot_config.
156 options: The options provided from optparse in main().
157 completed_stages_file: Where we store resume state.
158 archive_url: Where our artifacts for this builder will be archived.
159 tracking_branch: The tracking branch for this build.
160 release_tag: The associated "chrome os version" of this build.
161 gerrit_patches: Gerrit patches to be included in build.
162 local_patches: Local patches to be included in build.
163 """
164
165 def __init__(self, bot_id, options, build_config):
166 """Initializes instance variables. Must be called by all subclasses."""
167 self.bot_id = bot_id
168 self.build_config = build_config
169 self.options = options
170
171 # TODO, Remove here and in config after bug chromium-os:14649 is fixed.
172 if self.build_config['chromeos_official']:
173 os.environ['CHROMEOS_OFFICIAL'] = '1'
174
175 self.completed_stages_file = os.path.join(options.buildroot,
176 '.completed_stages')
177 self.archive_urls = {}
178 self.release_tag = None
179 self.tracking_branch = _GetChromiteTrackingBranch()
180 self.gerrit_patches = None
181 self.local_patches = None
182
183 def Initialize(self):
184 """Runs through the initialization steps of an actual build."""
185 if self.options.resume and os.path.exists(self.completed_stages_file):
186 with open(self.completed_stages_file, 'r') as load_file:
187 results_lib.Results.RestoreCompletedStages(load_file)
188
189 # We only want to do this if we need to patch changes.
190 if not results_lib.Results.GetPrevious().get(
191 self._GetStageInstance(stages.PatchChangesStage, None, None).name):
192 self.gerrit_patches, self.local_patches = _PreProcessPatches(
193 self.options.gerrit_patches, self.options.local_patches)
194
195 bs.BuilderStage.SetTrackingBranch(self.tracking_branch)
196
197 # Check branch matching early.
198 if _IsIncrementalBuild(self.options.buildroot, self.options.clobber):
199 _CheckBuildRootBranch(self.options.buildroot, self.tracking_branch)
200
201 self._RunStage(stages.CleanUpStage)
202
203 def _GetStageInstance(self, stage, *args, **kwargs):
204 """Helper function to get an instance given the args.
205
206 Useful as almost all stages just take in bot_id, options, and build_config.
207 """
208 return stage(self.bot_id, self.options, self.build_config, *args, **kwargs)
209
210 def _SetReleaseTag(self):
211 """Sets the release tag from the manifest_manager.
212
213 Must be run after sync stage as syncing enables us to have a release tag.
214 """
215 # Extract version we have decided to build into self.release_tag.
216 manifest_manager = stages.ManifestVersionedSyncStage.manifest_manager
217 if manifest_manager:
218 self.release_tag = manifest_manager.current_version
219
220 def _RunStage(self, stage, *args, **kwargs):
221 """Wrapper to run a stage."""
222 stage_instance = self._GetStageInstance(stage, *args, **kwargs)
223 return stage_instance.Run()
224
225 def GetSyncInstance(self):
226 """Returns an instance of a SyncStage that should be run.
227
228 Subclasses must override this method.
229 """
230 raise NotImplementedError()
231
232 def RunStages(self):
233 """Subclasses must override this method. Runs the appropriate code."""
234 raise NotImplementedError()
235
236 def _WriteCheckpoint(self):
237 """Drops a completed stages file with current state."""
238 with open(self.completed_stages_file, 'w+') as save_file:
239 results_lib.Results.SaveCompletedStages(save_file)
240
241 def _ShouldReExecuteInBuildRoot(self):
242 """Returns True if this build should be re-executed in the buildroot."""
243 abs_buildroot = os.path.abspath(self.options.buildroot)
244 return not os.path.abspath(__file__).startswith(abs_buildroot)
245
246 def _ReExecuteInBuildroot(self, sync_instance):
247 """Reexecutes self in buildroot and returns True if build succeeds.
248
249 This allows the buildbot code to test itself when changes are patched for
250 buildbot-related code. This is a no-op if the buildroot == buildroot
251 of the running chromite checkout.
252
253 Args:
254 sync_instance: Instance of the sync stage that was run to sync.
255
256 Returns:
257 True if the Build succeeded.
258 """
259 # If we are resuming, use last checkpoint.
260 if not self.options.resume:
261 self._WriteCheckpoint()
262
263 # Re-write paths to use absolute paths.
264 # Suppress any timeout options given from the commandline in the
265 # invoked cbuildbot; our timeout will enforce it instead.
266 args_to_append = ['--resume', '--timeout', '0', '--buildroot',
267 os.path.abspath(self.options.buildroot)]
268
269 if self.options.chrome_root:
270 args_to_append += ['--chrome_root',
271 os.path.abspath(self.options.chrome_root)]
272
273 if stages.ManifestVersionedSyncStage.manifest_manager:
274 ver = stages.ManifestVersionedSyncStage.manifest_manager.current_version
275 args_to_append += ['--version', ver]
276
277 if isinstance(sync_instance, stages.CommitQueueSyncStage):
278 vp_file = sync_instance.SaveValidationPool()
279 args_to_append += ['--validation_pool', vp_file]
280
281 # Re-run the command in the buildroot.
282 # Finally, be generous and give the invoked cbuildbot 30s to shutdown
283 # when something occurs. It should exit quicker, but the sigterm may
284 # hit while the system is particularly busy.
285 return_obj = cros_lib.RunCommand(
286 [_PATH_TO_CBUILDBOT] + sys.argv[1:] + args_to_append,
287 cwd=self.options.buildroot, error_code_ok=True, kill_timeout=30)
288 return return_obj.returncode == 0
289
290 def Run(self):
291 """Main runner for this builder class. Runs build and prints summary."""
292 print_report = True
293 success = True
294 try:
295 self.Initialize()
296 sync_instance = self.GetSyncInstance()
297 sync_instance.Run()
298 self._SetReleaseTag()
299
300 if self.gerrit_patches or self.local_patches:
301 self._RunStage(stages.PatchChangesStage,
302 self.gerrit_patches, self.local_patches)
303
304 if self._ShouldReExecuteInBuildRoot():
305 print_report = False
306 success = self._ReExecuteInBuildroot(sync_instance)
307 else:
308 self.RunStages()
309
310 finally:
311 if print_report:
312 self._WriteCheckpoint()
313 print '\n\n\n@@@BUILD_STEP Report@@@\n'
314 results_lib.Results.Report(sys.stdout, self.archive_urls,
315 self.release_tag)
316 success = results_lib.Results.BuildSucceededSoFar()
317
318 return success
319
320
321class SimpleBuilder(Builder):
322 """Builder that performs basic vetting operations."""
323
324 def GetSyncInstance(self):
325 """Sync to lkgm or TOT as necessary.
326
327 Returns: the instance of the sync stage that was run.
328 """
329 if self.options.lkgm or self.build_config['use_lkgm']:
330 sync_stage = self._GetStageInstance(stages.LKGMSyncStage)
331 else:
332 sync_stage = self._GetStageInstance(stages.SyncStage)
333
334 return sync_stage
335
336 def _RunStagesForBoard(self, board):
337 """Run board-specific stages for the specified board.
338
339 Returns: True if the stages completed successfully.
340 """
341 archive_stage = self._GetStageInstance(stages.ArchiveStage, board)
342 self._RunStage(stages.BuildTargetStage, board, archive_stage)
343
344 bg = background.BackgroundSteps()
345
346 vm_test_stage = self._GetStageInstance(stages.VMTestStage, board,
347 archive_stage)
348 chrome_test_stage = self._GetStageInstance(stages.ChromeTestStage,
349 board, archive_stage)
350 unit_test_stage = self._GetStageInstance(stages.UnitTestStage, board)
351 prebuilts_stage = self._GetStageInstance(stages.UploadPrebuiltsStage,
352 board)
353 self.archive_urls[board] = archive_stage.GetDownloadUrl()
354 try:
355 bg.AddStep(archive_stage.Run)
356 bg.start()
357
358 steps = []
359 if self.build_config['vm_tests']:
360 steps.append(vm_test_stage.Run)
361 if self.build_config['chrome_tests']:
362 steps.append(chrome_test_stage.Run)
Chris Sosab50dc932012-03-01 14:00:58 -0800363
364 # We can not run hw tests without archiving the payloads.
365 if self.options.archive:
366 for suite in self.build_config['hw_tests']:
367 hw_test_stage = self._GetStageInstance(
368 stages.HWTestStage,
369 board,
370 archive_stage=archive_stage,
371 suite=suite)
372 steps.append(hw_test_stage.Run)
373
Brian Harring3fec5a82012-03-01 05:57:03 -0800374 steps += [unit_test_stage.Run, prebuilts_stage.Run]
375 # Run the steps in parallel. If any exceptions occur, RunParallelSteps
376 # will combine them into a single BackgroundException and throw it.
377 background.RunParallelSteps(steps)
378
379 return True
380
381 # We skipped out of this build block early, so one of the tests we ran in
382 # the background or in parallel must have failed.
383 except background.BackgroundException:
384 return False
385 finally:
386 # Wait for archive stage to finish. Ignore any errors.
387 while not bg.Empty(): bg.WaitForStep()
388 bg.join()
389
390 def RunStages(self):
391 """Runs through build process."""
392 self._RunStage(stages.BuildBoardStage)
393
394 # TODO(sosa): Split these out into classes.
395 success = True
396 if self.build_config['build_type'] == constants.CHROOT_BUILDER_TYPE:
397 self._RunStage(stages.SDKTestStage)
398 self._RunStage(stages.UploadPrebuiltsStage,
399 constants.CHROOT_BUILDER_BOARD)
400 elif self.build_config['build_type'] == constants.REFRESH_PACKAGES_TYPE:
401 self._RunStage(stages.RefreshPackageStatusStage)
402 else:
403 self._RunStage(stages.UprevStage)
404 for board in self.build_config['boards']:
405 if not self._RunStagesForBoard(board):
406 success = False
407
408 return success
409
410
411class DistributedBuilder(SimpleBuilder):
412 """Build class that has special logic to handle distributed builds.
413
414 These builds sync using git/manifest logic in manifest_versions. In general
415 they use a non-distributed builder code for the bulk of the work.
416 """
417 def __init__(self, bot_id, options, build_config):
418 """Initializes a buildbot builder.
419
420 Extra variables:
421 completion_stage_class: Stage used to complete a build. Set in the Sync
422 stage.
423 """
424 super(DistributedBuilder, self).__init__(bot_id, options, build_config)
425 self.completion_stage_class = None
426
427 def GetSyncInstance(self):
428 """Syncs the tree using one of the distributed sync logic paths.
429
430 Returns: the instance of the sync stage that was run.
431 """
432 # Determine sync class to use. CQ overrides PFQ bits so should check it
433 # first.
434 if cbuildbot_config.IsCQType(self.build_config['build_type']):
435 sync_stage = self._GetStageInstance(stages.CommitQueueSyncStage)
436 self.completion_stage_class = stages.CommitQueueCompletionStage
437 elif cbuildbot_config.IsPFQType(self.build_config['build_type']):
438 sync_stage = self._GetStageInstance(stages.LKGMCandidateSyncStage)
439 self.completion_stage_class = stages.LKGMCandidateSyncCompletionStage
440 else:
441 sync_stage = self._GetStageInstance(stages.ManifestVersionedSyncStage)
442 self.completion_stage_class = stages.ManifestVersionedSyncCompletionStage
443
444 return sync_stage
445
446 def Publish(self, was_build_successful):
447 """Completes build by publishing any required information."""
448 completion_stage = self._GetStageInstance(self.completion_stage_class,
449 was_build_successful)
450 completion_stage.Run()
451 name = completion_stage.name
452 if not results_lib.Results.WasStageSuccessful(name):
453 should_publish_changes = False
454 else:
455 should_publish_changes = (self.build_config['master'] and
456 was_build_successful)
457
458 if should_publish_changes:
459 self._RunStage(stages.PublishUprevChangesStage)
460
461 def RunStages(self):
462 """Runs simple builder logic and publishes information to overlays."""
463 was_build_successful = False
464 try:
465 was_build_successful = super(DistributedBuilder, self).RunStages()
466 except SystemExit as ex:
467 # If a stage calls sys.exit(0), it's exiting with success, so that means
468 # we should mark ourselves as successful.
469 if ex.code == 0:
470 was_build_successful = True
471 raise
472 finally:
473 self.Publish(was_build_successful)
474
475 return was_build_successful
476
477
478def _ConfirmBuildRoot(buildroot):
479 """Confirm with user the inferred buildroot, and mark it as confirmed."""
480 warning = 'Using default directory %s as buildroot' % buildroot
481 response = cros_lib.YesNoPrompt(default=cros_lib.NO, warning=warning,
482 full=True)
483 if response == cros_lib.NO:
484 print('Please specify a buildroot with the --buildroot option.')
485 sys.exit(0)
486
487 if not os.path.exists(buildroot):
488 os.mkdir(buildroot)
489
490 repository.CreateTrybotMarker(buildroot)
491
492
493def _DetermineDefaultBuildRoot(internal_build):
494 """Default buildroot to be under the directory that contains current checkout.
495
496 Arguments:
497 internal_build: Whether the build is an internal build
498 """
499 repo_dir = cros_lib.FindRepoDir()
500 if not repo_dir:
501 cros_lib.Die('Could not find root of local checkout. Please specify'
502 'using --buildroot option.')
503
504 # Place trybot buildroot under the directory containing current checkout.
505 top_level = os.path.dirname(os.path.realpath(os.path.dirname(repo_dir)))
506 if internal_build:
507 buildroot = os.path.join(top_level, _DEFAULT_INT_BUILDROOT)
508 else:
509 buildroot = os.path.join(top_level, _DEFAULT_EXT_BUILDROOT)
510
511 return buildroot
512
513
514def _BackupPreviousLog(log_file, backup_limit=25):
515 """Rename previous log.
516
517 Args:
518 log_file: The absolute path to the previous log.
519 """
520 if os.path.exists(log_file):
521 old_logs = sorted(glob.glob(log_file + '.*'),
522 key=distutils.version.LooseVersion)
523
524 if len(old_logs) >= backup_limit:
525 os.remove(old_logs[0])
526
527 last = 0
528 if old_logs:
529 last = int(old_logs.pop().rpartition('.')[2])
530
531 os.rename(log_file, log_file + '.' + str(last + 1))
532
533
534def _RunBuildStagesWrapper(bot_id, options, build_config):
535 """Helper function that wraps RunBuildStages()."""
536 def IsDistributedBuilder():
537 """Determines whether the build_config should be a DistributedBuilder."""
538 if not options.buildbot:
539 return False
540 elif build_config['build_type'] in _DISTRIBUTED_TYPES:
541 chrome_rev = build_config['chrome_rev']
542 if options.chrome_rev: chrome_rev = options.chrome_rev
543 # We don't do distributed logic to TOT Chrome PFQ's, nor local
544 # chrome roots (e.g. chrome try bots)
545 if chrome_rev not in [constants.CHROME_REV_TOT,
546 constants.CHROME_REV_LOCAL,
547 constants.CHROME_REV_SPEC]:
548 return True
549
550 return False
551
552 # Start tee-ing output to file.
553 log_file = None
554 if options.tee:
555 default_dir = os.path.join(options.buildroot, _DEFAULT_LOG_DIR)
556 dirname = options.log_dir or default_dir
557 log_file = os.path.join(dirname, _BUILDBOT_LOG_FILE)
558
559 cros_lib.SafeMakedirs(dirname)
560 _BackupPreviousLog(log_file)
561
562 try:
563 with cros_lib.AllowDisabling(options.tee, tee.Tee, log_file):
564 cros_lib.Info("cbuildbot executed with args %s"
565 % ' '.join(map(repr, sys.argv)))
566 if IsDistributedBuilder():
567 buildbot = DistributedBuilder(bot_id, options, build_config)
568 else:
569 buildbot = SimpleBuilder(bot_id, options, build_config)
570
571 if not buildbot.Run():
572 sys.exit(1)
573 finally:
574 if options.tee:
575 cros_lib.Info('Output should be saved to %s' % log_file)
576
577
578# Parser related functions
579
580
581def _CheckAndSplitLocalPatches(options):
582 """Do an early quick check of the passed-in patches.
583
584 If the branch of a project is not specified we append the current branch the
585 project is on.
586 """
587 patch_args = options.local_patches.split()
588 options.local_patches = []
589 for patch in patch_args:
590 components = patch.split(':')
591 if len(components) > 2:
592 msg = 'Specify local patches in project[:branch] format.'
593 raise optparse.OptionValueError(msg)
594
595 # validate project
596 project = components[0]
597 if not cros_lib.DoesProjectExist('.', project):
598 raise optparse.OptionValueError('Project %s does not exist.' % project)
599
600 project_dir = cros_lib.GetProjectDir('.', project)
601
602 # If no branch was specified, we use the project's current branch.
603 if len(components) == 1:
604 branch = cros_lib.GetCurrentBranch(project_dir)
605 if not branch:
606 raise optparse.OptionValueError('project %s is not on a branch!'
607 % project)
608 # Append branch information to patch
609 patch = '%s:%s' % (project, branch)
610 else:
611 branch = components[1]
612 if not cros_lib.DoesLocalBranchExist(project_dir, branch):
613 raise optparse.OptionValueError('Project %s does not have branch %s'
614 % (project, branch))
615
616 options.local_patches.append(patch)
617
618
619def _CheckAndSplitGerritPatches(_option, _opt_str, value, parser):
620 """Early quick check of patches and convert them into a list."""
621 parser.values.gerrit_patches = value.split()
622
623
624def _CheckBuildRootOption(_option, _opt_str, value, parser):
625 """Validate and convert buildroot to full-path form."""
626 value = value.strip()
627 if not value or value == '/':
628 raise optparse.OptionValueError('Invalid buildroot specified')
629
630 parser.values.buildroot = os.path.realpath(os.path.expanduser(value))
631
632
633def _CheckLogDirOption(_option, _opt_str, value, parser):
634 """Validate and convert buildroot to full-path form."""
635 parser.values.log_dir = os.path.abspath(os.path.expanduser(value))
636
637
638def _CheckChromeVersionOption(_option, _opt_str, value, parser):
639 """Upgrade other options based on chrome_version being passed."""
640 value = value.strip()
641
642 if parser.values.chrome_rev is None and value:
643 parser.values.chrome_rev = constants.CHROME_REV_SPEC
644
645 parser.values.chrome_version = value
646
647
648def _CheckChromeRootOption(_option, _opt_str, value, parser):
649 """Validate and convert chrome_root to full-path form."""
650 value = value.strip()
651 if not value or value == '/':
652 raise optparse.OptionValueError('Invalid chrome_root specified')
653
654 if parser.values.chrome_rev is None:
655 parser.values.chrome_rev = constants.CHROME_REV_LOCAL
656
657 parser.values.chrome_root = os.path.realpath(os.path.expanduser(value))
658
659
660def _CheckChromeRevOption(_option, _opt_str, value, parser):
661 """Validate the chrome_rev option."""
662 value = value.strip()
663 if value not in constants.VALID_CHROME_REVISIONS:
664 raise optparse.OptionValueError('Invalid chrome rev specified')
665
666 parser.values.chrome_rev = value
667
668
669def _CreateParser():
670 """Generate and return the parser with all the options."""
671 # Parse options
672 usage = "usage: %prog [options] buildbot_config"
673 parser = optparse.OptionParser(usage=usage)
674
675 # Main options
676 parser.add_option('-a', '--all', action='store_true', dest='print_all',
677 default=False,
678 help=('List all of the buildbot configs available. Use '
679 'with the --list option'))
680 parser.add_option('-r', '--buildroot', action='callback', dest='buildroot',
681 type='string', callback=_CheckBuildRootOption,
682 help=('Root directory where source is checked out to, and '
683 'where the build occurs. For external build configs, '
684 "defaults to 'trybot' directory at top level of your "
685 'repo-managed checkout.'))
686 parser.add_option('--chrome_rev', default=None, type='string',
687 action='callback', dest='chrome_rev',
688 callback=_CheckChromeRevOption,
689 help=('Revision of Chrome to use, of type '
690 '[%s]' % '|'.join(constants.VALID_CHROME_REVISIONS)))
691 parser.add_option('-g', '--gerrit-patches', action='callback',
692 type='string', callback=_CheckAndSplitGerritPatches,
693 metavar="'Id1 *int_Id2...IdN'",
694 help=("Space-separated list of short-form Gerrit "
695 "Change-Id's or change numbers to patch. Please "
696 "prepend '*' to internal Change-Id's"))
697 parser.add_option('-l', '--list', action='store_true', dest='list',
698 default=False,
699 help=('List the suggested trybot configs to use. Use '
700 '--all to list all of the available configs.'))
701 parser.add_option('-p', '--local-patches', action='store', type='string',
702 metavar="'<project1>[:<branch1>]...<projectN>[:<branchN>]'",
703 help=('Space-separated list of project branches with '
704 'patches to apply. Projects are specified by name. '
705 'If no branch is specified the current branch of the '
706 'project will be used.'))
707 parser.add_option('--profile', default=None, type='string', action='store',
708 dest='profile',
709 help=('Name of profile to sub-specify board variant.'))
710 parser.add_option('--remote', default=False, action='store_true',
711 dest='remote',
712 help=('Specifies that this tryjob should be run remotely.'))
713
714 # Advanced options
715 group = optparse.OptionGroup(
716 parser,
717 'Advanced Options',
718 'Caution: use these options at your own risk.')
719
720 group.add_option('--buildbot', dest='buildbot', action='store_true',
721 default=False, help='This is running on a buildbot')
722 group.add_option('--buildnumber',
723 help='build number', type='int', default=0)
724 group.add_option('--chrome_root', default=None, type='string',
725 action='callback', dest='chrome_root',
726 callback=_CheckChromeRootOption,
727 help='Local checkout of Chrome to use.')
728 group.add_option('--chrome_version', default=None, type='string',
729 action='callback', dest='chrome_version',
730 callback=_CheckChromeVersionOption,
731 help='Used with SPEC logic to force a particular SVN '
732 'revision of chrome rather than the latest.')
733 group.add_option('--clobber', action='store_true', dest='clobber',
734 default=False,
735 help='Clears an old checkout before syncing')
736 group.add_option('--lkgm', action='store_true', dest='lkgm', default=False,
737 help='Sync to last known good manifest blessed by PFQ')
738 parser.add_option('--log_dir', action='callback', dest='log_dir',
739 type='string', callback=_CheckLogDirOption,
740 help=('Directory where logs are stored.'))
741 group.add_option('--maxarchives', dest='max_archive_builds',
742 default=3, type='int',
743 help="Change the local saved build count limit.")
744 group.add_option('--noarchive', action='store_false', dest='archive',
745 default=True,
746 help="Don't run archive stage.")
747 group.add_option('--nobuild', action='store_false', dest='build',
748 default=True,
749 help="Don't actually build (for cbuildbot dev")
750 group.add_option('--noclean', action='store_false', dest='clean',
751 default=True,
752 help="Don't clean the buildroot")
753 group.add_option('--noprebuilts', action='store_false', dest='prebuilts',
754 default=True,
755 help="Don't upload prebuilts.")
756 group.add_option('--nosync', action='store_false', dest='sync',
757 default=True,
758 help="Don't sync before building.")
759 group.add_option('--nocgroups', action='store_false', dest='cgroups',
760 default=True,
761 help='Disable cbuildbots usage of cgroups.')
762 group.add_option('--reference-repo', action='store', default=None,
763 dest='reference_repo',
764 help='Reuse git data stored in an existing repo '
765 'checkout. This can drastically reduce the network '
766 'time spent setting up the trybot checkout. By '
767 "default, if this option isn't given but cbuildbot "
768 'is invoked from a repo checkout, cbuildbot will '
769 'use the repo root.')
770 group.add_option('--timeout', action='store', type='int', default=0,
771 help="Specify the maximum amount of time this job can run "
772 "for, at which point the build will be aborted. If "
773 "set to zero, then there is no timeout")
774 group.add_option('--notests', action='store_false', dest='tests',
775 default=True,
776 help='Override values from buildconfig and run no tests.')
777 group.add_option('--nouprev', action='store_false', dest='uprev',
778 default=True,
779 help='Override values from buildconfig and never uprev.')
780 group.add_option('--resume', action='store_true',
781 default=False,
782 help='Skip stages already successfully completed.')
783 group.add_option('--validation_pool', default=None,
784 help='Path to a pickled validation pool. Intended for use '
785 'only with the commit queue.')
786 group.add_option('--version', dest='force_version', default=None,
787 help='Used with manifest logic. Forces use of this version '
788 'rather than create or get latest.')
789
790 parser.add_option_group(group)
791
792 # Debug options
793 group = optparse.OptionGroup(parser, "Debug Options")
794
795 group.add_option('--debug', action='store_true', dest='debug',
796 default=False,
797 help='Override some options to run as a developer.')
798 group.add_option('--dump_config', action='store_true', dest='dump_config',
799 default=False,
800 help='Dump out build config options, and exit.')
801 group.add_option('--notee', action='store_false', dest='tee', default=True,
802 help="Disable logging and internal tee process. Primarily "
803 "used for debugging cbuildbot itself.")
804 parser.add_option_group(group)
805 return parser
806
807
808def _PostParseCheck(options, args):
809 """Perform some usage validation after we've parsed the arguments
810
811 Args:
812 options: The options object returned by optparse
813 """
814 if cros_lib.IsInsideChroot():
815 cros_lib.Die('Please run cbuildbot from outside the chroot.')
816
817 if options.chrome_root:
818 if options.chrome_rev != constants.CHROME_REV_LOCAL:
819 cros_lib.Die('Chrome rev must be %s if chrome_root is set.' %
820 constants.CHROME_REV_LOCAL)
821 else:
822 if options.chrome_rev == constants.CHROME_REV_LOCAL:
823 cros_lib.Die('Chrome root must be set if chrome_rev is %s.' %
824 constants.CHROME_REV_LOCAL)
825
826 if options.chrome_version:
827 if options.chrome_rev != constants.CHROME_REV_SPEC:
828 cros_lib.Die('Chrome rev must be %s if chrome_version is set.' %
829 constants.CHROME_REV_SPEC)
830 else:
831 if options.chrome_rev == constants.CHROME_REV_SPEC:
832 cros_lib.Die('Chrome rev must not be %s if chrome_version is not set.' %
833 constants.CHROME_REV_SPEC)
834
835 if options.remote and options.local_patches:
836 cros_lib.Die('Local patching not yet supported with remote tryjobs.')
837
838 # Set debug correctly. If --debug is set explicitly, always set
839 # options.debug to true, o/w if not buildbot.
840 options.debug = options.debug | (not options.buildbot)
841
842 if not options.resume:
843 try:
844 if options.local_patches:
845 _CheckAndSplitLocalPatches(options)
846
847 except optparse.OptionValueError as e:
848 cros_lib.Die(str(e))
849
850 if options.remote and not options.gerrit_patches:
851 cros_lib.Die('Must provide patches when running with --remote.')
852
853 if len(args) > 1 and not options.remote:
854 cros_lib.Die('Multiple configs not supported if not running with --remote.')
855
856
857def main(argv):
858
859 # Set umask to 022 so files created by buildbot are readable.
860 os.umask(022)
861
862 parser = _CreateParser()
863 (options, args) = parser.parse_args(argv)
864 # Strip out null arguments.
865 # TODO(rcui): Remove when buildbot is fixed
866 args = [arg for arg in args if arg]
867
868 if options.list:
869 _PrintValidConfigs(not options.print_all)
870 sys.exit(0)
871
872 _PostParseCheck(options, args)
873
874 if options.remote:
875 # Verify configs are valid.
876 for bot in args:
877 _GetConfig(bot)
878
879 # Verify gerrit patches are valid.
880 _PreProcessPatches(options.gerrit_patches, options.local_patches)
881
882 remote_try.RemoteTryJob(options, args).Submit()
883 sys.exit(0)
884
885 if args:
886 # Only expecting one config
887 bot_id = args[-1]
888 build_config = _GetConfig(bot_id)
889 else:
890 parser.error('Invalid usage. Use -h to see usage.')
891
892 if options.reference_repo is None:
893 repo_path = os.path.join(constants.SOURCE_ROOT, '.repo')
894 # If we're being run from a repo checkout, reuse the repo's git pool to
895 # cut down on sync time.
896 if os.path.exists(repo_path):
897 options.reference_repo = constants.SOURCE_ROOT
898 elif options.reference_repo:
899 if not os.path.exists(options.reference_repo):
900 parser.error('Reference path %s does not exist'
901 % (options.reference_repo,))
902 elif not os.path.exists(os.path.join(options.reference_repo, '.repo')):
903 parser.error('Reference path %s does not look to be the base of a '
904 'repo checkout; no .repo exists in the root.'
905 % (options.reference_repo,))
906
907 if options.reference_repo:
908 options.reference_repo = os.path.abspath(options.reference_repo)
909
910 if options.dump_config:
911 # This works, but option ordering is bad...
912 print 'Configuration %s:' % bot_id
913 pretty_printer = pprint.PrettyPrinter(indent=2)
914 pretty_printer.pprint(build_config)
915 sys.exit(0)
916
917 if not options.buildroot:
918 if options.buildbot:
919 parser.error('Please specify a buildroot with the --buildroot option.')
920 else:
921 options.buildroot = _DetermineDefaultBuildRoot(build_config['internal'])
922 # We use a marker file in the buildroot to indicate the user has
923 # consented to using this directory.
924 if not os.path.exists(repository.GetTrybotMarkerPath(options.buildroot)):
925 _ConfirmBuildRoot(options.buildroot)
926
927 # Sanity check of buildroot- specifically that it's not pointing into the
928 # midst of an existing repo since git-repo doesn't support nesting.
929
930 if (not repository.IsARepoRoot(options.buildroot) and
David James6b80dc62012-02-29 15:34:40 -0800931 repository.InARepoRepository(options.buildroot)):
Brian Harring3fec5a82012-03-01 05:57:03 -0800932 parser.error('Configured buildroot %s points into a repository checkout, '
933 'rather than the root of it. This is not supported.'
934 % options.buildroot)
935
936 with sudo.SudoKeepAlive():
Brian Harringde588b52012-02-17 19:31:44 -0800937 with cros_lib.AllowDisabling(options.cgroups,
938 cgroups.ContainChildren, 'cbuildbot'):
Brian Harring3fec5a82012-03-01 05:57:03 -0800939 with cros_lib.AllowDisabling(options.timeout > 0,
940 cros_lib.Timeout, options.timeout):
941 if not options.buildbot:
942 build_config = cbuildbot_config.OverrideConfigForTrybot(build_config)
943 _RunBuildStagesWrapper(bot_id, options, build_config)