diff options
author | Steve Block <steveblock@google.com> | 2010-02-02 14:57:50 +0000 |
---|---|---|
committer | Steve Block <steveblock@google.com> | 2010-02-04 15:06:55 +0000 |
commit | d0825bca7fe65beaee391d30da42e937db621564 (patch) | |
tree | 7461c49eb5844ffd1f35d1ba2c8b7584c1620823 /WebKitTools/Scripts/webkitpy/autoinstall.py | |
parent | 3db770bd97c5a59b6c7574ca80a39e5a51c1defd (diff) | |
download | external_webkit-d0825bca7fe65beaee391d30da42e937db621564.zip external_webkit-d0825bca7fe65beaee391d30da42e937db621564.tar.gz external_webkit-d0825bca7fe65beaee391d30da42e937db621564.tar.bz2 |
Merge webkit.org at r54127 : Initial merge by git
Change-Id: Ib661abb595522f50ea406f72d3a0ce17f7193c82
Diffstat (limited to 'WebKitTools/Scripts/webkitpy/autoinstall.py')
-rw-r--r-- | WebKitTools/Scripts/webkitpy/autoinstall.py | 335 |
1 files changed, 335 insertions, 0 deletions
diff --git a/WebKitTools/Scripts/webkitpy/autoinstall.py b/WebKitTools/Scripts/webkitpy/autoinstall.py new file mode 100644 index 0000000..467e6b4 --- /dev/null +++ b/WebKitTools/Scripts/webkitpy/autoinstall.py @@ -0,0 +1,335 @@ +# Copyright (c) 2009, Daniel Krech All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# * Neither the name of the Daniel Krech nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +"""\ +package loader for auto installing Python packages. + +A package loader in the spirit of Zero Install that can be used to +inject dependencies into the import process. + + +To install:: + + easy_install -U autoinstall + + or + + download, unpack, python setup.py install + + or + + try the bootstrap loader. See below. + + +To use:: + + # You can bind any package name to a URL pointing to something + # that can be imported using the zipimporter. + + autoinstall.bind("pymarc", "http://pypi.python.org/packages/2.5/p/pymarc/pymarc-2.1-py2.5.egg") + + import pymarc + + print pymarc.__version__, pymarc.__file__ + + +Changelog:: + +- added support for non top level packages. +- cache files now use filename part from URL. +- applied patch from Eric Seidel <eseidel@google.com> to add support +for loading modules where the module is not at the root of the .zip +file. + + +TODO:: + +- a description of the intended use case +- address other issues pointed out in: + + http://mail.python.org/pipermail/python-dev/2008-March/077926.html + +Scribbles:: + +pull vs. push +user vs. system +web vs. filesystem +auto vs. manual + +manage development sandboxes + +optional interfaces... + + def get_data(pathname) -> string with file data. + + Return the data associated with 'pathname'. Raise IOError if + the file wasn't found."); + + def is_package, + "is_package(fullname) -> bool. + + Return True if the module specified by fullname is a package. + Raise ZipImportError is the module couldn't be found."); + + def get_code, + "get_code(fullname) -> code object. + + Return the code object for the specified module. Raise ZipImportError + is the module couldn't be found."); + + def get_source, + "get_source(fullname) -> source string. + + Return the source code for the specified module. Raise ZipImportError + is the module couldn't be found, return None if the archive does + contain the module, but has no source for it."); + + +Autoinstall can also be bootstraped with the nascent package loader +bootstrap module. For example:: + + # or via the bootstrap + # loader. + + try: + _version = "0.2" + import autoinstall + if autoinstall.__version__ != _version: + raise ImportError("A different version than expected found.") + except ImportError, e: + # http://svn.python.org/projects/sandbox/trunk/bootstrap/bootstrap.py + import bootstrap + pypi = "http://pypi.python.org" + dir = "packages/source/a/autoinstall" + url = "%s/%s/autoinstall-%s.tar.gz" % (pypi, dir, _version) + bootstrap.main((url,)) + import autoinstall + +References:: + + http://0install.net/ + http://www.python.org/dev/peps/pep-0302/ + http://svn.python.org/projects/sandbox/trunk/import_in_py + http://0install.net/injector-find.html + http://roscidus.com/desktop/node/903 + +""" + +# To allow use of the "with" keyword for Python 2.5 users. +from __future__ import with_statement + +__version__ = "0.2" +__docformat__ = "restructuredtext en" + +import os +import new +import sys +import urllib +import logging +import tempfile +import zipimport + +_logger = logging.getLogger(__name__) + + +_importer = None + +def _getImporter(): + global _importer + if _importer is None: + _importer = Importer() + sys.meta_path.append(_importer) + return _importer + +def bind(package_name, url, zip_subpath=None): + """bind a top level package name to a URL. + + The package name should be a package name and the url should be a + url to something that can be imported using the zipimporter. + + Optional zip_subpath parameter allows searching for modules + below the root level of the zip file. + """ + _getImporter().bind(package_name, url, zip_subpath) + + +class Cache(object): + + def __init__(self, directory=None): + if directory is None: + # Default to putting the cache directory in the same directory + # as this file. + containing_directory = os.path.dirname(__file__) + directory = os.path.join(containing_directory, "autoinstall.cache.d"); + + self.directory = directory + try: + if not os.path.exists(self.directory): + self._create_cache_directory() + except Exception, err: + _logger.exception(err) + self.cache_directry = tempfile.mkdtemp() + _logger.info("Using cache directory '%s'." % self.directory) + + def _create_cache_directory(self): + _logger.debug("Creating cache directory '%s'." % self.directory) + os.mkdir(self.directory) + readme_path = os.path.join(self.directory, "README") + with open(readme_path, "w") as f: + f.write("This directory was auto-generated by '%s'.\n" + "It is safe to delete.\n" % __file__) + + def get(self, url): + _logger.info("Getting '%s' from cache." % url) + filename = url.rsplit("/")[-1] + + # so that source url is significant in determining cache hits + d = os.path.join(self.directory, "%s" % hash(url)) + if not os.path.exists(d): + os.mkdir(d) + + filename = os.path.join(d, filename) + + if os.path.exists(filename): + _logger.debug("... already cached in file '%s'." % filename) + else: + _logger.debug("... not in cache. Caching in '%s'." % filename) + stream = file(filename, "wb") + self.download(url, stream) + stream.close() + return filename + + def download(self, url, stream): + _logger.info("Downloading: %s" % url) + try: + netstream = urllib.urlopen(url) + code = 200 + if hasattr(netstream, "getcode"): + code = netstream.getcode() + if not 200 <= code < 300: + raise ValueError("HTTP Error code %s" % code) + except Exception, err: + _logger.exception(err) + + BUFSIZE = 2**13 # 8KB + size = 0 + while True: + data = netstream.read(BUFSIZE) + if not data: + break + stream.write(data) + size += len(data) + netstream.close() + _logger.info("Downloaded %d bytes." % size) + + +class Importer(object): + + def __init__(self): + self.packages = {} + self.__cache = None + + def __get_store(self): + return self.__store + store = property(__get_store) + + def _get_cache(self): + if self.__cache is None: + self.__cache = Cache() + return self.__cache + def _set_cache(self, cache): + self.__cache = cache + cache = property(_get_cache, _set_cache) + + def find_module(self, fullname, path=None): + """-> self or None. + + Search for a module specified by 'fullname'. 'fullname' must be + the fully qualified (dotted) module name. It returns the + zipimporter instance itself if the module was found, or None if + it wasn't. The optional 'path' argument is ignored -- it's + there for compatibility with the importer protocol."); + """ + _logger.debug("find_module(%s, path=%s)" % (fullname, path)) + + if fullname in self.packages: + (url, zip_subpath) = self.packages[fullname] + filename = self.cache.get(url) + zip_path = "%s/%s" % (filename, zip_subpath) if zip_subpath else filename + _logger.debug("fullname: %s url: %s path: %s zip_path: %s" % (fullname, url, path, zip_path)) + try: + loader = zipimport.zipimporter(zip_path) + _logger.debug("returning: %s" % loader) + except Exception, e: + _logger.exception(e) + return None + return loader + return None + + def bind(self, package_name, url, zip_subpath): + _logger.info("binding: %s -> %s subpath: %s" % (package_name, url, zip_subpath)) + self.packages[package_name] = (url, zip_subpath) + + +if __name__=="__main__": + import logging + #logging.basicConfig() + logger = logging.getLogger() + + console = logging.StreamHandler() + console.setLevel(logging.DEBUG) + # set a format which is simpler for console use + formatter = logging.Formatter('%(name)-12s: %(levelname)-8s %(message)s') + # tell the handler to use this format + console.setFormatter(formatter) + # add the handler to the root logger + logger.addHandler(console) + logger.setLevel(logging.INFO) + + bind("pymarc", "http://pypi.python.org/packages/2.5/p/pymarc/pymarc-2.1-py2.5.egg") + + import pymarc + + print pymarc.__version__, pymarc.__file__ + + assert pymarc.__version__=="2.1" + + d = _getImporter().cache.directory + assert d in pymarc.__file__, "'%s' not found in pymarc.__file__ (%s)" % (d, pymarc.__file__) + + # Can now also bind to non top level packages. The packages + # leading up to the package being bound will need to be defined + # however. + # + # bind("rdf.plugins.stores.memory", + # "http://pypi.python.org/packages/2.5/r/rdf.plugins.stores.memeory/rdf.plugins.stores.memory-0.9a-py2.5.egg") + # + # from rdf.plugins.stores.memory import Memory + + |