blob: 5d919a7ae6c090b62e3e6f35f98d0a187f1bfa29 [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 Wu7d0c7592019-09-16 09:59:28 +080031import xml.etree.ElementTree
Kuang-che Wu41e8b592018-09-25 17:01:30 +080032
Kuang-che Wud3a4e842019-12-11 12:15:23 +080033import six
Kuang-che Wua7ddf9b2019-11-25 18:59:57 +080034from six.moves import urllib
35
Kuang-che Wu41e8b592018-09-25 17:01:30 +080036from bisect_kit import common
37from bisect_kit import configure
38from bisect_kit import gclient_util
39from bisect_kit import git_util
Kuang-che Wudc714412018-10-17 16:06:39 +080040from bisect_kit import locking
Kuang-che Wu41e8b592018-09-25 17:01:30 +080041from bisect_kit import repo_util
42from bisect_kit import util
43
44DEFAULT_MIRROR_BASE = os.path.expanduser('~/git-mirrors')
45DEFAULT_WORK_BASE = os.path.expanduser('~/bisect-workdir')
46CHECKOUT_TEMPLATE_NAME = 'template'
47
48logger = logging.getLogger(__name__)
49
50
51class DefaultProjectPathFactory(object):
52 """Factory for chromeos/chrome/android source tree paths."""
53
54 def __init__(self, mirror_base, work_base, session):
55 self.mirror_base = mirror_base
56 self.work_base = work_base
57 self.session = session
58
59 def get_chromeos_mirror(self):
60 return os.path.join(self.mirror_base, 'chromeos')
61
62 def get_chromeos_tree(self):
63 return os.path.join(self.work_base, self.session, 'chromeos')
64
65 def get_android_mirror(self, branch):
66 return os.path.join(self.mirror_base, 'android.%s' % branch)
67
68 def get_android_tree(self, branch):
69 return os.path.join(self.work_base, self.session, 'android.%s' % branch)
70
71 def get_chrome_cache(self):
72 return os.path.join(self.mirror_base, 'chrome')
73
74 def get_chrome_tree(self):
75 return os.path.join(self.work_base, self.session, 'chrome')
76
77
78def subvolume_or_makedirs(opts, path):
79 if os.path.exists(path):
80 return
81
82 path = os.path.abspath(path)
83 if opts.btrfs:
84 dirname, basename = os.path.split(path)
85 if not os.path.exists(dirname):
86 os.makedirs(dirname)
87 util.check_call('btrfs', 'subvolume', 'create', basename, cwd=dirname)
88 else:
89 os.makedirs(path)
90
91
92def is_btrfs_subvolume(path):
93 if util.check_output('stat', '-f', '--format=%T', path).strip() != 'btrfs':
94 return False
95 return util.check_output('stat', '--format=%i', path).strip() == '256'
96
97
98def snapshot_or_copytree(src, dst):
99 assert os.path.isdir(src), '%s does not exist' % src
100 assert os.path.isdir(os.path.dirname(dst))
101
102 # Make sure dst do not exist, otherwise it becomes "dst/name" (one extra
103 # depth) instead of "dst".
104 assert not os.path.exists(dst)
105
106 if is_btrfs_subvolume(src):
107 util.check_call('btrfs', 'subvolume', 'snapshot', src, dst)
108 else:
109 # -a for recursion and preserve all attributes.
110 util.check_call('cp', '-a', src, dst)
111
112
Kuang-che Wubfa64482018-10-16 11:49:49 +0800113def collect_removed_manifest_repos(repo_dir, last_sync_time, only_branch=None):
Kuang-che Wu67be74b2018-10-15 14:17:26 +0800114 manifest_dir = os.path.join(repo_dir, '.repo', 'manifests')
Kuang-che Wu67be74b2018-10-15 14:17:26 +0800115 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)):
Kuang-che Wu7d0c7592019-09-16 09:59:28 +0800126 try:
127 root = parser.parse_xml_recursive(git_rev, manifest_path)
128 except xml.etree.ElementTree.ParseError:
129 logger.warning('%s %s@%s syntax error, skip', manifest_dir, manifest_path,
130 git_rev[:12])
131 continue
Kuang-che Wubfa64482018-10-16 11:49:49 +0800132 if (only_branch and root.find('default') is not None and
133 root.find('default').get('revision') != only_branch):
134 break
Kuang-che Wu67be74b2018-10-15 14:17:26 +0800135 entries = parser.process_parsed_result(root)
136 if latest is None:
137 assert entries is not None
138 latest = entries
139 continue
140
141 for path, path_spec in entries.items():
142 if path in latest:
143 continue
144 if path in removed:
145 continue
146 removed[path] = path_spec
147
148 return removed
149
150
Kuang-che Wu41e8b592018-09-25 17:01:30 +0800151def setup_chromeos_repos(opts, path_factory):
152 chromeos_mirror = path_factory.get_chromeos_mirror()
153 chromeos_tree = path_factory.get_chromeos_tree()
154 subvolume_or_makedirs(opts, chromeos_mirror)
155 subvolume_or_makedirs(opts, chromeos_tree)
156
157 manifest_url = (
158 'https://chrome-internal.googlesource.com/chromeos/manifest-internal')
159 repo_url = 'https://chromium.googlesource.com/external/repo.git'
160
161 if os.path.exists(os.path.join(chromeos_mirror, '.repo', 'manifests')):
162 logger.warning(
163 '%s has already been initialized, assume it is setup properly',
164 chromeos_mirror)
165 else:
166 logger.info('repo init for chromeos mirror')
167 repo_util.init(
168 chromeos_mirror,
169 manifest_url=manifest_url,
170 repo_url=repo_url,
171 mirror=True)
172
173 local_manifest_dir = os.path.join(chromeos_mirror, '.repo',
174 'local_manifests')
175 os.mkdir(local_manifest_dir)
176 with open(os.path.join(local_manifest_dir, 'manifest-versions.xml'),
177 'w') as f:
Kuang-che Wuae6824b2019-08-27 22:20:01 +0800178 f.write("""<?xml version="1.0" encoding="UTF-8"?>
Kuang-che Wu41e8b592018-09-25 17:01:30 +0800179 <manifest>
180 <project name="chromeos/manifest-versions" remote="cros-internal" />
181 </manifest>
Kuang-che Wuae6824b2019-08-27 22:20:01 +0800182 """)
Kuang-che Wu41e8b592018-09-25 17:01:30 +0800183
184 logger.info('repo init for chromeos tree')
185 repo_util.init(
186 chromeos_tree,
187 manifest_url=manifest_url,
188 repo_url=repo_url,
189 reference=chromeos_mirror)
190
Kuang-che Wudc714412018-10-17 16:06:39 +0800191 with locking.lock_file(
192 os.path.join(chromeos_mirror, locking.LOCK_FILE_FOR_MIRROR_SYNC)):
193 logger.info('repo sync for chromeos mirror (this takes hours; be patient)')
194 repo_util.sync(chromeos_mirror)
Kuang-che Wu41e8b592018-09-25 17:01:30 +0800195
196 logger.info('repo sync for chromeos tree')
197 repo_util.sync(chromeos_tree)
198
199
Kuang-che Wu67be74b2018-10-15 14:17:26 +0800200def read_last_sync_time(repo_dir):
201 timestamp_path = os.path.join(repo_dir, 'last_sync_time')
202 if os.path.exists(timestamp_path):
203 with open(timestamp_path) as f:
204 return int(f.read())
205 else:
206 # 4 months should be enough for most bisect cases.
207 return int(time.time()) - 86400 * 120
208
209
210def write_sync_time(repo_dir, sync_time):
211 timestamp_path = os.path.join(repo_dir, 'last_sync_time')
212 with open(timestamp_path, 'w') as f:
213 f.write('%d\n' % sync_time)
214
215
216def write_extra_manifest_to_mirror(repo_dir, removed):
217 local_manifest_dir = os.path.join(repo_dir, '.repo', 'local_manifests')
218 if not os.path.exists(local_manifest_dir):
219 os.mkdir(local_manifest_dir)
220 with open(os.path.join(local_manifest_dir, 'deleted-repos.xml'), 'w') as f:
Kuang-che Wuae6824b2019-08-27 22:20:01 +0800221 f.write("""<?xml version="1.0" encoding="UTF-8"?>\n<manifest>\n""")
Kuang-che Wu67be74b2018-10-15 14:17:26 +0800222 remotes = {}
223 for path_spec in removed.values():
Kuang-che Wua7ddf9b2019-11-25 18:59:57 +0800224 scheme, netloc, remote_path = urllib.parse.urlsplit(
225 path_spec.repo_url)[:3]
Kuang-che Wu67be74b2018-10-15 14:17:26 +0800226 assert remote_path[0] == '/'
227 remote_path = remote_path[1:]
228 if (scheme, netloc) not in remotes:
229 remote_name = 'remote_for_deleted_repo_%s' % (scheme + netloc)
230 remotes[scheme, netloc] = remote_name
Kuang-che Wuae6824b2019-08-27 22:20:01 +0800231 f.write(""" <remote name="%s" fetch="%s" />\n""" %
Kuang-che Wu67be74b2018-10-15 14:17:26 +0800232 (remote_name, '%s://%s' % (scheme, netloc)))
Kuang-che Wubfa64482018-10-16 11:49:49 +0800233 f.write(
Kuang-che Wuae6824b2019-08-27 22:20:01 +0800234 """ <project name="%s" path="%s" remote="%s" revision="%s" />\n""" %
Kuang-che Wubfa64482018-10-16 11:49:49 +0800235 (remote_path, path_spec.path, remotes[scheme, netloc], path_spec.at))
Kuang-che Wuae6824b2019-08-27 22:20:01 +0800236 f.write("""</manifest>\n""")
Kuang-che Wu67be74b2018-10-15 14:17:26 +0800237
238
Kuang-che Wubfa64482018-10-16 11:49:49 +0800239def generate_extra_manifest_for_deleted_repo(repo_dir, only_branch=None):
240 last_sync_time = read_last_sync_time(repo_dir)
241 removed = collect_removed_manifest_repos(
242 repo_dir, last_sync_time, only_branch=only_branch)
243 write_extra_manifest_to_mirror(repo_dir, removed)
244 logger.info('since last sync, %d repo got removed', len(removed))
Kuang-che Wub76b7f62019-09-16 10:06:18 +0800245 return len(removed)
Kuang-che Wubfa64482018-10-16 11:49:49 +0800246
247
Kuang-che Wu41e8b592018-09-25 17:01:30 +0800248def sync_chromeos_code(opts, path_factory):
249 del opts # unused
250
Kuang-che Wu67be74b2018-10-15 14:17:26 +0800251 start_sync_time = int(time.time())
Kuang-che Wu41e8b592018-09-25 17:01:30 +0800252 chromeos_mirror = path_factory.get_chromeos_mirror()
Kuang-che Wu67be74b2018-10-15 14:17:26 +0800253
Kuang-che Wu67be74b2018-10-15 14:17:26 +0800254 logger.info('repo sync for chromeos mirror')
Kuang-che Wu41e8b592018-09-25 17:01:30 +0800255 repo_util.sync(chromeos_mirror)
Kuang-che Wub76b7f62019-09-16 10:06:18 +0800256 # If there are repos deleted after last sync, generate custom manifest and
257 # sync again for those repos. So we can mirror commits just before the repo
258 # deletion.
259 if generate_extra_manifest_for_deleted_repo(chromeos_mirror) != 0:
260 logger.info('repo sync again')
261 repo_util.sync(chromeos_mirror)
Kuang-che Wu67be74b2018-10-15 14:17:26 +0800262 write_sync_time(chromeos_mirror, start_sync_time)
Kuang-che Wu41e8b592018-09-25 17:01:30 +0800263
264 logger.info('repo sync for chromeos tree')
265 chromeos_tree = path_factory.get_chromeos_tree()
266 repo_util.sync(chromeos_tree)
267
Kuang-che Wu41e8b592018-09-25 17:01:30 +0800268
269def query_chrome_latest_branch():
Kuang-che Wud3a4e842019-12-11 12:15:23 +0800270 result = 0
Kuang-che Wua7ddf9b2019-11-25 18:59:57 +0800271 r = urllib.request.urlopen('https://omahaproxy.appspot.com/all')
Kuang-che Wud3a4e842019-12-11 12:15:23 +0800272 # TODO(kcwu): use io.TextIOWrapper after migrated to python3
273 content = r.read().decode('utf8')
274 for row in csv.DictReader(six.StringIO(content)):
Kuang-che Wu41e8b592018-09-25 17:01:30 +0800275 if row['true_branch'].isdigit():
276 result = max(result, int(row['true_branch']))
277 return result
278
279
280def setup_chrome_repos(opts, path_factory):
281 chrome_cache = path_factory.get_chrome_cache()
282 subvolume_or_makedirs(opts, chrome_cache)
283 chrome_tree = path_factory.get_chrome_tree()
284 subvolume_or_makedirs(opts, chrome_tree)
285
286 latest_branch = query_chrome_latest_branch()
287 logger.info('latest chrome branch is %d', latest_branch)
288 assert latest_branch
Kuang-che Wuae6824b2019-08-27 22:20:01 +0800289 spec = """
Kuang-che Wu41e8b592018-09-25 17:01:30 +0800290solutions = [
291 { "name" : "buildspec",
292 "url" : "https://chrome-internal.googlesource.com/a/chrome/tools/buildspec.git",
293 "deps_file" : "branches/%d/DEPS",
294 "custom_deps" : {
295 },
296 "custom_vars": {'checkout_src_internal': True},
297 },
298]
299target_os = ['chromeos']
300cache_dir = %r
Kuang-che Wuae6824b2019-08-27 22:20:01 +0800301""" % (latest_branch, chrome_cache)
Kuang-che Wu41e8b592018-09-25 17:01:30 +0800302
Kuang-che Wudc714412018-10-17 16:06:39 +0800303 with locking.lock_file(
304 os.path.join(chrome_cache, locking.LOCK_FILE_FOR_MIRROR_SYNC)):
305 logger.info('gclient config for chrome')
306 gclient_util.config(chrome_tree, spec=spec)
Kuang-che Wu41e8b592018-09-25 17:01:30 +0800307
Kuang-che Wudc714412018-10-17 16:06:39 +0800308 is_first_sync = not os.listdir(chrome_cache)
309 if is_first_sync:
310 logger.info('gclient sync for chrome (this takes hours; be patient)')
311 else:
312 logger.info('gclient sync for chrome')
313 gclient_util.sync(
314 chrome_tree, with_branch_heads=True, with_tags=True, ignore_locks=True)
Kuang-che Wu41e8b592018-09-25 17:01:30 +0800315
Kuang-che Wudc714412018-10-17 16:06:39 +0800316 # It's possible that some repos are removed from latest branch and thus
317 # their commit history is not fetched in recent gclient sync. So we call
318 # 'git fetch' for all existing git mirrors.
319 # TODO(kcwu): only sync repos not in DEPS files of latest branch
320 logger.info('additional sync for chrome mirror')
321 for git_repo_name in os.listdir(chrome_cache):
322 # another gclient is running or leftover of previous run; skip
323 if git_repo_name.startswith('_cache_tmp'):
324 continue
325 git_repo = os.path.join(chrome_cache, git_repo_name)
Kuang-che Wu08366542019-01-12 12:37:49 +0800326 if not git_util.is_git_bare_dir(git_repo):
Kuang-che Wudc714412018-10-17 16:06:39 +0800327 continue
Kuang-che Wu2b1286b2019-05-20 20:37:26 +0800328 git_util.fetch(git_repo)
Kuang-che Wu41e8b592018-09-25 17:01:30 +0800329
Kuang-che Wu1e49f512018-12-06 15:27:42 +0800330 # Some repos were removed from the DEPS and won't be synced here. They will
331 # be synced during DEPS file processing because the necessary information
332 # requires full DEPS parsing. (crbug.com/902238)
333
Kuang-che Wu41e8b592018-09-25 17:01:30 +0800334
335def sync_chrome_code(opts, path_factory):
336 # The sync step is identical to the initial gclient config step.
337 setup_chrome_repos(opts, path_factory)
338
339
340def setup_android_repos(opts, path_factory, branch):
341 android_mirror = path_factory.get_android_mirror(branch)
342 android_tree = path_factory.get_android_tree(branch)
343 subvolume_or_makedirs(opts, android_mirror)
344 subvolume_or_makedirs(opts, android_tree)
345
346 manifest_url = ('persistent-https://googleplex-android.git.corp.google.com'
347 '/platform/manifest')
348 repo_url = 'https://gerrit.googlesource.com/git-repo'
349
350 if os.path.exists(os.path.join(android_mirror, '.repo', 'manifests')):
351 logger.warning(
352 '%s has already been initialized, assume it is setup properly',
353 android_mirror)
354 else:
355 logger.info('repo init for android mirror branch=%s', branch)
356 repo_util.init(
357 android_mirror,
358 manifest_url=manifest_url,
359 repo_url=repo_url,
360 manifest_branch=branch,
361 mirror=True)
362
363 logger.info('repo init for android tree branch=%s', branch)
364 repo_util.init(
365 android_tree,
366 manifest_url=manifest_url,
367 repo_url=repo_url,
368 manifest_branch=branch,
369 reference=android_mirror)
370
371 logger.info('repo sync for android mirror (this takes hours; be patient)')
372 repo_util.sync(android_mirror, current_branch=True)
373
374 logger.info('repo sync for android tree branch=%s', branch)
375 repo_util.sync(android_tree, current_branch=True)
376
377
378def sync_android_code(opts, path_factory, branch):
379 del opts # unused
Kuang-che Wu67be74b2018-10-15 14:17:26 +0800380 start_sync_time = int(time.time())
Kuang-che Wu41e8b592018-09-25 17:01:30 +0800381 android_mirror = path_factory.get_android_mirror(branch)
382 android_tree = path_factory.get_android_tree(branch)
383
Kuang-che Wudc714412018-10-17 16:06:39 +0800384 with locking.lock_file(
385 os.path.join(android_mirror, locking.LOCK_FILE_FOR_MIRROR_SYNC)):
386 logger.info('repo sync for android mirror branch=%s', branch)
Kuang-che Wub76b7f62019-09-16 10:06:18 +0800387 repo_util.sync(android_mirror, current_branch=True)
Kuang-che Wudc714412018-10-17 16:06:39 +0800388 # Android usually big jump between milestone releases and add/delete lots of
389 # repos when switch releases. Because it's infeasible to bisect between such
390 # big jump, the deleted repo is useless. In order to save disk, do not sync
391 # repos deleted in other branches.
Kuang-che Wub76b7f62019-09-16 10:06:18 +0800392 if generate_extra_manifest_for_deleted_repo(
393 android_mirror, only_branch=branch) != 0:
394 logger.info('repo sync again')
395 repo_util.sync(android_mirror, current_branch=True)
Kuang-che Wudc714412018-10-17 16:06:39 +0800396 write_sync_time(android_mirror, start_sync_time)
Kuang-che Wu41e8b592018-09-25 17:01:30 +0800397
398 logger.info('repo sync for android tree branch=%s', branch)
399 repo_util.sync(android_tree, current_branch=True)
400
401
402def cmd_init(opts):
403 path_factory = DefaultProjectPathFactory(opts.mirror_base, opts.work_base,
404 CHECKOUT_TEMPLATE_NAME)
405
406 if opts.chromeos:
407 setup_chromeos_repos(opts, path_factory)
408 if opts.chrome:
409 setup_chrome_repos(opts, path_factory)
410 for branch in opts.android:
411 setup_android_repos(opts, path_factory, branch)
412
413
414def enumerate_android_branches_available(base):
415 branches = []
416 for name in os.listdir(base):
417 if name.startswith('android.'):
418 branches.append(name.partition('.')[2])
419 return branches
420
421
Kuang-che Wu22f207e2019-02-23 12:53:53 +0800422def do_sync(opts):
Kuang-che Wu41e8b592018-09-25 17:01:30 +0800423 path_factory = DefaultProjectPathFactory(opts.mirror_base, opts.work_base,
424 CHECKOUT_TEMPLATE_NAME)
425
426 sync_all = False
427 if not opts.chromeos and not opts.chrome and not opts.android:
428 logger.info('sync trees for all')
429 sync_all = True
430
431 if sync_all or opts.chromeos:
432 sync_chromeos_code(opts, path_factory)
433 if sync_all or opts.chrome:
434 sync_chrome_code(opts, path_factory)
435
436 if sync_all:
437 android_branches = enumerate_android_branches_available(opts.mirror_base)
438 else:
439 android_branches = opts.android
440 for branch in android_branches:
441 sync_android_code(opts, path_factory, branch)
442
443
Kuang-che Wu22f207e2019-02-23 12:53:53 +0800444def cmd_sync(opts):
445 try:
446 do_sync(opts)
447 except subprocess.CalledProcessError:
448 # Sync may fail due to network or server issues.
449 logger.exception('do_sync failed, will retry one minute later')
450 time.sleep(60)
451 do_sync(opts)
452
453
Kuang-che Wu41e8b592018-09-25 17:01:30 +0800454def cmd_new(opts):
455 work_dir = os.path.join(opts.work_base, opts.session)
456 if not os.path.exists(work_dir):
457 os.makedirs(work_dir)
458
459 template_factory = DefaultProjectPathFactory(opts.mirror_base, opts.work_base,
460 CHECKOUT_TEMPLATE_NAME)
461 path_factory = DefaultProjectPathFactory(opts.mirror_base, opts.work_base,
462 opts.session)
463
464 prepare_all = False
465 if not opts.chromeos and not opts.chrome and not opts.android:
466 logger.info('prepare trees for all')
467 prepare_all = True
468
469 chromeos_template = template_factory.get_chromeos_tree()
470 if (prepare_all and os.path.exists(chromeos_template)) or opts.chromeos:
471 logger.info('prepare tree for chromeos, %s',
472 path_factory.get_chromeos_tree())
473 snapshot_or_copytree(chromeos_template, path_factory.get_chromeos_tree())
474
475 chrome_template = template_factory.get_chrome_tree()
476 if (prepare_all and os.path.exists(chrome_template)) or opts.chrome:
477 logger.info('prepare tree for chrome, %s', path_factory.get_chrome_tree())
478 snapshot_or_copytree(chrome_template, path_factory.get_chrome_tree())
479
480 if prepare_all:
481 android_branches = enumerate_android_branches_available(opts.mirror_base)
482 else:
483 android_branches = opts.android
484 for branch in android_branches:
485 logger.info('prepare tree for android branch=%s, %s', branch,
486 path_factory.get_android_tree(branch))
487 snapshot_or_copytree(
488 template_factory.get_android_tree(branch),
489 path_factory.get_android_tree(branch))
490
491
492def delete_tree(path):
493 if is_btrfs_subvolume(path):
Kuang-che Wu9d3ccde2019-01-03 17:06:09 +0800494 # btrfs should be mounted with 'user_subvol_rm_allowed' option and thus
495 # normal user permission is enough.
Kuang-che Wu41e8b592018-09-25 17:01:30 +0800496 util.check_call('btrfs', 'subvolume', 'delete', path)
497 else:
Kuang-che Wu9d3ccde2019-01-03 17:06:09 +0800498 util.check_call('sudo', 'rm', '-rf', path)
Kuang-che Wu41e8b592018-09-25 17:01:30 +0800499
500
501def cmd_list(opts):
502 print('%-20s %s' % ('Session', 'Path'))
503 for name in os.listdir(opts.work_base):
504 if name == CHECKOUT_TEMPLATE_NAME:
505 continue
506 path = os.path.join(opts.work_base, name)
507 print('%-20s %s' % (name, path))
508
509
510def cmd_delete(opts):
511 assert opts.session
512 path_factory = DefaultProjectPathFactory(opts.mirror_base, opts.work_base,
513 opts.session)
514
515 chromeos_tree = path_factory.get_chromeos_tree()
516 if os.path.exists(chromeos_tree):
517 if os.path.exists(os.path.join(chromeos_tree, 'chromite')):
518 # ignore error
519 util.call('cros_sdk', '--unmount', cwd=chromeos_tree)
520 delete_tree(chromeos_tree)
521
522 chrome_tree = path_factory.get_chrome_tree()
523 if os.path.exists(chrome_tree):
524 delete_tree(chrome_tree)
525
526 android_branches = enumerate_android_branches_available(opts.mirror_base)
527 for branch in android_branches:
528 android_tree = path_factory.get_android_tree(branch)
529 if os.path.exists(android_tree):
530 delete_tree(android_tree)
531
532 os.rmdir(os.path.join(opts.work_base, opts.session))
533
534
535def create_parser():
536 parser = argparse.ArgumentParser(
537 formatter_class=argparse.RawDescriptionHelpFormatter, description=__doc__)
538 parser.add_argument(
539 '--mirror_base',
540 metavar='MIRROR_BASE',
541 default=configure.get('MIRROR_BASE', DEFAULT_MIRROR_BASE),
542 help='Directory for mirrors (default: %(default)s)')
543 parser.add_argument(
544 '--work_base',
545 metavar='WORK_BASE',
546 default=configure.get('WORK_BASE', DEFAULT_WORK_BASE),
547 help='Directory for bisection working directories (default: %(default)s)')
548 common.add_common_arguments(parser)
549 subparsers = parser.add_subparsers(
550 dest='command', title='commands', metavar='<command>')
551
552 parser_init = subparsers.add_parser(
553 'init', help='Mirror source trees and create template checkout')
554 parser_init.add_argument(
555 '--chrome', action='store_true', help='init chrome mirror and tree')
556 parser_init.add_argument(
557 '--chromeos', action='store_true', help='init chromeos mirror and tree')
558 parser_init.add_argument(
559 '--android',
560 metavar='BRANCH',
561 action='append',
562 default=[],
563 help='init android mirror and tree of BRANCH')
564 parser_init.add_argument(
565 '--btrfs',
566 action='store_true',
567 help='create btrfs subvolume for source tree')
568 parser_init.set_defaults(func=cmd_init)
569
570 parser_sync = subparsers.add_parser(
571 'sync',
572 help='Sync source trees',
573 description='Sync all if no projects are specified '
574 '(--chrome, --chromeos, or --android)')
575 parser_sync.add_argument(
576 '--chrome', action='store_true', help='sync chrome mirror and tree')
577 parser_sync.add_argument(
578 '--chromeos', action='store_true', help='sync chromeos mirror and tree')
579 parser_sync.add_argument(
580 '--android',
581 metavar='BRANCH',
582 action='append',
583 default=[],
584 help='sync android mirror and tree of BRANCH')
585 parser_sync.set_defaults(func=cmd_sync)
586
587 parser_new = subparsers.add_parser(
588 'new',
589 help='Create new source checkout for bisect',
590 description='Create for all if no projects are specified '
591 '(--chrome, --chromeos, or --android)')
592 parser_new.add_argument('--session', required=True)
593 parser_new.add_argument(
594 '--chrome', action='store_true', help='create chrome checkout')
595 parser_new.add_argument(
596 '--chromeos', action='store_true', help='create chromeos checkout')
597 parser_new.add_argument(
598 '--android',
599 metavar='BRANCH',
600 action='append',
601 default=[],
602 help='create android checkout of BRANCH')
603 parser_new.set_defaults(func=cmd_new)
604
605 parser_list = subparsers.add_parser(
606 'list', help='List existing sessions with source checkout')
607 parser_list.set_defaults(func=cmd_list)
608
609 parser_delete = subparsers.add_parser('delete', help='Delete source checkout')
610 parser_delete.add_argument('--session', required=True)
611 parser_delete.set_defaults(func=cmd_delete)
612
613 return parser
614
615
616def main():
617 common.init()
618 parser = create_parser()
619 opts = parser.parse_args()
620 common.config_logging(opts)
621
Kuang-che Wud3a4e842019-12-11 12:15:23 +0800622 # It's optional by default since python3.
623 if not opts.command:
624 parser.error('command is missing')
Kuang-che Wu41e8b592018-09-25 17:01:30 +0800625 opts.func(opts)
626
627
628if __name__ == '__main__':
629 main()