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

Unified Diff: build/get_syzygy_binaries.py

Issue 1236623002: Make get_syzygy_binaries.py resistant to cloud failures. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Fix nits. Created 5 years, 5 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: build/get_syzygy_binaries.py
diff --git a/build/get_syzygy_binaries.py b/build/get_syzygy_binaries.py
index 2577c7cb7fde733b974ebf66e7c11fe1cd9777ba..2a6469ffaa6de0f3184085d54c2c6626d246c153 100755
--- a/build/get_syzygy_binaries.py
+++ b/build/get_syzygy_binaries.py
@@ -5,7 +5,6 @@
"""A utility script for downloading versioned Syzygy binaries."""
-import cStringIO
import hashlib
import errno
import json
@@ -17,15 +16,15 @@ import shutil
import stat
import sys
import subprocess
-import urllib2
+import tempfile
+import time
import zipfile
_LOGGER = logging.getLogger(os.path.basename(__file__))
-# The URL where official builds are archived.
-_SYZYGY_ARCHIVE_URL = ('https://syzygy-archive.commondatastorage.googleapis.com'
- '/builds/official/%(revision)s')
+# The relative path where official builds are archived in their GS bucket.
+_SYZYGY_ARCHIVE_PATH = ('/builds/official/%(revision)s')
# A JSON file containing the state of the download directory. If this file and
# directory state do not agree, then the binaries will be downloaded and
@@ -49,17 +48,6 @@ _RESOURCES = [
lambda x: x.filename.endswith('.dll.pdb'))]
-def _Shell(*cmd, **kw):
- """Runs |cmd|, returns the results from Popen(cmd).communicate()."""
- _LOGGER.debug('Executing %s.', cmd)
- prog = subprocess.Popen(cmd, shell=True, **kw)
-
- stdout, stderr = prog.communicate()
- if prog.returncode != 0:
- raise RuntimeError('Command "%s" returned %d.' % (cmd, prog.returncode))
- return (stdout, stderr)
-
-
def _LoadState(output_dir):
"""Loads the contents of the state file for a given |output_dir|, returning
None if it doesn't exist.
@@ -248,12 +236,50 @@ def _CleanState(output_dir, state, dry_run=False):
return deleted
-def _Download(url):
- """Downloads the given URL and returns the contents as a string."""
- response = urllib2.urlopen(url)
- if response.code != 200:
- raise RuntimeError('Failed to download "%s".' % url)
- return response.read()
+def _FindGsUtil():
+ """Looks for depot_tools and returns the absolute path to gsutil.py."""
+ for path in os.environ['PATH'].split(os.pathsep):
+ path = os.path.abspath(path)
+ git_cl = os.path.join(path, 'git_cl.py')
+ gs_util = os.path.join(path, 'gsutil.py')
+ if os.path.exists(git_cl) and os.path.exists(gs_util):
+ return gs_util
+ return None
+
+
+def _GsUtil(*cmd):
+ """Runs the given command in gsutil with exponential backoff and retries."""
+ gs_util = _FindGsUtil()
+ cmd = [sys.executable, gs_util] + list(cmd)
+
+ retries = 3
+ timeout = 4 # Seconds.
+ while True:
+ _LOGGER.debug('Running %s', cmd)
+ prog = subprocess.Popen(cmd, shell=True)
+ prog.communicate()
+
+ # Stop retrying on success.
+ if prog.returncode == 0:
+ return
+
+ # Raise a permanent failure if retries have been exhausted.
+ if retries == 0:
+ raise RuntimeError('Command "%s" returned %d.' % (cmd, prog.returncode))
+
+ _LOGGER.debug('Sleeping %d seconds and trying again.', timeout)
+ time.sleep(timeout)
+ retries -= 1
+ timeout *= 2
+
+
+def _Download(resource):
+ """Downloads the given GS resource to a temporary file, returning its path."""
+ tmp = tempfile.mkstemp(suffix='syzygy_archive')
+ os.close(tmp[0])
+ url = 'gs://syzygy-archive' + resource
+ _GsUtil('cp', url, tmp[1])
+ return tmp[1]
def _InstallBinaries(options, deleted={}):
@@ -261,7 +287,7 @@ def _InstallBinaries(options, deleted={}):
already been cleaned, as it will refuse to overwrite existing files."""
contents = {}
state = { 'revision': options.revision, 'contents': contents }
- archive_url = _SYZYGY_ARCHIVE_URL % { 'revision': options.revision }
+ archive_path = _SYZYGY_ARCHIVE_PATH % { 'revision': options.revision }
if options.resources:
resources = [(resource, resource, '', None)
for resource in options.resources]
@@ -278,33 +304,37 @@ def _InstallBinaries(options, deleted={}):
if not options.dry_run:
os.makedirs(fulldir)
- # Download the archive.
- url = archive_url + '/' + base
- _LOGGER.debug('Retrieving %s archive at "%s".', name, url)
- data = _Download(url)
+ # Download and read the archive.
+ resource = archive_path + '/' + base
+ _LOGGER.debug('Retrieving %s archive at "%s".', name, resource)
+ path = _Download(resource)
_LOGGER.debug('Unzipping %s archive.', name)
- archive = zipfile.ZipFile(cStringIO.StringIO(data))
- for entry in archive.infolist():
- if not filt or filt(entry):
- fullpath = os.path.normpath(os.path.join(fulldir, entry.filename))
- relpath = os.path.relpath(fullpath, options.output_dir)
- if os.path.exists(fullpath):
- # If in a dry-run take into account the fact that the file *would*
- # have been deleted.
- if options.dry_run and relpath in deleted:
- pass
- else:
- raise Exception('Path already exists: %s' % fullpath)
-
- # Extract the file and update the state dictionary.
- _LOGGER.debug('Extracting "%s".', fullpath)
- if not options.dry_run:
- archive.extract(entry.filename, fulldir)
- md5 = _Md5(fullpath)
- contents[relpath] = md5
- if sys.platform == 'cygwin':
- os.chmod(fullpath, os.stat(fullpath).st_mode | stat.S_IXUSR)
+ with open(path, 'rb') as data:
+ archive = zipfile.ZipFile(data)
+ for entry in archive.infolist():
+ if not filt or filt(entry):
+ fullpath = os.path.normpath(os.path.join(fulldir, entry.filename))
+ relpath = os.path.relpath(fullpath, options.output_dir)
+ if os.path.exists(fullpath):
+ # If in a dry-run take into account the fact that the file *would*
+ # have been deleted.
+ if options.dry_run and relpath in deleted:
+ pass
+ else:
+ raise Exception('Path already exists: %s' % fullpath)
+
+ # Extract the file and update the state dictionary.
+ _LOGGER.debug('Extracting "%s".', fullpath)
+ if not options.dry_run:
+ archive.extract(entry.filename, fulldir)
+ md5 = _Md5(fullpath)
+ contents[relpath] = md5
+ if sys.platform == 'cygwin':
+ os.chmod(fullpath, os.stat(fullpath).st_mode | stat.S_IXUSR)
+
+ _LOGGER.debug('Removing temporary file "%s".', path)
+ os.remove(path)
return state
@@ -316,6 +346,9 @@ def _ParseCommandLine():
help='If true then will simply list actions that would be performed.')
option_parser.add_option('--force', action='store_true', default=False,
help='Force an installation even if the binaries are up to date.')
+ option_parser.add_option('--no-cleanup', action='store_true', default=False,
+ help='Allow installation on non-Windows platforms, and skip the forced '
+ 'cleanup step.')
option_parser.add_option('--output-dir', type='string',
help='The path where the binaries will be replaced. Existing binaries '
'will only be overwritten if not up to date.')
@@ -408,7 +441,10 @@ def main():
# wasn't gated on OS types, and those OSes downloaded and installed binaries.
# This will cleanup orphaned files on those operating systems.
if sys.platform not in ('win32', 'cygwin'):
- return _RemoveOrphanedFiles(options)
+ if options.no_cleanup:
+ _LOGGER.debug('Skipping usual cleanup for non-Windows platforms.')
+ else:
+ return _RemoveOrphanedFiles(options)
# Load the current installation state, and validate it against the
# requested installation.
« 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