Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(24)

Unified Diff: tools/bisect-builds.py

Issue 851333004: Add support to bisect nonofficial Android builds from tip of tree bot. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Created 5 years, 11 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « no previous file | no next file » | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: tools/bisect-builds.py
diff --git a/tools/bisect-builds.py b/tools/bisect-builds.py
index 52737259c8f1527894e64a0a93ed344ce260a3e0..bcaddfa0b36f0c79e63d8c3dfaf15b2d40182c7e 100755
--- a/tools/bisect-builds.py
+++ b/tools/bisect-builds.py
@@ -32,6 +32,9 @@ OFFICIAL_BASE_URL = 'http://%s/%s' % (GOOGLE_APIS_URL, GS_BUCKET_NAME)
# URL template for viewing changelogs between revisions.
CHANGELOG_URL = ('https://chromium.googlesource.com/chromium/src/+log/%s..%s')
+# GS bucket name for tip of tree android builds.
+ANDROID_TOT_BUCKET_NAME = ('chrome-android-tot/bisect')
+
# GS bucket name for android unsigned official builds.
ANDROID_BUCKET_NAME = 'chrome-unsigned/android-C4MPAR1'
@@ -141,11 +144,10 @@ class PathContext(object):
# the build.
self.githash_svn_dict = {}
self.pdf_path = pdf_path
- # android_apk defaults to Chrome.apk
- self.android_apk = android_apk if android_apk else 'Chrome.apk'
# The name of the ZIP file in a revision directory on the server.
self.archive_name = None
-
+ # Whether the build should be downloaded using gsutil.
+ self.download_with_gsutil = False
# If the script is run from a local Chromium checkout,
# "--use-local-repo" option can be used to make the script run faster.
# It uses "git svn find-rev <SHA1>" command to convert git hash to svn
@@ -153,7 +155,10 @@ class PathContext(object):
self.use_local_repo = use_local_repo
# If the script is being used for android builds.
- self.android = self.platform.startswith('android')
+ self.is_android = self.platform.startswith('android')
+ # android_apk defaults to Chrome.apk
+ if self.is_android:
+ self.android_apk = android_apk if android_apk else 'Chrome.apk'
# Set some internal members:
# _listing_platform_dir = Directory that holds revisions. Ends with a '/'.
@@ -168,7 +173,7 @@ class PathContext(object):
self.archive_name = 'chrome-win32.zip'
self._archive_extract_dir = 'chrome-win32'
self._binary_name = 'chrome.exe'
- elif self.android:
+ elif self.is_android:
pass
else:
raise Exception('Invalid platform: %s' % self.platform)
@@ -223,6 +228,11 @@ class PathContext(object):
self._binary_name = 'Chromium.app/Contents/MacOS/Chromium'
elif self.platform == 'win':
self._listing_platform_dir = 'Win/'
+ elif self.platform == 'android-arm':
+ self.archive_name = 'bisect_android.zip'
+ # Need to download builds using gsutil instead of visiting url for
+ # authentication reasons.
+ self.download_with_gsutil = True
def GetASANPlatformDir(self):
"""ASAN builds are in directories like "linux-release", or have filenames
@@ -252,7 +262,7 @@ class PathContext(object):
ASAN_BASE_URL, self.GetASANPlatformDir(), self.build_type,
self.GetASANBaseName(), revision)
if self.is_official:
- if self.android:
+ if self.is_android:
official_base_url = ANDROID_OFFICIAL_BASE_URL
else:
official_base_url = OFFICIAL_BASE_URL
@@ -260,10 +270,15 @@ class PathContext(object):
official_base_url, revision, self._listing_platform_dir,
self.archive_name)
else:
- if str(revision) in self.githash_svn_dict:
- revision = self.githash_svn_dict[str(revision)]
- return '%s/%s%s/%s' % (self.base_url, self._listing_platform_dir,
- revision, self.archive_name)
+ if self.is_android:
+ # These files need to be downloaded through gsutil.
+ return ('gs://%s/%s/%s' % (ANDROID_TOT_BUCKET_NAME, revision,
+ self.archive_name))
+ else:
+ if str(revision) in self.githash_svn_dict:
+ revision = self.githash_svn_dict[str(revision)]
+ return '%s/%s%s/%s' % (self.base_url, self._listing_platform_dir,
+ revision, self.archive_name)
def GetLastChangeURL(self):
"""Returns a URL to the LAST_CHANGE file."""
@@ -457,52 +472,51 @@ class PathContext(object):
self.bad_revision)
return revlist
+ def _GetHashToNumberDict(self):
+ """Gets the mapping of git hashes to git numbers from Google Storage."""
+ gs_file = 'gs://%s/gitnumbers_dict.json' % ANDROID_TOT_BUCKET_NAME
+ local_file = 'gitnumbers_dict.json'
+ GsutilDownload(gs_file, local_file)
+ json_data = open(local_file).read()
+ os.remove(local_file)
+ return json.loads(json_data)
+
+ def GetAndroidToTRevisions(self):
+ """Gets the ordered list of revisions between self.good_revision and
+ self.bad_revision from the Android tip of tree GS bucket.
+ """
+ # Dictionary that maps git hashes to git numbers. The git numbers
+ # let us order the revisions.
+ hash_to_num = self._GetHashToNumberDict()
+ try:
+ good_rev_num = hash_to_num[self.good_revision]
+ bad_rev_num = hash_to_num[self.bad_revision]
+ except KeyError:
+ exit('Error. Make sure the good and bad revisions are valid git hashes.')
+
+ # List of all builds by their git hashes in the storage bucket.
+ hash_list = GsutilList(ANDROID_TOT_BUCKET_NAME)
+
+ # Get list of builds that we want to bisect over.
+ final_list = []
+ minnum = min(good_rev_num, bad_rev_num)
+ maxnum = max(good_rev_num, bad_rev_num)
+ for githash in hash_list:
+ if len(githash) != 40:
+ continue
+ gitnumber = hash_to_num[githash]
+ if minnum < gitnumber < maxnum:
+ final_list.append(githash)
+ return sorted(final_list, key=lambda h: hash_to_num[h])
+
def GetOfficialBuildsList(self):
"""Gets the list of official build numbers between self.good_revision and
self.bad_revision."""
- def CheckDepotToolsInPath():
- delimiter = ';' if sys.platform.startswith('win') else ':'
- path_list = os.environ['PATH'].split(delimiter)
- for path in path_list:
- if path.rstrip(os.path.sep).endswith('depot_tools'):
- return path
- return None
-
- def RunGsutilCommand(args):
- gsutil_path = CheckDepotToolsInPath()
- if gsutil_path is None:
- print ('Follow the instructions in this document '
- 'http://dev.chromium.org/developers/how-tos/install-depot-tools'
- ' to install depot_tools and then try again.')
- sys.exit(1)
- gsutil_path = os.path.join(gsutil_path, 'third_party', 'gsutil', 'gsutil')
- gsutil = subprocess.Popen([sys.executable, gsutil_path] + args,
- stdout=subprocess.PIPE, stderr=subprocess.PIPE,
- env=None)
- stdout, stderr = gsutil.communicate()
- if gsutil.returncode:
- if (re.findall(r'status[ |=]40[1|3]', stderr) or
- stderr.startswith(CREDENTIAL_ERROR_MESSAGE)):
- print ('Follow these steps to configure your credentials and try'
- ' running the bisect-builds.py again.:\n'
- ' 1. Run "python %s config" and follow its instructions.\n'
- ' 2. If you have a @google.com account, use that account.\n'
- ' 3. For the project-id, just enter 0.' % gsutil_path)
- sys.exit(1)
- else:
- raise Exception('Error running the gsutil command: %s' % stderr)
- return stdout
-
- def GsutilList(bucket):
- query = 'gs://%s/' % bucket
- stdout = RunGsutilCommand(['ls', query])
- return [url[len(query):].strip('/') for url in stdout.splitlines()]
-
# Download the revlist and filter for just the range between good and bad.
minrev = min(self.good_revision, self.bad_revision)
maxrev = max(self.good_revision, self.bad_revision)
- if self.android:
+ if self.is_android:
gs_bucket_name = ANDROID_BUCKET_NAME
else:
gs_bucket_name = GS_BUCKET_NAME
@@ -527,6 +541,51 @@ class PathContext(object):
connection.close()
return final_list
Robert Sesek 2015/02/04 17:27:54 nit: add another blank line
mikecase (-- gone --) 2015/02/04 22:50:44 Done.
+def CheckDepotToolsInPath():
+ delimiter = ';' if sys.platform.startswith('win') else ':'
Robert Sesek 2015/02/04 17:27:54 nit: indent is wrong
mikecase (-- gone --) 2015/02/04 22:50:44 Good eye.
+ path_list = os.environ['PATH'].split(delimiter)
+ for path in path_list:
+ if path.rstrip(os.path.sep).endswith('depot_tools'):
+ return path
+ return None
+
+
+def RunGsutilCommand(args):
+ gsutil_path = CheckDepotToolsInPath()
+ if gsutil_path is None:
+ print ('Follow the instructions in this document '
+ 'http://dev.chromium.org/developers/how-tos/install-depot-tools'
+ ' to install depot_tools and then try again.')
+ sys.exit(1)
+ gsutil_path = os.path.join(gsutil_path, 'third_party', 'gsutil', 'gsutil')
+ gsutil = subprocess.Popen([sys.executable, gsutil_path] + args,
+ stdout=subprocess.PIPE, stderr=subprocess.PIPE,
+ env=None)
+ stdout, stderr = gsutil.communicate()
+ if gsutil.returncode:
+ if (re.findall(r'status[ |=]40[1|3]', stderr) or
+ stderr.startswith(CREDENTIAL_ERROR_MESSAGE)):
+ print ('Follow these steps to configure your credentials and try'
+ ' running the bisect-builds.py again.:\n'
+ ' 1. Run "python %s config" and follow its instructions.\n'
+ ' 2. If you have a @google.com account, use that account.\n'
+ ' 3. For the project-id, just enter 0.' % gsutil_path)
+ sys.exit(1)
+ else:
+ raise Exception('Error running the gsutil command: %s' % stderr)
+ return stdout
+
+
+def GsutilList(bucket):
+ query = 'gs://%s/' % bucket
+ stdout = RunGsutilCommand(['ls', query])
+ return [url[len(query):].strip('/') for url in stdout.splitlines()]
+
+
+def GsutilDownload(gs_download_url, filename):
+ RunGsutilCommand(['cp', gs_download_url, filename])
+
+
def UnzipFilenameToDir(filename, directory):
"""Unzip |filename| to |directory|."""
cwd = os.getcwd()
@@ -545,7 +604,7 @@ def UnzipFilenameToDir(filename, directory):
os.makedirs(name)
else: # file
directory = os.path.dirname(name)
- if not os.path.isdir(directory):
+ if directory and not os.path.isdir(directory):
os.makedirs(directory)
out = open(name, 'wb')
out.write(zf.read(name))
@@ -580,20 +639,32 @@ def FetchRevision(context, rev, filename, quit_event=None, progress_event=None):
# Send a \r to let all progress messages use just one line of output.
sys.stdout.write('\r' + progress)
sys.stdout.flush()
-
download_url = context.GetDownloadURL(rev)
try:
- urllib.urlretrieve(download_url, filename, ReportHook)
+ if context.download_with_gsutil:
+ GsutilDownload(download_url, filename)
+ else:
+ urllib.urlretrieve(download_url, filename, ReportHook)
if progress_event and progress_event.isSet():
print
+
except RuntimeError:
pass
+def RunADBCommand(args):
+ cmd = ['adb'] + args
+ adb = subprocess.Popen(['adb'] + args,
+ stdout=subprocess.PIPE, stderr=subprocess.PIPE,
+ env=None)
+ stdout, stderr = adb.communicate()
+ return stdout
+
+
def IsADBInstalled():
"""Checks if ADB is in the environment path."""
try:
- adb_output = subprocess.check_output(['adb', 'version'])
+ adb_output = RunADBCommand(['version'])
return ('Android Debug Bridge' in adb_output)
except OSError:
return False
@@ -601,7 +672,7 @@ def IsADBInstalled():
def GetAndroidDeviceList():
"""Returns the list of Android devices attached to the host machine."""
- lines = subprocess.check_output(['adb', 'devices']).split('\n')[1:]
+ lines = RunADBCommand(['devices']).split('\n')[1:]
devices = []
for line in lines:
m = re.match('^(.*?)\s+device$', line)
@@ -611,31 +682,38 @@ def GetAndroidDeviceList():
return devices
-def RunAndroidRevision(context, revision, apk_file):
+def RunAndroidRevision(context, revision, zip_file):
"""Given a Chrome apk, install it on a local device, and launch Chrome."""
devices = GetAndroidDeviceList()
if len(devices) is not 1:
sys.exit('Please have 1 Android device plugged in. %d devices found'
% len(devices))
- devnull = open(os.devnull, 'w')
- package_name = ANDROID_CHROME_PACKAGE_NAME[context.android_apk]
+ if context.is_official:
+ # Downloaded file is just the .apk in this case.
+ apk_file = zip_file
+ else:
+ cwd = os.getcwd()
+ tempdir = tempfile.mkdtemp(prefix='bisect_tmp')
+ UnzipFilenameToDir(zip_file, tempdir)
+ os.chdir(tempdir)
+ apk_file = context.android_apk
- print 'Installing new Chrome version...'
- subprocess.call(['adb', 'install', '-r', '-d', apk_file],
- stdout=devnull, stderr=devnull)
+ package_name = ANDROID_CHROME_PACKAGE_NAME[context.android_apk]
+ print 'Installing...'
+ RunADBCommand(['install', '-r', '-d', apk_file])
print 'Launching Chrome...\n'
- subprocess.call(['adb', 'shell', 'am', 'start', '-a',
+ RunADBCommand(['shell', 'am', 'start', '-a',
'android.intent.action.VIEW', '-n', package_name +
- '/com.google.android.apps.chrome.Main'], stdout=devnull, stderr=devnull)
+ '/com.google.android.apps.chrome.Main'])
def RunRevision(context, revision, zip_file, profile, num_runs, command, args):
"""Given a zipped revision, unzip it and run the test."""
print 'Trying revision %s...' % str(revision)
- if context.android:
+ if context.is_android:
RunAndroidRevision(context, revision, zip_file)
# TODO(mikecase): Support running command to auto-bisect Android.
return (None, None, None)
@@ -682,7 +760,6 @@ def RunRevision(context, revision, zip_file, profile, num_runs, command, args):
stderr=subprocess.PIPE)
(stdout, stderr) = subproc.communicate()
results.append((subproc.returncode, stdout, stderr))
-
os.chdir(cwd)
try:
shutil.rmtree(tempdir, True)
@@ -820,6 +897,8 @@ def Bisect(context,
'%s-%s' % (str(rev), context.archive_name))
if context.is_official:
revlist = context.GetOfficialBuildsList()
+ elif context.is_android: # Android non-official
+ revlist = context.GetAndroidToTRevisions()
else:
revlist = context.GetRevList()
@@ -1202,12 +1281,14 @@ def main():
opts.official_builds, opts.asan, opts.use_local_repo,
opts.flash_path, opts.pdf_path, opts.apk)
- # TODO(mikecase): Add support to bisect on nonofficial builds for Android.
- if context.android and not opts.official_builds:
- sys.exit('Can only bisect on official builds for Android.')
+ if context.is_android and not opts.official_builds:
+ if(context.platform != 'android-arm' or
Robert Sesek 2015/02/04 17:27:54 nit: space before (
mikecase (-- gone --) 2015/02/04 22:50:44 Done.
+ context.android_apk != 'Chrome.apk'):
+ sys.exit('For non-official builds, can only bisect'
+ ' Chrome.apk arm builds.')
# If bisecting Android, we make sure we have ADB setup.
- if context.android:
+ if context.is_android:
if opts.adb_path:
os.environ['PATH'] = '%s:%s' % (os.path.dirname(opts.adb_path),
os.environ['PATH'])
@@ -1236,6 +1317,9 @@ def main():
if opts.official_builds:
context.good_revision = LooseVersion(context.good_revision)
context.bad_revision = LooseVersion(context.bad_revision)
+ elif context.is_android:
+ # Revisions are git hashes and should be left as strings.
+ pass
else:
context.good_revision = int(context.good_revision)
context.bad_revision = int(context.bad_revision)
« no previous file with comments | « no previous file | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698