Upstream version 8.36.161.0
[platform/framework/web/crosswalk.git] / src / third_party / chromite / scripts / cbuildbot.py
1 #!/usr/bin/python
2
3 # Copyright (c) 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
9 Used by Chromium OS buildbot configuration for all Chromium OS builds including
10 full and pre-flight-queue builds.
11 """
12
13 import collections
14 import distutils.version
15 import glob
16 import logging
17 import optparse
18 import os
19 import multiprocessing
20 import pickle
21 import sys
22 import time
23 import traceback
24
25 from chromite.buildbot import cbuildbot_config
26 from chromite.buildbot import cbuildbot_stages as stages
27 from chromite.buildbot import cbuildbot_results as results_lib
28 from chromite.buildbot import cbuildbot_run
29 from chromite.buildbot import constants
30 from chromite.buildbot import manifest_version
31 from chromite.buildbot import remote_try
32 from chromite.buildbot import repository
33 from chromite.buildbot import tee
34 from chromite.buildbot import trybot_patch_pool
35
36 from chromite.lib import cgroups
37 from chromite.lib import cleanup
38 from chromite.lib import commandline
39 from chromite.lib import cros_build_lib
40 from chromite.lib import gclient
41 from chromite.lib import gerrit
42 from chromite.lib import git
43 from chromite.lib import osutils
44 from chromite.lib import patch as cros_patch
45 from chromite.lib import parallel
46 from chromite.lib import sudo
47 from chromite.lib import timeout_util
48
49 import mock
50
51
52 _DEFAULT_LOG_DIR = 'cbuildbot_logs'
53 _BUILDBOT_LOG_FILE = 'cbuildbot.log'
54 _DEFAULT_EXT_BUILDROOT = 'trybot'
55 _DEFAULT_INT_BUILDROOT = 'trybot-internal'
56 _BUILDBOT_REQUIRED_BINARIES = ('pbzip2',)
57 _API_VERSION_ATTR = 'api_version'
58
59
60 def _PrintValidConfigs(display_all=False):
61   """Print a list of valid buildbot configs.
62
63   Args:
64     display_all: Print all configs.  Otherwise, prints only configs with
65                  trybot_list=True.
66   """
67   def _GetSortKey(config_name):
68     config_dict = cbuildbot_config.config[config_name]
69     return (not config_dict['trybot_list'], config_dict['description'],
70             config_name)
71
72   COLUMN_WIDTH = 45
73   print
74   print 'config'.ljust(COLUMN_WIDTH), 'description'
75   print '------'.ljust(COLUMN_WIDTH), '-----------'
76   config_names = cbuildbot_config.config.keys()
77   config_names.sort(key=_GetSortKey)
78   for name in config_names:
79     if display_all or cbuildbot_config.config[name]['trybot_list']:
80       desc = cbuildbot_config.config[name].get('description')
81       desc = desc if desc else ''
82       print name.ljust(COLUMN_WIDTH), desc
83
84   print
85
86
87 def _GetConfig(config_name):
88   """Gets the configuration for the build if it exists, None otherwise."""
89   if cbuildbot_config.config.has_key(config_name):
90     return cbuildbot_config.config[config_name]
91
92
93 def AcquirePoolFromOptions(options):
94   """Generate patch objects from passed in options.
95
96   Args:
97     options: The options object generated by optparse.
98
99   Returns:
100     trybot_patch_pool.TrybotPatchPool object.
101
102   Raises:
103     gerrit.GerritException, cros_patch.PatchException
104   """
105   gerrit_patches = []
106   local_patches = []
107   remote_patches = []
108
109   if options.gerrit_patches:
110     gerrit_patches = gerrit.GetGerritPatchInfo(
111         options.gerrit_patches)
112     for patch in gerrit_patches:
113       if patch.IsAlreadyMerged():
114         cros_build_lib.Warning('Patch %s has already been merged.' % str(patch))
115
116   if options.local_patches:
117     manifest = git.ManifestCheckout.Cached(options.sourceroot)
118     local_patches = cros_patch.PrepareLocalPatches(manifest,
119                                                    options.local_patches)
120
121   if options.remote_patches:
122     remote_patches = cros_patch.PrepareRemotePatches(
123         options.remote_patches)
124
125   return trybot_patch_pool.TrybotPatchPool(gerrit_patches, local_patches,
126                                            remote_patches)
127
128
129 class Builder(object):
130   """Parent class for all builder types.
131
132   This class functions as an abstract parent class for various build types.
133   Its intended use is builder_instance.Run().
134
135   Attributes:
136     _run: The BuilderRun object for this run.
137     archive_stages: Dict of BuildConfig keys to ArchiveStage values.
138     patch_pool: TrybotPatchPool.
139   """
140
141   def __init__(self, builder_run):
142     """Initializes instance variables. Must be called by all subclasses."""
143     self._run = builder_run
144
145     if self._run.config.chromeos_official:
146       os.environ['CHROMEOS_OFFICIAL'] = '1'
147
148     self.archive_stages = {}
149     self.patch_pool = trybot_patch_pool.TrybotPatchPool()
150     self._build_image_lock = multiprocessing.Lock()
151
152   def Initialize(self):
153     """Runs through the initialization steps of an actual build."""
154     if self._run.options.resume:
155       results_lib.LoadCheckpoint(self._run.buildroot)
156
157     self._RunStage(stages.CleanUpStage)
158
159   def _GetStageInstance(self, stage, *args, **kwargs):
160     """Helper function to get a stage instance given the args.
161
162     Useful as almost all stages just take in builder_run.
163     """
164     # Normally the default BuilderRun (self._run) is used, but it can
165     # be overridden with "builder_run" kwargs (e.g. for child configs).
166     builder_run = kwargs.pop('builder_run', self._run)
167     return stage(builder_run, *args, **kwargs)
168
169   def _SetReleaseTag(self):
170     """Sets run.attrs.release_tag from the manifest manager used in sync.
171
172     Must be run after sync stage as syncing enables us to have a release tag,
173     and must be run before any usage of attrs.release_tag.
174
175     TODO(mtennant): Find a bottleneck place in syncing that can set this
176     directly.  Be careful, as there are several kinds of syncing stages, and
177     sync stages have been known to abort with sys.exit calls.
178     """
179     manifest_manager = getattr(self._run.attrs, 'manifest_manager', None)
180     if manifest_manager:
181       self._run.attrs.release_tag = manifest_manager.current_version
182     else:
183       self._run.attrs.release_tag = None
184
185     cros_build_lib.Debug('Saved release_tag value for run: %r',
186                          self._run.attrs.release_tag)
187
188   def _RunStage(self, stage, *args, **kwargs):
189     """Wrapper to run a stage.
190
191     Args:
192       stage: A BuilderStage class.
193       args: args to pass to stage constructor.
194       kwargs: kwargs to pass to stage constructor.
195
196     Returns:
197       Whatever the stage's Run method returns.
198     """
199     stage_instance = self._GetStageInstance(stage, *args, **kwargs)
200     return stage_instance.Run()
201
202   @staticmethod
203   def _RunParallelStages(stage_objs):
204     """Run the specified stages in parallel.
205
206     Args:
207       stage_objs: BuilderStage objects.
208     """
209     steps = [stage.Run for stage in stage_objs]
210     try:
211       parallel.RunParallelSteps(steps)
212
213     except BaseException as ex:
214       # If a stage threw an exception, it might not have correctly reported
215       # results (e.g. because it was killed before it could report the
216       # results.) In this case, attribute the exception to any stages that
217       # didn't report back correctly (if any).
218       for stage in stage_objs:
219         for name in stage.GetStageNames():
220           if not results_lib.Results.StageHasResults(name):
221             results_lib.Results.Record(name, ex, str(ex))
222
223       raise
224
225   def _RunSyncStage(self, sync_instance):
226     """Run given |sync_instance| stage and be sure attrs.release_tag set."""
227     try:
228       sync_instance.Run()
229     finally:
230       self._SetReleaseTag()
231
232   def GetSyncInstance(self):
233     """Returns an instance of a SyncStage that should be run.
234
235     Subclasses must override this method.
236     """
237     raise NotImplementedError()
238
239   def GetCompletionInstance(self):
240     """Returns the MasterSlaveSyncCompletionStage for this build.
241
242     Subclasses may override this method.
243
244     Returns:
245       None
246     """
247     return None
248
249   def RunStages(self):
250     """Subclasses must override this method.  Runs the appropriate code."""
251     raise NotImplementedError()
252
253   def _ReExecuteInBuildroot(self, sync_instance):
254     """Reexecutes self in buildroot and returns True if build succeeds.
255
256     This allows the buildbot code to test itself when changes are patched for
257     buildbot-related code.  This is a no-op if the buildroot == buildroot
258     of the running chromite checkout.
259
260     Args:
261       sync_instance: Instance of the sync stage that was run to sync.
262
263     Returns:
264       True if the Build succeeded.
265     """
266     if not self._run.options.resume:
267       results_lib.WriteCheckpoint(self._run.options.buildroot)
268
269     args = stages.BootstrapStage.FilterArgsForTargetCbuildbot(
270         self._run.options.buildroot, constants.PATH_TO_CBUILDBOT,
271         self._run.options)
272
273     # Specify a buildroot explicitly (just in case, for local trybot).
274     # Suppress any timeout options given from the commandline in the
275     # invoked cbuildbot; our timeout will enforce it instead.
276     args += ['--resume', '--timeout', '0', '--notee', '--nocgroups',
277              '--buildroot', os.path.abspath(self._run.options.buildroot)]
278
279     if hasattr(self._run.attrs, 'manifest_manager'):
280       # TODO(mtennant): Is this the same as self._run.attrs.release_tag?
281       ver = self._run.attrs.manifest_manager.current_version
282       args += ['--version', ver]
283
284     pool = getattr(sync_instance, 'pool', None)
285     if pool:
286       filename = os.path.join(self._run.options.buildroot,
287                               'validation_pool.dump')
288       pool.Save(filename)
289       args += ['--validation_pool', filename]
290
291     # Reset the cache dir so that the child will calculate it automatically.
292     if not self._run.options.cache_dir_specified:
293       commandline.BaseParser.ConfigureCacheDir(None)
294
295     # Re-run the command in the buildroot.
296     # Finally, be generous and give the invoked cbuildbot 30s to shutdown
297     # when something occurs.  It should exit quicker, but the sigterm may
298     # hit while the system is particularly busy.
299     return_obj = cros_build_lib.RunCommand(
300         args, cwd=self._run.options.buildroot, error_code_ok=True,
301         kill_timeout=30)
302     return return_obj.returncode == 0
303
304   def _InitializeTrybotPatchPool(self):
305     """Generate patch pool from patches specified on the command line.
306
307     Do this only if we need to patch changes later on.
308     """
309     changes_stage = stages.PatchChangesStage.StageNamePrefix()
310     check_func = results_lib.Results.PreviouslyCompletedRecord
311     if not check_func(changes_stage) or self._run.options.bootstrap:
312       self.patch_pool = AcquirePoolFromOptions(self._run.options)
313
314   def _GetBootstrapStage(self):
315     """Constructs and returns the BootStrapStage object.
316
317     We return None when there are no chromite patches to test, and
318     --test-bootstrap wasn't passed in.
319     """
320     stage = None
321     chromite_pool = self.patch_pool.Filter(project=constants.CHROMITE_PROJECT)
322     manifest_pool = self.patch_pool.FilterManifest()
323     chromite_branch = git.GetChromiteTrackingBranch()
324     if (chromite_pool or manifest_pool or
325         self._run.options.test_bootstrap or
326         chromite_branch != self._run.options.branch):
327       stage = stages.BootstrapStage(self._run, chromite_pool,
328                                     manifest_pool)
329     return stage
330
331   def Run(self):
332     """Main runner for this builder class.  Runs build and prints summary.
333
334     Returns:
335       Whether the build succeeded.
336     """
337     self._InitializeTrybotPatchPool()
338
339     if self._run.options.bootstrap:
340       bootstrap_stage = self._GetBootstrapStage()
341       if bootstrap_stage:
342         # BootstrapStage blocks on re-execution of cbuildbot.
343         bootstrap_stage.Run()
344         return bootstrap_stage.returncode == 0
345
346     print_report = True
347     exception_thrown = False
348     success = True
349     sync_instance = None
350     try:
351       self.Initialize()
352       sync_instance = self.GetSyncInstance()
353       self._RunSyncStage(sync_instance)
354
355       if self._run.ShouldPatchAfterSync():
356         # Filter out patches to manifest, since PatchChangesStage can't handle
357         # them.  Manifest patches are patched in the BootstrapStage.
358         non_manifest_patches = self.patch_pool.FilterManifest(negate=True)
359         if non_manifest_patches:
360           self._RunStage(stages.PatchChangesStage, non_manifest_patches)
361
362       if self._run.ShouldReexecAfterSync():
363         print_report = False
364         success = self._ReExecuteInBuildroot(sync_instance)
365       else:
366         self.RunStages()
367
368     except Exception as ex:
369       # If the build is marked as successful, but threw exceptions, that's a
370       # problem.
371       exception_thrown = True
372       if results_lib.Results.BuildSucceededSoFar():
373         traceback.print_exc(file=sys.stdout)
374         raise
375
376       if not (print_report and isinstance(ex, results_lib.StepFailure)):
377         raise
378
379     finally:
380       if print_report:
381         results_lib.WriteCheckpoint(self._run.options.buildroot)
382         completion_instance = self.GetCompletionInstance()
383         self._RunStage(stages.ReportStage, sync_instance, completion_instance)
384         success = results_lib.Results.BuildSucceededSoFar()
385         if exception_thrown and success:
386           success = False
387           cros_build_lib.PrintBuildbotStepWarnings()
388           print """\
389 Exception thrown, but all stages marked successful. This is an internal error,
390 because the stage that threw the exception should be marked as failing."""
391
392     return success
393
394
395 BoardConfig = collections.namedtuple('BoardConfig', ['board', 'name'])
396
397
398 class SimpleBuilder(Builder):
399   """Builder that performs basic vetting operations."""
400
401   def GetSyncInstance(self):
402     """Sync to lkgm or TOT as necessary.
403
404     Returns:
405       The instance of the sync stage to run.
406     """
407     if self._run.options.force_version:
408       sync_stage = self._GetStageInstance(stages.ManifestVersionedSyncStage)
409     elif self._run.config.use_lkgm:
410       sync_stage = self._GetStageInstance(stages.LKGMSyncStage)
411     elif self._run.config.use_chrome_lkgm:
412       sync_stage = self._GetStageInstance(stages.ChromeLKGMSyncStage)
413     else:
414       sync_stage = self._GetStageInstance(stages.SyncStage)
415
416     return sync_stage
417
418   def _RunHWTests(self, builder_run, board):
419     """Run hwtest-related stages for the specified board.
420
421     Args:
422       builder_run: BuilderRun object for these background stages.
423       board: Board name.
424     """
425     # Upload HWTest artifacts first.
426     self._RunStage(stages.UploadTestArtifactsStage, board,
427                    builder_run=builder_run)
428
429     # We can not run hw tests without archiving the payloads.
430     stage_list = []
431     config = builder_run.config
432     if builder_run.options.archive:
433       for suite_config in config.hw_tests:
434         if suite_config.async:
435           stage_list.append([stages.ASyncHWTestStage, board, suite_config])
436         elif suite_config.suite == constants.HWTEST_AU_SUITE:
437           stage_list.append([stages.AUTestStage, board, suite_config])
438         elif suite_config.suite == constants.HWTEST_QAV_SUITE:
439           stage_list.append([stages.QATestStage, board, suite_config])
440         else:
441           stage_list.append([stages.HWTestStage, board, suite_config])
442
443     stage_objs = [self._GetStageInstance(*x, builder_run=builder_run)
444                   for x in stage_list]
445     self._RunParallelStages(stage_objs)
446
447   def _RunBackgroundStagesForBoard(self, builder_run, board):
448     """Run background board-specific stages for the specified board.
449
450     Args:
451       builder_run: BuilderRun object for these background stages.
452       board: Board name.
453     """
454     config = builder_run.config
455
456     # TODO(mtennant): This is the last usage of self.archive_stages.  We can
457     # kill it once we migrate its uses to BuilderRun so that none of the
458     # stages below need it as an argument.
459     archive_stage = self.archive_stages[BoardConfig(board, config.name)]
460     if config.pgo_generate:
461       self._RunParallelStages([archive_stage])
462       return
463
464     # signer_results can't complete without push_image.
465     assert not config.signer_results or config.push_image
466
467     # paygen can't complete without signer_results.
468     assert not config.paygen or config.signer_results
469
470     if config.build_packages_in_background:
471       self._RunStage(stages.BuildPackagesStage, board, builder_run=builder_run)
472
473     if builder_run.config.compilecheck or builder_run.options.compilecheck:
474       self._RunStage(stages.UnitTestStage, board,
475                      builder_run=builder_run)
476       return
477
478     # Build the image first before doing anything else.
479     # TODO(davidjames): Remove this lock once http://crbug.com/352994 is fixed.
480     with self._build_image_lock:
481       self._RunStage(stages.BuildImageStage, board, builder_run=builder_run,
482                      pgo_use=config.pgo_use)
483
484     # While this stage list is run in parallel, the order here dictates the
485     # order that things will be shown in the log.  So group things together
486     # that make sense when read in order.  Also keep in mind that, since we
487     # gather output manually, early slow stages will prevent any output from
488     # later stages showing up until it finishes.
489     stage_list = []
490     if builder_run.options.chrome_sdk and config.chrome_sdk:
491       stage_list.append([stages.ChromeSDKStage, board])
492     stage_list += [
493         [stages.RetryStage, 1, stages.VMTestStage, board],
494         [stages.SignerTestStage, board, archive_stage],
495         [stages.SignerResultsStage, board, archive_stage],
496         [stages.PaygenStage, board, archive_stage],
497         [stages.UnitTestStage, board],
498         [stages.UploadPrebuiltsStage, board],
499         [stages.DevInstallerPrebuiltsStage, board],
500         [stages.DebugSymbolsStage, board],
501         [stages.CPEExportStage, board],
502     ]
503
504     stage_objs = [self._GetStageInstance(*x, builder_run=builder_run)
505                   for x in stage_list]
506
507     parallel.RunParallelSteps([
508         lambda: self._RunParallelStages(stage_objs + [archive_stage]),
509         lambda: self._RunHWTests(builder_run, board),
510     ])
511
512   def _RunSetupBoard(self):
513     """Run the SetupBoard stage for all child configs and boards."""
514     for builder_run in self._run.GetUngroupedBuilderRuns():
515       for board in builder_run.config.boards:
516         self._RunStage(stages.SetupBoardStage, board, builder_run=builder_run)
517
518   def _RunChrootBuilderTypeBuild(self):
519     """Runs through stages of a CHROOT_BUILDER_TYPE build."""
520     self._RunStage(stages.UprevStage, boards=[], enter_chroot=False)
521     self._RunStage(stages.InitSDKStage)
522     self._RunStage(stages.SetupBoardStage, constants.CHROOT_BUILDER_BOARD)
523     self._RunStage(stages.SyncChromeStage)
524     self._RunStage(stages.PatchChromeStage)
525     self._RunStage(stages.SDKPackageStage)
526     self._RunStage(stages.SDKTestStage)
527     self._RunStage(stages.UploadPrebuiltsStage, constants.CHROOT_BUILDER_BOARD)
528
529   def _RunRefreshPackagesTypeBuild(self):
530     """Runs through the stages of a REFRESH_PACKAGES_TYPE build."""
531     self._RunStage(stages.InitSDKStage)
532     self._RunSetupBoard()
533     self._RunStage(stages.RefreshPackageStatusStage)
534
535   def _RunMasterPaladinBuild(self):
536     """Runs through the stages of the paladin (commit queue) master build."""
537     self._RunStage(stages.InitSDKStage)
538     self._RunStage(stages.UprevStage)
539     # The CQ (paladin) master will not actually run the SyncChrome stage, but
540     # we want the logic that gets triggered when SyncChrome stage is skipped.
541     self._RunStage(stages.SyncChromeStage)
542     self._RunStage(stages.MasterUploadPrebuiltsStage)
543
544   def _RunPayloadsBuild(self):
545     """Run the PaygenStage once for each board."""
546     def _RunStageWrapper(board):
547       self._RunStage(stages.PaygenStage, board=board,
548                      channels=self._run.options.channels, archive_stage=None)
549
550     with parallel.BackgroundTaskRunner(_RunStageWrapper) as queue:
551       for board in self._run.config.boards:
552         queue.put([board])
553
554   def _RunDefaultTypeBuild(self):
555     """Runs through the stages of a non-special-type build."""
556     self._RunStage(stages.InitSDKStage)
557     self._RunStage(stages.UprevStage)
558     self._RunSetupBoard()
559     self._RunStage(stages.SyncChromeStage)
560     self._RunStage(stages.PatchChromeStage)
561
562     # Prepare stages to run in background.  If child_configs exist then
563     # run each of those here, otherwise use default config.
564     builder_runs = self._run.GetUngroupedBuilderRuns()
565
566     tasks = []
567     for builder_run in builder_runs:
568       # Prepare a local archive directory for each "run".
569       builder_run.GetArchive().SetupArchivePath()
570
571       for board in builder_run.config.boards:
572         archive_stage = self._GetStageInstance(
573             stages.ArchiveStage, board, builder_run=builder_run,
574             chrome_version=self._run.attrs.chrome_version)
575         board_config = BoardConfig(board, builder_run.config.name)
576         self.archive_stages[board_config] = archive_stage
577         tasks.append((builder_run, board))
578
579     # Set up a process pool to run test/archive stages in the background.
580     # This process runs task(board) for each board added to the queue.
581     task_runner = self._RunBackgroundStagesForBoard
582     with parallel.BackgroundTaskRunner(task_runner) as queue:
583       for builder_run, board in tasks:
584         if not builder_run.config.build_packages_in_background:
585           # Run BuildPackages in the foreground, generating or using PGO data
586           # if requested.
587           kwargs = {'builder_run': builder_run}
588           if builder_run.config.pgo_generate:
589             kwargs['pgo_generate'] = True
590           elif builder_run.config.pgo_use:
591             kwargs['pgo_use'] = True
592
593           self._RunStage(stages.BuildPackagesStage, board, **kwargs)
594
595           if builder_run.config.pgo_generate:
596             # Generate the PGO data before allowing any other tasks to run.
597             self._RunStage(stages.BuildImageStage, board, **kwargs)
598             self._RunStage(stages.UploadTestArtifactsStage, board,
599                            builder_run=builder_run, suffix='[pgo_generate]')
600             suite = cbuildbot_config.PGORecordTest()
601             self._RunStage(stages.HWTestStage, board, suite,
602                            builder_run=builder_run)
603
604         # Kick off our background stages.
605         queue.put([builder_run, board])
606
607   def RunStages(self):
608     """Runs through build process."""
609     # TODO(sosa): Split these out into classes.
610     if self._run.config.build_type == constants.PRE_CQ_LAUNCHER_TYPE:
611       self._RunStage(stages.PreCQLauncherStage)
612     elif self._run.config.build_type == constants.CREATE_BRANCH_TYPE:
613       self._RunStage(stages.BranchUtilStage)
614     elif self._run.config.build_type == constants.CHROOT_BUILDER_TYPE:
615       self._RunChrootBuilderTypeBuild()
616     elif self._run.config.build_type == constants.REFRESH_PACKAGES_TYPE:
617       self._RunRefreshPackagesTypeBuild()
618     elif (self._run.config.build_type == constants.PALADIN_TYPE and
619           self._run.config.master):
620       self._RunMasterPaladinBuild()
621     elif self._run.config.build_type == constants.PAYLOADS_TYPE:
622       self._RunPayloadsBuild()
623     else:
624       self._RunDefaultTypeBuild()
625
626
627 class DistributedBuilder(SimpleBuilder):
628   """Build class that has special logic to handle distributed builds.
629
630   These builds sync using git/manifest logic in manifest_versions.  In general
631   they use a non-distributed builder code for the bulk of the work.
632   """
633   def __init__(self, *args, **kwargs):
634     """Initializes a buildbot builder.
635
636     Extra variables:
637       completion_stage_class:  Stage used to complete a build.  Set in the Sync
638         stage.
639     """
640     super(DistributedBuilder, self).__init__(*args, **kwargs)
641     self.completion_stage_class = None
642     self.sync_stage = None
643     self._completion_stage = None
644
645   def GetSyncInstance(self):
646     """Syncs the tree using one of the distributed sync logic paths.
647
648     Returns:
649       The instance of the sync stage to run.
650     """
651     # Determine sync class to use.  CQ overrides PFQ bits so should check it
652     # first.
653     if self._run.config.pre_cq or self._run.options.pre_cq:
654       sync_stage = self._GetStageInstance(stages.PreCQSyncStage,
655                                           self.patch_pool.gerrit_patches)
656       self.completion_stage_class = stages.PreCQCompletionStage
657       self.patch_pool.gerrit_patches = []
658     elif cbuildbot_config.IsCQType(self._run.config.build_type):
659       if self._run.config.do_not_apply_cq_patches:
660         sync_stage = self._GetStageInstance(stages.MasterSlaveSyncStage)
661       else:
662         sync_stage = self._GetStageInstance(stages.CommitQueueSyncStage)
663       self.completion_stage_class = stages.CommitQueueCompletionStage
664     elif cbuildbot_config.IsPFQType(self._run.config.build_type):
665       sync_stage = self._GetStageInstance(stages.MasterSlaveSyncStage)
666       self.completion_stage_class = stages.MasterSlaveSyncCompletionStage
667     else:
668       sync_stage = self._GetStageInstance(stages.ManifestVersionedSyncStage)
669       self.completion_stage_class = stages.ManifestVersionedSyncCompletionStage
670
671     self.sync_stage = sync_stage
672     return self.sync_stage
673
674   def GetCompletionInstance(self):
675     """Returns the completion_stage_class instance that was used for this build.
676
677     Returns:
678       None if the completion_stage instance was not yet created (this
679       occurs during Publish).
680     """
681     return self._completion_stage
682
683   def Publish(self, was_build_successful):
684     """Completes build by publishing any required information."""
685     completion_stage = self._GetStageInstance(self.completion_stage_class,
686                                               self.sync_stage,
687                                               was_build_successful)
688     self._completion_stage = completion_stage
689     completion_successful = False
690     try:
691       completion_stage.Run()
692       completion_successful = True
693     finally:
694       if not completion_successful:
695         was_build_successful = False
696       if self._run.config.push_overlays:
697         self._RunStage(stages.PublishUprevChangesStage, was_build_successful)
698
699   def RunStages(self):
700     """Runs simple builder logic and publishes information to overlays."""
701     was_build_successful = False
702     try:
703       super(DistributedBuilder, self).RunStages()
704       was_build_successful = results_lib.Results.BuildSucceededSoFar()
705     except SystemExit as ex:
706       # If a stage calls sys.exit(0), it's exiting with success, so that means
707       # we should mark ourselves as successful.
708       if ex.code == 0:
709         was_build_successful = True
710       raise
711     finally:
712       self.Publish(was_build_successful)
713
714
715 def _ConfirmBuildRoot(buildroot):
716   """Confirm with user the inferred buildroot, and mark it as confirmed."""
717   cros_build_lib.Warning('Using default directory %s as buildroot', buildroot)
718   if not cros_build_lib.BooleanPrompt(default=False):
719     print('Please specify a different buildroot via the --buildroot option.')
720     sys.exit(0)
721
722   if not os.path.exists(buildroot):
723     os.mkdir(buildroot)
724
725   repository.CreateTrybotMarker(buildroot)
726
727
728 def _ConfirmRemoteBuildbotRun():
729   """Confirm user wants to run with --buildbot --remote."""
730   cros_build_lib.Warning(
731        'You are about to launch a PRODUCTION job!  This is *NOT* a '
732        'trybot run! Are you sure?')
733   if not cros_build_lib.BooleanPrompt(default=False):
734     print('Please specify --pass-through="--debug".')
735     sys.exit(0)
736
737
738 def _DetermineDefaultBuildRoot(sourceroot, internal_build):
739   """Default buildroot to be under the directory that contains current checkout.
740
741   Args:
742     internal_build: Whether the build is an internal build
743     sourceroot: Use specified sourceroot.
744   """
745   if not repository.IsARepoRoot(sourceroot):
746     cros_build_lib.Die(
747         'Could not find root of local checkout at %s.  Please specify '
748         'using the --sourceroot option.' % sourceroot)
749
750   # Place trybot buildroot under the directory containing current checkout.
751   top_level = os.path.dirname(os.path.realpath(sourceroot))
752   if internal_build:
753     buildroot = os.path.join(top_level, _DEFAULT_INT_BUILDROOT)
754   else:
755     buildroot = os.path.join(top_level, _DEFAULT_EXT_BUILDROOT)
756
757   return buildroot
758
759
760 def _BackupPreviousLog(log_file, backup_limit=25):
761   """Rename previous log.
762
763   Args:
764     log_file: The absolute path to the previous log.
765     backup_limit: Maximum number of old logs to keep.
766   """
767   if os.path.exists(log_file):
768     old_logs = sorted(glob.glob(log_file + '.*'),
769                       key=distutils.version.LooseVersion)
770
771     if len(old_logs) >= backup_limit:
772       os.remove(old_logs[0])
773
774     last = 0
775     if old_logs:
776       last = int(old_logs.pop().rpartition('.')[2])
777
778     os.rename(log_file, log_file + '.' + str(last + 1))
779
780
781 def _IsDistributedBuilder(options, chrome_rev, build_config):
782   """Determines whether the builder should be a DistributedBuilder.
783
784   Args:
785     options: options passed on the commandline.
786     chrome_rev: Chrome revision to build.
787     build_config: Builder configuration dictionary.
788
789   Returns:
790     True if the builder should be a distributed_builder
791   """
792   if build_config['pre_cq'] or options.pre_cq:
793     return True
794   elif not options.buildbot:
795     return False
796   elif chrome_rev in (constants.CHROME_REV_TOT,
797                       constants.CHROME_REV_LOCAL,
798                       constants.CHROME_REV_SPEC):
799     # We don't do distributed logic to TOT Chrome PFQ's, nor local
800     # chrome roots (e.g. chrome try bots)
801     # TODO(davidjames): Update any builders that rely on this logic to use
802     # manifest_version=False instead.
803     return False
804   elif build_config['manifest_version']:
805     return True
806
807   return False
808
809
810 def _RunBuildStagesWrapper(options, build_config):
811   """Helper function that wraps RunBuildStages()."""
812   cros_build_lib.Info('cbuildbot was executed with args %s' %
813                       cros_build_lib.CmdToStr(sys.argv))
814
815   chrome_rev = build_config['chrome_rev']
816   if options.chrome_rev:
817     chrome_rev = options.chrome_rev
818   if chrome_rev == constants.CHROME_REV_TOT:
819     # Build the TOT Chrome revision.
820     svn_url = gclient.GetBaseURLs()[0]
821     options.chrome_version = gclient.GetTipOfTrunkSvnRevision(svn_url)
822     options.chrome_rev = constants.CHROME_REV_SPEC
823
824   # If it's likely we'll need to build Chrome, fetch the source.
825   if build_config['sync_chrome'] is None:
826     options.managed_chrome = (chrome_rev != constants.CHROME_REV_LOCAL and
827         (not build_config['usepkg_build_packages'] or chrome_rev or
828          build_config['profile'] or options.rietveld_patches))
829   else:
830     options.managed_chrome = build_config['sync_chrome']
831
832   if options.managed_chrome:
833     # Tell Chrome to fetch the source locally.
834     internal = constants.USE_CHROME_INTERNAL in build_config['useflags']
835     chrome_src = 'chrome-src-internal' if internal else 'chrome-src'
836     options.chrome_root = os.path.join(options.cache_dir, 'distfiles', 'target',
837                                        chrome_src)
838   elif options.rietveld_patches:
839     cros_build_lib.Die('This builder does not support Rietveld patches.')
840
841   # We are done munging options values, so freeze options object now to avoid
842   # further abuse of it.
843   # TODO(mtennant): one by one identify each options value override and see if
844   # it can be handled another way.  Try to push this freeze closer and closer
845   # to the start of the script (e.g. in or after _PostParseCheck).
846   options.Freeze()
847
848   with parallel.Manager() as manager:
849     builder_run = cbuildbot_run.BuilderRun(options, build_config, manager)
850     if _IsDistributedBuilder(options, chrome_rev, build_config):
851       builder_cls = DistributedBuilder
852     else:
853       builder_cls = SimpleBuilder
854     builder = builder_cls(builder_run)
855     if not builder.Run():
856       sys.exit(1)
857
858
859 # Parser related functions
860 def _CheckLocalPatches(sourceroot, local_patches):
861   """Do an early quick check of the passed-in patches.
862
863   If the branch of a project is not specified we append the current branch the
864   project is on.
865
866   TODO(davidjames): The project:branch format isn't unique, so this means that
867   we can't differentiate what directory the user intended to apply patches to.
868   We should references by directory instead.
869
870   Args:
871     sourceroot: The checkout where patches are coming from.
872     local_patches: List of patches to check in project:branch format.
873
874   Returns:
875     A list of patches that have been verified, in project:branch format.
876   """
877   verified_patches = []
878   manifest = git.ManifestCheckout.Cached(sourceroot)
879   for patch in local_patches:
880     project, _, branch = patch.partition(':')
881
882     checkouts = manifest.FindCheckouts(project, only_patchable=True)
883     if not checkouts:
884       cros_build_lib.Die('Project %s does not exist.' % (project,))
885     if len(checkouts) > 1:
886       cros_build_lib.Die(
887           'We do not yet support local patching for projects that are checked '
888           'out to multiple directories. Try uploading your patch to gerrit '
889           'and referencing it via the -g option instead.'
890       )
891
892     ok = False
893     for checkout in checkouts:
894       project_dir = checkout.GetPath(absolute=True)
895
896       # If no branch was specified, we use the project's current branch.
897       if not branch:
898         local_branch = git.GetCurrentBranch(project_dir)
899       else:
900         local_branch = branch
901
902       if local_branch and git.DoesLocalBranchExist(project_dir, local_branch):
903         verified_patches.append('%s:%s' % (project, local_branch))
904         ok = True
905
906     if not ok:
907       if branch:
908         cros_build_lib.Die('Project %s does not have branch %s'
909                             % (project, branch))
910       else:
911         cros_build_lib.Die('Project %s is not on a branch!' % (project,))
912
913   return verified_patches
914
915
916 def _CheckChromeVersionOption(_option, _opt_str, value, parser):
917   """Upgrade other options based on chrome_version being passed."""
918   value = value.strip()
919
920   if parser.values.chrome_rev is None and value:
921     parser.values.chrome_rev = constants.CHROME_REV_SPEC
922
923   parser.values.chrome_version = value
924
925
926 def _CheckChromeRootOption(_option, _opt_str, value, parser):
927   """Validate and convert chrome_root to full-path form."""
928   if parser.values.chrome_rev is None:
929     parser.values.chrome_rev = constants.CHROME_REV_LOCAL
930
931   parser.values.chrome_root = value
932
933
934 def _CheckChromeRevOption(_option, _opt_str, value, parser):
935   """Validate the chrome_rev option."""
936   value = value.strip()
937   if value not in constants.VALID_CHROME_REVISIONS:
938     raise optparse.OptionValueError('Invalid chrome rev specified')
939
940   parser.values.chrome_rev = value
941
942
943 def FindCacheDir(_parser, _options):
944   return None
945
946
947 class CustomGroup(optparse.OptionGroup):
948   """Custom option group which supports arguments passed-through to trybot."""
949   def add_remote_option(self, *args, **kwargs):
950     """For arguments that are passed-through to remote trybot."""
951     return optparse.OptionGroup.add_option(self, *args,
952                                            remote_pass_through=True,
953                                            **kwargs)
954
955
956 class CustomOption(commandline.FilteringOption):
957   """Subclass FilteringOption class to implement pass-through and api."""
958
959   ACTIONS = commandline.FilteringOption.ACTIONS + ('extend',)
960   STORE_ACTIONS = commandline.FilteringOption.STORE_ACTIONS + ('extend',)
961   TYPED_ACTIONS = commandline.FilteringOption.TYPED_ACTIONS + ('extend',)
962   ALWAYS_TYPED_ACTIONS = (commandline.FilteringOption.ALWAYS_TYPED_ACTIONS +
963                           ('extend',))
964
965   def __init__(self, *args, **kwargs):
966     # The remote_pass_through argument specifies whether we should directly
967     # pass the argument (with its value) onto the remote trybot.
968     self.pass_through = kwargs.pop('remote_pass_through', False)
969     self.api_version = int(kwargs.pop('api', '0'))
970     commandline.FilteringOption.__init__(self, *args, **kwargs)
971
972   def take_action(self, action, dest, opt, value, values, parser):
973     if action == 'extend':
974       # If there is extra spaces between each argument, we get '' which later
975       # code barfs on, so skip those.  e.g. We see this with the forms:
976       #  cbuildbot -p 'proj:branch ' ...
977       #  cbuildbot -p ' proj:branch' ...
978       #  cbuildbot -p 'proj:branch  proj2:branch' ...
979       lvalue = value.split()
980       values.ensure_value(dest, []).extend(lvalue)
981
982     commandline.FilteringOption.take_action(
983         self, action, dest, opt, value, values, parser)
984
985
986 class CustomParser(commandline.FilteringParser):
987   """Custom option parser which supports arguments passed-trhough to trybot"""
988
989   DEFAULT_OPTION_CLASS = CustomOption
990
991   def add_remote_option(self, *args, **kwargs):
992     """For arguments that are passed-through to remote trybot."""
993     return self.add_option(*args, remote_pass_through=True, **kwargs)
994
995
996 def _CreateParser():
997   """Generate and return the parser with all the options."""
998   # Parse options
999   usage = "usage: %prog [options] buildbot_config [buildbot_config ...]"
1000   parser = CustomParser(usage=usage, caching=FindCacheDir)
1001
1002   # Main options
1003   parser.add_option('-l', '--list', action='store_true', dest='list',
1004                     default=False,
1005                     help='List the suggested trybot configs to use (see --all)')
1006   parser.add_option('-a', '--all', action='store_true', dest='print_all',
1007                     default=False,
1008                     help='List all of the buildbot configs available w/--list')
1009
1010   parser.add_option('--local', default=False, action='store_true',
1011                     help='Specifies that this tryjob should be run locally. '
1012                          'Implies --debug.')
1013   parser.add_option('--remote', default=False, action='store_true',
1014                     help='Specifies that this tryjob should be run remotely.')
1015
1016   parser.add_remote_option('-b', '--branch',
1017                            help='The manifest branch to test.  The branch to '
1018                                 'check the buildroot out to.')
1019   parser.add_option('-r', '--buildroot', dest='buildroot', type='path',
1020                     help='Root directory where source is checked out to, and '
1021                          'where the build occurs. For external build configs, '
1022                          "defaults to 'trybot' directory at top level of your "
1023                          'repo-managed checkout.')
1024   parser.add_remote_option('--chrome_rev', default=None, type='string',
1025                            action='callback', dest='chrome_rev',
1026                            callback=_CheckChromeRevOption,
1027                            help=('Revision of Chrome to use, of type [%s]'
1028                                  % '|'.join(constants.VALID_CHROME_REVISIONS)))
1029   parser.add_remote_option('--profile', default=None, type='string',
1030                            action='store', dest='profile',
1031                            help='Name of profile to sub-specify board variant.')
1032
1033   #
1034   # Patch selection options.
1035   #
1036
1037   group = CustomGroup(
1038       parser,
1039       'Patch Options')
1040
1041   group.add_remote_option('-g', '--gerrit-patches', action='extend',
1042                           default=[], type='string',
1043                           metavar="'Id1 *int_Id2...IdN'",
1044                           help="Space-separated list of short-form Gerrit "
1045                                "Change-Id's or change numbers to patch. "
1046                                "Please prepend '*' to internal Change-Id's")
1047   group.add_remote_option('-G', '--rietveld-patches', action='extend',
1048                           default=[], type='string',
1049                           metavar="'id1[:subdir1]...idN[:subdirN]'",
1050                           help='Space-separated list of short-form Rietveld '
1051                                'issue numbers to patch. If no subdir is '
1052                                'specified, the src directory is used.')
1053   group.add_option('-p', '--local-patches', action='extend', default=[],
1054                    metavar="'<project1>[:<branch1>]...<projectN>[:<branchN>]'",
1055                    help='Space-separated list of project branches with '
1056                         'patches to apply.  Projects are specified by name. '
1057                         'If no branch is specified the current branch of the '
1058                         'project will be used.')
1059
1060   parser.add_option_group(group)
1061
1062   #
1063   # Remote trybot options.
1064   #
1065
1066   group = CustomGroup(
1067       parser,
1068       'Remote Trybot Options (--remote)')
1069
1070   group.add_remote_option('--hwtest', dest='hwtest', action='store_true',
1071                            default=False,
1072                            help='Run the HWTest stage (tests on real hardware)')
1073   group.add_option('--remote-description', default=None,
1074                    help='Attach an optional description to a --remote run '
1075                         'to make it easier to identify the results when it '
1076                         'finishes')
1077   group.add_option('--slaves', action='extend', default=[],
1078                    help='Specify specific remote tryslaves to run on (e.g. '
1079                         'build149-m2); if the bot is busy, it will be queued')
1080   group.add_remote_option('--channel', dest='channels', action='extend',
1081                           default=[],
1082                           help='Specify a channel for a payloads trybot. Can be'
1083                                'specified multiple times. No valid for '
1084                                'non-payloads configs.')
1085   group.add_option('--test-tryjob', action='store_true',
1086                    default=False,
1087                    help='Submit a tryjob to the test repository.  Will not '
1088                         'show up on the production trybot waterfall.')
1089
1090   parser.add_option_group(group)
1091
1092   #
1093   # Branch creation options.
1094   #
1095
1096   group = CustomGroup(
1097       parser,
1098       'Branch Creation Options (used with branch-util)')
1099
1100   group.add_remote_option('--branch-name',
1101                           help='The branch to create or delete.')
1102   group.add_remote_option('--delete-branch', default=False, action='store_true',
1103                           help='Delete the branch specified in --branch-name.')
1104   group.add_remote_option('--rename-to', type='string',
1105                           help='Rename a branch to the specified name.')
1106   group.add_remote_option('--force-create', default=False, action='store_true',
1107                           help='Overwrites an existing branch.')
1108
1109   parser.add_option_group(group)
1110
1111   #
1112   # Advanced options.
1113   #
1114
1115   group = CustomGroup(
1116       parser,
1117       'Advanced Options',
1118       'Caution: use these options at your own risk.')
1119
1120   group.add_remote_option('--bootstrap-args', action='append', default=[],
1121                           help='Args passed directly to the bootstrap re-exec '
1122                                'to skip verification by the bootstrap code')
1123   group.add_remote_option('--buildbot', dest='buildbot', action='store_true',
1124                           default=False, help='This is running on a buildbot')
1125   group.add_remote_option('--buildnumber', help='build number', type='int',
1126                           default=0)
1127   group.add_option('--chrome_root', default=None, type='path',
1128                    action='callback', callback=_CheckChromeRootOption,
1129                    dest='chrome_root', help='Local checkout of Chrome to use.')
1130   group.add_remote_option('--chrome_version', default=None, type='string',
1131                           action='callback', dest='chrome_version',
1132                           callback=_CheckChromeVersionOption,
1133                           help='Used with SPEC logic to force a particular SVN '
1134                                'revision of chrome rather than the latest.')
1135   group.add_remote_option('--clobber', action='store_true', dest='clobber',
1136                           default=False,
1137                           help='Clears an old checkout before syncing')
1138   group.add_remote_option('--latest-toolchain', action='store_true',
1139                           default=False,
1140                           help='Use the latest toolchain.')
1141   parser.add_option('--log_dir', dest='log_dir', type='path',
1142                     help=('Directory where logs are stored.'))
1143   group.add_remote_option('--maxarchives', dest='max_archive_builds',
1144                           default=3, type='int',
1145                           help="Change the local saved build count limit.")
1146   parser.add_remote_option('--manifest-repo-url',
1147                            help=('Overrides the default manifest repo url.'))
1148   group.add_remote_option('--compilecheck', action='store_true', default=False,
1149                           help='Only verify compilation and unit tests.')
1150   group.add_remote_option('--noarchive', action='store_false', dest='archive',
1151                           default=True, help="Don't run archive stage.")
1152   group.add_remote_option('--nobootstrap', action='store_false',
1153                           dest='bootstrap', default=True,
1154                           help="Don't checkout and run from a standalone "
1155                                "chromite repo.")
1156   group.add_remote_option('--nobuild', action='store_false', dest='build',
1157                           default=True,
1158                           help="Don't actually build (for cbuildbot dev)")
1159   group.add_remote_option('--noclean', action='store_false', dest='clean',
1160                           default=True, help="Don't clean the buildroot")
1161   group.add_remote_option('--nocgroups', action='store_false', dest='cgroups',
1162                           default=True,
1163                           help='Disable cbuildbots usage of cgroups.')
1164   group.add_remote_option('--nochromesdk', action='store_false',
1165                           dest='chrome_sdk', default=True,
1166                           help="Don't run the ChromeSDK stage which builds "
1167                                "Chrome outside of the chroot.")
1168   group.add_remote_option('--noprebuilts', action='store_false',
1169                           dest='prebuilts', default=True,
1170                           help="Don't upload prebuilts.")
1171   group.add_remote_option('--nopatch', action='store_false',
1172                           dest='postsync_patch', default=True,
1173                           help=("Don't run PatchChanges stage.  This does not "
1174                                 "disable patching in of chromite patches "
1175                                 "during BootstrapStage."))
1176   group.add_remote_option('--nopaygen', action='store_false',
1177                           dest='paygen', default=True,
1178                           help="Don't generate payloads.")
1179   group.add_remote_option('--noreexec', action='store_false',
1180                           dest='postsync_reexec', default=True,
1181                           help="Don't reexec into the buildroot after syncing.")
1182   group.add_remote_option('--nosdk', action='store_true',
1183                           default=False,
1184                           help='Re-create the SDK from scratch.')
1185   group.add_remote_option('--nosigner-results', action='store_false',
1186                           dest='signer_results', default=True,
1187                           help="Don't display signing results.")
1188   group.add_remote_option('--nosync', action='store_false', dest='sync',
1189                           default=True, help="Don't sync before building.")
1190   group.add_remote_option('--notests', action='store_false', dest='tests',
1191                           default=True,
1192                           help='Override values from buildconfig and run no '
1193                                'tests.')
1194   group.add_remote_option('--nouprev', action='store_false', dest='uprev',
1195                           default=True,
1196                           help='Override values from buildconfig and never '
1197                                'uprev.')
1198   group.add_option('--reference-repo', action='store', default=None,
1199                    dest='reference_repo',
1200                    help='Reuse git data stored in an existing repo '
1201                         'checkout. This can drastically reduce the network '
1202                         'time spent setting up the trybot checkout.  By '
1203                         "default, if this option isn't given but cbuildbot "
1204                         'is invoked from a repo checkout, cbuildbot will '
1205                         'use the repo root.')
1206   group.add_option('--resume', action='store_true', default=False,
1207                    help='Skip stages already successfully completed.')
1208   group.add_remote_option('--timeout', action='store', type='int', default=0,
1209                           help='Specify the maximum amount of time this job '
1210                                'can run for, at which point the build will be '
1211                                'aborted.  If set to zero, then there is no '
1212                                'timeout.')
1213   group.add_remote_option('--version', dest='force_version', default=None,
1214                           help='Used with manifest logic.  Forces use of this '
1215                                'version rather than create or get latest. '
1216                                'Examples: 4815.0.0-rc1, 4815.1.2')
1217
1218   parser.add_option_group(group)
1219
1220   #
1221   # Internal options.
1222   #
1223
1224   group = CustomGroup(
1225       parser,
1226       'Internal Chromium OS Build Team Options',
1227       'Caution: these are for meant for the Chromium OS build team only')
1228
1229   group.add_remote_option('--archive-base', type='gs_path',
1230                           help='Base GS URL (gs://<bucket_name>/<path>) to '
1231                                'upload archive artifacts to')
1232   group.add_remote_option('--cq-gerrit-query', dest='cq_gerrit_override',
1233                           default=None,
1234                           help=
1235       "If given, this gerrit query will be used to find what patches to test, "
1236       "rather than the normal 'CommitQueue>=1 AND Verified=1 AND CodeReview=2' "
1237       "query it defaults to.  Use with care- note additionally this setting "
1238       "only has an effect if the buildbot target is a cq target, and we're "
1239       "in buildbot mode.")
1240   group.add_option('--pass-through', dest='pass_through_args', action='append',
1241                    type='string', default=[])
1242   group.add_remote_option('--pre-cq', action='store_true', default=False,
1243                           help='Mark CLs as tested by the PreCQ on success.')
1244   group.add_option('--reexec-api-version', dest='output_api_version',
1245                    action='store_true', default=False,
1246                    help='Used for handling forwards/backwards compatibility '
1247                         'with --resume and --bootstrap')
1248   group.add_option('--remote-trybot', dest='remote_trybot',
1249                    action='store_true', default=False,
1250                    help='Indicates this is running on a remote trybot machine')
1251   group.add_remote_option('--remote-patches', action='extend', default=[],
1252                           help='Patches uploaded by the trybot client when run '
1253                                'using the -p option')
1254   # Note the default here needs to be hardcoded to 3; that is the last version
1255   # that lacked this functionality.
1256   group.add_option('--remote-version', default=3, type=int, action='store',
1257                    help='Used for compatibility checks w/tryjobs running in '
1258                         'older chromite instances')
1259   group.add_option('--sourceroot', type='path', default=constants.SOURCE_ROOT)
1260   group.add_remote_option('--test-bootstrap', action='store_true',
1261                           default=False,
1262                           help='Causes cbuildbot to bootstrap itself twice, in '
1263                                'the sequence A->B->C: A(unpatched) patches and '
1264                                'bootstraps B; B patches and bootstraps C')
1265   group.add_remote_option('--validation_pool', default=None,
1266                           help='Path to a pickled validation pool. Intended '
1267                                'for use only with the commit queue.')
1268   group.add_remote_option('--mock-tree-status', dest='mock_tree_status',
1269                           default=None, action='store',
1270                           help='Override the tree status value that would be '
1271                                'returned from the the actual tree. Example '
1272                                'values: open, closed, throttled. When used '
1273                                'in conjunction with --debug, the tree status '
1274                                'will not be ignored as it usually is in a '
1275                                '--debug run.')
1276   group.add_remote_option('--mock-slave-status', dest='mock_slave_status',
1277                           default=None, action='store',
1278                           metavar='MOCK_SLAVE_STATUS_PICKLE_FILE',
1279                           help='Override the result of the _FetchSlaveStatuses '
1280                                'method of MasterSlaveSyncCompletionStage, by '
1281                                'specifying a file with a pickle of the result '
1282                                'to be returned.')
1283
1284   parser.add_option_group(group)
1285
1286   #
1287   # Debug options
1288   #
1289   # Temporary hack; in place till --dry-run replaces --debug.
1290   # pylint: disable=W0212
1291   group = parser.debug_group
1292   debug = [x for x in group.option_list if x._long_opts == ['--debug']][0]
1293   debug.help += "  Currently functions as --dry-run in addition."
1294   debug.pass_through = True
1295   group.add_option('--notee', action='store_false', dest='tee', default=True,
1296                     help="Disable logging and internal tee process.  Primarily "
1297                          "used for debugging cbuildbot itself.")
1298   return parser
1299
1300
1301 def _FinishParsing(options, args):
1302   """Perform some parsing tasks that need to take place after optparse.
1303
1304   This function needs to be easily testable!  Keep it free of
1305   environment-dependent code.  Put more detailed usage validation in
1306   _PostParseCheck().
1307
1308   Args:
1309     options: The options object returned by optparse
1310     args: The args object returned by optparse
1311   """
1312   # Populate options.pass_through_args.
1313   accepted, _ = commandline.FilteringParser.FilterArgs(
1314       options.parsed_args, lambda x: x.opt_inst.pass_through)
1315   options.pass_through_args.extend(accepted)
1316
1317   if options.chrome_root:
1318     if options.chrome_rev != constants.CHROME_REV_LOCAL:
1319       cros_build_lib.Die('Chrome rev must be %s if chrome_root is set.' %
1320                          constants.CHROME_REV_LOCAL)
1321   elif options.chrome_rev == constants.CHROME_REV_LOCAL:
1322     cros_build_lib.Die('Chrome root must be set if chrome_rev is %s.' %
1323                        constants.CHROME_REV_LOCAL)
1324
1325   if options.chrome_version:
1326     if options.chrome_rev != constants.CHROME_REV_SPEC:
1327       cros_build_lib.Die('Chrome rev must be %s if chrome_version is set.' %
1328                          constants.CHROME_REV_SPEC)
1329   elif options.chrome_rev == constants.CHROME_REV_SPEC:
1330     cros_build_lib.Die(
1331         'Chrome rev must not be %s if chrome_version is not set.'
1332         % constants.CHROME_REV_SPEC)
1333
1334   patches = bool(options.gerrit_patches or options.local_patches or
1335                  options.rietveld_patches)
1336   if options.remote:
1337     if options.local:
1338       cros_build_lib.Die('Cannot specify both --remote and --local')
1339
1340     if not options.buildbot and not patches:
1341       prompt = ('No patches were provided; are you sure you want to just '
1342                 'run a remote build of %s?' % (
1343                     options.branch if options.branch else 'ToT'))
1344       if not cros_build_lib.BooleanPrompt(prompt=prompt, default=False):
1345         cros_build_lib.Die('Must provide patches when running with --remote.')
1346
1347     # --debug needs to be explicitly passed through for remote invocations.
1348     release_mode_with_patches = (options.buildbot and patches and
1349                                  '--debug' not in options.pass_through_args)
1350   else:
1351     if len(args) > 1:
1352       cros_build_lib.Die('Multiple configs not supported if not running with '
1353                          '--remote.  Got %r', args)
1354
1355     if options.slaves:
1356       cros_build_lib.Die('Cannot use --slaves if not running with --remote.')
1357
1358     release_mode_with_patches = (options.buildbot and patches and
1359                                  not options.debug)
1360
1361   # When running in release mode, make sure we are running with checked-in code.
1362   # We want checked-in cbuildbot/scripts to prevent errors, and we want to build
1363   # a release image with checked-in code for CrOS packages.
1364   if release_mode_with_patches:
1365     cros_build_lib.Die(
1366         'Cannot provide patches when running with --buildbot!')
1367
1368   if options.buildbot and options.remote_trybot:
1369     cros_build_lib.Die(
1370         '--buildbot and --remote-trybot cannot be used together.')
1371
1372   # Record whether --debug was set explicitly vs. it was inferred.
1373   options.debug_forced = False
1374   if options.debug:
1375     options.debug_forced = True
1376   if not options.debug:
1377     # We don't set debug by default for
1378     # 1. --buildbot invocations.
1379     # 2. --remote invocations, because it needs to push changes to the tryjob
1380     #    repo.
1381     options.debug = not options.buildbot and not options.remote
1382
1383   # Record the configs targeted.
1384   options.build_targets = args[:]
1385
1386   if constants.BRANCH_UTIL_CONFIG in options.build_targets:
1387     if options.remote:
1388       cros_build_lib.Die(
1389           'Running %s as a remote tryjob is not yet supported.',
1390           constants.BRANCH_UTIL_CONFIG)
1391     if len(options.build_targets) > 1:
1392       cros_build_lib.Die(
1393           'Cannot run %s with any other configs.',
1394           constants.BRANCH_UTIL_CONFIG)
1395     if not options.branch_name:
1396       cros_build_lib.Die(
1397           'Must specify --branch-name with the %s config.',
1398           constants.BRANCH_UTIL_CONFIG)
1399     if options.branch and options.branch != options.branch_name:
1400       cros_build_lib.Die(
1401           'If --branch is specified with the %s config, it must'
1402           ' have the same value as --branch-name.',
1403           constants.BRANCH_UTIL_CONFIG)
1404
1405     exclusive_opts = {'--version': options.force_version,
1406                       '--delete-branch': options.delete_branch,
1407                       '--rename-to': options.rename_to,
1408                      }
1409     if 1 != sum(1 for x in exclusive_opts.values() if x):
1410       cros_build_lib.Die('When using the %s config, you must'
1411                          ' specifiy one and only one of the following'
1412                          ' options: %s.', constants.BRANCH_UTIL_CONFIG,
1413                          ', '.join(exclusive_opts.keys()))
1414
1415     # When deleting or renaming a branch, the --branch and --nobootstrap
1416     # options are implied.
1417     if options.delete_branch or options.rename_to:
1418       if not options.branch:
1419         cros_build_lib.Info('Automatically enabling sync to branch %s'
1420                             ' for this %s flow.', options.branch_name,
1421                             constants.BRANCH_UTIL_CONFIG)
1422         options.branch = options.branch_name
1423       if options.bootstrap:
1424         cros_build_lib.Info('Automatically disabling bootstrap step for'
1425                             ' this %s flow.', constants.BRANCH_UTIL_CONFIG)
1426         options.bootstrap = False
1427
1428   elif any([options.delete_branch, options.rename_to, options.branch_name]):
1429     cros_build_lib.Die(
1430         'Cannot specify --delete-branch, --rename-to or --branch-name when not '
1431         'running the %s config', constants.BRANCH_UTIL_CONFIG)
1432
1433
1434 # pylint: disable=W0613
1435 def _PostParseCheck(parser, options, args):
1436   """Perform some usage validation after we've parsed the arguments
1437
1438   Args:
1439     parser: Option parser that was used to parse arguments.
1440     options: The options returned by optparse.
1441     args: The args returned by optparse.
1442   """
1443   if not options.branch:
1444     options.branch = git.GetChromiteTrackingBranch()
1445
1446   if not repository.IsARepoRoot(options.sourceroot):
1447     if options.local_patches:
1448       raise Exception('Could not find repo checkout at %s!'
1449                       % options.sourceroot)
1450
1451   # Because the default cache dir depends on other options, FindCacheDir
1452   # always returns None, and we setup the default here.
1453   if options.cache_dir is None:
1454     # Note, options.sourceroot is set regardless of the path
1455     # actually existing.
1456     if options.buildroot is not None:
1457       options.cache_dir = os.path.join(options.buildroot, '.cache')
1458     elif os.path.exists(options.sourceroot):
1459       options.cache_dir = os.path.join(options.sourceroot, '.cache')
1460     else:
1461       options.cache_dir = parser.FindCacheDir(parser, options)
1462     options.cache_dir = os.path.abspath(options.cache_dir)
1463     parser.ConfigureCacheDir(options.cache_dir)
1464
1465   osutils.SafeMakedirsNonRoot(options.cache_dir)
1466
1467   if options.local_patches:
1468     options.local_patches = _CheckLocalPatches(
1469         options.sourceroot, options.local_patches)
1470
1471   default = os.environ.get('CBUILDBOT_DEFAULT_MODE')
1472   if (default and not any([options.local, options.buildbot,
1473                            options.remote, options.remote_trybot])):
1474     cros_build_lib.Info("CBUILDBOT_DEFAULT_MODE=%s env var detected, using it."
1475                         % default)
1476     default = default.lower()
1477     if default == 'local':
1478       options.local = True
1479     elif default == 'remote':
1480       options.remote = True
1481     elif default == 'buildbot':
1482       options.buildbot = True
1483     else:
1484       cros_build_lib.Die("CBUILDBOT_DEFAULT_MODE value %s isn't supported. "
1485                          % default)
1486
1487   # Ensure that all args are legitimate config targets.
1488   invalid_targets = []
1489   for arg in args:
1490     build_config = _GetConfig(arg)
1491
1492     if not build_config:
1493       invalid_targets.append(arg)
1494       cros_build_lib.Error('No such configuraton target: "%s".', arg)
1495       continue
1496
1497     if options.channels and build_config.build_type != constants.PAYLOADS_TYPE:
1498       cros_build_lib.Die('--channel must only be used with a payload config,'
1499                          ' not target (%s).' % arg)
1500
1501     # The --version option is not compatible with an external target unless the
1502     # --buildbot option is specified.  More correctly, only "paladin versions"
1503     # will work with external targets, and those are only used with --buildbot.
1504     # If --buildbot is specified, then user should know what they are doing and
1505     # only specify a version that will work.  See crbug.com/311648.
1506     if (options.force_version and
1507         not (options.buildbot or build_config.internal)):
1508       cros_build_lib.Die('Cannot specify --version without --buildbot for an'
1509                          ' external target (%s).' % arg)
1510
1511   if invalid_targets:
1512     cros_build_lib.Die('One or more invalid configuration targets specified. '
1513                        'You can check the available configs by running '
1514                        '`cbuildbot --list --all`')
1515
1516
1517 def _ParseCommandLine(parser, argv):
1518   """Completely parse the commandline arguments"""
1519   (options, args) = parser.parse_args(argv)
1520
1521   # Strip out null arguments.
1522   # TODO(rcui): Remove when buildbot is fixed
1523   args = [arg for arg in args if arg]
1524
1525   # A couple options, like --list, trigger a quick exit.
1526   if options.output_api_version:
1527     print constants.REEXEC_API_VERSION
1528     sys.exit(0)
1529
1530   if options.list:
1531     if args:
1532       cros_build_lib.Die('No arguments expected with the --list options.')
1533     _PrintValidConfigs(options.print_all)
1534     sys.exit(0)
1535
1536   if not args:
1537     parser.error('Invalid usage: no configuration targets provided.'
1538                  'Use -h to see usage.  Use -l to list supported configs.')
1539
1540   _FinishParsing(options, args)
1541   return options, args
1542
1543
1544 # TODO(build): This function is too damn long.
1545 def main(argv):
1546   # Turn on strict sudo checks.
1547   cros_build_lib.STRICT_SUDO = True
1548
1549   # Set umask to 022 so files created by buildbot are readable.
1550   os.umask(0o22)
1551
1552   parser = _CreateParser()
1553   (options, args) = _ParseCommandLine(parser, argv)
1554
1555   _PostParseCheck(parser, options, args)
1556
1557   cros_build_lib.AssertOutsideChroot()
1558
1559   if options.remote:
1560     cros_build_lib.logger.setLevel(logging.WARNING)
1561
1562     # Verify configs are valid.
1563     # If hwtest flag is enabled, post a warning that HWTest step may fail if the
1564     # specified board is not a released platform or it is a generic overlay.
1565     for bot in args:
1566       build_config = _GetConfig(bot)
1567       if options.hwtest:
1568         cros_build_lib.Warning(
1569             'If %s is not a released platform or it is a generic overlay, '
1570             'the HWTest step will most likely not run; please ask the lab '
1571             'team for help if this is unexpected.' % build_config['boards'])
1572
1573     # Verify gerrit patches are valid.
1574     print 'Verifying patches...'
1575     patch_pool = AcquirePoolFromOptions(options)
1576
1577     # --debug need to be explicitly passed through for remote invocations.
1578     if options.buildbot and '--debug' not in options.pass_through_args:
1579       _ConfirmRemoteBuildbotRun()
1580
1581     print 'Submitting tryjob...'
1582     tryjob = remote_try.RemoteTryJob(options, args, patch_pool.local_patches)
1583     tryjob.Submit(testjob=options.test_tryjob, dryrun=False)
1584     print 'Tryjob submitted!'
1585     print ('Go to %s to view the status of your job.'
1586            % tryjob.GetTrybotWaterfallLink())
1587     sys.exit(0)
1588
1589   elif (not options.buildbot and not options.remote_trybot
1590         and not options.resume and not options.local):
1591     options.local = True
1592     cros_build_lib.Warning(
1593         'Running in LOCAL TRYBOT mode!  Use --remote to submit REMOTE '
1594         'tryjobs.  Use --local to suppress this message.')
1595     cros_build_lib.Warning(
1596         'In the future, --local will be required to run the local '
1597         'trybot.')
1598     time.sleep(5)
1599
1600   # Only one config arg is allowed in this mode, which was confirmed earlier.
1601   bot_id = args[-1]
1602   build_config = _GetConfig(bot_id)
1603
1604   if options.reference_repo is None:
1605     repo_path = os.path.join(options.sourceroot, '.repo')
1606     # If we're being run from a repo checkout, reuse the repo's git pool to
1607     # cut down on sync time.
1608     if os.path.exists(repo_path):
1609       options.reference_repo = options.sourceroot
1610   elif options.reference_repo:
1611     if not os.path.exists(options.reference_repo):
1612       parser.error('Reference path %s does not exist'
1613                    % (options.reference_repo,))
1614     elif not os.path.exists(os.path.join(options.reference_repo, '.repo')):
1615       parser.error('Reference path %s does not look to be the base of a '
1616                    'repo checkout; no .repo exists in the root.'
1617                    % (options.reference_repo,))
1618
1619   if (options.buildbot or options.remote_trybot) and not options.resume:
1620     if not options.cgroups:
1621       parser.error('Options --buildbot/--remote-trybot and --nocgroups cannot '
1622                    'be used together.  Cgroup support is required for '
1623                    'buildbot/remote-trybot mode.')
1624     if not cgroups.Cgroup.IsSupported():
1625       parser.error('Option --buildbot/--remote-trybot was given, but this '
1626                    'system does not support cgroups.  Failing.')
1627
1628     missing = osutils.FindMissingBinaries(_BUILDBOT_REQUIRED_BINARIES)
1629     if missing:
1630       parser.error("Option --buildbot/--remote-trybot requires the following "
1631                    "binaries which couldn't be found in $PATH: %s"
1632                    % (', '.join(missing)))
1633
1634   if options.reference_repo:
1635     options.reference_repo = os.path.abspath(options.reference_repo)
1636
1637   if not options.buildroot:
1638     if options.buildbot:
1639       parser.error('Please specify a buildroot with the --buildbot option.')
1640
1641     options.buildroot = _DetermineDefaultBuildRoot(options.sourceroot,
1642                                                    build_config['internal'])
1643     # We use a marker file in the buildroot to indicate the user has
1644     # consented to using this directory.
1645     if not os.path.exists(repository.GetTrybotMarkerPath(options.buildroot)):
1646       _ConfirmBuildRoot(options.buildroot)
1647
1648   # Sanity check of buildroot- specifically that it's not pointing into the
1649   # midst of an existing repo since git-repo doesn't support nesting.
1650   if (not repository.IsARepoRoot(options.buildroot) and
1651       git.FindRepoDir(options.buildroot)):
1652     parser.error('Configured buildroot %s points into a repository checkout, '
1653                  'rather than the root of it.  This is not supported.'
1654                  % options.buildroot)
1655
1656   if not options.log_dir:
1657     options.log_dir = os.path.join(options.buildroot, _DEFAULT_LOG_DIR)
1658
1659   log_file = None
1660   if options.tee:
1661     log_file = os.path.join(options.log_dir, _BUILDBOT_LOG_FILE)
1662     osutils.SafeMakedirs(options.log_dir)
1663     _BackupPreviousLog(log_file)
1664
1665   with cros_build_lib.ContextManagerStack() as stack:
1666     # TODO(ferringb): update this once
1667     # https://chromium-review.googlesource.com/25359
1668     # is landed- it's sensitive to the manifest-versions cache path.
1669     options.preserve_paths = set(['manifest-versions', '.cache',
1670                                   'manifest-versions-internal'])
1671     if log_file is not None:
1672       # We don't want the critical section to try to clean up the tee process,
1673       # so we run Tee (forked off) outside of it. This prevents a deadlock
1674       # because the Tee process only exits when its pipe is closed, and the
1675       # critical section accidentally holds on to that file handle.
1676       stack.Add(tee.Tee, log_file)
1677       options.preserve_paths.add(_DEFAULT_LOG_DIR)
1678
1679     critical_section = stack.Add(cleanup.EnforcedCleanupSection)
1680     stack.Add(sudo.SudoKeepAlive)
1681
1682     if not options.resume:
1683       # If we're in resume mode, use our parents tempdir rather than
1684       # nesting another layer.
1685       stack.Add(osutils.TempDir, prefix='cbuildbot-tmp', set_global=True)
1686       logging.debug("Cbuildbot tempdir is %r.", os.environ.get('TMP'))
1687
1688     if options.cgroups:
1689       stack.Add(cgroups.SimpleContainChildren, 'cbuildbot')
1690
1691     # Mark everything between EnforcedCleanupSection and here as having to
1692     # be rolled back via the contextmanager cleanup handlers.  This
1693     # ensures that sudo bits cannot outlive cbuildbot, that anything
1694     # cgroups would kill gets killed, etc.
1695     stack.Add(critical_section.ForkWatchdog)
1696
1697     if options.timeout > 0:
1698       stack.Add(timeout_util.FatalTimeout, options.timeout)
1699
1700     if not options.buildbot:
1701       build_config = cbuildbot_config.OverrideConfigForTrybot(
1702           build_config, options)
1703
1704     if options.mock_tree_status is not None:
1705       stack.Add(mock.patch.object, timeout_util, '_GetStatus',
1706                 return_value=options.mock_tree_status)
1707
1708     if options.mock_slave_status is not None:
1709       with open(options.mock_slave_status, 'r') as f:
1710         mock_statuses = pickle.load(f)
1711         for key, value in mock_statuses.iteritems():
1712           mock_statuses[key] = manifest_version.BuilderStatus(**value)
1713       stack.Add(mock.patch.object, stages.MasterSlaveSyncCompletionStage,
1714                 '_FetchSlaveStatuses', return_value=mock_statuses)
1715
1716     _RunBuildStagesWrapper(options, build_config)