blob: f2b32e9d6ad84ace06f3c2dc0c80a6237d9fddfc [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
Zheng-Jie Chang61a645a2020-04-29 10:12:13 +080058# current earliest buildbucket buildable versions
59# picked from https://crrev.com/c/2072618
60buildbucket_cutover_versions = [
61 '12931.0.0',
62 '12871.26.0', # R81
63 '12871.24.2', # stabilize-12871.24.B
64 '12812.10.0', # factory-excelsior-12812.B
65 '12768.14.0', # firmware-servo-12768.B
66 '12739.85.0', # R80
67 '12739.67.1', # stabilize-excelsior-12739.67.B
68 '12692.36.0', # factory-hatch-12692.B
69 '12672.104.0', # firmware-hatch-12672.B
70 '12607.110.0', # R79
71 '12607.83.2', # stabilize-quickfix-12607.83.B
72 '12587.59.0', # factory-kukui-12587.B
73 '12573.78.0', # firmware-kukui-12573.B
74 '12499.96.0', # R78
75 '12422.33.0', # firmware-mistral-12422.B
76 '12371.190.0', # R77
77 '12361.38.0', # factory-mistral-12361.B
78 '12200.65.0', # firmware-sarien-12200.B
79 '12105.128.0', # R75
80 '12033.82.0', # factory-sarien-12033.B
81]
82
Kuang-che Wub9705bd2018-06-28 17:59:18 +080083chromeos_root_inside_chroot = '/mnt/host/source'
84# relative to chromeos_root
Kuang-che Wu7f82c6f2019-08-12 14:29:28 +080085prebuilt_autotest_dir = 'tmp/autotest-prebuilt'
Kuang-che Wu28980b22019-07-31 19:51:45 +080086# Relative to chromeos root. Images are cached_images_dir/$board/$image_name.
87cached_images_dir = 'src/build/images'
88test_image_filename = 'chromiumos_test_image.bin'
Kuang-che Wub9705bd2018-06-28 17:59:18 +080089
Kuang-che Wu2ea804f2017-11-28 17:11:41 +080090VERSION_KEY_CROS_SHORT_VERSION = 'cros_short_version'
91VERSION_KEY_CROS_FULL_VERSION = 'cros_full_version'
92VERSION_KEY_MILESTONE = 'milestone'
93VERSION_KEY_CR_VERSION = 'cr_version'
Kuang-che Wu708310b2018-03-28 17:24:34 +080094VERSION_KEY_ANDROID_BUILD_ID = 'android_build_id'
Kuang-che Wu2ea804f2017-11-28 17:11:41 +080095VERSION_KEY_ANDROID_BRANCH = 'android_branch'
Zheng-Jie Chang5f9ae4e2020-02-07 14:26:06 +080096CROSLAND_URL_TEMPLATE = 'https://crosland.corp.google.com/log/%s..%s'
Kuang-che Wu2ea804f2017-11-28 17:11:41 +080097
98
Kuang-che Wu9890ce82018-07-07 15:14:10 +080099class NeedRecreateChrootException(Exception):
100 """Failed to build ChromeOS because of chroot mismatch or corruption"""
101
102
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800103def is_cros_short_version(s):
Kuang-che Wuacb6efd2018-04-25 18:52:58 +0800104 """Determines if `s` is chromeos short version.
105
106 This function doesn't accept version number of local build.
107 """
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800108 return bool(re.match(re_chromeos_short_version, s))
109
110
Kuang-che Wuacb6efd2018-04-25 18:52:58 +0800111def is_cros_localbuild_version(s):
112 """Determines if `s` is chromeos local build version."""
113 return bool(re.match(re_chromeos_localbuild_version, s))
114
115
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800116def is_cros_full_version(s):
Kuang-che Wuacb6efd2018-04-25 18:52:58 +0800117 """Determines if `s` is chromeos full version.
118
119 This function doesn't accept version number of local build.
120 """
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800121 return bool(re.match(re_chromeos_full_version, s))
122
123
124def is_cros_version(s):
125 """Determines if `s` is chromeos version (either short or full)"""
126 return is_cros_short_version(s) or is_cros_full_version(s)
127
128
Zheng-Jie Chang127c3302019-09-10 17:17:04 +0800129def is_cros_snapshot_version(s):
130 """Determines if `s` is chromeos snapshot version"""
131 return bool(re.match(re_chromeos_snapshot_version, s))
132
133
Zheng-Jie Changaea4fba2020-02-17 17:12:09 +0800134def is_cros_version_lesseq(ver1, ver2):
135 """Determines if ver1 is less or equal to ver2.
136
137 Args:
138 ver1: a Chrome OS version in short, full, or snapshot format.
139 ver2: a Chrome OS version in short, full, or snapshot format.
140
141 Returns:
Zheng-Jie Chang61a645a2020-04-29 10:12:13 +0800142 True if ver1 is less or equal to ver2.
Zheng-Jie Changaea4fba2020-02-17 17:12:09 +0800143 """
144 assert is_cros_version(ver1) or is_cros_snapshot_version(ver1)
145 assert is_cros_version(ver2) or is_cros_snapshot_version(ver2)
146
147 ver1 = [int(x) for x in re.split(r'[.-]', ver1) if not x.startswith('R')]
148 ver2 = [int(x) for x in re.split(r'[.-]', ver2) if not x.startswith('R')]
149 return ver1 <= ver2
150
151
Zheng-Jie Chang61a645a2020-04-29 10:12:13 +0800152def is_buildbucket_buildable(version):
153 """Determines if a version is buildable on buildbucket."""
154 short_version = version_to_short(version)
155 # If given version is child of any cutover, then it's buildable
156 return any([
157 util.is_direct_relative_version(x, short_version) and
158 is_cros_version_lesseq(x, version) for x in buildbucket_cutover_versions
159 ])
160
161
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800162def make_cros_full_version(milestone, short_version):
163 """Makes full_version from milestone and short_version"""
Kuang-che Wuacb6efd2018-04-25 18:52:58 +0800164 assert milestone
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800165 return 'R%s-%s' % (milestone, short_version)
166
167
Zheng-Jie Chang127c3302019-09-10 17:17:04 +0800168def make_cros_snapshot_version(milestone, short_version, snapshot_id):
169 """Makes snapshot version from milestone, short_version and snapshot id"""
170 return 'R%s-%s-%s' % (milestone, short_version, snapshot_id)
171
172
173def version_split(version):
174 """Splits full_version or snapshot_version into milestone and short_version"""
175 assert is_cros_full_version(version) or is_cros_snapshot_version(version)
176 if is_cros_snapshot_version(version):
177 return snapshot_version_split(version)[0:2]
178 milestone, short_version = version.split('-')
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800179 return milestone[1:], short_version
180
181
Zheng-Jie Chang127c3302019-09-10 17:17:04 +0800182def snapshot_version_split(snapshot_version):
183 """Splits snapshot_version into milestone, short_version and snapshot_id"""
184 assert is_cros_snapshot_version(snapshot_version)
185 milestone, shot_version, snapshot_id = snapshot_version.split('-')
186 return milestone[1:], shot_version, snapshot_id
187
188
Zheng-Jie Changb8697042019-10-29 16:03:26 +0800189def query_snapshot_buildbucket_id(board, snapshot_version):
190 """Query buildbucket id of a snapshot"""
191 assert is_cros_snapshot_version(snapshot_version)
Zheng-Jie Chang54020832020-04-21 15:52:48 +0800192 path = ('gs://chromeos-image-archive/{board}-snapshot'
Zheng-Jie Changb8697042019-10-29 16:03:26 +0800193 '/{snapshot_version}-*/image.zip')
194 output = gsutil_ls(
195 '-d',
196 path.format(board=board, snapshot_version=snapshot_version),
197 ignore_errors=True)
198 for line in output:
Zheng-Jie Chang54020832020-04-21 15:52:48 +0800199 m = re.match(r'.*-snapshot/R\d+-\d+\.\d+\.\d+-\d+-(.+)/image\.zip', line)
Zheng-Jie Changb8697042019-10-29 16:03:26 +0800200 if m:
201 return m.group(1)
202 return None
203
204
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800205def argtype_cros_version(s):
Zheng-Jie Chang127c3302019-09-10 17:17:04 +0800206 if (not is_cros_version(s)) and (not is_cros_snapshot_version(s)):
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800207 msg = 'invalid cros version'
Kuang-che Wuce2f3be2019-10-28 19:44:54 +0800208 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 +0800209 return s
210
211
212def query_dut_lsb_release(host):
213 """Query /etc/lsb-release of given DUT
214
215 Args:
216 host: the DUT address
217
218 Returns:
Kuang-che Wu3eb6b502018-06-06 16:15:18 +0800219 dict for keys and values of /etc/lsb-release.
220
221 Raises:
Kuang-che Wu44278142019-03-04 11:33:57 +0800222 errors.SshConnectionError: cannot connect to host
223 errors.ExternalError: lsb-release file doesn't exist
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800224 """
225 try:
Kuang-che Wu44278142019-03-04 11:33:57 +0800226 output = util.ssh_cmd(host, 'cat', '/etc/lsb-release')
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800227 except subprocess.CalledProcessError:
Kuang-che Wu44278142019-03-04 11:33:57 +0800228 raise errors.ExternalError('unable to read /etc/lsb-release; not a DUT')
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800229 return dict(re.findall(r'^(\w+)=(.*)$', output, re.M))
230
231
232def is_dut(host):
Kuang-che Wubfc4a642018-04-19 11:54:08 +0800233 """Determines whether a host is a chromeos device.
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800234
235 Args:
236 host: the DUT address
237
238 Returns:
239 True if the host is a chromeos device.
240 """
Kuang-che Wu44278142019-03-04 11:33:57 +0800241 try:
242 return query_dut_lsb_release(host).get('DEVICETYPE') in [
243 'CHROMEBASE',
244 'CHROMEBIT',
245 'CHROMEBOOK',
246 'CHROMEBOX',
247 'REFERENCE',
248 ]
249 except (errors.ExternalError, errors.SshConnectionError):
250 return False
251
252
253def is_good_dut(host):
254 if not is_dut(host):
255 return False
256
257 # Sometimes python is broken after 'cros flash'.
258 try:
259 util.ssh_cmd(host, 'python', '-c', '1')
260 return True
261 except (subprocess.CalledProcessError, errors.SshConnectionError):
262 return False
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800263
264
265def query_dut_board(host):
266 """Query board name of a given DUT"""
267 return query_dut_lsb_release(host).get('CHROMEOS_RELEASE_BOARD')
268
269
270def query_dut_short_version(host):
Kuang-che Wuacb6efd2018-04-25 18:52:58 +0800271 """Query short version of a given DUT.
272
273 This function may return version of local build, which
274 is_cros_short_version() is false.
275 """
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800276 return query_dut_lsb_release(host).get('CHROMEOS_RELEASE_VERSION')
277
278
Zheng-Jie Chang26084542020-04-29 06:34:33 +0800279def query_dut_prebuilt_version(host):
280 """Return a snapshot version or short version of a given DUT.
281
282 Args:
283 host: dut host
284
285 Returns:
286 Snapshot version or short version.
287 """
288 lsb_release = query_dut_lsb_release(host)
289 release_version = lsb_release.get('CHROMEOS_RELEASE_VERSION')
290 builder_path = lsb_release.get('CHROMEOS_RELEASE_BUILDER_PATH')
291 match = re.match(r'\S+-(?:snapshot|postsubmit)/(R\d+-\d+\.\d+\.\d+-\d+)-\d+',
292 builder_path)
293 if match:
294 return match.group(1)
295 return release_version
296
297
Zheng-Jie Chang181be6f2020-03-17 16:16:08 +0800298def query_dut_is_by_official_builder(host):
299 """Query if given DUT is build by buildbucket builder"""
300 return query_dut_lsb_release(host).get('CHROMEOS_RELEASE_BUILD_TYPE',
301 '').startswith('Continuous Builder')
302
303
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800304def query_dut_boot_id(host, connect_timeout=None):
Kuang-che Wubfc4a642018-04-19 11:54:08 +0800305 """Query boot id.
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800306
307 Args:
308 host: DUT address
309 connect_timeout: connection timeout
310
311 Returns:
312 boot uuid
313 """
Kuang-che Wu44278142019-03-04 11:33:57 +0800314 return util.ssh_cmd(
315 host,
316 'cat',
317 '/proc/sys/kernel/random/boot_id',
318 connect_timeout=connect_timeout).strip()
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800319
320
321def reboot(host):
322 """Reboot a DUT and verify"""
323 logger.debug('reboot %s', host)
324 boot_id = query_dut_boot_id(host)
325
Kuang-che Wu44278142019-03-04 11:33:57 +0800326 try:
327 util.ssh_cmd(host, 'reboot')
Kuang-che Wu5f662e82019-03-05 11:49:56 +0800328 except errors.SshConnectionError:
329 # Depends on timing, ssh may return failure due to broken pipe, which is
330 # working as intended. Ignore such kind of errors.
Kuang-che Wu44278142019-03-04 11:33:57 +0800331 pass
Kuang-che Wu708310b2018-03-28 17:24:34 +0800332 wait_reboot_done(host, boot_id)
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800333
Kuang-che Wu708310b2018-03-28 17:24:34 +0800334
335def wait_reboot_done(host, boot_id):
Kuang-che Wu4fe945b2018-03-31 16:46:38 +0800336 # For dev-mode test image, the reboot time is roughly at least 16 seconds
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800337 # (dev screen short delay) or more (long delay).
338 time.sleep(15)
339 for _ in range(100):
340 try:
341 # During boot, DUT does not response and thus ssh may hang a while. So
342 # set a connect timeout. 3 seconds are enough and 2 are not. It's okay to
343 # set tight limit because it's inside retry loop.
344 assert boot_id != query_dut_boot_id(host, connect_timeout=3)
345 return
Kuang-che Wu5f662e82019-03-05 11:49:56 +0800346 except errors.SshConnectionError:
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800347 logger.debug('reboot not ready? sleep wait 1 sec')
348 time.sleep(1)
349
Kuang-che Wue121fae2018-11-09 16:18:39 +0800350 raise errors.ExternalError('reboot failed?')
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800351
352
Kuang-che Wu80bf6a52019-05-31 12:48:06 +0800353def gs_release_boardpath(board):
354 """Normalizes board name for gs://chromeos-releases/
355
356 This follows behavior of PushImage() in chromite/scripts/pushimage.py
357 Note, only gs://chromeos-releases/ needs normalization,
358 gs://chromeos-image-archive does not.
359
360 Args:
361 board: ChromeOS board name
362
363 Returns:
364 normalized board name
365 """
366 return board.replace('_', '-')
367
368
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800369def gsutil(*args, **kwargs):
Kuang-che Wubfc4a642018-04-19 11:54:08 +0800370 """gsutil command line wrapper.
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800371
372 Args:
373 args: command line arguments passed to gsutil
374 kwargs:
375 ignore_errors: if true, return '' for failures, for example 'gsutil ls'
376 but the path not found.
377
378 Returns:
379 stdout of gsutil
380
381 Raises:
Zheng-Jie Changb8697042019-10-29 16:03:26 +0800382 errors.ExternalError: gsutil failed to run
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800383 subprocess.CalledProcessError: command failed
384 """
385 stderr_lines = []
386 try:
387 return util.check_output(
388 gsutil_bin, *args, stderr_callback=stderr_lines.append)
389 except subprocess.CalledProcessError as e:
390 stderr = ''.join(stderr_lines)
391 if re.search(r'ServiceException:.* does not have .*access', stderr):
Kuang-che Wue121fae2018-11-09 16:18:39 +0800392 raise errors.ExternalError(
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800393 'gsutil failed due to permission. ' +
394 'Run "%s config" and follow its instruction. ' % gsutil_bin +
395 'Fill any string if it asks for project-id')
396 if kwargs.get('ignore_errors'):
397 return ''
398 raise
399 except OSError as e:
400 if e.errno == errno.ENOENT:
Kuang-che Wue121fae2018-11-09 16:18:39 +0800401 raise errors.ExternalError(
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800402 'Unable to run %s. gsutil is not installed or not in PATH?' %
403 gsutil_bin)
404 raise
405
406
407def gsutil_ls(*args, **kwargs):
Kuang-che Wubfc4a642018-04-19 11:54:08 +0800408 """gsutil ls.
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800409
410 Args:
411 args: arguments passed to 'gsutil ls'
412 kwargs: extra parameters, where
Kuang-che Wu4fe945b2018-03-31 16:46:38 +0800413 ignore_errors: if true, return empty list instead of raising
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800414 exception, ex. path not found.
415
416 Returns:
417 list of 'gsutil ls' result. One element for one line of gsutil output.
418
419 Raises:
420 subprocess.CalledProcessError: gsutil failed, usually means path not found
421 """
422 return gsutil('ls', *args, **kwargs).splitlines()
423
424
Zheng-Jie Chang127c3302019-09-10 17:17:04 +0800425def gsutil_stat_update_time(*args, **kwargs):
426 """Returns the last modified time of a file or multiple files.
427
428 Args:
429 args: arguments passed to 'gsutil stat'.
430 kwargs: extra parameters for gsutil.
431
432 Returns:
433 A integer indicates the last modified timestamp.
434
435 Raises:
436 subprocess.CalledProcessError: gsutil failed, usually means path not found
437 errors.ExternalError: update time is not found
438 """
439 result = -1
440 # Currently we believe stat always returns a UTC time, and strptime also
441 # parses a UTC time by default.
442 time_format = '%a, %d %b %Y %H:%M:%S GMT'
443
444 for line in gsutil('stat', *args, **kwargs).splitlines():
445 if ':' not in line:
446 continue
Kuang-che Wuc89f2a22019-11-26 15:30:50 +0800447 key, value = line.split(':', 1)
448 key, value = key.strip(), value.strip()
Zheng-Jie Chang127c3302019-09-10 17:17:04 +0800449 if key != 'Update time':
450 continue
451 dt = datetime.datetime.strptime(value, time_format)
Kuang-che Wu72b5a572019-10-29 20:37:57 +0800452 unixtime = int(calendar.timegm(dt.utctimetuple()))
Zheng-Jie Chang127c3302019-09-10 17:17:04 +0800453 result = max(result, unixtime)
454
455 if result == -1:
456 raise errors.ExternalError("didn't find update time")
457 return result
458
459
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800460def query_milestone_by_version(board, short_version):
Kuang-che Wubfc4a642018-04-19 11:54:08 +0800461 """Query milestone by ChromeOS version number.
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800462
463 Args:
464 board: ChromeOS board name
465 short_version: ChromeOS version number in short format, ex. 9300.0.0
466
467 Returns:
468 ChromeOS milestone number (string). For example, '58' for '9300.0.0'.
469 None if failed.
470 """
471 path = gs_archive_path.format(board=board) + '/R*-' + short_version
472 for line in gsutil_ls('-d', path, ignore_errors=True):
473 m = re.search(r'/R(\d+)-', line)
474 if not m:
475 continue
476 return m.group(1)
477
478 for channel in ['canary', 'dev', 'beta', 'stable']:
479 path = gs_release_path.format(
Kuang-che Wu80bf6a52019-05-31 12:48:06 +0800480 channel=channel,
481 boardpath=gs_release_boardpath(board),
482 short_version=short_version)
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800483 for line in gsutil_ls(path, ignore_errors=True):
484 m = re.search(r'\bR(\d+)-' + short_version, line)
485 if not m:
486 continue
487 return m.group(1)
488
Zheng-Jie Chang4e25a8d2020-01-08 16:00:13 +0800489 logger.debug('unable to query milestone of %s for %s', short_version, board)
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800490 return None
491
492
Kuang-che Wu80bf6a52019-05-31 12:48:06 +0800493def list_board_names(chromeos_root):
494 """List board names.
495
496 Args:
497 chromeos_root: chromeos tree root
498
499 Returns:
500 list of board names
501 """
502 # Following logic is simplified from chromite/lib/portage_util.py
503 cros_list_overlays = os.path.join(chromeos_root,
504 'chromite/bin/cros_list_overlays')
505 overlays = util.check_output(cros_list_overlays).splitlines()
506 result = set()
507 for overlay in overlays:
508 conf_file = os.path.join(overlay, 'metadata', 'layout.conf')
509 name = None
510 if os.path.exists(conf_file):
511 for line in open(conf_file):
512 m = re.match(r'^repo-name\s*=\s*(\S+)\s*$', line)
513 if m:
514 name = m.group(1)
515 break
516
517 if not name:
518 name_file = os.path.join(overlay, 'profiles', 'repo_name')
519 if os.path.exists(name_file):
Kuang-che Wua5723492019-11-25 20:59:34 +0800520 with open(name_file) as f:
521 name = f.read().strip()
Kuang-che Wu80bf6a52019-05-31 12:48:06 +0800522
523 if name:
524 name = re.sub(r'-private$', '', name)
525 result.add(name)
526
527 return list(result)
528
529
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800530def recognize_version(board, version):
Kuang-che Wubfc4a642018-04-19 11:54:08 +0800531 """Recognize ChromeOS version.
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800532
533 Args:
534 board: ChromeOS board name
535 version: ChromeOS version number in short or full format
536
537 Returns:
538 (milestone, version in short format)
539 """
540 if is_cros_short_version(version):
541 milestone = query_milestone_by_version(board, version)
542 short_version = version
543 else:
544 milestone, short_version = version_split(version)
545 return milestone, short_version
546
547
Zheng-Jie Chang127c3302019-09-10 17:17:04 +0800548def extract_major_version(version):
549 """Converts a version to its major version.
550
551 Args:
Kuang-che Wu9501f342019-11-15 17:15:21 +0800552 version: ChromeOS version number or snapshot version
Zheng-Jie Chang127c3302019-09-10 17:17:04 +0800553
554 Returns:
555 major version number in string format
556 """
557 version = version_to_short(version)
558 m = re.match(r'^(\d+)\.\d+\.\d+$', version)
559 return m.group(1)
560
561
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800562def version_to_short(version):
Kuang-che Wubfc4a642018-04-19 11:54:08 +0800563 """Convert ChromeOS version number to short format.
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800564
565 Args:
566 version: ChromeOS version number in short or full format
567
568 Returns:
569 version number in short format
570 """
571 if is_cros_short_version(version):
572 return version
573 _, short_version = version_split(version)
574 return short_version
575
576
577def version_to_full(board, version):
Kuang-che Wubfc4a642018-04-19 11:54:08 +0800578 """Convert ChromeOS version number to full format.
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800579
580 Args:
581 board: ChromeOS board name
582 version: ChromeOS version number in short or full format
583
584 Returns:
585 version number in full format
586 """
Zheng-Jie Changc47af3a2019-11-11 17:28:58 +0800587 if is_cros_snapshot_version(version):
588 milestone, short_version, _ = snapshot_version_split(version)
589 return make_cros_full_version(milestone, short_version)
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800590 if is_cros_full_version(version):
591 return version
592 milestone = query_milestone_by_version(board, version)
Kuang-che Wu0205f052019-05-23 12:48:37 +0800593 assert milestone, 'incorrect board=%s or version=%s ?' % (board, version)
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800594 return make_cros_full_version(milestone, version)
595
596
Zheng-Jie Chang127c3302019-09-10 17:17:04 +0800597def list_snapshots_from_image_archive(board, major_version):
Kuang-che Wu9501f342019-11-15 17:15:21 +0800598 """List ChromeOS snapshot image available from gs://chromeos-image-archive.
Zheng-Jie Chang127c3302019-09-10 17:17:04 +0800599
600 Args:
601 board: ChromeOS board
602 major_version: ChromeOS major version
603
604 Returns:
605 list of (version, gs_path):
606 version: Chrome OS snapshot version
607 gs_path: gs path of test image
608 """
609
Zheng-Jie Chang43a08412019-12-05 17:05:45 +0800610 def extract_snapshot_id(result):
611 m = re.match(r'^R\d+-\d+\.\d+\.\d+-(\d+)', result[0])
612 assert m
613 return int(m.group(1))
614
Zheng-Jie Changa331b872020-01-06 17:09:32 +0800615 short_version = '%s.0.0' % major_version
616 milestone = query_milestone_by_version(board, short_version)
617 if not milestone:
618 milestone = '*'
619
Zheng-Jie Chang54020832020-04-21 15:52:48 +0800620 path = ('gs://chromeos-image-archive/{board}-snapshot/R{milestone}-'
Zheng-Jie Changa331b872020-01-06 17:09:32 +0800621 '{short_version}-*/image.zip')
Zheng-Jie Chang127c3302019-09-10 17:17:04 +0800622 result = []
623 output = gsutil_ls(
Zheng-Jie Changa331b872020-01-06 17:09:32 +0800624 path.format(
625 board=board, milestone=milestone, short_version=short_version),
Zheng-Jie Chang127c3302019-09-10 17:17:04 +0800626 ignore_errors=True)
627
Zheng-Jie Changa331b872020-01-06 17:09:32 +0800628 for gs_path in sorted(output):
629 m = re.match(r'^gs:\S+(R\d+-\d+\.\d+\.\d+-\d+)', gs_path)
Zheng-Jie Chang127c3302019-09-10 17:17:04 +0800630 if m:
631 snapshot_version = m.group(1)
Zheng-Jie Chang43a08412019-12-05 17:05:45 +0800632 # we should skip if there is duplicate snapshot
633 if result and result[-1][0] == snapshot_version:
634 continue
Zheng-Jie Chang1ae64c32020-03-10 14:08:28 +0800635
636 # b/151054108: snapshot version in [29288, 29439] is broken
637 _, _, snapshot_id = snapshot_version_split(snapshot_version)
638 if 29288 <= int(snapshot_id) <= 29439:
639 continue
640
Zheng-Jie Chang127c3302019-09-10 17:17:04 +0800641 result.append((snapshot_version, gs_path))
Zheng-Jie Chang43a08412019-12-05 17:05:45 +0800642
643 # sort by its snapshot_id
644 result.sort(key=extract_snapshot_id)
Zheng-Jie Chang127c3302019-09-10 17:17:04 +0800645 return result
646
647
Kuang-che Wu575dc442019-03-05 10:30:55 +0800648def list_prebuilt_from_image_archive(board):
649 """Lists ChromeOS prebuilt image available from gs://chromeos-image-archive.
650
651 gs://chromeos-image-archive contains only recent builds (in two years).
652 We prefer this function to list_prebuilt_from_chromeos_releases() because
653 - this is what "cros flash" supports directly.
654 - the paths have milestone information, so we don't need to do slow query
655 by ourselves.
656
657 Args:
658 board: ChromeOS board name
659
660 Returns:
661 list of (version, gs_path):
662 version: Chrome OS version in full format
663 gs_path: gs path of test image
664 """
665 result = []
666 for line in gsutil_ls(gs_archive_path.format(board=board)):
667 m = re.match(r'^gs:\S+(R\d+-\d+\.\d+\.\d+)', line)
668 if m:
669 full_version = m.group(1)
670 test_image = 'chromiumos_test_image.tar.xz'
671 assert line.endswith('/')
672 gs_path = line + test_image
673 result.append((full_version, gs_path))
674 return result
675
676
677def list_prebuilt_from_chromeos_releases(board):
678 """Lists ChromeOS versions available from gs://chromeos-releases.
679
680 gs://chromeos-releases contains more builds. However, 'cros flash' doesn't
681 support it.
682
683 Args:
684 board: ChromeOS board name
685
686 Returns:
687 list of (version, gs_path):
688 version: Chrome OS version in short format
689 gs_path: gs path of test image (with wildcard)
690 """
691 result = []
692 for line in gsutil_ls(
Kuang-che Wu80bf6a52019-05-31 12:48:06 +0800693 gs_release_path.format(
694 channel='*', boardpath=gs_release_boardpath(board), short_version=''),
Kuang-che Wu575dc442019-03-05 10:30:55 +0800695 ignore_errors=True):
696 m = re.match(r'gs:\S+/(\d+\.\d+\.\d+)/$', line)
697 if m:
698 short_version = m.group(1)
699 test_image = 'ChromeOS-test-R*-{short_version}-{board}.tar.xz'.format(
700 short_version=short_version, board=board)
701 gs_path = line + test_image
702 result.append((short_version, gs_path))
703 return result
704
705
Kuang-che Wue1808402020-01-06 20:27:45 +0800706def has_test_image(board, version):
707 if is_cros_snapshot_version(version):
708 return bool(query_snapshot_buildbucket_id(board, version))
709
710 full_version = version_to_full(board, version)
711 short_version = version_to_short(version)
712 paths = [
713 gs_archive_path.format(board=board) +
714 '/%s/chromiumos_test_image.tar.xz' % full_version,
715 gs_release_path.format(
716 channel='*',
717 boardpath=gs_release_boardpath(board),
718 short_version=short_version),
719 ]
720
721 for path in paths:
722 if gsutil_ls(path, ignore_errors=True):
723 return True
724 return False
725
726
Kuang-che Wu575dc442019-03-05 10:30:55 +0800727def list_chromeos_prebuilt_versions(board,
728 old,
729 new,
730 only_good_build=True,
Zheng-Jie Chang127c3302019-09-10 17:17:04 +0800731 include_older_build=True,
732 use_snapshot=False):
Kuang-che Wu575dc442019-03-05 10:30:55 +0800733 """Lists ChromeOS version numbers with prebuilt between given range
734
735 Args:
736 board: ChromeOS board name
737 old: start version (inclusive)
738 new: end version (inclusive)
739 only_good_build: only if test image is available
740 include_older_build: include prebuilt in gs://chromeos-releases
Zheng-Jie Chang127c3302019-09-10 17:17:04 +0800741 use_snapshot: return snapshot versions if found
Kuang-che Wu575dc442019-03-05 10:30:55 +0800742
743 Returns:
744 list of sorted version numbers (in full format) between [old, new] range
745 (inclusive).
746 """
Zheng-Jie Changaea4fba2020-02-17 17:12:09 +0800747 old_short = version_to_short(old)
748 new_short = version_to_short(new)
Kuang-che Wu575dc442019-03-05 10:30:55 +0800749
Zheng-Jie Chang127c3302019-09-10 17:17:04 +0800750 rev_map = {
751 } # dict: short version -> list of (short/full or snapshot version, gs path)
Kuang-che Wu575dc442019-03-05 10:30:55 +0800752 for full_version, gs_path in list_prebuilt_from_image_archive(board):
753 short_version = version_to_short(full_version)
Zheng-Jie Chang127c3302019-09-10 17:17:04 +0800754 rev_map[short_version] = [(full_version, gs_path)]
Kuang-che Wu575dc442019-03-05 10:30:55 +0800755
Zheng-Jie Changaea4fba2020-02-17 17:12:09 +0800756 if include_older_build and old_short not in rev_map:
Kuang-che Wu575dc442019-03-05 10:30:55 +0800757 for short_version, gs_path in list_prebuilt_from_chromeos_releases(board):
758 if short_version not in rev_map:
Zheng-Jie Chang127c3302019-09-10 17:17:04 +0800759 rev_map[short_version] = [(short_version, gs_path)]
760
761 if use_snapshot:
762 for major_version in range(
763 int(extract_major_version(old)),
764 int(extract_major_version(new)) + 1):
765 short_version = '%s.0.0' % major_version
Zheng-Jie Changc47af3a2019-11-11 17:28:58 +0800766 next_short_version = '%s.0.0' % (major_version + 1)
Zheng-Jie Changb8697042019-10-29 16:03:26 +0800767 # If current version is smaller than cutover, ignore it as it might not
768 # contain enough information for continuing android and chrome bisection.
769 if not util.is_version_lesseq(snapshot_cutover_version, short_version):
770 continue
Zheng-Jie Changc47af3a2019-11-11 17:28:58 +0800771
772 # Given the fact that snapshots are images between two release versions.
773 # Adding snapshots of 12345.0.0 should be treated as adding commits
774 # between [12345.0.0, 12346.0.0).
775 # So in the following lines we check two facts:
776 # 1) If 12346.0.0(next_short_version) is a version between old and new
Zheng-Jie Changaea4fba2020-02-17 17:12:09 +0800777 if not util.is_direct_relative_version(next_short_version, old_short):
Zheng-Jie Changc47af3a2019-11-11 17:28:58 +0800778 continue
Zheng-Jie Changaea4fba2020-02-17 17:12:09 +0800779 if not util.is_direct_relative_version(next_short_version, new_short):
Zheng-Jie Changc47af3a2019-11-11 17:28:58 +0800780 continue
781 # 2) If 12345.0.0(short_version) is a version between old and new
Zheng-Jie Changaea4fba2020-02-17 17:12:09 +0800782 if not util.is_direct_relative_version(short_version, old_short):
Zheng-Jie Chang127c3302019-09-10 17:17:04 +0800783 continue
Zheng-Jie Changaea4fba2020-02-17 17:12:09 +0800784 if not util.is_direct_relative_version(short_version, new_short):
Zheng-Jie Chang127c3302019-09-10 17:17:04 +0800785 continue
Zheng-Jie Changc47af3a2019-11-11 17:28:58 +0800786
Zheng-Jie Chang127c3302019-09-10 17:17:04 +0800787 snapshots = list_snapshots_from_image_archive(board, str(major_version))
788 if snapshots:
789 # if snapshots found, we can append them after the release version,
790 # so the prebuilt image list of this version will be
791 # release_image, snapshot1, snapshot2,...
Zheng-Jie Changb8697042019-10-29 16:03:26 +0800792 if short_version not in rev_map:
793 rev_map[short_version] = []
Zheng-Jie Chang127c3302019-09-10 17:17:04 +0800794 rev_map[short_version] += snapshots
Kuang-che Wu575dc442019-03-05 10:30:55 +0800795
796 result = []
797 for rev in sorted(rev_map, key=util.version_key_func):
Zheng-Jie Changaea4fba2020-02-17 17:12:09 +0800798 if not util.is_direct_relative_version(new_short, rev):
Kuang-che Wu575dc442019-03-05 10:30:55 +0800799 continue
Zheng-Jie Changaea4fba2020-02-17 17:12:09 +0800800 if not util.is_version_lesseq(old_short, rev):
Kuang-che Wu575dc442019-03-05 10:30:55 +0800801 continue
Zheng-Jie Changaea4fba2020-02-17 17:12:09 +0800802 if not util.is_version_lesseq(rev, new_short):
Kuang-che Wu575dc442019-03-05 10:30:55 +0800803 continue
804
Zheng-Jie Chang127c3302019-09-10 17:17:04 +0800805 for version, gs_path in rev_map[rev]:
Kuang-che Wu575dc442019-03-05 10:30:55 +0800806
Zheng-Jie Chang127c3302019-09-10 17:17:04 +0800807 # version_to_full() and gsutil_ls() may take long time if versions are a
808 # lot. This is acceptable because we usually bisect only short range.
Kuang-che Wu575dc442019-03-05 10:30:55 +0800809
Zheng-Jie Changa331b872020-01-06 17:09:32 +0800810 if only_good_build and not is_cros_snapshot_version(version):
Zheng-Jie Chang127c3302019-09-10 17:17:04 +0800811 gs_result = gsutil_ls(gs_path, ignore_errors=True)
812 if not gs_result:
813 logger.warning('%s is not a good build, ignore', version)
814 continue
815 assert len(gs_result) == 1
816 m = re.search(r'(R\d+-\d+\.\d+\.\d+)', gs_result[0])
817 if not m:
818 logger.warning('format of image path is unexpected: %s', gs_result[0])
819 continue
Zheng-Jie Changa331b872020-01-06 17:09:32 +0800820 version = m.group(1)
Zheng-Jie Chang127c3302019-09-10 17:17:04 +0800821 elif is_cros_short_version(version):
822 version = version_to_full(board, version)
Kuang-che Wu575dc442019-03-05 10:30:55 +0800823
Zheng-Jie Changaea4fba2020-02-17 17:12:09 +0800824 if is_cros_version_lesseq(old, version) and is_cros_version_lesseq(
825 version, new):
826 result.append(version)
Kuang-che Wu575dc442019-03-05 10:30:55 +0800827
828 return result
829
830
Zheng-Jie Chang127c3302019-09-10 17:17:04 +0800831def prepare_snapshot_image(chromeos_root, board, snapshot_version):
832 """Prepare chromeos snapshot image.
833
834 Args:
835 chromeos_root: chromeos tree root
836 board: ChromeOS board name
837 snapshot_version: ChromeOS snapshot version number
838
839 Returns:
840 local file path of test image relative to chromeos_root
841 """
842 assert is_cros_snapshot_version(snapshot_version)
843 milestone, short_version, snapshot_id = snapshot_version_split(
844 snapshot_version)
845 full_version = make_cros_full_version(milestone, short_version)
846 tmp_dir = os.path.join(
847 chromeos_root, 'tmp',
848 'ChromeOS-test-%s-%s-%s' % (full_version, board, snapshot_id))
849 if not os.path.exists(tmp_dir):
850 os.makedirs(tmp_dir)
851
Zheng-Jie Chang54020832020-04-21 15:52:48 +0800852 gs_path = ('gs://chromeos-image-archive/{board}-snapshot/' +
Zheng-Jie Chang127c3302019-09-10 17:17:04 +0800853 '{snapshot_version}-*/image.zip')
854 gs_path = gs_path.format(board=board, snapshot_version=snapshot_version)
855
Zheng-Jie Changc47af3a2019-11-11 17:28:58 +0800856 full_path = os.path.join(tmp_dir, test_image_filename)
857 rel_path = os.path.relpath(full_path, chromeos_root)
858 if os.path.exists(full_path):
859 return rel_path
860
Zheng-Jie Chang127c3302019-09-10 17:17:04 +0800861 files = gsutil_ls(gs_path, ignore_errors=True)
Zheng-Jie Changeb7308f2019-12-05 14:25:05 +0800862 if len(files) >= 1:
Zheng-Jie Chang127c3302019-09-10 17:17:04 +0800863 gs_path = files[0]
864 gsutil('cp', gs_path, tmp_dir)
Zheng-Jie Chang127c3302019-09-10 17:17:04 +0800865 util.check_call(
866 'unzip', '-j', 'image.zip', test_image_filename, cwd=tmp_dir)
867 os.remove(os.path.join(tmp_dir, 'image.zip'))
Zheng-Jie Changc47af3a2019-11-11 17:28:58 +0800868 return rel_path
Zheng-Jie Chang127c3302019-09-10 17:17:04 +0800869
Zheng-Jie Changc47af3a2019-11-11 17:28:58 +0800870 assert False
Kuang-che Wua7ddf9b2019-11-25 18:59:57 +0800871 return None
Zheng-Jie Chang127c3302019-09-10 17:17:04 +0800872
873
Kuang-che Wu28980b22019-07-31 19:51:45 +0800874def prepare_prebuilt_image(chromeos_root, board, version):
Kuang-che Wubfc4a642018-04-19 11:54:08 +0800875 """Prepare chromeos prebuilt image.
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800876
Kuang-che Wubfc4a642018-04-19 11:54:08 +0800877 It searches for xbuddy image which "cros flash" can use, or fetch image to
878 local disk.
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800879
880 Args:
Kuang-che Wu28980b22019-07-31 19:51:45 +0800881 chromeos_root: chromeos tree root
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800882 board: ChromeOS board name
Kuang-che Wubfc4a642018-04-19 11:54:08 +0800883 version: ChromeOS version number in short or full format
884
885 Returns:
Kuang-che Wu28980b22019-07-31 19:51:45 +0800886 xbuddy path or file path (relative to chromeos_root)
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800887 """
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800888 assert is_cros_version(version)
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800889 full_version = version_to_full(board, version)
890 short_version = version_to_short(full_version)
891
892 image_path = None
893 gs_path = gs_archive_path.format(board=board) + '/' + full_version
894 if gsutil_ls('-d', gs_path, ignore_errors=True):
895 image_path = 'xbuddy://remote/{board}/{full_version}/test'.format(
896 board=board, full_version=full_version)
897 else:
Kuang-che Wu28980b22019-07-31 19:51:45 +0800898 tmp_dir = os.path.join(chromeos_root, 'tmp',
899 'ChromeOS-test-%s-%s' % (full_version, board))
Zheng-Jie Changc47af3a2019-11-11 17:28:58 +0800900 full_path = os.path.join(tmp_dir, test_image_filename)
901 rel_path = os.path.relpath(full_path, chromeos_root)
902 if os.path.exists(full_path):
903 return rel_path
904
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800905 if not os.path.exists(tmp_dir):
906 os.makedirs(tmp_dir)
907 # gs://chromeos-releases may have more old images than
Kuang-che Wu4fe945b2018-03-31 16:46:38 +0800908 # gs://chromeos-image-archive, but 'cros flash' doesn't support it. We have
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800909 # to fetch the image by ourselves
910 for channel in ['canary', 'dev', 'beta', 'stable']:
911 fn = 'ChromeOS-test-{full_version}-{board}.tar.xz'.format(
912 full_version=full_version, board=board)
913 gs_path = gs_release_path.format(
Kuang-che Wu80bf6a52019-05-31 12:48:06 +0800914 channel=channel,
915 boardpath=gs_release_boardpath(board),
916 short_version=short_version)
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800917 gs_path += '/' + fn
918 if gsutil_ls(gs_path, ignore_errors=True):
919 # TODO(kcwu): delete tmp
920 gsutil('cp', gs_path, tmp_dir)
921 util.check_call('tar', 'Jxvf', fn, cwd=tmp_dir)
Zheng-Jie Changc47af3a2019-11-11 17:28:58 +0800922 image_path = os.path.relpath(full_path, chromeos_root)
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800923 break
924
925 assert image_path
Kuang-che Wubfc4a642018-04-19 11:54:08 +0800926 return image_path
927
928
929def cros_flash(chromeos_root,
930 host,
931 board,
932 image_path,
933 version=None,
934 clobber_stateful=False,
Kuang-che Wu155fb6e2018-11-29 16:00:41 +0800935 disable_rootfs_verification=True):
Kuang-che Wubfc4a642018-04-19 11:54:08 +0800936 """Flash a DUT with given ChromeOS image.
937
938 This is implemented by 'cros flash' command line.
939
940 Args:
941 chromeos_root: use 'cros flash' of which chromeos tree
942 host: DUT address
943 board: ChromeOS board name
Kuang-che Wu28980b22019-07-31 19:51:45 +0800944 image_path: chromeos image xbuddy path or file path. For relative
945 path, it should be relative to chromeos_root.
Kuang-che Wubfc4a642018-04-19 11:54:08 +0800946 version: ChromeOS version in short or full format
947 clobber_stateful: Clobber stateful partition when performing update
948 disable_rootfs_verification: Disable rootfs verification after update
949 is completed
Kuang-che Wu414d67f2019-05-28 11:28:57 +0800950
951 Raises:
952 errors.ExternalError: cros flash failed
Kuang-che Wubfc4a642018-04-19 11:54:08 +0800953 """
954 logger.info('cros_flash %s %s %s %s', host, board, version, image_path)
955
956 # Reboot is necessary because sometimes previous 'cros flash' failed and
957 # entered a bad state.
958 reboot(host)
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800959
Kuang-che Wu28980b22019-07-31 19:51:45 +0800960 # Handle relative path.
961 if '://' not in image_path and not os.path.isabs(image_path):
962 assert os.path.exists(os.path.join(chromeos_root, image_path))
963 image_path = os.path.join(chromeos_root_inside_chroot, image_path)
964
Kuang-che Wuf3d03ca2019-03-11 17:31:40 +0800965 args = [
966 '--debug', '--no-ping', '--send-payload-in-parallel', host, image_path
967 ]
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800968 if clobber_stateful:
969 args.append('--clobber-stateful')
970 if disable_rootfs_verification:
971 args.append('--disable-rootfs-verification')
972
Kuang-che Wu414d67f2019-05-28 11:28:57 +0800973 try:
974 cros_sdk(chromeos_root, 'cros', 'flash', *args)
975 except subprocess.CalledProcessError:
976 raise errors.ExternalError('cros flash failed')
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800977
Kuang-che Wubfc4a642018-04-19 11:54:08 +0800978 if version:
979 # In the past, cros flash may fail with returncode=0
980 # So let's have an extra check.
Zheng-Jie Chang127c3302019-09-10 17:17:04 +0800981 if is_cros_snapshot_version(version):
982 builder_path = query_dut_lsb_release(host).get(
983 'CHROMEOS_RELEASE_BUILDER_PATH', '')
Zheng-Jie Chang54020832020-04-21 15:52:48 +0800984 expect_prefix = '%s-snapshot/%s-' % (board, version)
Zheng-Jie Chang127c3302019-09-10 17:17:04 +0800985 if not builder_path.startswith(expect_prefix):
986 raise errors.ExternalError(
987 'although cros flash succeeded, the OS builder path is '
988 'unexpected: actual=%s expect=%s' % (builder_path, expect_prefix))
989 else:
990 expect_version = version_to_short(version)
991 dut_version = query_dut_short_version(host)
992 if dut_version != expect_version:
993 raise errors.ExternalError(
994 'although cros flash succeeded, the OS version is unexpected: '
995 'actual=%s expect=%s' % (dut_version, expect_version))
Kuang-che Wu414d67f2019-05-28 11:28:57 +0800996
Kuang-che Wu4a81ea72019-10-05 15:35:17 +0800997 # "cros flash" may terminate successfully but the DUT starts self-repairing
Kuang-che Wu414d67f2019-05-28 11:28:57 +0800998 # (b/130786578), so it's necessary to do sanity check.
999 if not is_good_dut(host):
1000 raise errors.ExternalError(
1001 'although cros flash succeeded, the DUT is in bad state')
1002
1003
1004def cros_flash_with_retry(chromeos_root,
1005 host,
1006 board,
1007 image_path,
1008 version=None,
1009 clobber_stateful=False,
1010 disable_rootfs_verification=True,
1011 repair_callback=None):
1012 # 'cros flash' is not 100% reliable, retry if necessary.
1013 for attempt in range(2):
1014 if attempt > 0:
1015 logger.info('will retry 60 seconds later')
1016 time.sleep(60)
1017
1018 try:
1019 cros_flash(
1020 chromeos_root,
1021 host,
1022 board,
1023 image_path,
1024 version=version,
1025 clobber_stateful=clobber_stateful,
1026 disable_rootfs_verification=disable_rootfs_verification)
1027 return True
1028 except errors.ExternalError:
1029 logger.exception('cros flash failed')
1030 if repair_callback and not repair_callback(host):
1031 logger.warning('not repaired, assume it is harmless')
1032 continue
1033 return False
Kuang-che Wu2ea804f2017-11-28 17:11:41 +08001034
1035
1036def version_info(board, version):
1037 """Query subcomponents version info of given version of ChromeOS
1038
1039 Args:
1040 board: ChromeOS board name
1041 version: ChromeOS version number in short or full format
1042
1043 Returns:
1044 dict of component and version info, including (if available):
1045 cros_short_version: ChromeOS version
1046 cros_full_version: ChromeOS version
1047 milestone: milestone of ChromeOS
1048 cr_version: Chrome version
Kuang-che Wu708310b2018-03-28 17:24:34 +08001049 android_build_id: Android build id
Kuang-che Wu2ea804f2017-11-28 17:11:41 +08001050 android_branch: Android branch, in format like 'git_nyc-mr1-arc'
1051 """
Zheng-Jie Chang127c3302019-09-10 17:17:04 +08001052 if is_cros_snapshot_version(version):
Zheng-Jie Chang2b6d1472019-11-13 12:40:17 +08001053 api = buildbucket_util.BuildbucketApi()
Zheng-Jie Changb8697042019-10-29 16:03:26 +08001054 milestone, short_version, _ = snapshot_version_split(version)
1055 buildbucket_id = query_snapshot_buildbucket_id(board, version)
Zheng-Jie Chang597c38b2020-03-12 22:24:10 +08001056 data = api.get_build(int(buildbucket_id)).output.properties
Zheng-Jie Chang4fabff62019-12-08 21:54:35 +08001057 target_versions = json_format.MessageToDict(data['target_versions'])
Zheng-Jie Chang127c3302019-09-10 17:17:04 +08001058 return {
Zheng-Jie Changb8697042019-10-29 16:03:26 +08001059 VERSION_KEY_MILESTONE: milestone,
1060 VERSION_KEY_CROS_FULL_VERSION: version,
1061 VERSION_KEY_CROS_SHORT_VERSION: short_version,
Zheng-Jie Chang2d1dd9b2019-12-07 23:30:20 +08001062 VERSION_KEY_CR_VERSION: target_versions.get('chromeVersion'),
1063 VERSION_KEY_ANDROID_BUILD_ID: target_versions.get('androidVersion'),
1064 VERSION_KEY_ANDROID_BRANCH: target_versions.get('androidBranchVersion'),
Zheng-Jie Chang127c3302019-09-10 17:17:04 +08001065 }
Kuang-che Wu2ea804f2017-11-28 17:11:41 +08001066 info = {}
1067 full_version = version_to_full(board, version)
1068
1069 # Some boards may have only partial-metadata.json but no metadata.json.
1070 # e.g. caroline R60-9462.0.0
1071 # Let's try both.
1072 metadata = None
1073 for metadata_filename in ['metadata.json', 'partial-metadata.json']:
Kuang-che Wu0768b972019-10-05 15:18:59 +08001074 path = gs_archive_path.format(
1075 board=board) + '/%s/%s' % (full_version, metadata_filename)
Kuang-che Wu2ea804f2017-11-28 17:11:41 +08001076 metadata = gsutil('cat', path, ignore_errors=True)
1077 if metadata:
1078 o = json.loads(metadata)
1079 v = o['version']
1080 board_metadata = o['board-metadata'][board]
1081 info.update({
1082 VERSION_KEY_CROS_SHORT_VERSION: v['platform'],
1083 VERSION_KEY_CROS_FULL_VERSION: v['full'],
1084 VERSION_KEY_MILESTONE: v['milestone'],
1085 VERSION_KEY_CR_VERSION: v['chrome'],
1086 })
1087
1088 if 'android' in v:
Kuang-che Wu708310b2018-03-28 17:24:34 +08001089 info[VERSION_KEY_ANDROID_BUILD_ID] = v['android']
Kuang-che Wu2ea804f2017-11-28 17:11:41 +08001090 if 'android-branch' in v: # this appears since R58-9317.0.0
1091 info[VERSION_KEY_ANDROID_BRANCH] = v['android-branch']
1092 elif 'android-container-branch' in board_metadata:
1093 info[VERSION_KEY_ANDROID_BRANCH] = v['android-container-branch']
1094 break
1095 else:
1096 logger.error('Failed to read metadata from gs://chromeos-image-archive')
1097 logger.error(
1098 'Note, so far no quick way to look up version info for too old builds')
1099
1100 return info
Kuang-che Wu848b1af2018-02-01 20:59:36 +08001101
1102
1103def query_chrome_version(board, version):
Kuang-che Wubfc4a642018-04-19 11:54:08 +08001104 """Queries chrome version of chromeos build.
Kuang-che Wu848b1af2018-02-01 20:59:36 +08001105
1106 Args:
1107 board: ChromeOS board name
1108 version: ChromeOS version number in short or full format
1109
1110 Returns:
1111 Chrome version number
1112 """
1113 info = version_info(board, version)
1114 return info['cr_version']
Kuang-che Wu708310b2018-03-28 17:24:34 +08001115
1116
1117def query_android_build_id(board, rev):
1118 info = version_info(board, rev)
1119 rev = info['android_build_id']
1120 return rev
1121
1122
1123def query_android_branch(board, rev):
1124 info = version_info(board, rev)
1125 rev = info['android_branch']
1126 return rev
Kuang-che Wubfc4a642018-04-19 11:54:08 +08001127
1128
Kuang-che Wu3eb6b502018-06-06 16:15:18 +08001129def guess_chrome_version(board, rev):
1130 """Guess chrome version number.
1131
1132 Args:
1133 board: chromeos board name
1134 rev: chrome or chromeos version
1135
1136 Returns:
1137 chrome version number
1138 """
1139 if is_cros_version(rev):
1140 assert board, 'need to specify BOARD for cros version'
1141 rev = query_chrome_version(board, rev)
1142 assert cr_util.is_chrome_version(rev)
1143
1144 return rev
1145
1146
Kuang-che Wubfc4a642018-04-19 11:54:08 +08001147def is_inside_chroot():
1148 """Returns True if we are inside chroot."""
1149 return os.path.exists('/etc/cros_chroot_version')
1150
1151
Kuang-che Wuad5ac7b2020-02-20 19:02:52 +08001152def convert_path_outside_chroot(chromeos_root, path):
1153 """Converts path in chroot to outside.
1154
1155 Args:
1156 chromeos_root: chromeos tree root
1157 path: path inside chroot; support starting with '~/'
1158
1159 Returns:
1160 The corresponding path outside chroot assuming the chroot is mounted
1161 """
1162 if path.startswith('~/'):
1163 path = path.replace('~', '/home/' + os.environ['USER'])
1164 assert '~' not in path, 'tilde (~) character is not fully supported'
1165
1166 assert os.path.isabs(path)
1167 assert path[0] == os.sep
1168 return os.path.join(chromeos_root, 'chroot', path[1:])
1169
1170
Kuang-che Wubfc4a642018-04-19 11:54:08 +08001171def cros_sdk(chromeos_root, *args, **kwargs):
1172 """Run commands inside chromeos chroot.
1173
1174 Args:
1175 chromeos_root: chromeos tree root
1176 *args: command to run
1177 **kwargs:
Kuang-che Wud4603d72018-11-29 17:51:21 +08001178 chrome_root: pass to cros_sdk; mount this path into the SDK chroot
Kuang-che Wubfc4a642018-04-19 11:54:08 +08001179 env: (dict) environment variables for the command
Kuang-che Wubcafc552019-08-15 15:27:02 +08001180 log_stdout: Whether write the stdout output of the child process to log.
Kuang-che Wubfc4a642018-04-19 11:54:08 +08001181 stdin: standard input file handle for the command
Kuang-che Wu9890ce82018-07-07 15:14:10 +08001182 stderr_callback: Callback function for stderr. Called once per line.
Kuang-che Wua9a20bb2019-09-05 22:24:04 +08001183 goma_dir: Goma installed directory to mount into the chroot
Kuang-che Wubfc4a642018-04-19 11:54:08 +08001184 """
1185 envs = []
1186 for k, v in kwargs.get('env', {}).items():
1187 assert re.match(r'^[A-Za-z_][A-Za-z0-9_]*$', k)
1188 envs.append('%s=%s' % (k, v))
1189
1190 # Use --no-ns-pid to prevent cros_sdk change our pgid, otherwise subsequent
1191 # commands would be considered as background process.
Kuang-che Wu399d4662019-06-06 15:23:37 +08001192 prefix = ['chromite/bin/cros_sdk', '--no-ns-pid']
Kuang-che Wud4603d72018-11-29 17:51:21 +08001193
1194 if kwargs.get('chrome_root'):
Kuang-che Wu399d4662019-06-06 15:23:37 +08001195 prefix += ['--chrome_root', kwargs['chrome_root']]
Kuang-che Wua9a20bb2019-09-05 22:24:04 +08001196 if kwargs.get('goma_dir'):
1197 prefix += ['--goma_dir', kwargs['goma_dir']]
Kuang-che Wud4603d72018-11-29 17:51:21 +08001198
Kuang-che Wu399d4662019-06-06 15:23:37 +08001199 prefix += envs + ['--']
Kuang-che Wud4603d72018-11-29 17:51:21 +08001200
Kuang-che Wu399d4662019-06-06 15:23:37 +08001201 # In addition to the output of command we are interested, cros_sdk may
1202 # generate its own messages. For example, chroot creation messages if we run
1203 # cros_sdk the first time.
1204 # This is the hack to run dummy command once, so we can get clean output for
1205 # the command we are interested.
1206 cmd = prefix + ['true']
1207 util.check_call(*cmd, cwd=chromeos_root)
1208
1209 cmd = prefix + list(args)
Kuang-che Wu9890ce82018-07-07 15:14:10 +08001210 return util.check_output(
1211 *cmd,
1212 cwd=chromeos_root,
Kuang-che Wubcafc552019-08-15 15:27:02 +08001213 log_stdout=kwargs.get('log_stdout', True),
Kuang-che Wu9890ce82018-07-07 15:14:10 +08001214 stdin=kwargs.get('stdin'),
1215 stderr_callback=kwargs.get('stderr_callback'))
Kuang-che Wubfc4a642018-04-19 11:54:08 +08001216
1217
Kuang-che Wua9a20bb2019-09-05 22:24:04 +08001218def create_chroot(chromeos_root):
Kuang-che Wuad5ac7b2020-02-20 19:02:52 +08001219 """Creates ChromeOS chroot if necessary.
Kuang-che Wua9a20bb2019-09-05 22:24:04 +08001220
1221 Args:
1222 chromeos_root: chromeos tree root
1223 """
1224 if os.path.exists(os.path.join(chromeos_root, 'chroot')):
1225 return
1226 if os.path.exists(os.path.join(chromeos_root, 'chroot.img')):
1227 return
1228
1229 util.check_output('chromite/bin/cros_sdk', '--create', cwd=chromeos_root)
1230
1231
Kuang-che Wuad5ac7b2020-02-20 19:02:52 +08001232def mount_chroot(chromeos_root):
1233 """Creates ChromeOS chroot if necessary.
1234
1235 Args:
1236 chromeos_root: chromeos tree root
1237 """
1238 # An arbitrary file must exist in chroot.
1239 path = convert_path_outside_chroot(chromeos_root, '/bin/ls')
1240
1241 # Not created or mounted yet.
1242 if not os.path.exists(path):
1243 create_chroot(chromeos_root)
1244 # After this command, the chroot is mounted.
1245 cros_sdk(chromeos_root, 'true')
1246 assert os.path.exists(path)
1247
1248
1249def copy_into_chroot(chromeos_root, src, dst, overwrite=True):
Kuang-che Wubfc4a642018-04-19 11:54:08 +08001250 """Copies file into chromeos chroot.
1251
Kuang-che Wuad5ac7b2020-02-20 19:02:52 +08001252 The side effect is chroot created and mounted.
1253
Kuang-che Wubfc4a642018-04-19 11:54:08 +08001254 Args:
1255 chromeos_root: chromeos tree root
1256 src: path outside chroot
1257 dst: path inside chroot
Kuang-che Wuad5ac7b2020-02-20 19:02:52 +08001258 overwrite: overwrite if dst alreadys exists
Kuang-che Wubfc4a642018-04-19 11:54:08 +08001259 """
Kuang-che Wuad5ac7b2020-02-20 19:02:52 +08001260 mount_chroot(chromeos_root)
1261 src = os.path.expanduser(src)
1262 dst_outside = convert_path_outside_chroot(chromeos_root, dst)
1263 if not overwrite and os.path.exists(dst_outside):
1264 return
1265
1266 # Haven't support directory or special files yet.
1267 assert os.path.isfile(src)
1268 assert os.path.isfile(dst_outside) or not os.path.exists(dst_outside)
1269
1270 dirname = os.path.dirname(dst_outside)
1271 if not os.path.exists(dirname):
1272 os.makedirs(dirname)
1273 shutil.copy(src, dst_outside)
Kuang-che Wubfc4a642018-04-19 11:54:08 +08001274
1275
Kuang-che Wuad5ac7b2020-02-20 19:02:52 +08001276def prepare_chroot(chromeos_root):
1277 mount_chroot(chromeos_root)
Kuang-che Wubfc4a642018-04-19 11:54:08 +08001278
Kuang-che Wuad5ac7b2020-02-20 19:02:52 +08001279 # Work around b/149077936:
1280 # The creds file is copied into the chroot since 12866.0.0.
1281 # But earlier versions need this file as well because of cipd ACL change.
1282 creds_path = '~/.config/chrome_infra/auth/creds.json'
1283 if os.path.exists(os.path.expanduser(creds_path)):
1284 copy_into_chroot(chromeos_root, creds_path, creds_path, overwrite=False)
Kuang-che Wubfc4a642018-04-19 11:54:08 +08001285
1286
Kuang-che Wu9890ce82018-07-07 15:14:10 +08001287def check_if_need_recreate_chroot(stdout, stderr):
1288 """Analyze build log and determine if chroot should be recreated.
1289
1290 Args:
1291 stdout: stdout output of build
1292 stderr: stderr output of build
1293
1294 Returns:
1295 the reason if chroot needs recreated; None otherwise
1296 """
Kuang-che Wu74768d32018-09-07 12:03:24 +08001297 if re.search(
1298 r"The current version of portage supports EAPI '\d+'. "
Kuang-che Wuae6824b2019-08-27 22:20:01 +08001299 'You must upgrade', stderr):
Kuang-che Wu9890ce82018-07-07 15:14:10 +08001300 return 'EAPI version mismatch'
1301
Kuang-che Wu5ac81322018-11-26 14:04:06 +08001302 if 'Chroot is too new. Consider running:' in stderr:
1303 return 'chroot version is too new'
1304
1305 # old message before Oct 2018
Kuang-che Wu9890ce82018-07-07 15:14:10 +08001306 if 'Chroot version is too new. Consider running cros_sdk --replace' in stderr:
1307 return 'chroot version is too new'
1308
Kuang-che Wu6fe987f2018-08-28 15:24:20 +08001309 # https://groups.google.com/a/chromium.org/forum/#!msg/chromium-os-dev/uzwT5APspB4/NFakFyCIDwAJ
1310 if "undefined reference to 'std::__1::basic_string" in stdout:
1311 return 'might be due to compiler change'
1312
Kuang-che Wu94e3b452019-11-21 12:49:18 +08001313 # Detect failures due to file collisions.
1314 # For example, kernel uprev from 3.x to 4.x, they are two separate packages
1315 # and conflict with each other. Other possible cases are package renaming or
1316 # refactoring. Let's recreate chroot to work around them.
1317 if 'Detected file collision' in stdout:
1318 # Using wildcard between words because the text wraps to the next line
1319 # depending on length of package name and each line is prefixed with
1320 # package name.
1321 # Using ".{,100}" instead of ".*" to prevent regex matching time explodes
1322 # exponentially. 100 is chosen arbitrarily. It should be longer than any
1323 # package name (65 now).
1324 m = re.search(
1325 r'Package (\S+).{,100}NOT.{,100}merged.{,100}'
1326 r'due.{,100}to.{,100}file.{,100}collisions', stdout, re.S)
1327 if m:
1328 return 'failed to install package due to file collision: ' + m.group(1)
Kuang-che Wu356c3522019-11-19 16:11:05 +08001329
Kuang-che Wu9890ce82018-07-07 15:14:10 +08001330 return None
1331
1332
Kuang-che Wua9a20bb2019-09-05 22:24:04 +08001333def build_packages(chromeos_root,
1334 board,
1335 chrome_root=None,
1336 goma_dir=None,
1337 afdo_use=False):
Kuang-che Wu28980b22019-07-31 19:51:45 +08001338 """Build ChromeOS packages.
Kuang-che Wubfc4a642018-04-19 11:54:08 +08001339
1340 Args:
1341 chromeos_root: chromeos tree root
1342 board: ChromeOS board name
Kuang-che Wua9a20bb2019-09-05 22:24:04 +08001343 chrome_root: Chrome tree root. If specified, build chrome using the
1344 provided tree
1345 goma_dir: Goma installed directory to mount into the chroot. If specified,
1346 build chrome with goma.
1347 afdo_use: build chrome with AFDO optimization
Kuang-che Wubfc4a642018-04-19 11:54:08 +08001348 """
Zheng-Jie Changffd49462019-12-16 12:15:18 +08001349
1350 def has_build_package_argument(argument):
1351 stderr_lines = []
1352 try:
1353 util.check_call(
1354 'src/scripts/build_packages',
1355 '--help',
1356 cwd=chromeos_root,
1357 stderr_callback=stderr_lines.append)
1358 except subprocess.CalledProcessError:
1359 help_output = ''.join(stderr_lines)
1360 return '--[no]%s' % argument in help_output
1361
Kuang-che Wua9a20bb2019-09-05 22:24:04 +08001362 common_env = {
1363 'USE': '-cros-debug chrome_internal',
1364 'FEATURES': 'separatedebug',
1365 }
Kuang-che Wu9890ce82018-07-07 15:14:10 +08001366 stderr_lines = []
1367 try:
Kuang-che Wufb553102018-10-02 18:14:29 +08001368 with locking.lock_file(locking.LOCK_FILE_FOR_BUILD):
Kuang-che Wua9a20bb2019-09-05 22:24:04 +08001369 env = common_env.copy()
1370 env['FEATURES'] += ' -separatedebug splitdebug'
Kuang-che Wufb553102018-10-02 18:14:29 +08001371 cros_sdk(
1372 chromeos_root,
Kuang-che Wu28980b22019-07-31 19:51:45 +08001373 './update_chroot',
1374 '--toolchain_boards',
Kuang-che Wufb553102018-10-02 18:14:29 +08001375 board,
Kuang-che Wua9a20bb2019-09-05 22:24:04 +08001376 env=env,
Kuang-che Wu28980b22019-07-31 19:51:45 +08001377 stderr_callback=stderr_lines.append)
Kuang-che Wua9a20bb2019-09-05 22:24:04 +08001378
1379 env = common_env.copy()
1380 cmd = [
Kuang-che Wu28980b22019-07-31 19:51:45 +08001381 './build_packages',
1382 '--board',
1383 board,
1384 '--withdev',
1385 '--noworkon',
1386 '--skip_chroot_upgrade',
1387 '--accept_licenses=@CHROMEOS',
Kuang-che Wua9a20bb2019-09-05 22:24:04 +08001388 ]
Zheng-Jie Changffd49462019-12-16 12:15:18 +08001389
1390 # `use_any_chrome` flag is default on and will force to use a chrome
1391 # prebuilt even if the version doesn't match.
1392
1393 # As this argument is landed in 12681, we should check if the argument
1394 # exists before adding this.
1395 if has_build_package_argument('use_any_chrome'):
1396 cmd.append('--nouse_any_chrome')
1397
Kuang-che Wua9a20bb2019-09-05 22:24:04 +08001398 if goma_dir:
1399 # Tell build_packages to start and stop goma
1400 cmd.append('--run_goma')
1401 env['USE_GOMA'] = 'true'
1402 if afdo_use:
1403 env['USE'] += ' afdo_use'
1404 cros_sdk(
1405 chromeos_root,
1406 *cmd,
1407 env=env,
1408 chrome_root=chrome_root,
1409 stderr_callback=stderr_lines.append,
1410 goma_dir=goma_dir)
Kuang-che Wu9890ce82018-07-07 15:14:10 +08001411 except subprocess.CalledProcessError as e:
1412 # Detect failures due to incompatibility between chroot and source tree. If
1413 # so, notify the caller to recreate chroot and retry.
1414 reason = check_if_need_recreate_chroot(e.output, ''.join(stderr_lines))
1415 if reason:
1416 raise NeedRecreateChrootException(reason)
1417
1418 # For other failures, don't know how to handle. Just bail out.
1419 raise
1420
Kuang-che Wu28980b22019-07-31 19:51:45 +08001421
1422def build_image(chromeos_root, board):
1423 """Build ChromeOS image.
1424
1425 Args:
1426 chromeos_root: chromeos tree root
1427 board: ChromeOS board name
1428
1429 Returns:
1430 image folder; relative to chromeos_root
1431 """
1432 stderr_lines = []
1433 try:
1434 with locking.lock_file(locking.LOCK_FILE_FOR_BUILD):
1435 cros_sdk(
1436 chromeos_root,
1437 './build_image',
1438 '--board',
1439 board,
1440 '--noenable_rootfs_verification',
1441 'test',
1442 env={
1443 'USE': '-cros-debug chrome_internal',
1444 'FEATURES': 'separatedebug',
1445 },
1446 stderr_callback=stderr_lines.append)
1447 except subprocess.CalledProcessError as e:
1448 # Detect failures due to incompatibility between chroot and source tree. If
1449 # so, notify the caller to recreate chroot and retry.
1450 reason = check_if_need_recreate_chroot(e.output, ''.join(stderr_lines))
1451 if reason:
1452 raise NeedRecreateChrootException(reason)
1453
1454 # For other failures, don't know how to handle. Just bail out.
1455 raise
1456
1457 image_symlink = os.path.join(chromeos_root, cached_images_dir, board,
1458 'latest')
1459 assert os.path.exists(image_symlink)
1460 image_name = os.readlink(image_symlink)
1461 image_folder = os.path.join(cached_images_dir, board, image_name)
1462 assert os.path.exists(
1463 os.path.join(chromeos_root, image_folder, test_image_filename))
1464 return image_folder
Kuang-che Wubfc4a642018-04-19 11:54:08 +08001465
1466
Kuang-che Wu23192ad2020-03-11 18:12:46 +08001467class AutotestControlInfo:
Kuang-che Wub9705bd2018-06-28 17:59:18 +08001468 """Parsed content of autotest control file.
1469
1470 Attributes:
1471 name: test name
1472 path: control file path
1473 variables: dict of top-level control variables. Sample keys: NAME, AUTHOR,
1474 DOC, ATTRIBUTES, DEPENDENCIES, etc.
1475 """
1476
1477 def __init__(self, path, variables):
1478 self.name = variables['NAME']
1479 self.path = path
1480 self.variables = variables
1481
1482
1483def parse_autotest_control_file(path):
1484 """Parses autotest control file.
1485
1486 This only parses simple top-level string assignments.
1487
1488 Returns:
1489 AutotestControlInfo object
1490 """
1491 variables = {}
Kuang-che Wua5723492019-11-25 20:59:34 +08001492 with open(path) as f:
1493 code = ast.parse(f.read())
Kuang-che Wub9705bd2018-06-28 17:59:18 +08001494 for stmt in code.body:
1495 # Skip if not simple "NAME = *" assignment.
1496 if not (isinstance(stmt, ast.Assign) and len(stmt.targets) == 1 and
1497 isinstance(stmt.targets[0], ast.Name)):
1498 continue
1499
1500 # Only support string value.
1501 if isinstance(stmt.value, ast.Str):
1502 variables[stmt.targets[0].id] = stmt.value.s
1503
1504 return AutotestControlInfo(path, variables)
1505
1506
1507def enumerate_autotest_control_files(autotest_dir):
1508 """Enumerate autotest control files.
1509
1510 Args:
1511 autotest_dir: autotest folder
1512
1513 Returns:
1514 list of paths to control files
1515 """
1516 # Where to find control files. Relative to autotest_dir.
1517 subpaths = [
1518 'server/site_tests',
1519 'client/site_tests',
1520 'server/tests',
1521 'client/tests',
1522 ]
1523
1524 blacklist = ['site-packages', 'venv', 'results', 'logs', 'containers']
1525 result = []
1526 for subpath in subpaths:
1527 path = os.path.join(autotest_dir, subpath)
1528 for root, dirs, files in os.walk(path):
1529
1530 for black in blacklist:
1531 if black in dirs:
1532 dirs.remove(black)
1533
1534 for filename in files:
1535 if filename == 'control' or filename.startswith('control.'):
1536 result.append(os.path.join(root, filename))
1537
1538 return result
1539
1540
1541def get_autotest_test_info(autotest_dir, test_name):
1542 """Get metadata of given test.
1543
1544 Args:
1545 autotest_dir: autotest folder
1546 test_name: test name
1547
1548 Returns:
1549 AutotestControlInfo object. None if test not found.
1550 """
1551 for control_file in enumerate_autotest_control_files(autotest_dir):
Zheng-Jie Chang1504a552020-02-20 16:38:57 +08001552 try:
1553 info = parse_autotest_control_file(control_file)
1554 except SyntaxError:
1555 logger.warning('%s is not parsable, ignore', control_file)
1556 continue
1557
Kuang-che Wub9705bd2018-06-28 17:59:18 +08001558 if info.name == test_name:
1559 return info
1560 return None
1561
1562
Zheng-Jie Chang868c1752020-01-21 14:42:41 +08001563def detect_branch_level(branch):
1564 """Given a branch name of manifest-internal, detect it's branch level.
1565
1566 level1: if ChromeOS version is x.0.0
1567 level2: if ChromeOS version is x.x.0
1568 level3: if ChromeOS version is x.x.x
1569 Where x is an non-zero integer.
1570
1571 Args:
1572 branch: branch name or ref name in manifest-internal
1573
1574 Returns:
1575 An integer indicates the branch level, or zero if not detectable.
1576 """
1577 level1 = r'^(refs\/\S+(\/\S+)?/)?master$'
1578 level2 = r'^\S+-(\d+)(\.0)?\.B$'
1579 level3 = r'^\S+-(\d+)\.(\d+)(\.0)?\.B$'
1580
1581 if re.match(level1, branch):
1582 return 1
1583 if re.match(level2, branch):
1584 return 2
1585 if re.match(level3, branch):
1586 return 3
1587 return 0
1588
1589
Zheng-Jie Chang5f9ae4e2020-02-07 14:26:06 +08001590def get_crosland_link(old, new):
1591 """Generates crosland link between two versions.
1592
1593 Args:
1594 old: ChromeOS version
1595 new: ChromeOS version
1596
1597 Returns:
1598 A crosland url.
1599 """
1600
1601 def version_to_url_parameter(ver):
1602 if is_cros_snapshot_version(ver):
1603 return snapshot_version_split(ver)[2]
1604 return version_to_short(ver)
1605
1606 old_parameter = version_to_url_parameter(old)
1607 new_parameter = version_to_url_parameter(new)
1608 return CROSLAND_URL_TEMPLATE % (old_parameter, new_parameter)
1609
1610
Kuang-che Wue4bae0b2018-07-19 12:10:14 +08001611class ChromeOSSpecManager(codechange.SpecManager):
1612 """Repo manifest related operations.
1613
1614 This class enumerates chromeos manifest files, parses them,
1615 and sync to disk state according to them.
1616 """
Kuang-che Wubfc4a642018-04-19 11:54:08 +08001617
1618 def __init__(self, config):
1619 self.config = config
Kuang-che Wue4bae0b2018-07-19 12:10:14 +08001620 self.manifest_dir = os.path.join(self.config['chromeos_root'], '.repo',
1621 'manifests')
Zheng-Jie Chang127c3302019-09-10 17:17:04 +08001622 self.manifest_internal_dir = os.path.join(self.config['chromeos_mirror'],
1623 'manifest-internal.git')
Kuang-che Wue4bae0b2018-07-19 12:10:14 +08001624 self.historical_manifest_git_dir = os.path.join(
Kuang-che Wud8fc9572018-10-03 21:00:41 +08001625 self.config['chromeos_mirror'], 'chromeos/manifest-versions.git')
Kuang-che Wue4bae0b2018-07-19 12:10:14 +08001626 if not os.path.exists(self.historical_manifest_git_dir):
Kuang-che Wue121fae2018-11-09 16:18:39 +08001627 raise errors.InternalError('Manifest snapshots should be cloned into %s' %
1628 self.historical_manifest_git_dir)
Kuang-che Wubfc4a642018-04-19 11:54:08 +08001629
Zheng-Jie Chang127c3302019-09-10 17:17:04 +08001630 def lookup_snapshot_manifest_revisions(self, old, new):
1631 """Get manifest commits between snapshot versions.
Kuang-che Wue4bae0b2018-07-19 12:10:14 +08001632
Zheng-Jie Chang127c3302019-09-10 17:17:04 +08001633 Returns:
1634 list of (timestamp, commit_id, snapshot_id):
1635 timestamp: integer unix timestamp
1636 commit_id: a string indicates commit hash
1637 snapshot_id: a string indicates snapshot id
1638 """
1639 assert is_cros_snapshot_version(old)
1640 assert is_cros_snapshot_version(new)
1641
1642 gs_path = (
Zheng-Jie Chang54020832020-04-21 15:52:48 +08001643 'gs://chromeos-image-archive/{board}-snapshot/{version}-*/image.zip')
Zheng-Jie Chang127c3302019-09-10 17:17:04 +08001644 # Try to guess the commit time of a snapshot manifest, it is usually a few
1645 # minutes different between snapshot manifest commit and image.zip
1646 # generate.
1647 try:
1648 old_timestamp = gsutil_stat_update_time(
1649 gs_path.format(board=self.config['board'], version=old)) - 86400
1650 except subprocess.CalledProcessError:
1651 old_timestamp = None
1652 try:
1653 new_timestamp = gsutil_stat_update_time(
1654 gs_path.format(board=self.config['board'], version=new)) + 86400
1655 # 1558657989 is snapshot_id 5982's commit time, this ensures every time
1656 # we can find snapshot 5982
1657 # snapshot_id <= 5982 has different commit message format, so we need
1658 # to identify its id in different ways, see below comment for more info.
1659 new_timestamp = max(new_timestamp, 1558657989 + 1)
1660 except subprocess.CalledProcessError:
1661 new_timestamp = None
1662 result = []
1663 _, _, old_snapshot_id = snapshot_version_split(old)
1664 _, _, new_snapshot_id = snapshot_version_split(new)
1665 repo = self.manifest_internal_dir
1666 path = 'snapshot.xml'
1667 branch = 'snapshot'
1668 commits = git_util.get_history(
1669 repo,
1670 path,
1671 branch,
1672 after=old_timestamp,
1673 before=new_timestamp,
1674 with_subject=True)
1675
1676 # Unfortunately, we can not identify snapshot_id <= 5982 from its commit
1677 # subject, as their subjects are all `Annealing manifest snapshot.`.
1678 # So instead we count the snapshot_id manually.
1679 count = 5982
1680 # There are two snapshot_id = 2633 in commit history, ignore the former
1681 # one.
1682 ignore_list = ['95c8526a7f0798d02f692010669dcbd5a152439a']
1683 # We examine the commits in reverse order as there are some testing
1684 # commits before snapshot_id=2, this method works fine after
1685 # snapshot 2, except snapshot 2633
1686 for commit in reversed(commits):
1687 msg = commit[2]
1688 if commit[1] in ignore_list:
1689 continue
1690
1691 match = re.match(r'^annealing manifest snapshot (\d+)', msg)
1692 if match:
1693 snapshot_id = match.group(1)
1694 elif 'Annealing manifest snapshot' in msg:
1695 snapshot_id = str(count)
1696 count -= 1
1697 else:
1698 continue
Zheng-Jie Chang34595ea2020-03-10 13:04:22 +08001699 # b/151054108: snapshot version in [29288, 29439] is broken
1700 if 29288 <= int(snapshot_id) <= 29439:
1701 continue
Zheng-Jie Chang127c3302019-09-10 17:17:04 +08001702 if int(old_snapshot_id) <= int(snapshot_id) <= int(new_snapshot_id):
1703 result.append((commit[0], commit[1], snapshot_id))
1704 # We find commits in reversed order, now reverse it again to chronological
1705 # order.
1706 return list(reversed(result))
1707
1708 def lookup_build_timestamp(self, rev):
1709 assert is_cros_full_version(rev) or is_cros_snapshot_version(rev)
1710 if is_cros_full_version(rev):
1711 return self.lookup_release_build_timestamp(rev)
Kuang-che Wua7ddf9b2019-11-25 18:59:57 +08001712 return self.lookup_snapshot_build_timestamp(rev)
Zheng-Jie Chang127c3302019-09-10 17:17:04 +08001713
1714 def lookup_snapshot_build_timestamp(self, rev):
1715 assert is_cros_snapshot_version(rev)
1716 return int(self.lookup_snapshot_manifest_revisions(rev, rev)[0][0])
1717
1718 def lookup_release_build_timestamp(self, rev):
1719 assert is_cros_full_version(rev)
Kuang-che Wubfc4a642018-04-19 11:54:08 +08001720 milestone, short_version = version_split(rev)
Kuang-che Wue4bae0b2018-07-19 12:10:14 +08001721 path = os.path.join('buildspecs', milestone, short_version + '.xml')
1722 try:
1723 timestamp = git_util.get_commit_time(self.historical_manifest_git_dir,
1724 'refs/heads/master', path)
1725 except ValueError:
Kuang-che Wud1b74152020-05-20 08:46:46 +08001726 raise errors.InternalError('%s does not have %s' %
1727 (self.historical_manifest_git_dir, path))
Kuang-che Wue4bae0b2018-07-19 12:10:14 +08001728 return timestamp
Kuang-che Wubfc4a642018-04-19 11:54:08 +08001729
Zheng-Jie Chang868c1752020-01-21 14:42:41 +08001730 def detect_float_spec_branch_level(self, spec):
1731 results = [
1732 detect_branch_level(branch) for branch in git_util.get_branches(
1733 self.manifest_dir, commit=spec.name)
1734 ]
1735 results = [x for x in results if x > 0]
1736 return min(results) if results else 0
1737
1738 def branch_between_float_specs(self, old_spec, new_spec):
1739 if old_spec.spec_type != codechange.SPEC_FLOAT:
1740 return False
1741 if new_spec.spec_type != codechange.SPEC_FLOAT:
1742 return False
1743
1744 level_old = self.detect_float_spec_branch_level(old_spec)
1745 level_new = self.detect_float_spec_branch_level(new_spec)
1746
1747 if not level_old or not level_new:
1748 logger.warning('branch level detect failed, assume master')
1749 return False
1750 return level_old != level_new
1751
Zheng-Jie Changd968f552020-01-16 13:31:57 +08001752 def collect_float_spec(self, old, new, fixed_specs=None):
1753 assert fixed_specs
1754 branch = None
Zheng-Jie Chang3e191962020-02-06 14:25:31 +08001755 old_branches = []
1756 new_branches = []
Zheng-Jie Changd968f552020-01-16 13:31:57 +08001757
Zheng-Jie Chang3e191962020-02-06 14:25:31 +08001758 # There is no revision tag in snapshot's xml
1759 if fixed_specs[0].revision:
1760 old_branches = git_util.get_branches(
1761 self.manifest_dir, commit=fixed_specs[0].revision)
1762 if fixed_specs[-1].revision:
1763 new_branches = git_util.get_branches(
1764 self.manifest_dir, commit=fixed_specs[-1].revision)
1765
1766 # 1. if both are not snapshot, do AND operation
1767 # 2. if new is snapshot, branch = master
1768 # 3. if old is snapshot but new is not, respect new's branch
1769 if fixed_specs[0].revision and fixed_specs[-1].revision:
1770 branches = list(set(old_branches) & set(new_branches))
1771 elif not fixed_specs[-1].revision:
1772 branches = ['refs/remotes/origin/master']
1773 else:
1774 branches = new_branches
1775
Zheng-Jie Changd968f552020-01-16 13:31:57 +08001776 if branches:
1777 branch = branches[0]
1778 else:
1779 logger.warning(
1780 'unable to determine float spec branch, '
1781 'old = %s, new = %s', old_branches, new_branches)
1782
Kuang-che Wue4bae0b2018-07-19 12:10:14 +08001783 old_timestamp = self.lookup_build_timestamp(old)
1784 new_timestamp = self.lookup_build_timestamp(new)
Zheng-Jie Chang127c3302019-09-10 17:17:04 +08001785 # snapshot time is different from commit time
1786 # usually it's a few minutes different
1787 # 30 minutes should be safe in most cases
1788 if is_cros_snapshot_version(old):
1789 old_timestamp = old_timestamp - 1800
1790 if is_cros_snapshot_version(new):
1791 new_timestamp = new_timestamp + 1800
Kuang-che Wubfc4a642018-04-19 11:54:08 +08001792
Zheng-Jie Chang1ace3012020-02-15 04:51:05 +08001793 # TODO(zjchang): add logic to combine symlink target's (full.xml) history
Kuang-che Wue4bae0b2018-07-19 12:10:14 +08001794 result = []
Zheng-Jie Chang1ace3012020-02-15 04:51:05 +08001795 path = 'default.xml'
Kuang-che Wue4bae0b2018-07-19 12:10:14 +08001796 parser = repo_util.ManifestParser(self.manifest_dir)
1797 for timestamp, git_rev in parser.enumerate_manifest_commits(
Zheng-Jie Changd968f552020-01-16 13:31:57 +08001798 old_timestamp, new_timestamp, path, branch=branch):
Kuang-che Wue4bae0b2018-07-19 12:10:14 +08001799 result.append(
1800 codechange.Spec(codechange.SPEC_FLOAT, git_rev, timestamp, path))
1801 return result
Kuang-che Wubfc4a642018-04-19 11:54:08 +08001802
Kuang-che Wue4bae0b2018-07-19 12:10:14 +08001803 def collect_fixed_spec(self, old, new):
Zheng-Jie Chang127c3302019-09-10 17:17:04 +08001804 assert is_cros_full_version(old) or is_cros_snapshot_version(old)
1805 assert is_cros_full_version(new) or is_cros_snapshot_version(new)
1806
1807 # case 1: if both are snapshot, return a list of snapshot
1808 if is_cros_snapshot_version(old) and is_cros_snapshot_version(new):
1809 return self.collect_snapshot_specs(old, new)
1810
1811 # case 2: if both are release version
1812 # return a list of release version
1813 if is_cros_full_version(old) and is_cros_full_version(new):
1814 return self.collect_release_specs(old, new)
1815
1816 # case 3: return a list of release version and append a snapshot
1817 # before or at the end
Zheng-Jie Changc47af3a2019-11-11 17:28:58 +08001818 result = self.collect_release_specs(
1819 version_to_full(self.config['board'], old),
1820 version_to_full(self.config['board'], new))
Zheng-Jie Chang127c3302019-09-10 17:17:04 +08001821 if is_cros_snapshot_version(old):
Zheng-Jie Changc47af3a2019-11-11 17:28:58 +08001822 result = self.collect_snapshot_specs(old, old) + result[1:]
Zheng-Jie Chang127c3302019-09-10 17:17:04 +08001823 elif is_cros_snapshot_version(new):
Zheng-Jie Chang5cd62dd2019-12-09 13:21:12 +08001824 result += self.collect_snapshot_specs(new, new)
Zheng-Jie Chang127c3302019-09-10 17:17:04 +08001825 return result
1826
1827 def collect_snapshot_specs(self, old, new):
1828 assert is_cros_snapshot_version(old)
1829 assert is_cros_snapshot_version(new)
1830
1831 def guess_snapshot_version(board, snapshot_id, old, new):
1832 if old.endswith('-' + snapshot_id):
1833 return old
1834 if new.endswith('-' + snapshot_id):
1835 return new
Zheng-Jie Chang54020832020-04-21 15:52:48 +08001836 gs_path = ('gs://chromeos-image-archive/{board}-snapshot/'
Kuang-che Wuf791afa2019-10-28 19:53:26 +08001837 'R*-{snapshot_id}-*'.format(
1838 board=board, snapshot_id=snapshot_id))
Zheng-Jie Chang127c3302019-09-10 17:17:04 +08001839 for line in gsutil_ls(gs_path, ignore_errors=True):
1840 m = re.match(r'^gs:\S+(R\d+-\d+\.\d+\.\d+-\d+)\S+', line)
1841 if m:
1842 return m.group(1)
Zheng-Jie Chang026cd5d2019-12-04 12:13:01 +08001843 return None
Zheng-Jie Chang127c3302019-09-10 17:17:04 +08001844
1845 result = []
1846 path = 'snapshot.xml'
1847 revisions = self.lookup_snapshot_manifest_revisions(old, new)
Kuang-che Wuf791afa2019-10-28 19:53:26 +08001848 for timestamp, _git_rev, snapshot_id in revisions:
Zheng-Jie Chang127c3302019-09-10 17:17:04 +08001849 snapshot_version = guess_snapshot_version(self.config['board'],
1850 snapshot_id, old, new)
Zheng-Jie Chang026cd5d2019-12-04 12:13:01 +08001851 if snapshot_version:
1852 result.append(
1853 codechange.Spec(codechange.SPEC_FIXED, snapshot_version, timestamp,
1854 path))
1855 else:
1856 logger.warning('snapshot id %s is not found, ignore', snapshot_id)
Zheng-Jie Chang127c3302019-09-10 17:17:04 +08001857 return result
1858
1859 def collect_release_specs(self, old, new):
Kuang-che Wubfc4a642018-04-19 11:54:08 +08001860 assert is_cros_full_version(old)
1861 assert is_cros_full_version(new)
1862 old_milestone, old_short_version = version_split(old)
1863 new_milestone, new_short_version = version_split(new)
1864
Kuang-che Wubfc4a642018-04-19 11:54:08 +08001865 result = []
Kuang-che Wue4bae0b2018-07-19 12:10:14 +08001866 for milestone in git_util.list_dir_from_revision(
1867 self.historical_manifest_git_dir, 'refs/heads/master', 'buildspecs'):
1868 if not milestone.isdigit():
1869 continue
1870 if not int(old_milestone) <= int(milestone) <= int(new_milestone):
1871 continue
1872
Kuang-che Wu74768d32018-09-07 12:03:24 +08001873 files = git_util.list_dir_from_revision(
1874 self.historical_manifest_git_dir, 'refs/heads/master',
1875 os.path.join('buildspecs', milestone))
Kuang-che Wubfc4a642018-04-19 11:54:08 +08001876
1877 for fn in files:
Kuang-che Wue4bae0b2018-07-19 12:10:14 +08001878 path = os.path.join('buildspecs', milestone, fn)
Kuang-che Wubfc4a642018-04-19 11:54:08 +08001879 short_version, ext = os.path.splitext(fn)
1880 if ext != '.xml':
1881 continue
Kuang-che Wubfc4a642018-04-19 11:54:08 +08001882 if (util.is_version_lesseq(old_short_version, short_version) and
1883 util.is_version_lesseq(short_version, new_short_version) and
1884 util.is_direct_relative_version(short_version, new_short_version)):
Kuang-che Wue4bae0b2018-07-19 12:10:14 +08001885 rev = make_cros_full_version(milestone, short_version)
1886 timestamp = git_util.get_commit_time(self.historical_manifest_git_dir,
1887 'refs/heads/master', path)
1888 result.append(
1889 codechange.Spec(codechange.SPEC_FIXED, rev, timestamp, path))
Kuang-che Wubfc4a642018-04-19 11:54:08 +08001890
Kuang-che Wue4bae0b2018-07-19 12:10:14 +08001891 def version_key_func(spec):
1892 _milestone, short_version = version_split(spec.name)
Kuang-che Wubfc4a642018-04-19 11:54:08 +08001893 return util.version_key_func(short_version)
1894
1895 result.sort(key=version_key_func)
Kuang-che Wue4bae0b2018-07-19 12:10:14 +08001896 assert result[0].name == old
1897 assert result[-1].name == new
Kuang-che Wubfc4a642018-04-19 11:54:08 +08001898 return result
1899
Kuang-che Wue4bae0b2018-07-19 12:10:14 +08001900 def get_manifest(self, rev):
Zheng-Jie Chang127c3302019-09-10 17:17:04 +08001901 assert is_cros_full_version(rev) or is_cros_snapshot_version(rev)
1902 if is_cros_full_version(rev):
1903 milestone, short_version = version_split(rev)
1904 path = os.path.join('buildspecs', milestone, '%s.xml' % short_version)
1905 manifest = git_util.get_file_from_revision(
1906 self.historical_manifest_git_dir, 'refs/heads/master', path)
1907 else:
1908 revisions = self.lookup_snapshot_manifest_revisions(rev, rev)
1909 commit_id = revisions[0][1]
1910 manifest = git_util.get_file_from_revision(self.manifest_internal_dir,
1911 commit_id, 'snapshot.xml')
Zheng-Jie Chang0fc704b2019-12-09 18:43:38 +08001912 return manifest
1913
1914 def get_manifest_file(self, rev):
1915 assert is_cros_full_version(rev) or is_cros_snapshot_version(rev)
Kuang-che Wue4bae0b2018-07-19 12:10:14 +08001916 manifest_name = 'manifest_%s.xml' % rev
1917 manifest_path = os.path.join(self.manifest_dir, manifest_name)
1918 with open(manifest_path, 'w') as f:
Zheng-Jie Chang0fc704b2019-12-09 18:43:38 +08001919 f.write(self.get_manifest(rev))
Zheng-Jie Changec368b52020-03-02 16:25:25 +08001920
1921 # workaround for b/150572399
1922 # for chromeOS version < 12931.0.0, manifests are included from incorrect
1923 # folder .repo instead of.repo/manifests
1924 if is_cros_version_lesseq(rev, '12931.0.0'):
1925 repo_path = os.path.join(self.config['chromeos_root'], '.repo')
1926 manifest_patch_path = os.path.join(repo_path, manifest_name)
1927 with open(manifest_patch_path, 'w') as f:
1928 f.write(self.get_manifest(rev))
1929
Kuang-che Wue4bae0b2018-07-19 12:10:14 +08001930 return manifest_name
1931
1932 def parse_spec(self, spec):
1933 parser = repo_util.ManifestParser(self.manifest_dir)
1934 if spec.spec_type == codechange.SPEC_FIXED:
Zheng-Jie Chang0fc704b2019-12-09 18:43:38 +08001935 manifest_name = self.get_manifest_file(spec.name)
Kuang-che Wue4bae0b2018-07-19 12:10:14 +08001936 manifest_path = os.path.join(self.manifest_dir, manifest_name)
Kuang-che Wua5723492019-11-25 20:59:34 +08001937 with open(manifest_path) as f:
1938 content = f.read()
Kuang-che Wue4bae0b2018-07-19 12:10:14 +08001939 root = parser.parse_single_xml(content, allow_include=False)
1940 else:
1941 root = parser.parse_xml_recursive(spec.name, spec.path)
1942
1943 spec.entries = parser.process_parsed_result(root)
1944 if spec.spec_type == codechange.SPEC_FIXED:
Kuang-che Wufe1e88a2019-09-10 21:52:25 +08001945 if not spec.is_static():
Kuang-che Wud1b74152020-05-20 08:46:46 +08001946 raise ValueError('fixed spec %r has unexpected floating entries' %
1947 spec.name)
Zheng-Jie Changd968f552020-01-16 13:31:57 +08001948 spec.revision = root.get('revision')
Kuang-che Wue4bae0b2018-07-19 12:10:14 +08001949
1950 def sync_disk_state(self, rev):
Zheng-Jie Chang0fc704b2019-12-09 18:43:38 +08001951 manifest_name = self.get_manifest_file(rev)
Kuang-che Wue4bae0b2018-07-19 12:10:14 +08001952
1953 # For ChromeOS, mark_as_stable step requires 'repo init -m', which sticks
1954 # manifest. 'repo sync -m' is not enough
1955 repo_util.init(
1956 self.config['chromeos_root'],
1957 'https://chrome-internal.googlesource.com/chromeos/manifest-internal',
1958 manifest_name=manifest_name,
1959 repo_url='https://chromium.googlesource.com/external/repo.git',
Kuang-che Wud8fc9572018-10-03 21:00:41 +08001960 reference=self.config['chromeos_mirror'],
Zheng-Jie Chang85529ad2020-03-06 18:40:18 +08001961 # b/150753074: moblab is in non-default group and causes mark_as_stable
1962 # fail
Kuang-che Wu084eef22020-03-11 18:29:48 +08001963 groups='default,moblab,platform-linux',
Kuang-che Wue4bae0b2018-07-19 12:10:14 +08001964 )
1965
1966 # Note, don't sync with current_branch=True for chromeos. One of its
1967 # build steps (inside mark_as_stable) executes "git describe" which
1968 # needs git tag information.
1969 repo_util.sync(self.config['chromeos_root'])