blob: f73413a3ef7d3a6c3b1d7a24637ad3dc0ecbca82 [file] [log] [blame]
Kuang-che Wu2ea804f2017-11-28 17:11:41 +08001# Copyright 2017 The Chromium OS Authors. All rights reserved.
2# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4"""ChromeOS utility.
5
6Terminology used in this module.
7 short_version: ChromeOS version number without milestone, like "9876.0.0".
8 full_version: ChromeOS version number with milestone, like "R62-9876.0.0".
9 version: if not specified, it could be in short or full format.
10"""
11
12from __future__ import print_function
13import errno
14import json
15import logging
16import os
17import re
18import subprocess
19import time
20
21from bisect_kit import cli
Kuang-che Wu2ea804f2017-11-28 17:11:41 +080022from bisect_kit import core
Kuang-che Wubfc4a642018-04-19 11:54:08 +080023from bisect_kit import git_util
24from bisect_kit import repo_util
Kuang-che Wu2ea804f2017-11-28 17:11:41 +080025from bisect_kit import util
26
27logger = logging.getLogger(__name__)
28
Kuang-che Wu2ea804f2017-11-28 17:11:41 +080029re_chromeos_full_version = r'^R\d+-\d+\.\d+\.\d+$'
Kuang-che Wuacb6efd2018-04-25 18:52:58 +080030re_chromeos_localbuild_version = r'^\d+\.\d+\.\d{4}_\d\d_\d\d_\d{4}$'
31re_chromeos_short_version = r'^\d+\.\d+\.\d+$'
Kuang-che Wu2ea804f2017-11-28 17:11:41 +080032
33gs_archive_path = 'gs://chromeos-image-archive/{board}-release'
34gs_release_path = (
35 'gs://chromeos-releases/{channel}-channel/{board}/{short_version}')
36
37# Assume gsutil is in PATH.
38gsutil_bin = 'gsutil'
39
40VERSION_KEY_CROS_SHORT_VERSION = 'cros_short_version'
41VERSION_KEY_CROS_FULL_VERSION = 'cros_full_version'
42VERSION_KEY_MILESTONE = 'milestone'
43VERSION_KEY_CR_VERSION = 'cr_version'
Kuang-che Wu708310b2018-03-28 17:24:34 +080044VERSION_KEY_ANDROID_BUILD_ID = 'android_build_id'
Kuang-che Wu2ea804f2017-11-28 17:11:41 +080045VERSION_KEY_ANDROID_BRANCH = 'android_branch'
46
47
48def is_cros_short_version(s):
Kuang-che Wuacb6efd2018-04-25 18:52:58 +080049 """Determines if `s` is chromeos short version.
50
51 This function doesn't accept version number of local build.
52 """
Kuang-che Wu2ea804f2017-11-28 17:11:41 +080053 return bool(re.match(re_chromeos_short_version, s))
54
55
Kuang-che Wuacb6efd2018-04-25 18:52:58 +080056def is_cros_localbuild_version(s):
57 """Determines if `s` is chromeos local build version."""
58 return bool(re.match(re_chromeos_localbuild_version, s))
59
60
Kuang-che Wu2ea804f2017-11-28 17:11:41 +080061def is_cros_full_version(s):
Kuang-che Wuacb6efd2018-04-25 18:52:58 +080062 """Determines if `s` is chromeos full version.
63
64 This function doesn't accept version number of local build.
65 """
Kuang-che Wu2ea804f2017-11-28 17:11:41 +080066 return bool(re.match(re_chromeos_full_version, s))
67
68
69def is_cros_version(s):
70 """Determines if `s` is chromeos version (either short or full)"""
71 return is_cros_short_version(s) or is_cros_full_version(s)
72
73
74def make_cros_full_version(milestone, short_version):
75 """Makes full_version from milestone and short_version"""
Kuang-che Wuacb6efd2018-04-25 18:52:58 +080076 assert milestone
Kuang-che Wu2ea804f2017-11-28 17:11:41 +080077 return 'R%s-%s' % (milestone, short_version)
78
79
80def version_split(full_version):
81 """Splits full_version into milestone and short_version"""
82 assert is_cros_full_version(full_version)
83 milestone, short_version = full_version.split('-')
84 return milestone[1:], short_version
85
86
87def argtype_cros_version(s):
88 if not is_cros_version(s):
89 msg = 'invalid cros version'
90 raise cli.ArgTypeError(msg, '9876.0.0 or R62-9876.0.0')
91 return s
92
93
94def query_dut_lsb_release(host):
95 """Query /etc/lsb-release of given DUT
96
97 Args:
98 host: the DUT address
99
100 Returns:
101 dict for keys and values of /etc/lsb-release. Return empty dict if failed.
102 """
103 try:
104 output = util.check_output('ssh', host, 'cat', '/etc/lsb-release')
105 except subprocess.CalledProcessError:
106 return {}
107 return dict(re.findall(r'^(\w+)=(.*)$', output, re.M))
108
109
110def is_dut(host):
Kuang-che Wubfc4a642018-04-19 11:54:08 +0800111 """Determines whether a host is a chromeos device.
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800112
113 Args:
114 host: the DUT address
115
116 Returns:
117 True if the host is a chromeos device.
118 """
119 return query_dut_lsb_release(host).get('DEVICETYPE') in [
120 'CHROMEBASE',
121 'CHROMEBIT',
122 'CHROMEBOOK',
123 'CHROMEBOX',
124 'REFERENCE',
125 ]
126
127
128def query_dut_board(host):
129 """Query board name of a given DUT"""
130 return query_dut_lsb_release(host).get('CHROMEOS_RELEASE_BOARD')
131
132
133def query_dut_short_version(host):
Kuang-che Wuacb6efd2018-04-25 18:52:58 +0800134 """Query short version of a given DUT.
135
136 This function may return version of local build, which
137 is_cros_short_version() is false.
138 """
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800139 return query_dut_lsb_release(host).get('CHROMEOS_RELEASE_VERSION')
140
141
142def query_dut_boot_id(host, connect_timeout=None):
Kuang-che Wubfc4a642018-04-19 11:54:08 +0800143 """Query boot id.
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800144
145 Args:
146 host: DUT address
147 connect_timeout: connection timeout
148
149 Returns:
150 boot uuid
151 """
152 cmd = ['ssh']
153 if connect_timeout:
154 cmd += ['-oConnectTimeout=%d' % connect_timeout]
155 cmd += [host, 'cat', '/proc/sys/kernel/random/boot_id']
156 return util.check_output(*cmd).strip()
157
158
159def reboot(host):
160 """Reboot a DUT and verify"""
161 logger.debug('reboot %s', host)
162 boot_id = query_dut_boot_id(host)
163
164 # Depends on timing, ssh may return failure due to broken pipe,
165 # so don't check ssh return code.
166 util.call('ssh', host, 'reboot')
Kuang-che Wu708310b2018-03-28 17:24:34 +0800167 wait_reboot_done(host, boot_id)
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800168
Kuang-che Wu708310b2018-03-28 17:24:34 +0800169
170def wait_reboot_done(host, boot_id):
Kuang-che Wu4fe945b2018-03-31 16:46:38 +0800171 # For dev-mode test image, the reboot time is roughly at least 16 seconds
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800172 # (dev screen short delay) or more (long delay).
173 time.sleep(15)
174 for _ in range(100):
175 try:
176 # During boot, DUT does not response and thus ssh may hang a while. So
177 # set a connect timeout. 3 seconds are enough and 2 are not. It's okay to
178 # set tight limit because it's inside retry loop.
179 assert boot_id != query_dut_boot_id(host, connect_timeout=3)
180 return
181 except subprocess.CalledProcessError:
182 logger.debug('reboot not ready? sleep wait 1 sec')
183 time.sleep(1)
184
185 raise core.ExecutionFatalError('reboot failed?')
186
187
188def gsutil(*args, **kwargs):
Kuang-che Wubfc4a642018-04-19 11:54:08 +0800189 """gsutil command line wrapper.
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800190
191 Args:
192 args: command line arguments passed to gsutil
193 kwargs:
194 ignore_errors: if true, return '' for failures, for example 'gsutil ls'
195 but the path not found.
196
197 Returns:
198 stdout of gsutil
199
200 Raises:
201 core.ExecutionFatalError: gsutil failed to run
202 subprocess.CalledProcessError: command failed
203 """
204 stderr_lines = []
205 try:
206 return util.check_output(
207 gsutil_bin, *args, stderr_callback=stderr_lines.append)
208 except subprocess.CalledProcessError as e:
209 stderr = ''.join(stderr_lines)
210 if re.search(r'ServiceException:.* does not have .*access', stderr):
211 raise core.ExecutionFatalError(
212 'gsutil failed due to permission. ' +
213 'Run "%s config" and follow its instruction. ' % gsutil_bin +
214 'Fill any string if it asks for project-id')
215 if kwargs.get('ignore_errors'):
216 return ''
217 raise
218 except OSError as e:
219 if e.errno == errno.ENOENT:
220 raise core.ExecutionFatalError(
221 'Unable to run %s. gsutil is not installed or not in PATH?' %
222 gsutil_bin)
223 raise
224
225
226def gsutil_ls(*args, **kwargs):
Kuang-che Wubfc4a642018-04-19 11:54:08 +0800227 """gsutil ls.
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800228
229 Args:
230 args: arguments passed to 'gsutil ls'
231 kwargs: extra parameters, where
Kuang-che Wu4fe945b2018-03-31 16:46:38 +0800232 ignore_errors: if true, return empty list instead of raising
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800233 exception, ex. path not found.
234
235 Returns:
236 list of 'gsutil ls' result. One element for one line of gsutil output.
237
238 Raises:
239 subprocess.CalledProcessError: gsutil failed, usually means path not found
240 """
241 return gsutil('ls', *args, **kwargs).splitlines()
242
243
244def query_milestone_by_version(board, short_version):
Kuang-che Wubfc4a642018-04-19 11:54:08 +0800245 """Query milestone by ChromeOS version number.
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800246
247 Args:
248 board: ChromeOS board name
249 short_version: ChromeOS version number in short format, ex. 9300.0.0
250
251 Returns:
252 ChromeOS milestone number (string). For example, '58' for '9300.0.0'.
253 None if failed.
254 """
255 path = gs_archive_path.format(board=board) + '/R*-' + short_version
256 for line in gsutil_ls('-d', path, ignore_errors=True):
257 m = re.search(r'/R(\d+)-', line)
258 if not m:
259 continue
260 return m.group(1)
261
262 for channel in ['canary', 'dev', 'beta', 'stable']:
263 path = gs_release_path.format(
264 channel=channel, board=board, short_version=short_version)
265 for line in gsutil_ls(path, ignore_errors=True):
266 m = re.search(r'\bR(\d+)-' + short_version, line)
267 if not m:
268 continue
269 return m.group(1)
270
271 logger.error('unable to query milestone of %s for %s', short_version, board)
272 return None
273
274
275def recognize_version(board, version):
Kuang-che Wubfc4a642018-04-19 11:54:08 +0800276 """Recognize ChromeOS version.
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800277
278 Args:
279 board: ChromeOS board name
280 version: ChromeOS version number in short or full format
281
282 Returns:
283 (milestone, version in short format)
284 """
285 if is_cros_short_version(version):
286 milestone = query_milestone_by_version(board, version)
287 short_version = version
288 else:
289 milestone, short_version = version_split(version)
290 return milestone, short_version
291
292
293def version_to_short(version):
Kuang-che Wubfc4a642018-04-19 11:54:08 +0800294 """Convert ChromeOS version number to short format.
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800295
296 Args:
297 version: ChromeOS version number in short or full format
298
299 Returns:
300 version number in short format
301 """
302 if is_cros_short_version(version):
303 return version
304 _, short_version = version_split(version)
305 return short_version
306
307
308def version_to_full(board, version):
Kuang-che Wubfc4a642018-04-19 11:54:08 +0800309 """Convert ChromeOS version number to full format.
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800310
311 Args:
312 board: ChromeOS board name
313 version: ChromeOS version number in short or full format
314
315 Returns:
316 version number in full format
317 """
318 if is_cros_full_version(version):
319 return version
320 milestone = query_milestone_by_version(board, version)
Kuang-che Wuacb6efd2018-04-25 18:52:58 +0800321 assert milestone
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800322 return make_cros_full_version(milestone, version)
323
324
Kuang-che Wubfc4a642018-04-19 11:54:08 +0800325def prepare_prebuilt_image(board, version):
326 """Prepare chromeos prebuilt image.
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800327
Kuang-che Wubfc4a642018-04-19 11:54:08 +0800328 It searches for xbuddy image which "cros flash" can use, or fetch image to
329 local disk.
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800330
331 Args:
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800332 board: ChromeOS board name
Kuang-che Wubfc4a642018-04-19 11:54:08 +0800333 version: ChromeOS version number in short or full format
334
335 Returns:
336 xbuddy path or file path (outside chroot)
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800337 """
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800338 assert is_cros_version(version)
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800339 full_version = version_to_full(board, version)
340 short_version = version_to_short(full_version)
341
342 image_path = None
343 gs_path = gs_archive_path.format(board=board) + '/' + full_version
344 if gsutil_ls('-d', gs_path, ignore_errors=True):
345 image_path = 'xbuddy://remote/{board}/{full_version}/test'.format(
346 board=board, full_version=full_version)
347 else:
348 tmp_dir = 'tmp/ChromeOS-test-%s-%s' % (full_version, board)
349 if not os.path.exists(tmp_dir):
350 os.makedirs(tmp_dir)
351 # gs://chromeos-releases may have more old images than
Kuang-che Wu4fe945b2018-03-31 16:46:38 +0800352 # gs://chromeos-image-archive, but 'cros flash' doesn't support it. We have
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800353 # to fetch the image by ourselves
354 for channel in ['canary', 'dev', 'beta', 'stable']:
355 fn = 'ChromeOS-test-{full_version}-{board}.tar.xz'.format(
356 full_version=full_version, board=board)
357 gs_path = gs_release_path.format(
358 channel=channel, board=board, short_version=short_version)
359 gs_path += '/' + fn
360 if gsutil_ls(gs_path, ignore_errors=True):
361 # TODO(kcwu): delete tmp
362 gsutil('cp', gs_path, tmp_dir)
363 util.check_call('tar', 'Jxvf', fn, cwd=tmp_dir)
364 image_path = os.path.abspath(
365 os.path.join(tmp_dir, 'chromiumos_test_image.bin'))
366 break
367
368 assert image_path
Kuang-che Wubfc4a642018-04-19 11:54:08 +0800369 return image_path
370
371
372def cros_flash(chromeos_root,
373 host,
374 board,
375 image_path,
376 version=None,
377 clobber_stateful=False,
378 disable_rootfs_verification=True,
379 run_inside_chroot=False):
380 """Flash a DUT with given ChromeOS image.
381
382 This is implemented by 'cros flash' command line.
383
384 Args:
385 chromeos_root: use 'cros flash' of which chromeos tree
386 host: DUT address
387 board: ChromeOS board name
388 image_path: chromeos image xbuddy path or file path. If
389 run_inside_chroot is True, the file path is relative to src/scrips.
390 Otherwise, the file path is relative to chromeos_root.
391 version: ChromeOS version in short or full format
392 clobber_stateful: Clobber stateful partition when performing update
393 disable_rootfs_verification: Disable rootfs verification after update
394 is completed
395 run_inside_chroot: if True, run 'cros flash' command inside the chroot
396 """
397 logger.info('cros_flash %s %s %s %s', host, board, version, image_path)
398
399 # Reboot is necessary because sometimes previous 'cros flash' failed and
400 # entered a bad state.
401 reboot(host)
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800402
403 args = ['--no-ping', host, image_path]
404 if clobber_stateful:
405 args.append('--clobber-stateful')
406 if disable_rootfs_verification:
407 args.append('--disable-rootfs-verification')
408
Kuang-che Wubfc4a642018-04-19 11:54:08 +0800409 if run_inside_chroot:
410 cros_sdk(chromeos_root, 'cros', 'flash', *args)
411 else:
412 util.check_call('chromite/bin/cros', 'flash', *args, cwd=chromeos_root)
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800413
Kuang-che Wubfc4a642018-04-19 11:54:08 +0800414 if version:
415 # In the past, cros flash may fail with returncode=0
416 # So let's have an extra check.
417 short_version = version_to_short(version)
418 dut_version = query_dut_short_version(host)
419 assert dut_version == short_version
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800420
421
422def version_info(board, version):
423 """Query subcomponents version info of given version of ChromeOS
424
425 Args:
426 board: ChromeOS board name
427 version: ChromeOS version number in short or full format
428
429 Returns:
430 dict of component and version info, including (if available):
431 cros_short_version: ChromeOS version
432 cros_full_version: ChromeOS version
433 milestone: milestone of ChromeOS
434 cr_version: Chrome version
Kuang-che Wu708310b2018-03-28 17:24:34 +0800435 android_build_id: Android build id
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800436 android_branch: Android branch, in format like 'git_nyc-mr1-arc'
437 """
438 info = {}
439 full_version = version_to_full(board, version)
440
441 # Some boards may have only partial-metadata.json but no metadata.json.
442 # e.g. caroline R60-9462.0.0
443 # Let's try both.
444 metadata = None
445 for metadata_filename in ['metadata.json', 'partial-metadata.json']:
446 path = gs_archive_path.format(board=board) + '/%s/%s' % (full_version,
447 metadata_filename)
448 metadata = gsutil('cat', path, ignore_errors=True)
449 if metadata:
450 o = json.loads(metadata)
451 v = o['version']
452 board_metadata = o['board-metadata'][board]
453 info.update({
454 VERSION_KEY_CROS_SHORT_VERSION: v['platform'],
455 VERSION_KEY_CROS_FULL_VERSION: v['full'],
456 VERSION_KEY_MILESTONE: v['milestone'],
457 VERSION_KEY_CR_VERSION: v['chrome'],
458 })
459
460 if 'android' in v:
Kuang-che Wu708310b2018-03-28 17:24:34 +0800461 info[VERSION_KEY_ANDROID_BUILD_ID] = v['android']
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800462 if 'android-branch' in v: # this appears since R58-9317.0.0
463 info[VERSION_KEY_ANDROID_BRANCH] = v['android-branch']
464 elif 'android-container-branch' in board_metadata:
465 info[VERSION_KEY_ANDROID_BRANCH] = v['android-container-branch']
466 break
467 else:
468 logger.error('Failed to read metadata from gs://chromeos-image-archive')
469 logger.error(
470 'Note, so far no quick way to look up version info for too old builds')
471
472 return info
Kuang-che Wu848b1af2018-02-01 20:59:36 +0800473
474
475def query_chrome_version(board, version):
Kuang-che Wubfc4a642018-04-19 11:54:08 +0800476 """Queries chrome version of chromeos build.
Kuang-che Wu848b1af2018-02-01 20:59:36 +0800477
478 Args:
479 board: ChromeOS board name
480 version: ChromeOS version number in short or full format
481
482 Returns:
483 Chrome version number
484 """
485 info = version_info(board, version)
486 return info['cr_version']
Kuang-che Wu708310b2018-03-28 17:24:34 +0800487
488
489def query_android_build_id(board, rev):
490 info = version_info(board, rev)
491 rev = info['android_build_id']
492 return rev
493
494
495def query_android_branch(board, rev):
496 info = version_info(board, rev)
497 rev = info['android_branch']
498 return rev
Kuang-che Wubfc4a642018-04-19 11:54:08 +0800499
500
501def is_inside_chroot():
502 """Returns True if we are inside chroot."""
503 return os.path.exists('/etc/cros_chroot_version')
504
505
506def cros_sdk(chromeos_root, *args, **kwargs):
507 """Run commands inside chromeos chroot.
508
509 Args:
510 chromeos_root: chromeos tree root
511 *args: command to run
512 **kwargs:
513 env: (dict) environment variables for the command
514 stdin: standard input file handle for the command
515 """
516 envs = []
517 for k, v in kwargs.get('env', {}).items():
518 assert re.match(r'^[A-Za-z_][A-Za-z0-9_]*$', k)
519 envs.append('%s=%s' % (k, v))
520
521 # Use --no-ns-pid to prevent cros_sdk change our pgid, otherwise subsequent
522 # commands would be considered as background process.
523 cmd = ['chromite/bin/cros_sdk', '--no-ns-pid'] + envs + ['--'] + list(args)
524 return util.check_output(*cmd, cwd=chromeos_root, stdin=kwargs.get('stdin'))
525
526
527def copy_into_chroot(chromeos_root, src, dst):
528 """Copies file into chromeos chroot.
529
530 Args:
531 chromeos_root: chromeos tree root
532 src: path outside chroot
533 dst: path inside chroot
534 """
535 # chroot may be an image, so we cannot copy to corresponding path
536 # directly.
537 cros_sdk(chromeos_root, 'sh', '-c', 'cat > %s' % dst, stdin=open(src))
538
539
540def exists_in_chroot(chromeos_root, path):
541 """Determine whether a path exists in the chroot.
542
543 Args:
544 chromeos_root: chromeos tree root
545 path: path inside chroot, relative to src/scripts
546
547 Returns:
548 True if a path exists
549 """
550 try:
Kuang-che Wuacb6efd2018-04-25 18:52:58 +0800551 cros_sdk(chromeos_root, 'test', '-e', path)
Kuang-che Wubfc4a642018-04-19 11:54:08 +0800552 except subprocess.CalledProcessError:
553 return False
554 return True
555
556
557def build_image(chromeos_root, board, rev):
558 """Build ChromeOS image.
559
560 Args:
561 chromeos_root: chromeos tree root
562 board: ChromeOS board name
563 rev: the version name to build
564
565 Returns:
566 Image path
567 """
568
569 # If the given version is already built, reuse it.
570 image_name = 'bisect-%s' % rev
571 image_path = os.path.join('../build/images', board, image_name,
572 'chromiumos_test_image.bin')
573 if exists_in_chroot(chromeos_root, image_path):
574 logger.info('"%s" already exists, skip build step', image_path)
575 return image_path
576
577 dirname = os.path.dirname(os.path.abspath(__file__))
578 script_name = 'build_cros_helper.sh'
579 copy_into_chroot(chromeos_root, os.path.join(dirname, '..', script_name),
580 script_name)
581 cros_sdk(chromeos_root, 'chmod', '+x', script_name)
582 cros_sdk(chromeos_root, './%s' % script_name, board, image_name)
583 return image_path
584
585
586class ChromeOSManifestManager(repo_util.ManifestManager):
587 """Manifest operations for chromeos repo"""
588
589 def __init__(self, config):
590 self.config = config
591
592 def _query_manifest_name(self, rev):
593 assert is_cros_full_version(rev)
594 milestone, short_version = version_split(rev)
595 manifest_name = os.path.join('buildspecs', milestone,
596 '%s.xml' % short_version)
597 return manifest_name
598
599 def sync_disk_state(self, rev):
600 manifest_name = self._query_manifest_name(rev)
601
602 # For ChromeOS, mark_as_stable step requires 'repo init -m', which sticks
603 # manifest. 'repo sync -m' is not enough
604 repo_util.init(
605 self.config['repo_dir'],
606 'https://chrome-internal.googlesource.com/chromeos/manifest-versions/',
607 manifest_name=manifest_name,
608 repo_url='https://chromium.googlesource.com/external/repo.git')
609
610 # Note, don't sync with current_branch=True for chromeos. One of its
611 # build steps (inside mark_as_stable) executes "git describe" which
612 # needs git tag information.
613 repo_util.sync(self.config['repo_dir'])
614
615 def fetch_git_repos(self, rev):
616 # Early return if necessary git history are already fetched.
617 mf = self.fetch_manifest(rev)
618 repo_set = repo_util.parse_repo_set(self.config['repo_dir'], mf)
619 all_have = True
620 for path, git_rev in repo_set.items():
621 git_root = os.path.join(self.config['repo_dir'], path)
622 if not os.path.exists(git_root):
623 all_have = False
624 break
625 if not git_util.is_containing_commit(git_root, git_rev):
626 all_have = False
627 break
628 if all_have:
629 return
630
631 # TODO(kcwu): fetch git history but don't switch current disk state.
632 self.sync_disk_state(rev)
633
634 def enumerate_manifest(self, old, new):
635 assert is_cros_full_version(old)
636 assert is_cros_full_version(new)
637 old_milestone, old_short_version = version_split(old)
638 new_milestone, new_short_version = version_split(new)
639
640 # TODO(kcwu): fetch manifests but don't switch current disk state.
641 self.sync_disk_state(new)
642
643 spec_dir = os.path.join(self.config['repo_dir'], '.repo', 'manifests',
644 'buildspecs')
645 result = []
646 for root, dirs, files in os.walk(spec_dir):
647 dirs[:] = [
648 dn for dn in dirs if dn.isdigit() and
649 int(old_milestone) <= int(dn) <= int(new_milestone)
650 ]
651
652 for fn in files:
653 short_version, ext = os.path.splitext(fn)
654 if ext != '.xml':
655 continue
656 milestone = os.path.basename(root)
657 if (util.is_version_lesseq(old_short_version, short_version) and
658 util.is_version_lesseq(short_version, new_short_version) and
659 util.is_direct_relative_version(short_version, new_short_version)):
660 result.append(make_cros_full_version(milestone, short_version))
661
662 def version_key_func(full_version):
663 _milestone, short_version = version_split(full_version)
664 return util.version_key_func(short_version)
665
666 result.sort(key=version_key_func)
667 assert result[0] == old
668 assert result[-1] == new
669 return result
670
671 def fetch_manifest(self, rev):
672 # The manifest is already synced, no need to fetch.
673 return self._query_manifest_name(rev)