blob: 4d12890a6956fc23402cb23983293aa4dbb1f3b6 [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
Kuang-che Wud558a042020-06-06 02:11:00 +080026import sys
Kuang-che Wu2ea804f2017-11-28 17:11:41 +080027import time
28
Zheng-Jie Chang4fabff62019-12-08 21:54:35 +080029from google.protobuf import json_format
30
Zheng-Jie Chang2b6d1472019-11-13 12:40:17 +080031from bisect_kit import buildbucket_util
Kuang-che Wu2ea804f2017-11-28 17:11:41 +080032from bisect_kit import cli
Kuang-che Wue4bae0b2018-07-19 12:10:14 +080033from bisect_kit import codechange
Kuang-che Wu3eb6b502018-06-06 16:15:18 +080034from bisect_kit import cr_util
Kuang-che Wue121fae2018-11-09 16:18:39 +080035from bisect_kit import errors
Kuang-che Wubfc4a642018-04-19 11:54:08 +080036from bisect_kit import git_util
Kuang-che Wufb553102018-10-02 18:14:29 +080037from bisect_kit import locking
Kuang-che Wubfc4a642018-04-19 11:54:08 +080038from bisect_kit import repo_util
Kuang-che Wu2ea804f2017-11-28 17:11:41 +080039from bisect_kit import util
40
41logger = logging.getLogger(__name__)
42
Kuang-che Wu2ea804f2017-11-28 17:11:41 +080043re_chromeos_full_version = r'^R\d+-\d+\.\d+\.\d+$'
Kuang-che Wuacb6efd2018-04-25 18:52:58 +080044re_chromeos_localbuild_version = r'^\d+\.\d+\.\d{4}_\d\d_\d\d_\d{4}$'
45re_chromeos_short_version = r'^\d+\.\d+\.\d+$'
Zheng-Jie Chang127c3302019-09-10 17:17:04 +080046re_chromeos_snapshot_version = r'^R\d+-\d+\.\d+\.\d+-\d+$'
Kuang-che Wu2ea804f2017-11-28 17:11:41 +080047
48gs_archive_path = 'gs://chromeos-image-archive/{board}-release'
49gs_release_path = (
Kuang-che Wu80bf6a52019-05-31 12:48:06 +080050 'gs://chromeos-releases/{channel}-channel/{boardpath}/{short_version}')
Kuang-che Wu2ea804f2017-11-28 17:11:41 +080051
52# Assume gsutil is in PATH.
53gsutil_bin = 'gsutil'
Zheng-Jie Changb8697042019-10-29 16:03:26 +080054
55# Since snapshots with version >= 12618.0.0 have android and chrome version
56# info.
57snapshot_cutover_version = '12618.0.0'
Kuang-che Wu2ea804f2017-11-28 17:11:41 +080058
Zheng-Jie Chang61a645a2020-04-29 10:12:13 +080059# current earliest buildbucket buildable versions
60# picked from https://crrev.com/c/2072618
61buildbucket_cutover_versions = [
62 '12931.0.0',
63 '12871.26.0', # R81
64 '12871.24.2', # stabilize-12871.24.B
65 '12812.10.0', # factory-excelsior-12812.B
66 '12768.14.0', # firmware-servo-12768.B
67 '12739.85.0', # R80
68 '12739.67.1', # stabilize-excelsior-12739.67.B
69 '12692.36.0', # factory-hatch-12692.B
70 '12672.104.0', # firmware-hatch-12672.B
71 '12607.110.0', # R79
72 '12607.83.2', # stabilize-quickfix-12607.83.B
73 '12587.59.0', # factory-kukui-12587.B
74 '12573.78.0', # firmware-kukui-12573.B
75 '12499.96.0', # R78
76 '12422.33.0', # firmware-mistral-12422.B
77 '12371.190.0', # R77
78 '12361.38.0', # factory-mistral-12361.B
79 '12200.65.0', # firmware-sarien-12200.B
80 '12105.128.0', # R75
81 '12033.82.0', # factory-sarien-12033.B
82]
83
Kuang-che Wub9705bd2018-06-28 17:59:18 +080084chromeos_root_inside_chroot = '/mnt/host/source'
85# relative to chromeos_root
Kuang-che Wu7f82c6f2019-08-12 14:29:28 +080086prebuilt_autotest_dir = 'tmp/autotest-prebuilt'
Kuang-che Wu28980b22019-07-31 19:51:45 +080087# Relative to chromeos root. Images are cached_images_dir/$board/$image_name.
88cached_images_dir = 'src/build/images'
89test_image_filename = 'chromiumos_test_image.bin'
Kuang-che Wub9705bd2018-06-28 17:59:18 +080090
Kuang-che Wu2ea804f2017-11-28 17:11:41 +080091VERSION_KEY_CROS_SHORT_VERSION = 'cros_short_version'
92VERSION_KEY_CROS_FULL_VERSION = 'cros_full_version'
93VERSION_KEY_MILESTONE = 'milestone'
94VERSION_KEY_CR_VERSION = 'cr_version'
Kuang-che Wu708310b2018-03-28 17:24:34 +080095VERSION_KEY_ANDROID_BUILD_ID = 'android_build_id'
Kuang-che Wu2ea804f2017-11-28 17:11:41 +080096VERSION_KEY_ANDROID_BRANCH = 'android_branch'
Zheng-Jie Chang5f9ae4e2020-02-07 14:26:06 +080097CROSLAND_URL_TEMPLATE = 'https://crosland.corp.google.com/log/%s..%s'
Kuang-che Wu2ea804f2017-11-28 17:11:41 +080098
99
Kuang-che Wu9890ce82018-07-07 15:14:10 +0800100class NeedRecreateChrootException(Exception):
101 """Failed to build ChromeOS because of chroot mismatch or corruption"""
102
103
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800104def is_cros_short_version(s):
Kuang-che Wuacb6efd2018-04-25 18:52:58 +0800105 """Determines if `s` is chromeos short version.
106
107 This function doesn't accept version number of local build.
108 """
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800109 return bool(re.match(re_chromeos_short_version, s))
110
111
Kuang-che Wuacb6efd2018-04-25 18:52:58 +0800112def is_cros_localbuild_version(s):
113 """Determines if `s` is chromeos local build version."""
114 return bool(re.match(re_chromeos_localbuild_version, s))
115
116
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800117def is_cros_full_version(s):
Kuang-che Wuacb6efd2018-04-25 18:52:58 +0800118 """Determines if `s` is chromeos full version.
119
120 This function doesn't accept version number of local build.
121 """
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800122 return bool(re.match(re_chromeos_full_version, s))
123
124
125def is_cros_version(s):
126 """Determines if `s` is chromeos version (either short or full)"""
127 return is_cros_short_version(s) or is_cros_full_version(s)
128
129
Zheng-Jie Chang127c3302019-09-10 17:17:04 +0800130def is_cros_snapshot_version(s):
131 """Determines if `s` is chromeos snapshot version"""
132 return bool(re.match(re_chromeos_snapshot_version, s))
133
134
Zheng-Jie Changaea4fba2020-02-17 17:12:09 +0800135def is_cros_version_lesseq(ver1, ver2):
136 """Determines if ver1 is less or equal to ver2.
137
138 Args:
139 ver1: a Chrome OS version in short, full, or snapshot format.
140 ver2: a Chrome OS version in short, full, or snapshot format.
141
142 Returns:
Zheng-Jie Chang61a645a2020-04-29 10:12:13 +0800143 True if ver1 is less or equal to ver2.
Zheng-Jie Changaea4fba2020-02-17 17:12:09 +0800144 """
145 assert is_cros_version(ver1) or is_cros_snapshot_version(ver1)
146 assert is_cros_version(ver2) or is_cros_snapshot_version(ver2)
147
148 ver1 = [int(x) for x in re.split(r'[.-]', ver1) if not x.startswith('R')]
149 ver2 = [int(x) for x in re.split(r'[.-]', ver2) if not x.startswith('R')]
150 return ver1 <= ver2
151
152
Zheng-Jie Chang61a645a2020-04-29 10:12:13 +0800153def is_buildbucket_buildable(version):
154 """Determines if a version is buildable on buildbucket."""
155 short_version = version_to_short(version)
156 # If given version is child of any cutover, then it's buildable
157 return any([
158 util.is_direct_relative_version(x, short_version) and
159 is_cros_version_lesseq(x, version) for x in buildbucket_cutover_versions
160 ])
161
162
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800163def make_cros_full_version(milestone, short_version):
164 """Makes full_version from milestone and short_version"""
Kuang-che Wuacb6efd2018-04-25 18:52:58 +0800165 assert milestone
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800166 return 'R%s-%s' % (milestone, short_version)
167
168
Zheng-Jie Chang127c3302019-09-10 17:17:04 +0800169def make_cros_snapshot_version(milestone, short_version, snapshot_id):
170 """Makes snapshot version from milestone, short_version and snapshot id"""
171 return 'R%s-%s-%s' % (milestone, short_version, snapshot_id)
172
173
174def version_split(version):
175 """Splits full_version or snapshot_version into milestone and short_version"""
176 assert is_cros_full_version(version) or is_cros_snapshot_version(version)
177 if is_cros_snapshot_version(version):
178 return snapshot_version_split(version)[0:2]
179 milestone, short_version = version.split('-')
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800180 return milestone[1:], short_version
181
182
Zheng-Jie Chang127c3302019-09-10 17:17:04 +0800183def snapshot_version_split(snapshot_version):
184 """Splits snapshot_version into milestone, short_version and snapshot_id"""
185 assert is_cros_snapshot_version(snapshot_version)
186 milestone, shot_version, snapshot_id = snapshot_version.split('-')
187 return milestone[1:], shot_version, snapshot_id
188
189
Zheng-Jie Changb8697042019-10-29 16:03:26 +0800190def query_snapshot_buildbucket_id(board, snapshot_version):
191 """Query buildbucket id of a snapshot"""
192 assert is_cros_snapshot_version(snapshot_version)
Zheng-Jie Chang54020832020-04-21 15:52:48 +0800193 path = ('gs://chromeos-image-archive/{board}-snapshot'
Zheng-Jie Changb8697042019-10-29 16:03:26 +0800194 '/{snapshot_version}-*/image.zip')
195 output = gsutil_ls(
196 '-d',
197 path.format(board=board, snapshot_version=snapshot_version),
198 ignore_errors=True)
199 for line in output:
Zheng-Jie Chang54020832020-04-21 15:52:48 +0800200 m = re.match(r'.*-snapshot/R\d+-\d+\.\d+\.\d+-\d+-(.+)/image\.zip', line)
Zheng-Jie Changb8697042019-10-29 16:03:26 +0800201 if m:
202 return m.group(1)
203 return None
204
205
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800206def argtype_cros_version(s):
Zheng-Jie Chang127c3302019-09-10 17:17:04 +0800207 if (not is_cros_version(s)) and (not is_cros_snapshot_version(s)):
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800208 msg = 'invalid cros version'
Kuang-che Wuce2f3be2019-10-28 19:44:54 +0800209 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 +0800210 return s
211
212
213def query_dut_lsb_release(host):
214 """Query /etc/lsb-release of given DUT
215
216 Args:
217 host: the DUT address
218
219 Returns:
Kuang-che Wu3eb6b502018-06-06 16:15:18 +0800220 dict for keys and values of /etc/lsb-release.
221
222 Raises:
Kuang-che Wu44278142019-03-04 11:33:57 +0800223 errors.SshConnectionError: cannot connect to host
224 errors.ExternalError: lsb-release file doesn't exist
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800225 """
226 try:
Kuang-che Wu44278142019-03-04 11:33:57 +0800227 output = util.ssh_cmd(host, 'cat', '/etc/lsb-release')
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800228 except subprocess.CalledProcessError:
Kuang-che Wu44278142019-03-04 11:33:57 +0800229 raise errors.ExternalError('unable to read /etc/lsb-release; not a DUT')
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800230 return dict(re.findall(r'^(\w+)=(.*)$', output, re.M))
231
232
233def is_dut(host):
Kuang-che Wubfc4a642018-04-19 11:54:08 +0800234 """Determines whether a host is a chromeos device.
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800235
236 Args:
237 host: the DUT address
238
239 Returns:
240 True if the host is a chromeos device.
241 """
Kuang-che Wu44278142019-03-04 11:33:57 +0800242 try:
243 return query_dut_lsb_release(host).get('DEVICETYPE') in [
244 'CHROMEBASE',
245 'CHROMEBIT',
246 'CHROMEBOOK',
247 'CHROMEBOX',
248 'REFERENCE',
249 ]
250 except (errors.ExternalError, errors.SshConnectionError):
251 return False
252
253
254def is_good_dut(host):
255 if not is_dut(host):
256 return False
257
258 # Sometimes python is broken after 'cros flash'.
259 try:
260 util.ssh_cmd(host, 'python', '-c', '1')
261 return True
262 except (subprocess.CalledProcessError, errors.SshConnectionError):
263 return False
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800264
265
266def query_dut_board(host):
267 """Query board name of a given DUT"""
268 return query_dut_lsb_release(host).get('CHROMEOS_RELEASE_BOARD')
269
270
271def query_dut_short_version(host):
Kuang-che Wuacb6efd2018-04-25 18:52:58 +0800272 """Query short version of a given DUT.
273
274 This function may return version of local build, which
275 is_cros_short_version() is false.
276 """
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800277 return query_dut_lsb_release(host).get('CHROMEOS_RELEASE_VERSION')
278
279
Zheng-Jie Chang26084542020-04-29 06:34:33 +0800280def query_dut_prebuilt_version(host):
281 """Return a snapshot version or short version of a given DUT.
282
283 Args:
284 host: dut host
285
286 Returns:
287 Snapshot version or short version.
288 """
289 lsb_release = query_dut_lsb_release(host)
290 release_version = lsb_release.get('CHROMEOS_RELEASE_VERSION')
291 builder_path = lsb_release.get('CHROMEOS_RELEASE_BUILDER_PATH')
292 match = re.match(r'\S+-(?:snapshot|postsubmit)/(R\d+-\d+\.\d+\.\d+-\d+)-\d+',
293 builder_path)
294 if match:
295 return match.group(1)
296 return release_version
297
298
Zheng-Jie Chang181be6f2020-03-17 16:16:08 +0800299def query_dut_is_by_official_builder(host):
300 """Query if given DUT is build by buildbucket builder"""
301 return query_dut_lsb_release(host).get('CHROMEOS_RELEASE_BUILD_TYPE',
302 '').startswith('Continuous Builder')
303
304
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800305def query_dut_boot_id(host, connect_timeout=None):
Kuang-che Wubfc4a642018-04-19 11:54:08 +0800306 """Query boot id.
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800307
308 Args:
309 host: DUT address
310 connect_timeout: connection timeout
311
312 Returns:
313 boot uuid
314 """
Kuang-che Wu44278142019-03-04 11:33:57 +0800315 return util.ssh_cmd(
316 host,
317 'cat',
318 '/proc/sys/kernel/random/boot_id',
319 connect_timeout=connect_timeout).strip()
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800320
321
322def reboot(host):
323 """Reboot a DUT and verify"""
324 logger.debug('reboot %s', host)
325 boot_id = query_dut_boot_id(host)
326
Kuang-che Wu44278142019-03-04 11:33:57 +0800327 try:
328 util.ssh_cmd(host, 'reboot')
Kuang-che Wu5f662e82019-03-05 11:49:56 +0800329 except errors.SshConnectionError:
330 # Depends on timing, ssh may return failure due to broken pipe, which is
331 # working as intended. Ignore such kind of errors.
Kuang-che Wu44278142019-03-04 11:33:57 +0800332 pass
Kuang-che Wu708310b2018-03-28 17:24:34 +0800333 wait_reboot_done(host, boot_id)
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800334
Kuang-che Wu708310b2018-03-28 17:24:34 +0800335
336def wait_reboot_done(host, boot_id):
Kuang-che Wu4fe945b2018-03-31 16:46:38 +0800337 # For dev-mode test image, the reboot time is roughly at least 16 seconds
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800338 # (dev screen short delay) or more (long delay).
339 time.sleep(15)
340 for _ in range(100):
341 try:
342 # During boot, DUT does not response and thus ssh may hang a while. So
343 # set a connect timeout. 3 seconds are enough and 2 are not. It's okay to
344 # set tight limit because it's inside retry loop.
345 assert boot_id != query_dut_boot_id(host, connect_timeout=3)
346 return
Kuang-che Wu5f662e82019-03-05 11:49:56 +0800347 except errors.SshConnectionError:
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800348 logger.debug('reboot not ready? sleep wait 1 sec')
349 time.sleep(1)
350
Kuang-che Wue121fae2018-11-09 16:18:39 +0800351 raise errors.ExternalError('reboot failed?')
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800352
353
Kuang-che Wu80bf6a52019-05-31 12:48:06 +0800354def gs_release_boardpath(board):
355 """Normalizes board name for gs://chromeos-releases/
356
357 This follows behavior of PushImage() in chromite/scripts/pushimage.py
358 Note, only gs://chromeos-releases/ needs normalization,
359 gs://chromeos-image-archive does not.
360
361 Args:
362 board: ChromeOS board name
363
364 Returns:
365 normalized board name
366 """
367 return board.replace('_', '-')
368
369
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800370def gsutil(*args, **kwargs):
Kuang-che Wubfc4a642018-04-19 11:54:08 +0800371 """gsutil command line wrapper.
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800372
373 Args:
374 args: command line arguments passed to gsutil
375 kwargs:
376 ignore_errors: if true, return '' for failures, for example 'gsutil ls'
377 but the path not found.
378
379 Returns:
380 stdout of gsutil
381
382 Raises:
Zheng-Jie Changb8697042019-10-29 16:03:26 +0800383 errors.ExternalError: gsutil failed to run
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800384 subprocess.CalledProcessError: command failed
385 """
386 stderr_lines = []
387 try:
388 return util.check_output(
389 gsutil_bin, *args, stderr_callback=stderr_lines.append)
390 except subprocess.CalledProcessError as e:
391 stderr = ''.join(stderr_lines)
392 if re.search(r'ServiceException:.* does not have .*access', stderr):
Kuang-che Wue121fae2018-11-09 16:18:39 +0800393 raise errors.ExternalError(
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800394 'gsutil failed due to permission. ' +
395 'Run "%s config" and follow its instruction. ' % gsutil_bin +
396 'Fill any string if it asks for project-id')
397 if kwargs.get('ignore_errors'):
398 return ''
399 raise
400 except OSError as e:
401 if e.errno == errno.ENOENT:
Kuang-che Wue121fae2018-11-09 16:18:39 +0800402 raise errors.ExternalError(
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800403 'Unable to run %s. gsutil is not installed or not in PATH?' %
404 gsutil_bin)
405 raise
406
407
408def gsutil_ls(*args, **kwargs):
Kuang-che Wubfc4a642018-04-19 11:54:08 +0800409 """gsutil ls.
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800410
411 Args:
412 args: arguments passed to 'gsutil ls'
413 kwargs: extra parameters, where
Kuang-che Wu4fe945b2018-03-31 16:46:38 +0800414 ignore_errors: if true, return empty list instead of raising
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800415 exception, ex. path not found.
416
417 Returns:
418 list of 'gsutil ls' result. One element for one line of gsutil output.
419
420 Raises:
421 subprocess.CalledProcessError: gsutil failed, usually means path not found
422 """
423 return gsutil('ls', *args, **kwargs).splitlines()
424
425
Zheng-Jie Chang127c3302019-09-10 17:17:04 +0800426def gsutil_stat_update_time(*args, **kwargs):
427 """Returns the last modified time of a file or multiple files.
428
429 Args:
430 args: arguments passed to 'gsutil stat'.
431 kwargs: extra parameters for gsutil.
432
433 Returns:
434 A integer indicates the last modified timestamp.
435
436 Raises:
437 subprocess.CalledProcessError: gsutil failed, usually means path not found
438 errors.ExternalError: update time is not found
439 """
440 result = -1
441 # Currently we believe stat always returns a UTC time, and strptime also
442 # parses a UTC time by default.
443 time_format = '%a, %d %b %Y %H:%M:%S GMT'
444
445 for line in gsutil('stat', *args, **kwargs).splitlines():
446 if ':' not in line:
447 continue
Kuang-che Wuc89f2a22019-11-26 15:30:50 +0800448 key, value = line.split(':', 1)
449 key, value = key.strip(), value.strip()
Zheng-Jie Chang127c3302019-09-10 17:17:04 +0800450 if key != 'Update time':
451 continue
452 dt = datetime.datetime.strptime(value, time_format)
Kuang-che Wu72b5a572019-10-29 20:37:57 +0800453 unixtime = int(calendar.timegm(dt.utctimetuple()))
Zheng-Jie Chang127c3302019-09-10 17:17:04 +0800454 result = max(result, unixtime)
455
456 if result == -1:
457 raise errors.ExternalError("didn't find update time")
458 return result
459
460
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800461def query_milestone_by_version(board, short_version):
Kuang-che Wubfc4a642018-04-19 11:54:08 +0800462 """Query milestone by ChromeOS version number.
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800463
464 Args:
465 board: ChromeOS board name
466 short_version: ChromeOS version number in short format, ex. 9300.0.0
467
468 Returns:
469 ChromeOS milestone number (string). For example, '58' for '9300.0.0'.
470 None if failed.
471 """
472 path = gs_archive_path.format(board=board) + '/R*-' + short_version
473 for line in gsutil_ls('-d', path, ignore_errors=True):
474 m = re.search(r'/R(\d+)-', line)
475 if not m:
476 continue
477 return m.group(1)
478
479 for channel in ['canary', 'dev', 'beta', 'stable']:
480 path = gs_release_path.format(
Kuang-che Wu80bf6a52019-05-31 12:48:06 +0800481 channel=channel,
482 boardpath=gs_release_boardpath(board),
483 short_version=short_version)
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800484 for line in gsutil_ls(path, ignore_errors=True):
485 m = re.search(r'\bR(\d+)-' + short_version, line)
486 if not m:
487 continue
488 return m.group(1)
489
Zheng-Jie Chang4e25a8d2020-01-08 16:00:13 +0800490 logger.debug('unable to query milestone of %s for %s', short_version, board)
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800491 return None
492
493
Kuang-che Wu80bf6a52019-05-31 12:48:06 +0800494def list_board_names(chromeos_root):
495 """List board names.
496
497 Args:
498 chromeos_root: chromeos tree root
499
500 Returns:
501 list of board names
502 """
503 # Following logic is simplified from chromite/lib/portage_util.py
504 cros_list_overlays = os.path.join(chromeos_root,
505 'chromite/bin/cros_list_overlays')
506 overlays = util.check_output(cros_list_overlays).splitlines()
507 result = set()
508 for overlay in overlays:
509 conf_file = os.path.join(overlay, 'metadata', 'layout.conf')
510 name = None
511 if os.path.exists(conf_file):
512 for line in open(conf_file):
513 m = re.match(r'^repo-name\s*=\s*(\S+)\s*$', line)
514 if m:
515 name = m.group(1)
516 break
517
518 if not name:
519 name_file = os.path.join(overlay, 'profiles', 'repo_name')
520 if os.path.exists(name_file):
Kuang-che Wua5723492019-11-25 20:59:34 +0800521 with open(name_file) as f:
522 name = f.read().strip()
Kuang-che Wu80bf6a52019-05-31 12:48:06 +0800523
524 if name:
525 name = re.sub(r'-private$', '', name)
526 result.add(name)
527
528 return list(result)
529
530
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800531def recognize_version(board, version):
Kuang-che Wubfc4a642018-04-19 11:54:08 +0800532 """Recognize ChromeOS version.
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800533
534 Args:
535 board: ChromeOS board name
536 version: ChromeOS version number in short or full format
537
538 Returns:
539 (milestone, version in short format)
540 """
541 if is_cros_short_version(version):
542 milestone = query_milestone_by_version(board, version)
543 short_version = version
544 else:
545 milestone, short_version = version_split(version)
546 return milestone, short_version
547
548
Zheng-Jie Chang127c3302019-09-10 17:17:04 +0800549def extract_major_version(version):
550 """Converts a version to its major version.
551
552 Args:
Kuang-che Wu9501f342019-11-15 17:15:21 +0800553 version: ChromeOS version number or snapshot version
Zheng-Jie Chang127c3302019-09-10 17:17:04 +0800554
555 Returns:
556 major version number in string format
557 """
558 version = version_to_short(version)
559 m = re.match(r'^(\d+)\.\d+\.\d+$', version)
560 return m.group(1)
561
562
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800563def version_to_short(version):
Kuang-che Wubfc4a642018-04-19 11:54:08 +0800564 """Convert ChromeOS version number to short format.
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800565
566 Args:
567 version: ChromeOS version number in short or full format
568
569 Returns:
570 version number in short format
571 """
572 if is_cros_short_version(version):
573 return version
574 _, short_version = version_split(version)
575 return short_version
576
577
578def version_to_full(board, version):
Kuang-che Wubfc4a642018-04-19 11:54:08 +0800579 """Convert ChromeOS version number to full format.
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800580
581 Args:
582 board: ChromeOS board name
583 version: ChromeOS version number in short or full format
584
585 Returns:
586 version number in full format
587 """
Zheng-Jie Changc47af3a2019-11-11 17:28:58 +0800588 if is_cros_snapshot_version(version):
589 milestone, short_version, _ = snapshot_version_split(version)
590 return make_cros_full_version(milestone, short_version)
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800591 if is_cros_full_version(version):
592 return version
593 milestone = query_milestone_by_version(board, version)
Kuang-che Wu0205f052019-05-23 12:48:37 +0800594 assert milestone, 'incorrect board=%s or version=%s ?' % (board, version)
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800595 return make_cros_full_version(milestone, version)
596
597
Zheng-Jie Chang127c3302019-09-10 17:17:04 +0800598def list_snapshots_from_image_archive(board, major_version):
Kuang-che Wu9501f342019-11-15 17:15:21 +0800599 """List ChromeOS snapshot image available from gs://chromeos-image-archive.
Zheng-Jie Chang127c3302019-09-10 17:17:04 +0800600
601 Args:
602 board: ChromeOS board
603 major_version: ChromeOS major version
604
605 Returns:
606 list of (version, gs_path):
607 version: Chrome OS snapshot version
608 gs_path: gs path of test image
609 """
610
Zheng-Jie Chang43a08412019-12-05 17:05:45 +0800611 def extract_snapshot_id(result):
612 m = re.match(r'^R\d+-\d+\.\d+\.\d+-(\d+)', result[0])
613 assert m
614 return int(m.group(1))
615
Zheng-Jie Changa331b872020-01-06 17:09:32 +0800616 short_version = '%s.0.0' % major_version
617 milestone = query_milestone_by_version(board, short_version)
618 if not milestone:
619 milestone = '*'
620
Zheng-Jie Chang54020832020-04-21 15:52:48 +0800621 path = ('gs://chromeos-image-archive/{board}-snapshot/R{milestone}-'
Zheng-Jie Changa331b872020-01-06 17:09:32 +0800622 '{short_version}-*/image.zip')
Zheng-Jie Chang127c3302019-09-10 17:17:04 +0800623 result = []
624 output = gsutil_ls(
Zheng-Jie Changa331b872020-01-06 17:09:32 +0800625 path.format(
626 board=board, milestone=milestone, short_version=short_version),
Zheng-Jie Chang127c3302019-09-10 17:17:04 +0800627 ignore_errors=True)
628
Zheng-Jie Changa331b872020-01-06 17:09:32 +0800629 for gs_path in sorted(output):
630 m = re.match(r'^gs:\S+(R\d+-\d+\.\d+\.\d+-\d+)', gs_path)
Zheng-Jie Chang127c3302019-09-10 17:17:04 +0800631 if m:
632 snapshot_version = m.group(1)
Zheng-Jie Chang43a08412019-12-05 17:05:45 +0800633 # we should skip if there is duplicate snapshot
634 if result and result[-1][0] == snapshot_version:
635 continue
Zheng-Jie Chang1ae64c32020-03-10 14:08:28 +0800636
637 # b/151054108: snapshot version in [29288, 29439] is broken
638 _, _, snapshot_id = snapshot_version_split(snapshot_version)
639 if 29288 <= int(snapshot_id) <= 29439:
640 continue
641
Zheng-Jie Chang127c3302019-09-10 17:17:04 +0800642 result.append((snapshot_version, gs_path))
Zheng-Jie Chang43a08412019-12-05 17:05:45 +0800643
644 # sort by its snapshot_id
645 result.sort(key=extract_snapshot_id)
Zheng-Jie Chang127c3302019-09-10 17:17:04 +0800646 return result
647
648
Kuang-che Wu575dc442019-03-05 10:30:55 +0800649def list_prebuilt_from_image_archive(board):
650 """Lists ChromeOS prebuilt image available from gs://chromeos-image-archive.
651
652 gs://chromeos-image-archive contains only recent builds (in two years).
653 We prefer this function to list_prebuilt_from_chromeos_releases() because
654 - this is what "cros flash" supports directly.
655 - the paths have milestone information, so we don't need to do slow query
656 by ourselves.
657
658 Args:
659 board: ChromeOS board name
660
661 Returns:
662 list of (version, gs_path):
663 version: Chrome OS version in full format
664 gs_path: gs path of test image
665 """
666 result = []
667 for line in gsutil_ls(gs_archive_path.format(board=board)):
668 m = re.match(r'^gs:\S+(R\d+-\d+\.\d+\.\d+)', line)
669 if m:
670 full_version = m.group(1)
671 test_image = 'chromiumos_test_image.tar.xz'
672 assert line.endswith('/')
673 gs_path = line + test_image
674 result.append((full_version, gs_path))
675 return result
676
677
678def list_prebuilt_from_chromeos_releases(board):
679 """Lists ChromeOS versions available from gs://chromeos-releases.
680
681 gs://chromeos-releases contains more builds. However, 'cros flash' doesn't
682 support it.
683
684 Args:
685 board: ChromeOS board name
686
687 Returns:
688 list of (version, gs_path):
689 version: Chrome OS version in short format
690 gs_path: gs path of test image (with wildcard)
691 """
692 result = []
693 for line in gsutil_ls(
Kuang-che Wu80bf6a52019-05-31 12:48:06 +0800694 gs_release_path.format(
695 channel='*', boardpath=gs_release_boardpath(board), short_version=''),
Kuang-che Wu575dc442019-03-05 10:30:55 +0800696 ignore_errors=True):
697 m = re.match(r'gs:\S+/(\d+\.\d+\.\d+)/$', line)
698 if m:
699 short_version = m.group(1)
700 test_image = 'ChromeOS-test-R*-{short_version}-{board}.tar.xz'.format(
701 short_version=short_version, board=board)
702 gs_path = line + test_image
703 result.append((short_version, gs_path))
704 return result
705
706
Kuang-che Wue1808402020-01-06 20:27:45 +0800707def has_test_image(board, version):
708 if is_cros_snapshot_version(version):
709 return bool(query_snapshot_buildbucket_id(board, version))
710
711 full_version = version_to_full(board, version)
712 short_version = version_to_short(version)
713 paths = [
714 gs_archive_path.format(board=board) +
715 '/%s/chromiumos_test_image.tar.xz' % full_version,
716 gs_release_path.format(
717 channel='*',
718 boardpath=gs_release_boardpath(board),
719 short_version=short_version),
720 ]
721
722 for path in paths:
723 if gsutil_ls(path, ignore_errors=True):
724 return True
725 return False
726
727
Kuang-che Wu575dc442019-03-05 10:30:55 +0800728def list_chromeos_prebuilt_versions(board,
729 old,
730 new,
731 only_good_build=True,
Zheng-Jie Chang127c3302019-09-10 17:17:04 +0800732 include_older_build=True,
733 use_snapshot=False):
Kuang-che Wu575dc442019-03-05 10:30:55 +0800734 """Lists ChromeOS version numbers with prebuilt between given range
735
736 Args:
737 board: ChromeOS board name
738 old: start version (inclusive)
739 new: end version (inclusive)
740 only_good_build: only if test image is available
741 include_older_build: include prebuilt in gs://chromeos-releases
Zheng-Jie Chang127c3302019-09-10 17:17:04 +0800742 use_snapshot: return snapshot versions if found
Kuang-che Wu575dc442019-03-05 10:30:55 +0800743
744 Returns:
745 list of sorted version numbers (in full format) between [old, new] range
746 (inclusive).
747 """
Zheng-Jie Changaea4fba2020-02-17 17:12:09 +0800748 old_short = version_to_short(old)
749 new_short = version_to_short(new)
Kuang-che Wu575dc442019-03-05 10:30:55 +0800750
Zheng-Jie Chang127c3302019-09-10 17:17:04 +0800751 rev_map = {
752 } # dict: short version -> list of (short/full or snapshot version, gs path)
Kuang-che Wu575dc442019-03-05 10:30:55 +0800753 for full_version, gs_path in list_prebuilt_from_image_archive(board):
754 short_version = version_to_short(full_version)
Zheng-Jie Chang127c3302019-09-10 17:17:04 +0800755 rev_map[short_version] = [(full_version, gs_path)]
Kuang-che Wu575dc442019-03-05 10:30:55 +0800756
Zheng-Jie Changaea4fba2020-02-17 17:12:09 +0800757 if include_older_build and old_short not in rev_map:
Kuang-che Wu575dc442019-03-05 10:30:55 +0800758 for short_version, gs_path in list_prebuilt_from_chromeos_releases(board):
759 if short_version not in rev_map:
Zheng-Jie Chang127c3302019-09-10 17:17:04 +0800760 rev_map[short_version] = [(short_version, gs_path)]
761
762 if use_snapshot:
763 for major_version in range(
764 int(extract_major_version(old)),
765 int(extract_major_version(new)) + 1):
766 short_version = '%s.0.0' % major_version
Zheng-Jie Changc47af3a2019-11-11 17:28:58 +0800767 next_short_version = '%s.0.0' % (major_version + 1)
Zheng-Jie Changb8697042019-10-29 16:03:26 +0800768 # If current version is smaller than cutover, ignore it as it might not
769 # contain enough information for continuing android and chrome bisection.
770 if not util.is_version_lesseq(snapshot_cutover_version, short_version):
771 continue
Zheng-Jie Changc47af3a2019-11-11 17:28:58 +0800772
773 # Given the fact that snapshots are images between two release versions.
774 # Adding snapshots of 12345.0.0 should be treated as adding commits
775 # between [12345.0.0, 12346.0.0).
776 # So in the following lines we check two facts:
777 # 1) If 12346.0.0(next_short_version) is a version between old and new
Zheng-Jie Changaea4fba2020-02-17 17:12:09 +0800778 if not util.is_direct_relative_version(next_short_version, old_short):
Zheng-Jie Changc47af3a2019-11-11 17:28:58 +0800779 continue
Zheng-Jie Changaea4fba2020-02-17 17:12:09 +0800780 if not util.is_direct_relative_version(next_short_version, new_short):
Zheng-Jie Changc47af3a2019-11-11 17:28:58 +0800781 continue
782 # 2) If 12345.0.0(short_version) is a version between old and new
Zheng-Jie Changaea4fba2020-02-17 17:12:09 +0800783 if not util.is_direct_relative_version(short_version, old_short):
Zheng-Jie Chang127c3302019-09-10 17:17:04 +0800784 continue
Zheng-Jie Changaea4fba2020-02-17 17:12:09 +0800785 if not util.is_direct_relative_version(short_version, new_short):
Zheng-Jie Chang127c3302019-09-10 17:17:04 +0800786 continue
Zheng-Jie Changc47af3a2019-11-11 17:28:58 +0800787
Zheng-Jie Chang127c3302019-09-10 17:17:04 +0800788 snapshots = list_snapshots_from_image_archive(board, str(major_version))
789 if snapshots:
790 # if snapshots found, we can append them after the release version,
791 # so the prebuilt image list of this version will be
792 # release_image, snapshot1, snapshot2,...
Zheng-Jie Changb8697042019-10-29 16:03:26 +0800793 if short_version not in rev_map:
794 rev_map[short_version] = []
Zheng-Jie Chang127c3302019-09-10 17:17:04 +0800795 rev_map[short_version] += snapshots
Kuang-che Wu575dc442019-03-05 10:30:55 +0800796
797 result = []
798 for rev in sorted(rev_map, key=util.version_key_func):
Zheng-Jie Changaea4fba2020-02-17 17:12:09 +0800799 if not util.is_direct_relative_version(new_short, rev):
Kuang-che Wu575dc442019-03-05 10:30:55 +0800800 continue
Zheng-Jie Changaea4fba2020-02-17 17:12:09 +0800801 if not util.is_version_lesseq(old_short, rev):
Kuang-che Wu575dc442019-03-05 10:30:55 +0800802 continue
Zheng-Jie Changaea4fba2020-02-17 17:12:09 +0800803 if not util.is_version_lesseq(rev, new_short):
Kuang-che Wu575dc442019-03-05 10:30:55 +0800804 continue
805
Zheng-Jie Chang127c3302019-09-10 17:17:04 +0800806 for version, gs_path in rev_map[rev]:
Kuang-che Wu575dc442019-03-05 10:30:55 +0800807
Zheng-Jie Chang127c3302019-09-10 17:17:04 +0800808 # version_to_full() and gsutil_ls() may take long time if versions are a
809 # lot. This is acceptable because we usually bisect only short range.
Kuang-che Wu575dc442019-03-05 10:30:55 +0800810
Zheng-Jie Changa331b872020-01-06 17:09:32 +0800811 if only_good_build and not is_cros_snapshot_version(version):
Zheng-Jie Chang127c3302019-09-10 17:17:04 +0800812 gs_result = gsutil_ls(gs_path, ignore_errors=True)
813 if not gs_result:
814 logger.warning('%s is not a good build, ignore', version)
815 continue
816 assert len(gs_result) == 1
817 m = re.search(r'(R\d+-\d+\.\d+\.\d+)', gs_result[0])
818 if not m:
819 logger.warning('format of image path is unexpected: %s', gs_result[0])
820 continue
Zheng-Jie Changa331b872020-01-06 17:09:32 +0800821 version = m.group(1)
Zheng-Jie Chang127c3302019-09-10 17:17:04 +0800822 elif is_cros_short_version(version):
823 version = version_to_full(board, version)
Kuang-che Wu575dc442019-03-05 10:30:55 +0800824
Zheng-Jie Changaea4fba2020-02-17 17:12:09 +0800825 if is_cros_version_lesseq(old, version) and is_cros_version_lesseq(
826 version, new):
827 result.append(version)
Kuang-che Wu575dc442019-03-05 10:30:55 +0800828
829 return result
830
831
Zheng-Jie Chang127c3302019-09-10 17:17:04 +0800832def prepare_snapshot_image(chromeos_root, board, snapshot_version):
833 """Prepare chromeos snapshot image.
834
835 Args:
836 chromeos_root: chromeos tree root
837 board: ChromeOS board name
838 snapshot_version: ChromeOS snapshot version number
839
840 Returns:
841 local file path of test image relative to chromeos_root
842 """
843 assert is_cros_snapshot_version(snapshot_version)
844 milestone, short_version, snapshot_id = snapshot_version_split(
845 snapshot_version)
846 full_version = make_cros_full_version(milestone, short_version)
847 tmp_dir = os.path.join(
848 chromeos_root, 'tmp',
849 'ChromeOS-test-%s-%s-%s' % (full_version, board, snapshot_id))
850 if not os.path.exists(tmp_dir):
851 os.makedirs(tmp_dir)
852
Zheng-Jie Chang54020832020-04-21 15:52:48 +0800853 gs_path = ('gs://chromeos-image-archive/{board}-snapshot/' +
Zheng-Jie Chang127c3302019-09-10 17:17:04 +0800854 '{snapshot_version}-*/image.zip')
855 gs_path = gs_path.format(board=board, snapshot_version=snapshot_version)
856
Zheng-Jie Changc47af3a2019-11-11 17:28:58 +0800857 full_path = os.path.join(tmp_dir, test_image_filename)
858 rel_path = os.path.relpath(full_path, chromeos_root)
859 if os.path.exists(full_path):
860 return rel_path
861
Zheng-Jie Chang127c3302019-09-10 17:17:04 +0800862 files = gsutil_ls(gs_path, ignore_errors=True)
Zheng-Jie Changeb7308f2019-12-05 14:25:05 +0800863 if len(files) >= 1:
Zheng-Jie Chang127c3302019-09-10 17:17:04 +0800864 gs_path = files[0]
865 gsutil('cp', gs_path, tmp_dir)
Zheng-Jie Chang127c3302019-09-10 17:17:04 +0800866 util.check_call(
867 'unzip', '-j', 'image.zip', test_image_filename, cwd=tmp_dir)
868 os.remove(os.path.join(tmp_dir, 'image.zip'))
Zheng-Jie Changc47af3a2019-11-11 17:28:58 +0800869 return rel_path
Zheng-Jie Chang127c3302019-09-10 17:17:04 +0800870
Zheng-Jie Changc47af3a2019-11-11 17:28:58 +0800871 assert False
Kuang-che Wua7ddf9b2019-11-25 18:59:57 +0800872 return None
Zheng-Jie Chang127c3302019-09-10 17:17:04 +0800873
874
Kuang-che Wu28980b22019-07-31 19:51:45 +0800875def prepare_prebuilt_image(chromeos_root, board, version):
Kuang-che Wubfc4a642018-04-19 11:54:08 +0800876 """Prepare chromeos prebuilt image.
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800877
Kuang-che Wubfc4a642018-04-19 11:54:08 +0800878 It searches for xbuddy image which "cros flash" can use, or fetch image to
879 local disk.
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800880
881 Args:
Kuang-che Wu28980b22019-07-31 19:51:45 +0800882 chromeos_root: chromeos tree root
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800883 board: ChromeOS board name
Kuang-che Wubfc4a642018-04-19 11:54:08 +0800884 version: ChromeOS version number in short or full format
885
886 Returns:
Kuang-che Wu28980b22019-07-31 19:51:45 +0800887 xbuddy path or file path (relative to chromeos_root)
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800888 """
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800889 assert is_cros_version(version)
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800890 full_version = version_to_full(board, version)
891 short_version = version_to_short(full_version)
892
893 image_path = None
894 gs_path = gs_archive_path.format(board=board) + '/' + full_version
895 if gsutil_ls('-d', gs_path, ignore_errors=True):
896 image_path = 'xbuddy://remote/{board}/{full_version}/test'.format(
897 board=board, full_version=full_version)
898 else:
Kuang-che Wu28980b22019-07-31 19:51:45 +0800899 tmp_dir = os.path.join(chromeos_root, 'tmp',
900 'ChromeOS-test-%s-%s' % (full_version, board))
Zheng-Jie Changc47af3a2019-11-11 17:28:58 +0800901 full_path = os.path.join(tmp_dir, test_image_filename)
902 rel_path = os.path.relpath(full_path, chromeos_root)
903 if os.path.exists(full_path):
904 return rel_path
905
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800906 if not os.path.exists(tmp_dir):
907 os.makedirs(tmp_dir)
908 # gs://chromeos-releases may have more old images than
Kuang-che Wu4fe945b2018-03-31 16:46:38 +0800909 # gs://chromeos-image-archive, but 'cros flash' doesn't support it. We have
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800910 # to fetch the image by ourselves
911 for channel in ['canary', 'dev', 'beta', 'stable']:
912 fn = 'ChromeOS-test-{full_version}-{board}.tar.xz'.format(
913 full_version=full_version, board=board)
914 gs_path = gs_release_path.format(
Kuang-che Wu80bf6a52019-05-31 12:48:06 +0800915 channel=channel,
916 boardpath=gs_release_boardpath(board),
917 short_version=short_version)
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800918 gs_path += '/' + fn
919 if gsutil_ls(gs_path, ignore_errors=True):
920 # TODO(kcwu): delete tmp
921 gsutil('cp', gs_path, tmp_dir)
922 util.check_call('tar', 'Jxvf', fn, cwd=tmp_dir)
Zheng-Jie Changc47af3a2019-11-11 17:28:58 +0800923 image_path = os.path.relpath(full_path, chromeos_root)
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800924 break
925
926 assert image_path
Kuang-che Wubfc4a642018-04-19 11:54:08 +0800927 return image_path
928
929
930def cros_flash(chromeos_root,
931 host,
932 board,
933 image_path,
934 version=None,
935 clobber_stateful=False,
Kuang-che Wu155fb6e2018-11-29 16:00:41 +0800936 disable_rootfs_verification=True):
Kuang-che Wubfc4a642018-04-19 11:54:08 +0800937 """Flash a DUT with given ChromeOS image.
938
939 This is implemented by 'cros flash' command line.
940
941 Args:
942 chromeos_root: use 'cros flash' of which chromeos tree
943 host: DUT address
944 board: ChromeOS board name
Kuang-che Wu28980b22019-07-31 19:51:45 +0800945 image_path: chromeos image xbuddy path or file path. For relative
946 path, it should be relative to chromeos_root.
Kuang-che Wubfc4a642018-04-19 11:54:08 +0800947 version: ChromeOS version in short or full format
948 clobber_stateful: Clobber stateful partition when performing update
949 disable_rootfs_verification: Disable rootfs verification after update
950 is completed
Kuang-che Wu414d67f2019-05-28 11:28:57 +0800951
952 Raises:
953 errors.ExternalError: cros flash failed
Kuang-che Wubfc4a642018-04-19 11:54:08 +0800954 """
955 logger.info('cros_flash %s %s %s %s', host, board, version, image_path)
956
957 # Reboot is necessary because sometimes previous 'cros flash' failed and
958 # entered a bad state.
959 reboot(host)
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800960
Kuang-che Wu28980b22019-07-31 19:51:45 +0800961 # Handle relative path.
962 if '://' not in image_path and not os.path.isabs(image_path):
963 assert os.path.exists(os.path.join(chromeos_root, image_path))
964 image_path = os.path.join(chromeos_root_inside_chroot, image_path)
965
Kuang-che Wuf3d03ca2019-03-11 17:31:40 +0800966 args = [
967 '--debug', '--no-ping', '--send-payload-in-parallel', host, image_path
968 ]
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800969 if clobber_stateful:
970 args.append('--clobber-stateful')
971 if disable_rootfs_verification:
972 args.append('--disable-rootfs-verification')
973
Kuang-che Wu414d67f2019-05-28 11:28:57 +0800974 try:
975 cros_sdk(chromeos_root, 'cros', 'flash', *args)
976 except subprocess.CalledProcessError:
977 raise errors.ExternalError('cros flash failed')
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800978
Kuang-che Wubfc4a642018-04-19 11:54:08 +0800979 if version:
980 # In the past, cros flash may fail with returncode=0
981 # So let's have an extra check.
Zheng-Jie Chang127c3302019-09-10 17:17:04 +0800982 if is_cros_snapshot_version(version):
983 builder_path = query_dut_lsb_release(host).get(
984 'CHROMEOS_RELEASE_BUILDER_PATH', '')
Zheng-Jie Chang54020832020-04-21 15:52:48 +0800985 expect_prefix = '%s-snapshot/%s-' % (board, version)
Zheng-Jie Chang127c3302019-09-10 17:17:04 +0800986 if not builder_path.startswith(expect_prefix):
987 raise errors.ExternalError(
988 'although cros flash succeeded, the OS builder path is '
989 'unexpected: actual=%s expect=%s' % (builder_path, expect_prefix))
990 else:
991 expect_version = version_to_short(version)
992 dut_version = query_dut_short_version(host)
993 if dut_version != expect_version:
994 raise errors.ExternalError(
995 'although cros flash succeeded, the OS version is unexpected: '
996 'actual=%s expect=%s' % (dut_version, expect_version))
Kuang-che Wu414d67f2019-05-28 11:28:57 +0800997
Kuang-che Wu4a81ea72019-10-05 15:35:17 +0800998 # "cros flash" may terminate successfully but the DUT starts self-repairing
Kuang-che Wu414d67f2019-05-28 11:28:57 +0800999 # (b/130786578), so it's necessary to do sanity check.
1000 if not is_good_dut(host):
1001 raise errors.ExternalError(
1002 'although cros flash succeeded, the DUT is in bad state')
1003
1004
1005def cros_flash_with_retry(chromeos_root,
1006 host,
1007 board,
1008 image_path,
1009 version=None,
1010 clobber_stateful=False,
1011 disable_rootfs_verification=True,
1012 repair_callback=None):
1013 # 'cros flash' is not 100% reliable, retry if necessary.
1014 for attempt in range(2):
1015 if attempt > 0:
1016 logger.info('will retry 60 seconds later')
1017 time.sleep(60)
1018
1019 try:
1020 cros_flash(
1021 chromeos_root,
1022 host,
1023 board,
1024 image_path,
1025 version=version,
1026 clobber_stateful=clobber_stateful,
1027 disable_rootfs_verification=disable_rootfs_verification)
1028 return True
1029 except errors.ExternalError:
1030 logger.exception('cros flash failed')
1031 if repair_callback and not repair_callback(host):
1032 logger.warning('not repaired, assume it is harmless')
1033 continue
1034 return False
Kuang-che Wu2ea804f2017-11-28 17:11:41 +08001035
1036
1037def version_info(board, version):
1038 """Query subcomponents version info of given version of ChromeOS
1039
1040 Args:
1041 board: ChromeOS board name
1042 version: ChromeOS version number in short or full format
1043
1044 Returns:
1045 dict of component and version info, including (if available):
1046 cros_short_version: ChromeOS version
1047 cros_full_version: ChromeOS version
1048 milestone: milestone of ChromeOS
1049 cr_version: Chrome version
Kuang-che Wu708310b2018-03-28 17:24:34 +08001050 android_build_id: Android build id
Kuang-che Wu2ea804f2017-11-28 17:11:41 +08001051 android_branch: Android branch, in format like 'git_nyc-mr1-arc'
1052 """
Zheng-Jie Chang127c3302019-09-10 17:17:04 +08001053 if is_cros_snapshot_version(version):
Zheng-Jie Chang2b6d1472019-11-13 12:40:17 +08001054 api = buildbucket_util.BuildbucketApi()
Zheng-Jie Changb8697042019-10-29 16:03:26 +08001055 milestone, short_version, _ = snapshot_version_split(version)
1056 buildbucket_id = query_snapshot_buildbucket_id(board, version)
Zheng-Jie Chang597c38b2020-03-12 22:24:10 +08001057 data = api.get_build(int(buildbucket_id)).output.properties
Zheng-Jie Chang4fabff62019-12-08 21:54:35 +08001058 target_versions = json_format.MessageToDict(data['target_versions'])
Zheng-Jie Chang127c3302019-09-10 17:17:04 +08001059 return {
Zheng-Jie Changb8697042019-10-29 16:03:26 +08001060 VERSION_KEY_MILESTONE: milestone,
1061 VERSION_KEY_CROS_FULL_VERSION: version,
1062 VERSION_KEY_CROS_SHORT_VERSION: short_version,
Zheng-Jie Chang2d1dd9b2019-12-07 23:30:20 +08001063 VERSION_KEY_CR_VERSION: target_versions.get('chromeVersion'),
1064 VERSION_KEY_ANDROID_BUILD_ID: target_versions.get('androidVersion'),
1065 VERSION_KEY_ANDROID_BRANCH: target_versions.get('androidBranchVersion'),
Zheng-Jie Chang127c3302019-09-10 17:17:04 +08001066 }
Kuang-che Wu2ea804f2017-11-28 17:11:41 +08001067 info = {}
1068 full_version = version_to_full(board, version)
1069
1070 # Some boards may have only partial-metadata.json but no metadata.json.
1071 # e.g. caroline R60-9462.0.0
1072 # Let's try both.
1073 metadata = None
1074 for metadata_filename in ['metadata.json', 'partial-metadata.json']:
Kuang-che Wu0768b972019-10-05 15:18:59 +08001075 path = gs_archive_path.format(
1076 board=board) + '/%s/%s' % (full_version, metadata_filename)
Kuang-che Wu2ea804f2017-11-28 17:11:41 +08001077 metadata = gsutil('cat', path, ignore_errors=True)
1078 if metadata:
1079 o = json.loads(metadata)
1080 v = o['version']
1081 board_metadata = o['board-metadata'][board]
1082 info.update({
1083 VERSION_KEY_CROS_SHORT_VERSION: v['platform'],
1084 VERSION_KEY_CROS_FULL_VERSION: v['full'],
1085 VERSION_KEY_MILESTONE: v['milestone'],
1086 VERSION_KEY_CR_VERSION: v['chrome'],
1087 })
1088
1089 if 'android' in v:
Kuang-che Wu708310b2018-03-28 17:24:34 +08001090 info[VERSION_KEY_ANDROID_BUILD_ID] = v['android']
Kuang-che Wu2ea804f2017-11-28 17:11:41 +08001091 if 'android-branch' in v: # this appears since R58-9317.0.0
1092 info[VERSION_KEY_ANDROID_BRANCH] = v['android-branch']
1093 elif 'android-container-branch' in board_metadata:
1094 info[VERSION_KEY_ANDROID_BRANCH] = v['android-container-branch']
1095 break
1096 else:
1097 logger.error('Failed to read metadata from gs://chromeos-image-archive')
1098 logger.error(
1099 'Note, so far no quick way to look up version info for too old builds')
1100
1101 return info
Kuang-che Wu848b1af2018-02-01 20:59:36 +08001102
1103
1104def query_chrome_version(board, version):
Kuang-che Wubfc4a642018-04-19 11:54:08 +08001105 """Queries chrome version of chromeos build.
Kuang-che Wu848b1af2018-02-01 20:59:36 +08001106
1107 Args:
1108 board: ChromeOS board name
1109 version: ChromeOS version number in short or full format
1110
1111 Returns:
1112 Chrome version number
1113 """
1114 info = version_info(board, version)
1115 return info['cr_version']
Kuang-che Wu708310b2018-03-28 17:24:34 +08001116
1117
1118def query_android_build_id(board, rev):
1119 info = version_info(board, rev)
1120 rev = info['android_build_id']
1121 return rev
1122
1123
1124def query_android_branch(board, rev):
1125 info = version_info(board, rev)
1126 rev = info['android_branch']
1127 return rev
Kuang-che Wubfc4a642018-04-19 11:54:08 +08001128
1129
Kuang-che Wu3eb6b502018-06-06 16:15:18 +08001130def guess_chrome_version(board, rev):
1131 """Guess chrome version number.
1132
1133 Args:
1134 board: chromeos board name
1135 rev: chrome or chromeos version
1136
1137 Returns:
1138 chrome version number
1139 """
1140 if is_cros_version(rev):
1141 assert board, 'need to specify BOARD for cros version'
1142 rev = query_chrome_version(board, rev)
1143 assert cr_util.is_chrome_version(rev)
1144
1145 return rev
1146
1147
Kuang-che Wubfc4a642018-04-19 11:54:08 +08001148def is_inside_chroot():
1149 """Returns True if we are inside chroot."""
1150 return os.path.exists('/etc/cros_chroot_version')
1151
1152
Kuang-che Wuad5ac7b2020-02-20 19:02:52 +08001153def convert_path_outside_chroot(chromeos_root, path):
1154 """Converts path in chroot to outside.
1155
1156 Args:
1157 chromeos_root: chromeos tree root
1158 path: path inside chroot; support starting with '~/'
1159
1160 Returns:
1161 The corresponding path outside chroot assuming the chroot is mounted
1162 """
1163 if path.startswith('~/'):
1164 path = path.replace('~', '/home/' + os.environ['USER'])
1165 assert '~' not in path, 'tilde (~) character is not fully supported'
1166
1167 assert os.path.isabs(path)
1168 assert path[0] == os.sep
1169 return os.path.join(chromeos_root, 'chroot', path[1:])
1170
1171
Kuang-che Wubfc4a642018-04-19 11:54:08 +08001172def cros_sdk(chromeos_root, *args, **kwargs):
1173 """Run commands inside chromeos chroot.
1174
1175 Args:
1176 chromeos_root: chromeos tree root
1177 *args: command to run
1178 **kwargs:
Kuang-che Wud4603d72018-11-29 17:51:21 +08001179 chrome_root: pass to cros_sdk; mount this path into the SDK chroot
Kuang-che Wubfc4a642018-04-19 11:54:08 +08001180 env: (dict) environment variables for the command
Kuang-che Wubcafc552019-08-15 15:27:02 +08001181 log_stdout: Whether write the stdout output of the child process to log.
Kuang-che Wubfc4a642018-04-19 11:54:08 +08001182 stdin: standard input file handle for the command
Kuang-che Wu9890ce82018-07-07 15:14:10 +08001183 stderr_callback: Callback function for stderr. Called once per line.
Kuang-che Wua9a20bb2019-09-05 22:24:04 +08001184 goma_dir: Goma installed directory to mount into the chroot
Kuang-che Wubfc4a642018-04-19 11:54:08 +08001185 """
1186 envs = []
1187 for k, v in kwargs.get('env', {}).items():
1188 assert re.match(r'^[A-Za-z_][A-Za-z0-9_]*$', k)
1189 envs.append('%s=%s' % (k, v))
1190
1191 # Use --no-ns-pid to prevent cros_sdk change our pgid, otherwise subsequent
1192 # commands would be considered as background process.
Kuang-che Wu399d4662019-06-06 15:23:37 +08001193 prefix = ['chromite/bin/cros_sdk', '--no-ns-pid']
Kuang-che Wud4603d72018-11-29 17:51:21 +08001194
1195 if kwargs.get('chrome_root'):
Kuang-che Wu399d4662019-06-06 15:23:37 +08001196 prefix += ['--chrome_root', kwargs['chrome_root']]
Kuang-che Wua9a20bb2019-09-05 22:24:04 +08001197 if kwargs.get('goma_dir'):
1198 prefix += ['--goma_dir', kwargs['goma_dir']]
Kuang-che Wud4603d72018-11-29 17:51:21 +08001199
Kuang-che Wu399d4662019-06-06 15:23:37 +08001200 prefix += envs + ['--']
Kuang-che Wud4603d72018-11-29 17:51:21 +08001201
Kuang-che Wu399d4662019-06-06 15:23:37 +08001202 # In addition to the output of command we are interested, cros_sdk may
1203 # generate its own messages. For example, chroot creation messages if we run
1204 # cros_sdk the first time.
1205 # This is the hack to run dummy command once, so we can get clean output for
1206 # the command we are interested.
1207 cmd = prefix + ['true']
1208 util.check_call(*cmd, cwd=chromeos_root)
1209
1210 cmd = prefix + list(args)
Kuang-che Wu9890ce82018-07-07 15:14:10 +08001211 return util.check_output(
1212 *cmd,
1213 cwd=chromeos_root,
Kuang-che Wubcafc552019-08-15 15:27:02 +08001214 log_stdout=kwargs.get('log_stdout', True),
Kuang-che Wu9890ce82018-07-07 15:14:10 +08001215 stdin=kwargs.get('stdin'),
1216 stderr_callback=kwargs.get('stderr_callback'))
Kuang-che Wubfc4a642018-04-19 11:54:08 +08001217
1218
Kuang-che Wua9a20bb2019-09-05 22:24:04 +08001219def create_chroot(chromeos_root):
Kuang-che Wuad5ac7b2020-02-20 19:02:52 +08001220 """Creates ChromeOS chroot if necessary.
Kuang-che Wua9a20bb2019-09-05 22:24:04 +08001221
1222 Args:
1223 chromeos_root: chromeos tree root
1224 """
1225 if os.path.exists(os.path.join(chromeos_root, 'chroot')):
1226 return
1227 if os.path.exists(os.path.join(chromeos_root, 'chroot.img')):
1228 return
1229
1230 util.check_output('chromite/bin/cros_sdk', '--create', cwd=chromeos_root)
1231
1232
Kuang-che Wuad5ac7b2020-02-20 19:02:52 +08001233def mount_chroot(chromeos_root):
1234 """Creates ChromeOS chroot if necessary.
1235
1236 Args:
1237 chromeos_root: chromeos tree root
1238 """
1239 # An arbitrary file must exist in chroot.
1240 path = convert_path_outside_chroot(chromeos_root, '/bin/ls')
1241
1242 # Not created or mounted yet.
1243 if not os.path.exists(path):
1244 create_chroot(chromeos_root)
1245 # After this command, the chroot is mounted.
1246 cros_sdk(chromeos_root, 'true')
1247 assert os.path.exists(path)
1248
1249
1250def copy_into_chroot(chromeos_root, src, dst, overwrite=True):
Kuang-che Wubfc4a642018-04-19 11:54:08 +08001251 """Copies file into chromeos chroot.
1252
Kuang-che Wuad5ac7b2020-02-20 19:02:52 +08001253 The side effect is chroot created and mounted.
1254
Kuang-che Wubfc4a642018-04-19 11:54:08 +08001255 Args:
1256 chromeos_root: chromeos tree root
1257 src: path outside chroot
1258 dst: path inside chroot
Kuang-che Wuad5ac7b2020-02-20 19:02:52 +08001259 overwrite: overwrite if dst alreadys exists
Kuang-che Wubfc4a642018-04-19 11:54:08 +08001260 """
Kuang-che Wuad5ac7b2020-02-20 19:02:52 +08001261 mount_chroot(chromeos_root)
1262 src = os.path.expanduser(src)
1263 dst_outside = convert_path_outside_chroot(chromeos_root, dst)
1264 if not overwrite and os.path.exists(dst_outside):
1265 return
1266
1267 # Haven't support directory or special files yet.
1268 assert os.path.isfile(src)
1269 assert os.path.isfile(dst_outside) or not os.path.exists(dst_outside)
1270
1271 dirname = os.path.dirname(dst_outside)
1272 if not os.path.exists(dirname):
1273 os.makedirs(dirname)
1274 shutil.copy(src, dst_outside)
Kuang-che Wubfc4a642018-04-19 11:54:08 +08001275
1276
Kuang-che Wuad5ac7b2020-02-20 19:02:52 +08001277def prepare_chroot(chromeos_root):
1278 mount_chroot(chromeos_root)
Kuang-che Wubfc4a642018-04-19 11:54:08 +08001279
Kuang-che Wuad5ac7b2020-02-20 19:02:52 +08001280 # Work around b/149077936:
1281 # The creds file is copied into the chroot since 12866.0.0.
1282 # But earlier versions need this file as well because of cipd ACL change.
1283 creds_path = '~/.config/chrome_infra/auth/creds.json'
1284 if os.path.exists(os.path.expanduser(creds_path)):
1285 copy_into_chroot(chromeos_root, creds_path, creds_path, overwrite=False)
Kuang-che Wubfc4a642018-04-19 11:54:08 +08001286
1287
Kuang-che Wu9890ce82018-07-07 15:14:10 +08001288def check_if_need_recreate_chroot(stdout, stderr):
1289 """Analyze build log and determine if chroot should be recreated.
1290
1291 Args:
1292 stdout: stdout output of build
1293 stderr: stderr output of build
1294
1295 Returns:
1296 the reason if chroot needs recreated; None otherwise
1297 """
Kuang-che Wu74768d32018-09-07 12:03:24 +08001298 if re.search(
1299 r"The current version of portage supports EAPI '\d+'. "
Kuang-che Wuae6824b2019-08-27 22:20:01 +08001300 'You must upgrade', stderr):
Kuang-che Wu9890ce82018-07-07 15:14:10 +08001301 return 'EAPI version mismatch'
1302
Kuang-che Wu5ac81322018-11-26 14:04:06 +08001303 if 'Chroot is too new. Consider running:' in stderr:
1304 return 'chroot version is too new'
1305
1306 # old message before Oct 2018
Kuang-che Wu9890ce82018-07-07 15:14:10 +08001307 if 'Chroot version is too new. Consider running cros_sdk --replace' in stderr:
1308 return 'chroot version is too new'
1309
Kuang-che Wu6fe987f2018-08-28 15:24:20 +08001310 # https://groups.google.com/a/chromium.org/forum/#!msg/chromium-os-dev/uzwT5APspB4/NFakFyCIDwAJ
1311 if "undefined reference to 'std::__1::basic_string" in stdout:
1312 return 'might be due to compiler change'
1313
Kuang-che Wu94e3b452019-11-21 12:49:18 +08001314 # Detect failures due to file collisions.
1315 # For example, kernel uprev from 3.x to 4.x, they are two separate packages
1316 # and conflict with each other. Other possible cases are package renaming or
1317 # refactoring. Let's recreate chroot to work around them.
1318 if 'Detected file collision' in stdout:
1319 # Using wildcard between words because the text wraps to the next line
1320 # depending on length of package name and each line is prefixed with
1321 # package name.
1322 # Using ".{,100}" instead of ".*" to prevent regex matching time explodes
1323 # exponentially. 100 is chosen arbitrarily. It should be longer than any
1324 # package name (65 now).
1325 m = re.search(
1326 r'Package (\S+).{,100}NOT.{,100}merged.{,100}'
1327 r'due.{,100}to.{,100}file.{,100}collisions', stdout, re.S)
1328 if m:
1329 return 'failed to install package due to file collision: ' + m.group(1)
Kuang-che Wu356c3522019-11-19 16:11:05 +08001330
Kuang-che Wu9890ce82018-07-07 15:14:10 +08001331 return None
1332
1333
Kuang-che Wua9a20bb2019-09-05 22:24:04 +08001334def build_packages(chromeos_root,
1335 board,
1336 chrome_root=None,
1337 goma_dir=None,
1338 afdo_use=False):
Kuang-che Wu28980b22019-07-31 19:51:45 +08001339 """Build ChromeOS packages.
Kuang-che Wubfc4a642018-04-19 11:54:08 +08001340
1341 Args:
1342 chromeos_root: chromeos tree root
1343 board: ChromeOS board name
Kuang-che Wua9a20bb2019-09-05 22:24:04 +08001344 chrome_root: Chrome tree root. If specified, build chrome using the
1345 provided tree
1346 goma_dir: Goma installed directory to mount into the chroot. If specified,
1347 build chrome with goma.
1348 afdo_use: build chrome with AFDO optimization
Kuang-che Wubfc4a642018-04-19 11:54:08 +08001349 """
Zheng-Jie Changffd49462019-12-16 12:15:18 +08001350
1351 def has_build_package_argument(argument):
1352 stderr_lines = []
1353 try:
1354 util.check_call(
1355 'src/scripts/build_packages',
1356 '--help',
1357 cwd=chromeos_root,
1358 stderr_callback=stderr_lines.append)
1359 except subprocess.CalledProcessError:
1360 help_output = ''.join(stderr_lines)
1361 return '--[no]%s' % argument in help_output
1362
Kuang-che Wua9a20bb2019-09-05 22:24:04 +08001363 common_env = {
1364 'USE': '-cros-debug chrome_internal',
1365 'FEATURES': 'separatedebug',
1366 }
Kuang-che Wu9890ce82018-07-07 15:14:10 +08001367 stderr_lines = []
1368 try:
Kuang-che Wufb553102018-10-02 18:14:29 +08001369 with locking.lock_file(locking.LOCK_FILE_FOR_BUILD):
Kuang-che Wua9a20bb2019-09-05 22:24:04 +08001370 env = common_env.copy()
1371 env['FEATURES'] += ' -separatedebug splitdebug'
Kuang-che Wufb553102018-10-02 18:14:29 +08001372 cros_sdk(
1373 chromeos_root,
Kuang-che Wu28980b22019-07-31 19:51:45 +08001374 './update_chroot',
1375 '--toolchain_boards',
Kuang-che Wufb553102018-10-02 18:14:29 +08001376 board,
Kuang-che Wua9a20bb2019-09-05 22:24:04 +08001377 env=env,
Kuang-che Wu28980b22019-07-31 19:51:45 +08001378 stderr_callback=stderr_lines.append)
Kuang-che Wua9a20bb2019-09-05 22:24:04 +08001379
1380 env = common_env.copy()
1381 cmd = [
Kuang-che Wu28980b22019-07-31 19:51:45 +08001382 './build_packages',
1383 '--board',
1384 board,
1385 '--withdev',
1386 '--noworkon',
1387 '--skip_chroot_upgrade',
1388 '--accept_licenses=@CHROMEOS',
Kuang-che Wua9a20bb2019-09-05 22:24:04 +08001389 ]
Zheng-Jie Changffd49462019-12-16 12:15:18 +08001390
1391 # `use_any_chrome` flag is default on and will force to use a chrome
1392 # prebuilt even if the version doesn't match.
1393
1394 # As this argument is landed in 12681, we should check if the argument
1395 # exists before adding this.
1396 if has_build_package_argument('use_any_chrome'):
1397 cmd.append('--nouse_any_chrome')
1398
Kuang-che Wua9a20bb2019-09-05 22:24:04 +08001399 if goma_dir:
1400 # Tell build_packages to start and stop goma
1401 cmd.append('--run_goma')
1402 env['USE_GOMA'] = 'true'
1403 if afdo_use:
1404 env['USE'] += ' afdo_use'
1405 cros_sdk(
1406 chromeos_root,
1407 *cmd,
1408 env=env,
1409 chrome_root=chrome_root,
1410 stderr_callback=stderr_lines.append,
1411 goma_dir=goma_dir)
Kuang-che Wu9890ce82018-07-07 15:14:10 +08001412 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
Kuang-che Wu28980b22019-07-31 19:51:45 +08001422
1423def build_image(chromeos_root, board):
1424 """Build ChromeOS image.
1425
1426 Args:
1427 chromeos_root: chromeos tree root
1428 board: ChromeOS board name
1429
1430 Returns:
1431 image folder; relative to chromeos_root
1432 """
1433 stderr_lines = []
1434 try:
1435 with locking.lock_file(locking.LOCK_FILE_FOR_BUILD):
1436 cros_sdk(
1437 chromeos_root,
1438 './build_image',
1439 '--board',
1440 board,
1441 '--noenable_rootfs_verification',
1442 'test',
1443 env={
1444 'USE': '-cros-debug chrome_internal',
1445 'FEATURES': 'separatedebug',
1446 },
1447 stderr_callback=stderr_lines.append)
1448 except subprocess.CalledProcessError as e:
1449 # Detect failures due to incompatibility between chroot and source tree. If
1450 # so, notify the caller to recreate chroot and retry.
1451 reason = check_if_need_recreate_chroot(e.output, ''.join(stderr_lines))
1452 if reason:
1453 raise NeedRecreateChrootException(reason)
1454
1455 # For other failures, don't know how to handle. Just bail out.
1456 raise
1457
1458 image_symlink = os.path.join(chromeos_root, cached_images_dir, board,
1459 'latest')
1460 assert os.path.exists(image_symlink)
1461 image_name = os.readlink(image_symlink)
1462 image_folder = os.path.join(cached_images_dir, board, image_name)
1463 assert os.path.exists(
1464 os.path.join(chromeos_root, image_folder, test_image_filename))
1465 return image_folder
Kuang-che Wubfc4a642018-04-19 11:54:08 +08001466
1467
Kuang-che Wu23192ad2020-03-11 18:12:46 +08001468class AutotestControlInfo:
Kuang-che Wub9705bd2018-06-28 17:59:18 +08001469 """Parsed content of autotest control file.
1470
1471 Attributes:
1472 name: test name
1473 path: control file path
1474 variables: dict of top-level control variables. Sample keys: NAME, AUTHOR,
1475 DOC, ATTRIBUTES, DEPENDENCIES, etc.
1476 """
1477
1478 def __init__(self, path, variables):
1479 self.name = variables['NAME']
1480 self.path = path
1481 self.variables = variables
1482
1483
1484def parse_autotest_control_file(path):
1485 """Parses autotest control file.
1486
1487 This only parses simple top-level string assignments.
1488
1489 Returns:
1490 AutotestControlInfo object
1491 """
1492 variables = {}
Kuang-che Wua5723492019-11-25 20:59:34 +08001493 with open(path) as f:
1494 code = ast.parse(f.read())
Kuang-che Wub9705bd2018-06-28 17:59:18 +08001495 for stmt in code.body:
1496 # Skip if not simple "NAME = *" assignment.
1497 if not (isinstance(stmt, ast.Assign) and len(stmt.targets) == 1 and
1498 isinstance(stmt.targets[0], ast.Name)):
1499 continue
1500
1501 # Only support string value.
1502 if isinstance(stmt.value, ast.Str):
1503 variables[stmt.targets[0].id] = stmt.value.s
1504
1505 return AutotestControlInfo(path, variables)
1506
1507
1508def enumerate_autotest_control_files(autotest_dir):
1509 """Enumerate autotest control files.
1510
1511 Args:
1512 autotest_dir: autotest folder
1513
1514 Returns:
1515 list of paths to control files
1516 """
1517 # Where to find control files. Relative to autotest_dir.
1518 subpaths = [
1519 'server/site_tests',
1520 'client/site_tests',
1521 'server/tests',
1522 'client/tests',
1523 ]
1524
1525 blacklist = ['site-packages', 'venv', 'results', 'logs', 'containers']
1526 result = []
1527 for subpath in subpaths:
1528 path = os.path.join(autotest_dir, subpath)
1529 for root, dirs, files in os.walk(path):
1530
1531 for black in blacklist:
1532 if black in dirs:
1533 dirs.remove(black)
1534
1535 for filename in files:
1536 if filename == 'control' or filename.startswith('control.'):
1537 result.append(os.path.join(root, filename))
1538
1539 return result
1540
1541
1542def get_autotest_test_info(autotest_dir, test_name):
1543 """Get metadata of given test.
1544
1545 Args:
1546 autotest_dir: autotest folder
1547 test_name: test name
1548
1549 Returns:
1550 AutotestControlInfo object. None if test not found.
1551 """
1552 for control_file in enumerate_autotest_control_files(autotest_dir):
Zheng-Jie Chang1504a552020-02-20 16:38:57 +08001553 try:
1554 info = parse_autotest_control_file(control_file)
1555 except SyntaxError:
1556 logger.warning('%s is not parsable, ignore', control_file)
1557 continue
1558
Kuang-che Wub9705bd2018-06-28 17:59:18 +08001559 if info.name == test_name:
1560 return info
1561 return None
1562
1563
Zheng-Jie Chang868c1752020-01-21 14:42:41 +08001564def detect_branch_level(branch):
1565 """Given a branch name of manifest-internal, detect it's branch level.
1566
1567 level1: if ChromeOS version is x.0.0
1568 level2: if ChromeOS version is x.x.0
1569 level3: if ChromeOS version is x.x.x
1570 Where x is an non-zero integer.
1571
1572 Args:
1573 branch: branch name or ref name in manifest-internal
1574
1575 Returns:
1576 An integer indicates the branch level, or zero if not detectable.
1577 """
1578 level1 = r'^(refs\/\S+(\/\S+)?/)?master$'
1579 level2 = r'^\S+-(\d+)(\.0)?\.B$'
1580 level3 = r'^\S+-(\d+)\.(\d+)(\.0)?\.B$'
1581
1582 if re.match(level1, branch):
1583 return 1
1584 if re.match(level2, branch):
1585 return 2
1586 if re.match(level3, branch):
1587 return 3
1588 return 0
1589
1590
Zheng-Jie Chang5f9ae4e2020-02-07 14:26:06 +08001591def get_crosland_link(old, new):
1592 """Generates crosland link between two versions.
1593
1594 Args:
1595 old: ChromeOS version
1596 new: ChromeOS version
1597
1598 Returns:
1599 A crosland url.
1600 """
1601
1602 def version_to_url_parameter(ver):
1603 if is_cros_snapshot_version(ver):
1604 return snapshot_version_split(ver)[2]
1605 return version_to_short(ver)
1606
1607 old_parameter = version_to_url_parameter(old)
1608 new_parameter = version_to_url_parameter(new)
1609 return CROSLAND_URL_TEMPLATE % (old_parameter, new_parameter)
1610
1611
Kuang-che Wue4bae0b2018-07-19 12:10:14 +08001612class ChromeOSSpecManager(codechange.SpecManager):
1613 """Repo manifest related operations.
1614
1615 This class enumerates chromeos manifest files, parses them,
1616 and sync to disk state according to them.
1617 """
Kuang-che Wubfc4a642018-04-19 11:54:08 +08001618
1619 def __init__(self, config):
1620 self.config = config
Kuang-che Wue4bae0b2018-07-19 12:10:14 +08001621 self.manifest_dir = os.path.join(self.config['chromeos_root'], '.repo',
1622 'manifests')
Zheng-Jie Chang127c3302019-09-10 17:17:04 +08001623 self.manifest_internal_dir = os.path.join(self.config['chromeos_mirror'],
1624 'manifest-internal.git')
Kuang-che Wue4bae0b2018-07-19 12:10:14 +08001625 self.historical_manifest_git_dir = os.path.join(
Kuang-che Wud8fc9572018-10-03 21:00:41 +08001626 self.config['chromeos_mirror'], 'chromeos/manifest-versions.git')
Kuang-che Wue4bae0b2018-07-19 12:10:14 +08001627 if not os.path.exists(self.historical_manifest_git_dir):
Kuang-che Wue121fae2018-11-09 16:18:39 +08001628 raise errors.InternalError('Manifest snapshots should be cloned into %s' %
1629 self.historical_manifest_git_dir)
Kuang-che Wubfc4a642018-04-19 11:54:08 +08001630
Zheng-Jie Chang127c3302019-09-10 17:17:04 +08001631 def lookup_snapshot_manifest_revisions(self, old, new):
1632 """Get manifest commits between snapshot versions.
Kuang-che Wue4bae0b2018-07-19 12:10:14 +08001633
Zheng-Jie Chang127c3302019-09-10 17:17:04 +08001634 Returns:
1635 list of (timestamp, commit_id, snapshot_id):
1636 timestamp: integer unix timestamp
1637 commit_id: a string indicates commit hash
1638 snapshot_id: a string indicates snapshot id
1639 """
1640 assert is_cros_snapshot_version(old)
1641 assert is_cros_snapshot_version(new)
1642
1643 gs_path = (
Zheng-Jie Chang54020832020-04-21 15:52:48 +08001644 'gs://chromeos-image-archive/{board}-snapshot/{version}-*/image.zip')
Zheng-Jie Chang127c3302019-09-10 17:17:04 +08001645 # Try to guess the commit time of a snapshot manifest, it is usually a few
1646 # minutes different between snapshot manifest commit and image.zip
1647 # generate.
1648 try:
1649 old_timestamp = gsutil_stat_update_time(
1650 gs_path.format(board=self.config['board'], version=old)) - 86400
1651 except subprocess.CalledProcessError:
1652 old_timestamp = None
1653 try:
1654 new_timestamp = gsutil_stat_update_time(
1655 gs_path.format(board=self.config['board'], version=new)) + 86400
1656 # 1558657989 is snapshot_id 5982's commit time, this ensures every time
1657 # we can find snapshot 5982
1658 # snapshot_id <= 5982 has different commit message format, so we need
1659 # to identify its id in different ways, see below comment for more info.
1660 new_timestamp = max(new_timestamp, 1558657989 + 1)
1661 except subprocess.CalledProcessError:
1662 new_timestamp = None
1663 result = []
1664 _, _, old_snapshot_id = snapshot_version_split(old)
1665 _, _, new_snapshot_id = snapshot_version_split(new)
1666 repo = self.manifest_internal_dir
1667 path = 'snapshot.xml'
1668 branch = 'snapshot'
1669 commits = git_util.get_history(
1670 repo,
1671 path,
1672 branch,
1673 after=old_timestamp,
1674 before=new_timestamp,
1675 with_subject=True)
1676
1677 # Unfortunately, we can not identify snapshot_id <= 5982 from its commit
1678 # subject, as their subjects are all `Annealing manifest snapshot.`.
1679 # So instead we count the snapshot_id manually.
1680 count = 5982
1681 # There are two snapshot_id = 2633 in commit history, ignore the former
1682 # one.
1683 ignore_list = ['95c8526a7f0798d02f692010669dcbd5a152439a']
1684 # We examine the commits in reverse order as there are some testing
1685 # commits before snapshot_id=2, this method works fine after
1686 # snapshot 2, except snapshot 2633
1687 for commit in reversed(commits):
1688 msg = commit[2]
1689 if commit[1] in ignore_list:
1690 continue
1691
1692 match = re.match(r'^annealing manifest snapshot (\d+)', msg)
1693 if match:
1694 snapshot_id = match.group(1)
1695 elif 'Annealing manifest snapshot' in msg:
1696 snapshot_id = str(count)
1697 count -= 1
1698 else:
1699 continue
Zheng-Jie Chang34595ea2020-03-10 13:04:22 +08001700 # b/151054108: snapshot version in [29288, 29439] is broken
1701 if 29288 <= int(snapshot_id) <= 29439:
1702 continue
Zheng-Jie Chang127c3302019-09-10 17:17:04 +08001703 if int(old_snapshot_id) <= int(snapshot_id) <= int(new_snapshot_id):
1704 result.append((commit[0], commit[1], snapshot_id))
1705 # We find commits in reversed order, now reverse it again to chronological
1706 # order.
1707 return list(reversed(result))
1708
1709 def lookup_build_timestamp(self, rev):
1710 assert is_cros_full_version(rev) or is_cros_snapshot_version(rev)
1711 if is_cros_full_version(rev):
1712 return self.lookup_release_build_timestamp(rev)
Kuang-che Wua7ddf9b2019-11-25 18:59:57 +08001713 return self.lookup_snapshot_build_timestamp(rev)
Zheng-Jie Chang127c3302019-09-10 17:17:04 +08001714
1715 def lookup_snapshot_build_timestamp(self, rev):
1716 assert is_cros_snapshot_version(rev)
1717 return int(self.lookup_snapshot_manifest_revisions(rev, rev)[0][0])
1718
1719 def lookup_release_build_timestamp(self, rev):
1720 assert is_cros_full_version(rev)
Kuang-che Wubfc4a642018-04-19 11:54:08 +08001721 milestone, short_version = version_split(rev)
Kuang-che Wue4bae0b2018-07-19 12:10:14 +08001722 path = os.path.join('buildspecs', milestone, short_version + '.xml')
1723 try:
1724 timestamp = git_util.get_commit_time(self.historical_manifest_git_dir,
1725 'refs/heads/master', path)
1726 except ValueError:
Kuang-che Wud1b74152020-05-20 08:46:46 +08001727 raise errors.InternalError('%s does not have %s' %
1728 (self.historical_manifest_git_dir, path))
Kuang-che Wue4bae0b2018-07-19 12:10:14 +08001729 return timestamp
Kuang-che Wubfc4a642018-04-19 11:54:08 +08001730
Zheng-Jie Chang868c1752020-01-21 14:42:41 +08001731 def detect_float_spec_branch_level(self, spec):
1732 results = [
1733 detect_branch_level(branch) for branch in git_util.get_branches(
1734 self.manifest_dir, commit=spec.name)
1735 ]
1736 results = [x for x in results if x > 0]
1737 return min(results) if results else 0
1738
1739 def branch_between_float_specs(self, old_spec, new_spec):
1740 if old_spec.spec_type != codechange.SPEC_FLOAT:
1741 return False
1742 if new_spec.spec_type != codechange.SPEC_FLOAT:
1743 return False
1744
1745 level_old = self.detect_float_spec_branch_level(old_spec)
1746 level_new = self.detect_float_spec_branch_level(new_spec)
1747
1748 if not level_old or not level_new:
1749 logger.warning('branch level detect failed, assume master')
1750 return False
1751 return level_old != level_new
1752
Kuang-che Wud558a042020-06-06 02:11:00 +08001753 def _determine_float_branch(self, old, new, fixed_specs):
1754 # There is no revision tag in snapshot's xml. We know snapshot
1755 # builds are on master branch.
1756 master_refname = 'refs/remotes/origin/master'
Zheng-Jie Chang3e191962020-02-06 14:25:31 +08001757 if fixed_specs[0].revision:
1758 old_branches = git_util.get_branches(
Kuang-che Wud558a042020-06-06 02:11:00 +08001759 self.manifest_dir, commit=fixed_specs[0].revision, remote=True)
1760 else:
1761 old_branches = [master_refname]
1762
Zheng-Jie Chang3e191962020-02-06 14:25:31 +08001763 if fixed_specs[-1].revision:
1764 new_branches = git_util.get_branches(
Kuang-che Wud558a042020-06-06 02:11:00 +08001765 self.manifest_dir, commit=fixed_specs[-1].revision, remote=True)
Zheng-Jie Chang3e191962020-02-06 14:25:31 +08001766 else:
Kuang-che Wud558a042020-06-06 02:11:00 +08001767 new_branches = [master_refname]
Zheng-Jie Chang3e191962020-02-06 14:25:31 +08001768
Kuang-che Wud558a042020-06-06 02:11:00 +08001769 common_branches = list(set(old_branches) & set(new_branches))
1770 assert common_branches, '%s and %s are not on common branches?' % (old, new)
1771
1772 if len(common_branches) == 1:
1773 return common_branches[0]
1774
1775 # There are more than one common branches, use heuristic to tie breaking.
1776 # The heuristic is simple: choice the branch with "smallest" number.
1777 # "Smaller" means the more major branch (not branched) or branched later.
1778 #
1779 # Following is the commit graph of manifest-internal repo. It shows many
1780 # interesting cases.
1781 #
1782 # 84/13021.0.0 84/13022.0.0 84/13024.0.0
1783 # --A--+---X--------------X------B-------X-----------> master
1784 # \
1785 # \ 83/13020.1.0 83/13020.56.0 83/13020.68.0
1786 # C---X----D--+-------X-------+--------X-----> release-R83-13020.B
1787 # \ \
1788 # \ E------------> stabilize-13020.67.B
1789 # \ 83/13020.55.1
1790 # F-----X--------------------> stabilize-13020.55.B
1791 #
1792 # How to read this graph:
1793 # - Time goes from left to right. Branch names are on the right side of
1794 # arrows.
1795 # - Letters A-F are manifest commits.
1796 # - Marker X means release image build at that time, the version numbers
1797 # are labeled above the X marker.
1798 # For example,
1799 # 1) 13021.0.0 release is based on manifest A, which is on all branches
1800 # shown on the graph.
1801 # We know 13021.0.0 is on master (and R84 branch later, not shown in
1802 # this graph), not on 13020* branches.
1803 # 2) 13020.56.0 release is based on manifest D, which is on 3 branches
1804 # (R83-13020.B, 13020.67.B, and 13020.55.B).
1805 # We know 13020.56.0 is on R83-13020.B and 13020.67.B, but not
1806 # 13020.55.B.
1807 #
1808 # There is an important property here. Every time a new branch is created,
1809 # there will always be a commit (like C, E, and F) to fix "revision" field
1810 # in the manifest file. In other words, xxxxx.1.0 is impossible based on
1811 # manifest on master branch. xxxxx.yy.1 is impossible based on manifest on
1812 # xxxxx.B branch.
1813 #
1814 # With such property, among the branches containing the given manifest
1815 # file, the branch with "smallest" number guarantees where the release is.
1816
1817 def branch_key(s):
1818 if s == master_refname:
1819 return 0, 0, 0
1820 m = re.search(r'-(\d+)\.B$', s)
1821 if m:
1822 return int(m.group(1)), 0, 0
1823 m = re.search(r'-(\d+)\.(\d+)\.B$', s)
1824 if m:
1825 return int(m.group(1)), int(m.group(2)), 0
1826 m = re.search(r'-(\d+)\.(\d+)\.(\d+)\.B$', s)
1827 if m:
1828 return int(m.group(1)), int(m.group(2)), int(m.group(3))
1829
1830 logger.warning('unexpected branch name: %s', s)
1831 return (sys.maxsize, sys.maxsize, sys.maxsize, s)
1832
1833 common_branches.sort(key=branch_key)
1834 return common_branches[0]
1835
1836 def collect_float_spec(self, old, new, fixed_specs=None):
1837 assert fixed_specs
1838 branch = self._determine_float_branch(old, new, fixed_specs)
1839 logger.debug('float branch=%s', branch)
Zheng-Jie Changd968f552020-01-16 13:31:57 +08001840
Kuang-che Wue4bae0b2018-07-19 12:10:14 +08001841 old_timestamp = self.lookup_build_timestamp(old)
1842 new_timestamp = self.lookup_build_timestamp(new)
Zheng-Jie Chang127c3302019-09-10 17:17:04 +08001843 # snapshot time is different from commit time
1844 # usually it's a few minutes different
1845 # 30 minutes should be safe in most cases
1846 if is_cros_snapshot_version(old):
1847 old_timestamp = old_timestamp - 1800
1848 if is_cros_snapshot_version(new):
1849 new_timestamp = new_timestamp + 1800
Kuang-che Wubfc4a642018-04-19 11:54:08 +08001850
Zheng-Jie Chang1ace3012020-02-15 04:51:05 +08001851 # TODO(zjchang): add logic to combine symlink target's (full.xml) history
Kuang-che Wue4bae0b2018-07-19 12:10:14 +08001852 result = []
Zheng-Jie Chang1ace3012020-02-15 04:51:05 +08001853 path = 'default.xml'
Kuang-che Wue4bae0b2018-07-19 12:10:14 +08001854 parser = repo_util.ManifestParser(self.manifest_dir)
1855 for timestamp, git_rev in parser.enumerate_manifest_commits(
Zheng-Jie Changd968f552020-01-16 13:31:57 +08001856 old_timestamp, new_timestamp, path, branch=branch):
Kuang-che Wue4bae0b2018-07-19 12:10:14 +08001857 result.append(
1858 codechange.Spec(codechange.SPEC_FLOAT, git_rev, timestamp, path))
1859 return result
Kuang-che Wubfc4a642018-04-19 11:54:08 +08001860
Kuang-che Wue4bae0b2018-07-19 12:10:14 +08001861 def collect_fixed_spec(self, old, new):
Zheng-Jie Chang127c3302019-09-10 17:17:04 +08001862 assert is_cros_full_version(old) or is_cros_snapshot_version(old)
1863 assert is_cros_full_version(new) or is_cros_snapshot_version(new)
1864
1865 # case 1: if both are snapshot, return a list of snapshot
1866 if is_cros_snapshot_version(old) and is_cros_snapshot_version(new):
1867 return self.collect_snapshot_specs(old, new)
1868
1869 # case 2: if both are release version
1870 # return a list of release version
1871 if is_cros_full_version(old) and is_cros_full_version(new):
1872 return self.collect_release_specs(old, new)
1873
1874 # case 3: return a list of release version and append a snapshot
1875 # before or at the end
Zheng-Jie Changc47af3a2019-11-11 17:28:58 +08001876 result = self.collect_release_specs(
1877 version_to_full(self.config['board'], old),
1878 version_to_full(self.config['board'], new))
Zheng-Jie Chang127c3302019-09-10 17:17:04 +08001879 if is_cros_snapshot_version(old):
Zheng-Jie Changc47af3a2019-11-11 17:28:58 +08001880 result = self.collect_snapshot_specs(old, old) + result[1:]
Zheng-Jie Chang127c3302019-09-10 17:17:04 +08001881 elif is_cros_snapshot_version(new):
Zheng-Jie Chang5cd62dd2019-12-09 13:21:12 +08001882 result += self.collect_snapshot_specs(new, new)
Zheng-Jie Chang127c3302019-09-10 17:17:04 +08001883 return result
1884
1885 def collect_snapshot_specs(self, old, new):
1886 assert is_cros_snapshot_version(old)
1887 assert is_cros_snapshot_version(new)
1888
1889 def guess_snapshot_version(board, snapshot_id, old, new):
1890 if old.endswith('-' + snapshot_id):
1891 return old
1892 if new.endswith('-' + snapshot_id):
1893 return new
Zheng-Jie Chang54020832020-04-21 15:52:48 +08001894 gs_path = ('gs://chromeos-image-archive/{board}-snapshot/'
Kuang-che Wuf791afa2019-10-28 19:53:26 +08001895 'R*-{snapshot_id}-*'.format(
1896 board=board, snapshot_id=snapshot_id))
Zheng-Jie Chang127c3302019-09-10 17:17:04 +08001897 for line in gsutil_ls(gs_path, ignore_errors=True):
1898 m = re.match(r'^gs:\S+(R\d+-\d+\.\d+\.\d+-\d+)\S+', line)
1899 if m:
1900 return m.group(1)
Zheng-Jie Chang026cd5d2019-12-04 12:13:01 +08001901 return None
Zheng-Jie Chang127c3302019-09-10 17:17:04 +08001902
1903 result = []
1904 path = 'snapshot.xml'
1905 revisions = self.lookup_snapshot_manifest_revisions(old, new)
Kuang-che Wuf791afa2019-10-28 19:53:26 +08001906 for timestamp, _git_rev, snapshot_id in revisions:
Zheng-Jie Chang127c3302019-09-10 17:17:04 +08001907 snapshot_version = guess_snapshot_version(self.config['board'],
1908 snapshot_id, old, new)
Zheng-Jie Chang026cd5d2019-12-04 12:13:01 +08001909 if snapshot_version:
1910 result.append(
1911 codechange.Spec(codechange.SPEC_FIXED, snapshot_version, timestamp,
1912 path))
1913 else:
1914 logger.warning('snapshot id %s is not found, ignore', snapshot_id)
Zheng-Jie Chang127c3302019-09-10 17:17:04 +08001915 return result
1916
1917 def collect_release_specs(self, old, new):
Kuang-che Wubfc4a642018-04-19 11:54:08 +08001918 assert is_cros_full_version(old)
1919 assert is_cros_full_version(new)
1920 old_milestone, old_short_version = version_split(old)
1921 new_milestone, new_short_version = version_split(new)
1922
Kuang-che Wubfc4a642018-04-19 11:54:08 +08001923 result = []
Kuang-che Wue4bae0b2018-07-19 12:10:14 +08001924 for milestone in git_util.list_dir_from_revision(
1925 self.historical_manifest_git_dir, 'refs/heads/master', 'buildspecs'):
1926 if not milestone.isdigit():
1927 continue
1928 if not int(old_milestone) <= int(milestone) <= int(new_milestone):
1929 continue
1930
Kuang-che Wu74768d32018-09-07 12:03:24 +08001931 files = git_util.list_dir_from_revision(
1932 self.historical_manifest_git_dir, 'refs/heads/master',
1933 os.path.join('buildspecs', milestone))
Kuang-che Wubfc4a642018-04-19 11:54:08 +08001934
1935 for fn in files:
Kuang-che Wue4bae0b2018-07-19 12:10:14 +08001936 path = os.path.join('buildspecs', milestone, fn)
Kuang-che Wubfc4a642018-04-19 11:54:08 +08001937 short_version, ext = os.path.splitext(fn)
1938 if ext != '.xml':
1939 continue
Kuang-che Wubfc4a642018-04-19 11:54:08 +08001940 if (util.is_version_lesseq(old_short_version, short_version) and
1941 util.is_version_lesseq(short_version, new_short_version) and
1942 util.is_direct_relative_version(short_version, new_short_version)):
Kuang-che Wue4bae0b2018-07-19 12:10:14 +08001943 rev = make_cros_full_version(milestone, short_version)
1944 timestamp = git_util.get_commit_time(self.historical_manifest_git_dir,
1945 'refs/heads/master', path)
1946 result.append(
1947 codechange.Spec(codechange.SPEC_FIXED, rev, timestamp, path))
Kuang-che Wubfc4a642018-04-19 11:54:08 +08001948
Kuang-che Wue4bae0b2018-07-19 12:10:14 +08001949 def version_key_func(spec):
1950 _milestone, short_version = version_split(spec.name)
Kuang-che Wubfc4a642018-04-19 11:54:08 +08001951 return util.version_key_func(short_version)
1952
1953 result.sort(key=version_key_func)
Kuang-che Wue4bae0b2018-07-19 12:10:14 +08001954 assert result[0].name == old
1955 assert result[-1].name == new
Kuang-che Wubfc4a642018-04-19 11:54:08 +08001956 return result
1957
Kuang-che Wue4bae0b2018-07-19 12:10:14 +08001958 def get_manifest(self, rev):
Zheng-Jie Chang127c3302019-09-10 17:17:04 +08001959 assert is_cros_full_version(rev) or is_cros_snapshot_version(rev)
1960 if is_cros_full_version(rev):
1961 milestone, short_version = version_split(rev)
1962 path = os.path.join('buildspecs', milestone, '%s.xml' % short_version)
1963 manifest = git_util.get_file_from_revision(
1964 self.historical_manifest_git_dir, 'refs/heads/master', path)
1965 else:
1966 revisions = self.lookup_snapshot_manifest_revisions(rev, rev)
1967 commit_id = revisions[0][1]
1968 manifest = git_util.get_file_from_revision(self.manifest_internal_dir,
1969 commit_id, 'snapshot.xml')
Zheng-Jie Chang0fc704b2019-12-09 18:43:38 +08001970 return manifest
1971
1972 def get_manifest_file(self, rev):
1973 assert is_cros_full_version(rev) or is_cros_snapshot_version(rev)
Kuang-che Wue4bae0b2018-07-19 12:10:14 +08001974 manifest_name = 'manifest_%s.xml' % rev
1975 manifest_path = os.path.join(self.manifest_dir, manifest_name)
1976 with open(manifest_path, 'w') as f:
Zheng-Jie Chang0fc704b2019-12-09 18:43:38 +08001977 f.write(self.get_manifest(rev))
Zheng-Jie Changec368b52020-03-02 16:25:25 +08001978
1979 # workaround for b/150572399
1980 # for chromeOS version < 12931.0.0, manifests are included from incorrect
1981 # folder .repo instead of.repo/manifests
1982 if is_cros_version_lesseq(rev, '12931.0.0'):
1983 repo_path = os.path.join(self.config['chromeos_root'], '.repo')
1984 manifest_patch_path = os.path.join(repo_path, manifest_name)
1985 with open(manifest_patch_path, 'w') as f:
1986 f.write(self.get_manifest(rev))
1987
Kuang-che Wue4bae0b2018-07-19 12:10:14 +08001988 return manifest_name
1989
1990 def parse_spec(self, spec):
1991 parser = repo_util.ManifestParser(self.manifest_dir)
1992 if spec.spec_type == codechange.SPEC_FIXED:
Zheng-Jie Chang0fc704b2019-12-09 18:43:38 +08001993 manifest_name = self.get_manifest_file(spec.name)
Kuang-che Wue4bae0b2018-07-19 12:10:14 +08001994 manifest_path = os.path.join(self.manifest_dir, manifest_name)
Kuang-che Wua5723492019-11-25 20:59:34 +08001995 with open(manifest_path) as f:
1996 content = f.read()
Kuang-che Wue4bae0b2018-07-19 12:10:14 +08001997 root = parser.parse_single_xml(content, allow_include=False)
1998 else:
1999 root = parser.parse_xml_recursive(spec.name, spec.path)
2000
2001 spec.entries = parser.process_parsed_result(root)
2002 if spec.spec_type == codechange.SPEC_FIXED:
Kuang-che Wufe1e88a2019-09-10 21:52:25 +08002003 if not spec.is_static():
Kuang-che Wud1b74152020-05-20 08:46:46 +08002004 raise ValueError('fixed spec %r has unexpected floating entries' %
2005 spec.name)
Zheng-Jie Changd968f552020-01-16 13:31:57 +08002006 spec.revision = root.get('revision')
Kuang-che Wue4bae0b2018-07-19 12:10:14 +08002007
2008 def sync_disk_state(self, rev):
Zheng-Jie Chang0fc704b2019-12-09 18:43:38 +08002009 manifest_name = self.get_manifest_file(rev)
Kuang-che Wue4bae0b2018-07-19 12:10:14 +08002010
2011 # For ChromeOS, mark_as_stable step requires 'repo init -m', which sticks
2012 # manifest. 'repo sync -m' is not enough
2013 repo_util.init(
2014 self.config['chromeos_root'],
2015 'https://chrome-internal.googlesource.com/chromeos/manifest-internal',
2016 manifest_name=manifest_name,
2017 repo_url='https://chromium.googlesource.com/external/repo.git',
Kuang-che Wud8fc9572018-10-03 21:00:41 +08002018 reference=self.config['chromeos_mirror'],
Zheng-Jie Chang85529ad2020-03-06 18:40:18 +08002019 # b/150753074: moblab is in non-default group and causes mark_as_stable
2020 # fail
Kuang-che Wu084eef22020-03-11 18:29:48 +08002021 groups='default,moblab,platform-linux',
Kuang-che Wue4bae0b2018-07-19 12:10:14 +08002022 )
2023
2024 # Note, don't sync with current_branch=True for chromeos. One of its
2025 # build steps (inside mark_as_stable) executes "git describe" which
2026 # needs git tag information.
2027 repo_util.sync(self.config['chromeos_root'])