blob: 86679e64907c4de5beefdd50f79aeb1ec0f31600 [file] [log] [blame]
Kuang-che Wu6e4beca2018-06-27 17:45:02 +08001# -*- coding: utf-8 -*-
Kuang-che Wu2ea804f2017-11-28 17:11:41 +08002# Copyright 2017 The Chromium OS Authors. All rights reserved.
3# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
5"""ChromeOS utility.
6
7Terminology used in this module.
8 short_version: ChromeOS version number without milestone, like "9876.0.0".
9 full_version: ChromeOS version number with milestone, like "R62-9876.0.0".
10 version: if not specified, it could be in short or full format.
11"""
12
13from __future__ import print_function
Kuang-che Wub9705bd2018-06-28 17:59:18 +080014import ast
Kuang-che Wu2ea804f2017-11-28 17:11:41 +080015import errno
16import json
17import logging
18import os
19import re
20import subprocess
21import time
22
23from bisect_kit import cli
Kuang-che Wue4bae0b2018-07-19 12:10:14 +080024from bisect_kit import codechange
Kuang-che Wu3eb6b502018-06-06 16:15:18 +080025from bisect_kit import cr_util
Kuang-che Wue121fae2018-11-09 16:18:39 +080026from bisect_kit import errors
Kuang-che Wubfc4a642018-04-19 11:54:08 +080027from bisect_kit import git_util
Kuang-che Wufb553102018-10-02 18:14:29 +080028from bisect_kit import locking
Kuang-che Wubfc4a642018-04-19 11:54:08 +080029from bisect_kit import repo_util
Kuang-che Wu2ea804f2017-11-28 17:11:41 +080030from bisect_kit import util
31
32logger = logging.getLogger(__name__)
33
Kuang-che Wu2ea804f2017-11-28 17:11:41 +080034re_chromeos_full_version = r'^R\d+-\d+\.\d+\.\d+$'
Kuang-che Wuacb6efd2018-04-25 18:52:58 +080035re_chromeos_localbuild_version = r'^\d+\.\d+\.\d{4}_\d\d_\d\d_\d{4}$'
36re_chromeos_short_version = r'^\d+\.\d+\.\d+$'
Kuang-che Wu2ea804f2017-11-28 17:11:41 +080037
38gs_archive_path = 'gs://chromeos-image-archive/{board}-release'
39gs_release_path = (
Kuang-che Wu80bf6a52019-05-31 12:48:06 +080040 'gs://chromeos-releases/{channel}-channel/{boardpath}/{short_version}')
Kuang-che Wu2ea804f2017-11-28 17:11:41 +080041
42# Assume gsutil is in PATH.
43gsutil_bin = 'gsutil'
44
Kuang-che Wub9705bd2018-06-28 17:59:18 +080045chromeos_root_inside_chroot = '/mnt/host/source'
46# relative to chromeos_root
Kuang-che Wu7f82c6f2019-08-12 14:29:28 +080047prebuilt_autotest_dir = 'tmp/autotest-prebuilt'
Kuang-che Wu28980b22019-07-31 19:51:45 +080048# Relative to chromeos root. Images are cached_images_dir/$board/$image_name.
49cached_images_dir = 'src/build/images'
50test_image_filename = 'chromiumos_test_image.bin'
Kuang-che Wub9705bd2018-06-28 17:59:18 +080051
Kuang-che Wu2ea804f2017-11-28 17:11:41 +080052VERSION_KEY_CROS_SHORT_VERSION = 'cros_short_version'
53VERSION_KEY_CROS_FULL_VERSION = 'cros_full_version'
54VERSION_KEY_MILESTONE = 'milestone'
55VERSION_KEY_CR_VERSION = 'cr_version'
Kuang-che Wu708310b2018-03-28 17:24:34 +080056VERSION_KEY_ANDROID_BUILD_ID = 'android_build_id'
Kuang-che Wu2ea804f2017-11-28 17:11:41 +080057VERSION_KEY_ANDROID_BRANCH = 'android_branch'
58
59
Kuang-che Wu9890ce82018-07-07 15:14:10 +080060class NeedRecreateChrootException(Exception):
61 """Failed to build ChromeOS because of chroot mismatch or corruption"""
62
63
Kuang-che Wu2ea804f2017-11-28 17:11:41 +080064def is_cros_short_version(s):
Kuang-che Wuacb6efd2018-04-25 18:52:58 +080065 """Determines if `s` is chromeos short version.
66
67 This function doesn't accept version number of local build.
68 """
Kuang-che Wu2ea804f2017-11-28 17:11:41 +080069 return bool(re.match(re_chromeos_short_version, s))
70
71
Kuang-che Wuacb6efd2018-04-25 18:52:58 +080072def is_cros_localbuild_version(s):
73 """Determines if `s` is chromeos local build version."""
74 return bool(re.match(re_chromeos_localbuild_version, s))
75
76
Kuang-che Wu2ea804f2017-11-28 17:11:41 +080077def is_cros_full_version(s):
Kuang-che Wuacb6efd2018-04-25 18:52:58 +080078 """Determines if `s` is chromeos full version.
79
80 This function doesn't accept version number of local build.
81 """
Kuang-che Wu2ea804f2017-11-28 17:11:41 +080082 return bool(re.match(re_chromeos_full_version, s))
83
84
85def is_cros_version(s):
86 """Determines if `s` is chromeos version (either short or full)"""
87 return is_cros_short_version(s) or is_cros_full_version(s)
88
89
90def make_cros_full_version(milestone, short_version):
91 """Makes full_version from milestone and short_version"""
Kuang-che Wuacb6efd2018-04-25 18:52:58 +080092 assert milestone
Kuang-che Wu2ea804f2017-11-28 17:11:41 +080093 return 'R%s-%s' % (milestone, short_version)
94
95
96def version_split(full_version):
97 """Splits full_version into milestone and short_version"""
98 assert is_cros_full_version(full_version)
99 milestone, short_version = full_version.split('-')
100 return milestone[1:], short_version
101
102
103def argtype_cros_version(s):
104 if not is_cros_version(s):
105 msg = 'invalid cros version'
106 raise cli.ArgTypeError(msg, '9876.0.0 or R62-9876.0.0')
107 return s
108
109
110def query_dut_lsb_release(host):
111 """Query /etc/lsb-release of given DUT
112
113 Args:
114 host: the DUT address
115
116 Returns:
Kuang-che Wu3eb6b502018-06-06 16:15:18 +0800117 dict for keys and values of /etc/lsb-release.
118
119 Raises:
Kuang-che Wu44278142019-03-04 11:33:57 +0800120 errors.SshConnectionError: cannot connect to host
121 errors.ExternalError: lsb-release file doesn't exist
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800122 """
123 try:
Kuang-che Wu44278142019-03-04 11:33:57 +0800124 output = util.ssh_cmd(host, 'cat', '/etc/lsb-release')
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800125 except subprocess.CalledProcessError:
Kuang-che Wu44278142019-03-04 11:33:57 +0800126 raise errors.ExternalError('unable to read /etc/lsb-release; not a DUT')
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800127 return dict(re.findall(r'^(\w+)=(.*)$', output, re.M))
128
129
130def is_dut(host):
Kuang-che Wubfc4a642018-04-19 11:54:08 +0800131 """Determines whether a host is a chromeos device.
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800132
133 Args:
134 host: the DUT address
135
136 Returns:
137 True if the host is a chromeos device.
138 """
Kuang-che Wu44278142019-03-04 11:33:57 +0800139 try:
140 return query_dut_lsb_release(host).get('DEVICETYPE') in [
141 'CHROMEBASE',
142 'CHROMEBIT',
143 'CHROMEBOOK',
144 'CHROMEBOX',
145 'REFERENCE',
146 ]
147 except (errors.ExternalError, errors.SshConnectionError):
148 return False
149
150
151def is_good_dut(host):
152 if not is_dut(host):
153 return False
154
155 # Sometimes python is broken after 'cros flash'.
156 try:
157 util.ssh_cmd(host, 'python', '-c', '1')
158 return True
159 except (subprocess.CalledProcessError, errors.SshConnectionError):
160 return False
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800161
162
163def query_dut_board(host):
164 """Query board name of a given DUT"""
165 return query_dut_lsb_release(host).get('CHROMEOS_RELEASE_BOARD')
166
167
168def query_dut_short_version(host):
Kuang-che Wuacb6efd2018-04-25 18:52:58 +0800169 """Query short version of a given DUT.
170
171 This function may return version of local build, which
172 is_cros_short_version() is false.
173 """
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800174 return query_dut_lsb_release(host).get('CHROMEOS_RELEASE_VERSION')
175
176
177def query_dut_boot_id(host, connect_timeout=None):
Kuang-che Wubfc4a642018-04-19 11:54:08 +0800178 """Query boot id.
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800179
180 Args:
181 host: DUT address
182 connect_timeout: connection timeout
183
184 Returns:
185 boot uuid
186 """
Kuang-che Wu44278142019-03-04 11:33:57 +0800187 return util.ssh_cmd(
188 host,
189 'cat',
190 '/proc/sys/kernel/random/boot_id',
191 connect_timeout=connect_timeout).strip()
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800192
193
194def reboot(host):
195 """Reboot a DUT and verify"""
196 logger.debug('reboot %s', host)
197 boot_id = query_dut_boot_id(host)
198
Kuang-che Wu44278142019-03-04 11:33:57 +0800199 try:
200 util.ssh_cmd(host, 'reboot')
Kuang-che Wu5f662e82019-03-05 11:49:56 +0800201 except errors.SshConnectionError:
202 # Depends on timing, ssh may return failure due to broken pipe, which is
203 # working as intended. Ignore such kind of errors.
Kuang-che Wu44278142019-03-04 11:33:57 +0800204 pass
Kuang-che Wu708310b2018-03-28 17:24:34 +0800205 wait_reboot_done(host, boot_id)
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800206
Kuang-che Wu708310b2018-03-28 17:24:34 +0800207
208def wait_reboot_done(host, boot_id):
Kuang-che Wu4fe945b2018-03-31 16:46:38 +0800209 # For dev-mode test image, the reboot time is roughly at least 16 seconds
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800210 # (dev screen short delay) or more (long delay).
211 time.sleep(15)
212 for _ in range(100):
213 try:
214 # During boot, DUT does not response and thus ssh may hang a while. So
215 # set a connect timeout. 3 seconds are enough and 2 are not. It's okay to
216 # set tight limit because it's inside retry loop.
217 assert boot_id != query_dut_boot_id(host, connect_timeout=3)
218 return
Kuang-che Wu5f662e82019-03-05 11:49:56 +0800219 except errors.SshConnectionError:
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800220 logger.debug('reboot not ready? sleep wait 1 sec')
221 time.sleep(1)
222
Kuang-che Wue121fae2018-11-09 16:18:39 +0800223 raise errors.ExternalError('reboot failed?')
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800224
225
Kuang-che Wu80bf6a52019-05-31 12:48:06 +0800226def gs_release_boardpath(board):
227 """Normalizes board name for gs://chromeos-releases/
228
229 This follows behavior of PushImage() in chromite/scripts/pushimage.py
230 Note, only gs://chromeos-releases/ needs normalization,
231 gs://chromeos-image-archive does not.
232
233 Args:
234 board: ChromeOS board name
235
236 Returns:
237 normalized board name
238 """
239 return board.replace('_', '-')
240
241
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800242def gsutil(*args, **kwargs):
Kuang-che Wubfc4a642018-04-19 11:54:08 +0800243 """gsutil command line wrapper.
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800244
245 Args:
246 args: command line arguments passed to gsutil
247 kwargs:
248 ignore_errors: if true, return '' for failures, for example 'gsutil ls'
249 but the path not found.
250
251 Returns:
252 stdout of gsutil
253
254 Raises:
Kuang-che Wue121fae2018-11-09 16:18:39 +0800255 errors.InternalError: gsutil failed to run
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800256 subprocess.CalledProcessError: command failed
257 """
258 stderr_lines = []
259 try:
260 return util.check_output(
261 gsutil_bin, *args, stderr_callback=stderr_lines.append)
262 except subprocess.CalledProcessError as e:
263 stderr = ''.join(stderr_lines)
264 if re.search(r'ServiceException:.* does not have .*access', stderr):
Kuang-che Wue121fae2018-11-09 16:18:39 +0800265 raise errors.ExternalError(
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800266 'gsutil failed due to permission. ' +
267 'Run "%s config" and follow its instruction. ' % gsutil_bin +
268 'Fill any string if it asks for project-id')
269 if kwargs.get('ignore_errors'):
270 return ''
271 raise
272 except OSError as e:
273 if e.errno == errno.ENOENT:
Kuang-che Wue121fae2018-11-09 16:18:39 +0800274 raise errors.ExternalError(
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800275 'Unable to run %s. gsutil is not installed or not in PATH?' %
276 gsutil_bin)
277 raise
278
279
280def gsutil_ls(*args, **kwargs):
Kuang-che Wubfc4a642018-04-19 11:54:08 +0800281 """gsutil ls.
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800282
283 Args:
284 args: arguments passed to 'gsutil ls'
285 kwargs: extra parameters, where
Kuang-che Wu4fe945b2018-03-31 16:46:38 +0800286 ignore_errors: if true, return empty list instead of raising
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800287 exception, ex. path not found.
288
289 Returns:
290 list of 'gsutil ls' result. One element for one line of gsutil output.
291
292 Raises:
293 subprocess.CalledProcessError: gsutil failed, usually means path not found
294 """
295 return gsutil('ls', *args, **kwargs).splitlines()
296
297
298def query_milestone_by_version(board, short_version):
Kuang-che Wubfc4a642018-04-19 11:54:08 +0800299 """Query milestone by ChromeOS version number.
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800300
301 Args:
302 board: ChromeOS board name
303 short_version: ChromeOS version number in short format, ex. 9300.0.0
304
305 Returns:
306 ChromeOS milestone number (string). For example, '58' for '9300.0.0'.
307 None if failed.
308 """
309 path = gs_archive_path.format(board=board) + '/R*-' + short_version
310 for line in gsutil_ls('-d', path, ignore_errors=True):
311 m = re.search(r'/R(\d+)-', line)
312 if not m:
313 continue
314 return m.group(1)
315
316 for channel in ['canary', 'dev', 'beta', 'stable']:
317 path = gs_release_path.format(
Kuang-che Wu80bf6a52019-05-31 12:48:06 +0800318 channel=channel,
319 boardpath=gs_release_boardpath(board),
320 short_version=short_version)
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800321 for line in gsutil_ls(path, ignore_errors=True):
322 m = re.search(r'\bR(\d+)-' + short_version, line)
323 if not m:
324 continue
325 return m.group(1)
326
327 logger.error('unable to query milestone of %s for %s', short_version, board)
328 return None
329
330
Kuang-che Wu80bf6a52019-05-31 12:48:06 +0800331def list_board_names(chromeos_root):
332 """List board names.
333
334 Args:
335 chromeos_root: chromeos tree root
336
337 Returns:
338 list of board names
339 """
340 # Following logic is simplified from chromite/lib/portage_util.py
341 cros_list_overlays = os.path.join(chromeos_root,
342 'chromite/bin/cros_list_overlays')
343 overlays = util.check_output(cros_list_overlays).splitlines()
344 result = set()
345 for overlay in overlays:
346 conf_file = os.path.join(overlay, 'metadata', 'layout.conf')
347 name = None
348 if os.path.exists(conf_file):
349 for line in open(conf_file):
350 m = re.match(r'^repo-name\s*=\s*(\S+)\s*$', line)
351 if m:
352 name = m.group(1)
353 break
354
355 if not name:
356 name_file = os.path.join(overlay, 'profiles', 'repo_name')
357 if os.path.exists(name_file):
358 name = open(name_file).read().strip()
359
360 if name:
361 name = re.sub(r'-private$', '', name)
362 result.add(name)
363
364 return list(result)
365
366
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800367def recognize_version(board, version):
Kuang-che Wubfc4a642018-04-19 11:54:08 +0800368 """Recognize ChromeOS version.
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800369
370 Args:
371 board: ChromeOS board name
372 version: ChromeOS version number in short or full format
373
374 Returns:
375 (milestone, version in short format)
376 """
377 if is_cros_short_version(version):
378 milestone = query_milestone_by_version(board, version)
379 short_version = version
380 else:
381 milestone, short_version = version_split(version)
382 return milestone, short_version
383
384
385def version_to_short(version):
Kuang-che Wubfc4a642018-04-19 11:54:08 +0800386 """Convert ChromeOS version number to short format.
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800387
388 Args:
389 version: ChromeOS version number in short or full format
390
391 Returns:
392 version number in short format
393 """
394 if is_cros_short_version(version):
395 return version
396 _, short_version = version_split(version)
397 return short_version
398
399
400def version_to_full(board, version):
Kuang-che Wubfc4a642018-04-19 11:54:08 +0800401 """Convert ChromeOS version number to full format.
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800402
403 Args:
404 board: ChromeOS board name
405 version: ChromeOS version number in short or full format
406
407 Returns:
408 version number in full format
409 """
410 if is_cros_full_version(version):
411 return version
412 milestone = query_milestone_by_version(board, version)
Kuang-che Wu0205f052019-05-23 12:48:37 +0800413 assert milestone, 'incorrect board=%s or version=%s ?' % (board, version)
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800414 return make_cros_full_version(milestone, version)
415
416
Kuang-che Wu575dc442019-03-05 10:30:55 +0800417def list_prebuilt_from_image_archive(board):
418 """Lists ChromeOS prebuilt image available from gs://chromeos-image-archive.
419
420 gs://chromeos-image-archive contains only recent builds (in two years).
421 We prefer this function to list_prebuilt_from_chromeos_releases() because
422 - this is what "cros flash" supports directly.
423 - the paths have milestone information, so we don't need to do slow query
424 by ourselves.
425
426 Args:
427 board: ChromeOS board name
428
429 Returns:
430 list of (version, gs_path):
431 version: Chrome OS version in full format
432 gs_path: gs path of test image
433 """
434 result = []
435 for line in gsutil_ls(gs_archive_path.format(board=board)):
436 m = re.match(r'^gs:\S+(R\d+-\d+\.\d+\.\d+)', line)
437 if m:
438 full_version = m.group(1)
439 test_image = 'chromiumos_test_image.tar.xz'
440 assert line.endswith('/')
441 gs_path = line + test_image
442 result.append((full_version, gs_path))
443 return result
444
445
446def list_prebuilt_from_chromeos_releases(board):
447 """Lists ChromeOS versions available from gs://chromeos-releases.
448
449 gs://chromeos-releases contains more builds. However, 'cros flash' doesn't
450 support it.
451
452 Args:
453 board: ChromeOS board name
454
455 Returns:
456 list of (version, gs_path):
457 version: Chrome OS version in short format
458 gs_path: gs path of test image (with wildcard)
459 """
460 result = []
461 for line in gsutil_ls(
Kuang-che Wu80bf6a52019-05-31 12:48:06 +0800462 gs_release_path.format(
463 channel='*', boardpath=gs_release_boardpath(board), short_version=''),
Kuang-che Wu575dc442019-03-05 10:30:55 +0800464 ignore_errors=True):
465 m = re.match(r'gs:\S+/(\d+\.\d+\.\d+)/$', line)
466 if m:
467 short_version = m.group(1)
468 test_image = 'ChromeOS-test-R*-{short_version}-{board}.tar.xz'.format(
469 short_version=short_version, board=board)
470 gs_path = line + test_image
471 result.append((short_version, gs_path))
472 return result
473
474
475def list_chromeos_prebuilt_versions(board,
476 old,
477 new,
478 only_good_build=True,
479 include_older_build=True):
480 """Lists ChromeOS version numbers with prebuilt between given range
481
482 Args:
483 board: ChromeOS board name
484 old: start version (inclusive)
485 new: end version (inclusive)
486 only_good_build: only if test image is available
487 include_older_build: include prebuilt in gs://chromeos-releases
488
489 Returns:
490 list of sorted version numbers (in full format) between [old, new] range
491 (inclusive).
492 """
493 old = version_to_short(old)
494 new = version_to_short(new)
495
496 rev_map = {} # dict: short version -> (short or full version, gs line)
497 for full_version, gs_path in list_prebuilt_from_image_archive(board):
498 short_version = version_to_short(full_version)
499 rev_map[short_version] = full_version, gs_path
500
501 if include_older_build and old not in rev_map:
502 for short_version, gs_path in list_prebuilt_from_chromeos_releases(board):
503 if short_version not in rev_map:
504 rev_map[short_version] = short_version, gs_path
505
506 result = []
507 for rev in sorted(rev_map, key=util.version_key_func):
508 if not util.is_direct_relative_version(new, rev):
509 continue
510 if not util.is_version_lesseq(old, rev):
511 continue
512 if not util.is_version_lesseq(rev, new):
513 continue
514
515 version, gs_path = rev_map[rev]
516
517 # version_to_full() and gsutil_ls() may take long time if versions are a
518 # lot. This is acceptable because we usually bisect only short range.
519
520 if only_good_build:
521 gs_result = gsutil_ls(gs_path, ignore_errors=True)
522 if not gs_result:
523 logger.warning('%s is not a good build, ignore', version)
524 continue
525 assert len(gs_result) == 1
526 m = re.search(r'(R\d+-\d+\.\d+\.\d+)', gs_result[0])
527 if not m:
528 logger.warning('format of image path is unexpected: %s', gs_result[0])
529 continue
530 version = m.group(1)
531 elif is_cros_short_version(version):
532 version = version_to_full(board, version)
533
534 result.append(version)
535
536 return result
537
538
Kuang-che Wu28980b22019-07-31 19:51:45 +0800539def prepare_prebuilt_image(chromeos_root, board, version):
Kuang-che Wubfc4a642018-04-19 11:54:08 +0800540 """Prepare chromeos prebuilt image.
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800541
Kuang-che Wubfc4a642018-04-19 11:54:08 +0800542 It searches for xbuddy image which "cros flash" can use, or fetch image to
543 local disk.
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800544
545 Args:
Kuang-che Wu28980b22019-07-31 19:51:45 +0800546 chromeos_root: chromeos tree root
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800547 board: ChromeOS board name
Kuang-che Wubfc4a642018-04-19 11:54:08 +0800548 version: ChromeOS version number in short or full format
549
550 Returns:
Kuang-che Wu28980b22019-07-31 19:51:45 +0800551 xbuddy path or file path (relative to chromeos_root)
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800552 """
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800553 assert is_cros_version(version)
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800554 full_version = version_to_full(board, version)
555 short_version = version_to_short(full_version)
556
557 image_path = None
558 gs_path = gs_archive_path.format(board=board) + '/' + full_version
559 if gsutil_ls('-d', gs_path, ignore_errors=True):
560 image_path = 'xbuddy://remote/{board}/{full_version}/test'.format(
561 board=board, full_version=full_version)
562 else:
Kuang-che Wu28980b22019-07-31 19:51:45 +0800563 tmp_dir = os.path.join(chromeos_root, 'tmp',
564 'ChromeOS-test-%s-%s' % (full_version, board))
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800565 if not os.path.exists(tmp_dir):
566 os.makedirs(tmp_dir)
567 # gs://chromeos-releases may have more old images than
Kuang-che Wu4fe945b2018-03-31 16:46:38 +0800568 # gs://chromeos-image-archive, but 'cros flash' doesn't support it. We have
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800569 # to fetch the image by ourselves
570 for channel in ['canary', 'dev', 'beta', 'stable']:
571 fn = 'ChromeOS-test-{full_version}-{board}.tar.xz'.format(
572 full_version=full_version, board=board)
573 gs_path = gs_release_path.format(
Kuang-che Wu80bf6a52019-05-31 12:48:06 +0800574 channel=channel,
575 boardpath=gs_release_boardpath(board),
576 short_version=short_version)
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800577 gs_path += '/' + fn
578 if gsutil_ls(gs_path, ignore_errors=True):
579 # TODO(kcwu): delete tmp
580 gsutil('cp', gs_path, tmp_dir)
581 util.check_call('tar', 'Jxvf', fn, cwd=tmp_dir)
Kuang-che Wu28980b22019-07-31 19:51:45 +0800582 image_path = os.path.relpath(
583 os.path.join(tmp_dir, test_image_filename), chromeos_root)
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800584 break
585
586 assert image_path
Kuang-che Wubfc4a642018-04-19 11:54:08 +0800587 return image_path
588
589
590def cros_flash(chromeos_root,
591 host,
592 board,
593 image_path,
594 version=None,
595 clobber_stateful=False,
Kuang-che Wu155fb6e2018-11-29 16:00:41 +0800596 disable_rootfs_verification=True):
Kuang-che Wubfc4a642018-04-19 11:54:08 +0800597 """Flash a DUT with given ChromeOS image.
598
599 This is implemented by 'cros flash' command line.
600
601 Args:
602 chromeos_root: use 'cros flash' of which chromeos tree
603 host: DUT address
604 board: ChromeOS board name
Kuang-che Wu28980b22019-07-31 19:51:45 +0800605 image_path: chromeos image xbuddy path or file path. For relative
606 path, it should be relative to chromeos_root.
Kuang-che Wubfc4a642018-04-19 11:54:08 +0800607 version: ChromeOS version in short or full format
608 clobber_stateful: Clobber stateful partition when performing update
609 disable_rootfs_verification: Disable rootfs verification after update
610 is completed
Kuang-che Wu414d67f2019-05-28 11:28:57 +0800611
612 Raises:
613 errors.ExternalError: cros flash failed
Kuang-che Wubfc4a642018-04-19 11:54:08 +0800614 """
615 logger.info('cros_flash %s %s %s %s', host, board, version, image_path)
616
617 # Reboot is necessary because sometimes previous 'cros flash' failed and
618 # entered a bad state.
619 reboot(host)
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800620
Kuang-che Wu28980b22019-07-31 19:51:45 +0800621 # Handle relative path.
622 if '://' not in image_path and not os.path.isabs(image_path):
623 assert os.path.exists(os.path.join(chromeos_root, image_path))
624 image_path = os.path.join(chromeos_root_inside_chroot, image_path)
625
Kuang-che Wuf3d03ca2019-03-11 17:31:40 +0800626 args = [
627 '--debug', '--no-ping', '--send-payload-in-parallel', host, image_path
628 ]
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800629 if clobber_stateful:
630 args.append('--clobber-stateful')
631 if disable_rootfs_verification:
632 args.append('--disable-rootfs-verification')
633
Kuang-che Wu414d67f2019-05-28 11:28:57 +0800634 try:
635 cros_sdk(chromeos_root, 'cros', 'flash', *args)
636 except subprocess.CalledProcessError:
637 raise errors.ExternalError('cros flash failed')
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800638
Kuang-che Wubfc4a642018-04-19 11:54:08 +0800639 if version:
640 # In the past, cros flash may fail with returncode=0
641 # So let's have an extra check.
642 short_version = version_to_short(version)
643 dut_version = query_dut_short_version(host)
Kuang-che Wu414d67f2019-05-28 11:28:57 +0800644 if dut_version != short_version:
645 raise errors.ExternalError(
646 'although cros flash succeeded, the OS version is unexpected: '
647 'actual=%s expect=%s' % (dut_version, short_version))
648
649 # "cros flash" may terminate sucessfully but the DUT starts self-repairing
650 # (b/130786578), so it's necessary to do sanity check.
651 if not is_good_dut(host):
652 raise errors.ExternalError(
653 'although cros flash succeeded, the DUT is in bad state')
654
655
656def cros_flash_with_retry(chromeos_root,
657 host,
658 board,
659 image_path,
660 version=None,
661 clobber_stateful=False,
662 disable_rootfs_verification=True,
663 repair_callback=None):
664 # 'cros flash' is not 100% reliable, retry if necessary.
665 for attempt in range(2):
666 if attempt > 0:
667 logger.info('will retry 60 seconds later')
668 time.sleep(60)
669
670 try:
671 cros_flash(
672 chromeos_root,
673 host,
674 board,
675 image_path,
676 version=version,
677 clobber_stateful=clobber_stateful,
678 disable_rootfs_verification=disable_rootfs_verification)
679 return True
680 except errors.ExternalError:
681 logger.exception('cros flash failed')
682 if repair_callback and not repair_callback(host):
683 logger.warning('not repaired, assume it is harmless')
684 continue
685 return False
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800686
687
688def version_info(board, version):
689 """Query subcomponents version info of given version of ChromeOS
690
691 Args:
692 board: ChromeOS board name
693 version: ChromeOS version number in short or full format
694
695 Returns:
696 dict of component and version info, including (if available):
697 cros_short_version: ChromeOS version
698 cros_full_version: ChromeOS version
699 milestone: milestone of ChromeOS
700 cr_version: Chrome version
Kuang-che Wu708310b2018-03-28 17:24:34 +0800701 android_build_id: Android build id
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800702 android_branch: Android branch, in format like 'git_nyc-mr1-arc'
703 """
704 info = {}
705 full_version = version_to_full(board, version)
706
707 # Some boards may have only partial-metadata.json but no metadata.json.
708 # e.g. caroline R60-9462.0.0
709 # Let's try both.
710 metadata = None
711 for metadata_filename in ['metadata.json', 'partial-metadata.json']:
712 path = gs_archive_path.format(board=board) + '/%s/%s' % (full_version,
713 metadata_filename)
714 metadata = gsutil('cat', path, ignore_errors=True)
715 if metadata:
716 o = json.loads(metadata)
717 v = o['version']
718 board_metadata = o['board-metadata'][board]
719 info.update({
720 VERSION_KEY_CROS_SHORT_VERSION: v['platform'],
721 VERSION_KEY_CROS_FULL_VERSION: v['full'],
722 VERSION_KEY_MILESTONE: v['milestone'],
723 VERSION_KEY_CR_VERSION: v['chrome'],
724 })
725
726 if 'android' in v:
Kuang-che Wu708310b2018-03-28 17:24:34 +0800727 info[VERSION_KEY_ANDROID_BUILD_ID] = v['android']
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800728 if 'android-branch' in v: # this appears since R58-9317.0.0
729 info[VERSION_KEY_ANDROID_BRANCH] = v['android-branch']
730 elif 'android-container-branch' in board_metadata:
731 info[VERSION_KEY_ANDROID_BRANCH] = v['android-container-branch']
732 break
733 else:
734 logger.error('Failed to read metadata from gs://chromeos-image-archive')
735 logger.error(
736 'Note, so far no quick way to look up version info for too old builds')
737
738 return info
Kuang-che Wu848b1af2018-02-01 20:59:36 +0800739
740
741def query_chrome_version(board, version):
Kuang-che Wubfc4a642018-04-19 11:54:08 +0800742 """Queries chrome version of chromeos build.
Kuang-che Wu848b1af2018-02-01 20:59:36 +0800743
744 Args:
745 board: ChromeOS board name
746 version: ChromeOS version number in short or full format
747
748 Returns:
749 Chrome version number
750 """
751 info = version_info(board, version)
752 return info['cr_version']
Kuang-che Wu708310b2018-03-28 17:24:34 +0800753
754
755def query_android_build_id(board, rev):
756 info = version_info(board, rev)
757 rev = info['android_build_id']
758 return rev
759
760
761def query_android_branch(board, rev):
762 info = version_info(board, rev)
763 rev = info['android_branch']
764 return rev
Kuang-che Wubfc4a642018-04-19 11:54:08 +0800765
766
Kuang-che Wu3eb6b502018-06-06 16:15:18 +0800767def guess_chrome_version(board, rev):
768 """Guess chrome version number.
769
770 Args:
771 board: chromeos board name
772 rev: chrome or chromeos version
773
774 Returns:
775 chrome version number
776 """
777 if is_cros_version(rev):
778 assert board, 'need to specify BOARD for cros version'
779 rev = query_chrome_version(board, rev)
780 assert cr_util.is_chrome_version(rev)
781
782 return rev
783
784
Kuang-che Wubfc4a642018-04-19 11:54:08 +0800785def is_inside_chroot():
786 """Returns True if we are inside chroot."""
787 return os.path.exists('/etc/cros_chroot_version')
788
789
790def cros_sdk(chromeos_root, *args, **kwargs):
791 """Run commands inside chromeos chroot.
792
793 Args:
794 chromeos_root: chromeos tree root
795 *args: command to run
796 **kwargs:
Kuang-che Wud4603d72018-11-29 17:51:21 +0800797 chrome_root: pass to cros_sdk; mount this path into the SDK chroot
Kuang-che Wubfc4a642018-04-19 11:54:08 +0800798 env: (dict) environment variables for the command
Kuang-che Wubcafc552019-08-15 15:27:02 +0800799 log_stdout: Whether write the stdout output of the child process to log.
Kuang-che Wubfc4a642018-04-19 11:54:08 +0800800 stdin: standard input file handle for the command
Kuang-che Wu9890ce82018-07-07 15:14:10 +0800801 stderr_callback: Callback function for stderr. Called once per line.
Kuang-che Wua9a20bb2019-09-05 22:24:04 +0800802 goma_dir: Goma installed directory to mount into the chroot
Kuang-che Wubfc4a642018-04-19 11:54:08 +0800803 """
804 envs = []
805 for k, v in kwargs.get('env', {}).items():
806 assert re.match(r'^[A-Za-z_][A-Za-z0-9_]*$', k)
807 envs.append('%s=%s' % (k, v))
808
809 # Use --no-ns-pid to prevent cros_sdk change our pgid, otherwise subsequent
810 # commands would be considered as background process.
Kuang-che Wu399d4662019-06-06 15:23:37 +0800811 prefix = ['chromite/bin/cros_sdk', '--no-ns-pid']
Kuang-che Wud4603d72018-11-29 17:51:21 +0800812
813 if kwargs.get('chrome_root'):
Kuang-che Wu399d4662019-06-06 15:23:37 +0800814 prefix += ['--chrome_root', kwargs['chrome_root']]
Kuang-che Wua9a20bb2019-09-05 22:24:04 +0800815 if kwargs.get('goma_dir'):
816 prefix += ['--goma_dir', kwargs['goma_dir']]
Kuang-che Wud4603d72018-11-29 17:51:21 +0800817
Kuang-che Wu399d4662019-06-06 15:23:37 +0800818 prefix += envs + ['--']
Kuang-che Wud4603d72018-11-29 17:51:21 +0800819
Kuang-che Wu399d4662019-06-06 15:23:37 +0800820 # In addition to the output of command we are interested, cros_sdk may
821 # generate its own messages. For example, chroot creation messages if we run
822 # cros_sdk the first time.
823 # This is the hack to run dummy command once, so we can get clean output for
824 # the command we are interested.
825 cmd = prefix + ['true']
826 util.check_call(*cmd, cwd=chromeos_root)
827
828 cmd = prefix + list(args)
Kuang-che Wu9890ce82018-07-07 15:14:10 +0800829 return util.check_output(
830 *cmd,
831 cwd=chromeos_root,
Kuang-che Wubcafc552019-08-15 15:27:02 +0800832 log_stdout=kwargs.get('log_stdout', True),
Kuang-che Wu9890ce82018-07-07 15:14:10 +0800833 stdin=kwargs.get('stdin'),
834 stderr_callback=kwargs.get('stderr_callback'))
Kuang-che Wubfc4a642018-04-19 11:54:08 +0800835
836
Kuang-che Wua9a20bb2019-09-05 22:24:04 +0800837def create_chroot(chromeos_root):
838 """Creates ChromeOS chroot.
839
840 Args:
841 chromeos_root: chromeos tree root
842 """
843 if os.path.exists(os.path.join(chromeos_root, 'chroot')):
844 return
845 if os.path.exists(os.path.join(chromeos_root, 'chroot.img')):
846 return
847
848 util.check_output('chromite/bin/cros_sdk', '--create', cwd=chromeos_root)
849
850
Kuang-che Wubfc4a642018-04-19 11:54:08 +0800851def copy_into_chroot(chromeos_root, src, dst):
852 """Copies file into chromeos chroot.
853
854 Args:
855 chromeos_root: chromeos tree root
856 src: path outside chroot
857 dst: path inside chroot
858 """
859 # chroot may be an image, so we cannot copy to corresponding path
860 # directly.
861 cros_sdk(chromeos_root, 'sh', '-c', 'cat > %s' % dst, stdin=open(src))
862
863
864def exists_in_chroot(chromeos_root, path):
865 """Determine whether a path exists in the chroot.
866
867 Args:
868 chromeos_root: chromeos tree root
869 path: path inside chroot, relative to src/scripts
870
871 Returns:
872 True if a path exists
873 """
874 try:
Kuang-che Wuacb6efd2018-04-25 18:52:58 +0800875 cros_sdk(chromeos_root, 'test', '-e', path)
Kuang-che Wubfc4a642018-04-19 11:54:08 +0800876 except subprocess.CalledProcessError:
877 return False
878 return True
879
880
Kuang-che Wu9890ce82018-07-07 15:14:10 +0800881def check_if_need_recreate_chroot(stdout, stderr):
882 """Analyze build log and determine if chroot should be recreated.
883
884 Args:
885 stdout: stdout output of build
886 stderr: stderr output of build
887
888 Returns:
889 the reason if chroot needs recreated; None otherwise
890 """
Kuang-che Wu74768d32018-09-07 12:03:24 +0800891 if re.search(
892 r"The current version of portage supports EAPI '\d+'. "
Kuang-che Wuae6824b2019-08-27 22:20:01 +0800893 'You must upgrade', stderr):
Kuang-che Wu9890ce82018-07-07 15:14:10 +0800894 return 'EAPI version mismatch'
895
Kuang-che Wu5ac81322018-11-26 14:04:06 +0800896 if 'Chroot is too new. Consider running:' in stderr:
897 return 'chroot version is too new'
898
899 # old message before Oct 2018
Kuang-che Wu9890ce82018-07-07 15:14:10 +0800900 if 'Chroot version is too new. Consider running cros_sdk --replace' in stderr:
901 return 'chroot version is too new'
902
Kuang-che Wu6fe987f2018-08-28 15:24:20 +0800903 # https://groups.google.com/a/chromium.org/forum/#!msg/chromium-os-dev/uzwT5APspB4/NFakFyCIDwAJ
904 if "undefined reference to 'std::__1::basic_string" in stdout:
905 return 'might be due to compiler change'
906
Kuang-che Wu9890ce82018-07-07 15:14:10 +0800907 return None
908
909
Kuang-che Wua9a20bb2019-09-05 22:24:04 +0800910def build_packages(chromeos_root,
911 board,
912 chrome_root=None,
913 goma_dir=None,
914 afdo_use=False):
Kuang-che Wu28980b22019-07-31 19:51:45 +0800915 """Build ChromeOS packages.
Kuang-che Wubfc4a642018-04-19 11:54:08 +0800916
917 Args:
918 chromeos_root: chromeos tree root
919 board: ChromeOS board name
Kuang-che Wua9a20bb2019-09-05 22:24:04 +0800920 chrome_root: Chrome tree root. If specified, build chrome using the
921 provided tree
922 goma_dir: Goma installed directory to mount into the chroot. If specified,
923 build chrome with goma.
924 afdo_use: build chrome with AFDO optimization
Kuang-che Wubfc4a642018-04-19 11:54:08 +0800925 """
Kuang-che Wua9a20bb2019-09-05 22:24:04 +0800926 common_env = {
927 'USE': '-cros-debug chrome_internal',
928 'FEATURES': 'separatedebug',
929 }
Kuang-che Wu9890ce82018-07-07 15:14:10 +0800930 stderr_lines = []
931 try:
Kuang-che Wufb553102018-10-02 18:14:29 +0800932 with locking.lock_file(locking.LOCK_FILE_FOR_BUILD):
Kuang-che Wua9a20bb2019-09-05 22:24:04 +0800933 env = common_env.copy()
934 env['FEATURES'] += ' -separatedebug splitdebug'
Kuang-che Wufb553102018-10-02 18:14:29 +0800935 cros_sdk(
936 chromeos_root,
Kuang-che Wu28980b22019-07-31 19:51:45 +0800937 './update_chroot',
938 '--toolchain_boards',
Kuang-che Wufb553102018-10-02 18:14:29 +0800939 board,
Kuang-che Wua9a20bb2019-09-05 22:24:04 +0800940 env=env,
Kuang-che Wu28980b22019-07-31 19:51:45 +0800941 stderr_callback=stderr_lines.append)
Kuang-che Wua9a20bb2019-09-05 22:24:04 +0800942
943 env = common_env.copy()
944 cmd = [
Kuang-che Wu28980b22019-07-31 19:51:45 +0800945 './build_packages',
946 '--board',
947 board,
948 '--withdev',
949 '--noworkon',
950 '--skip_chroot_upgrade',
951 '--accept_licenses=@CHROMEOS',
Kuang-che Wua9a20bb2019-09-05 22:24:04 +0800952 ]
953 if goma_dir:
954 # Tell build_packages to start and stop goma
955 cmd.append('--run_goma')
956 env['USE_GOMA'] = 'true'
957 if afdo_use:
958 env['USE'] += ' afdo_use'
959 cros_sdk(
960 chromeos_root,
961 *cmd,
962 env=env,
963 chrome_root=chrome_root,
964 stderr_callback=stderr_lines.append,
965 goma_dir=goma_dir)
Kuang-che Wu9890ce82018-07-07 15:14:10 +0800966 except subprocess.CalledProcessError as e:
967 # Detect failures due to incompatibility between chroot and source tree. If
968 # so, notify the caller to recreate chroot and retry.
969 reason = check_if_need_recreate_chroot(e.output, ''.join(stderr_lines))
970 if reason:
971 raise NeedRecreateChrootException(reason)
972
973 # For other failures, don't know how to handle. Just bail out.
974 raise
975
Kuang-che Wu28980b22019-07-31 19:51:45 +0800976
977def build_image(chromeos_root, board):
978 """Build ChromeOS image.
979
980 Args:
981 chromeos_root: chromeos tree root
982 board: ChromeOS board name
983
984 Returns:
985 image folder; relative to chromeos_root
986 """
987 stderr_lines = []
988 try:
989 with locking.lock_file(locking.LOCK_FILE_FOR_BUILD):
990 cros_sdk(
991 chromeos_root,
992 './build_image',
993 '--board',
994 board,
995 '--noenable_rootfs_verification',
996 'test',
997 env={
998 'USE': '-cros-debug chrome_internal',
999 'FEATURES': 'separatedebug',
1000 },
1001 stderr_callback=stderr_lines.append)
1002 except subprocess.CalledProcessError as e:
1003 # Detect failures due to incompatibility between chroot and source tree. If
1004 # so, notify the caller to recreate chroot and retry.
1005 reason = check_if_need_recreate_chroot(e.output, ''.join(stderr_lines))
1006 if reason:
1007 raise NeedRecreateChrootException(reason)
1008
1009 # For other failures, don't know how to handle. Just bail out.
1010 raise
1011
1012 image_symlink = os.path.join(chromeos_root, cached_images_dir, board,
1013 'latest')
1014 assert os.path.exists(image_symlink)
1015 image_name = os.readlink(image_symlink)
1016 image_folder = os.path.join(cached_images_dir, board, image_name)
1017 assert os.path.exists(
1018 os.path.join(chromeos_root, image_folder, test_image_filename))
1019 return image_folder
Kuang-che Wubfc4a642018-04-19 11:54:08 +08001020
1021
Kuang-che Wub9705bd2018-06-28 17:59:18 +08001022class AutotestControlInfo(object):
1023 """Parsed content of autotest control file.
1024
1025 Attributes:
1026 name: test name
1027 path: control file path
1028 variables: dict of top-level control variables. Sample keys: NAME, AUTHOR,
1029 DOC, ATTRIBUTES, DEPENDENCIES, etc.
1030 """
1031
1032 def __init__(self, path, variables):
1033 self.name = variables['NAME']
1034 self.path = path
1035 self.variables = variables
1036
1037
1038def parse_autotest_control_file(path):
1039 """Parses autotest control file.
1040
1041 This only parses simple top-level string assignments.
1042
1043 Returns:
1044 AutotestControlInfo object
1045 """
1046 variables = {}
1047 code = ast.parse(open(path).read())
1048 for stmt in code.body:
1049 # Skip if not simple "NAME = *" assignment.
1050 if not (isinstance(stmt, ast.Assign) and len(stmt.targets) == 1 and
1051 isinstance(stmt.targets[0], ast.Name)):
1052 continue
1053
1054 # Only support string value.
1055 if isinstance(stmt.value, ast.Str):
1056 variables[stmt.targets[0].id] = stmt.value.s
1057
1058 return AutotestControlInfo(path, variables)
1059
1060
1061def enumerate_autotest_control_files(autotest_dir):
1062 """Enumerate autotest control files.
1063
1064 Args:
1065 autotest_dir: autotest folder
1066
1067 Returns:
1068 list of paths to control files
1069 """
1070 # Where to find control files. Relative to autotest_dir.
1071 subpaths = [
1072 'server/site_tests',
1073 'client/site_tests',
1074 'server/tests',
1075 'client/tests',
1076 ]
1077
1078 blacklist = ['site-packages', 'venv', 'results', 'logs', 'containers']
1079 result = []
1080 for subpath in subpaths:
1081 path = os.path.join(autotest_dir, subpath)
1082 for root, dirs, files in os.walk(path):
1083
1084 for black in blacklist:
1085 if black in dirs:
1086 dirs.remove(black)
1087
1088 for filename in files:
1089 if filename == 'control' or filename.startswith('control.'):
1090 result.append(os.path.join(root, filename))
1091
1092 return result
1093
1094
1095def get_autotest_test_info(autotest_dir, test_name):
1096 """Get metadata of given test.
1097
1098 Args:
1099 autotest_dir: autotest folder
1100 test_name: test name
1101
1102 Returns:
1103 AutotestControlInfo object. None if test not found.
1104 """
1105 for control_file in enumerate_autotest_control_files(autotest_dir):
1106 info = parse_autotest_control_file(control_file)
1107 if info.name == test_name:
1108 return info
1109 return None
1110
1111
Kuang-che Wue4bae0b2018-07-19 12:10:14 +08001112class ChromeOSSpecManager(codechange.SpecManager):
1113 """Repo manifest related operations.
1114
1115 This class enumerates chromeos manifest files, parses them,
1116 and sync to disk state according to them.
1117 """
Kuang-che Wubfc4a642018-04-19 11:54:08 +08001118
1119 def __init__(self, config):
1120 self.config = config
Kuang-che Wue4bae0b2018-07-19 12:10:14 +08001121 self.manifest_dir = os.path.join(self.config['chromeos_root'], '.repo',
1122 'manifests')
1123 self.historical_manifest_git_dir = os.path.join(
Kuang-che Wud8fc9572018-10-03 21:00:41 +08001124 self.config['chromeos_mirror'], 'chromeos/manifest-versions.git')
Kuang-che Wue4bae0b2018-07-19 12:10:14 +08001125 if not os.path.exists(self.historical_manifest_git_dir):
Kuang-che Wue121fae2018-11-09 16:18:39 +08001126 raise errors.InternalError('Manifest snapshots should be cloned into %s' %
1127 self.historical_manifest_git_dir)
Kuang-che Wubfc4a642018-04-19 11:54:08 +08001128
Kuang-che Wue4bae0b2018-07-19 12:10:14 +08001129 def lookup_build_timestamp(self, rev):
Kuang-che Wubfc4a642018-04-19 11:54:08 +08001130 assert is_cros_full_version(rev)
Kuang-che Wue4bae0b2018-07-19 12:10:14 +08001131
Kuang-che Wubfc4a642018-04-19 11:54:08 +08001132 milestone, short_version = version_split(rev)
Kuang-che Wue4bae0b2018-07-19 12:10:14 +08001133 path = os.path.join('buildspecs', milestone, short_version + '.xml')
1134 try:
1135 timestamp = git_util.get_commit_time(self.historical_manifest_git_dir,
1136 'refs/heads/master', path)
1137 except ValueError:
Kuang-che Wue121fae2018-11-09 16:18:39 +08001138 raise errors.InternalError(
Kuang-che Wu74768d32018-09-07 12:03:24 +08001139 '%s does not have %s' % (self.historical_manifest_git_dir, path))
Kuang-che Wue4bae0b2018-07-19 12:10:14 +08001140 return timestamp
Kuang-che Wubfc4a642018-04-19 11:54:08 +08001141
Kuang-che Wue4bae0b2018-07-19 12:10:14 +08001142 def collect_float_spec(self, old, new):
1143 old_timestamp = self.lookup_build_timestamp(old)
1144 new_timestamp = self.lookup_build_timestamp(new)
Kuang-che Wubfc4a642018-04-19 11:54:08 +08001145
Kuang-che Wue4bae0b2018-07-19 12:10:14 +08001146 path = os.path.join(self.manifest_dir, 'default.xml')
1147 if not os.path.islink(path) or os.readlink(path) != 'full.xml':
Kuang-che Wue121fae2018-11-09 16:18:39 +08001148 raise errors.InternalError(
1149 'default.xml not symlink to full.xml is not supported')
Kuang-che Wubfc4a642018-04-19 11:54:08 +08001150
Kuang-che Wue4bae0b2018-07-19 12:10:14 +08001151 result = []
1152 path = 'full.xml'
1153 parser = repo_util.ManifestParser(self.manifest_dir)
1154 for timestamp, git_rev in parser.enumerate_manifest_commits(
1155 old_timestamp, new_timestamp, path):
1156 result.append(
1157 codechange.Spec(codechange.SPEC_FLOAT, git_rev, timestamp, path))
1158 return result
Kuang-che Wubfc4a642018-04-19 11:54:08 +08001159
Kuang-che Wue4bae0b2018-07-19 12:10:14 +08001160 def collect_fixed_spec(self, old, new):
Kuang-che Wubfc4a642018-04-19 11:54:08 +08001161 assert is_cros_full_version(old)
1162 assert is_cros_full_version(new)
1163 old_milestone, old_short_version = version_split(old)
1164 new_milestone, new_short_version = version_split(new)
1165
Kuang-che Wubfc4a642018-04-19 11:54:08 +08001166 result = []
Kuang-che Wue4bae0b2018-07-19 12:10:14 +08001167 for milestone in git_util.list_dir_from_revision(
1168 self.historical_manifest_git_dir, 'refs/heads/master', 'buildspecs'):
1169 if not milestone.isdigit():
1170 continue
1171 if not int(old_milestone) <= int(milestone) <= int(new_milestone):
1172 continue
1173
Kuang-che Wu74768d32018-09-07 12:03:24 +08001174 files = git_util.list_dir_from_revision(
1175 self.historical_manifest_git_dir, 'refs/heads/master',
1176 os.path.join('buildspecs', milestone))
Kuang-che Wubfc4a642018-04-19 11:54:08 +08001177
1178 for fn in files:
Kuang-che Wue4bae0b2018-07-19 12:10:14 +08001179 path = os.path.join('buildspecs', milestone, fn)
Kuang-che Wubfc4a642018-04-19 11:54:08 +08001180 short_version, ext = os.path.splitext(fn)
1181 if ext != '.xml':
1182 continue
Kuang-che Wubfc4a642018-04-19 11:54:08 +08001183 if (util.is_version_lesseq(old_short_version, short_version) and
1184 util.is_version_lesseq(short_version, new_short_version) and
1185 util.is_direct_relative_version(short_version, new_short_version)):
Kuang-che Wue4bae0b2018-07-19 12:10:14 +08001186 rev = make_cros_full_version(milestone, short_version)
1187 timestamp = git_util.get_commit_time(self.historical_manifest_git_dir,
1188 'refs/heads/master', path)
1189 result.append(
1190 codechange.Spec(codechange.SPEC_FIXED, rev, timestamp, path))
Kuang-che Wubfc4a642018-04-19 11:54:08 +08001191
Kuang-che Wue4bae0b2018-07-19 12:10:14 +08001192 def version_key_func(spec):
1193 _milestone, short_version = version_split(spec.name)
Kuang-che Wubfc4a642018-04-19 11:54:08 +08001194 return util.version_key_func(short_version)
1195
1196 result.sort(key=version_key_func)
Kuang-che Wue4bae0b2018-07-19 12:10:14 +08001197 assert result[0].name == old
1198 assert result[-1].name == new
Kuang-che Wubfc4a642018-04-19 11:54:08 +08001199 return result
1200
Kuang-che Wue4bae0b2018-07-19 12:10:14 +08001201 def get_manifest(self, rev):
1202 assert is_cros_full_version(rev)
1203 milestone, short_version = version_split(rev)
1204 path = os.path.join('buildspecs', milestone, '%s.xml' % short_version)
1205 manifest = git_util.get_file_from_revision(self.historical_manifest_git_dir,
1206 'refs/heads/master', path)
1207
1208 manifest_name = 'manifest_%s.xml' % rev
1209 manifest_path = os.path.join(self.manifest_dir, manifest_name)
1210 with open(manifest_path, 'w') as f:
1211 f.write(manifest)
1212
1213 return manifest_name
1214
1215 def parse_spec(self, spec):
1216 parser = repo_util.ManifestParser(self.manifest_dir)
1217 if spec.spec_type == codechange.SPEC_FIXED:
1218 manifest_name = self.get_manifest(spec.name)
1219 manifest_path = os.path.join(self.manifest_dir, manifest_name)
1220 content = open(manifest_path).read()
1221 root = parser.parse_single_xml(content, allow_include=False)
1222 else:
1223 root = parser.parse_xml_recursive(spec.name, spec.path)
1224
1225 spec.entries = parser.process_parsed_result(root)
1226 if spec.spec_type == codechange.SPEC_FIXED:
1227 assert spec.is_static()
1228
1229 def sync_disk_state(self, rev):
1230 manifest_name = self.get_manifest(rev)
1231
1232 # For ChromeOS, mark_as_stable step requires 'repo init -m', which sticks
1233 # manifest. 'repo sync -m' is not enough
1234 repo_util.init(
1235 self.config['chromeos_root'],
1236 'https://chrome-internal.googlesource.com/chromeos/manifest-internal',
1237 manifest_name=manifest_name,
1238 repo_url='https://chromium.googlesource.com/external/repo.git',
Kuang-che Wud8fc9572018-10-03 21:00:41 +08001239 reference=self.config['chromeos_mirror'],
Kuang-che Wue4bae0b2018-07-19 12:10:14 +08001240 )
1241
1242 # Note, don't sync with current_branch=True for chromeos. One of its
1243 # build steps (inside mark_as_stable) executes "git describe" which
1244 # needs git tag information.
1245 repo_util.sync(self.config['chromeos_root'])