gsutil: Parallel-safe, specify target, add clean.

- Update "gsutil.py" to be cooperatively safe when invoked
  multiple times simultaneously.
- Allow the cache directory to be overridden by the
  DEPOT_TOOLS_GSUTIL_BIN_DIR environment variable.
- Add a "--clean" flag to force "gsutil.py" to do a clean download.

BUG=chromium:452497
TEST=local
  - for i in `seq 1 50`; do ./gsutil.py --clean -- version&; done

Review URL: https://codereview.chromium.org/1346213003

git-svn-id: svn://svn.chromium.org/chrome/trunk/tools/depot_tools@296772 0039d316-1c4b-4281-b951-d872f2087c98
changes/01/332501/1
dnj@chromium.org 10 years ago
parent c3d4413cab
commit 605d81dfb3

@ -8,12 +8,15 @@
import argparse import argparse
import base64 import base64
import contextlib
import hashlib import hashlib
import json import json
import os import os
import shutil import shutil
import subprocess import subprocess
import sys import sys
import tempfile
import time
import urllib2 import urllib2
import zipfile import zipfile
@ -26,7 +29,6 @@ DEFAULT_BIN_DIR = os.path.join(THIS_DIR, 'external_bin', 'gsutil')
DEFAULT_FALLBACK_GSUTIL = os.path.join( DEFAULT_FALLBACK_GSUTIL = os.path.join(
THIS_DIR, 'third_party', 'gsutil', 'gsutil') THIS_DIR, 'third_party', 'gsutil', 'gsutil')
class InvalidGsutilError(Exception): class InvalidGsutilError(Exception):
pass pass
@ -73,33 +75,54 @@ def check_gsutil(gsutil_bin):
[sys.executable, gsutil_bin, 'version'], [sys.executable, gsutil_bin, 'version'],
stdout=subprocess.PIPE, stderr=subprocess.STDOUT) == 0 stdout=subprocess.PIPE, stderr=subprocess.STDOUT) == 0
def ensure_gsutil(version, target): @contextlib.contextmanager
def temporary_directory(base):
tmpdir = tempfile.mkdtemp(prefix='gsutil_py', dir=base)
try:
yield tmpdir
finally:
if os.path.isdir(tmpdir):
shutil.rmtree(tmpdir)
def ensure_gsutil(version, target, clean):
bin_dir = os.path.join(target, 'gsutil_%s' % version) bin_dir = os.path.join(target, 'gsutil_%s' % version)
gsutil_bin = os.path.join(bin_dir, 'gsutil', 'gsutil') gsutil_bin = os.path.join(bin_dir, 'gsutil', 'gsutil')
if os.path.isfile(gsutil_bin) and check_gsutil(gsutil_bin): if not clean and os.path.isfile(gsutil_bin) and check_gsutil(gsutil_bin):
# Everything is awesome! we're all done here. # Everything is awesome! we're all done here.
return gsutil_bin return gsutil_bin
if os.path.isdir(bin_dir): if not os.path.exists(target):
os.makedirs(target)
with temporary_directory(target) as instance_dir:
# Clean up if we're redownloading a corrupted gsutil. # Clean up if we're redownloading a corrupted gsutil.
shutil.rmtree(bin_dir) cleanup_path = os.path.join(instance_dir, 'clean')
cache_dir = os.path.join(target, '.cache_dir') try:
if not os.path.isdir(cache_dir): os.rename(bin_dir, cleanup_path)
os.makedirs(cache_dir) except (OSError, IOError):
target_zip_filename = download_gsutil(version, cache_dir) cleanup_path = None
with zipfile.ZipFile(target_zip_filename, 'r') as target_zip: if cleanup_path:
target_zip.extractall(bin_dir) shutil.rmtree(cleanup_path)
download_dir = os.path.join(instance_dir, 'download')
target_zip_filename = download_gsutil(version, instance_dir)
with zipfile.ZipFile(target_zip_filename, 'r') as target_zip:
target_zip.extractall(download_dir)
try:
os.rename(download_dir, bin_dir)
except (OSError, IOError):
# Something else did this in parallel.
pass
# Final check that the gsutil bin is okay. This should never fail. # Final check that the gsutil bin is okay. This should never fail.
if not check_gsutil(gsutil_bin): if not check_gsutil(gsutil_bin):
raise InvalidGsutilError() raise InvalidGsutilError()
return gsutil_bin return gsutil_bin
def run_gsutil(force_version, fallback, target, args): def run_gsutil(force_version, fallback, target, args, clean=False):
if force_version: if force_version:
gsutil_bin = ensure_gsutil(force_version, target) gsutil_bin = ensure_gsutil(force_version, target, clean)
else: else:
gsutil_bin = fallback gsutil_bin = fallback
cmd = [sys.executable, gsutil_bin] + args cmd = [sys.executable, gsutil_bin] + args
@ -107,10 +130,16 @@ def run_gsutil(force_version, fallback, target, args):
def parse_args(): def parse_args():
bin_dir = os.environ.get('DEPOT_TOOLS_GSUTIL_BIN_DIR', DEFAULT_BIN_DIR)
parser = argparse.ArgumentParser() parser = argparse.ArgumentParser()
parser.add_argument('--force-version', default='4.13') parser.add_argument('--force-version', default='4.13')
parser.add_argument('--clean', action='store_true',
help='Clear any existing gsutil package, forcing a new download.')
parser.add_argument('--fallback', default=DEFAULT_FALLBACK_GSUTIL) parser.add_argument('--fallback', default=DEFAULT_FALLBACK_GSUTIL)
parser.add_argument('--target', default=DEFAULT_BIN_DIR) parser.add_argument('--target', default=bin_dir,
help='The target directory to download/store a gsutil version in. '
'(default is %(default)s).')
parser.add_argument('args', nargs=argparse.REMAINDER) parser.add_argument('args', nargs=argparse.REMAINDER)
args, extras = parser.parse_known_args() args, extras = parser.parse_known_args()
@ -118,12 +147,13 @@ def parse_args():
args.args.pop(0) args.args.pop(0)
if extras: if extras:
args.args = extras + args.args args.args = extras + args.args
return args.force_version, args.fallback, args.target, args.args return args
def main(): def main():
force_version, fallback, target, args = parse_args() args = parse_args()
return run_gsutil(force_version, fallback, target, args) return run_gsutil(args.force_version, args.fallback, args.target, args.args,
clean=args.clean)
if __name__ == '__main__': if __name__ == '__main__':
sys.exit(main()) sys.exit(main())

@ -144,8 +144,8 @@ class GsutilUnitTests(unittest.TestCase):
# This should delete the old bin and rewrite it with 'Fake gsutil' # This should delete the old bin and rewrite it with 'Fake gsutil'
self.assertRaises( self.assertRaises(
gsutil.InvalidGsutilError, gsutil.ensure_gsutil, version, self.tempdir) gsutil.InvalidGsutilError, gsutil.ensure_gsutil, version, self.tempdir,
self.assertTrue(os.path.isdir(os.path.join(self.tempdir, '.cache_dir'))) False)
self.assertTrue(os.path.exists(gsutil_bin)) self.assertTrue(os.path.exists(gsutil_bin))
with open(gsutil_bin, 'r') as f: with open(gsutil_bin, 'r') as f:
self.assertEquals(f.read(), fake_gsutil) self.assertEquals(f.read(), fake_gsutil)
@ -165,7 +165,7 @@ class GsutilUnitTests(unittest.TestCase):
with open(gsutil_bin, 'w') as f: with open(gsutil_bin, 'w') as f:
f.write('Foobar') f.write('Foobar')
self.assertEquals( self.assertEquals(
gsutil.ensure_gsutil(version, self.tempdir), gsutil_bin) gsutil.ensure_gsutil(version, self.tempdir, False), gsutil_bin)
if __name__ == '__main__': if __name__ == '__main__':
unittest.main() unittest.main()

Loading…
Cancel
Save