aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorPJ Eby <distutils-sig@python.org>2005-08-06 21:17:50 +0000
committerPJ Eby <distutils-sig@python.org>2005-08-06 21:17:50 +0000
commit899e59ff5150705852f15f90fddbfedf7544bec1 (patch)
tree6c0693cd07cf85f30fcab3f7c4d73610ebb5346e
parent568f7f51fb0dea510cfae83b178c642a06b801bd (diff)
downloadexternal_python_setuptools-899e59ff5150705852f15f90fddbfedf7544bec1.tar.gz
external_python_setuptools-899e59ff5150705852f15f90fddbfedf7544bec1.tar.bz2
external_python_setuptools-899e59ff5150705852f15f90fddbfedf7544bec1.zip
Allow distutils extensions to define new kinds of metadata that can be
written to EGG-INFO. Extensible applications and frameworks can thus make it possible for plugin projects to supply setup() metadata that can then be used by the application or framework. --HG-- branch : setuptools extra : convert_revision : svn%3A6015fed2-1504-0410-9fe1-9d1591cc4771/sandbox/trunk/setuptools%4041183
-rwxr-xr-xsetup.py36
-rwxr-xr-xsetuptools.egg-info/entry_points.txt10
-rwxr-xr-xsetuptools.txt84
-rwxr-xr-xsetuptools/command/egg_info.py213
4 files changed, 232 insertions, 111 deletions
diff --git a/setup.py b/setup.py
index e56e15f9..038c86fe 100755
--- a/setup.py
+++ b/setup.py
@@ -40,7 +40,6 @@ setup(
scripts = ['easy_install.py'],
zip_safe = False, # We want 'python -m easy_install' to work :(
-
entry_points = {
"distutils.commands" : [
"%(cmd)s = setuptools.command.%(cmd)s:%(cmd)s" % locals()
@@ -50,13 +49,23 @@ setup(
"eager_resources = setuptools.dist:assert_string_list",
"namespace_packages = setuptools.dist:check_nsp",
"extras_require = setuptools.dist:check_extras",
+ "install_requires = setuptools.dist:check_install_requires",
"entry_points = setuptools.dist:check_entry_points",
"test_suite = setuptools.dist:check_test_suite",
"zip_safe = setuptools.dist:assert_bool",
- ]
+ ],
+ "egg_info.writers": [
+ "PKG-INFO = setuptools.command.egg_info:write_pkg_info",
+ "requires.txt = setuptools.command.egg_info:write_requirements",
+ "entry_points.txt = setuptools.command.egg_info:write_entries",
+ "eager_resources.txt = setuptools.command.egg_info:write_arg",
+ "namespace_packages.txt = setuptools.command.egg_info:write_arg",
+ "top_level.txt = setuptools.command.egg_info:write_toplevel_names",
+ "depends.txt = setuptools.command.egg_info:warn_depends_obsolete",
+ ],
},
-
- setup_requires = ['setuptools>=0.6a0'],
+ # uncomment for testing
+ # setup_requires = ['setuptools>=0.6a0'],
classifiers = [f.strip() for f in """
Development Status :: 3 - Alpha
@@ -68,23 +77,6 @@ setup(
Topic :: Software Development :: Libraries :: Python Modules
Topic :: System :: Archiving :: Packaging
Topic :: System :: Systems Administration
- Topic :: Utilities
- """.splitlines() if f.strip()]
+ Topic :: Utilities""".splitlines() if f.strip()]
)
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/setuptools.egg-info/entry_points.txt b/setuptools.egg-info/entry_points.txt
index 82f0990e..c6b3f595 100755
--- a/setuptools.egg-info/entry_points.txt
+++ b/setuptools.egg-info/entry_points.txt
@@ -1,11 +1,21 @@
[distutils.setup_keywords]
entry_points = setuptools.dist:check_entry_points
extras_require = setuptools.dist:check_extras
+install_requires = setuptools.dist:check_install_requires
namespace_packages = setuptools.dist:check_nsp
test_suite = setuptools.dist:check_test_suite
eager_resources = setuptools.dist:assert_string_list
zip_safe = setuptools.dist:assert_bool
+[egg_info.writers]
+requires.txt = setuptools.command.egg_info:write_requirements
+PKG-INFO = setuptools.command.egg_info:write_pkg_info
+eager_resources.txt = setuptools.command.egg_info:write_arg
+top_level.txt = setuptools.command.egg_info:write_toplevel_names
+namespace_packages.txt = setuptools.command.egg_info:write_arg
+entry_points.txt = setuptools.command.egg_info:write_entries
+depends.txt = setuptools.command.egg_info:warn_depends_obsolete
+
[distutils.commands]
rotate = setuptools.command.rotate:rotate
develop = setuptools.command.develop:develop
diff --git a/setuptools.txt b/setuptools.txt
index 872830aa..66881d6c 100755
--- a/setuptools.txt
+++ b/setuptools.txt
@@ -622,6 +622,21 @@ invoking app or framework can ignore such errors if it wants to make an entry
point optional if a requirement isn't installed.)
+Defining Additional Metadata
+----------------------------
+
+Some extensible applications and frameworks may need to define their own kinds
+of metadata to include in eggs, which they can then access using the
+``pkg_resources`` metadata APIs. Ordinarily, this is done by having plugin
+developers include additional files in their ``ProjectName.egg-info``
+directory. However, since it can be tedious to create such files by hand, you
+may want to create a distutils extension that will create the necessary files
+from arguments to ``setup()``, in much the same way that ``setuptools`` does
+for many of the ``setup()`` arguments it adds. See the section below on
+`Creating distutils Extensions`_ for more details, especially the subsection on
+`Adding new EGG-INFO Files`_.
+
+
"Development Mode"
==================
@@ -1301,6 +1316,14 @@ the project's source directory or metadata should get it from this setting:
``package_dir`` argument to the ``setup()`` function, if any. If there is
no ``package_dir`` set, this option defaults to the current directory.
+In addition to writing the core egg metadata defined by ``setuptools`` and
+required by ``pkg_resources``, this command can be extended to write other
+metadata files as well, by defining entry points in the ``egg_info.writers``
+group. See the section on `Adding new EGG-INFO Files`_ below for more details.
+Note that using additional metadata writers may require you to include a
+``setup_requires`` argument to ``setup()`` in order to ensure that the desired
+writers are available on ``sys.path``.
+
.. _rotate:
@@ -1639,6 +1662,60 @@ sufficient to define the entry points in your extension, as long as the setup
script lists your extension in its ``setup_requires`` argument.
+Adding new EGG-INFO Files
+-------------------------
+
+Some extensible applications or frameworks may want to allow third parties to
+develop plugins with application or framework-specific metadata included in
+the plugins' EGG-INFO directory, for easy access via the ``pkg_resources``
+metadata API. The easiest way to allow this is to create a distutils extension
+to be used from the plugin projects' setup scripts (via ``setup_requires``)
+that defines a new setup keyword, and then uses that data to write an EGG-INFO
+file when the ``egg_info`` command is run.
+
+The ``egg_info`` command looks for extension points in an ``egg_info.writers``
+group, and calls them to write the files. Here's a simple example of a
+distutils extension defining a setup argument ``foo_bar``, which is a list of
+lines that will be written to ``foo_bar.txt`` in the EGG-INFO directory of any
+project that uses the argument::
+
+ setup(
+ # ...
+ entry_points = {
+ "distutils.setup_keywords": [
+ "foo_bar = setuptools.dist:assert_string_list",
+ ],
+ "egg_info.writers": [
+ "foo_bar.txt = setuptools.command.egg_info:write_arg",
+ ],
+ },
+ )
+
+This simple example makes use of two utility functions defined by setuptools
+for its own use: a routine to validate that a setup keyword is a sequence of
+strings, and another one that looks up a setup argument and writes it to
+a file. Here's what the writer utility looks like::
+
+ def write_arg(cmd, basename, filename):
+ argname = os.path.splitext(basename)[0]
+ value = getattr(cmd.distribution, argname, None)
+ if value is not None:
+ value = '\n'.join(value)+'\n'
+ cmd.write_or_delete_file(argname, filename, value)
+
+As you can see, ``egg_info.writers`` entry points must be a function taking
+three arguments: a ``egg_info`` command instance, the basename of the file to
+write (e.g. ``foo_bar.txt``), and the actual full filename that should be
+written to.
+
+In general, writer functions should honor the command object's ``dry_run``
+setting when writing files, and use the ``distutils.log`` object to do any
+console output. The easiest way to conform to this requirement is to use
+the ``cmd`` object's ``write_file()``, ``delete_file()``, and
+``write_or_delete_file()`` methods exclusively for your file operations. See
+those methods' docstrings for more details.
+
+
Subclassing ``Command``
-----------------------
@@ -1709,9 +1786,10 @@ Release Notes/Change History
``setup_requires`` allows you to automatically find and download packages
that are needed in order to *build* your project (as opposed to running it).
- * ``setuptools`` now finds its commands and ``setup()`` argument validators
- using entry points, so that they are extensible by third-party packages.
- See `Creating distutils Extensions`_ above for more details.
+ * ``setuptools`` now finds its commands, ``setup()`` argument validators, and
+ metadata writers using entry points, so that they can be extended by
+ third-party packages. See `Creating distutils Extensions`_ above for more
+ details.
* The vestigial ``depends`` command has been removed. It was never finished
or documented, and never would have worked without EasyInstall - which it
diff --git a/setuptools/command/egg_info.py b/setuptools/command/egg_info.py
index 7d0a1473..ae3c762b 100755
--- a/setuptools/command/egg_info.py
+++ b/setuptools/command/egg_info.py
@@ -8,7 +8,7 @@ from setuptools import Command
from distutils.errors import *
from distutils import log
from pkg_resources import parse_requirements, safe_name, \
- safe_version, yield_lines, EntryPoint
+ safe_version, yield_lines, EntryPoint, iter_entry_points
class egg_info(Command):
@@ -80,47 +80,55 @@ class egg_info(Command):
- def run(self):
- # Make the .egg-info directory, then write PKG-INFO and requires.txt
- self.mkpath(self.egg_info)
- log.info("writing %s" % os.path.join(self.egg_info,'PKG-INFO'))
-
- if not self.dry_run:
- metadata = self.distribution.metadata
- metadata.version, oldver = self.egg_version, metadata.version
- metadata.name, oldname = self.egg_name, metadata.name
- try:
- # write unescaped data to PKG-INFO, so older pkg_resources
- # can still parse it
- metadata.write_pkg_info(self.egg_info)
- finally:
- metadata.name, metadata.version = oldname, oldver
- self.write_entry_points()
- self.write_requirements()
- self.write_toplevel_names()
- self.write_or_delete_dist_arg('namespace_packages')
- self.write_or_delete_dist_arg('eager_resources')
- if os.path.exists(os.path.join(self.egg_info,'depends.txt')):
- log.warn(
- "WARNING: 'depends.txt' is not used by setuptools 0.6!\n"
- "Use the install_requires/extras_require setup() args instead."
- )
+ def write_or_delete_file(self, what, filename, data):
+ """Write `data` to `filename` or delete if empty
- def write_requirements(self):
- dist = self.distribution
- if not getattr(dist,'install_requires',None) and \
- not getattr(dist,'extras_require',None): return
+ If `data` is non-empty, this routine is the same as ``write_file()``.
+ If `data` is empty but not ``None``, this is the same as calling
+ ``delete_file(filename)`. If `data` is ``None``, then this is a no-op
+ unless `filename` exists, in which case a warning is issued about the
+ orphaned file.
+ """
+ if data:
+ self.write_file(what, filename, data)
+ elif os.path.exists(filename):
+ if data is None:
+ log.warn(
+ "%s not set in setup(), but %s exists", what, filename
+ )
+ return
+ else:
+ self.delete_file(filename)
- requires = os.path.join(self.egg_info,"requires.txt")
- log.info("writing %s", requires)
+ def write_file(self, what, filename, data):
+ """Write `data` to `filename` (if not a dry run) after announcing it
+ `what` is used in a log message to identify what is being written
+ to the file.
+ """
+ log.info("writing %s to %s", what, filename)
if not self.dry_run:
- f = open(requires, 'wt')
- f.write('\n'.join(yield_lines(dist.install_requires)))
- for extra,reqs in dist.extras_require.items():
- f.write('\n\n[%s]\n%s' % (extra, '\n'.join(yield_lines(reqs))))
+ f = open(filename, 'wb')
+ f.write(data)
f.close()
+ def delete_file(self, filename):
+ """Delete `filename` (if not a dry run) after announcing it"""
+ log.info("deleting %s", filename)
+ if not self.dry_run:
+ os.unlink(filename)
+
+
+
+
+ def run(self):
+ # Make the .egg-info directory, then write PKG-INFO and requires.txt
+ self.mkpath(self.egg_info)
+ installer = self.distribution.fetch_build_egg
+ for ep in iter_entry_points('egg_info.writers'):
+ writer = ep.load(installer=installer)
+ writer(self, ep.name, os.path.join(self.egg_info,ep.name))
+
def tagged_version(self):
version = self.distribution.get_version()
if self.tag_build:
@@ -132,7 +140,6 @@ class egg_info(Command):
version += time.strftime("-%Y%m%d")
return safe_version(version)
-
def get_svn_revision(self):
stdin, stdout = os.popen4("svn info -R"); stdin.close()
result = stdout.read(); stdout.close()
@@ -146,60 +153,94 @@ class egg_info(Command):
return str(max(revisions))
- def write_toplevel_names(self):
- pkgs = dict.fromkeys(
- [k.split('.',1)[0]
- for k in self.distribution.iter_distribution_names()
- ]
+
+
+
+
+
+
+
+
+
+def write_pkg_info(cmd, basename, filename):
+ log.info("writing %s", filename)
+ if not cmd.dry_run:
+ metadata = cmd.distribution.metadata
+ metadata.version, oldver = cmd.egg_version, metadata.version
+ metadata.name, oldname = cmd.egg_name, metadata.name
+ try:
+ # write unescaped data to PKG-INFO, so older pkg_resources
+ # can still parse it
+ metadata.write_pkg_info(cmd.egg_info)
+ finally:
+ metadata.name, metadata.version = oldname, oldver
+
+def warn_depends_obsolete(cmd, basename, filename):
+ if os.path.exists(filename):
+ log.warn(
+ "WARNING: 'depends.txt' is not used by setuptools 0.6!\n"
+ "Use the install_requires/extras_require setup() args instead."
)
- toplevel = os.path.join(self.egg_info, "top_level.txt")
- log.info("writing list of top-level names to %s" % toplevel)
- if not self.dry_run:
- f = open(toplevel, 'wt')
- f.write('\n'.join(pkgs))
- f.write('\n')
- f.close()
+
+
+def write_requirements(cmd, basename, filename):
+ dist = cmd.distribution
+ data = ['\n'.join(yield_lines(dist.install_requires or ()))]
+ for extra,reqs in (dist.extras_require or {}).items():
+ data.append('\n\n[%s]\n%s' % (extra, '\n'.join(yield_lines(reqs))))
+ cmd.write_or_delete_file("requirements", filename, ''.join(data))
+
+def write_toplevel_names(cmd, basename, filename):
+ pkgs = dict.fromkeys(
+ [k.split('.',1)[0]
+ for k in cmd.distribution.iter_distribution_names()
+ ]
+ )
+ cmd.write_file("top-level names", filename, '\n'.join(pkgs)+'\n')
+
+
+
+
+
+
+def write_arg(cmd, basename, filename):
+ argname = os.path.splitext(basename)[0]
+ value = getattr(cmd.distribution, argname, None)
+ if value is not None:
+ value = '\n'.join(value)+'\n'
+ cmd.write_or_delete_file(argname, filename, value)
+
+def write_entries(cmd, basename, filename):
+ ep = cmd.distribution.entry_points
+
+ if isinstance(ep,basestring) or ep is None:
+ data = ep
+ elif ep is not None:
+ data = []
+ for section, contents in ep.items():
+ if not isinstance(contents,basestring):
+ contents = EntryPoint.parse_list(section, contents)
+ contents = '\n'.join(map(str,contents.values()))
+ data.append('[%s]\n%s\n\n' % (section,contents))
+ data = ''.join(data)
+
+ cmd.write_or_delete_file('entry points', filename, data)
+
+
+
+
+
+
+
+
+
+
+
+
- def write_or_delete_dist_arg(self, argname, filename=None):
- value = getattr(self.distribution, argname, None)
- filename = filename or argname+'.txt'
- filename = os.path.join(self.egg_info,filename)
- if value:
- log.info("writing %s", filename)
- if not self.dry_run:
- f = open(filename, 'wt')
- f.write('\n'.join(value))
- f.write('\n')
- f.close()
- elif os.path.exists(filename):
- if value is None:
- log.warn(
- "%s not set in setup(), but %s exists", argname, filename
- )
- return
- log.info("deleting %s", filename)
- if not self.dry_run:
- os.unlink(filename)
- def write_entry_points(self):
- ep = getattr(self.distribution,'entry_points',None)
- if ep is None:
- return
- epname = os.path.join(self.egg_info,"entry_points.txt")
- log.info("writing %s", epname)
- if not self.dry_run:
- f = open(epname, 'wt')
- if isinstance(ep,basestring):
- f.write(ep)
- else:
- for section, contents in ep.items():
- if not isinstance(contents,basestring):
- contents = EntryPoint.parse_list(section, contents)
- contents = '\n'.join(map(str,contents.values()))
- f.write('[%s]\n%s\n\n' % (section,contents))
- f.close()