Mercurial > powerbox
diff manage.py @ 0:d3ccbd89f5cc
Origination.
author | Atul Varma <varmaa@toolness.com> |
---|---|
date | Thu, 06 Aug 2009 10:50:23 -0700 |
parents | |
children | 889c2fd4c9cf |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/manage.py Thu Aug 06 10:50:23 2009 -0700 @@ -0,0 +1,643 @@ +#! /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', + } + +# 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))