blob: 8d1e847f56dcf9f94cf5239349960c46a38bbbd1 [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'
Zheng-Jie Changb8697042019-10-29 16:03:26 +080048bb_bin = 'bb'
49
50# Since snapshots with version >= 12618.0.0 have android and chrome version
51# info.
52snapshot_cutover_version = '12618.0.0'
Kuang-che Wu2ea804f2017-11-28 17:11:41 +080053
Kuang-che Wub9705bd2018-06-28 17:59:18 +080054chromeos_root_inside_chroot = '/mnt/host/source'
55# relative to chromeos_root
Kuang-che Wu7f82c6f2019-08-12 14:29:28 +080056prebuilt_autotest_dir = 'tmp/autotest-prebuilt'
Kuang-che Wu28980b22019-07-31 19:51:45 +080057# Relative to chromeos root. Images are cached_images_dir/$board/$image_name.
58cached_images_dir = 'src/build/images'
59test_image_filename = 'chromiumos_test_image.bin'
Kuang-che Wub9705bd2018-06-28 17:59:18 +080060
Kuang-che Wu2ea804f2017-11-28 17:11:41 +080061VERSION_KEY_CROS_SHORT_VERSION = 'cros_short_version'
62VERSION_KEY_CROS_FULL_VERSION = 'cros_full_version'
63VERSION_KEY_MILESTONE = 'milestone'
64VERSION_KEY_CR_VERSION = 'cr_version'
Kuang-che Wu708310b2018-03-28 17:24:34 +080065VERSION_KEY_ANDROID_BUILD_ID = 'android_build_id'
Kuang-che Wu2ea804f2017-11-28 17:11:41 +080066VERSION_KEY_ANDROID_BRANCH = 'android_branch'
67
68
Kuang-che Wu9890ce82018-07-07 15:14:10 +080069class NeedRecreateChrootException(Exception):
70 """Failed to build ChromeOS because of chroot mismatch or corruption"""
71
72
Kuang-che Wu2ea804f2017-11-28 17:11:41 +080073def is_cros_short_version(s):
Kuang-che Wuacb6efd2018-04-25 18:52:58 +080074 """Determines if `s` is chromeos short version.
75
76 This function doesn't accept version number of local build.
77 """
Kuang-che Wu2ea804f2017-11-28 17:11:41 +080078 return bool(re.match(re_chromeos_short_version, s))
79
80
Kuang-che Wuacb6efd2018-04-25 18:52:58 +080081def is_cros_localbuild_version(s):
82 """Determines if `s` is chromeos local build version."""
83 return bool(re.match(re_chromeos_localbuild_version, s))
84
85
Kuang-che Wu2ea804f2017-11-28 17:11:41 +080086def is_cros_full_version(s):
Kuang-che Wuacb6efd2018-04-25 18:52:58 +080087 """Determines if `s` is chromeos full version.
88
89 This function doesn't accept version number of local build.
90 """
Kuang-che Wu2ea804f2017-11-28 17:11:41 +080091 return bool(re.match(re_chromeos_full_version, s))
92
93
94def is_cros_version(s):
95 """Determines if `s` is chromeos version (either short or full)"""
96 return is_cros_short_version(s) or is_cros_full_version(s)
97
98
Zheng-Jie Chang127c3302019-09-10 17:17:04 +080099def is_cros_snapshot_version(s):
100 """Determines if `s` is chromeos snapshot version"""
101 return bool(re.match(re_chromeos_snapshot_version, s))
102
103
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800104def make_cros_full_version(milestone, short_version):
105 """Makes full_version from milestone and short_version"""
Kuang-che Wuacb6efd2018-04-25 18:52:58 +0800106 assert milestone
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800107 return 'R%s-%s' % (milestone, short_version)
108
109
Zheng-Jie Chang127c3302019-09-10 17:17:04 +0800110def make_cros_snapshot_version(milestone, short_version, snapshot_id):
111 """Makes snapshot version from milestone, short_version and snapshot id"""
112 return 'R%s-%s-%s' % (milestone, short_version, snapshot_id)
113
114
115def version_split(version):
116 """Splits full_version or snapshot_version into milestone and short_version"""
117 assert is_cros_full_version(version) or is_cros_snapshot_version(version)
118 if is_cros_snapshot_version(version):
119 return snapshot_version_split(version)[0:2]
120 milestone, short_version = version.split('-')
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800121 return milestone[1:], short_version
122
123
Zheng-Jie Chang127c3302019-09-10 17:17:04 +0800124def snapshot_version_split(snapshot_version):
125 """Splits snapshot_version into milestone, short_version and snapshot_id"""
126 assert is_cros_snapshot_version(snapshot_version)
127 milestone, shot_version, snapshot_id = snapshot_version.split('-')
128 return milestone[1:], shot_version, snapshot_id
129
130
Zheng-Jie Changb8697042019-10-29 16:03:26 +0800131def query_snapshot_buildbucket_id(board, snapshot_version):
132 """Query buildbucket id of a snapshot"""
133 assert is_cros_snapshot_version(snapshot_version)
134 path = ('gs://chromeos-image-archive/{board}-postsubmit'
135 '/{snapshot_version}-*/image.zip')
136 output = gsutil_ls(
137 '-d',
138 path.format(board=board, snapshot_version=snapshot_version),
139 ignore_errors=True)
140 for line in output:
141 m = re.match(r'.*-postsubmit/R\d+-\d+\.\d+\.\d+-\d+-(.+)/image\.zip', line)
142 if m:
143 return m.group(1)
144 return None
145
146
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800147def argtype_cros_version(s):
Zheng-Jie Chang127c3302019-09-10 17:17:04 +0800148 if (not is_cros_version(s)) and (not is_cros_snapshot_version(s)):
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800149 msg = 'invalid cros version'
Kuang-che Wuce2f3be2019-10-28 19:44:54 +0800150 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 +0800151 return s
152
153
154def query_dut_lsb_release(host):
155 """Query /etc/lsb-release of given DUT
156
157 Args:
158 host: the DUT address
159
160 Returns:
Kuang-che Wu3eb6b502018-06-06 16:15:18 +0800161 dict for keys and values of /etc/lsb-release.
162
163 Raises:
Kuang-che Wu44278142019-03-04 11:33:57 +0800164 errors.SshConnectionError: cannot connect to host
165 errors.ExternalError: lsb-release file doesn't exist
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800166 """
167 try:
Kuang-che Wu44278142019-03-04 11:33:57 +0800168 output = util.ssh_cmd(host, 'cat', '/etc/lsb-release')
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800169 except subprocess.CalledProcessError:
Kuang-che Wu44278142019-03-04 11:33:57 +0800170 raise errors.ExternalError('unable to read /etc/lsb-release; not a DUT')
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800171 return dict(re.findall(r'^(\w+)=(.*)$', output, re.M))
172
173
174def is_dut(host):
Kuang-che Wubfc4a642018-04-19 11:54:08 +0800175 """Determines whether a host is a chromeos device.
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800176
177 Args:
178 host: the DUT address
179
180 Returns:
181 True if the host is a chromeos device.
182 """
Kuang-che Wu44278142019-03-04 11:33:57 +0800183 try:
184 return query_dut_lsb_release(host).get('DEVICETYPE') in [
185 'CHROMEBASE',
186 'CHROMEBIT',
187 'CHROMEBOOK',
188 'CHROMEBOX',
189 'REFERENCE',
190 ]
191 except (errors.ExternalError, errors.SshConnectionError):
192 return False
193
194
195def is_good_dut(host):
196 if not is_dut(host):
197 return False
198
199 # Sometimes python is broken after 'cros flash'.
200 try:
201 util.ssh_cmd(host, 'python', '-c', '1')
202 return True
203 except (subprocess.CalledProcessError, errors.SshConnectionError):
204 return False
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800205
206
207def query_dut_board(host):
208 """Query board name of a given DUT"""
209 return query_dut_lsb_release(host).get('CHROMEOS_RELEASE_BOARD')
210
211
212def query_dut_short_version(host):
Kuang-che Wuacb6efd2018-04-25 18:52:58 +0800213 """Query short version of a given DUT.
214
215 This function may return version of local build, which
216 is_cros_short_version() is false.
217 """
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800218 return query_dut_lsb_release(host).get('CHROMEOS_RELEASE_VERSION')
219
220
Zheng-Jie Chang127c3302019-09-10 17:17:04 +0800221def query_dut_is_snapshot(host):
222 """Query if given DUT is a snapshot version."""
223 path = query_dut_lsb_release(host).get('CHROMEOS_RELEASE_BUILDER_PATH', '')
224 return '-postsubmit' in path
225
226
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800227def query_dut_boot_id(host, connect_timeout=None):
Kuang-che Wubfc4a642018-04-19 11:54:08 +0800228 """Query boot id.
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800229
230 Args:
231 host: DUT address
232 connect_timeout: connection timeout
233
234 Returns:
235 boot uuid
236 """
Kuang-che Wu44278142019-03-04 11:33:57 +0800237 return util.ssh_cmd(
238 host,
239 'cat',
240 '/proc/sys/kernel/random/boot_id',
241 connect_timeout=connect_timeout).strip()
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800242
243
244def reboot(host):
245 """Reboot a DUT and verify"""
246 logger.debug('reboot %s', host)
247 boot_id = query_dut_boot_id(host)
248
Kuang-che Wu44278142019-03-04 11:33:57 +0800249 try:
250 util.ssh_cmd(host, 'reboot')
Kuang-che Wu5f662e82019-03-05 11:49:56 +0800251 except errors.SshConnectionError:
252 # Depends on timing, ssh may return failure due to broken pipe, which is
253 # working as intended. Ignore such kind of errors.
Kuang-che Wu44278142019-03-04 11:33:57 +0800254 pass
Kuang-che Wu708310b2018-03-28 17:24:34 +0800255 wait_reboot_done(host, boot_id)
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800256
Kuang-che Wu708310b2018-03-28 17:24:34 +0800257
258def wait_reboot_done(host, boot_id):
Kuang-che Wu4fe945b2018-03-31 16:46:38 +0800259 # For dev-mode test image, the reboot time is roughly at least 16 seconds
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800260 # (dev screen short delay) or more (long delay).
261 time.sleep(15)
262 for _ in range(100):
263 try:
264 # During boot, DUT does not response and thus ssh may hang a while. So
265 # set a connect timeout. 3 seconds are enough and 2 are not. It's okay to
266 # set tight limit because it's inside retry loop.
267 assert boot_id != query_dut_boot_id(host, connect_timeout=3)
268 return
Kuang-che Wu5f662e82019-03-05 11:49:56 +0800269 except errors.SshConnectionError:
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800270 logger.debug('reboot not ready? sleep wait 1 sec')
271 time.sleep(1)
272
Kuang-che Wue121fae2018-11-09 16:18:39 +0800273 raise errors.ExternalError('reboot failed?')
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800274
275
Kuang-che Wu80bf6a52019-05-31 12:48:06 +0800276def gs_release_boardpath(board):
277 """Normalizes board name for gs://chromeos-releases/
278
279 This follows behavior of PushImage() in chromite/scripts/pushimage.py
280 Note, only gs://chromeos-releases/ needs normalization,
281 gs://chromeos-image-archive does not.
282
283 Args:
284 board: ChromeOS board name
285
286 Returns:
287 normalized board name
288 """
289 return board.replace('_', '-')
290
291
Zheng-Jie Changb8697042019-10-29 16:03:26 +0800292def bb(*args, **kwargs):
293 """bb command line wrapper.
294
295 Args:
296 args: command line arguments passed to bb
297 kwargs:
298 parse_json: if True, return json parsed object of output
299
300 Returns:
301 stdout of bb
302
303 Raises:
304 errors.ExternalError: bb failed to run
305 subprocess.CalledProcessError: command failed
306 """
307 stderr_lines = []
308 try:
309 result = util.check_output(
310 bb_bin, *args, stderr_callback=stderr_lines.append)
311 if kwargs.get('parse_json'):
312 result = json.loads(result)
313 return result
314 except subprocess.CalledProcessError as e:
315 stderr = ''.join(stderr_lines)
316 if 'interactive login is required' in stderr:
317 raise errors.ExternalError(
318 '%s command is permission denied, please run %s auth-login' %
319 (bb_bin, bb_bin))
320 raise
321 except OSError as e:
322 if e.errno == errno.ENOENT:
323 raise errors.ExternalError('%s command is not found' % bb_bin)
324 raise
325
326
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800327def gsutil(*args, **kwargs):
Kuang-che Wubfc4a642018-04-19 11:54:08 +0800328 """gsutil command line wrapper.
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800329
330 Args:
331 args: command line arguments passed to gsutil
332 kwargs:
333 ignore_errors: if true, return '' for failures, for example 'gsutil ls'
334 but the path not found.
335
336 Returns:
337 stdout of gsutil
338
339 Raises:
Zheng-Jie Changb8697042019-10-29 16:03:26 +0800340 errors.ExternalError: gsutil failed to run
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800341 subprocess.CalledProcessError: command failed
342 """
343 stderr_lines = []
344 try:
345 return util.check_output(
346 gsutil_bin, *args, stderr_callback=stderr_lines.append)
347 except subprocess.CalledProcessError as e:
348 stderr = ''.join(stderr_lines)
349 if re.search(r'ServiceException:.* does not have .*access', stderr):
Kuang-che Wue121fae2018-11-09 16:18:39 +0800350 raise errors.ExternalError(
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800351 'gsutil failed due to permission. ' +
352 'Run "%s config" and follow its instruction. ' % gsutil_bin +
353 'Fill any string if it asks for project-id')
354 if kwargs.get('ignore_errors'):
355 return ''
356 raise
357 except OSError as e:
358 if e.errno == errno.ENOENT:
Kuang-che Wue121fae2018-11-09 16:18:39 +0800359 raise errors.ExternalError(
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800360 'Unable to run %s. gsutil is not installed or not in PATH?' %
361 gsutil_bin)
362 raise
363
364
365def gsutil_ls(*args, **kwargs):
Kuang-che Wubfc4a642018-04-19 11:54:08 +0800366 """gsutil ls.
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800367
368 Args:
369 args: arguments passed to 'gsutil ls'
370 kwargs: extra parameters, where
Kuang-che Wu4fe945b2018-03-31 16:46:38 +0800371 ignore_errors: if true, return empty list instead of raising
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800372 exception, ex. path not found.
373
374 Returns:
375 list of 'gsutil ls' result. One element for one line of gsutil output.
376
377 Raises:
378 subprocess.CalledProcessError: gsutil failed, usually means path not found
379 """
380 return gsutil('ls', *args, **kwargs).splitlines()
381
382
Zheng-Jie Chang127c3302019-09-10 17:17:04 +0800383def gsutil_stat_update_time(*args, **kwargs):
384 """Returns the last modified time of a file or multiple files.
385
386 Args:
387 args: arguments passed to 'gsutil stat'.
388 kwargs: extra parameters for gsutil.
389
390 Returns:
391 A integer indicates the last modified timestamp.
392
393 Raises:
394 subprocess.CalledProcessError: gsutil failed, usually means path not found
395 errors.ExternalError: update time is not found
396 """
397 result = -1
398 # Currently we believe stat always returns a UTC time, and strptime also
399 # parses a UTC time by default.
400 time_format = '%a, %d %b %Y %H:%M:%S GMT'
401
402 for line in gsutil('stat', *args, **kwargs).splitlines():
403 if ':' not in line:
404 continue
405 key, value = map(str.strip, line.split(':', 1))
406 if key != 'Update time':
407 continue
408 dt = datetime.datetime.strptime(value, time_format)
409 unixtime = int(time.mktime(dt.utctimetuple()))
410 result = max(result, unixtime)
411
412 if result == -1:
413 raise errors.ExternalError("didn't find update time")
414 return result
415
416
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800417def query_milestone_by_version(board, short_version):
Kuang-che Wubfc4a642018-04-19 11:54:08 +0800418 """Query milestone by ChromeOS version number.
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800419
420 Args:
421 board: ChromeOS board name
422 short_version: ChromeOS version number in short format, ex. 9300.0.0
423
424 Returns:
425 ChromeOS milestone number (string). For example, '58' for '9300.0.0'.
426 None if failed.
427 """
428 path = gs_archive_path.format(board=board) + '/R*-' + short_version
429 for line in gsutil_ls('-d', path, ignore_errors=True):
430 m = re.search(r'/R(\d+)-', line)
431 if not m:
432 continue
433 return m.group(1)
434
435 for channel in ['canary', 'dev', 'beta', 'stable']:
436 path = gs_release_path.format(
Kuang-che Wu80bf6a52019-05-31 12:48:06 +0800437 channel=channel,
438 boardpath=gs_release_boardpath(board),
439 short_version=short_version)
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800440 for line in gsutil_ls(path, ignore_errors=True):
441 m = re.search(r'\bR(\d+)-' + short_version, line)
442 if not m:
443 continue
444 return m.group(1)
445
446 logger.error('unable to query milestone of %s for %s', short_version, board)
447 return None
448
449
Kuang-che Wu80bf6a52019-05-31 12:48:06 +0800450def list_board_names(chromeos_root):
451 """List board names.
452
453 Args:
454 chromeos_root: chromeos tree root
455
456 Returns:
457 list of board names
458 """
459 # Following logic is simplified from chromite/lib/portage_util.py
460 cros_list_overlays = os.path.join(chromeos_root,
461 'chromite/bin/cros_list_overlays')
462 overlays = util.check_output(cros_list_overlays).splitlines()
463 result = set()
464 for overlay in overlays:
465 conf_file = os.path.join(overlay, 'metadata', 'layout.conf')
466 name = None
467 if os.path.exists(conf_file):
468 for line in open(conf_file):
469 m = re.match(r'^repo-name\s*=\s*(\S+)\s*$', line)
470 if m:
471 name = m.group(1)
472 break
473
474 if not name:
475 name_file = os.path.join(overlay, 'profiles', 'repo_name')
476 if os.path.exists(name_file):
477 name = open(name_file).read().strip()
478
479 if name:
480 name = re.sub(r'-private$', '', name)
481 result.add(name)
482
483 return list(result)
484
485
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800486def recognize_version(board, version):
Kuang-che Wubfc4a642018-04-19 11:54:08 +0800487 """Recognize ChromeOS version.
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800488
489 Args:
490 board: ChromeOS board name
491 version: ChromeOS version number in short or full format
492
493 Returns:
494 (milestone, version in short format)
495 """
496 if is_cros_short_version(version):
497 milestone = query_milestone_by_version(board, version)
498 short_version = version
499 else:
500 milestone, short_version = version_split(version)
501 return milestone, short_version
502
503
Zheng-Jie Chang127c3302019-09-10 17:17:04 +0800504def extract_major_version(version):
505 """Converts a version to its major version.
506
507 Args:
508 version: ChromsOS version number or snapshot version
509
510 Returns:
511 major version number in string format
512 """
513 version = version_to_short(version)
514 m = re.match(r'^(\d+)\.\d+\.\d+$', version)
515 return m.group(1)
516
517
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800518def version_to_short(version):
Kuang-che Wubfc4a642018-04-19 11:54:08 +0800519 """Convert ChromeOS version number to short format.
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800520
521 Args:
522 version: ChromeOS version number in short or full format
523
524 Returns:
525 version number in short format
526 """
527 if is_cros_short_version(version):
528 return version
529 _, short_version = version_split(version)
530 return short_version
531
532
533def version_to_full(board, version):
Kuang-che Wubfc4a642018-04-19 11:54:08 +0800534 """Convert ChromeOS version number to full format.
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800535
536 Args:
537 board: ChromeOS board name
538 version: ChromeOS version number in short or full format
539
540 Returns:
541 version number in full format
542 """
543 if is_cros_full_version(version):
544 return version
545 milestone = query_milestone_by_version(board, version)
Kuang-che Wu0205f052019-05-23 12:48:37 +0800546 assert milestone, 'incorrect board=%s or version=%s ?' % (board, version)
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800547 return make_cros_full_version(milestone, version)
548
549
Zheng-Jie Chang127c3302019-09-10 17:17:04 +0800550def list_snapshots_from_image_archive(board, major_version):
551 """List ChromeOS snapshot image avaliable from gs://chromeos-image-archive.
552
553 Args:
554 board: ChromeOS board
555 major_version: ChromeOS major version
556
557 Returns:
558 list of (version, gs_path):
559 version: Chrome OS snapshot version
560 gs_path: gs path of test image
561 """
562
563 path = (
564 'gs://chromeos-image-archive/{board}-postsubmit/R*-{major_version}.0.0-*')
565 result = []
566 output = gsutil_ls(
567 '-d',
568 path.format(board=board, major_version=major_version),
569 ignore_errors=True)
570
571 for path in output:
572 if not path.endswith('/'):
573 continue
574 m = re.match(r'^gs:\S+(R\d+-\d+\.\d+\.\d+-\d+)', path)
575 if m:
576 snapshot_version = m.group(1)
577 test_image = 'image.zip'
578 gs_path = path + test_image
579 result.append((snapshot_version, gs_path))
580 return result
581
582
Kuang-che Wu575dc442019-03-05 10:30:55 +0800583def list_prebuilt_from_image_archive(board):
584 """Lists ChromeOS prebuilt image available from gs://chromeos-image-archive.
585
586 gs://chromeos-image-archive contains only recent builds (in two years).
587 We prefer this function to list_prebuilt_from_chromeos_releases() because
588 - this is what "cros flash" supports directly.
589 - the paths have milestone information, so we don't need to do slow query
590 by ourselves.
591
592 Args:
593 board: ChromeOS board name
594
595 Returns:
596 list of (version, gs_path):
597 version: Chrome OS version in full format
598 gs_path: gs path of test image
599 """
600 result = []
601 for line in gsutil_ls(gs_archive_path.format(board=board)):
602 m = re.match(r'^gs:\S+(R\d+-\d+\.\d+\.\d+)', line)
603 if m:
604 full_version = m.group(1)
605 test_image = 'chromiumos_test_image.tar.xz'
606 assert line.endswith('/')
607 gs_path = line + test_image
608 result.append((full_version, gs_path))
609 return result
610
611
612def list_prebuilt_from_chromeos_releases(board):
613 """Lists ChromeOS versions available from gs://chromeos-releases.
614
615 gs://chromeos-releases contains more builds. However, 'cros flash' doesn't
616 support it.
617
618 Args:
619 board: ChromeOS board name
620
621 Returns:
622 list of (version, gs_path):
623 version: Chrome OS version in short format
624 gs_path: gs path of test image (with wildcard)
625 """
626 result = []
627 for line in gsutil_ls(
Kuang-che Wu80bf6a52019-05-31 12:48:06 +0800628 gs_release_path.format(
629 channel='*', boardpath=gs_release_boardpath(board), short_version=''),
Kuang-che Wu575dc442019-03-05 10:30:55 +0800630 ignore_errors=True):
631 m = re.match(r'gs:\S+/(\d+\.\d+\.\d+)/$', line)
632 if m:
633 short_version = m.group(1)
634 test_image = 'ChromeOS-test-R*-{short_version}-{board}.tar.xz'.format(
635 short_version=short_version, board=board)
636 gs_path = line + test_image
637 result.append((short_version, gs_path))
638 return result
639
640
641def list_chromeos_prebuilt_versions(board,
642 old,
643 new,
644 only_good_build=True,
Zheng-Jie Chang127c3302019-09-10 17:17:04 +0800645 include_older_build=True,
646 use_snapshot=False):
Kuang-che Wu575dc442019-03-05 10:30:55 +0800647 """Lists ChromeOS version numbers with prebuilt between given range
648
649 Args:
650 board: ChromeOS board name
651 old: start version (inclusive)
652 new: end version (inclusive)
653 only_good_build: only if test image is available
654 include_older_build: include prebuilt in gs://chromeos-releases
Zheng-Jie Chang127c3302019-09-10 17:17:04 +0800655 use_snapshot: return snapshot versions if found
Kuang-che Wu575dc442019-03-05 10:30:55 +0800656
657 Returns:
658 list of sorted version numbers (in full format) between [old, new] range
659 (inclusive).
660 """
661 old = version_to_short(old)
662 new = version_to_short(new)
663
Zheng-Jie Chang127c3302019-09-10 17:17:04 +0800664 rev_map = {
665 } # dict: short version -> list of (short/full or snapshot version, gs path)
Kuang-che Wu575dc442019-03-05 10:30:55 +0800666 for full_version, gs_path in list_prebuilt_from_image_archive(board):
667 short_version = version_to_short(full_version)
Zheng-Jie Chang127c3302019-09-10 17:17:04 +0800668 rev_map[short_version] = [(full_version, gs_path)]
Kuang-che Wu575dc442019-03-05 10:30:55 +0800669
670 if include_older_build and old not in rev_map:
671 for short_version, gs_path in list_prebuilt_from_chromeos_releases(board):
672 if short_version not in rev_map:
Zheng-Jie Chang127c3302019-09-10 17:17:04 +0800673 rev_map[short_version] = [(short_version, gs_path)]
674
675 if use_snapshot:
676 for major_version in range(
677 int(extract_major_version(old)),
678 int(extract_major_version(new)) + 1):
679 short_version = '%s.0.0' % major_version
Zheng-Jie Changb8697042019-10-29 16:03:26 +0800680 # If current version is smaller than cutover, ignore it as it might not
681 # contain enough information for continuing android and chrome bisection.
682 if not util.is_version_lesseq(snapshot_cutover_version, short_version):
683 continue
Zheng-Jie Chang127c3302019-09-10 17:17:04 +0800684 if not util.is_direct_relative_version(short_version, old):
685 continue
686 if not util.is_direct_relative_version(short_version, new):
687 continue
688 snapshots = list_snapshots_from_image_archive(board, str(major_version))
689 if snapshots:
690 # if snapshots found, we can append them after the release version,
691 # so the prebuilt image list of this version will be
692 # release_image, snapshot1, snapshot2,...
Zheng-Jie Changb8697042019-10-29 16:03:26 +0800693 if short_version not in rev_map:
694 rev_map[short_version] = []
Zheng-Jie Chang127c3302019-09-10 17:17:04 +0800695 rev_map[short_version] += snapshots
Kuang-che Wu575dc442019-03-05 10:30:55 +0800696
697 result = []
698 for rev in sorted(rev_map, key=util.version_key_func):
699 if not util.is_direct_relative_version(new, rev):
700 continue
701 if not util.is_version_lesseq(old, rev):
702 continue
703 if not util.is_version_lesseq(rev, new):
704 continue
705
Zheng-Jie Chang127c3302019-09-10 17:17:04 +0800706 for version, gs_path in rev_map[rev]:
Kuang-che Wu575dc442019-03-05 10:30:55 +0800707
Zheng-Jie Chang127c3302019-09-10 17:17:04 +0800708 # version_to_full() and gsutil_ls() may take long time if versions are a
709 # lot. This is acceptable because we usually bisect only short range.
Kuang-che Wu575dc442019-03-05 10:30:55 +0800710
Zheng-Jie Chang127c3302019-09-10 17:17:04 +0800711 if only_good_build:
712 gs_result = gsutil_ls(gs_path, ignore_errors=True)
713 if not gs_result:
714 logger.warning('%s is not a good build, ignore', version)
715 continue
716 assert len(gs_result) == 1
717 m = re.search(r'(R\d+-\d+\.\d+\.\d+)', gs_result[0])
718 if not m:
719 logger.warning('format of image path is unexpected: %s', gs_result[0])
720 continue
721 if not is_cros_snapshot_version(version):
722 version = m.group(1)
723 elif is_cros_short_version(version):
724 version = version_to_full(board, version)
Kuang-che Wu575dc442019-03-05 10:30:55 +0800725
Zheng-Jie Chang127c3302019-09-10 17:17:04 +0800726 result.append(version)
Kuang-che Wu575dc442019-03-05 10:30:55 +0800727
728 return result
729
730
Zheng-Jie Chang127c3302019-09-10 17:17:04 +0800731def prepare_snapshot_image(chromeos_root, board, snapshot_version):
732 """Prepare chromeos snapshot image.
733
734 Args:
735 chromeos_root: chromeos tree root
736 board: ChromeOS board name
737 snapshot_version: ChromeOS snapshot version number
738
739 Returns:
740 local file path of test image relative to chromeos_root
741 """
742 assert is_cros_snapshot_version(snapshot_version)
743 milestone, short_version, snapshot_id = snapshot_version_split(
744 snapshot_version)
745 full_version = make_cros_full_version(milestone, short_version)
746 tmp_dir = os.path.join(
747 chromeos_root, 'tmp',
748 'ChromeOS-test-%s-%s-%s' % (full_version, board, snapshot_id))
749 if not os.path.exists(tmp_dir):
750 os.makedirs(tmp_dir)
751
752 gs_path = ('gs://chromeos-image-archive/{board}-postsubmit/' +
753 '{snapshot_version}-*/image.zip')
754 gs_path = gs_path.format(board=board, snapshot_version=snapshot_version)
755
756 files = gsutil_ls(gs_path, ignore_errors=True)
757 if len(files) == 1:
758 gs_path = files[0]
759 gsutil('cp', gs_path, tmp_dir)
760 image_path = os.path.relpath(
761 os.path.join(tmp_dir, test_image_filename), chromeos_root)
762 util.check_call(
763 'unzip', '-j', 'image.zip', test_image_filename, cwd=tmp_dir)
764 os.remove(os.path.join(tmp_dir, 'image.zip'))
765
766 assert image_path
767 return image_path
768
769
Kuang-che Wu28980b22019-07-31 19:51:45 +0800770def prepare_prebuilt_image(chromeos_root, board, version):
Kuang-che Wubfc4a642018-04-19 11:54:08 +0800771 """Prepare chromeos prebuilt image.
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800772
Kuang-che Wubfc4a642018-04-19 11:54:08 +0800773 It searches for xbuddy image which "cros flash" can use, or fetch image to
774 local disk.
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800775
776 Args:
Kuang-che Wu28980b22019-07-31 19:51:45 +0800777 chromeos_root: chromeos tree root
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800778 board: ChromeOS board name
Kuang-che Wubfc4a642018-04-19 11:54:08 +0800779 version: ChromeOS version number in short or full format
780
781 Returns:
Kuang-che Wu28980b22019-07-31 19:51:45 +0800782 xbuddy path or file path (relative to chromeos_root)
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800783 """
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800784 assert is_cros_version(version)
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800785 full_version = version_to_full(board, version)
786 short_version = version_to_short(full_version)
787
788 image_path = None
789 gs_path = gs_archive_path.format(board=board) + '/' + full_version
790 if gsutil_ls('-d', gs_path, ignore_errors=True):
791 image_path = 'xbuddy://remote/{board}/{full_version}/test'.format(
792 board=board, full_version=full_version)
793 else:
Kuang-che Wu28980b22019-07-31 19:51:45 +0800794 tmp_dir = os.path.join(chromeos_root, 'tmp',
795 'ChromeOS-test-%s-%s' % (full_version, board))
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800796 if not os.path.exists(tmp_dir):
797 os.makedirs(tmp_dir)
798 # gs://chromeos-releases may have more old images than
Kuang-che Wu4fe945b2018-03-31 16:46:38 +0800799 # gs://chromeos-image-archive, but 'cros flash' doesn't support it. We have
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800800 # to fetch the image by ourselves
801 for channel in ['canary', 'dev', 'beta', 'stable']:
802 fn = 'ChromeOS-test-{full_version}-{board}.tar.xz'.format(
803 full_version=full_version, board=board)
804 gs_path = gs_release_path.format(
Kuang-che Wu80bf6a52019-05-31 12:48:06 +0800805 channel=channel,
806 boardpath=gs_release_boardpath(board),
807 short_version=short_version)
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800808 gs_path += '/' + fn
809 if gsutil_ls(gs_path, ignore_errors=True):
810 # TODO(kcwu): delete tmp
811 gsutil('cp', gs_path, tmp_dir)
812 util.check_call('tar', 'Jxvf', fn, cwd=tmp_dir)
Kuang-che Wu28980b22019-07-31 19:51:45 +0800813 image_path = os.path.relpath(
814 os.path.join(tmp_dir, test_image_filename), chromeos_root)
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800815 break
816
817 assert image_path
Kuang-che Wubfc4a642018-04-19 11:54:08 +0800818 return image_path
819
820
821def cros_flash(chromeos_root,
822 host,
823 board,
824 image_path,
825 version=None,
826 clobber_stateful=False,
Kuang-che Wu155fb6e2018-11-29 16:00:41 +0800827 disable_rootfs_verification=True):
Kuang-che Wubfc4a642018-04-19 11:54:08 +0800828 """Flash a DUT with given ChromeOS image.
829
830 This is implemented by 'cros flash' command line.
831
832 Args:
833 chromeos_root: use 'cros flash' of which chromeos tree
834 host: DUT address
835 board: ChromeOS board name
Kuang-che Wu28980b22019-07-31 19:51:45 +0800836 image_path: chromeos image xbuddy path or file path. For relative
837 path, it should be relative to chromeos_root.
Kuang-che Wubfc4a642018-04-19 11:54:08 +0800838 version: ChromeOS version in short or full format
839 clobber_stateful: Clobber stateful partition when performing update
840 disable_rootfs_verification: Disable rootfs verification after update
841 is completed
Kuang-che Wu414d67f2019-05-28 11:28:57 +0800842
843 Raises:
844 errors.ExternalError: cros flash failed
Kuang-che Wubfc4a642018-04-19 11:54:08 +0800845 """
846 logger.info('cros_flash %s %s %s %s', host, board, version, image_path)
847
848 # Reboot is necessary because sometimes previous 'cros flash' failed and
849 # entered a bad state.
850 reboot(host)
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800851
Kuang-che Wu28980b22019-07-31 19:51:45 +0800852 # Handle relative path.
853 if '://' not in image_path and not os.path.isabs(image_path):
854 assert os.path.exists(os.path.join(chromeos_root, image_path))
855 image_path = os.path.join(chromeos_root_inside_chroot, image_path)
856
Kuang-che Wuf3d03ca2019-03-11 17:31:40 +0800857 args = [
858 '--debug', '--no-ping', '--send-payload-in-parallel', host, image_path
859 ]
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800860 if clobber_stateful:
861 args.append('--clobber-stateful')
862 if disable_rootfs_verification:
863 args.append('--disable-rootfs-verification')
864
Kuang-che Wu414d67f2019-05-28 11:28:57 +0800865 try:
866 cros_sdk(chromeos_root, 'cros', 'flash', *args)
867 except subprocess.CalledProcessError:
868 raise errors.ExternalError('cros flash failed')
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800869
Kuang-che Wubfc4a642018-04-19 11:54:08 +0800870 if version:
871 # In the past, cros flash may fail with returncode=0
872 # So let's have an extra check.
Zheng-Jie Chang127c3302019-09-10 17:17:04 +0800873 if is_cros_snapshot_version(version):
874 builder_path = query_dut_lsb_release(host).get(
875 'CHROMEOS_RELEASE_BUILDER_PATH', '')
876 expect_prefix = '%s-postsubmit/%s-' % (board, version)
877 if not builder_path.startswith(expect_prefix):
878 raise errors.ExternalError(
879 'although cros flash succeeded, the OS builder path is '
880 'unexpected: actual=%s expect=%s' % (builder_path, expect_prefix))
881 else:
882 expect_version = version_to_short(version)
883 dut_version = query_dut_short_version(host)
884 if dut_version != expect_version:
885 raise errors.ExternalError(
886 'although cros flash succeeded, the OS version is unexpected: '
887 'actual=%s expect=%s' % (dut_version, expect_version))
Kuang-che Wu414d67f2019-05-28 11:28:57 +0800888
Kuang-che Wu4a81ea72019-10-05 15:35:17 +0800889 # "cros flash" may terminate successfully but the DUT starts self-repairing
Kuang-che Wu414d67f2019-05-28 11:28:57 +0800890 # (b/130786578), so it's necessary to do sanity check.
891 if not is_good_dut(host):
892 raise errors.ExternalError(
893 'although cros flash succeeded, the DUT is in bad state')
894
895
896def cros_flash_with_retry(chromeos_root,
897 host,
898 board,
899 image_path,
900 version=None,
901 clobber_stateful=False,
902 disable_rootfs_verification=True,
903 repair_callback=None):
904 # 'cros flash' is not 100% reliable, retry if necessary.
905 for attempt in range(2):
906 if attempt > 0:
907 logger.info('will retry 60 seconds later')
908 time.sleep(60)
909
910 try:
911 cros_flash(
912 chromeos_root,
913 host,
914 board,
915 image_path,
916 version=version,
917 clobber_stateful=clobber_stateful,
918 disable_rootfs_verification=disable_rootfs_verification)
919 return True
920 except errors.ExternalError:
921 logger.exception('cros flash failed')
922 if repair_callback and not repair_callback(host):
923 logger.warning('not repaired, assume it is harmless')
924 continue
925 return False
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800926
927
928def version_info(board, version):
929 """Query subcomponents version info of given version of ChromeOS
930
931 Args:
932 board: ChromeOS board name
933 version: ChromeOS version number in short or full format
934
935 Returns:
936 dict of component and version info, including (if available):
937 cros_short_version: ChromeOS version
938 cros_full_version: ChromeOS version
939 milestone: milestone of ChromeOS
940 cr_version: Chrome version
Kuang-che Wu708310b2018-03-28 17:24:34 +0800941 android_build_id: Android build id
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800942 android_branch: Android branch, in format like 'git_nyc-mr1-arc'
943 """
Zheng-Jie Chang127c3302019-09-10 17:17:04 +0800944 if is_cros_snapshot_version(version):
Zheng-Jie Changb8697042019-10-29 16:03:26 +0800945 milestone, short_version, _ = snapshot_version_split(version)
946 buildbucket_id = query_snapshot_buildbucket_id(board, version)
947 data = bb('get', buildbucket_id, '-json', '-p', parse_json=True)
948 target_versions = data['output']['properties']['target_versions']
Zheng-Jie Chang127c3302019-09-10 17:17:04 +0800949 return {
Zheng-Jie Changb8697042019-10-29 16:03:26 +0800950 VERSION_KEY_MILESTONE: milestone,
951 VERSION_KEY_CROS_FULL_VERSION: version,
952 VERSION_KEY_CROS_SHORT_VERSION: short_version,
953 VERSION_KEY_CR_VERSION: target_versions['chromeVersion'],
954 VERSION_KEY_ANDROID_BUILD_ID: target_versions['androidVersion'],
955 VERSION_KEY_ANDROID_BRANCH: target_versions['androidBranchVersion'],
Zheng-Jie Chang127c3302019-09-10 17:17:04 +0800956 }
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800957 info = {}
958 full_version = version_to_full(board, version)
959
960 # Some boards may have only partial-metadata.json but no metadata.json.
961 # e.g. caroline R60-9462.0.0
962 # Let's try both.
963 metadata = None
964 for metadata_filename in ['metadata.json', 'partial-metadata.json']:
Kuang-che Wu0768b972019-10-05 15:18:59 +0800965 path = gs_archive_path.format(
966 board=board) + '/%s/%s' % (full_version, metadata_filename)
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800967 metadata = gsutil('cat', path, ignore_errors=True)
968 if metadata:
969 o = json.loads(metadata)
970 v = o['version']
971 board_metadata = o['board-metadata'][board]
972 info.update({
973 VERSION_KEY_CROS_SHORT_VERSION: v['platform'],
974 VERSION_KEY_CROS_FULL_VERSION: v['full'],
975 VERSION_KEY_MILESTONE: v['milestone'],
976 VERSION_KEY_CR_VERSION: v['chrome'],
977 })
978
979 if 'android' in v:
Kuang-che Wu708310b2018-03-28 17:24:34 +0800980 info[VERSION_KEY_ANDROID_BUILD_ID] = v['android']
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800981 if 'android-branch' in v: # this appears since R58-9317.0.0
982 info[VERSION_KEY_ANDROID_BRANCH] = v['android-branch']
983 elif 'android-container-branch' in board_metadata:
984 info[VERSION_KEY_ANDROID_BRANCH] = v['android-container-branch']
985 break
986 else:
987 logger.error('Failed to read metadata from gs://chromeos-image-archive')
988 logger.error(
989 'Note, so far no quick way to look up version info for too old builds')
990
991 return info
Kuang-che Wu848b1af2018-02-01 20:59:36 +0800992
993
994def query_chrome_version(board, version):
Kuang-che Wubfc4a642018-04-19 11:54:08 +0800995 """Queries chrome version of chromeos build.
Kuang-che Wu848b1af2018-02-01 20:59:36 +0800996
997 Args:
998 board: ChromeOS board name
999 version: ChromeOS version number in short or full format
1000
1001 Returns:
1002 Chrome version number
1003 """
1004 info = version_info(board, version)
1005 return info['cr_version']
Kuang-che Wu708310b2018-03-28 17:24:34 +08001006
1007
1008def query_android_build_id(board, rev):
1009 info = version_info(board, rev)
1010 rev = info['android_build_id']
1011 return rev
1012
1013
1014def query_android_branch(board, rev):
1015 info = version_info(board, rev)
1016 rev = info['android_branch']
1017 return rev
Kuang-che Wubfc4a642018-04-19 11:54:08 +08001018
1019
Kuang-che Wu3eb6b502018-06-06 16:15:18 +08001020def guess_chrome_version(board, rev):
1021 """Guess chrome version number.
1022
1023 Args:
1024 board: chromeos board name
1025 rev: chrome or chromeos version
1026
1027 Returns:
1028 chrome version number
1029 """
1030 if is_cros_version(rev):
1031 assert board, 'need to specify BOARD for cros version'
1032 rev = query_chrome_version(board, rev)
1033 assert cr_util.is_chrome_version(rev)
1034
1035 return rev
1036
1037
Kuang-che Wubfc4a642018-04-19 11:54:08 +08001038def is_inside_chroot():
1039 """Returns True if we are inside chroot."""
1040 return os.path.exists('/etc/cros_chroot_version')
1041
1042
1043def cros_sdk(chromeos_root, *args, **kwargs):
1044 """Run commands inside chromeos chroot.
1045
1046 Args:
1047 chromeos_root: chromeos tree root
1048 *args: command to run
1049 **kwargs:
Kuang-che Wud4603d72018-11-29 17:51:21 +08001050 chrome_root: pass to cros_sdk; mount this path into the SDK chroot
Kuang-che Wubfc4a642018-04-19 11:54:08 +08001051 env: (dict) environment variables for the command
Kuang-che Wubcafc552019-08-15 15:27:02 +08001052 log_stdout: Whether write the stdout output of the child process to log.
Kuang-che Wubfc4a642018-04-19 11:54:08 +08001053 stdin: standard input file handle for the command
Kuang-che Wu9890ce82018-07-07 15:14:10 +08001054 stderr_callback: Callback function for stderr. Called once per line.
Kuang-che Wua9a20bb2019-09-05 22:24:04 +08001055 goma_dir: Goma installed directory to mount into the chroot
Kuang-che Wubfc4a642018-04-19 11:54:08 +08001056 """
1057 envs = []
1058 for k, v in kwargs.get('env', {}).items():
1059 assert re.match(r'^[A-Za-z_][A-Za-z0-9_]*$', k)
1060 envs.append('%s=%s' % (k, v))
1061
1062 # Use --no-ns-pid to prevent cros_sdk change our pgid, otherwise subsequent
1063 # commands would be considered as background process.
Kuang-che Wu399d4662019-06-06 15:23:37 +08001064 prefix = ['chromite/bin/cros_sdk', '--no-ns-pid']
Kuang-che Wud4603d72018-11-29 17:51:21 +08001065
1066 if kwargs.get('chrome_root'):
Kuang-che Wu399d4662019-06-06 15:23:37 +08001067 prefix += ['--chrome_root', kwargs['chrome_root']]
Kuang-che Wua9a20bb2019-09-05 22:24:04 +08001068 if kwargs.get('goma_dir'):
1069 prefix += ['--goma_dir', kwargs['goma_dir']]
Kuang-che Wud4603d72018-11-29 17:51:21 +08001070
Kuang-che Wu399d4662019-06-06 15:23:37 +08001071 prefix += envs + ['--']
Kuang-che Wud4603d72018-11-29 17:51:21 +08001072
Kuang-che Wu399d4662019-06-06 15:23:37 +08001073 # In addition to the output of command we are interested, cros_sdk may
1074 # generate its own messages. For example, chroot creation messages if we run
1075 # cros_sdk the first time.
1076 # This is the hack to run dummy command once, so we can get clean output for
1077 # the command we are interested.
1078 cmd = prefix + ['true']
1079 util.check_call(*cmd, cwd=chromeos_root)
1080
1081 cmd = prefix + list(args)
Kuang-che Wu9890ce82018-07-07 15:14:10 +08001082 return util.check_output(
1083 *cmd,
1084 cwd=chromeos_root,
Kuang-che Wubcafc552019-08-15 15:27:02 +08001085 log_stdout=kwargs.get('log_stdout', True),
Kuang-che Wu9890ce82018-07-07 15:14:10 +08001086 stdin=kwargs.get('stdin'),
1087 stderr_callback=kwargs.get('stderr_callback'))
Kuang-che Wubfc4a642018-04-19 11:54:08 +08001088
1089
Kuang-che Wua9a20bb2019-09-05 22:24:04 +08001090def create_chroot(chromeos_root):
1091 """Creates ChromeOS chroot.
1092
1093 Args:
1094 chromeos_root: chromeos tree root
1095 """
1096 if os.path.exists(os.path.join(chromeos_root, 'chroot')):
1097 return
1098 if os.path.exists(os.path.join(chromeos_root, 'chroot.img')):
1099 return
1100
1101 util.check_output('chromite/bin/cros_sdk', '--create', cwd=chromeos_root)
1102
1103
Kuang-che Wubfc4a642018-04-19 11:54:08 +08001104def copy_into_chroot(chromeos_root, src, dst):
1105 """Copies file into chromeos chroot.
1106
1107 Args:
1108 chromeos_root: chromeos tree root
1109 src: path outside chroot
1110 dst: path inside chroot
1111 """
1112 # chroot may be an image, so we cannot copy to corresponding path
1113 # directly.
1114 cros_sdk(chromeos_root, 'sh', '-c', 'cat > %s' % dst, stdin=open(src))
1115
1116
1117def exists_in_chroot(chromeos_root, path):
1118 """Determine whether a path exists in the chroot.
1119
1120 Args:
1121 chromeos_root: chromeos tree root
1122 path: path inside chroot, relative to src/scripts
1123
1124 Returns:
1125 True if a path exists
1126 """
1127 try:
Kuang-che Wuacb6efd2018-04-25 18:52:58 +08001128 cros_sdk(chromeos_root, 'test', '-e', path)
Kuang-che Wubfc4a642018-04-19 11:54:08 +08001129 except subprocess.CalledProcessError:
1130 return False
1131 return True
1132
1133
Kuang-che Wu9890ce82018-07-07 15:14:10 +08001134def check_if_need_recreate_chroot(stdout, stderr):
1135 """Analyze build log and determine if chroot should be recreated.
1136
1137 Args:
1138 stdout: stdout output of build
1139 stderr: stderr output of build
1140
1141 Returns:
1142 the reason if chroot needs recreated; None otherwise
1143 """
Kuang-che Wu74768d32018-09-07 12:03:24 +08001144 if re.search(
1145 r"The current version of portage supports EAPI '\d+'. "
Kuang-che Wuae6824b2019-08-27 22:20:01 +08001146 'You must upgrade', stderr):
Kuang-che Wu9890ce82018-07-07 15:14:10 +08001147 return 'EAPI version mismatch'
1148
Kuang-che Wu5ac81322018-11-26 14:04:06 +08001149 if 'Chroot is too new. Consider running:' in stderr:
1150 return 'chroot version is too new'
1151
1152 # old message before Oct 2018
Kuang-che Wu9890ce82018-07-07 15:14:10 +08001153 if 'Chroot version is too new. Consider running cros_sdk --replace' in stderr:
1154 return 'chroot version is too new'
1155
Kuang-che Wu6fe987f2018-08-28 15:24:20 +08001156 # https://groups.google.com/a/chromium.org/forum/#!msg/chromium-os-dev/uzwT5APspB4/NFakFyCIDwAJ
1157 if "undefined reference to 'std::__1::basic_string" in stdout:
1158 return 'might be due to compiler change'
1159
Kuang-che Wu9890ce82018-07-07 15:14:10 +08001160 return None
1161
1162
Kuang-che Wua9a20bb2019-09-05 22:24:04 +08001163def build_packages(chromeos_root,
1164 board,
1165 chrome_root=None,
1166 goma_dir=None,
1167 afdo_use=False):
Kuang-che Wu28980b22019-07-31 19:51:45 +08001168 """Build ChromeOS packages.
Kuang-che Wubfc4a642018-04-19 11:54:08 +08001169
1170 Args:
1171 chromeos_root: chromeos tree root
1172 board: ChromeOS board name
Kuang-che Wua9a20bb2019-09-05 22:24:04 +08001173 chrome_root: Chrome tree root. If specified, build chrome using the
1174 provided tree
1175 goma_dir: Goma installed directory to mount into the chroot. If specified,
1176 build chrome with goma.
1177 afdo_use: build chrome with AFDO optimization
Kuang-che Wubfc4a642018-04-19 11:54:08 +08001178 """
Kuang-che Wua9a20bb2019-09-05 22:24:04 +08001179 common_env = {
1180 'USE': '-cros-debug chrome_internal',
1181 'FEATURES': 'separatedebug',
1182 }
Kuang-che Wu9890ce82018-07-07 15:14:10 +08001183 stderr_lines = []
1184 try:
Kuang-che Wufb553102018-10-02 18:14:29 +08001185 with locking.lock_file(locking.LOCK_FILE_FOR_BUILD):
Kuang-che Wua9a20bb2019-09-05 22:24:04 +08001186 env = common_env.copy()
1187 env['FEATURES'] += ' -separatedebug splitdebug'
Kuang-che Wufb553102018-10-02 18:14:29 +08001188 cros_sdk(
1189 chromeos_root,
Kuang-che Wu28980b22019-07-31 19:51:45 +08001190 './update_chroot',
1191 '--toolchain_boards',
Kuang-che Wufb553102018-10-02 18:14:29 +08001192 board,
Kuang-che Wua9a20bb2019-09-05 22:24:04 +08001193 env=env,
Kuang-che Wu28980b22019-07-31 19:51:45 +08001194 stderr_callback=stderr_lines.append)
Kuang-che Wua9a20bb2019-09-05 22:24:04 +08001195
1196 env = common_env.copy()
1197 cmd = [
Kuang-che Wu28980b22019-07-31 19:51:45 +08001198 './build_packages',
1199 '--board',
1200 board,
1201 '--withdev',
1202 '--noworkon',
1203 '--skip_chroot_upgrade',
1204 '--accept_licenses=@CHROMEOS',
Kuang-che Wua9a20bb2019-09-05 22:24:04 +08001205 ]
1206 if goma_dir:
1207 # Tell build_packages to start and stop goma
1208 cmd.append('--run_goma')
1209 env['USE_GOMA'] = 'true'
1210 if afdo_use:
1211 env['USE'] += ' afdo_use'
1212 cros_sdk(
1213 chromeos_root,
1214 *cmd,
1215 env=env,
1216 chrome_root=chrome_root,
1217 stderr_callback=stderr_lines.append,
1218 goma_dir=goma_dir)
Kuang-che Wu9890ce82018-07-07 15:14:10 +08001219 except subprocess.CalledProcessError as e:
1220 # Detect failures due to incompatibility between chroot and source tree. If
1221 # so, notify the caller to recreate chroot and retry.
1222 reason = check_if_need_recreate_chroot(e.output, ''.join(stderr_lines))
1223 if reason:
1224 raise NeedRecreateChrootException(reason)
1225
1226 # For other failures, don't know how to handle. Just bail out.
1227 raise
1228
Kuang-che Wu28980b22019-07-31 19:51:45 +08001229
1230def build_image(chromeos_root, board):
1231 """Build ChromeOS image.
1232
1233 Args:
1234 chromeos_root: chromeos tree root
1235 board: ChromeOS board name
1236
1237 Returns:
1238 image folder; relative to chromeos_root
1239 """
1240 stderr_lines = []
1241 try:
1242 with locking.lock_file(locking.LOCK_FILE_FOR_BUILD):
1243 cros_sdk(
1244 chromeos_root,
1245 './build_image',
1246 '--board',
1247 board,
1248 '--noenable_rootfs_verification',
1249 'test',
1250 env={
1251 'USE': '-cros-debug chrome_internal',
1252 'FEATURES': 'separatedebug',
1253 },
1254 stderr_callback=stderr_lines.append)
1255 except subprocess.CalledProcessError as e:
1256 # Detect failures due to incompatibility between chroot and source tree. If
1257 # so, notify the caller to recreate chroot and retry.
1258 reason = check_if_need_recreate_chroot(e.output, ''.join(stderr_lines))
1259 if reason:
1260 raise NeedRecreateChrootException(reason)
1261
1262 # For other failures, don't know how to handle. Just bail out.
1263 raise
1264
1265 image_symlink = os.path.join(chromeos_root, cached_images_dir, board,
1266 'latest')
1267 assert os.path.exists(image_symlink)
1268 image_name = os.readlink(image_symlink)
1269 image_folder = os.path.join(cached_images_dir, board, image_name)
1270 assert os.path.exists(
1271 os.path.join(chromeos_root, image_folder, test_image_filename))
1272 return image_folder
Kuang-che Wubfc4a642018-04-19 11:54:08 +08001273
1274
Kuang-che Wub9705bd2018-06-28 17:59:18 +08001275class AutotestControlInfo(object):
1276 """Parsed content of autotest control file.
1277
1278 Attributes:
1279 name: test name
1280 path: control file path
1281 variables: dict of top-level control variables. Sample keys: NAME, AUTHOR,
1282 DOC, ATTRIBUTES, DEPENDENCIES, etc.
1283 """
1284
1285 def __init__(self, path, variables):
1286 self.name = variables['NAME']
1287 self.path = path
1288 self.variables = variables
1289
1290
1291def parse_autotest_control_file(path):
1292 """Parses autotest control file.
1293
1294 This only parses simple top-level string assignments.
1295
1296 Returns:
1297 AutotestControlInfo object
1298 """
1299 variables = {}
1300 code = ast.parse(open(path).read())
1301 for stmt in code.body:
1302 # Skip if not simple "NAME = *" assignment.
1303 if not (isinstance(stmt, ast.Assign) and len(stmt.targets) == 1 and
1304 isinstance(stmt.targets[0], ast.Name)):
1305 continue
1306
1307 # Only support string value.
1308 if isinstance(stmt.value, ast.Str):
1309 variables[stmt.targets[0].id] = stmt.value.s
1310
1311 return AutotestControlInfo(path, variables)
1312
1313
1314def enumerate_autotest_control_files(autotest_dir):
1315 """Enumerate autotest control files.
1316
1317 Args:
1318 autotest_dir: autotest folder
1319
1320 Returns:
1321 list of paths to control files
1322 """
1323 # Where to find control files. Relative to autotest_dir.
1324 subpaths = [
1325 'server/site_tests',
1326 'client/site_tests',
1327 'server/tests',
1328 'client/tests',
1329 ]
1330
1331 blacklist = ['site-packages', 'venv', 'results', 'logs', 'containers']
1332 result = []
1333 for subpath in subpaths:
1334 path = os.path.join(autotest_dir, subpath)
1335 for root, dirs, files in os.walk(path):
1336
1337 for black in blacklist:
1338 if black in dirs:
1339 dirs.remove(black)
1340
1341 for filename in files:
1342 if filename == 'control' or filename.startswith('control.'):
1343 result.append(os.path.join(root, filename))
1344
1345 return result
1346
1347
1348def get_autotest_test_info(autotest_dir, test_name):
1349 """Get metadata of given test.
1350
1351 Args:
1352 autotest_dir: autotest folder
1353 test_name: test name
1354
1355 Returns:
1356 AutotestControlInfo object. None if test not found.
1357 """
1358 for control_file in enumerate_autotest_control_files(autotest_dir):
1359 info = parse_autotest_control_file(control_file)
1360 if info.name == test_name:
1361 return info
1362 return None
1363
1364
Kuang-che Wue4bae0b2018-07-19 12:10:14 +08001365class ChromeOSSpecManager(codechange.SpecManager):
1366 """Repo manifest related operations.
1367
1368 This class enumerates chromeos manifest files, parses them,
1369 and sync to disk state according to them.
1370 """
Kuang-che Wubfc4a642018-04-19 11:54:08 +08001371
1372 def __init__(self, config):
1373 self.config = config
Kuang-che Wue4bae0b2018-07-19 12:10:14 +08001374 self.manifest_dir = os.path.join(self.config['chromeos_root'], '.repo',
1375 'manifests')
Zheng-Jie Chang127c3302019-09-10 17:17:04 +08001376 self.manifest_internal_dir = os.path.join(self.config['chromeos_mirror'],
1377 'manifest-internal.git')
Kuang-che Wue4bae0b2018-07-19 12:10:14 +08001378 self.historical_manifest_git_dir = os.path.join(
Kuang-che Wud8fc9572018-10-03 21:00:41 +08001379 self.config['chromeos_mirror'], 'chromeos/manifest-versions.git')
Kuang-che Wue4bae0b2018-07-19 12:10:14 +08001380 if not os.path.exists(self.historical_manifest_git_dir):
Kuang-che Wue121fae2018-11-09 16:18:39 +08001381 raise errors.InternalError('Manifest snapshots should be cloned into %s' %
1382 self.historical_manifest_git_dir)
Kuang-che Wubfc4a642018-04-19 11:54:08 +08001383
Zheng-Jie Chang127c3302019-09-10 17:17:04 +08001384 def lookup_snapshot_manifest_revisions(self, old, new):
1385 """Get manifest commits between snapshot versions.
Kuang-che Wue4bae0b2018-07-19 12:10:14 +08001386
Zheng-Jie Chang127c3302019-09-10 17:17:04 +08001387 Returns:
1388 list of (timestamp, commit_id, snapshot_id):
1389 timestamp: integer unix timestamp
1390 commit_id: a string indicates commit hash
1391 snapshot_id: a string indicates snapshot id
1392 """
1393 assert is_cros_snapshot_version(old)
1394 assert is_cros_snapshot_version(new)
1395
1396 gs_path = (
1397 'gs://chromeos-image-archive/{board}-postsubmit/{version}-*/image.zip')
1398 # Try to guess the commit time of a snapshot manifest, it is usually a few
1399 # minutes different between snapshot manifest commit and image.zip
1400 # generate.
1401 try:
1402 old_timestamp = gsutil_stat_update_time(
1403 gs_path.format(board=self.config['board'], version=old)) - 86400
1404 except subprocess.CalledProcessError:
1405 old_timestamp = None
1406 try:
1407 new_timestamp = gsutil_stat_update_time(
1408 gs_path.format(board=self.config['board'], version=new)) + 86400
1409 # 1558657989 is snapshot_id 5982's commit time, this ensures every time
1410 # we can find snapshot 5982
1411 # snapshot_id <= 5982 has different commit message format, so we need
1412 # to identify its id in different ways, see below comment for more info.
1413 new_timestamp = max(new_timestamp, 1558657989 + 1)
1414 except subprocess.CalledProcessError:
1415 new_timestamp = None
1416 result = []
1417 _, _, old_snapshot_id = snapshot_version_split(old)
1418 _, _, new_snapshot_id = snapshot_version_split(new)
1419 repo = self.manifest_internal_dir
1420 path = 'snapshot.xml'
1421 branch = 'snapshot'
1422 commits = git_util.get_history(
1423 repo,
1424 path,
1425 branch,
1426 after=old_timestamp,
1427 before=new_timestamp,
1428 with_subject=True)
1429
1430 # Unfortunately, we can not identify snapshot_id <= 5982 from its commit
1431 # subject, as their subjects are all `Annealing manifest snapshot.`.
1432 # So instead we count the snapshot_id manually.
1433 count = 5982
1434 # There are two snapshot_id = 2633 in commit history, ignore the former
1435 # one.
1436 ignore_list = ['95c8526a7f0798d02f692010669dcbd5a152439a']
1437 # We examine the commits in reverse order as there are some testing
1438 # commits before snapshot_id=2, this method works fine after
1439 # snapshot 2, except snapshot 2633
1440 for commit in reversed(commits):
1441 msg = commit[2]
1442 if commit[1] in ignore_list:
1443 continue
1444
1445 match = re.match(r'^annealing manifest snapshot (\d+)', msg)
1446 if match:
1447 snapshot_id = match.group(1)
1448 elif 'Annealing manifest snapshot' in msg:
1449 snapshot_id = str(count)
1450 count -= 1
1451 else:
1452 continue
1453 if int(old_snapshot_id) <= int(snapshot_id) <= int(new_snapshot_id):
1454 result.append((commit[0], commit[1], snapshot_id))
1455 # We find commits in reversed order, now reverse it again to chronological
1456 # order.
1457 return list(reversed(result))
1458
1459 def lookup_build_timestamp(self, rev):
1460 assert is_cros_full_version(rev) or is_cros_snapshot_version(rev)
1461 if is_cros_full_version(rev):
1462 return self.lookup_release_build_timestamp(rev)
1463 else:
1464 return self.lookup_snapshot_build_timestamp(rev)
1465
1466 def lookup_snapshot_build_timestamp(self, rev):
1467 assert is_cros_snapshot_version(rev)
1468 return int(self.lookup_snapshot_manifest_revisions(rev, rev)[0][0])
1469
1470 def lookup_release_build_timestamp(self, rev):
1471 assert is_cros_full_version(rev)
Kuang-che Wubfc4a642018-04-19 11:54:08 +08001472 milestone, short_version = version_split(rev)
Kuang-che Wue4bae0b2018-07-19 12:10:14 +08001473 path = os.path.join('buildspecs', milestone, short_version + '.xml')
1474 try:
1475 timestamp = git_util.get_commit_time(self.historical_manifest_git_dir,
1476 'refs/heads/master', path)
1477 except ValueError:
Kuang-che Wuce2f3be2019-10-28 19:44:54 +08001478 raise errors.InternalError(
1479 '%s does not have %s' % (self.historical_manifest_git_dir, path))
Kuang-che Wue4bae0b2018-07-19 12:10:14 +08001480 return timestamp
Kuang-che Wubfc4a642018-04-19 11:54:08 +08001481
Kuang-che Wue4bae0b2018-07-19 12:10:14 +08001482 def collect_float_spec(self, old, new):
1483 old_timestamp = self.lookup_build_timestamp(old)
1484 new_timestamp = self.lookup_build_timestamp(new)
Zheng-Jie Chang127c3302019-09-10 17:17:04 +08001485 # snapshot time is different from commit time
1486 # usually it's a few minutes different
1487 # 30 minutes should be safe in most cases
1488 if is_cros_snapshot_version(old):
1489 old_timestamp = old_timestamp - 1800
1490 if is_cros_snapshot_version(new):
1491 new_timestamp = new_timestamp + 1800
Kuang-che Wubfc4a642018-04-19 11:54:08 +08001492
Kuang-che Wue4bae0b2018-07-19 12:10:14 +08001493 path = os.path.join(self.manifest_dir, 'default.xml')
1494 if not os.path.islink(path) or os.readlink(path) != 'full.xml':
Kuang-che Wue121fae2018-11-09 16:18:39 +08001495 raise errors.InternalError(
1496 'default.xml not symlink to full.xml is not supported')
Kuang-che Wubfc4a642018-04-19 11:54:08 +08001497
Kuang-che Wue4bae0b2018-07-19 12:10:14 +08001498 result = []
1499 path = 'full.xml'
1500 parser = repo_util.ManifestParser(self.manifest_dir)
1501 for timestamp, git_rev in parser.enumerate_manifest_commits(
1502 old_timestamp, new_timestamp, path):
1503 result.append(
1504 codechange.Spec(codechange.SPEC_FLOAT, git_rev, timestamp, path))
1505 return result
Kuang-che Wubfc4a642018-04-19 11:54:08 +08001506
Kuang-che Wue4bae0b2018-07-19 12:10:14 +08001507 def collect_fixed_spec(self, old, new):
Zheng-Jie Chang127c3302019-09-10 17:17:04 +08001508 assert is_cros_full_version(old) or is_cros_snapshot_version(old)
1509 assert is_cros_full_version(new) or is_cros_snapshot_version(new)
1510
1511 # case 1: if both are snapshot, return a list of snapshot
1512 if is_cros_snapshot_version(old) and is_cros_snapshot_version(new):
1513 return self.collect_snapshot_specs(old, new)
1514
1515 # case 2: if both are release version
1516 # return a list of release version
1517 if is_cros_full_version(old) and is_cros_full_version(new):
1518 return self.collect_release_specs(old, new)
1519
1520 # case 3: return a list of release version and append a snapshot
1521 # before or at the end
1522 result = self.collect_release_specs(old, new)
1523 if is_cros_snapshot_version(old):
1524 result = self.collect_release_specs(old, old) + result[1:]
1525 elif is_cros_snapshot_version(new):
1526 result = result[:-1] + self.collect_release_specs(new, new)
1527 return result
1528
1529 def collect_snapshot_specs(self, old, new):
1530 assert is_cros_snapshot_version(old)
1531 assert is_cros_snapshot_version(new)
1532
1533 def guess_snapshot_version(board, snapshot_id, old, new):
1534 if old.endswith('-' + snapshot_id):
1535 return old
1536 if new.endswith('-' + snapshot_id):
1537 return new
Kuang-che Wuf791afa2019-10-28 19:53:26 +08001538 gs_path = ('gs://chromeos-image-archive/{board}-postsubmit/'
1539 'R*-{snapshot_id}-*'.format(
1540 board=board, snapshot_id=snapshot_id))
Zheng-Jie Chang127c3302019-09-10 17:17:04 +08001541 for line in gsutil_ls(gs_path, ignore_errors=True):
1542 m = re.match(r'^gs:\S+(R\d+-\d+\.\d+\.\d+-\d+)\S+', line)
1543 if m:
1544 return m.group(1)
1545 raise errors.ExternalError(
Kuang-che Wuf791afa2019-10-28 19:53:26 +08001546 'guess_snapshot_version failed, board=%s snapshot_id=%s '
1547 'old=%s new=%s' % (board, snapshot_id, old, new))
Zheng-Jie Chang127c3302019-09-10 17:17:04 +08001548
1549 result = []
1550 path = 'snapshot.xml'
1551 revisions = self.lookup_snapshot_manifest_revisions(old, new)
Kuang-che Wuf791afa2019-10-28 19:53:26 +08001552 for timestamp, _git_rev, snapshot_id in revisions:
Zheng-Jie Chang127c3302019-09-10 17:17:04 +08001553 snapshot_version = guess_snapshot_version(self.config['board'],
1554 snapshot_id, old, new)
1555 result.append(
1556 codechange.Spec(codechange.SPEC_FIXED, snapshot_version, timestamp,
1557 path))
1558 return result
1559
1560 def collect_release_specs(self, old, new):
Kuang-che Wubfc4a642018-04-19 11:54:08 +08001561 assert is_cros_full_version(old)
1562 assert is_cros_full_version(new)
1563 old_milestone, old_short_version = version_split(old)
1564 new_milestone, new_short_version = version_split(new)
1565
Kuang-che Wubfc4a642018-04-19 11:54:08 +08001566 result = []
Kuang-che Wue4bae0b2018-07-19 12:10:14 +08001567 for milestone in git_util.list_dir_from_revision(
1568 self.historical_manifest_git_dir, 'refs/heads/master', 'buildspecs'):
1569 if not milestone.isdigit():
1570 continue
1571 if not int(old_milestone) <= int(milestone) <= int(new_milestone):
1572 continue
1573
Kuang-che Wu74768d32018-09-07 12:03:24 +08001574 files = git_util.list_dir_from_revision(
1575 self.historical_manifest_git_dir, 'refs/heads/master',
1576 os.path.join('buildspecs', milestone))
Kuang-che Wubfc4a642018-04-19 11:54:08 +08001577
1578 for fn in files:
Kuang-che Wue4bae0b2018-07-19 12:10:14 +08001579 path = os.path.join('buildspecs', milestone, fn)
Kuang-che Wubfc4a642018-04-19 11:54:08 +08001580 short_version, ext = os.path.splitext(fn)
1581 if ext != '.xml':
1582 continue
Kuang-che Wubfc4a642018-04-19 11:54:08 +08001583 if (util.is_version_lesseq(old_short_version, short_version) and
1584 util.is_version_lesseq(short_version, new_short_version) and
1585 util.is_direct_relative_version(short_version, new_short_version)):
Kuang-che Wue4bae0b2018-07-19 12:10:14 +08001586 rev = make_cros_full_version(milestone, short_version)
1587 timestamp = git_util.get_commit_time(self.historical_manifest_git_dir,
1588 'refs/heads/master', path)
1589 result.append(
1590 codechange.Spec(codechange.SPEC_FIXED, rev, timestamp, path))
Kuang-che Wubfc4a642018-04-19 11:54:08 +08001591
Kuang-che Wue4bae0b2018-07-19 12:10:14 +08001592 def version_key_func(spec):
1593 _milestone, short_version = version_split(spec.name)
Kuang-che Wubfc4a642018-04-19 11:54:08 +08001594 return util.version_key_func(short_version)
1595
1596 result.sort(key=version_key_func)
Kuang-che Wue4bae0b2018-07-19 12:10:14 +08001597 assert result[0].name == old
1598 assert result[-1].name == new
Kuang-che Wubfc4a642018-04-19 11:54:08 +08001599 return result
1600
Kuang-che Wue4bae0b2018-07-19 12:10:14 +08001601 def get_manifest(self, rev):
Zheng-Jie Chang127c3302019-09-10 17:17:04 +08001602 assert is_cros_full_version(rev) or is_cros_snapshot_version(rev)
1603 if is_cros_full_version(rev):
1604 milestone, short_version = version_split(rev)
1605 path = os.path.join('buildspecs', milestone, '%s.xml' % short_version)
1606 manifest = git_util.get_file_from_revision(
1607 self.historical_manifest_git_dir, 'refs/heads/master', path)
1608 else:
1609 revisions = self.lookup_snapshot_manifest_revisions(rev, rev)
1610 commit_id = revisions[0][1]
1611 manifest = git_util.get_file_from_revision(self.manifest_internal_dir,
1612 commit_id, 'snapshot.xml')
Kuang-che Wue4bae0b2018-07-19 12:10:14 +08001613 manifest_name = 'manifest_%s.xml' % rev
1614 manifest_path = os.path.join(self.manifest_dir, manifest_name)
1615 with open(manifest_path, 'w') as f:
1616 f.write(manifest)
1617
1618 return manifest_name
1619
1620 def parse_spec(self, spec):
1621 parser = repo_util.ManifestParser(self.manifest_dir)
1622 if spec.spec_type == codechange.SPEC_FIXED:
1623 manifest_name = self.get_manifest(spec.name)
1624 manifest_path = os.path.join(self.manifest_dir, manifest_name)
1625 content = open(manifest_path).read()
1626 root = parser.parse_single_xml(content, allow_include=False)
1627 else:
1628 root = parser.parse_xml_recursive(spec.name, spec.path)
1629
1630 spec.entries = parser.process_parsed_result(root)
1631 if spec.spec_type == codechange.SPEC_FIXED:
Kuang-che Wufe1e88a2019-09-10 21:52:25 +08001632 if not spec.is_static():
1633 raise ValueError(
1634 'fixed spec %r has unexpected floating entries' % spec.name)
Kuang-che Wue4bae0b2018-07-19 12:10:14 +08001635
1636 def sync_disk_state(self, rev):
1637 manifest_name = self.get_manifest(rev)
1638
1639 # For ChromeOS, mark_as_stable step requires 'repo init -m', which sticks
1640 # manifest. 'repo sync -m' is not enough
1641 repo_util.init(
1642 self.config['chromeos_root'],
1643 'https://chrome-internal.googlesource.com/chromeos/manifest-internal',
1644 manifest_name=manifest_name,
1645 repo_url='https://chromium.googlesource.com/external/repo.git',
Kuang-che Wud8fc9572018-10-03 21:00:41 +08001646 reference=self.config['chromeos_mirror'],
Kuang-che Wue4bae0b2018-07-19 12:10:14 +08001647 )
1648
1649 # Note, don't sync with current_branch=True for chromeos. One of its
1650 # build steps (inside mark_as_stable) executes "git describe" which
1651 # needs git tag information.
1652 repo_util.sync(self.config['chromeos_root'])