blob: f4a1053d200cf7de51ebdcd8c853e36721d878a4 [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
24import subprocess
25import time
26
Zheng-Jie Chang2b6d1472019-11-13 12:40:17 +080027from bisect_kit import buildbucket_util
Kuang-che Wu2ea804f2017-11-28 17:11:41 +080028from bisect_kit import cli
Kuang-che Wue4bae0b2018-07-19 12:10:14 +080029from bisect_kit import codechange
Kuang-che Wu3eb6b502018-06-06 16:15:18 +080030from bisect_kit import cr_util
Kuang-che Wue121fae2018-11-09 16:18:39 +080031from bisect_kit import errors
Kuang-che Wubfc4a642018-04-19 11:54:08 +080032from bisect_kit import git_util
Kuang-che Wufb553102018-10-02 18:14:29 +080033from bisect_kit import locking
Kuang-che Wubfc4a642018-04-19 11:54:08 +080034from bisect_kit import repo_util
Kuang-che Wu2ea804f2017-11-28 17:11:41 +080035from bisect_kit import util
36
37logger = logging.getLogger(__name__)
38
Kuang-che Wu2ea804f2017-11-28 17:11:41 +080039re_chromeos_full_version = r'^R\d+-\d+\.\d+\.\d+$'
Kuang-che Wuacb6efd2018-04-25 18:52:58 +080040re_chromeos_localbuild_version = r'^\d+\.\d+\.\d{4}_\d\d_\d\d_\d{4}$'
41re_chromeos_short_version = r'^\d+\.\d+\.\d+$'
Zheng-Jie Chang127c3302019-09-10 17:17:04 +080042re_chromeos_snapshot_version = r'^R\d+-\d+\.\d+\.\d+-\d+$'
Kuang-che Wu2ea804f2017-11-28 17:11:41 +080043
44gs_archive_path = 'gs://chromeos-image-archive/{board}-release'
45gs_release_path = (
Kuang-che Wu80bf6a52019-05-31 12:48:06 +080046 'gs://chromeos-releases/{channel}-channel/{boardpath}/{short_version}')
Kuang-che Wu2ea804f2017-11-28 17:11:41 +080047
48# Assume gsutil is in PATH.
49gsutil_bin = 'gsutil'
Zheng-Jie Changb8697042019-10-29 16:03:26 +080050
51# Since snapshots with version >= 12618.0.0 have android and chrome version
52# info.
53snapshot_cutover_version = '12618.0.0'
Kuang-che Wu2ea804f2017-11-28 17:11:41 +080054
Kuang-che Wub9705bd2018-06-28 17:59:18 +080055chromeos_root_inside_chroot = '/mnt/host/source'
56# relative to chromeos_root
Kuang-che Wu7f82c6f2019-08-12 14:29:28 +080057prebuilt_autotest_dir = 'tmp/autotest-prebuilt'
Kuang-che Wu28980b22019-07-31 19:51:45 +080058# Relative to chromeos root. Images are cached_images_dir/$board/$image_name.
59cached_images_dir = 'src/build/images'
60test_image_filename = 'chromiumos_test_image.bin'
Kuang-che Wub9705bd2018-06-28 17:59:18 +080061
Kuang-che Wu2ea804f2017-11-28 17:11:41 +080062VERSION_KEY_CROS_SHORT_VERSION = 'cros_short_version'
63VERSION_KEY_CROS_FULL_VERSION = 'cros_full_version'
64VERSION_KEY_MILESTONE = 'milestone'
65VERSION_KEY_CR_VERSION = 'cr_version'
Kuang-che Wu708310b2018-03-28 17:24:34 +080066VERSION_KEY_ANDROID_BUILD_ID = 'android_build_id'
Kuang-che Wu2ea804f2017-11-28 17:11:41 +080067VERSION_KEY_ANDROID_BRANCH = 'android_branch'
68
69
Kuang-che Wu9890ce82018-07-07 15:14:10 +080070class NeedRecreateChrootException(Exception):
71 """Failed to build ChromeOS because of chroot mismatch or corruption"""
72
73
Kuang-che Wu2ea804f2017-11-28 17:11:41 +080074def is_cros_short_version(s):
Kuang-che Wuacb6efd2018-04-25 18:52:58 +080075 """Determines if `s` is chromeos short version.
76
77 This function doesn't accept version number of local build.
78 """
Kuang-che Wu2ea804f2017-11-28 17:11:41 +080079 return bool(re.match(re_chromeos_short_version, s))
80
81
Kuang-che Wuacb6efd2018-04-25 18:52:58 +080082def is_cros_localbuild_version(s):
83 """Determines if `s` is chromeos local build version."""
84 return bool(re.match(re_chromeos_localbuild_version, s))
85
86
Kuang-che Wu2ea804f2017-11-28 17:11:41 +080087def is_cros_full_version(s):
Kuang-che Wuacb6efd2018-04-25 18:52:58 +080088 """Determines if `s` is chromeos full version.
89
90 This function doesn't accept version number of local build.
91 """
Kuang-che Wu2ea804f2017-11-28 17:11:41 +080092 return bool(re.match(re_chromeos_full_version, s))
93
94
95def is_cros_version(s):
96 """Determines if `s` is chromeos version (either short or full)"""
97 return is_cros_short_version(s) or is_cros_full_version(s)
98
99
Zheng-Jie Chang127c3302019-09-10 17:17:04 +0800100def is_cros_snapshot_version(s):
101 """Determines if `s` is chromeos snapshot version"""
102 return bool(re.match(re_chromeos_snapshot_version, s))
103
104
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800105def make_cros_full_version(milestone, short_version):
106 """Makes full_version from milestone and short_version"""
Kuang-che Wuacb6efd2018-04-25 18:52:58 +0800107 assert milestone
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800108 return 'R%s-%s' % (milestone, short_version)
109
110
Zheng-Jie Chang127c3302019-09-10 17:17:04 +0800111def make_cros_snapshot_version(milestone, short_version, snapshot_id):
112 """Makes snapshot version from milestone, short_version and snapshot id"""
113 return 'R%s-%s-%s' % (milestone, short_version, snapshot_id)
114
115
116def version_split(version):
117 """Splits full_version or snapshot_version into milestone and short_version"""
118 assert is_cros_full_version(version) or is_cros_snapshot_version(version)
119 if is_cros_snapshot_version(version):
120 return snapshot_version_split(version)[0:2]
121 milestone, short_version = version.split('-')
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800122 return milestone[1:], short_version
123
124
Zheng-Jie Chang127c3302019-09-10 17:17:04 +0800125def snapshot_version_split(snapshot_version):
126 """Splits snapshot_version into milestone, short_version and snapshot_id"""
127 assert is_cros_snapshot_version(snapshot_version)
128 milestone, shot_version, snapshot_id = snapshot_version.split('-')
129 return milestone[1:], shot_version, snapshot_id
130
131
Zheng-Jie Changb8697042019-10-29 16:03:26 +0800132def query_snapshot_buildbucket_id(board, snapshot_version):
133 """Query buildbucket id of a snapshot"""
134 assert is_cros_snapshot_version(snapshot_version)
135 path = ('gs://chromeos-image-archive/{board}-postsubmit'
136 '/{snapshot_version}-*/image.zip')
137 output = gsutil_ls(
138 '-d',
139 path.format(board=board, snapshot_version=snapshot_version),
140 ignore_errors=True)
141 for line in output:
142 m = re.match(r'.*-postsubmit/R\d+-\d+\.\d+\.\d+-\d+-(.+)/image\.zip', line)
143 if m:
144 return m.group(1)
145 return None
146
147
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800148def argtype_cros_version(s):
Zheng-Jie Chang127c3302019-09-10 17:17:04 +0800149 if (not is_cros_version(s)) and (not is_cros_snapshot_version(s)):
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800150 msg = 'invalid cros version'
Kuang-che Wuce2f3be2019-10-28 19:44:54 +0800151 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 +0800152 return s
153
154
155def query_dut_lsb_release(host):
156 """Query /etc/lsb-release of given DUT
157
158 Args:
159 host: the DUT address
160
161 Returns:
Kuang-che Wu3eb6b502018-06-06 16:15:18 +0800162 dict for keys and values of /etc/lsb-release.
163
164 Raises:
Kuang-che Wu44278142019-03-04 11:33:57 +0800165 errors.SshConnectionError: cannot connect to host
166 errors.ExternalError: lsb-release file doesn't exist
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800167 """
168 try:
Kuang-che Wu44278142019-03-04 11:33:57 +0800169 output = util.ssh_cmd(host, 'cat', '/etc/lsb-release')
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800170 except subprocess.CalledProcessError:
Kuang-che Wu44278142019-03-04 11:33:57 +0800171 raise errors.ExternalError('unable to read /etc/lsb-release; not a DUT')
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800172 return dict(re.findall(r'^(\w+)=(.*)$', output, re.M))
173
174
175def is_dut(host):
Kuang-che Wubfc4a642018-04-19 11:54:08 +0800176 """Determines whether a host is a chromeos device.
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800177
178 Args:
179 host: the DUT address
180
181 Returns:
182 True if the host is a chromeos device.
183 """
Kuang-che Wu44278142019-03-04 11:33:57 +0800184 try:
185 return query_dut_lsb_release(host).get('DEVICETYPE') in [
186 'CHROMEBASE',
187 'CHROMEBIT',
188 'CHROMEBOOK',
189 'CHROMEBOX',
190 'REFERENCE',
191 ]
192 except (errors.ExternalError, errors.SshConnectionError):
193 return False
194
195
196def is_good_dut(host):
197 if not is_dut(host):
198 return False
199
200 # Sometimes python is broken after 'cros flash'.
201 try:
202 util.ssh_cmd(host, 'python', '-c', '1')
203 return True
204 except (subprocess.CalledProcessError, errors.SshConnectionError):
205 return False
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800206
207
208def query_dut_board(host):
209 """Query board name of a given DUT"""
210 return query_dut_lsb_release(host).get('CHROMEOS_RELEASE_BOARD')
211
212
213def query_dut_short_version(host):
Kuang-che Wuacb6efd2018-04-25 18:52:58 +0800214 """Query short version of a given DUT.
215
216 This function may return version of local build, which
217 is_cros_short_version() is false.
218 """
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800219 return query_dut_lsb_release(host).get('CHROMEOS_RELEASE_VERSION')
220
221
Zheng-Jie Chang127c3302019-09-10 17:17:04 +0800222def query_dut_is_snapshot(host):
223 """Query if given DUT is a snapshot version."""
224 path = query_dut_lsb_release(host).get('CHROMEOS_RELEASE_BUILDER_PATH', '')
225 return '-postsubmit' in path
226
227
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800228def query_dut_boot_id(host, connect_timeout=None):
Kuang-che Wubfc4a642018-04-19 11:54:08 +0800229 """Query boot id.
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800230
231 Args:
232 host: DUT address
233 connect_timeout: connection timeout
234
235 Returns:
236 boot uuid
237 """
Kuang-che Wu44278142019-03-04 11:33:57 +0800238 return util.ssh_cmd(
239 host,
240 'cat',
241 '/proc/sys/kernel/random/boot_id',
242 connect_timeout=connect_timeout).strip()
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800243
244
245def reboot(host):
246 """Reboot a DUT and verify"""
247 logger.debug('reboot %s', host)
248 boot_id = query_dut_boot_id(host)
249
Kuang-che Wu44278142019-03-04 11:33:57 +0800250 try:
251 util.ssh_cmd(host, 'reboot')
Kuang-che Wu5f662e82019-03-05 11:49:56 +0800252 except errors.SshConnectionError:
253 # Depends on timing, ssh may return failure due to broken pipe, which is
254 # working as intended. Ignore such kind of errors.
Kuang-che Wu44278142019-03-04 11:33:57 +0800255 pass
Kuang-che Wu708310b2018-03-28 17:24:34 +0800256 wait_reboot_done(host, boot_id)
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800257
Kuang-che Wu708310b2018-03-28 17:24:34 +0800258
259def wait_reboot_done(host, boot_id):
Kuang-che Wu4fe945b2018-03-31 16:46:38 +0800260 # For dev-mode test image, the reboot time is roughly at least 16 seconds
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800261 # (dev screen short delay) or more (long delay).
262 time.sleep(15)
263 for _ in range(100):
264 try:
265 # During boot, DUT does not response and thus ssh may hang a while. So
266 # set a connect timeout. 3 seconds are enough and 2 are not. It's okay to
267 # set tight limit because it's inside retry loop.
268 assert boot_id != query_dut_boot_id(host, connect_timeout=3)
269 return
Kuang-che Wu5f662e82019-03-05 11:49:56 +0800270 except errors.SshConnectionError:
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800271 logger.debug('reboot not ready? sleep wait 1 sec')
272 time.sleep(1)
273
Kuang-che Wue121fae2018-11-09 16:18:39 +0800274 raise errors.ExternalError('reboot failed?')
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800275
276
Kuang-che Wu80bf6a52019-05-31 12:48:06 +0800277def gs_release_boardpath(board):
278 """Normalizes board name for gs://chromeos-releases/
279
280 This follows behavior of PushImage() in chromite/scripts/pushimage.py
281 Note, only gs://chromeos-releases/ needs normalization,
282 gs://chromeos-image-archive does not.
283
284 Args:
285 board: ChromeOS board name
286
287 Returns:
288 normalized board name
289 """
290 return board.replace('_', '-')
291
292
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800293def gsutil(*args, **kwargs):
Kuang-che Wubfc4a642018-04-19 11:54:08 +0800294 """gsutil command line wrapper.
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800295
296 Args:
297 args: command line arguments passed to gsutil
298 kwargs:
299 ignore_errors: if true, return '' for failures, for example 'gsutil ls'
300 but the path not found.
301
302 Returns:
303 stdout of gsutil
304
305 Raises:
Zheng-Jie Changb8697042019-10-29 16:03:26 +0800306 errors.ExternalError: gsutil failed to run
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800307 subprocess.CalledProcessError: command failed
308 """
309 stderr_lines = []
310 try:
311 return util.check_output(
312 gsutil_bin, *args, stderr_callback=stderr_lines.append)
313 except subprocess.CalledProcessError as e:
314 stderr = ''.join(stderr_lines)
315 if re.search(r'ServiceException:.* does not have .*access', stderr):
Kuang-che Wue121fae2018-11-09 16:18:39 +0800316 raise errors.ExternalError(
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800317 'gsutil failed due to permission. ' +
318 'Run "%s config" and follow its instruction. ' % gsutil_bin +
319 'Fill any string if it asks for project-id')
320 if kwargs.get('ignore_errors'):
321 return ''
322 raise
323 except OSError as e:
324 if e.errno == errno.ENOENT:
Kuang-che Wue121fae2018-11-09 16:18:39 +0800325 raise errors.ExternalError(
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800326 'Unable to run %s. gsutil is not installed or not in PATH?' %
327 gsutil_bin)
328 raise
329
330
331def gsutil_ls(*args, **kwargs):
Kuang-che Wubfc4a642018-04-19 11:54:08 +0800332 """gsutil ls.
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800333
334 Args:
335 args: arguments passed to 'gsutil ls'
336 kwargs: extra parameters, where
Kuang-che Wu4fe945b2018-03-31 16:46:38 +0800337 ignore_errors: if true, return empty list instead of raising
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800338 exception, ex. path not found.
339
340 Returns:
341 list of 'gsutil ls' result. One element for one line of gsutil output.
342
343 Raises:
344 subprocess.CalledProcessError: gsutil failed, usually means path not found
345 """
346 return gsutil('ls', *args, **kwargs).splitlines()
347
348
Zheng-Jie Chang127c3302019-09-10 17:17:04 +0800349def gsutil_stat_update_time(*args, **kwargs):
350 """Returns the last modified time of a file or multiple files.
351
352 Args:
353 args: arguments passed to 'gsutil stat'.
354 kwargs: extra parameters for gsutil.
355
356 Returns:
357 A integer indicates the last modified timestamp.
358
359 Raises:
360 subprocess.CalledProcessError: gsutil failed, usually means path not found
361 errors.ExternalError: update time is not found
362 """
363 result = -1
364 # Currently we believe stat always returns a UTC time, and strptime also
365 # parses a UTC time by default.
366 time_format = '%a, %d %b %Y %H:%M:%S GMT'
367
368 for line in gsutil('stat', *args, **kwargs).splitlines():
369 if ':' not in line:
370 continue
Kuang-che Wuc89f2a22019-11-26 15:30:50 +0800371 key, value = line.split(':', 1)
372 key, value = key.strip(), value.strip()
Zheng-Jie Chang127c3302019-09-10 17:17:04 +0800373 if key != 'Update time':
374 continue
375 dt = datetime.datetime.strptime(value, time_format)
Kuang-che Wu72b5a572019-10-29 20:37:57 +0800376 unixtime = int(calendar.timegm(dt.utctimetuple()))
Zheng-Jie Chang127c3302019-09-10 17:17:04 +0800377 result = max(result, unixtime)
378
379 if result == -1:
380 raise errors.ExternalError("didn't find update time")
381 return result
382
383
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800384def query_milestone_by_version(board, short_version):
Kuang-che Wubfc4a642018-04-19 11:54:08 +0800385 """Query milestone by ChromeOS version number.
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800386
387 Args:
388 board: ChromeOS board name
389 short_version: ChromeOS version number in short format, ex. 9300.0.0
390
391 Returns:
392 ChromeOS milestone number (string). For example, '58' for '9300.0.0'.
393 None if failed.
394 """
395 path = gs_archive_path.format(board=board) + '/R*-' + short_version
396 for line in gsutil_ls('-d', path, ignore_errors=True):
397 m = re.search(r'/R(\d+)-', line)
398 if not m:
399 continue
400 return m.group(1)
401
402 for channel in ['canary', 'dev', 'beta', 'stable']:
403 path = gs_release_path.format(
Kuang-che Wu80bf6a52019-05-31 12:48:06 +0800404 channel=channel,
405 boardpath=gs_release_boardpath(board),
406 short_version=short_version)
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800407 for line in gsutil_ls(path, ignore_errors=True):
408 m = re.search(r'\bR(\d+)-' + short_version, line)
409 if not m:
410 continue
411 return m.group(1)
412
413 logger.error('unable to query milestone of %s for %s', short_version, board)
414 return None
415
416
Kuang-che Wu80bf6a52019-05-31 12:48:06 +0800417def list_board_names(chromeos_root):
418 """List board names.
419
420 Args:
421 chromeos_root: chromeos tree root
422
423 Returns:
424 list of board names
425 """
426 # Following logic is simplified from chromite/lib/portage_util.py
427 cros_list_overlays = os.path.join(chromeos_root,
428 'chromite/bin/cros_list_overlays')
429 overlays = util.check_output(cros_list_overlays).splitlines()
430 result = set()
431 for overlay in overlays:
432 conf_file = os.path.join(overlay, 'metadata', 'layout.conf')
433 name = None
434 if os.path.exists(conf_file):
435 for line in open(conf_file):
436 m = re.match(r'^repo-name\s*=\s*(\S+)\s*$', line)
437 if m:
438 name = m.group(1)
439 break
440
441 if not name:
442 name_file = os.path.join(overlay, 'profiles', 'repo_name')
443 if os.path.exists(name_file):
Kuang-che Wua5723492019-11-25 20:59:34 +0800444 with open(name_file) as f:
445 name = f.read().strip()
Kuang-che Wu80bf6a52019-05-31 12:48:06 +0800446
447 if name:
448 name = re.sub(r'-private$', '', name)
449 result.add(name)
450
451 return list(result)
452
453
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800454def recognize_version(board, version):
Kuang-che Wubfc4a642018-04-19 11:54:08 +0800455 """Recognize ChromeOS version.
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800456
457 Args:
458 board: ChromeOS board name
459 version: ChromeOS version number in short or full format
460
461 Returns:
462 (milestone, version in short format)
463 """
464 if is_cros_short_version(version):
465 milestone = query_milestone_by_version(board, version)
466 short_version = version
467 else:
468 milestone, short_version = version_split(version)
469 return milestone, short_version
470
471
Zheng-Jie Chang127c3302019-09-10 17:17:04 +0800472def extract_major_version(version):
473 """Converts a version to its major version.
474
475 Args:
Kuang-che Wu9501f342019-11-15 17:15:21 +0800476 version: ChromeOS version number or snapshot version
Zheng-Jie Chang127c3302019-09-10 17:17:04 +0800477
478 Returns:
479 major version number in string format
480 """
481 version = version_to_short(version)
482 m = re.match(r'^(\d+)\.\d+\.\d+$', version)
483 return m.group(1)
484
485
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800486def version_to_short(version):
Kuang-che Wubfc4a642018-04-19 11:54:08 +0800487 """Convert ChromeOS version number to short format.
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800488
489 Args:
490 version: ChromeOS version number in short or full format
491
492 Returns:
493 version number in short format
494 """
495 if is_cros_short_version(version):
496 return version
497 _, short_version = version_split(version)
498 return short_version
499
500
501def version_to_full(board, version):
Kuang-che Wubfc4a642018-04-19 11:54:08 +0800502 """Convert ChromeOS version number to full format.
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800503
504 Args:
505 board: ChromeOS board name
506 version: ChromeOS version number in short or full format
507
508 Returns:
509 version number in full format
510 """
Zheng-Jie Changc47af3a2019-11-11 17:28:58 +0800511 if is_cros_snapshot_version(version):
512 milestone, short_version, _ = snapshot_version_split(version)
513 return make_cros_full_version(milestone, short_version)
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800514 if is_cros_full_version(version):
515 return version
516 milestone = query_milestone_by_version(board, version)
Kuang-che Wu0205f052019-05-23 12:48:37 +0800517 assert milestone, 'incorrect board=%s or version=%s ?' % (board, version)
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800518 return make_cros_full_version(milestone, version)
519
520
Zheng-Jie Chang127c3302019-09-10 17:17:04 +0800521def list_snapshots_from_image_archive(board, major_version):
Kuang-che Wu9501f342019-11-15 17:15:21 +0800522 """List ChromeOS snapshot image available from gs://chromeos-image-archive.
Zheng-Jie Chang127c3302019-09-10 17:17:04 +0800523
524 Args:
525 board: ChromeOS board
526 major_version: ChromeOS major version
527
528 Returns:
529 list of (version, gs_path):
530 version: Chrome OS snapshot version
531 gs_path: gs path of test image
532 """
533
Zheng-Jie Chang43a08412019-12-05 17:05:45 +0800534 def extract_snapshot_id(result):
535 m = re.match(r'^R\d+-\d+\.\d+\.\d+-(\d+)', result[0])
536 assert m
537 return int(m.group(1))
538
Zheng-Jie Chang127c3302019-09-10 17:17:04 +0800539 path = (
540 'gs://chromeos-image-archive/{board}-postsubmit/R*-{major_version}.0.0-*')
541 result = []
542 output = gsutil_ls(
543 '-d',
544 path.format(board=board, major_version=major_version),
545 ignore_errors=True)
546
Zheng-Jie Chang43a08412019-12-05 17:05:45 +0800547 for path in sorted(output):
Zheng-Jie Chang127c3302019-09-10 17:17:04 +0800548 if not path.endswith('/'):
549 continue
550 m = re.match(r'^gs:\S+(R\d+-\d+\.\d+\.\d+-\d+)', path)
551 if m:
552 snapshot_version = m.group(1)
553 test_image = 'image.zip'
554 gs_path = path + test_image
Zheng-Jie Chang43a08412019-12-05 17:05:45 +0800555 # we should skip if there is duplicate snapshot
556 if result and result[-1][0] == snapshot_version:
557 continue
Zheng-Jie Chang127c3302019-09-10 17:17:04 +0800558 result.append((snapshot_version, gs_path))
Zheng-Jie Chang43a08412019-12-05 17:05:45 +0800559
560 # sort by its snapshot_id
561 result.sort(key=extract_snapshot_id)
Zheng-Jie Chang127c3302019-09-10 17:17:04 +0800562 return result
563
564
Kuang-che Wu575dc442019-03-05 10:30:55 +0800565def list_prebuilt_from_image_archive(board):
566 """Lists ChromeOS prebuilt image available from gs://chromeos-image-archive.
567
568 gs://chromeos-image-archive contains only recent builds (in two years).
569 We prefer this function to list_prebuilt_from_chromeos_releases() because
570 - this is what "cros flash" supports directly.
571 - the paths have milestone information, so we don't need to do slow query
572 by ourselves.
573
574 Args:
575 board: ChromeOS board name
576
577 Returns:
578 list of (version, gs_path):
579 version: Chrome OS version in full format
580 gs_path: gs path of test image
581 """
582 result = []
583 for line in gsutil_ls(gs_archive_path.format(board=board)):
584 m = re.match(r'^gs:\S+(R\d+-\d+\.\d+\.\d+)', line)
585 if m:
586 full_version = m.group(1)
587 test_image = 'chromiumos_test_image.tar.xz'
588 assert line.endswith('/')
589 gs_path = line + test_image
590 result.append((full_version, gs_path))
591 return result
592
593
594def list_prebuilt_from_chromeos_releases(board):
595 """Lists ChromeOS versions available from gs://chromeos-releases.
596
597 gs://chromeos-releases contains more builds. However, 'cros flash' doesn't
598 support it.
599
600 Args:
601 board: ChromeOS board name
602
603 Returns:
604 list of (version, gs_path):
605 version: Chrome OS version in short format
606 gs_path: gs path of test image (with wildcard)
607 """
608 result = []
609 for line in gsutil_ls(
Kuang-che Wu80bf6a52019-05-31 12:48:06 +0800610 gs_release_path.format(
611 channel='*', boardpath=gs_release_boardpath(board), short_version=''),
Kuang-che Wu575dc442019-03-05 10:30:55 +0800612 ignore_errors=True):
613 m = re.match(r'gs:\S+/(\d+\.\d+\.\d+)/$', line)
614 if m:
615 short_version = m.group(1)
616 test_image = 'ChromeOS-test-R*-{short_version}-{board}.tar.xz'.format(
617 short_version=short_version, board=board)
618 gs_path = line + test_image
619 result.append((short_version, gs_path))
620 return result
621
622
623def list_chromeos_prebuilt_versions(board,
624 old,
625 new,
626 only_good_build=True,
Zheng-Jie Chang127c3302019-09-10 17:17:04 +0800627 include_older_build=True,
628 use_snapshot=False):
Kuang-che Wu575dc442019-03-05 10:30:55 +0800629 """Lists ChromeOS version numbers with prebuilt between given range
630
631 Args:
632 board: ChromeOS board name
633 old: start version (inclusive)
634 new: end version (inclusive)
635 only_good_build: only if test image is available
636 include_older_build: include prebuilt in gs://chromeos-releases
Zheng-Jie Chang127c3302019-09-10 17:17:04 +0800637 use_snapshot: return snapshot versions if found
Kuang-che Wu575dc442019-03-05 10:30:55 +0800638
639 Returns:
640 list of sorted version numbers (in full format) between [old, new] range
641 (inclusive).
642 """
643 old = version_to_short(old)
644 new = version_to_short(new)
645
Zheng-Jie Chang127c3302019-09-10 17:17:04 +0800646 rev_map = {
647 } # dict: short version -> list of (short/full or snapshot version, gs path)
Kuang-che Wu575dc442019-03-05 10:30:55 +0800648 for full_version, gs_path in list_prebuilt_from_image_archive(board):
649 short_version = version_to_short(full_version)
Zheng-Jie Chang127c3302019-09-10 17:17:04 +0800650 rev_map[short_version] = [(full_version, gs_path)]
Kuang-che Wu575dc442019-03-05 10:30:55 +0800651
652 if include_older_build and old not in rev_map:
653 for short_version, gs_path in list_prebuilt_from_chromeos_releases(board):
654 if short_version not in rev_map:
Zheng-Jie Chang127c3302019-09-10 17:17:04 +0800655 rev_map[short_version] = [(short_version, gs_path)]
656
657 if use_snapshot:
658 for major_version in range(
659 int(extract_major_version(old)),
660 int(extract_major_version(new)) + 1):
661 short_version = '%s.0.0' % major_version
Zheng-Jie Changc47af3a2019-11-11 17:28:58 +0800662 next_short_version = '%s.0.0' % (major_version + 1)
Zheng-Jie Changb8697042019-10-29 16:03:26 +0800663 # If current version is smaller than cutover, ignore it as it might not
664 # contain enough information for continuing android and chrome bisection.
665 if not util.is_version_lesseq(snapshot_cutover_version, short_version):
666 continue
Zheng-Jie Changc47af3a2019-11-11 17:28:58 +0800667
668 # Given the fact that snapshots are images between two release versions.
669 # Adding snapshots of 12345.0.0 should be treated as adding commits
670 # between [12345.0.0, 12346.0.0).
671 # So in the following lines we check two facts:
672 # 1) If 12346.0.0(next_short_version) is a version between old and new
673 if not util.is_direct_relative_version(next_short_version, old):
674 continue
675 if not util.is_direct_relative_version(next_short_version, new):
676 continue
677 # 2) If 12345.0.0(short_version) is a version between old and new
Zheng-Jie Chang127c3302019-09-10 17:17:04 +0800678 if not util.is_direct_relative_version(short_version, old):
679 continue
680 if not util.is_direct_relative_version(short_version, new):
681 continue
Zheng-Jie Changc47af3a2019-11-11 17:28:58 +0800682
Zheng-Jie Chang127c3302019-09-10 17:17:04 +0800683 snapshots = list_snapshots_from_image_archive(board, str(major_version))
684 if snapshots:
685 # if snapshots found, we can append them after the release version,
686 # so the prebuilt image list of this version will be
687 # release_image, snapshot1, snapshot2,...
Zheng-Jie Changb8697042019-10-29 16:03:26 +0800688 if short_version not in rev_map:
689 rev_map[short_version] = []
Zheng-Jie Chang127c3302019-09-10 17:17:04 +0800690 rev_map[short_version] += snapshots
Kuang-che Wu575dc442019-03-05 10:30:55 +0800691
692 result = []
693 for rev in sorted(rev_map, key=util.version_key_func):
694 if not util.is_direct_relative_version(new, rev):
695 continue
696 if not util.is_version_lesseq(old, rev):
697 continue
698 if not util.is_version_lesseq(rev, new):
699 continue
700
Zheng-Jie Chang127c3302019-09-10 17:17:04 +0800701 for version, gs_path in rev_map[rev]:
Kuang-che Wu575dc442019-03-05 10:30:55 +0800702
Zheng-Jie Chang127c3302019-09-10 17:17:04 +0800703 # version_to_full() and gsutil_ls() may take long time if versions are a
704 # lot. This is acceptable because we usually bisect only short range.
Kuang-che Wu575dc442019-03-05 10:30:55 +0800705
Zheng-Jie Chang127c3302019-09-10 17:17:04 +0800706 if only_good_build:
707 gs_result = gsutil_ls(gs_path, ignore_errors=True)
708 if not gs_result:
709 logger.warning('%s is not a good build, ignore', version)
710 continue
711 assert len(gs_result) == 1
712 m = re.search(r'(R\d+-\d+\.\d+\.\d+)', gs_result[0])
713 if not m:
714 logger.warning('format of image path is unexpected: %s', gs_result[0])
715 continue
716 if not is_cros_snapshot_version(version):
717 version = m.group(1)
718 elif is_cros_short_version(version):
719 version = version_to_full(board, version)
Kuang-che Wu575dc442019-03-05 10:30:55 +0800720
Zheng-Jie Chang127c3302019-09-10 17:17:04 +0800721 result.append(version)
Kuang-che Wu575dc442019-03-05 10:30:55 +0800722
723 return result
724
725
Zheng-Jie Chang127c3302019-09-10 17:17:04 +0800726def prepare_snapshot_image(chromeos_root, board, snapshot_version):
727 """Prepare chromeos snapshot image.
728
729 Args:
730 chromeos_root: chromeos tree root
731 board: ChromeOS board name
732 snapshot_version: ChromeOS snapshot version number
733
734 Returns:
735 local file path of test image relative to chromeos_root
736 """
737 assert is_cros_snapshot_version(snapshot_version)
738 milestone, short_version, snapshot_id = snapshot_version_split(
739 snapshot_version)
740 full_version = make_cros_full_version(milestone, short_version)
741 tmp_dir = os.path.join(
742 chromeos_root, 'tmp',
743 'ChromeOS-test-%s-%s-%s' % (full_version, board, snapshot_id))
744 if not os.path.exists(tmp_dir):
745 os.makedirs(tmp_dir)
746
747 gs_path = ('gs://chromeos-image-archive/{board}-postsubmit/' +
748 '{snapshot_version}-*/image.zip')
749 gs_path = gs_path.format(board=board, snapshot_version=snapshot_version)
750
Zheng-Jie Changc47af3a2019-11-11 17:28:58 +0800751 full_path = os.path.join(tmp_dir, test_image_filename)
752 rel_path = os.path.relpath(full_path, chromeos_root)
753 if os.path.exists(full_path):
754 return rel_path
755
Zheng-Jie Chang127c3302019-09-10 17:17:04 +0800756 files = gsutil_ls(gs_path, ignore_errors=True)
Zheng-Jie Changeb7308f2019-12-05 14:25:05 +0800757 if len(files) >= 1:
Zheng-Jie Chang127c3302019-09-10 17:17:04 +0800758 gs_path = files[0]
759 gsutil('cp', gs_path, tmp_dir)
Zheng-Jie Chang127c3302019-09-10 17:17:04 +0800760 util.check_call(
761 'unzip', '-j', 'image.zip', test_image_filename, cwd=tmp_dir)
762 os.remove(os.path.join(tmp_dir, 'image.zip'))
Zheng-Jie Changc47af3a2019-11-11 17:28:58 +0800763 return rel_path
Zheng-Jie Chang127c3302019-09-10 17:17:04 +0800764
Zheng-Jie Changc47af3a2019-11-11 17:28:58 +0800765 assert False
Kuang-che Wua7ddf9b2019-11-25 18:59:57 +0800766 return None
Zheng-Jie Chang127c3302019-09-10 17:17:04 +0800767
768
Kuang-che Wu28980b22019-07-31 19:51:45 +0800769def prepare_prebuilt_image(chromeos_root, board, version):
Kuang-che Wubfc4a642018-04-19 11:54:08 +0800770 """Prepare chromeos prebuilt image.
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800771
Kuang-che Wubfc4a642018-04-19 11:54:08 +0800772 It searches for xbuddy image which "cros flash" can use, or fetch image to
773 local disk.
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800774
775 Args:
Kuang-che Wu28980b22019-07-31 19:51:45 +0800776 chromeos_root: chromeos tree root
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800777 board: ChromeOS board name
Kuang-che Wubfc4a642018-04-19 11:54:08 +0800778 version: ChromeOS version number in short or full format
779
780 Returns:
Kuang-che Wu28980b22019-07-31 19:51:45 +0800781 xbuddy path or file path (relative to chromeos_root)
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800782 """
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800783 assert is_cros_version(version)
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800784 full_version = version_to_full(board, version)
785 short_version = version_to_short(full_version)
786
787 image_path = None
788 gs_path = gs_archive_path.format(board=board) + '/' + full_version
789 if gsutil_ls('-d', gs_path, ignore_errors=True):
790 image_path = 'xbuddy://remote/{board}/{full_version}/test'.format(
791 board=board, full_version=full_version)
792 else:
Kuang-che Wu28980b22019-07-31 19:51:45 +0800793 tmp_dir = os.path.join(chromeos_root, 'tmp',
794 'ChromeOS-test-%s-%s' % (full_version, board))
Zheng-Jie Changc47af3a2019-11-11 17:28:58 +0800795 full_path = os.path.join(tmp_dir, test_image_filename)
796 rel_path = os.path.relpath(full_path, chromeos_root)
797 if os.path.exists(full_path):
798 return rel_path
799
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800800 if not os.path.exists(tmp_dir):
801 os.makedirs(tmp_dir)
802 # gs://chromeos-releases may have more old images than
Kuang-che Wu4fe945b2018-03-31 16:46:38 +0800803 # gs://chromeos-image-archive, but 'cros flash' doesn't support it. We have
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800804 # to fetch the image by ourselves
805 for channel in ['canary', 'dev', 'beta', 'stable']:
806 fn = 'ChromeOS-test-{full_version}-{board}.tar.xz'.format(
807 full_version=full_version, board=board)
808 gs_path = gs_release_path.format(
Kuang-che Wu80bf6a52019-05-31 12:48:06 +0800809 channel=channel,
810 boardpath=gs_release_boardpath(board),
811 short_version=short_version)
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800812 gs_path += '/' + fn
813 if gsutil_ls(gs_path, ignore_errors=True):
814 # TODO(kcwu): delete tmp
815 gsutil('cp', gs_path, tmp_dir)
816 util.check_call('tar', 'Jxvf', fn, cwd=tmp_dir)
Zheng-Jie Changc47af3a2019-11-11 17:28:58 +0800817 image_path = os.path.relpath(full_path, chromeos_root)
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800818 break
819
820 assert image_path
Kuang-che Wubfc4a642018-04-19 11:54:08 +0800821 return image_path
822
823
824def cros_flash(chromeos_root,
825 host,
826 board,
827 image_path,
828 version=None,
829 clobber_stateful=False,
Kuang-che Wu155fb6e2018-11-29 16:00:41 +0800830 disable_rootfs_verification=True):
Kuang-che Wubfc4a642018-04-19 11:54:08 +0800831 """Flash a DUT with given ChromeOS image.
832
833 This is implemented by 'cros flash' command line.
834
835 Args:
836 chromeos_root: use 'cros flash' of which chromeos tree
837 host: DUT address
838 board: ChromeOS board name
Kuang-che Wu28980b22019-07-31 19:51:45 +0800839 image_path: chromeos image xbuddy path or file path. For relative
840 path, it should be relative to chromeos_root.
Kuang-che Wubfc4a642018-04-19 11:54:08 +0800841 version: ChromeOS version in short or full format
842 clobber_stateful: Clobber stateful partition when performing update
843 disable_rootfs_verification: Disable rootfs verification after update
844 is completed
Kuang-che Wu414d67f2019-05-28 11:28:57 +0800845
846 Raises:
847 errors.ExternalError: cros flash failed
Kuang-che Wubfc4a642018-04-19 11:54:08 +0800848 """
849 logger.info('cros_flash %s %s %s %s', host, board, version, image_path)
850
851 # Reboot is necessary because sometimes previous 'cros flash' failed and
852 # entered a bad state.
853 reboot(host)
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800854
Kuang-che Wu28980b22019-07-31 19:51:45 +0800855 # Handle relative path.
856 if '://' not in image_path and not os.path.isabs(image_path):
857 assert os.path.exists(os.path.join(chromeos_root, image_path))
858 image_path = os.path.join(chromeos_root_inside_chroot, image_path)
859
Kuang-che Wuf3d03ca2019-03-11 17:31:40 +0800860 args = [
861 '--debug', '--no-ping', '--send-payload-in-parallel', host, image_path
862 ]
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800863 if clobber_stateful:
864 args.append('--clobber-stateful')
865 if disable_rootfs_verification:
866 args.append('--disable-rootfs-verification')
867
Kuang-che Wu414d67f2019-05-28 11:28:57 +0800868 try:
869 cros_sdk(chromeos_root, 'cros', 'flash', *args)
870 except subprocess.CalledProcessError:
871 raise errors.ExternalError('cros flash failed')
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800872
Kuang-che Wubfc4a642018-04-19 11:54:08 +0800873 if version:
874 # In the past, cros flash may fail with returncode=0
875 # So let's have an extra check.
Zheng-Jie Chang127c3302019-09-10 17:17:04 +0800876 if is_cros_snapshot_version(version):
877 builder_path = query_dut_lsb_release(host).get(
878 'CHROMEOS_RELEASE_BUILDER_PATH', '')
879 expect_prefix = '%s-postsubmit/%s-' % (board, version)
880 if not builder_path.startswith(expect_prefix):
881 raise errors.ExternalError(
882 'although cros flash succeeded, the OS builder path is '
883 'unexpected: actual=%s expect=%s' % (builder_path, expect_prefix))
884 else:
885 expect_version = version_to_short(version)
886 dut_version = query_dut_short_version(host)
887 if dut_version != expect_version:
888 raise errors.ExternalError(
889 'although cros flash succeeded, the OS version is unexpected: '
890 'actual=%s expect=%s' % (dut_version, expect_version))
Kuang-che Wu414d67f2019-05-28 11:28:57 +0800891
Kuang-che Wu4a81ea72019-10-05 15:35:17 +0800892 # "cros flash" may terminate successfully but the DUT starts self-repairing
Kuang-che Wu414d67f2019-05-28 11:28:57 +0800893 # (b/130786578), so it's necessary to do sanity check.
894 if not is_good_dut(host):
895 raise errors.ExternalError(
896 'although cros flash succeeded, the DUT is in bad state')
897
898
899def cros_flash_with_retry(chromeos_root,
900 host,
901 board,
902 image_path,
903 version=None,
904 clobber_stateful=False,
905 disable_rootfs_verification=True,
906 repair_callback=None):
907 # 'cros flash' is not 100% reliable, retry if necessary.
908 for attempt in range(2):
909 if attempt > 0:
910 logger.info('will retry 60 seconds later')
911 time.sleep(60)
912
913 try:
914 cros_flash(
915 chromeos_root,
916 host,
917 board,
918 image_path,
919 version=version,
920 clobber_stateful=clobber_stateful,
921 disable_rootfs_verification=disable_rootfs_verification)
922 return True
923 except errors.ExternalError:
924 logger.exception('cros flash failed')
925 if repair_callback and not repair_callback(host):
926 logger.warning('not repaired, assume it is harmless')
927 continue
928 return False
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800929
930
931def version_info(board, version):
932 """Query subcomponents version info of given version of ChromeOS
933
934 Args:
935 board: ChromeOS board name
936 version: ChromeOS version number in short or full format
937
938 Returns:
939 dict of component and version info, including (if available):
940 cros_short_version: ChromeOS version
941 cros_full_version: ChromeOS version
942 milestone: milestone of ChromeOS
943 cr_version: Chrome version
Kuang-che Wu708310b2018-03-28 17:24:34 +0800944 android_build_id: Android build id
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800945 android_branch: Android branch, in format like 'git_nyc-mr1-arc'
946 """
Zheng-Jie Chang127c3302019-09-10 17:17:04 +0800947 if is_cros_snapshot_version(version):
Zheng-Jie Chang2b6d1472019-11-13 12:40:17 +0800948 api = buildbucket_util.BuildbucketApi()
Zheng-Jie Changb8697042019-10-29 16:03:26 +0800949 milestone, short_version, _ = snapshot_version_split(version)
950 buildbucket_id = query_snapshot_buildbucket_id(board, version)
Zheng-Jie Chang2b6d1472019-11-13 12:40:17 +0800951 data = api.get(int(buildbucket_id)).output.properties
952 target_versions = data['target_versions']
Zheng-Jie Chang127c3302019-09-10 17:17:04 +0800953 return {
Zheng-Jie Changb8697042019-10-29 16:03:26 +0800954 VERSION_KEY_MILESTONE: milestone,
955 VERSION_KEY_CROS_FULL_VERSION: version,
956 VERSION_KEY_CROS_SHORT_VERSION: short_version,
957 VERSION_KEY_CR_VERSION: target_versions['chromeVersion'],
958 VERSION_KEY_ANDROID_BUILD_ID: target_versions['androidVersion'],
959 VERSION_KEY_ANDROID_BRANCH: target_versions['androidBranchVersion'],
Zheng-Jie Chang127c3302019-09-10 17:17:04 +0800960 }
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800961 info = {}
962 full_version = version_to_full(board, version)
963
964 # Some boards may have only partial-metadata.json but no metadata.json.
965 # e.g. caroline R60-9462.0.0
966 # Let's try both.
967 metadata = None
968 for metadata_filename in ['metadata.json', 'partial-metadata.json']:
Kuang-che Wu0768b972019-10-05 15:18:59 +0800969 path = gs_archive_path.format(
970 board=board) + '/%s/%s' % (full_version, metadata_filename)
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800971 metadata = gsutil('cat', path, ignore_errors=True)
972 if metadata:
973 o = json.loads(metadata)
974 v = o['version']
975 board_metadata = o['board-metadata'][board]
976 info.update({
977 VERSION_KEY_CROS_SHORT_VERSION: v['platform'],
978 VERSION_KEY_CROS_FULL_VERSION: v['full'],
979 VERSION_KEY_MILESTONE: v['milestone'],
980 VERSION_KEY_CR_VERSION: v['chrome'],
981 })
982
983 if 'android' in v:
Kuang-che Wu708310b2018-03-28 17:24:34 +0800984 info[VERSION_KEY_ANDROID_BUILD_ID] = v['android']
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800985 if 'android-branch' in v: # this appears since R58-9317.0.0
986 info[VERSION_KEY_ANDROID_BRANCH] = v['android-branch']
987 elif 'android-container-branch' in board_metadata:
988 info[VERSION_KEY_ANDROID_BRANCH] = v['android-container-branch']
989 break
990 else:
991 logger.error('Failed to read metadata from gs://chromeos-image-archive')
992 logger.error(
993 'Note, so far no quick way to look up version info for too old builds')
994
995 return info
Kuang-che Wu848b1af2018-02-01 20:59:36 +0800996
997
998def query_chrome_version(board, version):
Kuang-che Wubfc4a642018-04-19 11:54:08 +0800999 """Queries chrome version of chromeos build.
Kuang-che Wu848b1af2018-02-01 20:59:36 +08001000
1001 Args:
1002 board: ChromeOS board name
1003 version: ChromeOS version number in short or full format
1004
1005 Returns:
1006 Chrome version number
1007 """
1008 info = version_info(board, version)
1009 return info['cr_version']
Kuang-che Wu708310b2018-03-28 17:24:34 +08001010
1011
1012def query_android_build_id(board, rev):
1013 info = version_info(board, rev)
1014 rev = info['android_build_id']
1015 return rev
1016
1017
1018def query_android_branch(board, rev):
1019 info = version_info(board, rev)
1020 rev = info['android_branch']
1021 return rev
Kuang-che Wubfc4a642018-04-19 11:54:08 +08001022
1023
Kuang-che Wu3eb6b502018-06-06 16:15:18 +08001024def guess_chrome_version(board, rev):
1025 """Guess chrome version number.
1026
1027 Args:
1028 board: chromeos board name
1029 rev: chrome or chromeos version
1030
1031 Returns:
1032 chrome version number
1033 """
1034 if is_cros_version(rev):
1035 assert board, 'need to specify BOARD for cros version'
1036 rev = query_chrome_version(board, rev)
1037 assert cr_util.is_chrome_version(rev)
1038
1039 return rev
1040
1041
Kuang-che Wubfc4a642018-04-19 11:54:08 +08001042def is_inside_chroot():
1043 """Returns True if we are inside chroot."""
1044 return os.path.exists('/etc/cros_chroot_version')
1045
1046
1047def cros_sdk(chromeos_root, *args, **kwargs):
1048 """Run commands inside chromeos chroot.
1049
1050 Args:
1051 chromeos_root: chromeos tree root
1052 *args: command to run
1053 **kwargs:
Kuang-che Wud4603d72018-11-29 17:51:21 +08001054 chrome_root: pass to cros_sdk; mount this path into the SDK chroot
Kuang-che Wubfc4a642018-04-19 11:54:08 +08001055 env: (dict) environment variables for the command
Kuang-che Wubcafc552019-08-15 15:27:02 +08001056 log_stdout: Whether write the stdout output of the child process to log.
Kuang-che Wubfc4a642018-04-19 11:54:08 +08001057 stdin: standard input file handle for the command
Kuang-che Wu9890ce82018-07-07 15:14:10 +08001058 stderr_callback: Callback function for stderr. Called once per line.
Kuang-che Wua9a20bb2019-09-05 22:24:04 +08001059 goma_dir: Goma installed directory to mount into the chroot
Kuang-che Wubfc4a642018-04-19 11:54:08 +08001060 """
1061 envs = []
1062 for k, v in kwargs.get('env', {}).items():
1063 assert re.match(r'^[A-Za-z_][A-Za-z0-9_]*$', k)
1064 envs.append('%s=%s' % (k, v))
1065
1066 # Use --no-ns-pid to prevent cros_sdk change our pgid, otherwise subsequent
1067 # commands would be considered as background process.
Kuang-che Wu399d4662019-06-06 15:23:37 +08001068 prefix = ['chromite/bin/cros_sdk', '--no-ns-pid']
Kuang-che Wud4603d72018-11-29 17:51:21 +08001069
1070 if kwargs.get('chrome_root'):
Kuang-che Wu399d4662019-06-06 15:23:37 +08001071 prefix += ['--chrome_root', kwargs['chrome_root']]
Kuang-che Wua9a20bb2019-09-05 22:24:04 +08001072 if kwargs.get('goma_dir'):
1073 prefix += ['--goma_dir', kwargs['goma_dir']]
Kuang-che Wud4603d72018-11-29 17:51:21 +08001074
Kuang-che Wu399d4662019-06-06 15:23:37 +08001075 prefix += envs + ['--']
Kuang-che Wud4603d72018-11-29 17:51:21 +08001076
Kuang-che Wu399d4662019-06-06 15:23:37 +08001077 # In addition to the output of command we are interested, cros_sdk may
1078 # generate its own messages. For example, chroot creation messages if we run
1079 # cros_sdk the first time.
1080 # This is the hack to run dummy command once, so we can get clean output for
1081 # the command we are interested.
1082 cmd = prefix + ['true']
1083 util.check_call(*cmd, cwd=chromeos_root)
1084
1085 cmd = prefix + list(args)
Kuang-che Wu9890ce82018-07-07 15:14:10 +08001086 return util.check_output(
1087 *cmd,
1088 cwd=chromeos_root,
Kuang-che Wubcafc552019-08-15 15:27:02 +08001089 log_stdout=kwargs.get('log_stdout', True),
Kuang-che Wu9890ce82018-07-07 15:14:10 +08001090 stdin=kwargs.get('stdin'),
1091 stderr_callback=kwargs.get('stderr_callback'))
Kuang-che Wubfc4a642018-04-19 11:54:08 +08001092
1093
Kuang-che Wua9a20bb2019-09-05 22:24:04 +08001094def create_chroot(chromeos_root):
1095 """Creates ChromeOS chroot.
1096
1097 Args:
1098 chromeos_root: chromeos tree root
1099 """
1100 if os.path.exists(os.path.join(chromeos_root, 'chroot')):
1101 return
1102 if os.path.exists(os.path.join(chromeos_root, 'chroot.img')):
1103 return
1104
1105 util.check_output('chromite/bin/cros_sdk', '--create', cwd=chromeos_root)
1106
1107
Kuang-che Wubfc4a642018-04-19 11:54:08 +08001108def copy_into_chroot(chromeos_root, src, dst):
1109 """Copies file into chromeos chroot.
1110
1111 Args:
1112 chromeos_root: chromeos tree root
1113 src: path outside chroot
1114 dst: path inside chroot
1115 """
1116 # chroot may be an image, so we cannot copy to corresponding path
1117 # directly.
1118 cros_sdk(chromeos_root, 'sh', '-c', 'cat > %s' % dst, stdin=open(src))
1119
1120
1121def exists_in_chroot(chromeos_root, path):
1122 """Determine whether a path exists in the chroot.
1123
1124 Args:
1125 chromeos_root: chromeos tree root
1126 path: path inside chroot, relative to src/scripts
1127
1128 Returns:
1129 True if a path exists
1130 """
1131 try:
Kuang-che Wuacb6efd2018-04-25 18:52:58 +08001132 cros_sdk(chromeos_root, 'test', '-e', path)
Kuang-che Wubfc4a642018-04-19 11:54:08 +08001133 except subprocess.CalledProcessError:
1134 return False
1135 return True
1136
1137
Kuang-che Wu9890ce82018-07-07 15:14:10 +08001138def check_if_need_recreate_chroot(stdout, stderr):
1139 """Analyze build log and determine if chroot should be recreated.
1140
1141 Args:
1142 stdout: stdout output of build
1143 stderr: stderr output of build
1144
1145 Returns:
1146 the reason if chroot needs recreated; None otherwise
1147 """
Kuang-che Wu74768d32018-09-07 12:03:24 +08001148 if re.search(
1149 r"The current version of portage supports EAPI '\d+'. "
Kuang-che Wuae6824b2019-08-27 22:20:01 +08001150 'You must upgrade', stderr):
Kuang-che Wu9890ce82018-07-07 15:14:10 +08001151 return 'EAPI version mismatch'
1152
Kuang-che Wu5ac81322018-11-26 14:04:06 +08001153 if 'Chroot is too new. Consider running:' in stderr:
1154 return 'chroot version is too new'
1155
1156 # old message before Oct 2018
Kuang-che Wu9890ce82018-07-07 15:14:10 +08001157 if 'Chroot version is too new. Consider running cros_sdk --replace' in stderr:
1158 return 'chroot version is too new'
1159
Kuang-che Wu6fe987f2018-08-28 15:24:20 +08001160 # https://groups.google.com/a/chromium.org/forum/#!msg/chromium-os-dev/uzwT5APspB4/NFakFyCIDwAJ
1161 if "undefined reference to 'std::__1::basic_string" in stdout:
1162 return 'might be due to compiler change'
1163
Kuang-che Wu94e3b452019-11-21 12:49:18 +08001164 # Detect failures due to file collisions.
1165 # For example, kernel uprev from 3.x to 4.x, they are two separate packages
1166 # and conflict with each other. Other possible cases are package renaming or
1167 # refactoring. Let's recreate chroot to work around them.
1168 if 'Detected file collision' in stdout:
1169 # Using wildcard between words because the text wraps to the next line
1170 # depending on length of package name and each line is prefixed with
1171 # package name.
1172 # Using ".{,100}" instead of ".*" to prevent regex matching time explodes
1173 # exponentially. 100 is chosen arbitrarily. It should be longer than any
1174 # package name (65 now).
1175 m = re.search(
1176 r'Package (\S+).{,100}NOT.{,100}merged.{,100}'
1177 r'due.{,100}to.{,100}file.{,100}collisions', stdout, re.S)
1178 if m:
1179 return 'failed to install package due to file collision: ' + m.group(1)
Kuang-che Wu356c3522019-11-19 16:11:05 +08001180
Kuang-che Wu9890ce82018-07-07 15:14:10 +08001181 return None
1182
1183
Kuang-che Wua9a20bb2019-09-05 22:24:04 +08001184def build_packages(chromeos_root,
1185 board,
1186 chrome_root=None,
1187 goma_dir=None,
1188 afdo_use=False):
Kuang-che Wu28980b22019-07-31 19:51:45 +08001189 """Build ChromeOS packages.
Kuang-che Wubfc4a642018-04-19 11:54:08 +08001190
1191 Args:
1192 chromeos_root: chromeos tree root
1193 board: ChromeOS board name
Kuang-che Wua9a20bb2019-09-05 22:24:04 +08001194 chrome_root: Chrome tree root. If specified, build chrome using the
1195 provided tree
1196 goma_dir: Goma installed directory to mount into the chroot. If specified,
1197 build chrome with goma.
1198 afdo_use: build chrome with AFDO optimization
Kuang-che Wubfc4a642018-04-19 11:54:08 +08001199 """
Kuang-che Wua9a20bb2019-09-05 22:24:04 +08001200 common_env = {
1201 'USE': '-cros-debug chrome_internal',
1202 'FEATURES': 'separatedebug',
1203 }
Kuang-che Wu9890ce82018-07-07 15:14:10 +08001204 stderr_lines = []
1205 try:
Kuang-che Wufb553102018-10-02 18:14:29 +08001206 with locking.lock_file(locking.LOCK_FILE_FOR_BUILD):
Kuang-che Wua9a20bb2019-09-05 22:24:04 +08001207 env = common_env.copy()
1208 env['FEATURES'] += ' -separatedebug splitdebug'
Kuang-che Wufb553102018-10-02 18:14:29 +08001209 cros_sdk(
1210 chromeos_root,
Kuang-che Wu28980b22019-07-31 19:51:45 +08001211 './update_chroot',
1212 '--toolchain_boards',
Kuang-che Wufb553102018-10-02 18:14:29 +08001213 board,
Kuang-che Wua9a20bb2019-09-05 22:24:04 +08001214 env=env,
Kuang-che Wu28980b22019-07-31 19:51:45 +08001215 stderr_callback=stderr_lines.append)
Kuang-che Wua9a20bb2019-09-05 22:24:04 +08001216
1217 env = common_env.copy()
1218 cmd = [
Kuang-che Wu28980b22019-07-31 19:51:45 +08001219 './build_packages',
1220 '--board',
1221 board,
1222 '--withdev',
1223 '--noworkon',
1224 '--skip_chroot_upgrade',
1225 '--accept_licenses=@CHROMEOS',
Kuang-che Wua9a20bb2019-09-05 22:24:04 +08001226 ]
1227 if goma_dir:
1228 # Tell build_packages to start and stop goma
1229 cmd.append('--run_goma')
1230 env['USE_GOMA'] = 'true'
1231 if afdo_use:
1232 env['USE'] += ' afdo_use'
1233 cros_sdk(
1234 chromeos_root,
1235 *cmd,
1236 env=env,
1237 chrome_root=chrome_root,
1238 stderr_callback=stderr_lines.append,
1239 goma_dir=goma_dir)
Kuang-che Wu9890ce82018-07-07 15:14:10 +08001240 except subprocess.CalledProcessError as e:
1241 # Detect failures due to incompatibility between chroot and source tree. If
1242 # so, notify the caller to recreate chroot and retry.
1243 reason = check_if_need_recreate_chroot(e.output, ''.join(stderr_lines))
1244 if reason:
1245 raise NeedRecreateChrootException(reason)
1246
1247 # For other failures, don't know how to handle. Just bail out.
1248 raise
1249
Kuang-che Wu28980b22019-07-31 19:51:45 +08001250
1251def build_image(chromeos_root, board):
1252 """Build ChromeOS image.
1253
1254 Args:
1255 chromeos_root: chromeos tree root
1256 board: ChromeOS board name
1257
1258 Returns:
1259 image folder; relative to chromeos_root
1260 """
1261 stderr_lines = []
1262 try:
1263 with locking.lock_file(locking.LOCK_FILE_FOR_BUILD):
1264 cros_sdk(
1265 chromeos_root,
1266 './build_image',
1267 '--board',
1268 board,
1269 '--noenable_rootfs_verification',
1270 'test',
1271 env={
1272 'USE': '-cros-debug chrome_internal',
1273 'FEATURES': 'separatedebug',
1274 },
1275 stderr_callback=stderr_lines.append)
1276 except subprocess.CalledProcessError as e:
1277 # Detect failures due to incompatibility between chroot and source tree. If
1278 # so, notify the caller to recreate chroot and retry.
1279 reason = check_if_need_recreate_chroot(e.output, ''.join(stderr_lines))
1280 if reason:
1281 raise NeedRecreateChrootException(reason)
1282
1283 # For other failures, don't know how to handle. Just bail out.
1284 raise
1285
1286 image_symlink = os.path.join(chromeos_root, cached_images_dir, board,
1287 'latest')
1288 assert os.path.exists(image_symlink)
1289 image_name = os.readlink(image_symlink)
1290 image_folder = os.path.join(cached_images_dir, board, image_name)
1291 assert os.path.exists(
1292 os.path.join(chromeos_root, image_folder, test_image_filename))
1293 return image_folder
Kuang-che Wubfc4a642018-04-19 11:54:08 +08001294
1295
Kuang-che Wub9705bd2018-06-28 17:59:18 +08001296class AutotestControlInfo(object):
1297 """Parsed content of autotest control file.
1298
1299 Attributes:
1300 name: test name
1301 path: control file path
1302 variables: dict of top-level control variables. Sample keys: NAME, AUTHOR,
1303 DOC, ATTRIBUTES, DEPENDENCIES, etc.
1304 """
1305
1306 def __init__(self, path, variables):
1307 self.name = variables['NAME']
1308 self.path = path
1309 self.variables = variables
1310
1311
1312def parse_autotest_control_file(path):
1313 """Parses autotest control file.
1314
1315 This only parses simple top-level string assignments.
1316
1317 Returns:
1318 AutotestControlInfo object
1319 """
1320 variables = {}
Kuang-che Wua5723492019-11-25 20:59:34 +08001321 with open(path) as f:
1322 code = ast.parse(f.read())
Kuang-che Wub9705bd2018-06-28 17:59:18 +08001323 for stmt in code.body:
1324 # Skip if not simple "NAME = *" assignment.
1325 if not (isinstance(stmt, ast.Assign) and len(stmt.targets) == 1 and
1326 isinstance(stmt.targets[0], ast.Name)):
1327 continue
1328
1329 # Only support string value.
1330 if isinstance(stmt.value, ast.Str):
1331 variables[stmt.targets[0].id] = stmt.value.s
1332
1333 return AutotestControlInfo(path, variables)
1334
1335
1336def enumerate_autotest_control_files(autotest_dir):
1337 """Enumerate autotest control files.
1338
1339 Args:
1340 autotest_dir: autotest folder
1341
1342 Returns:
1343 list of paths to control files
1344 """
1345 # Where to find control files. Relative to autotest_dir.
1346 subpaths = [
1347 'server/site_tests',
1348 'client/site_tests',
1349 'server/tests',
1350 'client/tests',
1351 ]
1352
1353 blacklist = ['site-packages', 'venv', 'results', 'logs', 'containers']
1354 result = []
1355 for subpath in subpaths:
1356 path = os.path.join(autotest_dir, subpath)
1357 for root, dirs, files in os.walk(path):
1358
1359 for black in blacklist:
1360 if black in dirs:
1361 dirs.remove(black)
1362
1363 for filename in files:
1364 if filename == 'control' or filename.startswith('control.'):
1365 result.append(os.path.join(root, filename))
1366
1367 return result
1368
1369
1370def get_autotest_test_info(autotest_dir, test_name):
1371 """Get metadata of given test.
1372
1373 Args:
1374 autotest_dir: autotest folder
1375 test_name: test name
1376
1377 Returns:
1378 AutotestControlInfo object. None if test not found.
1379 """
1380 for control_file in enumerate_autotest_control_files(autotest_dir):
1381 info = parse_autotest_control_file(control_file)
1382 if info.name == test_name:
1383 return info
1384 return None
1385
1386
Kuang-che Wue4bae0b2018-07-19 12:10:14 +08001387class ChromeOSSpecManager(codechange.SpecManager):
1388 """Repo manifest related operations.
1389
1390 This class enumerates chromeos manifest files, parses them,
1391 and sync to disk state according to them.
1392 """
Kuang-che Wubfc4a642018-04-19 11:54:08 +08001393
1394 def __init__(self, config):
1395 self.config = config
Kuang-che Wue4bae0b2018-07-19 12:10:14 +08001396 self.manifest_dir = os.path.join(self.config['chromeos_root'], '.repo',
1397 'manifests')
Zheng-Jie Chang127c3302019-09-10 17:17:04 +08001398 self.manifest_internal_dir = os.path.join(self.config['chromeos_mirror'],
1399 'manifest-internal.git')
Kuang-che Wue4bae0b2018-07-19 12:10:14 +08001400 self.historical_manifest_git_dir = os.path.join(
Kuang-che Wud8fc9572018-10-03 21:00:41 +08001401 self.config['chromeos_mirror'], 'chromeos/manifest-versions.git')
Kuang-che Wue4bae0b2018-07-19 12:10:14 +08001402 if not os.path.exists(self.historical_manifest_git_dir):
Kuang-che Wue121fae2018-11-09 16:18:39 +08001403 raise errors.InternalError('Manifest snapshots should be cloned into %s' %
1404 self.historical_manifest_git_dir)
Kuang-che Wubfc4a642018-04-19 11:54:08 +08001405
Zheng-Jie Chang127c3302019-09-10 17:17:04 +08001406 def lookup_snapshot_manifest_revisions(self, old, new):
1407 """Get manifest commits between snapshot versions.
Kuang-che Wue4bae0b2018-07-19 12:10:14 +08001408
Zheng-Jie Chang127c3302019-09-10 17:17:04 +08001409 Returns:
1410 list of (timestamp, commit_id, snapshot_id):
1411 timestamp: integer unix timestamp
1412 commit_id: a string indicates commit hash
1413 snapshot_id: a string indicates snapshot id
1414 """
1415 assert is_cros_snapshot_version(old)
1416 assert is_cros_snapshot_version(new)
1417
1418 gs_path = (
1419 'gs://chromeos-image-archive/{board}-postsubmit/{version}-*/image.zip')
1420 # Try to guess the commit time of a snapshot manifest, it is usually a few
1421 # minutes different between snapshot manifest commit and image.zip
1422 # generate.
1423 try:
1424 old_timestamp = gsutil_stat_update_time(
1425 gs_path.format(board=self.config['board'], version=old)) - 86400
1426 except subprocess.CalledProcessError:
1427 old_timestamp = None
1428 try:
1429 new_timestamp = gsutil_stat_update_time(
1430 gs_path.format(board=self.config['board'], version=new)) + 86400
1431 # 1558657989 is snapshot_id 5982's commit time, this ensures every time
1432 # we can find snapshot 5982
1433 # snapshot_id <= 5982 has different commit message format, so we need
1434 # to identify its id in different ways, see below comment for more info.
1435 new_timestamp = max(new_timestamp, 1558657989 + 1)
1436 except subprocess.CalledProcessError:
1437 new_timestamp = None
1438 result = []
1439 _, _, old_snapshot_id = snapshot_version_split(old)
1440 _, _, new_snapshot_id = snapshot_version_split(new)
1441 repo = self.manifest_internal_dir
1442 path = 'snapshot.xml'
1443 branch = 'snapshot'
1444 commits = git_util.get_history(
1445 repo,
1446 path,
1447 branch,
1448 after=old_timestamp,
1449 before=new_timestamp,
1450 with_subject=True)
1451
1452 # Unfortunately, we can not identify snapshot_id <= 5982 from its commit
1453 # subject, as their subjects are all `Annealing manifest snapshot.`.
1454 # So instead we count the snapshot_id manually.
1455 count = 5982
1456 # There are two snapshot_id = 2633 in commit history, ignore the former
1457 # one.
1458 ignore_list = ['95c8526a7f0798d02f692010669dcbd5a152439a']
1459 # We examine the commits in reverse order as there are some testing
1460 # commits before snapshot_id=2, this method works fine after
1461 # snapshot 2, except snapshot 2633
1462 for commit in reversed(commits):
1463 msg = commit[2]
1464 if commit[1] in ignore_list:
1465 continue
1466
1467 match = re.match(r'^annealing manifest snapshot (\d+)', msg)
1468 if match:
1469 snapshot_id = match.group(1)
1470 elif 'Annealing manifest snapshot' in msg:
1471 snapshot_id = str(count)
1472 count -= 1
1473 else:
1474 continue
1475 if int(old_snapshot_id) <= int(snapshot_id) <= int(new_snapshot_id):
1476 result.append((commit[0], commit[1], snapshot_id))
1477 # We find commits in reversed order, now reverse it again to chronological
1478 # order.
1479 return list(reversed(result))
1480
1481 def lookup_build_timestamp(self, rev):
1482 assert is_cros_full_version(rev) or is_cros_snapshot_version(rev)
1483 if is_cros_full_version(rev):
1484 return self.lookup_release_build_timestamp(rev)
Kuang-che Wua7ddf9b2019-11-25 18:59:57 +08001485 return self.lookup_snapshot_build_timestamp(rev)
Zheng-Jie Chang127c3302019-09-10 17:17:04 +08001486
1487 def lookup_snapshot_build_timestamp(self, rev):
1488 assert is_cros_snapshot_version(rev)
1489 return int(self.lookup_snapshot_manifest_revisions(rev, rev)[0][0])
1490
1491 def lookup_release_build_timestamp(self, rev):
1492 assert is_cros_full_version(rev)
Kuang-che Wubfc4a642018-04-19 11:54:08 +08001493 milestone, short_version = version_split(rev)
Kuang-che Wue4bae0b2018-07-19 12:10:14 +08001494 path = os.path.join('buildspecs', milestone, short_version + '.xml')
1495 try:
1496 timestamp = git_util.get_commit_time(self.historical_manifest_git_dir,
1497 'refs/heads/master', path)
1498 except ValueError:
Kuang-che Wuce2f3be2019-10-28 19:44:54 +08001499 raise errors.InternalError(
1500 '%s does not have %s' % (self.historical_manifest_git_dir, path))
Kuang-che Wue4bae0b2018-07-19 12:10:14 +08001501 return timestamp
Kuang-che Wubfc4a642018-04-19 11:54:08 +08001502
Kuang-che Wue4bae0b2018-07-19 12:10:14 +08001503 def collect_float_spec(self, old, new):
1504 old_timestamp = self.lookup_build_timestamp(old)
1505 new_timestamp = self.lookup_build_timestamp(new)
Zheng-Jie Chang127c3302019-09-10 17:17:04 +08001506 # snapshot time is different from commit time
1507 # usually it's a few minutes different
1508 # 30 minutes should be safe in most cases
1509 if is_cros_snapshot_version(old):
1510 old_timestamp = old_timestamp - 1800
1511 if is_cros_snapshot_version(new):
1512 new_timestamp = new_timestamp + 1800
Kuang-che Wubfc4a642018-04-19 11:54:08 +08001513
Kuang-che Wue4bae0b2018-07-19 12:10:14 +08001514 path = os.path.join(self.manifest_dir, 'default.xml')
1515 if not os.path.islink(path) or os.readlink(path) != 'full.xml':
Kuang-che Wue121fae2018-11-09 16:18:39 +08001516 raise errors.InternalError(
1517 'default.xml not symlink to full.xml is not supported')
Kuang-che Wubfc4a642018-04-19 11:54:08 +08001518
Kuang-che Wue4bae0b2018-07-19 12:10:14 +08001519 result = []
1520 path = 'full.xml'
1521 parser = repo_util.ManifestParser(self.manifest_dir)
1522 for timestamp, git_rev in parser.enumerate_manifest_commits(
1523 old_timestamp, new_timestamp, path):
1524 result.append(
1525 codechange.Spec(codechange.SPEC_FLOAT, git_rev, timestamp, path))
1526 return result
Kuang-che Wubfc4a642018-04-19 11:54:08 +08001527
Kuang-che Wue4bae0b2018-07-19 12:10:14 +08001528 def collect_fixed_spec(self, old, new):
Zheng-Jie Chang127c3302019-09-10 17:17:04 +08001529 assert is_cros_full_version(old) or is_cros_snapshot_version(old)
1530 assert is_cros_full_version(new) or is_cros_snapshot_version(new)
1531
1532 # case 1: if both are snapshot, return a list of snapshot
1533 if is_cros_snapshot_version(old) and is_cros_snapshot_version(new):
1534 return self.collect_snapshot_specs(old, new)
1535
1536 # case 2: if both are release version
1537 # return a list of release version
1538 if is_cros_full_version(old) and is_cros_full_version(new):
1539 return self.collect_release_specs(old, new)
1540
1541 # case 3: return a list of release version and append a snapshot
1542 # before or at the end
Zheng-Jie Changc47af3a2019-11-11 17:28:58 +08001543 result = self.collect_release_specs(
1544 version_to_full(self.config['board'], old),
1545 version_to_full(self.config['board'], new))
Zheng-Jie Chang127c3302019-09-10 17:17:04 +08001546 if is_cros_snapshot_version(old):
Zheng-Jie Changc47af3a2019-11-11 17:28:58 +08001547 result = self.collect_snapshot_specs(old, old) + result[1:]
Zheng-Jie Chang127c3302019-09-10 17:17:04 +08001548 elif is_cros_snapshot_version(new):
Zheng-Jie Changc47af3a2019-11-11 17:28:58 +08001549 result = result[:-1] + self.collect_snapshot_specs(new, new)
Zheng-Jie Chang127c3302019-09-10 17:17:04 +08001550 return result
1551
1552 def collect_snapshot_specs(self, old, new):
1553 assert is_cros_snapshot_version(old)
1554 assert is_cros_snapshot_version(new)
1555
1556 def guess_snapshot_version(board, snapshot_id, old, new):
1557 if old.endswith('-' + snapshot_id):
1558 return old
1559 if new.endswith('-' + snapshot_id):
1560 return new
Kuang-che Wuf791afa2019-10-28 19:53:26 +08001561 gs_path = ('gs://chromeos-image-archive/{board}-postsubmit/'
1562 'R*-{snapshot_id}-*'.format(
1563 board=board, snapshot_id=snapshot_id))
Zheng-Jie Chang127c3302019-09-10 17:17:04 +08001564 for line in gsutil_ls(gs_path, ignore_errors=True):
1565 m = re.match(r'^gs:\S+(R\d+-\d+\.\d+\.\d+-\d+)\S+', line)
1566 if m:
1567 return m.group(1)
Zheng-Jie Chang026cd5d2019-12-04 12:13:01 +08001568 return None
Zheng-Jie Chang127c3302019-09-10 17:17:04 +08001569
1570 result = []
1571 path = 'snapshot.xml'
1572 revisions = self.lookup_snapshot_manifest_revisions(old, new)
Kuang-che Wuf791afa2019-10-28 19:53:26 +08001573 for timestamp, _git_rev, snapshot_id in revisions:
Zheng-Jie Chang127c3302019-09-10 17:17:04 +08001574 snapshot_version = guess_snapshot_version(self.config['board'],
1575 snapshot_id, old, new)
Zheng-Jie Chang026cd5d2019-12-04 12:13:01 +08001576 if snapshot_version:
1577 result.append(
1578 codechange.Spec(codechange.SPEC_FIXED, snapshot_version, timestamp,
1579 path))
1580 else:
1581 logger.warning('snapshot id %s is not found, ignore', snapshot_id)
Zheng-Jie Chang127c3302019-09-10 17:17:04 +08001582 return result
1583
1584 def collect_release_specs(self, old, new):
Kuang-che Wubfc4a642018-04-19 11:54:08 +08001585 assert is_cros_full_version(old)
1586 assert is_cros_full_version(new)
1587 old_milestone, old_short_version = version_split(old)
1588 new_milestone, new_short_version = version_split(new)
1589
Kuang-che Wubfc4a642018-04-19 11:54:08 +08001590 result = []
Kuang-che Wue4bae0b2018-07-19 12:10:14 +08001591 for milestone in git_util.list_dir_from_revision(
1592 self.historical_manifest_git_dir, 'refs/heads/master', 'buildspecs'):
1593 if not milestone.isdigit():
1594 continue
1595 if not int(old_milestone) <= int(milestone) <= int(new_milestone):
1596 continue
1597
Kuang-che Wu74768d32018-09-07 12:03:24 +08001598 files = git_util.list_dir_from_revision(
1599 self.historical_manifest_git_dir, 'refs/heads/master',
1600 os.path.join('buildspecs', milestone))
Kuang-che Wubfc4a642018-04-19 11:54:08 +08001601
1602 for fn in files:
Kuang-che Wue4bae0b2018-07-19 12:10:14 +08001603 path = os.path.join('buildspecs', milestone, fn)
Kuang-che Wubfc4a642018-04-19 11:54:08 +08001604 short_version, ext = os.path.splitext(fn)
1605 if ext != '.xml':
1606 continue
Kuang-che Wubfc4a642018-04-19 11:54:08 +08001607 if (util.is_version_lesseq(old_short_version, short_version) and
1608 util.is_version_lesseq(short_version, new_short_version) and
1609 util.is_direct_relative_version(short_version, new_short_version)):
Kuang-che Wue4bae0b2018-07-19 12:10:14 +08001610 rev = make_cros_full_version(milestone, short_version)
1611 timestamp = git_util.get_commit_time(self.historical_manifest_git_dir,
1612 'refs/heads/master', path)
1613 result.append(
1614 codechange.Spec(codechange.SPEC_FIXED, rev, timestamp, path))
Kuang-che Wubfc4a642018-04-19 11:54:08 +08001615
Kuang-che Wue4bae0b2018-07-19 12:10:14 +08001616 def version_key_func(spec):
1617 _milestone, short_version = version_split(spec.name)
Kuang-che Wubfc4a642018-04-19 11:54:08 +08001618 return util.version_key_func(short_version)
1619
1620 result.sort(key=version_key_func)
Kuang-che Wue4bae0b2018-07-19 12:10:14 +08001621 assert result[0].name == old
1622 assert result[-1].name == new
Kuang-che Wubfc4a642018-04-19 11:54:08 +08001623 return result
1624
Kuang-che Wue4bae0b2018-07-19 12:10:14 +08001625 def get_manifest(self, rev):
Zheng-Jie Chang127c3302019-09-10 17:17:04 +08001626 assert is_cros_full_version(rev) or is_cros_snapshot_version(rev)
1627 if is_cros_full_version(rev):
1628 milestone, short_version = version_split(rev)
1629 path = os.path.join('buildspecs', milestone, '%s.xml' % short_version)
1630 manifest = git_util.get_file_from_revision(
1631 self.historical_manifest_git_dir, 'refs/heads/master', path)
1632 else:
1633 revisions = self.lookup_snapshot_manifest_revisions(rev, rev)
1634 commit_id = revisions[0][1]
1635 manifest = git_util.get_file_from_revision(self.manifest_internal_dir,
1636 commit_id, 'snapshot.xml')
Kuang-che Wue4bae0b2018-07-19 12:10:14 +08001637 manifest_name = 'manifest_%s.xml' % rev
1638 manifest_path = os.path.join(self.manifest_dir, manifest_name)
1639 with open(manifest_path, 'w') as f:
1640 f.write(manifest)
1641
1642 return manifest_name
1643
1644 def parse_spec(self, spec):
1645 parser = repo_util.ManifestParser(self.manifest_dir)
1646 if spec.spec_type == codechange.SPEC_FIXED:
1647 manifest_name = self.get_manifest(spec.name)
1648 manifest_path = os.path.join(self.manifest_dir, manifest_name)
Kuang-che Wua5723492019-11-25 20:59:34 +08001649 with open(manifest_path) as f:
1650 content = f.read()
Kuang-che Wue4bae0b2018-07-19 12:10:14 +08001651 root = parser.parse_single_xml(content, allow_include=False)
1652 else:
1653 root = parser.parse_xml_recursive(spec.name, spec.path)
1654
1655 spec.entries = parser.process_parsed_result(root)
1656 if spec.spec_type == codechange.SPEC_FIXED:
Kuang-che Wufe1e88a2019-09-10 21:52:25 +08001657 if not spec.is_static():
1658 raise ValueError(
1659 'fixed spec %r has unexpected floating entries' % spec.name)
Kuang-che Wue4bae0b2018-07-19 12:10:14 +08001660
1661 def sync_disk_state(self, rev):
1662 manifest_name = self.get_manifest(rev)
1663
1664 # For ChromeOS, mark_as_stable step requires 'repo init -m', which sticks
1665 # manifest. 'repo sync -m' is not enough
1666 repo_util.init(
1667 self.config['chromeos_root'],
1668 'https://chrome-internal.googlesource.com/chromeos/manifest-internal',
1669 manifest_name=manifest_name,
1670 repo_url='https://chromium.googlesource.com/external/repo.git',
Kuang-che Wud8fc9572018-10-03 21:00:41 +08001671 reference=self.config['chromeos_mirror'],
Kuang-che Wue4bae0b2018-07-19 12:10:14 +08001672 )
1673
1674 # Note, don't sync with current_branch=True for chromeos. One of its
1675 # build steps (inside mark_as_stable) executes "git describe" which
1676 # needs git tag information.
1677 repo_util.sync(self.config['chromeos_root'])