blob: d736bbc903d1ac1ceb3c02aabb90db6c3d18dc7c [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'
Kuang-che Wucc2870b2021-02-18 15:36:00 +080048MANIFEST_FOR_DELETED = 'deleted-repos.xml'
Kuang-che Wu41e8b592018-09-25 17:01:30 +080049
50logger = logging.getLogger(__name__)
51
52
Kuang-che Wu23192ad2020-03-11 18:12:46 +080053class DefaultProjectPathFactory:
Kuang-che Wu41e8b592018-09-25 17:01:30 +080054 """Factory for chromeos/chrome/android source tree paths."""
55
56 def __init__(self, mirror_base, work_base, session):
57 self.mirror_base = mirror_base
58 self.work_base = work_base
59 self.session = session
60
61 def get_chromeos_mirror(self):
62 return os.path.join(self.mirror_base, 'chromeos')
63
64 def get_chromeos_tree(self):
65 return os.path.join(self.work_base, self.session, 'chromeos')
66
67 def get_android_mirror(self, branch):
68 return os.path.join(self.mirror_base, 'android.%s' % branch)
69
70 def get_android_tree(self, branch):
71 return os.path.join(self.work_base, self.session, 'android.%s' % branch)
72
73 def get_chrome_cache(self):
74 return os.path.join(self.mirror_base, 'chrome')
75
76 def get_chrome_tree(self):
77 return os.path.join(self.work_base, self.session, 'chrome')
78
79
80def subvolume_or_makedirs(opts, path):
81 if os.path.exists(path):
82 return
83
84 path = os.path.abspath(path)
85 if opts.btrfs:
86 dirname, basename = os.path.split(path)
87 if not os.path.exists(dirname):
88 os.makedirs(dirname)
89 util.check_call('btrfs', 'subvolume', 'create', basename, cwd=dirname)
90 else:
91 os.makedirs(path)
92
93
94def is_btrfs_subvolume(path):
Zheng-Jie Changaf9c27c2021-05-06 13:31:34 +080095 if os.path.islink(path):
96 return False
Kuang-che Wu41e8b592018-09-25 17:01:30 +080097 if util.check_output('stat', '-f', '--format=%T', path).strip() != 'btrfs':
98 return False
99 return util.check_output('stat', '--format=%i', path).strip() == '256'
100
101
102def snapshot_or_copytree(src, dst):
103 assert os.path.isdir(src), '%s does not exist' % src
104 assert os.path.isdir(os.path.dirname(dst))
105
106 # Make sure dst do not exist, otherwise it becomes "dst/name" (one extra
107 # depth) instead of "dst".
108 assert not os.path.exists(dst)
109
110 if is_btrfs_subvolume(src):
111 util.check_call('btrfs', 'subvolume', 'snapshot', src, dst)
112 else:
113 # -a for recursion and preserve all attributes.
114 util.check_call('cp', '-a', src, dst)
115
116
Kuang-che Wubfa64482018-10-16 11:49:49 +0800117def collect_removed_manifest_repos(repo_dir, last_sync_time, only_branch=None):
Kuang-che Wu67be74b2018-10-15 14:17:26 +0800118 manifest_dir = os.path.join(repo_dir, '.repo', 'manifests')
Kuang-che Wu67be74b2018-10-15 14:17:26 +0800119 manifest_path = 'default.xml'
120 manifest_full_path = os.path.join(manifest_dir, manifest_path)
121 # hack for chromeos symlink
122 if os.path.islink(manifest_full_path):
123 manifest_path = os.readlink(manifest_full_path)
124
125 parser = repo_util.ManifestParser(manifest_dir)
Kuang-che Wudedc5922020-12-17 17:19:23 +0800126 git_rev = git_util.get_commit_hash(manifest_dir, 'HEAD')
127 root = parser.parse_xml_recursive(git_rev, manifest_path)
128 latest_all = parser.process_parsed_result(root, group_constraint='all')
129 latest_default = parser.process_parsed_result(
130 root, group_constraint='default')
131
Kuang-che Wu67be74b2018-10-15 14:17:26 +0800132 removed = {}
133 for _, git_rev in reversed(
134 parser.enumerate_manifest_commits(last_sync_time, None, manifest_path)):
Kuang-che Wu7d0c7592019-09-16 09:59:28 +0800135 try:
136 root = parser.parse_xml_recursive(git_rev, manifest_path)
137 except xml.etree.ElementTree.ParseError:
138 logger.warning('%s %s@%s syntax error, skip', manifest_dir, manifest_path,
139 git_rev[:12])
140 continue
Kuang-che Wubfa64482018-10-16 11:49:49 +0800141 if (only_branch and root.find('default') is not None and
142 root.find('default').get('revision') != only_branch):
143 break
Kuang-che Wu67be74b2018-10-15 14:17:26 +0800144 entries = parser.process_parsed_result(root)
Kuang-che Wu67be74b2018-10-15 14:17:26 +0800145 for path, path_spec in entries.items():
Kuang-che Wudedc5922020-12-17 17:19:23 +0800146 if path in latest_default:
147 continue
148 if path in latest_all:
149 logger.warning(
150 'path=%s was removed from default group; assume skip is harmless',
151 path)
Kuang-che Wu67be74b2018-10-15 14:17:26 +0800152 continue
153 if path in removed:
154 continue
155 removed[path] = path_spec
156
157 return removed
158
159
Kuang-che Wu41e8b592018-09-25 17:01:30 +0800160def setup_chromeos_repos(opts, path_factory):
161 chromeos_mirror = path_factory.get_chromeos_mirror()
162 chromeos_tree = path_factory.get_chromeos_tree()
163 subvolume_or_makedirs(opts, chromeos_mirror)
164 subvolume_or_makedirs(opts, chromeos_tree)
165
166 manifest_url = (
167 'https://chrome-internal.googlesource.com/chromeos/manifest-internal')
168 repo_url = 'https://chromium.googlesource.com/external/repo.git'
169
170 if os.path.exists(os.path.join(chromeos_mirror, '.repo', 'manifests')):
171 logger.warning(
172 '%s has already been initialized, assume it is setup properly',
173 chromeos_mirror)
174 else:
175 logger.info('repo init for chromeos mirror')
176 repo_util.init(
177 chromeos_mirror,
178 manifest_url=manifest_url,
179 repo_url=repo_url,
180 mirror=True)
181
Kuang-che Wucc2870b2021-02-18 15:36:00 +0800182 local_manifest_dir = os.path.join(chromeos_mirror,
183 repo_util.LOCAL_MANIFESTS_DIR)
Kuang-che Wu41e8b592018-09-25 17:01:30 +0800184 os.mkdir(local_manifest_dir)
185 with open(os.path.join(local_manifest_dir, 'manifest-versions.xml'),
186 'w') as f:
Kuang-che Wuae6824b2019-08-27 22:20:01 +0800187 f.write("""<?xml version="1.0" encoding="UTF-8"?>
Kuang-che Wu41e8b592018-09-25 17:01:30 +0800188 <manifest>
189 <project name="chromeos/manifest-versions" remote="cros-internal" />
190 </manifest>
Kuang-che Wuae6824b2019-08-27 22:20:01 +0800191 """)
Kuang-che Wu41e8b592018-09-25 17:01:30 +0800192
193 logger.info('repo init for chromeos tree')
194 repo_util.init(
195 chromeos_tree,
196 manifest_url=manifest_url,
197 repo_url=repo_url,
198 reference=chromeos_mirror)
199
Kuang-che Wudc714412018-10-17 16:06:39 +0800200 with locking.lock_file(
201 os.path.join(chromeos_mirror, locking.LOCK_FILE_FOR_MIRROR_SYNC)):
202 logger.info('repo sync for chromeos mirror (this takes hours; be patient)')
203 repo_util.sync(chromeos_mirror)
Kuang-che Wu41e8b592018-09-25 17:01:30 +0800204
205 logger.info('repo sync for chromeos tree')
206 repo_util.sync(chromeos_tree)
207
208
Kuang-che Wu67be74b2018-10-15 14:17:26 +0800209def read_last_sync_time(repo_dir):
210 timestamp_path = os.path.join(repo_dir, 'last_sync_time')
211 if os.path.exists(timestamp_path):
212 with open(timestamp_path) as f:
213 return int(f.read())
214 else:
215 # 4 months should be enough for most bisect cases.
216 return int(time.time()) - 86400 * 120
217
218
219def write_sync_time(repo_dir, sync_time):
220 timestamp_path = os.path.join(repo_dir, 'last_sync_time')
221 with open(timestamp_path, 'w') as f:
222 f.write('%d\n' % sync_time)
223
224
225def write_extra_manifest_to_mirror(repo_dir, removed):
Kuang-che Wucc2870b2021-02-18 15:36:00 +0800226 local_manifest_dir = os.path.join(repo_dir, repo_util.LOCAL_MANIFESTS_DIR)
Kuang-che Wu67be74b2018-10-15 14:17:26 +0800227 if not os.path.exists(local_manifest_dir):
228 os.mkdir(local_manifest_dir)
Kuang-che Wucc2870b2021-02-18 15:36:00 +0800229 with open(os.path.join(local_manifest_dir, MANIFEST_FOR_DELETED), 'w') as f:
Kuang-che Wuae6824b2019-08-27 22:20:01 +0800230 f.write("""<?xml version="1.0" encoding="UTF-8"?>\n<manifest>\n""")
Kuang-che Wu67be74b2018-10-15 14:17:26 +0800231 remotes = {}
232 for path_spec in removed.values():
Kuang-che Wua7ddf9b2019-11-25 18:59:57 +0800233 scheme, netloc, remote_path = urllib.parse.urlsplit(
234 path_spec.repo_url)[:3]
Kuang-che Wu67be74b2018-10-15 14:17:26 +0800235 assert remote_path[0] == '/'
236 remote_path = remote_path[1:]
237 if (scheme, netloc) not in remotes:
238 remote_name = 'remote_for_deleted_repo_%s' % (scheme + netloc)
239 remotes[scheme, netloc] = remote_name
Kuang-che Wuae6824b2019-08-27 22:20:01 +0800240 f.write(""" <remote name="%s" fetch="%s" />\n""" %
Kuang-che Wu67be74b2018-10-15 14:17:26 +0800241 (remote_name, '%s://%s' % (scheme, netloc)))
Kuang-che Wubfa64482018-10-16 11:49:49 +0800242 f.write(
Kuang-che Wuae6824b2019-08-27 22:20:01 +0800243 """ <project name="%s" path="%s" remote="%s" revision="%s" />\n""" %
Kuang-che Wubfa64482018-10-16 11:49:49 +0800244 (remote_path, path_spec.path, remotes[scheme, netloc], path_spec.at))
Kuang-che Wuae6824b2019-08-27 22:20:01 +0800245 f.write("""</manifest>\n""")
Kuang-che Wu67be74b2018-10-15 14:17:26 +0800246
247
Kuang-che Wucc2870b2021-02-18 15:36:00 +0800248def delete_extra_manifest(repo_dir):
249 path = os.path.join(repo_dir, repo_util.LOCAL_MANIFESTS_DIR,
250 MANIFEST_FOR_DELETED)
251 if os.path.exists(path):
252 os.unlink(path)
253
254
Kuang-che Wubfa64482018-10-16 11:49:49 +0800255def generate_extra_manifest_for_deleted_repo(repo_dir, only_branch=None):
256 last_sync_time = read_last_sync_time(repo_dir)
257 removed = collect_removed_manifest_repos(
258 repo_dir, last_sync_time, only_branch=only_branch)
259 write_extra_manifest_to_mirror(repo_dir, removed)
260 logger.info('since last sync, %d repo got removed', len(removed))
Kuang-che Wub76b7f62019-09-16 10:06:18 +0800261 return len(removed)
Kuang-che Wubfa64482018-10-16 11:49:49 +0800262
263
Kuang-che Wu41e8b592018-09-25 17:01:30 +0800264def sync_chromeos_code(opts, path_factory):
265 del opts # unused
266
Kuang-che Wu67be74b2018-10-15 14:17:26 +0800267 start_sync_time = int(time.time())
Kuang-che Wu41e8b592018-09-25 17:01:30 +0800268 chromeos_mirror = path_factory.get_chromeos_mirror()
Kuang-che Wu67be74b2018-10-15 14:17:26 +0800269
Kuang-che Wu67be74b2018-10-15 14:17:26 +0800270 logger.info('repo sync for chromeos mirror')
Kuang-che Wucc2870b2021-02-18 15:36:00 +0800271 delete_extra_manifest(chromeos_mirror)
Kuang-che Wu41e8b592018-09-25 17:01:30 +0800272 repo_util.sync(chromeos_mirror)
Kuang-che Wub76b7f62019-09-16 10:06:18 +0800273 # If there are repos deleted after last sync, generate custom manifest and
274 # sync again for those repos. So we can mirror commits just before the repo
275 # deletion.
276 if generate_extra_manifest_for_deleted_repo(chromeos_mirror) != 0:
277 logger.info('repo sync again')
278 repo_util.sync(chromeos_mirror)
Kuang-che Wua7f29352020-03-02 17:07:53 +0800279
280 # Work around for b/149883148: 'repo' tool does not fetch all branches in
281 # mirror mode.
282 chromiumos_overlay_mirror = os.path.join(
283 chromeos_mirror, 'chromiumos/overlays/chromiumos-overlay.git')
284 git_util.fetch(chromiumos_overlay_mirror, 'cros')
285
Kuang-che Wu67be74b2018-10-15 14:17:26 +0800286 write_sync_time(chromeos_mirror, start_sync_time)
Kuang-che Wu41e8b592018-09-25 17:01:30 +0800287
288 logger.info('repo sync for chromeos tree')
289 chromeos_tree = path_factory.get_chromeos_tree()
290 repo_util.sync(chromeos_tree)
291
Kuang-che Wu41e8b592018-09-25 17:01:30 +0800292
293def query_chrome_latest_branch():
Kuang-che Wud3a4e842019-12-11 12:15:23 +0800294 result = 0
Kuang-che Wua7ddf9b2019-11-25 18:59:57 +0800295 r = urllib.request.urlopen('https://omahaproxy.appspot.com/all')
Kuang-che Wu6bf6ac32020-04-15 01:14:32 +0800296 for row in csv.DictReader(io.TextIOWrapper(r, encoding='utf-8')):
Kuang-che Wu41e8b592018-09-25 17:01:30 +0800297 if row['true_branch'].isdigit():
298 result = max(result, int(row['true_branch']))
299 return result
300
301
302def setup_chrome_repos(opts, path_factory):
303 chrome_cache = path_factory.get_chrome_cache()
304 subvolume_or_makedirs(opts, chrome_cache)
305 chrome_tree = path_factory.get_chrome_tree()
306 subvolume_or_makedirs(opts, chrome_tree)
307
308 latest_branch = query_chrome_latest_branch()
309 logger.info('latest chrome branch is %d', latest_branch)
310 assert latest_branch
Kuang-che Wuae6824b2019-08-27 22:20:01 +0800311 spec = """
Kuang-che Wu41e8b592018-09-25 17:01:30 +0800312solutions = [
313 { "name" : "buildspec",
314 "url" : "https://chrome-internal.googlesource.com/a/chrome/tools/buildspec.git",
315 "deps_file" : "branches/%d/DEPS",
316 "custom_deps" : {
317 },
318 "custom_vars": {'checkout_src_internal': True},
319 },
320]
321target_os = ['chromeos']
322cache_dir = %r
Kuang-che Wuae6824b2019-08-27 22:20:01 +0800323""" % (latest_branch, chrome_cache)
Kuang-che Wu41e8b592018-09-25 17:01:30 +0800324
Kuang-che Wudc714412018-10-17 16:06:39 +0800325 with locking.lock_file(
326 os.path.join(chrome_cache, locking.LOCK_FILE_FOR_MIRROR_SYNC)):
327 logger.info('gclient config for chrome')
328 gclient_util.config(chrome_tree, spec=spec)
Kuang-che Wu41e8b592018-09-25 17:01:30 +0800329
Kuang-che Wudc714412018-10-17 16:06:39 +0800330 is_first_sync = not os.listdir(chrome_cache)
331 if is_first_sync:
332 logger.info('gclient sync for chrome (this takes hours; be patient)')
333 else:
334 logger.info('gclient sync for chrome')
Kuang-che Wu6ee24b52020-11-02 11:33:49 +0800335 gclient_util.sync(chrome_tree, with_branch_heads=True, with_tags=True)
Kuang-che Wu41e8b592018-09-25 17:01:30 +0800336
Kuang-che Wudc714412018-10-17 16:06:39 +0800337 # It's possible that some repos are removed from latest branch and thus
338 # their commit history is not fetched in recent gclient sync. So we call
339 # 'git fetch' for all existing git mirrors.
340 # TODO(kcwu): only sync repos not in DEPS files of latest branch
341 logger.info('additional sync for chrome mirror')
342 for git_repo_name in os.listdir(chrome_cache):
343 # another gclient is running or leftover of previous run; skip
344 if git_repo_name.startswith('_cache_tmp'):
345 continue
346 git_repo = os.path.join(chrome_cache, git_repo_name)
Kuang-che Wu08366542019-01-12 12:37:49 +0800347 if not git_util.is_git_bare_dir(git_repo):
Kuang-che Wudc714412018-10-17 16:06:39 +0800348 continue
Kuang-che Wu2b1286b2019-05-20 20:37:26 +0800349 git_util.fetch(git_repo)
Kuang-che Wu41e8b592018-09-25 17:01:30 +0800350
Kuang-che Wu1e49f512018-12-06 15:27:42 +0800351 # Some repos were removed from the DEPS and won't be synced here. They will
352 # be synced during DEPS file processing because the necessary information
353 # requires full DEPS parsing. (crbug.com/902238)
354
Kuang-che Wu41e8b592018-09-25 17:01:30 +0800355
356def sync_chrome_code(opts, path_factory):
357 # The sync step is identical to the initial gclient config step.
358 setup_chrome_repos(opts, path_factory)
359
360
361def setup_android_repos(opts, path_factory, branch):
362 android_mirror = path_factory.get_android_mirror(branch)
363 android_tree = path_factory.get_android_tree(branch)
364 subvolume_or_makedirs(opts, android_mirror)
365 subvolume_or_makedirs(opts, android_tree)
366
367 manifest_url = ('persistent-https://googleplex-android.git.corp.google.com'
368 '/platform/manifest')
369 repo_url = 'https://gerrit.googlesource.com/git-repo'
370
371 if os.path.exists(os.path.join(android_mirror, '.repo', 'manifests')):
372 logger.warning(
373 '%s has already been initialized, assume it is setup properly',
374 android_mirror)
375 else:
376 logger.info('repo init for android mirror branch=%s', branch)
377 repo_util.init(
378 android_mirror,
379 manifest_url=manifest_url,
380 repo_url=repo_url,
381 manifest_branch=branch,
382 mirror=True)
383
384 logger.info('repo init for android tree branch=%s', branch)
385 repo_util.init(
386 android_tree,
387 manifest_url=manifest_url,
388 repo_url=repo_url,
389 manifest_branch=branch,
390 reference=android_mirror)
391
392 logger.info('repo sync for android mirror (this takes hours; be patient)')
393 repo_util.sync(android_mirror, current_branch=True)
394
395 logger.info('repo sync for android tree branch=%s', branch)
396 repo_util.sync(android_tree, current_branch=True)
397
398
399def sync_android_code(opts, path_factory, branch):
400 del opts # unused
Kuang-che Wu67be74b2018-10-15 14:17:26 +0800401 start_sync_time = int(time.time())
Kuang-che Wu41e8b592018-09-25 17:01:30 +0800402 android_mirror = path_factory.get_android_mirror(branch)
403 android_tree = path_factory.get_android_tree(branch)
404
Kuang-che Wudc714412018-10-17 16:06:39 +0800405 with locking.lock_file(
406 os.path.join(android_mirror, locking.LOCK_FILE_FOR_MIRROR_SYNC)):
407 logger.info('repo sync for android mirror branch=%s', branch)
Kuang-che Wucc2870b2021-02-18 15:36:00 +0800408 delete_extra_manifest(android_mirror)
Kuang-che Wub76b7f62019-09-16 10:06:18 +0800409 repo_util.sync(android_mirror, current_branch=True)
Kuang-che Wudc714412018-10-17 16:06:39 +0800410 # Android usually big jump between milestone releases and add/delete lots of
411 # repos when switch releases. Because it's infeasible to bisect between such
412 # big jump, the deleted repo is useless. In order to save disk, do not sync
413 # repos deleted in other branches.
Kuang-che Wub76b7f62019-09-16 10:06:18 +0800414 if generate_extra_manifest_for_deleted_repo(
415 android_mirror, only_branch=branch) != 0:
416 logger.info('repo sync again')
417 repo_util.sync(android_mirror, current_branch=True)
Kuang-che Wudc714412018-10-17 16:06:39 +0800418 write_sync_time(android_mirror, start_sync_time)
Kuang-che Wu41e8b592018-09-25 17:01:30 +0800419
420 logger.info('repo sync for android tree branch=%s', branch)
421 repo_util.sync(android_tree, current_branch=True)
422
423
424def cmd_init(opts):
425 path_factory = DefaultProjectPathFactory(opts.mirror_base, opts.work_base,
426 CHECKOUT_TEMPLATE_NAME)
427
428 if opts.chromeos:
429 setup_chromeos_repos(opts, path_factory)
430 if opts.chrome:
431 setup_chrome_repos(opts, path_factory)
432 for branch in opts.android:
433 setup_android_repos(opts, path_factory, branch)
434
435
436def enumerate_android_branches_available(base):
437 branches = []
438 for name in os.listdir(base):
439 if name.startswith('android.'):
440 branches.append(name.partition('.')[2])
441 return branches
442
443
Kuang-che Wu22f207e2019-02-23 12:53:53 +0800444def do_sync(opts):
Kuang-che Wu41e8b592018-09-25 17:01:30 +0800445 path_factory = DefaultProjectPathFactory(opts.mirror_base, opts.work_base,
446 CHECKOUT_TEMPLATE_NAME)
447
448 sync_all = False
449 if not opts.chromeos and not opts.chrome and not opts.android:
450 logger.info('sync trees for all')
451 sync_all = True
452
453 if sync_all or opts.chromeos:
454 sync_chromeos_code(opts, path_factory)
455 if sync_all or opts.chrome:
456 sync_chrome_code(opts, path_factory)
457
458 if sync_all:
459 android_branches = enumerate_android_branches_available(opts.mirror_base)
460 else:
461 android_branches = opts.android
462 for branch in android_branches:
463 sync_android_code(opts, path_factory, branch)
464
465
Kuang-che Wu22f207e2019-02-23 12:53:53 +0800466def cmd_sync(opts):
467 try:
468 do_sync(opts)
469 except subprocess.CalledProcessError:
470 # Sync may fail due to network or server issues.
471 logger.exception('do_sync failed, will retry one minute later')
472 time.sleep(60)
473 do_sync(opts)
474
475
Kuang-che Wu41e8b592018-09-25 17:01:30 +0800476def cmd_new(opts):
477 work_dir = os.path.join(opts.work_base, opts.session)
478 if not os.path.exists(work_dir):
479 os.makedirs(work_dir)
480
481 template_factory = DefaultProjectPathFactory(opts.mirror_base, opts.work_base,
482 CHECKOUT_TEMPLATE_NAME)
483 path_factory = DefaultProjectPathFactory(opts.mirror_base, opts.work_base,
484 opts.session)
485
486 prepare_all = False
487 if not opts.chromeos and not opts.chrome and not opts.android:
488 logger.info('prepare trees for all')
489 prepare_all = True
490
491 chromeos_template = template_factory.get_chromeos_tree()
492 if (prepare_all and os.path.exists(chromeos_template)) or opts.chromeos:
493 logger.info('prepare tree for chromeos, %s',
494 path_factory.get_chromeos_tree())
495 snapshot_or_copytree(chromeos_template, path_factory.get_chromeos_tree())
496
497 chrome_template = template_factory.get_chrome_tree()
498 if (prepare_all and os.path.exists(chrome_template)) or opts.chrome:
499 logger.info('prepare tree for chrome, %s', path_factory.get_chrome_tree())
500 snapshot_or_copytree(chrome_template, path_factory.get_chrome_tree())
501
502 if prepare_all:
503 android_branches = enumerate_android_branches_available(opts.mirror_base)
504 else:
505 android_branches = opts.android
506 for branch in android_branches:
507 logger.info('prepare tree for android branch=%s, %s', branch,
508 path_factory.get_android_tree(branch))
509 snapshot_or_copytree(
510 template_factory.get_android_tree(branch),
511 path_factory.get_android_tree(branch))
512
513
514def delete_tree(path):
515 if is_btrfs_subvolume(path):
Kuang-che Wu9d3ccde2019-01-03 17:06:09 +0800516 # btrfs should be mounted with 'user_subvol_rm_allowed' option and thus
517 # normal user permission is enough.
Kuang-che Wu41e8b592018-09-25 17:01:30 +0800518 util.check_call('btrfs', 'subvolume', 'delete', path)
519 else:
Kuang-che Wu9d3ccde2019-01-03 17:06:09 +0800520 util.check_call('sudo', 'rm', '-rf', path)
Kuang-che Wu41e8b592018-09-25 17:01:30 +0800521
522
523def cmd_list(opts):
524 print('%-20s %s' % ('Session', 'Path'))
525 for name in os.listdir(opts.work_base):
526 if name == CHECKOUT_TEMPLATE_NAME:
527 continue
528 path = os.path.join(opts.work_base, name)
529 print('%-20s %s' % (name, path))
530
531
532def cmd_delete(opts):
533 assert opts.session
534 path_factory = DefaultProjectPathFactory(opts.mirror_base, opts.work_base,
535 opts.session)
536
537 chromeos_tree = path_factory.get_chromeos_tree()
538 if os.path.exists(chromeos_tree):
539 if os.path.exists(os.path.join(chromeos_tree, 'chromite')):
540 # ignore error
541 util.call('cros_sdk', '--unmount', cwd=chromeos_tree)
542 delete_tree(chromeos_tree)
543
544 chrome_tree = path_factory.get_chrome_tree()
545 if os.path.exists(chrome_tree):
546 delete_tree(chrome_tree)
547
548 android_branches = enumerate_android_branches_available(opts.mirror_base)
549 for branch in android_branches:
550 android_tree = path_factory.get_android_tree(branch)
551 if os.path.exists(android_tree):
552 delete_tree(android_tree)
553
554 os.rmdir(os.path.join(opts.work_base, opts.session))
555
Zheng-Jie Changcd424c12020-01-10 14:32:08 +0800556 # remove caches
557 chromeos_root = os.getenv('DEFAULT_CHROMEOS_ROOT')
558 if chromeos_root:
559 path = os.path.join(chromeos_root, 'devserver/static')
560 if os.path.exists(path):
Kuang-che Wud2747442020-07-01 16:50:24 +0800561 logger.debug('remove cache (cros flash): %s', path)
Zheng-Jie Changcd424c12020-01-10 14:32:08 +0800562 util.call('cros', 'clean', '--flash', cwd=path)
563
564 for path in glob.glob(os.path.join(chromeos_root, 'chroot/tmp/*')):
Kuang-che Wud2747442020-07-01 16:50:24 +0800565 logger.debug('remove cache (chroot/tmp): %s', path)
Zheng-Jie Changcd424c12020-01-10 14:32:08 +0800566 delete_tree(path)
567
Zheng-Jie Changd164da42020-03-12 10:33:23 +0800568 for path in glob.glob(os.path.join(chromeos_root, 'tmp/*')):
Kuang-che Wud2747442020-07-01 16:50:24 +0800569 logger.debug('remove cache (chromeos root tmp): %s', path)
Zheng-Jie Changd164da42020-03-12 10:33:23 +0800570 delete_tree(path)
571
Zheng-Jie Changcd424c12020-01-10 14:32:08 +0800572 for path in glob.glob(
573 os.path.join(path_factory.get_chrome_cache(), '_cache_*')):
Kuang-che Wud2747442020-07-01 16:50:24 +0800574 logger.debug('remove cache (chrome gclient cache): %s', path)
Zheng-Jie Changcd424c12020-01-10 14:32:08 +0800575 delete_tree(path)
576
Kuang-che Wu41e8b592018-09-25 17:01:30 +0800577
578def create_parser():
Kuang-che Wud2d6e412021-01-28 16:26:41 +0800579 base_parser = argparse.ArgumentParser(add_help=False)
580 base_parser.add_argument(
Kuang-che Wu41e8b592018-09-25 17:01:30 +0800581 '--mirror_base',
582 metavar='MIRROR_BASE',
583 default=configure.get('MIRROR_BASE', DEFAULT_MIRROR_BASE),
584 help='Directory for mirrors (default: %(default)s)')
Kuang-che Wud2d6e412021-01-28 16:26:41 +0800585 base_parser.add_argument(
Kuang-che Wu41e8b592018-09-25 17:01:30 +0800586 '--work_base',
587 metavar='WORK_BASE',
588 default=configure.get('WORK_BASE', DEFAULT_WORK_BASE),
Kuang-che Wud2d6e412021-01-28 16:26:41 +0800589 help='Directory for bisection working directories'
590 ' (default: %(default)s)')
591 parents_session_optional = [
592 common.common_argument_parser, common.session_optional_parser, base_parser
593 ]
594 parents_session_required = [
595 common.common_argument_parser, common.session_required_parser, base_parser
596 ]
597
598 parser = argparse.ArgumentParser(
599 formatter_class=argparse.RawDescriptionHelpFormatter, description=__doc__)
Kuang-che Wu41e8b592018-09-25 17:01:30 +0800600 subparsers = parser.add_subparsers(
Kuang-che Wud2d6e412021-01-28 16:26:41 +0800601 dest='command', title='commands', metavar='<command>', required=True)
Kuang-che Wu41e8b592018-09-25 17:01:30 +0800602
603 parser_init = subparsers.add_parser(
Kuang-che Wud2d6e412021-01-28 16:26:41 +0800604 'init',
605 help='Mirror source trees and create template checkout',
606 parents=parents_session_optional)
Kuang-che Wu41e8b592018-09-25 17:01:30 +0800607 parser_init.add_argument(
608 '--chrome', action='store_true', help='init chrome mirror and tree')
609 parser_init.add_argument(
610 '--chromeos', action='store_true', help='init chromeos mirror and tree')
611 parser_init.add_argument(
612 '--android',
613 metavar='BRANCH',
614 action='append',
615 default=[],
616 help='init android mirror and tree of BRANCH')
617 parser_init.add_argument(
618 '--btrfs',
619 action='store_true',
620 help='create btrfs subvolume for source tree')
621 parser_init.set_defaults(func=cmd_init)
622
623 parser_sync = subparsers.add_parser(
624 'sync',
625 help='Sync source trees',
626 description='Sync all if no projects are specified '
Kuang-che Wud2d6e412021-01-28 16:26:41 +0800627 '(--chrome, --chromeos, or --android)',
628 parents=parents_session_optional)
Kuang-che Wu41e8b592018-09-25 17:01:30 +0800629 parser_sync.add_argument(
630 '--chrome', action='store_true', help='sync chrome mirror and tree')
631 parser_sync.add_argument(
632 '--chromeos', action='store_true', help='sync chromeos mirror and tree')
633 parser_sync.add_argument(
634 '--android',
635 metavar='BRANCH',
636 action='append',
637 default=[],
638 help='sync android mirror and tree of BRANCH')
639 parser_sync.set_defaults(func=cmd_sync)
640
641 parser_new = subparsers.add_parser(
642 'new',
643 help='Create new source checkout for bisect',
644 description='Create for all if no projects are specified '
Kuang-che Wud2d6e412021-01-28 16:26:41 +0800645 '(--chrome, --chromeos, or --android)',
646 parents=parents_session_required)
Kuang-che Wu41e8b592018-09-25 17:01:30 +0800647 parser_new.add_argument(
648 '--chrome', action='store_true', help='create chrome checkout')
649 parser_new.add_argument(
650 '--chromeos', action='store_true', help='create chromeos checkout')
651 parser_new.add_argument(
652 '--android',
653 metavar='BRANCH',
654 action='append',
655 default=[],
656 help='create android checkout of BRANCH')
657 parser_new.set_defaults(func=cmd_new)
658
659 parser_list = subparsers.add_parser(
Kuang-che Wud2d6e412021-01-28 16:26:41 +0800660 'list',
661 help='List existing sessions with source checkout',
662 parents=parents_session_optional)
Kuang-che Wu41e8b592018-09-25 17:01:30 +0800663 parser_list.set_defaults(func=cmd_list)
664
Kuang-che Wud2d6e412021-01-28 16:26:41 +0800665 parser_delete = subparsers.add_parser(
666 'delete', help='Delete source checkout', parents=parents_session_required)
Kuang-che Wu41e8b592018-09-25 17:01:30 +0800667 parser_delete.set_defaults(func=cmd_delete)
668
669 return parser
670
671
672def main():
673 common.init()
674 parser = create_parser()
675 opts = parser.parse_args()
676 common.config_logging(opts)
Kuang-che Wu41e8b592018-09-25 17:01:30 +0800677 opts.func(opts)
678
679
680if __name__ == '__main__':
681 main()