blob: 3e693189b6eb1d394b92952c1d6706bfb435bb4c [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
29import urllib2
30
31from bisect_kit import common
32from bisect_kit import configure
33from bisect_kit import gclient_util
34from bisect_kit import git_util
35from bisect_kit import repo_util
36from bisect_kit import util
37
38DEFAULT_MIRROR_BASE = os.path.expanduser('~/git-mirrors')
39DEFAULT_WORK_BASE = os.path.expanduser('~/bisect-workdir')
40CHECKOUT_TEMPLATE_NAME = 'template'
41
42logger = logging.getLogger(__name__)
43
44
45class DefaultProjectPathFactory(object):
46 """Factory for chromeos/chrome/android source tree paths."""
47
48 def __init__(self, mirror_base, work_base, session):
49 self.mirror_base = mirror_base
50 self.work_base = work_base
51 self.session = session
52
53 def get_chromeos_mirror(self):
54 return os.path.join(self.mirror_base, 'chromeos')
55
56 def get_chromeos_tree(self):
57 return os.path.join(self.work_base, self.session, 'chromeos')
58
59 def get_android_mirror(self, branch):
60 return os.path.join(self.mirror_base, 'android.%s' % branch)
61
62 def get_android_tree(self, branch):
63 return os.path.join(self.work_base, self.session, 'android.%s' % branch)
64
65 def get_chrome_cache(self):
66 return os.path.join(self.mirror_base, 'chrome')
67
68 def get_chrome_tree(self):
69 return os.path.join(self.work_base, self.session, 'chrome')
70
71
72def subvolume_or_makedirs(opts, path):
73 if os.path.exists(path):
74 return
75
76 path = os.path.abspath(path)
77 if opts.btrfs:
78 dirname, basename = os.path.split(path)
79 if not os.path.exists(dirname):
80 os.makedirs(dirname)
81 util.check_call('btrfs', 'subvolume', 'create', basename, cwd=dirname)
82 else:
83 os.makedirs(path)
84
85
86def is_btrfs_subvolume(path):
87 if util.check_output('stat', '-f', '--format=%T', path).strip() != 'btrfs':
88 return False
89 return util.check_output('stat', '--format=%i', path).strip() == '256'
90
91
92def snapshot_or_copytree(src, dst):
93 assert os.path.isdir(src), '%s does not exist' % src
94 assert os.path.isdir(os.path.dirname(dst))
95
96 # Make sure dst do not exist, otherwise it becomes "dst/name" (one extra
97 # depth) instead of "dst".
98 assert not os.path.exists(dst)
99
100 if is_btrfs_subvolume(src):
101 util.check_call('btrfs', 'subvolume', 'snapshot', src, dst)
102 else:
103 # -a for recursion and preserve all attributes.
104 util.check_call('cp', '-a', src, dst)
105
106
107def setup_chromeos_repos(opts, path_factory):
108 chromeos_mirror = path_factory.get_chromeos_mirror()
109 chromeos_tree = path_factory.get_chromeos_tree()
110 subvolume_or_makedirs(opts, chromeos_mirror)
111 subvolume_or_makedirs(opts, chromeos_tree)
112
113 manifest_url = (
114 'https://chrome-internal.googlesource.com/chromeos/manifest-internal')
115 repo_url = 'https://chromium.googlesource.com/external/repo.git'
116
117 if os.path.exists(os.path.join(chromeos_mirror, '.repo', 'manifests')):
118 logger.warning(
119 '%s has already been initialized, assume it is setup properly',
120 chromeos_mirror)
121 else:
122 logger.info('repo init for chromeos mirror')
123 repo_util.init(
124 chromeos_mirror,
125 manifest_url=manifest_url,
126 repo_url=repo_url,
127 mirror=True)
128
129 local_manifest_dir = os.path.join(chromeos_mirror, '.repo',
130 'local_manifests')
131 os.mkdir(local_manifest_dir)
132 with open(os.path.join(local_manifest_dir, 'manifest-versions.xml'),
133 'w') as f:
134 f.write('''<?xml version="1.0" encoding="UTF-8"?>
135 <manifest>
136 <project name="chromeos/manifest-versions" remote="cros-internal" />
137 </manifest>
138 ''')
139
140 logger.info('repo init for chromeos tree')
141 repo_util.init(
142 chromeos_tree,
143 manifest_url=manifest_url,
144 repo_url=repo_url,
145 reference=chromeos_mirror)
146
147 logger.info('repo sync for chromeos mirror (this takes hours; be patient)')
148 repo_util.sync(chromeos_mirror)
149
150 logger.info('repo sync for chromeos tree')
151 repo_util.sync(chromeos_tree)
152
153
154def sync_chromeos_code(opts, path_factory):
155 del opts # unused
156
157 logger.info('repo sync for chromeos mirror')
158 chromeos_mirror = path_factory.get_chromeos_mirror()
159 repo_util.sync(chromeos_mirror)
160
161 logger.info('repo sync for chromeos tree')
162 chromeos_tree = path_factory.get_chromeos_tree()
163 repo_util.sync(chromeos_tree)
164
165 # test_that may use this ssh key and ssh complains its permission is too open
166 util.check_call(
167 'chmod',
168 'o-r,g-r',
169 'src/scripts/mod_for_test_scripts/ssh_keys/testing_rsa',
170 cwd=chromeos_tree)
171
172
173def query_chrome_latest_branch():
174 result = None
175 r = urllib2.urlopen('https://omahaproxy.appspot.com/all')
176 for row in csv.DictReader(r):
177 if row['true_branch'].isdigit():
178 result = max(result, int(row['true_branch']))
179 return result
180
181
182def setup_chrome_repos(opts, path_factory):
183 chrome_cache = path_factory.get_chrome_cache()
184 subvolume_or_makedirs(opts, chrome_cache)
185 chrome_tree = path_factory.get_chrome_tree()
186 subvolume_or_makedirs(opts, chrome_tree)
187
188 latest_branch = query_chrome_latest_branch()
189 logger.info('latest chrome branch is %d', latest_branch)
190 assert latest_branch
191 spec = '''
192solutions = [
193 { "name" : "buildspec",
194 "url" : "https://chrome-internal.googlesource.com/a/chrome/tools/buildspec.git",
195 "deps_file" : "branches/%d/DEPS",
196 "custom_deps" : {
197 },
198 "custom_vars": {'checkout_src_internal': True},
199 },
200]
201target_os = ['chromeos']
202cache_dir = %r
203''' % (latest_branch, chrome_cache)
204
205 logger.info('gclient config for chrome')
206 gclient_util.config(chrome_tree, spec=spec)
207
208 is_first_sync = not os.listdir(chrome_cache)
209 if is_first_sync:
210 logger.info('gclient sync for chrome (this takes hours; be patient)')
211 else:
212 logger.info('gclient sync for chrome')
213 gclient_util.sync(chrome_tree, with_branch_heads=True, with_tags=True)
214
215 # It's possible that some repos are removed from latest branch and thus their
216 # commit history is not fetched in recent gclient sync. So we call 'git
217 # fetch' for all existing git mirrors.
218 # TODO(kcwu): only sync repos not in DEPS files of latest branch
219 logger.info('additional sync for chrome mirror')
220 for git_repo_name in os.listdir(chrome_cache):
221 # another gclient is running or leftover of previous run; skip
222 if git_repo_name.startswith('_cache_tmp'):
223 continue
224 git_repo = os.path.join(chrome_cache, git_repo_name)
225 if not git_util.is_git_rev(git_repo):
226 continue
227 util.check_call('git', 'fetch', cwd=git_repo)
228
229
230def sync_chrome_code(opts, path_factory):
231 # The sync step is identical to the initial gclient config step.
232 setup_chrome_repos(opts, path_factory)
233
234
235def setup_android_repos(opts, path_factory, branch):
236 android_mirror = path_factory.get_android_mirror(branch)
237 android_tree = path_factory.get_android_tree(branch)
238 subvolume_or_makedirs(opts, android_mirror)
239 subvolume_or_makedirs(opts, android_tree)
240
241 manifest_url = ('persistent-https://googleplex-android.git.corp.google.com'
242 '/platform/manifest')
243 repo_url = 'https://gerrit.googlesource.com/git-repo'
244
245 if os.path.exists(os.path.join(android_mirror, '.repo', 'manifests')):
246 logger.warning(
247 '%s has already been initialized, assume it is setup properly',
248 android_mirror)
249 else:
250 logger.info('repo init for android mirror branch=%s', branch)
251 repo_util.init(
252 android_mirror,
253 manifest_url=manifest_url,
254 repo_url=repo_url,
255 manifest_branch=branch,
256 mirror=True)
257
258 logger.info('repo init for android tree branch=%s', branch)
259 repo_util.init(
260 android_tree,
261 manifest_url=manifest_url,
262 repo_url=repo_url,
263 manifest_branch=branch,
264 reference=android_mirror)
265
266 logger.info('repo sync for android mirror (this takes hours; be patient)')
267 repo_util.sync(android_mirror, current_branch=True)
268
269 logger.info('repo sync for android tree branch=%s', branch)
270 repo_util.sync(android_tree, current_branch=True)
271
272
273def sync_android_code(opts, path_factory, branch):
274 del opts # unused
275 android_mirror = path_factory.get_android_mirror(branch)
276 android_tree = path_factory.get_android_tree(branch)
277
278 logger.info('repo sync for android mirror branch=%s', branch)
279 repo_util.sync(android_mirror, current_branch=True)
280
281 logger.info('repo sync for android tree branch=%s', branch)
282 repo_util.sync(android_tree, current_branch=True)
283
284
285def cmd_init(opts):
286 path_factory = DefaultProjectPathFactory(opts.mirror_base, opts.work_base,
287 CHECKOUT_TEMPLATE_NAME)
288
289 if opts.chromeos:
290 setup_chromeos_repos(opts, path_factory)
291 if opts.chrome:
292 setup_chrome_repos(opts, path_factory)
293 for branch in opts.android:
294 setup_android_repos(opts, path_factory, branch)
295
296
297def enumerate_android_branches_available(base):
298 branches = []
299 for name in os.listdir(base):
300 if name.startswith('android.'):
301 branches.append(name.partition('.')[2])
302 return branches
303
304
305def cmd_sync(opts):
306 path_factory = DefaultProjectPathFactory(opts.mirror_base, opts.work_base,
307 CHECKOUT_TEMPLATE_NAME)
308
309 sync_all = False
310 if not opts.chromeos and not opts.chrome and not opts.android:
311 logger.info('sync trees for all')
312 sync_all = True
313
314 if sync_all or opts.chromeos:
315 sync_chromeos_code(opts, path_factory)
316 if sync_all or opts.chrome:
317 sync_chrome_code(opts, path_factory)
318
319 if sync_all:
320 android_branches = enumerate_android_branches_available(opts.mirror_base)
321 else:
322 android_branches = opts.android
323 for branch in android_branches:
324 sync_android_code(opts, path_factory, branch)
325
326
327def cmd_new(opts):
328 work_dir = os.path.join(opts.work_base, opts.session)
329 if not os.path.exists(work_dir):
330 os.makedirs(work_dir)
331
332 template_factory = DefaultProjectPathFactory(opts.mirror_base, opts.work_base,
333 CHECKOUT_TEMPLATE_NAME)
334 path_factory = DefaultProjectPathFactory(opts.mirror_base, opts.work_base,
335 opts.session)
336
337 prepare_all = False
338 if not opts.chromeos and not opts.chrome and not opts.android:
339 logger.info('prepare trees for all')
340 prepare_all = True
341
342 chromeos_template = template_factory.get_chromeos_tree()
343 if (prepare_all and os.path.exists(chromeos_template)) or opts.chromeos:
344 logger.info('prepare tree for chromeos, %s',
345 path_factory.get_chromeos_tree())
346 snapshot_or_copytree(chromeos_template, path_factory.get_chromeos_tree())
347
348 chrome_template = template_factory.get_chrome_tree()
349 if (prepare_all and os.path.exists(chrome_template)) or opts.chrome:
350 logger.info('prepare tree for chrome, %s', path_factory.get_chrome_tree())
351 snapshot_or_copytree(chrome_template, path_factory.get_chrome_tree())
352
353 if prepare_all:
354 android_branches = enumerate_android_branches_available(opts.mirror_base)
355 else:
356 android_branches = opts.android
357 for branch in android_branches:
358 logger.info('prepare tree for android branch=%s, %s', branch,
359 path_factory.get_android_tree(branch))
360 snapshot_or_copytree(
361 template_factory.get_android_tree(branch),
362 path_factory.get_android_tree(branch))
363
364
365def delete_tree(path):
366 if is_btrfs_subvolume(path):
367 util.check_call('btrfs', 'subvolume', 'delete', path)
368 else:
369 util.check_call('rm', '-rf', path)
370
371
372def cmd_list(opts):
373 print('%-20s %s' % ('Session', 'Path'))
374 for name in os.listdir(opts.work_base):
375 if name == CHECKOUT_TEMPLATE_NAME:
376 continue
377 path = os.path.join(opts.work_base, name)
378 print('%-20s %s' % (name, path))
379
380
381def cmd_delete(opts):
382 assert opts.session
383 path_factory = DefaultProjectPathFactory(opts.mirror_base, opts.work_base,
384 opts.session)
385
386 chromeos_tree = path_factory.get_chromeos_tree()
387 if os.path.exists(chromeos_tree):
388 if os.path.exists(os.path.join(chromeos_tree, 'chromite')):
389 # ignore error
390 util.call('cros_sdk', '--unmount', cwd=chromeos_tree)
391 delete_tree(chromeos_tree)
392
393 chrome_tree = path_factory.get_chrome_tree()
394 if os.path.exists(chrome_tree):
395 delete_tree(chrome_tree)
396
397 android_branches = enumerate_android_branches_available(opts.mirror_base)
398 for branch in android_branches:
399 android_tree = path_factory.get_android_tree(branch)
400 if os.path.exists(android_tree):
401 delete_tree(android_tree)
402
403 os.rmdir(os.path.join(opts.work_base, opts.session))
404
405
406def create_parser():
407 parser = argparse.ArgumentParser(
408 formatter_class=argparse.RawDescriptionHelpFormatter, description=__doc__)
409 parser.add_argument(
410 '--mirror_base',
411 metavar='MIRROR_BASE',
412 default=configure.get('MIRROR_BASE', DEFAULT_MIRROR_BASE),
413 help='Directory for mirrors (default: %(default)s)')
414 parser.add_argument(
415 '--work_base',
416 metavar='WORK_BASE',
417 default=configure.get('WORK_BASE', DEFAULT_WORK_BASE),
418 help='Directory for bisection working directories (default: %(default)s)')
419 common.add_common_arguments(parser)
420 subparsers = parser.add_subparsers(
421 dest='command', title='commands', metavar='<command>')
422
423 parser_init = subparsers.add_parser(
424 'init', help='Mirror source trees and create template checkout')
425 parser_init.add_argument(
426 '--chrome', action='store_true', help='init chrome mirror and tree')
427 parser_init.add_argument(
428 '--chromeos', action='store_true', help='init chromeos mirror and tree')
429 parser_init.add_argument(
430 '--android',
431 metavar='BRANCH',
432 action='append',
433 default=[],
434 help='init android mirror and tree of BRANCH')
435 parser_init.add_argument(
436 '--btrfs',
437 action='store_true',
438 help='create btrfs subvolume for source tree')
439 parser_init.set_defaults(func=cmd_init)
440
441 parser_sync = subparsers.add_parser(
442 'sync',
443 help='Sync source trees',
444 description='Sync all if no projects are specified '
445 '(--chrome, --chromeos, or --android)')
446 parser_sync.add_argument(
447 '--chrome', action='store_true', help='sync chrome mirror and tree')
448 parser_sync.add_argument(
449 '--chromeos', action='store_true', help='sync chromeos mirror and tree')
450 parser_sync.add_argument(
451 '--android',
452 metavar='BRANCH',
453 action='append',
454 default=[],
455 help='sync android mirror and tree of BRANCH')
456 parser_sync.set_defaults(func=cmd_sync)
457
458 parser_new = subparsers.add_parser(
459 'new',
460 help='Create new source checkout for bisect',
461 description='Create for all if no projects are specified '
462 '(--chrome, --chromeos, or --android)')
463 parser_new.add_argument('--session', required=True)
464 parser_new.add_argument(
465 '--chrome', action='store_true', help='create chrome checkout')
466 parser_new.add_argument(
467 '--chromeos', action='store_true', help='create chromeos checkout')
468 parser_new.add_argument(
469 '--android',
470 metavar='BRANCH',
471 action='append',
472 default=[],
473 help='create android checkout of BRANCH')
474 parser_new.set_defaults(func=cmd_new)
475
476 parser_list = subparsers.add_parser(
477 'list', help='List existing sessions with source checkout')
478 parser_list.set_defaults(func=cmd_list)
479
480 parser_delete = subparsers.add_parser('delete', help='Delete source checkout')
481 parser_delete.add_argument('--session', required=True)
482 parser_delete.set_defaults(func=cmd_delete)
483
484 return parser
485
486
487def main():
488 common.init()
489 parser = create_parser()
490 opts = parser.parse_args()
491 common.config_logging(opts)
492
493 opts.func(opts)
494
495
496if __name__ == '__main__':
497 main()