Mercurial > powerbox
view manage.py @ 4:1c02976d8809
Changed to Firebug's progress listener, which allows us to inject our code before any scripts are executed on target pages.
author | Atul Varma <varmaa@toolness.com> |
---|---|
date | Thu, 06 Aug 2009 17:18:45 -0700 |
parents | 889c2fd4c9cf |
children |
line wrap: on
line source
#! /usr/bin/env python import os import sys if __name__ == '__main__': # This code is run if we're executed directly from the command-line. myfile = os.path.abspath(__file__) mydir = os.path.dirname(myfile) sys.path.insert(0, os.path.join(mydir, 'python-modules')) args = sys.argv[1:] if not args: args = ['help'] # Have paver run this very file as its pavement script. args = ['-f', myfile] + args import paver.tasks paver.tasks.main(args) sys.exit(0) # This code is run if we're executed as a pavement script by paver. import os import sys import fnmatch import distutils.dir_util import xml.dom.minidom import zipfile import shutil import distutils.dir_util import time import threading import subprocess import simplejson from ConfigParser import ConfigParser from paver.easy import * # Path to the root of the extension, relative to where this script is # located. EXT_SUBDIR = "extension" # Valid applications that this extension supports. The first one listed # is the default used if one isn't explicitly provided on the command-line. VALID_APPS = ['firefox', 'thunderbird'] # When launching a temporary new Firefox profile, use these preferences. DEFAULT_FIREFOX_PREFS = { 'browser.startup.homepage' : 'about:blank', 'startup.homepage_welcome_url' : 'about:blank', 'browser.dom.window.dump.enabled' : True, 'javascript.options.showInConsole' : True } # When launching a temporary new Thunderbird profile, use these preferences. # Note that these were taken from: # http://mxr.mozilla.org/comm-central/source/mail/test/mozmill/runtest.py DEFAULT_THUNDERBIRD_PREFS = { # say yes to debug output via dump 'browser.dom.window.dump.enabled': True, # say no to slow script warnings 'dom.max_chrome_script_run_time': 200, 'dom.max_script_run_time': 0, # disable extension stuffs 'extensions.update.enabled' : False, 'extensions.update.notifyUser' : False, # do not ask about being the default mail client 'mail.shell.checkDefaultClient': False, # disable non-gloda indexing daemons 'mail.winsearch.enable': False, 'mail.winsearch.firstRunDone': True, 'mail.spotlight.enable': False, 'mail.spotlight.firstRunDone': True, # disable address books for undisclosed reasons 'ldap_2.servers.osx.position': 0, 'ldap_2.servers.oe.position': 0, # disable the first use junk dialog 'mailnews.ui.junk.firstuse': False, # other unknown voodoo # -- dummied up local accounts to stop the account wizard 'mail.account.account1.server' : "server1", 'mail.account.account2.identities' : "id1", 'mail.account.account2.server' : "server2", 'mail.accountmanager.accounts' : "account1,account2", 'mail.accountmanager.defaultaccount' : "account2", 'mail.accountmanager.localfoldersserver' : "server1", 'mail.identity.id1.fullName' : "Tinderbox", 'mail.identity.id1.smtpServer' : "smtp1", 'mail.identity.id1.useremail' : "tinderbox@invalid.com", 'mail.identity.id1.valid' : True, 'mail.root.none-rel' : "[ProfD]Mail", 'mail.root.pop3-rel' : "[ProfD]Mail", 'mail.server.server1.directory-rel' : "[ProfD]Mail/Local Folders", 'mail.server.server1.hostname' : "Local Folders", 'mail.server.server1.name' : "Local Folders", 'mail.server.server1.type' : "none", 'mail.server.server1.userName' : "nobody", 'mail.server.server2.check_new_mail' : False, 'mail.server.server2.directory-rel' : "[ProfD]Mail/tinderbox", 'mail.server.server2.download_on_biff' : True, 'mail.server.server2.hostname' : "tinderbox", 'mail.server.server2.login_at_startup' : False, 'mail.server.server2.name' : "tinderbox@invalid.com", 'mail.server.server2.type' : "pop3", 'mail.server.server2.userName' : "tinderbox", 'mail.smtp.defaultserver' : "smtp1", 'mail.smtpserver.smtp1.hostname' : "tinderbox", 'mail.smtpserver.smtp1.username' : "tinderbox", 'mail.smtpservers' : "smtp1", 'mail.startup.enabledMailCheckOnce' : True, 'mailnews.start_page_override.mstone' : "ignore", } PROFILE_DIRS = Bunch( firefox = Bunch( darwin = "~/Library/Application Support/Firefox/", windows = "Mozilla\\Firefox", linux = "~/.mozilla/firefox/" ), thunderbird = Bunch( darwin = "~/Library/Thunderbird/", windows = "Mozilla\\Thunderbird", linux = "~/.thunderbird/" ) ) def clear_dir(dirname): if os.path.exists(dirname) and os.path.isdir(dirname): shutil.rmtree(dirname) def find_profile_dir(app, name): """ Given the name of an application and its profile, attempts to find the absolute path to its directory. If it can't be found, None is returned. """ base_path = None if sys.platform == "darwin": base_path = os.path.expanduser(PROFILE_DIRS[app].darwin) elif (sys.platform.startswith("win") or sys.platform == "cygwin"): # TODO: This only works on 2000/XP/Vista, not 98/Me. appdata = os.environ["APPDATA"] base_path = os.path.join(appdata, PROFILE_DIRS[app].windows) else: base_path = os.path.expanduser(PROFILE_DIRS[app].linux) inifile = os.path.join(base_path, "profiles.ini") config = ConfigParser() config.read(inifile) profiles = [section for section in config.sections() if section.startswith("Profile")] for profile in profiles: if config.get(profile, "Name") == name: # TODO: Look at IsRelative? path = config.get(profile, "Path") if not os.path.isabs(path): path = os.path.join(base_path, path) return path return None def get_install_rdf_dom(path_to_ext_root): rdf_path = os.path.join(path_to_ext_root, "install.rdf") rdf = xml.dom.minidom.parse(rdf_path) return rdf def get_install_rdf_property(path_to_ext_root, property): rdf = get_install_rdf_dom(path_to_ext_root) element = rdf.documentElement.getElementsByTagName(property)[0] return element.firstChild.nodeValue def resolve_options(options, ext_subdir = EXT_SUBDIR): if not options.get('app'): options.app = VALID_APPS[0] if not options.get('profile'): options.profile = 'default' if options.app not in VALID_APPS: print "Unrecognized or unsupported application: %s." % options.app sys.exit(1) options.my_dir = os.path.dirname(os.path.abspath(options.pavement_file)) options.profile_dir = find_profile_dir(options.app, options.profile) options.path_to_ext_root = os.path.join(options.my_dir, ext_subdir) options.ext_id = get_install_rdf_property(options.path_to_ext_root, "em:id") options.ext_version = get_install_rdf_property(options.path_to_ext_root, "em:version") options.ext_name = get_install_rdf_property(options.path_to_ext_root, "em:name") if options.profile_dir: options.extension_file = os.path.join(options.profile_dir, "extensions", options.ext_id) # If cygwin, change the path to windows format so firefox can # understand it. if sys.platform == "cygwin": # TODO: Will this work if path_to_ext_root has spaces in it? file = 'cygpath.exe -w ' + options.path_to_ext_root path = "".join(os.popen(file).readlines()) path = path.replace("\n", " ").rstrip() options.firefox_path_to_ext_root = path else: options.firefox_path_to_ext_root = options.path_to_ext_root def remove_extension(options): if not (options.profile_dir and os.path.exists(options.profile_dir) and os.path.isdir(options.profile_dir)): raise BuildFailure("Can't resolve profile directory; aborting.") files_to_remove = ["compreg.dat", "xpti.dat"] for filename in files_to_remove: abspath = os.path.join(options.profile_dir, filename) if os.path.exists(abspath): os.remove(abspath) if os.path.exists(options.extension_file): if os.path.isdir(options.extension_file): shutil.rmtree(options.extension_file) else: os.remove(options.extension_file) APP_OPTION = ("app=", "a", "Application to use. Defaults to %s. " "Valid choices are: %s." % (VALID_APPS[0], ", ".join(VALID_APPS))) INSTALL_OPTIONS = [("profile=", "p", "Profile name."), APP_OPTION] JSBRIDGE_OPTIONS = [("port=", "p", "Port to use for jsbridge communication."), ("binary=", "b", "Path to application binary."), APP_OPTION] @task @cmdopts(INSTALL_OPTIONS) def install(options): """Install the extension to an application profile.""" resolve_options(options) remove_extension(options) extdir = os.path.dirname(options.extension_file) if not os.path.exists(extdir): distutils.dir_util.mkpath(extdir) fileobj = open(options.extension_file, "w") fileobj.write(options.firefox_path_to_ext_root) fileobj.close() copy_libs(options) print "Extension '%s' installed to %s profile '%s'." % (options.ext_id, options.app, options.profile) @task @cmdopts(INSTALL_OPTIONS) def uninstall(options): """Uninstall the extension from an application profile.""" resolve_options(options) remove_extension(options) print "Extension '%s' uninstalled from %s profile '%s'." % (options.ext_id, options.app, options.profile) @task def xpi(options): """Build a distributable xpi installer for the extension.""" resolve_options(options) platforms = os.listdir(os.path.join(options.my_dir, "lib")) for platform in platforms: zfname = "%s-%s-%s.xpi" % (options.ext_name.lower(), options.ext_version, platform) copy_libs(options, platform) zf = zipfile.ZipFile(zfname, "w", zipfile.ZIP_DEFLATED) for dirpath, dirnames, filenames in os.walk(options.path_to_ext_root): if platform in dirnames: # We're in the extension/platform directory, get rid of files for # other platforms. dirnames[:] = [platform] for filename in filenames: abspath = os.path.join(dirpath, filename) arcpath = abspath[len(options.path_to_ext_root)+1:] zf.write(abspath, arcpath) print "Created %s." % zfname def start_jsbridge(options): import mozrunner import jsbridge resolve_options(options) if not options.get('port'): options.port = '24242' options.port = int(options.port) options.binary = options.get('binary') plugins = [jsbridge.extension_path, options.path_to_ext_root] if options.app == 'firefox': profile_class = mozrunner.FirefoxProfile preferences = DEFAULT_FIREFOX_PREFS runner_class = mozrunner.FirefoxRunner elif options.app == 'thunderbird': profile_class = mozrunner.ThunderbirdProfile preferences = DEFAULT_THUNDERBIRD_PREFS runner_class = mozrunner.ThunderbirdRunner profile = profile_class(plugins=plugins, preferences=preferences) runner = runner_class(profile=profile, binary=options.binary, cmdargs=["-jsbridge", str(options.port)]) runner.start() back_channel, bridge = jsbridge.wait_and_create_network("127.0.0.1", options.port) return Bunch(back_channel = back_channel, bridge = bridge, runner = runner) def start_jetpack(options, listener): remote = start_jsbridge(options) import jsbridge code = ( "((function() { var extension = {}; " "Components.utils.import('resource://jetpack/modules/init.js', " "extension); return extension; })())" ) remote.back_channel.add_global_listener(listener) extension = jsbridge.JSObject(remote.bridge, code) INTERVAL = 0.1 MAX_STARTUP_TIME = 5.0 is_done = False time_elapsed = 0.0 try: while not is_done: time.sleep(INTERVAL) time_elapsed += INTERVAL if time_elapsed > MAX_STARTUP_TIME: raise Exception('Maximum startup time exceeded.') url = 'chrome://jetpack/content/index.html' window = extension.get(url) if window is None: #print "Waiting for index to load." continue if hasattr(window, 'frameElement'): #print "Window is in an iframe." continue if window.closed: #print "Window is closed." continue if not hasattr(window, 'JSBridge'): #print "window.JSBridge does not exist." continue if not window.JSBridge.isReady: #print "Waiting for about:jetpack to be ready." continue is_done = True except: remote.runner.stop() raise remote.window = window return remote @task @cmdopts(JSBRIDGE_OPTIONS) def run(options): """Run the application in a temporary new profile with the extension installed.""" remote = start_jsbridge(options) try: print "Now running, press Ctrl-C to stop." remote.runner.wait() except KeyboardInterrupt: print "Received interrupt, stopping." remote.runner.stop() @task @cmdopts(JSBRIDGE_OPTIONS) def render_docs(options): """Render the API and tutorial documentation in HTML format, and output it to the website directory.""" # TODO: Render tutorial docs too (bug 496457). TEMPLATE = os.path.join("website", "templates", "api.html") OUTPUT = os.path.join("website", "api.html") done_event = threading.Event() result = Bunch() def listener(event_name, obj): if event_name == 'jetpack:result': result.update(obj) done_event.set() MAX_RENDER_RUN_TIME = 10.0 remote = start_jetpack(options, listener) try: remote.window.JSBridge.renderDocs() done_event.wait(MAX_RENDER_RUN_TIME) if not done_event.isSet(): raise Exception('Maximum render run time exceeded.') finally: remote.runner.stop() template = open(TEMPLATE).read(); template = template.replace( "[[CONTENT]]", result.apiHtml.encode("ascii", "xmlcharrefreplace") ) open(OUTPUT, "w").write(template) print "Wrote API docs to %s using template at %s." % (OUTPUT, TEMPLATE) @task @cmdopts(JSBRIDGE_OPTIONS + [("filter=", "f", "Run only test suites containing the given string.")]) def test(options): """Run unit and functional tests.""" done_event = threading.Event() result = Bunch() def listener(event_name, obj): if event_name == 'jetpack:message': if obj.get('isWarning', False): print "[WARNING]: %s" % obj['message'] elif obj.get('isError', False): print "[ERROR] : %s" % obj['message'] else: print "[message]: %s" % obj['message'] if obj.get('sourceName'): print " %s:L%s" % (obj['sourceName'], obj.get('lineNumber', '?')) elif event_name == 'jetpack:result': result.obj = obj done_event.set() MAX_TEST_RUN_TIME = 25.0 remote = start_jetpack(options, listener) try: remote.window.JSBridge.runTests(options.get("filter")) done_event.wait(MAX_TEST_RUN_TIME) if not done_event.isSet(): raise Exception('Maximum test run time exceeded.') finally: remote.runner.stop() print "Tests failed: %d" % result.obj['failed'] print "Tests succeeded: %d" % result.obj['succeeded'] if result.obj['failed'] > 0: sys.exit(result.obj['failed']) @task def clean(options): """Removes all intermediate and non-essential files.""" resolve_options(options) clear_dir(os.path.join(options.path_to_ext_root, "lib")) EXTENSIONS_TO_REMOVE = [".pyc", ".orig", ".rej"] for dirpath, dirnames, filenames in os.walk(os.getcwd()): if ".hg" in dirnames: dirnames.remove(".hg") for filename in filenames: fullpath = os.path.join(dirpath, filename) ext = os.path.splitext(filename)[1] if ext in EXTENSIONS_TO_REMOVE: os.remove(fullpath) def run_program(args, **kwargs): retval = subprocess.call(args, **kwargs) if retval: print "Process failed with exit code %d." % retval sys.exit(retval) def copy_libs(options, platform = None): """Copy the platform-specific dynamic library files from our versioned repository into the extension's temporary directory.""" if platform is None: if sys.platform == "darwin": platform = "Darwin_x86-gcc3" elif sys.platform.startswith("linux"): platform = "Linux_x86-gcc3" else: # Assume Windows. platform = "WINNT_x86-msvc" src_dir = os.path.join(options.my_dir, "lib", platform) dest_dir = os.path.join(options.path_to_ext_root, "lib") clear_dir(dest_dir) shutil.copytree(src_dir, dest_dir) @task @cmdopts([("srcdir=", "t", "The root of your mozilla-central checkout"), ("objdir=", "o", "The root of your objdir")]) def xpcom(options): """Builds binary XPCOM components for Jetpack.""" for option in ["srcdir", "objdir"]: if not options.get(option): raise Exception("Please specify a value for the '%s' option." % option) for dirname in ["srcdir", "objdir"]: options[dirname] = os.path.expanduser(options[dirname]) options[dirname] = os.path.abspath(options[dirname]) resolve_options(options) options.xpcshell = os.path.join(options.objdir, "dist", "bin", "xpcshell") xpcom_info = Bunch() xpcom_info.components_dir = os.path.join(options.objdir, "dist", "bin", "components") autoconf = open(os.path.join(options.objdir, "config", "autoconf.mk"), "r").readlines() for line in autoconf: if line.startswith("OS_TARGET"): xpcom_info.os = line.split("=")[1].strip() elif line.startswith("TARGET_XPCOM_ABI"): xpcom_info.abi = line.split("=")[1].strip() elif line.startswith("MOZILLA_VERSION"): xpcom_info.mozilla_version = line.split("=")[1].strip()[:5] elif (line.startswith("MOZ_DEBUG") and not line.startswith("MOZ_DEBUG_")): raw_value = line.split("=")[1].strip() if not raw_value: xpcom_info.is_debug = 0 else: xpcom_info.is_debug = int(raw_value) platform = "%(os)s_%(abi)s" % xpcom_info print "Building XPCOM binary components for %s" % platform comp_src_dir = os.path.join(options.my_dir, "components") rel_dest_dir = os.path.join("browser", "components", "jetpack") comp_dest_dir = os.path.join(options.srcdir, rel_dest_dir) comp_xpi_dir = os.path.join(options.objdir, "dist", "xpi-stage", "jetpack", "components") comp_plat_dir1 = os.path.join(options.my_dir, "lib", platform, xpcom_info.mozilla_version) comp_plat_dir2 = os.path.join(options.path_to_ext_root, "lib", xpcom_info.mozilla_version) clear_dir(comp_dest_dir) clear_dir(comp_xpi_dir) shutil.copytree(comp_src_dir, comp_dest_dir) # Ensure that these paths are unix-like on Windows. sh_pwd = subprocess.Popen(["sh", "-c", "pwd"], cwd=options.srcdir, stdout=subprocess.PIPE) sh_pwd.wait() unix_topsrcdir = sh_pwd.stdout.read().strip() unix_rel_dest_dir = rel_dest_dir.replace("\\", "/") # We're specifying 'perl' here because we have to for this # to work on Windows. run_program(["perl", os.path.join(options.srcdir, "build", "autoconf", "make-makefile"), "-t", unix_topsrcdir, unix_rel_dest_dir], cwd=options.objdir) run_program(["make"], cwd=os.path.join(options.objdir, rel_dest_dir)) xptfiles = [] libfiles = [] for filename in os.listdir(comp_xpi_dir): if fnmatch.fnmatch(filename, '*.xpt'): xptfiles.append(filename) else: libfiles.append(filename) def copy_libs(dest_dir): clear_dir(dest_dir) distutils.dir_util.mkpath(dest_dir) for filename in libfiles: shutil.copy(os.path.join(comp_xpi_dir, filename), dest_dir) if not xpcom_info.is_debug: copy_libs(comp_plat_dir1) copy_libs(comp_plat_dir2) for filename in xptfiles: shutil.copy(os.path.join(comp_xpi_dir, filename), os.path.join(options.path_to_ext_root, "components")) for filename in os.listdir(comp_xpi_dir): shutil.copy(os.path.join(comp_xpi_dir, filename), xpcom_info.components_dir) for filename in ["compreg.dat", "xpti.dat"]: fullpath = os.path.join(xpcom_info.components_dir, filename) if os.path.exists(fullpath): os.unlink(fullpath) # Now run unit tests via xpcshell. env = {} env.update(os.environ) if sys.platform.startswith("linux"): env['LD_LIBRARY_PATH'] = os.path.dirname(options.xpcshell) run_program([options.xpcshell, os.path.join(options.my_dir, "extension", "content", "js", "tests", "test-nsjetpack.js")], env=env, cwd=os.path.dirname(options.xpcshell))