blob: 66152e87574233ea94e1c5eb168fe89ea64d90ed [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".
Zheng-Jie Chang127c3302019-09-10 17:17:04 +080010 snapshot_version: ChromeOS version number with milestone and snapshot id,
11 like "R62-9876.0.0-12345".
Kuang-che Wu2ea804f2017-11-28 17:11:41 +080012 version: if not specified, it could be in short or full format.
13"""
14
15from __future__ import print_function
Kuang-che Wub9705bd2018-06-28 17:59:18 +080016import ast
Zheng-Jie Chang127c3302019-09-10 17:17:04 +080017import datetime
Kuang-che Wu2ea804f2017-11-28 17:11:41 +080018import errno
19import json
20import logging
21import os
22import re
23import subprocess
24import time
25
26from bisect_kit import cli
Kuang-che Wue4bae0b2018-07-19 12:10:14 +080027from bisect_kit import codechange
Kuang-che Wu3eb6b502018-06-06 16:15:18 +080028from bisect_kit import cr_util
Kuang-che Wue121fae2018-11-09 16:18:39 +080029from bisect_kit import errors
Kuang-che Wubfc4a642018-04-19 11:54:08 +080030from bisect_kit import git_util
Kuang-che Wufb553102018-10-02 18:14:29 +080031from bisect_kit import locking
Kuang-che Wubfc4a642018-04-19 11:54:08 +080032from bisect_kit import repo_util
Kuang-che Wu2ea804f2017-11-28 17:11:41 +080033from bisect_kit import util
34
35logger = logging.getLogger(__name__)
36
Kuang-che Wu2ea804f2017-11-28 17:11:41 +080037re_chromeos_full_version = r'^R\d+-\d+\.\d+\.\d+$'
Kuang-che Wuacb6efd2018-04-25 18:52:58 +080038re_chromeos_localbuild_version = r'^\d+\.\d+\.\d{4}_\d\d_\d\d_\d{4}$'
39re_chromeos_short_version = r'^\d+\.\d+\.\d+$'
Zheng-Jie Chang127c3302019-09-10 17:17:04 +080040re_chromeos_snapshot_version = r'^R\d+-\d+\.\d+\.\d+-\d+$'
Kuang-che Wu2ea804f2017-11-28 17:11:41 +080041
42gs_archive_path = 'gs://chromeos-image-archive/{board}-release'
43gs_release_path = (
Kuang-che Wu80bf6a52019-05-31 12:48:06 +080044 'gs://chromeos-releases/{channel}-channel/{boardpath}/{short_version}')
Kuang-che Wu2ea804f2017-11-28 17:11:41 +080045
46# Assume gsutil is in PATH.
47gsutil_bin = 'gsutil'
48
Kuang-che Wub9705bd2018-06-28 17:59:18 +080049chromeos_root_inside_chroot = '/mnt/host/source'
50# relative to chromeos_root
Kuang-che Wu7f82c6f2019-08-12 14:29:28 +080051prebuilt_autotest_dir = 'tmp/autotest-prebuilt'
Kuang-che Wu28980b22019-07-31 19:51:45 +080052# Relative to chromeos root. Images are cached_images_dir/$board/$image_name.
53cached_images_dir = 'src/build/images'
54test_image_filename = 'chromiumos_test_image.bin'
Kuang-che Wub9705bd2018-06-28 17:59:18 +080055
Kuang-che Wu2ea804f2017-11-28 17:11:41 +080056VERSION_KEY_CROS_SHORT_VERSION = 'cros_short_version'
57VERSION_KEY_CROS_FULL_VERSION = 'cros_full_version'
58VERSION_KEY_MILESTONE = 'milestone'
59VERSION_KEY_CR_VERSION = 'cr_version'
Kuang-che Wu708310b2018-03-28 17:24:34 +080060VERSION_KEY_ANDROID_BUILD_ID = 'android_build_id'
Kuang-che Wu2ea804f2017-11-28 17:11:41 +080061VERSION_KEY_ANDROID_BRANCH = 'android_branch'
62
63
Kuang-che Wu9890ce82018-07-07 15:14:10 +080064class NeedRecreateChrootException(Exception):
65 """Failed to build ChromeOS because of chroot mismatch or corruption"""
66
67
Kuang-che Wu2ea804f2017-11-28 17:11:41 +080068def is_cros_short_version(s):
Kuang-che Wuacb6efd2018-04-25 18:52:58 +080069 """Determines if `s` is chromeos short version.
70
71 This function doesn't accept version number of local build.
72 """
Kuang-che Wu2ea804f2017-11-28 17:11:41 +080073 return bool(re.match(re_chromeos_short_version, s))
74
75
Kuang-che Wuacb6efd2018-04-25 18:52:58 +080076def is_cros_localbuild_version(s):
77 """Determines if `s` is chromeos local build version."""
78 return bool(re.match(re_chromeos_localbuild_version, s))
79
80
Kuang-che Wu2ea804f2017-11-28 17:11:41 +080081def is_cros_full_version(s):
Kuang-che Wuacb6efd2018-04-25 18:52:58 +080082 """Determines if `s` is chromeos full version.
83
84 This function doesn't accept version number of local build.
85 """
Kuang-che Wu2ea804f2017-11-28 17:11:41 +080086 return bool(re.match(re_chromeos_full_version, s))
87
88
89def is_cros_version(s):
90 """Determines if `s` is chromeos version (either short or full)"""
91 return is_cros_short_version(s) or is_cros_full_version(s)
92
93
Zheng-Jie Chang127c3302019-09-10 17:17:04 +080094def is_cros_snapshot_version(s):
95 """Determines if `s` is chromeos snapshot version"""
96 return bool(re.match(re_chromeos_snapshot_version, s))
97
98
Kuang-che Wu2ea804f2017-11-28 17:11:41 +080099def make_cros_full_version(milestone, short_version):
100 """Makes full_version from milestone and short_version"""
Kuang-che Wuacb6efd2018-04-25 18:52:58 +0800101 assert milestone
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800102 return 'R%s-%s' % (milestone, short_version)
103
104
Zheng-Jie Chang127c3302019-09-10 17:17:04 +0800105def make_cros_snapshot_version(milestone, short_version, snapshot_id):
106 """Makes snapshot version from milestone, short_version and snapshot id"""
107 return 'R%s-%s-%s' % (milestone, short_version, snapshot_id)
108
109
110def version_split(version):
111 """Splits full_version or snapshot_version into milestone and short_version"""
112 assert is_cros_full_version(version) or is_cros_snapshot_version(version)
113 if is_cros_snapshot_version(version):
114 return snapshot_version_split(version)[0:2]
115 milestone, short_version = version.split('-')
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800116 return milestone[1:], short_version
117
118
Zheng-Jie Chang127c3302019-09-10 17:17:04 +0800119def snapshot_version_split(snapshot_version):
120 """Splits snapshot_version into milestone, short_version and snapshot_id"""
121 assert is_cros_snapshot_version(snapshot_version)
122 milestone, shot_version, snapshot_id = snapshot_version.split('-')
123 return milestone[1:], shot_version, snapshot_id
124
125
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800126def argtype_cros_version(s):
Zheng-Jie Chang127c3302019-09-10 17:17:04 +0800127 if (not is_cros_version(s)) and (not is_cros_snapshot_version(s)):
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800128 msg = 'invalid cros version'
Kuang-che Wuce2f3be2019-10-28 19:44:54 +0800129 raise cli.ArgTypeError(msg, '9876.0.0, R62-9876.0.0 or R77-12369.0.0-11681')
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800130 return s
131
132
133def query_dut_lsb_release(host):
134 """Query /etc/lsb-release of given DUT
135
136 Args:
137 host: the DUT address
138
139 Returns:
Kuang-che Wu3eb6b502018-06-06 16:15:18 +0800140 dict for keys and values of /etc/lsb-release.
141
142 Raises:
Kuang-che Wu44278142019-03-04 11:33:57 +0800143 errors.SshConnectionError: cannot connect to host
144 errors.ExternalError: lsb-release file doesn't exist
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800145 """
146 try:
Kuang-che Wu44278142019-03-04 11:33:57 +0800147 output = util.ssh_cmd(host, 'cat', '/etc/lsb-release')
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800148 except subprocess.CalledProcessError:
Kuang-che Wu44278142019-03-04 11:33:57 +0800149 raise errors.ExternalError('unable to read /etc/lsb-release; not a DUT')
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800150 return dict(re.findall(r'^(\w+)=(.*)$', output, re.M))
151
152
153def is_dut(host):
Kuang-che Wubfc4a642018-04-19 11:54:08 +0800154 """Determines whether a host is a chromeos device.
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800155
156 Args:
157 host: the DUT address
158
159 Returns:
160 True if the host is a chromeos device.
161 """
Kuang-che Wu44278142019-03-04 11:33:57 +0800162 try:
163 return query_dut_lsb_release(host).get('DEVICETYPE') in [
164 'CHROMEBASE',
165 'CHROMEBIT',
166 'CHROMEBOOK',
167 'CHROMEBOX',
168 'REFERENCE',
169 ]
170 except (errors.ExternalError, errors.SshConnectionError):
171 return False
172
173
174def is_good_dut(host):
175 if not is_dut(host):
176 return False
177
178 # Sometimes python is broken after 'cros flash'.
179 try:
180 util.ssh_cmd(host, 'python', '-c', '1')
181 return True
182 except (subprocess.CalledProcessError, errors.SshConnectionError):
183 return False
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800184
185
186def query_dut_board(host):
187 """Query board name of a given DUT"""
188 return query_dut_lsb_release(host).get('CHROMEOS_RELEASE_BOARD')
189
190
191def query_dut_short_version(host):
Kuang-che Wuacb6efd2018-04-25 18:52:58 +0800192 """Query short version of a given DUT.
193
194 This function may return version of local build, which
195 is_cros_short_version() is false.
196 """
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800197 return query_dut_lsb_release(host).get('CHROMEOS_RELEASE_VERSION')
198
199
Zheng-Jie Chang127c3302019-09-10 17:17:04 +0800200def query_dut_is_snapshot(host):
201 """Query if given DUT is a snapshot version."""
202 path = query_dut_lsb_release(host).get('CHROMEOS_RELEASE_BUILDER_PATH', '')
203 return '-postsubmit' in path
204
205
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800206def query_dut_boot_id(host, connect_timeout=None):
Kuang-che Wubfc4a642018-04-19 11:54:08 +0800207 """Query boot id.
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800208
209 Args:
210 host: DUT address
211 connect_timeout: connection timeout
212
213 Returns:
214 boot uuid
215 """
Kuang-che Wu44278142019-03-04 11:33:57 +0800216 return util.ssh_cmd(
217 host,
218 'cat',
219 '/proc/sys/kernel/random/boot_id',
220 connect_timeout=connect_timeout).strip()
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800221
222
223def reboot(host):
224 """Reboot a DUT and verify"""
225 logger.debug('reboot %s', host)
226 boot_id = query_dut_boot_id(host)
227
Kuang-che Wu44278142019-03-04 11:33:57 +0800228 try:
229 util.ssh_cmd(host, 'reboot')
Kuang-che Wu5f662e82019-03-05 11:49:56 +0800230 except errors.SshConnectionError:
231 # Depends on timing, ssh may return failure due to broken pipe, which is
232 # working as intended. Ignore such kind of errors.
Kuang-che Wu44278142019-03-04 11:33:57 +0800233 pass
Kuang-che Wu708310b2018-03-28 17:24:34 +0800234 wait_reboot_done(host, boot_id)
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800235
Kuang-che Wu708310b2018-03-28 17:24:34 +0800236
237def wait_reboot_done(host, boot_id):
Kuang-che Wu4fe945b2018-03-31 16:46:38 +0800238 # For dev-mode test image, the reboot time is roughly at least 16 seconds
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800239 # (dev screen short delay) or more (long delay).
240 time.sleep(15)
241 for _ in range(100):
242 try:
243 # During boot, DUT does not response and thus ssh may hang a while. So
244 # set a connect timeout. 3 seconds are enough and 2 are not. It's okay to
245 # set tight limit because it's inside retry loop.
246 assert boot_id != query_dut_boot_id(host, connect_timeout=3)
247 return
Kuang-che Wu5f662e82019-03-05 11:49:56 +0800248 except errors.SshConnectionError:
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800249 logger.debug('reboot not ready? sleep wait 1 sec')
250 time.sleep(1)
251
Kuang-che Wue121fae2018-11-09 16:18:39 +0800252 raise errors.ExternalError('reboot failed?')
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800253
254
Kuang-che Wu80bf6a52019-05-31 12:48:06 +0800255def gs_release_boardpath(board):
256 """Normalizes board name for gs://chromeos-releases/
257
258 This follows behavior of PushImage() in chromite/scripts/pushimage.py
259 Note, only gs://chromeos-releases/ needs normalization,
260 gs://chromeos-image-archive does not.
261
262 Args:
263 board: ChromeOS board name
264
265 Returns:
266 normalized board name
267 """
268 return board.replace('_', '-')
269
270
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800271def gsutil(*args, **kwargs):
Kuang-che Wubfc4a642018-04-19 11:54:08 +0800272 """gsutil command line wrapper.
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800273
274 Args:
275 args: command line arguments passed to gsutil
276 kwargs:
277 ignore_errors: if true, return '' for failures, for example 'gsutil ls'
278 but the path not found.
279
280 Returns:
281 stdout of gsutil
282
283 Raises:
Kuang-che Wue121fae2018-11-09 16:18:39 +0800284 errors.InternalError: gsutil failed to run
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800285 subprocess.CalledProcessError: command failed
286 """
287 stderr_lines = []
288 try:
289 return util.check_output(
290 gsutil_bin, *args, stderr_callback=stderr_lines.append)
291 except subprocess.CalledProcessError as e:
292 stderr = ''.join(stderr_lines)
293 if re.search(r'ServiceException:.* does not have .*access', stderr):
Kuang-che Wue121fae2018-11-09 16:18:39 +0800294 raise errors.ExternalError(
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800295 'gsutil failed due to permission. ' +
296 'Run "%s config" and follow its instruction. ' % gsutil_bin +
297 'Fill any string if it asks for project-id')
298 if kwargs.get('ignore_errors'):
299 return ''
300 raise
301 except OSError as e:
302 if e.errno == errno.ENOENT:
Kuang-che Wue121fae2018-11-09 16:18:39 +0800303 raise errors.ExternalError(
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800304 'Unable to run %s. gsutil is not installed or not in PATH?' %
305 gsutil_bin)
306 raise
307
308
309def gsutil_ls(*args, **kwargs):
Kuang-che Wubfc4a642018-04-19 11:54:08 +0800310 """gsutil ls.
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800311
312 Args:
313 args: arguments passed to 'gsutil ls'
314 kwargs: extra parameters, where
Kuang-che Wu4fe945b2018-03-31 16:46:38 +0800315 ignore_errors: if true, return empty list instead of raising
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800316 exception, ex. path not found.
317
318 Returns:
319 list of 'gsutil ls' result. One element for one line of gsutil output.
320
321 Raises:
322 subprocess.CalledProcessError: gsutil failed, usually means path not found
323 """
324 return gsutil('ls', *args, **kwargs).splitlines()
325
326
Zheng-Jie Chang127c3302019-09-10 17:17:04 +0800327def gsutil_stat_update_time(*args, **kwargs):
328 """Returns the last modified time of a file or multiple files.
329
330 Args:
331 args: arguments passed to 'gsutil stat'.
332 kwargs: extra parameters for gsutil.
333
334 Returns:
335 A integer indicates the last modified timestamp.
336
337 Raises:
338 subprocess.CalledProcessError: gsutil failed, usually means path not found
339 errors.ExternalError: update time is not found
340 """
341 result = -1
342 # Currently we believe stat always returns a UTC time, and strptime also
343 # parses a UTC time by default.
344 time_format = '%a, %d %b %Y %H:%M:%S GMT'
345
346 for line in gsutil('stat', *args, **kwargs).splitlines():
347 if ':' not in line:
348 continue
349 key, value = map(str.strip, line.split(':', 1))
350 if key != 'Update time':
351 continue
352 dt = datetime.datetime.strptime(value, time_format)
353 unixtime = int(time.mktime(dt.utctimetuple()))
354 result = max(result, unixtime)
355
356 if result == -1:
357 raise errors.ExternalError("didn't find update time")
358 return result
359
360
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800361def query_milestone_by_version(board, short_version):
Kuang-che Wubfc4a642018-04-19 11:54:08 +0800362 """Query milestone by ChromeOS version number.
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800363
364 Args:
365 board: ChromeOS board name
366 short_version: ChromeOS version number in short format, ex. 9300.0.0
367
368 Returns:
369 ChromeOS milestone number (string). For example, '58' for '9300.0.0'.
370 None if failed.
371 """
372 path = gs_archive_path.format(board=board) + '/R*-' + short_version
373 for line in gsutil_ls('-d', path, ignore_errors=True):
374 m = re.search(r'/R(\d+)-', line)
375 if not m:
376 continue
377 return m.group(1)
378
379 for channel in ['canary', 'dev', 'beta', 'stable']:
380 path = gs_release_path.format(
Kuang-che Wu80bf6a52019-05-31 12:48:06 +0800381 channel=channel,
382 boardpath=gs_release_boardpath(board),
383 short_version=short_version)
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800384 for line in gsutil_ls(path, ignore_errors=True):
385 m = re.search(r'\bR(\d+)-' + short_version, line)
386 if not m:
387 continue
388 return m.group(1)
389
390 logger.error('unable to query milestone of %s for %s', short_version, board)
391 return None
392
393
Kuang-che Wu80bf6a52019-05-31 12:48:06 +0800394def list_board_names(chromeos_root):
395 """List board names.
396
397 Args:
398 chromeos_root: chromeos tree root
399
400 Returns:
401 list of board names
402 """
403 # Following logic is simplified from chromite/lib/portage_util.py
404 cros_list_overlays = os.path.join(chromeos_root,
405 'chromite/bin/cros_list_overlays')
406 overlays = util.check_output(cros_list_overlays).splitlines()
407 result = set()
408 for overlay in overlays:
409 conf_file = os.path.join(overlay, 'metadata', 'layout.conf')
410 name = None
411 if os.path.exists(conf_file):
412 for line in open(conf_file):
413 m = re.match(r'^repo-name\s*=\s*(\S+)\s*$', line)
414 if m:
415 name = m.group(1)
416 break
417
418 if not name:
419 name_file = os.path.join(overlay, 'profiles', 'repo_name')
420 if os.path.exists(name_file):
421 name = open(name_file).read().strip()
422
423 if name:
424 name = re.sub(r'-private$', '', name)
425 result.add(name)
426
427 return list(result)
428
429
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800430def recognize_version(board, version):
Kuang-che Wubfc4a642018-04-19 11:54:08 +0800431 """Recognize ChromeOS version.
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800432
433 Args:
434 board: ChromeOS board name
435 version: ChromeOS version number in short or full format
436
437 Returns:
438 (milestone, version in short format)
439 """
440 if is_cros_short_version(version):
441 milestone = query_milestone_by_version(board, version)
442 short_version = version
443 else:
444 milestone, short_version = version_split(version)
445 return milestone, short_version
446
447
Zheng-Jie Chang127c3302019-09-10 17:17:04 +0800448def extract_major_version(version):
449 """Converts a version to its major version.
450
451 Args:
452 version: ChromsOS version number or snapshot version
453
454 Returns:
455 major version number in string format
456 """
457 version = version_to_short(version)
458 m = re.match(r'^(\d+)\.\d+\.\d+$', version)
459 return m.group(1)
460
461
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800462def version_to_short(version):
Kuang-che Wubfc4a642018-04-19 11:54:08 +0800463 """Convert ChromeOS version number to short format.
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800464
465 Args:
466 version: ChromeOS version number in short or full format
467
468 Returns:
469 version number in short format
470 """
471 if is_cros_short_version(version):
472 return version
473 _, short_version = version_split(version)
474 return short_version
475
476
477def version_to_full(board, version):
Kuang-che Wubfc4a642018-04-19 11:54:08 +0800478 """Convert ChromeOS version number to full format.
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800479
480 Args:
481 board: ChromeOS board name
482 version: ChromeOS version number in short or full format
483
484 Returns:
485 version number in full format
486 """
487 if is_cros_full_version(version):
488 return version
489 milestone = query_milestone_by_version(board, version)
Kuang-che Wu0205f052019-05-23 12:48:37 +0800490 assert milestone, 'incorrect board=%s or version=%s ?' % (board, version)
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800491 return make_cros_full_version(milestone, version)
492
493
Zheng-Jie Chang127c3302019-09-10 17:17:04 +0800494def list_snapshots_from_image_archive(board, major_version):
495 """List ChromeOS snapshot image avaliable from gs://chromeos-image-archive.
496
497 Args:
498 board: ChromeOS board
499 major_version: ChromeOS major version
500
501 Returns:
502 list of (version, gs_path):
503 version: Chrome OS snapshot version
504 gs_path: gs path of test image
505 """
506
507 path = (
508 'gs://chromeos-image-archive/{board}-postsubmit/R*-{major_version}.0.0-*')
509 result = []
510 output = gsutil_ls(
511 '-d',
512 path.format(board=board, major_version=major_version),
513 ignore_errors=True)
514
515 for path in output:
516 if not path.endswith('/'):
517 continue
518 m = re.match(r'^gs:\S+(R\d+-\d+\.\d+\.\d+-\d+)', path)
519 if m:
520 snapshot_version = m.group(1)
521 test_image = 'image.zip'
522 gs_path = path + test_image
523 result.append((snapshot_version, gs_path))
524 return result
525
526
Kuang-che Wu575dc442019-03-05 10:30:55 +0800527def list_prebuilt_from_image_archive(board):
528 """Lists ChromeOS prebuilt image available from gs://chromeos-image-archive.
529
530 gs://chromeos-image-archive contains only recent builds (in two years).
531 We prefer this function to list_prebuilt_from_chromeos_releases() because
532 - this is what "cros flash" supports directly.
533 - the paths have milestone information, so we don't need to do slow query
534 by ourselves.
535
536 Args:
537 board: ChromeOS board name
538
539 Returns:
540 list of (version, gs_path):
541 version: Chrome OS version in full format
542 gs_path: gs path of test image
543 """
544 result = []
545 for line in gsutil_ls(gs_archive_path.format(board=board)):
546 m = re.match(r'^gs:\S+(R\d+-\d+\.\d+\.\d+)', line)
547 if m:
548 full_version = m.group(1)
549 test_image = 'chromiumos_test_image.tar.xz'
550 assert line.endswith('/')
551 gs_path = line + test_image
552 result.append((full_version, gs_path))
553 return result
554
555
556def list_prebuilt_from_chromeos_releases(board):
557 """Lists ChromeOS versions available from gs://chromeos-releases.
558
559 gs://chromeos-releases contains more builds. However, 'cros flash' doesn't
560 support it.
561
562 Args:
563 board: ChromeOS board name
564
565 Returns:
566 list of (version, gs_path):
567 version: Chrome OS version in short format
568 gs_path: gs path of test image (with wildcard)
569 """
570 result = []
571 for line in gsutil_ls(
Kuang-che Wu80bf6a52019-05-31 12:48:06 +0800572 gs_release_path.format(
573 channel='*', boardpath=gs_release_boardpath(board), short_version=''),
Kuang-che Wu575dc442019-03-05 10:30:55 +0800574 ignore_errors=True):
575 m = re.match(r'gs:\S+/(\d+\.\d+\.\d+)/$', line)
576 if m:
577 short_version = m.group(1)
578 test_image = 'ChromeOS-test-R*-{short_version}-{board}.tar.xz'.format(
579 short_version=short_version, board=board)
580 gs_path = line + test_image
581 result.append((short_version, gs_path))
582 return result
583
584
585def list_chromeos_prebuilt_versions(board,
586 old,
587 new,
588 only_good_build=True,
Zheng-Jie Chang127c3302019-09-10 17:17:04 +0800589 include_older_build=True,
590 use_snapshot=False):
Kuang-che Wu575dc442019-03-05 10:30:55 +0800591 """Lists ChromeOS version numbers with prebuilt between given range
592
593 Args:
594 board: ChromeOS board name
595 old: start version (inclusive)
596 new: end version (inclusive)
597 only_good_build: only if test image is available
598 include_older_build: include prebuilt in gs://chromeos-releases
Zheng-Jie Chang127c3302019-09-10 17:17:04 +0800599 use_snapshot: return snapshot versions if found
Kuang-che Wu575dc442019-03-05 10:30:55 +0800600
601 Returns:
602 list of sorted version numbers (in full format) between [old, new] range
603 (inclusive).
604 """
605 old = version_to_short(old)
606 new = version_to_short(new)
607
Zheng-Jie Chang127c3302019-09-10 17:17:04 +0800608 rev_map = {
609 } # dict: short version -> list of (short/full or snapshot version, gs path)
Kuang-che Wu575dc442019-03-05 10:30:55 +0800610 for full_version, gs_path in list_prebuilt_from_image_archive(board):
611 short_version = version_to_short(full_version)
Zheng-Jie Chang127c3302019-09-10 17:17:04 +0800612 rev_map[short_version] = [(full_version, gs_path)]
Kuang-che Wu575dc442019-03-05 10:30:55 +0800613
614 if include_older_build and old not in rev_map:
615 for short_version, gs_path in list_prebuilt_from_chromeos_releases(board):
616 if short_version not in rev_map:
Zheng-Jie Chang127c3302019-09-10 17:17:04 +0800617 rev_map[short_version] = [(short_version, gs_path)]
618
619 if use_snapshot:
620 for major_version in range(
621 int(extract_major_version(old)),
622 int(extract_major_version(new)) + 1):
623 short_version = '%s.0.0' % major_version
624 if not util.is_direct_relative_version(short_version, old):
625 continue
626 if not util.is_direct_relative_version(short_version, new):
627 continue
628 snapshots = list_snapshots_from_image_archive(board, str(major_version))
629 if snapshots:
630 # if snapshots found, we can append them after the release version,
631 # so the prebuilt image list of this version will be
632 # release_image, snapshot1, snapshot2,...
633 rev_map[short_version] += snapshots
Kuang-che Wu575dc442019-03-05 10:30:55 +0800634
635 result = []
636 for rev in sorted(rev_map, key=util.version_key_func):
637 if not util.is_direct_relative_version(new, rev):
638 continue
639 if not util.is_version_lesseq(old, rev):
640 continue
641 if not util.is_version_lesseq(rev, new):
642 continue
643
Zheng-Jie Chang127c3302019-09-10 17:17:04 +0800644 for version, gs_path in rev_map[rev]:
Kuang-che Wu575dc442019-03-05 10:30:55 +0800645
Zheng-Jie Chang127c3302019-09-10 17:17:04 +0800646 # version_to_full() and gsutil_ls() may take long time if versions are a
647 # lot. This is acceptable because we usually bisect only short range.
Kuang-che Wu575dc442019-03-05 10:30:55 +0800648
Zheng-Jie Chang127c3302019-09-10 17:17:04 +0800649 if only_good_build:
650 gs_result = gsutil_ls(gs_path, ignore_errors=True)
651 if not gs_result:
652 logger.warning('%s is not a good build, ignore', version)
653 continue
654 assert len(gs_result) == 1
655 m = re.search(r'(R\d+-\d+\.\d+\.\d+)', gs_result[0])
656 if not m:
657 logger.warning('format of image path is unexpected: %s', gs_result[0])
658 continue
659 if not is_cros_snapshot_version(version):
660 version = m.group(1)
661 elif is_cros_short_version(version):
662 version = version_to_full(board, version)
Kuang-che Wu575dc442019-03-05 10:30:55 +0800663
Zheng-Jie Chang127c3302019-09-10 17:17:04 +0800664 result.append(version)
Kuang-che Wu575dc442019-03-05 10:30:55 +0800665
666 return result
667
668
Zheng-Jie Chang127c3302019-09-10 17:17:04 +0800669def prepare_snapshot_image(chromeos_root, board, snapshot_version):
670 """Prepare chromeos snapshot image.
671
672 Args:
673 chromeos_root: chromeos tree root
674 board: ChromeOS board name
675 snapshot_version: ChromeOS snapshot version number
676
677 Returns:
678 local file path of test image relative to chromeos_root
679 """
680 assert is_cros_snapshot_version(snapshot_version)
681 milestone, short_version, snapshot_id = snapshot_version_split(
682 snapshot_version)
683 full_version = make_cros_full_version(milestone, short_version)
684 tmp_dir = os.path.join(
685 chromeos_root, 'tmp',
686 'ChromeOS-test-%s-%s-%s' % (full_version, board, snapshot_id))
687 if not os.path.exists(tmp_dir):
688 os.makedirs(tmp_dir)
689
690 gs_path = ('gs://chromeos-image-archive/{board}-postsubmit/' +
691 '{snapshot_version}-*/image.zip')
692 gs_path = gs_path.format(board=board, snapshot_version=snapshot_version)
693
694 files = gsutil_ls(gs_path, ignore_errors=True)
695 if len(files) == 1:
696 gs_path = files[0]
697 gsutil('cp', gs_path, tmp_dir)
698 image_path = os.path.relpath(
699 os.path.join(tmp_dir, test_image_filename), chromeos_root)
700 util.check_call(
701 'unzip', '-j', 'image.zip', test_image_filename, cwd=tmp_dir)
702 os.remove(os.path.join(tmp_dir, 'image.zip'))
703
704 assert image_path
705 return image_path
706
707
Kuang-che Wu28980b22019-07-31 19:51:45 +0800708def prepare_prebuilt_image(chromeos_root, board, version):
Kuang-che Wubfc4a642018-04-19 11:54:08 +0800709 """Prepare chromeos prebuilt image.
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800710
Kuang-che Wubfc4a642018-04-19 11:54:08 +0800711 It searches for xbuddy image which "cros flash" can use, or fetch image to
712 local disk.
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800713
714 Args:
Kuang-che Wu28980b22019-07-31 19:51:45 +0800715 chromeos_root: chromeos tree root
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800716 board: ChromeOS board name
Kuang-che Wubfc4a642018-04-19 11:54:08 +0800717 version: ChromeOS version number in short or full format
718
719 Returns:
Kuang-che Wu28980b22019-07-31 19:51:45 +0800720 xbuddy path or file path (relative to chromeos_root)
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800721 """
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800722 assert is_cros_version(version)
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800723 full_version = version_to_full(board, version)
724 short_version = version_to_short(full_version)
725
726 image_path = None
727 gs_path = gs_archive_path.format(board=board) + '/' + full_version
728 if gsutil_ls('-d', gs_path, ignore_errors=True):
729 image_path = 'xbuddy://remote/{board}/{full_version}/test'.format(
730 board=board, full_version=full_version)
731 else:
Kuang-che Wu28980b22019-07-31 19:51:45 +0800732 tmp_dir = os.path.join(chromeos_root, 'tmp',
733 'ChromeOS-test-%s-%s' % (full_version, board))
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800734 if not os.path.exists(tmp_dir):
735 os.makedirs(tmp_dir)
736 # gs://chromeos-releases may have more old images than
Kuang-che Wu4fe945b2018-03-31 16:46:38 +0800737 # gs://chromeos-image-archive, but 'cros flash' doesn't support it. We have
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800738 # to fetch the image by ourselves
739 for channel in ['canary', 'dev', 'beta', 'stable']:
740 fn = 'ChromeOS-test-{full_version}-{board}.tar.xz'.format(
741 full_version=full_version, board=board)
742 gs_path = gs_release_path.format(
Kuang-che Wu80bf6a52019-05-31 12:48:06 +0800743 channel=channel,
744 boardpath=gs_release_boardpath(board),
745 short_version=short_version)
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800746 gs_path += '/' + fn
747 if gsutil_ls(gs_path, ignore_errors=True):
748 # TODO(kcwu): delete tmp
749 gsutil('cp', gs_path, tmp_dir)
750 util.check_call('tar', 'Jxvf', fn, cwd=tmp_dir)
Kuang-che Wu28980b22019-07-31 19:51:45 +0800751 image_path = os.path.relpath(
752 os.path.join(tmp_dir, test_image_filename), chromeos_root)
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800753 break
754
755 assert image_path
Kuang-che Wubfc4a642018-04-19 11:54:08 +0800756 return image_path
757
758
759def cros_flash(chromeos_root,
760 host,
761 board,
762 image_path,
763 version=None,
764 clobber_stateful=False,
Kuang-che Wu155fb6e2018-11-29 16:00:41 +0800765 disable_rootfs_verification=True):
Kuang-che Wubfc4a642018-04-19 11:54:08 +0800766 """Flash a DUT with given ChromeOS image.
767
768 This is implemented by 'cros flash' command line.
769
770 Args:
771 chromeos_root: use 'cros flash' of which chromeos tree
772 host: DUT address
773 board: ChromeOS board name
Kuang-che Wu28980b22019-07-31 19:51:45 +0800774 image_path: chromeos image xbuddy path or file path. For relative
775 path, it should be relative to chromeos_root.
Kuang-che Wubfc4a642018-04-19 11:54:08 +0800776 version: ChromeOS version in short or full format
777 clobber_stateful: Clobber stateful partition when performing update
778 disable_rootfs_verification: Disable rootfs verification after update
779 is completed
Kuang-che Wu414d67f2019-05-28 11:28:57 +0800780
781 Raises:
782 errors.ExternalError: cros flash failed
Kuang-che Wubfc4a642018-04-19 11:54:08 +0800783 """
784 logger.info('cros_flash %s %s %s %s', host, board, version, image_path)
785
786 # Reboot is necessary because sometimes previous 'cros flash' failed and
787 # entered a bad state.
788 reboot(host)
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800789
Kuang-che Wu28980b22019-07-31 19:51:45 +0800790 # Handle relative path.
791 if '://' not in image_path and not os.path.isabs(image_path):
792 assert os.path.exists(os.path.join(chromeos_root, image_path))
793 image_path = os.path.join(chromeos_root_inside_chroot, image_path)
794
Kuang-che Wuf3d03ca2019-03-11 17:31:40 +0800795 args = [
796 '--debug', '--no-ping', '--send-payload-in-parallel', host, image_path
797 ]
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800798 if clobber_stateful:
799 args.append('--clobber-stateful')
800 if disable_rootfs_verification:
801 args.append('--disable-rootfs-verification')
802
Kuang-che Wu414d67f2019-05-28 11:28:57 +0800803 try:
804 cros_sdk(chromeos_root, 'cros', 'flash', *args)
805 except subprocess.CalledProcessError:
806 raise errors.ExternalError('cros flash failed')
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800807
Kuang-che Wubfc4a642018-04-19 11:54:08 +0800808 if version:
809 # In the past, cros flash may fail with returncode=0
810 # So let's have an extra check.
Zheng-Jie Chang127c3302019-09-10 17:17:04 +0800811 if is_cros_snapshot_version(version):
812 builder_path = query_dut_lsb_release(host).get(
813 'CHROMEOS_RELEASE_BUILDER_PATH', '')
814 expect_prefix = '%s-postsubmit/%s-' % (board, version)
815 if not builder_path.startswith(expect_prefix):
816 raise errors.ExternalError(
817 'although cros flash succeeded, the OS builder path is '
818 'unexpected: actual=%s expect=%s' % (builder_path, expect_prefix))
819 else:
820 expect_version = version_to_short(version)
821 dut_version = query_dut_short_version(host)
822 if dut_version != expect_version:
823 raise errors.ExternalError(
824 'although cros flash succeeded, the OS version is unexpected: '
825 'actual=%s expect=%s' % (dut_version, expect_version))
Kuang-che Wu414d67f2019-05-28 11:28:57 +0800826
Kuang-che Wu4a81ea72019-10-05 15:35:17 +0800827 # "cros flash" may terminate successfully but the DUT starts self-repairing
Kuang-che Wu414d67f2019-05-28 11:28:57 +0800828 # (b/130786578), so it's necessary to do sanity check.
829 if not is_good_dut(host):
830 raise errors.ExternalError(
831 'although cros flash succeeded, the DUT is in bad state')
832
833
834def cros_flash_with_retry(chromeos_root,
835 host,
836 board,
837 image_path,
838 version=None,
839 clobber_stateful=False,
840 disable_rootfs_verification=True,
841 repair_callback=None):
842 # 'cros flash' is not 100% reliable, retry if necessary.
843 for attempt in range(2):
844 if attempt > 0:
845 logger.info('will retry 60 seconds later')
846 time.sleep(60)
847
848 try:
849 cros_flash(
850 chromeos_root,
851 host,
852 board,
853 image_path,
854 version=version,
855 clobber_stateful=clobber_stateful,
856 disable_rootfs_verification=disable_rootfs_verification)
857 return True
858 except errors.ExternalError:
859 logger.exception('cros flash failed')
860 if repair_callback and not repair_callback(host):
861 logger.warning('not repaired, assume it is harmless')
862 continue
863 return False
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800864
865
866def version_info(board, version):
867 """Query subcomponents version info of given version of ChromeOS
868
869 Args:
870 board: ChromeOS board name
871 version: ChromeOS version number in short or full format
872
873 Returns:
874 dict of component and version info, including (if available):
875 cros_short_version: ChromeOS version
876 cros_full_version: ChromeOS version
877 milestone: milestone of ChromeOS
878 cr_version: Chrome version
Kuang-che Wu708310b2018-03-28 17:24:34 +0800879 android_build_id: Android build id
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800880 android_branch: Android branch, in format like 'git_nyc-mr1-arc'
881 """
Zheng-Jie Chang127c3302019-09-10 17:17:04 +0800882 # TODO(zjchang): add implementation
883 if is_cros_snapshot_version(version):
884 logger.warning(
885 'The version_info function of a snapshot is not implemented ' +
886 'completely. Currently we do not provide enough information ' +
887 'for continuing Android and Chrome bisection, so we will ' +
888 'skip Android and Chrome relatead bisections.')
889 milestone, _, _ = snapshot_version_split(version)
890 return {
891 'milestone': milestone,
892 'cros_full_version': version,
893 'cros_short_version': version_to_short(version)
894 }
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800895 info = {}
896 full_version = version_to_full(board, version)
897
898 # Some boards may have only partial-metadata.json but no metadata.json.
899 # e.g. caroline R60-9462.0.0
900 # Let's try both.
901 metadata = None
902 for metadata_filename in ['metadata.json', 'partial-metadata.json']:
Kuang-che Wu0768b972019-10-05 15:18:59 +0800903 path = gs_archive_path.format(
904 board=board) + '/%s/%s' % (full_version, metadata_filename)
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800905 metadata = gsutil('cat', path, ignore_errors=True)
906 if metadata:
907 o = json.loads(metadata)
908 v = o['version']
909 board_metadata = o['board-metadata'][board]
910 info.update({
911 VERSION_KEY_CROS_SHORT_VERSION: v['platform'],
912 VERSION_KEY_CROS_FULL_VERSION: v['full'],
913 VERSION_KEY_MILESTONE: v['milestone'],
914 VERSION_KEY_CR_VERSION: v['chrome'],
915 })
916
917 if 'android' in v:
Kuang-che Wu708310b2018-03-28 17:24:34 +0800918 info[VERSION_KEY_ANDROID_BUILD_ID] = v['android']
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800919 if 'android-branch' in v: # this appears since R58-9317.0.0
920 info[VERSION_KEY_ANDROID_BRANCH] = v['android-branch']
921 elif 'android-container-branch' in board_metadata:
922 info[VERSION_KEY_ANDROID_BRANCH] = v['android-container-branch']
923 break
924 else:
925 logger.error('Failed to read metadata from gs://chromeos-image-archive')
926 logger.error(
927 'Note, so far no quick way to look up version info for too old builds')
928
929 return info
Kuang-che Wu848b1af2018-02-01 20:59:36 +0800930
931
932def query_chrome_version(board, version):
Kuang-che Wubfc4a642018-04-19 11:54:08 +0800933 """Queries chrome version of chromeos build.
Kuang-che Wu848b1af2018-02-01 20:59:36 +0800934
935 Args:
936 board: ChromeOS board name
937 version: ChromeOS version number in short or full format
938
939 Returns:
940 Chrome version number
941 """
942 info = version_info(board, version)
943 return info['cr_version']
Kuang-che Wu708310b2018-03-28 17:24:34 +0800944
945
946def query_android_build_id(board, rev):
947 info = version_info(board, rev)
948 rev = info['android_build_id']
949 return rev
950
951
952def query_android_branch(board, rev):
953 info = version_info(board, rev)
954 rev = info['android_branch']
955 return rev
Kuang-che Wubfc4a642018-04-19 11:54:08 +0800956
957
Kuang-che Wu3eb6b502018-06-06 16:15:18 +0800958def guess_chrome_version(board, rev):
959 """Guess chrome version number.
960
961 Args:
962 board: chromeos board name
963 rev: chrome or chromeos version
964
965 Returns:
966 chrome version number
967 """
968 if is_cros_version(rev):
969 assert board, 'need to specify BOARD for cros version'
970 rev = query_chrome_version(board, rev)
971 assert cr_util.is_chrome_version(rev)
972
973 return rev
974
975
Kuang-che Wubfc4a642018-04-19 11:54:08 +0800976def is_inside_chroot():
977 """Returns True if we are inside chroot."""
978 return os.path.exists('/etc/cros_chroot_version')
979
980
981def cros_sdk(chromeos_root, *args, **kwargs):
982 """Run commands inside chromeos chroot.
983
984 Args:
985 chromeos_root: chromeos tree root
986 *args: command to run
987 **kwargs:
Kuang-che Wud4603d72018-11-29 17:51:21 +0800988 chrome_root: pass to cros_sdk; mount this path into the SDK chroot
Kuang-che Wubfc4a642018-04-19 11:54:08 +0800989 env: (dict) environment variables for the command
Kuang-che Wubcafc552019-08-15 15:27:02 +0800990 log_stdout: Whether write the stdout output of the child process to log.
Kuang-che Wubfc4a642018-04-19 11:54:08 +0800991 stdin: standard input file handle for the command
Kuang-che Wu9890ce82018-07-07 15:14:10 +0800992 stderr_callback: Callback function for stderr. Called once per line.
Kuang-che Wua9a20bb2019-09-05 22:24:04 +0800993 goma_dir: Goma installed directory to mount into the chroot
Kuang-che Wubfc4a642018-04-19 11:54:08 +0800994 """
995 envs = []
996 for k, v in kwargs.get('env', {}).items():
997 assert re.match(r'^[A-Za-z_][A-Za-z0-9_]*$', k)
998 envs.append('%s=%s' % (k, v))
999
1000 # Use --no-ns-pid to prevent cros_sdk change our pgid, otherwise subsequent
1001 # commands would be considered as background process.
Kuang-che Wu399d4662019-06-06 15:23:37 +08001002 prefix = ['chromite/bin/cros_sdk', '--no-ns-pid']
Kuang-che Wud4603d72018-11-29 17:51:21 +08001003
1004 if kwargs.get('chrome_root'):
Kuang-che Wu399d4662019-06-06 15:23:37 +08001005 prefix += ['--chrome_root', kwargs['chrome_root']]
Kuang-che Wua9a20bb2019-09-05 22:24:04 +08001006 if kwargs.get('goma_dir'):
1007 prefix += ['--goma_dir', kwargs['goma_dir']]
Kuang-che Wud4603d72018-11-29 17:51:21 +08001008
Kuang-che Wu399d4662019-06-06 15:23:37 +08001009 prefix += envs + ['--']
Kuang-che Wud4603d72018-11-29 17:51:21 +08001010
Kuang-che Wu399d4662019-06-06 15:23:37 +08001011 # In addition to the output of command we are interested, cros_sdk may
1012 # generate its own messages. For example, chroot creation messages if we run
1013 # cros_sdk the first time.
1014 # This is the hack to run dummy command once, so we can get clean output for
1015 # the command we are interested.
1016 cmd = prefix + ['true']
1017 util.check_call(*cmd, cwd=chromeos_root)
1018
1019 cmd = prefix + list(args)
Kuang-che Wu9890ce82018-07-07 15:14:10 +08001020 return util.check_output(
1021 *cmd,
1022 cwd=chromeos_root,
Kuang-che Wubcafc552019-08-15 15:27:02 +08001023 log_stdout=kwargs.get('log_stdout', True),
Kuang-che Wu9890ce82018-07-07 15:14:10 +08001024 stdin=kwargs.get('stdin'),
1025 stderr_callback=kwargs.get('stderr_callback'))
Kuang-che Wubfc4a642018-04-19 11:54:08 +08001026
1027
Kuang-che Wua9a20bb2019-09-05 22:24:04 +08001028def create_chroot(chromeos_root):
1029 """Creates ChromeOS chroot.
1030
1031 Args:
1032 chromeos_root: chromeos tree root
1033 """
1034 if os.path.exists(os.path.join(chromeos_root, 'chroot')):
1035 return
1036 if os.path.exists(os.path.join(chromeos_root, 'chroot.img')):
1037 return
1038
1039 util.check_output('chromite/bin/cros_sdk', '--create', cwd=chromeos_root)
1040
1041
Kuang-che Wubfc4a642018-04-19 11:54:08 +08001042def copy_into_chroot(chromeos_root, src, dst):
1043 """Copies file into chromeos chroot.
1044
1045 Args:
1046 chromeos_root: chromeos tree root
1047 src: path outside chroot
1048 dst: path inside chroot
1049 """
1050 # chroot may be an image, so we cannot copy to corresponding path
1051 # directly.
1052 cros_sdk(chromeos_root, 'sh', '-c', 'cat > %s' % dst, stdin=open(src))
1053
1054
1055def exists_in_chroot(chromeos_root, path):
1056 """Determine whether a path exists in the chroot.
1057
1058 Args:
1059 chromeos_root: chromeos tree root
1060 path: path inside chroot, relative to src/scripts
1061
1062 Returns:
1063 True if a path exists
1064 """
1065 try:
Kuang-che Wuacb6efd2018-04-25 18:52:58 +08001066 cros_sdk(chromeos_root, 'test', '-e', path)
Kuang-che Wubfc4a642018-04-19 11:54:08 +08001067 except subprocess.CalledProcessError:
1068 return False
1069 return True
1070
1071
Kuang-che Wu9890ce82018-07-07 15:14:10 +08001072def check_if_need_recreate_chroot(stdout, stderr):
1073 """Analyze build log and determine if chroot should be recreated.
1074
1075 Args:
1076 stdout: stdout output of build
1077 stderr: stderr output of build
1078
1079 Returns:
1080 the reason if chroot needs recreated; None otherwise
1081 """
Kuang-che Wu74768d32018-09-07 12:03:24 +08001082 if re.search(
1083 r"The current version of portage supports EAPI '\d+'. "
Kuang-che Wuae6824b2019-08-27 22:20:01 +08001084 'You must upgrade', stderr):
Kuang-che Wu9890ce82018-07-07 15:14:10 +08001085 return 'EAPI version mismatch'
1086
Kuang-che Wu5ac81322018-11-26 14:04:06 +08001087 if 'Chroot is too new. Consider running:' in stderr:
1088 return 'chroot version is too new'
1089
1090 # old message before Oct 2018
Kuang-che Wu9890ce82018-07-07 15:14:10 +08001091 if 'Chroot version is too new. Consider running cros_sdk --replace' in stderr:
1092 return 'chroot version is too new'
1093
Kuang-che Wu6fe987f2018-08-28 15:24:20 +08001094 # https://groups.google.com/a/chromium.org/forum/#!msg/chromium-os-dev/uzwT5APspB4/NFakFyCIDwAJ
1095 if "undefined reference to 'std::__1::basic_string" in stdout:
1096 return 'might be due to compiler change'
1097
Kuang-che Wu9890ce82018-07-07 15:14:10 +08001098 return None
1099
1100
Kuang-che Wua9a20bb2019-09-05 22:24:04 +08001101def build_packages(chromeos_root,
1102 board,
1103 chrome_root=None,
1104 goma_dir=None,
1105 afdo_use=False):
Kuang-che Wu28980b22019-07-31 19:51:45 +08001106 """Build ChromeOS packages.
Kuang-che Wubfc4a642018-04-19 11:54:08 +08001107
1108 Args:
1109 chromeos_root: chromeos tree root
1110 board: ChromeOS board name
Kuang-che Wua9a20bb2019-09-05 22:24:04 +08001111 chrome_root: Chrome tree root. If specified, build chrome using the
1112 provided tree
1113 goma_dir: Goma installed directory to mount into the chroot. If specified,
1114 build chrome with goma.
1115 afdo_use: build chrome with AFDO optimization
Kuang-che Wubfc4a642018-04-19 11:54:08 +08001116 """
Kuang-che Wua9a20bb2019-09-05 22:24:04 +08001117 common_env = {
1118 'USE': '-cros-debug chrome_internal',
1119 'FEATURES': 'separatedebug',
1120 }
Kuang-che Wu9890ce82018-07-07 15:14:10 +08001121 stderr_lines = []
1122 try:
Kuang-che Wufb553102018-10-02 18:14:29 +08001123 with locking.lock_file(locking.LOCK_FILE_FOR_BUILD):
Kuang-che Wua9a20bb2019-09-05 22:24:04 +08001124 env = common_env.copy()
1125 env['FEATURES'] += ' -separatedebug splitdebug'
Kuang-che Wufb553102018-10-02 18:14:29 +08001126 cros_sdk(
1127 chromeos_root,
Kuang-che Wu28980b22019-07-31 19:51:45 +08001128 './update_chroot',
1129 '--toolchain_boards',
Kuang-che Wufb553102018-10-02 18:14:29 +08001130 board,
Kuang-che Wua9a20bb2019-09-05 22:24:04 +08001131 env=env,
Kuang-che Wu28980b22019-07-31 19:51:45 +08001132 stderr_callback=stderr_lines.append)
Kuang-che Wua9a20bb2019-09-05 22:24:04 +08001133
1134 env = common_env.copy()
1135 cmd = [
Kuang-che Wu28980b22019-07-31 19:51:45 +08001136 './build_packages',
1137 '--board',
1138 board,
1139 '--withdev',
1140 '--noworkon',
1141 '--skip_chroot_upgrade',
1142 '--accept_licenses=@CHROMEOS',
Kuang-che Wua9a20bb2019-09-05 22:24:04 +08001143 ]
1144 if goma_dir:
1145 # Tell build_packages to start and stop goma
1146 cmd.append('--run_goma')
1147 env['USE_GOMA'] = 'true'
1148 if afdo_use:
1149 env['USE'] += ' afdo_use'
1150 cros_sdk(
1151 chromeos_root,
1152 *cmd,
1153 env=env,
1154 chrome_root=chrome_root,
1155 stderr_callback=stderr_lines.append,
1156 goma_dir=goma_dir)
Kuang-che Wu9890ce82018-07-07 15:14:10 +08001157 except subprocess.CalledProcessError as e:
1158 # Detect failures due to incompatibility between chroot and source tree. If
1159 # so, notify the caller to recreate chroot and retry.
1160 reason = check_if_need_recreate_chroot(e.output, ''.join(stderr_lines))
1161 if reason:
1162 raise NeedRecreateChrootException(reason)
1163
1164 # For other failures, don't know how to handle. Just bail out.
1165 raise
1166
Kuang-che Wu28980b22019-07-31 19:51:45 +08001167
1168def build_image(chromeos_root, board):
1169 """Build ChromeOS image.
1170
1171 Args:
1172 chromeos_root: chromeos tree root
1173 board: ChromeOS board name
1174
1175 Returns:
1176 image folder; relative to chromeos_root
1177 """
1178 stderr_lines = []
1179 try:
1180 with locking.lock_file(locking.LOCK_FILE_FOR_BUILD):
1181 cros_sdk(
1182 chromeos_root,
1183 './build_image',
1184 '--board',
1185 board,
1186 '--noenable_rootfs_verification',
1187 'test',
1188 env={
1189 'USE': '-cros-debug chrome_internal',
1190 'FEATURES': 'separatedebug',
1191 },
1192 stderr_callback=stderr_lines.append)
1193 except subprocess.CalledProcessError as e:
1194 # Detect failures due to incompatibility between chroot and source tree. If
1195 # so, notify the caller to recreate chroot and retry.
1196 reason = check_if_need_recreate_chroot(e.output, ''.join(stderr_lines))
1197 if reason:
1198 raise NeedRecreateChrootException(reason)
1199
1200 # For other failures, don't know how to handle. Just bail out.
1201 raise
1202
1203 image_symlink = os.path.join(chromeos_root, cached_images_dir, board,
1204 'latest')
1205 assert os.path.exists(image_symlink)
1206 image_name = os.readlink(image_symlink)
1207 image_folder = os.path.join(cached_images_dir, board, image_name)
1208 assert os.path.exists(
1209 os.path.join(chromeos_root, image_folder, test_image_filename))
1210 return image_folder
Kuang-che Wubfc4a642018-04-19 11:54:08 +08001211
1212
Kuang-che Wub9705bd2018-06-28 17:59:18 +08001213class AutotestControlInfo(object):
1214 """Parsed content of autotest control file.
1215
1216 Attributes:
1217 name: test name
1218 path: control file path
1219 variables: dict of top-level control variables. Sample keys: NAME, AUTHOR,
1220 DOC, ATTRIBUTES, DEPENDENCIES, etc.
1221 """
1222
1223 def __init__(self, path, variables):
1224 self.name = variables['NAME']
1225 self.path = path
1226 self.variables = variables
1227
1228
1229def parse_autotest_control_file(path):
1230 """Parses autotest control file.
1231
1232 This only parses simple top-level string assignments.
1233
1234 Returns:
1235 AutotestControlInfo object
1236 """
1237 variables = {}
1238 code = ast.parse(open(path).read())
1239 for stmt in code.body:
1240 # Skip if not simple "NAME = *" assignment.
1241 if not (isinstance(stmt, ast.Assign) and len(stmt.targets) == 1 and
1242 isinstance(stmt.targets[0], ast.Name)):
1243 continue
1244
1245 # Only support string value.
1246 if isinstance(stmt.value, ast.Str):
1247 variables[stmt.targets[0].id] = stmt.value.s
1248
1249 return AutotestControlInfo(path, variables)
1250
1251
1252def enumerate_autotest_control_files(autotest_dir):
1253 """Enumerate autotest control files.
1254
1255 Args:
1256 autotest_dir: autotest folder
1257
1258 Returns:
1259 list of paths to control files
1260 """
1261 # Where to find control files. Relative to autotest_dir.
1262 subpaths = [
1263 'server/site_tests',
1264 'client/site_tests',
1265 'server/tests',
1266 'client/tests',
1267 ]
1268
1269 blacklist = ['site-packages', 'venv', 'results', 'logs', 'containers']
1270 result = []
1271 for subpath in subpaths:
1272 path = os.path.join(autotest_dir, subpath)
1273 for root, dirs, files in os.walk(path):
1274
1275 for black in blacklist:
1276 if black in dirs:
1277 dirs.remove(black)
1278
1279 for filename in files:
1280 if filename == 'control' or filename.startswith('control.'):
1281 result.append(os.path.join(root, filename))
1282
1283 return result
1284
1285
1286def get_autotest_test_info(autotest_dir, test_name):
1287 """Get metadata of given test.
1288
1289 Args:
1290 autotest_dir: autotest folder
1291 test_name: test name
1292
1293 Returns:
1294 AutotestControlInfo object. None if test not found.
1295 """
1296 for control_file in enumerate_autotest_control_files(autotest_dir):
1297 info = parse_autotest_control_file(control_file)
1298 if info.name == test_name:
1299 return info
1300 return None
1301
1302
Kuang-che Wue4bae0b2018-07-19 12:10:14 +08001303class ChromeOSSpecManager(codechange.SpecManager):
1304 """Repo manifest related operations.
1305
1306 This class enumerates chromeos manifest files, parses them,
1307 and sync to disk state according to them.
1308 """
Kuang-che Wubfc4a642018-04-19 11:54:08 +08001309
1310 def __init__(self, config):
1311 self.config = config
Kuang-che Wue4bae0b2018-07-19 12:10:14 +08001312 self.manifest_dir = os.path.join(self.config['chromeos_root'], '.repo',
1313 'manifests')
Zheng-Jie Chang127c3302019-09-10 17:17:04 +08001314 self.manifest_internal_dir = os.path.join(self.config['chromeos_mirror'],
1315 'manifest-internal.git')
Kuang-che Wue4bae0b2018-07-19 12:10:14 +08001316 self.historical_manifest_git_dir = os.path.join(
Kuang-che Wud8fc9572018-10-03 21:00:41 +08001317 self.config['chromeos_mirror'], 'chromeos/manifest-versions.git')
Kuang-che Wue4bae0b2018-07-19 12:10:14 +08001318 if not os.path.exists(self.historical_manifest_git_dir):
Kuang-che Wue121fae2018-11-09 16:18:39 +08001319 raise errors.InternalError('Manifest snapshots should be cloned into %s' %
1320 self.historical_manifest_git_dir)
Kuang-che Wubfc4a642018-04-19 11:54:08 +08001321
Zheng-Jie Chang127c3302019-09-10 17:17:04 +08001322 def lookup_snapshot_manifest_revisions(self, old, new):
1323 """Get manifest commits between snapshot versions.
Kuang-che Wue4bae0b2018-07-19 12:10:14 +08001324
Zheng-Jie Chang127c3302019-09-10 17:17:04 +08001325 Returns:
1326 list of (timestamp, commit_id, snapshot_id):
1327 timestamp: integer unix timestamp
1328 commit_id: a string indicates commit hash
1329 snapshot_id: a string indicates snapshot id
1330 """
1331 assert is_cros_snapshot_version(old)
1332 assert is_cros_snapshot_version(new)
1333
1334 gs_path = (
1335 'gs://chromeos-image-archive/{board}-postsubmit/{version}-*/image.zip')
1336 # Try to guess the commit time of a snapshot manifest, it is usually a few
1337 # minutes different between snapshot manifest commit and image.zip
1338 # generate.
1339 try:
1340 old_timestamp = gsutil_stat_update_time(
1341 gs_path.format(board=self.config['board'], version=old)) - 86400
1342 except subprocess.CalledProcessError:
1343 old_timestamp = None
1344 try:
1345 new_timestamp = gsutil_stat_update_time(
1346 gs_path.format(board=self.config['board'], version=new)) + 86400
1347 # 1558657989 is snapshot_id 5982's commit time, this ensures every time
1348 # we can find snapshot 5982
1349 # snapshot_id <= 5982 has different commit message format, so we need
1350 # to identify its id in different ways, see below comment for more info.
1351 new_timestamp = max(new_timestamp, 1558657989 + 1)
1352 except subprocess.CalledProcessError:
1353 new_timestamp = None
1354 result = []
1355 _, _, old_snapshot_id = snapshot_version_split(old)
1356 _, _, new_snapshot_id = snapshot_version_split(new)
1357 repo = self.manifest_internal_dir
1358 path = 'snapshot.xml'
1359 branch = 'snapshot'
1360 commits = git_util.get_history(
1361 repo,
1362 path,
1363 branch,
1364 after=old_timestamp,
1365 before=new_timestamp,
1366 with_subject=True)
1367
1368 # Unfortunately, we can not identify snapshot_id <= 5982 from its commit
1369 # subject, as their subjects are all `Annealing manifest snapshot.`.
1370 # So instead we count the snapshot_id manually.
1371 count = 5982
1372 # There are two snapshot_id = 2633 in commit history, ignore the former
1373 # one.
1374 ignore_list = ['95c8526a7f0798d02f692010669dcbd5a152439a']
1375 # We examine the commits in reverse order as there are some testing
1376 # commits before snapshot_id=2, this method works fine after
1377 # snapshot 2, except snapshot 2633
1378 for commit in reversed(commits):
1379 msg = commit[2]
1380 if commit[1] in ignore_list:
1381 continue
1382
1383 match = re.match(r'^annealing manifest snapshot (\d+)', msg)
1384 if match:
1385 snapshot_id = match.group(1)
1386 elif 'Annealing manifest snapshot' in msg:
1387 snapshot_id = str(count)
1388 count -= 1
1389 else:
1390 continue
1391 if int(old_snapshot_id) <= int(snapshot_id) <= int(new_snapshot_id):
1392 result.append((commit[0], commit[1], snapshot_id))
1393 # We find commits in reversed order, now reverse it again to chronological
1394 # order.
1395 return list(reversed(result))
1396
1397 def lookup_build_timestamp(self, rev):
1398 assert is_cros_full_version(rev) or is_cros_snapshot_version(rev)
1399 if is_cros_full_version(rev):
1400 return self.lookup_release_build_timestamp(rev)
1401 else:
1402 return self.lookup_snapshot_build_timestamp(rev)
1403
1404 def lookup_snapshot_build_timestamp(self, rev):
1405 assert is_cros_snapshot_version(rev)
1406 return int(self.lookup_snapshot_manifest_revisions(rev, rev)[0][0])
1407
1408 def lookup_release_build_timestamp(self, rev):
1409 assert is_cros_full_version(rev)
Kuang-che Wubfc4a642018-04-19 11:54:08 +08001410 milestone, short_version = version_split(rev)
Kuang-che Wue4bae0b2018-07-19 12:10:14 +08001411 path = os.path.join('buildspecs', milestone, short_version + '.xml')
1412 try:
1413 timestamp = git_util.get_commit_time(self.historical_manifest_git_dir,
1414 'refs/heads/master', path)
1415 except ValueError:
Kuang-che Wuce2f3be2019-10-28 19:44:54 +08001416 raise errors.InternalError(
1417 '%s does not have %s' % (self.historical_manifest_git_dir, path))
Kuang-che Wue4bae0b2018-07-19 12:10:14 +08001418 return timestamp
Kuang-che Wubfc4a642018-04-19 11:54:08 +08001419
Kuang-che Wue4bae0b2018-07-19 12:10:14 +08001420 def collect_float_spec(self, old, new):
1421 old_timestamp = self.lookup_build_timestamp(old)
1422 new_timestamp = self.lookup_build_timestamp(new)
Zheng-Jie Chang127c3302019-09-10 17:17:04 +08001423 # snapshot time is different from commit time
1424 # usually it's a few minutes different
1425 # 30 minutes should be safe in most cases
1426 if is_cros_snapshot_version(old):
1427 old_timestamp = old_timestamp - 1800
1428 if is_cros_snapshot_version(new):
1429 new_timestamp = new_timestamp + 1800
Kuang-che Wubfc4a642018-04-19 11:54:08 +08001430
Kuang-che Wue4bae0b2018-07-19 12:10:14 +08001431 path = os.path.join(self.manifest_dir, 'default.xml')
1432 if not os.path.islink(path) or os.readlink(path) != 'full.xml':
Kuang-che Wue121fae2018-11-09 16:18:39 +08001433 raise errors.InternalError(
1434 'default.xml not symlink to full.xml is not supported')
Kuang-che Wubfc4a642018-04-19 11:54:08 +08001435
Kuang-che Wue4bae0b2018-07-19 12:10:14 +08001436 result = []
1437 path = 'full.xml'
1438 parser = repo_util.ManifestParser(self.manifest_dir)
1439 for timestamp, git_rev in parser.enumerate_manifest_commits(
1440 old_timestamp, new_timestamp, path):
1441 result.append(
1442 codechange.Spec(codechange.SPEC_FLOAT, git_rev, timestamp, path))
1443 return result
Kuang-che Wubfc4a642018-04-19 11:54:08 +08001444
Kuang-che Wue4bae0b2018-07-19 12:10:14 +08001445 def collect_fixed_spec(self, old, new):
Zheng-Jie Chang127c3302019-09-10 17:17:04 +08001446 assert is_cros_full_version(old) or is_cros_snapshot_version(old)
1447 assert is_cros_full_version(new) or is_cros_snapshot_version(new)
1448
1449 # case 1: if both are snapshot, return a list of snapshot
1450 if is_cros_snapshot_version(old) and is_cros_snapshot_version(new):
1451 return self.collect_snapshot_specs(old, new)
1452
1453 # case 2: if both are release version
1454 # return a list of release version
1455 if is_cros_full_version(old) and is_cros_full_version(new):
1456 return self.collect_release_specs(old, new)
1457
1458 # case 3: return a list of release version and append a snapshot
1459 # before or at the end
1460 result = self.collect_release_specs(old, new)
1461 if is_cros_snapshot_version(old):
1462 result = self.collect_release_specs(old, old) + result[1:]
1463 elif is_cros_snapshot_version(new):
1464 result = result[:-1] + self.collect_release_specs(new, new)
1465 return result
1466
1467 def collect_snapshot_specs(self, old, new):
1468 assert is_cros_snapshot_version(old)
1469 assert is_cros_snapshot_version(new)
1470
1471 def guess_snapshot_version(board, snapshot_id, old, new):
1472 if old.endswith('-' + snapshot_id):
1473 return old
1474 if new.endswith('-' + snapshot_id):
1475 return new
Kuang-che Wuf791afa2019-10-28 19:53:26 +08001476 gs_path = ('gs://chromeos-image-archive/{board}-postsubmit/'
1477 'R*-{snapshot_id}-*'.format(
1478 board=board, snapshot_id=snapshot_id))
Zheng-Jie Chang127c3302019-09-10 17:17:04 +08001479 for line in gsutil_ls(gs_path, ignore_errors=True):
1480 m = re.match(r'^gs:\S+(R\d+-\d+\.\d+\.\d+-\d+)\S+', line)
1481 if m:
1482 return m.group(1)
1483 raise errors.ExternalError(
Kuang-che Wuf791afa2019-10-28 19:53:26 +08001484 'guess_snapshot_version failed, board=%s snapshot_id=%s '
1485 'old=%s new=%s' % (board, snapshot_id, old, new))
Zheng-Jie Chang127c3302019-09-10 17:17:04 +08001486
1487 result = []
1488 path = 'snapshot.xml'
1489 revisions = self.lookup_snapshot_manifest_revisions(old, new)
Kuang-che Wuf791afa2019-10-28 19:53:26 +08001490 for timestamp, _git_rev, snapshot_id in revisions:
Zheng-Jie Chang127c3302019-09-10 17:17:04 +08001491 snapshot_version = guess_snapshot_version(self.config['board'],
1492 snapshot_id, old, new)
1493 result.append(
1494 codechange.Spec(codechange.SPEC_FIXED, snapshot_version, timestamp,
1495 path))
1496 return result
1497
1498 def collect_release_specs(self, old, new):
Kuang-che Wubfc4a642018-04-19 11:54:08 +08001499 assert is_cros_full_version(old)
1500 assert is_cros_full_version(new)
1501 old_milestone, old_short_version = version_split(old)
1502 new_milestone, new_short_version = version_split(new)
1503
Kuang-che Wubfc4a642018-04-19 11:54:08 +08001504 result = []
Kuang-che Wue4bae0b2018-07-19 12:10:14 +08001505 for milestone in git_util.list_dir_from_revision(
1506 self.historical_manifest_git_dir, 'refs/heads/master', 'buildspecs'):
1507 if not milestone.isdigit():
1508 continue
1509 if not int(old_milestone) <= int(milestone) <= int(new_milestone):
1510 continue
1511
Kuang-che Wu74768d32018-09-07 12:03:24 +08001512 files = git_util.list_dir_from_revision(
1513 self.historical_manifest_git_dir, 'refs/heads/master',
1514 os.path.join('buildspecs', milestone))
Kuang-che Wubfc4a642018-04-19 11:54:08 +08001515
1516 for fn in files:
Kuang-che Wue4bae0b2018-07-19 12:10:14 +08001517 path = os.path.join('buildspecs', milestone, fn)
Kuang-che Wubfc4a642018-04-19 11:54:08 +08001518 short_version, ext = os.path.splitext(fn)
1519 if ext != '.xml':
1520 continue
Kuang-che Wubfc4a642018-04-19 11:54:08 +08001521 if (util.is_version_lesseq(old_short_version, short_version) and
1522 util.is_version_lesseq(short_version, new_short_version) and
1523 util.is_direct_relative_version(short_version, new_short_version)):
Kuang-che Wue4bae0b2018-07-19 12:10:14 +08001524 rev = make_cros_full_version(milestone, short_version)
1525 timestamp = git_util.get_commit_time(self.historical_manifest_git_dir,
1526 'refs/heads/master', path)
1527 result.append(
1528 codechange.Spec(codechange.SPEC_FIXED, rev, timestamp, path))
Kuang-che Wubfc4a642018-04-19 11:54:08 +08001529
Kuang-che Wue4bae0b2018-07-19 12:10:14 +08001530 def version_key_func(spec):
1531 _milestone, short_version = version_split(spec.name)
Kuang-che Wubfc4a642018-04-19 11:54:08 +08001532 return util.version_key_func(short_version)
1533
1534 result.sort(key=version_key_func)
Kuang-che Wue4bae0b2018-07-19 12:10:14 +08001535 assert result[0].name == old
1536 assert result[-1].name == new
Kuang-che Wubfc4a642018-04-19 11:54:08 +08001537 return result
1538
Kuang-che Wue4bae0b2018-07-19 12:10:14 +08001539 def get_manifest(self, rev):
Zheng-Jie Chang127c3302019-09-10 17:17:04 +08001540 assert is_cros_full_version(rev) or is_cros_snapshot_version(rev)
1541 if is_cros_full_version(rev):
1542 milestone, short_version = version_split(rev)
1543 path = os.path.join('buildspecs', milestone, '%s.xml' % short_version)
1544 manifest = git_util.get_file_from_revision(
1545 self.historical_manifest_git_dir, 'refs/heads/master', path)
1546 else:
1547 revisions = self.lookup_snapshot_manifest_revisions(rev, rev)
1548 commit_id = revisions[0][1]
1549 manifest = git_util.get_file_from_revision(self.manifest_internal_dir,
1550 commit_id, 'snapshot.xml')
Kuang-che Wue4bae0b2018-07-19 12:10:14 +08001551 manifest_name = 'manifest_%s.xml' % rev
1552 manifest_path = os.path.join(self.manifest_dir, manifest_name)
1553 with open(manifest_path, 'w') as f:
1554 f.write(manifest)
1555
1556 return manifest_name
1557
1558 def parse_spec(self, spec):
1559 parser = repo_util.ManifestParser(self.manifest_dir)
1560 if spec.spec_type == codechange.SPEC_FIXED:
1561 manifest_name = self.get_manifest(spec.name)
1562 manifest_path = os.path.join(self.manifest_dir, manifest_name)
1563 content = open(manifest_path).read()
1564 root = parser.parse_single_xml(content, allow_include=False)
1565 else:
1566 root = parser.parse_xml_recursive(spec.name, spec.path)
1567
1568 spec.entries = parser.process_parsed_result(root)
1569 if spec.spec_type == codechange.SPEC_FIXED:
Kuang-che Wufe1e88a2019-09-10 21:52:25 +08001570 if not spec.is_static():
1571 raise ValueError(
1572 'fixed spec %r has unexpected floating entries' % spec.name)
Kuang-che Wue4bae0b2018-07-19 12:10:14 +08001573
1574 def sync_disk_state(self, rev):
1575 manifest_name = self.get_manifest(rev)
1576
1577 # For ChromeOS, mark_as_stable step requires 'repo init -m', which sticks
1578 # manifest. 'repo sync -m' is not enough
1579 repo_util.init(
1580 self.config['chromeos_root'],
1581 'https://chrome-internal.googlesource.com/chromeos/manifest-internal',
1582 manifest_name=manifest_name,
1583 repo_url='https://chromium.googlesource.com/external/repo.git',
Kuang-che Wud8fc9572018-10-03 21:00:41 +08001584 reference=self.config['chromeos_mirror'],
Kuang-che Wue4bae0b2018-07-19 12:10:14 +08001585 )
1586
1587 # Note, don't sync with current_branch=True for chromeos. One of its
1588 # build steps (inside mark_as_stable) executes "git describe" which
1589 # needs git tag information.
1590 repo_util.sync(self.config['chromeos_root'])