Commits

Alexis Metaireau  committed b6abb10 Draft

work in progress

  • Participants
  • Parent commits d1b93b7

Comments (0)

Files changed (6)

File distutils2/database.py

 
         elif path.endswith('.egg-info'):
             if os.path.isdir(path):
+                req_path = os.path.join(path, 'requires.txt')
                 path = os.path.join(path, 'PKG-INFO')
-                req_path = os.path.join(path, 'requires.txt')
                 requires = parse_requires(req_path)
             self.metadata = Metadata(path=path)
             self.name = self.metadata['Name']
     return '-'.join([name, normalized_version]) + file_extension
 
 
-def get_distributions(use_egg_info=True, paths=None):
+def get_distributions(use_egg_info=True, paths=None, use_cache=True):
     """
     Provides an iterator that looks for ``.dist-info`` directories in
     ``sys.path`` and returns :class:`Distribution` instances for each one of
     them. If the parameters *use_egg_info* is ``True``, then the ``.egg-info``
     files and directores are iterated as well.
 
+    :param use_cache: if set to false, do not try to check information in the
+                      cache.
+
     :rtype: iterator of :class:`Distribution` and :class:`EggInfoDistribution`
             instances
     """
     if paths is None:
         paths = sys.path
 
-    if not _cache_enabled:
+    if not _cache_enabled or not use_cache:
         for dist in _yield_distributions(True, use_egg_info, paths):
             yield dist
     else:
                 yield dist
 
 
-def get_distribution(name, use_egg_info=True, paths=None):
+def get_distribution(name, use_egg_info=True, paths=None, use_cache=True):
     """
     Scans all elements in ``sys.path`` and looks for all directories
     ending with ``.dist-info``. Returns a :class:`Distribution`
     This function only returns the first result found, as no more than one
     value is expected. If the directory is not found, ``None`` is returned.
 
+    :param use_cache: if set to false, do not try to check information in the
+                      cache.
+
     :rtype: :class:`Distribution` or :class:`EggInfoDistribution` or None
     """
     if paths is None:
         paths = sys.path
 
-    if not _cache_enabled:
+    if not _cache_enabled or not use_cache:
         for dist in _yield_distributions(True, use_egg_info, paths):
             if dist.name == name:
                 return dist

File distutils2/install.py

     if where is None:
         raise ValueError('Cannot locate the unpacked archive')
 
-    return _run_install_from_archive(where, path)
+    return _run_install_from_dir(where, path)
 
 
 def install_local_project(path):
         _unpacked_dir = tempfile.mkdtemp()
         try:
             shutil.unpack_archive(path, _unpacked_dir)
-            return _run_install_from_archive(_unpacked_dir)
+            return _run_install_from_dir(_unpacked_dir)
         finally:
             shutil.rmtree(_unpacked_dir)
     else:
         return False
 
 
-def _run_install_from_archive(source_dir, dest_dir):
-    # XXX need a better way
-    for item in os.listdir(source_dir):
-        fullpath = os.path.join(source_dir, item)
-        if os.path.isdir(fullpath):
-            source_dir = fullpath
-            break
-    return _run_install_from_dir(source_dir, dest_dir)
-
-
 install_methods = {
     'distutils2': _run_packaging_install,
     'setuptools': _run_setuptools_install,
     :param path: base path to install distribution in
     :param paths: list of paths (defaults to sys.path) to look for info
     """
-
     installed_dists = []
     for dist in dists:
         logger.info('Installing %s...', dist)
 
 
 def install_from_info(install_path=None, install=[], remove=[], conflicts=[],
-                       paths=None):
+                      paths=None):
     """Install and remove the given distributions.
 
     The function signature is made to be compatible with the one of get_info.
-    The aim of this script is to povide a way to install/remove what's asked,
-    and to rollback if needed.
+    The aim of this script is to provide a way to install/remove what is
+    specified by the install, remove and conflict parameter, and rollback to
+    the state before calling this method if any error arises during
+    installation / uninstallation.
 
-    So, it's not possible to be in an inconsistant state, it could be either
+    Thus, it is not possible to be in an inconsistant state, it could be either
     installed, either uninstalled, not half-installed.
 
-    The process follow those steps:
+    The process follow these steps:
 
         1. Move all distributions that will be removed in a temporary location
         2. Install all the distributions that will be installed in a temp. loc.
         3. If the installation fails, rollback (eg. move back) those
            distributions, or remove what have been installed.
-        4. Else, move the distributions to the right locations, and remove for
-           real the distributions thats need to be removed.
+        4. Else, move the distributions to the right locations, and actually
+           remove the distributions which need to be removed.
 
-    :param install_path: the installation path where we want to install the
-                         distributions.
-    :param install: list of distributions that will be installed; install_path
-                    must be provided if this list is not empty.
-    :param remove: list of distributions that will be removed.
-    :param conflicts: list of conflicting distributions, eg. that will be in
-                      conflict once the install and remove distribution will be
-                      processed.
+    :param install_path: the installation path.
+    :param install: the list of distributions to install.
+                    install_path must be provided if this list is not empty.
+    :param remove: list of distributions to remove.
+    :param conflicts: the list of conflicting distributions, eg. the one which
+                      will be in conflict at the end of the process.
+                      Those are only passed here for compatibility reasons: the
+                      current implementation.
     :param paths: list of paths (defaults to sys.path) to look for info
     """
     # first of all, if we have conflicts, stop here.
         shutil.rmtree(temp_dir)
 
 
+def _run_setuptools_egginfo(path):
+    """Runs the setuptools egg_info command and output the results in the given
+    path.
+    """
+    cmd = '%s setup.py egg_info -e %s'
+    return os.system(cmd % (sys.executable, path)) == 0
+
+
 def _get_setuptools_deps(release):
-    # NotImplementedError
-    pass
+    """return a list of dependencies given a setuptools release"""
+    logger.info('Fetching %s dependencies from setuptools egg_info' %
+                release.name)
+    # we create a temporary folder to output the egginfo to, and then ensure
+    # that it's being deleted.
+    # First, download the release and unpack it
+    dist_path = release.unpack()  # fetches the last version and unpacks it
+
+    # try to find the path it unpacked to (the root), which should by
+    # of the form name-version
+    name = "%s-%s" % (release.name, release.version)
+    potential_path = os.path.join(dist_path, name)
+    if os.path.isdir(potential_path):
+        dist_path = potential_path
+
+    # change the current working dir to dist_path
+    os.chdir(dist_path)
+
+    dirname = tempfile.mkdtemp()
+    dependencies = []
+    old_dir = os.getcwd()
+    try:
+        if _run_setuptools_egginfo(dirname):
+            # get_distribution is finding the right egg-info directory and
+            # read/convert the metadata it finds into PEP 345 compatible
+            # metadata (and that's how we can get the requires_dist field)
+            dist = get_distribution(name=release.name, use_egg_info=True,
+                                    paths=(dirname,), use_cache=False)
+            dependencies = dist.metadata['requires_dist']
+    finally:
+        shutil.rmtree(dirname)
+        # restore the previous cwd
+        os.chdir(old_dir)
+    return dependencies
 
 
 def get_info(requirements, index=None, installed=None, prefer_final=True):
     """Return the informations on what's going to be installed and upgraded.
 
+    This function checks out the setuptools projects and runs egg-info on them
+    to get the list of dependencies for them, if any.
+
     :param requirements: is a *string* containing the requirements for this
                          project (for instance "FooBar 1.1" or "BarBaz (<1.2)")
     :param index: If an index is specified, use this one, otherwise, use
 
     metadata = release.fetch_metadata()
 
-    # we need to build setuptools deps if any
+    # build setuptools deps if any
+    # we base this comparison on the requires_dist field because it had been
+    # introduced by PEP 345 and is not present in setuptools based
+    # distributions
     if 'requires_dist' not in metadata:
+        from pdb import set_trace; set_trace()
         metadata['requires_dist'] = _get_setuptools_deps(release)
 
     # build the dependency graph with local and required dependencies

File distutils2/pypi/dist.py

 """
 
 import re
+import os
 import tempfile
 import urllib
 import urlparse
 
     def add_distribution(self, dist_type='sdist', python_version=None,
                          **params):
-        """Add distribution informations to this release.
+        """Add distribution information to this release.
         If distribution information is already set for this distribution type,
-        add the given url paths to the distribution. This can be useful while
+        add the given url paths to the distribution. This can be useful if
         some of them fails to download.
 
         :param dist_type: the distribution type (eg. "sdist", "bdist", etc.)
             unpack_archive(filename, path)
             self._unpacked_dir = path
 
-        return self._unpacked_dir
+        # try to find the path it unpacked to (the root), which should by
+        # of the form name-version
+        name = "%s-%s" % (self.release.name, self.release.version)
+        potential_path = os.path.join(self._unpacked_dir, name)
+        if os.path.isdir(potential_path):
+            path = potential_path
+        else:
+            path = self._unpacked_dir
+
+        return path
 
     def _check_md5(self, filename):
         """Check that the md5 checksum of the given file matches the one in

File distutils2/pypi/xmlrpc.py

             project_name = projects[0]
 
         metadata = self.proxy.release_data(project_name, version)
+        from pdb import set_trace; set_trace()
         project = self._get_project(project_name)
         if version not in project.get_versions():
             project.add_release(release=ReleaseInfo(project_name, version,

File distutils2/tests/pypi_server.py

             static_filesystem_paths = ["default"]
 
         #TODO allow to serve XMLRPC and HTTP static files at the same time.
-        if not self._serve_xmlrpc:
+        if self._serve_xmlrpc:  # XMLRPC server
+            self.server = PyPIXMLRPCServer(('127.0.0.1', 0))
+            self.xmlrpc = XMLRPCMockIndex()
+            # register the xmlrpc methods
+            self.server.register_introspection_functions()
+            self.server.register_instance(self.xmlrpc)
+        else:  # HTTP static server
             self.server = HTTPServer(('127.0.0.1', 0), PyPIRequestHandler)
             self.server.RequestHandlerClass.pypi_server = self
 
             self.static_filesystem_paths = [
                 PYPI_DEFAULT_STATIC_PATH + "/" + path
                 for path in static_filesystem_paths]
-        else:
-            # XMLRPC server
-            self.server = PyPIXMLRPCServer(('127.0.0.1', 0))
-            self.xmlrpc = XMLRPCMockIndex()
-            # register the xmlrpc methods
-            self.server.register_introspection_functions()
-            self.server.register_instance(self.xmlrpc)
 
         self.address = ('127.0.0.1', self.server.server_port)
         # to not have unwanted outputs.

File distutils2/tests/test_pypi_dist.py

 """Tests for the distutils2.pypi.dist module."""
 
 import os
+from collections import namedtuple
 from distutils2.version import VersionPredicate
 from distutils2.pypi.dist import (ReleaseInfo, ReleasesList, DistInfo,
                                   split_archive_name, get_info_from_url)
     from distutils2.tests.pypi_server import use_pypi_server
 except ImportError:
     threading = None
-    use_pypi_server = fake_dec
+    use_pypi_servere= fake_dec
 
 
-def Dist(*args, **kwargs):
+def Dist(release=None, *args, **kwargs):
     # DistInfo takes a release as a first parameter, avoid this in tests.
-    return DistInfo(None, *args, **kwargs)
+    return DistInfo(release, *args, **kwargs)
+
+Release = namedtuple("Release", ('name', 'version'))
 
 
 class TestReleaseInfo(unittest.TestCase):
     @use_pypi_server('downloads_with_md5')
     def test_unpack(self, server):
         url = server.full_address + self.srcpath
-        dist1 = Dist(url=url)
+        release = Release("foo", "1.0")
+        dist1 = Dist(release, url=url)
 
         # unpack the distribution in a specfied folder
         dist1_here = self.mkdtemp()
         dist1_there = dist1.unpack(path=dist1_here)
 
         # assert we unpack to the path provided
-        self.assertEqual(dist1_here, dist1_there)
+        self.assertIn(dist1_there, dist1_here)
         dist1_result = os.listdir(dist1_there)
         self.assertIn('paf', dist1_result)
         os.remove(os.path.join(dist1_there, 'paf'))
 
         # Test unpack works without a path argument
-        dist2 = Dist(url=url)
+        dist2 = Dist(release, url=url)
         # doing an unpack
         dist2_there = dist2.unpack()
         self.addCleanup(shutil.rmtree, dist2_there)