blob: 9f857e0403a31c6a7205f83eac60c7b987140302 [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]
Brian Harring351ce442012-03-09 16:38:14 -080048_BUILDBOT_REQUIRED_BINARIES = ('pbzip2',)
Brian Harring3fec5a82012-03-01 05:57:03 -080049
50
51def _PrintValidConfigs(trybot_only=True):
52 """Print a list of valid buildbot configs.
53
54 Arguments:
55 trybot_only: Only print selected trybot configs, as specified by the
56 'trybot_list' config setting.
57 """
58 COLUMN_WIDTH = 45
59 print 'config'.ljust(COLUMN_WIDTH), 'description'
60 print '------'.ljust(COLUMN_WIDTH), '-----------'
61 config_names = cbuildbot_config.config.keys()
62 config_names.sort()
63 for name in config_names:
64 if not trybot_only or cbuildbot_config.config[name]['trybot_list']:
65 desc = ''
66 if cbuildbot_config.config[name]['description']:
67 desc = cbuildbot_config.config[name]['description']
68
69 print name.ljust(COLUMN_WIDTH), desc
70
71
72def _GetConfig(config_name):
73 """Gets the configuration for the build"""
74 if not cbuildbot_config.config.has_key(config_name):
75 print 'Non-existent configuration %s specified.' % config_name
76 print 'Please specify one of:'
77 _PrintValidConfigs()
78 sys.exit(1)
79
80 result = cbuildbot_config.config[config_name]
81
82 return result
83
84
85def _GetChromiteTrackingBranch():
86 """Returns the current Chromite tracking_branch.
87
88 If Chromite is on a detached HEAD, we assume it's the manifest branch.
89 """
90 cwd = os.path.dirname(os.path.realpath(__file__))
91 current_branch = cros_lib.GetCurrentBranch(cwd)
92 if current_branch:
93 (_, tracking_branch) = cros_lib.GetPushBranch(current_branch, cwd)
94 else:
95 tracking_branch = cros_lib.GetManifestDefaultBranch(cwd)
96
97 return tracking_branch
98
99
100def _CheckBuildRootBranch(buildroot, tracking_branch):
101 """Make sure buildroot branch is the same as Chromite branch."""
102 manifest_branch = cros_lib.GetManifestDefaultBranch(buildroot)
103 if manifest_branch != tracking_branch:
104 cros_lib.Die('Chromite is not on same branch as buildroot checkout\n' +
105 'Chromite is on branch %s.\n' % tracking_branch +
106 'Buildroot checked out to %s\n' % manifest_branch)
107
108
109def _PreProcessPatches(gerrit_patches, local_patches):
110 """Validate patches ASAP to catch user errors. Also generate patch info.
111
112 Args:
113 gerrit_patches: List of gerrit CL ID's passed in by user.
114 local_patches: List of local project branches to generate patches from.
115
116 Returns:
117 A tuple containing a list of cros_patch.GerritPatch and a list of
118 cros_patch.LocalPatch objects.
119 """
120 gerrit_patch_info = []
121 local_patch_info = []
122
123 try:
124 if gerrit_patches:
125 gerrit_patch_info = gerrit_helper.GetGerritPatchInfo(gerrit_patches)
126 for patch in gerrit_patch_info:
127 if patch.IsAlreadyMerged():
128 cros_lib.Warning('Patch %s has already been merged.' % str(patch))
129 except gerrit_helper.GerritException as e:
130 cros_lib.Die(str(e))
131
132 try:
133 if local_patches:
134 local_patch_info = cros_patch.PrepareLocalPatches(
135 local_patches,
136 _GetChromiteTrackingBranch())
137
138 except cros_patch.PatchException as e:
139 cros_lib.Die(str(e))
140
141 return gerrit_patch_info, local_patch_info
142
143
144def _IsIncrementalBuild(buildroot, clobber):
145 """Returns True if we are reusing an existing buildroot."""
146 repo_dir = os.path.join(buildroot, '.repo')
147 return not clobber and os.path.isdir(repo_dir)
148
149
150class Builder(object):
151 """Parent class for all builder types.
152
153 This class functions as a parent class for various build types. It's intended
154 use is builder_instance.Run().
155
156 Vars:
Brian Harring3fec5a82012-03-01 05:57:03 -0800157 build_config: The configuration dictionary from cbuildbot_config.
158 options: The options provided from optparse in main().
159 completed_stages_file: Where we store resume state.
160 archive_url: Where our artifacts for this builder will be archived.
161 tracking_branch: The tracking branch for this build.
162 release_tag: The associated "chrome os version" of this build.
163 gerrit_patches: Gerrit patches to be included in build.
164 local_patches: Local patches to be included in build.
165 """
166
David James944a48e2012-03-07 12:19:03 -0800167 def __init__(self, options, build_config):
Brian Harring3fec5a82012-03-01 05:57:03 -0800168 """Initializes instance variables. Must be called by all subclasses."""
Brian Harring3fec5a82012-03-01 05:57:03 -0800169 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
David James944a48e2012-03-07 12:19:03 -0800208 Useful as almost all stages just take in options and build_config.
Brian Harring3fec5a82012-03-01 05:57:03 -0800209 """
David James944a48e2012-03-07 12:19:03 -0800210 config = kwargs.pop('config', self.build_config)
211 return stage(self.options, config, *args, **kwargs)
Brian Harring3fec5a82012-03-01 05:57:03 -0800212
213 def _SetReleaseTag(self):
214 """Sets the release tag from the manifest_manager.
215
216 Must be run after sync stage as syncing enables us to have a release tag.
217 """
218 # Extract version we have decided to build into self.release_tag.
219 manifest_manager = stages.ManifestVersionedSyncStage.manifest_manager
220 if manifest_manager:
221 self.release_tag = manifest_manager.current_version
222
223 def _RunStage(self, stage, *args, **kwargs):
224 """Wrapper to run a stage."""
225 stage_instance = self._GetStageInstance(stage, *args, **kwargs)
226 return stage_instance.Run()
227
228 def GetSyncInstance(self):
229 """Returns an instance of a SyncStage that should be run.
230
231 Subclasses must override this method.
232 """
233 raise NotImplementedError()
234
235 def RunStages(self):
236 """Subclasses must override this method. Runs the appropriate code."""
237 raise NotImplementedError()
238
239 def _WriteCheckpoint(self):
240 """Drops a completed stages file with current state."""
241 with open(self.completed_stages_file, 'w+') as save_file:
242 results_lib.Results.SaveCompletedStages(save_file)
243
244 def _ShouldReExecuteInBuildRoot(self):
245 """Returns True if this build should be re-executed in the buildroot."""
246 abs_buildroot = os.path.abspath(self.options.buildroot)
247 return not os.path.abspath(__file__).startswith(abs_buildroot)
248
249 def _ReExecuteInBuildroot(self, sync_instance):
250 """Reexecutes self in buildroot and returns True if build succeeds.
251
252 This allows the buildbot code to test itself when changes are patched for
253 buildbot-related code. This is a no-op if the buildroot == buildroot
254 of the running chromite checkout.
255
256 Args:
257 sync_instance: Instance of the sync stage that was run to sync.
258
259 Returns:
260 True if the Build succeeded.
261 """
262 # If we are resuming, use last checkpoint.
263 if not self.options.resume:
264 self._WriteCheckpoint()
265
266 # Re-write paths to use absolute paths.
267 # Suppress any timeout options given from the commandline in the
268 # invoked cbuildbot; our timeout will enforce it instead.
269 args_to_append = ['--resume', '--timeout', '0', '--buildroot',
270 os.path.abspath(self.options.buildroot)]
271
272 if self.options.chrome_root:
273 args_to_append += ['--chrome_root',
274 os.path.abspath(self.options.chrome_root)]
275
276 if stages.ManifestVersionedSyncStage.manifest_manager:
277 ver = stages.ManifestVersionedSyncStage.manifest_manager.current_version
278 args_to_append += ['--version', ver]
279
280 if isinstance(sync_instance, stages.CommitQueueSyncStage):
281 vp_file = sync_instance.SaveValidationPool()
282 args_to_append += ['--validation_pool', vp_file]
283
284 # Re-run the command in the buildroot.
285 # Finally, be generous and give the invoked cbuildbot 30s to shutdown
286 # when something occurs. It should exit quicker, but the sigterm may
287 # hit while the system is particularly busy.
288 return_obj = cros_lib.RunCommand(
289 [_PATH_TO_CBUILDBOT] + sys.argv[1:] + args_to_append,
290 cwd=self.options.buildroot, error_code_ok=True, kill_timeout=30)
291 return return_obj.returncode == 0
292
293 def Run(self):
294 """Main runner for this builder class. Runs build and prints summary."""
295 print_report = True
296 success = True
297 try:
298 self.Initialize()
299 sync_instance = self.GetSyncInstance()
300 sync_instance.Run()
301 self._SetReleaseTag()
302
303 if self.gerrit_patches or self.local_patches:
304 self._RunStage(stages.PatchChangesStage,
305 self.gerrit_patches, self.local_patches)
306
307 if self._ShouldReExecuteInBuildRoot():
308 print_report = False
309 success = self._ReExecuteInBuildroot(sync_instance)
310 else:
311 self.RunStages()
312
313 finally:
314 if print_report:
315 self._WriteCheckpoint()
316 print '\n\n\n@@@BUILD_STEP Report@@@\n'
317 results_lib.Results.Report(sys.stdout, self.archive_urls,
318 self.release_tag)
319 success = results_lib.Results.BuildSucceededSoFar()
320
321 return success
322
323
324class SimpleBuilder(Builder):
325 """Builder that performs basic vetting operations."""
326
327 def GetSyncInstance(self):
328 """Sync to lkgm or TOT as necessary.
329
330 Returns: the instance of the sync stage that was run.
331 """
332 if self.options.lkgm or self.build_config['use_lkgm']:
333 sync_stage = self._GetStageInstance(stages.LKGMSyncStage)
334 else:
335 sync_stage = self._GetStageInstance(stages.SyncStage)
336
337 return sync_stage
338
David James58e0c092012-03-04 20:31:12 -0800339 def _RunBackgroundStagesForBoard(self, board):
340 """Run background board-specific stages for the specified board."""
David James58e0c092012-03-04 20:31:12 -0800341 archive_stage = self.archive_stages[board]
David James944a48e2012-03-07 12:19:03 -0800342 configs = self.build_config['board_specific_configs']
343 config = configs.get(board, self.build_config)
344 stage_list = [[stages.VMTestStage, board, archive_stage],
345 [stages.ChromeTestStage, board, archive_stage],
346 [stages.UnitTestStage, board],
347 [stages.UploadPrebuiltsStage, board]]
Brian Harring3fec5a82012-03-01 05:57:03 -0800348
David James58e0c092012-03-04 20:31:12 -0800349 # We can not run hw tests without archiving the payloads.
350 if self.options.archive:
David James944a48e2012-03-07 12:19:03 -0800351 for suite in config['hw_tests']:
352 stage_list.append([stages.HWTestStage, board, archive_stage, suite])
Chris Sosab50dc932012-03-01 14:00:58 -0800353
David James944a48e2012-03-07 12:19:03 -0800354 steps = [self._GetStageInstance(*x, config=config).Run for x in stage_list]
355 background.RunParallelSteps(steps + [archive_stage.Run])
Brian Harring3fec5a82012-03-01 05:57:03 -0800356
357 def RunStages(self):
358 """Runs through build process."""
359 self._RunStage(stages.BuildBoardStage)
360
361 # TODO(sosa): Split these out into classes.
Brian Harring3fec5a82012-03-01 05:57:03 -0800362 if self.build_config['build_type'] == constants.CHROOT_BUILDER_TYPE:
363 self._RunStage(stages.SDKTestStage)
364 self._RunStage(stages.UploadPrebuiltsStage,
365 constants.CHROOT_BUILDER_BOARD)
366 elif self.build_config['build_type'] == constants.REFRESH_PACKAGES_TYPE:
367 self._RunStage(stages.RefreshPackageStatusStage)
368 else:
369 self._RunStage(stages.UprevStage)
Brian Harring3fec5a82012-03-01 05:57:03 -0800370
David James944a48e2012-03-07 12:19:03 -0800371 configs = self.build_config['board_specific_configs']
David James58e0c092012-03-04 20:31:12 -0800372 for board in self.build_config['boards']:
David James944a48e2012-03-07 12:19:03 -0800373 config = configs.get(board, self.build_config)
374 archive_stage = self._GetStageInstance(stages.ArchiveStage, board,
375 config=config)
David James58e0c092012-03-04 20:31:12 -0800376 self.archive_stages[board] = archive_stage
377
David James944a48e2012-03-07 12:19:03 -0800378 # Set up a process pool to run test/archive stages in the background.
379 # This process runs task(board) for each board added to the queue.
David James58e0c092012-03-04 20:31:12 -0800380 queue = multiprocessing.Queue()
381 task = self._RunBackgroundStagesForBoard
382 with background.BackgroundTaskRunner(queue, task):
David James944a48e2012-03-07 12:19:03 -0800383 for board in self.build_config['boards']:
David James58e0c092012-03-04 20:31:12 -0800384 # Run BuildTarget in the foreground.
David James944a48e2012-03-07 12:19:03 -0800385 archive_stage = self.archive_stages[board]
386 config = configs.get(board, self.build_config)
387 self._RunStage(stages.BuildTargetStage, board, archive_stage,
388 config=config)
David James58e0c092012-03-04 20:31:12 -0800389 self.archive_urls[board] = archive_stage.GetDownloadUrl()
390
David James944a48e2012-03-07 12:19:03 -0800391 # Kick off task(board) in the background.
David James58e0c092012-03-04 20:31:12 -0800392 queue.put([board])
393
Brian Harring3fec5a82012-03-01 05:57:03 -0800394
395class DistributedBuilder(SimpleBuilder):
396 """Build class that has special logic to handle distributed builds.
397
398 These builds sync using git/manifest logic in manifest_versions. In general
399 they use a non-distributed builder code for the bulk of the work.
400 """
David James944a48e2012-03-07 12:19:03 -0800401 def __init__(self, options, build_config):
Brian Harring3fec5a82012-03-01 05:57:03 -0800402 """Initializes a buildbot builder.
403
404 Extra variables:
405 completion_stage_class: Stage used to complete a build. Set in the Sync
406 stage.
407 """
David James944a48e2012-03-07 12:19:03 -0800408 super(DistributedBuilder, self).__init__(options, build_config)
Brian Harring3fec5a82012-03-01 05:57:03 -0800409 self.completion_stage_class = None
410
411 def GetSyncInstance(self):
412 """Syncs the tree using one of the distributed sync logic paths.
413
414 Returns: the instance of the sync stage that was run.
415 """
416 # Determine sync class to use. CQ overrides PFQ bits so should check it
417 # first.
418 if cbuildbot_config.IsCQType(self.build_config['build_type']):
419 sync_stage = self._GetStageInstance(stages.CommitQueueSyncStage)
420 self.completion_stage_class = stages.CommitQueueCompletionStage
421 elif cbuildbot_config.IsPFQType(self.build_config['build_type']):
422 sync_stage = self._GetStageInstance(stages.LKGMCandidateSyncStage)
423 self.completion_stage_class = stages.LKGMCandidateSyncCompletionStage
424 else:
425 sync_stage = self._GetStageInstance(stages.ManifestVersionedSyncStage)
426 self.completion_stage_class = stages.ManifestVersionedSyncCompletionStage
427
428 return sync_stage
429
430 def Publish(self, was_build_successful):
431 """Completes build by publishing any required information."""
432 completion_stage = self._GetStageInstance(self.completion_stage_class,
433 was_build_successful)
434 completion_stage.Run()
435 name = completion_stage.name
436 if not results_lib.Results.WasStageSuccessful(name):
437 should_publish_changes = False
438 else:
439 should_publish_changes = (self.build_config['master'] and
440 was_build_successful)
441
442 if should_publish_changes:
443 self._RunStage(stages.PublishUprevChangesStage)
444
445 def RunStages(self):
446 """Runs simple builder logic and publishes information to overlays."""
447 was_build_successful = False
448 try:
David Jamesf55709e2012-03-13 09:10:15 -0700449 super(DistributedBuilder, self).RunStages()
450 was_build_successful = results_lib.Results.BuildSucceededSoFar()
Brian Harring3fec5a82012-03-01 05:57:03 -0800451 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
Brian Harring3fec5a82012-03-01 05:57:03 -0800460
461def _ConfirmBuildRoot(buildroot):
462 """Confirm with user the inferred buildroot, and mark it as confirmed."""
463 warning = 'Using default directory %s as buildroot' % buildroot
464 response = cros_lib.YesNoPrompt(default=cros_lib.NO, warning=warning,
465 full=True)
466 if response == cros_lib.NO:
467 print('Please specify a buildroot with the --buildroot option.')
468 sys.exit(0)
469
470 if not os.path.exists(buildroot):
471 os.mkdir(buildroot)
472
473 repository.CreateTrybotMarker(buildroot)
474
475
476def _DetermineDefaultBuildRoot(internal_build):
477 """Default buildroot to be under the directory that contains current checkout.
478
479 Arguments:
480 internal_build: Whether the build is an internal build
481 """
482 repo_dir = cros_lib.FindRepoDir()
483 if not repo_dir:
484 cros_lib.Die('Could not find root of local checkout. Please specify'
485 'using --buildroot option.')
486
487 # Place trybot buildroot under the directory containing current checkout.
488 top_level = os.path.dirname(os.path.realpath(os.path.dirname(repo_dir)))
489 if internal_build:
490 buildroot = os.path.join(top_level, _DEFAULT_INT_BUILDROOT)
491 else:
492 buildroot = os.path.join(top_level, _DEFAULT_EXT_BUILDROOT)
493
494 return buildroot
495
496
497def _BackupPreviousLog(log_file, backup_limit=25):
498 """Rename previous log.
499
500 Args:
501 log_file: The absolute path to the previous log.
502 """
503 if os.path.exists(log_file):
504 old_logs = sorted(glob.glob(log_file + '.*'),
505 key=distutils.version.LooseVersion)
506
507 if len(old_logs) >= backup_limit:
508 os.remove(old_logs[0])
509
510 last = 0
511 if old_logs:
512 last = int(old_logs.pop().rpartition('.')[2])
513
514 os.rename(log_file, log_file + '.' + str(last + 1))
515
516
David James944a48e2012-03-07 12:19:03 -0800517def _RunBuildStagesWrapper(options, build_config):
Brian Harring3fec5a82012-03-01 05:57:03 -0800518 """Helper function that wraps RunBuildStages()."""
519 def IsDistributedBuilder():
520 """Determines whether the build_config should be a DistributedBuilder."""
521 if not options.buildbot:
522 return False
523 elif build_config['build_type'] in _DISTRIBUTED_TYPES:
524 chrome_rev = build_config['chrome_rev']
525 if options.chrome_rev: chrome_rev = options.chrome_rev
526 # We don't do distributed logic to TOT Chrome PFQ's, nor local
527 # chrome roots (e.g. chrome try bots)
528 if chrome_rev not in [constants.CHROME_REV_TOT,
529 constants.CHROME_REV_LOCAL,
530 constants.CHROME_REV_SPEC]:
531 return True
532
533 return False
534
535 # Start tee-ing output to file.
536 log_file = None
537 if options.tee:
538 default_dir = os.path.join(options.buildroot, _DEFAULT_LOG_DIR)
539 dirname = options.log_dir or default_dir
540 log_file = os.path.join(dirname, _BUILDBOT_LOG_FILE)
541
542 cros_lib.SafeMakedirs(dirname)
543 _BackupPreviousLog(log_file)
544
545 try:
546 with cros_lib.AllowDisabling(options.tee, tee.Tee, log_file):
547 cros_lib.Info("cbuildbot executed with args %s"
548 % ' '.join(map(repr, sys.argv)))
549 if IsDistributedBuilder():
David James944a48e2012-03-07 12:19:03 -0800550 buildbot = DistributedBuilder(options, build_config)
Brian Harring3fec5a82012-03-01 05:57:03 -0800551 else:
David James944a48e2012-03-07 12:19:03 -0800552 buildbot = SimpleBuilder(options, build_config)
Brian Harring3fec5a82012-03-01 05:57:03 -0800553
554 if not buildbot.Run():
555 sys.exit(1)
556 finally:
557 if options.tee:
558 cros_lib.Info('Output should be saved to %s' % log_file)
559
560
561# Parser related functions
562
563
564def _CheckAndSplitLocalPatches(options):
565 """Do an early quick check of the passed-in patches.
566
567 If the branch of a project is not specified we append the current branch the
568 project is on.
569 """
570 patch_args = options.local_patches.split()
571 options.local_patches = []
572 for patch in patch_args:
573 components = patch.split(':')
574 if len(components) > 2:
575 msg = 'Specify local patches in project[:branch] format.'
576 raise optparse.OptionValueError(msg)
577
578 # validate project
579 project = components[0]
580 if not cros_lib.DoesProjectExist('.', project):
581 raise optparse.OptionValueError('Project %s does not exist.' % project)
582
583 project_dir = cros_lib.GetProjectDir('.', project)
584
585 # If no branch was specified, we use the project's current branch.
586 if len(components) == 1:
587 branch = cros_lib.GetCurrentBranch(project_dir)
588 if not branch:
589 raise optparse.OptionValueError('project %s is not on a branch!'
590 % project)
591 # Append branch information to patch
592 patch = '%s:%s' % (project, branch)
593 else:
594 branch = components[1]
595 if not cros_lib.DoesLocalBranchExist(project_dir, branch):
596 raise optparse.OptionValueError('Project %s does not have branch %s'
597 % (project, branch))
598
599 options.local_patches.append(patch)
600
601
602def _CheckAndSplitGerritPatches(_option, _opt_str, value, parser):
603 """Early quick check of patches and convert them into a list."""
604 parser.values.gerrit_patches = value.split()
605
606
607def _CheckBuildRootOption(_option, _opt_str, value, parser):
608 """Validate and convert buildroot to full-path form."""
609 value = value.strip()
610 if not value or value == '/':
611 raise optparse.OptionValueError('Invalid buildroot specified')
612
613 parser.values.buildroot = os.path.realpath(os.path.expanduser(value))
614
615
616def _CheckLogDirOption(_option, _opt_str, value, parser):
617 """Validate and convert buildroot to full-path form."""
618 parser.values.log_dir = os.path.abspath(os.path.expanduser(value))
619
620
621def _CheckChromeVersionOption(_option, _opt_str, value, parser):
622 """Upgrade other options based on chrome_version being passed."""
623 value = value.strip()
624
625 if parser.values.chrome_rev is None and value:
626 parser.values.chrome_rev = constants.CHROME_REV_SPEC
627
628 parser.values.chrome_version = value
629
630
631def _CheckChromeRootOption(_option, _opt_str, value, parser):
632 """Validate and convert chrome_root to full-path form."""
633 value = value.strip()
634 if not value or value == '/':
635 raise optparse.OptionValueError('Invalid chrome_root specified')
636
637 if parser.values.chrome_rev is None:
638 parser.values.chrome_rev = constants.CHROME_REV_LOCAL
639
640 parser.values.chrome_root = os.path.realpath(os.path.expanduser(value))
641
642
643def _CheckChromeRevOption(_option, _opt_str, value, parser):
644 """Validate the chrome_rev option."""
645 value = value.strip()
646 if value not in constants.VALID_CHROME_REVISIONS:
647 raise optparse.OptionValueError('Invalid chrome rev specified')
648
649 parser.values.chrome_rev = value
650
651
652def _CreateParser():
653 """Generate and return the parser with all the options."""
654 # Parse options
655 usage = "usage: %prog [options] buildbot_config"
656 parser = optparse.OptionParser(usage=usage)
657
658 # Main options
659 parser.add_option('-a', '--all', action='store_true', dest='print_all',
660 default=False,
661 help=('List all of the buildbot configs available. Use '
662 'with the --list option'))
663 parser.add_option('-r', '--buildroot', action='callback', dest='buildroot',
664 type='string', callback=_CheckBuildRootOption,
665 help=('Root directory where source is checked out to, and '
666 'where the build occurs. For external build configs, '
667 "defaults to 'trybot' directory at top level of your "
668 'repo-managed checkout.'))
669 parser.add_option('--chrome_rev', default=None, type='string',
670 action='callback', dest='chrome_rev',
671 callback=_CheckChromeRevOption,
672 help=('Revision of Chrome to use, of type '
673 '[%s]' % '|'.join(constants.VALID_CHROME_REVISIONS)))
674 parser.add_option('-g', '--gerrit-patches', action='callback',
675 type='string', callback=_CheckAndSplitGerritPatches,
676 metavar="'Id1 *int_Id2...IdN'",
677 help=("Space-separated list of short-form Gerrit "
678 "Change-Id's or change numbers to patch. Please "
679 "prepend '*' to internal Change-Id's"))
680 parser.add_option('-l', '--list', action='store_true', dest='list',
681 default=False,
682 help=('List the suggested trybot configs to use. Use '
683 '--all to list all of the available configs.'))
684 parser.add_option('-p', '--local-patches', action='store', type='string',
685 metavar="'<project1>[:<branch1>]...<projectN>[:<branchN>]'",
686 help=('Space-separated list of project branches with '
687 'patches to apply. Projects are specified by name. '
688 'If no branch is specified the current branch of the '
689 'project will be used.'))
690 parser.add_option('--profile', default=None, type='string', action='store',
691 dest='profile',
692 help=('Name of profile to sub-specify board variant.'))
693 parser.add_option('--remote', default=False, action='store_true',
694 dest='remote',
695 help=('Specifies that this tryjob should be run remotely.'))
696
697 # Advanced options
698 group = optparse.OptionGroup(
699 parser,
700 'Advanced Options',
701 'Caution: use these options at your own risk.')
702
703 group.add_option('--buildbot', dest='buildbot', action='store_true',
704 default=False, help='This is running on a buildbot')
705 group.add_option('--buildnumber',
706 help='build number', type='int', default=0)
707 group.add_option('--chrome_root', default=None, type='string',
708 action='callback', dest='chrome_root',
709 callback=_CheckChromeRootOption,
710 help='Local checkout of Chrome to use.')
711 group.add_option('--chrome_version', default=None, type='string',
712 action='callback', dest='chrome_version',
713 callback=_CheckChromeVersionOption,
714 help='Used with SPEC logic to force a particular SVN '
715 'revision of chrome rather than the latest.')
716 group.add_option('--clobber', action='store_true', dest='clobber',
717 default=False,
718 help='Clears an old checkout before syncing')
719 group.add_option('--lkgm', action='store_true', dest='lkgm', default=False,
720 help='Sync to last known good manifest blessed by PFQ')
721 parser.add_option('--log_dir', action='callback', dest='log_dir',
722 type='string', callback=_CheckLogDirOption,
723 help=('Directory where logs are stored.'))
724 group.add_option('--maxarchives', dest='max_archive_builds',
725 default=3, type='int',
726 help="Change the local saved build count limit.")
727 group.add_option('--noarchive', action='store_false', dest='archive',
728 default=True,
729 help="Don't run archive stage.")
730 group.add_option('--nobuild', action='store_false', dest='build',
731 default=True,
732 help="Don't actually build (for cbuildbot dev")
733 group.add_option('--noclean', action='store_false', dest='clean',
734 default=True,
735 help="Don't clean the buildroot")
736 group.add_option('--noprebuilts', action='store_false', dest='prebuilts',
737 default=True,
738 help="Don't upload prebuilts.")
739 group.add_option('--nosync', action='store_false', dest='sync',
740 default=True,
741 help="Don't sync before building.")
742 group.add_option('--nocgroups', action='store_false', dest='cgroups',
743 default=True,
744 help='Disable cbuildbots usage of cgroups.')
745 group.add_option('--reference-repo', action='store', default=None,
746 dest='reference_repo',
747 help='Reuse git data stored in an existing repo '
748 'checkout. This can drastically reduce the network '
749 'time spent setting up the trybot checkout. By '
750 "default, if this option isn't given but cbuildbot "
751 'is invoked from a repo checkout, cbuildbot will '
752 'use the repo root.')
Ryan Cuiba41ad32012-03-08 17:15:29 -0800753 group.add_option('--remote-trybot', dest='remote_trybot', action='store_true',
754 default=False,
755 help=('This is running on a remote trybot machine. '
756 'Requires --user and --timestamp to be set.'))
Brian Harring3fec5a82012-03-01 05:57:03 -0800757 group.add_option('--timeout', action='store', type='int', default=0,
758 help="Specify the maximum amount of time this job can run "
759 "for, at which point the build will be aborted. If "
760 "set to zero, then there is no timeout")
761 group.add_option('--notests', action='store_false', dest='tests',
762 default=True,
763 help='Override values from buildconfig and run no tests.')
764 group.add_option('--nouprev', action='store_false', dest='uprev',
765 default=True,
766 help='Override values from buildconfig and never uprev.')
767 group.add_option('--resume', action='store_true',
768 default=False,
769 help='Skip stages already successfully completed.')
770 group.add_option('--validation_pool', default=None,
771 help='Path to a pickled validation pool. Intended for use '
772 'only with the commit queue.')
773 group.add_option('--version', dest='force_version', default=None,
774 help='Used with manifest logic. Forces use of this version '
775 'rather than create or get latest.')
776
777 parser.add_option_group(group)
778
779 # Debug options
780 group = optparse.OptionGroup(parser, "Debug Options")
781
Ryan Cui85867972012-02-23 18:21:49 -0800782 group.add_option('--debug', action='store_true', default=None,
Brian Harring3fec5a82012-03-01 05:57:03 -0800783 help='Override some options to run as a developer.')
784 group.add_option('--dump_config', action='store_true', dest='dump_config',
785 default=False,
786 help='Dump out build config options, and exit.')
787 group.add_option('--notee', action='store_false', dest='tee', default=True,
788 help="Disable logging and internal tee process. Primarily "
789 "used for debugging cbuildbot itself.")
790 parser.add_option_group(group)
791 return parser
792
793
Ryan Cui85867972012-02-23 18:21:49 -0800794def _FinishParsing(options, args):
795 """Perform some parsing tasks that need to take place after optparse.
796
797 This function needs to be easily testable! Keep it free of
798 environment-dependent code. Put more detailed usage validation in
799 _PostParseCheck().
Brian Harring3fec5a82012-03-01 05:57:03 -0800800
801 Args:
Ryan Cui85867972012-02-23 18:21:49 -0800802 options, args: The options/args object returned by optparse
Brian Harring3fec5a82012-03-01 05:57:03 -0800803 """
Brian Harring3fec5a82012-03-01 05:57:03 -0800804 if options.chrome_root:
805 if options.chrome_rev != constants.CHROME_REV_LOCAL:
806 cros_lib.Die('Chrome rev must be %s if chrome_root is set.' %
807 constants.CHROME_REV_LOCAL)
808 else:
809 if options.chrome_rev == constants.CHROME_REV_LOCAL:
810 cros_lib.Die('Chrome root must be set if chrome_rev is %s.' %
811 constants.CHROME_REV_LOCAL)
812
813 if options.chrome_version:
814 if options.chrome_rev != constants.CHROME_REV_SPEC:
815 cros_lib.Die('Chrome rev must be %s if chrome_version is set.' %
816 constants.CHROME_REV_SPEC)
817 else:
818 if options.chrome_rev == constants.CHROME_REV_SPEC:
819 cros_lib.Die('Chrome rev must not be %s if chrome_version is not set.' %
820 constants.CHROME_REV_SPEC)
821
822 if options.remote and options.local_patches:
823 cros_lib.Die('Local patching not yet supported with remote tryjobs.')
824
Brian Harring3fec5a82012-03-01 05:57:03 -0800825 if options.remote and not options.gerrit_patches:
826 cros_lib.Die('Must provide patches when running with --remote.')
827
828 if len(args) > 1 and not options.remote:
829 cros_lib.Die('Multiple configs not supported if not running with --remote.')
830
Ryan Cuiba41ad32012-03-08 17:15:29 -0800831 if options.buildbot and options.remote_trybot:
832 cros_lib.Die('--buildbot and --remote-trybot cannot be used together.')
833
Ryan Cui85867972012-02-23 18:21:49 -0800834 # Record whether --debug was set explicitly vs. it was inferred.
835 options.debug_forced = False
836 if options.debug:
837 options.debug_forced = True
838 else:
839 options.debug = not options.buildbot
Brian Harring3fec5a82012-03-01 05:57:03 -0800840
Brian Harring3fec5a82012-03-01 05:57:03 -0800841
Ryan Cui85867972012-02-23 18:21:49 -0800842def _PostParseCheck(options, args):
843 """Perform some usage validation after we've parsed the arguments
Brian Harring3fec5a82012-03-01 05:57:03 -0800844
Ryan Cui85867972012-02-23 18:21:49 -0800845 Args:
846 options/args: The options/args object returned by optparse
847 """
848 if not options.resume:
849 try:
850 # TODO(rcui): Split this into two stages, one that parses, another that
851 # validates. Parsing step will be called by _FinishParsing().
852 if options.local_patches:
853 _CheckAndSplitLocalPatches(options)
854
855 except optparse.OptionValueError as e:
856 cros_lib.Die(str(e))
857
858
859def _ParseCommandLine(parser, argv):
860 """Completely parse the commandline arguments"""
Brian Harring3fec5a82012-03-01 05:57:03 -0800861 (options, args) = parser.parse_args(argv)
862 # Strip out null arguments.
863 # TODO(rcui): Remove when buildbot is fixed
864 args = [arg for arg in args if arg]
Ryan Cui85867972012-02-23 18:21:49 -0800865 _FinishParsing(options, args)
866 return options, args
867
868
869def main(argv):
870 # Set umask to 022 so files created by buildbot are readable.
871 os.umask(022)
872
873 if cros_lib.IsInsideChroot():
874 cros_lib.Die('Please run cbuildbot from outside the chroot.')
875
876 parser = _CreateParser()
877 (options, args) = _ParseCommandLine(parser, argv)
Brian Harring3fec5a82012-03-01 05:57:03 -0800878
879 if options.list:
880 _PrintValidConfigs(not options.print_all)
881 sys.exit(0)
882
883 _PostParseCheck(options, args)
884
885 if options.remote:
886 # Verify configs are valid.
887 for bot in args:
888 _GetConfig(bot)
889
890 # Verify gerrit patches are valid.
891 _PreProcessPatches(options.gerrit_patches, options.local_patches)
892
893 remote_try.RemoteTryJob(options, args).Submit()
894 sys.exit(0)
895
896 if args:
897 # Only expecting one config
898 bot_id = args[-1]
899 build_config = _GetConfig(bot_id)
900 else:
901 parser.error('Invalid usage. Use -h to see usage.')
902
903 if options.reference_repo is None:
904 repo_path = os.path.join(constants.SOURCE_ROOT, '.repo')
905 # If we're being run from a repo checkout, reuse the repo's git pool to
906 # cut down on sync time.
907 if os.path.exists(repo_path):
908 options.reference_repo = constants.SOURCE_ROOT
909 elif options.reference_repo:
910 if not os.path.exists(options.reference_repo):
911 parser.error('Reference path %s does not exist'
912 % (options.reference_repo,))
913 elif not os.path.exists(os.path.join(options.reference_repo, '.repo')):
914 parser.error('Reference path %s does not look to be the base of a '
915 'repo checkout; no .repo exists in the root.'
916 % (options.reference_repo,))
Brian Harring470f6112012-03-02 11:47:10 -0800917 if options.buildbot:
918 if not options.cgroups:
919 parser.error('Options --buildbot and --nocgroups cannot be used '
920 'together. Cgroup support is required for buildbot mode.')
921 if not cgroups.Cgroup.CgroupsSupported():
922 parser.error('Option --buildbot was given, but this system does not '
923 'support cgroups. Failing.')
Brian Harring3fec5a82012-03-01 05:57:03 -0800924
Brian Harring351ce442012-03-09 16:38:14 -0800925 missing = []
926 for program in _BUILDBOT_REQUIRED_BINARIES:
927 ret = cros_lib.RunCommand('which %s' % program, shell=True,
928 redirect_stderr=True, redirect_stdout=True,
929 error_code_ok=True, print_cmd=False)
930 if ret.returncode != 0:
931 missing.append(program)
932
933 if missing:
934 parser.error("Option --buildbot requires the following binaries which "
935 "couldn't be found in $PATH: %s"
936 % (', '.join(missing)))
937
938
939
Brian Harring3fec5a82012-03-01 05:57:03 -0800940 if options.reference_repo:
941 options.reference_repo = os.path.abspath(options.reference_repo)
942
943 if options.dump_config:
944 # This works, but option ordering is bad...
945 print 'Configuration %s:' % bot_id
946 pretty_printer = pprint.PrettyPrinter(indent=2)
947 pretty_printer.pprint(build_config)
948 sys.exit(0)
949
950 if not options.buildroot:
951 if options.buildbot:
952 parser.error('Please specify a buildroot with the --buildroot option.')
Brian Harring470f6112012-03-02 11:47:10 -0800953 options.buildroot = _DetermineDefaultBuildRoot(build_config['internal'])
954 # We use a marker file in the buildroot to indicate the user has
955 # consented to using this directory.
956 if not os.path.exists(repository.GetTrybotMarkerPath(options.buildroot)):
957 _ConfirmBuildRoot(options.buildroot)
Brian Harring3fec5a82012-03-01 05:57:03 -0800958
959 # Sanity check of buildroot- specifically that it's not pointing into the
960 # midst of an existing repo since git-repo doesn't support nesting.
Brian Harring3fec5a82012-03-01 05:57:03 -0800961 if (not repository.IsARepoRoot(options.buildroot) and
David James6b80dc62012-02-29 15:34:40 -0800962 repository.InARepoRepository(options.buildroot)):
Brian Harring3fec5a82012-03-01 05:57:03 -0800963 parser.error('Configured buildroot %s points into a repository checkout, '
964 'rather than the root of it. This is not supported.'
965 % options.buildroot)
966
Brian Harringa184efa2012-03-04 11:51:25 -0800967 with cleanup.EnforcedCleanupSection() as critical_section:
968 with sudo.SudoKeepAlive():
969 with cros_lib.AllowDisabling(options.cgroups,
970 cgroups.ContainChildren, 'cbuildbot'):
971 # Mark everything between EnforcedCleanupSection and here as having to
972 # be rolled back via the contextmanager cleanup handlers. This ensures
973 # that sudo bits cannot outlive cbuildbot, that anything cgroups
974 # would kill gets killed, etc.
975 critical_section.ForkWatchdog()
976
977 with cros_lib.AllowDisabling(options.timeout > 0,
978 cros_lib.Timeout, options.timeout):
979 if not options.buildbot:
980 build_config = cbuildbot_config.OverrideConfigForTrybot(
981 build_config)
982
983 _RunBuildStagesWrapper(options, build_config)