#!/usr/bin/env python # # Copyright (c) 2011, the Dart project authors. Please see the AUTHORS file # for details. All rights reserved. Use of this source code is governed by a # BSD-style license that can be found in the LICENSE file. # Gets or updates a content shell (a nearly headless build of chrome). This is # used for running browser tests of client applications. import json import optparse import os import platform import shutil import subprocess import sys import tempfile import zipfile import utils def NormJoin(path1, path2): return os.path.normpath(os.path.join(path1, path2)) # Change into the dart directory as we want the project to be rooted here. dart_src = NormJoin(os.path.dirname(sys.argv[0]), os.pardir) os.chdir(dart_src) GSUTIL_DIR = os.path.join('third_party', 'gsutil') GSUTIL = GSUTIL_DIR + '/gsutil' DRT_DIR = os.path.join('client', 'tests', 'drt') DRT_VERSION = os.path.join(DRT_DIR, 'LAST_VERSION') DRT_LATEST_PATTERN = ( 'gs://dartium-archive/latest/drt-%(osname)s-%(bot)s-*.zip') DRT_PERMANENT_PATTERN = ('gs://dartium-archive/drt-%(osname)s-%(bot)s/drt-' '%(osname)s-%(bot)s-%(num1)s.%(num2)s.zip') DARTIUM_DIR = os.path.join('client', 'tests', 'dartium') DARTIUM_VERSION = os.path.join(DARTIUM_DIR, 'LAST_VERSION') DARTIUM_LATEST_PATTERN = ( 'gs://dartium-archive/latest/dartium-%(osname)s-%(bot)s-*.zip') DARTIUM_PERMANENT_PATTERN = ('gs://dartium-archive/dartium-%(osname)s-%(bot)s/' 'dartium-%(osname)s-%(bot)s-%(num1)s.%(num2)s.zip') SDK_DIR = os.path.join(utils.GetBuildRoot(utils.GuessOS(), 'release', 'ia32'), 'dart-sdk') SDK_VERSION = os.path.join(SDK_DIR, 'LAST_VERSION') SDK_LATEST_PATTERN = 'gs://dart-archive/channels/dev/raw/latest/VERSION' # TODO(efortuna): Once the x64 VM also is optimized, select the version # based on whether we are running on a 32-bit or 64-bit system. SDK_PERMANENT = ('gs://dart-archive/channels/dev/raw/%(version_num)s/sdk/' + 'dartsdk-%(osname)s-ia32-release.zip') # Dictionary storing the earliest revision of each download we have stored. LAST_VALID = {'dartium': 4285, 'chromedriver': 7823, 'sdk': 9761, 'drt': 5342} sys.path.append(os.path.join(GSUTIL_DIR, 'third_party', 'boto')) import boto def ExecuteCommand(*cmd): """Execute a command in a subprocess.""" pipe = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) output, error = pipe.communicate() return pipe.returncode, output def ExecuteCommandVisible(*cmd): """Execute a command in a subprocess, but show stdout/stderr.""" result = subprocess.call(cmd, stdout=sys.stdout, stderr=sys.stderr, stdin=sys.stdin) if result != 0: raise Exception('Execution of "%s" failed' % ' '.join(cmd)) def Gsutil(*cmd): return ExecuteCommand('python', GSUTIL, *cmd) def GsutilVisible(*cmd): ExecuteCommandVisible('python', GSUTIL, *cmd) def HasBotoConfig(): """Returns true if boto config exists.""" config_paths = boto.pyami.config.BotoConfigLocations if 'AWS_CREDENTIAL_FILE' in os.environ: config_paths.append(os.environ['AWS_CREDENTIAL_FILE']) for config_path in config_paths: if os.path.exists(config_path): return True return False def InRunhooks(): """True if this script was called by "gclient runhooks" or "gclient sync\"""" return 'runhooks' in sys.argv def GetDartiumRevision(name, bot, directory, version_file, latest_pattern, permanent_prefix, revision_num=None): """Get the latest binary that is stored in the dartium archive. Args: name: the name of the desired download. directory: target directory (recreated) to install binary version_file: name of file with the current version stamp latest_pattern: the google store url pattern pointing to the latest binary permanent_prefix: stable google store folder used to download versions revision_num: The desired revision number to retrieve. If revision_num is None, we return the latest revision. If the revision number is specified but unavailable, find the nearest older revision and use that instead. """ osdict = {'Darwin':'mac-x64', 'Linux':'linux-x64', 'Windows':'win-ia32'} def FindPermanentUrl(out, osname, the_revision_num): output_lines = out.split() latest = output_lines[-1] if not the_revision_num: latest = (permanent_prefix[:permanent_prefix.rindex('/')] % { 'osname' : osname, 'bot' : bot } + latest[latest.rindex('/'):]) else: latest = (permanent_prefix % { 'osname' : osname, 'num1' : the_revision_num, 'num2' : the_revision_num, 'bot' : bot }) foundURL = False while not foundURL: # Test to ensure this URL exists because the dartium-archive builds can # have unusual numbering (a range of CL numbers) sometimes. result, out = Gsutil('ls', permanent_prefix % {'osname' : osname, 'num1': the_revision_num, 'num2': '*', 'bot': bot }) if result == 0: # First try to find one with the second number the same as the # requested number. latest = out.split()[0] # Now test that the permissions are correct so you can actually # download it. temp_dir = tempfile.mkdtemp() temp_zip = os.path.join(temp_dir, 'foo.zip') returncode, out = Gsutil('cp', latest, 'file://' + temp_zip) if returncode == 0: foundURL = True else: # Unable to download this item (most likely because something went # wrong on the upload and the permissions are bad). Keep looking for # a different URL. the_revision_num = int(the_revision_num) - 1 shutil.rmtree(temp_dir) else: # Now try to find one with a nearby CL num. the_revision_num = int(the_revision_num) - 1 if the_revision_num <= 0: TooEarlyError() return latest GetFromGsutil(name, directory, version_file, latest_pattern, osdict, FindPermanentUrl, revision_num, bot) def GetSdkRevision(name, directory, version_file, latest_pattern, permanent_prefix, revision_num): """Get a revision of the SDK from the editor build archive. Args: name: the name of the desired download directory: target directory (recreated) to install binary version_file: name of file with the current version stamp latest_pattern: the google store url pattern pointing to the latest binary permanent_prefix: stable google store folder used to download versions revision_num: the desired revision number, or None for the most recent """ osdict = {'Darwin':'macos', 'Linux':'linux', 'Windows':'win32'} def FindPermanentUrl(out, osname, not_used): rev_num = revision_num if not rev_num: temp_file = tempfile.NamedTemporaryFile(delete=False) temp_file.close() temp_file_url = 'file://' + temp_file.name Gsutil('cp', latest_pattern % {'osname' : osname }, temp_file_url) temp_file = open(temp_file.name) temp_file.seek(0) version_info = temp_file.read() temp_file.close() os.unlink(temp_file.name) if version_info != '': rev_num = json.loads(version_info)['revision'] else: print 'Unable to get latest version information.' return '' latest = (permanent_prefix % { 'osname' : osname, 'version_num': rev_num}) return latest GetFromGsutil(name, directory, version_file, latest_pattern, osdict, FindPermanentUrl, revision_num) def GetFromGsutil(name, directory, version_file, latest_pattern, os_name_dict, get_permanent_url, revision_num = '', bot = None): """Download and unzip the desired file from Google Storage. Args: name: the name of the desired download directory: target directory (recreated) to install binary version_file: name of file with the current version stamp latest_pattern: the google store url pattern pointing to the latest binary os_name_dict: a dictionary of operating system names and their corresponding strings on the google storage site. get_permanent_url: a function that accepts a listing of available files and the os name, and returns a permanent URL for downloading. revision_num: the desired revision number to get (if not supplied, we get the latest revision) """ system = platform.system() try: osname = os_name_dict[system] except KeyError: print >>sys.stderr, ('WARNING: platform "%s" does not support' '%s.') % (system, name) return 0 # Query for the latest version pattern = latest_pattern % { 'osname' : osname, 'bot' : bot } result, out = Gsutil('ls', pattern) if result == 0: # use permanent link instead, just in case the latest zip entry gets deleted # while we are downloading it. latest = get_permanent_url(out, osname, revision_num) else: # e.g. no access print "Couldn't download %s: %s\n%s" % (name, pattern, out) if not os.path.exists(version_file): print "Using %s will not work. Please try again later." % name return 0 # Check if we need to update the file if os.path.exists(version_file): v = open(version_file, 'r').read() if v == latest: if not InRunhooks(): print name + ' is up to date.\nVersion: ' + latest return 0 # up to date # download the zip file to a temporary path, and unzip to the target location temp_dir = tempfile.mkdtemp() try: temp_zip = os.path.join(temp_dir, 'drt.zip') temp_zip_url = 'file://' + temp_zip # It's nice to show download progress GsutilVisible('cp', latest, temp_zip_url) if platform.system() != 'Windows': # The Python zip utility does not preserve executable permissions, but # this does not seem to be a problem for Windows, which does not have a # built in zip utility. :-/ result, out = ExecuteCommand('unzip', temp_zip, '-d', temp_dir) if result != 0: raise Exception('Execution of "unzip %s -d %s" failed: %s' % (temp_zip, temp_dir, str(out))) unzipped_dir = temp_dir + '/' + os.path.basename(latest)[:-len('.zip')] else: z = zipfile.ZipFile(temp_zip) z.extractall(temp_dir) unzipped_dir = os.path.join(temp_dir, os.path.basename(latest)[:-len('.zip')]) z.close() if directory == SDK_DIR: unzipped_dir = os.path.join(temp_dir, 'dart-sdk') if os.path.exists(directory): print 'Removing old %s tree %s' % (name, directory) shutil.rmtree(directory) if os.path.exists(directory): raise Exception( 'Removal of directory %s failed. Is the executable running?' % directory) shutil.move(unzipped_dir, directory) finally: shutil.rmtree(temp_dir) # create the version stamp v = open(version_file, 'w') v.write(latest) v.close() print 'Successfully downloaded to %s' % directory return 0 def TooEarlyError(): """Quick shortcutting function, to return early if someone requests a revision that is smaller than the earliest stored. This saves us from doing repeated requests until we get down to 0.""" print ('Unable to download requested revision because it is earlier than the ' 'earliest revision stored.') sys.exit(1) def CopyDrtFont(drt_dir): if platform.system() != 'Windows': return shutil.copy('third_party/drt_resources/AHEM____.TTF', drt_dir) def main(): parser = optparse.OptionParser(usage='usage: %prog [options] download_name') parser.add_option('-r', '--revision', dest='revision', help='Desired revision number to retrieve for the SDK. If ' 'unspecified, retrieve the latest SDK build.', action='store', default=None) parser.add_option('-d', '--debug', dest='debug', help='Download a debug archive instead of a release.', action='store_true', default=False) args, positional = parser.parse_args() if args.revision and int(args.revision) < LAST_VALID[positional[0]]: return TooEarlyError() # Use the incremental release bot ('dartium-*-inc-be') by default. # Issue 13399 Quick fix, update with channel support. bot = 'inc-be' if args.debug: print >>sys.stderr, ( 'Debug versions of Dartium and content_shell not available') return 1 if positional[0] == 'dartium': GetDartiumRevision('Dartium', bot, DARTIUM_DIR, DARTIUM_VERSION, DARTIUM_LATEST_PATTERN, DARTIUM_PERMANENT_PATTERN, args.revision) elif positional[0] == 'sdk': GetSdkRevision('sdk', SDK_DIR, SDK_VERSION, SDK_LATEST_PATTERN, SDK_PERMANENT, args.revision) elif positional[0] == 'drt': GetDartiumRevision('content_shell', bot, DRT_DIR, DRT_VERSION, DRT_LATEST_PATTERN, DRT_PERMANENT_PATTERN, args.revision) CopyDrtFont(DRT_DIR) else: print ('Please specify the target you wish to download from Google Storage ' '("drt", "dartium", "chromedriver", or "sdk")') if __name__ == '__main__': sys.exit(main())