blob: 23bad0e89d9ccd6bb028a09ca23550ba5409282f [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
Ryan Cui16d9e1f2012-05-11 10:50:18 -070034from chromite.buildbot import trybot_patch_pool
Brian Harring3fec5a82012-03-01 05:57:03 -080035
Brian Harringc92a7012012-02-29 10:11:34 -080036from chromite.lib import cgroups
Brian Harringa184efa2012-03-04 11:51:25 -080037from chromite.lib import cleanup
Brian Harring3fec5a82012-03-01 05:57:03 -080038from chromite.lib import cros_build_lib as cros_lib
Brian Harringaf019fb2012-05-10 15:06:13 -070039from chromite.lib import osutils
Brian Harring3fec5a82012-03-01 05:57:03 -080040from chromite.lib import sudo
41
Ryan Cuiadd49122012-03-21 22:19:58 -070042
Brian Harring3fec5a82012-03-01 05:57:03 -080043cros_lib.STRICT_SUDO = True
44
45_DEFAULT_LOG_DIR = 'cbuildbot_logs'
46_BUILDBOT_LOG_FILE = 'cbuildbot.log'
47_DEFAULT_EXT_BUILDROOT = 'trybot'
48_DEFAULT_INT_BUILDROOT = 'trybot-internal'
Matt Tennantf1e30972012-03-02 16:30:07 -080049_PATH_TO_CBUILDBOT = os.path.join(constants.CHROMITE_BIN_SUBDIR, 'cbuildbot')
Brian Harring3fec5a82012-03-01 05:57:03 -080050_DISTRIBUTED_TYPES = [constants.COMMIT_QUEUE_TYPE, constants.PFQ_TYPE,
51 constants.CANARY_TYPE, constants.CHROME_PFQ_TYPE,
52 constants.PALADIN_TYPE]
Brian Harring351ce442012-03-09 16:38:14 -080053_BUILDBOT_REQUIRED_BINARIES = ('pbzip2',)
Brian Harring3fec5a82012-03-01 05:57:03 -080054
55
Ryan Cui4f6cf7e2012-04-18 16:12:27 -070056def _PrintValidConfigs(display_all=False):
Brian Harring3fec5a82012-03-01 05:57:03 -080057 """Print a list of valid buildbot configs.
58
59 Arguments:
Ryan Cui4f6cf7e2012-04-18 16:12:27 -070060 display_all: Print all configs. Otherwise, prints only configs with
61 trybot_list=True.
Brian Harring3fec5a82012-03-01 05:57:03 -080062 """
Ryan Cui4f6cf7e2012-04-18 16:12:27 -070063 def _GetSortKey(config_name):
64 config_dict = cbuildbot_config.config[config_name]
65 return (not config_dict['trybot_list'], config_dict['description'],
66 config_name)
67
Brian Harring3fec5a82012-03-01 05:57:03 -080068 COLUMN_WIDTH = 45
69 print 'config'.ljust(COLUMN_WIDTH), 'description'
70 print '------'.ljust(COLUMN_WIDTH), '-----------'
71 config_names = cbuildbot_config.config.keys()
Ryan Cui4f6cf7e2012-04-18 16:12:27 -070072 config_names.sort(key=_GetSortKey)
Brian Harring3fec5a82012-03-01 05:57:03 -080073 for name in config_names:
Ryan Cui4f6cf7e2012-04-18 16:12:27 -070074 if display_all or cbuildbot_config.config[name]['trybot_list']:
75 desc = cbuildbot_config.config[name].get('description')
76 desc = desc if desc else ''
Brian Harring3fec5a82012-03-01 05:57:03 -080077 print name.ljust(COLUMN_WIDTH), desc
78
79
80def _GetConfig(config_name):
81 """Gets the configuration for the build"""
82 if not cbuildbot_config.config.has_key(config_name):
83 print 'Non-existent configuration %s specified.' % config_name
84 print 'Please specify one of:'
85 _PrintValidConfigs()
86 sys.exit(1)
87
88 result = cbuildbot_config.config[config_name]
89
90 return result
91
92
93def _GetChromiteTrackingBranch():
David James66009462012-03-25 10:08:38 -070094 """Returns the remote branch associated with chromite."""
Brian Harring3fec5a82012-03-01 05:57:03 -080095 cwd = os.path.dirname(os.path.realpath(__file__))
David James66009462012-03-25 10:08:38 -070096 branch = cros_lib.GetCurrentBranch(cwd)
97 if branch:
98 tracking_branch = cros_lib.GetTrackingBranch(branch, cwd)[1]
99 if tracking_branch.startswith('refs/heads/'):
100 return tracking_branch.replace('refs/heads/', '')
101 # If we are not on a branch, or if the tracking branch is a revision,
David James8b3c1bf2012-03-28 09:10:16 -0700102 # use the push branch. For repo repositories, this will be the manifest
103 # branch configured for this project. For other repositories, we'll just
104 # guess 'master', since there's no easy way to find out what branch
105 # we're on.
106 return cros_lib.GetPushBranch(cwd)[1]
Brian Harring3fec5a82012-03-01 05:57:03 -0800107
108
109def _CheckBuildRootBranch(buildroot, tracking_branch):
110 """Make sure buildroot branch is the same as Chromite branch."""
111 manifest_branch = cros_lib.GetManifestDefaultBranch(buildroot)
112 if manifest_branch != tracking_branch:
113 cros_lib.Die('Chromite is not on same branch as buildroot checkout\n' +
114 'Chromite is on branch %s.\n' % tracking_branch +
115 'Buildroot checked out to %s\n' % manifest_branch)
116
117
Ryan Cui16d9e1f2012-05-11 10:50:18 -0700118def AcquirePoolFromOptions(options, target_manifest_branch):
119 """Generate patch objects from passed in options.
Brian Harring3fec5a82012-03-01 05:57:03 -0800120
121 Args:
Ryan Cui16d9e1f2012-05-11 10:50:18 -0700122 options: The options object generated by optparse.
Brian Harring3fec5a82012-03-01 05:57:03 -0800123
Ryan Cui16d9e1f2012-05-11 10:50:18 -0700124 Raises:
125 gerrit_helper.GerritException, cros_patch.PatchException
Brian Harring3fec5a82012-03-01 05:57:03 -0800126 """
Ryan Cui16d9e1f2012-05-11 10:50:18 -0700127 gerrit_patches = []
128 local_patches = []
129 remote_patches = []
Brian Harring3fec5a82012-03-01 05:57:03 -0800130
Ryan Cui16d9e1f2012-05-11 10:50:18 -0700131 if options.gerrit_patches:
132 gerrit_patches = gerrit_helper.GetGerritPatchInfo(
133 options.gerrit_patches)
134 for patch in gerrit_patches:
135 if patch.IsAlreadyMerged():
136 cros_lib.Warning('Patch %s has already been merged.' % str(patch))
Brian Harring3fec5a82012-03-01 05:57:03 -0800137
Ryan Cui16d9e1f2012-05-11 10:50:18 -0700138 if options.local_patches:
139 local_patches = cros_patch.PrepareLocalPatches(
Ryan Cui5ba7e152012-05-10 14:36:52 -0700140 options.sourceroot,
Ryan Cui16d9e1f2012-05-11 10:50:18 -0700141 options.local_patches,
142 target_manifest_branch)
Brian Harring3fec5a82012-03-01 05:57:03 -0800143
Ryan Cui16d9e1f2012-05-11 10:50:18 -0700144 if options.remote_patches:
145 remote_patches = cros_patch.PrepareRemotePatches(
146 options.remote_patches)
Brian Harring3fec5a82012-03-01 05:57:03 -0800147
Ryan Cui16d9e1f2012-05-11 10:50:18 -0700148 return trybot_patch_pool.TrybotPatchPool(gerrit_patches, local_patches,
149 remote_patches)
Brian Harring3fec5a82012-03-01 05:57:03 -0800150
151
152def _IsIncrementalBuild(buildroot, clobber):
153 """Returns True if we are reusing an existing buildroot."""
154 repo_dir = os.path.join(buildroot, '.repo')
155 return not clobber and os.path.isdir(repo_dir)
156
157
158class Builder(object):
159 """Parent class for all builder types.
160
161 This class functions as a parent class for various build types. It's intended
162 use is builder_instance.Run().
163
164 Vars:
Brian Harring3fec5a82012-03-01 05:57:03 -0800165 build_config: The configuration dictionary from cbuildbot_config.
166 options: The options provided from optparse in main().
167 completed_stages_file: Where we store resume state.
168 archive_url: Where our artifacts for this builder will be archived.
169 tracking_branch: The tracking branch for this build.
170 release_tag: The associated "chrome os version" of this build.
171 gerrit_patches: Gerrit patches to be included in build.
172 local_patches: Local patches to be included in build.
Ryan Cui16d9e1f2012-05-11 10:50:18 -0700173 remote_patches: Uploaded local patches to be included in build.
Brian Harring3fec5a82012-03-01 05:57:03 -0800174 """
175
David James944a48e2012-03-07 12:19:03 -0800176 def __init__(self, options, build_config):
Brian Harring3fec5a82012-03-01 05:57:03 -0800177 """Initializes instance variables. Must be called by all subclasses."""
Brian Harring3fec5a82012-03-01 05:57:03 -0800178 self.build_config = build_config
179 self.options = options
180
181 # TODO, Remove here and in config after bug chromium-os:14649 is fixed.
182 if self.build_config['chromeos_official']:
183 os.environ['CHROMEOS_OFFICIAL'] = '1'
184
185 self.completed_stages_file = os.path.join(options.buildroot,
186 '.completed_stages')
David James58e0c092012-03-04 20:31:12 -0800187 self.archive_stages = {}
Brian Harring3fec5a82012-03-01 05:57:03 -0800188 self.archive_urls = {}
189 self.release_tag = None
Brian Harringeb237932012-05-07 02:08:06 -0700190 self.target_manifest_branch = _GetChromiteTrackingBranch()
Ryan Cui16d9e1f2012-05-11 10:50:18 -0700191 self.patch_pool = trybot_patch_pool.GetEmptyPool()
Brian Harring3fec5a82012-03-01 05:57:03 -0800192
193 def Initialize(self):
194 """Runs through the initialization steps of an actual build."""
195 if self.options.resume and os.path.exists(self.completed_stages_file):
196 with open(self.completed_stages_file, 'r') as load_file:
197 results_lib.Results.RestoreCompletedStages(load_file)
198
199 # We only want to do this if we need to patch changes.
200 if not results_lib.Results.GetPrevious().get(
Ryan Cui16d9e1f2012-05-11 10:50:18 -0700201 stages.PatchChangesStage.StageNamePrefix()):
202 self.patch_pool = AcquirePoolFromOptions(self.options,
203 self.target_manifest_branch)
Brian Harring3fec5a82012-03-01 05:57:03 -0800204
Brian Harringeb237932012-05-07 02:08:06 -0700205 bs.BuilderStage.SetManifestBranch(self.target_manifest_branch)
Brian Harring3fec5a82012-03-01 05:57:03 -0800206
207 # Check branch matching early.
208 if _IsIncrementalBuild(self.options.buildroot, self.options.clobber):
Brian Harringeb237932012-05-07 02:08:06 -0700209 _CheckBuildRootBranch(self.options.buildroot, self.target_manifest_branch)
Brian Harring3fec5a82012-03-01 05:57:03 -0800210
211 self._RunStage(stages.CleanUpStage)
212
213 def _GetStageInstance(self, stage, *args, **kwargs):
214 """Helper function to get an instance given the args.
215
David James944a48e2012-03-07 12:19:03 -0800216 Useful as almost all stages just take in options and build_config.
Brian Harring3fec5a82012-03-01 05:57:03 -0800217 """
David James944a48e2012-03-07 12:19:03 -0800218 config = kwargs.pop('config', self.build_config)
219 return stage(self.options, config, *args, **kwargs)
Brian Harring3fec5a82012-03-01 05:57:03 -0800220
221 def _SetReleaseTag(self):
222 """Sets the release tag from the manifest_manager.
223
224 Must be run after sync stage as syncing enables us to have a release tag.
225 """
226 # Extract version we have decided to build into self.release_tag.
227 manifest_manager = stages.ManifestVersionedSyncStage.manifest_manager
228 if manifest_manager:
229 self.release_tag = manifest_manager.current_version
230
231 def _RunStage(self, stage, *args, **kwargs):
232 """Wrapper to run a stage."""
233 stage_instance = self._GetStageInstance(stage, *args, **kwargs)
234 return stage_instance.Run()
235
236 def GetSyncInstance(self):
237 """Returns an instance of a SyncStage that should be run.
238
239 Subclasses must override this method.
240 """
241 raise NotImplementedError()
242
243 def RunStages(self):
244 """Subclasses must override this method. Runs the appropriate code."""
245 raise NotImplementedError()
246
247 def _WriteCheckpoint(self):
248 """Drops a completed stages file with current state."""
249 with open(self.completed_stages_file, 'w+') as save_file:
250 results_lib.Results.SaveCompletedStages(save_file)
251
252 def _ShouldReExecuteInBuildRoot(self):
253 """Returns True if this build should be re-executed in the buildroot."""
254 abs_buildroot = os.path.abspath(self.options.buildroot)
255 return not os.path.abspath(__file__).startswith(abs_buildroot)
256
257 def _ReExecuteInBuildroot(self, sync_instance):
258 """Reexecutes self in buildroot and returns True if build succeeds.
259
260 This allows the buildbot code to test itself when changes are patched for
261 buildbot-related code. This is a no-op if the buildroot == buildroot
262 of the running chromite checkout.
263
264 Args:
265 sync_instance: Instance of the sync stage that was run to sync.
266
267 Returns:
268 True if the Build succeeded.
269 """
270 # If we are resuming, use last checkpoint.
271 if not self.options.resume:
272 self._WriteCheckpoint()
273
274 # Re-write paths to use absolute paths.
275 # Suppress any timeout options given from the commandline in the
276 # invoked cbuildbot; our timeout will enforce it instead.
Brian Harringf11bf682012-05-14 15:53:43 -0700277 args_to_append = ['--resume', '--timeout', '0', '--notee', '--nocgroups',
278 '--buildroot', os.path.abspath(self.options.buildroot)]
Brian Harring3fec5a82012-03-01 05:57:03 -0800279
280 if self.options.chrome_root:
281 args_to_append += ['--chrome_root',
282 os.path.abspath(self.options.chrome_root)]
283
284 if stages.ManifestVersionedSyncStage.manifest_manager:
285 ver = stages.ManifestVersionedSyncStage.manifest_manager.current_version
286 args_to_append += ['--version', ver]
287
288 if isinstance(sync_instance, stages.CommitQueueSyncStage):
289 vp_file = sync_instance.SaveValidationPool()
290 args_to_append += ['--validation_pool', vp_file]
291
292 # Re-run the command in the buildroot.
293 # Finally, be generous and give the invoked cbuildbot 30s to shutdown
294 # when something occurs. It should exit quicker, but the sigterm may
295 # hit while the system is particularly busy.
296 return_obj = cros_lib.RunCommand(
297 [_PATH_TO_CBUILDBOT] + sys.argv[1:] + args_to_append,
298 cwd=self.options.buildroot, error_code_ok=True, kill_timeout=30)
299 return return_obj.returncode == 0
300
301 def Run(self):
302 """Main runner for this builder class. Runs build and prints summary."""
303 print_report = True
David James3d4d3502012-04-09 15:12:06 -0700304 exception_thrown = False
Brian Harring3fec5a82012-03-01 05:57:03 -0800305 success = True
306 try:
307 self.Initialize()
308 sync_instance = self.GetSyncInstance()
309 sync_instance.Run()
310 self._SetReleaseTag()
311
Ryan Cui16d9e1f2012-05-11 10:50:18 -0700312 if self.patch_pool:
313 self._RunStage(stages.PatchChangesStage, self.patch_pool)
Brian Harring3fec5a82012-03-01 05:57:03 -0800314
315 if self._ShouldReExecuteInBuildRoot():
316 print_report = False
317 success = self._ReExecuteInBuildroot(sync_instance)
318 else:
319 self.RunStages()
David James3d4d3502012-04-09 15:12:06 -0700320 except Exception:
321 exception_thrown = True
322 raise
Brian Harring3fec5a82012-03-01 05:57:03 -0800323 finally:
324 if print_report:
325 self._WriteCheckpoint()
326 print '\n\n\n@@@BUILD_STEP Report@@@\n'
327 results_lib.Results.Report(sys.stdout, self.archive_urls,
328 self.release_tag)
329 success = results_lib.Results.BuildSucceededSoFar()
David James3d4d3502012-04-09 15:12:06 -0700330 if exception_thrown and success:
331 success = False
332 print >> sys.stderr, """
333@@@STEP_FAILURE@@@
334Exception thrown, but all stages marked successful. This is an internal error,
335because the stage that threw the exception should be marked as failing."""
Brian Harring3fec5a82012-03-01 05:57:03 -0800336
337 return success
338
339
340class SimpleBuilder(Builder):
341 """Builder that performs basic vetting operations."""
342
343 def GetSyncInstance(self):
344 """Sync to lkgm or TOT as necessary.
345
346 Returns: the instance of the sync stage that was run.
347 """
348 if self.options.lkgm or self.build_config['use_lkgm']:
349 sync_stage = self._GetStageInstance(stages.LKGMSyncStage)
350 else:
351 sync_stage = self._GetStageInstance(stages.SyncStage)
352
353 return sync_stage
354
David James58e0c092012-03-04 20:31:12 -0800355 def _RunBackgroundStagesForBoard(self, board):
356 """Run background board-specific stages for the specified board."""
David James58e0c092012-03-04 20:31:12 -0800357 archive_stage = self.archive_stages[board]
David James944a48e2012-03-07 12:19:03 -0800358 configs = self.build_config['board_specific_configs']
359 config = configs.get(board, self.build_config)
360 stage_list = [[stages.VMTestStage, board, archive_stage],
361 [stages.ChromeTestStage, board, archive_stage],
362 [stages.UnitTestStage, board],
Chris Sosa6a5dceb2012-05-14 13:48:56 -0700363 [stages.UploadPrebuiltsStage, board, archive_stage]]
Brian Harring3fec5a82012-03-01 05:57:03 -0800364
David James58e0c092012-03-04 20:31:12 -0800365 # We can not run hw tests without archiving the payloads.
366 if self.options.archive:
David James944a48e2012-03-07 12:19:03 -0800367 for suite in config['hw_tests']:
368 stage_list.append([stages.HWTestStage, board, archive_stage, suite])
Chris Sosab50dc932012-03-01 14:00:58 -0800369
David James944a48e2012-03-07 12:19:03 -0800370 steps = [self._GetStageInstance(*x, config=config).Run for x in stage_list]
371 background.RunParallelSteps(steps + [archive_stage.Run])
Brian Harring3fec5a82012-03-01 05:57:03 -0800372
373 def RunStages(self):
374 """Runs through build process."""
375 self._RunStage(stages.BuildBoardStage)
376
377 # TODO(sosa): Split these out into classes.
Brian Harring3fec5a82012-03-01 05:57:03 -0800378 if self.build_config['build_type'] == constants.CHROOT_BUILDER_TYPE:
379 self._RunStage(stages.SDKTestStage)
380 self._RunStage(stages.UploadPrebuiltsStage,
Chris Sosa6a5dceb2012-05-14 13:48:56 -0700381 constants.CHROOT_BUILDER_BOARD, None)
Brian Harring3fec5a82012-03-01 05:57:03 -0800382 elif self.build_config['build_type'] == constants.REFRESH_PACKAGES_TYPE:
383 self._RunStage(stages.RefreshPackageStatusStage)
384 else:
385 self._RunStage(stages.UprevStage)
Brian Harring3fec5a82012-03-01 05:57:03 -0800386
David James944a48e2012-03-07 12:19:03 -0800387 configs = self.build_config['board_specific_configs']
David James58e0c092012-03-04 20:31:12 -0800388 for board in self.build_config['boards']:
David James944a48e2012-03-07 12:19:03 -0800389 config = configs.get(board, self.build_config)
390 archive_stage = self._GetStageInstance(stages.ArchiveStage, board,
391 config=config)
David James58e0c092012-03-04 20:31:12 -0800392 self.archive_stages[board] = archive_stage
393
David James944a48e2012-03-07 12:19:03 -0800394 # Set up a process pool to run test/archive stages in the background.
395 # This process runs task(board) for each board added to the queue.
David James58e0c092012-03-04 20:31:12 -0800396 queue = multiprocessing.Queue()
397 task = self._RunBackgroundStagesForBoard
398 with background.BackgroundTaskRunner(queue, task):
David James944a48e2012-03-07 12:19:03 -0800399 for board in self.build_config['boards']:
David James58e0c092012-03-04 20:31:12 -0800400 # Run BuildTarget in the foreground.
David James944a48e2012-03-07 12:19:03 -0800401 archive_stage = self.archive_stages[board]
402 config = configs.get(board, self.build_config)
403 self._RunStage(stages.BuildTargetStage, board, archive_stage,
Chris Sosa1a87b3e2012-04-12 13:20:42 -0700404 self.release_tag, config=config)
David James58e0c092012-03-04 20:31:12 -0800405 self.archive_urls[board] = archive_stage.GetDownloadUrl()
406
David James944a48e2012-03-07 12:19:03 -0800407 # Kick off task(board) in the background.
David James58e0c092012-03-04 20:31:12 -0800408 queue.put([board])
409
Brian Harring3fec5a82012-03-01 05:57:03 -0800410
411class DistributedBuilder(SimpleBuilder):
412 """Build class that has special logic to handle distributed builds.
413
414 These builds sync using git/manifest logic in manifest_versions. In general
415 they use a non-distributed builder code for the bulk of the work.
416 """
David James944a48e2012-03-07 12:19:03 -0800417 def __init__(self, options, build_config):
Brian Harring3fec5a82012-03-01 05:57:03 -0800418 """Initializes a buildbot builder.
419
420 Extra variables:
421 completion_stage_class: Stage used to complete a build. Set in the Sync
422 stage.
423 """
David James944a48e2012-03-07 12:19:03 -0800424 super(DistributedBuilder, self).__init__(options, build_config)
Brian Harring3fec5a82012-03-01 05:57:03 -0800425 self.completion_stage_class = None
426
427 def GetSyncInstance(self):
428 """Syncs the tree using one of the distributed sync logic paths.
429
430 Returns: the instance of the sync stage that was run.
431 """
432 # Determine sync class to use. CQ overrides PFQ bits so should check it
433 # first.
434 if cbuildbot_config.IsCQType(self.build_config['build_type']):
435 sync_stage = self._GetStageInstance(stages.CommitQueueSyncStage)
436 self.completion_stage_class = stages.CommitQueueCompletionStage
437 elif cbuildbot_config.IsPFQType(self.build_config['build_type']):
438 sync_stage = self._GetStageInstance(stages.LKGMCandidateSyncStage)
439 self.completion_stage_class = stages.LKGMCandidateSyncCompletionStage
440 else:
441 sync_stage = self._GetStageInstance(stages.ManifestVersionedSyncStage)
442 self.completion_stage_class = stages.ManifestVersionedSyncCompletionStage
443
444 return sync_stage
445
446 def Publish(self, was_build_successful):
447 """Completes build by publishing any required information."""
448 completion_stage = self._GetStageInstance(self.completion_stage_class,
449 was_build_successful)
450 completion_stage.Run()
451 name = completion_stage.name
452 if not results_lib.Results.WasStageSuccessful(name):
453 should_publish_changes = False
454 else:
455 should_publish_changes = (self.build_config['master'] and
456 was_build_successful)
457
458 if should_publish_changes:
459 self._RunStage(stages.PublishUprevChangesStage)
460
461 def RunStages(self):
462 """Runs simple builder logic and publishes information to overlays."""
463 was_build_successful = False
464 try:
David Jamesf55709e2012-03-13 09:10:15 -0700465 super(DistributedBuilder, self).RunStages()
466 was_build_successful = results_lib.Results.BuildSucceededSoFar()
Brian Harring3fec5a82012-03-01 05:57:03 -0800467 except SystemExit as ex:
468 # If a stage calls sys.exit(0), it's exiting with success, so that means
469 # we should mark ourselves as successful.
470 if ex.code == 0:
471 was_build_successful = True
472 raise
473 finally:
474 self.Publish(was_build_successful)
475
Brian Harring3fec5a82012-03-01 05:57:03 -0800476
477def _ConfirmBuildRoot(buildroot):
478 """Confirm with user the inferred buildroot, and mark it as confirmed."""
479 warning = 'Using default directory %s as buildroot' % buildroot
480 response = cros_lib.YesNoPrompt(default=cros_lib.NO, warning=warning,
481 full=True)
482 if response == cros_lib.NO:
483 print('Please specify a buildroot with the --buildroot option.')
484 sys.exit(0)
485
486 if not os.path.exists(buildroot):
487 os.mkdir(buildroot)
488
489 repository.CreateTrybotMarker(buildroot)
490
491
Ryan Cuieaa9efd2012-04-25 17:56:45 -0700492def _ConfirmRemoteBuildbotRun():
493 """Confirm user wants to run with --buildbot --remote."""
494 warning = ('You are about to launch a PRODUCTION job! This is *NOT* a '
495 'trybot run! Are you sure?')
496 response = cros_lib.YesNoPrompt(default=cros_lib.NO, warning=warning,
497 full=True)
498
499 if response == cros_lib.NO:
500 print('Please specify --pass-through="--debug".')
501 sys.exit(0)
502
503
Ryan Cui5ba7e152012-05-10 14:36:52 -0700504def _DetermineDefaultBuildRoot(sourceroot, internal_build):
Brian Harring3fec5a82012-03-01 05:57:03 -0800505 """Default buildroot to be under the directory that contains current checkout.
506
507 Arguments:
508 internal_build: Whether the build is an internal build
Ryan Cui5ba7e152012-05-10 14:36:52 -0700509 sourceroot: Use specified sourceroot.
Brian Harring3fec5a82012-03-01 05:57:03 -0800510 """
Ryan Cui5ba7e152012-05-10 14:36:52 -0700511 if not repository.IsARepoRoot(sourceroot):
512 cros_lib.Die('Could not find root of local checkout at %s. Please specify '
513 'using the --sourceroot option.' % sourceroot)
Brian Harring3fec5a82012-03-01 05:57:03 -0800514
515 # Place trybot buildroot under the directory containing current checkout.
Ryan Cui5ba7e152012-05-10 14:36:52 -0700516 top_level = os.path.dirname(os.path.realpath(sourceroot))
Brian Harring3fec5a82012-03-01 05:57:03 -0800517 if internal_build:
518 buildroot = os.path.join(top_level, _DEFAULT_INT_BUILDROOT)
519 else:
520 buildroot = os.path.join(top_level, _DEFAULT_EXT_BUILDROOT)
521
522 return buildroot
523
524
525def _BackupPreviousLog(log_file, backup_limit=25):
526 """Rename previous log.
527
528 Args:
529 log_file: The absolute path to the previous log.
530 """
531 if os.path.exists(log_file):
532 old_logs = sorted(glob.glob(log_file + '.*'),
533 key=distutils.version.LooseVersion)
534
535 if len(old_logs) >= backup_limit:
536 os.remove(old_logs[0])
537
538 last = 0
539 if old_logs:
540 last = int(old_logs.pop().rpartition('.')[2])
541
542 os.rename(log_file, log_file + '.' + str(last + 1))
543
544
David James944a48e2012-03-07 12:19:03 -0800545def _RunBuildStagesWrapper(options, build_config):
Brian Harring3fec5a82012-03-01 05:57:03 -0800546 """Helper function that wraps RunBuildStages()."""
547 def IsDistributedBuilder():
548 """Determines whether the build_config should be a DistributedBuilder."""
549 if not options.buildbot:
550 return False
551 elif build_config['build_type'] in _DISTRIBUTED_TYPES:
552 chrome_rev = build_config['chrome_rev']
553 if options.chrome_rev: chrome_rev = options.chrome_rev
554 # We don't do distributed logic to TOT Chrome PFQ's, nor local
555 # chrome roots (e.g. chrome try bots)
556 if chrome_rev not in [constants.CHROME_REV_TOT,
557 constants.CHROME_REV_LOCAL,
558 constants.CHROME_REV_SPEC]:
559 return True
560
561 return False
562
Brian Harringd166aaf2012-05-14 18:31:53 -0700563 cros_lib.Info("cbuildbot executed with args %s"
564 % ' '.join(map(repr, sys.argv)))
565 if IsDistributedBuilder():
566 buildbot = DistributedBuilder(options, build_config)
567 else:
568 buildbot = SimpleBuilder(options, build_config)
Brian Harring3fec5a82012-03-01 05:57:03 -0800569
Brian Harringd166aaf2012-05-14 18:31:53 -0700570 if not buildbot.Run():
571 sys.exit(1)
Brian Harring3fec5a82012-03-01 05:57:03 -0800572
573
574# Parser related functions
Ryan Cui5ba7e152012-05-10 14:36:52 -0700575def _CheckLocalPatches(sourceroot, local_patches):
Brian Harring3fec5a82012-03-01 05:57:03 -0800576 """Do an early quick check of the passed-in patches.
577
578 If the branch of a project is not specified we append the current branch the
579 project is on.
Ryan Cui5ba7e152012-05-10 14:36:52 -0700580
581 Args:
582 sourceroot: The checkout where patches are coming from.
Brian Harring3fec5a82012-03-01 05:57:03 -0800583 """
Ryan Cuicedd8a52012-03-22 02:28:35 -0700584 verified_patches = []
585 for patch in local_patches:
Brian Harring3fec5a82012-03-01 05:57:03 -0800586 components = patch.split(':')
587 if len(components) > 2:
588 msg = 'Specify local patches in project[:branch] format.'
589 raise optparse.OptionValueError(msg)
590
591 # validate project
592 project = components[0]
Ryan Cui5ba7e152012-05-10 14:36:52 -0700593 if not cros_lib.DoesProjectExist(sourceroot, project):
Brian Harring3fec5a82012-03-01 05:57:03 -0800594 raise optparse.OptionValueError('Project %s does not exist.' % project)
595
Ryan Cui5ba7e152012-05-10 14:36:52 -0700596 project_dir = cros_lib.GetProjectDir(sourceroot, project)
Brian Harring3fec5a82012-03-01 05:57:03 -0800597
598 # If no branch was specified, we use the project's current branch.
599 if len(components) == 1:
600 branch = cros_lib.GetCurrentBranch(project_dir)
601 if not branch:
602 raise optparse.OptionValueError('project %s is not on a branch!'
603 % project)
604 # Append branch information to patch
605 patch = '%s:%s' % (project, branch)
606 else:
607 branch = components[1]
608 if not cros_lib.DoesLocalBranchExist(project_dir, branch):
609 raise optparse.OptionValueError('Project %s does not have branch %s'
610 % (project, branch))
611
Ryan Cuicedd8a52012-03-22 02:28:35 -0700612 verified_patches.append(patch)
Brian Harring3fec5a82012-03-01 05:57:03 -0800613
Ryan Cuicedd8a52012-03-22 02:28:35 -0700614 return verified_patches
Brian Harring3fec5a82012-03-01 05:57:03 -0800615
616
Brian Harring3fec5a82012-03-01 05:57:03 -0800617def _CheckChromeVersionOption(_option, _opt_str, value, parser):
618 """Upgrade other options based on chrome_version being passed."""
619 value = value.strip()
620
621 if parser.values.chrome_rev is None and value:
622 parser.values.chrome_rev = constants.CHROME_REV_SPEC
623
624 parser.values.chrome_version = value
625
626
627def _CheckChromeRootOption(_option, _opt_str, value, parser):
628 """Validate and convert chrome_root to full-path form."""
Brian Harring3fec5a82012-03-01 05:57:03 -0800629 if parser.values.chrome_rev is None:
630 parser.values.chrome_rev = constants.CHROME_REV_LOCAL
631
Ryan Cui5ba7e152012-05-10 14:36:52 -0700632 parser.values.chrome_root = value
Brian Harring3fec5a82012-03-01 05:57:03 -0800633
634
635def _CheckChromeRevOption(_option, _opt_str, value, parser):
636 """Validate the chrome_rev option."""
637 value = value.strip()
638 if value not in constants.VALID_CHROME_REVISIONS:
639 raise optparse.OptionValueError('Invalid chrome rev specified')
640
641 parser.values.chrome_rev = value
642
643
Ryan Cui5ba7e152012-05-10 14:36:52 -0700644class CustomParser(optparse.OptionParser):
645 def add_remote_option(self, *args, **kwargs):
646 """For arguments that are passed-through to remote trybot."""
647 return optparse.OptionParser.add_option(self, *args,
648 remote_pass_through=True,
649 **kwargs)
650
651
652class CustomGroup(optparse.OptionGroup):
653 def add_remote_option(self, *args, **kwargs):
654 """For arguments that are passed-through to remote trybot."""
655 return optparse.OptionGroup.add_option(self, *args,
656 remote_pass_through=True,
657 **kwargs)
658
659
660def check_path(option, opt, value):
661 """Expand paths and make them absolute."""
662 expanded = osutils.ExpandPath(value)
663 if expanded == '/':
664 raise optparse.OptionValueError('Invalid path %s specified for %s'
665 % (expanded, opt))
666
667 return expanded
668
669
670class CustomOption(optparse.Option):
671 """Subclass Option class to implement pass-through and path evaluation."""
672 TYPES = optparse.Option.TYPES + ('path',)
673 TYPE_CHECKER = optparse.Option.TYPE_CHECKER.copy()
674 TYPE_CHECKER['path'] = check_path
675
676 def __init__(self, *args, **kwargs):
677 # The remote_pass_through argument specifies whether we should directly
678 # pass the argument (with its value) onto the remote trybot.
679 self.pass_through = kwargs.pop('remote_pass_through', False)
680 optparse.Option.__init__(self, *args, **kwargs)
681
682 def take_action(self, action, dest, opt, value, values, parser):
683 optparse.Option.take_action(self, action, dest, opt, value, values,
684 parser)
685 if self.pass_through:
686 parser.values.pass_through_args.append(opt)
687 if self.nargs and self.nargs > 1:
688 # value is a tuple if nargs > 1
689 string_list = [str(val) for val in list(value)]
690 parser.values.pass_through_args.extend(string_list)
691 elif value:
692 parser.values.pass_through_args.append(str(value))
693
694
Brian Harring3fec5a82012-03-01 05:57:03 -0800695def _CreateParser():
Ryan Cui16d9e1f2012-05-11 10:50:18 -0700696 """Generate and return the parser with all the options."""
Brian Harring3fec5a82012-03-01 05:57:03 -0800697 # Parse options
698 usage = "usage: %prog [options] buildbot_config"
Ryan Cuieaa9efd2012-04-25 17:56:45 -0700699 parser = CustomParser(usage=usage, option_class=CustomOption)
Brian Harring3fec5a82012-03-01 05:57:03 -0800700
701 # Main options
Ryan Cuieaa9efd2012-04-25 17:56:45 -0700702 # The remote_pass_through parameter to add_option is implemented by the
703 # CustomOption class. See CustomOption for more information.
Brian Harring3fec5a82012-03-01 05:57:03 -0800704 parser.add_option('-a', '--all', action='store_true', dest='print_all',
705 default=False,
706 help=('List all of the buildbot configs available. Use '
707 'with the --list option'))
Ryan Cui5ba7e152012-05-10 14:36:52 -0700708 parser.add_option('-r', '--buildroot', dest='buildroot', type='path',
Ryan Cuieaa9efd2012-04-25 17:56:45 -0700709 help='Root directory where source is checked out to, and '
710 'where the build occurs. For external build configs, '
711 "defaults to 'trybot' directory at top level of your "
712 'repo-managed checkout.')
713 parser.add_remote_option('--chrome_rev', default=None, type='string',
714 action='callback', dest='chrome_rev',
715 callback=_CheckChromeRevOption,
716 help=('Revision of Chrome to use, of type [%s]'
717 % '|'.join(constants.VALID_CHROME_REVISIONS)))
718 parser.add_remote_option('-g', '--gerrit-patches', action='append',
719 default=[], type='string',
720 metavar="'Id1 *int_Id2...IdN'",
721 help=("Space-separated list of short-form Gerrit "
722 "Change-Id's or change numbers to patch. "
723 "Please prepend '*' to internal Change-Id's"))
Brian Harring3fec5a82012-03-01 05:57:03 -0800724 parser.add_option('-l', '--list', action='store_true', dest='list',
725 default=False,
726 help=('List the suggested trybot configs to use. Use '
727 '--all to list all of the available configs.'))
Ryan Cui54da0702012-04-19 18:38:08 -0700728 parser.add_option('--local', default=False, action='store_true',
729 help=('Specifies that this tryjob should be run locally.'))
Ryan Cuicedd8a52012-03-22 02:28:35 -0700730 parser.add_option('-p', '--local-patches', action='append', default=[],
Brian Harring3fec5a82012-03-01 05:57:03 -0800731 metavar="'<project1>[:<branch1>]...<projectN>[:<branchN>]'",
732 help=('Space-separated list of project branches with '
733 'patches to apply. Projects are specified by name. '
734 'If no branch is specified the current branch of the '
735 'project will be used.'))
Ryan Cuieaa9efd2012-04-25 17:56:45 -0700736 parser.add_remote_option('--profile', default=None, type='string',
737 action='store', dest='profile',
738 help='Name of profile to sub-specify board variant.')
Brian Harring3fec5a82012-03-01 05:57:03 -0800739 parser.add_option('--remote', default=False, action='store_true',
Brian Harring3fec5a82012-03-01 05:57:03 -0800740 help=('Specifies that this tryjob should be run remotely.'))
741
742 # Advanced options
Ryan Cuieaa9efd2012-04-25 17:56:45 -0700743 group = CustomGroup(
Brian Harring3fec5a82012-03-01 05:57:03 -0800744 parser,
745 'Advanced Options',
746 'Caution: use these options at your own risk.')
747
Ryan Cuieaa9efd2012-04-25 17:56:45 -0700748 group.add_remote_option('--buildbot', dest='buildbot', action='store_true',
749 default=False, help='This is running on a buildbot')
750 group.add_remote_option('--buildnumber', help='build number', type='int',
751 default=0)
Ryan Cui5ba7e152012-05-10 14:36:52 -0700752 group.add_option('--chrome_root', default=None, type='path',
753 action='callback', callback=_CheckChromeRootOption,
754 dest='chrome_root', help='Local checkout of Chrome to use.')
Ryan Cuieaa9efd2012-04-25 17:56:45 -0700755 group.add_remote_option('--chrome_version', default=None, type='string',
756 action='callback', dest='chrome_version',
757 callback=_CheckChromeVersionOption,
758 help='Used with SPEC logic to force a particular SVN '
759 'revision of chrome rather than the latest.')
760 group.add_remote_option('--clobber', action='store_true', dest='clobber',
761 default=False,
762 help='Clears an old checkout before syncing')
763 group.add_remote_option('--lkgm', action='store_true', dest='lkgm',
764 default=False,
765 help='Sync to last known good manifest blessed by '
766 'PFQ')
Ryan Cui5ba7e152012-05-10 14:36:52 -0700767 parser.add_option('--log_dir', dest='log_dir', type='path',
Brian Harring3fec5a82012-03-01 05:57:03 -0800768 help=('Directory where logs are stored.'))
Ryan Cuieaa9efd2012-04-25 17:56:45 -0700769 group.add_remote_option('--maxarchives', dest='max_archive_builds',
770 default=3, type='int',
771 help="Change the local saved build count limit.")
772 group.add_remote_option('--noarchive', action='store_false', dest='archive',
773 default=True, help="Don't run archive stage.")
774 group.add_remote_option('--nobuild', action='store_false', dest='build',
775 default=True,
776 help="Don't actually build (for cbuildbot dev)")
777 group.add_remote_option('--noclean', action='store_false', dest='clean',
778 default=True, help="Don't clean the buildroot")
779 group.add_remote_option('--noprebuilts', action='store_false',
780 dest='prebuilts', default=True,
781 help="Don't upload prebuilts.")
782 group.add_remote_option('--nosync', action='store_false', dest='sync',
783 default=True, help="Don't sync before building.")
784 group.add_remote_option('--nocgroups', action='store_false', dest='cgroups',
785 default=True,
786 help='Disable cbuildbots usage of cgroups.')
787 group.add_remote_option('--notests', action='store_false', dest='tests',
788 default=True,
789 help='Override values from buildconfig and run no '
790 'tests.')
791 group.add_remote_option('--nouprev', action='store_false', dest='uprev',
792 default=True,
793 help='Override values from buildconfig and never '
794 'uprev.')
795 group.add_option('--pass-through', dest='pass_through_args', action='append',
796 type='string', default=[], help=optparse.SUPPRESS_HELP)
Brian Harring3fec5a82012-03-01 05:57:03 -0800797 group.add_option('--reference-repo', action='store', default=None,
Ryan Cuieaa9efd2012-04-25 17:56:45 -0700798 dest='reference_repo',
799 help='Reuse git data stored in an existing repo '
800 'checkout. This can drastically reduce the network '
801 'time spent setting up the trybot checkout. By '
802 "default, if this option isn't given but cbuildbot "
803 'is invoked from a repo checkout, cbuildbot will '
804 'use the repo root.')
805 # Indicates this is running on a remote trybot machine.
Ryan Cuiba41ad32012-03-08 17:15:29 -0800806 group.add_option('--remote-trybot', dest='remote_trybot', action='store_true',
Ryan Cuieaa9efd2012-04-25 17:56:45 -0700807 default=False, help=optparse.SUPPRESS_HELP)
Ryan Cuicedd8a52012-03-22 02:28:35 -0700808 # Patches uploaded by trybot client when run using the -p option.
Ryan Cuieaa9efd2012-04-25 17:56:45 -0700809 group.add_remote_option('--remote-patches', action='append', default=[],
810 help=optparse.SUPPRESS_HELP)
Ryan Cuicedd8a52012-03-22 02:28:35 -0700811 group.add_option('--resume', action='store_true', default=False,
Ryan Cuieaa9efd2012-04-25 17:56:45 -0700812 help='Skip stages already successfully completed.')
813 group.add_remote_option('--timeout', action='store', type='int', default=0,
814 help='Specify the maximum amount of time this job '
815 'can run for, at which point the build will be '
816 'aborted. If set to zero, then there is no '
817 'timeout.')
Ryan Cui5ba7e152012-05-10 14:36:52 -0700818 group.add_option('--sourceroot', type='path', default=constants.SOURCE_ROOT,
819 help=optparse.SUPPRESS_HELP)
Ryan Cui39bdbbf2012-02-29 16:15:39 -0800820 group.add_option('--test-tryjob', action='store_true',
821 default=False,
822 help='Submit a tryjob to the test repository. Will not '
823 'show up on the production trybot waterfall.')
Ryan Cuieaa9efd2012-04-25 17:56:45 -0700824 group.add_remote_option('--validation_pool', default=None,
825 help='Path to a pickled validation pool. Intended '
826 'for use only with the commit queue.')
827 group.add_remote_option('--version', dest='force_version', default=None,
828 help='Used with manifest logic. Forces use of this '
829 'version rather than create or get latest.')
Brian Harring3fec5a82012-03-01 05:57:03 -0800830
831 parser.add_option_group(group)
832
833 # Debug options
Ryan Cuieaa9efd2012-04-25 17:56:45 -0700834 group = CustomGroup(parser, "Debug Options")
Brian Harring3fec5a82012-03-01 05:57:03 -0800835
Ryan Cui85867972012-02-23 18:21:49 -0800836 group.add_option('--debug', action='store_true', default=None,
Brian Harring3fec5a82012-03-01 05:57:03 -0800837 help='Override some options to run as a developer.')
838 group.add_option('--dump_config', action='store_true', dest='dump_config',
839 default=False,
840 help='Dump out build config options, and exit.')
841 group.add_option('--notee', action='store_false', dest='tee', default=True,
842 help="Disable logging and internal tee process. Primarily "
843 "used for debugging cbuildbot itself.")
844 parser.add_option_group(group)
845 return parser
846
847
Ryan Cui85867972012-02-23 18:21:49 -0800848def _FinishParsing(options, args):
849 """Perform some parsing tasks that need to take place after optparse.
850
851 This function needs to be easily testable! Keep it free of
852 environment-dependent code. Put more detailed usage validation in
853 _PostParseCheck().
Brian Harring3fec5a82012-03-01 05:57:03 -0800854
855 Args:
Ryan Cui85867972012-02-23 18:21:49 -0800856 options, args: The options/args object returned by optparse
Brian Harring3fec5a82012-03-01 05:57:03 -0800857 """
Brian Harring07039b52012-05-13 17:56:47 -0700858 # Setup logging levels first so any parsing triggered log messages
859 # are appropriately filtered.
860 logging.getLogger().setLevel(
861 logging.DEBUG if options.debug else logging.INFO)
862
Brian Harring3fec5a82012-03-01 05:57:03 -0800863 if options.chrome_root:
864 if options.chrome_rev != constants.CHROME_REV_LOCAL:
865 cros_lib.Die('Chrome rev must be %s if chrome_root is set.' %
866 constants.CHROME_REV_LOCAL)
867 else:
868 if options.chrome_rev == constants.CHROME_REV_LOCAL:
869 cros_lib.Die('Chrome root must be set if chrome_rev is %s.' %
870 constants.CHROME_REV_LOCAL)
871
872 if options.chrome_version:
873 if options.chrome_rev != constants.CHROME_REV_SPEC:
874 cros_lib.Die('Chrome rev must be %s if chrome_version is set.' %
875 constants.CHROME_REV_SPEC)
876 else:
877 if options.chrome_rev == constants.CHROME_REV_SPEC:
878 cros_lib.Die('Chrome rev must not be %s if chrome_version is not set.' %
879 constants.CHROME_REV_SPEC)
880
Ryan Cuieaa9efd2012-04-25 17:56:45 -0700881 patches = bool(options.gerrit_patches or options.local_patches)
882 if options.remote:
883 if options.local:
884 cros_lib.Die('Cannot specify both --remote and --local')
Ryan Cui54da0702012-04-19 18:38:08 -0700885
Ryan Cuieaa9efd2012-04-25 17:56:45 -0700886 if not options.buildbot and not patches:
887 cros_lib.Die('Must provide patches when running with --remote.')
Brian Harring3fec5a82012-03-01 05:57:03 -0800888
Ryan Cuieaa9efd2012-04-25 17:56:45 -0700889 # --debug needs to be explicitly passed through for remote invocations.
890 release_mode_with_patches = (options.buildbot and patches and
891 '--debug' not in options.pass_through_args)
892 else:
893 if len(args) > 1:
894 cros_lib.Die('Multiple configs not supported if not running with '
895 '--remote.')
896
897 release_mode_with_patches = (options.buildbot and patches and
898 not options.debug)
899
900 # When running in release mode, make sure we are running with checked-in code.
901 # We want checked-in cbuildbot/scripts to prevent errors, and we want to build
902 # a release image with checked-in code for CrOS packages.
903 if release_mode_with_patches:
904 cros_lib.Die('Cannot provide patches when running with --buildbot!')
Brian Harring3fec5a82012-03-01 05:57:03 -0800905
Ryan Cuiba41ad32012-03-08 17:15:29 -0800906 if options.buildbot and options.remote_trybot:
907 cros_lib.Die('--buildbot and --remote-trybot cannot be used together.')
908
Ryan Cui85867972012-02-23 18:21:49 -0800909 # Record whether --debug was set explicitly vs. it was inferred.
910 options.debug_forced = False
911 if options.debug:
912 options.debug_forced = True
913 else:
Ryan Cui16ca5812012-03-08 20:34:27 -0800914 # We don't set debug by default for
915 # 1. --buildbot invocations.
916 # 2. --remote invocations, because it needs to push changes to the tryjob
917 # repo.
918 options.debug = not options.buildbot and not options.remote
Brian Harring3fec5a82012-03-01 05:57:03 -0800919
Brian Harring3fec5a82012-03-01 05:57:03 -0800920
Ryan Cuicedd8a52012-03-22 02:28:35 -0700921def _SplitAndFlatten(appended_items):
922 """Given a list of space-separated items, split into flattened list.
923
924 Given ['abc def', 'hij'] return ['abc', 'def', 'hij'].
925 Arguments:
926 appended_items: List of delimiter-separated items.
927
928 Returns: Flattened list.
929 """
930 new_list = []
931 for item in appended_items:
Mike Frysinger4bd23892012-03-26 15:08:52 -0400932 new_list.extend(item.split())
Ryan Cuicedd8a52012-03-22 02:28:35 -0700933 return new_list
934
935
Brian Harring1d7ba942012-04-24 06:37:18 -0700936# pylint: disable=W0613
Ryan Cui85867972012-02-23 18:21:49 -0800937def _PostParseCheck(options, args):
938 """Perform some usage validation after we've parsed the arguments
Brian Harring3fec5a82012-03-01 05:57:03 -0800939
Ryan Cui85867972012-02-23 18:21:49 -0800940 Args:
941 options/args: The options/args object returned by optparse
942 """
Brian Harring1d7ba942012-04-24 06:37:18 -0700943 if options.resume:
944 return
945
Ryan Cui5ba7e152012-05-10 14:36:52 -0700946 if options.local_patches and not repository.IsARepoRoot(options.sourceroot):
947 raise Exception('Could not find repo checkout at %s!'
948 % options.sourceroot)
949
Brian Harring1d7ba942012-04-24 06:37:18 -0700950 options.gerrit_patches = _SplitAndFlatten(options.gerrit_patches)
951 options.remote_patches = _SplitAndFlatten(options.remote_patches)
952 try:
953 # TODO(rcui): Split this into two stages, one that parses, another that
954 # validates. Parsing step will be called by _FinishParsing().
955 options.local_patches = _CheckLocalPatches(
Ryan Cui5ba7e152012-05-10 14:36:52 -0700956 options.sourceroot,
Brian Harring1d7ba942012-04-24 06:37:18 -0700957 _SplitAndFlatten(options.local_patches))
958 except optparse.OptionValueError as e:
959 cros_lib.Die(str(e))
960
961 default = os.environ.get('CBUILDBOT_DEFAULT_MODE')
962 if (default and not any([options.local, options.buildbot,
963 options.remote, options.remote_trybot])):
964 cros_lib.Info("CBUILDBOT_DEFAULT_MODE=%s env var detected, using it."
965 % default)
966 default = default.lower()
967 if default == 'local':
968 options.local = True
969 elif default == 'remote':
970 options.remote = True
971 elif default == 'buildbot':
972 options.buildbot = True
973 else:
974 cros_lib.Die("CBUILDBOT_DEFAULT_MODE value %s isn't supported. "
975 % default)
Ryan Cui85867972012-02-23 18:21:49 -0800976
977
978def _ParseCommandLine(parser, argv):
979 """Completely parse the commandline arguments"""
Brian Harring3fec5a82012-03-01 05:57:03 -0800980 (options, args) = parser.parse_args(argv)
Ryan Cui54da0702012-04-19 18:38:08 -0700981 if options.list:
982 _PrintValidConfigs(options.print_all)
983 sys.exit(0)
984
Ryan Cui8be16062012-04-24 12:05:26 -0700985 # Strip out null arguments.
986 # TODO(rcui): Remove when buildbot is fixed
987 args = [arg for arg in args if arg]
988 if not args:
989 parser.error('Invalid usage. Use -h to see usage. Use -l to list '
990 'supported configs.')
991
Ryan Cui85867972012-02-23 18:21:49 -0800992 _FinishParsing(options, args)
993 return options, args
994
995
996def main(argv):
997 # Set umask to 022 so files created by buildbot are readable.
998 os.umask(022)
999
1000 if cros_lib.IsInsideChroot():
1001 cros_lib.Die('Please run cbuildbot from outside the chroot.')
1002
1003 parser = _CreateParser()
1004 (options, args) = _ParseCommandLine(parser, argv)
Brian Harring3fec5a82012-03-01 05:57:03 -08001005
Brian Harring3fec5a82012-03-01 05:57:03 -08001006 _PostParseCheck(options, args)
1007
1008 if options.remote:
Chris Sosa4f6ffaf2012-05-01 17:05:44 -07001009 cros_lib.logger.setLevel(logging.WARNING)
Ryan Cui16ca5812012-03-08 20:34:27 -08001010
Brian Harring3fec5a82012-03-01 05:57:03 -08001011 # Verify configs are valid.
1012 for bot in args:
1013 _GetConfig(bot)
1014
1015 # Verify gerrit patches are valid.
Ryan Cui16ca5812012-03-08 20:34:27 -08001016 print 'Verifying patches...'
Ryan Cui16d9e1f2012-05-11 10:50:18 -07001017 patch_pool = AcquirePoolFromOptions(options, _GetChromiteTrackingBranch())
1018
Ryan Cuieaa9efd2012-04-25 17:56:45 -07001019 # --debug need to be explicitly passed through for remote invocations.
1020 if options.buildbot and '--debug' not in options.pass_through_args:
1021 _ConfirmRemoteBuildbotRun()
1022
Ryan Cui16ca5812012-03-08 20:34:27 -08001023 print 'Submitting tryjob...'
Ryan Cui16d9e1f2012-05-11 10:50:18 -07001024 tryjob = remote_try.RemoteTryJob(options, args, patch_pool.local_patches)
Ryan Cui39bdbbf2012-02-29 16:15:39 -08001025 tryjob.Submit(testjob=options.test_tryjob, dryrun=options.debug)
Ryan Cui16ca5812012-03-08 20:34:27 -08001026 print 'Tryjob submitted!'
1027 print ('Go to %s to view the status of your job.'
Ryan Cui4906e1c2012-04-03 20:09:34 -07001028 % tryjob.GetTrybotWaterfallLink())
Brian Harring3fec5a82012-03-01 05:57:03 -08001029 sys.exit(0)
Ryan Cui54da0702012-04-19 18:38:08 -07001030 elif (not options.buildbot and not options.remote_trybot
1031 and not options.resume and not options.local):
1032 cros_lib.Warning('Running in LOCAL TRYBOT mode! Use --remote to submit '
1033 'REMOTE tryjobs. Use --local to suppress this message.')
1034 cros_lib.Warning('Starting April 30th, --local will be required to run the '
1035 'local trybot.')
1036 time.sleep(5)
Brian Harring3fec5a82012-03-01 05:57:03 -08001037
Ryan Cui8be16062012-04-24 12:05:26 -07001038 # Only expecting one config
1039 bot_id = args[-1]
1040 build_config = _GetConfig(bot_id)
Brian Harring3fec5a82012-03-01 05:57:03 -08001041
1042 if options.reference_repo is None:
Ryan Cui5ba7e152012-05-10 14:36:52 -07001043 repo_path = os.path.join(options.sourceroot, '.repo')
Brian Harring3fec5a82012-03-01 05:57:03 -08001044 # If we're being run from a repo checkout, reuse the repo's git pool to
1045 # cut down on sync time.
1046 if os.path.exists(repo_path):
Ryan Cui5ba7e152012-05-10 14:36:52 -07001047 options.reference_repo = options.sourceroot
Brian Harring3fec5a82012-03-01 05:57:03 -08001048 elif options.reference_repo:
1049 if not os.path.exists(options.reference_repo):
1050 parser.error('Reference path %s does not exist'
1051 % (options.reference_repo,))
1052 elif not os.path.exists(os.path.join(options.reference_repo, '.repo')):
1053 parser.error('Reference path %s does not look to be the base of a '
1054 'repo checkout; no .repo exists in the root.'
1055 % (options.reference_repo,))
Ryan Cuid4a24212012-04-04 18:08:12 -07001056
Brian Harringf11bf682012-05-14 15:53:43 -07001057 if (options.buildbot or options.remote_trybot) and not options.resume:
Brian Harring470f6112012-03-02 11:47:10 -08001058 if not options.cgroups:
Ryan Cuid4a24212012-04-04 18:08:12 -07001059 parser.error('Options --buildbot/--remote-trybot and --nocgroups cannot '
1060 'be used together. Cgroup support is required for '
1061 'buildbot/remote-trybot mode.')
Brian Harring470f6112012-03-02 11:47:10 -08001062 if not cgroups.Cgroup.CgroupsSupported():
Ryan Cuid4a24212012-04-04 18:08:12 -07001063 parser.error('Option --buildbot/--remote-trybot was given, but this '
1064 'system does not support cgroups. Failing.')
Brian Harring3fec5a82012-03-01 05:57:03 -08001065
Brian Harring351ce442012-03-09 16:38:14 -08001066 missing = []
1067 for program in _BUILDBOT_REQUIRED_BINARIES:
1068 ret = cros_lib.RunCommand('which %s' % program, shell=True,
1069 redirect_stderr=True, redirect_stdout=True,
1070 error_code_ok=True, print_cmd=False)
1071 if ret.returncode != 0:
1072 missing.append(program)
1073
1074 if missing:
Ryan Cuid4a24212012-04-04 18:08:12 -07001075 parser.error("Option --buildbot/--remote-trybot requires the following "
1076 "binaries which couldn't be found in $PATH: %s"
Brian Harring351ce442012-03-09 16:38:14 -08001077 % (', '.join(missing)))
1078
Brian Harring3fec5a82012-03-01 05:57:03 -08001079 if options.reference_repo:
1080 options.reference_repo = os.path.abspath(options.reference_repo)
1081
1082 if options.dump_config:
1083 # This works, but option ordering is bad...
1084 print 'Configuration %s:' % bot_id
1085 pretty_printer = pprint.PrettyPrinter(indent=2)
1086 pretty_printer.pprint(build_config)
1087 sys.exit(0)
1088
1089 if not options.buildroot:
1090 if options.buildbot:
1091 parser.error('Please specify a buildroot with the --buildroot option.')
Matt Tennantd55b1f42012-04-13 14:15:01 -07001092
Ryan Cui5ba7e152012-05-10 14:36:52 -07001093 options.buildroot = _DetermineDefaultBuildRoot(options.sourceroot,
1094 build_config['internal'])
Brian Harring470f6112012-03-02 11:47:10 -08001095 # We use a marker file in the buildroot to indicate the user has
1096 # consented to using this directory.
1097 if not os.path.exists(repository.GetTrybotMarkerPath(options.buildroot)):
1098 _ConfirmBuildRoot(options.buildroot)
Brian Harring3fec5a82012-03-01 05:57:03 -08001099
1100 # Sanity check of buildroot- specifically that it's not pointing into the
1101 # midst of an existing repo since git-repo doesn't support nesting.
Brian Harring3fec5a82012-03-01 05:57:03 -08001102 if (not repository.IsARepoRoot(options.buildroot) and
David James6b80dc62012-02-29 15:34:40 -08001103 repository.InARepoRepository(options.buildroot)):
Brian Harring3fec5a82012-03-01 05:57:03 -08001104 parser.error('Configured buildroot %s points into a repository checkout, '
1105 'rather than the root of it. This is not supported.'
1106 % options.buildroot)
1107
Brian Harringd166aaf2012-05-14 18:31:53 -07001108 log_file = None
1109 if options.tee:
1110 default_dir = os.path.join(options.buildroot, _DEFAULT_LOG_DIR)
1111 dirname = options.log_dir or default_dir
1112 log_file = os.path.join(dirname, _BUILDBOT_LOG_FILE)
1113
1114 osutils.SafeMakedirs(dirname)
1115 _BackupPreviousLog(log_file)
1116
Brian Harringc2d09d92012-05-13 22:03:15 -07001117 with cros_lib.ContextManagerStack() as stack:
1118 critical_section = stack.Add(cleanup.EnforcedCleanupSection)
1119 stack.Add(sudo.SudoKeepAlive)
Brian Harringd166aaf2012-05-14 18:31:53 -07001120
Brian Harringc2d09d92012-05-13 22:03:15 -07001121 if not options.resume:
Brian Harring2bf55e12012-05-13 21:31:55 -07001122 # If we're in resume mode, use our parents tempdir rather than
1123 # nesting another layer.
Brian Harringc2d09d92012-05-13 22:03:15 -07001124 stack.Add(osutils.TempDirContextManager, 'cbuildbot-tmp')
1125 logging.debug("Cbuildbot tempdir is %r.", os.environ.get('TMP'))
Brian Harringd166aaf2012-05-14 18:31:53 -07001126
1127 if log_file is not None:
1128 stack.Add(tee.Tee, log_file)
1129 options.preserve_paths = set([_DEFAULT_LOG_DIR])
1130
Brian Harringc2d09d92012-05-13 22:03:15 -07001131 if options.cgroups:
1132 stack.Add(cgroups.SimpleContainChildren, 'cbuildbot')
Brian Harringa184efa2012-03-04 11:51:25 -08001133
Brian Harringc2d09d92012-05-13 22:03:15 -07001134 # Mark everything between EnforcedCleanupSection and here as having to
1135 # be rolled back via the contextmanager cleanup handlers. This
1136 # ensures that sudo bits cannot outlive cbuildbot, that anything
1137 # cgroups would kill gets killed, etc.
1138 critical_section.ForkWatchdog()
Brian Harringd166aaf2012-05-14 18:31:53 -07001139
Brian Harringc2d09d92012-05-13 22:03:15 -07001140 if options.timeout > 0:
1141 stack.Add(cros_lib.Timeout, options.timeout)
Brian Harringa184efa2012-03-04 11:51:25 -08001142
Brian Harringc2d09d92012-05-13 22:03:15 -07001143 if not options.buildbot:
1144 build_config = cbuildbot_config.OverrideConfigForTrybot(
1145 build_config,
1146 options.remote_trybot)
1147
1148 _RunBuildStagesWrapper(options, build_config)