blob: 65fdb2a90d3cf9d9acd6e1184588780d763b0a1e [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
Brian Harring4ef305c2012-07-09 18:04:51 -070014import errno
Brian Harring3fec5a82012-03-01 05:57:03 -080015import glob
Chris Sosa4f6ffaf2012-05-01 17:05:44 -070016import logging
David James58e0c092012-03-04 20:31:12 -080017import multiprocessing
Brian Harring3fec5a82012-03-01 05:57:03 -080018import optparse
19import os
20import pprint
21import sys
Ryan Cui54da0702012-04-19 18:38:08 -070022import time
Brian Harring3fec5a82012-03-01 05:57:03 -080023
24from chromite.buildbot import builderstage as bs
25from chromite.buildbot import cbuildbot_background as background
26from chromite.buildbot import cbuildbot_config
27from chromite.buildbot import cbuildbot_stages as stages
28from chromite.buildbot import cbuildbot_results as results_lib
Brian Harring3fec5a82012-03-01 05:57:03 -080029from chromite.buildbot import constants
30from chromite.buildbot import gerrit_helper
31from chromite.buildbot import patch as cros_patch
32from chromite.buildbot import remote_try
33from chromite.buildbot import repository
34from chromite.buildbot import tee
Ryan Cui16d9e1f2012-05-11 10:50:18 -070035from chromite.buildbot import trybot_patch_pool
Brian Harring3fec5a82012-03-01 05:57:03 -080036
Brian Harringc92a7012012-02-29 10:11:34 -080037from chromite.lib import cgroups
Brian Harringa184efa2012-03-04 11:51:25 -080038from chromite.lib import cleanup
Brian Harring1b8c4c82012-05-29 23:03:04 -070039from chromite.lib import cros_build_lib
Brian Harringaf019fb2012-05-10 15:06:13 -070040from chromite.lib import osutils
Brian Harring3fec5a82012-03-01 05:57:03 -080041from chromite.lib import sudo
42
Ryan Cuiadd49122012-03-21 22:19:58 -070043
Brian Harring1b8c4c82012-05-29 23:03:04 -070044cros_build_lib.STRICT_SUDO = True
Brian Harring3fec5a82012-03-01 05:57:03 -080045
46_DEFAULT_LOG_DIR = 'cbuildbot_logs'
47_BUILDBOT_LOG_FILE = 'cbuildbot.log'
48_DEFAULT_EXT_BUILDROOT = 'trybot'
49_DEFAULT_INT_BUILDROOT = 'trybot-internal'
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
Brian Harring37e559b2012-05-22 20:47:32 -070055# Used by --resume and --bootstrap to decipher which options they
56# can pass to the target cbuildbot (since it may not have that
57# option).
58# Format is Major:Minor. Minor is used for tracking new options added
59# that aren't critical to the older version if it's not ran.
60# Major is used for tracking heavy API breakage- for example, no longer
61# supporting the --resume option.
62_REEXEC_API_MAJOR = 0
63_REEXEC_API_MINOR = 1
64_REEXEC_API_VERSION = '%i.%i' % (_REEXEC_API_MAJOR, _REEXEC_API_MINOR)
65
Brian Harring3fec5a82012-03-01 05:57:03 -080066
Ryan Cui4f6cf7e2012-04-18 16:12:27 -070067def _PrintValidConfigs(display_all=False):
Brian Harring3fec5a82012-03-01 05:57:03 -080068 """Print a list of valid buildbot configs.
69
70 Arguments:
Ryan Cui4f6cf7e2012-04-18 16:12:27 -070071 display_all: Print all configs. Otherwise, prints only configs with
72 trybot_list=True.
Brian Harring3fec5a82012-03-01 05:57:03 -080073 """
Ryan Cui4f6cf7e2012-04-18 16:12:27 -070074 def _GetSortKey(config_name):
75 config_dict = cbuildbot_config.config[config_name]
76 return (not config_dict['trybot_list'], config_dict['description'],
77 config_name)
78
Brian Harring3fec5a82012-03-01 05:57:03 -080079 COLUMN_WIDTH = 45
80 print 'config'.ljust(COLUMN_WIDTH), 'description'
81 print '------'.ljust(COLUMN_WIDTH), '-----------'
82 config_names = cbuildbot_config.config.keys()
Ryan Cui4f6cf7e2012-04-18 16:12:27 -070083 config_names.sort(key=_GetSortKey)
Brian Harring3fec5a82012-03-01 05:57:03 -080084 for name in config_names:
Ryan Cui4f6cf7e2012-04-18 16:12:27 -070085 if display_all or cbuildbot_config.config[name]['trybot_list']:
86 desc = cbuildbot_config.config[name].get('description')
87 desc = desc if desc else ''
Brian Harring3fec5a82012-03-01 05:57:03 -080088 print name.ljust(COLUMN_WIDTH), desc
89
90
91def _GetConfig(config_name):
92 """Gets the configuration for the build"""
93 if not cbuildbot_config.config.has_key(config_name):
94 print 'Non-existent configuration %s specified.' % config_name
95 print 'Please specify one of:'
96 _PrintValidConfigs()
97 sys.exit(1)
98
99 result = cbuildbot_config.config[config_name]
100
101 return result
102
103
Ryan Cuie1e4e662012-05-21 16:39:46 -0700104def AcquirePoolFromOptions(options):
Ryan Cui16d9e1f2012-05-11 10:50:18 -0700105 """Generate patch objects from passed in options.
Brian Harring3fec5a82012-03-01 05:57:03 -0800106
107 Args:
Ryan Cui16d9e1f2012-05-11 10:50:18 -0700108 options: The options object generated by optparse.
Brian Harring3fec5a82012-03-01 05:57:03 -0800109
Ryan Cuif7f24692012-05-18 16:35:33 -0700110 Returns:
111 trybot_patch_pool.TrybotPatchPool object.
112
Ryan Cui16d9e1f2012-05-11 10:50:18 -0700113 Raises:
114 gerrit_helper.GerritException, cros_patch.PatchException
Brian Harring3fec5a82012-03-01 05:57:03 -0800115 """
Ryan Cui16d9e1f2012-05-11 10:50:18 -0700116 gerrit_patches = []
117 local_patches = []
118 remote_patches = []
Brian Harring3fec5a82012-03-01 05:57:03 -0800119
Ryan Cui16d9e1f2012-05-11 10:50:18 -0700120 if options.gerrit_patches:
121 gerrit_patches = gerrit_helper.GetGerritPatchInfo(
122 options.gerrit_patches)
123 for patch in gerrit_patches:
124 if patch.IsAlreadyMerged():
Brian Harring1b8c4c82012-05-29 23:03:04 -0700125 cros_build_lib.Warning('Patch %s has already been merged.' % str(patch))
Brian Harring3fec5a82012-03-01 05:57:03 -0800126
Ryan Cui16d9e1f2012-05-11 10:50:18 -0700127 if options.local_patches:
Brian Harring1b8c4c82012-05-29 23:03:04 -0700128 manifest = cros_build_lib.ManifestCheckout.Cached(options.sourceroot)
Brian Harring609dc4e2012-05-07 02:17:44 -0700129 local_patches = cros_patch.PrepareLocalPatches(manifest,
130 options.local_patches)
Brian Harring3fec5a82012-03-01 05:57:03 -0800131
Ryan Cui16d9e1f2012-05-11 10:50:18 -0700132 if options.remote_patches:
133 remote_patches = cros_patch.PrepareRemotePatches(
134 options.remote_patches)
Brian Harring3fec5a82012-03-01 05:57:03 -0800135
Ryan Cui16d9e1f2012-05-11 10:50:18 -0700136 return trybot_patch_pool.TrybotPatchPool(gerrit_patches, local_patches,
137 remote_patches)
Brian Harring3fec5a82012-03-01 05:57:03 -0800138
139
Brian Harring3fec5a82012-03-01 05:57:03 -0800140class Builder(object):
141 """Parent class for all builder types.
142
143 This class functions as a parent class for various build types. It's intended
144 use is builder_instance.Run().
145
146 Vars:
Brian Harring3fec5a82012-03-01 05:57:03 -0800147 build_config: The configuration dictionary from cbuildbot_config.
148 options: The options provided from optparse in main().
Brian Harring3fec5a82012-03-01 05:57:03 -0800149 archive_url: Where our artifacts for this builder will be archived.
150 tracking_branch: The tracking branch for this build.
151 release_tag: The associated "chrome os version" of this build.
Brian Harring3fec5a82012-03-01 05:57:03 -0800152 """
153
Ryan Cuie1e4e662012-05-21 16:39:46 -0700154 def __init__(self, options, build_config):
Brian Harring3fec5a82012-03-01 05:57:03 -0800155 """Initializes instance variables. Must be called by all subclasses."""
Brian Harring3fec5a82012-03-01 05:57:03 -0800156 self.build_config = build_config
157 self.options = options
158
159 # TODO, Remove here and in config after bug chromium-os:14649 is fixed.
160 if self.build_config['chromeos_official']:
161 os.environ['CHROMEOS_OFFICIAL'] = '1'
162
David James58e0c092012-03-04 20:31:12 -0800163 self.archive_stages = {}
Brian Harring3fec5a82012-03-01 05:57:03 -0800164 self.archive_urls = {}
165 self.release_tag = None
Brian Harring4ef305c2012-07-09 18:04:51 -0700166 self.patch_pool = trybot_patch_pool.GetEmptyPool()
Brian Harring3fec5a82012-03-01 05:57:03 -0800167
Ryan Cuie1e4e662012-05-21 16:39:46 -0700168 bs.BuilderStage.SetManifestBranch(self.options.branch)
Ryan Cuif7f24692012-05-18 16:35:33 -0700169
Brian Harring3fec5a82012-03-01 05:57:03 -0800170 def Initialize(self):
171 """Runs through the initialization steps of an actual build."""
Ryan Cuif7f24692012-05-18 16:35:33 -0700172 if self.options.resume:
173 results_lib.LoadCheckpoint(self.options.buildroot)
Brian Harring3fec5a82012-03-01 05:57:03 -0800174
Brian Harring3fec5a82012-03-01 05:57:03 -0800175 self._RunStage(stages.CleanUpStage)
176
177 def _GetStageInstance(self, stage, *args, **kwargs):
178 """Helper function to get an instance given the args.
179
David James944a48e2012-03-07 12:19:03 -0800180 Useful as almost all stages just take in options and build_config.
Brian Harring3fec5a82012-03-01 05:57:03 -0800181 """
David James944a48e2012-03-07 12:19:03 -0800182 config = kwargs.pop('config', self.build_config)
183 return stage(self.options, config, *args, **kwargs)
Brian Harring3fec5a82012-03-01 05:57:03 -0800184
185 def _SetReleaseTag(self):
186 """Sets the release tag from the manifest_manager.
187
188 Must be run after sync stage as syncing enables us to have a release tag.
189 """
190 # Extract version we have decided to build into self.release_tag.
191 manifest_manager = stages.ManifestVersionedSyncStage.manifest_manager
192 if manifest_manager:
193 self.release_tag = manifest_manager.current_version
194
195 def _RunStage(self, stage, *args, **kwargs):
196 """Wrapper to run a stage."""
197 stage_instance = self._GetStageInstance(stage, *args, **kwargs)
198 return stage_instance.Run()
199
200 def GetSyncInstance(self):
201 """Returns an instance of a SyncStage that should be run.
202
203 Subclasses must override this method.
204 """
205 raise NotImplementedError()
206
207 def RunStages(self):
208 """Subclasses must override this method. Runs the appropriate code."""
209 raise NotImplementedError()
210
Brian Harring3fec5a82012-03-01 05:57:03 -0800211 def _ShouldReExecuteInBuildRoot(self):
212 """Returns True if this build should be re-executed in the buildroot."""
213 abs_buildroot = os.path.abspath(self.options.buildroot)
214 return not os.path.abspath(__file__).startswith(abs_buildroot)
215
216 def _ReExecuteInBuildroot(self, sync_instance):
217 """Reexecutes self in buildroot and returns True if build succeeds.
218
219 This allows the buildbot code to test itself when changes are patched for
220 buildbot-related code. This is a no-op if the buildroot == buildroot
221 of the running chromite checkout.
222
223 Args:
224 sync_instance: Instance of the sync stage that was run to sync.
225
226 Returns:
227 True if the Build succeeded.
228 """
Brian Harring3fec5a82012-03-01 05:57:03 -0800229 if not self.options.resume:
Ryan Cuif7f24692012-05-18 16:35:33 -0700230 results_lib.WriteCheckpoint(self.options.buildroot)
Brian Harring3fec5a82012-03-01 05:57:03 -0800231
Brian Harring37e559b2012-05-22 20:47:32 -0700232 # Get the re-exec API version of the target chromite; if it's incompatible
233 # with us, bail now.
Brian Harring1b8c4c82012-05-29 23:03:04 -0700234 api = cros_build_lib.RunCommandCaptureOutput(
Brian Harring37e559b2012-05-22 20:47:32 -0700235 [constants.PATH_TO_CBUILDBOT] + ['--reexec-api-version'],
236 cwd=self.options.buildroot, error_code_ok=True)
237 # If the command failed, then we're targeting a cbuildbot that lacks the
238 # option; assume 0:0 (ie, initial state).
239 major, minor = 0, 0
240 if api.returncode == 0:
241 major, minor = map(int, api.output.strip().split('.', 1))
242
243 if major != _REEXEC_API_MAJOR:
Brian Harring1b8c4c82012-05-29 23:03:04 -0700244 cros_build_lib.Die(
Brian Harring37e559b2012-05-22 20:47:32 -0700245 'The targeted version of chromite in buildroot %s requires '
246 'api version %i, but we are api version %i. We cannot proceed.'
247 % (self.options.buildroot, major, _REEXEC_API_MAJOR))
248
Brian Harring3fec5a82012-03-01 05:57:03 -0800249 # Re-write paths to use absolute paths.
250 # Suppress any timeout options given from the commandline in the
251 # invoked cbuildbot; our timeout will enforce it instead.
Brian Harringf11bf682012-05-14 15:53:43 -0700252 args_to_append = ['--resume', '--timeout', '0', '--notee', '--nocgroups',
253 '--buildroot', os.path.abspath(self.options.buildroot)]
Brian Harring3fec5a82012-03-01 05:57:03 -0800254
255 if self.options.chrome_root:
256 args_to_append += ['--chrome_root',
257 os.path.abspath(self.options.chrome_root)]
258
259 if stages.ManifestVersionedSyncStage.manifest_manager:
260 ver = stages.ManifestVersionedSyncStage.manifest_manager.current_version
261 args_to_append += ['--version', ver]
262
263 if isinstance(sync_instance, stages.CommitQueueSyncStage):
264 vp_file = sync_instance.SaveValidationPool()
265 args_to_append += ['--validation_pool', vp_file]
266
267 # Re-run the command in the buildroot.
268 # Finally, be generous and give the invoked cbuildbot 30s to shutdown
269 # when something occurs. It should exit quicker, but the sigterm may
270 # hit while the system is particularly busy.
Brian Harring1b8c4c82012-05-29 23:03:04 -0700271 return_obj = cros_build_lib.RunCommand(
Ryan Cuif7f24692012-05-18 16:35:33 -0700272 [constants.PATH_TO_CBUILDBOT] + sys.argv[1:] + args_to_append,
Brian Harring3fec5a82012-03-01 05:57:03 -0800273 cwd=self.options.buildroot, error_code_ok=True, kill_timeout=30)
274 return return_obj.returncode == 0
275
Ryan Cuif7f24692012-05-18 16:35:33 -0700276 def _InitializeTrybotPatchPool(self):
277 """Generate patch pool from patches specified on the command line.
278
279 Do this only if we need to patch changes later on.
280 """
281 changes_stage = stages.PatchChangesStage.StageNamePrefix()
282 check_func = results_lib.Results.PreviouslyCompletedRecord
283 if not check_func(changes_stage) or self.options.bootstrap:
Ryan Cuie1e4e662012-05-21 16:39:46 -0700284 self.patch_pool = AcquirePoolFromOptions(self.options)
Ryan Cuif7f24692012-05-18 16:35:33 -0700285
286 def _GetBootstrapStage(self):
287 """Constructs and returns the BootStrapStage object.
288
289 We return None when there are no chromite patches to test, and
290 --test-bootstrap wasn't passed in.
291 """
292 stage = None
293 chromite_pool = self.patch_pool.Filter(project=constants.CHROMITE_PROJECT)
Chris Sosa126103a2012-06-18 09:03:17 -0700294 chromite_branch = cros_build_lib.GetChromiteTrackingBranch()
Ryan Cuie1e4e662012-05-21 16:39:46 -0700295 if (chromite_pool or self.options.test_bootstrap
296 or chromite_branch != self.options.branch):
Ryan Cuif7f24692012-05-18 16:35:33 -0700297 stage = stages.BootstrapStage(self.options, self.build_config,
298 chromite_pool)
299 return stage
300
Brian Harring3fec5a82012-03-01 05:57:03 -0800301 def Run(self):
Ryan Cuif7f24692012-05-18 16:35:33 -0700302 """Main runner for this builder class. Runs build and prints summary.
303
304 Returns:
305 Whether the build succeeded.
306 """
307 self._InitializeTrybotPatchPool()
308
309 if self.options.bootstrap:
310 bootstrap_stage = self._GetBootstrapStage()
311 if bootstrap_stage:
312 # BootstrapStage blocks on re-execution of cbuildbot.
313 bootstrap_stage.Run()
314 return bootstrap_stage.returncode == 0
315
Brian Harring3fec5a82012-03-01 05:57:03 -0800316 print_report = True
David James3d4d3502012-04-09 15:12:06 -0700317 exception_thrown = False
Brian Harring3fec5a82012-03-01 05:57:03 -0800318 success = True
319 try:
320 self.Initialize()
321 sync_instance = self.GetSyncInstance()
322 sync_instance.Run()
323 self._SetReleaseTag()
324
Ryan Cui16d9e1f2012-05-11 10:50:18 -0700325 if self.patch_pool:
326 self._RunStage(stages.PatchChangesStage, self.patch_pool)
Brian Harring3fec5a82012-03-01 05:57:03 -0800327
328 if self._ShouldReExecuteInBuildRoot():
329 print_report = False
330 success = self._ReExecuteInBuildroot(sync_instance)
331 else:
332 self.RunStages()
David James3d4d3502012-04-09 15:12:06 -0700333 except Exception:
334 exception_thrown = True
335 raise
Brian Harring3fec5a82012-03-01 05:57:03 -0800336 finally:
337 if print_report:
Ryan Cuif7f24692012-05-18 16:35:33 -0700338 results_lib.WriteCheckpoint(self.options.buildroot)
Brian Harring3fec5a82012-03-01 05:57:03 -0800339 print '\n\n\n@@@BUILD_STEP Report@@@\n'
340 results_lib.Results.Report(sys.stdout, self.archive_urls,
341 self.release_tag)
342 success = results_lib.Results.BuildSucceededSoFar()
David James3d4d3502012-04-09 15:12:06 -0700343 if exception_thrown and success:
344 success = False
David James62d2be12012-06-16 21:02:26 -0700345 print >> sys.stderr, "\n" + constants.STEP_WARNINGS + """
David James3d4d3502012-04-09 15:12:06 -0700346Exception thrown, but all stages marked successful. This is an internal error,
347because the stage that threw the exception should be marked as failing."""
Brian Harring3fec5a82012-03-01 05:57:03 -0800348
349 return success
350
351
352class SimpleBuilder(Builder):
353 """Builder that performs basic vetting operations."""
354
355 def GetSyncInstance(self):
356 """Sync to lkgm or TOT as necessary.
357
358 Returns: the instance of the sync stage that was run.
359 """
360 if self.options.lkgm or self.build_config['use_lkgm']:
361 sync_stage = self._GetStageInstance(stages.LKGMSyncStage)
362 else:
363 sync_stage = self._GetStageInstance(stages.SyncStage)
364
365 return sync_stage
366
David James58e0c092012-03-04 20:31:12 -0800367 def _RunBackgroundStagesForBoard(self, board):
368 """Run background board-specific stages for the specified board."""
David James58e0c092012-03-04 20:31:12 -0800369 archive_stage = self.archive_stages[board]
David James944a48e2012-03-07 12:19:03 -0800370 configs = self.build_config['board_specific_configs']
371 config = configs.get(board, self.build_config)
372 stage_list = [[stages.VMTestStage, board, archive_stage],
373 [stages.ChromeTestStage, board, archive_stage],
374 [stages.UnitTestStage, board],
Chris Sosa6a5dceb2012-05-14 13:48:56 -0700375 [stages.UploadPrebuiltsStage, board, archive_stage]]
Brian Harring3fec5a82012-03-01 05:57:03 -0800376
David James58e0c092012-03-04 20:31:12 -0800377 # We can not run hw tests without archiving the payloads.
378 if self.options.archive:
David James944a48e2012-03-07 12:19:03 -0800379 for suite in config['hw_tests']:
Chris Sosa57dca2a2012-06-25 15:09:59 -0700380 if cbuildbot_config.IsCQType(config['build_type']):
381 stage_list.append([stages.PaladinHWTestStage, board, archive_stage,
382 suite])
383 else:
384 stage_list.append([stages.HWTestStage, board, archive_stage, suite])
Chris Sosab50dc932012-03-01 14:00:58 -0800385
David James944a48e2012-03-07 12:19:03 -0800386 steps = [self._GetStageInstance(*x, config=config).Run for x in stage_list]
387 background.RunParallelSteps(steps + [archive_stage.Run])
Brian Harring3fec5a82012-03-01 05:57:03 -0800388
389 def RunStages(self):
390 """Runs through build process."""
391 self._RunStage(stages.BuildBoardStage)
392
393 # TODO(sosa): Split these out into classes.
Brian Harring3fec5a82012-03-01 05:57:03 -0800394 if self.build_config['build_type'] == constants.CHROOT_BUILDER_TYPE:
395 self._RunStage(stages.SDKTestStage)
396 self._RunStage(stages.UploadPrebuiltsStage,
Chris Sosa6a5dceb2012-05-14 13:48:56 -0700397 constants.CHROOT_BUILDER_BOARD, None)
Brian Harring3fec5a82012-03-01 05:57:03 -0800398 elif self.build_config['build_type'] == constants.REFRESH_PACKAGES_TYPE:
399 self._RunStage(stages.RefreshPackageStatusStage)
400 else:
401 self._RunStage(stages.UprevStage)
Brian Harring3fec5a82012-03-01 05:57:03 -0800402
David James944a48e2012-03-07 12:19:03 -0800403 configs = self.build_config['board_specific_configs']
David James58e0c092012-03-04 20:31:12 -0800404 for board in self.build_config['boards']:
David James944a48e2012-03-07 12:19:03 -0800405 config = configs.get(board, self.build_config)
406 archive_stage = self._GetStageInstance(stages.ArchiveStage, board,
407 config=config)
David James58e0c092012-03-04 20:31:12 -0800408 self.archive_stages[board] = archive_stage
409
David James944a48e2012-03-07 12:19:03 -0800410 # Set up a process pool to run test/archive stages in the background.
411 # This process runs task(board) for each board added to the queue.
David James58e0c092012-03-04 20:31:12 -0800412 queue = multiprocessing.Queue()
413 task = self._RunBackgroundStagesForBoard
414 with background.BackgroundTaskRunner(queue, task):
David James944a48e2012-03-07 12:19:03 -0800415 for board in self.build_config['boards']:
David James58e0c092012-03-04 20:31:12 -0800416 # Run BuildTarget in the foreground.
David James944a48e2012-03-07 12:19:03 -0800417 archive_stage = self.archive_stages[board]
418 config = configs.get(board, self.build_config)
419 self._RunStage(stages.BuildTargetStage, board, archive_stage,
Chris Sosa1a87b3e2012-04-12 13:20:42 -0700420 self.release_tag, config=config)
David James58e0c092012-03-04 20:31:12 -0800421 self.archive_urls[board] = archive_stage.GetDownloadUrl()
422
David James944a48e2012-03-07 12:19:03 -0800423 # Kick off task(board) in the background.
David James58e0c092012-03-04 20:31:12 -0800424 queue.put([board])
425
Brian Harring3fec5a82012-03-01 05:57:03 -0800426
427class DistributedBuilder(SimpleBuilder):
428 """Build class that has special logic to handle distributed builds.
429
430 These builds sync using git/manifest logic in manifest_versions. In general
431 they use a non-distributed builder code for the bulk of the work.
432 """
Ryan Cuif7f24692012-05-18 16:35:33 -0700433 def __init__(self, *args, **kwargs):
Brian Harring3fec5a82012-03-01 05:57:03 -0800434 """Initializes a buildbot builder.
435
436 Extra variables:
437 completion_stage_class: Stage used to complete a build. Set in the Sync
438 stage.
439 """
Ryan Cuif7f24692012-05-18 16:35:33 -0700440 super(DistributedBuilder, self).__init__(*args, **kwargs)
Brian Harring3fec5a82012-03-01 05:57:03 -0800441 self.completion_stage_class = None
442
443 def GetSyncInstance(self):
444 """Syncs the tree using one of the distributed sync logic paths.
445
446 Returns: the instance of the sync stage that was run.
447 """
448 # Determine sync class to use. CQ overrides PFQ bits so should check it
449 # first.
450 if cbuildbot_config.IsCQType(self.build_config['build_type']):
451 sync_stage = self._GetStageInstance(stages.CommitQueueSyncStage)
452 self.completion_stage_class = stages.CommitQueueCompletionStage
453 elif cbuildbot_config.IsPFQType(self.build_config['build_type']):
454 sync_stage = self._GetStageInstance(stages.LKGMCandidateSyncStage)
455 self.completion_stage_class = stages.LKGMCandidateSyncCompletionStage
456 else:
457 sync_stage = self._GetStageInstance(stages.ManifestVersionedSyncStage)
458 self.completion_stage_class = stages.ManifestVersionedSyncCompletionStage
459
460 return sync_stage
461
462 def Publish(self, was_build_successful):
463 """Completes build by publishing any required information."""
464 completion_stage = self._GetStageInstance(self.completion_stage_class,
465 was_build_successful)
466 completion_stage.Run()
467 name = completion_stage.name
468 if not results_lib.Results.WasStageSuccessful(name):
469 should_publish_changes = False
470 else:
471 should_publish_changes = (self.build_config['master'] and
472 was_build_successful)
473
474 if should_publish_changes:
475 self._RunStage(stages.PublishUprevChangesStage)
476
477 def RunStages(self):
478 """Runs simple builder logic and publishes information to overlays."""
479 was_build_successful = False
480 try:
David Jamesf55709e2012-03-13 09:10:15 -0700481 super(DistributedBuilder, self).RunStages()
482 was_build_successful = results_lib.Results.BuildSucceededSoFar()
Brian Harring3fec5a82012-03-01 05:57:03 -0800483 except SystemExit as ex:
484 # If a stage calls sys.exit(0), it's exiting with success, so that means
485 # we should mark ourselves as successful.
486 if ex.code == 0:
487 was_build_successful = True
488 raise
489 finally:
490 self.Publish(was_build_successful)
491
Brian Harring3fec5a82012-03-01 05:57:03 -0800492
493def _ConfirmBuildRoot(buildroot):
494 """Confirm with user the inferred buildroot, and mark it as confirmed."""
495 warning = 'Using default directory %s as buildroot' % buildroot
Brian Harring1b8c4c82012-05-29 23:03:04 -0700496 response = cros_build_lib.YesNoPrompt(
497 default=cros_build_lib.NO, warning=warning, full=True)
498 if response == cros_build_lib.NO:
Brian Harring3fec5a82012-03-01 05:57:03 -0800499 print('Please specify a buildroot with the --buildroot option.')
500 sys.exit(0)
501
502 if not os.path.exists(buildroot):
503 os.mkdir(buildroot)
504
505 repository.CreateTrybotMarker(buildroot)
506
507
Ryan Cuieaa9efd2012-04-25 17:56:45 -0700508def _ConfirmRemoteBuildbotRun():
509 """Confirm user wants to run with --buildbot --remote."""
510 warning = ('You are about to launch a PRODUCTION job! This is *NOT* a '
511 'trybot run! Are you sure?')
Brian Harring1b8c4c82012-05-29 23:03:04 -0700512 response = cros_build_lib.YesNoPrompt(
513 default=cros_build_lib.NO, warning=warning, full=True)
Ryan Cuieaa9efd2012-04-25 17:56:45 -0700514
Brian Harring1b8c4c82012-05-29 23:03:04 -0700515 if response == cros_build_lib.NO:
Ryan Cuieaa9efd2012-04-25 17:56:45 -0700516 print('Please specify --pass-through="--debug".')
517 sys.exit(0)
518
519
Ryan Cui5ba7e152012-05-10 14:36:52 -0700520def _DetermineDefaultBuildRoot(sourceroot, internal_build):
Brian Harring3fec5a82012-03-01 05:57:03 -0800521 """Default buildroot to be under the directory that contains current checkout.
522
523 Arguments:
524 internal_build: Whether the build is an internal build
Ryan Cui5ba7e152012-05-10 14:36:52 -0700525 sourceroot: Use specified sourceroot.
Brian Harring3fec5a82012-03-01 05:57:03 -0800526 """
Ryan Cui5ba7e152012-05-10 14:36:52 -0700527 if not repository.IsARepoRoot(sourceroot):
Brian Harring1b8c4c82012-05-29 23:03:04 -0700528 cros_build_lib.Die(
529 'Could not find root of local checkout at %s. Please specify '
530 'using the --sourceroot option.' % sourceroot)
Brian Harring3fec5a82012-03-01 05:57:03 -0800531
532 # Place trybot buildroot under the directory containing current checkout.
Ryan Cui5ba7e152012-05-10 14:36:52 -0700533 top_level = os.path.dirname(os.path.realpath(sourceroot))
Brian Harring3fec5a82012-03-01 05:57:03 -0800534 if internal_build:
535 buildroot = os.path.join(top_level, _DEFAULT_INT_BUILDROOT)
536 else:
537 buildroot = os.path.join(top_level, _DEFAULT_EXT_BUILDROOT)
538
539 return buildroot
540
541
542def _BackupPreviousLog(log_file, backup_limit=25):
543 """Rename previous log.
544
545 Args:
546 log_file: The absolute path to the previous log.
547 """
548 if os.path.exists(log_file):
549 old_logs = sorted(glob.glob(log_file + '.*'),
550 key=distutils.version.LooseVersion)
551
552 if len(old_logs) >= backup_limit:
553 os.remove(old_logs[0])
554
555 last = 0
556 if old_logs:
557 last = int(old_logs.pop().rpartition('.')[2])
558
559 os.rename(log_file, log_file + '.' + str(last + 1))
560
David James944a48e2012-03-07 12:19:03 -0800561def _RunBuildStagesWrapper(options, build_config):
Brian Harring3fec5a82012-03-01 05:57:03 -0800562 """Helper function that wraps RunBuildStages()."""
563 def IsDistributedBuilder():
564 """Determines whether the build_config should be a DistributedBuilder."""
565 if not options.buildbot:
566 return False
567 elif build_config['build_type'] in _DISTRIBUTED_TYPES:
568 chrome_rev = build_config['chrome_rev']
569 if options.chrome_rev: chrome_rev = options.chrome_rev
570 # We don't do distributed logic to TOT Chrome PFQ's, nor local
571 # chrome roots (e.g. chrome try bots)
572 if chrome_rev not in [constants.CHROME_REV_TOT,
573 constants.CHROME_REV_LOCAL,
574 constants.CHROME_REV_SPEC]:
575 return True
576
577 return False
578
Brian Harring1b8c4c82012-05-29 23:03:04 -0700579 cros_build_lib.Info("cbuildbot executed with args %s"
580 % ' '.join(map(repr, sys.argv)))
Brian Harring3fec5a82012-03-01 05:57:03 -0800581
Ryan Cuif7f24692012-05-18 16:35:33 -0700582 target = DistributedBuilder if IsDistributedBuilder() else SimpleBuilder
Ryan Cuie1e4e662012-05-21 16:39:46 -0700583 buildbot = target(options, build_config)
Brian Harringd166aaf2012-05-14 18:31:53 -0700584 if not buildbot.Run():
585 sys.exit(1)
Brian Harring3fec5a82012-03-01 05:57:03 -0800586
587
588# Parser related functions
Ryan Cui5ba7e152012-05-10 14:36:52 -0700589def _CheckLocalPatches(sourceroot, local_patches):
Brian Harring3fec5a82012-03-01 05:57:03 -0800590 """Do an early quick check of the passed-in patches.
591
592 If the branch of a project is not specified we append the current branch the
593 project is on.
Ryan Cui5ba7e152012-05-10 14:36:52 -0700594
595 Args:
596 sourceroot: The checkout where patches are coming from.
Brian Harring3fec5a82012-03-01 05:57:03 -0800597 """
Ryan Cuicedd8a52012-03-22 02:28:35 -0700598 verified_patches = []
Brian Harring1b8c4c82012-05-29 23:03:04 -0700599 manifest = cros_build_lib.ManifestCheckout.Cached(sourceroot)
Ryan Cuicedd8a52012-03-22 02:28:35 -0700600 for patch in local_patches:
Brian Harring3fec5a82012-03-01 05:57:03 -0800601 components = patch.split(':')
602 if len(components) > 2:
Brian Harring1b8c4c82012-05-29 23:03:04 -0700603 cros_build_lib.Die(
604 'Specify local patches in project[:branch] format. Got %s' % patch)
Brian Harring3fec5a82012-03-01 05:57:03 -0800605
606 # validate project
607 project = components[0]
Brian Harring3fec5a82012-03-01 05:57:03 -0800608
Brian Harring609dc4e2012-05-07 02:17:44 -0700609 try:
610 project_dir = manifest.GetProjectPath(project, True)
611 except KeyError:
Brian Harring1b8c4c82012-05-29 23:03:04 -0700612 cros_build_lib.Die('Project %s does not exist.' % project)
Brian Harring3fec5a82012-03-01 05:57:03 -0800613
614 # If no branch was specified, we use the project's current branch.
615 if len(components) == 1:
Brian Harring1b8c4c82012-05-29 23:03:04 -0700616 branch = cros_build_lib.GetCurrentBranch(project_dir)
Brian Harring3fec5a82012-03-01 05:57:03 -0800617 if not branch:
Brian Harring1b8c4c82012-05-29 23:03:04 -0700618 cros_build_lib.Die('Project %s is not on a branch!' % project)
Brian Harring3fec5a82012-03-01 05:57:03 -0800619 else:
620 branch = components[1]
Brian Harring1b8c4c82012-05-29 23:03:04 -0700621 if not cros_build_lib.DoesLocalBranchExist(project_dir, branch):
622 cros_build_lib.Die('Project %s does not have branch %s'
623 % (project, branch))
Brian Harring3fec5a82012-03-01 05:57:03 -0800624
Brian Harring609dc4e2012-05-07 02:17:44 -0700625 verified_patches.append('%s:%s' % (project, branch))
Brian Harring3fec5a82012-03-01 05:57:03 -0800626
Ryan Cuicedd8a52012-03-22 02:28:35 -0700627 return verified_patches
Brian Harring3fec5a82012-03-01 05:57:03 -0800628
629
Brian Harring3fec5a82012-03-01 05:57:03 -0800630def _CheckChromeVersionOption(_option, _opt_str, value, parser):
631 """Upgrade other options based on chrome_version being passed."""
632 value = value.strip()
633
634 if parser.values.chrome_rev is None and value:
635 parser.values.chrome_rev = constants.CHROME_REV_SPEC
636
637 parser.values.chrome_version = value
638
639
640def _CheckChromeRootOption(_option, _opt_str, value, parser):
641 """Validate and convert chrome_root to full-path form."""
Brian Harring3fec5a82012-03-01 05:57:03 -0800642 if parser.values.chrome_rev is None:
643 parser.values.chrome_rev = constants.CHROME_REV_LOCAL
644
Ryan Cui5ba7e152012-05-10 14:36:52 -0700645 parser.values.chrome_root = value
Brian Harring3fec5a82012-03-01 05:57:03 -0800646
647
648def _CheckChromeRevOption(_option, _opt_str, value, parser):
649 """Validate the chrome_rev option."""
650 value = value.strip()
651 if value not in constants.VALID_CHROME_REVISIONS:
652 raise optparse.OptionValueError('Invalid chrome rev specified')
653
654 parser.values.chrome_rev = value
655
656
Ryan Cui5ba7e152012-05-10 14:36:52 -0700657class CustomParser(optparse.OptionParser):
658 def add_remote_option(self, *args, **kwargs):
659 """For arguments that are passed-through to remote trybot."""
660 return optparse.OptionParser.add_option(self, *args,
661 remote_pass_through=True,
662 **kwargs)
663
664
665class CustomGroup(optparse.OptionGroup):
666 def add_remote_option(self, *args, **kwargs):
667 """For arguments that are passed-through to remote trybot."""
668 return optparse.OptionGroup.add_option(self, *args,
669 remote_pass_through=True,
670 **kwargs)
671
672
Ryan Cuif7f24692012-05-18 16:35:33 -0700673# pylint: disable=W0613
Ryan Cui5ba7e152012-05-10 14:36:52 -0700674def check_path(option, opt, value):
675 """Expand paths and make them absolute."""
676 expanded = osutils.ExpandPath(value)
677 if expanded == '/':
678 raise optparse.OptionValueError('Invalid path %s specified for %s'
679 % (expanded, opt))
680
681 return expanded
682
Ryan Cui6196fc22012-06-27 17:52:18 -0700683# pylint: disable=W0613
684def check_gs_path(option, opt, value):
685 """Expand paths and make them absolute."""
686 value = value.strip().rstrip('/')
687 if not value.startswith('gs://'):
688 raise optparse.OptionValueError('Invalid gs path %s specified for %s'
689 % (value, opt))
690
691 return value
692
Ryan Cui5ba7e152012-05-10 14:36:52 -0700693
694class CustomOption(optparse.Option):
695 """Subclass Option class to implement pass-through and path evaluation."""
Ryan Cui6196fc22012-06-27 17:52:18 -0700696 TYPES = optparse.Option.TYPES + ('path', 'gs_path')
Ryan Cui5ba7e152012-05-10 14:36:52 -0700697 TYPE_CHECKER = optparse.Option.TYPE_CHECKER.copy()
698 TYPE_CHECKER['path'] = check_path
Ryan Cui6196fc22012-06-27 17:52:18 -0700699 TYPE_CHECKER['gs_path'] = check_gs_path
Ryan Cui5ba7e152012-05-10 14:36:52 -0700700
Ryan Cui79319ab2012-05-21 12:59:18 -0700701 ACTIONS = optparse.Option.ACTIONS + ('extend',)
702 STORE_ACTIONS = optparse.Option.STORE_ACTIONS + ('extend',)
703 TYPED_ACTIONS = optparse.Option.TYPED_ACTIONS + ('extend',)
704 ALWAYS_TYPED_ACTIONS = optparse.Option.ALWAYS_TYPED_ACTIONS + ('extend',)
705
Ryan Cui5ba7e152012-05-10 14:36:52 -0700706 def __init__(self, *args, **kwargs):
707 # The remote_pass_through argument specifies whether we should directly
708 # pass the argument (with its value) onto the remote trybot.
709 self.pass_through = kwargs.pop('remote_pass_through', False)
710 optparse.Option.__init__(self, *args, **kwargs)
711
712 def take_action(self, action, dest, opt, value, values, parser):
Ryan Cui79319ab2012-05-21 12:59:18 -0700713 if action == 'extend':
714 lvalue = value.split(' ')
715 values.ensure_value(dest, []).extend(lvalue)
716 else:
717 optparse.Option.take_action(self, action, dest, opt, value, values,
718 parser)
719
Ryan Cui5ba7e152012-05-10 14:36:52 -0700720 if self.pass_through:
721 parser.values.pass_through_args.append(opt)
722 if self.nargs and self.nargs > 1:
723 # value is a tuple if nargs > 1
724 string_list = [str(val) for val in list(value)]
725 parser.values.pass_through_args.extend(string_list)
726 elif value:
727 parser.values.pass_through_args.append(str(value))
728
729
Brian Harring3fec5a82012-03-01 05:57:03 -0800730def _CreateParser():
Ryan Cui16d9e1f2012-05-11 10:50:18 -0700731 """Generate and return the parser with all the options."""
Brian Harring3fec5a82012-03-01 05:57:03 -0800732 # Parse options
733 usage = "usage: %prog [options] buildbot_config"
Ryan Cuieaa9efd2012-04-25 17:56:45 -0700734 parser = CustomParser(usage=usage, option_class=CustomOption)
Brian Harring3fec5a82012-03-01 05:57:03 -0800735
736 # Main options
Ryan Cuieaa9efd2012-04-25 17:56:45 -0700737 # The remote_pass_through parameter to add_option is implemented by the
738 # CustomOption class. See CustomOption for more information.
Brian Harring3fec5a82012-03-01 05:57:03 -0800739 parser.add_option('-a', '--all', action='store_true', dest='print_all',
740 default=False,
741 help=('List all of the buildbot configs available. Use '
742 'with the --list option'))
Ryan Cuie1e4e662012-05-21 16:39:46 -0700743 parser.add_remote_option('-b', '--branch',
744 help='The manifest branch to test. The branch to '
745 'check the buildroot out to.')
Ryan Cui5ba7e152012-05-10 14:36:52 -0700746 parser.add_option('-r', '--buildroot', dest='buildroot', type='path',
Ryan Cuieaa9efd2012-04-25 17:56:45 -0700747 help='Root directory where source is checked out to, and '
748 'where the build occurs. For external build configs, '
749 "defaults to 'trybot' directory at top level of your "
750 'repo-managed checkout.')
751 parser.add_remote_option('--chrome_rev', default=None, type='string',
752 action='callback', dest='chrome_rev',
753 callback=_CheckChromeRevOption,
754 help=('Revision of Chrome to use, of type [%s]'
755 % '|'.join(constants.VALID_CHROME_REVISIONS)))
Ryan Cui79319ab2012-05-21 12:59:18 -0700756 parser.add_remote_option('-g', '--gerrit-patches', action='extend',
Ryan Cuieaa9efd2012-04-25 17:56:45 -0700757 default=[], type='string',
758 metavar="'Id1 *int_Id2...IdN'",
759 help=("Space-separated list of short-form Gerrit "
760 "Change-Id's or change numbers to patch. "
761 "Please prepend '*' to internal Change-Id's"))
Brian Harring3fec5a82012-03-01 05:57:03 -0800762 parser.add_option('-l', '--list', action='store_true', dest='list',
763 default=False,
764 help=('List the suggested trybot configs to use. Use '
765 '--all to list all of the available configs.'))
Ryan Cui54da0702012-04-19 18:38:08 -0700766 parser.add_option('--local', default=False, action='store_true',
767 help=('Specifies that this tryjob should be run locally.'))
Ryan Cui79319ab2012-05-21 12:59:18 -0700768 parser.add_option('-p', '--local-patches', action='extend', default=[],
Brian Harring3fec5a82012-03-01 05:57:03 -0800769 metavar="'<project1>[:<branch1>]...<projectN>[:<branchN>]'",
770 help=('Space-separated list of project branches with '
771 'patches to apply. Projects are specified by name. '
772 'If no branch is specified the current branch of the '
773 'project will be used.'))
Ryan Cuieaa9efd2012-04-25 17:56:45 -0700774 parser.add_remote_option('--profile', default=None, type='string',
775 action='store', dest='profile',
776 help='Name of profile to sub-specify board variant.')
Brian Harring3fec5a82012-03-01 05:57:03 -0800777 parser.add_option('--remote', default=False, action='store_true',
Brian Harring3fec5a82012-03-01 05:57:03 -0800778 help=('Specifies that this tryjob should be run remotely.'))
779
Ryan Cuif4f84be2012-07-09 18:50:41 -0700780 #
Brian Harring3fec5a82012-03-01 05:57:03 -0800781 # Advanced options
Ryan Cuif4f84be2012-07-09 18:50:41 -0700782 #
783
Ryan Cuieaa9efd2012-04-25 17:56:45 -0700784 group = CustomGroup(
Brian Harring3fec5a82012-03-01 05:57:03 -0800785 parser,
786 'Advanced Options',
787 'Caution: use these options at your own risk.')
788
Ryan Cuieaa9efd2012-04-25 17:56:45 -0700789 group.add_remote_option('--buildbot', dest='buildbot', action='store_true',
790 default=False, help='This is running on a buildbot')
791 group.add_remote_option('--buildnumber', help='build number', type='int',
792 default=0)
Ryan Cui5ba7e152012-05-10 14:36:52 -0700793 group.add_option('--chrome_root', default=None, type='path',
794 action='callback', callback=_CheckChromeRootOption,
795 dest='chrome_root', help='Local checkout of Chrome to use.')
Ryan Cuieaa9efd2012-04-25 17:56:45 -0700796 group.add_remote_option('--chrome_version', default=None, type='string',
797 action='callback', dest='chrome_version',
798 callback=_CheckChromeVersionOption,
799 help='Used with SPEC logic to force a particular SVN '
800 'revision of chrome rather than the latest.')
801 group.add_remote_option('--clobber', action='store_true', dest='clobber',
802 default=False,
803 help='Clears an old checkout before syncing')
Yu-Ju Hong52134292012-06-28 12:50:42 -0700804 group.add_remote_option('--hwtest', dest='hwtest', action='store_true',
805 default=False,
806 help='This adds HW test for remote trybot')
Ryan Cuieaa9efd2012-04-25 17:56:45 -0700807 group.add_remote_option('--lkgm', action='store_true', dest='lkgm',
808 default=False,
809 help='Sync to last known good manifest blessed by '
810 'PFQ')
Ryan Cui5ba7e152012-05-10 14:36:52 -0700811 parser.add_option('--log_dir', dest='log_dir', type='path',
Brian Harring3fec5a82012-03-01 05:57:03 -0800812 help=('Directory where logs are stored.'))
Ryan Cuieaa9efd2012-04-25 17:56:45 -0700813 group.add_remote_option('--maxarchives', dest='max_archive_builds',
814 default=3, type='int',
815 help="Change the local saved build count limit.")
816 group.add_remote_option('--noarchive', action='store_false', dest='archive',
817 default=True, help="Don't run archive stage.")
Ryan Cuif7f24692012-05-18 16:35:33 -0700818 group.add_remote_option('--nobootstrap', action='store_false',
819 dest='bootstrap', default=True,
820 help="Don't checkout and run from a standalone "
821 "chromite repo.")
Ryan Cuieaa9efd2012-04-25 17:56:45 -0700822 group.add_remote_option('--nobuild', action='store_false', dest='build',
823 default=True,
824 help="Don't actually build (for cbuildbot dev)")
825 group.add_remote_option('--noclean', action='store_false', dest='clean',
826 default=True, help="Don't clean the buildroot")
Ryan Cuif7f24692012-05-18 16:35:33 -0700827 group.add_remote_option('--nocgroups', action='store_false', dest='cgroups',
828 default=True,
829 help='Disable cbuildbots usage of cgroups.')
Ryan Cuieaa9efd2012-04-25 17:56:45 -0700830 group.add_remote_option('--noprebuilts', action='store_false',
831 dest='prebuilts', default=True,
832 help="Don't upload prebuilts.")
833 group.add_remote_option('--nosync', action='store_false', dest='sync',
834 default=True, help="Don't sync before building.")
Ryan Cuieaa9efd2012-04-25 17:56:45 -0700835 group.add_remote_option('--notests', action='store_false', dest='tests',
836 default=True,
837 help='Override values from buildconfig and run no '
838 'tests.')
839 group.add_remote_option('--nouprev', action='store_false', dest='uprev',
840 default=True,
841 help='Override values from buildconfig and never '
842 'uprev.')
Brian Harring3fec5a82012-03-01 05:57:03 -0800843 group.add_option('--reference-repo', action='store', default=None,
Ryan Cuieaa9efd2012-04-25 17:56:45 -0700844 dest='reference_repo',
845 help='Reuse git data stored in an existing repo '
846 'checkout. This can drastically reduce the network '
847 'time spent setting up the trybot checkout. By '
848 "default, if this option isn't given but cbuildbot "
849 'is invoked from a repo checkout, cbuildbot will '
850 'use the repo root.')
Ryan Cuicedd8a52012-03-22 02:28:35 -0700851 group.add_option('--resume', action='store_true', default=False,
Ryan Cuieaa9efd2012-04-25 17:56:45 -0700852 help='Skip stages already successfully completed.')
853 group.add_remote_option('--timeout', action='store', type='int', default=0,
854 help='Specify the maximum amount of time this job '
855 'can run for, at which point the build will be '
856 'aborted. If set to zero, then there is no '
857 'timeout.')
Ryan Cui39bdbbf2012-02-29 16:15:39 -0800858 group.add_option('--test-tryjob', action='store_true',
859 default=False,
860 help='Submit a tryjob to the test repository. Will not '
861 'show up on the production trybot waterfall.')
Ryan Cuieaa9efd2012-04-25 17:56:45 -0700862 group.add_remote_option('--validation_pool', default=None,
863 help='Path to a pickled validation pool. Intended '
864 'for use only with the commit queue.')
865 group.add_remote_option('--version', dest='force_version', default=None,
866 help='Used with manifest logic. Forces use of this '
867 'version rather than create or get latest.')
Brian Harring3fec5a82012-03-01 05:57:03 -0800868
869 parser.add_option_group(group)
870
Ryan Cuif4f84be2012-07-09 18:50:41 -0700871 #
872 # Hidden options.
873 #
874
875 # The base GS URL (gs://<bucket_name>/<path>) to archive artifacts to.
876 parser.add_remote_option('--archive-base', type='gs_path',
877 help=optparse.SUPPRESS_HELP)
878 # bootstrap-args are not verified by the bootstrap code. It gets passed
879 # direcly to the bootstrap re-execution.
880 parser.add_remote_option('--bootstrap-args', action='append',
881 default=[], help=optparse.SUPPRESS_HELP)
882 parser.add_option('--pass-through', dest='pass_through_args', action='append',
883 type='string', default=[], help=optparse.SUPPRESS_HELP)
884 # Used for handling forwards/backwards compatibility for --resume and
885 # --bootstrap.
886 parser.add_option('--reexec-api-version', dest='output_api_version',
887 action='store_true', default=False,
888 help=optparse.SUPPRESS_HELP)
889 # Indicates this is running on a remote trybot machine.
890 parser.add_option('--remote-trybot', dest='remote_trybot',
891 action='store_true', default=False,
892 help=optparse.SUPPRESS_HELP)
893 # Patches uploaded by trybot client when run using the -p option.
894 parser.add_remote_option('--remote-patches', action='extend', default=[],
895 help=optparse.SUPPRESS_HELP)
896 # Specify specific remote tryslaves to run on.
897 parser.add_option('--slaves', action='extend', default=[],
898 help=optparse.SUPPRESS_HELP)
899 parser.add_option('--sourceroot', type='path', default=constants.SOURCE_ROOT,
900 help=optparse.SUPPRESS_HELP)
901 # Causes cbuildbot to bootstrap itself twice, in the sequence A->B->C.
902 # A(unpatched) patches and bootstraps B. B patches and bootstraps C.
903 parser.add_remote_option('--test-bootstrap', action='store_true',
904 default=False, help=optparse.SUPPRESS_HELP)
905
906 #
Brian Harring3fec5a82012-03-01 05:57:03 -0800907 # Debug options
Ryan Cuif4f84be2012-07-09 18:50:41 -0700908 #
Ryan Cuieaa9efd2012-04-25 17:56:45 -0700909 group = CustomGroup(parser, "Debug Options")
Brian Harring3fec5a82012-03-01 05:57:03 -0800910
Ryan Cuia25d8eb2012-07-11 14:54:27 -0700911 group.add_remote_option('--debug', action='store_true', default=None,
912 help='Override some options to run as a developer.')
Brian Harring3fec5a82012-03-01 05:57:03 -0800913 group.add_option('--dump_config', action='store_true', dest='dump_config',
914 default=False,
915 help='Dump out build config options, and exit.')
916 group.add_option('--notee', action='store_false', dest='tee', default=True,
917 help="Disable logging and internal tee process. Primarily "
918 "used for debugging cbuildbot itself.")
919 parser.add_option_group(group)
920 return parser
921
922
Ryan Cui85867972012-02-23 18:21:49 -0800923def _FinishParsing(options, args):
924 """Perform some parsing tasks that need to take place after optparse.
925
926 This function needs to be easily testable! Keep it free of
927 environment-dependent code. Put more detailed usage validation in
928 _PostParseCheck().
Brian Harring3fec5a82012-03-01 05:57:03 -0800929
930 Args:
Ryan Cui85867972012-02-23 18:21:49 -0800931 options, args: The options/args object returned by optparse
Brian Harring3fec5a82012-03-01 05:57:03 -0800932 """
Brian Harring07039b52012-05-13 17:56:47 -0700933 # Setup logging levels first so any parsing triggered log messages
934 # are appropriately filtered.
935 logging.getLogger().setLevel(
936 logging.DEBUG if options.debug else logging.INFO)
937
Brian Harring3fec5a82012-03-01 05:57:03 -0800938 if options.chrome_root:
939 if options.chrome_rev != constants.CHROME_REV_LOCAL:
Brian Harring1b8c4c82012-05-29 23:03:04 -0700940 cros_build_lib.Die('Chrome rev must be %s if chrome_root is set.' %
941 constants.CHROME_REV_LOCAL)
Brian Harring3fec5a82012-03-01 05:57:03 -0800942 else:
943 if options.chrome_rev == constants.CHROME_REV_LOCAL:
Brian Harring1b8c4c82012-05-29 23:03:04 -0700944 cros_build_lib.Die('Chrome root must be set if chrome_rev is %s.' %
945 constants.CHROME_REV_LOCAL)
Brian Harring3fec5a82012-03-01 05:57:03 -0800946
947 if options.chrome_version:
948 if options.chrome_rev != constants.CHROME_REV_SPEC:
Brian Harring1b8c4c82012-05-29 23:03:04 -0700949 cros_build_lib.Die('Chrome rev must be %s if chrome_version is set.' %
950 constants.CHROME_REV_SPEC)
Brian Harring3fec5a82012-03-01 05:57:03 -0800951 else:
952 if options.chrome_rev == constants.CHROME_REV_SPEC:
Brian Harring1b8c4c82012-05-29 23:03:04 -0700953 cros_build_lib.Die(
954 'Chrome rev must not be %s if chrome_version is not set.'
955 % constants.CHROME_REV_SPEC)
Brian Harring3fec5a82012-03-01 05:57:03 -0800956
Ryan Cuieaa9efd2012-04-25 17:56:45 -0700957 patches = bool(options.gerrit_patches or options.local_patches)
958 if options.remote:
959 if options.local:
Brian Harring1b8c4c82012-05-29 23:03:04 -0700960 cros_build_lib.Die('Cannot specify both --remote and --local')
Ryan Cui54da0702012-04-19 18:38:08 -0700961
Ryan Cuieaa9efd2012-04-25 17:56:45 -0700962 if not options.buildbot and not patches:
Brian Harring1b8c4c82012-05-29 23:03:04 -0700963 cros_build_lib.Die('Must provide patches when running with --remote.')
Brian Harring3fec5a82012-03-01 05:57:03 -0800964
Ryan Cuieaa9efd2012-04-25 17:56:45 -0700965 # --debug needs to be explicitly passed through for remote invocations.
966 release_mode_with_patches = (options.buildbot and patches and
967 '--debug' not in options.pass_through_args)
968 else:
969 if len(args) > 1:
Brian Harring1b8c4c82012-05-29 23:03:04 -0700970 cros_build_lib.Die('Multiple configs not supported if not running with '
971 '--remote.')
Ryan Cuieaa9efd2012-04-25 17:56:45 -0700972
Ryan Cui79319ab2012-05-21 12:59:18 -0700973 if options.slaves:
Brian Harring1b8c4c82012-05-29 23:03:04 -0700974 cros_build_lib.Die('Cannot use --slaves if not running with --remote.')
Ryan Cui79319ab2012-05-21 12:59:18 -0700975
Ryan Cuieaa9efd2012-04-25 17:56:45 -0700976 release_mode_with_patches = (options.buildbot and patches and
977 not options.debug)
978
979 # When running in release mode, make sure we are running with checked-in code.
980 # We want checked-in cbuildbot/scripts to prevent errors, and we want to build
981 # a release image with checked-in code for CrOS packages.
982 if release_mode_with_patches:
Brian Harring1b8c4c82012-05-29 23:03:04 -0700983 cros_build_lib.Die(
984 'Cannot provide patches when running with --buildbot!')
Brian Harring3fec5a82012-03-01 05:57:03 -0800985
Ryan Cuiba41ad32012-03-08 17:15:29 -0800986 if options.buildbot and options.remote_trybot:
Brian Harring1b8c4c82012-05-29 23:03:04 -0700987 cros_build_lib.Die(
988 '--buildbot and --remote-trybot cannot be used together.')
Ryan Cuiba41ad32012-03-08 17:15:29 -0800989
Ryan Cui85867972012-02-23 18:21:49 -0800990 # Record whether --debug was set explicitly vs. it was inferred.
991 options.debug_forced = False
992 if options.debug:
993 options.debug_forced = True
994 else:
Ryan Cui16ca5812012-03-08 20:34:27 -0800995 # We don't set debug by default for
996 # 1. --buildbot invocations.
997 # 2. --remote invocations, because it needs to push changes to the tryjob
998 # repo.
999 options.debug = not options.buildbot and not options.remote
Brian Harring3fec5a82012-03-01 05:57:03 -08001000
Brian Harring3fec5a82012-03-01 05:57:03 -08001001
Brian Harring1d7ba942012-04-24 06:37:18 -07001002# pylint: disable=W0613
Ryan Cui85867972012-02-23 18:21:49 -08001003def _PostParseCheck(options, args):
1004 """Perform some usage validation after we've parsed the arguments
Brian Harring3fec5a82012-03-01 05:57:03 -08001005
Ryan Cui85867972012-02-23 18:21:49 -08001006 Args:
1007 options/args: The options/args object returned by optparse
1008 """
Ryan Cuie1e4e662012-05-21 16:39:46 -07001009 if not options.branch:
Chris Sosa126103a2012-06-18 09:03:17 -07001010 options.branch = cros_build_lib.GetChromiteTrackingBranch()
Ryan Cuie1e4e662012-05-21 16:39:46 -07001011
Ryan Cui5ba7e152012-05-10 14:36:52 -07001012 if options.local_patches and not repository.IsARepoRoot(options.sourceroot):
1013 raise Exception('Could not find repo checkout at %s!'
1014 % options.sourceroot)
1015
Brian Harring609dc4e2012-05-07 02:17:44 -07001016 if options.local_patches:
Brian Harring1d7ba942012-04-24 06:37:18 -07001017 options.local_patches = _CheckLocalPatches(
Brian Harring609dc4e2012-05-07 02:17:44 -07001018 options.sourceroot, options.local_patches)
Brian Harring1d7ba942012-04-24 06:37:18 -07001019
1020 default = os.environ.get('CBUILDBOT_DEFAULT_MODE')
1021 if (default and not any([options.local, options.buildbot,
1022 options.remote, options.remote_trybot])):
Brian Harring1b8c4c82012-05-29 23:03:04 -07001023 cros_build_lib.Info("CBUILDBOT_DEFAULT_MODE=%s env var detected, using it."
1024 % default)
Brian Harring1d7ba942012-04-24 06:37:18 -07001025 default = default.lower()
1026 if default == 'local':
1027 options.local = True
1028 elif default == 'remote':
1029 options.remote = True
1030 elif default == 'buildbot':
1031 options.buildbot = True
1032 else:
Brian Harring1b8c4c82012-05-29 23:03:04 -07001033 cros_build_lib.Die("CBUILDBOT_DEFAULT_MODE value %s isn't supported. "
1034 % default)
Ryan Cui85867972012-02-23 18:21:49 -08001035
1036
1037def _ParseCommandLine(parser, argv):
1038 """Completely parse the commandline arguments"""
Brian Harring3fec5a82012-03-01 05:57:03 -08001039 (options, args) = parser.parse_args(argv)
Brian Harring37e559b2012-05-22 20:47:32 -07001040
1041 if options.output_api_version:
1042 print _REEXEC_API_VERSION
1043 sys.exit(0)
1044
Ryan Cui54da0702012-04-19 18:38:08 -07001045 if options.list:
1046 _PrintValidConfigs(options.print_all)
1047 sys.exit(0)
1048
Ryan Cui8be16062012-04-24 12:05:26 -07001049 # Strip out null arguments.
1050 # TODO(rcui): Remove when buildbot is fixed
1051 args = [arg for arg in args if arg]
1052 if not args:
1053 parser.error('Invalid usage. Use -h to see usage. Use -l to list '
1054 'supported configs.')
1055
Ryan Cui85867972012-02-23 18:21:49 -08001056 _FinishParsing(options, args)
1057 return options, args
1058
1059
1060def main(argv):
1061 # Set umask to 022 so files created by buildbot are readable.
1062 os.umask(022)
1063
Brian Harring1b8c4c82012-05-29 23:03:04 -07001064 if cros_build_lib.IsInsideChroot():
1065 cros_build_lib.Die('Please run cbuildbot from outside the chroot.')
Ryan Cui85867972012-02-23 18:21:49 -08001066
1067 parser = _CreateParser()
1068 (options, args) = _ParseCommandLine(parser, argv)
Brian Harring3fec5a82012-03-01 05:57:03 -08001069
Brian Harring3fec5a82012-03-01 05:57:03 -08001070 _PostParseCheck(options, args)
1071
1072 if options.remote:
Brian Harring1b8c4c82012-05-29 23:03:04 -07001073 cros_build_lib.logger.setLevel(logging.WARNING)
Ryan Cui16ca5812012-03-08 20:34:27 -08001074
Brian Harring3fec5a82012-03-01 05:57:03 -08001075 # Verify configs are valid.
1076 for bot in args:
1077 _GetConfig(bot)
1078
1079 # Verify gerrit patches are valid.
Ryan Cui16ca5812012-03-08 20:34:27 -08001080 print 'Verifying patches...'
Ryan Cuie1e4e662012-05-21 16:39:46 -07001081 patch_pool = AcquirePoolFromOptions(options)
Ryan Cui16d9e1f2012-05-11 10:50:18 -07001082
Ryan Cuieaa9efd2012-04-25 17:56:45 -07001083 # --debug need to be explicitly passed through for remote invocations.
1084 if options.buildbot and '--debug' not in options.pass_through_args:
1085 _ConfirmRemoteBuildbotRun()
1086
Ryan Cui16ca5812012-03-08 20:34:27 -08001087 print 'Submitting tryjob...'
Ryan Cui16d9e1f2012-05-11 10:50:18 -07001088 tryjob = remote_try.RemoteTryJob(options, args, patch_pool.local_patches)
Ryan Cuia25d8eb2012-07-11 14:54:27 -07001089 tryjob.Submit(testjob=options.test_tryjob, dryrun=False)
Ryan Cui16ca5812012-03-08 20:34:27 -08001090 print 'Tryjob submitted!'
1091 print ('Go to %s to view the status of your job.'
Ryan Cui4906e1c2012-04-03 20:09:34 -07001092 % tryjob.GetTrybotWaterfallLink())
Brian Harringe2078b92012-05-24 03:21:38 -07001093 if options.debug:
1094 print
Ryan Cuia25d8eb2012-07-11 14:54:27 -07001095 print "Keep in mind that you actually submitted a tryjob run with "
1096 print "--debug enabled."
Brian Harring3fec5a82012-03-01 05:57:03 -08001097 sys.exit(0)
Ryan Cui54da0702012-04-19 18:38:08 -07001098 elif (not options.buildbot and not options.remote_trybot
1099 and not options.resume and not options.local):
Brian Harring1b8c4c82012-05-29 23:03:04 -07001100 cros_build_lib.Warning(
1101 'Running in LOCAL TRYBOT mode! Use --remote to submit REMOTE '
1102 'tryjobs. Use --local to suppress this message.')
1103 cros_build_lib.Warning(
Ryan Cui51591352012-07-09 15:15:53 -07001104 'In the future, --local will be required to run the local '
Brian Harring1b8c4c82012-05-29 23:03:04 -07001105 'trybot.')
Ryan Cui54da0702012-04-19 18:38:08 -07001106 time.sleep(5)
Brian Harring3fec5a82012-03-01 05:57:03 -08001107
Ryan Cui8be16062012-04-24 12:05:26 -07001108 # Only expecting one config
1109 bot_id = args[-1]
1110 build_config = _GetConfig(bot_id)
Brian Harring3fec5a82012-03-01 05:57:03 -08001111
1112 if options.reference_repo is None:
Ryan Cui5ba7e152012-05-10 14:36:52 -07001113 repo_path = os.path.join(options.sourceroot, '.repo')
Brian Harring3fec5a82012-03-01 05:57:03 -08001114 # If we're being run from a repo checkout, reuse the repo's git pool to
1115 # cut down on sync time.
1116 if os.path.exists(repo_path):
Ryan Cui5ba7e152012-05-10 14:36:52 -07001117 options.reference_repo = options.sourceroot
Brian Harring3fec5a82012-03-01 05:57:03 -08001118 elif options.reference_repo:
1119 if not os.path.exists(options.reference_repo):
1120 parser.error('Reference path %s does not exist'
1121 % (options.reference_repo,))
1122 elif not os.path.exists(os.path.join(options.reference_repo, '.repo')):
1123 parser.error('Reference path %s does not look to be the base of a '
1124 'repo checkout; no .repo exists in the root.'
1125 % (options.reference_repo,))
Ryan Cuid4a24212012-04-04 18:08:12 -07001126
Brian Harringf11bf682012-05-14 15:53:43 -07001127 if (options.buildbot or options.remote_trybot) and not options.resume:
Brian Harring470f6112012-03-02 11:47:10 -08001128 if not options.cgroups:
Ryan Cuid4a24212012-04-04 18:08:12 -07001129 parser.error('Options --buildbot/--remote-trybot and --nocgroups cannot '
1130 'be used together. Cgroup support is required for '
1131 'buildbot/remote-trybot mode.')
Brian Harring470f6112012-03-02 11:47:10 -08001132 if not cgroups.Cgroup.CgroupsSupported():
Ryan Cuid4a24212012-04-04 18:08:12 -07001133 parser.error('Option --buildbot/--remote-trybot was given, but this '
1134 'system does not support cgroups. Failing.')
Brian Harring3fec5a82012-03-01 05:57:03 -08001135
Brian Harring351ce442012-03-09 16:38:14 -08001136 missing = []
1137 for program in _BUILDBOT_REQUIRED_BINARIES:
Brian Harring1b8c4c82012-05-29 23:03:04 -07001138 ret = cros_build_lib.RunCommand(
1139 'which %s' % program, shell=True, redirect_stderr=True,
1140 redirect_stdout=True, error_code_ok=True, print_cmd=False)
Brian Harring351ce442012-03-09 16:38:14 -08001141 if ret.returncode != 0:
1142 missing.append(program)
1143
1144 if missing:
Ryan Cuid4a24212012-04-04 18:08:12 -07001145 parser.error("Option --buildbot/--remote-trybot requires the following "
1146 "binaries which couldn't be found in $PATH: %s"
Brian Harring351ce442012-03-09 16:38:14 -08001147 % (', '.join(missing)))
1148
Brian Harring3fec5a82012-03-01 05:57:03 -08001149 if options.reference_repo:
1150 options.reference_repo = os.path.abspath(options.reference_repo)
1151
1152 if options.dump_config:
1153 # This works, but option ordering is bad...
1154 print 'Configuration %s:' % bot_id
1155 pretty_printer = pprint.PrettyPrinter(indent=2)
1156 pretty_printer.pprint(build_config)
1157 sys.exit(0)
1158
1159 if not options.buildroot:
1160 if options.buildbot:
1161 parser.error('Please specify a buildroot with the --buildroot option.')
Matt Tennantd55b1f42012-04-13 14:15:01 -07001162
Ryan Cui5ba7e152012-05-10 14:36:52 -07001163 options.buildroot = _DetermineDefaultBuildRoot(options.sourceroot,
1164 build_config['internal'])
Brian Harring470f6112012-03-02 11:47:10 -08001165 # We use a marker file in the buildroot to indicate the user has
1166 # consented to using this directory.
1167 if not os.path.exists(repository.GetTrybotMarkerPath(options.buildroot)):
1168 _ConfirmBuildRoot(options.buildroot)
Brian Harring3fec5a82012-03-01 05:57:03 -08001169
1170 # Sanity check of buildroot- specifically that it's not pointing into the
1171 # midst of an existing repo since git-repo doesn't support nesting.
Brian Harring3fec5a82012-03-01 05:57:03 -08001172 if (not repository.IsARepoRoot(options.buildroot) and
David James6b80dc62012-02-29 15:34:40 -08001173 repository.InARepoRepository(options.buildroot)):
Brian Harring3fec5a82012-03-01 05:57:03 -08001174 parser.error('Configured buildroot %s points into a repository checkout, '
1175 'rather than the root of it. This is not supported.'
1176 % options.buildroot)
1177
Brian Harringd166aaf2012-05-14 18:31:53 -07001178 log_file = None
1179 if options.tee:
1180 default_dir = os.path.join(options.buildroot, _DEFAULT_LOG_DIR)
1181 dirname = options.log_dir or default_dir
1182 log_file = os.path.join(dirname, _BUILDBOT_LOG_FILE)
1183
1184 osutils.SafeMakedirs(dirname)
1185 _BackupPreviousLog(log_file)
1186
Brian Harring1b8c4c82012-05-29 23:03:04 -07001187 with cros_build_lib.ContextManagerStack() as stack:
Brian Harringc2d09d92012-05-13 22:03:15 -07001188 critical_section = stack.Add(cleanup.EnforcedCleanupSection)
1189 stack.Add(sudo.SudoKeepAlive)
Brian Harringd166aaf2012-05-14 18:31:53 -07001190
Brian Harringc2d09d92012-05-13 22:03:15 -07001191 if not options.resume:
Brian Harring2bf55e12012-05-13 21:31:55 -07001192 # If we're in resume mode, use our parents tempdir rather than
1193 # nesting another layer.
Brian Harringc2d09d92012-05-13 22:03:15 -07001194 stack.Add(osutils.TempDirContextManager, 'cbuildbot-tmp')
1195 logging.debug("Cbuildbot tempdir is %r.", os.environ.get('TMP'))
Brian Harringd166aaf2012-05-14 18:31:53 -07001196
Brian Harring2d8f9ff2012-06-30 15:58:28 -07001197 options.preserve_paths = set()
Brian Harringd166aaf2012-05-14 18:31:53 -07001198 if log_file is not None:
1199 stack.Add(tee.Tee, log_file)
Brian Harring2d8f9ff2012-06-30 15:58:28 -07001200 options.preserve_paths.add(_DEFAULT_LOG_DIR)
Brian Harringd166aaf2012-05-14 18:31:53 -07001201
Brian Harringc2d09d92012-05-13 22:03:15 -07001202 if options.cgroups:
1203 stack.Add(cgroups.SimpleContainChildren, 'cbuildbot')
Brian Harringa184efa2012-03-04 11:51:25 -08001204
Brian Harringc2d09d92012-05-13 22:03:15 -07001205 # Mark everything between EnforcedCleanupSection and here as having to
1206 # be rolled back via the contextmanager cleanup handlers. This
1207 # ensures that sudo bits cannot outlive cbuildbot, that anything
1208 # cgroups would kill gets killed, etc.
1209 critical_section.ForkWatchdog()
Brian Harringd166aaf2012-05-14 18:31:53 -07001210
Brian Harringc2d09d92012-05-13 22:03:15 -07001211 if options.timeout > 0:
Brian Harring1b8c4c82012-05-29 23:03:04 -07001212 stack.Add(cros_build_lib.Timeout, options.timeout)
Brian Harringa184efa2012-03-04 11:51:25 -08001213
Brian Harringc2d09d92012-05-13 22:03:15 -07001214 if not options.buildbot:
1215 build_config = cbuildbot_config.OverrideConfigForTrybot(
1216 build_config,
1217 options.remote_trybot)
1218
1219 _RunBuildStagesWrapper(options, build_config)