blob: 3a0f286a67b41c7d4d58b7bc8bfbe75584e8f1c7 [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
Chris Sosa4f6ffaf2012-05-01 17:05:44 -070015import logging
David James58e0c092012-03-04 20:31:12 -080016import multiprocessing
Brian Harring3fec5a82012-03-01 05:57:03 -080017import optparse
18import os
19import pprint
20import sys
Ryan Cui54da0702012-04-19 18:38:08 -070021import time
Brian Harring3fec5a82012-03-01 05:57:03 -080022
23from chromite.buildbot import builderstage as bs
24from chromite.buildbot import cbuildbot_background as background
25from chromite.buildbot import cbuildbot_config
26from chromite.buildbot import cbuildbot_stages as stages
27from chromite.buildbot import cbuildbot_results as results_lib
Brian Harring3fec5a82012-03-01 05:57:03 -080028from chromite.buildbot import constants
29from chromite.buildbot import gerrit_helper
30from chromite.buildbot import patch as cros_patch
31from chromite.buildbot import remote_try
32from chromite.buildbot import repository
33from chromite.buildbot import tee
34
Brian Harringc92a7012012-02-29 10:11:34 -080035from chromite.lib import cgroups
Brian Harringa184efa2012-03-04 11:51:25 -080036from chromite.lib import cleanup
Brian Harring3fec5a82012-03-01 05:57:03 -080037from chromite.lib import cros_build_lib as cros_lib
38from chromite.lib import sudo
39
Ryan Cuiadd49122012-03-21 22:19:58 -070040
Brian Harring3fec5a82012-03-01 05:57:03 -080041cros_lib.STRICT_SUDO = True
42
43_DEFAULT_LOG_DIR = 'cbuildbot_logs'
44_BUILDBOT_LOG_FILE = 'cbuildbot.log'
45_DEFAULT_EXT_BUILDROOT = 'trybot'
46_DEFAULT_INT_BUILDROOT = 'trybot-internal'
Matt Tennantf1e30972012-03-02 16:30:07 -080047_PATH_TO_CBUILDBOT = os.path.join(constants.CHROMITE_BIN_SUBDIR, 'cbuildbot')
Brian Harring3fec5a82012-03-01 05:57:03 -080048_DISTRIBUTED_TYPES = [constants.COMMIT_QUEUE_TYPE, constants.PFQ_TYPE,
49 constants.CANARY_TYPE, constants.CHROME_PFQ_TYPE,
50 constants.PALADIN_TYPE]
Brian Harring351ce442012-03-09 16:38:14 -080051_BUILDBOT_REQUIRED_BINARIES = ('pbzip2',)
Brian Harring3fec5a82012-03-01 05:57:03 -080052
53
Ryan Cui4f6cf7e2012-04-18 16:12:27 -070054def _PrintValidConfigs(display_all=False):
Brian Harring3fec5a82012-03-01 05:57:03 -080055 """Print a list of valid buildbot configs.
56
57 Arguments:
Ryan Cui4f6cf7e2012-04-18 16:12:27 -070058 display_all: Print all configs. Otherwise, prints only configs with
59 trybot_list=True.
Brian Harring3fec5a82012-03-01 05:57:03 -080060 """
Ryan Cui4f6cf7e2012-04-18 16:12:27 -070061 def _GetSortKey(config_name):
62 config_dict = cbuildbot_config.config[config_name]
63 return (not config_dict['trybot_list'], config_dict['description'],
64 config_name)
65
Brian Harring3fec5a82012-03-01 05:57:03 -080066 COLUMN_WIDTH = 45
67 print 'config'.ljust(COLUMN_WIDTH), 'description'
68 print '------'.ljust(COLUMN_WIDTH), '-----------'
69 config_names = cbuildbot_config.config.keys()
Ryan Cui4f6cf7e2012-04-18 16:12:27 -070070 config_names.sort(key=_GetSortKey)
Brian Harring3fec5a82012-03-01 05:57:03 -080071 for name in config_names:
Ryan Cui4f6cf7e2012-04-18 16:12:27 -070072 if display_all or cbuildbot_config.config[name]['trybot_list']:
73 desc = cbuildbot_config.config[name].get('description')
74 desc = desc if desc else ''
Brian Harring3fec5a82012-03-01 05:57:03 -080075 print name.ljust(COLUMN_WIDTH), desc
76
77
78def _GetConfig(config_name):
79 """Gets the configuration for the build"""
80 if not cbuildbot_config.config.has_key(config_name):
81 print 'Non-existent configuration %s specified.' % config_name
82 print 'Please specify one of:'
83 _PrintValidConfigs()
84 sys.exit(1)
85
86 result = cbuildbot_config.config[config_name]
87
88 return result
89
90
91def _GetChromiteTrackingBranch():
David James66009462012-03-25 10:08:38 -070092 """Returns the remote branch associated with chromite."""
Brian Harring3fec5a82012-03-01 05:57:03 -080093 cwd = os.path.dirname(os.path.realpath(__file__))
David James66009462012-03-25 10:08:38 -070094 branch = cros_lib.GetCurrentBranch(cwd)
95 if branch:
96 tracking_branch = cros_lib.GetTrackingBranch(branch, cwd)[1]
97 if tracking_branch.startswith('refs/heads/'):
98 return tracking_branch.replace('refs/heads/', '')
99 # If we are not on a branch, or if the tracking branch is a revision,
David James8b3c1bf2012-03-28 09:10:16 -0700100 # use the push branch. For repo repositories, this will be the manifest
101 # branch configured for this project. For other repositories, we'll just
102 # guess 'master', since there's no easy way to find out what branch
103 # we're on.
104 return cros_lib.GetPushBranch(cwd)[1]
Brian Harring3fec5a82012-03-01 05:57:03 -0800105
106
107def _CheckBuildRootBranch(buildroot, tracking_branch):
108 """Make sure buildroot branch is the same as Chromite branch."""
109 manifest_branch = cros_lib.GetManifestDefaultBranch(buildroot)
110 if manifest_branch != tracking_branch:
111 cros_lib.Die('Chromite is not on same branch as buildroot checkout\n' +
112 'Chromite is on branch %s.\n' % tracking_branch +
113 'Buildroot checked out to %s\n' % manifest_branch)
114
115
116def _PreProcessPatches(gerrit_patches, local_patches):
117 """Validate patches ASAP to catch user errors. Also generate patch info.
118
119 Args:
120 gerrit_patches: List of gerrit CL ID's passed in by user.
121 local_patches: List of local project branches to generate patches from.
122
123 Returns:
124 A tuple containing a list of cros_patch.GerritPatch and a list of
Matt Tennantd55b1f42012-04-13 14:15:01 -0700125 cros_patch.LocalGitRepoPatch objects.
Brian Harring3fec5a82012-03-01 05:57:03 -0800126 """
127 gerrit_patch_info = []
128 local_patch_info = []
129
130 try:
131 if gerrit_patches:
132 gerrit_patch_info = gerrit_helper.GetGerritPatchInfo(gerrit_patches)
133 for patch in gerrit_patch_info:
134 if patch.IsAlreadyMerged():
135 cros_lib.Warning('Patch %s has already been merged.' % str(patch))
136 except gerrit_helper.GerritException as e:
137 cros_lib.Die(str(e))
138
139 try:
140 if local_patches:
141 local_patch_info = cros_patch.PrepareLocalPatches(
142 local_patches,
143 _GetChromiteTrackingBranch())
144
145 except cros_patch.PatchException as e:
146 cros_lib.Die(str(e))
147
148 return gerrit_patch_info, local_patch_info
149
150
151def _IsIncrementalBuild(buildroot, clobber):
152 """Returns True if we are reusing an existing buildroot."""
153 repo_dir = os.path.join(buildroot, '.repo')
154 return not clobber and os.path.isdir(repo_dir)
155
156
157class Builder(object):
158 """Parent class for all builder types.
159
160 This class functions as a parent class for various build types. It's intended
161 use is builder_instance.Run().
162
163 Vars:
Brian Harring3fec5a82012-03-01 05:57:03 -0800164 build_config: The configuration dictionary from cbuildbot_config.
165 options: The options provided from optparse in main().
166 completed_stages_file: Where we store resume state.
167 archive_url: Where our artifacts for this builder will be archived.
168 tracking_branch: The tracking branch for this build.
169 release_tag: The associated "chrome os version" of this build.
170 gerrit_patches: Gerrit patches to be included in build.
171 local_patches: Local patches to be included in build.
172 """
173
David James944a48e2012-03-07 12:19:03 -0800174 def __init__(self, options, build_config):
Brian Harring3fec5a82012-03-01 05:57:03 -0800175 """Initializes instance variables. Must be called by all subclasses."""
Brian Harring3fec5a82012-03-01 05:57:03 -0800176 self.build_config = build_config
177 self.options = options
178
179 # TODO, Remove here and in config after bug chromium-os:14649 is fixed.
180 if self.build_config['chromeos_official']:
181 os.environ['CHROMEOS_OFFICIAL'] = '1'
182
183 self.completed_stages_file = os.path.join(options.buildroot,
184 '.completed_stages')
David James58e0c092012-03-04 20:31:12 -0800185 self.archive_stages = {}
Brian Harring3fec5a82012-03-01 05:57:03 -0800186 self.archive_urls = {}
187 self.release_tag = None
Brian Harringeb237932012-05-07 02:08:06 -0700188 self.target_manifest_branch = _GetChromiteTrackingBranch()
Brian Harring3fec5a82012-03-01 05:57:03 -0800189 self.gerrit_patches = None
190 self.local_patches = None
191
192 def Initialize(self):
193 """Runs through the initialization steps of an actual build."""
194 if self.options.resume and os.path.exists(self.completed_stages_file):
195 with open(self.completed_stages_file, 'r') as load_file:
196 results_lib.Results.RestoreCompletedStages(load_file)
197
198 # We only want to do this if we need to patch changes.
199 if not results_lib.Results.GetPrevious().get(
200 self._GetStageInstance(stages.PatchChangesStage, None, None).name):
201 self.gerrit_patches, self.local_patches = _PreProcessPatches(
202 self.options.gerrit_patches, self.options.local_patches)
203
Brian Harringeb237932012-05-07 02:08:06 -0700204 bs.BuilderStage.SetManifestBranch(self.target_manifest_branch)
Brian Harring3fec5a82012-03-01 05:57:03 -0800205
206 # Check branch matching early.
207 if _IsIncrementalBuild(self.options.buildroot, self.options.clobber):
Brian Harringeb237932012-05-07 02:08:06 -0700208 _CheckBuildRootBranch(self.options.buildroot, self.target_manifest_branch)
Brian Harring3fec5a82012-03-01 05:57:03 -0800209
210 self._RunStage(stages.CleanUpStage)
211
212 def _GetStageInstance(self, stage, *args, **kwargs):
213 """Helper function to get an instance given the args.
214
David James944a48e2012-03-07 12:19:03 -0800215 Useful as almost all stages just take in options and build_config.
Brian Harring3fec5a82012-03-01 05:57:03 -0800216 """
David James944a48e2012-03-07 12:19:03 -0800217 config = kwargs.pop('config', self.build_config)
218 return stage(self.options, config, *args, **kwargs)
Brian Harring3fec5a82012-03-01 05:57:03 -0800219
220 def _SetReleaseTag(self):
221 """Sets the release tag from the manifest_manager.
222
223 Must be run after sync stage as syncing enables us to have a release tag.
224 """
225 # Extract version we have decided to build into self.release_tag.
226 manifest_manager = stages.ManifestVersionedSyncStage.manifest_manager
227 if manifest_manager:
228 self.release_tag = manifest_manager.current_version
229
230 def _RunStage(self, stage, *args, **kwargs):
231 """Wrapper to run a stage."""
232 stage_instance = self._GetStageInstance(stage, *args, **kwargs)
233 return stage_instance.Run()
234
235 def GetSyncInstance(self):
236 """Returns an instance of a SyncStage that should be run.
237
238 Subclasses must override this method.
239 """
240 raise NotImplementedError()
241
242 def RunStages(self):
243 """Subclasses must override this method. Runs the appropriate code."""
244 raise NotImplementedError()
245
246 def _WriteCheckpoint(self):
247 """Drops a completed stages file with current state."""
248 with open(self.completed_stages_file, 'w+') as save_file:
249 results_lib.Results.SaveCompletedStages(save_file)
250
251 def _ShouldReExecuteInBuildRoot(self):
252 """Returns True if this build should be re-executed in the buildroot."""
253 abs_buildroot = os.path.abspath(self.options.buildroot)
254 return not os.path.abspath(__file__).startswith(abs_buildroot)
255
256 def _ReExecuteInBuildroot(self, sync_instance):
257 """Reexecutes self in buildroot and returns True if build succeeds.
258
259 This allows the buildbot code to test itself when changes are patched for
260 buildbot-related code. This is a no-op if the buildroot == buildroot
261 of the running chromite checkout.
262
263 Args:
264 sync_instance: Instance of the sync stage that was run to sync.
265
266 Returns:
267 True if the Build succeeded.
268 """
269 # If we are resuming, use last checkpoint.
270 if not self.options.resume:
271 self._WriteCheckpoint()
272
273 # Re-write paths to use absolute paths.
274 # Suppress any timeout options given from the commandline in the
275 # invoked cbuildbot; our timeout will enforce it instead.
Brian Harring585cd882012-04-10 13:23:19 -0700276 args_to_append = ['--resume', '--timeout', '0', '--notee', '--buildroot',
Brian Harring3fec5a82012-03-01 05:57:03 -0800277 os.path.abspath(self.options.buildroot)]
278
279 if self.options.chrome_root:
280 args_to_append += ['--chrome_root',
281 os.path.abspath(self.options.chrome_root)]
282
283 if stages.ManifestVersionedSyncStage.manifest_manager:
284 ver = stages.ManifestVersionedSyncStage.manifest_manager.current_version
285 args_to_append += ['--version', ver]
286
287 if isinstance(sync_instance, stages.CommitQueueSyncStage):
288 vp_file = sync_instance.SaveValidationPool()
289 args_to_append += ['--validation_pool', vp_file]
290
291 # Re-run the command in the buildroot.
292 # Finally, be generous and give the invoked cbuildbot 30s to shutdown
293 # when something occurs. It should exit quicker, but the sigterm may
294 # hit while the system is particularly busy.
295 return_obj = cros_lib.RunCommand(
296 [_PATH_TO_CBUILDBOT] + sys.argv[1:] + args_to_append,
297 cwd=self.options.buildroot, error_code_ok=True, kill_timeout=30)
298 return return_obj.returncode == 0
299
300 def Run(self):
301 """Main runner for this builder class. Runs build and prints summary."""
302 print_report = True
David James3d4d3502012-04-09 15:12:06 -0700303 exception_thrown = False
Brian Harring3fec5a82012-03-01 05:57:03 -0800304 success = True
305 try:
306 self.Initialize()
307 sync_instance = self.GetSyncInstance()
308 sync_instance.Run()
309 self._SetReleaseTag()
310
Ryan Cuicedd8a52012-03-22 02:28:35 -0700311 if (self.gerrit_patches or self.local_patches
312 or self.options.remote_patches):
Brian Harring3fec5a82012-03-01 05:57:03 -0800313 self._RunStage(stages.PatchChangesStage,
314 self.gerrit_patches, self.local_patches)
315
316 if self._ShouldReExecuteInBuildRoot():
317 print_report = False
318 success = self._ReExecuteInBuildroot(sync_instance)
319 else:
320 self.RunStages()
David James3d4d3502012-04-09 15:12:06 -0700321 except Exception:
322 exception_thrown = True
323 raise
Brian Harring3fec5a82012-03-01 05:57:03 -0800324 finally:
325 if print_report:
326 self._WriteCheckpoint()
327 print '\n\n\n@@@BUILD_STEP Report@@@\n'
328 results_lib.Results.Report(sys.stdout, self.archive_urls,
329 self.release_tag)
330 success = results_lib.Results.BuildSucceededSoFar()
David James3d4d3502012-04-09 15:12:06 -0700331 if exception_thrown and success:
332 success = False
333 print >> sys.stderr, """
334@@@STEP_FAILURE@@@
335Exception thrown, but all stages marked successful. This is an internal error,
336because the stage that threw the exception should be marked as failing."""
Brian Harring3fec5a82012-03-01 05:57:03 -0800337
338 return success
339
340
341class SimpleBuilder(Builder):
342 """Builder that performs basic vetting operations."""
343
344 def GetSyncInstance(self):
345 """Sync to lkgm or TOT as necessary.
346
347 Returns: the instance of the sync stage that was run.
348 """
349 if self.options.lkgm or self.build_config['use_lkgm']:
350 sync_stage = self._GetStageInstance(stages.LKGMSyncStage)
351 else:
352 sync_stage = self._GetStageInstance(stages.SyncStage)
353
354 return sync_stage
355
David James58e0c092012-03-04 20:31:12 -0800356 def _RunBackgroundStagesForBoard(self, board):
357 """Run background board-specific stages for the specified board."""
David James58e0c092012-03-04 20:31:12 -0800358 archive_stage = self.archive_stages[board]
David James944a48e2012-03-07 12:19:03 -0800359 configs = self.build_config['board_specific_configs']
360 config = configs.get(board, self.build_config)
361 stage_list = [[stages.VMTestStage, board, archive_stage],
362 [stages.ChromeTestStage, board, archive_stage],
363 [stages.UnitTestStage, board],
Chris Sosa0558ddf2012-05-08 17:36:39 -0700364 [stages.UploadPrebuiltsStage, board, archive_stage]]
Brian Harring3fec5a82012-03-01 05:57:03 -0800365
David James58e0c092012-03-04 20:31:12 -0800366 # We can not run hw tests without archiving the payloads.
367 if self.options.archive:
David James944a48e2012-03-07 12:19:03 -0800368 for suite in config['hw_tests']:
369 stage_list.append([stages.HWTestStage, board, archive_stage, suite])
Chris Sosab50dc932012-03-01 14:00:58 -0800370
David James944a48e2012-03-07 12:19:03 -0800371 steps = [self._GetStageInstance(*x, config=config).Run for x in stage_list]
372 background.RunParallelSteps(steps + [archive_stage.Run])
Brian Harring3fec5a82012-03-01 05:57:03 -0800373
374 def RunStages(self):
375 """Runs through build process."""
376 self._RunStage(stages.BuildBoardStage)
377
378 # TODO(sosa): Split these out into classes.
Brian Harring3fec5a82012-03-01 05:57:03 -0800379 if self.build_config['build_type'] == constants.CHROOT_BUILDER_TYPE:
380 self._RunStage(stages.SDKTestStage)
381 self._RunStage(stages.UploadPrebuiltsStage,
Chris Sosa0558ddf2012-05-08 17:36:39 -0700382 constants.CHROOT_BUILDER_BOARD, None)
Brian Harring3fec5a82012-03-01 05:57:03 -0800383 elif self.build_config['build_type'] == constants.REFRESH_PACKAGES_TYPE:
384 self._RunStage(stages.RefreshPackageStatusStage)
385 else:
386 self._RunStage(stages.UprevStage)
Brian Harring3fec5a82012-03-01 05:57:03 -0800387
David James944a48e2012-03-07 12:19:03 -0800388 configs = self.build_config['board_specific_configs']
David James58e0c092012-03-04 20:31:12 -0800389 for board in self.build_config['boards']:
David James944a48e2012-03-07 12:19:03 -0800390 config = configs.get(board, self.build_config)
391 archive_stage = self._GetStageInstance(stages.ArchiveStage, board,
392 config=config)
David James58e0c092012-03-04 20:31:12 -0800393 self.archive_stages[board] = archive_stage
394
David James944a48e2012-03-07 12:19:03 -0800395 # Set up a process pool to run test/archive stages in the background.
396 # This process runs task(board) for each board added to the queue.
David James58e0c092012-03-04 20:31:12 -0800397 queue = multiprocessing.Queue()
398 task = self._RunBackgroundStagesForBoard
399 with background.BackgroundTaskRunner(queue, task):
David James944a48e2012-03-07 12:19:03 -0800400 for board in self.build_config['boards']:
David James58e0c092012-03-04 20:31:12 -0800401 # Run BuildTarget in the foreground.
David James944a48e2012-03-07 12:19:03 -0800402 archive_stage = self.archive_stages[board]
403 config = configs.get(board, self.build_config)
404 self._RunStage(stages.BuildTargetStage, board, archive_stage,
Chris Sosa1a87b3e2012-04-12 13:20:42 -0700405 self.release_tag, config=config)
David James58e0c092012-03-04 20:31:12 -0800406 self.archive_urls[board] = archive_stage.GetDownloadUrl()
407
David James944a48e2012-03-07 12:19:03 -0800408 # Kick off task(board) in the background.
David James58e0c092012-03-04 20:31:12 -0800409 queue.put([board])
410
Brian Harring3fec5a82012-03-01 05:57:03 -0800411
412class DistributedBuilder(SimpleBuilder):
413 """Build class that has special logic to handle distributed builds.
414
415 These builds sync using git/manifest logic in manifest_versions. In general
416 they use a non-distributed builder code for the bulk of the work.
417 """
David James944a48e2012-03-07 12:19:03 -0800418 def __init__(self, options, build_config):
Brian Harring3fec5a82012-03-01 05:57:03 -0800419 """Initializes a buildbot builder.
420
421 Extra variables:
422 completion_stage_class: Stage used to complete a build. Set in the Sync
423 stage.
424 """
David James944a48e2012-03-07 12:19:03 -0800425 super(DistributedBuilder, self).__init__(options, build_config)
Brian Harring3fec5a82012-03-01 05:57:03 -0800426 self.completion_stage_class = None
427
428 def GetSyncInstance(self):
429 """Syncs the tree using one of the distributed sync logic paths.
430
431 Returns: the instance of the sync stage that was run.
432 """
433 # Determine sync class to use. CQ overrides PFQ bits so should check it
434 # first.
435 if cbuildbot_config.IsCQType(self.build_config['build_type']):
436 sync_stage = self._GetStageInstance(stages.CommitQueueSyncStage)
437 self.completion_stage_class = stages.CommitQueueCompletionStage
438 elif cbuildbot_config.IsPFQType(self.build_config['build_type']):
439 sync_stage = self._GetStageInstance(stages.LKGMCandidateSyncStage)
440 self.completion_stage_class = stages.LKGMCandidateSyncCompletionStage
441 else:
442 sync_stage = self._GetStageInstance(stages.ManifestVersionedSyncStage)
443 self.completion_stage_class = stages.ManifestVersionedSyncCompletionStage
444
445 return sync_stage
446
447 def Publish(self, was_build_successful):
448 """Completes build by publishing any required information."""
449 completion_stage = self._GetStageInstance(self.completion_stage_class,
450 was_build_successful)
451 completion_stage.Run()
452 name = completion_stage.name
453 if not results_lib.Results.WasStageSuccessful(name):
454 should_publish_changes = False
455 else:
456 should_publish_changes = (self.build_config['master'] and
457 was_build_successful)
458
459 if should_publish_changes:
460 self._RunStage(stages.PublishUprevChangesStage)
461
462 def RunStages(self):
463 """Runs simple builder logic and publishes information to overlays."""
464 was_build_successful = False
465 try:
David Jamesf55709e2012-03-13 09:10:15 -0700466 super(DistributedBuilder, self).RunStages()
467 was_build_successful = results_lib.Results.BuildSucceededSoFar()
Brian Harring3fec5a82012-03-01 05:57:03 -0800468 except SystemExit as ex:
469 # If a stage calls sys.exit(0), it's exiting with success, so that means
470 # we should mark ourselves as successful.
471 if ex.code == 0:
472 was_build_successful = True
473 raise
474 finally:
475 self.Publish(was_build_successful)
476
Brian Harring3fec5a82012-03-01 05:57:03 -0800477
478def _ConfirmBuildRoot(buildroot):
479 """Confirm with user the inferred buildroot, and mark it as confirmed."""
480 warning = 'Using default directory %s as buildroot' % buildroot
481 response = cros_lib.YesNoPrompt(default=cros_lib.NO, warning=warning,
482 full=True)
483 if response == cros_lib.NO:
484 print('Please specify a buildroot with the --buildroot option.')
485 sys.exit(0)
486
487 if not os.path.exists(buildroot):
488 os.mkdir(buildroot)
489
490 repository.CreateTrybotMarker(buildroot)
491
492
Ryan Cuieaa9efd2012-04-25 17:56:45 -0700493def _ConfirmRemoteBuildbotRun():
494 """Confirm user wants to run with --buildbot --remote."""
495 warning = ('You are about to launch a PRODUCTION job! This is *NOT* a '
496 'trybot run! Are you sure?')
497 response = cros_lib.YesNoPrompt(default=cros_lib.NO, warning=warning,
498 full=True)
499
500 if response == cros_lib.NO:
501 print('Please specify --pass-through="--debug".')
502 sys.exit(0)
503
504
Brian Harring3fec5a82012-03-01 05:57:03 -0800505def _DetermineDefaultBuildRoot(internal_build):
506 """Default buildroot to be under the directory that contains current checkout.
507
508 Arguments:
509 internal_build: Whether the build is an internal build
510 """
511 repo_dir = cros_lib.FindRepoDir()
512 if not repo_dir:
513 cros_lib.Die('Could not find root of local checkout. Please specify'
514 'using --buildroot option.')
515
516 # Place trybot buildroot under the directory containing current checkout.
517 top_level = os.path.dirname(os.path.realpath(os.path.dirname(repo_dir)))
518 if internal_build:
519 buildroot = os.path.join(top_level, _DEFAULT_INT_BUILDROOT)
520 else:
521 buildroot = os.path.join(top_level, _DEFAULT_EXT_BUILDROOT)
522
523 return buildroot
524
525
526def _BackupPreviousLog(log_file, backup_limit=25):
527 """Rename previous log.
528
529 Args:
530 log_file: The absolute path to the previous log.
531 """
532 if os.path.exists(log_file):
533 old_logs = sorted(glob.glob(log_file + '.*'),
534 key=distutils.version.LooseVersion)
535
536 if len(old_logs) >= backup_limit:
537 os.remove(old_logs[0])
538
539 last = 0
540 if old_logs:
541 last = int(old_logs.pop().rpartition('.')[2])
542
543 os.rename(log_file, log_file + '.' + str(last + 1))
544
545
David James944a48e2012-03-07 12:19:03 -0800546def _RunBuildStagesWrapper(options, build_config):
Brian Harring3fec5a82012-03-01 05:57:03 -0800547 """Helper function that wraps RunBuildStages()."""
548 def IsDistributedBuilder():
549 """Determines whether the build_config should be a DistributedBuilder."""
550 if not options.buildbot:
551 return False
552 elif build_config['build_type'] in _DISTRIBUTED_TYPES:
553 chrome_rev = build_config['chrome_rev']
554 if options.chrome_rev: chrome_rev = options.chrome_rev
555 # We don't do distributed logic to TOT Chrome PFQ's, nor local
556 # chrome roots (e.g. chrome try bots)
557 if chrome_rev not in [constants.CHROME_REV_TOT,
558 constants.CHROME_REV_LOCAL,
559 constants.CHROME_REV_SPEC]:
560 return True
561
562 return False
563
564 # Start tee-ing output to file.
565 log_file = None
566 if options.tee:
567 default_dir = os.path.join(options.buildroot, _DEFAULT_LOG_DIR)
568 dirname = options.log_dir or default_dir
569 log_file = os.path.join(dirname, _BUILDBOT_LOG_FILE)
570
571 cros_lib.SafeMakedirs(dirname)
572 _BackupPreviousLog(log_file)
573
574 try:
575 with cros_lib.AllowDisabling(options.tee, tee.Tee, log_file):
576 cros_lib.Info("cbuildbot executed with args %s"
577 % ' '.join(map(repr, sys.argv)))
Brian Harring585cd882012-04-10 13:23:19 -0700578 options.preserve_paths = set([_DEFAULT_LOG_DIR])
Brian Harring3fec5a82012-03-01 05:57:03 -0800579 if IsDistributedBuilder():
David James944a48e2012-03-07 12:19:03 -0800580 buildbot = DistributedBuilder(options, build_config)
Brian Harring3fec5a82012-03-01 05:57:03 -0800581 else:
David James944a48e2012-03-07 12:19:03 -0800582 buildbot = SimpleBuilder(options, build_config)
Brian Harring3fec5a82012-03-01 05:57:03 -0800583
584 if not buildbot.Run():
585 sys.exit(1)
586 finally:
587 if options.tee:
588 cros_lib.Info('Output should be saved to %s' % log_file)
589
590
591# Parser related functions
Ryan Cuicedd8a52012-03-22 02:28:35 -0700592def _CheckLocalPatches(local_patches):
Brian Harring3fec5a82012-03-01 05:57:03 -0800593 """Do an early quick check of the passed-in patches.
594
595 If the branch of a project is not specified we append the current branch the
596 project is on.
597 """
Ryan Cuicedd8a52012-03-22 02:28:35 -0700598 verified_patches = []
599 for patch in local_patches:
Brian Harring3fec5a82012-03-01 05:57:03 -0800600 components = patch.split(':')
601 if len(components) > 2:
602 msg = 'Specify local patches in project[:branch] format.'
603 raise optparse.OptionValueError(msg)
604
605 # validate project
606 project = components[0]
607 if not cros_lib.DoesProjectExist('.', project):
608 raise optparse.OptionValueError('Project %s does not exist.' % project)
609
610 project_dir = cros_lib.GetProjectDir('.', project)
611
612 # If no branch was specified, we use the project's current branch.
613 if len(components) == 1:
614 branch = cros_lib.GetCurrentBranch(project_dir)
615 if not branch:
616 raise optparse.OptionValueError('project %s is not on a branch!'
617 % project)
618 # Append branch information to patch
619 patch = '%s:%s' % (project, branch)
620 else:
621 branch = components[1]
622 if not cros_lib.DoesLocalBranchExist(project_dir, branch):
623 raise optparse.OptionValueError('Project %s does not have branch %s'
624 % (project, branch))
625
Ryan Cuicedd8a52012-03-22 02:28:35 -0700626 verified_patches.append(patch)
Brian Harring3fec5a82012-03-01 05:57:03 -0800627
Ryan Cuicedd8a52012-03-22 02:28:35 -0700628 return verified_patches
Brian Harring3fec5a82012-03-01 05:57:03 -0800629
630
631def _CheckBuildRootOption(_option, _opt_str, value, parser):
632 """Validate and convert buildroot to full-path form."""
633 value = value.strip()
634 if not value or value == '/':
635 raise optparse.OptionValueError('Invalid buildroot specified')
636
637 parser.values.buildroot = os.path.realpath(os.path.expanduser(value))
638
639
640def _CheckLogDirOption(_option, _opt_str, value, parser):
641 """Validate and convert buildroot to full-path form."""
642 parser.values.log_dir = os.path.abspath(os.path.expanduser(value))
643
644
645def _CheckChromeVersionOption(_option, _opt_str, value, parser):
646 """Upgrade other options based on chrome_version being passed."""
647 value = value.strip()
648
649 if parser.values.chrome_rev is None and value:
650 parser.values.chrome_rev = constants.CHROME_REV_SPEC
651
652 parser.values.chrome_version = value
653
654
655def _CheckChromeRootOption(_option, _opt_str, value, parser):
656 """Validate and convert chrome_root to full-path form."""
657 value = value.strip()
658 if not value or value == '/':
659 raise optparse.OptionValueError('Invalid chrome_root specified')
660
661 if parser.values.chrome_rev is None:
662 parser.values.chrome_rev = constants.CHROME_REV_LOCAL
663
664 parser.values.chrome_root = os.path.realpath(os.path.expanduser(value))
665
666
667def _CheckChromeRevOption(_option, _opt_str, value, parser):
668 """Validate the chrome_rev option."""
669 value = value.strip()
670 if value not in constants.VALID_CHROME_REVISIONS:
671 raise optparse.OptionValueError('Invalid chrome rev specified')
672
673 parser.values.chrome_rev = value
674
675
676def _CreateParser():
Ryan Cuieaa9efd2012-04-25 17:56:45 -0700677 class CustomParser(optparse.OptionParser):
678 def add_remote_option(self, *args, **kwargs):
679 """For arguments that are passed-through to remote trybot."""
680 return optparse.OptionParser.add_option(self, *args,
681 remote_pass_through=True,
682 **kwargs)
683
684 class CustomGroup(optparse.OptionGroup):
685 def add_remote_option(self, *args, **kwargs):
686 """For arguments that are passed-through to remote trybot."""
687 return optparse.OptionGroup.add_option(self, *args,
688 remote_pass_through=True,
689 **kwargs)
690
691 class CustomOption(optparse.Option):
692 """Subclass Option class to implement pass-through."""
693 def __init__(self, *args, **kwargs):
694 # The remote_pass_through argument specifies whether we should directly
695 # pass the argument (with its value) onto the remote trybot.
696 self.pass_through = kwargs.pop('remote_pass_through', False)
697 optparse.Option.__init__(self, *args, **kwargs)
698
699 def take_action(self, action, dest, opt, value, values, parser):
700 optparse.Option.take_action(self, action, dest, opt, value, values,
701 parser)
702 if self.pass_through:
703 parser.values.pass_through_args.append(opt)
704 if self.nargs and self.nargs > 1:
705 # value is a tuple if nargs > 1
706 string_list = [str(val) for val in list(value)]
707 parser.values.pass_through_args.extend(string_list)
708 elif value:
709 parser.values.pass_through_args.append(str(value))
710
Brian Harring3fec5a82012-03-01 05:57:03 -0800711 """Generate and return the parser with all the options."""
712 # Parse options
713 usage = "usage: %prog [options] buildbot_config"
Ryan Cuieaa9efd2012-04-25 17:56:45 -0700714 parser = CustomParser(usage=usage, option_class=CustomOption)
Brian Harring3fec5a82012-03-01 05:57:03 -0800715
716 # Main options
Ryan Cuieaa9efd2012-04-25 17:56:45 -0700717 # The remote_pass_through parameter to add_option is implemented by the
718 # CustomOption class. See CustomOption for more information.
Brian Harring3fec5a82012-03-01 05:57:03 -0800719 parser.add_option('-a', '--all', action='store_true', dest='print_all',
720 default=False,
721 help=('List all of the buildbot configs available. Use '
722 'with the --list option'))
723 parser.add_option('-r', '--buildroot', action='callback', dest='buildroot',
724 type='string', callback=_CheckBuildRootOption,
Ryan Cuieaa9efd2012-04-25 17:56:45 -0700725 help='Root directory where source is checked out to, and '
726 'where the build occurs. For external build configs, '
727 "defaults to 'trybot' directory at top level of your "
728 'repo-managed checkout.')
729 parser.add_remote_option('--chrome_rev', default=None, type='string',
730 action='callback', dest='chrome_rev',
731 callback=_CheckChromeRevOption,
732 help=('Revision of Chrome to use, of type [%s]'
733 % '|'.join(constants.VALID_CHROME_REVISIONS)))
734 parser.add_remote_option('-g', '--gerrit-patches', action='append',
735 default=[], type='string',
736 metavar="'Id1 *int_Id2...IdN'",
737 help=("Space-separated list of short-form Gerrit "
738 "Change-Id's or change numbers to patch. "
739 "Please prepend '*' to internal Change-Id's"))
Brian Harring3fec5a82012-03-01 05:57:03 -0800740 parser.add_option('-l', '--list', action='store_true', dest='list',
741 default=False,
742 help=('List the suggested trybot configs to use. Use '
743 '--all to list all of the available configs.'))
Ryan Cui54da0702012-04-19 18:38:08 -0700744 parser.add_option('--local', default=False, action='store_true',
745 help=('Specifies that this tryjob should be run locally.'))
Ryan Cuicedd8a52012-03-22 02:28:35 -0700746 parser.add_option('-p', '--local-patches', action='append', default=[],
Brian Harring3fec5a82012-03-01 05:57:03 -0800747 metavar="'<project1>[:<branch1>]...<projectN>[:<branchN>]'",
748 help=('Space-separated list of project branches with '
749 'patches to apply. Projects are specified by name. '
750 'If no branch is specified the current branch of the '
751 'project will be used.'))
Ryan Cuieaa9efd2012-04-25 17:56:45 -0700752 parser.add_remote_option('--profile', default=None, type='string',
753 action='store', dest='profile',
754 help='Name of profile to sub-specify board variant.')
Brian Harring3fec5a82012-03-01 05:57:03 -0800755 parser.add_option('--remote', default=False, action='store_true',
Brian Harring3fec5a82012-03-01 05:57:03 -0800756 help=('Specifies that this tryjob should be run remotely.'))
757
758 # Advanced options
Ryan Cuieaa9efd2012-04-25 17:56:45 -0700759 group = CustomGroup(
Brian Harring3fec5a82012-03-01 05:57:03 -0800760 parser,
761 'Advanced Options',
762 'Caution: use these options at your own risk.')
763
Ryan Cuieaa9efd2012-04-25 17:56:45 -0700764 group.add_remote_option('--buildbot', dest='buildbot', action='store_true',
765 default=False, help='This is running on a buildbot')
766 group.add_remote_option('--buildnumber', help='build number', type='int',
767 default=0)
Brian Harring3fec5a82012-03-01 05:57:03 -0800768 group.add_option('--chrome_root', default=None, type='string',
Ryan Cuieaa9efd2012-04-25 17:56:45 -0700769 action='callback', dest='chrome_root',
770 callback=_CheckChromeRootOption,
771 help='Local checkout of Chrome to use.')
772 group.add_remote_option('--chrome_version', default=None, type='string',
773 action='callback', dest='chrome_version',
774 callback=_CheckChromeVersionOption,
775 help='Used with SPEC logic to force a particular SVN '
776 'revision of chrome rather than the latest.')
777 group.add_remote_option('--clobber', action='store_true', dest='clobber',
778 default=False,
779 help='Clears an old checkout before syncing')
780 group.add_remote_option('--lkgm', action='store_true', dest='lkgm',
781 default=False,
782 help='Sync to last known good manifest blessed by '
783 'PFQ')
Brian Harring3fec5a82012-03-01 05:57:03 -0800784 parser.add_option('--log_dir', action='callback', dest='log_dir',
785 type='string', callback=_CheckLogDirOption,
786 help=('Directory where logs are stored.'))
Ryan Cuieaa9efd2012-04-25 17:56:45 -0700787 group.add_remote_option('--maxarchives', dest='max_archive_builds',
788 default=3, type='int',
789 help="Change the local saved build count limit.")
790 group.add_remote_option('--noarchive', action='store_false', dest='archive',
791 default=True, help="Don't run archive stage.")
792 group.add_remote_option('--nobuild', action='store_false', dest='build',
793 default=True,
794 help="Don't actually build (for cbuildbot dev)")
795 group.add_remote_option('--noclean', action='store_false', dest='clean',
796 default=True, help="Don't clean the buildroot")
797 group.add_remote_option('--noprebuilts', action='store_false',
798 dest='prebuilts', default=True,
799 help="Don't upload prebuilts.")
800 group.add_remote_option('--nosync', action='store_false', dest='sync',
801 default=True, help="Don't sync before building.")
802 group.add_remote_option('--nocgroups', action='store_false', dest='cgroups',
803 default=True,
804 help='Disable cbuildbots usage of cgroups.')
805 group.add_remote_option('--notests', action='store_false', dest='tests',
806 default=True,
807 help='Override values from buildconfig and run no '
808 'tests.')
809 group.add_remote_option('--nouprev', action='store_false', dest='uprev',
810 default=True,
811 help='Override values from buildconfig and never '
812 'uprev.')
813 group.add_option('--pass-through', dest='pass_through_args', action='append',
814 type='string', default=[], help=optparse.SUPPRESS_HELP)
Brian Harring3fec5a82012-03-01 05:57:03 -0800815 group.add_option('--reference-repo', action='store', default=None,
Ryan Cuieaa9efd2012-04-25 17:56:45 -0700816 dest='reference_repo',
817 help='Reuse git data stored in an existing repo '
818 'checkout. This can drastically reduce the network '
819 'time spent setting up the trybot checkout. By '
820 "default, if this option isn't given but cbuildbot "
821 'is invoked from a repo checkout, cbuildbot will '
822 'use the repo root.')
823 # Indicates this is running on a remote trybot machine.
Ryan Cuiba41ad32012-03-08 17:15:29 -0800824 group.add_option('--remote-trybot', dest='remote_trybot', action='store_true',
Ryan Cuieaa9efd2012-04-25 17:56:45 -0700825 default=False, help=optparse.SUPPRESS_HELP)
Ryan Cuicedd8a52012-03-22 02:28:35 -0700826 # Patches uploaded by trybot client when run using the -p option.
Ryan Cuieaa9efd2012-04-25 17:56:45 -0700827 group.add_remote_option('--remote-patches', action='append', default=[],
828 help=optparse.SUPPRESS_HELP)
Ryan Cuicedd8a52012-03-22 02:28:35 -0700829 group.add_option('--resume', action='store_true', default=False,
Ryan Cuieaa9efd2012-04-25 17:56:45 -0700830 help='Skip stages already successfully completed.')
831 group.add_remote_option('--timeout', action='store', type='int', default=0,
832 help='Specify the maximum amount of time this job '
833 'can run for, at which point the build will be '
834 'aborted. If set to zero, then there is no '
835 'timeout.')
Ryan Cui39bdbbf2012-02-29 16:15:39 -0800836 group.add_option('--test-tryjob', action='store_true',
837 default=False,
838 help='Submit a tryjob to the test repository. Will not '
839 'show up on the production trybot waterfall.')
Ryan Cuieaa9efd2012-04-25 17:56:45 -0700840 group.add_remote_option('--validation_pool', default=None,
841 help='Path to a pickled validation pool. Intended '
842 'for use only with the commit queue.')
843 group.add_remote_option('--version', dest='force_version', default=None,
844 help='Used with manifest logic. Forces use of this '
845 'version rather than create or get latest.')
Brian Harring3fec5a82012-03-01 05:57:03 -0800846
847 parser.add_option_group(group)
848
849 # Debug options
Ryan Cuieaa9efd2012-04-25 17:56:45 -0700850 group = CustomGroup(parser, "Debug Options")
Brian Harring3fec5a82012-03-01 05:57:03 -0800851
Ryan Cui85867972012-02-23 18:21:49 -0800852 group.add_option('--debug', action='store_true', default=None,
Brian Harring3fec5a82012-03-01 05:57:03 -0800853 help='Override some options to run as a developer.')
854 group.add_option('--dump_config', action='store_true', dest='dump_config',
855 default=False,
856 help='Dump out build config options, and exit.')
857 group.add_option('--notee', action='store_false', dest='tee', default=True,
858 help="Disable logging and internal tee process. Primarily "
859 "used for debugging cbuildbot itself.")
860 parser.add_option_group(group)
861 return parser
862
863
Ryan Cui85867972012-02-23 18:21:49 -0800864def _FinishParsing(options, args):
865 """Perform some parsing tasks that need to take place after optparse.
866
867 This function needs to be easily testable! Keep it free of
868 environment-dependent code. Put more detailed usage validation in
869 _PostParseCheck().
Brian Harring3fec5a82012-03-01 05:57:03 -0800870
871 Args:
Ryan Cui85867972012-02-23 18:21:49 -0800872 options, args: The options/args object returned by optparse
Brian Harring3fec5a82012-03-01 05:57:03 -0800873 """
Brian Harring3fec5a82012-03-01 05:57:03 -0800874 if options.chrome_root:
875 if options.chrome_rev != constants.CHROME_REV_LOCAL:
876 cros_lib.Die('Chrome rev must be %s if chrome_root is set.' %
877 constants.CHROME_REV_LOCAL)
878 else:
879 if options.chrome_rev == constants.CHROME_REV_LOCAL:
880 cros_lib.Die('Chrome root must be set if chrome_rev is %s.' %
881 constants.CHROME_REV_LOCAL)
882
883 if options.chrome_version:
884 if options.chrome_rev != constants.CHROME_REV_SPEC:
885 cros_lib.Die('Chrome rev must be %s if chrome_version is set.' %
886 constants.CHROME_REV_SPEC)
887 else:
888 if options.chrome_rev == constants.CHROME_REV_SPEC:
889 cros_lib.Die('Chrome rev must not be %s if chrome_version is not set.' %
890 constants.CHROME_REV_SPEC)
891
Ryan Cuieaa9efd2012-04-25 17:56:45 -0700892 patches = bool(options.gerrit_patches or options.local_patches)
893 if options.remote:
894 if options.local:
895 cros_lib.Die('Cannot specify both --remote and --local')
Ryan Cui54da0702012-04-19 18:38:08 -0700896
Ryan Cuieaa9efd2012-04-25 17:56:45 -0700897 if not options.buildbot and not patches:
898 cros_lib.Die('Must provide patches when running with --remote.')
Brian Harring3fec5a82012-03-01 05:57:03 -0800899
Ryan Cuieaa9efd2012-04-25 17:56:45 -0700900 # --debug needs to be explicitly passed through for remote invocations.
901 release_mode_with_patches = (options.buildbot and patches and
902 '--debug' not in options.pass_through_args)
903 else:
904 if len(args) > 1:
905 cros_lib.Die('Multiple configs not supported if not running with '
906 '--remote.')
907
908 release_mode_with_patches = (options.buildbot and patches and
909 not options.debug)
910
911 # When running in release mode, make sure we are running with checked-in code.
912 # We want checked-in cbuildbot/scripts to prevent errors, and we want to build
913 # a release image with checked-in code for CrOS packages.
914 if release_mode_with_patches:
915 cros_lib.Die('Cannot provide patches when running with --buildbot!')
Brian Harring3fec5a82012-03-01 05:57:03 -0800916
Ryan Cuiba41ad32012-03-08 17:15:29 -0800917 if options.buildbot and options.remote_trybot:
918 cros_lib.Die('--buildbot and --remote-trybot cannot be used together.')
919
Ryan Cui85867972012-02-23 18:21:49 -0800920 # Record whether --debug was set explicitly vs. it was inferred.
921 options.debug_forced = False
922 if options.debug:
923 options.debug_forced = True
924 else:
Ryan Cui16ca5812012-03-08 20:34:27 -0800925 # We don't set debug by default for
926 # 1. --buildbot invocations.
927 # 2. --remote invocations, because it needs to push changes to the tryjob
928 # repo.
929 options.debug = not options.buildbot and not options.remote
Brian Harring3fec5a82012-03-01 05:57:03 -0800930
Brian Harring3fec5a82012-03-01 05:57:03 -0800931
Ryan Cuicedd8a52012-03-22 02:28:35 -0700932def _SplitAndFlatten(appended_items):
933 """Given a list of space-separated items, split into flattened list.
934
935 Given ['abc def', 'hij'] return ['abc', 'def', 'hij'].
936 Arguments:
937 appended_items: List of delimiter-separated items.
938
939 Returns: Flattened list.
940 """
941 new_list = []
942 for item in appended_items:
Mike Frysinger4bd23892012-03-26 15:08:52 -0400943 new_list.extend(item.split())
Ryan Cuicedd8a52012-03-22 02:28:35 -0700944 return new_list
945
946
Brian Harring1d7ba942012-04-24 06:37:18 -0700947# pylint: disable=W0613
Ryan Cui85867972012-02-23 18:21:49 -0800948def _PostParseCheck(options, args):
949 """Perform some usage validation after we've parsed the arguments
Brian Harring3fec5a82012-03-01 05:57:03 -0800950
Ryan Cui85867972012-02-23 18:21:49 -0800951 Args:
952 options/args: The options/args object returned by optparse
953 """
Brian Harring1d7ba942012-04-24 06:37:18 -0700954 if options.resume:
955 return
956
957 options.gerrit_patches = _SplitAndFlatten(options.gerrit_patches)
958 options.remote_patches = _SplitAndFlatten(options.remote_patches)
959 try:
960 # TODO(rcui): Split this into two stages, one that parses, another that
961 # validates. Parsing step will be called by _FinishParsing().
962 options.local_patches = _CheckLocalPatches(
963 _SplitAndFlatten(options.local_patches))
964 except optparse.OptionValueError as e:
965 cros_lib.Die(str(e))
966
967 default = os.environ.get('CBUILDBOT_DEFAULT_MODE')
968 if (default and not any([options.local, options.buildbot,
969 options.remote, options.remote_trybot])):
970 cros_lib.Info("CBUILDBOT_DEFAULT_MODE=%s env var detected, using it."
971 % default)
972 default = default.lower()
973 if default == 'local':
974 options.local = True
975 elif default == 'remote':
976 options.remote = True
977 elif default == 'buildbot':
978 options.buildbot = True
979 else:
980 cros_lib.Die("CBUILDBOT_DEFAULT_MODE value %s isn't supported. "
981 % default)
Ryan Cui85867972012-02-23 18:21:49 -0800982
983
984def _ParseCommandLine(parser, argv):
985 """Completely parse the commandline arguments"""
Brian Harring3fec5a82012-03-01 05:57:03 -0800986 (options, args) = parser.parse_args(argv)
Ryan Cui54da0702012-04-19 18:38:08 -0700987 if options.list:
988 _PrintValidConfigs(options.print_all)
989 sys.exit(0)
990
Ryan Cui8be16062012-04-24 12:05:26 -0700991 # Strip out null arguments.
992 # TODO(rcui): Remove when buildbot is fixed
993 args = [arg for arg in args if arg]
994 if not args:
995 parser.error('Invalid usage. Use -h to see usage. Use -l to list '
996 'supported configs.')
997
Ryan Cui85867972012-02-23 18:21:49 -0800998 _FinishParsing(options, args)
999 return options, args
1000
1001
1002def main(argv):
1003 # Set umask to 022 so files created by buildbot are readable.
1004 os.umask(022)
1005
1006 if cros_lib.IsInsideChroot():
1007 cros_lib.Die('Please run cbuildbot from outside the chroot.')
1008
1009 parser = _CreateParser()
1010 (options, args) = _ParseCommandLine(parser, argv)
Brian Harring3fec5a82012-03-01 05:57:03 -08001011
Brian Harring3fec5a82012-03-01 05:57:03 -08001012 _PostParseCheck(options, args)
1013
1014 if options.remote:
Chris Sosa4f6ffaf2012-05-01 17:05:44 -07001015 cros_lib.logger.setLevel(logging.WARNING)
Ryan Cui16ca5812012-03-08 20:34:27 -08001016
Brian Harring3fec5a82012-03-01 05:57:03 -08001017 # Verify configs are valid.
1018 for bot in args:
1019 _GetConfig(bot)
1020
1021 # Verify gerrit patches are valid.
Ryan Cui16ca5812012-03-08 20:34:27 -08001022 print 'Verifying patches...'
Ryan Cuicedd8a52012-03-22 02:28:35 -07001023 _, local_patches = _PreProcessPatches(options.gerrit_patches,
1024 options.local_patches)
Ryan Cuieaa9efd2012-04-25 17:56:45 -07001025 # --debug need to be explicitly passed through for remote invocations.
1026 if options.buildbot and '--debug' not in options.pass_through_args:
1027 _ConfirmRemoteBuildbotRun()
1028
Ryan Cui16ca5812012-03-08 20:34:27 -08001029 print 'Submitting tryjob...'
Ryan Cuicedd8a52012-03-22 02:28:35 -07001030 tryjob = remote_try.RemoteTryJob(options, args, local_patches)
Ryan Cui39bdbbf2012-02-29 16:15:39 -08001031 tryjob.Submit(testjob=options.test_tryjob, dryrun=options.debug)
Ryan Cui16ca5812012-03-08 20:34:27 -08001032 print 'Tryjob submitted!'
1033 print ('Go to %s to view the status of your job.'
Ryan Cui4906e1c2012-04-03 20:09:34 -07001034 % tryjob.GetTrybotWaterfallLink())
Brian Harring3fec5a82012-03-01 05:57:03 -08001035 sys.exit(0)
Ryan Cui54da0702012-04-19 18:38:08 -07001036 elif (not options.buildbot and not options.remote_trybot
1037 and not options.resume and not options.local):
1038 cros_lib.Warning('Running in LOCAL TRYBOT mode! Use --remote to submit '
1039 'REMOTE tryjobs. Use --local to suppress this message.')
1040 cros_lib.Warning('Starting April 30th, --local will be required to run the '
1041 'local trybot.')
1042 time.sleep(5)
Brian Harring3fec5a82012-03-01 05:57:03 -08001043
Ryan Cui8be16062012-04-24 12:05:26 -07001044 # Only expecting one config
1045 bot_id = args[-1]
1046 build_config = _GetConfig(bot_id)
Brian Harring3fec5a82012-03-01 05:57:03 -08001047
1048 if options.reference_repo is None:
1049 repo_path = os.path.join(constants.SOURCE_ROOT, '.repo')
1050 # If we're being run from a repo checkout, reuse the repo's git pool to
1051 # cut down on sync time.
1052 if os.path.exists(repo_path):
1053 options.reference_repo = constants.SOURCE_ROOT
1054 elif options.reference_repo:
1055 if not os.path.exists(options.reference_repo):
1056 parser.error('Reference path %s does not exist'
1057 % (options.reference_repo,))
1058 elif not os.path.exists(os.path.join(options.reference_repo, '.repo')):
1059 parser.error('Reference path %s does not look to be the base of a '
1060 'repo checkout; no .repo exists in the root.'
1061 % (options.reference_repo,))
Ryan Cuid4a24212012-04-04 18:08:12 -07001062
1063 if options.buildbot or options.remote_trybot:
Brian Harring470f6112012-03-02 11:47:10 -08001064 if not options.cgroups:
Ryan Cuid4a24212012-04-04 18:08:12 -07001065 parser.error('Options --buildbot/--remote-trybot and --nocgroups cannot '
1066 'be used together. Cgroup support is required for '
1067 'buildbot/remote-trybot mode.')
Brian Harring470f6112012-03-02 11:47:10 -08001068 if not cgroups.Cgroup.CgroupsSupported():
Ryan Cuid4a24212012-04-04 18:08:12 -07001069 parser.error('Option --buildbot/--remote-trybot was given, but this '
1070 'system does not support cgroups. Failing.')
Brian Harring3fec5a82012-03-01 05:57:03 -08001071
Brian Harring351ce442012-03-09 16:38:14 -08001072 missing = []
1073 for program in _BUILDBOT_REQUIRED_BINARIES:
1074 ret = cros_lib.RunCommand('which %s' % program, shell=True,
1075 redirect_stderr=True, redirect_stdout=True,
1076 error_code_ok=True, print_cmd=False)
1077 if ret.returncode != 0:
1078 missing.append(program)
1079
1080 if missing:
Ryan Cuid4a24212012-04-04 18:08:12 -07001081 parser.error("Option --buildbot/--remote-trybot requires the following "
1082 "binaries which couldn't be found in $PATH: %s"
Brian Harring351ce442012-03-09 16:38:14 -08001083 % (', '.join(missing)))
1084
Brian Harring3fec5a82012-03-01 05:57:03 -08001085 if options.reference_repo:
1086 options.reference_repo = os.path.abspath(options.reference_repo)
1087
1088 if options.dump_config:
1089 # This works, but option ordering is bad...
1090 print 'Configuration %s:' % bot_id
1091 pretty_printer = pprint.PrettyPrinter(indent=2)
1092 pretty_printer.pprint(build_config)
1093 sys.exit(0)
1094
1095 if not options.buildroot:
1096 if options.buildbot:
1097 parser.error('Please specify a buildroot with the --buildroot option.')
Matt Tennantd55b1f42012-04-13 14:15:01 -07001098
Brian Harring470f6112012-03-02 11:47:10 -08001099 options.buildroot = _DetermineDefaultBuildRoot(build_config['internal'])
1100 # We use a marker file in the buildroot to indicate the user has
1101 # consented to using this directory.
1102 if not os.path.exists(repository.GetTrybotMarkerPath(options.buildroot)):
1103 _ConfirmBuildRoot(options.buildroot)
Brian Harring3fec5a82012-03-01 05:57:03 -08001104
1105 # Sanity check of buildroot- specifically that it's not pointing into the
1106 # midst of an existing repo since git-repo doesn't support nesting.
Brian Harring3fec5a82012-03-01 05:57:03 -08001107 if (not repository.IsARepoRoot(options.buildroot) and
David James6b80dc62012-02-29 15:34:40 -08001108 repository.InARepoRepository(options.buildroot)):
Brian Harring3fec5a82012-03-01 05:57:03 -08001109 parser.error('Configured buildroot %s points into a repository checkout, '
1110 'rather than the root of it. This is not supported.'
1111 % options.buildroot)
1112
Brian Harringa184efa2012-03-04 11:51:25 -08001113 with cleanup.EnforcedCleanupSection() as critical_section:
1114 with sudo.SudoKeepAlive():
1115 with cros_lib.AllowDisabling(options.cgroups,
Brian Harring4e6412d2012-03-09 20:54:02 -08001116 cgroups.SimpleContainChildren, 'cbuildbot'):
Brian Harringa184efa2012-03-04 11:51:25 -08001117 # Mark everything between EnforcedCleanupSection and here as having to
1118 # be rolled back via the contextmanager cleanup handlers. This ensures
1119 # that sudo bits cannot outlive cbuildbot, that anything cgroups
1120 # would kill gets killed, etc.
1121 critical_section.ForkWatchdog()
1122
1123 with cros_lib.AllowDisabling(options.timeout > 0,
1124 cros_lib.Timeout, options.timeout):
1125 if not options.buildbot:
1126 build_config = cbuildbot_config.OverrideConfigForTrybot(
Ryan Cui3d6b4742012-03-14 11:42:24 -07001127 build_config,
1128 options.remote_trybot)
Brian Harringa184efa2012-03-04 11:51:25 -08001129
1130 _RunBuildStagesWrapper(options, build_config)