blob: c9c53e7d83a36acf098825426bd0fc2a3ac53a33 [file] [log] [blame]
Kuang-che Wu41e8b592018-09-25 17:01:30 +08001#!/usr/bin/env python2
2# -*- coding: utf-8 -*-
3# Copyright 2018 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"""Helper script to prepare source trees for ChromeOS bisection.
7
8Typical usage:
9
10 Initial setup:
11 $ %(prog)s init --chromeos
12 $ %(prog)s init --chrome
13 $ %(prog)s init --android=pi-arc-dev
14
15 Sync code if necessary:
16 $ %(prog)s sync
17
18 Create source trees for bisection
19 $ %(prog)s new --session=12345
20
21 After bisection finished, delete trees
22 $ %(prog)s delete --session=12345
23"""
24from __future__ import print_function
25import argparse
26import csv
27import logging
28import os
Kuang-che Wu22f207e2019-02-23 12:53:53 +080029import subprocess
Kuang-che Wu67be74b2018-10-15 14:17:26 +080030import time
Kuang-che Wu41e8b592018-09-25 17:01:30 +080031import urllib2
Kuang-che Wu67be74b2018-10-15 14:17:26 +080032import urlparse
Kuang-che Wu41e8b592018-09-25 17:01:30 +080033
34from bisect_kit import common
35from bisect_kit import configure
36from bisect_kit import gclient_util
37from bisect_kit import git_util
Kuang-che Wudc714412018-10-17 16:06:39 +080038from bisect_kit import locking
Kuang-che Wu41e8b592018-09-25 17:01:30 +080039from bisect_kit import repo_util
40from bisect_kit import util
41
42DEFAULT_MIRROR_BASE = os.path.expanduser('~/git-mirrors')
43DEFAULT_WORK_BASE = os.path.expanduser('~/bisect-workdir')
44CHECKOUT_TEMPLATE_NAME = 'template'
45
46logger = logging.getLogger(__name__)
47
48
49class DefaultProjectPathFactory(object):
50 """Factory for chromeos/chrome/android source tree paths."""
51
52 def __init__(self, mirror_base, work_base, session):
53 self.mirror_base = mirror_base
54 self.work_base = work_base
55 self.session = session
56
57 def get_chromeos_mirror(self):
58 return os.path.join(self.mirror_base, 'chromeos')
59
60 def get_chromeos_tree(self):
61 return os.path.join(self.work_base, self.session, 'chromeos')
62
63 def get_android_mirror(self, branch):
64 return os.path.join(self.mirror_base, 'android.%s' % branch)
65
66 def get_android_tree(self, branch):
67 return os.path.join(self.work_base, self.session, 'android.%s' % branch)
68
69 def get_chrome_cache(self):
70 return os.path.join(self.mirror_base, 'chrome')
71
72 def get_chrome_tree(self):
73 return os.path.join(self.work_base, self.session, 'chrome')
74
75
76def subvolume_or_makedirs(opts, path):
77 if os.path.exists(path):
78 return
79
80 path = os.path.abspath(path)
81 if opts.btrfs:
82 dirname, basename = os.path.split(path)
83 if not os.path.exists(dirname):
84 os.makedirs(dirname)
85 util.check_call('btrfs', 'subvolume', 'create', basename, cwd=dirname)
86 else:
87 os.makedirs(path)
88
89
90def is_btrfs_subvolume(path):
91 if util.check_output('stat', '-f', '--format=%T', path).strip() != 'btrfs':
92 return False
93 return util.check_output('stat', '--format=%i', path).strip() == '256'
94
95
96def snapshot_or_copytree(src, dst):
97 assert os.path.isdir(src), '%s does not exist' % src
98 assert os.path.isdir(os.path.dirname(dst))
99
100 # Make sure dst do not exist, otherwise it becomes "dst/name" (one extra
101 # depth) instead of "dst".
102 assert not os.path.exists(dst)
103
104 if is_btrfs_subvolume(src):
105 util.check_call('btrfs', 'subvolume', 'snapshot', src, dst)
106 else:
107 # -a for recursion and preserve all attributes.
108 util.check_call('cp', '-a', src, dst)
109
110
Kuang-che Wubfa64482018-10-16 11:49:49 +0800111def collect_removed_manifest_repos(repo_dir, last_sync_time, only_branch=None):
Kuang-che Wu67be74b2018-10-15 14:17:26 +0800112 manifest_dir = os.path.join(repo_dir, '.repo', 'manifests')
Kuang-che Wu2b1286b2019-05-20 20:37:26 +0800113 git_util.fetch(manifest_dir)
Kuang-che Wu67be74b2018-10-15 14:17:26 +0800114
115 manifest_path = 'default.xml'
116 manifest_full_path = os.path.join(manifest_dir, manifest_path)
117 # hack for chromeos symlink
118 if os.path.islink(manifest_full_path):
119 manifest_path = os.readlink(manifest_full_path)
120
121 parser = repo_util.ManifestParser(manifest_dir)
122 latest = None
123 removed = {}
124 for _, git_rev in reversed(
125 parser.enumerate_manifest_commits(last_sync_time, None, manifest_path)):
126 root = parser.parse_xml_recursive(git_rev, manifest_path)
Kuang-che Wubfa64482018-10-16 11:49:49 +0800127 if (only_branch and root.find('default') is not None and
128 root.find('default').get('revision') != only_branch):
129 break
Kuang-che Wu67be74b2018-10-15 14:17:26 +0800130 entries = parser.process_parsed_result(root)
131 if latest is None:
132 assert entries is not None
133 latest = entries
134 continue
135
136 for path, path_spec in entries.items():
137 if path in latest:
138 continue
139 if path in removed:
140 continue
141 removed[path] = path_spec
142
143 return removed
144
145
Kuang-che Wu41e8b592018-09-25 17:01:30 +0800146def setup_chromeos_repos(opts, path_factory):
147 chromeos_mirror = path_factory.get_chromeos_mirror()
148 chromeos_tree = path_factory.get_chromeos_tree()
149 subvolume_or_makedirs(opts, chromeos_mirror)
150 subvolume_or_makedirs(opts, chromeos_tree)
151
152 manifest_url = (
153 'https://chrome-internal.googlesource.com/chromeos/manifest-internal')
154 repo_url = 'https://chromium.googlesource.com/external/repo.git'
155
156 if os.path.exists(os.path.join(chromeos_mirror, '.repo', 'manifests')):
157 logger.warning(
158 '%s has already been initialized, assume it is setup properly',
159 chromeos_mirror)
160 else:
161 logger.info('repo init for chromeos mirror')
162 repo_util.init(
163 chromeos_mirror,
164 manifest_url=manifest_url,
165 repo_url=repo_url,
166 mirror=True)
167
168 local_manifest_dir = os.path.join(chromeos_mirror, '.repo',
169 'local_manifests')
170 os.mkdir(local_manifest_dir)
171 with open(os.path.join(local_manifest_dir, 'manifest-versions.xml'),
172 'w') as f:
Kuang-che Wuae6824b2019-08-27 22:20:01 +0800173 f.write("""<?xml version="1.0" encoding="UTF-8"?>
Kuang-che Wu41e8b592018-09-25 17:01:30 +0800174 <manifest>
175 <project name="chromeos/manifest-versions" remote="cros-internal" />
176 </manifest>
Kuang-che Wuae6824b2019-08-27 22:20:01 +0800177 """)
Kuang-che Wu41e8b592018-09-25 17:01:30 +0800178
179 logger.info('repo init for chromeos tree')
180 repo_util.init(
181 chromeos_tree,
182 manifest_url=manifest_url,
183 repo_url=repo_url,
184 reference=chromeos_mirror)
185
Kuang-che Wudc714412018-10-17 16:06:39 +0800186 with locking.lock_file(
187 os.path.join(chromeos_mirror, locking.LOCK_FILE_FOR_MIRROR_SYNC)):
188 logger.info('repo sync for chromeos mirror (this takes hours; be patient)')
189 repo_util.sync(chromeos_mirror)
Kuang-che Wu41e8b592018-09-25 17:01:30 +0800190
191 logger.info('repo sync for chromeos tree')
192 repo_util.sync(chromeos_tree)
193
194
Kuang-che Wu67be74b2018-10-15 14:17:26 +0800195def read_last_sync_time(repo_dir):
196 timestamp_path = os.path.join(repo_dir, 'last_sync_time')
197 if os.path.exists(timestamp_path):
198 with open(timestamp_path) as f:
199 return int(f.read())
200 else:
201 # 4 months should be enough for most bisect cases.
202 return int(time.time()) - 86400 * 120
203
204
205def write_sync_time(repo_dir, sync_time):
206 timestamp_path = os.path.join(repo_dir, 'last_sync_time')
207 with open(timestamp_path, 'w') as f:
208 f.write('%d\n' % sync_time)
209
210
211def write_extra_manifest_to_mirror(repo_dir, removed):
212 local_manifest_dir = os.path.join(repo_dir, '.repo', 'local_manifests')
213 if not os.path.exists(local_manifest_dir):
214 os.mkdir(local_manifest_dir)
215 with open(os.path.join(local_manifest_dir, 'deleted-repos.xml'), 'w') as f:
Kuang-che Wuae6824b2019-08-27 22:20:01 +0800216 f.write("""<?xml version="1.0" encoding="UTF-8"?>\n<manifest>\n""")
Kuang-che Wu67be74b2018-10-15 14:17:26 +0800217 remotes = {}
218 for path_spec in removed.values():
219 scheme, netloc, remote_path = urlparse.urlsplit(path_spec.repo_url)[:3]
220 assert remote_path[0] == '/'
221 remote_path = remote_path[1:]
222 if (scheme, netloc) not in remotes:
223 remote_name = 'remote_for_deleted_repo_%s' % (scheme + netloc)
224 remotes[scheme, netloc] = remote_name
Kuang-che Wuae6824b2019-08-27 22:20:01 +0800225 f.write(""" <remote name="%s" fetch="%s" />\n""" %
Kuang-che Wu67be74b2018-10-15 14:17:26 +0800226 (remote_name, '%s://%s' % (scheme, netloc)))
Kuang-che Wubfa64482018-10-16 11:49:49 +0800227 f.write(
Kuang-che Wuae6824b2019-08-27 22:20:01 +0800228 """ <project name="%s" path="%s" remote="%s" revision="%s" />\n""" %
Kuang-che Wubfa64482018-10-16 11:49:49 +0800229 (remote_path, path_spec.path, remotes[scheme, netloc], path_spec.at))
Kuang-che Wuae6824b2019-08-27 22:20:01 +0800230 f.write("""</manifest>\n""")
Kuang-che Wu67be74b2018-10-15 14:17:26 +0800231
232
Kuang-che Wubfa64482018-10-16 11:49:49 +0800233def generate_extra_manifest_for_deleted_repo(repo_dir, only_branch=None):
234 last_sync_time = read_last_sync_time(repo_dir)
235 removed = collect_removed_manifest_repos(
236 repo_dir, last_sync_time, only_branch=only_branch)
237 write_extra_manifest_to_mirror(repo_dir, removed)
238 logger.info('since last sync, %d repo got removed', len(removed))
239
240
Kuang-che Wu41e8b592018-09-25 17:01:30 +0800241def sync_chromeos_code(opts, path_factory):
242 del opts # unused
243
Kuang-che Wu67be74b2018-10-15 14:17:26 +0800244 start_sync_time = int(time.time())
Kuang-che Wu41e8b592018-09-25 17:01:30 +0800245 chromeos_mirror = path_factory.get_chromeos_mirror()
Kuang-che Wu67be74b2018-10-15 14:17:26 +0800246
Kuang-che Wu67be74b2018-10-15 14:17:26 +0800247 logger.info('repo sync for chromeos mirror')
Kuang-che Wubfa64482018-10-16 11:49:49 +0800248 generate_extra_manifest_for_deleted_repo(chromeos_mirror)
Kuang-che Wu41e8b592018-09-25 17:01:30 +0800249 repo_util.sync(chromeos_mirror)
Kuang-che Wu67be74b2018-10-15 14:17:26 +0800250 write_sync_time(chromeos_mirror, start_sync_time)
Kuang-che Wu41e8b592018-09-25 17:01:30 +0800251
252 logger.info('repo sync for chromeos tree')
253 chromeos_tree = path_factory.get_chromeos_tree()
254 repo_util.sync(chromeos_tree)
255
Kuang-che Wu41e8b592018-09-25 17:01:30 +0800256
257def query_chrome_latest_branch():
258 result = None
259 r = urllib2.urlopen('https://omahaproxy.appspot.com/all')
260 for row in csv.DictReader(r):
261 if row['true_branch'].isdigit():
262 result = max(result, int(row['true_branch']))
263 return result
264
265
266def setup_chrome_repos(opts, path_factory):
267 chrome_cache = path_factory.get_chrome_cache()
268 subvolume_or_makedirs(opts, chrome_cache)
269 chrome_tree = path_factory.get_chrome_tree()
270 subvolume_or_makedirs(opts, chrome_tree)
271
272 latest_branch = query_chrome_latest_branch()
273 logger.info('latest chrome branch is %d', latest_branch)
274 assert latest_branch
Kuang-che Wuae6824b2019-08-27 22:20:01 +0800275 spec = """
Kuang-che Wu41e8b592018-09-25 17:01:30 +0800276solutions = [
277 { "name" : "buildspec",
278 "url" : "https://chrome-internal.googlesource.com/a/chrome/tools/buildspec.git",
279 "deps_file" : "branches/%d/DEPS",
280 "custom_deps" : {
281 },
282 "custom_vars": {'checkout_src_internal': True},
283 },
284]
285target_os = ['chromeos']
286cache_dir = %r
Kuang-che Wuae6824b2019-08-27 22:20:01 +0800287""" % (latest_branch, chrome_cache)
Kuang-che Wu41e8b592018-09-25 17:01:30 +0800288
Kuang-che Wudc714412018-10-17 16:06:39 +0800289 with locking.lock_file(
290 os.path.join(chrome_cache, locking.LOCK_FILE_FOR_MIRROR_SYNC)):
291 logger.info('gclient config for chrome')
292 gclient_util.config(chrome_tree, spec=spec)
Kuang-che Wu41e8b592018-09-25 17:01:30 +0800293
Kuang-che Wudc714412018-10-17 16:06:39 +0800294 is_first_sync = not os.listdir(chrome_cache)
295 if is_first_sync:
296 logger.info('gclient sync for chrome (this takes hours; be patient)')
297 else:
298 logger.info('gclient sync for chrome')
299 gclient_util.sync(
300 chrome_tree, with_branch_heads=True, with_tags=True, ignore_locks=True)
Kuang-che Wu41e8b592018-09-25 17:01:30 +0800301
Kuang-che Wudc714412018-10-17 16:06:39 +0800302 # It's possible that some repos are removed from latest branch and thus
303 # their commit history is not fetched in recent gclient sync. So we call
304 # 'git fetch' for all existing git mirrors.
305 # TODO(kcwu): only sync repos not in DEPS files of latest branch
306 logger.info('additional sync for chrome mirror')
307 for git_repo_name in os.listdir(chrome_cache):
308 # another gclient is running or leftover of previous run; skip
309 if git_repo_name.startswith('_cache_tmp'):
310 continue
311 git_repo = os.path.join(chrome_cache, git_repo_name)
Kuang-che Wu08366542019-01-12 12:37:49 +0800312 if not git_util.is_git_bare_dir(git_repo):
Kuang-che Wudc714412018-10-17 16:06:39 +0800313 continue
Kuang-che Wu2b1286b2019-05-20 20:37:26 +0800314 git_util.fetch(git_repo)
Kuang-che Wu41e8b592018-09-25 17:01:30 +0800315
Kuang-che Wu1e49f512018-12-06 15:27:42 +0800316 # Some repos were removed from the DEPS and won't be synced here. They will
317 # be synced during DEPS file processing because the necessary information
318 # requires full DEPS parsing. (crbug.com/902238)
319
Kuang-che Wu41e8b592018-09-25 17:01:30 +0800320
321def sync_chrome_code(opts, path_factory):
322 # The sync step is identical to the initial gclient config step.
323 setup_chrome_repos(opts, path_factory)
324
325
326def setup_android_repos(opts, path_factory, branch):
327 android_mirror = path_factory.get_android_mirror(branch)
328 android_tree = path_factory.get_android_tree(branch)
329 subvolume_or_makedirs(opts, android_mirror)
330 subvolume_or_makedirs(opts, android_tree)
331
332 manifest_url = ('persistent-https://googleplex-android.git.corp.google.com'
333 '/platform/manifest')
334 repo_url = 'https://gerrit.googlesource.com/git-repo'
335
336 if os.path.exists(os.path.join(android_mirror, '.repo', 'manifests')):
337 logger.warning(
338 '%s has already been initialized, assume it is setup properly',
339 android_mirror)
340 else:
341 logger.info('repo init for android mirror branch=%s', branch)
342 repo_util.init(
343 android_mirror,
344 manifest_url=manifest_url,
345 repo_url=repo_url,
346 manifest_branch=branch,
347 mirror=True)
348
349 logger.info('repo init for android tree branch=%s', branch)
350 repo_util.init(
351 android_tree,
352 manifest_url=manifest_url,
353 repo_url=repo_url,
354 manifest_branch=branch,
355 reference=android_mirror)
356
357 logger.info('repo sync for android mirror (this takes hours; be patient)')
358 repo_util.sync(android_mirror, current_branch=True)
359
360 logger.info('repo sync for android tree branch=%s', branch)
361 repo_util.sync(android_tree, current_branch=True)
362
363
364def sync_android_code(opts, path_factory, branch):
365 del opts # unused
Kuang-che Wu67be74b2018-10-15 14:17:26 +0800366 start_sync_time = int(time.time())
Kuang-che Wu41e8b592018-09-25 17:01:30 +0800367 android_mirror = path_factory.get_android_mirror(branch)
368 android_tree = path_factory.get_android_tree(branch)
369
Kuang-che Wudc714412018-10-17 16:06:39 +0800370 with locking.lock_file(
371 os.path.join(android_mirror, locking.LOCK_FILE_FOR_MIRROR_SYNC)):
372 logger.info('repo sync for android mirror branch=%s', branch)
373 # Android usually big jump between milestone releases and add/delete lots of
374 # repos when switch releases. Because it's infeasible to bisect between such
375 # big jump, the deleted repo is useless. In order to save disk, do not sync
376 # repos deleted in other branches.
377 generate_extra_manifest_for_deleted_repo(android_mirror, only_branch=branch)
378 repo_util.sync(android_mirror, current_branch=True)
379 write_sync_time(android_mirror, start_sync_time)
Kuang-che Wu41e8b592018-09-25 17:01:30 +0800380
381 logger.info('repo sync for android tree branch=%s', branch)
382 repo_util.sync(android_tree, current_branch=True)
383
384
385def cmd_init(opts):
386 path_factory = DefaultProjectPathFactory(opts.mirror_base, opts.work_base,
387 CHECKOUT_TEMPLATE_NAME)
388
389 if opts.chromeos:
390 setup_chromeos_repos(opts, path_factory)
391 if opts.chrome:
392 setup_chrome_repos(opts, path_factory)
393 for branch in opts.android:
394 setup_android_repos(opts, path_factory, branch)
395
396
397def enumerate_android_branches_available(base):
398 branches = []
399 for name in os.listdir(base):
400 if name.startswith('android.'):
401 branches.append(name.partition('.')[2])
402 return branches
403
404
Kuang-che Wu22f207e2019-02-23 12:53:53 +0800405def do_sync(opts):
Kuang-che Wu41e8b592018-09-25 17:01:30 +0800406 path_factory = DefaultProjectPathFactory(opts.mirror_base, opts.work_base,
407 CHECKOUT_TEMPLATE_NAME)
408
409 sync_all = False
410 if not opts.chromeos and not opts.chrome and not opts.android:
411 logger.info('sync trees for all')
412 sync_all = True
413
414 if sync_all or opts.chromeos:
415 sync_chromeos_code(opts, path_factory)
416 if sync_all or opts.chrome:
417 sync_chrome_code(opts, path_factory)
418
419 if sync_all:
420 android_branches = enumerate_android_branches_available(opts.mirror_base)
421 else:
422 android_branches = opts.android
423 for branch in android_branches:
424 sync_android_code(opts, path_factory, branch)
425
426
Kuang-che Wu22f207e2019-02-23 12:53:53 +0800427def cmd_sync(opts):
428 try:
429 do_sync(opts)
430 except subprocess.CalledProcessError:
431 # Sync may fail due to network or server issues.
432 logger.exception('do_sync failed, will retry one minute later')
433 time.sleep(60)
434 do_sync(opts)
435
436
Kuang-che Wu41e8b592018-09-25 17:01:30 +0800437def cmd_new(opts):
438 work_dir = os.path.join(opts.work_base, opts.session)
439 if not os.path.exists(work_dir):
440 os.makedirs(work_dir)
441
442 template_factory = DefaultProjectPathFactory(opts.mirror_base, opts.work_base,
443 CHECKOUT_TEMPLATE_NAME)
444 path_factory = DefaultProjectPathFactory(opts.mirror_base, opts.work_base,
445 opts.session)
446
447 prepare_all = False
448 if not opts.chromeos and not opts.chrome and not opts.android:
449 logger.info('prepare trees for all')
450 prepare_all = True
451
452 chromeos_template = template_factory.get_chromeos_tree()
453 if (prepare_all and os.path.exists(chromeos_template)) or opts.chromeos:
454 logger.info('prepare tree for chromeos, %s',
455 path_factory.get_chromeos_tree())
456 snapshot_or_copytree(chromeos_template, path_factory.get_chromeos_tree())
457
458 chrome_template = template_factory.get_chrome_tree()
459 if (prepare_all and os.path.exists(chrome_template)) or opts.chrome:
460 logger.info('prepare tree for chrome, %s', path_factory.get_chrome_tree())
461 snapshot_or_copytree(chrome_template, path_factory.get_chrome_tree())
462
463 if prepare_all:
464 android_branches = enumerate_android_branches_available(opts.mirror_base)
465 else:
466 android_branches = opts.android
467 for branch in android_branches:
468 logger.info('prepare tree for android branch=%s, %s', branch,
469 path_factory.get_android_tree(branch))
470 snapshot_or_copytree(
471 template_factory.get_android_tree(branch),
472 path_factory.get_android_tree(branch))
473
474
475def delete_tree(path):
476 if is_btrfs_subvolume(path):
Kuang-che Wu9d3ccde2019-01-03 17:06:09 +0800477 # btrfs should be mounted with 'user_subvol_rm_allowed' option and thus
478 # normal user permission is enough.
Kuang-che Wu41e8b592018-09-25 17:01:30 +0800479 util.check_call('btrfs', 'subvolume', 'delete', path)
480 else:
Kuang-che Wu9d3ccde2019-01-03 17:06:09 +0800481 util.check_call('sudo', 'rm', '-rf', path)
Kuang-che Wu41e8b592018-09-25 17:01:30 +0800482
483
484def cmd_list(opts):
485 print('%-20s %s' % ('Session', 'Path'))
486 for name in os.listdir(opts.work_base):
487 if name == CHECKOUT_TEMPLATE_NAME:
488 continue
489 path = os.path.join(opts.work_base, name)
490 print('%-20s %s' % (name, path))
491
492
493def cmd_delete(opts):
494 assert opts.session
495 path_factory = DefaultProjectPathFactory(opts.mirror_base, opts.work_base,
496 opts.session)
497
498 chromeos_tree = path_factory.get_chromeos_tree()
499 if os.path.exists(chromeos_tree):
500 if os.path.exists(os.path.join(chromeos_tree, 'chromite')):
501 # ignore error
502 util.call('cros_sdk', '--unmount', cwd=chromeos_tree)
503 delete_tree(chromeos_tree)
504
505 chrome_tree = path_factory.get_chrome_tree()
506 if os.path.exists(chrome_tree):
507 delete_tree(chrome_tree)
508
509 android_branches = enumerate_android_branches_available(opts.mirror_base)
510 for branch in android_branches:
511 android_tree = path_factory.get_android_tree(branch)
512 if os.path.exists(android_tree):
513 delete_tree(android_tree)
514
515 os.rmdir(os.path.join(opts.work_base, opts.session))
516
517
518def create_parser():
519 parser = argparse.ArgumentParser(
520 formatter_class=argparse.RawDescriptionHelpFormatter, description=__doc__)
521 parser.add_argument(
522 '--mirror_base',
523 metavar='MIRROR_BASE',
524 default=configure.get('MIRROR_BASE', DEFAULT_MIRROR_BASE),
525 help='Directory for mirrors (default: %(default)s)')
526 parser.add_argument(
527 '--work_base',
528 metavar='WORK_BASE',
529 default=configure.get('WORK_BASE', DEFAULT_WORK_BASE),
530 help='Directory for bisection working directories (default: %(default)s)')
531 common.add_common_arguments(parser)
532 subparsers = parser.add_subparsers(
533 dest='command', title='commands', metavar='<command>')
534
535 parser_init = subparsers.add_parser(
536 'init', help='Mirror source trees and create template checkout')
537 parser_init.add_argument(
538 '--chrome', action='store_true', help='init chrome mirror and tree')
539 parser_init.add_argument(
540 '--chromeos', action='store_true', help='init chromeos mirror and tree')
541 parser_init.add_argument(
542 '--android',
543 metavar='BRANCH',
544 action='append',
545 default=[],
546 help='init android mirror and tree of BRANCH')
547 parser_init.add_argument(
548 '--btrfs',
549 action='store_true',
550 help='create btrfs subvolume for source tree')
551 parser_init.set_defaults(func=cmd_init)
552
553 parser_sync = subparsers.add_parser(
554 'sync',
555 help='Sync source trees',
556 description='Sync all if no projects are specified '
557 '(--chrome, --chromeos, or --android)')
558 parser_sync.add_argument(
559 '--chrome', action='store_true', help='sync chrome mirror and tree')
560 parser_sync.add_argument(
561 '--chromeos', action='store_true', help='sync chromeos mirror and tree')
562 parser_sync.add_argument(
563 '--android',
564 metavar='BRANCH',
565 action='append',
566 default=[],
567 help='sync android mirror and tree of BRANCH')
568 parser_sync.set_defaults(func=cmd_sync)
569
570 parser_new = subparsers.add_parser(
571 'new',
572 help='Create new source checkout for bisect',
573 description='Create for all if no projects are specified '
574 '(--chrome, --chromeos, or --android)')
575 parser_new.add_argument('--session', required=True)
576 parser_new.add_argument(
577 '--chrome', action='store_true', help='create chrome checkout')
578 parser_new.add_argument(
579 '--chromeos', action='store_true', help='create chromeos checkout')
580 parser_new.add_argument(
581 '--android',
582 metavar='BRANCH',
583 action='append',
584 default=[],
585 help='create android checkout of BRANCH')
586 parser_new.set_defaults(func=cmd_new)
587
588 parser_list = subparsers.add_parser(
589 'list', help='List existing sessions with source checkout')
590 parser_list.set_defaults(func=cmd_list)
591
592 parser_delete = subparsers.add_parser('delete', help='Delete source checkout')
593 parser_delete.add_argument('--session', required=True)
594 parser_delete.set_defaults(func=cmd_delete)
595
596 return parser
597
598
599def main():
600 common.init()
601 parser = create_parser()
602 opts = parser.parse_args()
603 common.config_logging(opts)
604
605 opts.func(opts)
606
607
608if __name__ == '__main__':
609 main()