Lei Zhang | 5471122f | 2022-06-28 19:40:32 | [diff] [blame] | 1 | #!/usr/bin/env python3 |
Avi Drissman | dfd88085 | 2022-09-15 20:11:09 | [diff] [blame] | 2 | # Copyright 2012 The Chromium Authors |
rsesek@chromium.org | 67e0bc6 | 2009-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.org | 7ad66a7 | 2009-09-04 17:52:33 | [diff] [blame] | 8 | This script bisects a snapshot archive using binary search. It starts at |
rsesek@chromium.org | 67e0bc6 | 2009-09-03 22:06:09 | [diff] [blame] | 9 | a bad revision (it will try to guess HEAD) and asks for a last known-good |
| 10 | revision. It will then binary search across this revision range by downloading, |
| 11 | unzipping, and opening Chromium for you. After testing the specific revision, |
| 12 | it will ask you whether it is good or bad before continuing the search. |
Paul Irish | 4f6e48c | 2025-03-27 17:40:08 | [diff] [blame] | 13 | |
| 14 | Docs: https://www.chromium.org/developers/bisect-builds-py/ |
| 15 | Googlers: go/chrome-bisect |
rsesek@chromium.org | 67e0bc6 | 2009-09-03 22:06:09 | [diff] [blame] | 16 | """ |
| 17 | |
Kuan Huang | 49a2c641 | 2024-08-12 22:12:33 | [diff] [blame] | 18 | import abc |
Kuan Huang | 95f6df07 | 2024-09-12 20:56:42 | [diff] [blame] | 19 | import argparse |
Sven Zheng | e3441db | 2024-05-13 19:59:32 | [diff] [blame] | 20 | import base64 |
Kuan Huang | 95f6df07 | 2024-09-12 20:56:42 | [diff] [blame] | 21 | import copy |
Kuan Huang | 6f7f5794 | 2024-12-05 19:26:30 | [diff] [blame] | 22 | import functools |
Kuan Huang | d608f159 | 2024-08-15 23:31:35 | [diff] [blame] | 23 | import glob |
Sven Zheng | 8e07956 | 2024-05-10 20:11:06 | [diff] [blame] | 24 | import importlib |
| 25 | import json |
Sven Zheng | 8e07956 | 2024-05-10 20:11:06 | [diff] [blame] | 26 | import os |
| 27 | import platform |
| 28 | import re |
| 29 | import shlex |
Sven Zheng | 8e07956 | 2024-05-10 20:11:06 | [diff] [blame] | 30 | import subprocess |
| 31 | import sys |
Kuan Huang | b4c8df5 | 2024-09-05 17:59:18 | [diff] [blame] | 32 | import tarfile |
Sven Zheng | 8e07956 | 2024-05-10 20:11:06 | [diff] [blame] | 33 | import tempfile |
Sven Zheng | 8e07956 | 2024-05-10 20:11:06 | [diff] [blame] | 34 | import threading |
| 35 | import traceback |
Kuan Huang | 6f7f5794 | 2024-12-05 19:26:30 | [diff] [blame] | 36 | import urllib.parse |
| 37 | import urllib.request |
Sven Zheng | 8e07956 | 2024-05-10 20:11:06 | [diff] [blame] | 38 | from xml.etreeimportElementTree |
| 39 | import zipfile |
| 40 | |
Kuan Huang | 6f7f5794 | 2024-12-05 19:26:30 | [diff] [blame] | 41 | |
Sven Zheng | 8e07956 | 2024-05-10 20:11:06 | [diff] [blame] | 42 | # These constants are used for android bisect which depends on |
| 43 | # Catapult repo. |
| 44 | DEFAULT_CATAPULT_DIR= os.path.abspath( |
| 45 | os.path.join(os.path.dirname(__file__),'catapult_bisect_dep')) |
| 46 | CATAPULT_DIR= os.environ.get('CATAPULT_DIR', DEFAULT_CATAPULT_DIR) |
| 47 | CATAPULT_REPO='https://github.com/catapult-project/catapult.git' |
| 48 | DEVIL_PATH= os.path.abspath(os.path.join(CATAPULT_DIR,'devil')) |
Raul Tambre | 57e09d6 | 2019-09-22 17:18:52 | [diff] [blame] | 49 | |
qyearsley@chromium.org | 4df583c | 2014-07-31 17:11:55 | [diff] [blame] | 50 | # The base URL for stored build archives. |
| 51 | CHROMIUM_BASE_URL=('http://commondatastorage.googleapis.com' |
| 52 | '/chromium-browser-snapshots') |
cmumford@chromium.org | 01188669 | 2014-08-01 21:00:21 | [diff] [blame] | 53 | ASAN_BASE_URL=('http://commondatastorage.googleapis.com' |
| 54 | '/chromium-browser-asan') |
Kuan Huang | 2dd3a33f | 2024-12-13 17:52:52 | [diff] [blame] | 55 | CHROME_FOR_TESTING_BASE_URL=('https://storage.googleapis.com/' |
| 56 | 'chrome-for-testing-per-commit-public') |
rsesek@chromium.org | 67e0bc6 | 2009-09-03 22:06:09 | [diff] [blame] | 57 | |
Sven Zheng | 8e07956 | 2024-05-10 20:11:06 | [diff] [blame] | 58 | GSUTILS_PATH=None |
| 59 | |
| 60 | # GS bucket name for perf builds |
| 61 | PERF_BASE_URL='gs://chrome-test-builds/official-by-commit' |
| 62 | # GS bucket name. |
| 63 | RELEASE_BASE_URL='gs://chrome-unsigned/desktop-5c0tCh' |
| 64 | |
| 65 | # Android bucket starting at M45. |
| 66 | ANDROID_RELEASE_BASE_URL='gs://chrome-unsigned/android-B0urB0N' |
| 67 | ANDROID_RELEASE_BASE_URL_SIGNED='gs://chrome-signed/android-B0urB0N' |
| 68 | |
| 69 | # A special bucket that need to be skipped. |
| 70 | ANDROID_INVALID_BUCKET='gs://chrome-signed/android-B0urB0N/Test' |
| 71 | |
Kuan Huang | 2277007 | 2024-08-27 20:06:53 | [diff] [blame] | 72 | # iOS bucket |
| 73 | IOS_RELEASE_BASE_URL='gs://chrome-unsigned/ios-G1N' |
| 74 | IOS_RELEASE_BASE_URL_SIGNED='gs://chrome-signed/ios-G1N' |
Kuan Huang | b4c8df5 | 2024-09-05 17:59:18 | [diff] [blame] | 75 | IOS_ARCHIVE_BASE_URL='gs://bling-archive' |
Kuan Huang | 2277007 | 2024-08-27 20:06:53 | [diff] [blame] | 76 | |
Sven Zheng | 8e07956 | 2024-05-10 20:11:06 | [diff] [blame] | 77 | # Base URL for downloading release builds. |
| 78 | GOOGLE_APIS_URL='commondatastorage.googleapis.com' |
| 79 | |
qyearsley@chromium.org | 4df583c | 2014-07-31 17:11:55 | [diff] [blame] | 80 | # URL template for viewing changelogs between revisions. |
Kuan Huang | b790d5d | 2024-11-07 21:53:00 | [diff] [blame] | 81 | SHORT_CHANGELOG_URL='https://crrev.com/%s..%s' |
pshenoy | 9ce271f | 2014-09-02 22:14:05 | [diff] [blame] | 82 | CHANGELOG_URL=('https://chromium.googlesource.com/chromium/src/+log/%s..%s') |
| 83 | |
| 84 | # URL to convert SVN revision to git hash. |
pshenoy | 13cb79e0 | 2014-09-05 01:42:53 | [diff] [blame] | 85 | CRREV_URL=('https://cr-rev.appspot.com/_ah/api/crrev/v1/redirect/') |
nirnimesh@chromium.org | f6a71a7 | 2009-10-08 19:55:38 | [diff] [blame] | 86 | |
Sven Zheng | 8e07956 | 2024-05-10 20:11:06 | [diff] [blame] | 87 | # URL template for viewing changelogs between release versions. |
| 88 | RELEASE_CHANGELOG_URL=('https://chromium.googlesource.com/chromium/' |
Kuan Huang | b790d5d | 2024-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 |
| 92 | STEPS_TO_SHOW_CHANGELOG_URL=5 |
Sven Zheng | 8e07956 | 2024-05-10 20:11:06 | [diff] [blame] | 93 | |
thakis@chromium.org | b2fe7f2 | 2011-10-25 22:58:31 | [diff] [blame] | 94 | # DEPS file URL. |
Sven Zheng | 8e07956 | 2024-05-10 20:11:06 | [diff] [blame] | 95 | DEPS_FILE_OLD=('http://src.chromium.org/viewvc/chrome/trunk/src/' |
| 96 | 'DEPS?revision=%d') |
| 97 | DEPS_FILE_NEW=('https://chromium.googlesource.com/chromium/src/+/%s/DEPS') |
thakis@chromium.org | b2fe7f2 | 2011-10-25 22:58:31 | [diff] [blame] | 98 | |
Kuan Huang | 008965f9 | 2024-10-08 21:53:15 | [diff] [blame] | 99 | # Source Tag |
| 100 | SOURCE_TAG_URL=('https://chromium.googlesource.com/chromium/src/' |
| 101 | '+/refs/tags/%s?format=JSON') |
| 102 | |
qyearsley@chromium.org | 4df583c | 2014-07-31 17:11:55 | [diff] [blame] | 103 | |
| 104 | DONE_MESSAGE_GOOD_MIN=('You are probably looking for a change made after %s (' |
| 105 | 'known good), but no later than %s (first known bad).') |
| 106 | DONE_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.org | 05ff3fd | 2012-04-17 23:24:06 | [diff] [blame] | 108 | |
Sven Zheng | 8e07956 | 2024-05-10 20:11:06 | [diff] [blame] | 109 | VERSION_INFO_URL=('https://chromiumdash.appspot.com/fetch_version?version=%s') |
Alex Mineer | e309d00 | 2022-04-21 22:42:50 | [diff] [blame] | 110 | |
Kuan Huang | c93ac26 | 2024-09-19 18:53:17 | [diff] [blame] | 111 | MILESTONES_URL=('https://chromiumdash.appspot.com/fetch_milestones?mstone=%s') |
Joel Hockey | 60c4f5b | 2024-07-15 22:03:08 | [diff] [blame] | 112 | |
pshenoy@chromium.org | 48036978 | 2014-08-22 20:15:58 | [diff] [blame] | 113 | CREDENTIAL_ERROR_MESSAGE=('You are attempting to access protected data with ' |
| 114 | 'no configured credentials') |
Sven Zheng | 8e07956 | 2024-05-10 20:11:06 | [diff] [blame] | 115 | PATH_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 Huang | feb4fa55 | 2024-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 Wen | 9156ea40 | 2024-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 Huang | 2277007 | 2024-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 Huang | b4c8df5 | 2024-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 Zheng | 8e07956 | 2024-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 Huang | 89fe2eef | 2024-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 Zheng | 8e07956 | 2024-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 Huang | 89fe2eef | 2024-09-13 23:04:02 | [diff] [blame] | 178 | 'archive_extract_dir':'chrome-mac', |
Sven Zheng | 8e07956 | 2024-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 Huang | 89fe2eef | 2024-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 Zheng | 8e07956 | 2024-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 Huang | 89fe2eef | 2024-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 Zheng | 8e07956 | 2024-05-10 20:11:06 | [diff] [blame] | 195 | }, |
Kuan Huang | 7b45b64 | 2024-09-10 17:30:29 | [diff] [blame] | 196 | 'win':{ |
Sven Zheng | 8e07956 | 2024-05-10 20:11:06 | [diff] [blame] | 197 | 'binary_name':'chrome.exe', |
Kuan Huang | 7b45b64 | 2024-09-10 17:30:29 | [diff] [blame] | 198 | # Release builds switched to -clang in M64. |
Sven Zheng | 8e07956 | 2024-05-10 20:11:06 | [diff] [blame] | 199 | 'listing_platform_dir':'win-clang/', |
| 200 | 'archive_name':'chrome-win-clang.zip', |
Kuan Huang | 89fe2eef | 2024-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 Zheng | 8e07956 | 2024-05-10 20:11:06 | [diff] [blame] | 204 | }, |
Kuan Huang | 7b45b64 | 2024-09-10 17:30:29 | [diff] [blame] | 205 | 'win64':{ |
Sven Zheng | 8e07956 | 2024-05-10 20:11:06 | [diff] [blame] | 206 | 'binary_name':'chrome.exe', |
Kuan Huang | 7b45b64 | 2024-09-10 17:30:29 | [diff] [blame] | 207 | # Release builds switched to -clang in M64. |
Sven Zheng | 8e07956 | 2024-05-10 20:11:06 | [diff] [blame] | 208 | 'listing_platform_dir':'win64-clang/', |
| 209 | 'archive_name':'chrome-win64-clang.zip', |
Kuan Huang | 89fe2eef | 2024-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 Zheng | 8e07956 | 2024-05-10 20:11:06 | [diff] [blame] | 213 | }, |
Kuan Huang | a860dcd | 2024-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 Zheng | 8e07956 | 2024-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 Huang | feb4fa55 | 2024-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 Zheng | 8e07956 | 2024-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 Huang | 89fe2eef | 2024-09-13 23:04:02 | [diff] [blame] | 246 | 'archive_extract_dir':'full-build-linux', |
| 247 | 'chromedriver_binary_name':'chromedriver', |
Sven Zheng | 8e07956 | 2024-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 Huang | 89fe2eef | 2024-09-13 23:04:02 | [diff] [blame] | 253 | 'archive_extract_dir':'full-build-mac', |
| 254 | 'chromedriver_binary_name':'chromedriver', |
Sven Zheng | 8e07956 | 2024-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 Huang | 89fe2eef | 2024-09-13 23:04:02 | [diff] [blame] | 260 | 'archive_extract_dir':'full-build-mac', |
| 261 | 'chromedriver_binary_name':'chromedriver', |
Sven Zheng | 8e07956 | 2024-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 Huang | 89fe2eef | 2024-09-13 23:04:02 | [diff] [blame] | 267 | 'archive_extract_dir':'full-build-win32', |
| 268 | 'chromedriver_binary_name':'chromedriver.exe', |
Sven Zheng | 8e07956 | 2024-05-10 20:11:06 | [diff] [blame] | 269 | }, |
Sven Zheng | 8e07956 | 2024-05-10 20:11:06 | [diff] [blame] | 270 | }, |
| 271 | 'snapshot':{ |
Peter Wen | b4ba3048 | 2024-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 Zheng | 8e07956 | 2024-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 Huang | 89fe2eef | 2024-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 Zheng | 8e07956 | 2024-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 Zheng | 8e07956 | 2024-05-10 20:11:06 | [diff] [blame] | 298 | 'chromeos':{ |
| 299 | 'binary_name':'chrome', |
| 300 | 'listing_platform_dir':'Linux_ChromiumOS_Full/', |
Kuan Huang | 8b5e33dc | 2024-08-15 17:51:24 | [diff] [blame] | 301 | 'archive_name':'chrome-chromeos.zip', |
| 302 | 'archive_extract_dir':'chrome-chromeos' |
Sven Zheng | 8e07956 | 2024-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 Huang | 89fe2eef | 2024-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 Zheng | 8e07956 | 2024-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 Huang | 89fe2eef | 2024-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 Zheng | 8e07956 | 2024-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 Huang | 89fe2eef | 2024-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 Zheng | 8e07956 | 2024-05-10 20:11:06 | [diff] [blame] | 327 | }, |
Sven Zheng | 8e07956 | 2024-05-10 20:11:06 | [diff] [blame] | 328 | 'win':{ |
| 329 | 'binary_name':'chrome.exe', |
| 330 | 'listing_platform_dir':'Win/', |
Kuan Huang | 8b5e33dc | 2024-08-15 17:51:24 | [diff] [blame] | 331 | 'archive_name':'chrome-win.zip', |
Kuan Huang | 89fe2eef | 2024-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 Zheng | 8e07956 | 2024-05-10 20:11:06 | [diff] [blame] | 335 | }, |
| 336 | 'win64':{ |
| 337 | 'binary_name':'chrome.exe', |
| 338 | 'listing_platform_dir':'Win_x64/', |
Kuan Huang | 8b5e33dc | 2024-08-15 17:51:24 | [diff] [blame] | 339 | 'archive_name':'chrome-win.zip', |
Kuan Huang | 89fe2eef | 2024-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 Zheng | 8e07956 | 2024-05-10 20:11:06 | [diff] [blame] | 343 | }, |
Kuan Huang | 147e34e | 2024-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 Huang | 89fe2eef | 2024-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 Huang | 147e34e | 2024-07-10 23:17:18 | [diff] [blame] | 351 | }, |
Kuan Huang | 49a2c641 | 2024-08-12 22:12:33 | [diff] [blame] | 352 | }, |
Kuan Huang | 37d0daa | 2024-09-19 18:03:08 | [diff] [blame] | 353 | 'asan':{ |
| 354 | 'linux':{}, |
| 355 | 'mac':{}, |
| 356 | 'win':{}, |
| 357 | }, |
Kuan Huang | 2dd3a33f | 2024-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 Zheng | 8e07956 | 2024-05-10 20:11:06 | [diff] [blame] | 382 | } |
pshenoy@chromium.org | 48036978 | 2014-08-22 20:15:58 | [diff] [blame] | 383 | |
Sven Zheng | 8e07956 | 2024-05-10 20:11:06 | [diff] [blame] | 384 | CHROME_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 Joyce | 34b4c73 | 2023-04-26 21:29:23 | [diff] [blame] | 392 | |
Sven Zheng | 8e07956 | 2024-05-10 20:11:06 | [diff] [blame] | 393 | CHROME_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.org | 67e0bc6 | 2009-09-03 22:06:09 | [diff] [blame] | 401 | |
Sven Zheng | 8e07956 | 2024-05-10 20:11:06 | [diff] [blame] | 402 | MONOCHROME_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.org | bd8dcb9 | 2010-03-31 01:05:24 | [diff] [blame] | 410 | |
Kuan Huang | feb4fa55 | 2024-08-21 20:40:28 | [diff] [blame] | 411 | TRICHROME_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 | |
| 419 | TRICHROME64_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 Nguyen | d391d05 | 2025-06-04 09:35:09 | [diff] [blame] | 425 | 'system_webview':'TrichromeWebViewGoogle6432.apks', |
Kuan Huang | feb4fa55 | 2024-08-21 20:40:28 | [diff] [blame] | 426 | } |
| 427 | |
| 428 | TRICHROME_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 | |
| 436 | TRICHROME64_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 Nguyen | d391d05 | 2025-06-04 09:35:09 | [diff] [blame] | 442 | 'system_webview':'TrichromeLibraryGoogle6432.apk', |
Kuan Huang | feb4fa55 | 2024-08-21 20:40:28 | [diff] [blame] | 443 | } |
| 444 | |
Sven Zheng | 8e07956 | 2024-05-10 20:11:06 | [diff] [blame] | 445 | WEBVIEW_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 |
| 455 | OFFICIAL_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 Huang | 0d24cfd9 | 2024-07-12 16:50:30 | [diff] [blame] | 470 | PLATFORM_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 Zheng | 8e07956 | 2024-05-10 20:11:06 | [diff] [blame] | 480 | # Set only during initialization. |
| 481 | is_verbose=False |
| 482 | |
| 483 | |
| 484 | classBisectException(Exception): |
| 485 | |
| 486 | def __str__(self): |
| 487 | return'[Bisect Exception]: %s\n'% self.args[0] |
| 488 | |
| 489 | |
Kuan Huang | c63de40 | 2024-08-12 22:15:27 | [diff] [blame] | 490 | defRunGsutilCommand(args, can_fail=False, ignore_fail=False): |
Kuan Huang | d86de1a | 2024-07-10 18:45:40 | [diff] [blame] | 491 | ifnot GSUTILS_PATH: |
| 492 | raiseBisectException('gsutils is not found in path.') |
Sven Zheng | 8e07956 | 2024-05-10 20:11:06 | [diff] [blame] | 493 | if is_verbose: |
| 494 | print('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") |
| 503 | if gsutil.returncode: |
| 504 | if(re.findall(r'(status|ServiceException:)[ |=]40[1|3]', stderr) |
| 505 | or stderr.startswith(CREDENTIAL_ERROR_MESSAGE)): |
| 506 | print(('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)) |
| 511 | print('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 Huang | c3a4cd1e | 2024-10-03 21:17:31 | [diff] [blame] | 514 | raiseBisectException('gsutil credential error') |
Sven Zheng | 8e07956 | 2024-05-10 20:11:06 | [diff] [blame] | 515 | elif can_fail: |
| 516 | return stderr |
Kuan Huang | c63de40 | 2024-08-12 22:15:27 | [diff] [blame] | 517 | elif ignore_fail: |
| 518 | return stdout |
Sven Zheng | 8e07956 | 2024-05-10 20:11:06 | [diff] [blame] | 519 | else: |
| 520 | raiseException('Error running the gsutil command:\n%s\n%s'% |
| 521 | (args, stderr)) |
| 522 | return stdout |
Bruce Dawson | 039777ef | 2020-10-26 20:34:47 | [diff] [blame] | 523 | |
maruel@chromium.org | cb155a8 | 2011-11-29 17:25:34 | [diff] [blame] | 524 | |
Kuan Huang | c63de40 | 2024-08-12 22:15:27 | [diff] [blame] | 525 | defGsutilList(*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=[] |
| 546 | for linein stdout.splitlines(): |
| 547 | parts= line.split(maxsplit=2) |
| 548 | ifnot parts[-1].startswith('gs://'): |
| 549 | continue |
| 550 | # Check whether there is a size field. For release builds the listing |
| 551 | # will be directories so there will be no size field. |
| 552 | if len(parts)>1: |
| 553 | if ANDROID_INVALID_BUCKETin line: |
| 554 | continue |
| 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. |
| 558 | if parts[-1].endswith('LAST_CHANGE')or size>1000: |
| 559 | lines.append(parts[-1]) |
| 560 | else: |
| 561 | lines.append(parts[-1]) |
| 562 | return lines |
| 563 | |
| 564 | |
Kuan Huang | 94c4a2d1 | 2024-11-21 21:46:09 | [diff] [blame] | 565 | def join_args(args: list)-> str: |
| 566 | """Join the args into a single command line.""" |
| 567 | if 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. |
| 570 | return subprocess.list2cmdline(args) |
| 571 | else: |
| 572 | return shlex.join(args) |
| 573 | |
| 574 | |
| 575 | def quote_arg(arg: str)-> str: |
| 576 | """Quote the arg for the shell.""" |
| 577 | return join_args([arg]) |
| 578 | |
| 579 | |
Kuan Huang | 49a2c641 | 2024-08-12 22:12:33 | [diff] [blame] | 580 | classArchiveBuild(abc.ABC): |
| 581 | """Base class for a archived build.""" |
| 582 | |
| 583 | def __init__(self, options): |
| 584 | self.platform= options.archive |
Kuan Huang | 49a2c641 | 2024-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 Huang | 89fe2eef | 2024-09-13 23:04:02 | [diff] [blame] | 588 | self.chromedriver= options.chromedriver |
Kuan Huang | d608f159 | 2024-08-15 23:31:35 | [diff] [blame] | 589 | # PATH_CONTEXT |
Kuan Huang | 49a2c641 | 2024-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 Huang | 89fe2eef | 2024-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') |
| 598 | if self.chromedriverandnot self.chromedriver_binary_name: |
| 599 | raiseBisectException( |
| 600 | 'Could not find chromedriver_binary_name, ' |
| 601 | f'--chromedriver might not supported on {self.platform}.') |
Kuan Huang | d608f159 | 2024-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 Huang | 49a2c641 | 2024-08-12 22:12:33 | [diff] [blame] | 606 | |
Kuan Huang | 49a2c641 | 2024-08-12 22:12:33 | [diff] [blame] | 607 | @property |
| 608 | @abc.abstractmethod |
| 609 | def build_type(self): |
| 610 | raiseNotImplemented() |
| 611 | |
| 612 | @abc.abstractmethod |
Kuan Huang | c8dd77a | 2024-08-12 22:13:42 | [diff] [blame] | 613 | def _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 Huang | 49a2c641 | 2024-08-12 22:12:33 | [diff] [blame] | 628 | raiseNotImplemented() |
| 629 | |
Kuan Huang | c8dd77a | 2024-08-12 22:13:42 | [diff] [blame] | 630 | @property |
| 631 | def _rev_list_cache_filename(self): |
| 632 | return os.path.join(os.path.abspath(os.path.dirname(__file__)), |
| 633 | '.bisect-builds-cache.json') |
| 634 | |
| 635 | @property |
Kuan Huang | 49a2c641 | 2024-08-12 22:12:33 | [diff] [blame] | 636 | @abc.abstractmethod |
Kuan Huang | c8dd77a | 2024-08-12 22:13:42 | [diff] [blame] | 637 | def _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 Huang | 49a2c641 | 2024-08-12 22:12:33 | [diff] [blame] | 640 | raiseNotImplemented() |
| 641 | |
Kuan Huang | c8dd77a | 2024-08-12 22:13:42 | [diff] [blame] | 642 | def _load_rev_list_cache(self): |
| 643 | ifnot self.use_local_cache: |
| 644 | return[] |
| 645 | cache_filename= self._rev_list_cache_filename |
| 646 | try: |
| 647 | with open(cache_filename)as cache_file: |
| 648 | cache= json.load(cache_file) |
| 649 | revisions= cache.get(self._rev_list_cache_key,[]) |
| 650 | if revisions: |
| 651 | print('Loaded revisions %s-%s from %s'% |
| 652 | (revisions[0], revisions[-1], cache_filename)) |
| 653 | return revisions |
| 654 | exceptFileNotFoundError: |
| 655 | return[] |
| 656 | except(EnvironmentError,ValueError)as e: |
| 657 | print('Load revisions cache error:', e) |
| 658 | return[] |
| 659 | |
| 660 | def _save_rev_list_cache(self, revisions): |
| 661 | ifnot self.use_local_cache: |
| 662 | return |
Kuan Huang | feb4fa55 | 2024-08-21 20:40:28 | [diff] [blame] | 663 | ifnot revisions: |
| 664 | return |
Kuan Huang | c8dd77a | 2024-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. |
| 668 | try: |
| 669 | with open(cache_filename)as cache_file: |
| 670 | cache= json.load(cache_file) |
| 671 | exceptFileNotFoundError: |
| 672 | pass |
| 673 | except(EnvironmentError,ValueError)as e: |
| 674 | print('Load existing revisions cache error:', e) |
| 675 | return |
| 676 | # Update and save cache for current build. |
| 677 | cache[self._rev_list_cache_key]= revisions |
| 678 | try: |
| 679 | with open(cache_filename,'w')as cache_file: |
| 680 | json.dump(cache, cache_file) |
| 681 | print('Saved revisions %s-%s to %s'% |
| 682 | (revisions[0], revisions[-1], cache_filename)) |
| 683 | exceptEnvironmentErroras e: |
| 684 | print('Save revisions cache error:', e) |
| 685 | return |
Kuan Huang | 49a2c641 | 2024-08-12 22:12:33 | [diff] [blame] | 686 | |
| 687 | def 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 Huang | c8dd77a | 2024-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() |
| 693 | ifnot 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) |
| 696 | else: |
| 697 | rev_list_min, rev_list_max= rev_list_all[0], rev_list_all[-1] |
| 698 | if 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. |
| 707 | ifnot rev_list_all: |
| 708 | rev_list_all= sorted(self._get_rev_list()) |
| 709 | self._save_rev_list_cache(rev_list_all) |
| 710 | ifnot rev_list_all: |
| 711 | raiseBisectException('Could not retrieve the revisions for %s.'% |
| 712 | self.platform) |
Kuan Huang | 49a2c641 | 2024-08-12 22:12:33 | [diff] [blame] | 713 | |
Kuan Huang | c8dd77a | 2024-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 Huang | 8b5e33dc | 2024-08-15 17:51:24 | [diff] [blame] | 716 | # Don't have enough builds to bisect. |
| 717 | if len(rev_list)<2: |
Kuan Huang | c8dd77a | 2024-08-12 22:13:42 | [diff] [blame] | 718 | rev_list_min, rev_list_max= rev_list_all[0], rev_list_all[-1] |
Kuan Huang | 49a2c641 | 2024-08-12 22:12:33 | [diff] [blame] | 719 | # Check for specifying a number before the available range. |
Kuan Huang | c8dd77a | 2024-08-12 22:13:42 | [diff] [blame] | 720 | if max_rev< rev_list_min: |
Kuan Huang | 49a2c641 | 2024-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 Huang | c8dd77a | 2024-08-12 22:13:42 | [diff] [blame] | 724 | (self.platform, rev_list_min)) |
| 725 | raiseBisectException(msg) |
Kuan Huang | 49a2c641 | 2024-08-12 22:12:33 | [diff] [blame] | 726 | # Check for specifying a number beyond the available range. |
Kuan Huang | c8dd77a | 2024-08-12 22:13:42 | [diff] [blame] | 727 | if min_rev> rev_list_max: |
Kuan Huang | 49a2c641 | 2024-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. |
| 730 | if self.platform=='linux': |
| 731 | msg=('Last available bisect revision for %s is %d. Try linux64 ' |
Kuan Huang | c8dd77a | 2024-08-12 22:13:42 | [diff] [blame] | 732 | 'instead.'%(self.platform, rev_list_max)) |
Kuan Huang | 49a2c641 | 2024-08-12 22:12:33 | [diff] [blame] | 733 | else: |
| 734 | msg=('Last available bisect revision for %s is %d. Try a different ' |
Kuan Huang | c8dd77a | 2024-08-12 22:13:42 | [diff] [blame] | 735 | 'good/bad range.'%(self.platform, rev_list_max)) |
| 736 | raiseBisectException(msg) |
Kuan Huang | 49a2c641 | 2024-08-12 22:12:33 | [diff] [blame] | 737 | # Otherwise give a generic message. |
Kuan Huang | c8dd77a | 2024-08-12 22:13:42 | [diff] [blame] | 738 | msg='We don\'t have enough builds to bisect. rev_list: %s'% rev_list |
| 739 | raiseBisectException(msg) |
Kuan Huang | 49a2c641 | 2024-08-12 22:12:33 | [diff] [blame] | 740 | |
Kuan Huang | c8dd77a | 2024-08-12 22:13:42 | [diff] [blame] | 741 | # Set good and bad revisions to be legit revisions. |
| 742 | if rev_list: |
| 743 | if self.good_revision< self.bad_revision: |
| 744 | self.good_revision= rev_list[0] |
| 745 | self.bad_revision= rev_list[-1] |
| 746 | else: |
| 747 | self.bad_revision= rev_list[0] |
| 748 | self.good_revision= rev_list[-1] |
| 749 | return rev_list |
Kuan Huang | 49a2c641 | 2024-08-12 22:12:33 | [diff] [blame] | 750 | |
Kuan Huang | 8b5e33dc | 2024-08-15 17:51:24 | [diff] [blame] | 751 | @abc.abstractmethod |
| 752 | def get_download_url(self, revision): |
| 753 | """Gets the download URL for the specific revision.""" |
| 754 | raiseNotImplemented() |
| 755 | |
| 756 | def get_download_job(self, revision, name=None): |
| 757 | """Gets as a DownloadJob that download the specific revision in threads.""" |
| 758 | returnDownloadJob(self.get_download_url(revision), revision, name) |
| 759 | |
Kuan Huang | d608f159 | 2024-08-15 23:31:35 | [diff] [blame] | 760 | def _get_extra_args(self): |
| 761 | """Get extra chrome args""" |
| 762 | return['--user-data-dir=%s'% self.profile] |
| 763 | |
| 764 | def _get_extract_binary_glob(self, tempdir): |
| 765 | """Get the pathname for extracted chrome binary""" |
| 766 | return'%s/*/%s'%(tempdir, self.binary_name) |
| 767 | |
Kuan Huang | 89fe2eef | 2024-09-13 23:04:02 | [diff] [blame] | 768 | def _get_chromedriver_binary_glob(self, tempdir): |
| 769 | """Get the pathname for extracted chromedriver binary""" |
| 770 | ifnot self.chromedriver_binary_name: |
| 771 | raiseBisectException(f"chromedriver is not supported on {self.platform}") |
| 772 | return'%s/*/%s'%(tempdir, self.chromedriver_binary_name) |
| 773 | |
Kuan Huang | 70f5efe6 | 2024-11-07 23:26:35 | [diff] [blame] | 774 | def _run(self, runcommand, cwd=None, shell=False, print_when_error=True): |
Kuan Huang | b4c8df5 | 2024-09-05 17:59:18 | [diff] [blame] | 775 | # is_verbos is a global variable. |
| 776 | if is_verbose: |
| 777 | print(('Running '+ str(runcommand))) |
| 778 | subproc= subprocess.Popen(runcommand, |
| 779 | cwd=cwd, |
Kuan Huang | abd69f2 | 2024-09-13 23:03:48 | [diff] [blame] | 780 | shell=shell, |
Kuan Huang | b4c8df5 | 2024-09-05 17:59:18 | [diff] [blame] | 781 | bufsize=-1, |
| 782 | stdout=subprocess.PIPE, |
| 783 | stderr=subprocess.PIPE) |
| 784 | (stdout, stderr)= subproc.communicate() |
Kuan Huang | 70f5efe6 | 2024-11-07 23:26:35 | [diff] [blame] | 785 | if print_when_errorand subproc.returncode: |
| 786 | print('command: '+ str(runcommand)) |
| 787 | if is_verboseor(print_when_errorand subproc.returncode): |
| 788 | print(f'retcode: {subproc.returncode}\nstdout:\n') |
Kuan Huang | abd69f2 | 2024-09-13 23:03:48 | [diff] [blame] | 789 | sys.stdout.buffer.write(stdout) |
| 790 | sys.stdout.flush() |
| 791 | print('stderr:\n') |
| 792 | sys.stderr.buffer.write(stderr) |
| 793 | sys.stderr.flush() |
Kuan Huang | b4c8df5 | 2024-09-05 17:59:18 | [diff] [blame] | 794 | return subproc.returncode, stdout, stderr |
| 795 | |
Kuan Huang | 89fe2eef | 2024-09-13 23:04:02 | [diff] [blame] | 796 | @staticmethod |
| 797 | def _glob_with_unique_match(executable_name, tempdir, pathname): |
| 798 | executables= glob.glob(pathname) |
Kuan Huang | d608f159 | 2024-08-15 23:31:35 | [diff] [blame] | 799 | if len(executables)==0: |
Kuan Huang | 89fe2eef | 2024-09-13 23:04:02 | [diff] [blame] | 800 | raiseBisectException( |
| 801 | f'Can not find the {executable_name} binary from {tempdir}') |
Kuan Huang | d608f159 | 2024-08-15 23:31:35 | [diff] [blame] | 802 | elif len(executables)>1: |
Kuan Huang | 89fe2eef | 2024-09-13 23:04:02 | [diff] [blame] | 803 | raiseBisectException( |
| 804 | f'Multiple {executable_name} executables found: {executables}') |
Kuan Huang | d608f159 | 2024-08-15 23:31:35 | [diff] [blame] | 805 | return os.path.abspath(executables[0]) |
| 806 | |
Kuan Huang | 89fe2eef | 2024-09-13 23:04:02 | [diff] [blame] | 807 | def _install_revision(self, download, tempdir): |
| 808 | """Unzip and/or install the given download to tempdir. Return executable |
| 809 | binaries in a dict.""" |
| 810 | if isinstance(download, dict): |
| 811 | for eachin download.values(): |
| 812 | UnzipFilenameToDir(each, tempdir) |
| 813 | else: |
| 814 | UnzipFilenameToDir(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)) |
| 820 | if self.chromedriver: |
| 821 | result['chromedriver']= self._glob_with_unique_match( |
| 822 | 'chromedriver', tempdir, self._get_chromedriver_binary_glob(tempdir)) |
| 823 | return result |
| 824 | |
| 825 | def _launch_revision(self, tempdir, executables, args=()): |
Kuan Huang | d608f159 | 2024-08-15 23:31:35 | [diff] [blame] | 826 | args=[*self._get_extra_args(),*args] |
Kuan Huang | 94c4a2d1 | 2024-11-21 21:46:09 | [diff] [blame] | 827 | args_str= join_args(args) |
| 828 | command=(self.command.replace(r'%p', quote_arg( |
Kuan Huang | d3c862c0 | 2024-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 Huang | 89fe2eef | 2024-09-13 23:04:02 | [diff] [blame] | 831 | if self.chromedriver: |
Kuan Huang | 94c4a2d1 | 2024-11-21 21:46:09 | [diff] [blame] | 832 | command= command.replace(r'%d', quote_arg(executables['chromedriver'])) |
Kuan Huang | abd69f2 | 2024-09-13 23:03:48 | [diff] [blame] | 833 | return self._run(command, shell=True) |
Kuan Huang | d608f159 | 2024-08-15 23:31:35 | [diff] [blame] | 834 | |
Kuan Huang | c2fec79 | 2024-09-03 21:57:38 | [diff] [blame] | 835 | def run_revision(self, download, tempdir, args=()): |
Kuan Huang | d608f159 | 2024-08-15 23:31:35 | [diff] [blame] | 836 | """Run downloaded archive""" |
Kuan Huang | 89fe2eef | 2024-09-13 23:04:02 | [diff] [blame] | 837 | executables= self._install_revision(download, tempdir) |
Kuan Huang | c2fec79 | 2024-09-03 21:57:38 | [diff] [blame] | 838 | result=None |
| 839 | for _in range(self.num_runs): |
Kuan Huang | 89fe2eef | 2024-09-13 23:04:02 | [diff] [blame] | 840 | returncode, _, _= result= self._launch_revision(tempdir, executables, |
Kuan Huang | c2fec79 | 2024-09-03 21:57:38 | [diff] [blame] | 841 | args) |
| 842 | if returncode: |
| 843 | break |
| 844 | return result |
Kuan Huang | d608f159 | 2024-08-15 23:31:35 | [diff] [blame] | 845 | |
| 846 | |
Kuan Huang | 6f7f5794 | 2024-12-05 19:26:30 | [diff] [blame] | 847 | @functools.total_ordering |
| 848 | classChromiumVersion: |
| 849 | """Chromium version numbers consist of 4 parts: MAJOR.MINOR.BUILD.PATCH. |
| 850 | |
| 851 | This class is used to compare the version numbers. |
| 852 | """ |
| 853 | def __init__(self, vstring: str): |
| 854 | self.vstring= vstring |
| 855 | self.version= tuple(int(x)for xin vstring.split('.')) |
| 856 | |
| 857 | def __str__(self): |
| 858 | return self.vstring |
| 859 | |
| 860 | def __repr__(self): |
| 861 | return"ChromiumVersion ('%s')"% str(self) |
| 862 | |
| 863 | def __lt__(self, other): |
| 864 | if isinstance(other, str): |
| 865 | other=ChromiumVersion(other) |
| 866 | return self.version< other.version |
| 867 | |
| 868 | def __eq__(self, other): |
| 869 | if isinstance(other, str): |
| 870 | other=ChromiumVersion(other) |
| 871 | return self.version== other.version |
Kuan Huang | d608f159 | 2024-08-15 23:31:35 | [diff] [blame] | 872 | |
| 873 | def __hash__(self): |
| 874 | return hash(str(self)) |
| 875 | |
Kuan Huang | 49a2c641 | 2024-08-12 22:12:33 | [diff] [blame] | 876 | |
| 877 | classReleaseBuild(ArchiveBuild): |
| 878 | |
Kuan Huang | c63de40 | 2024-08-12 22:15:27 | [diff] [blame] | 879 | def __init__(self, options): |
| 880 | super().__init__(options) |
Kuan Huang | 6f7f5794 | 2024-12-05 19:26:30 | [diff] [blame] | 881 | self.good_revision=ChromiumVersion(self.good_revision) |
| 882 | self.bad_revision=ChromiumVersion(self.bad_revision) |
Kuan Huang | c63de40 | 2024-08-12 22:15:27 | [diff] [blame] | 883 | |
Kuan Huang | 49a2c641 | 2024-08-12 22:12:33 | [diff] [blame] | 884 | @property |
| 885 | def build_type(self): |
| 886 | return'release' |
| 887 | |
| 888 | def _get_release_bucket(self): |
Kuan Huang | 49a2c641 | 2024-08-12 22:12:33 | [diff] [blame] | 889 | return RELEASE_BASE_URL |
| 890 | |
Kuan Huang | c63de40 | 2024-08-12 22:15:27 | [diff] [blame] | 891 | def _get_rev_list(self, min_rev=None, max_rev=None): |
| 892 | # Get all build numbers in the build bucket. |
| 893 | build_numbers=[] |
Kuan Huang | 49a2c641 | 2024-08-12 22:12:33 | [diff] [blame] | 894 | revision_re= re.compile(r'(\d+\.\d\.\d{4}\.\d+)') |
Kuan Huang | c63de40 | 2024-08-12 22:15:27 | [diff] [blame] | 895 | for pathinGsutilList(self._get_release_bucket()): |
| 896 | match= revision_re.search(path) |
| 897 | if match: |
Kuan Huang | 6f7f5794 | 2024-12-05 19:26:30 | [diff] [blame] | 898 | build_numbers.append(ChromiumVersion(match[1])) |
Kuan Huang | c63de40 | 2024-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 |
| 902 | if(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 Huang | 49a2c641 | 2024-08-12 22:12:33 | [diff] [blame] | 908 | final_list=[] |
Kuan Huang | c63de40 | 2024-08-12 22:15:27 | [diff] [blame] | 909 | for batchin(build_numbers[i:i+ batch_size] |
| 910 | for 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. |
| 916 | for pathinGsutilList(*[self._get_archive_path(x)for xin batch], |
| 917 | ignore_fail=True): |
| 918 | match= revision_re.search(path) |
| 919 | if match: |
Kuan Huang | 6f7f5794 | 2024-12-05 19:26:30 | [diff] [blame] | 920 | final_list.append(ChromiumVersion(match[1])) |
Kuan Huang | c63de40 | 2024-08-12 22:15:27 | [diff] [blame] | 921 | sys.stdout.write('\r') |
| 922 | sys.stdout.flush() |
Kuan Huang | 49a2c641 | 2024-08-12 22:12:33 | [diff] [blame] | 923 | return final_list |
| 924 | |
Kuan Huang | c63de40 | 2024-08-12 22:15:27 | [diff] [blame] | 925 | def _get_listing_url(self): |
| 926 | return self._get_release_bucket() |
Kuan Huang | 49a2c641 | 2024-08-12 22:12:33 | [diff] [blame] | 927 | |
Kuan Huang | feb4fa55 | 2024-08-21 20:40:28 | [diff] [blame] | 928 | def _get_archive_path(self, build_number, archive_name=None): |
| 929 | if archive_nameisNone: |
| 930 | archive_name= self.archive_name |
Kuan Huang | c63de40 | 2024-08-12 22:15:27 | [diff] [blame] | 931 | return'/'.join((self._get_release_bucket(), str(build_number), |
Kuan Huang | feb4fa55 | 2024-08-21 20:40:28 | [diff] [blame] | 932 | self.listing_platform_dir.rstrip('/'), archive_name)) |
Kuan Huang | 49a2c641 | 2024-08-12 22:12:33 | [diff] [blame] | 933 | |
Kuan Huang | c8dd77a | 2024-08-12 22:13:42 | [diff] [blame] | 934 | @property |
| 935 | def _rev_list_cache_key(self): |
Kuan Huang | c63de40 | 2024-08-12 22:15:27 | [diff] [blame] | 936 | return self._get_archive_path('**') |
| 937 | |
| 938 | def _save_rev_list_cache(self, revisions): |
Kuan Huang | 6f7f5794 | 2024-12-05 19:26:30 | [diff] [blame] | 939 | # ChromiumVersion is not json-able, convert it back to string format. |
Kuan Huang | c63de40 | 2024-08-12 22:15:27 | [diff] [blame] | 940 | super()._save_rev_list_cache([str(x)for xin revisions]) |
| 941 | |
| 942 | def _load_rev_list_cache(self): |
Kuan Huang | 6f7f5794 | 2024-12-05 19:26:30 | [diff] [blame] | 943 | # Convert to ChromiumVersion that revisions can be correctly compared. |
Kuan Huang | c63de40 | 2024-08-12 22:15:27 | [diff] [blame] | 944 | revisions= super()._load_rev_list_cache() |
Kuan Huang | 6f7f5794 | 2024-12-05 19:26:30 | [diff] [blame] | 945 | return[ChromiumVersion(x)for xin revisions] |
Kuan Huang | c63de40 | 2024-08-12 22:15:27 | [diff] [blame] | 946 | |
Kuan Huang | 8b5e33dc | 2024-08-15 17:51:24 | [diff] [blame] | 947 | def get_download_url(self, revision): |
Kuan Huang | 89fe2eef | 2024-09-13 23:04:02 | [diff] [blame] | 948 | if self.chromedriver: |
| 949 | return{ |
| 950 | 'chrome': |
| 951 | self._get_archive_path(revision), |
| 952 | 'chromedriver': |
| 953 | self._get_archive_path(revision, self.chromedriver_archive_name), |
| 954 | } |
Kuan Huang | feb4fa55 | 2024-08-21 20:40:28 | [diff] [blame] | 955 | return self._get_archive_path(revision) |
Kuan Huang | 8b5e33dc | 2024-08-15 17:51:24 | [diff] [blame] | 956 | |
Kuan Huang | c63de40 | 2024-08-12 22:15:27 | [diff] [blame] | 957 | |
Kuan Huang | c63de40 | 2024-08-12 22:15:27 | [diff] [blame] | 958 | classArchiveBuildWithCommitPosition(ArchiveBuild): |
| 959 | """Class for ArchiveBuilds that organized based on commit position.""" |
| 960 | |
| 961 | def get_last_change_url(self): |
Kuan Huang | c8dd77a | 2024-08-12 22:13:42 | [diff] [blame] | 962 | returnNone |
| 963 | |
Kuan Huang | 49a2c641 | 2024-08-12 22:12:33 | [diff] [blame] | 964 | def __init__(self, options): |
| 965 | super().__init__(options) |
| 966 | # convert good and bad to commit position as int. |
Kuan Huang | c63de40 | 2024-08-12 22:15:27 | [diff] [blame] | 967 | self.good_revision=GetRevision(self.good_revision) |
Kuan Huang | 49a2c641 | 2024-08-12 22:12:33 | [diff] [blame] | 968 | ifnot options.bad: |
| 969 | self.bad_revision=GetChromiumRevision(self.get_last_change_url()) |
Kuan Huang | 49a2c641 | 2024-08-12 22:12:33 | [diff] [blame] | 970 | self.bad_revision=GetRevision(self.bad_revision) |
| 971 | |
Kuan Huang | c63de40 | 2024-08-12 22:15:27 | [diff] [blame] | 972 | |
| 973 | classOfficialBuild(ArchiveBuildWithCommitPosition): |
| 974 | |
Kuan Huang | 49a2c641 | 2024-08-12 22:12:33 | [diff] [blame] | 975 | @property |
| 976 | def build_type(self): |
| 977 | return'official' |
| 978 | |
Kuan Huang | a716032a | 2024-08-14 22:15:46 | [diff] [blame] | 979 | def _get_listing_url(self): |
| 980 | return'/'.join((PERF_BASE_URL, self.listing_platform_dir)) |
Kuan Huang | 49a2c641 | 2024-08-12 22:12:33 | [diff] [blame] | 981 | |
Kuan Huang | c8dd77a | 2024-08-12 22:13:42 | [diff] [blame] | 982 | def _get_rev_list(self, min_rev=None, max_rev=None): |
Kuan Huang | 49a2c641 | 2024-08-12 22:12:33 | [diff] [blame] | 983 | # For official builds, it's getting the list from perf build bucket. |
Kuan Huang | a716032a | 2024-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 Huang | 49a2c641 | 2024-08-12 22:12:33 | [diff] [blame] | 986 | revision_re= re.compile(r'%s_(\d+)\.zip'%(self.archive_extract_dir)) |
Kuan Huang | a716032a | 2024-08-14 22:15:46 | [diff] [blame] | 987 | revision_files=GsutilList(self._get_listing_url()) |
Kuan Huang | 49a2c641 | 2024-08-12 22:12:33 | [diff] [blame] | 988 | revision_numbers=[] |
| 989 | for revision_filein revision_files: |
Kuan Huang | c63de40 | 2024-08-12 22:15:27 | [diff] [blame] | 990 | revision_num= revision_re.search(revision_file) |
Kuan Huang | 49a2c641 | 2024-08-12 22:12:33 | [diff] [blame] | 991 | if revision_num: |
Kuan Huang | c63de40 | 2024-08-12 22:15:27 | [diff] [blame] | 992 | revision_numbers.append(int(revision_num[1])) |
Kuan Huang | a716032a | 2024-08-14 22:15:46 | [diff] [blame] | 993 | return revision_numbers |
Kuan Huang | 49a2c641 | 2024-08-12 22:12:33 | [diff] [blame] | 994 | |
Kuan Huang | c8dd77a | 2024-08-12 22:13:42 | [diff] [blame] | 995 | @property |
| 996 | def _rev_list_cache_key(self): |
| 997 | return self._get_listing_url() |
| 998 | |
Kuan Huang | 8b5e33dc | 2024-08-15 17:51:24 | [diff] [blame] | 999 | def get_download_url(self, revision): |
| 1000 | return'%s/%s%s_%s.zip'%(PERF_BASE_URL, self.listing_platform_dir, |
| 1001 | self.archive_extract_dir, revision) |
| 1002 | |
Kuan Huang | c8dd77a | 2024-08-12 22:13:42 | [diff] [blame] | 1003 | |
Kuan Huang | c63de40 | 2024-08-12 22:15:27 | [diff] [blame] | 1004 | classSnapshotBuild(ArchiveBuildWithCommitPosition): |
Kuan Huang | 49a2c641 | 2024-08-12 22:12:33 | [diff] [blame] | 1005 | |
Kuan Huang | 2dd3a33f | 2024-12-13 17:52:52 | [diff] [blame] | 1006 | @property |
| 1007 | def base_url(self): |
| 1008 | return CHROMIUM_BASE_URL |
Kuan Huang | 49a2c641 | 2024-08-12 22:12:33 | [diff] [blame] | 1009 | |
| 1010 | @property |
| 1011 | def build_type(self): |
| 1012 | return'snapshot' |
| 1013 | |
Kuan Huang | a716032a | 2024-08-14 22:15:46 | [diff] [blame] | 1014 | def _get_marker_for_revision(self, revision): |
Kuan Huang | 49a2c641 | 2024-08-12 22:12:33 | [diff] [blame] | 1015 | return'%s%d'%(self.listing_platform_dir, revision) |
| 1016 | |
Kuan Huang | a716032a | 2024-08-14 22:15:46 | [diff] [blame] | 1017 | def _fetch_and_parse(self, url): |
Kuan Huang | 49a2c641 | 2024-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 Huang | 49a2c641 | 2024-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('}') |
| 1028 | if end_ns_pos==-1: |
| 1029 | raiseException('Could not locate end namespace for directory index') |
| 1030 | namespace= root_tag[:end_ns_pos+1] |
Kuan Huang | 49a2c641 | 2024-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') |
| 1036 | if 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 Huang | a716032a | 2024-08-14 22:15:46 | [diff] [blame] | 1040 | revision_re= re.compile(r'(\d+)') |
Kuan Huang | 49a2c641 | 2024-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 |
| 1046 | for prefixin all_prefixes: |
Kuan Huang | a716032a | 2024-08-14 22:15:46 | [diff] [blame] | 1047 | match= revision_re.search(prefix.text[prefix_len:]) |
| 1048 | if match: |
| 1049 | revisions.append(int(match[1])) |
| 1050 | return revisions, next_marker |
Kuan Huang | 49a2c641 | 2024-08-12 22:12:33 | [diff] [blame] | 1051 | |
| 1052 | def get_last_change_url(self): |
| 1053 | """Returns a URL to the LAST_CHANGE file.""" |
| 1054 | return self.base_url+'/'+ self.listing_platform_dir+'LAST_CHANGE' |
| 1055 | |
Kuan Huang | c8dd77a | 2024-08-12 22:13:42 | [diff] [blame] | 1056 | def _get_rev_list(self, min_rev=None, max_rev=None): |
Kuan Huang | a716032a | 2024-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. |
| 1060 | ifnot 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 |
| 1068 | if 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 Huang | 49a2c641 | 2024-08-12 22:12:33 | [diff] [blame] | 1071 | else: |
Kuan Huang | a716032a | 2024-08-14 22:15:46 | [diff] [blame] | 1072 | max_rev=None |
Kuan Huang | 49a2c641 | 2024-08-12 22:12:33 | [diff] [blame] | 1073 | |
Kuan Huang | a716032a | 2024-08-14 22:15:46 | [diff] [blame] | 1074 | revisions=[] |
| 1075 | whileTrue: |
Kuan Huang | 49a2c641 | 2024-08-12 22:12:33 | [diff] [blame] | 1076 | sys.stdout.write('\rFetching revisions at marker %s'% next_marker) |
| 1077 | sys.stdout.flush() |
Kuan Huang | a716032a | 2024-08-14 22:15:46 | [diff] [blame] | 1078 | new_revisions, next_marker= self._fetch_and_parse( |
| 1079 | self._get_listing_url(next_marker)) |
Kuan Huang | 49a2c641 | 2024-08-12 22:12:33 | [diff] [blame] | 1080 | revisions.extend(new_revisions) |
Kuan Huang | a716032a | 2024-08-14 22:15:46 | [diff] [blame] | 1081 | if max_revand new_revisionsand max_rev<= max(new_revisions): |
| 1082 | break |
| 1083 | ifnot next_marker: |
Kuan Huang | 49a2c641 | 2024-08-12 22:12:33 | [diff] [blame] | 1084 | break |
| 1085 | sys.stdout.write('\r') |
| 1086 | sys.stdout.flush() |
Kuan Huang | a716032a | 2024-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. |
| 1089 | if start_markeror next_marker: |
| 1090 | return[ |
| 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` |
| 1095 | else: |
| 1096 | return revisions |
Kuan Huang | 49a2c641 | 2024-08-12 22:12:33 | [diff] [blame] | 1097 | |
| 1098 | def _get_listing_url(self, marker=None): |
| 1099 | """Returns the URL for a directory listing, with an optional marker.""" |
| 1100 | marker_param='' |
| 1101 | if marker: |
| 1102 | marker_param='&marker='+ str(marker) |
| 1103 | return(self.base_url+'/?delimiter=/&prefix='+ |
| 1104 | self.listing_platform_dir+ marker_param) |
| 1105 | |
Kuan Huang | c8dd77a | 2024-08-12 22:13:42 | [diff] [blame] | 1106 | @property |
| 1107 | def _rev_list_cache_key(self): |
| 1108 | return self._get_listing_url() |
| 1109 | |
Kuan Huang | 8b5e33dc | 2024-08-15 17:51:24 | [diff] [blame] | 1110 | def get_download_url(self, revision): |
Kuan Huang | 7f55421d | 2024-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. |
| 1114 | if revision<591483: |
| 1115 | if self.platform=='chromeos': |
| 1116 | archive_name='chrome-linux.zip' |
| 1117 | elif self.platformin('win','win64'): |
| 1118 | archive_name='chrome-win32.zip' |
Kuan Huang | 89fe2eef | 2024-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 |
| 1122 | if self.chromedriver: |
| 1123 | return{ |
| 1124 | 'chrome': chrome_url, |
| 1125 | 'chromedriver': url_prefix+ self.chromedriver_archive_name, |
| 1126 | } |
| 1127 | return chrome_url |
Kuan Huang | 8b5e33dc | 2024-08-15 17:51:24 | [diff] [blame] | 1128 | |
Kuan Huang | 49a2c641 | 2024-08-12 22:12:33 | [diff] [blame] | 1129 | |
Kuan Huang | 2dd3a33f | 2024-12-13 17:52:52 | [diff] [blame] | 1130 | classChromeForTestingBuild(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 |
| 1139 | def base_url(self): |
| 1140 | return CHROME_FOR_TESTING_BASE_URL |
| 1141 | |
| 1142 | @property |
| 1143 | def build_type(self): |
| 1144 | return'cft' |
| 1145 | |
| 1146 | def _get_marker_for_revision(self, revision): |
| 1147 | return'%sr%d'%(self.listing_platform_dir, revision) |
| 1148 | |
| 1149 | def 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 |
| 1153 | if self.chromedriver: |
| 1154 | return{ |
| 1155 | 'chrome': chrome_url, |
| 1156 | 'chromedriver': url_prefix+ self.chromedriver_archive_name, |
| 1157 | } |
| 1158 | return chrome_url |
| 1159 | |
| 1160 | |
Kuan Huang | 49a2c641 | 2024-08-12 22:12:33 | [diff] [blame] | 1161 | classASANBuild(SnapshotBuild): |
| 1162 | """ASANBuilds works like SnapshotBuild which fetch from commondatastorage, but |
Kuan Huang | a716032a | 2024-08-14 22:15:46 | [diff] [blame] | 1163 | with a different listing url.""" |
Kuan Huang | 49a2c641 | 2024-08-12 22:12:33 | [diff] [blame] | 1164 | |
| 1165 | def __init__(self, options): |
| 1166 | super().__init__(options) |
Kuan Huang | 49a2c641 | 2024-08-12 22:12:33 | [diff] [blame] | 1167 | self.asan_build_type='release' |
Kuan Huang | 49a2c641 | 2024-08-12 22:12:33 | [diff] [blame] | 1168 | |
| 1169 | @property |
Kuan Huang | 2dd3a33f | 2024-12-13 17:52:52 | [diff] [blame] | 1170 | def base_url(self): |
| 1171 | return ASAN_BASE_URL |
| 1172 | |
| 1173 | @property |
Kuan Huang | 49a2c641 | 2024-08-12 22:12:33 | [diff] [blame] | 1174 | def build_type(self): |
| 1175 | return'asan' |
| 1176 | |
| 1177 | defGetASANPlatformDir(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".""" |
| 1181 | if self.platform=='win': |
| 1182 | return'win32' |
| 1183 | else: |
| 1184 | return self.platform |
| 1185 | |
| 1186 | defGetASANBaseName(self): |
| 1187 | """Returns the base name of the ASAN zip file.""" |
Kuan Huang | a716032a | 2024-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 Huang | 49a2c641 | 2024-08-12 22:12:33 | [diff] [blame] | 1190 | if'linux'in self.platform: |
| 1191 | return'asan-symbolized-%s-%s'%(self.GetASANPlatformDir(), |
| 1192 | self.asan_build_type) |
| 1193 | else: |
| 1194 | return'asan-%s-%s'%(self.GetASANPlatformDir(), self.asan_build_type) |
| 1195 | |
| 1196 | def get_last_change_url(self): |
| 1197 | # LAST_CHANGE is not supported in asan build. |
| 1198 | returnNone |
| 1199 | |
| 1200 | def _get_listing_url(self, marker=None): |
| 1201 | """Returns the URL for a directory listing, with an optional marker.""" |
| 1202 | marker_param='' |
| 1203 | if marker: |
| 1204 | marker_param='&marker='+ str(marker) |
Kuan Huang | a716032a | 2024-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. |
| 1209 | return self.base_url+'/?delimiter=.zip&prefix='+ prefix+ marker_param |
Kuan Huang | 49a2c641 | 2024-08-12 22:12:33 | [diff] [blame] | 1210 | |
Kuan Huang | a716032a | 2024-08-14 22:15:46 | [diff] [blame] | 1211 | def _get_marker_for_revision(self, revision): |
Kuan Huang | 49a2c641 | 2024-08-12 22:12:33 | [diff] [blame] | 1212 | # The build type is hardcoded as release in the original code. |
Kuan Huang | a716032a | 2024-08-14 22:15:46 | [diff] [blame] | 1213 | return'%s-%s/%s-%d.zip'%(self.GetASANPlatformDir(), self.asan_build_type, |
| 1214 | self.GetASANBaseName(), revision) |
Kuan Huang | 49a2c641 | 2024-08-12 22:12:33 | [diff] [blame] | 1215 | |
Kuan Huang | 8b5e33dc | 2024-08-15 17:51:24 | [diff] [blame] | 1216 | def get_download_url(self, revision): |
| 1217 | return'%s/%s'%(self.base_url, self._get_marker_for_revision(revision)) |
| 1218 | |
Kuan Huang | 49a2c641 | 2024-08-12 22:12:33 | [diff] [blame] | 1219 | |
Kuan Huang | d608f159 | 2024-08-15 23:31:35 | [diff] [blame] | 1220 | classAndroidBuildMixin: |
| 1221 | |
| 1222 | def __init__(self, options): |
| 1223 | super().__init__(options) |
| 1224 | self.apk= options.apk |
| 1225 | self.device=InitializeAndroidDevice(options.device_id, self.apk,None) |
Kuan Huang | fb55c27 | 2024-09-16 20:56:42 | [diff] [blame] | 1226 | self.flag_changer=None |
Kuan Huang | d608f159 | 2024-08-15 23:31:35 | [diff] [blame] | 1227 | ifnot self.device: |
| 1228 | raiseBisectException('Failed to initialize device.') |
Kuan Huang | feb4fa55 | 2024-08-21 20:40:28 | [diff] [blame] | 1229 | self.binary_name= self._get_apk_filename() |
| 1230 | |
Kuan Huang | beba95a5 | 2025-01-06 23:10:10 | [diff] [blame] | 1231 | def _get_apk_mapping(self): |
Kuan Huang | feb4fa55 | 2024-08-21 20:40:28 | [diff] [blame] | 1232 | sdk= self.device.build_version_sdk |
Kuan Huang | feb4fa55 | 2024-08-21 20:40:28 | [diff] [blame] | 1233 | if'webview'in self.apk.lower(): |
Kuan Huang | beba95a5 | 2025-01-06 23:10:10 | [diff] [blame] | 1234 | return WEBVIEW_APK_FILENAMES |
Kuan Huang | feb4fa55 | 2024-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. |
| 1238 | elif sdk< version_codes.LOLLIPOP: |
Kuan Huang | beba95a5 | 2025-01-06 23:10:10 | [diff] [blame] | 1239 | return CHROME_APK_FILENAMES |
Kuan Huang | feb4fa55 | 2024-08-21 20:40:28 | [diff] [blame] | 1240 | elif sdk< version_codes.NOUGAT: |
Kuan Huang | beba95a5 | 2025-01-06 23:10:10 | [diff] [blame] | 1241 | return CHROME_MODERN_APK_FILENAMES |
Kuan Huang | feb4fa55 | 2024-08-21 20:40:28 | [diff] [blame] | 1242 | else: |
Kuan Huang | beba95a5 | 2025-01-06 23:10:10 | [diff] [blame] | 1243 | return MONOCHROME_APK_FILENAMES |
| 1244 | |
| 1245 | def _get_apk_filename(self): |
| 1246 | apk_mapping= self._get_apk_mapping() |
Kuan Huang | feb4fa55 | 2024-08-21 20:40:28 | [diff] [blame] | 1247 | if self.apknotin apk_mapping: |
| 1248 | raiseBisectException( |
| 1249 | 'Bisecting on Android only supported for these apks: [%s].'% |
| 1250 | '|'.join(apk_mapping)) |
| 1251 | return apk_mapping[self.apk] |
Kuan Huang | d608f159 | 2024-08-15 23:31:35 | [diff] [blame] | 1252 | |
Kuan Huang | beba95a5 | 2025-01-06 23:10:10 | [diff] [blame] | 1253 | def _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()} |
| 1258 | for apk_pathin glob.glob(self._get_extract_binary_glob(tempdir,"*")): |
| 1259 | apk_name= os.path.basename(apk_path) |
Mark Mentovai | 3738988 | 2025-06-16 22:08:28 | [diff] [blame] | 1260 | ifnot re.search(r'\.apks?$', apk_name): |
Kuan Huang | beba95a5 | 2025-01-06 23:10:10 | [diff] [blame] | 1261 | continue |
| 1262 | all_apks.append(apk_name) |
| 1263 | if apk_namein reversed_apk_mapping: |
| 1264 | available_apks.append(reversed_apk_mapping[apk_name]) |
| 1265 | if available_apks: |
| 1266 | print(f"The list of available --apk: {{{','.join(available_apks)}}}") |
| 1267 | elif all_apks: |
| 1268 | print("No supported apk found. But found following APK(s): " |
| 1269 | f"{{{','.join(all_apks)}}}") |
| 1270 | else: |
| 1271 | print("No APK(s) found.") |
| 1272 | |
Kuan Huang | d608f159 | 2024-08-15 23:31:35 | [diff] [blame] | 1273 | def _install_revision(self, download, tempdir): |
Kuan Huang | beba95a5 | 2025-01-06 23:10:10 | [diff] [blame] | 1274 | UnzipFilenameToDir(download, tempdir) |
| 1275 | apk_path= glob.glob(self._get_extract_binary_glob(tempdir)) |
| 1276 | if len(apk_path)==0: |
| 1277 | self._show_available_apks(tempdir) |
| 1278 | raiseBisectException(f'Can not find {self.binary_name} from {tempdir}') |
| 1279 | InstallOnAndroid(self.device, apk_path[0]) |
Kuan Huang | d608f159 | 2024-08-15 23:31:35 | [diff] [blame] | 1280 | |
Kuan Huang | 89fe2eef | 2024-09-13 23:04:02 | [diff] [blame] | 1281 | def _launch_revision(self, tempdir, executables, args=()): |
Kuan Huang | fb55c27 | 2024-09-16 20:56:42 | [diff] [blame] | 1282 | if args: |
| 1283 | if self.apknotin chrome.PACKAGE_INFO: |
| 1284 | raiseBisectException( |
| 1285 | f'Launching args are not supported for {self.apk}') |
| 1286 | ifnot 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 Huang | d608f159 | 2024-08-15 23:31:35 | [diff] [blame] | 1290 | LaunchOnAndroid(self.device, self.apk) |
| 1291 | return(0, sys.stdout, sys.stderr) |
| 1292 | |
Kuan Huang | beba95a5 | 2025-01-06 23:10:10 | [diff] [blame] | 1293 | def _get_extract_binary_glob(self, tempdir, binary_name=None): |
| 1294 | if binary_nameisNone: |
| 1295 | binary_name= self.binary_name |
| 1296 | return'%s/*/apks/%s'%(tempdir, binary_name) |
Kuan Huang | d608f159 | 2024-08-15 23:31:35 | [diff] [blame] | 1297 | |
| 1298 | |
Kuan Huang | feb4fa55 | 2024-08-21 20:40:28 | [diff] [blame] | 1299 | classAndroidTrichromeMixin(AndroidBuildMixin): |
| 1300 | |
| 1301 | def __init__(self, options): |
| 1302 | self._64bit_platforms=('android-arm64','android-x64', |
| 1303 | 'android-arm64-high') |
| 1304 | super().__init__(options) |
| 1305 | if self.device.build_version_sdk< version_codes.Q: |
| 1306 | raiseBisectException("Trichrome is only supported after Android Q.") |
| 1307 | self.library_binary_name= self._get_library_filename() |
| 1308 | |
Kuan Huang | beba95a5 | 2025-01-06 23:10:10 | [diff] [blame] | 1309 | def _get_apk_mapping(self, prefer_64bit=True): |
Kuan Huang | feb4fa55 | 2024-08-21 20:40:28 | [diff] [blame] | 1310 | if self.platformin self._64bit_platformsand prefer_64bit: |
Kuan Huang | beba95a5 | 2025-01-06 23:10:10 | [diff] [blame] | 1311 | return TRICHROME64_APK_FILENAMES |
Kuan Huang | feb4fa55 | 2024-08-21 20:40:28 | [diff] [blame] | 1312 | else: |
Kuan Huang | beba95a5 | 2025-01-06 23:10:10 | [diff] [blame] | 1313 | return TRICHROME_APK_FILENAMES |
| 1314 | |
| 1315 | def _get_apk_filename(self, prefer_64bit=True): |
| 1316 | apk_mapping= self._get_apk_mapping(prefer_64bit) |
Kuan Huang | feb4fa55 | 2024-08-21 20:40:28 | [diff] [blame] | 1317 | if self.apknotin apk_mapping: |
| 1318 | raiseBisectException( |
| 1319 | 'Bisecting on Android only supported for these apks: [%s].'% |
| 1320 | '|'.join(apk_mapping)) |
| 1321 | return apk_mapping[self.apk] |
| 1322 | |
| 1323 | def _get_library_filename(self, prefer_64bit=True): |
| 1324 | apk_mapping=None |
| 1325 | if self.platformin self._64bit_platformsand prefer_64bit: |
| 1326 | apk_mapping= TRICHROME64_LIBRARY_FILENAMES |
| 1327 | else: |
| 1328 | apk_mapping= TRICHROME_LIBRARY_FILENAMES |
| 1329 | if self.apknotin apk_mapping: |
| 1330 | raiseBisectException( |
| 1331 | 'Bisecting for Android Trichrome only supported for these apks: [%s].' |
| 1332 | %'|'.join(apk_mapping)) |
| 1333 | return apk_mapping[self.apk] |
| 1334 | |
Kuan Huang | beba95a5 | 2025-01-06 23:10:10 | [diff] [blame] | 1335 | def _install_revision(self, download, tempdir): |
| 1336 | UnzipFilenameToDir(download, tempdir) |
| 1337 | trichrome_library_filename= self._get_library_filename() |
| 1338 | trichrome_library_path= glob.glob( |
| 1339 | f'{tempdir}/*/apks/{trichrome_library_filename}') |
| 1340 | if len(trichrome_library_path)==0: |
| 1341 | self._show_available_apks(tempdir) |
| 1342 | raiseBisectException( |
| 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}') |
| 1346 | if len(trichrome_path)==0: |
| 1347 | self._show_available_apks(tempdir) |
| 1348 | raiseBisectException(f'Can not find {trichrome_filename} from {tempdir}') |
| 1349 | InstallOnAndroid(self.device, trichrome_library_path[0]) |
| 1350 | InstallOnAndroid(self.device, trichrome_path[0]) |
| 1351 | |
Kuan Huang | feb4fa55 | 2024-08-21 20:40:28 | [diff] [blame] | 1352 | |
Kuan Huang | d608f159 | 2024-08-15 23:31:35 | [diff] [blame] | 1353 | classAndroidReleaseBuild(AndroidBuildMixin,ReleaseBuild): |
| 1354 | |
| 1355 | def __init__(self, options): |
| 1356 | super().__init__(options) |
| 1357 | self.signed= options.signed |
| 1358 | # We could download the apk directly from build bucket |
Kuan Huang | feb4fa55 | 2024-08-21 20:40:28 | [diff] [blame] | 1359 | self.archive_name= self.binary_name |
Kuan Huang | d608f159 | 2024-08-15 23:31:35 | [diff] [blame] | 1360 | |
| 1361 | def _get_release_bucket(self): |
| 1362 | if self.signed: |
| 1363 | return ANDROID_RELEASE_BASE_URL_SIGNED |
| 1364 | else: |
| 1365 | return ANDROID_RELEASE_BASE_URL |
| 1366 | |
| 1367 | def _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. |
| 1371 | ifnot min_revornot max_rev: |
| 1372 | raiseBisectException( |
| 1373 | "Could not found enough revisions for Android %s release channel."% |
| 1374 | self.apk) |
| 1375 | return super()._get_rev_list(min_rev, max_rev) |
| 1376 | |
| 1377 | def _install_revision(self, download, tempdir): |
| 1378 | # AndroidRelease build downloads the apks directly from GCS bucket. |
| 1379 | InstallOnAndroid(self.device, download) |
| 1380 | |
| 1381 | |
Kuan Huang | feb4fa55 | 2024-08-21 20:40:28 | [diff] [blame] | 1382 | classAndroidTrichromeReleaseBuild(AndroidTrichromeMixin,AndroidReleaseBuild): |
| 1383 | |
| 1384 | def __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 | |
| 1390 | def _get_library_filename(self, prefer_64bit=True): |
| 1391 | if self.apk=='chrome'and self.platform=='android-arm64-high': |
| 1392 | raiseBisectException('chrome debug build is not supported for %s'% |
| 1393 | self.platform) |
| 1394 | return super()._get_library_filename(prefer_64bit) |
| 1395 | |
| 1396 | def 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 Huang | 6f7f5794 | 2024-12-05 19:26:30 | [diff] [blame] | 1399 | if revision>=ChromiumVersion('112'): |
Kuan Huang | feb4fa55 | 2024-08-21 20:40:28 | [diff] [blame] | 1400 | trichrome= self.binary_name |
| 1401 | trichrome_library= self.library_binary_name |
| 1402 | else: |
| 1403 | trichrome= self._get_apk_filename(prefer_64bit=False) |
| 1404 | trichrome_library= self._get_library_filename(prefer_64bit=False) |
| 1405 | return{ |
| 1406 | 'trichrome': self._get_archive_path(revision, trichrome), |
| 1407 | 'trichrome_library': self._get_archive_path(revision, |
| 1408 | trichrome_library), |
| 1409 | } |
| 1410 | |
| 1411 | def _install_revision(self, download, tempdir): |
| 1412 | ifnot isinstance(download, dict): |
| 1413 | raiseException("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. |
| 1416 | InstallOnAndroid(self.device, download['trichrome_library']) |
| 1417 | InstallOnAndroid(self.device, download['trichrome']) |
| 1418 | |
| 1419 | |
| 1420 | classAndroidTrichromeOfficialBuild(AndroidTrichromeMixin,OfficialBuild): |
| 1421 | |
Trung Nguyen | d391d05 | 2025-06-04 09:35:09 | [diff] [blame] | 1422 | def __init__(self, options): |
| 1423 | super().__init__(options) |
| 1424 | if'webview'in options.apk.lower(): |
| 1425 | # Trichrome APKs targets were introduced in crrev.com/c/5719255 |
| 1426 | if int(options.good)<1334017or int(options.bad)<1334017: |
| 1427 | raiseBisectException( |
| 1428 | "Bisecting WebView only supports version >= 1334017") |
| 1429 | |
| 1430 | |
Kuan Huang | beba95a5 | 2025-01-06 23:10:10 | [diff] [blame] | 1431 | def _get_apk_mapping(self, prefer_64bit=True): |
| 1432 | return{ |
| 1433 | k: v.replace(".apks",".minimal.apks") |
| 1434 | for k, vin super()._get_apk_mapping(prefer_64bit).items() |
| 1435 | } |
Kuan Huang | feb4fa55 | 2024-08-21 20:40:28 | [diff] [blame] | 1436 | |
| 1437 | |
Kuan Huang | d608f159 | 2024-08-15 23:31:35 | [diff] [blame] | 1438 | classLinuxReleaseBuild(ReleaseBuild): |
| 1439 | |
| 1440 | def _get_extra_args(self): |
| 1441 | args= super()._get_extra_args() |
| 1442 | # The sandbox must be run as root on release Chrome, so bypass it. |
| 1443 | if self.platform.startswith('linux'): |
| 1444 | args.append('--no-sandbox') |
| 1445 | return args |
| 1446 | |
| 1447 | |
| 1448 | classAndroidOfficialBuild(AndroidBuildMixin,OfficialBuild): |
| 1449 | pass |
| 1450 | |
| 1451 | |
| 1452 | classAndroidSnapshotBuild(AndroidBuildMixin,SnapshotBuild): |
| 1453 | pass |
| 1454 | |
| 1455 | |
Kuan Huang | 2277007 | 2024-08-27 20:06:53 | [diff] [blame] | 1456 | classIOSReleaseBuild(ReleaseBuild): |
| 1457 | |
| 1458 | def __init__(self, options): |
| 1459 | super().__init__(options) |
| 1460 | self.signed= options.signed |
| 1461 | ifnot self.signed: |
| 1462 | print('WARNING: --signed is recommended for iOS release builds.') |
| 1463 | self.device_id= options.device_id |
| 1464 | ifnot self.device_id: |
| 1465 | raiseBisectException('--device-id is required for iOS builds.') |
| 1466 | self.ipa= options.ipa |
| 1467 | ifnot self.ipa: |
| 1468 | raiseBisectException('--ipa is required for iOS builds.') |
| 1469 | if self.ipa.endswith('.ipa'): |
| 1470 | self.ipa= self.ipa[:-4] |
| 1471 | self.binary_name= self.archive_name= f'{self.ipa}.ipa' |
| 1472 | |
| 1473 | def _get_release_bucket(self): |
| 1474 | if self.signed: |
| 1475 | return IOS_RELEASE_BASE_URL_SIGNED |
| 1476 | return IOS_RELEASE_BASE_URL |
| 1477 | |
| 1478 | def _get_archive_path(self, build_number, archive_name=None): |
| 1479 | if 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. |
| 1486 | return(f'{self._get_release_bucket()}/{build_number}/*/' |
| 1487 | f'{self.listing_platform_dir.rstrip("/")}/*/{archive_name}') |
| 1488 | |
Kuan Huang | 2277007 | 2024-08-27 20:06:53 | [diff] [blame] | 1489 | def _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 | ]) |
| 1495 | if retcode: |
| 1496 | raiseBisectException(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. |
| 1500 | UnzipFilenameToDir(download, tempdir) |
| 1501 | plist= glob.glob(f'{tempdir}/Payload/*/Info.plist') |
| 1502 | ifnot plist: |
| 1503 | raiseBisectException(f'Could not find Info.plist from {tempdir}.') |
| 1504 | retcode, stdout, stderr= self._run( |
| 1505 | ['plutil','-extract','CFBundleIdentifier','raw', plist[0]]) |
| 1506 | if retcode: |
| 1507 | raiseBisectException(f'Extract bundle identifier error, code:{retcode}\n' |
| 1508 | f'stdout:\n{stdout}\n' |
| 1509 | f'stderr:\n{stderr}') |
| 1510 | bundle_identifier= stdout.strip() |
| 1511 | return bundle_identifier |
| 1512 | |
| 1513 | def _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 | ]) |
| 1518 | if retcode: |
| 1519 | print(f'Warning: App launching error, code:{retcode}\n' |
| 1520 | f'stdout:\n{stdout}\n' |
| 1521 | f'stderr:\n{stderr}') |
| 1522 | return retcode, stdout, stderr |
| 1523 | |
| 1524 | |
Kuan Huang | b4c8df5 | 2024-09-05 17:59:18 | [diff] [blame] | 1525 | classIOSSimulatorReleaseBuild(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 | |
| 1532 | def __init__(self, options): |
| 1533 | super().__init__(options) |
| 1534 | self.device_id= options.device_id |
| 1535 | ifnot self.device_id: |
| 1536 | raiseBisectException('--device-id is required for iOS Simulator.') |
Kuan Huang | 725c9305 | 2024-10-10 19:33:10 | [diff] [blame] | 1537 | retcode, stdout, stderr= self._run( |
| 1538 | ['xcrun','simctl','boot', self.device_id]) |
| 1539 | if retcode: |
| 1540 | print(f'Warning: Boot Simulator error, code:{retcode}\n' |
| 1541 | f'stdout:\n{stdout}\n' |
| 1542 | f'stderr:\n{stderr}') |
Kuan Huang | b4c8df5 | 2024-09-05 17:59:18 | [diff] [blame] | 1543 | |
| 1544 | def _get_release_bucket(self): |
| 1545 | return IOS_ARCHIVE_BASE_URL |
| 1546 | |
| 1547 | def _get_archive_path(self, build_number, archive_name=None): |
| 1548 | if 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. |
| 1554 | return f'{self._get_release_bucket()}/{build_number}/*/{archive_name}' |
| 1555 | |
| 1556 | def _get_extract_binary_glob(self, tempdir): |
| 1557 | return f'{tempdir}/{self.binary_name}' |
| 1558 | |
| 1559 | def _install_revision(self, download, tempdir): |
Kuan Huang | 89fe2eef | 2024-09-13 23:04:02 | [diff] [blame] | 1560 | executables= super()._install_revision(download, tempdir) |
| 1561 | executable= executables['chrome'] |
Kuan Huang | b4c8df5 | 2024-09-05 17:59:18 | [diff] [blame] | 1562 | # install app |
| 1563 | retcode, stdout, stderr= self._run( |
| 1564 | ['xcrun','simctl','install', self.device_id, executable]) |
| 1565 | if retcode: |
| 1566 | raiseBisectException(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') |
| 1571 | ifnot plist: |
| 1572 | raiseBisectException(f'Could not find Info.plist from {executable}.') |
| 1573 | retcode, stdout, stderr= self._run( |
| 1574 | ['plutil','-extract','CFBundleIdentifier','raw', plist[0]]) |
| 1575 | if retcode: |
| 1576 | raiseBisectException(f'Extract bundle identifier error, code:{retcode}\n' |
| 1577 | f'stdout:\n{stdout}\n' |
| 1578 | f'stderr:\n{stderr}') |
| 1579 | bundle_identifier= stdout.strip() |
| 1580 | return bundle_identifier |
| 1581 | |
| 1582 | def _launch_revision(self, tempdir, bundle_identifier, args=()): |
| 1583 | retcode, stdout, stderr= self._run( |
| 1584 | ['xcrun','simctl','launch', self.device_id, bundle_identifier,*args]) |
| 1585 | if retcode: |
| 1586 | print(f'Warning: App launching error, code:{retcode}\n' |
| 1587 | f'stdout:\n{stdout}\n' |
| 1588 | f'stderr:\n{stderr}') |
| 1589 | return retcode, stdout, stderr |
| 1590 | |
| 1591 | |
Kuan Huang | d608f159 | 2024-08-15 23:31:35 | [diff] [blame] | 1592 | def create_archive_build(options): |
Kuan Huang | 37d0daa | 2024-09-19 18:03:08 | [diff] [blame] | 1593 | if options.build_type=='release': |
Kuan Huang | feb4fa55 | 2024-08-21 20:40:28 | [diff] [blame] | 1594 | if options.archive=='android-arm64-high': |
| 1595 | returnAndroidTrichromeReleaseBuild(options) |
| 1596 | elif options.archive.startswith('android'): |
Kuan Huang | d608f159 | 2024-08-15 23:31:35 | [diff] [blame] | 1597 | returnAndroidReleaseBuild(options) |
| 1598 | elif options.archive.startswith('linux'): |
| 1599 | returnLinuxReleaseBuild(options) |
Kuan Huang | b4c8df5 | 2024-09-05 17:59:18 | [diff] [blame] | 1600 | elif options.archive=='ios-simulator': |
| 1601 | returnIOSSimulatorReleaseBuild(options) |
| 1602 | elif options.archive=='ios': |
Kuan Huang | 2277007 | 2024-08-27 20:06:53 | [diff] [blame] | 1603 | returnIOSReleaseBuild(options) |
Kuan Huang | 49a2c641 | 2024-08-12 22:12:33 | [diff] [blame] | 1604 | returnReleaseBuild(options) |
Kuan Huang | 37d0daa | 2024-09-19 18:03:08 | [diff] [blame] | 1605 | elif options.build_type=='official': |
Kuan Huang | feb4fa55 | 2024-08-21 20:40:28 | [diff] [blame] | 1606 | if options.archive=='android-arm64-high': |
| 1607 | returnAndroidTrichromeOfficialBuild(options) |
| 1608 | elif options.archive.startswith('android'): |
Kuan Huang | d608f159 | 2024-08-15 23:31:35 | [diff] [blame] | 1609 | returnAndroidOfficialBuild(options) |
Kuan Huang | 49a2c641 | 2024-08-12 22:12:33 | [diff] [blame] | 1610 | returnOfficialBuild(options) |
Kuan Huang | 37d0daa | 2024-09-19 18:03:08 | [diff] [blame] | 1611 | elif options.build_type=='asan': |
Kuan Huang | d608f159 | 2024-08-15 23:31:35 | [diff] [blame] | 1612 | # ASANBuild is only supported on win/linux/mac. |
Kuan Huang | 49a2c641 | 2024-08-12 22:12:33 | [diff] [blame] | 1613 | returnASANBuild(options) |
Kuan Huang | 2dd3a33f | 2024-12-13 17:52:52 | [diff] [blame] | 1614 | elif options.build_type=='cft': |
| 1615 | returnChromeForTestingBuild(options) |
Kuan Huang | 49a2c641 | 2024-08-12 22:12:33 | [diff] [blame] | 1616 | else: |
Kuan Huang | d608f159 | 2024-08-15 23:31:35 | [diff] [blame] | 1617 | if options.archive.startswith('android'): |
| 1618 | returnAndroidSnapshotBuild(options) |
Kuan Huang | 49a2c641 | 2024-08-12 22:12:33 | [diff] [blame] | 1619 | returnSnapshotBuild(options) |
| 1620 | |
| 1621 | |
prasadv | 2375e6d | 2017-03-20 19:23:23 | [diff] [blame] | 1622 | defIsMac(): |
| 1623 | return sys.platform.startswith('darwin') |
| 1624 | |
| 1625 | |
ihf@chromium.org | fc3702e | 2013-11-09 04:23:00 | [diff] [blame] | 1626 | defUnzipFilenameToDir(filename, directory): |
| 1627 | """Unzip |filename| to |directory|.""" |
szager@google.com | afe3066 | 2011-07-30 01:05:52 | [diff] [blame] | 1628 | cwd= os.getcwd() |
| 1629 | ifnot os.path.isabs(filename): |
| 1630 | filename= os.path.join(cwd, filename) |
nirnimesh@chromium.org | bd8dcb9 | 2010-03-31 01:05:24 | [diff] [blame] | 1631 | # Make base. |
ihf@chromium.org | fc3702e | 2013-11-09 04:23:00 | [diff] [blame] | 1632 | ifnot os.path.isdir(directory): |
| 1633 | os.mkdir(directory) |
| 1634 | os.chdir(directory) |
prasadv | 2375e6d | 2017-03-20 19:23:23 | [diff] [blame] | 1635 | |
Kuan Huang | b4c8df5 | 2024-09-05 17:59:18 | [diff] [blame] | 1636 | # Support for tar archives. |
| 1637 | if tarfile.is_tarfile(filename): |
| 1638 | tf= tarfile.open(filename,'r') |
| 1639 | tf.extractall(directory) |
| 1640 | os.chdir(cwd) |
| 1641 | return |
| 1642 | |
prasadv | 2375e6d | 2017-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. |
| 1645 | ifIsMac(): |
| 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) |
| 1651 | return |
| 1652 | |
| 1653 | zf= zipfile.ZipFile(filename) |
vitalybuka@chromium.org | e29c08c | 2012-09-17 20:50:50 | [diff] [blame] | 1654 | # Extract files. |
| 1655 | for infoin zf.infolist(): |
| 1656 | name= info.filename |
| 1657 | if name.endswith('/'):# dir |
| 1658 | ifnot os.path.isdir(name): |
| 1659 | os.makedirs(name) |
| 1660 | else:# file |
ihf@chromium.org | fc3702e | 2013-11-09 04:23:00 | [diff] [blame] | 1661 | directory= os.path.dirname(name) |
Daniel Cheng | 0826fa9 | 2024-03-23 21:52:34 | [diff] [blame] | 1662 | if directoryandnot os.path.isdir(directory): |
ihf@chromium.org | fc3702e | 2013-11-09 04:23:00 | [diff] [blame] | 1663 | os.makedirs(directory) |
vitalybuka@chromium.org | e29c08c | 2012-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 Dawson | 039777ef | 2020-10-26 20:34:47 | [diff] [blame] | 1668 | os.chmod(name, info.external_attr>>16) |
vitalybuka@chromium.org | e29c08c | 2012-09-17 20:50:50 | [diff] [blame] | 1669 | os.chdir(cwd) |
nirnimesh@chromium.org | bd8dcb9 | 2010-03-31 01:05:24 | [diff] [blame] | 1670 | |
rsesek@chromium.org | 67e0bc6 | 2009-09-03 22:06:09 | [diff] [blame] | 1671 | |
Sven Zheng | 8e07956 | 2024-05-10 20:11:06 | [diff] [blame] | 1672 | def gsutil_download(download_url, filename): |
| 1673 | command=['cp', download_url, filename] |
| 1674 | RunGsutilCommand(command) |
| 1675 | |
| 1676 | |
Kuan Huang | c2fec79 | 2024-09-03 21:57:38 | [diff] [blame] | 1677 | defEvaluateRevision(archive_build, download, revision, args, evaluate): |
| 1678 | """fetch.wait_for(), archive_build.run_revision() and evaluate the result.""" |
| 1679 | whileTrue: |
| 1680 | exit_status= stdout= stderr=None |
| 1681 | # Create a temp directory and unzip the revision into it. |
| 1682 | with tempfile.TemporaryDirectory(prefix='bisect_tmp')as tempdir: |
| 1683 | # On Windows 10, file system needs to be readable from App Container. |
| 1684 | if 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 |
| 1692 | print(f'Trying revision {revision!s}: {download!s} in {tempdir!s}') |
| 1693 | try: |
| 1694 | exit_status, stdout, stderr= archive_build.run_revision( |
| 1695 | download, tempdir, args) |
| 1696 | exceptSystemExit: |
| 1697 | raise |
| 1698 | exceptException: |
| 1699 | traceback.print_exc(file=sys.stderr) |
| 1700 | # evaluate |
| 1701 | answer= evaluate(revision, exit_status, stdout, stderr) |
| 1702 | if answer!='r': |
| 1703 | return answer |
evan@chromium.org | 79f1474 | 2010-03-10 01:01:57 | [diff] [blame] | 1704 | |
maruel@chromium.org | cb155a8 | 2011-11-29 17:25:34 | [diff] [blame] | 1705 | |
Sven Zheng | 8e07956 | 2024-05-10 20:11:06 | [diff] [blame] | 1706 | # The arguments release_builds, status, stdout and stderr are unused. |
qyearsley@chromium.org | 4df583c | 2014-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 Huang | d608f159 | 2024-08-15 23:31:35 | [diff] [blame] | 1710 | defAskIsGoodBuild(rev, exit_status, stdout, stderr): |
qyearsley@chromium.org | 4df583c | 2014-07-31 17:11:55 | [diff] [blame] | 1711 | """Asks the user whether build |rev| is good or bad.""" |
evan@chromium.org | 79f1474 | 2010-03-10 01:01:57 | [diff] [blame] | 1712 | # Loop until we get a response that we can parse. |
rsesek@chromium.org | 67e0bc6 | 2009-09-03 22:06:09 | [diff] [blame] | 1713 | whileTrue: |
Sven Zheng | 8e07956 | 2024-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)) |
wangxianzhu | d8c4c56 | 2015-12-15 23:39:51 | [diff] [blame] | 1717 | if responsein('g','b','r','u'): |
asvitkine@chromium.org | 53bb634 | 2012-06-01 04:11:00 | [diff] [blame] | 1718 | return response |
wangxianzhu | d8c4c56 | 2015-12-15 23:39:51 | [diff] [blame] | 1719 | if response=='q': |
szager@google.com | afe3066 | 2011-07-30 01:05:52 | [diff] [blame] | 1720 | raiseSystemExit() |
wangxianzhu | d8c4c56 | 2015-12-15 23:39:51 | [diff] [blame] | 1721 | if response=='s': |
Sven Zheng | 8e07956 | 2024-05-10 20:11:06 | [diff] [blame] | 1722 | print(stdout) |
| 1723 | print(stderr) |
rsesek@chromium.org | 67e0bc6 | 2009-09-03 22:06:09 | [diff] [blame] | 1724 | |
maruel@chromium.org | cb155a8 | 2011-11-29 17:25:34 | [diff] [blame] | 1725 | |
Kuan Huang | d608f159 | 2024-08-15 23:31:35 | [diff] [blame] | 1726 | defIsGoodASANBuild(rev, exit_status, stdout, stderr): |
cmumford@chromium.org | 01188669 | 2014-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.""" |
| 1731 | if stderr: |
| 1732 | bad_count=0 |
| 1733 | for linein stderr.splitlines(): |
Raul Tambre | 57e09d6 | 2019-09-22 17:18:52 | [diff] [blame] | 1734 | print(line) |
cmumford@chromium.org | 01188669 | 2014-08-01 21:00:21 | [diff] [blame] | 1735 | if line.find('ERROR: AddressSanitizer:')!=-1: |
| 1736 | bad_count+=1 |
| 1737 | if bad_count>0: |
Raul Tambre | 57e09d6 | 2019-09-22 17:18:52 | [diff] [blame] | 1738 | print('Revision %d determined to be bad.'% rev) |
cmumford@chromium.org | 01188669 | 2014-08-01 21:00:21 | [diff] [blame] | 1739 | return'b' |
Kuan Huang | d608f159 | 2024-08-15 23:31:35 | [diff] [blame] | 1740 | returnAskIsGoodBuild(rev, exit_status, stdout, stderr) |
skobes | 21b5cdfb | 2016-03-21 23:13:02 | [diff] [blame] | 1741 | |
| 1742 | |
Kuan Huang | d608f159 | 2024-08-15 23:31:35 | [diff] [blame] | 1743 | defDidCommandSucceed(rev, exit_status, stdout, stderr): |
skobes | 21b5cdfb | 2016-03-21 23:13:02 | [diff] [blame] | 1744 | if exit_status: |
Raul Tambre | 57e09d6 | 2019-09-22 17:18:52 | [diff] [blame] | 1745 | print('Bad revision: %s'% rev) |
skobes | 21b5cdfb | 2016-03-21 23:13:02 | [diff] [blame] | 1746 | return'b' |
| 1747 | else: |
Raul Tambre | 57e09d6 | 2019-09-22 17:18:52 | [diff] [blame] | 1748 | print('Good revision: %s'% rev) |
skobes | 21b5cdfb | 2016-03-21 23:13:02 | [diff] [blame] | 1749 | return'g' |
| 1750 | |
cmumford@chromium.org | 01188669 | 2014-08-01 21:00:21 | [diff] [blame] | 1751 | |
Kuan Huang | 8b5e33dc | 2024-08-15 17:51:24 | [diff] [blame] | 1752 | classDownloadJob: |
Kuan Huang | feb4fa55 | 2024-08-21 20:40:28 | [diff] [blame] | 1753 | """ |
| 1754 | DownloadJob represents a task to download a given url. |
| 1755 | """ |
qyearsley@chromium.org | 4df583c | 2014-07-31 17:11:55 | [diff] [blame] | 1756 | |
Kuan Huang | 8b5e33dc | 2024-08-15 17:51:24 | [diff] [blame] | 1757 | def __init__(self, url, rev, name=None): |
Kuan Huang | feb4fa55 | 2024-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 | """ |
| 1765 | if isinstance(url, dict): |
| 1766 | self.is_multiple=True |
| 1767 | self.urls= url |
| 1768 | else: |
| 1769 | self.is_multiple=False |
| 1770 | self.urls={None: url} |
asvitkine@chromium.org | 53bb634 | 2012-06-01 04:11:00 | [diff] [blame] | 1771 | self.rev= rev |
Kuan Huang | 8b5e33dc | 2024-08-15 17:51:24 | [diff] [blame] | 1772 | self.name= name |
| 1773 | |
Kuan Huang | feb4fa55 | 2024-08-21 20:40:28 | [diff] [blame] | 1774 | self.results={} |
Kuan Huang | c3a4cd1e | 2024-10-03 21:17:31 | [diff] [blame] | 1775 | self.exc_info=None# capture exception from worker thread |
asvitkine@chromium.org | 53bb634 | 2012-06-01 04:11:00 | [diff] [blame] | 1776 | self.quit_event= threading.Event() |
| 1777 | self.progress_event= threading.Event() |
qyearsley@chromium.org | 4df583c | 2014-07-31 17:11:55 | [diff] [blame] | 1778 | self.thread=None |
asvitkine@chromium.org | 53bb634 | 2012-06-01 04:11:00 | [diff] [blame] | 1779 | |
Kuan Huang | 8b5e33dc | 2024-08-15 17:51:24 | [diff] [blame] | 1780 | def _clear_up_tmp_files(self): |
Kuan Huang | feb4fa55 | 2024-08-21 20:40:28 | [diff] [blame] | 1781 | ifnot self.results: |
| 1782 | return |
| 1783 | for tmp_filein self.results.values(): |
Kuan Huang | 8b5e33dc | 2024-08-15 17:51:24 | [diff] [blame] | 1784 | try: |
Kuan Huang | feb4fa55 | 2024-08-21 20:40:28 | [diff] [blame] | 1785 | os.unlink(tmp_file) |
Kuan Huang | 8b5e33dc | 2024-08-15 17:51:24 | [diff] [blame] | 1786 | exceptFileNotFoundError: |
| 1787 | # Handle missing archives. |
| 1788 | pass |
Kuan Huang | feb4fa55 | 2024-08-21 20:40:28 | [diff] [blame] | 1789 | self.results=None |
asvitkine@chromium.org | 53bb634 | 2012-06-01 04:11:00 | [diff] [blame] | 1790 | |
Kuan Huang | 8b5e33dc | 2024-08-15 17:51:24 | [diff] [blame] | 1791 | def __del__(self): |
| 1792 | self._clear_up_tmp_files() |
| 1793 | |
| 1794 | def _report_hook(self, blocknum, blocksize, totalsize): |
| 1795 | if self.quit_eventand self.quit_event.is_set(): |
| 1796 | raiseRuntimeError('Aborting download of revision %s'% str(self.rev)) |
| 1797 | ifnot self.progress_eventornot self.progress_event.is_set(): |
| 1798 | return |
| 1799 | size= blocknum* blocksize |
| 1800 | if totalsize==-1:# Total size not known. |
| 1801 | progress='Received %d bytes'% size |
| 1802 | else: |
| 1803 | size= min(totalsize, size) |
| 1804 | progress='Received %d of %d bytes, %.2f%%'%(size, totalsize, |
| 1805 | 100.0* size/ totalsize) |
| 1806 | # Send a \r to let all progress messages use just one line of output. |
| 1807 | print(progress, end='\r', flush=True) |
| 1808 | |
Kuan Huang | feb4fa55 | 2024-08-21 20:40:28 | [diff] [blame] | 1809 | def _fetch(self, url, tmp_file): |
| 1810 | if url.startswith('gs'): |
| 1811 | gsutil_download(url, tmp_file) |
| 1812 | else: |
| 1813 | urllib.request.urlretrieve(url, tmp_file, self._report_hook) |
| 1814 | if self.progress_eventand self.progress_event.is_set(): |
| 1815 | print() |
| 1816 | |
Kuan Huang | 8b5e33dc | 2024-08-15 17:51:24 | [diff] [blame] | 1817 | def fetch(self): |
| 1818 | try: |
Kuan Huang | feb4fa55 | 2024-08-21 20:40:28 | [diff] [blame] | 1819 | for key, urlin self.urls.items(): |
Kuan Huang | b4c8df5 | 2024-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 Huang | feb4fa55 | 2024-08-21 20:40:28 | [diff] [blame] | 1824 | self.results[key]= tmp_file |
| 1825 | os.close(fd) |
| 1826 | self._fetch(url, tmp_file) |
Kuan Huang | 8b5e33dc | 2024-08-15 17:51:24 | [diff] [blame] | 1827 | exceptRuntimeError: |
| 1828 | pass |
Kuan Huang | c3a4cd1e | 2024-10-03 21:17:31 | [diff] [blame] | 1829 | exceptBaseException: |
| 1830 | self.exc_info= sys.exc_info() |
Kuan Huang | 8b5e33dc | 2024-08-15 17:51:24 | [diff] [blame] | 1831 | |
| 1832 | def start(self): |
| 1833 | """Start the download in a thread.""" |
| 1834 | assert self.threadisNone,"DownloadJob is already started." |
| 1835 | self.thread= threading.Thread(target=self.fetch, name=self.name) |
| 1836 | self.thread.start() |
| 1837 | return self |
| 1838 | |
| 1839 | def stop(self): |
asvitkine@chromium.org | 53bb634 | 2012-06-01 04:11:00 | [diff] [blame] | 1840 | """Stops the download which must have been started previously.""" |
qyearsley@chromium.org | 4df583c | 2014-07-31 17:11:55 | [diff] [blame] | 1841 | assert self.thread,'DownloadJob must be started before Stop is called.' |
asvitkine@chromium.org | 53bb634 | 2012-06-01 04:11:00 | [diff] [blame] | 1842 | self.quit_event.set() |
| 1843 | self.thread.join() |
Kuan Huang | 8b5e33dc | 2024-08-15 17:51:24 | [diff] [blame] | 1844 | self._clear_up_tmp_files() |
asvitkine@chromium.org | 53bb634 | 2012-06-01 04:11:00 | [diff] [blame] | 1845 | |
Kuan Huang | 8b5e33dc | 2024-08-15 17:51:24 | [diff] [blame] | 1846 | def 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 | """ |
| 1850 | ifnot self.thread: |
| 1851 | self.start() |
Raul Tambre | 57e09d6 | 2019-09-22 17:18:52 | [diff] [blame] | 1852 | print('Downloading revision %s...'% str(self.rev)) |
asvitkine@chromium.org | 53bb634 | 2012-06-01 04:11:00 | [diff] [blame] | 1853 | self.progress_event.set()# Display progress of download. |
rob | 8a4543f | 2016-01-20 00:43:59 | [diff] [blame] | 1854 | try: |
Bruce Dawson | 039777ef | 2020-10-26 20:34:47 | [diff] [blame] | 1855 | while self.thread.is_alive(): |
rob | 8a4543f | 2016-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 Huang | c3a4cd1e | 2024-10-03 21:17:31 | [diff] [blame] | 1859 | if self.exc_info: |
| 1860 | raise self.exc_info[1].with_traceback(self.exc_info[2]) |
Kuan Huang | feb4fa55 | 2024-08-21 20:40:28 | [diff] [blame] | 1861 | if self.quit_event.is_set(): |
| 1862 | raiseException('The DownloadJob was stopped.') |
| 1863 | if self.is_multiple: |
| 1864 | return self.results |
| 1865 | else: |
| 1866 | return self.results[None] |
rob | 8a4543f | 2016-01-20 00:43:59 | [diff] [blame] | 1867 | except(KeyboardInterrupt,SystemExit): |
Kuan Huang | 8b5e33dc | 2024-08-15 17:51:24 | [diff] [blame] | 1868 | self.stop() |
rob | 8a4543f | 2016-01-20 00:43:59 | [diff] [blame] | 1869 | raise |
asvitkine@chromium.org | 53bb634 | 2012-06-01 04:11:00 | [diff] [blame] | 1870 | |
| 1871 | |
Kuan Huang | d608f159 | 2024-08-15 23:31:35 | [diff] [blame] | 1872 | defBisect(archive_build, |
szager@chromium.org | 60ac66e3 | 2011-07-18 16:08:25 | [diff] [blame] | 1873 | try_args=(), |
skobes | 21b5cdfb | 2016-03-21 23:13:02 | [diff] [blame] | 1874 | evaluate=AskIsGoodBuild, |
Kuan Huang | d608f159 | 2024-08-15 23:31:35 | [diff] [blame] | 1875 | verify_range=False): |
Sven Zheng | 8e07956 | 2024-05-10 20:11:06 | [diff] [blame] | 1876 | """Runs a binary search on to determine the last known good revision. |
szager@chromium.org | 60ac66e3 | 2011-07-18 16:08:25 | [diff] [blame] | 1877 | |
Sven Zheng | 8e07956 | 2024-05-10 20:11:06 | [diff] [blame] | 1878 | Args: |
Kuan Huang | 49a2c641 | 2024-08-12 22:12:33 | [diff] [blame] | 1879 | archive_build: ArchiveBuild object initialized with user provided |
| 1880 | parameters. |
Sven Zheng | 8e07956 | 2024-05-10 20:11:06 | [diff] [blame] | 1881 | try_args: A tuple of arguments to pass to the test application. |
Sven Zheng | 8e07956 | 2024-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.com | afe3066 | 2011-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.com | afe3066 | 2011-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.com | afe3066 | 2011-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.org | 60ac66e3 | 2011-07-18 16:08:25 | [diff] [blame] | 1898 | """ |
Sven Zheng | 8e07956 | 2024-05-10 20:11:06 | [diff] [blame] | 1899 | print('Downloading list of known revisions.', end=' ') |
| 1900 | print('If the range is large, this can take several minutes...') |
Kuan Huang | d608f159 | 2024-08-15 23:31:35 | [diff] [blame] | 1901 | ifnot archive_build.use_local_cache: |
Raul Tambre | 57e09d6 | 2019-09-22 17:18:52 | [diff] [blame] | 1902 | print('(use --use-local-cache to cache and re-use the list of revisions)') |
pdr@chromium.org | 28a3c12 | 2014-08-09 11:04:51 | [diff] [blame] | 1903 | else: |
Raul Tambre | 57e09d6 | 2019-09-22 17:18:52 | [diff] [blame] | 1904 | print() |
Kuan Huang | c2fec79 | 2024-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. |
| 1907 | if archive_build.good_revision> archive_build.bad_revision: |
| 1908 | rev_list= rev_list[::-1] |
Kuan Huang | b790d5d | 2024-11-07 21:53:00 | [diff] [blame] | 1909 | ifIsVersionNumber(rev_list[0]): |
| 1910 | change_log_url_fn=GetReleaseChangeLogURL |
| 1911 | else: |
| 1912 | change_log_url_fn=GetShortChangeLogURL |
skobes | 21b5cdfb | 2016-03-21 23:13:02 | [diff] [blame] | 1913 | |
| 1914 | if verify_range: |
Kuan Huang | c2fec79 | 2024-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() |
skobes | 21b5cdfb | 2016-03-21 23:13:02 | [diff] [blame] | 1919 | try: |
Kuan Huang | c2fec79 | 2024-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) |
| 1923 | if answer!='g': |
| 1924 | print(f'Expecting revision {rev_list[0]} to be good but got {answer}. ' |
| 1925 | 'Please make sure the --good is a good revision.') |
| 1926 | raiseSystemExit |
| 1927 | bad_download= bad_rev_fetch.wait_for() |
| 1928 | answer=EvaluateRevision(archive_build, bad_download, rev_list[-1], |
| 1929 | try_args, evaluate) |
| 1930 | if answer!='b': |
| 1931 | print(f'Expecting revision {rev_list[-1]} to be bad but got {answer}. ' |
| 1932 | 'Please make sure that the issue can be reproduced for --bad.') |
| 1933 | raiseSystemExit |
skobes | 21b5cdfb | 2016-03-21 23:13:02 | [diff] [blame] | 1934 | except(KeyboardInterrupt,SystemExit): |
Raul Tambre | 57e09d6 | 2019-09-22 17:18:52 | [diff] [blame] | 1935 | print('Cleaning up...') |
Kuan Huang | c2fec79 | 2024-09-03 21:57:38 | [diff] [blame] | 1936 | returnNone,None |
skobes | 21b5cdfb | 2016-03-21 23:13:02 | [diff] [blame] | 1937 | finally: |
Kuan Huang | c2fec79 | 2024-09-03 21:57:38 | [diff] [blame] | 1938 | good_rev_fetch.stop() |
| 1939 | bad_rev_fetch.stop() |
skobes | 21b5cdfb | 2016-03-21 23:13:02 | [diff] [blame] | 1940 | |
Kuan Huang | c2fec79 | 2024-09-03 21:57:38 | [diff] [blame] | 1941 | prefetch={} |
| 1942 | try: |
| 1943 | while len(rev_list)>2: |
Kuan Huang | 008965f9 | 2024-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 Huang | c2fec79 | 2024-09-03 21:57:38 | [diff] [blame] | 1946 | print('You have %d revisions with about %d steps left.'% |
Kuan Huang | 008965f9 | 2024-10-08 21:53:15 | [diff] [blame] | 1947 | (len(rev_list),((len(rev_list)-2).bit_length()))) |
Kuan Huang | b790d5d | 2024-11-07 21:53:00 | [diff] [blame] | 1948 | change_log_url="" |
| 1949 | if(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])})" |
| 1951 | print('Bisecting range [%s (bad), %s (good)]%s.'%( |
| 1952 | rev_list[-1], rev_list[0], change_log_url)) |
Kuan Huang | c2fec79 | 2024-09-03 21:57:38 | [diff] [blame] | 1953 | # clean prefetch to keep only the valid fetches |
| 1954 | for keyin list(prefetch.keys()): |
| 1955 | if keynotin rev_list: |
| 1956 | prefetch.pop(key).stop() |
| 1957 | # get next revision to evaluate from prefetch |
| 1958 | if 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) |
| 1963 | for 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 |
| 1967 | if 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.org | 53bb634 | 2012-06-01 04:11:00 | [diff] [blame] | 1972 | else: |
Kuan Huang | c2fec79 | 2024-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 Huang | d1f29a1 | 2024-10-31 20:00:11 | [diff] [blame] | 1975 | |
Kuan Huang | c2fec79 | 2024-09-03 21:57:38 | [diff] [blame] | 1976 | try: |
Kuan Huang | c2fec79 | 2024-09-03 21:57:38 | [diff] [blame] | 1977 | download= fetch.wait_for() |
Kuan Huang | d1f29a1 | 2024-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] |
| 1980 | if 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] |
| 1985 | if 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 Huang | c2fec79 | 2024-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. |
| 1992 | if answer=='g':# good |
| 1993 | rev_list= rev_list[pivot:] |
| 1994 | elif 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] |
| 1998 | elif answer=='u':# unknown |
| 1999 | # Nuke the revision from the rev_list. |
| 2000 | rev_list.pop(pivot) |
| 2001 | else: |
| 2002 | assertFalse,'Unexpected return value from evaluate(): '+ answer |
| 2003 | finally: |
Kuan Huang | 8b5e33dc | 2024-08-15 17:51:24 | [diff] [blame] | 2004 | fetch.stop() |
Kuan Huang | c2fec79 | 2024-09-03 21:57:38 | [diff] [blame] | 2005 | # end of `while len(rev_list) > 2` |
| 2006 | finally: |
| 2007 | for eachin prefetch.values(): |
| 2008 | each.stop() |
| 2009 | prefetch.clear() |
| 2010 | return sorted((rev_list[0], rev_list[-1])) |
szager@chromium.org | 60ac66e3 | 2011-07-18 16:08:25 | [diff] [blame] | 2011 | |
Kuan Huang | c8dd77a | 2024-08-12 22:13:42 | [diff] [blame] | 2012 | defGetChromiumRevision(url, default=999999999): |
vitalybuka@chromium.org | 801fb65 | 2012-07-20 20:13:50 | [diff] [blame] | 2013 | """Returns the chromium revision read from given URL.""" |
Kuan Huang | c8dd77a | 2024-08-12 22:13:42 | [diff] [blame] | 2014 | ifnot url: |
| 2015 | return default |
vitalybuka@chromium.org | 801fb65 | 2012-07-20 20:13:50 | [diff] [blame] | 2016 | try: |
| 2017 | # Location of the latest build revision number |
Sven Zheng | 8e07956 | 2024-05-10 20:11:06 | [diff] [blame] | 2018 | latest_revision= urllib.request.urlopen(url).read() |
alancutter@chromium.org | 5980b75 | 2014-07-02 00:34:40 | [diff] [blame] | 2019 | if latest_revision.isdigit(): |
| 2020 | return int(latest_revision) |
Kuan Huang | c8dd77a | 2024-08-12 22:13:42 | [diff] [blame] | 2021 | return default |
qyearsley@chromium.org | 4df583c | 2014-07-31 17:11:55 | [diff] [blame] | 2022 | exceptException: |
Raul Tambre | 57e09d6 | 2019-09-22 17:18:52 | [diff] [blame] | 2023 | print('Could not determine latest revision. This could be bad...') |
Kuan Huang | c8dd77a | 2024-08-12 22:13:42 | [diff] [blame] | 2024 | return default |
vitalybuka@chromium.org | 801fb65 | 2012-07-20 20:13:50 | [diff] [blame] | 2025 | |
Bruce Dawson | cd63ea2 | 2020-10-26 16:37:41 | [diff] [blame] | 2026 | |
Sven Zheng | 8e07956 | 2024-05-10 20:11:06 | [diff] [blame] | 2027 | defFetchJsonFromURL(url): |
| 2028 | """Returns JSON data from the given URL""" |
Sven Zheng | 8e07956 | 2024-05-10 20:11:06 | [diff] [blame] | 2029 | # Allow retry for 3 times for unexpected network error |
| 2030 | for iin range(3): |
Kuan Huang | c93ac26 | 2024-09-19 18:53:17 | [diff] [blame] | 2031 | try: |
Kuan Huang | 0505884 | 2024-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") |
| 2035 | return json.loads(data) |
Kuan Huang | c93ac26 | 2024-09-19 18:53:17 | [diff] [blame] | 2036 | except urllib.request.HTTPErroras e: |
| 2037 | print(f'urlopen {url} HTTPError: {e}') |
| 2038 | except json.JSONDecodeErroras e: |
| 2039 | print(f'urlopen {url} JSON decode error: {e}') |
Sven Zheng | 8e07956 | 2024-05-10 20:11:06 | [diff] [blame] | 2040 | returnNone |
Bruce Dawson | cd63ea2 | 2020-10-26 16:37:41 | [diff] [blame] | 2041 | |
pshenoy | cd6bd68 | 2014-09-10 20:50:22 | [diff] [blame] | 2042 | defGetGitHashFromSVNRevision(svn_revision): |
Sven Zheng | 8e07956 | 2024-05-10 20:11:06 | [diff] [blame] | 2043 | """Returns GitHash from SVN Revision""" |
pshenoy | cd6bd68 | 2014-09-10 20:50:22 | [diff] [blame] | 2044 | crrev_url= CRREV_URL+ str(svn_revision) |
Sven Zheng | 8e07956 | 2024-05-10 20:11:06 | [diff] [blame] | 2045 | data=FetchJsonFromURL(crrev_url) |
| 2046 | if dataand'git_sha'in data: |
| 2047 | return data['git_sha'] |
| 2048 | returnNone |
pshenoy | cd6bd68 | 2014-09-10 20:50:22 | [diff] [blame] | 2049 | |
Kuan Huang | b790d5d | 2024-11-07 21:53:00 | [diff] [blame] | 2050 | defGetShortChangeLogURL(rev1, rev2): |
| 2051 | min_rev, max_rev= sorted([rev1, rev2]) |
| 2052 | return SHORT_CHANGELOG_URL%(min_rev, max_rev) |
| 2053 | |
| 2054 | defGetChangeLogURL(rev1, rev2): |
pshenoy | 9ce271f | 2014-09-02 22:14:05 | [diff] [blame] | 2055 | """Prints the changelog URL.""" |
Kuan Huang | b790d5d | 2024-11-07 21:53:00 | [diff] [blame] | 2056 | min_rev, max_rev= sorted([rev1, rev2]) |
| 2057 | return CHANGELOG_URL%(GetGitHashFromSVNRevision(min_rev), |
| 2058 | GetGitHashFromSVNRevision(max_rev)) |
| 2059 | |
| 2060 | defGetReleaseChangeLogURL(version1, version2): |
| 2061 | """Prints the changelog URL.""" |
| 2062 | min_ver, max_ver= sorted([version1, version2]) |
| 2063 | return RELEASE_CHANGELOG_URL%(min_ver, max_ver) |
Raul Tambre | 57e09d6 | 2019-09-22 17:18:52 | [diff] [blame] | 2064 | |
pshenoy | 9ce271f | 2014-09-02 22:14:05 | [diff] [blame] | 2065 | |
Sven Zheng | 8e07956 | 2024-05-10 20:11:06 | [diff] [blame] | 2066 | defIsVersionNumber(revision): |
| 2067 | """Checks if provided revision is version_number""" |
Kuan Huang | 6f7f5794 | 2024-12-05 19:26:30 | [diff] [blame] | 2068 | if isinstance(revision,ChromiumVersion): |
Kuan Huang | b790d5d | 2024-11-07 21:53:00 | [diff] [blame] | 2069 | returnTrue |
| 2070 | ifnot isinstance(revision, str): |
| 2071 | returnFalse |
Sven Zheng | 8e07956 | 2024-05-10 20:11:06 | [diff] [blame] | 2072 | return re.match(r'^\d+\.\d+\.\d+\.\d+$', revision)isnotNone |
vitalybuka@chromium.org | 801fb65 | 2012-07-20 20:13:50 | [diff] [blame] | 2073 | |
Sven Zheng | 8e07956 | 2024-05-10 20:11:06 | [diff] [blame] | 2074 | |
Kuan Huang | 008965f9 | 2024-10-08 21:53:15 | [diff] [blame] | 2075 | defGetRevisionFromSourceTag(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','')) |
| 2084 | ifnot 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 Huang | 0505884 | 2024-10-24 21:22:26 | [diff] [blame] | 2088 | source_url= SOURCE_TAG_URL%(str(tag)+'^') |
Kuan Huang | 008965f9 | 2024-10-08 21:53:15 | [diff] [blame] | 2089 | data=FetchJsonFromURL(source_url) |
| 2090 | match= revision_regex.search(data.get('message','')) |
| 2091 | if match: |
| 2092 | return int(match.group(1)) |
| 2093 | |
| 2094 | |
Sven Zheng | 8e07956 | 2024-05-10 20:11:06 | [diff] [blame] | 2095 | defGetRevisionFromVersion(version): |
| 2096 | """Returns Base Commit Position from a version number""" |
| 2097 | chromiumdash_url= VERSION_INFO_URL% str(version) |
| 2098 | data=FetchJsonFromURL(chromiumdash_url) |
Kuan Huang | c93ac26 | 2024-09-19 18:53:17 | [diff] [blame] | 2099 | ifnot 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 Huang | 008965f9 | 2024-10-08 21:53:15 | [diff] [blame] | 2109 | if dataand data.get('chromium_main_branch_position'): |
Sven Zheng | 8e07956 | 2024-05-10 20:11:06 | [diff] [blame] | 2110 | return data['chromium_main_branch_position'] |
Kuan Huang | 008965f9 | 2024-10-08 21:53:15 | [diff] [blame] | 2111 | revision_from_source_tag=GetRevisionFromSourceTag(version) |
| 2112 | if revision_from_source_tag: |
| 2113 | return revision_from_source_tag |
| 2114 | raiseBisectException( |
| 2115 | f'Can not find revision for {version} from chromiumdash and source') |
Sven Zheng | 8e07956 | 2024-05-10 20:11:06 | [diff] [blame] | 2116 | |
| 2117 | |
Joel Hockey | 60c4f5b | 2024-07-15 22:03:08 | [diff] [blame] | 2118 | defGetRevisionFromMilestone(milestone): |
| 2119 | """Get revision (e.g. 782793) from milestone such as 85.""" |
Kuan Huang | c93ac26 | 2024-09-19 18:53:17 | [diff] [blame] | 2120 | response= urllib.request.urlopen(MILESTONES_URL% milestone) |
Joel Hockey | 60c4f5b | 2024-07-15 22:03:08 | [diff] [blame] | 2121 | milestones= json.loads(response.read()) |
| 2122 | for min milestones: |
Kuan Huang | 70f5efe6 | 2024-11-07 23:26:35 | [diff] [blame] | 2123 | if m['milestone']== milestoneand m.get('chromium_main_branch_position'): |
Joel Hockey | 60c4f5b | 2024-07-15 22:03:08 | [diff] [blame] | 2124 | return m['chromium_main_branch_position'] |
Kuan Huang | 008965f9 | 2024-10-08 21:53:15 | [diff] [blame] | 2125 | raiseBisectException(f'Can not find revision for milestone {milestone}') |
Joel Hockey | 60c4f5b | 2024-07-15 22:03:08 | [diff] [blame] | 2126 | |
| 2127 | |
| 2128 | defGetRevision(revision): |
Sven Zheng | 5a5a314 | 2024-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 Hockey | 60c4f5b | 2024-07-15 22:03:08 | [diff] [blame] | 2132 | if type(revision)== type(0): |
| 2133 | return revision |
| 2134 | ifIsVersionNumber(revision): |
| 2135 | returnGetRevisionFromVersion(revision) |
| 2136 | elif revision[:1].upper()=='M'and revision[1:].isdigit(): |
| 2137 | returnGetRevisionFromMilestone(int(revision[1:])) |
Sven Zheng | 5a5a314 | 2024-07-18 21:44:20 | [diff] [blame] | 2138 | # By default, we assume it's a commit position. |
| 2139 | return int(revision) |
Joel Hockey | 60c4f5b | 2024-07-15 22:03:08 | [diff] [blame] | 2140 | |
| 2141 | |
Sven Zheng | 8e07956 | 2024-05-10 20:11:06 | [diff] [blame] | 2142 | defCheckDepotToolsInPath(): |
| 2143 | delimiter=';'if sys.platform.startswith('win')else':' |
| 2144 | path_list= os.environ['PATH'].split(delimiter) |
| 2145 | for pathin path_list: |
| 2146 | if path.rstrip(os.path.sep).endswith('depot_tools'): |
| 2147 | return path |
| 2148 | returnNone |
| 2149 | |
| 2150 | |
| 2151 | defSetupEnvironment(options): |
| 2152 | global is_verbose |
Kuan Huang | d86de1a | 2024-07-10 18:45:40 | [diff] [blame] | 2153 | global GSUTILS_PATH |
Sven Zheng | 8e07956 | 2024-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 Huang | 37d0daa | 2024-09-19 18:03:08 | [diff] [blame] | 2159 | if(options.build_typein('release','official')andnot gsutil_path): |
Sven Zheng | 8e07956 | 2024-05-10 20:11:06 | [diff] [blame] | 2160 | raiseBisectException( |
| 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 Huang | d86de1a | 2024-07-10 18:45:40 | [diff] [blame] | 2165 | elif gsutil_path: |
| 2166 | GSUTILS_PATH= os.path.join(gsutil_path,'gsutil.py') |
Sven Zheng | 8e07956 | 2024-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 Wen | 9156ea40 | 2024-07-04 15:44:54 | [diff] [blame] | 2170 | if options.archive.startswith('android-'): |
Sven Zheng | 8e07956 | 2024-05-10 20:11:06 | [diff] [blame] | 2171 | SetupAndroidEnvironment() |
| 2172 | |
| 2173 | # Set up verbose logging if requested. |
| 2174 | if options.verbose: |
| 2175 | is_verbose=True |
| 2176 | |
| 2177 | |
| 2178 | defSetupAndroidEnvironment(): |
| 2179 | |
| 2180 | defSetupCatapult(): |
| 2181 | print('Setting up Catapult in %s.'% CATAPULT_DIR) |
| 2182 | print('Set the environment var CATAPULT_DIR to override ' |
| 2183 | 'Catapult directory.') |
| 2184 | if(os.path.exists(CATAPULT_DIR)): |
| 2185 | print('Updating Catapult...\n') |
| 2186 | process= subprocess.Popen(args=['git','pull','--rebase'], |
| 2187 | cwd=CATAPULT_DIR) |
| 2188 | exit_code= process.wait() |
| 2189 | if exit_code!=0: |
| 2190 | raiseBisectException('Android bisect requires Catapult repo checkout. ' |
| 2191 | 'Attempt to update Catapult failed.') |
| 2192 | else: |
| 2193 | print('Downloading Catapult...\n') |
| 2194 | process= subprocess.Popen( |
| 2195 | args=['git','clone', CATAPULT_REPO, CATAPULT_DIR]) |
| 2196 | exit_code= process.wait() |
| 2197 | if exit_code!=0: |
| 2198 | raiseBisectException('Android bisect requires Catapult repo checkout. ' |
| 2199 | 'Attempt to download Catapult failed.') |
| 2200 | |
| 2201 | SetupCatapult() |
| 2202 | sys.path.append(DEVIL_PATH) |
| 2203 | from 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. |
| 2218 | for i, jin devil_imports.items(): |
| 2219 | globals()[i]= importlib.import_module(j) |
| 2220 | |
| 2221 | print('Done setting up Catapult.\n') |
| 2222 | |
| 2223 | |
| 2224 | defInitializeAndroidDevice(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] |
| 2229 | if chrome_flags: |
| 2230 | flags= flag_changer.FlagChanger(device, |
| 2231 | chrome.PACKAGE_INFO[apk].cmdline_file) |
| 2232 | flags.AddFlags(chrome_flags) |
| 2233 | return device |
| 2234 | |
| 2235 | |
Peter Wen | b4ba3048 | 2024-07-08 19:10:40 | [diff] [blame] | 2236 | defInstallOnAndroid(device, apk_path): |
Sven Zheng | 8e07956 | 2024-05-10 20:11:06 | [diff] [blame] | 2237 | """Installs the chromium build on a given device.""" |
| 2238 | print('Installing %s on android device...'% apk_path) |
| 2239 | device.Install(apk_path) |
| 2240 | |
| 2241 | |
| 2242 | defLaunchOnAndroid(device, apk): |
| 2243 | """Launches the chromium build on a given device.""" |
| 2244 | if'webview'in apk: |
| 2245 | return |
| 2246 | |
| 2247 | print('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 | |
| 2255 | def_CreateCommandLineParser(): |
| 2256 | """Creates a parser with bisect options. |
| 2257 | |
| 2258 | Returns: |
| 2259 | An instance of argparse.ArgumentParser. |
| 2260 | """ |
Kuan Huang | 37d0daa | 2024-09-19 18:03:08 | [diff] [blame] | 2261 | description=""" |
| 2262 | Performs binary search on the chrome binaries to find a minimal range of \ |
Sven Zheng | 8e07956 | 2024-05-10 20:11:06 | [diff] [blame] | 2263 | revisions where a behavior change happened. |
Kuan Huang | 37d0daa | 2024-09-19 18:03:08 | [diff] [blame] | 2264 | The behaviors are described as "good" and "bad". It is NOT assumed that the \ |
Sven Zheng | 8e07956 | 2024-05-10 20:11:06 | [diff] [blame] | 2265 | behavior of the later revision is the bad one. |
| 2266 | |
| 2267 | Revision 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 Huang | 37d0daa | 2024-09-19 18:03:08 | [diff] [blame] | 2270 | Use chromium_main_branch_position from \ |
| 2271 | https://chromiumdash.appspot.com/fetch_version?version=<chrome_version> |
| 2272 | Please Note: Chrome's about: build number and chromiumdash branch \ |
| 2273 | revision are incorrect, they are from branches. |
Sven Zheng | 8e07956 | 2024-05-10 20:11:06 | [diff] [blame] | 2274 | |
| 2275 | Tip: add "-- --no-first-run" to bypass the first run prompts. |
| 2276 | """ |
| 2277 | |
Kuan Huang | 37d0daa | 2024-09-19 18:03:08 | [diff] [blame] | 2278 | parser= argparse.ArgumentParser( |
| 2279 | formatter_class=argparse.RawTextHelpFormatter, description=description) |
evan@chromium.org | 1a45d22 | 2009-09-19 01:58:57 | [diff] [blame] | 2280 | # Strangely, the default help output doesn't include the choice list. |
Kuan Huang | 147e34e | 2024-07-10 23:17:18 | [diff] [blame] | 2281 | choices= sorted( |
| 2282 | set(archfor buildin PATH_CONTEXTfor archin PATH_CONTEXT[build])) |
Kuan Huang | 37d0daa | 2024-09-19 18:03:08 | [diff] [blame] | 2283 | parser.add_argument( |
| 2284 | '-a', |
| 2285 | '--archive', |
| 2286 | choices=choices, |
| 2287 | metavar='ARCHIVE', |
Kuan Huang | 37d0daa | 2024-09-19 18:03:08 | [diff] [blame] | 2288 | help='The buildbot platform to bisect {%s}.'%','.join(choices), |
| 2289 | ) |
pshenoy@chromium.org | b3b2051 | 2013-08-26 18:51:04 | [diff] [blame] | 2290 | |
Kuan Huang | 37d0daa | 2024-09-19 18:03:08 | [diff] [blame] | 2291 | build_type_group= parser.add_mutually_exclusive_group() |
| 2292 | build_type_group.add_argument( |
Kuan Huang | 9a1ad2b | 2024-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 Huang | 37d0daa | 2024-09-19 18:03:08 | [diff] [blame] | 2301 | '-r', |
| 2302 | dest='build_type', |
| 2303 | action='store_const', |
| 2304 | const='release', |
Kuan Huang | 37d0daa | 2024-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 Huang | 2dd3a33f | 2024-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 Huang | 37d0daa | 2024-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 Ihlenfeldt | b7e3597 | 2025-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 Huang | 37d0daa | 2024-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 Huang | beba95a5 | 2025-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 Huang | 37d0daa | 2024-09-19 18:03:08 | [diff] [blame] | 2424 | parser.add_argument( |
| 2425 | '--apk', |
| 2426 | choices=apk_choices, |
| 2427 | dest='apk', |
Kuan Huang | 908c5521 | 2025-01-06 23:09:01 | [diff] [blame] | 2428 | # default='chromium', when using android archives |
Kuan Huang | 37d0daa | 2024-09-19 18:03:08 | [diff] [blame] | 2429 | metavar='{chromium,chrome_dev,android_webview...}', |
Kuan Huang | 908c5521 | 2025-01-06 23:09:01 | [diff] [blame] | 2430 | help=(f'Apk you want to bisect {{{",".join(apk_choices)}}}. ' |
| 2431 | '(Default: chromium/chrome)'), |
Kuan Huang | 37d0daa | 2024-09-19 18:03:08 | [diff] [blame] | 2432 | ) |
| 2433 | parser.add_argument( |
| 2434 | '--ipa', |
| 2435 | dest='ipa', |
Kuan Huang | 908c5521 | 2025-01-06 23:09:01 | [diff] [blame] | 2436 | # default='canary.ipa', when using ios archives |
Kuan Huang | 37d0daa | 2024-09-19 18:03:08 | [diff] [blame] | 2437 | metavar='{canary,beta,stable...}', |
Kuan Huang | 908c5521 | 2025-01-06 23:09:01 | [diff] [blame] | 2438 | help='ipa you want to bisect. (Default: canary)', |
Kuan Huang | 37d0daa | 2024-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 Huang | 342fec5 | 2024-10-31 18:00:44 | [diff] [blame] | 2457 | action=UpdateScriptAction, |
| 2458 | nargs=0, |
Kuan Huang | 37d0daa | 2024-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 Zheng | 8e07956 | 2024-05-10 20:11:06 | [diff] [blame] | 2467 | return parser |
mmoss@chromium.org | 7ad66a7 | 2009-09-04 17:52:33 | [diff] [blame] | 2468 | |
Sven Zheng | 8e07956 | 2024-05-10 20:11:06 | [diff] [blame] | 2469 | |
Kuan Huang | 908c5521 | 2025-01-06 23:09:01 | [diff] [blame] | 2470 | def_DetectArchive(opts=None): |
Kuan Huang | 0d24cfd9 | 2024-07-12 16:50:30 | [diff] [blame] | 2471 | """Detect the buildbot archive to use based on local environment.""" |
Kuan Huang | 908c5521 | 2025-01-06 23:09:01 | [diff] [blame] | 2472 | if opts: |
| 2473 | if opts.apk: |
| 2474 | return'android-arm64' |
| 2475 | elif opts.ipa: |
| 2476 | return'ios-simulator' |
| 2477 | |
Kuan Huang | 0d24cfd9 | 2024-07-12 16:50:30 | [diff] [blame] | 2478 | os_name=None |
| 2479 | plat= sys.platform |
| 2480 | if plat.startswith('linux'): |
| 2481 | os_name='linux' |
| 2482 | elif platin('win32','cygwin'): |
| 2483 | os_name='win' |
| 2484 | elif plat=='darwin': |
| 2485 | os_name='mac' |
| 2486 | |
| 2487 | arch=None |
| 2488 | machine= platform.machine().lower() |
| 2489 | if machine.startswith(('arm','aarch')): |
| 2490 | arch='arm' |
| 2491 | elif machinein('amd64','x86_64'): |
| 2492 | arch='x64' |
| 2493 | elif machinein('i386','i686','i86pc','x86'): |
| 2494 | arch='x86' |
| 2495 | |
| 2496 | return PLATFORM_ARCH_TO_ARCHIVE_MAPPING.get((os_name, arch),None) |
| 2497 | |
| 2498 | |
Sven Zheng | 8e07956 | 2024-05-10 20:11:06 | [diff] [blame] | 2499 | defParseCommandLine(args=None): |
| 2500 | """Parses the command line for bisect options.""" |
Sven Zheng | 8e07956 | 2024-05-10 20:11:06 | [diff] [blame] | 2501 | parser=_CreateCommandLineParser() |
Kuan Huang | 37d0daa | 2024-09-19 18:03:08 | [diff] [blame] | 2502 | opts= parser.parse_args(args) |
Sven Zheng | e3441db | 2024-05-13 19:59:32 | [diff] [blame] | 2503 | |
mmoss@chromium.org | 7ad66a7 | 2009-09-04 17:52:33 | [diff] [blame] | 2504 | if opts.archiveisNone: |
Kuan Huang | 908c5521 | 2025-01-06 23:09:01 | [diff] [blame] | 2505 | archive=_DetectArchive(opts) |
Kuan Huang | 0d24cfd9 | 2024-07-12 16:50:30 | [diff] [blame] | 2506 | if archive: |
| 2507 | print('The buildbot archive (-a/--archive) detected as:', archive) |
| 2508 | opts.archive= archive |
| 2509 | else: |
Kuan Huang | 37d0daa | 2024-09-19 18:03:08 | [diff] [blame] | 2510 | parser.error('Error: Missing required parameter: --archive') |
| 2511 | |
| 2512 | if opts.archivenotin PATH_CONTEXT[opts.build_type]: |
Kuan Huang | 9a1ad2b | 2024-09-30 23:16:31 | [diff] [blame] | 2513 | supported_build_types=[ |
| 2514 | "%s(%s)"%(b,BuildTypeToCommandLineArgument(b, omit_default=False)) |
| 2515 | for 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.org | 7ad66a7 | 2009-09-04 17:52:33 | [diff] [blame] | 2522 | |
Kuan Huang | 908c5521 | 2025-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 Zheng | 8e07956 | 2024-05-10 20:11:06 | [diff] [blame] | 2527 | |
Kuan Huang | 908c5521 | 2025-01-06 23:09:01 | [diff] [blame] | 2528 | # Set default apk/ipa for mobile platforms. |
| 2529 | if opts.archivein android_archivesandnot opts.apk: |
| 2530 | opts.apk='chromium'if opts.build_type=='snapshot'else'chrome' |
| 2531 | elif opts.archivein ios_archivesandnot opts.ipa: |
| 2532 | opts.ipa='canary' |
| 2533 | # Or raise error if apk/ipa is set for non-mobile platforms. |
| 2534 | if opts.apkand opts.archivenotin android_archives: |
| 2535 | parser.error('--apk is only supported for Android platform (-a/--archive): ' |
| 2536 | f'{{{",".join(android_archives)}}}') |
| 2537 | elif opts.ipaand opts.archivenotin ios_archives: |
| 2538 | parser.error('--ipa is only supported for iOS platform (-a/--archive): ' |
| 2539 | f'{{{",".join(ios_archives)}}}') |
| 2540 | |
| 2541 | if 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)}}}') |
| 2545 | elif opts.signedandnot opts.build_type=='release': |
Kuan Huang | 37d0daa | 2024-09-19 18:03:08 | [diff] [blame] | 2546 | parser.error('--signed is only supported for release bisection.') |
Sven Zheng | 8e07956 | 2024-05-10 20:11:06 | [diff] [blame] | 2547 | |
Kuan Huang | 37d0daa | 2024-09-19 18:03:08 | [diff] [blame] | 2548 | if opts.build_type=='official': |
Sven Zheng | 8e07956 | 2024-05-10 20:11:06 | [diff] [blame] | 2549 | print('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.org | 01188669 | 2014-08-01 21:00:21 | [diff] [blame] | 2553 | |
Kuan Huang | 95f6df07 | 2024-09-12 20:56:42 | [diff] [blame] | 2554 | ifnot opts.good: |
Kuan Huang | 37d0daa | 2024-09-19 18:03:08 | [diff] [blame] | 2555 | parser.error('Please specify a good version.') |
Kuan Huang | 95f6df07 | 2024-09-12 20:56:42 | [diff] [blame] | 2556 | |
Kuan Huang | 37d0daa | 2024-09-19 18:03:08 | [diff] [blame] | 2557 | if opts.build_type=='release': |
Kuan Huang | 95f6df07 | 2024-09-12 20:56:42 | [diff] [blame] | 2558 | ifnot opts.bad: |
Kuan Huang | 37d0daa | 2024-09-19 18:03:08 | [diff] [blame] | 2559 | parser.error('Please specify a bad version.') |
Kuan Huang | 95f6df07 | 2024-09-12 20:56:42 | [diff] [blame] | 2560 | ifnotIsVersionNumber(opts.good)ornotIsVersionNumber(opts.bad): |
Kuan Huang | 37d0daa | 2024-09-19 18:03:08 | [diff] [blame] | 2561 | parser.error('For release, you can only use chrome version to bisect.') |
Kuan Huang | 95f6df07 | 2024-09-12 20:56:42 | [diff] [blame] | 2562 | if 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. |
| 2566 | if'_'notin opts.apk: |
| 2567 | print('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 Nguyen | d391d05 | 2025-06-04 09:35:09 | [diff] [blame] | 2573 | if opts.apkand'webview'in opts.apk: |
| 2574 | if 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 Huang | 95f6df07 | 2024-09-12 20:56:42 | [diff] [blame] | 2579 | if opts.times<1: |
Kuan Huang | 37d0daa | 2024-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 Huang | 95f6df07 | 2024-09-12 20:56:42 | [diff] [blame] | 2582 | |
Kuan Huang | 37d0daa | 2024-09-19 18:03:08 | [diff] [blame] | 2583 | return opts |
karen@chromium.org | 4c6fec6b | 2013-09-17 17:44:08 | [diff] [blame] | 2584 | |
mikecase | a8cd284c | 2014-12-02 21:30:58 | [diff] [blame] | 2585 | |
Kuan Huang | 9a1ad2b | 2024-09-30 23:16:31 | [diff] [blame] | 2586 | defBuildTypeToCommandLineArgument(build_type, omit_default=True): |
| 2587 | """Convert the build_type back to command line argument.""" |
| 2588 | if build_type=='release': |
| 2589 | return'-r' |
| 2590 | elif build_type=='official': |
| 2591 | return'-o' |
| 2592 | elif build_type=='snapshot': |
| 2593 | ifnot omit_default: |
| 2594 | return'-s' |
| 2595 | else: |
| 2596 | return'' |
| 2597 | elif build_type=='asan': |
| 2598 | return'--asan' |
| 2599 | else: |
| 2600 | raiseValueError(f'Unknown build type: {build_type}') |
| 2601 | |
| 2602 | |
Kuan Huang | 95f6df07 | 2024-09-12 20:56:42 | [diff] [blame] | 2603 | defGenerateCommandLine(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 Huang | 9a1ad2b | 2024-09-30 23:16:31 | [diff] [blame] | 2625 | args.append(BuildTypeToCommandLineArgument(opts.build_type)) |
Kuan Huang | 95f6df07 | 2024-09-12 20:56:42 | [diff] [blame] | 2626 | if opts.archive: |
| 2627 | args.extend(['-a', opts.archive]) |
| 2628 | if opts.signed: |
| 2629 | args.append('--signed') |
| 2630 | if opts.good: |
| 2631 | args.extend(['-g', opts.good]) |
| 2632 | if opts.bad: |
| 2633 | args.extend(['-b', opts.bad]) |
| 2634 | if opts.verify_range: |
| 2635 | args.append('--verify-range') |
| 2636 | return['./%s'% os.path.relpath(__file__)]+ args+ remaining_args |
| 2637 | |
| 2638 | |
| 2639 | defMaybeSwitchBuildType(opts, good, bad): |
| 2640 | """Generate and print suggestions to use official build to bisect for a more |
| 2641 | precise range when possible.""" |
Kuan Huang | 37d0daa | 2024-09-19 18:03:08 | [diff] [blame] | 2642 | if opts.build_type!='release': |
Kuan Huang | 95f6df07 | 2024-09-12 20:56:42 | [diff] [blame] | 2643 | return |
| 2644 | if opts.archivenotin PATH_CONTEXT['official']: |
| 2645 | return |
| 2646 | new_opts= copy.deepcopy(opts) |
Kuan Huang | 95f6df07 | 2024-09-12 20:56:42 | [diff] [blame] | 2647 | new_opts.signed=False# --signed is only supported by release builds |
Kuan Huang | 37d0daa | 2024-09-19 18:03:08 | [diff] [blame] | 2648 | new_opts.build_type='official' |
Kuan Huang | 95f6df07 | 2024-09-12 20:56:42 | [diff] [blame] | 2649 | new_opts.verify_range=True# always verify_range when switching the build |
Kuan Huang | 6f7f5794 | 2024-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 Huang | 95f6df07 | 2024-09-12 20:56:42 | [diff] [blame] | 2652 | rev_list=None |
| 2653 | if opts.use_local_cache: |
| 2654 | print('Checking available official builds (-o) for %s.'% new_opts.archive) |
| 2655 | archive_build= create_archive_build(new_opts) |
| 2656 | try: |
| 2657 | rev_list= archive_build.get_rev_list() |
| 2658 | exceptBisectExceptionas e: |
| 2659 | # We don't have enough builds from official builder, skip suggesting. |
| 2660 | print("But we don't have more builds from official builder.") |
| 2661 | return |
| 2662 | if len(rev_list)<=2: |
| 2663 | print("But we don't have more builds from official builder.") |
| 2664 | return |
| 2665 | if rev_list: |
| 2666 | print( |
| 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]))) |
| 2670 | else: |
| 2671 | print( |
| 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 Huang | 94c4a2d1 | 2024-11-21 21:46:09 | [diff] [blame] | 2675 | print(join_args(command_line)) |
Kuan Huang | 95f6df07 | 2024-09-12 20:56:42 | [diff] [blame] | 2676 | return command_line |
| 2677 | |
| 2678 | |
Kuan Huang | 342fec5 | 2024-10-31 18:00:44 | [diff] [blame] | 2679 | classUpdateScriptAction(argparse.Action): |
| 2680 | def __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') |
| 2687 | with open(script_path,"w")as f: |
| 2688 | f.write(script_content) |
| 2689 | print("Update successful!") |
| 2690 | exit(0) |
Sven Zheng | e3441db | 2024-05-13 19:59:32 | [diff] [blame] | 2691 | |
| 2692 | |
Sven Zheng | 8e07956 | 2024-05-10 20:11:06 | [diff] [blame] | 2693 | def main(): |
Kuan Huang | 37d0daa | 2024-09-19 18:03:08 | [diff] [blame] | 2694 | opts=ParseCommandLine() |
Sven Zheng | 8e07956 | 2024-05-10 20:11:06 | [diff] [blame] | 2695 | |
Sven Zheng | 8e07956 | 2024-05-10 20:11:06 | [diff] [blame] | 2696 | try: |
| 2697 | SetupEnvironment(opts) |
| 2698 | exceptBisectExceptionas e: |
| 2699 | print(e) |
| 2700 | sys.exit(1) |
| 2701 | |
Kuan Huang | c63de40 | 2024-08-12 22:15:27 | [diff] [blame] | 2702 | # Create the AbstractBuild object. |
Kuan Huang | d608f159 | 2024-08-15 23:31:35 | [diff] [blame] | 2703 | archive_build= create_archive_build(opts) |
Sven Zheng | 8e07956 | 2024-05-10 20:11:06 | [diff] [blame] | 2704 | |
skobes | 21b5cdfb | 2016-03-21 23:13:02 | [diff] [blame] | 2705 | if opts.not_interactive: |
| 2706 | evaluator=DidCommandSucceed |
Kuan Huang | 37d0daa | 2024-09-19 18:03:08 | [diff] [blame] | 2707 | elif opts.build_type=='asan': |
cmumford@chromium.org | 01188669 | 2014-08-01 21:00:21 | [diff] [blame] | 2708 | evaluator=IsGoodASANBuild |
| 2709 | else: |
| 2710 | evaluator=AskIsGoodBuild |
| 2711 | |
pshenoy@chromium.org | 2e0f267 | 2014-08-13 20:32:58 | [diff] [blame] | 2712 | # Save these revision numbers to compare when showing the changelog URL |
| 2713 | # after the bisect. |
Kuan Huang | 0cb06a40 | 2024-08-15 23:31:49 | [diff] [blame] | 2714 | good_rev= archive_build.good_revision |
| 2715 | bad_rev= archive_build.bad_revision |
pshenoy@chromium.org | 2e0f267 | 2014-08-13 20:32:58 | [diff] [blame] | 2716 | |
Kuan Huang | 37d0daa | 2024-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 Huang | c2fec79 | 2024-09-03 21:57:38 | [diff] [blame] | 2719 | if min_chromium_revisNoneor max_chromium_revisNone: |
| 2720 | return |
Kuan Huang | 0cb06a40 | 2024-08-15 23:31:49 | [diff] [blame] | 2721 | # We're done. Let the user know the results in an official manner. |
| 2722 | if good_rev> bad_rev: |
| 2723 | print(DONE_MESSAGE_GOOD_MAX% |
| 2724 | (str(min_chromium_rev), str(max_chromium_rev))) |
Kuan Huang | 95f6df07 | 2024-09-12 20:56:42 | [diff] [blame] | 2725 | good_rev, bad_rev= max_chromium_rev, min_chromium_rev |
anantha@chromium.org | d0149c5c | 2012-05-29 21:12:11 | [diff] [blame] | 2726 | else: |
Kuan Huang | 0cb06a40 | 2024-08-15 23:31:49 | [diff] [blame] | 2727 | print(DONE_MESSAGE_GOOD_MIN% |
| 2728 | (str(min_chromium_rev), str(max_chromium_rev))) |
Kuan Huang | 95f6df07 | 2024-09-12 20:56:42 | [diff] [blame] | 2729 | good_rev, bad_rev= min_chromium_rev, max_chromium_rev |
karen@chromium.org | 3bdaa475 | 2013-09-30 20:13:36 | [diff] [blame] | 2730 | |
Kuan Huang | 0cb06a40 | 2024-08-15 23:31:49 | [diff] [blame] | 2731 | print('CHANGELOG URL:') |
Kuan Huang | 37d0daa | 2024-09-19 18:03:08 | [diff] [blame] | 2732 | if opts.build_type=='release': |
Kuan Huang | b790d5d | 2024-11-07 21:53:00 | [diff] [blame] | 2733 | print(GetReleaseChangeLogURL(min_chromium_rev, max_chromium_rev)) |
Kuan Huang | 95f6df07 | 2024-09-12 20:56:42 | [diff] [blame] | 2734 | MaybeSwitchBuildType(opts, good=good_rev, bad=bad_rev) |
Kuan Huang | 0cb06a40 | 2024-08-15 23:31:49 | [diff] [blame] | 2735 | else: |
Kuan Huang | b790d5d | 2024-11-07 21:53:00 | [diff] [blame] | 2736 | print(GetChangeLogURL(min_chromium_rev, max_chromium_rev)) |
Kuan Huang | 37d0daa | 2024-09-19 18:03:08 | [diff] [blame] | 2737 | if opts.build_type=='official': |
Kuan Huang | 0cb06a40 | 2024-08-15 23:31:49 | [diff] [blame] | 2738 | print('The script might not always return single CL as suspect ' |
| 2739 | 'as some perf builds might get missing due to failure.') |
qyearsley@chromium.org | 4df583c | 2014-07-31 17:11:55 | [diff] [blame] | 2740 | |
rsesek@chromium.org | 67e0bc6 | 2009-09-03 22:06:09 | [diff] [blame] | 2741 | if __name__=='__main__': |
mmoss@chromium.org | 7ad66a7 | 2009-09-04 17:52:33 | [diff] [blame] | 2742 | sys.exit(main()) |