summaryrefslogtreecommitdiff
path: root/src/distutils2
diff options
context:
space:
mode:
Diffstat (limited to 'src/distutils2')
-rw-r--r--src/distutils2/_backport/pkgutil.py165
-rw-r--r--src/distutils2/_backport/tests/fake_dists/grammar-1.0a4.dist-info/METADATA1
-rw-r--r--src/distutils2/_backport/tests/fake_dists/truffles-5.0.egg-info3
-rw-r--r--src/distutils2/_backport/tests/test_pkgutil.py2
-rw-r--r--src/distutils2/command/install.py29
-rw-r--r--src/distutils2/command/install_distinfo.py174
-rw-r--r--src/distutils2/command/install_egg_info.py83
-rw-r--r--src/distutils2/tests/test_install.py7
8 files changed, 344 insertions, 120 deletions
diff --git a/src/distutils2/_backport/pkgutil.py b/src/distutils2/_backport/pkgutil.py
index b033082..bb09a23 100644
--- a/src/distutils2/_backport/pkgutil.py
+++ b/src/distutils2/_backport/pkgutil.py
@@ -20,13 +20,15 @@ except ImportError:
import re
import warnings
+
__all__ = [
'get_importer', 'iter_importers', 'get_loader', 'find_loader',
'walk_packages', 'iter_modules', 'get_data',
'ImpImporter', 'ImpLoader', 'read_code', 'extend_path',
'Distribution', 'EggInfoDistribution', 'distinfo_dirname',
'get_distributions', 'get_distribution', 'get_file_users',
- 'provides_distribution', 'obsoletes_distribution',
+ 'provides_distribution', 'obsoletes_distribution', 'set_cache_enabled',
+ 'clear_cache'
]
@@ -187,8 +189,8 @@ class ImpImporter(object):
searches the current ``sys.path``, plus any modules that are frozen
or built-in.
- Note that :class:`ImpImporter` does not currently support being used by placement
- on ``sys.meta_path``.
+ Note that :class:`ImpImporter` does not currently support being used by
+ placement on ``sys.meta_path``.
"""
def __init__(self, path=None):
@@ -577,7 +579,8 @@ def get_data(package, resource):
argument should be the name of a package, in standard module format
(``foo.bar``). The resource argument should be in the form of a relative
filename, using ``'/'`` as the path separator. The parent directory name
- ``'..'`` is not allowed, and nor is a rooted name (starting with a ``'/'``).
+ ``'..'`` is not allowed, and nor is a rooted name (starting with a
+ ``'/'``).
The function returns a binary string, which is the contents of the
specified resource.
@@ -613,6 +616,90 @@ def get_data(package, resource):
DIST_FILES = ('INSTALLER', 'METADATA', 'RECORD', 'REQUESTED',)
+# Cache
+_cache_name = {} # maps names to Distribution instances
+_cache_name_egg = {} # maps names to EggInfoDistribution instances
+_cache_path = {} # maps paths to Distribution instances
+_cache_path_egg = {} # maps paths to EggInfoDistribution instances
+_cache_generated = False # indicates if .dist-info distributions are cached
+_cache_generated_egg = False # indicates if .dist-info and .egg are cached
+_cache_enabled = True
+
+
+def set_cache_enabled(flag):
+ """
+ Enables or disables the internal cache depending on *flag*.
+
+ Note that this function will not clear the cache in any case, for that
+ functionality see :func:`clear_cache`.
+
+ :parameter flag:
+ :type flag: boolean
+ """
+ global _cache_enabled
+
+ _cache_enabled = flag
+
+
+def clear_cache():
+ """ Clears the internal cache. """
+ global _cache_name, _cache_name_egg, cache_path, _cache_path_egg, \
+ _cache_generated, _cache_generated_egg
+
+ _cache_name = {}
+ _cache_name_egg = {}
+ _cache_path = {}
+ _cache_path_egg = {}
+ _cache_generated = False
+ _cache_generated_egg = False
+
+
+def _yield_distributions(dist, egg):
+ """
+ Yield .dist-info and .egg(-info) distributions, based on the arguments
+
+ :parameter dist: yield .dist-info distributions
+ :parameter egg: yield .egg(-info) distributions
+ """
+ for path in sys.path:
+ realpath = os.path.realpath(path)
+ if not os.path.isdir(realpath):
+ continue
+ for dir in os.listdir(realpath):
+ dist_path = os.path.join(realpath, dir)
+ if dist and dir.endswith('.dist-info'):
+ yield Distribution(dist_path)
+ elif egg and (dir.endswith('.egg-info') or
+ dir.endswith('.egg')):
+ yield EggInfoDistribution(dist_path)
+
+
+def _generate_cache(use_egg_info=False):
+ global _cache_generated, _cache_generated_egg
+
+ if _cache_generated_egg or (_cache_generated and not use_egg_info):
+ return
+ else:
+ gen_dist = not _cache_generated
+ gen_egg = use_egg_info
+
+ for dist in _yield_distributions(gen_dist, gen_egg):
+ if isinstance(dist, Distribution):
+ _cache_path[dist.path] = dist
+ if not dist.name in _cache_name:
+ _cache_name[dist.name] = []
+ _cache_name[dist.name].append(dist)
+ else:
+ _cache_path_egg[dist.path] = dist
+ if not dist.name in _cache_name_egg:
+ _cache_name_egg[dist.name] = []
+ _cache_name_egg[dist.name].append(dist)
+
+ if gen_dist:
+ _cache_generated = True
+ if gen_egg:
+ _cache_generated_egg = True
+
class Distribution(object):
"""Created with the *path* of the ``.dist-info`` directory provided to the
@@ -627,15 +714,23 @@ class Distribution(object):
"""A :class:`distutils2.metadata.DistributionMetadata` instance loaded with
the distribution's ``METADATA`` file."""
requested = False
- """A boolean that indicates whether the ``REQUESTED`` metadata file is present
- (in other words, whether the package was installed by user request)."""
+ """A boolean that indicates whether the ``REQUESTED`` metadata file is
+ present (in other words, whether the package was installed by user
+ request)."""
def __init__(self, path):
+ if _cache_enabled and path in _cache_path:
+ self.metadata = _cache_path[path].metadata
+ else:
+ metadata_path = os.path.join(path, 'METADATA')
+ self.metadata = DistributionMetadata(path=metadata_path)
+
self.path = path
- metadata_path = os.path.join(path, 'METADATA')
- self.metadata = DistributionMetadata(path=metadata_path)
self.name = self.metadata['name']
+ if _cache_enabled and not path in _cache_path:
+ _cache_path[path] = self
+
def _get_records(self, local=False):
RECORD = os.path.join(self.path, 'RECORD')
record_reader = csv_reader(open(RECORD, 'rb'), delimiter=',')
@@ -756,6 +851,11 @@ class EggInfoDistribution(object):
def __init__(self, path):
self.path = path
+ if _cache_enabled and path in _cache_path_egg:
+ self.metadata = _cache_path_egg[path].metadata
+ self.name = self.metadata['Name']
+ return
+
# reused from Distribute's pkg_resources
def yield_lines(strs):
"""Yield non-empty/non-comment lines of a ``basestring`` or sequence"""
@@ -787,7 +887,7 @@ class EggInfoDistribution(object):
requires = zipf.get_data('EGG-INFO/requires.txt')
except IOError:
requires = None
- self.name = self.metadata['name']
+ self.name = self.metadata['Name']
elif path.endswith('.egg-info'):
if os.path.isdir(path):
path = os.path.join(path, 'PKG-INFO')
@@ -840,6 +940,9 @@ class EggInfoDistribution(object):
else:
self.metadata['Requires'] += reqs
+ if _cache_enabled:
+ _cache_path_egg[self.path] = self
+
def get_installed_files(self, local=False):
return []
@@ -898,17 +1001,17 @@ def get_distributions(use_egg_info=False):
:rtype: iterator of :class:`Distribution` and :class:`EggInfoDistribution`
instances"""
- for path in sys.path:
- realpath = os.path.realpath(path)
- if not os.path.isdir(realpath):
- continue
- for dir in os.listdir(realpath):
- if dir.endswith('.dist-info'):
- dist = Distribution(os.path.join(realpath, dir))
- yield dist
- elif use_egg_info and (dir.endswith('.egg-info') or
- dir.endswith('.egg')):
- dist = EggInfoDistribution(os.path.join(realpath, dir))
+ if not _cache_enabled:
+ for dist in _yield_distributions(use_egg_info):
+ yield dist
+ else:
+ _generate_cache(use_egg_info)
+
+ for dist in _cache_path.itervalues():
+ yield dist
+
+ if use_egg_info:
+ for dist in _cache_path_egg.itervalues():
yield dist
@@ -928,17 +1031,19 @@ def get_distribution(name, use_egg_info=False):
value is expected. If the directory is not found, ``None`` is returned.
:rtype: :class:`Distribution` or :class:`EggInfoDistribution` or None"""
- found = None
- for dist in get_distributions():
- if dist.name == name:
- found = dist
- break
- if use_egg_info:
- for dist in get_distributions(True):
+ if not _cache_enabled:
+ for dist in _yield_distributions(use_egg_info):
if dist.name == name:
- found = dist
- break
- return found
+ return dist
+ else:
+ _generate_cache(use_egg_info)
+
+ if name in _cache_name:
+ return _cache_name[name][0]
+ elif use_egg_info and name in _cache_name_egg:
+ return _cache_name_egg[name][0]
+ else:
+ return None
def obsoletes_distribution(name, version=None, use_egg_info=False):
diff --git a/src/distutils2/_backport/tests/fake_dists/grammar-1.0a4.dist-info/METADATA b/src/distutils2/_backport/tests/fake_dists/grammar-1.0a4.dist-info/METADATA
index 1978be7..0b99f52 100644
--- a/src/distutils2/_backport/tests/fake_dists/grammar-1.0a4.dist-info/METADATA
+++ b/src/distutils2/_backport/tests/fake_dists/grammar-1.0a4.dist-info/METADATA
@@ -2,3 +2,4 @@ Metadata-Version: 1.2
Name: grammar
Version: 1.0a4
Requires-Dist: truffles (>=1.2)
+Author: Sherlock Holmes
diff --git a/src/distutils2/_backport/tests/fake_dists/truffles-5.0.egg-info b/src/distutils2/_backport/tests/fake_dists/truffles-5.0.egg-info
new file mode 100644
index 0000000..45f0cf8
--- /dev/null
+++ b/src/distutils2/_backport/tests/fake_dists/truffles-5.0.egg-info
@@ -0,0 +1,3 @@
+Metadata-Version: 1.2
+Name: truffles
+Version: 5.0
diff --git a/src/distutils2/_backport/tests/test_pkgutil.py b/src/distutils2/_backport/tests/test_pkgutil.py
index fafb9a8..3312d28 100644
--- a/src/distutils2/_backport/tests/test_pkgutil.py
+++ b/src/distutils2/_backport/tests/test_pkgutil.py
@@ -485,7 +485,7 @@ class TestPkgUtilPEP376(unittest.TestCase):
l = [dist.name for dist in provides_distribution('truffles', '>1.5',
use_egg_info=True)]
- checkLists(l, ['bacon'])
+ checkLists(l, ['bacon', 'truffles'])
l = [dist.name for dist in provides_distribution('truffles', '>=1.0')]
checkLists(l, ['choxie', 'towel-stuff'])
diff --git a/src/distutils2/command/install.py b/src/distutils2/command/install.py
index 588786f..4f0ccc3 100644
--- a/src/distutils2/command/install.py
+++ b/src/distutils2/command/install.py
@@ -79,15 +79,26 @@ class install(Command):
('record=', None,
"filename in which to record list of installed files"),
+
+ # .dist-info related arguments, read by install_dist_info
+ ('no-distinfo', None, 'do not create a .dist-info directory'),
+ ('distinfo-dir=', None,
+ 'directory where the the .dist-info directory will '
+ 'be installed'),
+ ('installer=', None, 'the name of the installer'),
+ ('requested', None, 'generate a REQUESTED file'),
+ ('no-requested', None, 'do not generate a REQUESTED file'),
+ ('no-distinfo-record', None, 'do not generate a RECORD file'),
]
- boolean_options = ['compile', 'force', 'skip-build']
+ boolean_options = ['compile', 'force', 'skip-build', 'no-dist-info',
+ 'requested', 'no-dist-record',]
user_options.append(('user', None,
"install in user site-package '%s'" % \
get_path('purelib', '%s_user' % os.name)))
boolean_options.append('user')
- negative_opt = {'no-compile' : 'compile'}
+ negative_opt = {'no-compile' : 'compile', 'no-requested': 'requested'}
def initialize_options(self):
@@ -159,6 +170,13 @@ class install(Command):
#self.install_info = None
self.record = None
+
+ # .dist-info related options
+ self.no_distinfo = None
+ self.distinfo_dir = None
+ self.installer = None
+ self.requested = None
+ self.no_distinfo_record = None
# -- Option finalizing methods -------------------------------------
@@ -305,6 +323,9 @@ class install(Command):
# Punt on doc directories for now -- after all, we're punting on
# documentation completely!
+
+ if self.no_distinfo is None:
+ self.no_distinfo = False
def dump_dirs(self, msg):
"""Dumps the list of user options."""
@@ -586,5 +607,7 @@ class install(Command):
('install_headers', has_headers),
('install_scripts', has_scripts),
('install_data', has_data),
- ('install_egg_info', lambda self:True),
+ # keep install_distinfo last, as it needs the record
+ # with files to be completely generated
+ ('install_distinfo', lambda self: not self.no_distinfo),
]
diff --git a/src/distutils2/command/install_distinfo.py b/src/distutils2/command/install_distinfo.py
new file mode 100644
index 0000000..e5b1665
--- /dev/null
+++ b/src/distutils2/command/install_distinfo.py
@@ -0,0 +1,174 @@
+"""
+distutils.command.install_distinfo
+==================================
+
+:Author: Josip Djolonga
+
+This module implements the ``install_distinfo`` command that creates the
+``.dist-info`` directory for the distribution, as specified in :pep:`376`.
+Usually, you do not have to call this command directly, it gets called
+automatically by the ``install`` command.
+"""
+
+from distutils2.command.cmd import Command
+from distutils2 import log
+from distutils2._backport.shutil import rmtree
+
+
+import csv
+import hashlib
+import os
+import re
+
+
+class install_distinfo(Command):
+ """Install a .dist-info directory for the package"""
+
+ description = 'Install a .dist-info directory for the package'
+
+ user_options = [
+ ('dist-info-dir=', None,
+ 'directory where the the .dist-info directory will '
+ 'be installed'),
+ ('installer=', None, 'the name of the installer'),
+ ('requested', None, 'generate a REQUESTED file'),
+ ('no-requested', None, 'do not generate a REQUESTED file'),
+ ('no-dist-record', None, 'do not generate a RECORD file'),
+ ]
+
+ boolean_options = [
+ 'requested',
+ 'no-dist-record',
+ ]
+
+ negative_opt = {'no-requested': 'requested'}
+
+ def initialize_options(self):
+ self.distinfo_dir = None
+ self.installer = None
+ self.requested = None
+ self.no_distinfo_record = None
+
+ def finalize_options(self):
+ self.set_undefined_options('install',
+ ('distinfo_dir', 'distinfo_dir'),
+ ('installer', 'installer'),
+ ('requested', 'requested'),
+ ('no_distinfo_record',
+ 'no_distinfo_record'))
+
+ self.set_undefined_options('install_lib',
+ ('install_dir', 'distinfo_dir'))
+
+ if self.installer is None:
+ self.installer = 'distutils'
+ if self.requested is None:
+ self.requested = True
+ if self.no_distinfo_record is None:
+ self.no_distinfo_record = False
+
+ metadata = self.distribution.metadata
+
+ basename = "%s-%s.dist-info" % (
+ to_filename(safe_name(metadata['Name'])),
+ to_filename(safe_version(metadata['Version'])),
+ )
+
+ self.distinfo_dir = os.path.join(self.distinfo_dir, basename)
+ self.outputs = []
+
+ def run(self):
+ if not self.dry_run:
+ target = self.distinfo_dir
+
+ if os.path.isdir(target) and not os.path.islink(target):
+ rmtree(target)
+ elif os.path.exists(target):
+ self.execute(os.unlink, (self.distinfo_dir,),
+ "Removing " + target)
+
+ self.execute(os.makedirs, (target,), "Creating " + target)
+
+ metadata_path = os.path.join(self.distinfo_dir, 'METADATA')
+ log.info('Creating %s' % (metadata_path,))
+ self.distribution.metadata.write(metadata_path)
+ self.outputs.append(metadata_path)
+
+ installer_path = os.path.join(self.distinfo_dir, 'INSTALLER')
+ log.info('Creating %s' % (installer_path,))
+ f = open(installer_path, 'w')
+ try:
+ f.write(self.installer)
+ finally:
+ f.close()
+ self.outputs.append(installer_path)
+
+ if self.requested:
+ requested_path = os.path.join(self.distinfo_dir, 'REQUESTED')
+ log.info('Creating %s' % (requested_path,))
+ f = open(requested_path, 'w')
+ f.close()
+ self.outputs.append(requested_path)
+
+ if not self.no_distinfo_record:
+ record_path = os.path.join(self.distinfo_dir, 'RECORD')
+ log.info('Creating %s' % (record_path,))
+ f = open(record_path, 'wb')
+ try:
+ writer = csv.writer(f, delimiter=',',
+ lineterminator=os.linesep,
+ quotechar='"')
+
+ install = self.get_finalized_command('install')
+
+ for fpath in install.get_outputs():
+ if fpath.endswith('.pyc') or fpath.endswith('.pyo'):
+ # do not put size and md5 hash, as in PEP-376
+ writer.writerow((fpath, '', ''))
+ else:
+ size = os.path.getsize(fpath)
+ fd = open(fpath, 'r')
+ hash = hashlib.md5()
+ hash.update(fd.read())
+ md5sum = hash.hexdigest()
+ writer.writerow((fpath, md5sum, size))
+
+ # add the RECORD file itself
+ writer.writerow((record_path, '', ''))
+ self.outputs.append(record_path)
+ finally:
+ f.close()
+
+ def get_outputs(self):
+ return self.outputs
+
+
+# The following routines are taken from setuptools' pkg_resources module and
+# can be replaced by importing them from pkg_resources once it is included
+# in the stdlib.
+
+
+def safe_name(name):
+ """Convert an arbitrary string to a standard distribution name
+
+ Any runs of non-alphanumeric/. characters are replaced with a single '-'.
+ """
+ return re.sub('[^A-Za-z0-9.]+', '-', name)
+
+
+def safe_version(version):
+ """Convert an arbitrary string to a standard version string
+
+ Spaces become dots, and all other non-alphanumeric characters become
+ dashes, with runs of multiple dashes condensed to a single dash.
+ """
+ version = version.replace(' ', '.')
+ return re.sub('[^A-Za-z0-9.]+', '-', version)
+
+
+def to_filename(name):
+ """Convert a project or version name to its filename-escaped form
+
+ Any '-' characters are currently replaced with '_'.
+ """
+ return name.replace('-', '_')
diff --git a/src/distutils2/command/install_egg_info.py b/src/distutils2/command/install_egg_info.py
deleted file mode 100644
index c8b568a..0000000
--- a/src/distutils2/command/install_egg_info.py
+++ /dev/null
@@ -1,83 +0,0 @@
-"""distutils.command.install_egg_info
-
-Implements the Distutils 'install_egg_info' command, for installing
-a package's PKG-INFO metadata."""
-
-
-from distutils2.command.cmd import Command
-from distutils2 import log
-from distutils2._backport.shutil import rmtree
-import os, sys, re
-
-class install_egg_info(Command):
- """Install an .egg-info file for the package"""
-
- description = "Install package's PKG-INFO metadata as an .egg-info file"
- user_options = [
- ('install-dir=', 'd', "directory to install to"),
- ]
-
- def initialize_options(self):
- self.install_dir = None
-
- def finalize_options(self):
- metadata = self.distribution.metadata
- self.set_undefined_options('install_lib',('install_dir','install_dir'))
- basename = "%s-%s-py%s.egg-info" % (
- to_filename(safe_name(metadata['Name'])),
- to_filename(safe_version(metadata['Version'])),
- sys.version[:3]
- )
- self.target = os.path.join(self.install_dir, basename)
- self.outputs = [self.target]
-
- def run(self):
- target = self.target
- if os.path.isdir(target) and not os.path.islink(target):
- if self.dry_run:
- pass # XXX
- else:
- rmtree(target)
- elif os.path.exists(target):
- self.execute(os.unlink,(self.target,),"Removing "+target)
- elif not os.path.isdir(self.install_dir):
- self.execute(os.makedirs, (self.install_dir,),
- "Creating "+self.install_dir)
- log.info("Writing %s", target)
- if not self.dry_run:
- f = open(target, 'w')
- self.distribution.metadata.write_file(f)
- f.close()
-
- def get_outputs(self):
- return self.outputs
-
-
-# The following routines are taken from setuptools' pkg_resources module and
-# can be replaced by importing them from pkg_resources once it is included
-# in the stdlib.
-
-def safe_name(name):
- """Convert an arbitrary string to a standard distribution name
-
- Any runs of non-alphanumeric/. characters are replaced with a single '-'.
- """
- return re.sub('[^A-Za-z0-9.]+', '-', name)
-
-
-def safe_version(version):
- """Convert an arbitrary string to a standard version string
-
- Spaces become dots, and all other non-alphanumeric characters become
- dashes, with runs of multiple dashes condensed to a single dash.
- """
- version = version.replace(' ','.')
- return re.sub('[^A-Za-z0-9.]+', '-', version)
-
-
-def to_filename(name):
- """Convert a project or version name to its filename-escaped form
-
- Any '-' characters are currently replaced with '_'.
- """
- return name.replace('-','_')
diff --git a/src/distutils2/tests/test_install.py b/src/distutils2/tests/test_install.py
index 5cf6cd9..a566892 100644
--- a/src/distutils2/tests/test_install.py
+++ b/src/distutils2/tests/test_install.py
@@ -195,11 +195,12 @@ class InstallTestCase(support.TempdirManager,
cmd.ensure_finalized()
cmd.run()
- # let's check the RECORD file was created with one
- # line (the egg info file)
+ # let's check the RECORD file was created with four
+ # lines, one for each .dist-info entry: METADATA,
+ # INSTALLER, REQUSTED, RECORD
f = open(cmd.record)
try:
- self.assertEqual(len(f.readlines()), 1)
+ self.assertEqual(len(f.readlines()), 4)
finally:
f.close()