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