blob: 9c97ebc23fb447ea7785f270f7b8ba7cf20a98b9 [file] [log] [blame]
Kuang-che Wu875c89a2020-01-08 14:30:55 +08001#!/usr/bin/env python3
Kuang-che Wu41e8b592018-09-25 17:01:30 +08002# -*- 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
Zheng-Jie Changcd424c12020-01-10 14:32:08 +080027import glob
Kuang-che Wu999893c2020-04-13 22:06:22 +080028import io
Kuang-che Wu41e8b592018-09-25 17:01:30 +080029import logging
30import os
Kuang-che Wu22f207e2019-02-23 12:53:53 +080031import subprocess
Kuang-che Wu67be74b2018-10-15 14:17:26 +080032import time
Kuang-che Wu7d0c7592019-09-16 09:59:28 +080033import xml.etree.ElementTree
Kuang-che Wu999893c2020-04-13 22:06:22 +080034import urllib.parse
35import urllib.request
Kuang-che Wua7ddf9b2019-11-25 18:59:57 +080036
Kuang-che Wu41e8b592018-09-25 17:01:30 +080037from bisect_kit import common
38from bisect_kit import configure
39from bisect_kit import gclient_util
40from bisect_kit import git_util
Kuang-che Wudc714412018-10-17 16:06:39 +080041from bisect_kit import locking
Kuang-che Wu41e8b592018-09-25 17:01:30 +080042from bisect_kit import repo_util
43from bisect_kit import util
44
45DEFAULT_MIRROR_BASE = os.path.expanduser('~/git-mirrors')
46DEFAULT_WORK_BASE = os.path.expanduser('~/bisect-workdir')
47CHECKOUT_TEMPLATE_NAME = 'template'
48
49logger = logging.getLogger(__name__)
50
51
Kuang-che Wu23192ad2020-03-11 18:12:46 +080052class DefaultProjectPathFactory:
Kuang-che Wu41e8b592018-09-25 17:01:30 +080053 """Factory for chromeos/chrome/android source tree paths."""
54
55 def __init__(self, mirror_base, work_base, session):
56 self.mirror_base = mirror_base
57 self.work_base = work_base
58 self.session = session
59
60 def get_chromeos_mirror(self):
61 return os.path.join(self.mirror_base, 'chromeos')
62
63 def get_chromeos_tree(self):
64 return os.path.join(self.work_base, self.session, 'chromeos')
65
66 def get_android_mirror(self, branch):
67 return os.path.join(self.mirror_base, 'android.%s' % branch)
68
69 def get_android_tree(self, branch):
70 return os.path.join(self.work_base, self.session, 'android.%s' % branch)
71
72 def get_chrome_cache(self):
73 return os.path.join(self.mirror_base, 'chrome')
74
75 def get_chrome_tree(self):
76 return os.path.join(self.work_base, self.session, 'chrome')
77
78
79def subvolume_or_makedirs(opts, path):
80 if os.path.exists(path):
81 return
82
83 path = os.path.abspath(path)
84 if opts.btrfs:
85 dirname, basename = os.path.split(path)
86 if not os.path.exists(dirname):
87 os.makedirs(dirname)
88 util.check_call('btrfs', 'subvolume', 'create', basename, cwd=dirname)
89 else:
90 os.makedirs(path)
91
92
93def is_btrfs_subvolume(path):
94 if util.check_output('stat', '-f', '--format=%T', path).strip() != 'btrfs':
95 return False
96 return util.check_output('stat', '--format=%i', path).strip() == '256'
97
98
99def snapshot_or_copytree(src, dst):
100 assert os.path.isdir(src), '%s does not exist' % src
101 assert os.path.isdir(os.path.dirname(dst))
102
103 # Make sure dst do not exist, otherwise it becomes "dst/name" (one extra
104 # depth) instead of "dst".
105 assert not os.path.exists(dst)
106
107 if is_btrfs_subvolume(src):
108 util.check_call('btrfs', 'subvolume', 'snapshot', src, dst)
109 else:
110 # -a for recursion and preserve all attributes.
111 util.check_call('cp', '-a', src, dst)
112
113
Kuang-che Wubfa64482018-10-16 11:49:49 +0800114def collect_removed_manifest_repos(repo_dir, last_sync_time, only_branch=None):
Kuang-che Wu67be74b2018-10-15 14:17:26 +0800115 manifest_dir = os.path.join(repo_dir, '.repo', 'manifests')
Kuang-che Wu67be74b2018-10-15 14:17:26 +0800116 manifest_path = 'default.xml'
117 manifest_full_path = os.path.join(manifest_dir, manifest_path)
118 # hack for chromeos symlink
119 if os.path.islink(manifest_full_path):
120 manifest_path = os.readlink(manifest_full_path)
121
122 parser = repo_util.ManifestParser(manifest_dir)
Kuang-che Wudedc5922020-12-17 17:19:23 +0800123 git_rev = git_util.get_commit_hash(manifest_dir, 'HEAD')
124 root = parser.parse_xml_recursive(git_rev, manifest_path)
125 latest_all = parser.process_parsed_result(root, group_constraint='all')
126 latest_default = parser.process_parsed_result(
127 root, group_constraint='default')
128
Kuang-che Wu67be74b2018-10-15 14:17:26 +0800129 removed = {}
130 for _, git_rev in reversed(
131 parser.enumerate_manifest_commits(last_sync_time, None, manifest_path)):
Kuang-che Wu7d0c7592019-09-16 09:59:28 +0800132 try:
133 root = parser.parse_xml_recursive(git_rev, manifest_path)
134 except xml.etree.ElementTree.ParseError:
135 logger.warning('%s %s@%s syntax error, skip', manifest_dir, manifest_path,
136 git_rev[:12])
137 continue
Kuang-che Wubfa64482018-10-16 11:49:49 +0800138 if (only_branch and root.find('default') is not None and
139 root.find('default').get('revision') != only_branch):
140 break
Kuang-che Wu67be74b2018-10-15 14:17:26 +0800141 entries = parser.process_parsed_result(root)
Kuang-che Wu67be74b2018-10-15 14:17:26 +0800142 for path, path_spec in entries.items():
Kuang-che Wudedc5922020-12-17 17:19:23 +0800143 if path in latest_default:
144 continue
145 if path in latest_all:
146 logger.warning(
147 'path=%s was removed from default group; assume skip is harmless',
148 path)
Kuang-che Wu67be74b2018-10-15 14:17:26 +0800149 continue
150 if path in removed:
151 continue
152 removed[path] = path_spec
153
154 return removed
155
156
Kuang-che Wu41e8b592018-09-25 17:01:30 +0800157def setup_chromeos_repos(opts, path_factory):
158 chromeos_mirror = path_factory.get_chromeos_mirror()
159 chromeos_tree = path_factory.get_chromeos_tree()
160 subvolume_or_makedirs(opts, chromeos_mirror)
161 subvolume_or_makedirs(opts, chromeos_tree)
162
163 manifest_url = (
164 'https://chrome-internal.googlesource.com/chromeos/manifest-internal')
165 repo_url = 'https://chromium.googlesource.com/external/repo.git'
166
167 if os.path.exists(os.path.join(chromeos_mirror, '.repo', 'manifests')):
168 logger.warning(
169 '%s has already been initialized, assume it is setup properly',
170 chromeos_mirror)
171 else:
172 logger.info('repo init for chromeos mirror')
173 repo_util.init(
174 chromeos_mirror,
175 manifest_url=manifest_url,
176 repo_url=repo_url,
177 mirror=True)
178
179 local_manifest_dir = os.path.join(chromeos_mirror, '.repo',
180 'local_manifests')
181 os.mkdir(local_manifest_dir)
182 with open(os.path.join(local_manifest_dir, 'manifest-versions.xml'),
183 'w') as f:
Kuang-che Wuae6824b2019-08-27 22:20:01 +0800184 f.write("""<?xml version="1.0" encoding="UTF-8"?>
Kuang-che Wu41e8b592018-09-25 17:01:30 +0800185 <manifest>
186 <project name="chromeos/manifest-versions" remote="cros-internal" />
187 </manifest>
Kuang-che Wuae6824b2019-08-27 22:20:01 +0800188 """)
Kuang-che Wu41e8b592018-09-25 17:01:30 +0800189
190 logger.info('repo init for chromeos tree')
191 repo_util.init(
192 chromeos_tree,
193 manifest_url=manifest_url,
194 repo_url=repo_url,
195 reference=chromeos_mirror)
196
Kuang-che Wudc714412018-10-17 16:06:39 +0800197 with locking.lock_file(
198 os.path.join(chromeos_mirror, locking.LOCK_FILE_FOR_MIRROR_SYNC)):
199 logger.info('repo sync for chromeos mirror (this takes hours; be patient)')
200 repo_util.sync(chromeos_mirror)
Kuang-che Wu41e8b592018-09-25 17:01:30 +0800201
202 logger.info('repo sync for chromeos tree')
203 repo_util.sync(chromeos_tree)
204
205
Kuang-che Wu67be74b2018-10-15 14:17:26 +0800206def read_last_sync_time(repo_dir):
207 timestamp_path = os.path.join(repo_dir, 'last_sync_time')
208 if os.path.exists(timestamp_path):
209 with open(timestamp_path) as f:
210 return int(f.read())
211 else:
212 # 4 months should be enough for most bisect cases.
213 return int(time.time()) - 86400 * 120
214
215
216def write_sync_time(repo_dir, sync_time):
217 timestamp_path = os.path.join(repo_dir, 'last_sync_time')
218 with open(timestamp_path, 'w') as f:
219 f.write('%d\n' % sync_time)
220
221
222def write_extra_manifest_to_mirror(repo_dir, removed):
223 local_manifest_dir = os.path.join(repo_dir, '.repo', 'local_manifests')
224 if not os.path.exists(local_manifest_dir):
225 os.mkdir(local_manifest_dir)
226 with open(os.path.join(local_manifest_dir, 'deleted-repos.xml'), 'w') as f:
Kuang-che Wuae6824b2019-08-27 22:20:01 +0800227 f.write("""<?xml version="1.0" encoding="UTF-8"?>\n<manifest>\n""")
Kuang-che Wu67be74b2018-10-15 14:17:26 +0800228 remotes = {}
229 for path_spec in removed.values():
Kuang-che Wua7ddf9b2019-11-25 18:59:57 +0800230 scheme, netloc, remote_path = urllib.parse.urlsplit(
231 path_spec.repo_url)[:3]
Kuang-che Wu67be74b2018-10-15 14:17:26 +0800232 assert remote_path[0] == '/'
233 remote_path = remote_path[1:]
234 if (scheme, netloc) not in remotes:
235 remote_name = 'remote_for_deleted_repo_%s' % (scheme + netloc)
236 remotes[scheme, netloc] = remote_name
Kuang-che Wuae6824b2019-08-27 22:20:01 +0800237 f.write(""" <remote name="%s" fetch="%s" />\n""" %
Kuang-che Wu67be74b2018-10-15 14:17:26 +0800238 (remote_name, '%s://%s' % (scheme, netloc)))
Kuang-che Wubfa64482018-10-16 11:49:49 +0800239 f.write(
Kuang-che Wuae6824b2019-08-27 22:20:01 +0800240 """ <project name="%s" path="%s" remote="%s" revision="%s" />\n""" %
Kuang-che Wubfa64482018-10-16 11:49:49 +0800241 (remote_path, path_spec.path, remotes[scheme, netloc], path_spec.at))
Kuang-che Wuae6824b2019-08-27 22:20:01 +0800242 f.write("""</manifest>\n""")
Kuang-che Wu67be74b2018-10-15 14:17:26 +0800243
244
Kuang-che Wubfa64482018-10-16 11:49:49 +0800245def generate_extra_manifest_for_deleted_repo(repo_dir, only_branch=None):
246 last_sync_time = read_last_sync_time(repo_dir)
247 removed = collect_removed_manifest_repos(
248 repo_dir, last_sync_time, only_branch=only_branch)
249 write_extra_manifest_to_mirror(repo_dir, removed)
250 logger.info('since last sync, %d repo got removed', len(removed))
Kuang-che Wub76b7f62019-09-16 10:06:18 +0800251 return len(removed)
Kuang-che Wubfa64482018-10-16 11:49:49 +0800252
253
Kuang-che Wu41e8b592018-09-25 17:01:30 +0800254def sync_chromeos_code(opts, path_factory):
255 del opts # unused
256
Kuang-che Wu67be74b2018-10-15 14:17:26 +0800257 start_sync_time = int(time.time())
Kuang-che Wu41e8b592018-09-25 17:01:30 +0800258 chromeos_mirror = path_factory.get_chromeos_mirror()
Kuang-che Wu67be74b2018-10-15 14:17:26 +0800259
Kuang-che Wu67be74b2018-10-15 14:17:26 +0800260 logger.info('repo sync for chromeos mirror')
Kuang-che Wu41e8b592018-09-25 17:01:30 +0800261 repo_util.sync(chromeos_mirror)
Kuang-che Wub76b7f62019-09-16 10:06:18 +0800262 # If there are repos deleted after last sync, generate custom manifest and
263 # sync again for those repos. So we can mirror commits just before the repo
264 # deletion.
265 if generate_extra_manifest_for_deleted_repo(chromeos_mirror) != 0:
266 logger.info('repo sync again')
267 repo_util.sync(chromeos_mirror)
Kuang-che Wua7f29352020-03-02 17:07:53 +0800268
269 # Work around for b/149883148: 'repo' tool does not fetch all branches in
270 # mirror mode.
271 chromiumos_overlay_mirror = os.path.join(
272 chromeos_mirror, 'chromiumos/overlays/chromiumos-overlay.git')
273 git_util.fetch(chromiumos_overlay_mirror, 'cros')
274
Kuang-che Wu67be74b2018-10-15 14:17:26 +0800275 write_sync_time(chromeos_mirror, start_sync_time)
Kuang-che Wu41e8b592018-09-25 17:01:30 +0800276
277 logger.info('repo sync for chromeos tree')
278 chromeos_tree = path_factory.get_chromeos_tree()
279 repo_util.sync(chromeos_tree)
280
Kuang-che Wu41e8b592018-09-25 17:01:30 +0800281
282def query_chrome_latest_branch():
Kuang-che Wud3a4e842019-12-11 12:15:23 +0800283 result = 0
Kuang-che Wua7ddf9b2019-11-25 18:59:57 +0800284 r = urllib.request.urlopen('https://omahaproxy.appspot.com/all')
Kuang-che Wu6bf6ac32020-04-15 01:14:32 +0800285 for row in csv.DictReader(io.TextIOWrapper(r, encoding='utf-8')):
Kuang-che Wu41e8b592018-09-25 17:01:30 +0800286 if row['true_branch'].isdigit():
287 result = max(result, int(row['true_branch']))
288 return result
289
290
291def setup_chrome_repos(opts, path_factory):
292 chrome_cache = path_factory.get_chrome_cache()
293 subvolume_or_makedirs(opts, chrome_cache)
294 chrome_tree = path_factory.get_chrome_tree()
295 subvolume_or_makedirs(opts, chrome_tree)
296
297 latest_branch = query_chrome_latest_branch()
298 logger.info('latest chrome branch is %d', latest_branch)
299 assert latest_branch
Kuang-che Wuae6824b2019-08-27 22:20:01 +0800300 spec = """
Kuang-che Wu41e8b592018-09-25 17:01:30 +0800301solutions = [
302 { "name" : "buildspec",
303 "url" : "https://chrome-internal.googlesource.com/a/chrome/tools/buildspec.git",
304 "deps_file" : "branches/%d/DEPS",
305 "custom_deps" : {
306 },
307 "custom_vars": {'checkout_src_internal': True},
308 },
309]
310target_os = ['chromeos']
311cache_dir = %r
Kuang-che Wuae6824b2019-08-27 22:20:01 +0800312""" % (latest_branch, chrome_cache)
Kuang-che Wu41e8b592018-09-25 17:01:30 +0800313
Kuang-che Wudc714412018-10-17 16:06:39 +0800314 with locking.lock_file(
315 os.path.join(chrome_cache, locking.LOCK_FILE_FOR_MIRROR_SYNC)):
316 logger.info('gclient config for chrome')
317 gclient_util.config(chrome_tree, spec=spec)
Kuang-che Wu41e8b592018-09-25 17:01:30 +0800318
Kuang-che Wudc714412018-10-17 16:06:39 +0800319 is_first_sync = not os.listdir(chrome_cache)
320 if is_first_sync:
321 logger.info('gclient sync for chrome (this takes hours; be patient)')
322 else:
323 logger.info('gclient sync for chrome')
Kuang-che Wu6ee24b52020-11-02 11:33:49 +0800324 gclient_util.sync(chrome_tree, with_branch_heads=True, with_tags=True)
Kuang-che Wu41e8b592018-09-25 17:01:30 +0800325
Kuang-che Wudc714412018-10-17 16:06:39 +0800326 # It's possible that some repos are removed from latest branch and thus
327 # their commit history is not fetched in recent gclient sync. So we call
328 # 'git fetch' for all existing git mirrors.
329 # TODO(kcwu): only sync repos not in DEPS files of latest branch
330 logger.info('additional sync for chrome mirror')
331 for git_repo_name in os.listdir(chrome_cache):
332 # another gclient is running or leftover of previous run; skip
333 if git_repo_name.startswith('_cache_tmp'):
334 continue
335 git_repo = os.path.join(chrome_cache, git_repo_name)
Kuang-che Wu08366542019-01-12 12:37:49 +0800336 if not git_util.is_git_bare_dir(git_repo):
Kuang-che Wudc714412018-10-17 16:06:39 +0800337 continue
Kuang-che Wu2b1286b2019-05-20 20:37:26 +0800338 git_util.fetch(git_repo)
Kuang-che Wu41e8b592018-09-25 17:01:30 +0800339
Kuang-che Wu1e49f512018-12-06 15:27:42 +0800340 # Some repos were removed from the DEPS and won't be synced here. They will
341 # be synced during DEPS file processing because the necessary information
342 # requires full DEPS parsing. (crbug.com/902238)
343
Kuang-che Wu41e8b592018-09-25 17:01:30 +0800344
345def sync_chrome_code(opts, path_factory):
346 # The sync step is identical to the initial gclient config step.
347 setup_chrome_repos(opts, path_factory)
348
349
350def setup_android_repos(opts, path_factory, branch):
351 android_mirror = path_factory.get_android_mirror(branch)
352 android_tree = path_factory.get_android_tree(branch)
353 subvolume_or_makedirs(opts, android_mirror)
354 subvolume_or_makedirs(opts, android_tree)
355
356 manifest_url = ('persistent-https://googleplex-android.git.corp.google.com'
357 '/platform/manifest')
358 repo_url = 'https://gerrit.googlesource.com/git-repo'
359
360 if os.path.exists(os.path.join(android_mirror, '.repo', 'manifests')):
361 logger.warning(
362 '%s has already been initialized, assume it is setup properly',
363 android_mirror)
364 else:
365 logger.info('repo init for android mirror branch=%s', branch)
366 repo_util.init(
367 android_mirror,
368 manifest_url=manifest_url,
369 repo_url=repo_url,
370 manifest_branch=branch,
371 mirror=True)
372
373 logger.info('repo init for android tree branch=%s', branch)
374 repo_util.init(
375 android_tree,
376 manifest_url=manifest_url,
377 repo_url=repo_url,
378 manifest_branch=branch,
379 reference=android_mirror)
380
381 logger.info('repo sync for android mirror (this takes hours; be patient)')
382 repo_util.sync(android_mirror, current_branch=True)
383
384 logger.info('repo sync for android tree branch=%s', branch)
385 repo_util.sync(android_tree, current_branch=True)
386
387
388def sync_android_code(opts, path_factory, branch):
389 del opts # unused
Kuang-che Wu67be74b2018-10-15 14:17:26 +0800390 start_sync_time = int(time.time())
Kuang-che Wu41e8b592018-09-25 17:01:30 +0800391 android_mirror = path_factory.get_android_mirror(branch)
392 android_tree = path_factory.get_android_tree(branch)
393
Kuang-che Wudc714412018-10-17 16:06:39 +0800394 with locking.lock_file(
395 os.path.join(android_mirror, locking.LOCK_FILE_FOR_MIRROR_SYNC)):
396 logger.info('repo sync for android mirror branch=%s', branch)
Kuang-che Wub76b7f62019-09-16 10:06:18 +0800397 repo_util.sync(android_mirror, current_branch=True)
Kuang-che Wudc714412018-10-17 16:06:39 +0800398 # Android usually big jump between milestone releases and add/delete lots of
399 # repos when switch releases. Because it's infeasible to bisect between such
400 # big jump, the deleted repo is useless. In order to save disk, do not sync
401 # repos deleted in other branches.
Kuang-che Wub76b7f62019-09-16 10:06:18 +0800402 if generate_extra_manifest_for_deleted_repo(
403 android_mirror, only_branch=branch) != 0:
404 logger.info('repo sync again')
405 repo_util.sync(android_mirror, current_branch=True)
Kuang-che Wudc714412018-10-17 16:06:39 +0800406 write_sync_time(android_mirror, start_sync_time)
Kuang-che Wu41e8b592018-09-25 17:01:30 +0800407
408 logger.info('repo sync for android tree branch=%s', branch)
409 repo_util.sync(android_tree, current_branch=True)
410
411
412def cmd_init(opts):
413 path_factory = DefaultProjectPathFactory(opts.mirror_base, opts.work_base,
414 CHECKOUT_TEMPLATE_NAME)
415
416 if opts.chromeos:
417 setup_chromeos_repos(opts, path_factory)
418 if opts.chrome:
419 setup_chrome_repos(opts, path_factory)
420 for branch in opts.android:
421 setup_android_repos(opts, path_factory, branch)
422
423
424def enumerate_android_branches_available(base):
425 branches = []
426 for name in os.listdir(base):
427 if name.startswith('android.'):
428 branches.append(name.partition('.')[2])
429 return branches
430
431
Kuang-che Wu22f207e2019-02-23 12:53:53 +0800432def do_sync(opts):
Kuang-che Wu41e8b592018-09-25 17:01:30 +0800433 path_factory = DefaultProjectPathFactory(opts.mirror_base, opts.work_base,
434 CHECKOUT_TEMPLATE_NAME)
435
436 sync_all = False
437 if not opts.chromeos and not opts.chrome and not opts.android:
438 logger.info('sync trees for all')
439 sync_all = True
440
441 if sync_all or opts.chromeos:
442 sync_chromeos_code(opts, path_factory)
443 if sync_all or opts.chrome:
444 sync_chrome_code(opts, path_factory)
445
446 if sync_all:
447 android_branches = enumerate_android_branches_available(opts.mirror_base)
448 else:
449 android_branches = opts.android
450 for branch in android_branches:
451 sync_android_code(opts, path_factory, branch)
452
453
Kuang-che Wu22f207e2019-02-23 12:53:53 +0800454def cmd_sync(opts):
455 try:
456 do_sync(opts)
457 except subprocess.CalledProcessError:
458 # Sync may fail due to network or server issues.
459 logger.exception('do_sync failed, will retry one minute later')
460 time.sleep(60)
461 do_sync(opts)
462
463
Kuang-che Wu41e8b592018-09-25 17:01:30 +0800464def cmd_new(opts):
465 work_dir = os.path.join(opts.work_base, opts.session)
466 if not os.path.exists(work_dir):
467 os.makedirs(work_dir)
468
469 template_factory = DefaultProjectPathFactory(opts.mirror_base, opts.work_base,
470 CHECKOUT_TEMPLATE_NAME)
471 path_factory = DefaultProjectPathFactory(opts.mirror_base, opts.work_base,
472 opts.session)
473
474 prepare_all = False
475 if not opts.chromeos and not opts.chrome and not opts.android:
476 logger.info('prepare trees for all')
477 prepare_all = True
478
479 chromeos_template = template_factory.get_chromeos_tree()
480 if (prepare_all and os.path.exists(chromeos_template)) or opts.chromeos:
481 logger.info('prepare tree for chromeos, %s',
482 path_factory.get_chromeos_tree())
483 snapshot_or_copytree(chromeos_template, path_factory.get_chromeos_tree())
484
485 chrome_template = template_factory.get_chrome_tree()
486 if (prepare_all and os.path.exists(chrome_template)) or opts.chrome:
487 logger.info('prepare tree for chrome, %s', path_factory.get_chrome_tree())
488 snapshot_or_copytree(chrome_template, path_factory.get_chrome_tree())
489
490 if prepare_all:
491 android_branches = enumerate_android_branches_available(opts.mirror_base)
492 else:
493 android_branches = opts.android
494 for branch in android_branches:
495 logger.info('prepare tree for android branch=%s, %s', branch,
496 path_factory.get_android_tree(branch))
497 snapshot_or_copytree(
498 template_factory.get_android_tree(branch),
499 path_factory.get_android_tree(branch))
500
501
502def delete_tree(path):
503 if is_btrfs_subvolume(path):
Kuang-che Wu9d3ccde2019-01-03 17:06:09 +0800504 # btrfs should be mounted with 'user_subvol_rm_allowed' option and thus
505 # normal user permission is enough.
Kuang-che Wu41e8b592018-09-25 17:01:30 +0800506 util.check_call('btrfs', 'subvolume', 'delete', path)
507 else:
Kuang-che Wu9d3ccde2019-01-03 17:06:09 +0800508 util.check_call('sudo', 'rm', '-rf', path)
Kuang-che Wu41e8b592018-09-25 17:01:30 +0800509
510
511def cmd_list(opts):
512 print('%-20s %s' % ('Session', 'Path'))
513 for name in os.listdir(opts.work_base):
514 if name == CHECKOUT_TEMPLATE_NAME:
515 continue
516 path = os.path.join(opts.work_base, name)
517 print('%-20s %s' % (name, path))
518
519
520def cmd_delete(opts):
521 assert opts.session
522 path_factory = DefaultProjectPathFactory(opts.mirror_base, opts.work_base,
523 opts.session)
524
525 chromeos_tree = path_factory.get_chromeos_tree()
526 if os.path.exists(chromeos_tree):
527 if os.path.exists(os.path.join(chromeos_tree, 'chromite')):
528 # ignore error
529 util.call('cros_sdk', '--unmount', cwd=chromeos_tree)
530 delete_tree(chromeos_tree)
531
532 chrome_tree = path_factory.get_chrome_tree()
533 if os.path.exists(chrome_tree):
534 delete_tree(chrome_tree)
535
536 android_branches = enumerate_android_branches_available(opts.mirror_base)
537 for branch in android_branches:
538 android_tree = path_factory.get_android_tree(branch)
539 if os.path.exists(android_tree):
540 delete_tree(android_tree)
541
542 os.rmdir(os.path.join(opts.work_base, opts.session))
543
Zheng-Jie Changcd424c12020-01-10 14:32:08 +0800544 # remove caches
545 chromeos_root = os.getenv('DEFAULT_CHROMEOS_ROOT')
546 if chromeos_root:
547 path = os.path.join(chromeos_root, 'devserver/static')
548 if os.path.exists(path):
Kuang-che Wud2747442020-07-01 16:50:24 +0800549 logger.debug('remove cache (cros flash): %s', path)
Zheng-Jie Changcd424c12020-01-10 14:32:08 +0800550 util.call('cros', 'clean', '--flash', cwd=path)
551
552 for path in glob.glob(os.path.join(chromeos_root, 'chroot/tmp/*')):
Kuang-che Wud2747442020-07-01 16:50:24 +0800553 logger.debug('remove cache (chroot/tmp): %s', path)
Zheng-Jie Changcd424c12020-01-10 14:32:08 +0800554 delete_tree(path)
555
Zheng-Jie Changd164da42020-03-12 10:33:23 +0800556 for path in glob.glob(os.path.join(chromeos_root, 'tmp/*')):
Kuang-che Wud2747442020-07-01 16:50:24 +0800557 logger.debug('remove cache (chromeos root tmp): %s', path)
Zheng-Jie Changd164da42020-03-12 10:33:23 +0800558 delete_tree(path)
559
Zheng-Jie Changcd424c12020-01-10 14:32:08 +0800560 for path in glob.glob(
561 os.path.join(path_factory.get_chrome_cache(), '_cache_*')):
Kuang-che Wud2747442020-07-01 16:50:24 +0800562 logger.debug('remove cache (chrome gclient cache): %s', path)
Zheng-Jie Changcd424c12020-01-10 14:32:08 +0800563 delete_tree(path)
564
Kuang-che Wu41e8b592018-09-25 17:01:30 +0800565
566def create_parser():
567 parser = argparse.ArgumentParser(
568 formatter_class=argparse.RawDescriptionHelpFormatter, description=__doc__)
569 parser.add_argument(
570 '--mirror_base',
571 metavar='MIRROR_BASE',
572 default=configure.get('MIRROR_BASE', DEFAULT_MIRROR_BASE),
573 help='Directory for mirrors (default: %(default)s)')
574 parser.add_argument(
575 '--work_base',
576 metavar='WORK_BASE',
577 default=configure.get('WORK_BASE', DEFAULT_WORK_BASE),
578 help='Directory for bisection working directories (default: %(default)s)')
579 common.add_common_arguments(parser)
580 subparsers = parser.add_subparsers(
581 dest='command', title='commands', metavar='<command>')
582
583 parser_init = subparsers.add_parser(
584 'init', help='Mirror source trees and create template checkout')
585 parser_init.add_argument(
586 '--chrome', action='store_true', help='init chrome mirror and tree')
587 parser_init.add_argument(
588 '--chromeos', action='store_true', help='init chromeos mirror and tree')
589 parser_init.add_argument(
590 '--android',
591 metavar='BRANCH',
592 action='append',
593 default=[],
594 help='init android mirror and tree of BRANCH')
595 parser_init.add_argument(
596 '--btrfs',
597 action='store_true',
598 help='create btrfs subvolume for source tree')
599 parser_init.set_defaults(func=cmd_init)
600
601 parser_sync = subparsers.add_parser(
602 'sync',
603 help='Sync source trees',
604 description='Sync all if no projects are specified '
605 '(--chrome, --chromeos, or --android)')
606 parser_sync.add_argument(
607 '--chrome', action='store_true', help='sync chrome mirror and tree')
608 parser_sync.add_argument(
609 '--chromeos', action='store_true', help='sync chromeos mirror and tree')
610 parser_sync.add_argument(
611 '--android',
612 metavar='BRANCH',
613 action='append',
614 default=[],
615 help='sync android mirror and tree of BRANCH')
616 parser_sync.set_defaults(func=cmd_sync)
617
618 parser_new = subparsers.add_parser(
619 'new',
620 help='Create new source checkout for bisect',
621 description='Create for all if no projects are specified '
622 '(--chrome, --chromeos, or --android)')
623 parser_new.add_argument('--session', required=True)
624 parser_new.add_argument(
625 '--chrome', action='store_true', help='create chrome checkout')
626 parser_new.add_argument(
627 '--chromeos', action='store_true', help='create chromeos checkout')
628 parser_new.add_argument(
629 '--android',
630 metavar='BRANCH',
631 action='append',
632 default=[],
633 help='create android checkout of BRANCH')
634 parser_new.set_defaults(func=cmd_new)
635
636 parser_list = subparsers.add_parser(
637 'list', help='List existing sessions with source checkout')
638 parser_list.set_defaults(func=cmd_list)
639
640 parser_delete = subparsers.add_parser('delete', help='Delete source checkout')
641 parser_delete.add_argument('--session', required=True)
642 parser_delete.set_defaults(func=cmd_delete)
643
644 return parser
645
646
647def main():
648 common.init()
649 parser = create_parser()
650 opts = parser.parse_args()
651 common.config_logging(opts)
652
Kuang-che Wud3a4e842019-12-11 12:15:23 +0800653 # It's optional by default since python3.
654 if not opts.command:
655 parser.error('command is missing')
Kuang-che Wu41e8b592018-09-25 17:01:30 +0800656 opts.func(opts)
657
658
659if __name__ == '__main__':
660 main()