blob: 66190aa1b46a5be6c8847c6b448dbbca1e02003c [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
38cros_lib.STRICT_SUDO = True
39
40_DEFAULT_LOG_DIR = 'cbuildbot_logs'
41_BUILDBOT_LOG_FILE = 'cbuildbot.log'
42_DEFAULT_EXT_BUILDROOT = 'trybot'
43_DEFAULT_INT_BUILDROOT = 'trybot-internal'
44_PATH_TO_CBUILDBOT = 'chromite/bin/cbuildbot'
45_DISTRIBUTED_TYPES = [constants.COMMIT_QUEUE_TYPE, constants.PFQ_TYPE,
46 constants.CANARY_TYPE, constants.CHROME_PFQ_TYPE,
47 constants.PALADIN_TYPE]
48
49
50def _PrintValidConfigs(trybot_only=True):
51 """Print a list of valid buildbot configs.
52
53 Arguments:
54 trybot_only: Only print selected trybot configs, as specified by the
55 'trybot_list' config setting.
56 """
57 COLUMN_WIDTH = 45
58 print 'config'.ljust(COLUMN_WIDTH), 'description'
59 print '------'.ljust(COLUMN_WIDTH), '-----------'
60 config_names = cbuildbot_config.config.keys()
61 config_names.sort()
62 for name in config_names:
63 if not trybot_only or cbuildbot_config.config[name]['trybot_list']:
64 desc = ''
65 if cbuildbot_config.config[name]['description']:
66 desc = cbuildbot_config.config[name]['description']
67
68 print name.ljust(COLUMN_WIDTH), desc
69
70
71def _GetConfig(config_name):
72 """Gets the configuration for the build"""
73 if not cbuildbot_config.config.has_key(config_name):
74 print 'Non-existent configuration %s specified.' % config_name
75 print 'Please specify one of:'
76 _PrintValidConfigs()
77 sys.exit(1)
78
79 result = cbuildbot_config.config[config_name]
80
81 return result
82
83
84def _GetChromiteTrackingBranch():
85 """Returns the current Chromite tracking_branch.
86
87 If Chromite is on a detached HEAD, we assume it's the manifest branch.
88 """
89 cwd = os.path.dirname(os.path.realpath(__file__))
90 current_branch = cros_lib.GetCurrentBranch(cwd)
91 if current_branch:
92 (_, tracking_branch) = cros_lib.GetPushBranch(current_branch, cwd)
93 else:
94 tracking_branch = cros_lib.GetManifestDefaultBranch(cwd)
95
96 return tracking_branch
97
98
99def _CheckBuildRootBranch(buildroot, tracking_branch):
100 """Make sure buildroot branch is the same as Chromite branch."""
101 manifest_branch = cros_lib.GetManifestDefaultBranch(buildroot)
102 if manifest_branch != tracking_branch:
103 cros_lib.Die('Chromite is not on same branch as buildroot checkout\n' +
104 'Chromite is on branch %s.\n' % tracking_branch +
105 'Buildroot checked out to %s\n' % manifest_branch)
106
107
108def _PreProcessPatches(gerrit_patches, local_patches):
109 """Validate patches ASAP to catch user errors. Also generate patch info.
110
111 Args:
112 gerrit_patches: List of gerrit CL ID's passed in by user.
113 local_patches: List of local project branches to generate patches from.
114
115 Returns:
116 A tuple containing a list of cros_patch.GerritPatch and a list of
117 cros_patch.LocalPatch objects.
118 """
119 gerrit_patch_info = []
120 local_patch_info = []
121
122 try:
123 if gerrit_patches:
124 gerrit_patch_info = gerrit_helper.GetGerritPatchInfo(gerrit_patches)
125 for patch in gerrit_patch_info:
126 if patch.IsAlreadyMerged():
127 cros_lib.Warning('Patch %s has already been merged.' % str(patch))
128 except gerrit_helper.GerritException as e:
129 cros_lib.Die(str(e))
130
131 try:
132 if local_patches:
133 local_patch_info = cros_patch.PrepareLocalPatches(
134 local_patches,
135 _GetChromiteTrackingBranch())
136
137 except cros_patch.PatchException as e:
138 cros_lib.Die(str(e))
139
140 return gerrit_patch_info, local_patch_info
141
142
143def _IsIncrementalBuild(buildroot, clobber):
144 """Returns True if we are reusing an existing buildroot."""
145 repo_dir = os.path.join(buildroot, '.repo')
146 return not clobber and os.path.isdir(repo_dir)
147
148
149class Builder(object):
150 """Parent class for all builder types.
151
152 This class functions as a parent class for various build types. It's intended
153 use is builder_instance.Run().
154
155 Vars:
Brian Harring3fec5a82012-03-01 05:57:03 -0800156 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
David James944a48e2012-03-07 12:19:03 -0800166 def __init__(self, options, build_config):
Brian Harring3fec5a82012-03-01 05:57:03 -0800167 """Initializes instance variables. Must be called by all subclasses."""
Brian Harring3fec5a82012-03-01 05:57:03 -0800168 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')
David James58e0c092012-03-04 20:31:12 -0800177 self.archive_stages = {}
Brian Harring3fec5a82012-03-01 05:57:03 -0800178 self.archive_urls = {}
179 self.release_tag = None
180 self.tracking_branch = _GetChromiteTrackingBranch()
181 self.gerrit_patches = None
182 self.local_patches = None
183
184 def Initialize(self):
185 """Runs through the initialization steps of an actual build."""
186 if self.options.resume and os.path.exists(self.completed_stages_file):
187 with open(self.completed_stages_file, 'r') as load_file:
188 results_lib.Results.RestoreCompletedStages(load_file)
189
190 # We only want to do this if we need to patch changes.
191 if not results_lib.Results.GetPrevious().get(
192 self._GetStageInstance(stages.PatchChangesStage, None, None).name):
193 self.gerrit_patches, self.local_patches = _PreProcessPatches(
194 self.options.gerrit_patches, self.options.local_patches)
195
196 bs.BuilderStage.SetTrackingBranch(self.tracking_branch)
197
198 # Check branch matching early.
199 if _IsIncrementalBuild(self.options.buildroot, self.options.clobber):
200 _CheckBuildRootBranch(self.options.buildroot, self.tracking_branch)
201
202 self._RunStage(stages.CleanUpStage)
203
204 def _GetStageInstance(self, stage, *args, **kwargs):
205 """Helper function to get an instance given the args.
206
David James944a48e2012-03-07 12:19:03 -0800207 Useful as almost all stages just take in options and build_config.
Brian Harring3fec5a82012-03-01 05:57:03 -0800208 """
David James944a48e2012-03-07 12:19:03 -0800209 config = kwargs.pop('config', self.build_config)
210 return stage(self.options, config, *args, **kwargs)
Brian Harring3fec5a82012-03-01 05:57:03 -0800211
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."""
David James58e0c092012-03-04 20:31:12 -0800340 archive_stage = self.archive_stages[board]
David James944a48e2012-03-07 12:19:03 -0800341 configs = self.build_config['board_specific_configs']
342 config = configs.get(board, self.build_config)
343 stage_list = [[stages.VMTestStage, board, archive_stage],
344 [stages.ChromeTestStage, board, archive_stage],
345 [stages.UnitTestStage, board],
346 [stages.UploadPrebuiltsStage, board]]
Brian Harring3fec5a82012-03-01 05:57:03 -0800347
David James58e0c092012-03-04 20:31:12 -0800348 # We can not run hw tests without archiving the payloads.
349 if self.options.archive:
David James944a48e2012-03-07 12:19:03 -0800350 for suite in config['hw_tests']:
351 stage_list.append([stages.HWTestStage, board, archive_stage, suite])
Chris Sosab50dc932012-03-01 14:00:58 -0800352
David James944a48e2012-03-07 12:19:03 -0800353 steps = [self._GetStageInstance(*x, config=config).Run for x in stage_list]
354 background.RunParallelSteps(steps + [archive_stage.Run])
Brian Harring3fec5a82012-03-01 05:57:03 -0800355
356 def RunStages(self):
357 """Runs through build process."""
358 self._RunStage(stages.BuildBoardStage)
359
360 # TODO(sosa): Split these out into classes.
Brian Harring3fec5a82012-03-01 05:57:03 -0800361 if self.build_config['build_type'] == constants.CHROOT_BUILDER_TYPE:
362 self._RunStage(stages.SDKTestStage)
363 self._RunStage(stages.UploadPrebuiltsStage,
364 constants.CHROOT_BUILDER_BOARD)
365 elif self.build_config['build_type'] == constants.REFRESH_PACKAGES_TYPE:
366 self._RunStage(stages.RefreshPackageStatusStage)
367 else:
368 self._RunStage(stages.UprevStage)
Brian Harring3fec5a82012-03-01 05:57:03 -0800369
David James944a48e2012-03-07 12:19:03 -0800370 configs = self.build_config['board_specific_configs']
David James58e0c092012-03-04 20:31:12 -0800371 for board in self.build_config['boards']:
David James944a48e2012-03-07 12:19:03 -0800372 config = configs.get(board, self.build_config)
373 archive_stage = self._GetStageInstance(stages.ArchiveStage, board,
374 config=config)
David James58e0c092012-03-04 20:31:12 -0800375 self.archive_stages[board] = archive_stage
376
David James944a48e2012-03-07 12:19:03 -0800377 # Set up a process pool to run test/archive stages in the background.
378 # This process runs task(board) for each board added to the queue.
David James58e0c092012-03-04 20:31:12 -0800379 queue = multiprocessing.Queue()
380 task = self._RunBackgroundStagesForBoard
381 with background.BackgroundTaskRunner(queue, task):
David James944a48e2012-03-07 12:19:03 -0800382 for board in self.build_config['boards']:
David James58e0c092012-03-04 20:31:12 -0800383 # Run BuildTarget in the foreground.
David James944a48e2012-03-07 12:19:03 -0800384 archive_stage = self.archive_stages[board]
385 config = configs.get(board, self.build_config)
386 self._RunStage(stages.BuildTargetStage, board, archive_stage,
387 config=config)
David James58e0c092012-03-04 20:31:12 -0800388 self.archive_urls[board] = archive_stage.GetDownloadUrl()
389
David James944a48e2012-03-07 12:19:03 -0800390 # Kick off task(board) in the background.
David James58e0c092012-03-04 20:31:12 -0800391 queue.put([board])
392
393 return True
Brian Harring3fec5a82012-03-01 05:57:03 -0800394
395
396class DistributedBuilder(SimpleBuilder):
397 """Build class that has special logic to handle distributed builds.
398
399 These builds sync using git/manifest logic in manifest_versions. In general
400 they use a non-distributed builder code for the bulk of the work.
401 """
David James944a48e2012-03-07 12:19:03 -0800402 def __init__(self, options, build_config):
Brian Harring3fec5a82012-03-01 05:57:03 -0800403 """Initializes a buildbot builder.
404
405 Extra variables:
406 completion_stage_class: Stage used to complete a build. Set in the Sync
407 stage.
408 """
David James944a48e2012-03-07 12:19:03 -0800409 super(DistributedBuilder, self).__init__(options, build_config)
Brian Harring3fec5a82012-03-01 05:57:03 -0800410 self.completion_stage_class = None
411
412 def GetSyncInstance(self):
413 """Syncs the tree using one of the distributed sync logic paths.
414
415 Returns: the instance of the sync stage that was run.
416 """
417 # Determine sync class to use. CQ overrides PFQ bits so should check it
418 # first.
419 if cbuildbot_config.IsCQType(self.build_config['build_type']):
420 sync_stage = self._GetStageInstance(stages.CommitQueueSyncStage)
421 self.completion_stage_class = stages.CommitQueueCompletionStage
422 elif cbuildbot_config.IsPFQType(self.build_config['build_type']):
423 sync_stage = self._GetStageInstance(stages.LKGMCandidateSyncStage)
424 self.completion_stage_class = stages.LKGMCandidateSyncCompletionStage
425 else:
426 sync_stage = self._GetStageInstance(stages.ManifestVersionedSyncStage)
427 self.completion_stage_class = stages.ManifestVersionedSyncCompletionStage
428
429 return sync_stage
430
431 def Publish(self, was_build_successful):
432 """Completes build by publishing any required information."""
433 completion_stage = self._GetStageInstance(self.completion_stage_class,
434 was_build_successful)
435 completion_stage.Run()
436 name = completion_stage.name
437 if not results_lib.Results.WasStageSuccessful(name):
438 should_publish_changes = False
439 else:
440 should_publish_changes = (self.build_config['master'] and
441 was_build_successful)
442
443 if should_publish_changes:
444 self._RunStage(stages.PublishUprevChangesStage)
445
446 def RunStages(self):
447 """Runs simple builder logic and publishes information to overlays."""
448 was_build_successful = False
449 try:
450 was_build_successful = super(DistributedBuilder, self).RunStages()
451 except SystemExit as ex:
452 # If a stage calls sys.exit(0), it's exiting with success, so that means
453 # we should mark ourselves as successful.
454 if ex.code == 0:
455 was_build_successful = True
456 raise
457 finally:
458 self.Publish(was_build_successful)
459
460 return was_build_successful
461
462
463def _ConfirmBuildRoot(buildroot):
464 """Confirm with user the inferred buildroot, and mark it as confirmed."""
465 warning = 'Using default directory %s as buildroot' % buildroot
466 response = cros_lib.YesNoPrompt(default=cros_lib.NO, warning=warning,
467 full=True)
468 if response == cros_lib.NO:
469 print('Please specify a buildroot with the --buildroot option.')
470 sys.exit(0)
471
472 if not os.path.exists(buildroot):
473 os.mkdir(buildroot)
474
475 repository.CreateTrybotMarker(buildroot)
476
477
478def _DetermineDefaultBuildRoot(internal_build):
479 """Default buildroot to be under the directory that contains current checkout.
480
481 Arguments:
482 internal_build: Whether the build is an internal build
483 """
484 repo_dir = cros_lib.FindRepoDir()
485 if not repo_dir:
486 cros_lib.Die('Could not find root of local checkout. Please specify'
487 'using --buildroot option.')
488
489 # Place trybot buildroot under the directory containing current checkout.
490 top_level = os.path.dirname(os.path.realpath(os.path.dirname(repo_dir)))
491 if internal_build:
492 buildroot = os.path.join(top_level, _DEFAULT_INT_BUILDROOT)
493 else:
494 buildroot = os.path.join(top_level, _DEFAULT_EXT_BUILDROOT)
495
496 return buildroot
497
498
499def _BackupPreviousLog(log_file, backup_limit=25):
500 """Rename previous log.
501
502 Args:
503 log_file: The absolute path to the previous log.
504 """
505 if os.path.exists(log_file):
506 old_logs = sorted(glob.glob(log_file + '.*'),
507 key=distutils.version.LooseVersion)
508
509 if len(old_logs) >= backup_limit:
510 os.remove(old_logs[0])
511
512 last = 0
513 if old_logs:
514 last = int(old_logs.pop().rpartition('.')[2])
515
516 os.rename(log_file, log_file + '.' + str(last + 1))
517
518
David James944a48e2012-03-07 12:19:03 -0800519def _RunBuildStagesWrapper(options, build_config):
Brian Harring3fec5a82012-03-01 05:57:03 -0800520 """Helper function that wraps RunBuildStages()."""
521 def IsDistributedBuilder():
522 """Determines whether the build_config should be a DistributedBuilder."""
523 if not options.buildbot:
524 return False
525 elif build_config['build_type'] in _DISTRIBUTED_TYPES:
526 chrome_rev = build_config['chrome_rev']
527 if options.chrome_rev: chrome_rev = options.chrome_rev
528 # We don't do distributed logic to TOT Chrome PFQ's, nor local
529 # chrome roots (e.g. chrome try bots)
530 if chrome_rev not in [constants.CHROME_REV_TOT,
531 constants.CHROME_REV_LOCAL,
532 constants.CHROME_REV_SPEC]:
533 return True
534
535 return False
536
537 # Start tee-ing output to file.
538 log_file = None
539 if options.tee:
540 default_dir = os.path.join(options.buildroot, _DEFAULT_LOG_DIR)
541 dirname = options.log_dir or default_dir
542 log_file = os.path.join(dirname, _BUILDBOT_LOG_FILE)
543
544 cros_lib.SafeMakedirs(dirname)
545 _BackupPreviousLog(log_file)
546
547 try:
548 with cros_lib.AllowDisabling(options.tee, tee.Tee, log_file):
549 cros_lib.Info("cbuildbot executed with args %s"
550 % ' '.join(map(repr, sys.argv)))
551 if IsDistributedBuilder():
David James944a48e2012-03-07 12:19:03 -0800552 buildbot = DistributedBuilder(options, build_config)
Brian Harring3fec5a82012-03-01 05:57:03 -0800553 else:
David James944a48e2012-03-07 12:19:03 -0800554 buildbot = SimpleBuilder(options, build_config)
Brian Harring3fec5a82012-03-01 05:57:03 -0800555
556 if not buildbot.Run():
557 sys.exit(1)
558 finally:
559 if options.tee:
560 cros_lib.Info('Output should be saved to %s' % log_file)
561
562
563# Parser related functions
564
565
566def _CheckAndSplitLocalPatches(options):
567 """Do an early quick check of the passed-in patches.
568
569 If the branch of a project is not specified we append the current branch the
570 project is on.
571 """
572 patch_args = options.local_patches.split()
573 options.local_patches = []
574 for patch in patch_args:
575 components = patch.split(':')
576 if len(components) > 2:
577 msg = 'Specify local patches in project[:branch] format.'
578 raise optparse.OptionValueError(msg)
579
580 # validate project
581 project = components[0]
582 if not cros_lib.DoesProjectExist('.', project):
583 raise optparse.OptionValueError('Project %s does not exist.' % project)
584
585 project_dir = cros_lib.GetProjectDir('.', project)
586
587 # If no branch was specified, we use the project's current branch.
588 if len(components) == 1:
589 branch = cros_lib.GetCurrentBranch(project_dir)
590 if not branch:
591 raise optparse.OptionValueError('project %s is not on a branch!'
592 % project)
593 # Append branch information to patch
594 patch = '%s:%s' % (project, branch)
595 else:
596 branch = components[1]
597 if not cros_lib.DoesLocalBranchExist(project_dir, branch):
598 raise optparse.OptionValueError('Project %s does not have branch %s'
599 % (project, branch))
600
601 options.local_patches.append(patch)
602
603
604def _CheckAndSplitGerritPatches(_option, _opt_str, value, parser):
605 """Early quick check of patches and convert them into a list."""
606 parser.values.gerrit_patches = value.split()
607
608
609def _CheckBuildRootOption(_option, _opt_str, value, parser):
610 """Validate and convert buildroot to full-path form."""
611 value = value.strip()
612 if not value or value == '/':
613 raise optparse.OptionValueError('Invalid buildroot specified')
614
615 parser.values.buildroot = os.path.realpath(os.path.expanduser(value))
616
617
618def _CheckLogDirOption(_option, _opt_str, value, parser):
619 """Validate and convert buildroot to full-path form."""
620 parser.values.log_dir = os.path.abspath(os.path.expanduser(value))
621
622
623def _CheckChromeVersionOption(_option, _opt_str, value, parser):
624 """Upgrade other options based on chrome_version being passed."""
625 value = value.strip()
626
627 if parser.values.chrome_rev is None and value:
628 parser.values.chrome_rev = constants.CHROME_REV_SPEC
629
630 parser.values.chrome_version = value
631
632
633def _CheckChromeRootOption(_option, _opt_str, value, parser):
634 """Validate and convert chrome_root to full-path form."""
635 value = value.strip()
636 if not value or value == '/':
637 raise optparse.OptionValueError('Invalid chrome_root specified')
638
639 if parser.values.chrome_rev is None:
640 parser.values.chrome_rev = constants.CHROME_REV_LOCAL
641
642 parser.values.chrome_root = os.path.realpath(os.path.expanduser(value))
643
644
645def _CheckChromeRevOption(_option, _opt_str, value, parser):
646 """Validate the chrome_rev option."""
647 value = value.strip()
648 if value not in constants.VALID_CHROME_REVISIONS:
649 raise optparse.OptionValueError('Invalid chrome rev specified')
650
651 parser.values.chrome_rev = value
652
653
654def _CreateParser():
655 """Generate and return the parser with all the options."""
656 # Parse options
657 usage = "usage: %prog [options] buildbot_config"
658 parser = optparse.OptionParser(usage=usage)
659
660 # Main options
661 parser.add_option('-a', '--all', action='store_true', dest='print_all',
662 default=False,
663 help=('List all of the buildbot configs available. Use '
664 'with the --list option'))
665 parser.add_option('-r', '--buildroot', action='callback', dest='buildroot',
666 type='string', callback=_CheckBuildRootOption,
667 help=('Root directory where source is checked out to, and '
668 'where the build occurs. For external build configs, '
669 "defaults to 'trybot' directory at top level of your "
670 'repo-managed checkout.'))
671 parser.add_option('--chrome_rev', default=None, type='string',
672 action='callback', dest='chrome_rev',
673 callback=_CheckChromeRevOption,
674 help=('Revision of Chrome to use, of type '
675 '[%s]' % '|'.join(constants.VALID_CHROME_REVISIONS)))
676 parser.add_option('-g', '--gerrit-patches', action='callback',
677 type='string', callback=_CheckAndSplitGerritPatches,
678 metavar="'Id1 *int_Id2...IdN'",
679 help=("Space-separated list of short-form Gerrit "
680 "Change-Id's or change numbers to patch. Please "
681 "prepend '*' to internal Change-Id's"))
682 parser.add_option('-l', '--list', action='store_true', dest='list',
683 default=False,
684 help=('List the suggested trybot configs to use. Use '
685 '--all to list all of the available configs.'))
686 parser.add_option('-p', '--local-patches', action='store', type='string',
687 metavar="'<project1>[:<branch1>]...<projectN>[:<branchN>]'",
688 help=('Space-separated list of project branches with '
689 'patches to apply. Projects are specified by name. '
690 'If no branch is specified the current branch of the '
691 'project will be used.'))
692 parser.add_option('--profile', default=None, type='string', action='store',
693 dest='profile',
694 help=('Name of profile to sub-specify board variant.'))
695 parser.add_option('--remote', default=False, action='store_true',
696 dest='remote',
697 help=('Specifies that this tryjob should be run remotely.'))
698
699 # Advanced options
700 group = optparse.OptionGroup(
701 parser,
702 'Advanced Options',
703 'Caution: use these options at your own risk.')
704
705 group.add_option('--buildbot', dest='buildbot', action='store_true',
706 default=False, help='This is running on a buildbot')
707 group.add_option('--buildnumber',
708 help='build number', type='int', default=0)
709 group.add_option('--chrome_root', default=None, type='string',
710 action='callback', dest='chrome_root',
711 callback=_CheckChromeRootOption,
712 help='Local checkout of Chrome to use.')
713 group.add_option('--chrome_version', default=None, type='string',
714 action='callback', dest='chrome_version',
715 callback=_CheckChromeVersionOption,
716 help='Used with SPEC logic to force a particular SVN '
717 'revision of chrome rather than the latest.')
718 group.add_option('--clobber', action='store_true', dest='clobber',
719 default=False,
720 help='Clears an old checkout before syncing')
721 group.add_option('--lkgm', action='store_true', dest='lkgm', default=False,
722 help='Sync to last known good manifest blessed by PFQ')
723 parser.add_option('--log_dir', action='callback', dest='log_dir',
724 type='string', callback=_CheckLogDirOption,
725 help=('Directory where logs are stored.'))
726 group.add_option('--maxarchives', dest='max_archive_builds',
727 default=3, type='int',
728 help="Change the local saved build count limit.")
729 group.add_option('--noarchive', action='store_false', dest='archive',
730 default=True,
731 help="Don't run archive stage.")
732 group.add_option('--nobuild', action='store_false', dest='build',
733 default=True,
734 help="Don't actually build (for cbuildbot dev")
735 group.add_option('--noclean', action='store_false', dest='clean',
736 default=True,
737 help="Don't clean the buildroot")
738 group.add_option('--noprebuilts', action='store_false', dest='prebuilts',
739 default=True,
740 help="Don't upload prebuilts.")
741 group.add_option('--nosync', action='store_false', dest='sync',
742 default=True,
743 help="Don't sync before building.")
744 group.add_option('--nocgroups', action='store_false', dest='cgroups',
745 default=True,
746 help='Disable cbuildbots usage of cgroups.')
747 group.add_option('--reference-repo', action='store', default=None,
748 dest='reference_repo',
749 help='Reuse git data stored in an existing repo '
750 'checkout. This can drastically reduce the network '
751 'time spent setting up the trybot checkout. By '
752 "default, if this option isn't given but cbuildbot "
753 'is invoked from a repo checkout, cbuildbot will '
754 'use the repo root.')
Ryan Cuiba41ad32012-03-08 17:15:29 -0800755 group.add_option('--remote-trybot', dest='remote_trybot', action='store_true',
756 default=False,
757 help=('This is running on a remote trybot machine. '
758 'Requires --user and --timestamp to be set.'))
Brian Harring3fec5a82012-03-01 05:57:03 -0800759 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
Ryan Cui85867972012-02-23 18:21:49 -0800784 group.add_option('--debug', action='store_true', default=None,
Brian Harring3fec5a82012-03-01 05:57:03 -0800785 help='Override some options to run as a developer.')
786 group.add_option('--dump_config', action='store_true', dest='dump_config',
787 default=False,
788 help='Dump out build config options, and exit.')
789 group.add_option('--notee', action='store_false', dest='tee', default=True,
790 help="Disable logging and internal tee process. Primarily "
791 "used for debugging cbuildbot itself.")
792 parser.add_option_group(group)
793 return parser
794
795
Ryan Cui85867972012-02-23 18:21:49 -0800796def _FinishParsing(options, args):
797 """Perform some parsing tasks that need to take place after optparse.
798
799 This function needs to be easily testable! Keep it free of
800 environment-dependent code. Put more detailed usage validation in
801 _PostParseCheck().
Brian Harring3fec5a82012-03-01 05:57:03 -0800802
803 Args:
Ryan Cui85867972012-02-23 18:21:49 -0800804 options, args: The options/args object returned by optparse
Brian Harring3fec5a82012-03-01 05:57:03 -0800805 """
Brian Harring3fec5a82012-03-01 05:57:03 -0800806 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
Brian Harring3fec5a82012-03-01 05:57:03 -0800827 if options.remote and not options.gerrit_patches:
828 cros_lib.Die('Must provide patches when running with --remote.')
829
830 if len(args) > 1 and not options.remote:
831 cros_lib.Die('Multiple configs not supported if not running with --remote.')
832
Ryan Cuiba41ad32012-03-08 17:15:29 -0800833 if options.buildbot and options.remote_trybot:
834 cros_lib.Die('--buildbot and --remote-trybot cannot be used together.')
835
Ryan Cui85867972012-02-23 18:21:49 -0800836 # Record whether --debug was set explicitly vs. it was inferred.
837 options.debug_forced = False
838 if options.debug:
839 options.debug_forced = True
840 else:
841 options.debug = not options.buildbot
Brian Harring3fec5a82012-03-01 05:57:03 -0800842
Brian Harring3fec5a82012-03-01 05:57:03 -0800843
Ryan Cui85867972012-02-23 18:21:49 -0800844def _PostParseCheck(options, args):
845 """Perform some usage validation after we've parsed the arguments
Brian Harring3fec5a82012-03-01 05:57:03 -0800846
Ryan Cui85867972012-02-23 18:21:49 -0800847 Args:
848 options/args: The options/args object returned by optparse
849 """
850 if not options.resume:
851 try:
852 # TODO(rcui): Split this into two stages, one that parses, another that
853 # validates. Parsing step will be called by _FinishParsing().
854 if options.local_patches:
855 _CheckAndSplitLocalPatches(options)
856
857 except optparse.OptionValueError as e:
858 cros_lib.Die(str(e))
859
860
861def _ParseCommandLine(parser, argv):
862 """Completely parse the commandline arguments"""
Brian Harring3fec5a82012-03-01 05:57:03 -0800863 (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]
Ryan Cui85867972012-02-23 18:21:49 -0800867 _FinishParsing(options, args)
868 return options, args
869
870
871def main(argv):
872 # Set umask to 022 so files created by buildbot are readable.
873 os.umask(022)
874
875 if cros_lib.IsInsideChroot():
876 cros_lib.Die('Please run cbuildbot from outside the chroot.')
877
878 parser = _CreateParser()
879 (options, args) = _ParseCommandLine(parser, argv)
Brian Harring3fec5a82012-03-01 05:57:03 -0800880
881 if options.list:
882 _PrintValidConfigs(not options.print_all)
883 sys.exit(0)
884
885 _PostParseCheck(options, args)
886
887 if options.remote:
888 # Verify configs are valid.
889 for bot in args:
890 _GetConfig(bot)
891
892 # Verify gerrit patches are valid.
893 _PreProcessPatches(options.gerrit_patches, options.local_patches)
894
895 remote_try.RemoteTryJob(options, args).Submit()
896 sys.exit(0)
897
898 if args:
899 # Only expecting one config
900 bot_id = args[-1]
901 build_config = _GetConfig(bot_id)
902 else:
903 parser.error('Invalid usage. Use -h to see usage.')
904
905 if options.reference_repo is None:
906 repo_path = os.path.join(constants.SOURCE_ROOT, '.repo')
907 # If we're being run from a repo checkout, reuse the repo's git pool to
908 # cut down on sync time.
909 if os.path.exists(repo_path):
910 options.reference_repo = constants.SOURCE_ROOT
911 elif options.reference_repo:
912 if not os.path.exists(options.reference_repo):
913 parser.error('Reference path %s does not exist'
914 % (options.reference_repo,))
915 elif not os.path.exists(os.path.join(options.reference_repo, '.repo')):
916 parser.error('Reference path %s does not look to be the base of a '
917 'repo checkout; no .repo exists in the root.'
918 % (options.reference_repo,))
Brian Harring470f6112012-03-02 11:47:10 -0800919 if options.buildbot:
920 if not options.cgroups:
921 parser.error('Options --buildbot and --nocgroups cannot be used '
922 'together. Cgroup support is required for buildbot mode.')
923 if not cgroups.Cgroup.CgroupsSupported():
924 parser.error('Option --buildbot was given, but this system does not '
925 'support cgroups. Failing.')
Brian Harring3fec5a82012-03-01 05:57:03 -0800926
927 if options.reference_repo:
928 options.reference_repo = os.path.abspath(options.reference_repo)
929
930 if options.dump_config:
931 # This works, but option ordering is bad...
932 print 'Configuration %s:' % bot_id
933 pretty_printer = pprint.PrettyPrinter(indent=2)
934 pretty_printer.pprint(build_config)
935 sys.exit(0)
936
937 if not options.buildroot:
938 if options.buildbot:
939 parser.error('Please specify a buildroot with the --buildroot option.')
Brian Harring470f6112012-03-02 11:47:10 -0800940 options.buildroot = _DetermineDefaultBuildRoot(build_config['internal'])
941 # We use a marker file in the buildroot to indicate the user has
942 # consented to using this directory.
943 if not os.path.exists(repository.GetTrybotMarkerPath(options.buildroot)):
944 _ConfirmBuildRoot(options.buildroot)
Brian Harring3fec5a82012-03-01 05:57:03 -0800945
946 # Sanity check of buildroot- specifically that it's not pointing into the
947 # midst of an existing repo since git-repo doesn't support nesting.
Brian Harring3fec5a82012-03-01 05:57:03 -0800948 if (not repository.IsARepoRoot(options.buildroot) and
David James6b80dc62012-02-29 15:34:40 -0800949 repository.InARepoRepository(options.buildroot)):
Brian Harring3fec5a82012-03-01 05:57:03 -0800950 parser.error('Configured buildroot %s points into a repository checkout, '
951 'rather than the root of it. This is not supported.'
952 % options.buildroot)
953
Brian Harringa184efa2012-03-04 11:51:25 -0800954 with cleanup.EnforcedCleanupSection() as critical_section:
955 with sudo.SudoKeepAlive():
956 with cros_lib.AllowDisabling(options.cgroups,
957 cgroups.ContainChildren, 'cbuildbot'):
958 # Mark everything between EnforcedCleanupSection and here as having to
959 # be rolled back via the contextmanager cleanup handlers. This ensures
960 # that sudo bits cannot outlive cbuildbot, that anything cgroups
961 # would kill gets killed, etc.
962 critical_section.ForkWatchdog()
963
964 with cros_lib.AllowDisabling(options.timeout > 0,
965 cros_lib.Timeout, options.timeout):
966 if not options.buildbot:
967 build_config = cbuildbot_config.OverrideConfigForTrybot(
968 build_config)
969
970 _RunBuildStagesWrapper(options, build_config)