#!/usr/bin/python # Copyright (c) 2012, 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. # Run to install the necessary components to run webdriver on the buildbots or # on your local machine. # Note: The setup steps can be done fairly easily by hand. This script is # intended to simply and reduce the time for setup since there are a fair number # of steps. # TODO(efortuna): Rewrite this script in Dart when the Process module has a # better high level API. import HTMLParser import optparse import os import platform import re import shutil import string import subprocess import sys import urllib import urllib2 import zipfile def run_cmd(cmd, stdin=None): """Run the command on the command line in the shell. We print the output of the command. """ print cmd p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, stdin=subprocess.PIPE, shell=True) output, stderr = p.communicate(input=stdin) if output: print output if stderr: print stderr def parse_args(): parser = optparse.OptionParser() parser.add_option('--firefox', '-f', dest='firefox', help="Don't install Firefox", action='store_true', default=False) parser.add_option('--opera', '-o', dest='opera', default=False, help="Don't install Opera", action='store_true') parser.add_option('--chromedriver', '-c', dest='chromedriver', help="Don't install chromedriver.", action='store_true', default=False) parser.add_option('--iedriver', '-i', dest='iedriver', help="Don't install iedriver (only used on Windows).", action='store_true', default=False) parser.add_option('--seleniumrc', '-s', dest='seleniumrc', help="Don't install the Selenium RC server (used for Safari and Opera " "tests).", action='store_true', default=False) parser.add_option('--python', '-p', dest='python', help="Don't install Selenium python bindings.", action='store_true', default=False) parser.add_option('--buildbot', '-b', dest='buildbot', action='store_true', help='Perform a buildbot selenium setup (buildbots have a different' + 'location for their python executable).', default=False) args, _ = parser.parse_args() return args def find_depot_tools_location(is_buildbot): """Depot_tools is our default install location for chromedriver, so we find its location on the filesystem. Arguments: is_buildbot - True if we are running buildbot machine setup (we can't detect this automatically because this script is not run at build time). """ if is_buildbot: depot_tools = os.sep + os.path.join('b', 'depot_tools') if 'win32' in sys.platform or 'cygwin' in sys.platform: depot_tools = os.path.join('e:', depot_tools) return depot_tools else: path = os.environ['PATH'].split(os.pathsep) for loc in path: if 'depot_tools' in loc: return loc raise Exception("Could not find depot_tools in your path.") class GoogleBasedInstaller(object): """Install a project from a Google source, pulling latest version.""" def __init__(self, project_name, destination, download_path_func): """Create an object that will install the project. Arguments: project_name - Google code name of the project, such as "selenium" or "chromedriver." destination - Where to download the desired file on our filesystem. download_path_func - A function that takes a dictionary (currently with keys "os" and "version", but more can be added) that calculates the string representing the path of the download we want. """ self.project_name = project_name self.destination = destination self.download_path_func = download_path_func @property def get_os_str(self): """The strings to indicate what OS a download is for.""" os_str = 'win' if 'darwin' in sys.platform: os_str = 'mac' elif 'linux' in sys.platform: os_str = 'linux32' if '64bit' in platform.architecture()[0]: os_str = 'linux64' if self.project_name == 'chromedriver' and ( os_str == 'mac' or os_str == 'win'): os_str += '32' return os_str def run(self): """Download and install the project.""" print 'Installing %s' % self.project_name os_str = self.get_os_str version = self.find_latest_version() download_path = self.download_path_func({'os': os_str, 'version': version}) download_name = os.path.basename(download_path) urllib.urlretrieve(os.path.join(self.source_path(), download_path), os.path.join(self.destination, download_name)) if download_name.endswith('.zip'): 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. :-/ run_cmd('unzip -u %s -d %s' % (os.path.join(self.destination, download_name), self.destination), stdin='y') else: z = zipfile.ZipFile(os.path.join(self.destination, download_name)) z.extractall(self.destination) z.close() os.remove(os.path.join(self.destination, download_name)) chrome_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'orig-chromedriver') if self.project_name == 'chromedriver' and os.path.exists(chrome_path): # We have one additional location to make sure chromedriver is updated. # TODO(efortuna): Remove this. See move_chrome_driver_if_needed in # perf_testing/run_perf_tests.py driver = 'chromedriver' if platform.system() == 'Windows': driver += '.exe' shutil.copy(os.path.join(self.destination, driver), os.path.join(chrome_path, driver)) class ChromeDriverInstaller(GoogleBasedInstaller): """Install chromedriver from Google Storage.""" def __init__(self, destination): """Create an object to install ChromeDriver destination - Where to download the desired file on our filesystem. """ super(ChromeDriverInstaller, self).__init__('chromedriver', destination, lambda x: '%(version)s/chromedriver_%(os)s.zip' % x) def find_latest_version(self): """Find the latest version number of ChromeDriver.""" source_page = urllib2.urlopen(self.source_path()) source_text = source_page.read() regex = re.compile('(?:)(\d+\.\d+)') latest = max(regex.findall(source_text)) return latest def source_path(self): return 'http://chromedriver.storage.googleapis.com' class GoogleCodeInstaller(GoogleBasedInstaller): """Install a project from Google Code.""" def google_code_downloads_page(self): return 'http://code.google.com/p/%s/downloads/list' % self.project_name def find_latest_version(self): """Find the latest version number of some code available for download on a Google code page. This was unfortunately done in an ad hoc manner because Google Code does not seem to have an API for their list of current downloads(!). """ google_code_site = self.google_code_downloads_page() f = urllib2.urlopen(google_code_site) latest = '' download_regex_str = self.download_path_func({'os': self.get_os_str, 'version': '.+'}) for line in f.readlines(): if re.search(download_regex_str, line): suffix_index = line.find( download_regex_str[download_regex_str.rfind('.'):]) name_end = download_regex_str.rfind('.+') name = self.download_path_func({'os': self.get_os_str, 'version': ''}) name = name[:name.rfind('.')] version_str = line[line.find(name) + len(name) : suffix_index] orig_version_str = version_str if version_str.count('.') == 0: version_str = version_str.replace('_', '.') version_str = re.compile(r'[^\d.]+').sub('', version_str) if latest == '': latest = '0.' * version_str.count('.') latest += '0' orig_latest_str = latest else: orig_latest_str = latest latest = latest.replace('_', '.') latest = re.compile(r'[^\d.]+').sub('', latest) nums = version_str.split('.') latest_nums = latest.split('.') for (num, latest_num) in zip(nums, latest_nums): if int(num) > int(latest_num): latest = orig_version_str break else: latest = orig_latest_str if latest == '': raise Exception("Couldn't find the desired download on " + \ ' %s.' % google_code_site) return latest def source_path(self): return 'http://%s.googlecode.com/files/' % self.project_name class FirefoxInstaller(object): """Installs the latest version of Firefox on the machine.""" def ff_download_site(self, os_name): return 'http://releases.mozilla.org/pub/mozilla.org/firefox/releases/' + \ 'latest/%s/en-US/' % os_name @property def get_os_str(self): """Returns the string that Mozilla uses to denote which operating system a Firefox binary is for.""" os_str = ('win32', '.exe') if 'darwin' in sys.platform: os_str = ('mac', '.dmg') elif 'linux' in sys.platform: os_str = ('linux-i686', '.tar.bz2') if '64bit' in platform.architecture()[0]: os_str = ('linux-x86_64', '.tar.bz2') return os_str def get_download_url(self): """Parse the html on the page to determine what is the latest download appropriate for our system.""" f = urllib2.urlopen(self.ff_download_site(self.get_os_str[0])) download_name = '' for line in f.readlines(): suffix = self.get_os_str[1] if (suffix + '"') in line: link_str = ' current: return int(version_string) return current version = self.find_latest_version( download_name, lambda x: x[0] in string.digits and 'b' not in x and 'rc' not in x, higher_revision) download_name += version if ('linux' in sys.platform and platform.linux_distribution()[0] == 'Ubuntu'): # Last time I tried, the .deb file you download directly from opera was # not installing correctly on Ubuntu. This installs Opera more nicely. os.system("sudo sh -c 'wget -O - http://deb.opera.com/archive.key | " "apt-key add -'") os.system("""sudo sh -c 'echo "deb http://deb.opera.com/opera/ """ """stable non-free" > /etc/apt/sources.list.d/opera.list'""") run_cmd('sudo apt-get update') run_cmd('sudo apt-get install opera', stdin='y') else: if 'darwin' in sys.platform: dotted_version = '%s.%s' % (version[:2], version[2:]) download_name += '/Opera_%s_Setup_Intel.dmg' % dotted_version urllib.urlretrieve(download_name, 'opera.dmg') run_cmd('hdiutil mount opera.dmg', stdin='qY\n') run_cmd('sudo cp -R /Volumes/Opera/Opera.app /Applications') run_cmd('hdiutil unmount /Volumes/Opera/') elif 'win' in sys.platform: download_name += '/en/Opera_%s_en_Setup.exe' % version urllib.urlretrieve(download_name, 'opera_install.exe') run_cmd('opera_install.exe -ms') else: # For all other flavors of linux, download the tar. download_name += '/' extension = '.tar.bz2' if '64bit' in platform.architecture()[0]: platform_str = '.x86_64' else: platform_str = '.i386' def get_acceptable_file(new_version_str, current): return new_version_str latest = self.find_latest_version( download_name, lambda x: x.startswith('opera') and x.endswith(extension) and platform_str in x, get_acceptable_file) download_name += latest run_cmd('wget -O - %s | tar -C ~ -jxv' % download_name) print ('PLEASE MANUALLY RUN "~/%s/install" TO COMPLETE OPERA ' 'INSTALLATION' % download_name[download_name.rfind('/') + 1:-len(extension)]) @property def get_os_str(self): """The strings to indicate what OS a download is.""" os_str = 'win' if 'darwin' in sys.platform: os_str = 'mac' elif 'linux' in sys.platform: os_str = 'linux' return os_str def main(): args = parse_args() if not args.python: SeleniumBindingsInstaller(args.buildbot).run() if not args.chromedriver: ChromeDriverInstaller(find_depot_tools_location(args.buildbot)).run() if not args.seleniumrc: GoogleCodeInstaller('selenium', os.path.dirname(os.path.abspath(__file__)), lambda x: 'selenium-server-standalone-%(version)s.jar' % x).run() if not args.iedriver and platform.system() == 'Windows': GoogleCodeInstaller('selenium', find_depot_tools_location(args.buildbot), lambda x: 'IEDriverServer_Win32_%(version)s.zip' % x).run() if not args.firefox: FirefoxInstaller().run() if not args.opera: OperaInstaller().run() if __name__ == '__main__': main()