Movatterモバイル変換


[0]ホーム

URL:


Google Git
Sign in
chromium /chromium /src /refs/heads/main /. /tools /bisect-builds.py
blob: 529aa3b8316f3b459dd118f3b769dc8ffb69642a [file] [log] [blame]
Lei Zhang5471122f2022-06-28 19:40:32[diff] [blame]1#!/usr/bin/env python3
Avi Drissmandfd880852022-09-15 20:11:09[diff] [blame]2# Copyright 2012 The Chromium Authors
rsesek@chromium.org67e0bc62009-09-03 22:06:09[diff] [blame]3# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
5
6"""Snapshot Build Bisect Tool
7
mmoss@chromium.org7ad66a72009-09-04 17:52:33[diff] [blame]8This script bisects a snapshot archive using binary search. It starts at
rsesek@chromium.org67e0bc62009-09-03 22:06:09[diff] [blame]9a bad revision (it will try to guess HEAD) and asks for a last known-good
10revision. It will then binary search across this revision range by downloading,
11unzipping, and opening Chromium for you. After testing the specific revision,
12it will ask you whether it is good or bad before continuing the search.
Paul Irish4f6e48c2025-03-27 17:40:08[diff] [blame]13
14Docs: https://www.chromium.org/developers/bisect-builds-py/
15Googlers: go/chrome-bisect
rsesek@chromium.org67e0bc62009-09-03 22:06:09[diff] [blame]16"""
17
Kuan Huang49a2c6412024-08-12 22:12:33[diff] [blame]18import abc
Kuan Huang95f6df072024-09-12 20:56:42[diff] [blame]19import argparse
Sven Zhenge3441db2024-05-13 19:59:32[diff] [blame]20import base64
Kuan Huang95f6df072024-09-12 20:56:42[diff] [blame]21import copy
Kuan Huang6f7f57942024-12-05 19:26:30[diff] [blame]22import functools
Kuan Huangd608f1592024-08-15 23:31:35[diff] [blame]23import glob
Sven Zheng8e079562024-05-10 20:11:06[diff] [blame]24import importlib
25import json
Sven Zheng8e079562024-05-10 20:11:06[diff] [blame]26import os
27import platform
28import re
29import shlex
Sven Zheng8e079562024-05-10 20:11:06[diff] [blame]30import subprocess
31import sys
Kuan Huangb4c8df52024-09-05 17:59:18[diff] [blame]32import tarfile
Sven Zheng8e079562024-05-10 20:11:06[diff] [blame]33import tempfile
Sven Zheng8e079562024-05-10 20:11:06[diff] [blame]34import threading
35import traceback
Kuan Huang6f7f57942024-12-05 19:26:30[diff] [blame]36import urllib.parse
37import urllib.request
Sven Zheng8e079562024-05-10 20:11:06[diff] [blame]38from xml.etreeimportElementTree
39import zipfile
40
Kuan Huang6f7f57942024-12-05 19:26:30[diff] [blame]41
Sven Zheng8e079562024-05-10 20:11:06[diff] [blame]42# These constants are used for android bisect which depends on
43# Catapult repo.
44DEFAULT_CATAPULT_DIR= os.path.abspath(
45 os.path.join(os.path.dirname(__file__),'catapult_bisect_dep'))
46CATAPULT_DIR= os.environ.get('CATAPULT_DIR', DEFAULT_CATAPULT_DIR)
47CATAPULT_REPO='https://github.com/catapult-project/catapult.git'
48DEVIL_PATH= os.path.abspath(os.path.join(CATAPULT_DIR,'devil'))
Raul Tambre57e09d62019-09-22 17:18:52[diff] [blame]49
qyearsley@chromium.org4df583c2014-07-31 17:11:55[diff] [blame]50# The base URL for stored build archives.
51CHROMIUM_BASE_URL=('http://commondatastorage.googleapis.com'
52'/chromium-browser-snapshots')
cmumford@chromium.org011886692014-08-01 21:00:21[diff] [blame]53ASAN_BASE_URL=('http://commondatastorage.googleapis.com'
54'/chromium-browser-asan')
Kuan Huang2dd3a33f2024-12-13 17:52:52[diff] [blame]55CHROME_FOR_TESTING_BASE_URL=('https://storage.googleapis.com/'
56'chrome-for-testing-per-commit-public')
rsesek@chromium.org67e0bc62009-09-03 22:06:09[diff] [blame]57
Sven Zheng8e079562024-05-10 20:11:06[diff] [blame]58GSUTILS_PATH=None
59
60# GS bucket name for perf builds
61PERF_BASE_URL='gs://chrome-test-builds/official-by-commit'
62# GS bucket name.
63RELEASE_BASE_URL='gs://chrome-unsigned/desktop-5c0tCh'
64
65# Android bucket starting at M45.
66ANDROID_RELEASE_BASE_URL='gs://chrome-unsigned/android-B0urB0N'
67ANDROID_RELEASE_BASE_URL_SIGNED='gs://chrome-signed/android-B0urB0N'
68
69# A special bucket that need to be skipped.
70ANDROID_INVALID_BUCKET='gs://chrome-signed/android-B0urB0N/Test'
71
Kuan Huang22770072024-08-27 20:06:53[diff] [blame]72# iOS bucket
73IOS_RELEASE_BASE_URL='gs://chrome-unsigned/ios-G1N'
74IOS_RELEASE_BASE_URL_SIGNED='gs://chrome-signed/ios-G1N'
Kuan Huangb4c8df52024-09-05 17:59:18[diff] [blame]75IOS_ARCHIVE_BASE_URL='gs://bling-archive'
Kuan Huang22770072024-08-27 20:06:53[diff] [blame]76
Sven Zheng8e079562024-05-10 20:11:06[diff] [blame]77# Base URL for downloading release builds.
78GOOGLE_APIS_URL='commondatastorage.googleapis.com'
79
qyearsley@chromium.org4df583c2014-07-31 17:11:55[diff] [blame]80# URL template for viewing changelogs between revisions.
Kuan Huangb790d5d2024-11-07 21:53:00[diff] [blame]81SHORT_CHANGELOG_URL='https://crrev.com/%s..%s'
pshenoy9ce271f2014-09-02 22:14:05[diff] [blame]82CHANGELOG_URL=('https://chromium.googlesource.com/chromium/src/+log/%s..%s')
83
84# URL to convert SVN revision to git hash.
pshenoy13cb79e02014-09-05 01:42:53[diff] [blame]85CRREV_URL=('https://cr-rev.appspot.com/_ah/api/crrev/v1/redirect/')
nirnimesh@chromium.orgf6a71a72009-10-08 19:55:38[diff] [blame]86
Sven Zheng8e079562024-05-10 20:11:06[diff] [blame]87# URL template for viewing changelogs between release versions.
88RELEASE_CHANGELOG_URL=('https://chromium.googlesource.com/chromium/'
Kuan Huangb790d5d2024-11-07 21:53:00[diff] [blame]89'src/+log/%s..%s?n=10000')
90
91# show change logs during bisecting for last 5 steps
92STEPS_TO_SHOW_CHANGELOG_URL=5
Sven Zheng8e079562024-05-10 20:11:06[diff] [blame]93
thakis@chromium.orgb2fe7f22011-10-25 22:58:31[diff] [blame]94# DEPS file URL.
Sven Zheng8e079562024-05-10 20:11:06[diff] [blame]95DEPS_FILE_OLD=('http://src.chromium.org/viewvc/chrome/trunk/src/'
96'DEPS?revision=%d')
97DEPS_FILE_NEW=('https://chromium.googlesource.com/chromium/src/+/%s/DEPS')
thakis@chromium.orgb2fe7f22011-10-25 22:58:31[diff] [blame]98
Kuan Huang008965f92024-10-08 21:53:15[diff] [blame]99# Source Tag
100SOURCE_TAG_URL=('https://chromium.googlesource.com/chromium/src/'
101'+/refs/tags/%s?format=JSON')
102
qyearsley@chromium.org4df583c2014-07-31 17:11:55[diff] [blame]103
104DONE_MESSAGE_GOOD_MIN=('You are probably looking for a change made after %s ('
105'known good), but no later than %s (first known bad).')
106DONE_MESSAGE_GOOD_MAX=('You are probably looking for a change made after %s ('
107'known bad), but no later than %s (first known good).')
dmazzoni@chromium.org05ff3fd2012-04-17 23:24:06[diff] [blame]108
Sven Zheng8e079562024-05-10 20:11:06[diff] [blame]109VERSION_INFO_URL=('https://chromiumdash.appspot.com/fetch_version?version=%s')
Alex Mineere309d002022-04-21 22:42:50[diff] [blame]110
Kuan Huangc93ac262024-09-19 18:53:17[diff] [blame]111MILESTONES_URL=('https://chromiumdash.appspot.com/fetch_milestones?mstone=%s')
Joel Hockey60c4f5b2024-07-15 22:03:08[diff] [blame]112
pshenoy@chromium.org480369782014-08-22 20:15:58[diff] [blame]113CREDENTIAL_ERROR_MESSAGE=('You are attempting to access protected data with '
114'no configured credentials')
Sven Zheng8e079562024-05-10 20:11:06[diff] [blame]115PATH_CONTEXT={
116'release':{
117'android-arm':{
118# Binary name is the Chrome binary filename. On Android, we don't
119# use it to launch Chrome.
120'binary_name':None,
121'listing_platform_dir':'arm/',
122# Archive name is the zip file on gcs. For Android, we don't have
123# such zip file. Instead we have a lot of apk files directly stored
124# on gcs. The archive_name is used to find zip file for other
125# platforms, but it will be apk filename defined by --apk for
126# Android platform.
127'archive_name':None,
128'archive_extract_dir':'android-arm'
129},
130'android-arm64':{
131'binary_name':None,
132'listing_platform_dir':'arm_64/',
133'archive_name':None,
134'archive_extract_dir':'android-arm64'
135},
Kuan Huangfeb4fa552024-08-21 20:40:28[diff] [blame]136'android-arm64-high':{
137'binary_name':None,
138'listing_platform_dir':'high-arm_64/',
139'archive_name':None,
140'archive_extract_dir':'android-arm64'
141},
Peter Wen9156ea402024-07-04 15:44:54[diff] [blame]142'android-x86':{
143'binary_name':None,
144'listing_platform_dir':'x86/',
145'archive_name':None,
146'archive_extract_dir':'android-x86'
147},
148'android-x64':{
149'binary_name':None,
150'listing_platform_dir':'x86_64/',
151'archive_name':None,
152'archive_extract_dir':'android-x64'
153},
Kuan Huang22770072024-08-27 20:06:53[diff] [blame]154'ios':{
155'binary_name':None,
156'listing_platform_dir':'ios/',
157'archive_name':None,
158'archive_extract_dir':None,
159},
Kuan Huangb4c8df52024-09-05 17:59:18[diff] [blame]160'ios-simulator':{
161'binary_name':'Chromium.app',
162'listing_platform_dir':'',
163'archive_name':'Chromium.tar.gz',
164'archive_extract_dir':None,
165},
Sven Zheng8e079562024-05-10 20:11:06[diff] [blame]166'linux64':{
167'binary_name':'chrome',
168'listing_platform_dir':'linux64/',
169'archive_name':'chrome-linux64.zip',
Kuan Huang89fe2eef2024-09-13 23:04:02[diff] [blame]170'archive_extract_dir':'chrome-linux64',
171'chromedriver_binary_name':'chromedriver',
172'chromedriver_archive_name':'chromedriver_linux64.zip',
Sven Zheng8e079562024-05-10 20:11:06[diff] [blame]173},
174'mac':{
175'binary_name':'Google Chrome.app/Contents/MacOS/Google Chrome',
176'listing_platform_dir':'mac/',
177'archive_name':'chrome-mac.zip',
Kuan Huang89fe2eef2024-09-13 23:04:02[diff] [blame]178'archive_extract_dir':'chrome-mac',
Sven Zheng8e079562024-05-10 20:11:06[diff] [blame]179},
180'mac64':{
181'binary_name':'Google Chrome.app/Contents/MacOS/Google Chrome',
182'listing_platform_dir':'mac64/',
183'archive_name':'chrome-mac.zip',
Kuan Huang89fe2eef2024-09-13 23:04:02[diff] [blame]184'archive_extract_dir':'chrome-mac',
185'chromedriver_binary_name':'chromedriver',
186'chromedriver_archive_name':'chromedriver_mac64.zip',
Sven Zheng8e079562024-05-10 20:11:06[diff] [blame]187},
188'mac-arm':{
189'binary_name':'Google Chrome.app/Contents/MacOS/Google Chrome',
190'listing_platform_dir':'mac-arm64/',
191'archive_name':'chrome-mac.zip',
Kuan Huang89fe2eef2024-09-13 23:04:02[diff] [blame]192'archive_extract_dir':'chrome-mac',
193'chromedriver_binary_name':'chromedriver',
194'chromedriver_archive_name':'chromedriver_mac64.zip',
Sven Zheng8e079562024-05-10 20:11:06[diff] [blame]195},
Kuan Huang7b45b642024-09-10 17:30:29[diff] [blame]196'win':{
Sven Zheng8e079562024-05-10 20:11:06[diff] [blame]197'binary_name':'chrome.exe',
Kuan Huang7b45b642024-09-10 17:30:29[diff] [blame]198# Release builds switched to -clang in M64.
Sven Zheng8e079562024-05-10 20:11:06[diff] [blame]199'listing_platform_dir':'win-clang/',
200'archive_name':'chrome-win-clang.zip',
Kuan Huang89fe2eef2024-09-13 23:04:02[diff] [blame]201'archive_extract_dir':'chrome-win-clang',
202'chromedriver_binary_name':'chromedriver.exe',
203'chromedriver_archive_name':'chromedriver_win32.zip',
Sven Zheng8e079562024-05-10 20:11:06[diff] [blame]204},
Kuan Huang7b45b642024-09-10 17:30:29[diff] [blame]205'win64':{
Sven Zheng8e079562024-05-10 20:11:06[diff] [blame]206'binary_name':'chrome.exe',
Kuan Huang7b45b642024-09-10 17:30:29[diff] [blame]207# Release builds switched to -clang in M64.
Sven Zheng8e079562024-05-10 20:11:06[diff] [blame]208'listing_platform_dir':'win64-clang/',
209'archive_name':'chrome-win64-clang.zip',
Kuan Huang89fe2eef2024-09-13 23:04:02[diff] [blame]210'archive_extract_dir':'chrome-win64-clang',
211'chromedriver_binary_name':'chromedriver.exe',
212'chromedriver_archive_name':'chromedriver_win64.zip',
Sven Zheng8e079562024-05-10 20:11:06[diff] [blame]213},
Kuan Huanga860dcd2024-09-30 18:10:14[diff] [blame]214'win-arm64':{
215'binary_name':'chrome.exe',
216'listing_platform_dir':'win-arm64-clang/',
217'archive_name':'chrome-win-arm64-clang.zip',
218'archive_extract_dir':'chrome-win-arm64-clang',
219'chromedriver_binary_name':'chromedriver.exe',
220'chromedriver_archive_name':'chromedriver_win64.zip',
221},
Sven Zheng8e079562024-05-10 20:11:06[diff] [blame]222},
223'official':{
224'android-arm':{
225'binary_name':None,
226'listing_platform_dir':'android-builder-perf/',
227'archive_name':'full-build-linux.zip',
228'archive_extract_dir':'full-build-linux'
229},
230'android-arm64':{
231'binary_name':None,
232'listing_platform_dir':'android_arm64-builder-perf/',
233'archive_name':'full-build-linux.zip',
234'archive_extract_dir':'full-build-linux'
235},
Kuan Huangfeb4fa552024-08-21 20:40:28[diff] [blame]236'android-arm64-high':{
237'binary_name':None,
238'listing_platform_dir':'android_arm64_high_end-builder-perf/',
239'archive_name':'full-build-linux.zip',
240'archive_extract_dir':'full-build-linux'
241},
Sven Zheng8e079562024-05-10 20:11:06[diff] [blame]242'linux64':{
243'binary_name':'chrome',
244'listing_platform_dir':'linux-builder-perf/',
245'archive_name':'chrome-perf-linux.zip',
Kuan Huang89fe2eef2024-09-13 23:04:02[diff] [blame]246'archive_extract_dir':'full-build-linux',
247'chromedriver_binary_name':'chromedriver',
Sven Zheng8e079562024-05-10 20:11:06[diff] [blame]248},
249'mac':{
250'binary_name':'Google Chrome.app/Contents/MacOS/Google Chrome',
251'listing_platform_dir':'mac-builder-perf/',
252'archive_name':'chrome-perf-mac.zip',
Kuan Huang89fe2eef2024-09-13 23:04:02[diff] [blame]253'archive_extract_dir':'full-build-mac',
254'chromedriver_binary_name':'chromedriver',
Sven Zheng8e079562024-05-10 20:11:06[diff] [blame]255},
256'mac-arm':{
257'binary_name':'Google Chrome.app/Contents/MacOS/Google Chrome',
258'listing_platform_dir':'mac-arm-builder-perf/',
259'archive_name':'chrome-perf-mac.zip',
Kuan Huang89fe2eef2024-09-13 23:04:02[diff] [blame]260'archive_extract_dir':'full-build-mac',
261'chromedriver_binary_name':'chromedriver',
Sven Zheng8e079562024-05-10 20:11:06[diff] [blame]262},
263'win64':{
264'binary_name':'chrome.exe',
265'listing_platform_dir':'win64-builder-perf/',
266'archive_name':'chrome-perf-win.zip',
Kuan Huang89fe2eef2024-09-13 23:04:02[diff] [blame]267'archive_extract_dir':'full-build-win32',
268'chromedriver_binary_name':'chromedriver.exe',
Sven Zheng8e079562024-05-10 20:11:06[diff] [blame]269},
Sven Zheng8e079562024-05-10 20:11:06[diff] [blame]270},
271'snapshot':{
Peter Wenb4ba30482024-07-08 19:10:40[diff] [blame]272'android-arm':{
273'binary_name':None,
274'listing_platform_dir':'Android/',
275'archive_name':'chrome-android.zip',
276'archive_extract_dir':'chrome-android'
277},
278'android-arm64':{
279'binary_name':None,
280'listing_platform_dir':'Android_Arm64/',
281'archive_name':'chrome-android.zip',
282'archive_extract_dir':'chrome-android'
283},
Sven Zheng8e079562024-05-10 20:11:06[diff] [blame]284'linux64':{
285'binary_name':'chrome',
286'listing_platform_dir':'Linux_x64/',
287'archive_name':'chrome-linux.zip',
Kuan Huang89fe2eef2024-09-13 23:04:02[diff] [blame]288'archive_extract_dir':'chrome-linux',
289'chromedriver_binary_name':'chromedriver',
290'chromedriver_archive_name':'chromedriver_linux64.zip',
Sven Zheng8e079562024-05-10 20:11:06[diff] [blame]291},
292'linux-arm':{
293'binary_name':'chrome',
294'listing_platform_dir':'Linux_ARM_Cross-Compile/',
295'archive_name':'chrome-linux.zip',
296'archive_extract_dir':'chrome-linux'
297},
Sven Zheng8e079562024-05-10 20:11:06[diff] [blame]298'chromeos':{
299'binary_name':'chrome',
300'listing_platform_dir':'Linux_ChromiumOS_Full/',
Kuan Huang8b5e33dc2024-08-15 17:51:24[diff] [blame]301'archive_name':'chrome-chromeos.zip',
302'archive_extract_dir':'chrome-chromeos'
Sven Zheng8e079562024-05-10 20:11:06[diff] [blame]303},
304'mac':{
305'binary_name':'Chromium.app/Contents/MacOS/Chromium',
306'listing_platform_dir':'Mac/',
307'archive_name':'chrome-mac.zip',
Kuan Huang89fe2eef2024-09-13 23:04:02[diff] [blame]308'archive_extract_dir':'chrome-mac',
309'chromedriver_binary_name':'chromedriver',
310'chromedriver_archive_name':'chromedriver_mac64.zip',
Sven Zheng8e079562024-05-10 20:11:06[diff] [blame]311},
312'mac64':{
313'binary_name':'Chromium.app/Contents/MacOS/Chromium',
314'listing_platform_dir':'Mac/',
315'archive_name':'chrome-mac.zip',
Kuan Huang89fe2eef2024-09-13 23:04:02[diff] [blame]316'archive_extract_dir':'chrome-mac',
317'chromedriver_binary_name':'chromedriver',
318'chromedriver_archive_name':'chromedriver_mac64.zip',
Sven Zheng8e079562024-05-10 20:11:06[diff] [blame]319},
320'mac-arm':{
321'binary_name':'Chromium.app/Contents/MacOS/Chromium',
322'listing_platform_dir':'Mac_Arm/',
323'archive_name':'chrome-mac.zip',
Kuan Huang89fe2eef2024-09-13 23:04:02[diff] [blame]324'archive_extract_dir':'chrome-mac',
325'chromedriver_binary_name':'chromedriver',
326'chromedriver_archive_name':'chromedriver_mac64.zip',
Sven Zheng8e079562024-05-10 20:11:06[diff] [blame]327},
Sven Zheng8e079562024-05-10 20:11:06[diff] [blame]328'win':{
329'binary_name':'chrome.exe',
330'listing_platform_dir':'Win/',
Kuan Huang8b5e33dc2024-08-15 17:51:24[diff] [blame]331'archive_name':'chrome-win.zip',
Kuan Huang89fe2eef2024-09-13 23:04:02[diff] [blame]332'archive_extract_dir':'chrome-win',
333'chromedriver_binary_name':'chromedriver.exe',
334'chromedriver_archive_name':'chromedriver_win32.zip',
Sven Zheng8e079562024-05-10 20:11:06[diff] [blame]335},
336'win64':{
337'binary_name':'chrome.exe',
338'listing_platform_dir':'Win_x64/',
Kuan Huang8b5e33dc2024-08-15 17:51:24[diff] [blame]339'archive_name':'chrome-win.zip',
Kuan Huang89fe2eef2024-09-13 23:04:02[diff] [blame]340'archive_extract_dir':'chrome-win',
341'chromedriver_binary_name':'chromedriver.exe',
342'chromedriver_archive_name':'chromedriver_win32.zip',
Sven Zheng8e079562024-05-10 20:11:06[diff] [blame]343},
Kuan Huang147e34e2024-07-10 23:17:18[diff] [blame]344'win-arm64':{
345'binary_name':'chrome.exe',
346'listing_platform_dir':'Win_Arm64/',
347'archive_name':'chrome-win.zip',
Kuan Huang89fe2eef2024-09-13 23:04:02[diff] [blame]348'archive_extract_dir':'chrome-win',
349'chromedriver_binary_name':'chromedriver.exe',
350'chromedriver_archive_name':'chromedriver_win64.zip',
Kuan Huang147e34e2024-07-10 23:17:18[diff] [blame]351},
Kuan Huang49a2c6412024-08-12 22:12:33[diff] [blame]352},
Kuan Huang37d0daa2024-09-19 18:03:08[diff] [blame]353'asan':{
354'linux':{},
355'mac':{},
356'win':{},
357},
Kuan Huang2dd3a33f2024-12-13 17:52:52[diff] [blame]358'cft':{
359'linux64':{
360'listing_platform_dir':'linux64/',
361'binary_name':'chrome',
362'archive_name':'chrome-linux64.zip',
363'chromedriver_binary_name':'chromedriver',
364'chromedriver_archive_name':'chromedriver-linux64.zip',
365},
366'mac-arm':{
367'listing_platform_dir':'mac-arm64/',
368'binary_name':'Google Chrome for Testing.app/Contents/MacOS'
369'/Google Chrome for Testing',
370'archive_name':'chrome-mac-arm64.zip',
371'chromedriver_binary_name':'chromedriver',
372'chromedriver_archive_name':'chromedriver-mac-arm64.zip',
373},
374'win64':{
375'listing_platform_dir':'win64/',
376'binary_name':'chrome.exe',
377'archive_name':'chrome-win64.zip',
378'chromedriver_binary_name':'chromedriver.exe',
379'chromedriver_archive_name':'chromedriver-win64.zip',
380},
381},
Sven Zheng8e079562024-05-10 20:11:06[diff] [blame]382}
pshenoy@chromium.org480369782014-08-22 20:15:58[diff] [blame]383
Sven Zheng8e079562024-05-10 20:11:06[diff] [blame]384CHROME_APK_FILENAMES={
385'chrome':'Chrome.apk',
386'chrome_beta':'ChromeBeta.apk',
387'chrome_canary':'ChromeCanary.apk',
388'chrome_dev':'ChromeDev.apk',
389'chrome_stable':'ChromeStable.apk',
390'chromium':'ChromePublic.apk',
391}
Ben Joyce34b4c732023-04-26 21:29:23[diff] [blame]392
Sven Zheng8e079562024-05-10 20:11:06[diff] [blame]393CHROME_MODERN_APK_FILENAMES={
394'chrome':'ChromeModern.apk',
395'chrome_beta':'ChromeModernBeta.apk',
396'chrome_canary':'ChromeModernCanary.apk',
397'chrome_dev':'ChromeModernDev.apk',
398'chrome_stable':'ChromeModernStable.apk',
399'chromium':'ChromePublic.apk',
400}
rsesek@chromium.org67e0bc62009-09-03 22:06:09[diff] [blame]401
Sven Zheng8e079562024-05-10 20:11:06[diff] [blame]402MONOCHROME_APK_FILENAMES={
403'chrome':'Monochrome.apk',
404'chrome_beta':'MonochromeBeta.apk',
405'chrome_canary':'MonochromeCanary.apk',
406'chrome_dev':'MonochromeDev.apk',
407'chrome_stable':'MonochromeStable.apk',
408'chromium':'ChromePublic.apk',
409}
nirnimesh@chromium.orgbd8dcb92010-03-31 01:05:24[diff] [blame]410
Kuan Huangfeb4fa552024-08-21 20:40:28[diff] [blame]411TRICHROME_APK_FILENAMES={
412'chrome':'TrichromeChromeGoogle.apks',
413'chrome_beta':'TrichromeChromeGoogleBeta.apks',
414'chrome_canary':'TrichromeChromeGoogleCanary.apks',
415'chrome_dev':'TrichromeChromeGoogleDev.apks',
416'chrome_stable':'TrichromeChromeGoogleStable.apks',
417}
418
419TRICHROME64_APK_FILENAMES={
420'chrome':'TrichromeChromeGoogle6432.apks',
421'chrome_beta':'TrichromeChromeGoogle6432Beta.apks',
422'chrome_canary':'TrichromeChromeGoogle6432Canary.apks',
423'chrome_dev':'TrichromeChromeGoogle6432Dev.apks',
424'chrome_stable':'TrichromeChromeGoogle6432Stable.apks',
Trung Nguyend391d052025-06-04 09:35:09[diff] [blame]425'system_webview':'TrichromeWebViewGoogle6432.apks',
Kuan Huangfeb4fa552024-08-21 20:40:28[diff] [blame]426}
427
428TRICHROME_LIBRARY_FILENAMES={
429'chrome':'TrichromeLibraryGoogle.apk',
430'chrome_beta':'TrichromeLibraryGoogleBeta.apk',
431'chrome_canary':'TrichromeLibraryGoogleCanary.apk',
432'chrome_dev':'TrichromeLibraryGoogleDev.apk',
433'chrome_stable':'TrichromeLibraryGoogleStable.apk',
434}
435
436TRICHROME64_LIBRARY_FILENAMES={
437'chrome':'TrichromeLibraryGoogle6432.apk',
438'chrome_beta':'TrichromeLibraryGoogle6432Beta.apk',
439'chrome_canary':'TrichromeLibraryGoogle6432Canary.apk',
440'chrome_dev':'TrichromeLibraryGoogle6432Dev.apk',
441'chrome_stable':'TrichromeLibraryGoogle6432Stable.apk',
Trung Nguyend391d052025-06-04 09:35:09[diff] [blame]442'system_webview':'TrichromeLibraryGoogle6432.apk',
Kuan Huangfeb4fa552024-08-21 20:40:28[diff] [blame]443}
444
Sven Zheng8e079562024-05-10 20:11:06[diff] [blame]445WEBVIEW_APK_FILENAMES={
446# clank release
447'android_webview':'AndroidWebview.apk',
448# clank official
449'system_webview_google':'SystemWebViewGoogle.apk',
450# upstream
451'system_webview':'SystemWebView.apk',
452}
453
454# Old storage locations for per CL builds
455OFFICIAL_BACKUP_BUILDS={
456'android-arm':{
457'listing_platform_dir':['Android Builder/'],
458},
459'linux64':{
460'listing_platform_dir':['Linux Builder Perf/'],
461},
462'mac':{
463'listing_platform_dir':['Mac Builder Perf/'],
464},
465'win64':{
466'listing_platform_dir':['Win x64 Builder Perf/'],
467}
468}
469
Kuan Huang0d24cfd92024-07-12 16:50:30[diff] [blame]470PLATFORM_ARCH_TO_ARCHIVE_MAPPING={
471('linux','x64'):'linux64',
472('mac','x64'):'mac64',
473('mac','x86'):'mac',
474('mac','arm'):'mac-arm',
475('win','x64'):'win64',
476('win','x86'):'win',
477('win','arm'):'win-arm64',
478}
479
Sven Zheng8e079562024-05-10 20:11:06[diff] [blame]480# Set only during initialization.
481is_verbose=False
482
483
484classBisectException(Exception):
485
486def __str__(self):
487return'[Bisect Exception]: %s\n'% self.args[0]
488
489
Kuan Huangc63de402024-08-12 22:15:27[diff] [blame]490defRunGsutilCommand(args, can_fail=False, ignore_fail=False):
Kuan Huangd86de1a2024-07-10 18:45:40[diff] [blame]491ifnot GSUTILS_PATH:
492raiseBisectException('gsutils is not found in path.')
Sven Zheng8e079562024-05-10 20:11:06[diff] [blame]493if is_verbose:
494print('Running gsutil command: '+
495 str([sys.executable, GSUTILS_PATH]+ args))
496 gsutil= subprocess.Popen([sys.executable, GSUTILS_PATH]+ args,
497 stdout=subprocess.PIPE,
498 stderr=subprocess.PIPE,
499 env=None)
500 stdout_b, stderr_b= gsutil.communicate()
501 stdout= stdout_b.decode("utf-8")
502 stderr= stderr_b.decode("utf-8")
503if gsutil.returncode:
504if(re.findall(r'(status|ServiceException:)[ |=]40[1|3]', stderr)
505or stderr.startswith(CREDENTIAL_ERROR_MESSAGE)):
506print(('Follow these steps to configure your credentials and try'
507' running the bisect-builds.py again.:\n'
508' 1. Run "python3 %s config" and follow its instructions.\n'
509' 2. If you have a @google.com account, use that account.\n'
510' 3. For the project-id, just enter 0.'% GSUTILS_PATH))
511print('Warning: You might have an outdated .boto file. If this issue '
512'persists after running `gsutil.py config`, try removing your '
513'.boto, usually located in your home directory.')
Kuan Huangc3a4cd1e2024-10-03 21:17:31[diff] [blame]514raiseBisectException('gsutil credential error')
Sven Zheng8e079562024-05-10 20:11:06[diff] [blame]515elif can_fail:
516return stderr
Kuan Huangc63de402024-08-12 22:15:27[diff] [blame]517elif ignore_fail:
518return stdout
Sven Zheng8e079562024-05-10 20:11:06[diff] [blame]519else:
520raiseException('Error running the gsutil command:\n%s\n%s'%
521(args, stderr))
522return stdout
Bruce Dawson039777ef2020-10-26 20:34:47[diff] [blame]523
maruel@chromium.orgcb155a82011-11-29 17:25:34[diff] [blame]524
Kuan Huangc63de402024-08-12 22:15:27[diff] [blame]525defGsutilList(*urls, ignore_fail=False):
526"""List GCloud Storage with URLs and return a list of paths.
527
528 This method lists all archive builds in a GCS bucket; it filters out invalid
529 archive builds or files.
530
531 Arguments:
532 * urls - one or more gs:// URLs
533 * ignore_fail - ignore gsutil command errors, e.g., 'matched no objects'
534
535 Return:
536 * list of paths that match the given URLs
537 """
538# Get a directory listing with file sizes. Typical output looks like:
539# 7 2023-11-27T21:08:36Z gs://.../LAST_CHANGE
540# 144486938 2023-03-07T14:41:25Z gs://.../full-build-win32_1113893.zip
541# TOTAL: 114167 objects, 15913845813421 bytes (14.47 TiB)
542# This lets us ignore empty .zip files that will otherwise cause errors.
543 stdout=RunGsutilCommand(['ls','-l',*urls], ignore_fail=ignore_fail)
544# Trim off the summary line that only happens with -l
545 lines=[]
546for linein stdout.splitlines():
547 parts= line.split(maxsplit=2)
548ifnot parts[-1].startswith('gs://'):
549continue
550# Check whether there is a size field. For release builds the listing
551# will be directories so there will be no size field.
552if len(parts)>1:
553if ANDROID_INVALID_BUCKETin line:
554continue
555 size= int(parts[0])
556# Empty .zip files are 22 bytes. Ignore anything less than 1,000 bytes,
557# but keep the LAST_CHANGE file since the code seems to expect that.
558if parts[-1].endswith('LAST_CHANGE')or size>1000:
559 lines.append(parts[-1])
560else:
561 lines.append(parts[-1])
562return lines
563
564
Kuan Huang94c4a2d12024-11-21 21:46:09[diff] [blame]565def join_args(args: list)-> str:
566"""Join the args into a single command line."""
567if sys.platform.startswith('win'):
568# subprocess.list2cmdline is an API for subprocess internal use. However to
569# reduce the external dependency, we use it for Windows to quote the args.
570return subprocess.list2cmdline(args)
571else:
572return shlex.join(args)
573
574
575def quote_arg(arg: str)-> str:
576"""Quote the arg for the shell."""
577return join_args([arg])
578
579
Kuan Huang49a2c6412024-08-12 22:12:33[diff] [blame]580classArchiveBuild(abc.ABC):
581"""Base class for a archived build."""
582
583def __init__(self, options):
584 self.platform= options.archive
Kuan Huang49a2c6412024-08-12 22:12:33[diff] [blame]585 self.good_revision= options.good
586 self.bad_revision= options.bad
587 self.use_local_cache= options.use_local_cache
Kuan Huang89fe2eef2024-09-13 23:04:02[diff] [blame]588 self.chromedriver= options.chromedriver
Kuan Huangd608f1592024-08-15 23:31:35[diff] [blame]589# PATH_CONTEXT
Kuan Huang49a2c6412024-08-12 22:12:33[diff] [blame]590 path_context= PATH_CONTEXT[self.build_type].get(self.platform,{})
591 self.binary_name= path_context.get('binary_name')
592 self.listing_platform_dir= path_context.get('listing_platform_dir')
593 self.archive_name= path_context.get('archive_name')
594 self.archive_extract_dir= path_context.get('archive_extract_dir')
Kuan Huang89fe2eef2024-09-13 23:04:02[diff] [blame]595 self.chromedriver_binary_name= path_context.get('chromedriver_binary_name')
596 self.chromedriver_archive_name= path_context.get(
597'chromedriver_archive_name')
598if self.chromedriverandnot self.chromedriver_binary_name:
599raiseBisectException(
600'Could not find chromedriver_binary_name, '
601 f'--chromedriver might not supported on {self.platform}.')
Kuan Huangd608f1592024-08-15 23:31:35[diff] [blame]602# run_revision options
603 self.profile= options.profile
604 self.command= options.command
605 self.num_runs= options.times
Kuan Huang49a2c6412024-08-12 22:12:33[diff] [blame]606
Kuan Huang49a2c6412024-08-12 22:12:33[diff] [blame]607@property
608@abc.abstractmethod
609def build_type(self):
610raiseNotImplemented()
611
612@abc.abstractmethod
Kuan Huangc8dd77a2024-08-12 22:13:42[diff] [blame]613def _get_rev_list(self, min_rev=None, max_rev=None):
614"""The actual method to get revision list without cache.
615
616 min_rev and max_rev could be None, indicating that the method should return
617 all revisions.
618
619 The method should return at least the revision list that exists for the
620 given (min_rev, max_rev) range. However, it could return a revision list
621 with revisions beyond min_rev and max_rev based on the implementation for
622 better caching. The rev_list should contain all available revisions between
623 the returned minimum and maximum values.
624
625 The return value of revisions in the list should match the type of
626 good_revision and bad_revision, and should be comparable.
627 """
Kuan Huang49a2c6412024-08-12 22:12:33[diff] [blame]628raiseNotImplemented()
629
Kuan Huangc8dd77a2024-08-12 22:13:42[diff] [blame]630@property
631def _rev_list_cache_filename(self):
632return os.path.join(os.path.abspath(os.path.dirname(__file__)),
633'.bisect-builds-cache.json')
634
635@property
Kuan Huang49a2c6412024-08-12 22:12:33[diff] [blame]636@abc.abstractmethod
Kuan Huangc8dd77a2024-08-12 22:13:42[diff] [blame]637def _rev_list_cache_key(self):
638"""Returns the cache key for archive build. The cache key should be able to
639 distinguish like build_type, platform."""
Kuan Huang49a2c6412024-08-12 22:12:33[diff] [blame]640raiseNotImplemented()
641
Kuan Huangc8dd77a2024-08-12 22:13:42[diff] [blame]642def _load_rev_list_cache(self):
643ifnot self.use_local_cache:
644return[]
645 cache_filename= self._rev_list_cache_filename
646try:
647with open(cache_filename)as cache_file:
648 cache= json.load(cache_file)
649 revisions= cache.get(self._rev_list_cache_key,[])
650if revisions:
651print('Loaded revisions %s-%s from %s'%
652(revisions[0], revisions[-1], cache_filename))
653return revisions
654exceptFileNotFoundError:
655return[]
656except(EnvironmentError,ValueError)as e:
657print('Load revisions cache error:', e)
658return[]
659
660def _save_rev_list_cache(self, revisions):
661ifnot self.use_local_cache:
662return
Kuan Huangfeb4fa552024-08-21 20:40:28[diff] [blame]663ifnot revisions:
664return
Kuan Huangc8dd77a2024-08-12 22:13:42[diff] [blame]665 cache={}
666 cache_filename= self._rev_list_cache_filename
667# Load cache for all of the builds.
668try:
669with open(cache_filename)as cache_file:
670 cache= json.load(cache_file)
671exceptFileNotFoundError:
672pass
673except(EnvironmentError,ValueError)as e:
674print('Load existing revisions cache error:', e)
675return
676# Update and save cache for current build.
677 cache[self._rev_list_cache_key]= revisions
678try:
679with open(cache_filename,'w')as cache_file:
680 json.dump(cache, cache_file)
681print('Saved revisions %s-%s to %s'%
682(revisions[0], revisions[-1], cache_filename))
683exceptEnvironmentErroras e:
684print('Save revisions cache error:', e)
685return
Kuan Huang49a2c6412024-08-12 22:12:33[diff] [blame]686
687def get_rev_list(self):
688"""Gets the list of revision numbers between self.good_revision and
689 self.bad_revision. The result might be cached when use_local_cache."""
Kuan Huangc8dd77a2024-08-12 22:13:42[diff] [blame]690# Download the rev_list_all
691 min_rev, max_rev= sorted((self.good_revision, self.bad_revision))
692 rev_list_all= self._load_rev_list_cache()
693ifnot rev_list_all:
694 rev_list_all= sorted(self._get_rev_list(min_rev, max_rev))
695 self._save_rev_list_cache(rev_list_all)
696else:
697 rev_list_min, rev_list_max= rev_list_all[0], rev_list_all[-1]
698if min_rev< rev_list_minor max_rev> rev_list_max:
699# We only need to request and merge the rev_list beyond the cache.
700 rev_list_requested= self._get_rev_list(
701 min_revif min_rev< rev_list_minelse rev_list_max,
702 max_revif max_rev> rev_list_maxelse rev_list_min)
703 rev_list_all= sorted(set().union(rev_list_all, rev_list_requested))
704 self._save_rev_list_cache(rev_list_all)
705# If we still don't get a rev_list_all for the given range, adjust the
706# range to get the full revision list for better messaging.
707ifnot rev_list_all:
708 rev_list_all= sorted(self._get_rev_list())
709 self._save_rev_list_cache(rev_list_all)
710ifnot rev_list_all:
711raiseBisectException('Could not retrieve the revisions for %s.'%
712 self.platform)
Kuan Huang49a2c6412024-08-12 22:12:33[diff] [blame]713
Kuan Huangc8dd77a2024-08-12 22:13:42[diff] [blame]714# Filter for just the range between good and bad.
715 rev_list=[xfor xin rev_list_allif min_rev<= x<= max_rev]
Kuan Huang8b5e33dc2024-08-15 17:51:24[diff] [blame]716# Don't have enough builds to bisect.
717if len(rev_list)<2:
Kuan Huangc8dd77a2024-08-12 22:13:42[diff] [blame]718 rev_list_min, rev_list_max= rev_list_all[0], rev_list_all[-1]
Kuan Huang49a2c6412024-08-12 22:12:33[diff] [blame]719# Check for specifying a number before the available range.
Kuan Huangc8dd77a2024-08-12 22:13:42[diff] [blame]720if max_rev< rev_list_min:
Kuan Huang49a2c6412024-08-12 22:12:33[diff] [blame]721 msg=(
722'First available bisect revision for %s is %d. Be sure to specify '
723'revision numbers, not branch numbers.'%
Kuan Huangc8dd77a2024-08-12 22:13:42[diff] [blame]724(self.platform, rev_list_min))
725raiseBisectException(msg)
Kuan Huang49a2c6412024-08-12 22:12:33[diff] [blame]726# Check for specifying a number beyond the available range.
Kuan Huangc8dd77a2024-08-12 22:13:42[diff] [blame]727if min_rev> rev_list_max:
Kuan Huang49a2c6412024-08-12 22:12:33[diff] [blame]728# Check for the special case of linux where bisect builds stopped at
729# revision 382086, around March 2016.
730if self.platform=='linux':
731 msg=('Last available bisect revision for %s is %d. Try linux64 '
Kuan Huangc8dd77a2024-08-12 22:13:42[diff] [blame]732'instead.'%(self.platform, rev_list_max))
Kuan Huang49a2c6412024-08-12 22:12:33[diff] [blame]733else:
734 msg=('Last available bisect revision for %s is %d. Try a different '
Kuan Huangc8dd77a2024-08-12 22:13:42[diff] [blame]735'good/bad range.'%(self.platform, rev_list_max))
736raiseBisectException(msg)
Kuan Huang49a2c6412024-08-12 22:12:33[diff] [blame]737# Otherwise give a generic message.
Kuan Huangc8dd77a2024-08-12 22:13:42[diff] [blame]738 msg='We don\'t have enough builds to bisect. rev_list: %s'% rev_list
739raiseBisectException(msg)
Kuan Huang49a2c6412024-08-12 22:12:33[diff] [blame]740
Kuan Huangc8dd77a2024-08-12 22:13:42[diff] [blame]741# Set good and bad revisions to be legit revisions.
742if rev_list:
743if self.good_revision< self.bad_revision:
744 self.good_revision= rev_list[0]
745 self.bad_revision= rev_list[-1]
746else:
747 self.bad_revision= rev_list[0]
748 self.good_revision= rev_list[-1]
749return rev_list
Kuan Huang49a2c6412024-08-12 22:12:33[diff] [blame]750
Kuan Huang8b5e33dc2024-08-15 17:51:24[diff] [blame]751@abc.abstractmethod
752def get_download_url(self, revision):
753"""Gets the download URL for the specific revision."""
754raiseNotImplemented()
755
756def get_download_job(self, revision, name=None):
757"""Gets as a DownloadJob that download the specific revision in threads."""
758returnDownloadJob(self.get_download_url(revision), revision, name)
759
Kuan Huangd608f1592024-08-15 23:31:35[diff] [blame]760def _get_extra_args(self):
761"""Get extra chrome args"""
762return['--user-data-dir=%s'% self.profile]
763
764def _get_extract_binary_glob(self, tempdir):
765"""Get the pathname for extracted chrome binary"""
766return'%s/*/%s'%(tempdir, self.binary_name)
767
Kuan Huang89fe2eef2024-09-13 23:04:02[diff] [blame]768def _get_chromedriver_binary_glob(self, tempdir):
769"""Get the pathname for extracted chromedriver binary"""
770ifnot self.chromedriver_binary_name:
771raiseBisectException(f"chromedriver is not supported on {self.platform}")
772return'%s/*/%s'%(tempdir, self.chromedriver_binary_name)
773
Kuan Huang70f5efe62024-11-07 23:26:35[diff] [blame]774def _run(self, runcommand, cwd=None, shell=False, print_when_error=True):
Kuan Huangb4c8df52024-09-05 17:59:18[diff] [blame]775# is_verbos is a global variable.
776if is_verbose:
777print(('Running '+ str(runcommand)))
778 subproc= subprocess.Popen(runcommand,
779 cwd=cwd,
Kuan Huangabd69f22024-09-13 23:03:48[diff] [blame]780 shell=shell,
Kuan Huangb4c8df52024-09-05 17:59:18[diff] [blame]781 bufsize=-1,
782 stdout=subprocess.PIPE,
783 stderr=subprocess.PIPE)
784(stdout, stderr)= subproc.communicate()
Kuan Huang70f5efe62024-11-07 23:26:35[diff] [blame]785if print_when_errorand subproc.returncode:
786print('command: '+ str(runcommand))
787if is_verboseor(print_when_errorand subproc.returncode):
788print(f'retcode: {subproc.returncode}\nstdout:\n')
Kuan Huangabd69f22024-09-13 23:03:48[diff] [blame]789 sys.stdout.buffer.write(stdout)
790 sys.stdout.flush()
791print('stderr:\n')
792 sys.stderr.buffer.write(stderr)
793 sys.stderr.flush()
Kuan Huangb4c8df52024-09-05 17:59:18[diff] [blame]794return subproc.returncode, stdout, stderr
795
Kuan Huang89fe2eef2024-09-13 23:04:02[diff] [blame]796@staticmethod
797def _glob_with_unique_match(executable_name, tempdir, pathname):
798 executables= glob.glob(pathname)
Kuan Huangd608f1592024-08-15 23:31:35[diff] [blame]799if len(executables)==0:
Kuan Huang89fe2eef2024-09-13 23:04:02[diff] [blame]800raiseBisectException(
801 f'Can not find the {executable_name} binary from {tempdir}')
Kuan Huangd608f1592024-08-15 23:31:35[diff] [blame]802elif len(executables)>1:
Kuan Huang89fe2eef2024-09-13 23:04:02[diff] [blame]803raiseBisectException(
804 f'Multiple {executable_name} executables found: {executables}')
Kuan Huangd608f1592024-08-15 23:31:35[diff] [blame]805return os.path.abspath(executables[0])
806
Kuan Huang89fe2eef2024-09-13 23:04:02[diff] [blame]807def _install_revision(self, download, tempdir):
808"""Unzip and/or install the given download to tempdir. Return executable
809 binaries in a dict."""
810if isinstance(download, dict):
811for eachin download.values():
812UnzipFilenameToDir(each, tempdir)
813else:
814UnzipFilenameToDir(download, tempdir)
815# Searching for the executable, it's unlikely the zip file contains multiple
816# folders with the binary_name.
817 result={}
818 result['chrome']= self._glob_with_unique_match(
819'chrome', tempdir, self._get_extract_binary_glob(tempdir))
820if self.chromedriver:
821 result['chromedriver']= self._glob_with_unique_match(
822'chromedriver', tempdir, self._get_chromedriver_binary_glob(tempdir))
823return result
824
825def _launch_revision(self, tempdir, executables, args=()):
Kuan Huangd608f1592024-08-15 23:31:35[diff] [blame]826 args=[*self._get_extra_args(),*args]
Kuan Huang94c4a2d12024-11-21 21:46:09[diff] [blame]827 args_str= join_args(args)
828 command=(self.command.replace(r'%p', quote_arg(
Kuan Huangd3c862c02024-10-31 04:02:14[diff] [blame]829 executables['chrome'])).replace(r'%s', args_str).replace(
830 r'%a', args_str).replace(r'%t', tempdir))
Kuan Huang89fe2eef2024-09-13 23:04:02[diff] [blame]831if self.chromedriver:
Kuan Huang94c4a2d12024-11-21 21:46:09[diff] [blame]832 command= command.replace(r'%d', quote_arg(executables['chromedriver']))
Kuan Huangabd69f22024-09-13 23:03:48[diff] [blame]833return self._run(command, shell=True)
Kuan Huangd608f1592024-08-15 23:31:35[diff] [blame]834
Kuan Huangc2fec792024-09-03 21:57:38[diff] [blame]835def run_revision(self, download, tempdir, args=()):
Kuan Huangd608f1592024-08-15 23:31:35[diff] [blame]836"""Run downloaded archive"""
Kuan Huang89fe2eef2024-09-13 23:04:02[diff] [blame]837 executables= self._install_revision(download, tempdir)
Kuan Huangc2fec792024-09-03 21:57:38[diff] [blame]838 result=None
839for _in range(self.num_runs):
Kuan Huang89fe2eef2024-09-13 23:04:02[diff] [blame]840 returncode, _, _= result= self._launch_revision(tempdir, executables,
Kuan Huangc2fec792024-09-03 21:57:38[diff] [blame]841 args)
842if returncode:
843break
844return result
Kuan Huangd608f1592024-08-15 23:31:35[diff] [blame]845
846
Kuan Huang6f7f57942024-12-05 19:26:30[diff] [blame]847@functools.total_ordering
848classChromiumVersion:
849"""Chromium version numbers consist of 4 parts: MAJOR.MINOR.BUILD.PATCH.
850
851 This class is used to compare the version numbers.
852 """
853def __init__(self, vstring: str):
854 self.vstring= vstring
855 self.version= tuple(int(x)for xin vstring.split('.'))
856
857def __str__(self):
858return self.vstring
859
860def __repr__(self):
861return"ChromiumVersion ('%s')"% str(self)
862
863def __lt__(self, other):
864if isinstance(other, str):
865 other=ChromiumVersion(other)
866return self.version< other.version
867
868def __eq__(self, other):
869if isinstance(other, str):
870 other=ChromiumVersion(other)
871return self.version== other.version
Kuan Huangd608f1592024-08-15 23:31:35[diff] [blame]872
873def __hash__(self):
874return hash(str(self))
875
Kuan Huang49a2c6412024-08-12 22:12:33[diff] [blame]876
877classReleaseBuild(ArchiveBuild):
878
Kuan Huangc63de402024-08-12 22:15:27[diff] [blame]879def __init__(self, options):
880 super().__init__(options)
Kuan Huang6f7f57942024-12-05 19:26:30[diff] [blame]881 self.good_revision=ChromiumVersion(self.good_revision)
882 self.bad_revision=ChromiumVersion(self.bad_revision)
Kuan Huangc63de402024-08-12 22:15:27[diff] [blame]883
Kuan Huang49a2c6412024-08-12 22:12:33[diff] [blame]884@property
885def build_type(self):
886return'release'
887
888def _get_release_bucket(self):
Kuan Huang49a2c6412024-08-12 22:12:33[diff] [blame]889return RELEASE_BASE_URL
890
Kuan Huangc63de402024-08-12 22:15:27[diff] [blame]891def _get_rev_list(self, min_rev=None, max_rev=None):
892# Get all build numbers in the build bucket.
893 build_numbers=[]
Kuan Huang49a2c6412024-08-12 22:12:33[diff] [blame]894 revision_re= re.compile(r'(\d+\.\d\.\d{4}\.\d+)')
Kuan Huangc63de402024-08-12 22:15:27[diff] [blame]895for pathinGsutilList(self._get_release_bucket()):
896 match= revision_re.search(path)
897if match:
Kuan Huang6f7f57942024-12-05 19:26:30[diff] [blame]898 build_numbers.append(ChromiumVersion(match[1]))
Kuan Huangc63de402024-08-12 22:15:27[diff] [blame]899# Filter the versions between min_rev and max_rev.
900 build_numbers=[
901 xfor xin build_numbers
902if(not min_revor min_rev<= x)and(not max_revor x<= max_rev)
903]
904# Check if target archive build exists in batches.
905# batch size is limited by maximum length for the command line. Which is
906# 32,768 characters on Windows, which should be enough up to 400 files.
907 batch_size=100
Kuan Huang49a2c6412024-08-12 22:12:33[diff] [blame]908 final_list=[]
Kuan Huangc63de402024-08-12 22:15:27[diff] [blame]909for batchin(build_numbers[i:i+ batch_size]
910for iin range(0, len(build_numbers), batch_size)):
911 sys.stdout.write('\rFetching revisions at marker %s'% batch[0])
912 sys.stdout.flush()
913# List the files that exists with listing_platform_dir and archive_name.
914# Gsutil could fail because some of the path not exists. It's safe to
915# ignore them.
916for pathinGsutilList(*[self._get_archive_path(x)for xin batch],
917 ignore_fail=True):
918 match= revision_re.search(path)
919if match:
Kuan Huang6f7f57942024-12-05 19:26:30[diff] [blame]920 final_list.append(ChromiumVersion(match[1]))
Kuan Huangc63de402024-08-12 22:15:27[diff] [blame]921 sys.stdout.write('\r')
922 sys.stdout.flush()
Kuan Huang49a2c6412024-08-12 22:12:33[diff] [blame]923return final_list
924
Kuan Huangc63de402024-08-12 22:15:27[diff] [blame]925def _get_listing_url(self):
926return self._get_release_bucket()
Kuan Huang49a2c6412024-08-12 22:12:33[diff] [blame]927
Kuan Huangfeb4fa552024-08-21 20:40:28[diff] [blame]928def _get_archive_path(self, build_number, archive_name=None):
929if archive_nameisNone:
930 archive_name= self.archive_name
Kuan Huangc63de402024-08-12 22:15:27[diff] [blame]931return'/'.join((self._get_release_bucket(), str(build_number),
Kuan Huangfeb4fa552024-08-21 20:40:28[diff] [blame]932 self.listing_platform_dir.rstrip('/'), archive_name))
Kuan Huang49a2c6412024-08-12 22:12:33[diff] [blame]933
Kuan Huangc8dd77a2024-08-12 22:13:42[diff] [blame]934@property
935def _rev_list_cache_key(self):
Kuan Huangc63de402024-08-12 22:15:27[diff] [blame]936return self._get_archive_path('**')
937
938def _save_rev_list_cache(self, revisions):
Kuan Huang6f7f57942024-12-05 19:26:30[diff] [blame]939# ChromiumVersion is not json-able, convert it back to string format.
Kuan Huangc63de402024-08-12 22:15:27[diff] [blame]940 super()._save_rev_list_cache([str(x)for xin revisions])
941
942def _load_rev_list_cache(self):
Kuan Huang6f7f57942024-12-05 19:26:30[diff] [blame]943# Convert to ChromiumVersion that revisions can be correctly compared.
Kuan Huangc63de402024-08-12 22:15:27[diff] [blame]944 revisions= super()._load_rev_list_cache()
Kuan Huang6f7f57942024-12-05 19:26:30[diff] [blame]945return[ChromiumVersion(x)for xin revisions]
Kuan Huangc63de402024-08-12 22:15:27[diff] [blame]946
Kuan Huang8b5e33dc2024-08-15 17:51:24[diff] [blame]947def get_download_url(self, revision):
Kuan Huang89fe2eef2024-09-13 23:04:02[diff] [blame]948if self.chromedriver:
949return{
950'chrome':
951 self._get_archive_path(revision),
952'chromedriver':
953 self._get_archive_path(revision, self.chromedriver_archive_name),
954}
Kuan Huangfeb4fa552024-08-21 20:40:28[diff] [blame]955return self._get_archive_path(revision)
Kuan Huang8b5e33dc2024-08-15 17:51:24[diff] [blame]956
Kuan Huangc63de402024-08-12 22:15:27[diff] [blame]957
Kuan Huangc63de402024-08-12 22:15:27[diff] [blame]958classArchiveBuildWithCommitPosition(ArchiveBuild):
959"""Class for ArchiveBuilds that organized based on commit position."""
960
961def get_last_change_url(self):
Kuan Huangc8dd77a2024-08-12 22:13:42[diff] [blame]962returnNone
963
Kuan Huang49a2c6412024-08-12 22:12:33[diff] [blame]964def __init__(self, options):
965 super().__init__(options)
966# convert good and bad to commit position as int.
Kuan Huangc63de402024-08-12 22:15:27[diff] [blame]967 self.good_revision=GetRevision(self.good_revision)
Kuan Huang49a2c6412024-08-12 22:12:33[diff] [blame]968ifnot options.bad:
969 self.bad_revision=GetChromiumRevision(self.get_last_change_url())
Kuan Huang49a2c6412024-08-12 22:12:33[diff] [blame]970 self.bad_revision=GetRevision(self.bad_revision)
971
Kuan Huangc63de402024-08-12 22:15:27[diff] [blame]972
973classOfficialBuild(ArchiveBuildWithCommitPosition):
974
Kuan Huang49a2c6412024-08-12 22:12:33[diff] [blame]975@property
976def build_type(self):
977return'official'
978
Kuan Huanga716032a2024-08-14 22:15:46[diff] [blame]979def _get_listing_url(self):
980return'/'.join((PERF_BASE_URL, self.listing_platform_dir))
Kuan Huang49a2c6412024-08-12 22:12:33[diff] [blame]981
Kuan Huangc8dd77a2024-08-12 22:13:42[diff] [blame]982def _get_rev_list(self, min_rev=None, max_rev=None):
Kuan Huang49a2c6412024-08-12 22:12:33[diff] [blame]983# For official builds, it's getting the list from perf build bucket.
Kuan Huanga716032a2024-08-14 22:15:46[diff] [blame]984# Since it's cheap to get full list, we are returning the full list for
985# caching.
Kuan Huang49a2c6412024-08-12 22:12:33[diff] [blame]986 revision_re= re.compile(r'%s_(\d+)\.zip'%(self.archive_extract_dir))
Kuan Huanga716032a2024-08-14 22:15:46[diff] [blame]987 revision_files=GsutilList(self._get_listing_url())
Kuan Huang49a2c6412024-08-12 22:12:33[diff] [blame]988 revision_numbers=[]
989for revision_filein revision_files:
Kuan Huangc63de402024-08-12 22:15:27[diff] [blame]990 revision_num= revision_re.search(revision_file)
Kuan Huang49a2c6412024-08-12 22:12:33[diff] [blame]991if revision_num:
Kuan Huangc63de402024-08-12 22:15:27[diff] [blame]992 revision_numbers.append(int(revision_num[1]))
Kuan Huanga716032a2024-08-14 22:15:46[diff] [blame]993return revision_numbers
Kuan Huang49a2c6412024-08-12 22:12:33[diff] [blame]994
Kuan Huangc8dd77a2024-08-12 22:13:42[diff] [blame]995@property
996def _rev_list_cache_key(self):
997return self._get_listing_url()
998
Kuan Huang8b5e33dc2024-08-15 17:51:24[diff] [blame]999def get_download_url(self, revision):
1000return'%s/%s%s_%s.zip'%(PERF_BASE_URL, self.listing_platform_dir,
1001 self.archive_extract_dir, revision)
1002
Kuan Huangc8dd77a2024-08-12 22:13:42[diff] [blame]1003
Kuan Huangc63de402024-08-12 22:15:27[diff] [blame]1004classSnapshotBuild(ArchiveBuildWithCommitPosition):
Kuan Huang49a2c6412024-08-12 22:12:33[diff] [blame]1005
Kuan Huang2dd3a33f2024-12-13 17:52:52[diff] [blame]1006@property
1007def base_url(self):
1008return CHROMIUM_BASE_URL
Kuan Huang49a2c6412024-08-12 22:12:33[diff] [blame]1009
1010@property
1011def build_type(self):
1012return'snapshot'
1013
Kuan Huanga716032a2024-08-14 22:15:46[diff] [blame]1014def _get_marker_for_revision(self, revision):
Kuan Huang49a2c6412024-08-12 22:12:33[diff] [blame]1015return'%s%d'%(self.listing_platform_dir, revision)
1016
Kuan Huanga716032a2024-08-14 22:15:46[diff] [blame]1017def _fetch_and_parse(self, url):
Kuan Huang49a2c6412024-08-12 22:12:33[diff] [blame]1018"""Fetches a URL and returns a 2-Tuple of ([revisions], next-marker). If
1019 next-marker is not None, then the listing is a partial listing and another
1020 fetch should be performed with next-marker being the marker= GET
1021 parameter."""
1022 handle= urllib.request.urlopen(url)
1023 document=ElementTree.parse(handle)
Kuan Huang49a2c6412024-08-12 22:12:33[diff] [blame]1024# All nodes in the tree are namespaced. Get the root's tag name to extract
1025# the namespace. Etree does namespaces as |{namespace}tag|.
1026 root_tag= document.getroot().tag
1027 end_ns_pos= root_tag.find('}')
1028if end_ns_pos==-1:
1029raiseException('Could not locate end namespace for directory index')
1030 namespace= root_tag[:end_ns_pos+1]
Kuan Huang49a2c6412024-08-12 22:12:33[diff] [blame]1031# Find the prefix (_listing_platform_dir) and whether or not the list is
1032# truncated.
1033 prefix_len= len(document.find(namespace+'Prefix').text)
1034 next_marker=None
1035 is_truncated= document.find(namespace+'IsTruncated')
1036if is_truncatedisnotNoneand is_truncated.text.lower()=='true':
1037 next_marker= document.find(namespace+'NextMarker').text
1038# Get a list of all the revisions.
1039 revisions=[]
Kuan Huanga716032a2024-08-14 22:15:46[diff] [blame]1040 revision_re= re.compile(r'(\d+)')
Kuan Huang49a2c6412024-08-12 22:12:33[diff] [blame]1041 all_prefixes= document.findall(namespace+'CommonPrefixes/'+ namespace+
1042'Prefix')
1043# The <Prefix> nodes have content of the form of
1044# |_listing_platform_dir/revision/|. Strip off the platform dir and the
1045# trailing slash to just have a number.go
1046for prefixin all_prefixes:
Kuan Huanga716032a2024-08-14 22:15:46[diff] [blame]1047 match= revision_re.search(prefix.text[prefix_len:])
1048if match:
1049 revisions.append(int(match[1]))
1050return revisions, next_marker
Kuan Huang49a2c6412024-08-12 22:12:33[diff] [blame]1051
1052def get_last_change_url(self):
1053"""Returns a URL to the LAST_CHANGE file."""
1054return self.base_url+'/'+ self.listing_platform_dir+'LAST_CHANGE'
1055
Kuan Huangc8dd77a2024-08-12 22:13:42[diff] [blame]1056def _get_rev_list(self, min_rev=None, max_rev=None):
Kuan Huanga716032a2024-08-14 22:15:46[diff] [blame]1057# This method works by parsing the Google Storage directory listing into a
1058# list of revision numbers. This method can return a full revision list for
1059# a full scan.
1060ifnot max_rev:
1061 max_rev=GetChromiumRevision(self.get_last_change_url())
1062# The commondatastorage API listing the files by alphabetical order instead
1063# of numerical order (e.g. 1, 10, 2, 3, 4). That starting or breaking the
1064# pagination from a known position is only valid when the number of digits
1065# of min_rev == max_rev.
1066 start_marker=None
1067 next_marker=None
1068if min_revisnotNoneand max_revisnotNoneand len(str(min_rev))== len(
1069 str(max_rev)):
1070 start_marker= next_marker= self._get_marker_for_revision(min_rev)
Kuan Huang49a2c6412024-08-12 22:12:33[diff] [blame]1071else:
Kuan Huanga716032a2024-08-14 22:15:46[diff] [blame]1072 max_rev=None
Kuan Huang49a2c6412024-08-12 22:12:33[diff] [blame]1073
Kuan Huanga716032a2024-08-14 22:15:46[diff] [blame]1074 revisions=[]
1075whileTrue:
Kuan Huang49a2c6412024-08-12 22:12:33[diff] [blame]1076 sys.stdout.write('\rFetching revisions at marker %s'% next_marker)
1077 sys.stdout.flush()
Kuan Huanga716032a2024-08-14 22:15:46[diff] [blame]1078 new_revisions, next_marker= self._fetch_and_parse(
1079 self._get_listing_url(next_marker))
Kuan Huang49a2c6412024-08-12 22:12:33[diff] [blame]1080 revisions.extend(new_revisions)
Kuan Huanga716032a2024-08-14 22:15:46[diff] [blame]1081if max_revand new_revisionsand max_rev<= max(new_revisions):
1082break
1083ifnot next_marker:
Kuan Huang49a2c6412024-08-12 22:12:33[diff] [blame]1084break
1085 sys.stdout.write('\r')
1086 sys.stdout.flush()
Kuan Huanga716032a2024-08-14 22:15:46[diff] [blame]1087# We can only ensure the revisions have no gap (due to the alphabetical
1088# order) between min_rev and max_rev.
1089if start_markeror next_marker:
1090return[
1091 xfor xin revisionsif((min_revisNoneor min_rev<= x)and(
1092 max_revisNoneor x<= max_rev))
1093]
1094# Unless we did a full scan. `not start_marker and not next_marker`
1095else:
1096return revisions
Kuan Huang49a2c6412024-08-12 22:12:33[diff] [blame]1097
1098def _get_listing_url(self, marker=None):
1099"""Returns the URL for a directory listing, with an optional marker."""
1100 marker_param=''
1101if marker:
1102 marker_param='&marker='+ str(marker)
1103return(self.base_url+'/?delimiter=/&prefix='+
1104 self.listing_platform_dir+ marker_param)
1105
Kuan Huangc8dd77a2024-08-12 22:13:42[diff] [blame]1106@property
1107def _rev_list_cache_key(self):
1108return self._get_listing_url()
1109
Kuan Huang8b5e33dc2024-08-15 17:51:24[diff] [blame]1110def get_download_url(self, revision):
Kuan Huang7f55421d2024-08-16 18:59:36[diff] [blame]1111 archive_name= self.archive_name
1112# `archive_name` was changed for chromeos, win and win64 at revision 591483
1113# This is patched for backward compatibility.
1114if revision<591483:
1115if self.platform=='chromeos':
1116 archive_name='chrome-linux.zip'
1117elif self.platformin('win','win64'):
1118 archive_name='chrome-win32.zip'
Kuan Huang89fe2eef2024-09-13 23:04:02[diff] [blame]1119 url_prefix='%s/%s%s/'%(self.base_url, self.listing_platform_dir,
1120 revision)
1121 chrome_url= url_prefix+ archive_name
1122if self.chromedriver:
1123return{
1124'chrome': chrome_url,
1125'chromedriver': url_prefix+ self.chromedriver_archive_name,
1126}
1127return chrome_url
Kuan Huang8b5e33dc2024-08-15 17:51:24[diff] [blame]1128
Kuan Huang49a2c6412024-08-12 22:12:33[diff] [blame]1129
Kuan Huang2dd3a33f2024-12-13 17:52:52[diff] [blame]1130classChromeForTestingBuild(SnapshotBuild):
1131"""Chrome for Testing pre-revision build is public through
1132 storage.googleapis.com.
1133
1134 The archive URL format for CfT builds is:
1135 https://storage.googleapis.com/chrome-for-testing-per-commit-public/{platform}/r{revision}/chrome-{platform}.zip
1136 """
1137
1138@property
1139def base_url(self):
1140return CHROME_FOR_TESTING_BASE_URL
1141
1142@property
1143def build_type(self):
1144return'cft'
1145
1146def _get_marker_for_revision(self, revision):
1147return'%sr%d'%(self.listing_platform_dir, revision)
1148
1149def get_download_url(self, revision):
1150 url_prefix='%s/%sr%d/'%(self.base_url, self.listing_platform_dir,
1151 revision)
1152 chrome_url= url_prefix+ self.archive_name
1153if self.chromedriver:
1154return{
1155'chrome': chrome_url,
1156'chromedriver': url_prefix+ self.chromedriver_archive_name,
1157}
1158return chrome_url
1159
1160
Kuan Huang49a2c6412024-08-12 22:12:33[diff] [blame]1161classASANBuild(SnapshotBuild):
1162"""ASANBuilds works like SnapshotBuild which fetch from commondatastorage, but
Kuan Huanga716032a2024-08-14 22:15:46[diff] [blame]1163 with a different listing url."""
Kuan Huang49a2c6412024-08-12 22:12:33[diff] [blame]1164
1165def __init__(self, options):
1166 super().__init__(options)
Kuan Huang49a2c6412024-08-12 22:12:33[diff] [blame]1167 self.asan_build_type='release'
Kuan Huang49a2c6412024-08-12 22:12:33[diff] [blame]1168
1169@property
Kuan Huang2dd3a33f2024-12-13 17:52:52[diff] [blame]1170def base_url(self):
1171return ASAN_BASE_URL
1172
1173@property
Kuan Huang49a2c6412024-08-12 22:12:33[diff] [blame]1174def build_type(self):
1175return'asan'
1176
1177defGetASANPlatformDir(self):
1178"""ASAN builds are in directories like "linux-release", or have filenames
1179 like "asan-win32-release-277079.zip". This aligns to our platform names
1180 except in the case of Windows where they use "win32" instead of "win"."""
1181if self.platform=='win':
1182return'win32'
1183else:
1184return self.platform
1185
1186defGetASANBaseName(self):
1187"""Returns the base name of the ASAN zip file."""
Kuan Huanga716032a2024-08-14 22:15:46[diff] [blame]1188# TODO: These files were not update since 2016 for linux, 2021 for win.
1189# Need to confirm if it's moved.
Kuan Huang49a2c6412024-08-12 22:12:33[diff] [blame]1190if'linux'in self.platform:
1191return'asan-symbolized-%s-%s'%(self.GetASANPlatformDir(),
1192 self.asan_build_type)
1193else:
1194return'asan-%s-%s'%(self.GetASANPlatformDir(), self.asan_build_type)
1195
1196def get_last_change_url(self):
1197# LAST_CHANGE is not supported in asan build.
1198returnNone
1199
1200def _get_listing_url(self, marker=None):
1201"""Returns the URL for a directory listing, with an optional marker."""
1202 marker_param=''
1203if marker:
1204 marker_param='&marker='+ str(marker)
Kuan Huanga716032a2024-08-14 22:15:46[diff] [blame]1205 prefix='%s-%s/%s'%(self.GetASANPlatformDir(), self.asan_build_type,
1206 self.GetASANBaseName())
1207# This is a hack for delimiter to make commondata API return file path as
1208# prefix that can reuse the code of SnapshotBuild._fetch_and_parse.
1209return self.base_url+'/?delimiter=.zip&prefix='+ prefix+ marker_param
Kuan Huang49a2c6412024-08-12 22:12:33[diff] [blame]1210
Kuan Huanga716032a2024-08-14 22:15:46[diff] [blame]1211def _get_marker_for_revision(self, revision):
Kuan Huang49a2c6412024-08-12 22:12:33[diff] [blame]1212# The build type is hardcoded as release in the original code.
Kuan Huanga716032a2024-08-14 22:15:46[diff] [blame]1213return'%s-%s/%s-%d.zip'%(self.GetASANPlatformDir(), self.asan_build_type,
1214 self.GetASANBaseName(), revision)
Kuan Huang49a2c6412024-08-12 22:12:33[diff] [blame]1215
Kuan Huang8b5e33dc2024-08-15 17:51:24[diff] [blame]1216def get_download_url(self, revision):
1217return'%s/%s'%(self.base_url, self._get_marker_for_revision(revision))
1218
Kuan Huang49a2c6412024-08-12 22:12:33[diff] [blame]1219
Kuan Huangd608f1592024-08-15 23:31:35[diff] [blame]1220classAndroidBuildMixin:
1221
1222def __init__(self, options):
1223 super().__init__(options)
1224 self.apk= options.apk
1225 self.device=InitializeAndroidDevice(options.device_id, self.apk,None)
Kuan Huangfb55c272024-09-16 20:56:42[diff] [blame]1226 self.flag_changer=None
Kuan Huangd608f1592024-08-15 23:31:35[diff] [blame]1227ifnot self.device:
1228raiseBisectException('Failed to initialize device.')
Kuan Huangfeb4fa552024-08-21 20:40:28[diff] [blame]1229 self.binary_name= self._get_apk_filename()
1230
Kuan Huangbeba95a52025-01-06 23:10:10[diff] [blame]1231def _get_apk_mapping(self):
Kuan Huangfeb4fa552024-08-21 20:40:28[diff] [blame]1232 sdk= self.device.build_version_sdk
Kuan Huangfeb4fa552024-08-21 20:40:28[diff] [blame]1233if'webview'in self.apk.lower():
Kuan Huangbeba95a52025-01-06 23:10:10[diff] [blame]1234return WEBVIEW_APK_FILENAMES
Kuan Huangfeb4fa552024-08-21 20:40:28[diff] [blame]1235# Need these logic to bisect very old build. Release binaries are stored
1236# forever and occasionally there are requests to bisect issues introduced
1237# in very old versions.
1238elif sdk< version_codes.LOLLIPOP:
Kuan Huangbeba95a52025-01-06 23:10:10[diff] [blame]1239return CHROME_APK_FILENAMES
Kuan Huangfeb4fa552024-08-21 20:40:28[diff] [blame]1240elif sdk< version_codes.NOUGAT:
Kuan Huangbeba95a52025-01-06 23:10:10[diff] [blame]1241return CHROME_MODERN_APK_FILENAMES
Kuan Huangfeb4fa552024-08-21 20:40:28[diff] [blame]1242else:
Kuan Huangbeba95a52025-01-06 23:10:10[diff] [blame]1243return MONOCHROME_APK_FILENAMES
1244
1245def _get_apk_filename(self):
1246 apk_mapping= self._get_apk_mapping()
Kuan Huangfeb4fa552024-08-21 20:40:28[diff] [blame]1247if self.apknotin apk_mapping:
1248raiseBisectException(
1249'Bisecting on Android only supported for these apks: [%s].'%
1250'|'.join(apk_mapping))
1251return apk_mapping[self.apk]
Kuan Huangd608f1592024-08-15 23:31:35[diff] [blame]1252
Kuan Huangbeba95a52025-01-06 23:10:10[diff] [blame]1253def _show_available_apks(self, tempdir):
1254"""glob and show available apks for the path."""
1255 available_apks=[]
1256 all_apks=[]
1257 reversed_apk_mapping={v: kfor k, vin self._get_apk_mapping().items()}
1258for apk_pathin glob.glob(self._get_extract_binary_glob(tempdir,"*")):
1259 apk_name= os.path.basename(apk_path)
Mark Mentovai37389882025-06-16 22:08:28[diff] [blame]1260ifnot re.search(r'\.apks?$', apk_name):
Kuan Huangbeba95a52025-01-06 23:10:10[diff] [blame]1261continue
1262 all_apks.append(apk_name)
1263if apk_namein reversed_apk_mapping:
1264 available_apks.append(reversed_apk_mapping[apk_name])
1265if available_apks:
1266print(f"The list of available --apk: {{{','.join(available_apks)}}}")
1267elif all_apks:
1268print("No supported apk found. But found following APK(s): "
1269 f"{{{','.join(all_apks)}}}")
1270else:
1271print("No APK(s) found.")
1272
Kuan Huangd608f1592024-08-15 23:31:35[diff] [blame]1273def _install_revision(self, download, tempdir):
Kuan Huangbeba95a52025-01-06 23:10:10[diff] [blame]1274UnzipFilenameToDir(download, tempdir)
1275 apk_path= glob.glob(self._get_extract_binary_glob(tempdir))
1276if len(apk_path)==0:
1277 self._show_available_apks(tempdir)
1278raiseBisectException(f'Can not find {self.binary_name} from {tempdir}')
1279InstallOnAndroid(self.device, apk_path[0])
Kuan Huangd608f1592024-08-15 23:31:35[diff] [blame]1280
Kuan Huang89fe2eef2024-09-13 23:04:02[diff] [blame]1281def _launch_revision(self, tempdir, executables, args=()):
Kuan Huangfb55c272024-09-16 20:56:42[diff] [blame]1282if args:
1283if self.apknotin chrome.PACKAGE_INFO:
1284raiseBisectException(
1285 f'Launching args are not supported for {self.apk}')
1286ifnot self.flag_changer:
1287 self.flag_changer= flag_changer.FlagChanger(
1288 self.device, chrome.PACKAGE_INFO[self.apk].cmdline_file)
1289 self.flag_changer.ReplaceFlags(args)
Kuan Huangd608f1592024-08-15 23:31:35[diff] [blame]1290LaunchOnAndroid(self.device, self.apk)
1291return(0, sys.stdout, sys.stderr)
1292
Kuan Huangbeba95a52025-01-06 23:10:10[diff] [blame]1293def _get_extract_binary_glob(self, tempdir, binary_name=None):
1294if binary_nameisNone:
1295 binary_name= self.binary_name
1296return'%s/*/apks/%s'%(tempdir, binary_name)
Kuan Huangd608f1592024-08-15 23:31:35[diff] [blame]1297
1298
Kuan Huangfeb4fa552024-08-21 20:40:28[diff] [blame]1299classAndroidTrichromeMixin(AndroidBuildMixin):
1300
1301def __init__(self, options):
1302 self._64bit_platforms=('android-arm64','android-x64',
1303'android-arm64-high')
1304 super().__init__(options)
1305if self.device.build_version_sdk< version_codes.Q:
1306raiseBisectException("Trichrome is only supported after Android Q.")
1307 self.library_binary_name= self._get_library_filename()
1308
Kuan Huangbeba95a52025-01-06 23:10:10[diff] [blame]1309def _get_apk_mapping(self, prefer_64bit=True):
Kuan Huangfeb4fa552024-08-21 20:40:28[diff] [blame]1310if self.platformin self._64bit_platformsand prefer_64bit:
Kuan Huangbeba95a52025-01-06 23:10:10[diff] [blame]1311return TRICHROME64_APK_FILENAMES
Kuan Huangfeb4fa552024-08-21 20:40:28[diff] [blame]1312else:
Kuan Huangbeba95a52025-01-06 23:10:10[diff] [blame]1313return TRICHROME_APK_FILENAMES
1314
1315def _get_apk_filename(self, prefer_64bit=True):
1316 apk_mapping= self._get_apk_mapping(prefer_64bit)
Kuan Huangfeb4fa552024-08-21 20:40:28[diff] [blame]1317if self.apknotin apk_mapping:
1318raiseBisectException(
1319'Bisecting on Android only supported for these apks: [%s].'%
1320'|'.join(apk_mapping))
1321return apk_mapping[self.apk]
1322
1323def _get_library_filename(self, prefer_64bit=True):
1324 apk_mapping=None
1325if self.platformin self._64bit_platformsand prefer_64bit:
1326 apk_mapping= TRICHROME64_LIBRARY_FILENAMES
1327else:
1328 apk_mapping= TRICHROME_LIBRARY_FILENAMES
1329if self.apknotin apk_mapping:
1330raiseBisectException(
1331'Bisecting for Android Trichrome only supported for these apks: [%s].'
1332%'|'.join(apk_mapping))
1333return apk_mapping[self.apk]
1334
Kuan Huangbeba95a52025-01-06 23:10:10[diff] [blame]1335def _install_revision(self, download, tempdir):
1336UnzipFilenameToDir(download, tempdir)
1337 trichrome_library_filename= self._get_library_filename()
1338 trichrome_library_path= glob.glob(
1339 f'{tempdir}/*/apks/{trichrome_library_filename}')
1340if len(trichrome_library_path)==0:
1341 self._show_available_apks(tempdir)
1342raiseBisectException(
1343 f'Can not find {trichrome_library_filename} from {tempdir}')
1344 trichrome_filename= self._get_apk_filename()
1345 trichrome_path= glob.glob(f'{tempdir}/*/apks/{trichrome_filename}')
1346if len(trichrome_path)==0:
1347 self._show_available_apks(tempdir)
1348raiseBisectException(f'Can not find {trichrome_filename} from {tempdir}')
1349InstallOnAndroid(self.device, trichrome_library_path[0])
1350InstallOnAndroid(self.device, trichrome_path[0])
1351
Kuan Huangfeb4fa552024-08-21 20:40:28[diff] [blame]1352
Kuan Huangd608f1592024-08-15 23:31:35[diff] [blame]1353classAndroidReleaseBuild(AndroidBuildMixin,ReleaseBuild):
1354
1355def __init__(self, options):
1356 super().__init__(options)
1357 self.signed= options.signed
1358# We could download the apk directly from build bucket
Kuan Huangfeb4fa552024-08-21 20:40:28[diff] [blame]1359 self.archive_name= self.binary_name
Kuan Huangd608f1592024-08-15 23:31:35[diff] [blame]1360
1361def _get_release_bucket(self):
1362if self.signed:
1363return ANDROID_RELEASE_BASE_URL_SIGNED
1364else:
1365return ANDROID_RELEASE_BASE_URL
1366
1367def _get_rev_list(self, min_rev=None, max_rev=None):
1368# Android release builds store archives directly in a GCS bucket that
1369# contains a large number of objects. Listing the full revision list takes
1370# too much time, so we should disallow it and fail fast.
1371ifnot min_revornot max_rev:
1372raiseBisectException(
1373"Could not found enough revisions for Android %s release channel."%
1374 self.apk)
1375return super()._get_rev_list(min_rev, max_rev)
1376
1377def _install_revision(self, download, tempdir):
1378# AndroidRelease build downloads the apks directly from GCS bucket.
1379InstallOnAndroid(self.device, download)
1380
1381
Kuan Huangfeb4fa552024-08-21 20:40:28[diff] [blame]1382classAndroidTrichromeReleaseBuild(AndroidTrichromeMixin,AndroidReleaseBuild):
1383
1384def __init__(self, options):
1385 super().__init__(options)
1386# Release build will download the binary directly from GCS bucket.
1387 self.archive_name= self.binary_name
1388 self.library_archive_name= self.library_binary_name
1389
1390def _get_library_filename(self, prefer_64bit=True):
1391if self.apk=='chrome'and self.platform=='android-arm64-high':
1392raiseBisectException('chrome debug build is not supported for %s'%
1393 self.platform)
1394return super()._get_library_filename(prefer_64bit)
1395
1396def get_download_url(self, revision):
1397# M112 is when we started serving 6432 to 4GB+ devices. Before this it was
1398# only to 6GB+ devices.
Kuan Huang6f7f57942024-12-05 19:26:30[diff] [blame]1399if revision>=ChromiumVersion('112'):
Kuan Huangfeb4fa552024-08-21 20:40:28[diff] [blame]1400 trichrome= self.binary_name
1401 trichrome_library= self.library_binary_name
1402else:
1403 trichrome= self._get_apk_filename(prefer_64bit=False)
1404 trichrome_library= self._get_library_filename(prefer_64bit=False)
1405return{
1406'trichrome': self._get_archive_path(revision, trichrome),
1407'trichrome_library': self._get_archive_path(revision,
1408 trichrome_library),
1409}
1410
1411def _install_revision(self, download, tempdir):
1412ifnot isinstance(download, dict):
1413raiseException("Trichrome should download multiple files from GCS.")
1414# AndroidRelease build downloads the apks directly from GCS bucket.
1415# Trichrome need to install the trichrome_library first.
1416InstallOnAndroid(self.device, download['trichrome_library'])
1417InstallOnAndroid(self.device, download['trichrome'])
1418
1419
1420classAndroidTrichromeOfficialBuild(AndroidTrichromeMixin,OfficialBuild):
1421
Trung Nguyend391d052025-06-04 09:35:09[diff] [blame]1422def __init__(self, options):
1423 super().__init__(options)
1424if'webview'in options.apk.lower():
1425# Trichrome APKs targets were introduced in crrev.com/c/5719255
1426if int(options.good)<1334017or int(options.bad)<1334017:
1427raiseBisectException(
1428"Bisecting WebView only supports version >= 1334017")
1429
1430
Kuan Huangbeba95a52025-01-06 23:10:10[diff] [blame]1431def _get_apk_mapping(self, prefer_64bit=True):
1432return{
1433 k: v.replace(".apks",".minimal.apks")
1434for k, vin super()._get_apk_mapping(prefer_64bit).items()
1435}
Kuan Huangfeb4fa552024-08-21 20:40:28[diff] [blame]1436
1437
Kuan Huangd608f1592024-08-15 23:31:35[diff] [blame]1438classLinuxReleaseBuild(ReleaseBuild):
1439
1440def _get_extra_args(self):
1441 args= super()._get_extra_args()
1442# The sandbox must be run as root on release Chrome, so bypass it.
1443if self.platform.startswith('linux'):
1444 args.append('--no-sandbox')
1445return args
1446
1447
1448classAndroidOfficialBuild(AndroidBuildMixin,OfficialBuild):
1449pass
1450
1451
1452classAndroidSnapshotBuild(AndroidBuildMixin,SnapshotBuild):
1453pass
1454
1455
Kuan Huang22770072024-08-27 20:06:53[diff] [blame]1456classIOSReleaseBuild(ReleaseBuild):
1457
1458def __init__(self, options):
1459 super().__init__(options)
1460 self.signed= options.signed
1461ifnot self.signed:
1462print('WARNING: --signed is recommended for iOS release builds.')
1463 self.device_id= options.device_id
1464ifnot self.device_id:
1465raiseBisectException('--device-id is required for iOS builds.')
1466 self.ipa= options.ipa
1467ifnot self.ipa:
1468raiseBisectException('--ipa is required for iOS builds.')
1469if self.ipa.endswith('.ipa'):
1470 self.ipa= self.ipa[:-4]
1471 self.binary_name= self.archive_name= f'{self.ipa}.ipa'
1472
1473def _get_release_bucket(self):
1474if self.signed:
1475return IOS_RELEASE_BASE_URL_SIGNED
1476return IOS_RELEASE_BASE_URL
1477
1478def _get_archive_path(self, build_number, archive_name=None):
1479if archive_nameisNone:
1480 archive_name= self.archive_name
1481# The format for iOS build is
1482# {IOS_RELEASE_BASE_URL}/{build_number}/{sdk_version}
1483# /{builder_name}/{build_number}/{archive_name}
1484# that it's not possible to generate the actual archive_path for a build.
1485# That we are returning a path with wildcards and expecting only one match.
1486return(f'{self._get_release_bucket()}/{build_number}/*/'
1487 f'{self.listing_platform_dir.rstrip("/")}/*/{archive_name}')
1488
Kuan Huang22770072024-08-27 20:06:53[diff] [blame]1489def _install_revision(self, download, tempdir):
1490# install ipa
1491 retcode, stdout, stderr= self._run([
1492'xcrun','devicectl','device','install','app','--device',
1493 self.device_id, download
1494])
1495if retcode:
1496raiseBisectException(f'Install app error, code:{retcode}\n'
1497 f'stdout:\n{stdout}\n'
1498 f'stderr:\n{stderr}')
1499# extract and return CFBundleIdentifier from ipa.
1500UnzipFilenameToDir(download, tempdir)
1501 plist= glob.glob(f'{tempdir}/Payload/*/Info.plist')
1502ifnot plist:
1503raiseBisectException(f'Could not find Info.plist from {tempdir}.')
1504 retcode, stdout, stderr= self._run(
1505['plutil','-extract','CFBundleIdentifier','raw', plist[0]])
1506if retcode:
1507raiseBisectException(f'Extract bundle identifier error, code:{retcode}\n'
1508 f'stdout:\n{stdout}\n'
1509 f'stderr:\n{stderr}')
1510 bundle_identifier= stdout.strip()
1511return bundle_identifier
1512
1513def _launch_revision(self, tempdir, bundle_identifier, args=()):
1514 retcode, stdout, stderr= self._run([
1515'xcrun','devicectl','device','process','launch','--device',
1516 self.device_id, bundle_identifier,*args
1517])
1518if retcode:
1519print(f'Warning: App launching error, code:{retcode}\n'
1520 f'stdout:\n{stdout}\n'
1521 f'stderr:\n{stderr}')
1522return retcode, stdout, stderr
1523
1524
Kuan Huangb4c8df52024-09-05 17:59:18[diff] [blame]1525classIOSSimulatorReleaseBuild(ReleaseBuild):
1526"""
1527 chrome/ci/ios-simulator is generating this build and archiving it in
1528 gs://bling-archive with Chrome versions. It's not actually a release build,
1529 but it's similar to one.
1530 """
1531
1532def __init__(self, options):
1533 super().__init__(options)
1534 self.device_id= options.device_id
1535ifnot self.device_id:
1536raiseBisectException('--device-id is required for iOS Simulator.')
Kuan Huang725c93052024-10-10 19:33:10[diff] [blame]1537 retcode, stdout, stderr= self._run(
1538['xcrun','simctl','boot', self.device_id])
1539if retcode:
1540print(f'Warning: Boot Simulator error, code:{retcode}\n'
1541 f'stdout:\n{stdout}\n'
1542 f'stderr:\n{stderr}')
Kuan Huangb4c8df52024-09-05 17:59:18[diff] [blame]1543
1544def _get_release_bucket(self):
1545return IOS_ARCHIVE_BASE_URL
1546
1547def _get_archive_path(self, build_number, archive_name=None):
1548if archive_nameisNone:
1549 archive_name= self.archive_name
1550# The path format for ios-simulator build is
1551# {%chromium_version%}/{%timestamp%}/Chromium.tar.gz
1552# that it's not possible to generate the actual archive_path for a build.
1553# We are returning a path with wildcards and expecting only one match.
1554return f'{self._get_release_bucket()}/{build_number}/*/{archive_name}'
1555
1556def _get_extract_binary_glob(self, tempdir):
1557return f'{tempdir}/{self.binary_name}'
1558
1559def _install_revision(self, download, tempdir):
Kuan Huang89fe2eef2024-09-13 23:04:02[diff] [blame]1560 executables= super()._install_revision(download, tempdir)
1561 executable= executables['chrome']
Kuan Huangb4c8df52024-09-05 17:59:18[diff] [blame]1562# install app
1563 retcode, stdout, stderr= self._run(
1564['xcrun','simctl','install', self.device_id, executable])
1565if retcode:
1566raiseBisectException(f'Install app error, code:{retcode}\n'
1567 f'stdout:\n{stdout}\n'
1568 f'stderr:\n{stderr}')
1569# extract and return CFBundleIdentifier from app.
1570 plist= glob.glob(f'{executable}/Info.plist')
1571ifnot plist:
1572raiseBisectException(f'Could not find Info.plist from {executable}.')
1573 retcode, stdout, stderr= self._run(
1574['plutil','-extract','CFBundleIdentifier','raw', plist[0]])
1575if retcode:
1576raiseBisectException(f'Extract bundle identifier error, code:{retcode}\n'
1577 f'stdout:\n{stdout}\n'
1578 f'stderr:\n{stderr}')
1579 bundle_identifier= stdout.strip()
1580return bundle_identifier
1581
1582def _launch_revision(self, tempdir, bundle_identifier, args=()):
1583 retcode, stdout, stderr= self._run(
1584['xcrun','simctl','launch', self.device_id, bundle_identifier,*args])
1585if retcode:
1586print(f'Warning: App launching error, code:{retcode}\n'
1587 f'stdout:\n{stdout}\n'
1588 f'stderr:\n{stderr}')
1589return retcode, stdout, stderr
1590
1591
Kuan Huangd608f1592024-08-15 23:31:35[diff] [blame]1592def create_archive_build(options):
Kuan Huang37d0daa2024-09-19 18:03:08[diff] [blame]1593if options.build_type=='release':
Kuan Huangfeb4fa552024-08-21 20:40:28[diff] [blame]1594if options.archive=='android-arm64-high':
1595returnAndroidTrichromeReleaseBuild(options)
1596elif options.archive.startswith('android'):
Kuan Huangd608f1592024-08-15 23:31:35[diff] [blame]1597returnAndroidReleaseBuild(options)
1598elif options.archive.startswith('linux'):
1599returnLinuxReleaseBuild(options)
Kuan Huangb4c8df52024-09-05 17:59:18[diff] [blame]1600elif options.archive=='ios-simulator':
1601returnIOSSimulatorReleaseBuild(options)
1602elif options.archive=='ios':
Kuan Huang22770072024-08-27 20:06:53[diff] [blame]1603returnIOSReleaseBuild(options)
Kuan Huang49a2c6412024-08-12 22:12:33[diff] [blame]1604returnReleaseBuild(options)
Kuan Huang37d0daa2024-09-19 18:03:08[diff] [blame]1605elif options.build_type=='official':
Kuan Huangfeb4fa552024-08-21 20:40:28[diff] [blame]1606if options.archive=='android-arm64-high':
1607returnAndroidTrichromeOfficialBuild(options)
1608elif options.archive.startswith('android'):
Kuan Huangd608f1592024-08-15 23:31:35[diff] [blame]1609returnAndroidOfficialBuild(options)
Kuan Huang49a2c6412024-08-12 22:12:33[diff] [blame]1610returnOfficialBuild(options)
Kuan Huang37d0daa2024-09-19 18:03:08[diff] [blame]1611elif options.build_type=='asan':
Kuan Huangd608f1592024-08-15 23:31:35[diff] [blame]1612# ASANBuild is only supported on win/linux/mac.
Kuan Huang49a2c6412024-08-12 22:12:33[diff] [blame]1613returnASANBuild(options)
Kuan Huang2dd3a33f2024-12-13 17:52:52[diff] [blame]1614elif options.build_type=='cft':
1615returnChromeForTestingBuild(options)
Kuan Huang49a2c6412024-08-12 22:12:33[diff] [blame]1616else:
Kuan Huangd608f1592024-08-15 23:31:35[diff] [blame]1617if options.archive.startswith('android'):
1618returnAndroidSnapshotBuild(options)
Kuan Huang49a2c6412024-08-12 22:12:33[diff] [blame]1619returnSnapshotBuild(options)
1620
1621
prasadv2375e6d2017-03-20 19:23:23[diff] [blame]1622defIsMac():
1623return sys.platform.startswith('darwin')
1624
1625
ihf@chromium.orgfc3702e2013-11-09 04:23:00[diff] [blame]1626defUnzipFilenameToDir(filename, directory):
1627"""Unzip |filename| to |directory|."""
szager@google.comafe30662011-07-30 01:05:52[diff] [blame]1628 cwd= os.getcwd()
1629ifnot os.path.isabs(filename):
1630 filename= os.path.join(cwd, filename)
nirnimesh@chromium.orgbd8dcb92010-03-31 01:05:24[diff] [blame]1631# Make base.
ihf@chromium.orgfc3702e2013-11-09 04:23:00[diff] [blame]1632ifnot os.path.isdir(directory):
1633 os.mkdir(directory)
1634 os.chdir(directory)
prasadv2375e6d2017-03-20 19:23:23[diff] [blame]1635
Kuan Huangb4c8df52024-09-05 17:59:18[diff] [blame]1636# Support for tar archives.
1637if tarfile.is_tarfile(filename):
1638 tf= tarfile.open(filename,'r')
1639 tf.extractall(directory)
1640 os.chdir(cwd)
1641return
1642
prasadv2375e6d2017-03-20 19:23:23[diff] [blame]1643# The Python ZipFile does not support symbolic links, which makes it
1644# unsuitable for Mac builds. so use ditto instead.
1645ifIsMac():
1646 unzip_cmd=['ditto','-x','-k', filename,'.']
1647 proc= subprocess.Popen(unzip_cmd, bufsize=0, stdout=subprocess.PIPE,
1648 stderr=subprocess.PIPE)
1649 proc.communicate()
1650 os.chdir(cwd)
1651return
1652
1653 zf= zipfile.ZipFile(filename)
vitalybuka@chromium.orge29c08c2012-09-17 20:50:50[diff] [blame]1654# Extract files.
1655for infoin zf.infolist():
1656 name= info.filename
1657if name.endswith('/'):# dir
1658ifnot os.path.isdir(name):
1659 os.makedirs(name)
1660else:# file
ihf@chromium.orgfc3702e2013-11-09 04:23:00[diff] [blame]1661 directory= os.path.dirname(name)
Daniel Cheng0826fa92024-03-23 21:52:34[diff] [blame]1662if directoryandnot os.path.isdir(directory):
ihf@chromium.orgfc3702e2013-11-09 04:23:00[diff] [blame]1663 os.makedirs(directory)
vitalybuka@chromium.orge29c08c2012-09-17 20:50:50[diff] [blame]1664 out= open(name,'wb')
1665 out.write(zf.read(name))
1666 out.close()
1667# Set permissions. Permission info in external_attr is shifted 16 bits.
Bruce Dawson039777ef2020-10-26 20:34:47[diff] [blame]1668 os.chmod(name, info.external_attr>>16)
vitalybuka@chromium.orge29c08c2012-09-17 20:50:50[diff] [blame]1669 os.chdir(cwd)
nirnimesh@chromium.orgbd8dcb92010-03-31 01:05:24[diff] [blame]1670
rsesek@chromium.org67e0bc62009-09-03 22:06:09[diff] [blame]1671
Sven Zheng8e079562024-05-10 20:11:06[diff] [blame]1672def gsutil_download(download_url, filename):
1673 command=['cp', download_url, filename]
1674RunGsutilCommand(command)
1675
1676
Kuan Huangc2fec792024-09-03 21:57:38[diff] [blame]1677defEvaluateRevision(archive_build, download, revision, args, evaluate):
1678"""fetch.wait_for(), archive_build.run_revision() and evaluate the result."""
1679whileTrue:
1680 exit_status= stdout= stderr=None
1681# Create a temp directory and unzip the revision into it.
1682with tempfile.TemporaryDirectory(prefix='bisect_tmp')as tempdir:
1683# On Windows 10, file system needs to be readable from App Container.
1684if sys.platform=='win32'and platform.release()=='10':
1685 icacls_cmd=['icacls', tempdir,'/grant','*S-1-15-2-2:(OI)(CI)(RX)']
1686 proc= subprocess.Popen(icacls_cmd,
1687 bufsize=0,
1688 stdout=subprocess.PIPE,
1689 stderr=subprocess.PIPE)
1690 proc.communicate()
1691# run_revision
1692print(f'Trying revision {revision!s}: {download!s} in {tempdir!s}')
1693try:
1694 exit_status, stdout, stderr= archive_build.run_revision(
1695 download, tempdir, args)
1696exceptSystemExit:
1697raise
1698exceptException:
1699 traceback.print_exc(file=sys.stderr)
1700# evaluate
1701 answer= evaluate(revision, exit_status, stdout, stderr)
1702if answer!='r':
1703return answer
evan@chromium.org79f14742010-03-10 01:01:57[diff] [blame]1704
maruel@chromium.orgcb155a82011-11-29 17:25:34[diff] [blame]1705
Sven Zheng8e079562024-05-10 20:11:06[diff] [blame]1706# The arguments release_builds, status, stdout and stderr are unused.
qyearsley@chromium.org4df583c2014-07-31 17:11:55[diff] [blame]1707# They are present here because this function is passed to Bisect which then
1708# calls it with 5 arguments.
1709# pylint: disable=W0613
Kuan Huangd608f1592024-08-15 23:31:35[diff] [blame]1710defAskIsGoodBuild(rev, exit_status, stdout, stderr):
qyearsley@chromium.org4df583c2014-07-31 17:11:55[diff] [blame]1711"""Asks the user whether build |rev| is good or bad."""
evan@chromium.org79f14742010-03-10 01:01:57[diff] [blame]1712# Loop until we get a response that we can parse.
rsesek@chromium.org67e0bc62009-09-03 22:06:09[diff] [blame]1713whileTrue:
Sven Zheng8e079562024-05-10 20:11:06[diff] [blame]1714 response= input('Revision %s is '
1715'[(g)ood/(b)ad/(r)etry/(u)nknown/(s)tdout/(q)uit]: '%
1716 str(rev))
wangxianzhud8c4c562015-12-15 23:39:51[diff] [blame]1717if responsein('g','b','r','u'):
asvitkine@chromium.org53bb6342012-06-01 04:11:00[diff] [blame]1718return response
wangxianzhud8c4c562015-12-15 23:39:51[diff] [blame]1719if response=='q':
szager@google.comafe30662011-07-30 01:05:52[diff] [blame]1720raiseSystemExit()
wangxianzhud8c4c562015-12-15 23:39:51[diff] [blame]1721if response=='s':
Sven Zheng8e079562024-05-10 20:11:06[diff] [blame]1722print(stdout)
1723print(stderr)
rsesek@chromium.org67e0bc62009-09-03 22:06:09[diff] [blame]1724
maruel@chromium.orgcb155a82011-11-29 17:25:34[diff] [blame]1725
Kuan Huangd608f1592024-08-15 23:31:35[diff] [blame]1726defIsGoodASANBuild(rev, exit_status, stdout, stderr):
cmumford@chromium.org011886692014-08-01 21:00:21[diff] [blame]1727"""Determine if an ASAN build |rev| is good or bad
1728
1729 Will examine stderr looking for the error message emitted by ASAN. If not
1730 found then will fallback to asking the user."""
1731if stderr:
1732 bad_count=0
1733for linein stderr.splitlines():
Raul Tambre57e09d62019-09-22 17:18:52[diff] [blame]1734print(line)
cmumford@chromium.org011886692014-08-01 21:00:21[diff] [blame]1735if line.find('ERROR: AddressSanitizer:')!=-1:
1736 bad_count+=1
1737if bad_count>0:
Raul Tambre57e09d62019-09-22 17:18:52[diff] [blame]1738print('Revision %d determined to be bad.'% rev)
cmumford@chromium.org011886692014-08-01 21:00:21[diff] [blame]1739return'b'
Kuan Huangd608f1592024-08-15 23:31:35[diff] [blame]1740returnAskIsGoodBuild(rev, exit_status, stdout, stderr)
skobes21b5cdfb2016-03-21 23:13:02[diff] [blame]1741
1742
Kuan Huangd608f1592024-08-15 23:31:35[diff] [blame]1743defDidCommandSucceed(rev, exit_status, stdout, stderr):
skobes21b5cdfb2016-03-21 23:13:02[diff] [blame]1744if exit_status:
Raul Tambre57e09d62019-09-22 17:18:52[diff] [blame]1745print('Bad revision: %s'% rev)
skobes21b5cdfb2016-03-21 23:13:02[diff] [blame]1746return'b'
1747else:
Raul Tambre57e09d62019-09-22 17:18:52[diff] [blame]1748print('Good revision: %s'% rev)
skobes21b5cdfb2016-03-21 23:13:02[diff] [blame]1749return'g'
1750
cmumford@chromium.org011886692014-08-01 21:00:21[diff] [blame]1751
Kuan Huang8b5e33dc2024-08-15 17:51:24[diff] [blame]1752classDownloadJob:
Kuan Huangfeb4fa552024-08-21 20:40:28[diff] [blame]1753"""
1754 DownloadJob represents a task to download a given url.
1755 """
qyearsley@chromium.org4df583c2014-07-31 17:11:55[diff] [blame]1756
Kuan Huang8b5e33dc2024-08-15 17:51:24[diff] [blame]1757def __init__(self, url, rev, name=None):
Kuan Huangfeb4fa552024-08-21 20:40:28[diff] [blame]1758"""
1759 Args:
1760 url: The url to download or a dict of {key: url} to download multiple
1761 targets.
1762 rev: The revision of the target.
1763 name: The name of the thread.
1764 """
1765if isinstance(url, dict):
1766 self.is_multiple=True
1767 self.urls= url
1768else:
1769 self.is_multiple=False
1770 self.urls={None: url}
asvitkine@chromium.org53bb6342012-06-01 04:11:00[diff] [blame]1771 self.rev= rev
Kuan Huang8b5e33dc2024-08-15 17:51:24[diff] [blame]1772 self.name= name
1773
Kuan Huangfeb4fa552024-08-21 20:40:28[diff] [blame]1774 self.results={}
Kuan Huangc3a4cd1e2024-10-03 21:17:31[diff] [blame]1775 self.exc_info=None# capture exception from worker thread
asvitkine@chromium.org53bb6342012-06-01 04:11:00[diff] [blame]1776 self.quit_event= threading.Event()
1777 self.progress_event= threading.Event()
qyearsley@chromium.org4df583c2014-07-31 17:11:55[diff] [blame]1778 self.thread=None
asvitkine@chromium.org53bb6342012-06-01 04:11:00[diff] [blame]1779
Kuan Huang8b5e33dc2024-08-15 17:51:24[diff] [blame]1780def _clear_up_tmp_files(self):
Kuan Huangfeb4fa552024-08-21 20:40:28[diff] [blame]1781ifnot self.results:
1782return
1783for tmp_filein self.results.values():
Kuan Huang8b5e33dc2024-08-15 17:51:24[diff] [blame]1784try:
Kuan Huangfeb4fa552024-08-21 20:40:28[diff] [blame]1785 os.unlink(tmp_file)
Kuan Huang8b5e33dc2024-08-15 17:51:24[diff] [blame]1786exceptFileNotFoundError:
1787# Handle missing archives.
1788pass
Kuan Huangfeb4fa552024-08-21 20:40:28[diff] [blame]1789 self.results=None
asvitkine@chromium.org53bb6342012-06-01 04:11:00[diff] [blame]1790
Kuan Huang8b5e33dc2024-08-15 17:51:24[diff] [blame]1791def __del__(self):
1792 self._clear_up_tmp_files()
1793
1794def _report_hook(self, blocknum, blocksize, totalsize):
1795if self.quit_eventand self.quit_event.is_set():
1796raiseRuntimeError('Aborting download of revision %s'% str(self.rev))
1797ifnot self.progress_eventornot self.progress_event.is_set():
1798return
1799 size= blocknum* blocksize
1800if totalsize==-1:# Total size not known.
1801 progress='Received %d bytes'% size
1802else:
1803 size= min(totalsize, size)
1804 progress='Received %d of %d bytes, %.2f%%'%(size, totalsize,
1805100.0* size/ totalsize)
1806# Send a \r to let all progress messages use just one line of output.
1807print(progress, end='\r', flush=True)
1808
Kuan Huangfeb4fa552024-08-21 20:40:28[diff] [blame]1809def _fetch(self, url, tmp_file):
1810if url.startswith('gs'):
1811 gsutil_download(url, tmp_file)
1812else:
1813 urllib.request.urlretrieve(url, tmp_file, self._report_hook)
1814if self.progress_eventand self.progress_event.is_set():
1815print()
1816
Kuan Huang8b5e33dc2024-08-15 17:51:24[diff] [blame]1817def fetch(self):
1818try:
Kuan Huangfeb4fa552024-08-21 20:40:28[diff] [blame]1819for key, urlin self.urls.items():
Kuan Huangb4c8df52024-09-05 17:59:18[diff] [blame]1820# Keep the basename as part of tempfile name that make it easier to
1821# identify what's been downloaded.
1822 basename= os.path.basename(urllib.parse.urlparse(url).path)
1823 fd, tmp_file= tempfile.mkstemp(suffix=basename)
Kuan Huangfeb4fa552024-08-21 20:40:28[diff] [blame]1824 self.results[key]= tmp_file
1825 os.close(fd)
1826 self._fetch(url, tmp_file)
Kuan Huang8b5e33dc2024-08-15 17:51:24[diff] [blame]1827exceptRuntimeError:
1828pass
Kuan Huangc3a4cd1e2024-10-03 21:17:31[diff] [blame]1829exceptBaseException:
1830 self.exc_info= sys.exc_info()
Kuan Huang8b5e33dc2024-08-15 17:51:24[diff] [blame]1831
1832def start(self):
1833"""Start the download in a thread."""
1834assert self.threadisNone,"DownloadJob is already started."
1835 self.thread= threading.Thread(target=self.fetch, name=self.name)
1836 self.thread.start()
1837return self
1838
1839def stop(self):
asvitkine@chromium.org53bb6342012-06-01 04:11:00[diff] [blame]1840"""Stops the download which must have been started previously."""
qyearsley@chromium.org4df583c2014-07-31 17:11:55[diff] [blame]1841assert self.thread,'DownloadJob must be started before Stop is called.'
asvitkine@chromium.org53bb6342012-06-01 04:11:00[diff] [blame]1842 self.quit_event.set()
1843 self.thread.join()
Kuan Huang8b5e33dc2024-08-15 17:51:24[diff] [blame]1844 self._clear_up_tmp_files()
asvitkine@chromium.org53bb6342012-06-01 04:11:00[diff] [blame]1845
Kuan Huang8b5e33dc2024-08-15 17:51:24[diff] [blame]1846def wait_for(self):
1847"""Prints a message and waits for the download to complete.
1848 The method will return the path of downloaded files.
1849 """
1850ifnot self.thread:
1851 self.start()
Raul Tambre57e09d62019-09-22 17:18:52[diff] [blame]1852print('Downloading revision %s...'% str(self.rev))
asvitkine@chromium.org53bb6342012-06-01 04:11:00[diff] [blame]1853 self.progress_event.set()# Display progress of download.
rob8a4543f2016-01-20 00:43:59[diff] [blame]1854try:
Bruce Dawson039777ef2020-10-26 20:34:47[diff] [blame]1855while self.thread.is_alive():
rob8a4543f2016-01-20 00:43:59[diff] [blame]1856# The parameter to join is needed to keep the main thread responsive to
1857# signals. Without it, the program will not respond to interruptions.
1858 self.thread.join(1)
Kuan Huangc3a4cd1e2024-10-03 21:17:31[diff] [blame]1859if self.exc_info:
1860raise self.exc_info[1].with_traceback(self.exc_info[2])
Kuan Huangfeb4fa552024-08-21 20:40:28[diff] [blame]1861if self.quit_event.is_set():
1862raiseException('The DownloadJob was stopped.')
1863if self.is_multiple:
1864return self.results
1865else:
1866return self.results[None]
rob8a4543f2016-01-20 00:43:59[diff] [blame]1867except(KeyboardInterrupt,SystemExit):
Kuan Huang8b5e33dc2024-08-15 17:51:24[diff] [blame]1868 self.stop()
rob8a4543f2016-01-20 00:43:59[diff] [blame]1869raise
asvitkine@chromium.org53bb6342012-06-01 04:11:00[diff] [blame]1870
1871
Kuan Huangd608f1592024-08-15 23:31:35[diff] [blame]1872defBisect(archive_build,
szager@chromium.org60ac66e32011-07-18 16:08:25[diff] [blame]1873 try_args=(),
skobes21b5cdfb2016-03-21 23:13:02[diff] [blame]1874 evaluate=AskIsGoodBuild,
Kuan Huangd608f1592024-08-15 23:31:35[diff] [blame]1875 verify_range=False):
Sven Zheng8e079562024-05-10 20:11:06[diff] [blame]1876"""Runs a binary search on to determine the last known good revision.
szager@chromium.org60ac66e32011-07-18 16:08:25[diff] [blame]1877
Sven Zheng8e079562024-05-10 20:11:06[diff] [blame]1878 Args:
Kuan Huang49a2c6412024-08-12 22:12:33[diff] [blame]1879 archive_build: ArchiveBuild object initialized with user provided
1880 parameters.
Sven Zheng8e079562024-05-10 20:11:06[diff] [blame]1881 try_args: A tuple of arguments to pass to the test application.
Sven Zheng8e079562024-05-10 20:11:06[diff] [blame]1882 evaluate: A function which returns 'g' if the argument build is good,
1883 'b' if it's bad or 'u' if unknown.
1884 verify_range: If true, tests the first and last revisions in the range
1885 before proceeding with the bisect.
szager@google.comafe30662011-07-30 01:05:52[diff] [blame]1886
1887 Threading is used to fetch Chromium revisions in the background, speeding up
1888 the user's experience. For example, suppose the bounds of the search are
1889 good_rev=0, bad_rev=100. The first revision to be checked is 50. Depending on
1890 whether revision 50 is good or bad, the next revision to check will be either
1891 25 or 75. So, while revision 50 is being checked, the script will download
1892 revisions 25 and 75 in the background. Once the good/bad verdict on rev 50 is
1893 known:
szager@google.comafe30662011-07-30 01:05:52[diff] [blame]1894 - If rev 50 is good, the download of rev 25 is cancelled, and the next test
1895 is run on rev 75.
szager@google.comafe30662011-07-30 01:05:52[diff] [blame]1896 - If rev 50 is bad, the download of rev 75 is cancelled, and the next test
1897 is run on rev 25.
szager@chromium.org60ac66e32011-07-18 16:08:25[diff] [blame]1898 """
Sven Zheng8e079562024-05-10 20:11:06[diff] [blame]1899print('Downloading list of known revisions.', end=' ')
1900print('If the range is large, this can take several minutes...')
Kuan Huangd608f1592024-08-15 23:31:35[diff] [blame]1901ifnot archive_build.use_local_cache:
Raul Tambre57e09d62019-09-22 17:18:52[diff] [blame]1902print('(use --use-local-cache to cache and re-use the list of revisions)')
pdr@chromium.org28a3c122014-08-09 11:04:51[diff] [blame]1903else:
Raul Tambre57e09d62019-09-22 17:18:52[diff] [blame]1904print()
Kuan Huangc2fec792024-09-03 21:57:38[diff] [blame]1905 rev_list= archive_build.get_rev_list()
1906# Ensure rev_list[0] is good and rev_list[-1] is bad for easier process.
1907if archive_build.good_revision> archive_build.bad_revision:
1908 rev_list= rev_list[::-1]
Kuan Huangb790d5d2024-11-07 21:53:00[diff] [blame]1909ifIsVersionNumber(rev_list[0]):
1910 change_log_url_fn=GetReleaseChangeLogURL
1911else:
1912 change_log_url_fn=GetShortChangeLogURL
skobes21b5cdfb2016-03-21 23:13:02[diff] [blame]1913
1914if verify_range:
Kuan Huangc2fec792024-09-03 21:57:38[diff] [blame]1915 good_rev_fetch= archive_build.get_download_job(rev_list[0],
1916'good_rev_fetch').start()
1917 bad_rev_fetch= archive_build.get_download_job(rev_list[-1],
1918'bad_rev_fetch').start()
skobes21b5cdfb2016-03-21 23:13:02[diff] [blame]1919try:
Kuan Huangc2fec792024-09-03 21:57:38[diff] [blame]1920 good_download= good_rev_fetch.wait_for()
1921 answer=EvaluateRevision(archive_build, good_download, rev_list[0],
1922 try_args, evaluate)
1923if answer!='g':
1924print(f'Expecting revision {rev_list[0]} to be good but got {answer}. '
1925'Please make sure the --good is a good revision.')
1926raiseSystemExit
1927 bad_download= bad_rev_fetch.wait_for()
1928 answer=EvaluateRevision(archive_build, bad_download, rev_list[-1],
1929 try_args, evaluate)
1930if answer!='b':
1931print(f'Expecting revision {rev_list[-1]} to be bad but got {answer}. '
1932'Please make sure that the issue can be reproduced for --bad.')
1933raiseSystemExit
skobes21b5cdfb2016-03-21 23:13:02[diff] [blame]1934except(KeyboardInterrupt,SystemExit):
Raul Tambre57e09d62019-09-22 17:18:52[diff] [blame]1935print('Cleaning up...')
Kuan Huangc2fec792024-09-03 21:57:38[diff] [blame]1936returnNone,None
skobes21b5cdfb2016-03-21 23:13:02[diff] [blame]1937finally:
Kuan Huangc2fec792024-09-03 21:57:38[diff] [blame]1938 good_rev_fetch.stop()
1939 bad_rev_fetch.stop()
skobes21b5cdfb2016-03-21 23:13:02[diff] [blame]1940
Kuan Huangc2fec792024-09-03 21:57:38[diff] [blame]1941 prefetch={}
1942try:
1943while len(rev_list)>2:
Kuan Huang008965f92024-10-08 21:53:15[diff] [blame]1944# We are retaining the boundary elements in the rev_list, that should not
1945# count towards the steps when calculating the number the steps.
Kuan Huangc2fec792024-09-03 21:57:38[diff] [blame]1946print('You have %d revisions with about %d steps left.'%
Kuan Huang008965f92024-10-08 21:53:15[diff] [blame]1947(len(rev_list),((len(rev_list)-2).bit_length())))
Kuan Huangb790d5d2024-11-07 21:53:00[diff] [blame]1948 change_log_url=""
1949if(len(rev_list)-2).bit_length()<= STEPS_TO_SHOW_CHANGELOG_URL:
1950 change_log_url= f"({change_log_url_fn(rev_list[-1], rev_list[0])})"
1951print('Bisecting range [%s (bad), %s (good)]%s.'%(
1952 rev_list[-1], rev_list[0], change_log_url))
Kuan Huangc2fec792024-09-03 21:57:38[diff] [blame]1953# clean prefetch to keep only the valid fetches
1954for keyin list(prefetch.keys()):
1955if keynotin rev_list:
1956 prefetch.pop(key).stop()
1957# get next revision to evaluate from prefetch
1958if prefetch:
1959 fetch=None
1960# For any possible index in rev_list, abs(mid - index) < abs(mid -
1961# pivot). This will ensure that we can always get a fetch from prefetch.
1962 pivot= len(rev_list)
1963for revision, pfetchin prefetch.items():
1964 prefetch_pivot= rev_list.index(revision)
1965# Prefer the revision closer to the mid point.
1966 mid_point= len(rev_list)//2
1967if abs(mid_point- pivot)> abs(mid_point- prefetch_pivot):
1968 fetch= pfetch
1969 pivot= prefetch_pivot
1970 prefetch.pop(rev_list[pivot])
1971# or just the mid point
asvitkine@chromium.org53bb6342012-06-01 04:11:00[diff] [blame]1972else:
Kuan Huangc2fec792024-09-03 21:57:38[diff] [blame]1973 pivot= len(rev_list)//2
1974 fetch= archive_build.get_download_job(rev_list[pivot],'fetch').start()
Kuan Huangd1f29a12024-10-31 20:00:11[diff] [blame]1975
Kuan Huangc2fec792024-09-03 21:57:38[diff] [blame]1976try:
Kuan Huangc2fec792024-09-03 21:57:38[diff] [blame]1977 download= fetch.wait_for()
Kuan Huangd1f29a12024-10-31 20:00:11[diff] [blame]1978# prefetch left_pivot = len(rev_list[:pivot+1]) // 2
1979 left_revision= rev_list[(pivot+1)//2]
1980if left_revision!= rev_list[0]and left_revisionnotin prefetch:
1981 prefetch[left_revision]= archive_build.get_download_job(
1982 left_revision,'prefetch').start()
1983# prefetch right_pivot = len(rev_list[pivot:]) // 2
1984 right_revision= rev_list[(len(rev_list)+ pivot)//2]
1985if right_revision!= rev_list[-1]and right_revisionnotin prefetch:
1986 prefetch[right_revision]= archive_build.get_download_job(
1987 right_revision,'prefetch').start()
1988# evaluate the revision
Kuan Huangc2fec792024-09-03 21:57:38[diff] [blame]1989 answer=EvaluateRevision(archive_build, download, rev_list[pivot],
1990 try_args, evaluate)
1991# Ensure rev_list[0] is good and rev_list[-1] is bad after adjust.
1992if answer=='g':# good
1993 rev_list= rev_list[pivot:]
1994elif answer=='b':# bad
1995# Retain the pivot element within the list to act as a confirmed
1996# boundary for identifying bad revisions.
1997 rev_list= rev_list[:pivot+1]
1998elif answer=='u':# unknown
1999# Nuke the revision from the rev_list.
2000 rev_list.pop(pivot)
2001else:
2002assertFalse,'Unexpected return value from evaluate(): '+ answer
2003finally:
Kuan Huang8b5e33dc2024-08-15 17:51:24[diff] [blame]2004 fetch.stop()
Kuan Huangc2fec792024-09-03 21:57:38[diff] [blame]2005# end of `while len(rev_list) > 2`
2006finally:
2007for eachin prefetch.values():
2008 each.stop()
2009 prefetch.clear()
2010return sorted((rev_list[0], rev_list[-1]))
szager@chromium.org60ac66e32011-07-18 16:08:25[diff] [blame]2011
Kuan Huangc8dd77a2024-08-12 22:13:42[diff] [blame]2012defGetChromiumRevision(url, default=999999999):
vitalybuka@chromium.org801fb652012-07-20 20:13:50[diff] [blame]2013"""Returns the chromium revision read from given URL."""
Kuan Huangc8dd77a2024-08-12 22:13:42[diff] [blame]2014ifnot url:
2015return default
vitalybuka@chromium.org801fb652012-07-20 20:13:50[diff] [blame]2016try:
2017# Location of the latest build revision number
Sven Zheng8e079562024-05-10 20:11:06[diff] [blame]2018 latest_revision= urllib.request.urlopen(url).read()
alancutter@chromium.org5980b752014-07-02 00:34:40[diff] [blame]2019if latest_revision.isdigit():
2020return int(latest_revision)
Kuan Huangc8dd77a2024-08-12 22:13:42[diff] [blame]2021return default
qyearsley@chromium.org4df583c2014-07-31 17:11:55[diff] [blame]2022exceptException:
Raul Tambre57e09d62019-09-22 17:18:52[diff] [blame]2023print('Could not determine latest revision. This could be bad...')
Kuan Huangc8dd77a2024-08-12 22:13:42[diff] [blame]2024return default
vitalybuka@chromium.org801fb652012-07-20 20:13:50[diff] [blame]2025
Bruce Dawsoncd63ea22020-10-26 16:37:41[diff] [blame]2026
Sven Zheng8e079562024-05-10 20:11:06[diff] [blame]2027defFetchJsonFromURL(url):
2028"""Returns JSON data from the given URL"""
Sven Zheng8e079562024-05-10 20:11:06[diff] [blame]2029# Allow retry for 3 times for unexpected network error
2030for iin range(3):
Kuan Huangc93ac262024-09-19 18:53:17[diff] [blame]2031try:
Kuan Huang05058842024-10-24 21:22:26[diff] [blame]2032 data= urllib.request.urlopen(url).read()
2033# Remove the potential XSSI prefix from JSON output
2034 data= data.lstrip(b")]}',\n")
2035return json.loads(data)
Kuan Huangc93ac262024-09-19 18:53:17[diff] [blame]2036except urllib.request.HTTPErroras e:
2037print(f'urlopen {url} HTTPError: {e}')
2038except json.JSONDecodeErroras e:
2039print(f'urlopen {url} JSON decode error: {e}')
Sven Zheng8e079562024-05-10 20:11:06[diff] [blame]2040returnNone
Bruce Dawsoncd63ea22020-10-26 16:37:41[diff] [blame]2041
pshenoycd6bd682014-09-10 20:50:22[diff] [blame]2042defGetGitHashFromSVNRevision(svn_revision):
Sven Zheng8e079562024-05-10 20:11:06[diff] [blame]2043"""Returns GitHash from SVN Revision"""
pshenoycd6bd682014-09-10 20:50:22[diff] [blame]2044 crrev_url= CRREV_URL+ str(svn_revision)
Sven Zheng8e079562024-05-10 20:11:06[diff] [blame]2045 data=FetchJsonFromURL(crrev_url)
2046if dataand'git_sha'in data:
2047return data['git_sha']
2048returnNone
pshenoycd6bd682014-09-10 20:50:22[diff] [blame]2049
Kuan Huangb790d5d2024-11-07 21:53:00[diff] [blame]2050defGetShortChangeLogURL(rev1, rev2):
2051 min_rev, max_rev= sorted([rev1, rev2])
2052return SHORT_CHANGELOG_URL%(min_rev, max_rev)
2053
2054defGetChangeLogURL(rev1, rev2):
pshenoy9ce271f2014-09-02 22:14:05[diff] [blame]2055"""Prints the changelog URL."""
Kuan Huangb790d5d2024-11-07 21:53:00[diff] [blame]2056 min_rev, max_rev= sorted([rev1, rev2])
2057return CHANGELOG_URL%(GetGitHashFromSVNRevision(min_rev),
2058GetGitHashFromSVNRevision(max_rev))
2059
2060defGetReleaseChangeLogURL(version1, version2):
2061"""Prints the changelog URL."""
2062 min_ver, max_ver= sorted([version1, version2])
2063return RELEASE_CHANGELOG_URL%(min_ver, max_ver)
Raul Tambre57e09d62019-09-22 17:18:52[diff] [blame]2064
pshenoy9ce271f2014-09-02 22:14:05[diff] [blame]2065
Sven Zheng8e079562024-05-10 20:11:06[diff] [blame]2066defIsVersionNumber(revision):
2067"""Checks if provided revision is version_number"""
Kuan Huang6f7f57942024-12-05 19:26:30[diff] [blame]2068if isinstance(revision,ChromiumVersion):
Kuan Huangb790d5d2024-11-07 21:53:00[diff] [blame]2069returnTrue
2070ifnot isinstance(revision, str):
2071returnFalse
Sven Zheng8e079562024-05-10 20:11:06[diff] [blame]2072return re.match(r'^\d+\.\d+\.\d+\.\d+$', revision)isnotNone
vitalybuka@chromium.org801fb652012-07-20 20:13:50[diff] [blame]2073
Sven Zheng8e079562024-05-10 20:11:06[diff] [blame]2074
Kuan Huang008965f92024-10-08 21:53:15[diff] [blame]2075defGetRevisionFromSourceTag(tag):
2076"""Return Base Commit Position based on the commit message of a version tag"""
2077# Searching from commit message for
2078# Cr-Branched-From: (?P<githash>\w+)-refs/heads/master@{#857950}
2079# Cr-Commit-Position: refs/heads/main@{#992738}
2080 revision_regex= re.compile(r'refs/heads/\w+@{#(\d+)}$')
2081 source_url= SOURCE_TAG_URL% str(tag)
2082 data=FetchJsonFromURL(source_url)
2083 match= revision_regex.search(data.get('message',''))
2084ifnot match:
2085# The commit message for version tag before M116 doesn't contains
2086# Cr-Branched-From and Cr-Commit-Position message lines. However they might
2087# exists in the parent commit.
Kuan Huang05058842024-10-24 21:22:26[diff] [blame]2088 source_url= SOURCE_TAG_URL%(str(tag)+'^')
Kuan Huang008965f92024-10-08 21:53:15[diff] [blame]2089 data=FetchJsonFromURL(source_url)
2090 match= revision_regex.search(data.get('message',''))
2091if match:
2092return int(match.group(1))
2093
2094
Sven Zheng8e079562024-05-10 20:11:06[diff] [blame]2095defGetRevisionFromVersion(version):
2096"""Returns Base Commit Position from a version number"""
2097 chromiumdash_url= VERSION_INFO_URL% str(version)
2098 data=FetchJsonFromURL(chromiumdash_url)
Kuan Huangc93ac262024-09-19 18:53:17[diff] [blame]2099ifnot data:
2100# Not every tag is released as a version for users. Some "versions" from the
2101# release builder might not exist in the VERSION_INFO_URL API. With the
2102# `MaybeSwitchBuildType` functionality, users might get such unreleased
2103# versions and try to use them with the -o flag, resulting in a 404 error.
2104# Meanwhile, this method retrieves the `chromium_main_branch_position`,
2105# which should be the same for all 127.0.6533.* versions, so we can get the
2106# branch position from 127.0.6533.0 instead.
2107 chromiumdash_url= VERSION_INFO_URL% re.sub(r'\d+$','0', str(version))
2108 data=FetchJsonFromURL(chromiumdash_url)
Kuan Huang008965f92024-10-08 21:53:15[diff] [blame]2109if dataand data.get('chromium_main_branch_position'):
Sven Zheng8e079562024-05-10 20:11:06[diff] [blame]2110return data['chromium_main_branch_position']
Kuan Huang008965f92024-10-08 21:53:15[diff] [blame]2111 revision_from_source_tag=GetRevisionFromSourceTag(version)
2112if revision_from_source_tag:
2113return revision_from_source_tag
2114raiseBisectException(
2115 f'Can not find revision for {version} from chromiumdash and source')
Sven Zheng8e079562024-05-10 20:11:06[diff] [blame]2116
2117
Joel Hockey60c4f5b2024-07-15 22:03:08[diff] [blame]2118defGetRevisionFromMilestone(milestone):
2119"""Get revision (e.g. 782793) from milestone such as 85."""
Kuan Huangc93ac262024-09-19 18:53:17[diff] [blame]2120 response= urllib.request.urlopen(MILESTONES_URL% milestone)
Joel Hockey60c4f5b2024-07-15 22:03:08[diff] [blame]2121 milestones= json.loads(response.read())
2122for min milestones:
Kuan Huang70f5efe62024-11-07 23:26:35[diff] [blame]2123if m['milestone']== milestoneand m.get('chromium_main_branch_position'):
Joel Hockey60c4f5b2024-07-15 22:03:08[diff] [blame]2124return m['chromium_main_branch_position']
Kuan Huang008965f92024-10-08 21:53:15[diff] [blame]2125raiseBisectException(f'Can not find revision for milestone {milestone}')
Joel Hockey60c4f5b2024-07-15 22:03:08[diff] [blame]2126
2127
2128defGetRevision(revision):
Sven Zheng5a5a3142024-07-18 21:44:20[diff] [blame]2129"""Get revision from either milestone M85, full version 85.0.4183.0,
2130 or a commit position.
2131 """
Joel Hockey60c4f5b2024-07-15 22:03:08[diff] [blame]2132if type(revision)== type(0):
2133return revision
2134ifIsVersionNumber(revision):
2135returnGetRevisionFromVersion(revision)
2136elif revision[:1].upper()=='M'and revision[1:].isdigit():
2137returnGetRevisionFromMilestone(int(revision[1:]))
Sven Zheng5a5a3142024-07-18 21:44:20[diff] [blame]2138# By default, we assume it's a commit position.
2139return int(revision)
Joel Hockey60c4f5b2024-07-15 22:03:08[diff] [blame]2140
2141
Sven Zheng8e079562024-05-10 20:11:06[diff] [blame]2142defCheckDepotToolsInPath():
2143 delimiter=';'if sys.platform.startswith('win')else':'
2144 path_list= os.environ['PATH'].split(delimiter)
2145for pathin path_list:
2146if path.rstrip(os.path.sep).endswith('depot_tools'):
2147return path
2148returnNone
2149
2150
2151defSetupEnvironment(options):
2152global is_verbose
Kuan Huangd86de1a2024-07-10 18:45:40[diff] [blame]2153global GSUTILS_PATH
Sven Zheng8e079562024-05-10 20:11:06[diff] [blame]2154
2155# Release and Official builds bisect requires "gsutil" inorder to
2156# List and Download binaries.
2157# Check if depot_tools is installed and path is set.
2158 gsutil_path=CheckDepotToolsInPath()
Kuan Huang37d0daa2024-09-19 18:03:08[diff] [blame]2159if(options.build_typein('release','official')andnot gsutil_path):
Sven Zheng8e079562024-05-10 20:11:06[diff] [blame]2160raiseBisectException(
2161'Looks like depot_tools is not installed.\n'
2162'Follow the instructions in this document '
2163'http://dev.chromium.org/developers/how-tos/install-depot-tools '
2164'to install depot_tools and then try again.')
Kuan Huangd86de1a2024-07-10 18:45:40[diff] [blame]2165elif gsutil_path:
2166 GSUTILS_PATH= os.path.join(gsutil_path,'gsutil.py')
Sven Zheng8e079562024-05-10 20:11:06[diff] [blame]2167
2168# Catapult repo is required for Android bisect,
2169# Update Catapult repo if it exists otherwise checkout repo.
Peter Wen9156ea402024-07-04 15:44:54[diff] [blame]2170if options.archive.startswith('android-'):
Sven Zheng8e079562024-05-10 20:11:06[diff] [blame]2171SetupAndroidEnvironment()
2172
2173# Set up verbose logging if requested.
2174if options.verbose:
2175 is_verbose=True
2176
2177
2178defSetupAndroidEnvironment():
2179
2180defSetupCatapult():
2181print('Setting up Catapult in %s.'% CATAPULT_DIR)
2182print('Set the environment var CATAPULT_DIR to override '
2183'Catapult directory.')
2184if(os.path.exists(CATAPULT_DIR)):
2185print('Updating Catapult...\n')
2186 process= subprocess.Popen(args=['git','pull','--rebase'],
2187 cwd=CATAPULT_DIR)
2188 exit_code= process.wait()
2189if exit_code!=0:
2190raiseBisectException('Android bisect requires Catapult repo checkout. '
2191'Attempt to update Catapult failed.')
2192else:
2193print('Downloading Catapult...\n')
2194 process= subprocess.Popen(
2195 args=['git','clone', CATAPULT_REPO, CATAPULT_DIR])
2196 exit_code= process.wait()
2197if exit_code!=0:
2198raiseBisectException('Android bisect requires Catapult repo checkout. '
2199'Attempt to download Catapult failed.')
2200
2201SetupCatapult()
2202 sys.path.append(DEVIL_PATH)
2203from devil.android.sdkimport version_codes
2204
2205# Modules required from devil
2206 devil_imports={
2207'devil_env':'devil.devil_env',
2208'device_errors':'devil.android.device_errors',
2209'device_utils':'devil.android.device_utils',
2210'flag_changer':'devil.android.flag_changer',
2211'chrome':'devil.android.constants.chrome',
2212'adb_wrapper':'devil.android.sdk.adb_wrapper',
2213'intent':'devil.android.sdk.intent',
2214'version_codes':'devil.android.sdk.version_codes',
2215'run_tests_helper':'devil.utils.run_tests_helper'
2216}
2217# Dynamically import devil modules required for android bisect.
2218for i, jin devil_imports.items():
2219 globals()[i]= importlib.import_module(j)
2220
2221print('Done setting up Catapult.\n')
2222
2223
2224defInitializeAndroidDevice(device_id, apk, chrome_flags):
2225"""Initializes device and sets chrome flags."""
2226 devil_env.config.Initialize()
2227 run_tests_helper.SetLogLevel(0)
2228 device= device_utils.DeviceUtils.HealthyDevices(device_arg=device_id)[0]
2229if chrome_flags:
2230 flags= flag_changer.FlagChanger(device,
2231 chrome.PACKAGE_INFO[apk].cmdline_file)
2232 flags.AddFlags(chrome_flags)
2233return device
2234
2235
Peter Wenb4ba30482024-07-08 19:10:40[diff] [blame]2236defInstallOnAndroid(device, apk_path):
Sven Zheng8e079562024-05-10 20:11:06[diff] [blame]2237"""Installs the chromium build on a given device."""
2238print('Installing %s on android device...'% apk_path)
2239 device.Install(apk_path)
2240
2241
2242defLaunchOnAndroid(device, apk):
2243"""Launches the chromium build on a given device."""
2244if'webview'in apk:
2245return
2246
2247print('Launching chrome on android device...')
2248 device.StartActivity(intent.Intent(action='android.intent.action.MAIN',
2249 activity=chrome.PACKAGE_INFO[apk].activity,
2250 package=chrome.PACKAGE_INFO[apk].package),
2251 blocking=True,
2252 force_stop=True)
2253
2254
2255def_CreateCommandLineParser():
2256"""Creates a parser with bisect options.
2257
2258 Returns:
2259 An instance of argparse.ArgumentParser.
2260 """
Kuan Huang37d0daa2024-09-19 18:03:08[diff] [blame]2261 description="""
2262Performs binary search on the chrome binaries to find a minimal range of \
Sven Zheng8e079562024-05-10 20:11:06[diff] [blame]2263revisions where a behavior change happened.
Kuan Huang37d0daa2024-09-19 18:03:08[diff] [blame]2264The behaviors are described as "good" and "bad". It is NOT assumed that the \
Sven Zheng8e079562024-05-10 20:11:06[diff] [blame]2265behavior of the later revision is the bad one.
2266
2267Revision numbers should use:
2268 a) Release versions: (e.g. 1.0.1000.0) for release builds. (-r)
2269 b) Commit Positions: (e.g. 123456) for chromium builds, from trunk.
Kuan Huang37d0daa2024-09-19 18:03:08[diff] [blame]2270 Use chromium_main_branch_position from \
2271https://chromiumdash.appspot.com/fetch_version?version=<chrome_version>
2272 Please Note: Chrome's about: build number and chromiumdash branch \
2273revision are incorrect, they are from branches.
Sven Zheng8e079562024-05-10 20:11:06[diff] [blame]2274
2275Tip: add "-- --no-first-run" to bypass the first run prompts.
2276"""
2277
Kuan Huang37d0daa2024-09-19 18:03:08[diff] [blame]2278 parser= argparse.ArgumentParser(
2279 formatter_class=argparse.RawTextHelpFormatter, description=description)
evan@chromium.org1a45d222009-09-19 01:58:57[diff] [blame]2280# Strangely, the default help output doesn't include the choice list.
Kuan Huang147e34e2024-07-10 23:17:18[diff] [blame]2281 choices= sorted(
2282 set(archfor buildin PATH_CONTEXTfor archin PATH_CONTEXT[build]))
Kuan Huang37d0daa2024-09-19 18:03:08[diff] [blame]2283 parser.add_argument(
2284'-a',
2285'--archive',
2286 choices=choices,
2287 metavar='ARCHIVE',
Kuan Huang37d0daa2024-09-19 18:03:08[diff] [blame]2288 help='The buildbot platform to bisect {%s}.'%','.join(choices),
2289)
pshenoy@chromium.orgb3b20512013-08-26 18:51:04[diff] [blame]2290
Kuan Huang37d0daa2024-09-19 18:03:08[diff] [blame]2291 build_type_group= parser.add_mutually_exclusive_group()
2292 build_type_group.add_argument(
Kuan Huang9a1ad2b2024-09-30 23:16:31[diff] [blame]2293'-s',
2294 dest='build_type',
2295 action='store_const',
2296 const='snapshot',
2297 default='snapshot',
2298 help='Bisect across Chromium snapshot archives (default).',
2299)
2300 build_type_group.add_argument(
Kuan Huang37d0daa2024-09-19 18:03:08[diff] [blame]2301'-r',
2302 dest='build_type',
2303 action='store_const',
2304 const='release',
Kuan Huang37d0daa2024-09-19 18:03:08[diff] [blame]2305 help='Bisect across release Chrome builds (internal only) instead of '
2306'Chromium archives.',
2307)
2308 build_type_group.add_argument(
2309'-o',
2310 dest='build_type',
2311 action='store_const',
2312 const='official',
2313 help='Bisect across continuous perf official Chrome builds (internal '
2314'only) instead of Chromium archives.',
2315)
2316 build_type_group.add_argument(
Kuan Huang2dd3a33f2024-12-13 17:52:52[diff] [blame]2317'-cft',
2318'--cft',
2319 dest='build_type',
2320 action='store_const',
2321 const='cft',
2322 help='Bisect across Chrome for Testing (publicly accessible) archives.',
2323)
2324 build_type_group.add_argument(
Kuan Huang37d0daa2024-09-19 18:03:08[diff] [blame]2325'--asan',
2326 dest='build_type',
2327 action='store_const',
2328 const='asan',
2329 help='Allow the script to bisect ASAN builds',
2330)
2331
2332 parser.add_argument(
2333'-g',
2334'--good',
2335 type=str,
2336 metavar='GOOD_REVISION',
2337 required=True,
2338 help='A good revision to start bisection. May be earlier or later than '
2339'the bad revision.',
2340)
2341 parser.add_argument(
2342'-b',
2343'--bad',
2344 type=str,
2345 metavar='BAD_REVISION',
2346 help='A bad revision to start bisection. May be earlier or later than '
2347'the good revision. Default is HEAD.',
2348)
2349 parser.add_argument(
2350'-p',
2351'--profile',
2352'--user-data-dir',
2353 type=str,
2354 default='%t/profile',
Max Ihlenfeldtb7e35972025-04-21 17:53:51[diff] [blame]2355 help='Profile to use; this will not reset every run. Defaults to a new, '
2356'clean profile for every run.',
Kuan Huang37d0daa2024-09-19 18:03:08[diff] [blame]2357)
2358 parser.add_argument(
2359'-t',
2360'--times',
2361 type=int,
2362 default=1,
2363 help='Number of times to run each build before asking if it\'s good or '
2364'bad. Temporary profiles are reused.',
2365)
2366 parser.add_argument(
2367'--chromedriver',
2368 action='store_true',
2369 help='Also download ChromeDriver. Use %%d in --command to reference the '
2370'ChromeDriver path in the command line.',
2371)
2372 parser.add_argument(
2373'-c',
2374'--command',
2375 type=str,
2376 default=r'%p %a',
2377 help='Command to execute. %%p and %%a refer to Chrome executable and '
2378'specified extra arguments respectively. Use %%t for tempdir where '
2379'Chrome extracted. Use %%d for chromedriver path when --chromedriver '
2380'enabled. Defaults to "%%p %%a". Note that any extra paths specified '
2381'should be absolute.',
2382)
2383 parser.add_argument(
2384'-v',
2385'--verbose',
2386 action='store_true',
2387 help='Log more verbose information.',
2388)
2389 parser.add_argument(
2390'--not-interactive',
2391 action='store_true',
2392 default=False,
2393 help='Use command exit code to tell good/bad revision.',
2394)
2395
2396 local_cache_group= parser.add_mutually_exclusive_group()
2397 local_cache_group.add_argument(
2398'--use-local-cache',
2399 dest='use_local_cache',
2400 action='store_true',
2401 default=True,
2402 help='Use a local file in the current directory to cache a list of known '
2403'revisions to speed up the initialization of this script.',
2404)
2405 local_cache_group.add_argument(
2406'--no-local-cache',
2407 dest='use_local_cache',
2408 action='store_false',
2409 help='Do not use local file for known revisions.',
2410)
2411
2412 parser.add_argument(
2413'--verify-range',
2414 dest='verify_range',
2415 action='store_true',
2416 default=False,
2417 help='Test the first and last revisions in the range before proceeding '
2418'with the bisect.',
2419)
Kuan Huangbeba95a52025-01-06 23:10:10[diff] [blame]2420 apk_choices= sorted(
2421 set().union(CHROME_APK_FILENAMES, CHROME_MODERN_APK_FILENAMES,
2422 MONOCHROME_APK_FILENAMES, WEBVIEW_APK_FILENAMES,
2423 TRICHROME_APK_FILENAMES, TRICHROME64_APK_FILENAMES))
Kuan Huang37d0daa2024-09-19 18:03:08[diff] [blame]2424 parser.add_argument(
2425'--apk',
2426 choices=apk_choices,
2427 dest='apk',
Kuan Huang908c55212025-01-06 23:09:01[diff] [blame]2428# default='chromium', when using android archives
Kuan Huang37d0daa2024-09-19 18:03:08[diff] [blame]2429 metavar='{chromium,chrome_dev,android_webview...}',
Kuan Huang908c55212025-01-06 23:09:01[diff] [blame]2430 help=(f'Apk you want to bisect {{{",".join(apk_choices)}}}. '
2431'(Default: chromium/chrome)'),
Kuan Huang37d0daa2024-09-19 18:03:08[diff] [blame]2432)
2433 parser.add_argument(
2434'--ipa',
2435 dest='ipa',
Kuan Huang908c55212025-01-06 23:09:01[diff] [blame]2436# default='canary.ipa', when using ios archives
Kuan Huang37d0daa2024-09-19 18:03:08[diff] [blame]2437 metavar='{canary,beta,stable...}',
Kuan Huang908c55212025-01-06 23:09:01[diff] [blame]2438 help='ipa you want to bisect. (Default: canary)',
Kuan Huang37d0daa2024-09-19 18:03:08[diff] [blame]2439)
2440 parser.add_argument(
2441'--signed',
2442 dest='signed',
2443 action='store_true',
2444 default=False,
2445 help='Using signed binary for release build. Only support iOS and '
2446'Android platforms.',
2447)
2448 parser.add_argument(
2449'-d',
2450'--device-id',
2451 dest='device_id',
2452 type=str,
2453 help='Device to run the bisect on.',
2454)
2455 parser.add_argument(
2456'--update-script',
Kuan Huang342fec52024-10-31 18:00:44[diff] [blame]2457 action=UpdateScriptAction,
2458 nargs=0,
Kuan Huang37d0daa2024-09-19 18:03:08[diff] [blame]2459 help='Update this script to the latest.',
2460)
2461 parser.add_argument(
2462'args',
2463 nargs='*',
2464 metavar='chromium-option',
2465 help='Additional chromium options passed to chromium process.',
2466)
Sven Zheng8e079562024-05-10 20:11:06[diff] [blame]2467return parser
mmoss@chromium.org7ad66a72009-09-04 17:52:33[diff] [blame]2468
Sven Zheng8e079562024-05-10 20:11:06[diff] [blame]2469
Kuan Huang908c55212025-01-06 23:09:01[diff] [blame]2470def_DetectArchive(opts=None):
Kuan Huang0d24cfd92024-07-12 16:50:30[diff] [blame]2471"""Detect the buildbot archive to use based on local environment."""
Kuan Huang908c55212025-01-06 23:09:01[diff] [blame]2472if opts:
2473if opts.apk:
2474return'android-arm64'
2475elif opts.ipa:
2476return'ios-simulator'
2477
Kuan Huang0d24cfd92024-07-12 16:50:30[diff] [blame]2478 os_name=None
2479 plat= sys.platform
2480if plat.startswith('linux'):
2481 os_name='linux'
2482elif platin('win32','cygwin'):
2483 os_name='win'
2484elif plat=='darwin':
2485 os_name='mac'
2486
2487 arch=None
2488 machine= platform.machine().lower()
2489if machine.startswith(('arm','aarch')):
2490 arch='arm'
2491elif machinein('amd64','x86_64'):
2492 arch='x64'
2493elif machinein('i386','i686','i86pc','x86'):
2494 arch='x86'
2495
2496return PLATFORM_ARCH_TO_ARCHIVE_MAPPING.get((os_name, arch),None)
2497
2498
Sven Zheng8e079562024-05-10 20:11:06[diff] [blame]2499defParseCommandLine(args=None):
2500"""Parses the command line for bisect options."""
Sven Zheng8e079562024-05-10 20:11:06[diff] [blame]2501 parser=_CreateCommandLineParser()
Kuan Huang37d0daa2024-09-19 18:03:08[diff] [blame]2502 opts= parser.parse_args(args)
Sven Zhenge3441db2024-05-13 19:59:32[diff] [blame]2503
mmoss@chromium.org7ad66a72009-09-04 17:52:33[diff] [blame]2504if opts.archiveisNone:
Kuan Huang908c55212025-01-06 23:09:01[diff] [blame]2505 archive=_DetectArchive(opts)
Kuan Huang0d24cfd92024-07-12 16:50:30[diff] [blame]2506if archive:
2507print('The buildbot archive (-a/--archive) detected as:', archive)
2508 opts.archive= archive
2509else:
Kuan Huang37d0daa2024-09-19 18:03:08[diff] [blame]2510 parser.error('Error: Missing required parameter: --archive')
2511
2512if opts.archivenotin PATH_CONTEXT[opts.build_type]:
Kuan Huang9a1ad2b2024-09-30 23:16:31[diff] [blame]2513 supported_build_types=[
2514"%s(%s)"%(b,BuildTypeToCommandLineArgument(b, omit_default=False))
2515for b, contextin PATH_CONTEXT.items()if opts.archivein context
2516]
2517 parser.error(f'Bisecting on {opts.build_type} is only supported on these '
2518'platforms (-a/--archive): '
2519 f'{{{",".join(PATH_CONTEXT[opts.build_type].keys())}}}\n'
2520 f'To bisect for {opts.archive}, please choose from '
2521 f'{", ".join(supported_build_types)}')
mmoss@chromium.org7ad66a72009-09-04 17:52:33[diff] [blame]2522
Kuan Huang908c55212025-01-06 23:09:01[diff] [blame]2523 all_archives= sorted(
2524 set(archfor buildin PATH_CONTEXTfor archin PATH_CONTEXT[build]))
2525 android_archives=[xfor xin all_archivesif x.startswith('android-')]
2526 ios_archives=[xfor xin all_archivesif x.startswith('ios')]
Sven Zheng8e079562024-05-10 20:11:06[diff] [blame]2527
Kuan Huang908c55212025-01-06 23:09:01[diff] [blame]2528# Set default apk/ipa for mobile platforms.
2529if opts.archivein android_archivesandnot opts.apk:
2530 opts.apk='chromium'if opts.build_type=='snapshot'else'chrome'
2531elif opts.archivein ios_archivesandnot opts.ipa:
2532 opts.ipa='canary'
2533# Or raise error if apk/ipa is set for non-mobile platforms.
2534if opts.apkand opts.archivenotin android_archives:
2535 parser.error('--apk is only supported for Android platform (-a/--archive): '
2536 f'{{{",".join(android_archives)}}}')
2537elif opts.ipaand opts.archivenotin ios_archives:
2538 parser.error('--ipa is only supported for iOS platform (-a/--archive): '
2539 f'{{{",".join(ios_archives)}}}')
2540
2541if opts.signedand opts.archivenotin(android_archives+ ios_archives):
2542 parser.error('--signed is only supported for Android and iOS platform '
2543'(-a/--archive): '
2544 f'{{{",".join(android_archives+ios_archives)}}}')
2545elif opts.signedandnot opts.build_type=='release':
Kuan Huang37d0daa2024-09-19 18:03:08[diff] [blame]2546 parser.error('--signed is only supported for release bisection.')
Sven Zheng8e079562024-05-10 20:11:06[diff] [blame]2547
Kuan Huang37d0daa2024-09-19 18:03:08[diff] [blame]2548if opts.build_type=='official':
Sven Zheng8e079562024-05-10 20:11:06[diff] [blame]2549print('Bisecting on continuous Chrome builds. If you would like '
2550'to bisect on release builds, try running with -r option '
2551'instead. Previous -o options is currently changed to -r option '
2552'as continous official builds were added for bisect')
cmumford@chromium.org011886692014-08-01 21:00:21[diff] [blame]2553
Kuan Huang95f6df072024-09-12 20:56:42[diff] [blame]2554ifnot opts.good:
Kuan Huang37d0daa2024-09-19 18:03:08[diff] [blame]2555 parser.error('Please specify a good version.')
Kuan Huang95f6df072024-09-12 20:56:42[diff] [blame]2556
Kuan Huang37d0daa2024-09-19 18:03:08[diff] [blame]2557if opts.build_type=='release':
Kuan Huang95f6df072024-09-12 20:56:42[diff] [blame]2558ifnot opts.bad:
Kuan Huang37d0daa2024-09-19 18:03:08[diff] [blame]2559 parser.error('Please specify a bad version.')
Kuan Huang95f6df072024-09-12 20:56:42[diff] [blame]2560ifnotIsVersionNumber(opts.good)ornotIsVersionNumber(opts.bad):
Kuan Huang37d0daa2024-09-19 18:03:08[diff] [blame]2561 parser.error('For release, you can only use chrome version to bisect.')
Kuan Huang95f6df072024-09-12 20:56:42[diff] [blame]2562if opts.archive.startswith('android-'):
2563# Channel builds have _ in their names, e.g. chrome_canary or chrome_beta.
2564# Non-channel builds don't, e.g. chrome or chromium. Make this a warning
2565# instead of an error since older archives might have non-channel builds.
2566if'_'notin opts.apk:
2567print('WARNING: Android release typically only uploads channel builds, '
2568 f'so you will often see "Found 0 builds" with --apk={opts.apk}'
2569'. Switch to using --apk=chrome_stable or one of the other '
2570'channels if you see `[Bisect Exception]: Could not found enough'
2571'revisions for Android chrome release channel.\n')
2572
Trung Nguyend391d052025-06-04 09:35:09[diff] [blame]2573if opts.apkand'webview'in opts.apk:
2574if opts.archive=='android-arm64-high'and opts.build_type!='official':
2575 parser.error(
2576'Bisecting WebView for android-arm64-high, please choose official '
2577'builds (-o)')
2578
Kuan Huang95f6df072024-09-12 20:56:42[diff] [blame]2579if opts.times<1:
Kuan Huang37d0daa2024-09-19 18:03:08[diff] [blame]2580 parser.error(f'Number of times to run ({opts.times}) must be greater than '
2581'or equal to 1.')
Kuan Huang95f6df072024-09-12 20:56:42[diff] [blame]2582
Kuan Huang37d0daa2024-09-19 18:03:08[diff] [blame]2583return opts
karen@chromium.org4c6fec6b2013-09-17 17:44:08[diff] [blame]2584
mikecasea8cd284c2014-12-02 21:30:58[diff] [blame]2585
Kuan Huang9a1ad2b2024-09-30 23:16:31[diff] [blame]2586defBuildTypeToCommandLineArgument(build_type, omit_default=True):
2587"""Convert the build_type back to command line argument."""
2588if build_type=='release':
2589return'-r'
2590elif build_type=='official':
2591return'-o'
2592elif build_type=='snapshot':
2593ifnot omit_default:
2594return'-s'
2595else:
2596return''
2597elif build_type=='asan':
2598return'--asan'
2599else:
2600raiseValueError(f'Unknown build type: {build_type}')
2601
2602
Kuan Huang95f6df072024-09-12 20:56:42[diff] [blame]2603defGenerateCommandLine(opts):
2604"""Generate a command line for bisect options.
2605
2606 Args:
2607 opts: The new bisect options to generate the command line for.
2608
2609 This generates prompts for the suggestion to use another build type
2610 (MaybeSwitchBuildType). Not all options are supported when generating the new
2611 command, however the remaining unsupported args would be copied from sys.argv.
2612 """
2613# Using a parser to remove the arguments (key and value) that we are going to
2614# generate based on the opts and appending the remaining args as is in command
2615# line.
2616 parser_to_remove_known_options= argparse.ArgumentParser()
2617 parser_to_remove_known_options.add_argument('-a','--archive','-g','--good',
2618'-b','--bad')
2619 parser_to_remove_known_options.add_argument('-r',
2620'-o',
2621'--signed',
2622 action='store_true')
2623 _, remaining_args= parser_to_remove_known_options.parse_known_args()
2624 args=[]
Kuan Huang9a1ad2b2024-09-30 23:16:31[diff] [blame]2625 args.append(BuildTypeToCommandLineArgument(opts.build_type))
Kuan Huang95f6df072024-09-12 20:56:42[diff] [blame]2626if opts.archive:
2627 args.extend(['-a', opts.archive])
2628if opts.signed:
2629 args.append('--signed')
2630if opts.good:
2631 args.extend(['-g', opts.good])
2632if opts.bad:
2633 args.extend(['-b', opts.bad])
2634if opts.verify_range:
2635 args.append('--verify-range')
2636return['./%s'% os.path.relpath(__file__)]+ args+ remaining_args
2637
2638
2639defMaybeSwitchBuildType(opts, good, bad):
2640"""Generate and print suggestions to use official build to bisect for a more
2641 precise range when possible."""
Kuan Huang37d0daa2024-09-19 18:03:08[diff] [blame]2642if opts.build_type!='release':
Kuan Huang95f6df072024-09-12 20:56:42[diff] [blame]2643return
2644if opts.archivenotin PATH_CONTEXT['official']:
2645return
2646 new_opts= copy.deepcopy(opts)
Kuan Huang95f6df072024-09-12 20:56:42[diff] [blame]2647 new_opts.signed=False# --signed is only supported by release builds
Kuan Huang37d0daa2024-09-19 18:03:08[diff] [blame]2648 new_opts.build_type='official'
Kuan Huang95f6df072024-09-12 20:56:42[diff] [blame]2649 new_opts.verify_range=True# always verify_range when switching the build
Kuan Huang6f7f57942024-12-05 19:26:30[diff] [blame]2650 new_opts.good= str(good)# good could be ChromiumVersion
2651 new_opts.bad= str(bad)# bad could be ChromiumVersion
Kuan Huang95f6df072024-09-12 20:56:42[diff] [blame]2652 rev_list=None
2653if opts.use_local_cache:
2654print('Checking available official builds (-o) for %s.'% new_opts.archive)
2655 archive_build= create_archive_build(new_opts)
2656try:
2657 rev_list= archive_build.get_rev_list()
2658exceptBisectExceptionas e:
2659# We don't have enough builds from official builder, skip suggesting.
2660print("But we don't have more builds from official builder.")
2661return
2662if len(rev_list)<=2:
2663print("But we don't have more builds from official builder.")
2664return
2665if rev_list:
2666print(
2667"There are %d revisions between %s and %s from the continuous official "
2668"build (-o). You could try to get a more precise culprit range using "
2669"the following command:"%(len(rev_list),*sorted([good, bad])))
2670else:
2671print(
2672"You could try to get a more precise culprit range with the continuous "
2673"official build (-o) using the following command:")
2674 command_line=GenerateCommandLine(new_opts)
Kuan Huang94c4a2d12024-11-21 21:46:09[diff] [blame]2675print(join_args(command_line))
Kuan Huang95f6df072024-09-12 20:56:42[diff] [blame]2676return command_line
2677
2678
Kuan Huang342fec52024-10-31 18:00:44[diff] [blame]2679classUpdateScriptAction(argparse.Action):
2680def __call__(self, parser, namespace, values, option_string=None):
2681 script_path= sys.argv[0]
2682 script_content= str(
2683 base64.b64decode(
2684 urllib.request.urlopen(
2685"https://chromium.googlesource.com/chromium/src/+/HEAD/"
2686"tools/bisect-builds.py?format=TEXT").read()),'utf-8')
2687with open(script_path,"w")as f:
2688 f.write(script_content)
2689print("Update successful!")
2690 exit(0)
Sven Zhenge3441db2024-05-13 19:59:32[diff] [blame]2691
2692
Sven Zheng8e079562024-05-10 20:11:06[diff] [blame]2693def main():
Kuan Huang37d0daa2024-09-19 18:03:08[diff] [blame]2694 opts=ParseCommandLine()
Sven Zheng8e079562024-05-10 20:11:06[diff] [blame]2695
Sven Zheng8e079562024-05-10 20:11:06[diff] [blame]2696try:
2697SetupEnvironment(opts)
2698exceptBisectExceptionas e:
2699print(e)
2700 sys.exit(1)
2701
Kuan Huangc63de402024-08-12 22:15:27[diff] [blame]2702# Create the AbstractBuild object.
Kuan Huangd608f1592024-08-15 23:31:35[diff] [blame]2703 archive_build= create_archive_build(opts)
Sven Zheng8e079562024-05-10 20:11:06[diff] [blame]2704
skobes21b5cdfb2016-03-21 23:13:02[diff] [blame]2705if opts.not_interactive:
2706 evaluator=DidCommandSucceed
Kuan Huang37d0daa2024-09-19 18:03:08[diff] [blame]2707elif opts.build_type=='asan':
cmumford@chromium.org011886692014-08-01 21:00:21[diff] [blame]2708 evaluator=IsGoodASANBuild
2709else:
2710 evaluator=AskIsGoodBuild
2711
pshenoy@chromium.org2e0f2672014-08-13 20:32:58[diff] [blame]2712# Save these revision numbers to compare when showing the changelog URL
2713# after the bisect.
Kuan Huang0cb06a402024-08-15 23:31:49[diff] [blame]2714 good_rev= archive_build.good_revision
2715 bad_rev= archive_build.bad_revision
pshenoy@chromium.org2e0f2672014-08-13 20:32:58[diff] [blame]2716
Kuan Huang37d0daa2024-09-19 18:03:08[diff] [blame]2717 min_chromium_rev, max_chromium_rev=Bisect(archive_build, opts.args,
2718 evaluator, opts.verify_range)
Kuan Huangc2fec792024-09-03 21:57:38[diff] [blame]2719if min_chromium_revisNoneor max_chromium_revisNone:
2720return
Kuan Huang0cb06a402024-08-15 23:31:49[diff] [blame]2721# We're done. Let the user know the results in an official manner.
2722if good_rev> bad_rev:
2723print(DONE_MESSAGE_GOOD_MAX%
2724(str(min_chromium_rev), str(max_chromium_rev)))
Kuan Huang95f6df072024-09-12 20:56:42[diff] [blame]2725 good_rev, bad_rev= max_chromium_rev, min_chromium_rev
anantha@chromium.orgd0149c5c2012-05-29 21:12:11[diff] [blame]2726else:
Kuan Huang0cb06a402024-08-15 23:31:49[diff] [blame]2727print(DONE_MESSAGE_GOOD_MIN%
2728(str(min_chromium_rev), str(max_chromium_rev)))
Kuan Huang95f6df072024-09-12 20:56:42[diff] [blame]2729 good_rev, bad_rev= min_chromium_rev, max_chromium_rev
karen@chromium.org3bdaa4752013-09-30 20:13:36[diff] [blame]2730
Kuan Huang0cb06a402024-08-15 23:31:49[diff] [blame]2731print('CHANGELOG URL:')
Kuan Huang37d0daa2024-09-19 18:03:08[diff] [blame]2732if opts.build_type=='release':
Kuan Huangb790d5d2024-11-07 21:53:00[diff] [blame]2733print(GetReleaseChangeLogURL(min_chromium_rev, max_chromium_rev))
Kuan Huang95f6df072024-09-12 20:56:42[diff] [blame]2734MaybeSwitchBuildType(opts, good=good_rev, bad=bad_rev)
Kuan Huang0cb06a402024-08-15 23:31:49[diff] [blame]2735else:
Kuan Huangb790d5d2024-11-07 21:53:00[diff] [blame]2736print(GetChangeLogURL(min_chromium_rev, max_chromium_rev))
Kuan Huang37d0daa2024-09-19 18:03:08[diff] [blame]2737if opts.build_type=='official':
Kuan Huang0cb06a402024-08-15 23:31:49[diff] [blame]2738print('The script might not always return single CL as suspect '
2739'as some perf builds might get missing due to failure.')
qyearsley@chromium.org4df583c2014-07-31 17:11:55[diff] [blame]2740
rsesek@chromium.org67e0bc62009-09-03 22:06:09[diff] [blame]2741if __name__=='__main__':
mmoss@chromium.org7ad66a72009-09-04 17:52:33[diff] [blame]2742 sys.exit(main())

[8]ページ先頭

©2009-2025 Movatter.jp