blob: 2664557375e68a122fc6c50aa69153e4d8f57ab5 [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
Kuang-che Wu72b5a572019-10-29 20:37:57 +080017import calendar
Zheng-Jie Chang127c3302019-09-10 17:17:04 +080018import datetime
Kuang-che Wu2ea804f2017-11-28 17:11:41 +080019import errno
20import json
21import logging
22import os
23import re
Kuang-che Wuad5ac7b2020-02-20 19:02:52 +080024import shutil
Kuang-che Wu2ea804f2017-11-28 17:11:41 +080025import subprocess
26import time
27
Zheng-Jie Chang4fabff62019-12-08 21:54:35 +080028from google.protobuf import json_format
29
Zheng-Jie Chang2b6d1472019-11-13 12:40:17 +080030from bisect_kit import buildbucket_util
Kuang-che Wu2ea804f2017-11-28 17:11:41 +080031from bisect_kit import cli
Kuang-che Wue4bae0b2018-07-19 12:10:14 +080032from bisect_kit import codechange
Kuang-che Wu3eb6b502018-06-06 16:15:18 +080033from bisect_kit import cr_util
Kuang-che Wue121fae2018-11-09 16:18:39 +080034from bisect_kit import errors
Kuang-che Wubfc4a642018-04-19 11:54:08 +080035from bisect_kit import git_util
Kuang-che Wufb553102018-10-02 18:14:29 +080036from bisect_kit import locking
Kuang-che Wubfc4a642018-04-19 11:54:08 +080037from bisect_kit import repo_util
Kuang-che Wu2ea804f2017-11-28 17:11:41 +080038from bisect_kit import util
39
40logger = logging.getLogger(__name__)
41
Kuang-che Wu2ea804f2017-11-28 17:11:41 +080042re_chromeos_full_version = r'^R\d+-\d+\.\d+\.\d+$'
Kuang-che Wuacb6efd2018-04-25 18:52:58 +080043re_chromeos_localbuild_version = r'^\d+\.\d+\.\d{4}_\d\d_\d\d_\d{4}$'
44re_chromeos_short_version = r'^\d+\.\d+\.\d+$'
Zheng-Jie Chang127c3302019-09-10 17:17:04 +080045re_chromeos_snapshot_version = r'^R\d+-\d+\.\d+\.\d+-\d+$'
Kuang-che Wu2ea804f2017-11-28 17:11:41 +080046
47gs_archive_path = 'gs://chromeos-image-archive/{board}-release'
48gs_release_path = (
Kuang-che Wu80bf6a52019-05-31 12:48:06 +080049 'gs://chromeos-releases/{channel}-channel/{boardpath}/{short_version}')
Kuang-che Wu2ea804f2017-11-28 17:11:41 +080050
51# Assume gsutil is in PATH.
52gsutil_bin = 'gsutil'
Zheng-Jie Changb8697042019-10-29 16:03:26 +080053
54# Since snapshots with version >= 12618.0.0 have android and chrome version
55# info.
56snapshot_cutover_version = '12618.0.0'
Kuang-che Wu2ea804f2017-11-28 17:11:41 +080057
Kuang-che Wub9705bd2018-06-28 17:59:18 +080058chromeos_root_inside_chroot = '/mnt/host/source'
59# relative to chromeos_root
Kuang-che Wu7f82c6f2019-08-12 14:29:28 +080060prebuilt_autotest_dir = 'tmp/autotest-prebuilt'
Kuang-che Wu28980b22019-07-31 19:51:45 +080061# Relative to chromeos root. Images are cached_images_dir/$board/$image_name.
62cached_images_dir = 'src/build/images'
63test_image_filename = 'chromiumos_test_image.bin'
Kuang-che Wub9705bd2018-06-28 17:59:18 +080064
Kuang-che Wu2ea804f2017-11-28 17:11:41 +080065VERSION_KEY_CROS_SHORT_VERSION = 'cros_short_version'
66VERSION_KEY_CROS_FULL_VERSION = 'cros_full_version'
67VERSION_KEY_MILESTONE = 'milestone'
68VERSION_KEY_CR_VERSION = 'cr_version'
Kuang-che Wu708310b2018-03-28 17:24:34 +080069VERSION_KEY_ANDROID_BUILD_ID = 'android_build_id'
Kuang-che Wu2ea804f2017-11-28 17:11:41 +080070VERSION_KEY_ANDROID_BRANCH = 'android_branch'
Zheng-Jie Chang5f9ae4e2020-02-07 14:26:06 +080071CROSLAND_URL_TEMPLATE = 'https://crosland.corp.google.com/log/%s..%s'
Kuang-che Wu2ea804f2017-11-28 17:11:41 +080072
73
Kuang-che Wu9890ce82018-07-07 15:14:10 +080074class NeedRecreateChrootException(Exception):
75 """Failed to build ChromeOS because of chroot mismatch or corruption"""
76
77
Kuang-che Wu2ea804f2017-11-28 17:11:41 +080078def is_cros_short_version(s):
Kuang-che Wuacb6efd2018-04-25 18:52:58 +080079 """Determines if `s` is chromeos short version.
80
81 This function doesn't accept version number of local build.
82 """
Kuang-che Wu2ea804f2017-11-28 17:11:41 +080083 return bool(re.match(re_chromeos_short_version, s))
84
85
Kuang-che Wuacb6efd2018-04-25 18:52:58 +080086def is_cros_localbuild_version(s):
87 """Determines if `s` is chromeos local build version."""
88 return bool(re.match(re_chromeos_localbuild_version, s))
89
90
Kuang-che Wu2ea804f2017-11-28 17:11:41 +080091def is_cros_full_version(s):
Kuang-che Wuacb6efd2018-04-25 18:52:58 +080092 """Determines if `s` is chromeos full version.
93
94 This function doesn't accept version number of local build.
95 """
Kuang-che Wu2ea804f2017-11-28 17:11:41 +080096 return bool(re.match(re_chromeos_full_version, s))
97
98
99def is_cros_version(s):
100 """Determines if `s` is chromeos version (either short or full)"""
101 return is_cros_short_version(s) or is_cros_full_version(s)
102
103
Zheng-Jie Chang127c3302019-09-10 17:17:04 +0800104def is_cros_snapshot_version(s):
105 """Determines if `s` is chromeos snapshot version"""
106 return bool(re.match(re_chromeos_snapshot_version, s))
107
108
Zheng-Jie Changaea4fba2020-02-17 17:12:09 +0800109def is_cros_version_lesseq(ver1, ver2):
110 """Determines if ver1 is less or equal to ver2.
111
112 Args:
113 ver1: a Chrome OS version in short, full, or snapshot format.
114 ver2: a Chrome OS version in short, full, or snapshot format.
115
116 Returns:
117 True if ver1 is smaller than ver2.
118 """
119 assert is_cros_version(ver1) or is_cros_snapshot_version(ver1)
120 assert is_cros_version(ver2) or is_cros_snapshot_version(ver2)
121
122 ver1 = [int(x) for x in re.split(r'[.-]', ver1) if not x.startswith('R')]
123 ver2 = [int(x) for x in re.split(r'[.-]', ver2) if not x.startswith('R')]
124 return ver1 <= ver2
125
126
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800127def make_cros_full_version(milestone, short_version):
128 """Makes full_version from milestone and short_version"""
Kuang-che Wuacb6efd2018-04-25 18:52:58 +0800129 assert milestone
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800130 return 'R%s-%s' % (milestone, short_version)
131
132
Zheng-Jie Chang127c3302019-09-10 17:17:04 +0800133def make_cros_snapshot_version(milestone, short_version, snapshot_id):
134 """Makes snapshot version from milestone, short_version and snapshot id"""
135 return 'R%s-%s-%s' % (milestone, short_version, snapshot_id)
136
137
138def version_split(version):
139 """Splits full_version or snapshot_version into milestone and short_version"""
140 assert is_cros_full_version(version) or is_cros_snapshot_version(version)
141 if is_cros_snapshot_version(version):
142 return snapshot_version_split(version)[0:2]
143 milestone, short_version = version.split('-')
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800144 return milestone[1:], short_version
145
146
Zheng-Jie Chang127c3302019-09-10 17:17:04 +0800147def snapshot_version_split(snapshot_version):
148 """Splits snapshot_version into milestone, short_version and snapshot_id"""
149 assert is_cros_snapshot_version(snapshot_version)
150 milestone, shot_version, snapshot_id = snapshot_version.split('-')
151 return milestone[1:], shot_version, snapshot_id
152
153
Zheng-Jie Changb8697042019-10-29 16:03:26 +0800154def query_snapshot_buildbucket_id(board, snapshot_version):
155 """Query buildbucket id of a snapshot"""
156 assert is_cros_snapshot_version(snapshot_version)
Zheng-Jie Chang54020832020-04-21 15:52:48 +0800157 path = ('gs://chromeos-image-archive/{board}-snapshot'
Zheng-Jie Changb8697042019-10-29 16:03:26 +0800158 '/{snapshot_version}-*/image.zip')
159 output = gsutil_ls(
160 '-d',
161 path.format(board=board, snapshot_version=snapshot_version),
162 ignore_errors=True)
163 for line in output:
Zheng-Jie Chang54020832020-04-21 15:52:48 +0800164 m = re.match(r'.*-snapshot/R\d+-\d+\.\d+\.\d+-\d+-(.+)/image\.zip', line)
Zheng-Jie Changb8697042019-10-29 16:03:26 +0800165 if m:
166 return m.group(1)
167 return None
168
169
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800170def argtype_cros_version(s):
Zheng-Jie Chang127c3302019-09-10 17:17:04 +0800171 if (not is_cros_version(s)) and (not is_cros_snapshot_version(s)):
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800172 msg = 'invalid cros version'
Kuang-che Wuce2f3be2019-10-28 19:44:54 +0800173 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 +0800174 return s
175
176
177def query_dut_lsb_release(host):
178 """Query /etc/lsb-release of given DUT
179
180 Args:
181 host: the DUT address
182
183 Returns:
Kuang-che Wu3eb6b502018-06-06 16:15:18 +0800184 dict for keys and values of /etc/lsb-release.
185
186 Raises:
Kuang-che Wu44278142019-03-04 11:33:57 +0800187 errors.SshConnectionError: cannot connect to host
188 errors.ExternalError: lsb-release file doesn't exist
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800189 """
190 try:
Kuang-che Wu44278142019-03-04 11:33:57 +0800191 output = util.ssh_cmd(host, 'cat', '/etc/lsb-release')
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800192 except subprocess.CalledProcessError:
Kuang-che Wu44278142019-03-04 11:33:57 +0800193 raise errors.ExternalError('unable to read /etc/lsb-release; not a DUT')
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800194 return dict(re.findall(r'^(\w+)=(.*)$', output, re.M))
195
196
197def is_dut(host):
Kuang-che Wubfc4a642018-04-19 11:54:08 +0800198 """Determines whether a host is a chromeos device.
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800199
200 Args:
201 host: the DUT address
202
203 Returns:
204 True if the host is a chromeos device.
205 """
Kuang-che Wu44278142019-03-04 11:33:57 +0800206 try:
207 return query_dut_lsb_release(host).get('DEVICETYPE') in [
208 'CHROMEBASE',
209 'CHROMEBIT',
210 'CHROMEBOOK',
211 'CHROMEBOX',
212 'REFERENCE',
213 ]
214 except (errors.ExternalError, errors.SshConnectionError):
215 return False
216
217
218def is_good_dut(host):
219 if not is_dut(host):
220 return False
221
222 # Sometimes python is broken after 'cros flash'.
223 try:
224 util.ssh_cmd(host, 'python', '-c', '1')
225 return True
226 except (subprocess.CalledProcessError, errors.SshConnectionError):
227 return False
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800228
229
230def query_dut_board(host):
231 """Query board name of a given DUT"""
232 return query_dut_lsb_release(host).get('CHROMEOS_RELEASE_BOARD')
233
234
235def query_dut_short_version(host):
Kuang-che Wuacb6efd2018-04-25 18:52:58 +0800236 """Query short version of a given DUT.
237
238 This function may return version of local build, which
239 is_cros_short_version() is false.
240 """
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800241 return query_dut_lsb_release(host).get('CHROMEOS_RELEASE_VERSION')
242
243
Zheng-Jie Chang26084542020-04-29 06:34:33 +0800244def query_dut_prebuilt_version(host):
245 """Return a snapshot version or short version of a given DUT.
246
247 Args:
248 host: dut host
249
250 Returns:
251 Snapshot version or short version.
252 """
253 lsb_release = query_dut_lsb_release(host)
254 release_version = lsb_release.get('CHROMEOS_RELEASE_VERSION')
255 builder_path = lsb_release.get('CHROMEOS_RELEASE_BUILDER_PATH')
256 match = re.match(r'\S+-(?:snapshot|postsubmit)/(R\d+-\d+\.\d+\.\d+-\d+)-\d+',
257 builder_path)
258 if match:
259 return match.group(1)
260 return release_version
261
262
Zheng-Jie Chang181be6f2020-03-17 16:16:08 +0800263def query_dut_is_by_official_builder(host):
264 """Query if given DUT is build by buildbucket builder"""
265 return query_dut_lsb_release(host).get('CHROMEOS_RELEASE_BUILD_TYPE',
266 '').startswith('Continuous Builder')
267
268
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800269def query_dut_boot_id(host, connect_timeout=None):
Kuang-che Wubfc4a642018-04-19 11:54:08 +0800270 """Query boot id.
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800271
272 Args:
273 host: DUT address
274 connect_timeout: connection timeout
275
276 Returns:
277 boot uuid
278 """
Kuang-che Wu44278142019-03-04 11:33:57 +0800279 return util.ssh_cmd(
280 host,
281 'cat',
282 '/proc/sys/kernel/random/boot_id',
283 connect_timeout=connect_timeout).strip()
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800284
285
286def reboot(host):
287 """Reboot a DUT and verify"""
288 logger.debug('reboot %s', host)
289 boot_id = query_dut_boot_id(host)
290
Kuang-che Wu44278142019-03-04 11:33:57 +0800291 try:
292 util.ssh_cmd(host, 'reboot')
Kuang-che Wu5f662e82019-03-05 11:49:56 +0800293 except errors.SshConnectionError:
294 # Depends on timing, ssh may return failure due to broken pipe, which is
295 # working as intended. Ignore such kind of errors.
Kuang-che Wu44278142019-03-04 11:33:57 +0800296 pass
Kuang-che Wu708310b2018-03-28 17:24:34 +0800297 wait_reboot_done(host, boot_id)
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800298
Kuang-che Wu708310b2018-03-28 17:24:34 +0800299
300def wait_reboot_done(host, boot_id):
Kuang-che Wu4fe945b2018-03-31 16:46:38 +0800301 # For dev-mode test image, the reboot time is roughly at least 16 seconds
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800302 # (dev screen short delay) or more (long delay).
303 time.sleep(15)
304 for _ in range(100):
305 try:
306 # During boot, DUT does not response and thus ssh may hang a while. So
307 # set a connect timeout. 3 seconds are enough and 2 are not. It's okay to
308 # set tight limit because it's inside retry loop.
309 assert boot_id != query_dut_boot_id(host, connect_timeout=3)
310 return
Kuang-che Wu5f662e82019-03-05 11:49:56 +0800311 except errors.SshConnectionError:
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800312 logger.debug('reboot not ready? sleep wait 1 sec')
313 time.sleep(1)
314
Kuang-che Wue121fae2018-11-09 16:18:39 +0800315 raise errors.ExternalError('reboot failed?')
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800316
317
Kuang-che Wu80bf6a52019-05-31 12:48:06 +0800318def gs_release_boardpath(board):
319 """Normalizes board name for gs://chromeos-releases/
320
321 This follows behavior of PushImage() in chromite/scripts/pushimage.py
322 Note, only gs://chromeos-releases/ needs normalization,
323 gs://chromeos-image-archive does not.
324
325 Args:
326 board: ChromeOS board name
327
328 Returns:
329 normalized board name
330 """
331 return board.replace('_', '-')
332
333
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800334def gsutil(*args, **kwargs):
Kuang-che Wubfc4a642018-04-19 11:54:08 +0800335 """gsutil command line wrapper.
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800336
337 Args:
338 args: command line arguments passed to gsutil
339 kwargs:
340 ignore_errors: if true, return '' for failures, for example 'gsutil ls'
341 but the path not found.
342
343 Returns:
344 stdout of gsutil
345
346 Raises:
Zheng-Jie Changb8697042019-10-29 16:03:26 +0800347 errors.ExternalError: gsutil failed to run
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800348 subprocess.CalledProcessError: command failed
349 """
350 stderr_lines = []
351 try:
352 return util.check_output(
353 gsutil_bin, *args, stderr_callback=stderr_lines.append)
354 except subprocess.CalledProcessError as e:
355 stderr = ''.join(stderr_lines)
356 if re.search(r'ServiceException:.* does not have .*access', stderr):
Kuang-che Wue121fae2018-11-09 16:18:39 +0800357 raise errors.ExternalError(
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800358 'gsutil failed due to permission. ' +
359 'Run "%s config" and follow its instruction. ' % gsutil_bin +
360 'Fill any string if it asks for project-id')
361 if kwargs.get('ignore_errors'):
362 return ''
363 raise
364 except OSError as e:
365 if e.errno == errno.ENOENT:
Kuang-che Wue121fae2018-11-09 16:18:39 +0800366 raise errors.ExternalError(
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800367 'Unable to run %s. gsutil is not installed or not in PATH?' %
368 gsutil_bin)
369 raise
370
371
372def gsutil_ls(*args, **kwargs):
Kuang-che Wubfc4a642018-04-19 11:54:08 +0800373 """gsutil ls.
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800374
375 Args:
376 args: arguments passed to 'gsutil ls'
377 kwargs: extra parameters, where
Kuang-che Wu4fe945b2018-03-31 16:46:38 +0800378 ignore_errors: if true, return empty list instead of raising
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800379 exception, ex. path not found.
380
381 Returns:
382 list of 'gsutil ls' result. One element for one line of gsutil output.
383
384 Raises:
385 subprocess.CalledProcessError: gsutil failed, usually means path not found
386 """
387 return gsutil('ls', *args, **kwargs).splitlines()
388
389
Zheng-Jie Chang127c3302019-09-10 17:17:04 +0800390def gsutil_stat_update_time(*args, **kwargs):
391 """Returns the last modified time of a file or multiple files.
392
393 Args:
394 args: arguments passed to 'gsutil stat'.
395 kwargs: extra parameters for gsutil.
396
397 Returns:
398 A integer indicates the last modified timestamp.
399
400 Raises:
401 subprocess.CalledProcessError: gsutil failed, usually means path not found
402 errors.ExternalError: update time is not found
403 """
404 result = -1
405 # Currently we believe stat always returns a UTC time, and strptime also
406 # parses a UTC time by default.
407 time_format = '%a, %d %b %Y %H:%M:%S GMT'
408
409 for line in gsutil('stat', *args, **kwargs).splitlines():
410 if ':' not in line:
411 continue
Kuang-che Wuc89f2a22019-11-26 15:30:50 +0800412 key, value = line.split(':', 1)
413 key, value = key.strip(), value.strip()
Zheng-Jie Chang127c3302019-09-10 17:17:04 +0800414 if key != 'Update time':
415 continue
416 dt = datetime.datetime.strptime(value, time_format)
Kuang-che Wu72b5a572019-10-29 20:37:57 +0800417 unixtime = int(calendar.timegm(dt.utctimetuple()))
Zheng-Jie Chang127c3302019-09-10 17:17:04 +0800418 result = max(result, unixtime)
419
420 if result == -1:
421 raise errors.ExternalError("didn't find update time")
422 return result
423
424
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800425def query_milestone_by_version(board, short_version):
Kuang-che Wubfc4a642018-04-19 11:54:08 +0800426 """Query milestone by ChromeOS version number.
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800427
428 Args:
429 board: ChromeOS board name
430 short_version: ChromeOS version number in short format, ex. 9300.0.0
431
432 Returns:
433 ChromeOS milestone number (string). For example, '58' for '9300.0.0'.
434 None if failed.
435 """
436 path = gs_archive_path.format(board=board) + '/R*-' + short_version
437 for line in gsutil_ls('-d', path, ignore_errors=True):
438 m = re.search(r'/R(\d+)-', line)
439 if not m:
440 continue
441 return m.group(1)
442
443 for channel in ['canary', 'dev', 'beta', 'stable']:
444 path = gs_release_path.format(
Kuang-che Wu80bf6a52019-05-31 12:48:06 +0800445 channel=channel,
446 boardpath=gs_release_boardpath(board),
447 short_version=short_version)
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800448 for line in gsutil_ls(path, ignore_errors=True):
449 m = re.search(r'\bR(\d+)-' + short_version, line)
450 if not m:
451 continue
452 return m.group(1)
453
Zheng-Jie Chang4e25a8d2020-01-08 16:00:13 +0800454 logger.debug('unable to query milestone of %s for %s', short_version, board)
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800455 return None
456
457
Kuang-che Wu80bf6a52019-05-31 12:48:06 +0800458def list_board_names(chromeos_root):
459 """List board names.
460
461 Args:
462 chromeos_root: chromeos tree root
463
464 Returns:
465 list of board names
466 """
467 # Following logic is simplified from chromite/lib/portage_util.py
468 cros_list_overlays = os.path.join(chromeos_root,
469 'chromite/bin/cros_list_overlays')
470 overlays = util.check_output(cros_list_overlays).splitlines()
471 result = set()
472 for overlay in overlays:
473 conf_file = os.path.join(overlay, 'metadata', 'layout.conf')
474 name = None
475 if os.path.exists(conf_file):
476 for line in open(conf_file):
477 m = re.match(r'^repo-name\s*=\s*(\S+)\s*$', line)
478 if m:
479 name = m.group(1)
480 break
481
482 if not name:
483 name_file = os.path.join(overlay, 'profiles', 'repo_name')
484 if os.path.exists(name_file):
Kuang-che Wua5723492019-11-25 20:59:34 +0800485 with open(name_file) as f:
486 name = f.read().strip()
Kuang-che Wu80bf6a52019-05-31 12:48:06 +0800487
488 if name:
489 name = re.sub(r'-private$', '', name)
490 result.add(name)
491
492 return list(result)
493
494
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800495def recognize_version(board, version):
Kuang-che Wubfc4a642018-04-19 11:54:08 +0800496 """Recognize ChromeOS version.
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800497
498 Args:
499 board: ChromeOS board name
500 version: ChromeOS version number in short or full format
501
502 Returns:
503 (milestone, version in short format)
504 """
505 if is_cros_short_version(version):
506 milestone = query_milestone_by_version(board, version)
507 short_version = version
508 else:
509 milestone, short_version = version_split(version)
510 return milestone, short_version
511
512
Zheng-Jie Chang127c3302019-09-10 17:17:04 +0800513def extract_major_version(version):
514 """Converts a version to its major version.
515
516 Args:
Kuang-che Wu9501f342019-11-15 17:15:21 +0800517 version: ChromeOS version number or snapshot version
Zheng-Jie Chang127c3302019-09-10 17:17:04 +0800518
519 Returns:
520 major version number in string format
521 """
522 version = version_to_short(version)
523 m = re.match(r'^(\d+)\.\d+\.\d+$', version)
524 return m.group(1)
525
526
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800527def version_to_short(version):
Kuang-che Wubfc4a642018-04-19 11:54:08 +0800528 """Convert ChromeOS version number to short format.
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800529
530 Args:
531 version: ChromeOS version number in short or full format
532
533 Returns:
534 version number in short format
535 """
536 if is_cros_short_version(version):
537 return version
538 _, short_version = version_split(version)
539 return short_version
540
541
542def version_to_full(board, version):
Kuang-che Wubfc4a642018-04-19 11:54:08 +0800543 """Convert ChromeOS version number to full format.
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800544
545 Args:
546 board: ChromeOS board name
547 version: ChromeOS version number in short or full format
548
549 Returns:
550 version number in full format
551 """
Zheng-Jie Changc47af3a2019-11-11 17:28:58 +0800552 if is_cros_snapshot_version(version):
553 milestone, short_version, _ = snapshot_version_split(version)
554 return make_cros_full_version(milestone, short_version)
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800555 if is_cros_full_version(version):
556 return version
557 milestone = query_milestone_by_version(board, version)
Kuang-che Wu0205f052019-05-23 12:48:37 +0800558 assert milestone, 'incorrect board=%s or version=%s ?' % (board, version)
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800559 return make_cros_full_version(milestone, version)
560
561
Zheng-Jie Chang127c3302019-09-10 17:17:04 +0800562def list_snapshots_from_image_archive(board, major_version):
Kuang-che Wu9501f342019-11-15 17:15:21 +0800563 """List ChromeOS snapshot image available from gs://chromeos-image-archive.
Zheng-Jie Chang127c3302019-09-10 17:17:04 +0800564
565 Args:
566 board: ChromeOS board
567 major_version: ChromeOS major version
568
569 Returns:
570 list of (version, gs_path):
571 version: Chrome OS snapshot version
572 gs_path: gs path of test image
573 """
574
Zheng-Jie Chang43a08412019-12-05 17:05:45 +0800575 def extract_snapshot_id(result):
576 m = re.match(r'^R\d+-\d+\.\d+\.\d+-(\d+)', result[0])
577 assert m
578 return int(m.group(1))
579
Zheng-Jie Changa331b872020-01-06 17:09:32 +0800580 short_version = '%s.0.0' % major_version
581 milestone = query_milestone_by_version(board, short_version)
582 if not milestone:
583 milestone = '*'
584
Zheng-Jie Chang54020832020-04-21 15:52:48 +0800585 path = ('gs://chromeos-image-archive/{board}-snapshot/R{milestone}-'
Zheng-Jie Changa331b872020-01-06 17:09:32 +0800586 '{short_version}-*/image.zip')
Zheng-Jie Chang127c3302019-09-10 17:17:04 +0800587 result = []
588 output = gsutil_ls(
Zheng-Jie Changa331b872020-01-06 17:09:32 +0800589 path.format(
590 board=board, milestone=milestone, short_version=short_version),
Zheng-Jie Chang127c3302019-09-10 17:17:04 +0800591 ignore_errors=True)
592
Zheng-Jie Changa331b872020-01-06 17:09:32 +0800593 for gs_path in sorted(output):
594 m = re.match(r'^gs:\S+(R\d+-\d+\.\d+\.\d+-\d+)', gs_path)
Zheng-Jie Chang127c3302019-09-10 17:17:04 +0800595 if m:
596 snapshot_version = m.group(1)
Zheng-Jie Chang43a08412019-12-05 17:05:45 +0800597 # we should skip if there is duplicate snapshot
598 if result and result[-1][0] == snapshot_version:
599 continue
Zheng-Jie Chang1ae64c32020-03-10 14:08:28 +0800600
601 # b/151054108: snapshot version in [29288, 29439] is broken
602 _, _, snapshot_id = snapshot_version_split(snapshot_version)
603 if 29288 <= int(snapshot_id) <= 29439:
604 continue
605
Zheng-Jie Chang127c3302019-09-10 17:17:04 +0800606 result.append((snapshot_version, gs_path))
Zheng-Jie Chang43a08412019-12-05 17:05:45 +0800607
608 # sort by its snapshot_id
609 result.sort(key=extract_snapshot_id)
Zheng-Jie Chang127c3302019-09-10 17:17:04 +0800610 return result
611
612
Kuang-che Wu575dc442019-03-05 10:30:55 +0800613def list_prebuilt_from_image_archive(board):
614 """Lists ChromeOS prebuilt image available from gs://chromeos-image-archive.
615
616 gs://chromeos-image-archive contains only recent builds (in two years).
617 We prefer this function to list_prebuilt_from_chromeos_releases() because
618 - this is what "cros flash" supports directly.
619 - the paths have milestone information, so we don't need to do slow query
620 by ourselves.
621
622 Args:
623 board: ChromeOS board name
624
625 Returns:
626 list of (version, gs_path):
627 version: Chrome OS version in full format
628 gs_path: gs path of test image
629 """
630 result = []
631 for line in gsutil_ls(gs_archive_path.format(board=board)):
632 m = re.match(r'^gs:\S+(R\d+-\d+\.\d+\.\d+)', line)
633 if m:
634 full_version = m.group(1)
635 test_image = 'chromiumos_test_image.tar.xz'
636 assert line.endswith('/')
637 gs_path = line + test_image
638 result.append((full_version, gs_path))
639 return result
640
641
642def list_prebuilt_from_chromeos_releases(board):
643 """Lists ChromeOS versions available from gs://chromeos-releases.
644
645 gs://chromeos-releases contains more builds. However, 'cros flash' doesn't
646 support it.
647
648 Args:
649 board: ChromeOS board name
650
651 Returns:
652 list of (version, gs_path):
653 version: Chrome OS version in short format
654 gs_path: gs path of test image (with wildcard)
655 """
656 result = []
657 for line in gsutil_ls(
Kuang-che Wu80bf6a52019-05-31 12:48:06 +0800658 gs_release_path.format(
659 channel='*', boardpath=gs_release_boardpath(board), short_version=''),
Kuang-che Wu575dc442019-03-05 10:30:55 +0800660 ignore_errors=True):
661 m = re.match(r'gs:\S+/(\d+\.\d+\.\d+)/$', line)
662 if m:
663 short_version = m.group(1)
664 test_image = 'ChromeOS-test-R*-{short_version}-{board}.tar.xz'.format(
665 short_version=short_version, board=board)
666 gs_path = line + test_image
667 result.append((short_version, gs_path))
668 return result
669
670
Kuang-che Wue1808402020-01-06 20:27:45 +0800671def has_test_image(board, version):
672 if is_cros_snapshot_version(version):
673 return bool(query_snapshot_buildbucket_id(board, version))
674
675 full_version = version_to_full(board, version)
676 short_version = version_to_short(version)
677 paths = [
678 gs_archive_path.format(board=board) +
679 '/%s/chromiumos_test_image.tar.xz' % full_version,
680 gs_release_path.format(
681 channel='*',
682 boardpath=gs_release_boardpath(board),
683 short_version=short_version),
684 ]
685
686 for path in paths:
687 if gsutil_ls(path, ignore_errors=True):
688 return True
689 return False
690
691
Kuang-che Wu575dc442019-03-05 10:30:55 +0800692def list_chromeos_prebuilt_versions(board,
693 old,
694 new,
695 only_good_build=True,
Zheng-Jie Chang127c3302019-09-10 17:17:04 +0800696 include_older_build=True,
697 use_snapshot=False):
Kuang-che Wu575dc442019-03-05 10:30:55 +0800698 """Lists ChromeOS version numbers with prebuilt between given range
699
700 Args:
701 board: ChromeOS board name
702 old: start version (inclusive)
703 new: end version (inclusive)
704 only_good_build: only if test image is available
705 include_older_build: include prebuilt in gs://chromeos-releases
Zheng-Jie Chang127c3302019-09-10 17:17:04 +0800706 use_snapshot: return snapshot versions if found
Kuang-che Wu575dc442019-03-05 10:30:55 +0800707
708 Returns:
709 list of sorted version numbers (in full format) between [old, new] range
710 (inclusive).
711 """
Zheng-Jie Changaea4fba2020-02-17 17:12:09 +0800712 old_short = version_to_short(old)
713 new_short = version_to_short(new)
Kuang-che Wu575dc442019-03-05 10:30:55 +0800714
Zheng-Jie Chang127c3302019-09-10 17:17:04 +0800715 rev_map = {
716 } # dict: short version -> list of (short/full or snapshot version, gs path)
Kuang-che Wu575dc442019-03-05 10:30:55 +0800717 for full_version, gs_path in list_prebuilt_from_image_archive(board):
718 short_version = version_to_short(full_version)
Zheng-Jie Chang127c3302019-09-10 17:17:04 +0800719 rev_map[short_version] = [(full_version, gs_path)]
Kuang-che Wu575dc442019-03-05 10:30:55 +0800720
Zheng-Jie Changaea4fba2020-02-17 17:12:09 +0800721 if include_older_build and old_short not in rev_map:
Kuang-che Wu575dc442019-03-05 10:30:55 +0800722 for short_version, gs_path in list_prebuilt_from_chromeos_releases(board):
723 if short_version not in rev_map:
Zheng-Jie Chang127c3302019-09-10 17:17:04 +0800724 rev_map[short_version] = [(short_version, gs_path)]
725
726 if use_snapshot:
727 for major_version in range(
728 int(extract_major_version(old)),
729 int(extract_major_version(new)) + 1):
730 short_version = '%s.0.0' % major_version
Zheng-Jie Changc47af3a2019-11-11 17:28:58 +0800731 next_short_version = '%s.0.0' % (major_version + 1)
Zheng-Jie Changb8697042019-10-29 16:03:26 +0800732 # If current version is smaller than cutover, ignore it as it might not
733 # contain enough information for continuing android and chrome bisection.
734 if not util.is_version_lesseq(snapshot_cutover_version, short_version):
735 continue
Zheng-Jie Changc47af3a2019-11-11 17:28:58 +0800736
737 # Given the fact that snapshots are images between two release versions.
738 # Adding snapshots of 12345.0.0 should be treated as adding commits
739 # between [12345.0.0, 12346.0.0).
740 # So in the following lines we check two facts:
741 # 1) If 12346.0.0(next_short_version) is a version between old and new
Zheng-Jie Changaea4fba2020-02-17 17:12:09 +0800742 if not util.is_direct_relative_version(next_short_version, old_short):
Zheng-Jie Changc47af3a2019-11-11 17:28:58 +0800743 continue
Zheng-Jie Changaea4fba2020-02-17 17:12:09 +0800744 if not util.is_direct_relative_version(next_short_version, new_short):
Zheng-Jie Changc47af3a2019-11-11 17:28:58 +0800745 continue
746 # 2) If 12345.0.0(short_version) is a version between old and new
Zheng-Jie Changaea4fba2020-02-17 17:12:09 +0800747 if not util.is_direct_relative_version(short_version, old_short):
Zheng-Jie Chang127c3302019-09-10 17:17:04 +0800748 continue
Zheng-Jie Changaea4fba2020-02-17 17:12:09 +0800749 if not util.is_direct_relative_version(short_version, new_short):
Zheng-Jie Chang127c3302019-09-10 17:17:04 +0800750 continue
Zheng-Jie Changc47af3a2019-11-11 17:28:58 +0800751
Zheng-Jie Chang127c3302019-09-10 17:17:04 +0800752 snapshots = list_snapshots_from_image_archive(board, str(major_version))
753 if snapshots:
754 # if snapshots found, we can append them after the release version,
755 # so the prebuilt image list of this version will be
756 # release_image, snapshot1, snapshot2,...
Zheng-Jie Changb8697042019-10-29 16:03:26 +0800757 if short_version not in rev_map:
758 rev_map[short_version] = []
Zheng-Jie Chang127c3302019-09-10 17:17:04 +0800759 rev_map[short_version] += snapshots
Kuang-che Wu575dc442019-03-05 10:30:55 +0800760
761 result = []
762 for rev in sorted(rev_map, key=util.version_key_func):
Zheng-Jie Changaea4fba2020-02-17 17:12:09 +0800763 if not util.is_direct_relative_version(new_short, rev):
Kuang-che Wu575dc442019-03-05 10:30:55 +0800764 continue
Zheng-Jie Changaea4fba2020-02-17 17:12:09 +0800765 if not util.is_version_lesseq(old_short, rev):
Kuang-che Wu575dc442019-03-05 10:30:55 +0800766 continue
Zheng-Jie Changaea4fba2020-02-17 17:12:09 +0800767 if not util.is_version_lesseq(rev, new_short):
Kuang-che Wu575dc442019-03-05 10:30:55 +0800768 continue
769
Zheng-Jie Chang127c3302019-09-10 17:17:04 +0800770 for version, gs_path in rev_map[rev]:
Kuang-che Wu575dc442019-03-05 10:30:55 +0800771
Zheng-Jie Chang127c3302019-09-10 17:17:04 +0800772 # version_to_full() and gsutil_ls() may take long time if versions are a
773 # lot. This is acceptable because we usually bisect only short range.
Kuang-che Wu575dc442019-03-05 10:30:55 +0800774
Zheng-Jie Changa331b872020-01-06 17:09:32 +0800775 if only_good_build and not is_cros_snapshot_version(version):
Zheng-Jie Chang127c3302019-09-10 17:17:04 +0800776 gs_result = gsutil_ls(gs_path, ignore_errors=True)
777 if not gs_result:
778 logger.warning('%s is not a good build, ignore', version)
779 continue
780 assert len(gs_result) == 1
781 m = re.search(r'(R\d+-\d+\.\d+\.\d+)', gs_result[0])
782 if not m:
783 logger.warning('format of image path is unexpected: %s', gs_result[0])
784 continue
Zheng-Jie Changa331b872020-01-06 17:09:32 +0800785 version = m.group(1)
Zheng-Jie Chang127c3302019-09-10 17:17:04 +0800786 elif is_cros_short_version(version):
787 version = version_to_full(board, version)
Kuang-che Wu575dc442019-03-05 10:30:55 +0800788
Zheng-Jie Changaea4fba2020-02-17 17:12:09 +0800789 if is_cros_version_lesseq(old, version) and is_cros_version_lesseq(
790 version, new):
791 result.append(version)
Kuang-che Wu575dc442019-03-05 10:30:55 +0800792
793 return result
794
795
Zheng-Jie Chang127c3302019-09-10 17:17:04 +0800796def prepare_snapshot_image(chromeos_root, board, snapshot_version):
797 """Prepare chromeos snapshot image.
798
799 Args:
800 chromeos_root: chromeos tree root
801 board: ChromeOS board name
802 snapshot_version: ChromeOS snapshot version number
803
804 Returns:
805 local file path of test image relative to chromeos_root
806 """
807 assert is_cros_snapshot_version(snapshot_version)
808 milestone, short_version, snapshot_id = snapshot_version_split(
809 snapshot_version)
810 full_version = make_cros_full_version(milestone, short_version)
811 tmp_dir = os.path.join(
812 chromeos_root, 'tmp',
813 'ChromeOS-test-%s-%s-%s' % (full_version, board, snapshot_id))
814 if not os.path.exists(tmp_dir):
815 os.makedirs(tmp_dir)
816
Zheng-Jie Chang54020832020-04-21 15:52:48 +0800817 gs_path = ('gs://chromeos-image-archive/{board}-snapshot/' +
Zheng-Jie Chang127c3302019-09-10 17:17:04 +0800818 '{snapshot_version}-*/image.zip')
819 gs_path = gs_path.format(board=board, snapshot_version=snapshot_version)
820
Zheng-Jie Changc47af3a2019-11-11 17:28:58 +0800821 full_path = os.path.join(tmp_dir, test_image_filename)
822 rel_path = os.path.relpath(full_path, chromeos_root)
823 if os.path.exists(full_path):
824 return rel_path
825
Zheng-Jie Chang127c3302019-09-10 17:17:04 +0800826 files = gsutil_ls(gs_path, ignore_errors=True)
Zheng-Jie Changeb7308f2019-12-05 14:25:05 +0800827 if len(files) >= 1:
Zheng-Jie Chang127c3302019-09-10 17:17:04 +0800828 gs_path = files[0]
829 gsutil('cp', gs_path, tmp_dir)
Zheng-Jie Chang127c3302019-09-10 17:17:04 +0800830 util.check_call(
831 'unzip', '-j', 'image.zip', test_image_filename, cwd=tmp_dir)
832 os.remove(os.path.join(tmp_dir, 'image.zip'))
Zheng-Jie Changc47af3a2019-11-11 17:28:58 +0800833 return rel_path
Zheng-Jie Chang127c3302019-09-10 17:17:04 +0800834
Zheng-Jie Changc47af3a2019-11-11 17:28:58 +0800835 assert False
Kuang-che Wua7ddf9b2019-11-25 18:59:57 +0800836 return None
Zheng-Jie Chang127c3302019-09-10 17:17:04 +0800837
838
Kuang-che Wu28980b22019-07-31 19:51:45 +0800839def prepare_prebuilt_image(chromeos_root, board, version):
Kuang-che Wubfc4a642018-04-19 11:54:08 +0800840 """Prepare chromeos prebuilt image.
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800841
Kuang-che Wubfc4a642018-04-19 11:54:08 +0800842 It searches for xbuddy image which "cros flash" can use, or fetch image to
843 local disk.
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800844
845 Args:
Kuang-che Wu28980b22019-07-31 19:51:45 +0800846 chromeos_root: chromeos tree root
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800847 board: ChromeOS board name
Kuang-che Wubfc4a642018-04-19 11:54:08 +0800848 version: ChromeOS version number in short or full format
849
850 Returns:
Kuang-che Wu28980b22019-07-31 19:51:45 +0800851 xbuddy path or file path (relative to chromeos_root)
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800852 """
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800853 assert is_cros_version(version)
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800854 full_version = version_to_full(board, version)
855 short_version = version_to_short(full_version)
856
857 image_path = None
858 gs_path = gs_archive_path.format(board=board) + '/' + full_version
859 if gsutil_ls('-d', gs_path, ignore_errors=True):
860 image_path = 'xbuddy://remote/{board}/{full_version}/test'.format(
861 board=board, full_version=full_version)
862 else:
Kuang-che Wu28980b22019-07-31 19:51:45 +0800863 tmp_dir = os.path.join(chromeos_root, 'tmp',
864 'ChromeOS-test-%s-%s' % (full_version, board))
Zheng-Jie Changc47af3a2019-11-11 17:28:58 +0800865 full_path = os.path.join(tmp_dir, test_image_filename)
866 rel_path = os.path.relpath(full_path, chromeos_root)
867 if os.path.exists(full_path):
868 return rel_path
869
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800870 if not os.path.exists(tmp_dir):
871 os.makedirs(tmp_dir)
872 # gs://chromeos-releases may have more old images than
Kuang-che Wu4fe945b2018-03-31 16:46:38 +0800873 # gs://chromeos-image-archive, but 'cros flash' doesn't support it. We have
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800874 # to fetch the image by ourselves
875 for channel in ['canary', 'dev', 'beta', 'stable']:
876 fn = 'ChromeOS-test-{full_version}-{board}.tar.xz'.format(
877 full_version=full_version, board=board)
878 gs_path = gs_release_path.format(
Kuang-che Wu80bf6a52019-05-31 12:48:06 +0800879 channel=channel,
880 boardpath=gs_release_boardpath(board),
881 short_version=short_version)
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800882 gs_path += '/' + fn
883 if gsutil_ls(gs_path, ignore_errors=True):
884 # TODO(kcwu): delete tmp
885 gsutil('cp', gs_path, tmp_dir)
886 util.check_call('tar', 'Jxvf', fn, cwd=tmp_dir)
Zheng-Jie Changc47af3a2019-11-11 17:28:58 +0800887 image_path = os.path.relpath(full_path, chromeos_root)
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800888 break
889
890 assert image_path
Kuang-che Wubfc4a642018-04-19 11:54:08 +0800891 return image_path
892
893
894def cros_flash(chromeos_root,
895 host,
896 board,
897 image_path,
898 version=None,
899 clobber_stateful=False,
Kuang-che Wu155fb6e2018-11-29 16:00:41 +0800900 disable_rootfs_verification=True):
Kuang-che Wubfc4a642018-04-19 11:54:08 +0800901 """Flash a DUT with given ChromeOS image.
902
903 This is implemented by 'cros flash' command line.
904
905 Args:
906 chromeos_root: use 'cros flash' of which chromeos tree
907 host: DUT address
908 board: ChromeOS board name
Kuang-che Wu28980b22019-07-31 19:51:45 +0800909 image_path: chromeos image xbuddy path or file path. For relative
910 path, it should be relative to chromeos_root.
Kuang-che Wubfc4a642018-04-19 11:54:08 +0800911 version: ChromeOS version in short or full format
912 clobber_stateful: Clobber stateful partition when performing update
913 disable_rootfs_verification: Disable rootfs verification after update
914 is completed
Kuang-che Wu414d67f2019-05-28 11:28:57 +0800915
916 Raises:
917 errors.ExternalError: cros flash failed
Kuang-che Wubfc4a642018-04-19 11:54:08 +0800918 """
919 logger.info('cros_flash %s %s %s %s', host, board, version, image_path)
920
921 # Reboot is necessary because sometimes previous 'cros flash' failed and
922 # entered a bad state.
923 reboot(host)
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800924
Kuang-che Wu28980b22019-07-31 19:51:45 +0800925 # Handle relative path.
926 if '://' not in image_path and not os.path.isabs(image_path):
927 assert os.path.exists(os.path.join(chromeos_root, image_path))
928 image_path = os.path.join(chromeos_root_inside_chroot, image_path)
929
Kuang-che Wuf3d03ca2019-03-11 17:31:40 +0800930 args = [
931 '--debug', '--no-ping', '--send-payload-in-parallel', host, image_path
932 ]
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800933 if clobber_stateful:
934 args.append('--clobber-stateful')
935 if disable_rootfs_verification:
936 args.append('--disable-rootfs-verification')
937
Kuang-che Wu414d67f2019-05-28 11:28:57 +0800938 try:
939 cros_sdk(chromeos_root, 'cros', 'flash', *args)
940 except subprocess.CalledProcessError:
941 raise errors.ExternalError('cros flash failed')
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800942
Kuang-che Wubfc4a642018-04-19 11:54:08 +0800943 if version:
944 # In the past, cros flash may fail with returncode=0
945 # So let's have an extra check.
Zheng-Jie Chang127c3302019-09-10 17:17:04 +0800946 if is_cros_snapshot_version(version):
947 builder_path = query_dut_lsb_release(host).get(
948 'CHROMEOS_RELEASE_BUILDER_PATH', '')
Zheng-Jie Chang54020832020-04-21 15:52:48 +0800949 expect_prefix = '%s-snapshot/%s-' % (board, version)
Zheng-Jie Chang127c3302019-09-10 17:17:04 +0800950 if not builder_path.startswith(expect_prefix):
951 raise errors.ExternalError(
952 'although cros flash succeeded, the OS builder path is '
953 'unexpected: actual=%s expect=%s' % (builder_path, expect_prefix))
954 else:
955 expect_version = version_to_short(version)
956 dut_version = query_dut_short_version(host)
957 if dut_version != expect_version:
958 raise errors.ExternalError(
959 'although cros flash succeeded, the OS version is unexpected: '
960 'actual=%s expect=%s' % (dut_version, expect_version))
Kuang-che Wu414d67f2019-05-28 11:28:57 +0800961
Kuang-che Wu4a81ea72019-10-05 15:35:17 +0800962 # "cros flash" may terminate successfully but the DUT starts self-repairing
Kuang-che Wu414d67f2019-05-28 11:28:57 +0800963 # (b/130786578), so it's necessary to do sanity check.
964 if not is_good_dut(host):
965 raise errors.ExternalError(
966 'although cros flash succeeded, the DUT is in bad state')
967
968
969def cros_flash_with_retry(chromeos_root,
970 host,
971 board,
972 image_path,
973 version=None,
974 clobber_stateful=False,
975 disable_rootfs_verification=True,
976 repair_callback=None):
977 # 'cros flash' is not 100% reliable, retry if necessary.
978 for attempt in range(2):
979 if attempt > 0:
980 logger.info('will retry 60 seconds later')
981 time.sleep(60)
982
983 try:
984 cros_flash(
985 chromeos_root,
986 host,
987 board,
988 image_path,
989 version=version,
990 clobber_stateful=clobber_stateful,
991 disable_rootfs_verification=disable_rootfs_verification)
992 return True
993 except errors.ExternalError:
994 logger.exception('cros flash failed')
995 if repair_callback and not repair_callback(host):
996 logger.warning('not repaired, assume it is harmless')
997 continue
998 return False
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800999
1000
1001def version_info(board, version):
1002 """Query subcomponents version info of given version of ChromeOS
1003
1004 Args:
1005 board: ChromeOS board name
1006 version: ChromeOS version number in short or full format
1007
1008 Returns:
1009 dict of component and version info, including (if available):
1010 cros_short_version: ChromeOS version
1011 cros_full_version: ChromeOS version
1012 milestone: milestone of ChromeOS
1013 cr_version: Chrome version
Kuang-che Wu708310b2018-03-28 17:24:34 +08001014 android_build_id: Android build id
Kuang-che Wu2ea804f2017-11-28 17:11:41 +08001015 android_branch: Android branch, in format like 'git_nyc-mr1-arc'
1016 """
Zheng-Jie Chang127c3302019-09-10 17:17:04 +08001017 if is_cros_snapshot_version(version):
Zheng-Jie Chang2b6d1472019-11-13 12:40:17 +08001018 api = buildbucket_util.BuildbucketApi()
Zheng-Jie Changb8697042019-10-29 16:03:26 +08001019 milestone, short_version, _ = snapshot_version_split(version)
1020 buildbucket_id = query_snapshot_buildbucket_id(board, version)
Zheng-Jie Chang597c38b2020-03-12 22:24:10 +08001021 data = api.get_build(int(buildbucket_id)).output.properties
Zheng-Jie Chang4fabff62019-12-08 21:54:35 +08001022 target_versions = json_format.MessageToDict(data['target_versions'])
Zheng-Jie Chang127c3302019-09-10 17:17:04 +08001023 return {
Zheng-Jie Changb8697042019-10-29 16:03:26 +08001024 VERSION_KEY_MILESTONE: milestone,
1025 VERSION_KEY_CROS_FULL_VERSION: version,
1026 VERSION_KEY_CROS_SHORT_VERSION: short_version,
Zheng-Jie Chang2d1dd9b2019-12-07 23:30:20 +08001027 VERSION_KEY_CR_VERSION: target_versions.get('chromeVersion'),
1028 VERSION_KEY_ANDROID_BUILD_ID: target_versions.get('androidVersion'),
1029 VERSION_KEY_ANDROID_BRANCH: target_versions.get('androidBranchVersion'),
Zheng-Jie Chang127c3302019-09-10 17:17:04 +08001030 }
Kuang-che Wu2ea804f2017-11-28 17:11:41 +08001031 info = {}
1032 full_version = version_to_full(board, version)
1033
1034 # Some boards may have only partial-metadata.json but no metadata.json.
1035 # e.g. caroline R60-9462.0.0
1036 # Let's try both.
1037 metadata = None
1038 for metadata_filename in ['metadata.json', 'partial-metadata.json']:
Kuang-che Wu0768b972019-10-05 15:18:59 +08001039 path = gs_archive_path.format(
1040 board=board) + '/%s/%s' % (full_version, metadata_filename)
Kuang-che Wu2ea804f2017-11-28 17:11:41 +08001041 metadata = gsutil('cat', path, ignore_errors=True)
1042 if metadata:
1043 o = json.loads(metadata)
1044 v = o['version']
1045 board_metadata = o['board-metadata'][board]
1046 info.update({
1047 VERSION_KEY_CROS_SHORT_VERSION: v['platform'],
1048 VERSION_KEY_CROS_FULL_VERSION: v['full'],
1049 VERSION_KEY_MILESTONE: v['milestone'],
1050 VERSION_KEY_CR_VERSION: v['chrome'],
1051 })
1052
1053 if 'android' in v:
Kuang-che Wu708310b2018-03-28 17:24:34 +08001054 info[VERSION_KEY_ANDROID_BUILD_ID] = v['android']
Kuang-che Wu2ea804f2017-11-28 17:11:41 +08001055 if 'android-branch' in v: # this appears since R58-9317.0.0
1056 info[VERSION_KEY_ANDROID_BRANCH] = v['android-branch']
1057 elif 'android-container-branch' in board_metadata:
1058 info[VERSION_KEY_ANDROID_BRANCH] = v['android-container-branch']
1059 break
1060 else:
1061 logger.error('Failed to read metadata from gs://chromeos-image-archive')
1062 logger.error(
1063 'Note, so far no quick way to look up version info for too old builds')
1064
1065 return info
Kuang-che Wu848b1af2018-02-01 20:59:36 +08001066
1067
1068def query_chrome_version(board, version):
Kuang-che Wubfc4a642018-04-19 11:54:08 +08001069 """Queries chrome version of chromeos build.
Kuang-che Wu848b1af2018-02-01 20:59:36 +08001070
1071 Args:
1072 board: ChromeOS board name
1073 version: ChromeOS version number in short or full format
1074
1075 Returns:
1076 Chrome version number
1077 """
1078 info = version_info(board, version)
1079 return info['cr_version']
Kuang-che Wu708310b2018-03-28 17:24:34 +08001080
1081
1082def query_android_build_id(board, rev):
1083 info = version_info(board, rev)
1084 rev = info['android_build_id']
1085 return rev
1086
1087
1088def query_android_branch(board, rev):
1089 info = version_info(board, rev)
1090 rev = info['android_branch']
1091 return rev
Kuang-che Wubfc4a642018-04-19 11:54:08 +08001092
1093
Kuang-che Wu3eb6b502018-06-06 16:15:18 +08001094def guess_chrome_version(board, rev):
1095 """Guess chrome version number.
1096
1097 Args:
1098 board: chromeos board name
1099 rev: chrome or chromeos version
1100
1101 Returns:
1102 chrome version number
1103 """
1104 if is_cros_version(rev):
1105 assert board, 'need to specify BOARD for cros version'
1106 rev = query_chrome_version(board, rev)
1107 assert cr_util.is_chrome_version(rev)
1108
1109 return rev
1110
1111
Kuang-che Wubfc4a642018-04-19 11:54:08 +08001112def is_inside_chroot():
1113 """Returns True if we are inside chroot."""
1114 return os.path.exists('/etc/cros_chroot_version')
1115
1116
Kuang-che Wuad5ac7b2020-02-20 19:02:52 +08001117def convert_path_outside_chroot(chromeos_root, path):
1118 """Converts path in chroot to outside.
1119
1120 Args:
1121 chromeos_root: chromeos tree root
1122 path: path inside chroot; support starting with '~/'
1123
1124 Returns:
1125 The corresponding path outside chroot assuming the chroot is mounted
1126 """
1127 if path.startswith('~/'):
1128 path = path.replace('~', '/home/' + os.environ['USER'])
1129 assert '~' not in path, 'tilde (~) character is not fully supported'
1130
1131 assert os.path.isabs(path)
1132 assert path[0] == os.sep
1133 return os.path.join(chromeos_root, 'chroot', path[1:])
1134
1135
Kuang-che Wubfc4a642018-04-19 11:54:08 +08001136def cros_sdk(chromeos_root, *args, **kwargs):
1137 """Run commands inside chromeos chroot.
1138
1139 Args:
1140 chromeos_root: chromeos tree root
1141 *args: command to run
1142 **kwargs:
Kuang-che Wud4603d72018-11-29 17:51:21 +08001143 chrome_root: pass to cros_sdk; mount this path into the SDK chroot
Kuang-che Wubfc4a642018-04-19 11:54:08 +08001144 env: (dict) environment variables for the command
Kuang-che Wubcafc552019-08-15 15:27:02 +08001145 log_stdout: Whether write the stdout output of the child process to log.
Kuang-che Wubfc4a642018-04-19 11:54:08 +08001146 stdin: standard input file handle for the command
Kuang-che Wu9890ce82018-07-07 15:14:10 +08001147 stderr_callback: Callback function for stderr. Called once per line.
Kuang-che Wua9a20bb2019-09-05 22:24:04 +08001148 goma_dir: Goma installed directory to mount into the chroot
Kuang-che Wubfc4a642018-04-19 11:54:08 +08001149 """
1150 envs = []
1151 for k, v in kwargs.get('env', {}).items():
1152 assert re.match(r'^[A-Za-z_][A-Za-z0-9_]*$', k)
1153 envs.append('%s=%s' % (k, v))
1154
1155 # Use --no-ns-pid to prevent cros_sdk change our pgid, otherwise subsequent
1156 # commands would be considered as background process.
Kuang-che Wu399d4662019-06-06 15:23:37 +08001157 prefix = ['chromite/bin/cros_sdk', '--no-ns-pid']
Kuang-che Wud4603d72018-11-29 17:51:21 +08001158
1159 if kwargs.get('chrome_root'):
Kuang-che Wu399d4662019-06-06 15:23:37 +08001160 prefix += ['--chrome_root', kwargs['chrome_root']]
Kuang-che Wua9a20bb2019-09-05 22:24:04 +08001161 if kwargs.get('goma_dir'):
1162 prefix += ['--goma_dir', kwargs['goma_dir']]
Kuang-che Wud4603d72018-11-29 17:51:21 +08001163
Kuang-che Wu399d4662019-06-06 15:23:37 +08001164 prefix += envs + ['--']
Kuang-che Wud4603d72018-11-29 17:51:21 +08001165
Kuang-che Wu399d4662019-06-06 15:23:37 +08001166 # In addition to the output of command we are interested, cros_sdk may
1167 # generate its own messages. For example, chroot creation messages if we run
1168 # cros_sdk the first time.
1169 # This is the hack to run dummy command once, so we can get clean output for
1170 # the command we are interested.
1171 cmd = prefix + ['true']
1172 util.check_call(*cmd, cwd=chromeos_root)
1173
1174 cmd = prefix + list(args)
Kuang-che Wu9890ce82018-07-07 15:14:10 +08001175 return util.check_output(
1176 *cmd,
1177 cwd=chromeos_root,
Kuang-che Wubcafc552019-08-15 15:27:02 +08001178 log_stdout=kwargs.get('log_stdout', True),
Kuang-che Wu9890ce82018-07-07 15:14:10 +08001179 stdin=kwargs.get('stdin'),
1180 stderr_callback=kwargs.get('stderr_callback'))
Kuang-che Wubfc4a642018-04-19 11:54:08 +08001181
1182
Kuang-che Wua9a20bb2019-09-05 22:24:04 +08001183def create_chroot(chromeos_root):
Kuang-che Wuad5ac7b2020-02-20 19:02:52 +08001184 """Creates ChromeOS chroot if necessary.
Kuang-che Wua9a20bb2019-09-05 22:24:04 +08001185
1186 Args:
1187 chromeos_root: chromeos tree root
1188 """
1189 if os.path.exists(os.path.join(chromeos_root, 'chroot')):
1190 return
1191 if os.path.exists(os.path.join(chromeos_root, 'chroot.img')):
1192 return
1193
1194 util.check_output('chromite/bin/cros_sdk', '--create', cwd=chromeos_root)
1195
1196
Kuang-che Wuad5ac7b2020-02-20 19:02:52 +08001197def mount_chroot(chromeos_root):
1198 """Creates ChromeOS chroot if necessary.
1199
1200 Args:
1201 chromeos_root: chromeos tree root
1202 """
1203 # An arbitrary file must exist in chroot.
1204 path = convert_path_outside_chroot(chromeos_root, '/bin/ls')
1205
1206 # Not created or mounted yet.
1207 if not os.path.exists(path):
1208 create_chroot(chromeos_root)
1209 # After this command, the chroot is mounted.
1210 cros_sdk(chromeos_root, 'true')
1211 assert os.path.exists(path)
1212
1213
1214def copy_into_chroot(chromeos_root, src, dst, overwrite=True):
Kuang-che Wubfc4a642018-04-19 11:54:08 +08001215 """Copies file into chromeos chroot.
1216
Kuang-che Wuad5ac7b2020-02-20 19:02:52 +08001217 The side effect is chroot created and mounted.
1218
Kuang-che Wubfc4a642018-04-19 11:54:08 +08001219 Args:
1220 chromeos_root: chromeos tree root
1221 src: path outside chroot
1222 dst: path inside chroot
Kuang-che Wuad5ac7b2020-02-20 19:02:52 +08001223 overwrite: overwrite if dst alreadys exists
Kuang-che Wubfc4a642018-04-19 11:54:08 +08001224 """
Kuang-che Wuad5ac7b2020-02-20 19:02:52 +08001225 mount_chroot(chromeos_root)
1226 src = os.path.expanduser(src)
1227 dst_outside = convert_path_outside_chroot(chromeos_root, dst)
1228 if not overwrite and os.path.exists(dst_outside):
1229 return
1230
1231 # Haven't support directory or special files yet.
1232 assert os.path.isfile(src)
1233 assert os.path.isfile(dst_outside) or not os.path.exists(dst_outside)
1234
1235 dirname = os.path.dirname(dst_outside)
1236 if not os.path.exists(dirname):
1237 os.makedirs(dirname)
1238 shutil.copy(src, dst_outside)
Kuang-che Wubfc4a642018-04-19 11:54:08 +08001239
1240
Kuang-che Wuad5ac7b2020-02-20 19:02:52 +08001241def prepare_chroot(chromeos_root):
1242 mount_chroot(chromeos_root)
Kuang-che Wubfc4a642018-04-19 11:54:08 +08001243
Kuang-che Wuad5ac7b2020-02-20 19:02:52 +08001244 # Work around b/149077936:
1245 # The creds file is copied into the chroot since 12866.0.0.
1246 # But earlier versions need this file as well because of cipd ACL change.
1247 creds_path = '~/.config/chrome_infra/auth/creds.json'
1248 if os.path.exists(os.path.expanduser(creds_path)):
1249 copy_into_chroot(chromeos_root, creds_path, creds_path, overwrite=False)
Kuang-che Wubfc4a642018-04-19 11:54:08 +08001250
1251
Kuang-che Wu9890ce82018-07-07 15:14:10 +08001252def check_if_need_recreate_chroot(stdout, stderr):
1253 """Analyze build log and determine if chroot should be recreated.
1254
1255 Args:
1256 stdout: stdout output of build
1257 stderr: stderr output of build
1258
1259 Returns:
1260 the reason if chroot needs recreated; None otherwise
1261 """
Kuang-che Wu74768d32018-09-07 12:03:24 +08001262 if re.search(
1263 r"The current version of portage supports EAPI '\d+'. "
Kuang-che Wuae6824b2019-08-27 22:20:01 +08001264 'You must upgrade', stderr):
Kuang-che Wu9890ce82018-07-07 15:14:10 +08001265 return 'EAPI version mismatch'
1266
Kuang-che Wu5ac81322018-11-26 14:04:06 +08001267 if 'Chroot is too new. Consider running:' in stderr:
1268 return 'chroot version is too new'
1269
1270 # old message before Oct 2018
Kuang-che Wu9890ce82018-07-07 15:14:10 +08001271 if 'Chroot version is too new. Consider running cros_sdk --replace' in stderr:
1272 return 'chroot version is too new'
1273
Kuang-che Wu6fe987f2018-08-28 15:24:20 +08001274 # https://groups.google.com/a/chromium.org/forum/#!msg/chromium-os-dev/uzwT5APspB4/NFakFyCIDwAJ
1275 if "undefined reference to 'std::__1::basic_string" in stdout:
1276 return 'might be due to compiler change'
1277
Kuang-che Wu94e3b452019-11-21 12:49:18 +08001278 # Detect failures due to file collisions.
1279 # For example, kernel uprev from 3.x to 4.x, they are two separate packages
1280 # and conflict with each other. Other possible cases are package renaming or
1281 # refactoring. Let's recreate chroot to work around them.
1282 if 'Detected file collision' in stdout:
1283 # Using wildcard between words because the text wraps to the next line
1284 # depending on length of package name and each line is prefixed with
1285 # package name.
1286 # Using ".{,100}" instead of ".*" to prevent regex matching time explodes
1287 # exponentially. 100 is chosen arbitrarily. It should be longer than any
1288 # package name (65 now).
1289 m = re.search(
1290 r'Package (\S+).{,100}NOT.{,100}merged.{,100}'
1291 r'due.{,100}to.{,100}file.{,100}collisions', stdout, re.S)
1292 if m:
1293 return 'failed to install package due to file collision: ' + m.group(1)
Kuang-che Wu356c3522019-11-19 16:11:05 +08001294
Kuang-che Wu9890ce82018-07-07 15:14:10 +08001295 return None
1296
1297
Kuang-che Wua9a20bb2019-09-05 22:24:04 +08001298def build_packages(chromeos_root,
1299 board,
1300 chrome_root=None,
1301 goma_dir=None,
1302 afdo_use=False):
Kuang-che Wu28980b22019-07-31 19:51:45 +08001303 """Build ChromeOS packages.
Kuang-che Wubfc4a642018-04-19 11:54:08 +08001304
1305 Args:
1306 chromeos_root: chromeos tree root
1307 board: ChromeOS board name
Kuang-che Wua9a20bb2019-09-05 22:24:04 +08001308 chrome_root: Chrome tree root. If specified, build chrome using the
1309 provided tree
1310 goma_dir: Goma installed directory to mount into the chroot. If specified,
1311 build chrome with goma.
1312 afdo_use: build chrome with AFDO optimization
Kuang-che Wubfc4a642018-04-19 11:54:08 +08001313 """
Zheng-Jie Changffd49462019-12-16 12:15:18 +08001314
1315 def has_build_package_argument(argument):
1316 stderr_lines = []
1317 try:
1318 util.check_call(
1319 'src/scripts/build_packages',
1320 '--help',
1321 cwd=chromeos_root,
1322 stderr_callback=stderr_lines.append)
1323 except subprocess.CalledProcessError:
1324 help_output = ''.join(stderr_lines)
1325 return '--[no]%s' % argument in help_output
1326
Kuang-che Wua9a20bb2019-09-05 22:24:04 +08001327 common_env = {
1328 'USE': '-cros-debug chrome_internal',
1329 'FEATURES': 'separatedebug',
1330 }
Kuang-che Wu9890ce82018-07-07 15:14:10 +08001331 stderr_lines = []
1332 try:
Kuang-che Wufb553102018-10-02 18:14:29 +08001333 with locking.lock_file(locking.LOCK_FILE_FOR_BUILD):
Kuang-che Wua9a20bb2019-09-05 22:24:04 +08001334 env = common_env.copy()
1335 env['FEATURES'] += ' -separatedebug splitdebug'
Kuang-che Wufb553102018-10-02 18:14:29 +08001336 cros_sdk(
1337 chromeos_root,
Kuang-che Wu28980b22019-07-31 19:51:45 +08001338 './update_chroot',
1339 '--toolchain_boards',
Kuang-che Wufb553102018-10-02 18:14:29 +08001340 board,
Kuang-che Wua9a20bb2019-09-05 22:24:04 +08001341 env=env,
Kuang-che Wu28980b22019-07-31 19:51:45 +08001342 stderr_callback=stderr_lines.append)
Kuang-che Wua9a20bb2019-09-05 22:24:04 +08001343
1344 env = common_env.copy()
1345 cmd = [
Kuang-che Wu28980b22019-07-31 19:51:45 +08001346 './build_packages',
1347 '--board',
1348 board,
1349 '--withdev',
1350 '--noworkon',
1351 '--skip_chroot_upgrade',
1352 '--accept_licenses=@CHROMEOS',
Kuang-che Wua9a20bb2019-09-05 22:24:04 +08001353 ]
Zheng-Jie Changffd49462019-12-16 12:15:18 +08001354
1355 # `use_any_chrome` flag is default on and will force to use a chrome
1356 # prebuilt even if the version doesn't match.
1357
1358 # As this argument is landed in 12681, we should check if the argument
1359 # exists before adding this.
1360 if has_build_package_argument('use_any_chrome'):
1361 cmd.append('--nouse_any_chrome')
1362
Kuang-che Wua9a20bb2019-09-05 22:24:04 +08001363 if goma_dir:
1364 # Tell build_packages to start and stop goma
1365 cmd.append('--run_goma')
1366 env['USE_GOMA'] = 'true'
1367 if afdo_use:
1368 env['USE'] += ' afdo_use'
1369 cros_sdk(
1370 chromeos_root,
1371 *cmd,
1372 env=env,
1373 chrome_root=chrome_root,
1374 stderr_callback=stderr_lines.append,
1375 goma_dir=goma_dir)
Kuang-che Wu9890ce82018-07-07 15:14:10 +08001376 except subprocess.CalledProcessError as e:
1377 # Detect failures due to incompatibility between chroot and source tree. If
1378 # so, notify the caller to recreate chroot and retry.
1379 reason = check_if_need_recreate_chroot(e.output, ''.join(stderr_lines))
1380 if reason:
1381 raise NeedRecreateChrootException(reason)
1382
1383 # For other failures, don't know how to handle. Just bail out.
1384 raise
1385
Kuang-che Wu28980b22019-07-31 19:51:45 +08001386
1387def build_image(chromeos_root, board):
1388 """Build ChromeOS image.
1389
1390 Args:
1391 chromeos_root: chromeos tree root
1392 board: ChromeOS board name
1393
1394 Returns:
1395 image folder; relative to chromeos_root
1396 """
1397 stderr_lines = []
1398 try:
1399 with locking.lock_file(locking.LOCK_FILE_FOR_BUILD):
1400 cros_sdk(
1401 chromeos_root,
1402 './build_image',
1403 '--board',
1404 board,
1405 '--noenable_rootfs_verification',
1406 'test',
1407 env={
1408 'USE': '-cros-debug chrome_internal',
1409 'FEATURES': 'separatedebug',
1410 },
1411 stderr_callback=stderr_lines.append)
1412 except subprocess.CalledProcessError as e:
1413 # Detect failures due to incompatibility between chroot and source tree. If
1414 # so, notify the caller to recreate chroot and retry.
1415 reason = check_if_need_recreate_chroot(e.output, ''.join(stderr_lines))
1416 if reason:
1417 raise NeedRecreateChrootException(reason)
1418
1419 # For other failures, don't know how to handle. Just bail out.
1420 raise
1421
1422 image_symlink = os.path.join(chromeos_root, cached_images_dir, board,
1423 'latest')
1424 assert os.path.exists(image_symlink)
1425 image_name = os.readlink(image_symlink)
1426 image_folder = os.path.join(cached_images_dir, board, image_name)
1427 assert os.path.exists(
1428 os.path.join(chromeos_root, image_folder, test_image_filename))
1429 return image_folder
Kuang-che Wubfc4a642018-04-19 11:54:08 +08001430
1431
Kuang-che Wu23192ad2020-03-11 18:12:46 +08001432class AutotestControlInfo:
Kuang-che Wub9705bd2018-06-28 17:59:18 +08001433 """Parsed content of autotest control file.
1434
1435 Attributes:
1436 name: test name
1437 path: control file path
1438 variables: dict of top-level control variables. Sample keys: NAME, AUTHOR,
1439 DOC, ATTRIBUTES, DEPENDENCIES, etc.
1440 """
1441
1442 def __init__(self, path, variables):
1443 self.name = variables['NAME']
1444 self.path = path
1445 self.variables = variables
1446
1447
1448def parse_autotest_control_file(path):
1449 """Parses autotest control file.
1450
1451 This only parses simple top-level string assignments.
1452
1453 Returns:
1454 AutotestControlInfo object
1455 """
1456 variables = {}
Kuang-che Wua5723492019-11-25 20:59:34 +08001457 with open(path) as f:
1458 code = ast.parse(f.read())
Kuang-che Wub9705bd2018-06-28 17:59:18 +08001459 for stmt in code.body:
1460 # Skip if not simple "NAME = *" assignment.
1461 if not (isinstance(stmt, ast.Assign) and len(stmt.targets) == 1 and
1462 isinstance(stmt.targets[0], ast.Name)):
1463 continue
1464
1465 # Only support string value.
1466 if isinstance(stmt.value, ast.Str):
1467 variables[stmt.targets[0].id] = stmt.value.s
1468
1469 return AutotestControlInfo(path, variables)
1470
1471
1472def enumerate_autotest_control_files(autotest_dir):
1473 """Enumerate autotest control files.
1474
1475 Args:
1476 autotest_dir: autotest folder
1477
1478 Returns:
1479 list of paths to control files
1480 """
1481 # Where to find control files. Relative to autotest_dir.
1482 subpaths = [
1483 'server/site_tests',
1484 'client/site_tests',
1485 'server/tests',
1486 'client/tests',
1487 ]
1488
1489 blacklist = ['site-packages', 'venv', 'results', 'logs', 'containers']
1490 result = []
1491 for subpath in subpaths:
1492 path = os.path.join(autotest_dir, subpath)
1493 for root, dirs, files in os.walk(path):
1494
1495 for black in blacklist:
1496 if black in dirs:
1497 dirs.remove(black)
1498
1499 for filename in files:
1500 if filename == 'control' or filename.startswith('control.'):
1501 result.append(os.path.join(root, filename))
1502
1503 return result
1504
1505
1506def get_autotest_test_info(autotest_dir, test_name):
1507 """Get metadata of given test.
1508
1509 Args:
1510 autotest_dir: autotest folder
1511 test_name: test name
1512
1513 Returns:
1514 AutotestControlInfo object. None if test not found.
1515 """
1516 for control_file in enumerate_autotest_control_files(autotest_dir):
Zheng-Jie Chang1504a552020-02-20 16:38:57 +08001517 try:
1518 info = parse_autotest_control_file(control_file)
1519 except SyntaxError:
1520 logger.warning('%s is not parsable, ignore', control_file)
1521 continue
1522
Kuang-che Wub9705bd2018-06-28 17:59:18 +08001523 if info.name == test_name:
1524 return info
1525 return None
1526
1527
Zheng-Jie Chang868c1752020-01-21 14:42:41 +08001528def detect_branch_level(branch):
1529 """Given a branch name of manifest-internal, detect it's branch level.
1530
1531 level1: if ChromeOS version is x.0.0
1532 level2: if ChromeOS version is x.x.0
1533 level3: if ChromeOS version is x.x.x
1534 Where x is an non-zero integer.
1535
1536 Args:
1537 branch: branch name or ref name in manifest-internal
1538
1539 Returns:
1540 An integer indicates the branch level, or zero if not detectable.
1541 """
1542 level1 = r'^(refs\/\S+(\/\S+)?/)?master$'
1543 level2 = r'^\S+-(\d+)(\.0)?\.B$'
1544 level3 = r'^\S+-(\d+)\.(\d+)(\.0)?\.B$'
1545
1546 if re.match(level1, branch):
1547 return 1
1548 if re.match(level2, branch):
1549 return 2
1550 if re.match(level3, branch):
1551 return 3
1552 return 0
1553
1554
Zheng-Jie Chang5f9ae4e2020-02-07 14:26:06 +08001555def get_crosland_link(old, new):
1556 """Generates crosland link between two versions.
1557
1558 Args:
1559 old: ChromeOS version
1560 new: ChromeOS version
1561
1562 Returns:
1563 A crosland url.
1564 """
1565
1566 def version_to_url_parameter(ver):
1567 if is_cros_snapshot_version(ver):
1568 return snapshot_version_split(ver)[2]
1569 return version_to_short(ver)
1570
1571 old_parameter = version_to_url_parameter(old)
1572 new_parameter = version_to_url_parameter(new)
1573 return CROSLAND_URL_TEMPLATE % (old_parameter, new_parameter)
1574
1575
Kuang-che Wue4bae0b2018-07-19 12:10:14 +08001576class ChromeOSSpecManager(codechange.SpecManager):
1577 """Repo manifest related operations.
1578
1579 This class enumerates chromeos manifest files, parses them,
1580 and sync to disk state according to them.
1581 """
Kuang-che Wubfc4a642018-04-19 11:54:08 +08001582
1583 def __init__(self, config):
1584 self.config = config
Kuang-che Wue4bae0b2018-07-19 12:10:14 +08001585 self.manifest_dir = os.path.join(self.config['chromeos_root'], '.repo',
1586 'manifests')
Zheng-Jie Chang127c3302019-09-10 17:17:04 +08001587 self.manifest_internal_dir = os.path.join(self.config['chromeos_mirror'],
1588 'manifest-internal.git')
Kuang-che Wue4bae0b2018-07-19 12:10:14 +08001589 self.historical_manifest_git_dir = os.path.join(
Kuang-che Wud8fc9572018-10-03 21:00:41 +08001590 self.config['chromeos_mirror'], 'chromeos/manifest-versions.git')
Kuang-che Wue4bae0b2018-07-19 12:10:14 +08001591 if not os.path.exists(self.historical_manifest_git_dir):
Kuang-che Wue121fae2018-11-09 16:18:39 +08001592 raise errors.InternalError('Manifest snapshots should be cloned into %s' %
1593 self.historical_manifest_git_dir)
Kuang-che Wubfc4a642018-04-19 11:54:08 +08001594
Zheng-Jie Chang127c3302019-09-10 17:17:04 +08001595 def lookup_snapshot_manifest_revisions(self, old, new):
1596 """Get manifest commits between snapshot versions.
Kuang-che Wue4bae0b2018-07-19 12:10:14 +08001597
Zheng-Jie Chang127c3302019-09-10 17:17:04 +08001598 Returns:
1599 list of (timestamp, commit_id, snapshot_id):
1600 timestamp: integer unix timestamp
1601 commit_id: a string indicates commit hash
1602 snapshot_id: a string indicates snapshot id
1603 """
1604 assert is_cros_snapshot_version(old)
1605 assert is_cros_snapshot_version(new)
1606
1607 gs_path = (
Zheng-Jie Chang54020832020-04-21 15:52:48 +08001608 'gs://chromeos-image-archive/{board}-snapshot/{version}-*/image.zip')
Zheng-Jie Chang127c3302019-09-10 17:17:04 +08001609 # Try to guess the commit time of a snapshot manifest, it is usually a few
1610 # minutes different between snapshot manifest commit and image.zip
1611 # generate.
1612 try:
1613 old_timestamp = gsutil_stat_update_time(
1614 gs_path.format(board=self.config['board'], version=old)) - 86400
1615 except subprocess.CalledProcessError:
1616 old_timestamp = None
1617 try:
1618 new_timestamp = gsutil_stat_update_time(
1619 gs_path.format(board=self.config['board'], version=new)) + 86400
1620 # 1558657989 is snapshot_id 5982's commit time, this ensures every time
1621 # we can find snapshot 5982
1622 # snapshot_id <= 5982 has different commit message format, so we need
1623 # to identify its id in different ways, see below comment for more info.
1624 new_timestamp = max(new_timestamp, 1558657989 + 1)
1625 except subprocess.CalledProcessError:
1626 new_timestamp = None
1627 result = []
1628 _, _, old_snapshot_id = snapshot_version_split(old)
1629 _, _, new_snapshot_id = snapshot_version_split(new)
1630 repo = self.manifest_internal_dir
1631 path = 'snapshot.xml'
1632 branch = 'snapshot'
1633 commits = git_util.get_history(
1634 repo,
1635 path,
1636 branch,
1637 after=old_timestamp,
1638 before=new_timestamp,
1639 with_subject=True)
1640
1641 # Unfortunately, we can not identify snapshot_id <= 5982 from its commit
1642 # subject, as their subjects are all `Annealing manifest snapshot.`.
1643 # So instead we count the snapshot_id manually.
1644 count = 5982
1645 # There are two snapshot_id = 2633 in commit history, ignore the former
1646 # one.
1647 ignore_list = ['95c8526a7f0798d02f692010669dcbd5a152439a']
1648 # We examine the commits in reverse order as there are some testing
1649 # commits before snapshot_id=2, this method works fine after
1650 # snapshot 2, except snapshot 2633
1651 for commit in reversed(commits):
1652 msg = commit[2]
1653 if commit[1] in ignore_list:
1654 continue
1655
1656 match = re.match(r'^annealing manifest snapshot (\d+)', msg)
1657 if match:
1658 snapshot_id = match.group(1)
1659 elif 'Annealing manifest snapshot' in msg:
1660 snapshot_id = str(count)
1661 count -= 1
1662 else:
1663 continue
Zheng-Jie Chang34595ea2020-03-10 13:04:22 +08001664 # b/151054108: snapshot version in [29288, 29439] is broken
1665 if 29288 <= int(snapshot_id) <= 29439:
1666 continue
Zheng-Jie Chang127c3302019-09-10 17:17:04 +08001667 if int(old_snapshot_id) <= int(snapshot_id) <= int(new_snapshot_id):
1668 result.append((commit[0], commit[1], snapshot_id))
1669 # We find commits in reversed order, now reverse it again to chronological
1670 # order.
1671 return list(reversed(result))
1672
1673 def lookup_build_timestamp(self, rev):
1674 assert is_cros_full_version(rev) or is_cros_snapshot_version(rev)
1675 if is_cros_full_version(rev):
1676 return self.lookup_release_build_timestamp(rev)
Kuang-che Wua7ddf9b2019-11-25 18:59:57 +08001677 return self.lookup_snapshot_build_timestamp(rev)
Zheng-Jie Chang127c3302019-09-10 17:17:04 +08001678
1679 def lookup_snapshot_build_timestamp(self, rev):
1680 assert is_cros_snapshot_version(rev)
1681 return int(self.lookup_snapshot_manifest_revisions(rev, rev)[0][0])
1682
1683 def lookup_release_build_timestamp(self, rev):
1684 assert is_cros_full_version(rev)
Kuang-che Wubfc4a642018-04-19 11:54:08 +08001685 milestone, short_version = version_split(rev)
Kuang-che Wue4bae0b2018-07-19 12:10:14 +08001686 path = os.path.join('buildspecs', milestone, short_version + '.xml')
1687 try:
1688 timestamp = git_util.get_commit_time(self.historical_manifest_git_dir,
1689 'refs/heads/master', path)
1690 except ValueError:
Kuang-che Wuce2f3be2019-10-28 19:44:54 +08001691 raise errors.InternalError(
1692 '%s does not have %s' % (self.historical_manifest_git_dir, path))
Kuang-che Wue4bae0b2018-07-19 12:10:14 +08001693 return timestamp
Kuang-che Wubfc4a642018-04-19 11:54:08 +08001694
Zheng-Jie Chang868c1752020-01-21 14:42:41 +08001695 def detect_float_spec_branch_level(self, spec):
1696 results = [
1697 detect_branch_level(branch) for branch in git_util.get_branches(
1698 self.manifest_dir, commit=spec.name)
1699 ]
1700 results = [x for x in results if x > 0]
1701 return min(results) if results else 0
1702
1703 def branch_between_float_specs(self, old_spec, new_spec):
1704 if old_spec.spec_type != codechange.SPEC_FLOAT:
1705 return False
1706 if new_spec.spec_type != codechange.SPEC_FLOAT:
1707 return False
1708
1709 level_old = self.detect_float_spec_branch_level(old_spec)
1710 level_new = self.detect_float_spec_branch_level(new_spec)
1711
1712 if not level_old or not level_new:
1713 logger.warning('branch level detect failed, assume master')
1714 return False
1715 return level_old != level_new
1716
Zheng-Jie Changd968f552020-01-16 13:31:57 +08001717 def collect_float_spec(self, old, new, fixed_specs=None):
1718 assert fixed_specs
1719 branch = None
Zheng-Jie Chang3e191962020-02-06 14:25:31 +08001720 old_branches = []
1721 new_branches = []
Zheng-Jie Changd968f552020-01-16 13:31:57 +08001722
Zheng-Jie Chang3e191962020-02-06 14:25:31 +08001723 # There is no revision tag in snapshot's xml
1724 if fixed_specs[0].revision:
1725 old_branches = git_util.get_branches(
1726 self.manifest_dir, commit=fixed_specs[0].revision)
1727 if fixed_specs[-1].revision:
1728 new_branches = git_util.get_branches(
1729 self.manifest_dir, commit=fixed_specs[-1].revision)
1730
1731 # 1. if both are not snapshot, do AND operation
1732 # 2. if new is snapshot, branch = master
1733 # 3. if old is snapshot but new is not, respect new's branch
1734 if fixed_specs[0].revision and fixed_specs[-1].revision:
1735 branches = list(set(old_branches) & set(new_branches))
1736 elif not fixed_specs[-1].revision:
1737 branches = ['refs/remotes/origin/master']
1738 else:
1739 branches = new_branches
1740
Zheng-Jie Changd968f552020-01-16 13:31:57 +08001741 if branches:
1742 branch = branches[0]
1743 else:
1744 logger.warning(
1745 'unable to determine float spec branch, '
1746 'old = %s, new = %s', old_branches, new_branches)
1747
Kuang-che Wue4bae0b2018-07-19 12:10:14 +08001748 old_timestamp = self.lookup_build_timestamp(old)
1749 new_timestamp = self.lookup_build_timestamp(new)
Zheng-Jie Chang127c3302019-09-10 17:17:04 +08001750 # snapshot time is different from commit time
1751 # usually it's a few minutes different
1752 # 30 minutes should be safe in most cases
1753 if is_cros_snapshot_version(old):
1754 old_timestamp = old_timestamp - 1800
1755 if is_cros_snapshot_version(new):
1756 new_timestamp = new_timestamp + 1800
Kuang-che Wubfc4a642018-04-19 11:54:08 +08001757
Zheng-Jie Chang1ace3012020-02-15 04:51:05 +08001758 # TODO(zjchang): add logic to combine symlink target's (full.xml) history
Kuang-che Wue4bae0b2018-07-19 12:10:14 +08001759 result = []
Zheng-Jie Chang1ace3012020-02-15 04:51:05 +08001760 path = 'default.xml'
Kuang-che Wue4bae0b2018-07-19 12:10:14 +08001761 parser = repo_util.ManifestParser(self.manifest_dir)
1762 for timestamp, git_rev in parser.enumerate_manifest_commits(
Zheng-Jie Changd968f552020-01-16 13:31:57 +08001763 old_timestamp, new_timestamp, path, branch=branch):
Kuang-che Wue4bae0b2018-07-19 12:10:14 +08001764 result.append(
1765 codechange.Spec(codechange.SPEC_FLOAT, git_rev, timestamp, path))
1766 return result
Kuang-che Wubfc4a642018-04-19 11:54:08 +08001767
Kuang-che Wue4bae0b2018-07-19 12:10:14 +08001768 def collect_fixed_spec(self, old, new):
Zheng-Jie Chang127c3302019-09-10 17:17:04 +08001769 assert is_cros_full_version(old) or is_cros_snapshot_version(old)
1770 assert is_cros_full_version(new) or is_cros_snapshot_version(new)
1771
1772 # case 1: if both are snapshot, return a list of snapshot
1773 if is_cros_snapshot_version(old) and is_cros_snapshot_version(new):
1774 return self.collect_snapshot_specs(old, new)
1775
1776 # case 2: if both are release version
1777 # return a list of release version
1778 if is_cros_full_version(old) and is_cros_full_version(new):
1779 return self.collect_release_specs(old, new)
1780
1781 # case 3: return a list of release version and append a snapshot
1782 # before or at the end
Zheng-Jie Changc47af3a2019-11-11 17:28:58 +08001783 result = self.collect_release_specs(
1784 version_to_full(self.config['board'], old),
1785 version_to_full(self.config['board'], new))
Zheng-Jie Chang127c3302019-09-10 17:17:04 +08001786 if is_cros_snapshot_version(old):
Zheng-Jie Changc47af3a2019-11-11 17:28:58 +08001787 result = self.collect_snapshot_specs(old, old) + result[1:]
Zheng-Jie Chang127c3302019-09-10 17:17:04 +08001788 elif is_cros_snapshot_version(new):
Zheng-Jie Chang5cd62dd2019-12-09 13:21:12 +08001789 result += self.collect_snapshot_specs(new, new)
Zheng-Jie Chang127c3302019-09-10 17:17:04 +08001790 return result
1791
1792 def collect_snapshot_specs(self, old, new):
1793 assert is_cros_snapshot_version(old)
1794 assert is_cros_snapshot_version(new)
1795
1796 def guess_snapshot_version(board, snapshot_id, old, new):
1797 if old.endswith('-' + snapshot_id):
1798 return old
1799 if new.endswith('-' + snapshot_id):
1800 return new
Zheng-Jie Chang54020832020-04-21 15:52:48 +08001801 gs_path = ('gs://chromeos-image-archive/{board}-snapshot/'
Kuang-che Wuf791afa2019-10-28 19:53:26 +08001802 'R*-{snapshot_id}-*'.format(
1803 board=board, snapshot_id=snapshot_id))
Zheng-Jie Chang127c3302019-09-10 17:17:04 +08001804 for line in gsutil_ls(gs_path, ignore_errors=True):
1805 m = re.match(r'^gs:\S+(R\d+-\d+\.\d+\.\d+-\d+)\S+', line)
1806 if m:
1807 return m.group(1)
Zheng-Jie Chang026cd5d2019-12-04 12:13:01 +08001808 return None
Zheng-Jie Chang127c3302019-09-10 17:17:04 +08001809
1810 result = []
1811 path = 'snapshot.xml'
1812 revisions = self.lookup_snapshot_manifest_revisions(old, new)
Kuang-che Wuf791afa2019-10-28 19:53:26 +08001813 for timestamp, _git_rev, snapshot_id in revisions:
Zheng-Jie Chang127c3302019-09-10 17:17:04 +08001814 snapshot_version = guess_snapshot_version(self.config['board'],
1815 snapshot_id, old, new)
Zheng-Jie Chang026cd5d2019-12-04 12:13:01 +08001816 if snapshot_version:
1817 result.append(
1818 codechange.Spec(codechange.SPEC_FIXED, snapshot_version, timestamp,
1819 path))
1820 else:
1821 logger.warning('snapshot id %s is not found, ignore', snapshot_id)
Zheng-Jie Chang127c3302019-09-10 17:17:04 +08001822 return result
1823
1824 def collect_release_specs(self, old, new):
Kuang-che Wubfc4a642018-04-19 11:54:08 +08001825 assert is_cros_full_version(old)
1826 assert is_cros_full_version(new)
1827 old_milestone, old_short_version = version_split(old)
1828 new_milestone, new_short_version = version_split(new)
1829
Kuang-che Wubfc4a642018-04-19 11:54:08 +08001830 result = []
Kuang-che Wue4bae0b2018-07-19 12:10:14 +08001831 for milestone in git_util.list_dir_from_revision(
1832 self.historical_manifest_git_dir, 'refs/heads/master', 'buildspecs'):
1833 if not milestone.isdigit():
1834 continue
1835 if not int(old_milestone) <= int(milestone) <= int(new_milestone):
1836 continue
1837
Kuang-che Wu74768d32018-09-07 12:03:24 +08001838 files = git_util.list_dir_from_revision(
1839 self.historical_manifest_git_dir, 'refs/heads/master',
1840 os.path.join('buildspecs', milestone))
Kuang-che Wubfc4a642018-04-19 11:54:08 +08001841
1842 for fn in files:
Kuang-che Wue4bae0b2018-07-19 12:10:14 +08001843 path = os.path.join('buildspecs', milestone, fn)
Kuang-che Wubfc4a642018-04-19 11:54:08 +08001844 short_version, ext = os.path.splitext(fn)
1845 if ext != '.xml':
1846 continue
Kuang-che Wubfc4a642018-04-19 11:54:08 +08001847 if (util.is_version_lesseq(old_short_version, short_version) and
1848 util.is_version_lesseq(short_version, new_short_version) and
1849 util.is_direct_relative_version(short_version, new_short_version)):
Kuang-che Wue4bae0b2018-07-19 12:10:14 +08001850 rev = make_cros_full_version(milestone, short_version)
1851 timestamp = git_util.get_commit_time(self.historical_manifest_git_dir,
1852 'refs/heads/master', path)
1853 result.append(
1854 codechange.Spec(codechange.SPEC_FIXED, rev, timestamp, path))
Kuang-che Wubfc4a642018-04-19 11:54:08 +08001855
Kuang-che Wue4bae0b2018-07-19 12:10:14 +08001856 def version_key_func(spec):
1857 _milestone, short_version = version_split(spec.name)
Kuang-che Wubfc4a642018-04-19 11:54:08 +08001858 return util.version_key_func(short_version)
1859
1860 result.sort(key=version_key_func)
Kuang-che Wue4bae0b2018-07-19 12:10:14 +08001861 assert result[0].name == old
1862 assert result[-1].name == new
Kuang-che Wubfc4a642018-04-19 11:54:08 +08001863 return result
1864
Kuang-che Wue4bae0b2018-07-19 12:10:14 +08001865 def get_manifest(self, rev):
Zheng-Jie Chang127c3302019-09-10 17:17:04 +08001866 assert is_cros_full_version(rev) or is_cros_snapshot_version(rev)
1867 if is_cros_full_version(rev):
1868 milestone, short_version = version_split(rev)
1869 path = os.path.join('buildspecs', milestone, '%s.xml' % short_version)
1870 manifest = git_util.get_file_from_revision(
1871 self.historical_manifest_git_dir, 'refs/heads/master', path)
1872 else:
1873 revisions = self.lookup_snapshot_manifest_revisions(rev, rev)
1874 commit_id = revisions[0][1]
1875 manifest = git_util.get_file_from_revision(self.manifest_internal_dir,
1876 commit_id, 'snapshot.xml')
Zheng-Jie Chang0fc704b2019-12-09 18:43:38 +08001877 return manifest
1878
1879 def get_manifest_file(self, rev):
1880 assert is_cros_full_version(rev) or is_cros_snapshot_version(rev)
Kuang-che Wue4bae0b2018-07-19 12:10:14 +08001881 manifest_name = 'manifest_%s.xml' % rev
1882 manifest_path = os.path.join(self.manifest_dir, manifest_name)
1883 with open(manifest_path, 'w') as f:
Zheng-Jie Chang0fc704b2019-12-09 18:43:38 +08001884 f.write(self.get_manifest(rev))
Zheng-Jie Changec368b52020-03-02 16:25:25 +08001885
1886 # workaround for b/150572399
1887 # for chromeOS version < 12931.0.0, manifests are included from incorrect
1888 # folder .repo instead of.repo/manifests
1889 if is_cros_version_lesseq(rev, '12931.0.0'):
1890 repo_path = os.path.join(self.config['chromeos_root'], '.repo')
1891 manifest_patch_path = os.path.join(repo_path, manifest_name)
1892 with open(manifest_patch_path, 'w') as f:
1893 f.write(self.get_manifest(rev))
1894
Kuang-che Wue4bae0b2018-07-19 12:10:14 +08001895 return manifest_name
1896
1897 def parse_spec(self, spec):
1898 parser = repo_util.ManifestParser(self.manifest_dir)
1899 if spec.spec_type == codechange.SPEC_FIXED:
Zheng-Jie Chang0fc704b2019-12-09 18:43:38 +08001900 manifest_name = self.get_manifest_file(spec.name)
Kuang-che Wue4bae0b2018-07-19 12:10:14 +08001901 manifest_path = os.path.join(self.manifest_dir, manifest_name)
Kuang-che Wua5723492019-11-25 20:59:34 +08001902 with open(manifest_path) as f:
1903 content = f.read()
Kuang-che Wue4bae0b2018-07-19 12:10:14 +08001904 root = parser.parse_single_xml(content, allow_include=False)
1905 else:
1906 root = parser.parse_xml_recursive(spec.name, spec.path)
1907
1908 spec.entries = parser.process_parsed_result(root)
1909 if spec.spec_type == codechange.SPEC_FIXED:
Kuang-che Wufe1e88a2019-09-10 21:52:25 +08001910 if not spec.is_static():
1911 raise ValueError(
1912 'fixed spec %r has unexpected floating entries' % spec.name)
Zheng-Jie Changd968f552020-01-16 13:31:57 +08001913 spec.revision = root.get('revision')
Kuang-che Wue4bae0b2018-07-19 12:10:14 +08001914
1915 def sync_disk_state(self, rev):
Zheng-Jie Chang0fc704b2019-12-09 18:43:38 +08001916 manifest_name = self.get_manifest_file(rev)
Kuang-che Wue4bae0b2018-07-19 12:10:14 +08001917
1918 # For ChromeOS, mark_as_stable step requires 'repo init -m', which sticks
1919 # manifest. 'repo sync -m' is not enough
1920 repo_util.init(
1921 self.config['chromeos_root'],
1922 'https://chrome-internal.googlesource.com/chromeos/manifest-internal',
1923 manifest_name=manifest_name,
1924 repo_url='https://chromium.googlesource.com/external/repo.git',
Kuang-che Wud8fc9572018-10-03 21:00:41 +08001925 reference=self.config['chromeos_mirror'],
Zheng-Jie Chang85529ad2020-03-06 18:40:18 +08001926 # b/150753074: moblab is in non-default group and causes mark_as_stable
1927 # fail
Kuang-che Wu084eef22020-03-11 18:29:48 +08001928 groups='default,moblab,platform-linux',
Kuang-che Wue4bae0b2018-07-19 12:10:14 +08001929 )
1930
1931 # Note, don't sync with current_branch=True for chromeos. One of its
1932 # build steps (inside mark_as_stable) executes "git describe" which
1933 # needs git tag information.
1934 repo_util.sync(self.config['chromeos_root'])