[Python-checkins] distutils2: merge changes from central repo

tarek.ziade python-checkins at python.org
Wed Feb 16 22:23:56 CET 2011


tarek.ziade pushed 1e1b39856550 to distutils2:

http://hg.python.org/distutils2/rev/1e1b39856550
changeset:   998:1e1b39856550
parent:      997:a73c351afd57
parent:      992:a832bc472995
user:        Godefroid Chapelle <gotcha at bubblenet.be>
date:        Tue Feb 01 10:31:10 2011 +0100
summary:
  merge changes from central repo

files:
  

diff --git a/distutils2/_backport/__init__.py b/distutils2/_backport/__init__.py
--- a/distutils2/_backport/__init__.py
+++ b/distutils2/_backport/__init__.py
@@ -1,2 +1,8 @@
 """Things that will land in the Python 3.3 std lib but which we must drag along
-with us for now to support 2.x."""
+ us for now to support 2.x."""
+
+def any(seq):
+    for elem in seq:
+        if elem:
+            return True
+    return False
diff --git a/distutils2/_backport/pkgutil.py b/distutils2/_backport/pkgutil.py
--- a/distutils2/_backport/pkgutil.py
+++ b/distutils2/_backport/pkgutil.py
@@ -7,6 +7,13 @@
 import warnings
 from csv import reader as csv_reader
 from types import ModuleType
+from stat import ST_SIZE
+
+try:
+    from hashlib import md5
+except ImportError:
+    from md5 import md5
+
 from distutils2.errors import DistutilsError
 from distutils2.metadata import DistributionMetadata
 from distutils2.version import suggest_normalized_version, VersionPredicate
@@ -922,10 +929,6 @@
                 for field in ('Obsoletes', 'Requires', 'Provides'):
                     del self.metadata[field]
 
-            provides = "%s (%s)" % (self.metadata['name'],
-                                    self.metadata['version'])
-            self.metadata['Provides-Dist'] += (provides,)
-
         reqs = []
 
         if requires is not None:
@@ -973,6 +976,33 @@
         return '%s-%s at %s' % (self.name, self.metadata.version, self.path)
 
     def get_installed_files(self, local=False):
+
+        def _md5(path):
+            f = open(path)
+            try:
+                content = f.read()
+            finally:
+                f.close()
+            return md5(content).hexdigest()
+
+        def _size(path):
+            return os.stat(path)[ST_SIZE]
+
+        path = self.path
+        if local:
+            path = path.replace('/', os.sep)
+
+        # XXX What about scripts and data files ?
+        if os.path.isfile(path):
+            return [(path, _md5(path), _size(path))]
+        else:
+            files = []
+            for root, dir, files_ in os.walk(path):
+                for item in files_:
+                    item = os.path.join(root, item)
+                    files.append((item, _md5(item), _size(item)))
+            return files
+
         return []
 
     def uses(self, path):
@@ -1029,7 +1059,7 @@
         for dist in _yield_distributions(True, use_egg_info, paths):
             yield dist
     else:
-        _generate_cache(use_egg_info)
+        _generate_cache(use_egg_info, paths)
 
         for dist in _cache_path.itervalues():
             yield dist
@@ -1061,7 +1091,7 @@
             if dist.name == name:
                 return dist
     else:
-        _generate_cache(use_egg_info)
+        _generate_cache(use_egg_info, paths)
 
         if name in _cache_name:
             return _cache_name[name][0]
diff --git a/distutils2/_backport/shutil.py b/distutils2/_backport/shutil.py
--- a/distutils2/_backport/shutil.py
+++ b/distutils2/_backport/shutil.py
@@ -742,6 +742,8 @@
     if extract_dir is None:
         extract_dir = os.getcwd()
 
+    func = None
+
     if format is not None:
         try:
             format_info = _UNPACK_FORMATS[format]
@@ -758,4 +760,9 @@
 
         func = _UNPACK_FORMATS[format][1]
         kwargs = dict(_UNPACK_FORMATS[format][2])
-    raise ValueError('Unknown archive format: %s' % filename)
+        func(filename, extract_dir, **kwargs)
+
+    if func is None:
+        raise ValueError('Unknown archive format: %s' % filename)
+
+    return extract_dir
diff --git a/distutils2/_backport/tests/fake_dists/coconuts-aster-10.3.egg-info/PKG-INFO b/distutils2/_backport/tests/fake_dists/coconuts-aster-10.3.egg-info/PKG-INFO
new file mode 100644
--- /dev/null
+++ b/distutils2/_backport/tests/fake_dists/coconuts-aster-10.3.egg-info/PKG-INFO
@@ -0,0 +1,5 @@
+Metadata-Version: 1.2
+Name: coconuts-aster
+Version: 10.3
+Provides-Dist: strawberry (0.6)
+Provides-Dist: banana (0.4)
diff --git a/distutils2/_backport/tests/test_pkgutil.py b/distutils2/_backport/tests/test_pkgutil.py
--- a/distutils2/_backport/tests/test_pkgutil.py
+++ b/distutils2/_backport/tests/test_pkgutil.py
@@ -389,6 +389,7 @@
 
         # Now, test if the egg-info distributions are found correctly as well
         fake_dists += [('bacon', '0.1'), ('cheese', '2.0.2'),
+                       ('coconuts-aster', '10.3'),
                        ('banana', '0.4'), ('strawberry', '0.6'),
                        ('truffles', '5.0'), ('nut', 'funkyversion')]
         found_dists = []
@@ -494,18 +495,18 @@
 
         l = [dist.name for dist in provides_distribution('truffles', '>1.5',
                                                          use_egg_info=True)]
-        checkLists(l, ['bacon', 'truffles'])
+        checkLists(l, ['bacon'])
 
         l = [dist.name for dist in provides_distribution('truffles', '>=1.0')]
         checkLists(l, ['choxie', 'towel-stuff'])
 
         l = [dist.name for dist in provides_distribution('strawberry', '0.6',
                                                          use_egg_info=True)]
-        checkLists(l, ['strawberry'])
+        checkLists(l, ['coconuts-aster'])
 
         l = [dist.name for dist in provides_distribution('strawberry', '>=0.5',
                                                          use_egg_info=True)]
-        checkLists(l, ['strawberry'])
+        checkLists(l, ['coconuts-aster'])
 
         l = [dist.name for dist in provides_distribution('strawberry', '>0.6',
                                                          use_egg_info=True)]
@@ -513,11 +514,11 @@
 
         l = [dist.name for dist in provides_distribution('banana', '0.4',
                                                          use_egg_info=True)]
-        checkLists(l, ['banana'])
+        checkLists(l, ['coconuts-aster'])
 
         l = [dist.name for dist in provides_distribution('banana', '>=0.3',
                                                          use_egg_info=True)]
-        checkLists(l, ['banana'])
+        checkLists(l, ['coconuts-aster'])
 
         l = [dist.name for dist in provides_distribution('banana', '!=0.4',
                                                          use_egg_info=True)]
@@ -557,7 +558,7 @@
 
         eggs = [('bacon', '0.1'), ('banana', '0.4'), ('strawberry', '0.6'),
                 ('truffles', '5.0'), ('cheese', '2.0.2'),
-                ('nut', 'funkyversion')]
+                ('coconuts-aster', '10.3'), ('nut', 'funkyversion')]
         dists = [('choxie', '2.0.0.9'), ('grammar', '1.0a4'),
                  ('towel-stuff', '0.1')]
 
diff --git a/distutils2/config.py b/distutils2/config.py
--- a/distutils2/config.py
+++ b/distutils2/config.py
@@ -3,14 +3,36 @@
     Know how to read all config files Distutils2 uses.
 """
 import os
+import re
 import sys
 from ConfigParser import RawConfigParser
+from shlex import split
 
 from distutils2 import logger
 from distutils2.errors import DistutilsOptionError
+from distutils2.compiler.extension import Extension
 from distutils2.util import check_environ, resolve_name, strtobool
 from distutils2.compiler import set_compiler
 from distutils2.command import set_command
+from distutils2.markers import interpret
+
+
+def _pop_values(values_dct, key):
+    """Remove values from the dictionary and convert them as a list"""
+    vals_str = values_dct.pop(key, '')
+    if not vals_str:
+        return
+    fields = []
+    for field in vals_str.split(os.linesep):
+        tmp_vals = field.split('--')
+        if (len(tmp_vals) == 2) and (not interpret(tmp_vals[1])):
+            continue
+        fields.append(tmp_vals[0])
+    # Get bash options like `gcc -print-file-name=libgcc.a`
+    vals = split(' '.join(fields))
+    if vals:
+        return vals
+
 
 class Config(object):
     """Reads configuration files and work with the Distribution instance
@@ -182,6 +204,34 @@
             # manifest template
             self.dist.extra_files = files.get('extra_files', [])
 
+        ext_modules = self.dist.ext_modules
+        for section_key in content:
+            labels = section_key.split('=')
+            if (len(labels) == 2) and (labels[0] == 'extension'):
+                # labels[1] not used from now but should be implemented
+                # for extension build dependency
+                values_dct = content[section_key]
+                ext_modules.append(Extension(
+                    values_dct.pop('name'),
+                    _pop_values(values_dct, 'sources'),
+                    _pop_values(values_dct, 'include_dirs'),
+                    _pop_values(values_dct, 'define_macros'),
+                    _pop_values(values_dct, 'undef_macros'),
+                    _pop_values(values_dct, 'library_dirs'),
+                    _pop_values(values_dct, 'libraries'),
+                    _pop_values(values_dct, 'runtime_library_dirs'),
+                    _pop_values(values_dct, 'extra_objects'),
+                    _pop_values(values_dct, 'extra_compile_args'),
+                    _pop_values(values_dct, 'extra_link_args'),
+                    _pop_values(values_dct, 'export_symbols'),
+                    _pop_values(values_dct, 'swig_opts'),
+                    _pop_values(values_dct, 'depends'),
+                    values_dct.pop('language', None),
+                    values_dct.pop('optional', None),
+                    **values_dct
+                ))
+
+
     def parse_config_files(self, filenames=None):
         if filenames is None:
             filenames = self.find_config_files()
diff --git a/distutils2/index/dist.py b/distutils2/index/dist.py
--- a/distutils2/index/dist.py
+++ b/distutils2/index/dist.py
@@ -149,6 +149,16 @@
                 dist = self.dists.values()[0]
             return dist
 
+    def unpack(self, path=None, prefer_source=True):
+        """Unpack the distribution to the given path.
+
+        If not destination is given, creates a temporary location.
+
+        Returns the location of the extracted files (root).
+        """
+        return self.get_distribution(prefer_source=prefer_source)\
+                   .unpack(path=path)
+
     def download(self, temp_path=None, prefer_source=True):
         """Download the distribution, using the requirements.
 
@@ -312,7 +322,7 @@
             if path is None:
                 path = tempfile.mkdtemp()
 
-            filename = self.download()
+            filename = self.download(path)
             content_type = mimetypes.guess_type(filename)[0]
             self._unpacked_dir = unpack_archive(filename)
 
@@ -332,8 +342,11 @@
                     % (hashval.hexdigest(), expected_hashval))
 
     def __repr__(self):
+        if self.release is None:
+            return "<? ? %s>" % self.dist_type
+
         return "<%s %s %s>" % (
-            self.release.name, self.release.version, self.dist_type or "")
+                self.release.name, self.release.version, self.dist_type or "")
 
 
 class ReleasesList(IndexReference):
diff --git a/distutils2/install.py b/distutils2/install.py
--- a/distutils2/install.py
+++ b/distutils2/install.py
@@ -1,17 +1,3 @@
-from tempfile import mkdtemp
-import shutil
-import os
-import errno
-import itertools
-
-from distutils2 import logger
-from distutils2._backport.pkgutil import get_distributions
-from distutils2._backport.sysconfig import get_config_var
-from distutils2.depgraph import generate_graph
-from distutils2.index import wrapper
-from distutils2.index.errors import ProjectNotFound, ReleaseNotFound
-from distutils2.version import get_version_predicate
-
 """Provides installations scripts.
 
 The goal of this script is to install a release from the indexes (eg.
@@ -20,6 +6,27 @@
 It uses the work made in pkgutil and by the index crawlers to browse the
 installed distributions, and rely on the instalation commands to install.
 """
+import shutil
+import os
+import sys
+import stat
+import errno
+import itertools
+import tempfile
+
+from distutils2 import logger
+from distutils2._backport.pkgutil import get_distributions
+from distutils2._backport.pkgutil import get_distribution
+from distutils2._backport.sysconfig import get_config_var
+from distutils2.depgraph import generate_graph
+from distutils2.index import wrapper
+from distutils2.index.errors import ProjectNotFound, ReleaseNotFound
+from distutils2.errors import DistutilsError
+from distutils2.version import get_version_predicate
+
+
+__all__ = ['install_dists', 'install_from_infos', 'get_infos', 'remove',
+           'install']
 
 
 class InstallationException(Exception):
@@ -30,7 +37,7 @@
     """Raised when a conflict is detected"""
 
 
-def move_files(files, destination=None):
+def _move_files(files, destination):
     """Move the list of files in the destination folder, keeping the same
     structure.
 
@@ -38,13 +45,11 @@
 
     :param files: a list of files to move.
     :param destination: the destination directory to put on the files.
-                        if not defined, create a new one, using mkdtemp
     """
-    if not destination:
-        destination = mkdtemp()
-
     for old in files:
-        new = '%s%s' % (destination, old)
+        # not using os.path.join() because basename() might not be
+        # unique in destination
+        new = "%s%s" % (destination, old)
 
         # try to make the paths.
         try:
@@ -55,7 +60,7 @@
             else:
                 raise e
         os.rename(old, new)
-        yield (old, new)
+        yield old, new
 
 
 def _run_d1_install(archive_dir, path):
@@ -88,7 +93,7 @@
     * copy the files in "path"
     * determine if the distribution is distutils2 or distutils1.
     """
-    where = dist.unpack(archive)
+    where = dist.unpack(path)
 
     # get into the dir
     archive_dir = None
@@ -114,7 +119,7 @@
         os.chdir(old_dir)
 
 
-def install_dists(dists, path=None):
+def install_dists(dists, path, paths=sys.path):
     """Install all distributions provided in dists, with the given prefix.
 
     If an error occurs while installing one of the distributions, uninstall all
@@ -124,27 +129,28 @@
 
     :param dists: distributions to install
     :param path: base path to install distribution in
+    :param paths: list of paths (defaults to sys.path) to look for info
     """
-    if not path:
-        path = mkdtemp()
 
     installed_dists, installed_files = [], []
-    for d in dists:
-        logger.info('Installing %s %s' % (d.name, d.version))
+    for dist in dists:
+        logger.info('Installing %s %s' % (dist.name, dist.version))
         try:
-            installed_files.extend(_install_dist(d, path))
-            installed_dists.append(d)
-        except Exception, e :
+            installed_files.extend(_install_dist(dist, path))
+            installed_dists.append(dist)
+        except Exception, e:
             logger.info('Failed. %s' % str(e))
 
             # reverting
-            for d in installed_dists:
-                uninstall(d)
+            for installed_dist in installed_dists:
+                _remove_dist(installed_dist, paths)
             raise e
+
     return installed_files
 
 
-def install_from_infos(install=[], remove=[], conflicts=[], install_path=None):
+def install_from_infos(install_path=None, install=[], remove=[], conflicts=[],
+                       paths=sys.path):
     """Install and remove the given distributions.
 
     The function signature is made to be compatible with the one of get_infos.
@@ -163,35 +169,43 @@
         4. Else, move the distributions to the right locations, and remove for
            real the distributions thats need to be removed.
 
-    :param install: list of distributions that will be installed.
+    :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 where we want to install the
-                         distributions.
+    :param paths: list of paths (defaults to sys.path) to look for info
     """
     # first of all, if we have conflicts, stop here.
     if conflicts:
         raise InstallationConflict(conflicts)
 
+    if install and not install_path:
+        raise ValueError("Distributions are to be installed but `install_path`"
+                         " is not provided.")
+
     # before removing the files, we will start by moving them away
     # then, if any error occurs, we could replace them in the good place.
     temp_files = {}  # contains lists of {dist: (old, new)} paths
+    temp_dir = None
     if remove:
+        temp_dir = tempfile.mkdtemp()
         for dist in remove:
             files = dist.get_installed_files()
-            temp_files[dist] = move_files(files)
+            temp_files[dist] = _move_files(files, temp_dir)
     try:
         if install:
-            installed_files = install_dists(install, install_path)  # install to tmp first
-
+            install_dists(install, install_path, paths)
     except:
-        # if an error occurs, put back the files in the good place.
+        # if an error occurs, put back the files in the right place.
         for files in temp_files.values():
             for old, new in files:
                 shutil.move(new, old)
-
+        if temp_dir:
+            shutil.rmtree(temp_dir)
         # now re-raising
         raise
 
@@ -199,6 +213,8 @@
     for files in temp_files.values():
         for old, new in files:
             os.remove(new)
+    if temp_dir:
+        shutil.rmtree(temp_dir)
 
 
 def _get_setuptools_deps(release):
@@ -260,9 +276,9 @@
     # Get all the releases that match the requirements
     try:
         releases = index.get_releases(requirements)
-    except (ReleaseNotFound, ProjectNotFound), e:
+    except (ReleaseNotFound, ProjectNotFound):
         raise InstallationException('Release not found: "%s"' % requirements)
-    
+
     # Pick up a release, and try to get the dependency tree
     release = releases.get_last(requirements, prefer_final=prefer_final)
 
@@ -279,6 +295,8 @@
     else:
         deps = metadata['requires_dist']
 
+    # XXX deps not used
+
     distributions = itertools.chain(installed, [release])
     depgraph = generate_graph(distributions)
 
@@ -310,18 +328,71 @@
             infos[key].extend(new_infos[key])
 
 
-def remove(project_name):
+def _remove_dist(dist, paths=sys.path):
+    remove(dist.name, paths)
+
+
+def remove(project_name, paths=sys.path):
     """Removes a single project from the installation"""
-    pass
+    dist = get_distribution(project_name, use_egg_info=True, paths=paths)
+    if dist is None:
+        raise DistutilsError('Distribution "%s" not found' % project_name)
+    files = dist.get_installed_files(local=True)
+    rmdirs = []
+    rmfiles = []
+    tmp = tempfile.mkdtemp(prefix=project_name + '-uninstall')
+    try:
+        for file_, md5, size in files:
+            if os.path.isfile(file_):
+                dirname, filename = os.path.split(file_)
+                tmpfile = os.path.join(tmp, filename)
+                try:
+                    os.rename(file_, tmpfile)
+                finally:
+                    if not os.path.isfile(file_):
+                        os.rename(tmpfile, file_)
+                if file_ not in rmfiles:
+                    rmfiles.append(file_)
+                if dirname not in rmdirs:
+                    rmdirs.append(dirname)
+    finally:
+        shutil.rmtree(tmp)
 
+    logger.info('Removing %r...' % project_name)
 
+    file_count = 0
+    for file_ in rmfiles:
+        os.remove(file_)
+        file_count +=1
 
+    dir_count = 0
+    for dirname in rmdirs:
+        if not os.path.exists(dirname):
+            # could
+            continue
 
-def main(**attrs):
-    if 'script_args' not in attrs:
-        import sys
-        attrs['requirements'] = sys.argv[1]
-    get_infos(**attrs)
+        files_count = 0
+        for root, dir, files in os.walk(dirname):
+            files_count += len(files)
+
+        if files_count > 0:
+            # XXX Warning
+            continue
+
+        # empty dirs with only empty dirs
+        if bool(os.stat(dirname).st_mode & stat.S_IWUSR):
+            # XXX Add a callable in shutil.rmtree to count
+            # the number of deleted elements
+            shutil.rmtree(dirname)
+            dir_count += 1
+
+    # removing the top path
+    # XXX count it ?
+    if os.path.exists(dist.path):
+        shutil.rmtree(dist.path)
+
+    logger.info('Success ! Removed %d files and %d dirs' % \
+            (file_count, dir_count))
 
 
 def install(project):
@@ -338,13 +409,20 @@
 
     install_path = get_config_var('base')
     try:
-        install_from_infos(info['install'], info['remove'], info['conflict'],
-                           install_path=install_path)
+        install_from_infos(install_path,
+                           info['install'], info['remove'], info['conflict'])
 
     except InstallationConflict, e:
         projects = ['%s %s' % (p.name, p.version) for p in e.args[0]]
         logger.info('"%s" conflicts with "%s"' % (project, ','.join(projects)))
 
 
+def _main(**attrs):
+    if 'script_args' not in attrs:
+        import sys
+        attrs['requirements'] = sys.argv[1]
+    get_infos(**attrs)
+
+
 if __name__ == '__main__':
-    main()
+    _main()
diff --git a/distutils2/mkcfg.py b/distutils2/mkcfg.py
--- a/distutils2/mkcfg.py
+++ b/distutils2/mkcfg.py
@@ -20,17 +20,27 @@
 #  Ask for the dependencies.
 #  Ask for the Requires-Dist
 #  Ask for the Provides-Dist
+#  Ask for a description
 #  Detect scripts (not sure how.  #! outside of package?)
 
 import os
 import sys
 import re
 import shutil
+import glob
+import re
 from ConfigParser import RawConfigParser
 from textwrap import dedent
+if sys.version_info[:2] < (2, 6):
+    from sets import Set as set
+try:
+    from hashlib import md5
+except ImportError:
+    from md5 import md5
 # importing this with an underscore as it should be replaced by the
 # dict form or another structures for all purposes
 from distutils2._trove import all_classifiers as _CLASSIFIERS_LIST
+from distutils2._backport import sysconfig
 
 _FILENAME = 'setup.cfg'
 
@@ -82,6 +92,10 @@
 Optionally, you can set other trove identifiers for things such as the
 human language, programming language, user interface, etc...
 ''',
+    'setup.py found':'''
+The setup.py script will be executed to retrieve the metadata.
+A wizard will be run if you answer "n",
+'''
 }
 
 # XXX everything needs docstrings and tests (both low-level tests of various
@@ -158,16 +172,18 @@
 
 LICENCES = _build_licences(_CLASSIFIERS_LIST)
 
-
 class MainProgram(object):
     def __init__(self):
         self.configparser = None
-        self.classifiers = {}
+        self.classifiers = set([])
         self.data = {}
         self.data['classifier'] = self.classifiers
         self.data['packages'] = []
         self.data['modules'] = []
+        self.data['platform'] = []
+        self.data['resources'] = []
         self.data['extra_files'] = []
+        self.data['scripts'] = []
         self.load_config_file()
 
     def lookup_option(self, key):
@@ -178,6 +194,7 @@
     def load_config_file(self):
         self.configparser = RawConfigParser()
         # TODO replace with section in distutils config file
+        #XXX freedesktop
         self.configparser.read(os.path.expanduser('~/.mkcfg'))
         self.data['author'] = self.lookup_option('author')
         self.data['author_email'] = self.lookup_option('author_email')
@@ -194,6 +211,7 @@
         if not valuesDifferent:
             return
 
+        #XXX freedesktop
         fp = open(os.path.expanduser('~/.mkcfgpy'), 'w')
         try:
             self.configparser.write(fp)
@@ -201,19 +219,122 @@
             fp.close()
 
     def load_existing_setup_script(self):
-        raise NotImplementedError
-        # Ideas:
-        # - define a mock module to assign to sys.modules['distutils'] before
-        # importing the setup script as a module (or executing it); it would
-        # provide setup (a function that just returns its args as a dict),
-        # Extension (ditto), find_packages (the real function)
-        # - we could even mock Distribution and commands to handle more setup
-        # scripts
-        # - we could use a sandbox (http://bugs.python.org/issue8680)
-        # - the cleanest way is to parse the file, not import it, but there is
-        # no way to do that across versions (the compiler package is
-        # deprecated or removed in recent Pythons, the ast module is not
-        # present before 2.6)
+        """ Generate a setup.cfg from an existing setup.py.
+
+        It only exports the distutils metadata (setuptools specific metadata
+        is not actually supported).
+        """
+        setuppath = 'setup.py'
+        if not os.path.exists(setuppath):
+            return
+        else:
+            ans = ask_yn(('A legacy setup.py has been found.\n'
+                          'Would you like to convert it to a setup.cfg ?'),
+                         'y',
+                         _helptext['setup.py found'])
+            if ans != 'y':
+                return
+
+        #_______mock setup start
+        data = self.data
+        def setup(**attrs):
+            """Mock the setup(**attrs) in order to retrive metadata."""
+            # use the distutils v1 processings to correctly parse metadata.
+            #XXX we could also use the setuptools distibution ???
+            from distutils.dist import Distribution
+            dist = Distribution(attrs)
+            dist.parse_config_files()
+            # 1. retrieves metadata that are quite similar PEP314<->PEP345
+            labels = (('name',) * 2,
+                      ('version',) * 2,
+                      ('author',) * 2,
+                      ('author_email',) * 2,
+                      ('maintainer',) * 2,
+                      ('maintainer_email',) * 2,
+                      ('description', 'summary'),
+                      ('long_description', 'description'),
+                      ('url', 'home_page'),
+                      ('platforms', 'platform'))
+
+            if sys.version[:3] >= '2.5':
+                labels += (('provides', 'provides-dist'),
+                           ('obsoletes', 'obsoletes-dist'),
+                           ('requires', 'requires-dist'),)
+            get = lambda lab: getattr(dist.metadata, lab.replace('-', '_'))
+            data.update((new, get(old)) for (old, new) in labels if get(old))
+            # 2. retrieves data that requires special processings.
+            data['classifier'].update(dist.get_classifiers() or [])
+            data['scripts'].extend(dist.scripts or [])
+            data['packages'].extend(dist.packages or [])
+            data['modules'].extend(dist.py_modules or [])
+            # 2.1 data_files -> resources.
+            if dist.data_files:
+                if len(dist.data_files) < 2 or \
+                   isinstance(dist.data_files[1], str):
+                    dist.data_files = [('', dist.data_files)]
+                #add tokens in the destination paths
+                vars = {'distribution.name':data['name']}
+                path_tokens = sysconfig.get_paths(vars=vars).items()
+                #sort tokens to use the longest one first
+                path_tokens.sort(cmp=lambda x,y: cmp(len(y), len(x)),
+                                 key=lambda x: x[1])
+                for dest, srcs in (dist.data_files or []):
+                    dest = os.path.join(sys.prefix, dest)
+                    for tok, path in path_tokens:
+                        if dest.startswith(path):
+                            dest = ('{%s}' % tok) + dest[len(path):]
+                            files = [('/ '.join(src.rsplit('/', 1)), dest)
+                                     for src in srcs]
+                            data['resources'].extend(files)
+                            continue
+            # 2.2 package_data -> extra_files
+            package_dirs = dist.package_dir or {}
+            for package, extras in dist.package_data.iteritems() or []:
+                package_dir = package_dirs.get(package, package)
+                fils = [os.path.join(package_dir, fil) for fil in extras]
+                data['extra_files'].extend(fils)
+
+            # Use README file if its content is the desciption
+            if "description" in data:
+                ref = md5(re.sub('\s', '', self.data['description']).lower())
+                ref = ref.digest()
+                for readme in glob.glob('README*'):
+                    fob = open(readme)
+                    val = md5(re.sub('\s', '', fob.read()).lower()).digest()
+                    fob.close()
+                    if val == ref:
+                        del data['description']
+                        data['description-file'] = readme
+                        break
+        #_________ mock setup end
+
+        # apply monkey patch to distutils (v1) and setuptools (if needed)
+        # (abord the feature if distutils v1 has been killed)
+        try:
+            import distutils.core as DC
+            getattr(DC, 'setup') # ensure distutils v1
+        except ImportError, AttributeError:
+            return
+        saved_setups = [(DC, DC.setup)]
+        DC.setup = setup
+        try:
+            import setuptools
+            saved_setups.append((setuptools, setuptools.setup))
+            setuptools.setup = setup
+        except ImportError, AttributeError:
+            pass
+        # get metadata by executing the setup.py with the patched setup(...)
+        success = False # for python < 2.4
+        try:
+            pyenv = globals().copy()
+            execfile(setuppath, pyenv)
+            success = True
+        finally: #revert monkey patches
+            for patched_module, original_setup in saved_setups:
+                patched_module.setup = original_setup
+        if not self.data:
+            raise ValueError('Unable to load metadata from setup.py')
+        return success
 
     def inspect_file(self, path):
         fp = open(path, 'r')
@@ -222,9 +343,11 @@
                 m = re.match(r'^#!.*python((?P<major>\d)(\.\d+)?)?$', line)
                 if m:
                     if m.group('major') == '3':
-                        self.classifiers['Programming Language :: Python :: 3'] = 1
+                        self.classifiers.add(
+                            'Programming Language :: Python :: 3')
                     else:
-                        self.classifiers['Programming Language :: Python :: 2'] = 1
+                        self.classifiers.add(
+                        'Programming Language :: Python :: 2')
         finally:
             fp.close()
 
@@ -370,7 +493,7 @@
         for key in sorted(trove):
             if len(trove[key]) == 0:
                 if ask_yn('Add "%s"' % desc[4:] + ' :: ' + key, 'n') == 'y':
-                    classifiers[desc[4:] + ' :: ' + key] = 1
+                    classifiers.add(desc[4:] + ' :: ' + key)
                 continue
 
             if ask_yn('Do you want to set items under\n   "%s" (%d sub-items)'
@@ -421,7 +544,7 @@
                 print ("ERROR: Invalid selection, type a number from the list "
                        "above.")
 
-            classifiers[_CLASSIFIERS_LIST[index]] = 1
+            classifiers.add(_CLASSIFIERS_LIST[index])
             return
 
     def set_devel_status(self, classifiers):
@@ -448,7 +571,7 @@
                            'Development Status :: 5 - Production/Stable',
                            'Development Status :: 6 - Mature',
                            'Development Status :: 7 - Inactive'][choice]
-                    classifiers[key] = 1
+                    classifiers.add(key)
                     return
                 except (IndexError, ValueError):
                     print ("ERROR: Invalid selection, type a single digit "
@@ -475,28 +598,39 @@
         fp = open(_FILENAME, 'w')
         try:
             fp.write('[metadata]\n')
-            fp.write('name = %s\n' % self.data['name'])
-            fp.write('version = %s\n' % self.data['version'])
-            fp.write('author = %s\n' % self.data['author'])
-            fp.write('author_email = %s\n' % self.data['author_email'])
-            fp.write('summary = %s\n' % self.data['summary'])
-            fp.write('home_page = %s\n' % self.data['home_page'])
-            fp.write('\n')
-            if len(self.data['classifier']) > 0:
-                classifiers = '\n'.join(['    %s' % clas for clas in
-                                         self.data['classifier']])
-                fp.write('classifier = %s\n' % classifiers.strip())
-                fp.write('\n')
-
-            fp.write('[files]\n')
-            for element in ('packages', 'modules', 'extra_files'):
-                if len(self.data[element]) == 0:
+            # simple string entries
+            for name in ('name', 'version', 'summary', 'download_url'):
+                fp.write('%s = %s\n' % (name, self.data.get(name, 'UNKNOWN')))
+            # optional string entries
+            if 'keywords' in self.data and self.data['keywords']:
+                fp.write('keywords = %s\n' % ' '.join(self.data['keywords']))
+            for name in ('home_page', 'author', 'author_email',
+                         'maintainer', 'maintainer_email', 'description-file'):
+                if name in self.data and self.data[name]:
+                    fp.write('%s = %s\n' % (name, self.data[name]))
+            if 'description' in self.data:
+                fp.write(
+                    'description = %s\n'
+                    % '\n       |'.join(self.data['description'].split('\n')))
+            # multiple use string entries
+            for name in ('platform', 'supported-platform', 'classifier',
+                         'requires-dist', 'provides-dist', 'obsoletes-dist',
+                         'requires-external'):
+                if not(name in self.data and self.data[name]):
                     continue
-                items = '\n'.join(['    %s' % item for item in
-                                  self.data[element]])
-                fp.write('%s = %s\n' % (element, items.strip()))
-
-            fp.write('\n')
+                fp.write('%s = ' % name)
+                fp.write(''.join('    %s\n' % val
+                                 for val in self.data[name]).lstrip())
+            fp.write('\n[files]\n')
+            for name in ('packages', 'modules', 'scripts',
+                         'package_data', 'extra_files'):
+                if not(name in self.data and self.data[name]):
+                    continue
+                fp.write('%s = %s\n'
+                         % (name, '\n    '.join(self.data[name]).strip()))
+            fp.write('\n[resources]\n')
+            for src, dest in self.data['resources']:
+                fp.write('%s = %s\n' % (src, dest))
         finally:
             fp.close()
 
@@ -508,11 +642,12 @@
     """Main entry point."""
     program = MainProgram()
     # uncomment when implemented
-    #program.load_existing_setup_script()
-    program.inspect_directory()
-    program.query_user()
-    program.update_config_file()
+    if not program.load_existing_setup_script():
+        program.inspect_directory()
+        program.query_user()
+        program.update_config_file()
     program.write_setup_script()
+    # istutils2.util.generate_distutils_setup_py()
 
 
 if __name__ == '__main__':
diff --git a/distutils2/run.py b/distutils2/run.py
--- a/distutils2/run.py
+++ b/distutils2/run.py
@@ -11,7 +11,7 @@
 from distutils2 import __version__
 from distutils2._backport.pkgutil import get_distributions, get_distribution
 from distutils2.depgraph import generate_graph
-from distutils2.install import install
+from distutils2.install import install, remove
 
 # This is a barebones help message generated displayed when the user
 # runs the setup script with no arguments at all.  More useful help
@@ -83,10 +83,10 @@
         dist = distclass(attrs)
     except DistutilsSetupError, msg:
         if 'name' in attrs:
-            raise SystemExit, "error in %s setup command: %s" % \
-                  (attrs['name'], msg)
+            raise SystemExit("error in %s setup command: %s" % \
+                  (attrs['name'], msg))
         else:
-            raise SystemExit, "error in setup command: %s" % msg
+            raise SystemExit("error in setup command: %s" % msg)
 
     # Find and parse the config file(s): they will override options from
     # the setup script, but be overridden by the command line.
@@ -98,22 +98,21 @@
     try:
         res = dist.parse_command_line()
     except DistutilsArgError, msg:
-        raise SystemExit, gen_usage(dist.script_name) + "\nerror: %s" % msg
+        raise SystemExit(gen_usage(dist.script_name) + "\nerror: %s" % msg)
 
     # And finally, run all the commands found on the command line.
     if res:
         try:
             dist.run_commands()
         except KeyboardInterrupt:
-            raise SystemExit, "interrupted"
+            raise SystemExit("interrupted")
         except (IOError, os.error), exc:
             error = grok_environment_error(exc)
-            raise SystemExit, error
+            raise SystemExit(error)
 
         except (DistutilsError,
                 CCompilerError), msg:
-            raise
-            raise SystemExit, "error: " + str(msg)
+            raise SystemExit("error: " + str(msg))
 
     return dist
 
@@ -127,7 +126,10 @@
 
 
 def main():
-    """Main entry point for Distutils2"""
+    """Main entry point for Distutils2
+
+    Execute an action or delegate to the commands system.
+    """
     _set_logger()
     parser = OptionParser()
     parser.disable_interspersed_args()
@@ -164,7 +166,7 @@
     options, args = parser.parse_args()
     if options.version:
         print('Distutils2 %s' % __version__)
-#        sys.exit(0)
+        return 0
 
     if len(options.metadata):
         from distutils2.dist import Distribution
@@ -178,18 +180,18 @@
             keys = options.metadata
             if len(keys) == 1:
                 print metadata[keys[0]]
-                sys.exit(0)
+                return
 
         for key in keys:
             if key in metadata:
-                print(metadata._convert_name(key)+':')
+                print(metadata._convert_name(key) + ':')
                 value = metadata[key]
                 if isinstance(value, list):
                     for v in value:
-                        print('    '+v)
+                        print('    ' + v)
                 else:
-                    print('    '+value.replace('\n', '\n    '))
-        sys.exit(0)
+                    print('    ' + value.replace('\n', '\n    '))
+        return 0
 
     if options.search is not None:
         search = options.search.lower()
@@ -199,7 +201,7 @@
                 print('%s %s at %s' % (dist.name, dist.metadata['version'],
                                      dist.path))
 
-        sys.exit(0)
+        return 0
 
     if options.graph is not None:
         name = options.graph
@@ -211,25 +213,29 @@
             graph = generate_graph(dists)
             print(graph.repr_node(dist))
 
-        sys.exit(0)
+        return 0
 
     if options.fgraph:
         dists = get_distributions(use_egg_info=True)
         graph = generate_graph(dists)
         print(graph)
-        sys.exit(0)
+        return 0
 
     if options.install is not None:
         install(options.install)
-        sys.exit(0)
+        return 0
+
+    if options.remove is not None:
+        remove(options.remove)
+        return 0
 
     if len(args) == 0:
         parser.print_help()
-        sys.exit(0)
+        return 0
 
-    return commands_main()
-#    sys.exit(0)
+    commands_main()
+    return 0
 
 
 if __name__ == '__main__':
-    main()
+    sys.exit(main())
diff --git a/distutils2/tests/pypiserver/downloads_with_md5/simple/foobar/foobar-0.1.tar.gz b/distutils2/tests/pypiserver/downloads_with_md5/simple/foobar/foobar-0.1.tar.gz
index 0000000000000000000000000000000000000000..333961eb18a6e7db80fefd41c339ab218d5180c4
GIT binary patch
literal 110
zc$|~(=3uy!>FUeC{PvtR-ysJc)&sVu<PP%fK3N&$;N1M<j{R(=POen}XGQA#H*w#;
z=4~0pQ=DD>?9yZ7`(A1Di)P(6s!I71JWZ;--fWND`LA)=lAmk-7Jbj=XMlnFEsQ#U
Kd|Vkc7#IK&xGYxy

diff --git a/distutils2/tests/support.py b/distutils2/tests/support.py
--- a/distutils2/tests/support.py
+++ b/distutils2/tests/support.py
@@ -159,6 +159,21 @@
         dist = Distribution(attrs=kw)
         return project_dir, dist
 
+    def assertIsFile(self, *args):
+        path = os.path.join(*args)
+        dirname = os.path.dirname(path)
+        file = os.path.basename(path)
+        if os.path.isdir(dirname):
+            files = os.listdir(dirname)
+            msg = "%s not found in %s: %s" % (file, dirname, files)
+            assert os.path.isfile(path), msg
+        else:
+            raise AssertionError(
+                    '%s not found. %s does not exist' % (file, dirname))
+
+    def assertIsNotFile(self, *args):
+        path = os.path.join(*args)
+        assert not os.path.isfile(path), "%s exist" % path
 
 class EnvironGuard(object):
     """TestCase-compatible mixin to save and restore the environment."""
diff --git a/distutils2/tests/test_config.py b/distutils2/tests/test_config.py
--- a/distutils2/tests/test_config.py
+++ b/distutils2/tests/test_config.py
@@ -93,6 +93,34 @@
 sub_commands = foo
 """
 
+# Can not be merged with SETUP_CFG else install_dist
+# command will fail when trying to compile C sources
+EXT_SETUP_CFG = """
+[files]
+packages = one
+           two
+
+[extension=speed_coconuts]
+name = one.speed_coconuts
+sources = c_src/speed_coconuts.c
+extra_link_args = "`gcc -print-file-name=libgcc.a`" -shared
+define_macros = HAVE_CAIRO HAVE_GTK2
+libraries = gecodeint gecodekernel -- sys.platform != 'win32'
+    GecodeInt GecodeKernel -- sys.platform == 'win32'
+
+[extension=fast_taunt]
+name = three.fast_taunt
+sources = cxx_src/utils_taunt.cxx
+          cxx_src/python_module.cxx
+include_dirs = /usr/include/gecode
+    /usr/include/blitz
+extra_compile_args = -fPIC -O2
+    -DGECODE_VERSION=$(./gecode_version) -- sys.platform != 'win32'
+    /DGECODE_VERSION='win32' -- sys.platform == 'win32'
+language = cxx
+
+"""
+
 
 class DCompiler(object):
     name = 'd'
@@ -134,7 +162,14 @@
         super(ConfigTestCase, self).setUp()
         self.addCleanup(setattr, sys, 'stdout', sys.stdout)
         self.addCleanup(setattr, sys, 'stderr', sys.stderr)
+        sys.stdout = sys.stderr = StringIO()
+
         self.addCleanup(os.chdir, os.getcwd())
+        tempdir = self.mkdtemp()
+        os.chdir(tempdir)
+        self.tempdir = tempdir
+
+        self.addCleanup(setattr, sys, 'argv', sys.argv)
 
     def write_setup(self, kwargs=None):
         opts = {'description-file': 'README', 'extra-files':''}
@@ -156,8 +191,6 @@
         return dist
 
     def test_config(self):
-        tempdir = self.mkdtemp()
-        os.chdir(tempdir)
         self.write_setup()
         self.write_file('README', 'yeah')
 
@@ -232,9 +265,6 @@
 
 
     def test_multiple_description_file(self):
-        tempdir = self.mkdtemp()
-        os.chdir(tempdir)
-
         self.write_setup({'description-file': 'README  CHANGES'})
         self.write_file('README', 'yeah')
         self.write_file('CHANGES', 'changelog2')
@@ -242,9 +272,6 @@
         self.assertEqual(dist.metadata.requires_files, ['README', 'CHANGES'])
 
     def test_multiline_description_file(self):
-        tempdir = self.mkdtemp()
-        os.chdir(tempdir)
-
         self.write_setup({'description-file': 'README\n  CHANGES'})
         self.write_file('README', 'yeah')
         self.write_file('CHANGES', 'changelog')
@@ -252,9 +279,37 @@
         self.assertEqual(dist.metadata['description'], 'yeah\nchangelog')
         self.assertEqual(dist.metadata.requires_files, ['README', 'CHANGES'])
 
+    def test_parse_extensions_in_config(self):
+        self.write_file('setup.cfg', EXT_SETUP_CFG)
+        dist = self.run_setup('--version')
+
+        ext_modules = dict((mod.name, mod) for mod in dist.ext_modules)
+        self.assertEqual(len(ext_modules), 2)
+        ext = ext_modules.get('one.speed_coconuts')
+        self.assertEqual(ext.sources, ['c_src/speed_coconuts.c'])
+        self.assertEqual(ext.define_macros, ['HAVE_CAIRO', 'HAVE_GTK2'])
+        libs = ['gecodeint', 'gecodekernel']
+        if sys.platform == 'win32':
+            libs = ['GecodeInt', 'GecodeKernel']
+        self.assertEqual(ext.libraries, libs)
+        self.assertEqual(ext.extra_link_args,
+            ['`gcc -print-file-name=libgcc.a`', '-shared'])
+
+        ext = ext_modules.get('three.fast_taunt')
+        self.assertEqual(ext.sources,
+            ['cxx_src/utils_taunt.cxx', 'cxx_src/python_module.cxx'])
+        self.assertEqual(ext.include_dirs,
+            ['/usr/include/gecode', '/usr/include/blitz'])
+        cargs = ['-fPIC', '-O2']
+        if sys.platform == 'win32':
+            cargs.append("/DGECODE_VERSION='win32'")
+        else:
+            cargs.append('-DGECODE_VERSION=$(./gecode_version)')
+        self.assertEqual(ext.extra_compile_args, cargs)
+        self.assertEqual(ext.language, 'cxx')
+
+
     def test_metadata_requires_description_files_missing(self):
-        tempdir = self.mkdtemp()
-        os.chdir(tempdir)
         self.write_setup({'description-file': 'README\n  README2'})
         self.write_file('README', 'yeah')
         self.write_file('README2', 'yeah')
@@ -278,8 +333,6 @@
         self.assertRaises(DistutilsFileError, cmd.make_distribution)
 
     def test_metadata_requires_description_files(self):
-        tempdir = self.mkdtemp()
-        os.chdir(tempdir)
         self.write_setup({'description-file': 'README\n  README2',
                           'extra-files':'\n  README2'})
         self.write_file('README', 'yeah')
@@ -315,8 +368,6 @@
         self.assertIn('README\nREADME2\n', open('MANIFEST').read())
 
     def test_sub_commands(self):
-        tempdir = self.mkdtemp()
-        os.chdir(tempdir)
         self.write_setup()
         self.write_file('README', 'yeah')
         self.write_file('haven.py', '#')
diff --git a/distutils2/tests/test_index_dist.py b/distutils2/tests/test_index_dist.py
--- a/distutils2/tests/test_index_dist.py
+++ b/distutils2/tests/test_index_dist.py
@@ -127,7 +127,7 @@
         url = "%s/simple/foobar/foobar-0.1.tar.gz" % server.full_address
         # check md5 if given
         dist = Dist(url=url, hashname="md5",
-                    hashval="d41d8cd98f00b204e9800998ecf8427e")
+                    hashval="fe18804c5b722ff024cabdf514924fc4")
         dist.download(self.mkdtemp())
 
         # a wrong md5 fails
@@ -157,6 +157,25 @@
                           hashname="invalid_hashname",
                           hashval="value")
 
+    @use_pypi_server('downloads_with_md5')
+    def test_unpack(self, server):
+        url = "%s/simple/foobar/foobar-0.1.tar.gz" % server.full_address
+        dist = Dist(url=url)
+        # doing an unpack
+        here = self.mkdtemp()
+        there = dist.unpack(here)
+        result = os.listdir(there)
+        self.assertIn('paf', result)
+        os.remove('paf')
+
+    def test_hashname(self):
+        # Invalid hashnames raises an exception on assignation
+        Dist(hashname="md5", hashval="value")
+
+        self.assertRaises(UnsupportedHashName, Dist,
+                          hashname="invalid_hashname",
+                          hashval="value")
+
 
 class TestReleasesList(unittest.TestCase):
 
diff --git a/distutils2/tests/test_install.py b/distutils2/tests/test_install.py
--- a/distutils2/tests/test_install.py
+++ b/distutils2/tests/test_install.py
@@ -39,6 +39,11 @@
             for f in range(0,3):
                self._real_files.append(mkstemp())
 
+    def _unlink_installed_files(self):
+        if self._files:
+            for f in self._real_files:
+                os.unlink(f[1])
+
     def get_installed_files(self, **args):
         if self._files:
             return [f[1] for f in self._real_files]
@@ -54,14 +59,14 @@
         self._called_with = []
         self._return_value = return_value
         self._raise = raise_exception
-    
+
     def __call__(self, *args, **kwargs):
         self.called = True
         self._times_called = self._times_called + 1
         self._called_with.append((args, kwargs))
         iterable = hasattr(self._raise, '__iter__')
         if self._raise:
-            if ((not iterable and self._raise) 
+            if ((not iterable and self._raise)
                     or self._raise[self._times_called - 1]):
                 raise Exception
         return self._return_value
@@ -70,25 +75,8 @@
         return (args, kwargs) in self._called_with
 
 
-def patch(parent, to_patch):
-    """monkey match a module"""
-    def wrapper(func):
-        print func
-        print dir(func)
-        old_func = getattr(parent, to_patch)
-        def wrapped(*args, **kwargs):
-            parent.__dict__[to_patch] = MagicMock()
-            try:
-                out = func(*args, **kwargs)
-            finally:
-                setattr(parent, to_patch, old_func)
-            return out
-        return wrapped
-    return wrapper
-
-
 def get_installed_dists(dists):
-    """Return a list of fake installed dists. 
+    """Return a list of fake installed dists.
     The list is name, version, deps"""
     objects = []
     for (name, version, deps) in dists:
@@ -100,12 +88,6 @@
     def _get_client(self, server, *args, **kwargs):
         return Client(server.full_address, *args, **kwargs)
 
-    def _patch_run_install(self):
-        """Patch run install"""
-
-    def _unpatch_run_install(self):
-        """Unpatch run install for d2 and d1"""
-
     def _get_results(self, output):
         """return a list of results"""
         installed = [(o.name, '%s' % o.version) for o in output['install']]
@@ -205,7 +187,7 @@
             ])
 
         # name, version, deps.
-        already_installed = [('bacon', '0.1', []), 
+        already_installed = [('bacon', '0.1', []),
                              ('chicken', '1.1', ['bacon (0.1)'])]
         output = install.get_infos("choxie", index=client, installed=
                            get_installed_dists(already_installed))
@@ -236,7 +218,7 @@
         files = [os.path.join(path, '%s' % x) for x in range(1, 20)]
         for f in files:
             file(f, 'a+')
-        output = [o for o in install.move_files(files, newpath)]
+        output = [o for o in install._move_files(files, newpath)]
 
         # check that output return the list of old/new places
         for f in files:
@@ -265,19 +247,19 @@
         old_install_dist = install._install_dist
         old_uninstall = getattr(install, 'uninstall', None)
 
-        install._install_dist = MagicMock(return_value=[], 
+        install._install_dist = MagicMock(return_value=[],
                 raise_exception=(False, True))
-        install.uninstall = MagicMock()
+        install.remove = MagicMock()
         try:
             d1 = ToInstallDist()
             d2 = ToInstallDist()
             path = self.mkdtemp()
             self.assertRaises(Exception, install.install_dists, [d1, d2], path)
             self.assertTrue(install._install_dist.called_with(d1, path))
-            self.assertTrue(install.uninstall.called)
+            self.assertTrue(install.remove.called)
         finally:
             install._install_dist = old_install_dist
-            install.uninstall = old_uninstall
+            install.remove = old_uninstall
 
 
     def test_install_dists_success(self):
@@ -322,7 +304,7 @@
         old_install_dist = install._install_dist
         old_uninstall = getattr(install, 'uninstall', None)
 
-        install._install_dist = MagicMock(return_value=[], 
+        install._install_dist = MagicMock(return_value=[],
                 raise_exception=(False, True))
         install.uninstall = MagicMock()
         try:
@@ -331,14 +313,17 @@
             for i in range(0,2):
                 remove.append(ToInstallDist(files=True))
             to_install = [ToInstallDist(), ToInstallDist()]
+            temp_dir = self.mkdtemp()
 
-            self.assertRaises(Exception, install.install_from_infos, 
-                    remove=remove, install=to_install)
+            self.assertRaises(Exception, install.install_from_infos,
+                              install_path=temp_dir, install=to_install,
+                              remove=remove)
             # assert that the files are in the same place
             # assert that the files have been removed
             for dist in remove:
                 for f in dist.get_installed_files():
                     self.assertTrue(os.path.exists(f))
+                dist._unlink_installed_files()
         finally:
             install.install_dist = old_install_dist
             install.uninstall = old_uninstall
@@ -352,8 +337,7 @@
             install_path = "my_install_path"
             to_install = [ToInstallDist(), ToInstallDist()]
 
-            install.install_from_infos(install=to_install,
-                                             install_path=install_path)
+            install.install_from_infos(install_path, install=to_install)
             for dist in to_install:
                 install._install_dist.called_with(install_path)
         finally:
diff --git a/distutils2/tests/test_mkcfg.py b/distutils2/tests/test_mkcfg.py
--- a/distutils2/tests/test_mkcfg.py
+++ b/distutils2/tests/test_mkcfg.py
@@ -1,10 +1,17 @@
+# -*- coding: utf-8 -*-
 """Tests for distutils.mkcfg."""
 import os
+import os.path as osp
 import sys
 import StringIO
+if sys.version_info[:2] < (2, 6):
+    from sets import Set as set
+from textwrap import dedent
+
 from distutils2.tests import run_unittest, support, unittest
 from distutils2.mkcfg import MainProgram
-from distutils2.mkcfg import ask_yn, ask
+from distutils2.mkcfg import ask_yn, ask, main
+
 
 class MkcfgTestCase(support.TempdirManager,
                     unittest.TestCase):
@@ -12,16 +19,20 @@
     def setUp(self):
         super(MkcfgTestCase, self).setUp()
         self._stdin = sys.stdin
-        self._stdout = sys.stdout        
+        self._stdout = sys.stdout
         sys.stdin = StringIO.StringIO()
         sys.stdout = StringIO.StringIO()
-        
+        self._cwd = os.getcwd()
+        self.wdir = self.mkdtemp()
+        os.chdir(self.wdir)
+
     def tearDown(self):
         super(MkcfgTestCase, self).tearDown()
         sys.stdin = self._stdin
         sys.stdout = self._stdout
-        
-    def test_ask_yn(self):        
+        os.chdir(self._cwd)
+
+    def test_ask_yn(self):
         sys.stdin.write('y\n')
         sys.stdin.seek(0)
         self.assertEqual('y', ask_yn('is this a test'))
@@ -40,13 +51,13 @@
         main.data['author'] = []
         main._set_multi('_set_multi test', 'author')
         self.assertEqual(['aaaaa'], main.data['author'])
-        
+
     def test_find_files(self):
         # making sure we scan a project dir correctly
         main = MainProgram()
 
         # building the structure
-        tempdir = self.mkdtemp()
+        tempdir = self.wdir
         dirs = ['pkg1', 'data', 'pkg2', 'pkg2/sub']
         files = ['README', 'setup.cfg', 'foo.py',
                  'pkg1/__init__.py', 'pkg1/bar.py',
@@ -60,12 +71,7 @@
             path = os.path.join(tempdir, file_)
             self.write_file(path, 'xxx')
 
-        old_dir = os.getcwd()
-        os.chdir(tempdir)
-        try:
-            main._find_files()
-        finally:
-            os.chdir(old_dir)
+        main._find_files()
 
         # do we have what we want ?
         self.assertEqual(main.data['packages'], ['pkg1', 'pkg2', 'pkg2.sub'])
@@ -73,6 +79,131 @@
         self.assertEqual(set(main.data['extra_files']),
                          set(['setup.cfg', 'README', 'data/data1']))
 
+    def test_convert_setup_py_to_cfg(self):
+        self.write_file((self.wdir, 'setup.py'),
+                        dedent("""
+        # -*- coding: utf-8 -*-
+        from distutils.core import setup
+        lg_dsc = '''My super Death-scription
+        barbar is now on the public domain,
+        ho, baby !'''
+        setup(name='pyxfoil',
+              version='0.2',
+              description='Python bindings for the Xfoil engine',
+              long_description = lg_dsc,
+              maintainer='André Espaze',
+              maintainer_email='andre.espaze at logilab.fr',
+              url='http://www.python-science.org/project/pyxfoil',
+              license='GPLv2',
+              packages=['pyxfoil', 'babar', 'me'],
+              data_files=[('share/doc/pyxfoil', ['README.rst']),
+                          ('share/man', ['pyxfoil.1']),
+                         ],
+              py_modules = ['my_lib', 'mymodule'],
+              package_dir = {'babar' : '',
+                             'me' : 'Martinique/Lamentin',
+                            },
+              package_data = {'babar': ['Pom', 'Flora', 'Alexander'],
+                              'me': ['dady', 'mumy', 'sys', 'bro'],
+                              '':  ['setup.py', 'README'],
+                              'pyxfoil' : ['fengine.so'],
+                             },
+              scripts = ['my_script', 'bin/run'],
+              )
+        """))
+        sys.stdin.write('y\n')
+        sys.stdin.seek(0)
+        main()
+        fid = open(osp.join(self.wdir, 'setup.cfg'))
+        lines = set([line.rstrip() for line in fid])
+        fid.close()
+        self.assertEqual(lines, set(['',
+            '[metadata]',
+            'version = 0.2',
+            'name = pyxfoil',
+            'maintainer = André Espaze',
+            'description = My super Death-scription',
+            '       |barbar is now on the public domain,',
+            '       |ho, baby !',
+            'maintainer_email = andre.espaze at logilab.fr',
+            'home_page = http://www.python-science.org/project/pyxfoil',
+            'download_url = UNKNOWN',
+            'summary = Python bindings for the Xfoil engine',
+            '[files]',
+            'modules = my_lib',
+            '    mymodule',
+            'packages = pyxfoil',
+            '    babar',
+            '    me',
+            'extra_files = Martinique/Lamentin/dady',
+            '    Martinique/Lamentin/mumy',
+            '    Martinique/Lamentin/sys',
+            '    Martinique/Lamentin/bro',
+            '    Pom',
+            '    Flora',
+            '    Alexander',
+            '    setup.py',
+            '    README',
+            '    pyxfoil/fengine.so',
+            'scripts = my_script',
+            '    bin/run',
+            '[resources]',
+            'README.rst = {doc}',
+            'pyxfoil.1 = {man}',
+        ]))
+
+    def test_convert_setup_py_to_cfg_with_description_in_readme(self):
+        self.write_file((self.wdir, 'setup.py'),
+                        dedent("""
+        # -*- coding: utf-8 -*-
+        from distutils.core import setup
+        lg_dsc = open('README.txt').read()
+        setup(name='pyxfoil',
+              version='0.2',
+              description='Python bindings for the Xfoil engine',
+              long_description=lg_dsc,
+              maintainer='André Espaze',
+              maintainer_email='andre.espaze at logilab.fr',
+              url='http://www.python-science.org/project/pyxfoil',
+              license='GPLv2',
+              packages=['pyxfoil'],
+              package_data={'pyxfoil' : ['fengine.so']},
+              data_files=[
+                ('share/doc/pyxfoil', ['README.rst']),
+                ('share/man', ['pyxfoil.1']),
+              ],
+        )
+        """))
+        self.write_file((self.wdir, 'README.txt'),
+                        dedent('''
+My super Death-scription
+barbar is now on the public domain,
+ho, baby !
+                        '''))
+        sys.stdin.write('y\n')
+        sys.stdin.seek(0)
+        main()
+        fid = open(osp.join(self.wdir, 'setup.cfg'))
+        lines = set([line.strip() for line in fid])
+        fid.close()
+        self.assertEqual(lines, set(['',
+            '[metadata]',
+            'version = 0.2',
+            'name = pyxfoil',
+            'maintainer = André Espaze',
+            'maintainer_email = andre.espaze at logilab.fr',
+            'home_page = http://www.python-science.org/project/pyxfoil',
+            'download_url = UNKNOWN',
+            'summary = Python bindings for the Xfoil engine',
+            'description-file = README.txt',
+            '[files]',
+            'packages = pyxfoil',
+            'extra_files = pyxfoil/fengine.so',
+            '[resources]',
+            'README.rst = {doc}',
+            'pyxfoil.1 = {man}',
+        ]))
+
 
 def test_suite():
     return unittest.makeSuite(MkcfgTestCase)
diff --git a/distutils2/tests/test_uninstall.py b/distutils2/tests/test_uninstall.py
new file mode 100644
--- /dev/null
+++ b/distutils2/tests/test_uninstall.py
@@ -0,0 +1,93 @@
+"""Tests for the uninstall command."""
+import os
+import sys
+from StringIO import StringIO
+from distutils2._backport.pkgutil import disable_cache, enable_cache
+from distutils2.tests import unittest, support, run_unittest
+from distutils2.errors import DistutilsError
+from distutils2.install import remove
+
+SETUP_CFG = """
+[metadata]
+name = %(name)s
+version = %(version)s
+
+[files]
+packages =
+    %(name)s
+    %(name)s.sub
+"""
+
+class UninstallTestCase(support.TempdirManager,
+                     support.LoggingCatcher,
+                     unittest.TestCase):
+
+    def setUp(self):
+        super(UninstallTestCase, self).setUp()
+        self.addCleanup(setattr, sys, 'stdout', sys.stdout)
+        self.addCleanup(setattr, sys, 'stderr', sys.stderr)
+        self.addCleanup(os.chdir, os.getcwd())
+        self.addCleanup(enable_cache)
+        self.root_dir = self.mkdtemp()
+        disable_cache()
+
+    def run_setup(self, *args):
+        # run setup with args
+        #sys.stdout = StringIO()
+        sys.argv[:] = [''] + list(args)
+        old_sys = sys.argv[:]
+        try:
+            from distutils2.run import commands_main
+            dist = commands_main()
+        finally:
+            sys.argv[:] = old_sys
+        return dist
+
+    def get_path(self, dist, name):
+        from distutils2.command.install_dist import install_dist
+        cmd = install_dist(dist)
+        cmd.prefix = self.root_dir
+        cmd.finalize_options()
+        return getattr(cmd, 'install_'+name)
+
+    def make_dist(self, pkg_name='foo', **kw):
+        dirname = self.mkdtemp()
+        kw['name'] = pkg_name
+        if 'version' not in kw:
+            kw['version'] = '0.1'
+        self.write_file((dirname, 'setup.cfg'), SETUP_CFG % kw)
+        os.mkdir(os.path.join(dirname, pkg_name))
+        self.write_file((dirname, '__init__.py'), '#')
+        self.write_file((dirname, pkg_name+'_utils.py'), '#')
+        os.mkdir(os.path.join(dirname, pkg_name, 'sub'))
+        self.write_file((dirname, pkg_name, 'sub', '__init__.py'), '#')
+        self.write_file((dirname, pkg_name, 'sub', pkg_name+'_utils.py'), '#')
+        return dirname
+
+    def install_dist(self, pkg_name='foo', dirname=None, **kw):
+        if not dirname:
+            dirname = self.make_dist(pkg_name, **kw)
+        os.chdir(dirname)
+        dist = self.run_setup('install_dist', '--prefix='+self.root_dir)
+        install_lib = self.get_path(dist, 'purelib')
+        return dist, install_lib
+
+    def test_uninstall_unknow_distribution(self):
+        self.assertRaises(DistutilsError, remove, 'foo', paths=[self.root_dir])
+
+    def test_uninstall(self):
+        dist, install_lib = self.install_dist()
+        self.assertIsFile(install_lib, 'foo', 'sub', '__init__.py')
+        self.assertIsFile(install_lib, 'foo-0.1.dist-info', 'RECORD')
+        remove('foo', paths=[install_lib])
+        self.assertIsNotFile(install_lib, 'foo', 'sub', '__init__.py')
+        self.assertIsNotFile(install_lib, 'foo-0.1.dist-info', 'RECORD')
+
+
+
+
+def test_suite():
+    return unittest.makeSuite(UninstallTestCase)
+
+if __name__ == '__main__':
+    run_unittest(test_suite())
diff --git a/distutils2/tests/test_util.py b/distutils2/tests/test_util.py
--- a/distutils2/tests/test_util.py
+++ b/distutils2/tests/test_util.py
@@ -414,6 +414,10 @@
     @unittest.skipUnless(os.name in ('nt', 'posix'),
                          'runs only under posix or nt')
     def test_spawn(self):
+        # Do not patch subprocess on unix because
+        # distutils2.util._spawn_posix uses it
+        if os.name in 'posix':
+            subprocess.Popen = self.old_popen
         tmpdir = self.mkdtemp()
 
         # creating something executable
diff --git a/distutils2/util.py b/distutils2/util.py
--- a/distutils2/util.py
+++ b/distutils2/util.py
@@ -12,6 +12,7 @@
 import shutil
 import tarfile
 import zipfile
+from subprocess import call as sub_call
 from copy import copy
 from fnmatch import fnmatchcase
 from ConfigParser import RawConfigParser
@@ -800,65 +801,16 @@
                   "command '%s' failed with exit status %d" % (cmd[0], rc))
 
 
-def _spawn_posix(cmd, search_path=1, verbose=0, dry_run=0, env=None):
-    logger.info(' '.join(cmd))
+def _spawn_posix(cmd, search_path=1, verbose=1, dry_run=0, env=None):
+    cmd = ' '.join(cmd)
+    if verbose:
+        logger.info(cmd)
     if dry_run:
         return
-
-    if env is None:
-        exec_fn = search_path and os.execvp or os.execv
-    else:
-        exec_fn = search_path and os.execvpe or os.execve
-
-    pid = os.fork()
-
-    if pid == 0:  # in the child
-        try:
-            if env is None:
-                exec_fn(cmd[0], cmd)
-            else:
-                exec_fn(cmd[0], cmd, env)
-        except OSError, e:
-            sys.stderr.write("unable to execute %s: %s\n" %
-                             (cmd[0], e.strerror))
-            os._exit(1)
-
-        sys.stderr.write("unable to execute %s for unknown reasons" % cmd[0])
-        os._exit(1)
-    else:   # in the parent
-        # Loop until the child either exits or is terminated by a signal
-        # (ie. keep waiting if it's merely stopped)
-        while 1:
-            try:
-                pid, status = os.waitpid(pid, 0)
-            except OSError, exc:
-                import errno
-                if exc.errno == errno.EINTR:
-                    continue
-                raise DistutilsExecError(
-                      "command '%s' failed: %s" % (cmd[0], exc[-1]))
-            if os.WIFSIGNALED(status):
-                raise DistutilsExecError(
-                      "command '%s' terminated by signal %d" % \
-                      (cmd[0], os.WTERMSIG(status)))
-
-            elif os.WIFEXITED(status):
-                exit_status = os.WEXITSTATUS(status)
-                if exit_status == 0:
-                    return   # hey, it succeeded!
-                else:
-                    raise DistutilsExecError(
-                          "command '%s' failed with exit status %d" % \
-                          (cmd[0], exit_status))
-
-            elif os.WIFSTOPPED(status):
-                continue
-
-            else:
-                raise DistutilsExecError(
-                      "unknown error executing '%s': termination status %d" % \
-                      (cmd[0], status))
-
+    exit_status = sub_call(cmd, shell=True, env=env)
+    if exit_status != 0:
+        msg = "command '%s' failed with exit status %d"
+        raise DistutilsExecError(msg % (cmd, exit_status))
 
 def find_executable(executable, path=None):
     """Tries to find 'executable' in the directories listed in 'path'.
@@ -1128,7 +1080,6 @@
             # There is no such option in the setup.cfg
             if arg == "long_description":
                 filename = has_get_option(config, section, "description_file")
-                print "We have a filename", filename
                 if filename:
                     in_cfg_value = open(filename).read()
             else:
@@ -1156,12 +1107,18 @@
         raise DistutilsFileError("A pre existing setup.py file exists")
 
     handle = open("setup.py", "w")
-    handle.write("# Distutils script using distutils2 setup.cfg to call the\n")
-    handle.write("# distutils.core.setup() with the right args.\n\n\n")
-    handle.write("import os\n")
-    handle.write("from distutils.core import setup\n")
-    handle.write("from ConfigParser import RawConfigParser\n\n")
-    handle.write(getsource(generate_distutils_kwargs_from_setup_cfg))
-    handle.write("\n\nkwargs = generate_distutils_kwargs_from_setup_cfg()\n")
-    handle.write("setup(**kwargs)")
-    handle.close()
+    try:
+        handle.write(
+            "# Distutils script using distutils2 setup.cfg to call the\n"
+            "# distutils.core.setup() with the right args.\n\n"
+            "import os\n"
+            "from distutils.core import setup\n"
+            "from ConfigParser import RawConfigParser\n\n"
+            "" + getsource(generate_distutils_kwargs_from_setup_cfg) + "\n\n"
+            "kwargs = generate_distutils_kwargs_from_setup_cfg()\n"
+            "setup(**kwargs)\n"
+        )
+    finally:
+        handle.close()
+
+generate_distutils_setup_py()
diff --git a/docs/design/configfile.rst b/docs/design/configfile.rst
new file mode 100644
--- /dev/null
+++ b/docs/design/configfile.rst
@@ -0,0 +1,132 @@
+.. _setup-config:
+
+************************************
+Writing the Setup Configuration File
+************************************
+
+Often, it's not possible to write down everything needed to build a distribution
+*a priori*: you may need to get some information from the user, or from the
+user's system, in order to proceed.  As long as that information is fairly
+simple---a list of directories to search for C header files or libraries, for
+example---then providing a configuration file, :file:`setup.cfg`, for users to
+edit is a cheap and easy way to solicit it.  Configuration files also let you
+provide default values for any command option, which the installer can then
+override either on the command line or by editing the config file.
+
+The setup configuration file is a useful middle-ground between the setup script
+---which, ideally, would be opaque to installers [#]_---and the command line to
+the setup script, which is outside of your control and entirely up to the
+installer.  In fact, :file:`setup.cfg` (and any other Distutils configuration
+files present on the target system) are processed after the contents of the
+setup script, but before the command line.  This has  several useful
+consequences:
+
+.. If you have more advanced needs, such as determining which extensions to
+   build based on what capabilities are present on the target system, then you
+   need the Distutils auto-configuration facility.  This started to appear in
+   Distutils 0.9 but, as of this writing, isn't mature or stable enough yet
+   for real-world use.
+
+* installers can override some of what you put in :file:`setup.py` by editing
+  :file:`setup.cfg`
+
+* you can provide non-standard defaults for options that are not easily set in
+  :file:`setup.py`
+
+* installers can override anything in :file:`setup.cfg` using the command-line
+  options to :file:`setup.py`
+
+The basic syntax of the configuration file is simple::
+
+   [command]
+   option=value
+   ...
+
+where *command* is one of the Distutils commands (e.g. :command:`build_py`,
+:command:`install`), and *option* is one of the options that command supports.
+Any number of options can be supplied for each command, and any number of
+command sections can be included in the file.  Blank lines are ignored, as are
+comments, which run from a ``'#'`` character until the end of the line.  Long
+option values can be split across multiple lines simply by indenting the
+continuation lines.
+
+You can find out the list of options supported by a particular command with the
+universal :option:`--help` option, e.g. ::
+
+   > python setup.py --help build_ext
+   [...]
+   Options for 'build_ext' command:
+     --build-lib (-b)     directory for compiled extension modules
+     --build-temp (-t)    directory for temporary files (build by-products)
+     --inplace (-i)       ignore build-lib and put compiled extensions into the
+                          source directory alongside your pure Python modules
+     --include-dirs (-I)  list of directories to search for header files
+     --define (-D)        C preprocessor macros to define
+     --undef (-U)         C preprocessor macros to undefine
+     --swig-opts          list of SWIG command-line options
+   [...]
+
+.. XXX do we want to support ``setup.py --help metadata``?
+
+Note that an option spelled :option:`--foo-bar` on the command line  is spelled
+:option:`foo_bar` in configuration files.
+
+For example, say you want your extensions to be built "in-place"---that is, you
+have an extension :mod:`pkg.ext`, and you want the compiled extension file
+(:file:`ext.so` on Unix, say) to be put in the same source directory as your
+pure Python modules :mod:`pkg.mod1` and :mod:`pkg.mod2`.  You can always use the
+:option:`--inplace` option on the command line to ensure this::
+
+   python setup.py build_ext --inplace
+
+But this requires that you always specify the :command:`build_ext` command
+explicitly, and remember to provide :option:`--inplace`. An easier way is to
+"set and forget" this option, by encoding it in :file:`setup.cfg`, the
+configuration file for this distribution::
+
+   [build_ext]
+   inplace=1
+
+This will affect all builds of this module distribution, whether or not you
+explicitly specify :command:`build_ext`.  If you include :file:`setup.cfg` in
+your source distribution, it will also affect end-user builds---which is
+probably a bad idea for this option, since always building extensions in-place
+would break installation of the module distribution.  In certain peculiar cases,
+though, modules are built right in their installation directory, so this is
+conceivably a useful ability.  (Distributing extensions that expect to be built
+in their installation directory is almost always a bad idea, though.)
+
+Another example: certain commands take a lot of options that don't change from
+run to run; for example, :command:`bdist_rpm` needs to know everything required
+to generate a "spec" file for creating an RPM distribution.  Some of this
+information comes from the setup script, and some is automatically generated by
+the Distutils (such as the list of files installed).  But some of it has to be
+supplied as options to :command:`bdist_rpm`, which would be very tedious to do
+on the command line for every run.  Hence, here is a snippet from the Distutils'
+own :file:`setup.cfg`::
+
+   [bdist_rpm]
+   release = 1
+   packager = Greg Ward <gward at python.net>
+   doc_files = CHANGES.txt
+               README.txt
+               USAGE.txt
+               doc/
+               examples/
+
+Note that the :option:`doc_files` option is simply a whitespace-separated string
+split across multiple lines for readability.
+
+
+.. seealso::
+
+   :ref:`inst-config-syntax` in "Installing Python Modules"
+      More information on the configuration files is available in the manual for
+      system administrators.
+
+
+.. rubric:: Footnotes
+
+.. [#] This ideal probably won't be achieved until auto-configuration is fully
+   supported by the Distutils.
+
diff --git a/docs/source/contributing.rst b/docs/source/contributing.rst
new file mode 100644
--- /dev/null
+++ b/docs/source/contributing.rst
@@ -0,0 +1,52 @@
+==========================
+Contributing to Distutils2
+==========================
+
+----------------
+Reporting Issues
+----------------
+
+When using, testing, developping distutils2, you may encounter issues. Please report to the following sections to know how these issues should be reported.
+
+Please keep in mind that this guide is intended to ease the triage and fixing processes by giving the maximum information to the developers. It should not be viewed as mandatory, only advised ;).
+
+Issues regarding distutils2 commands
+====================================
+
+- Go to http://bugs.python.org/ (you'll need a Python Bugs account), then "Issues" > "Create ticket".
+- **Title**: write in a short summary of the issue. 
+    * You may prefix the issue title with [d2_component], where d2_component can be : installer, sdist, setup.cfg, ... This will ease up the triage process.
+
+- **Components**: choose "Distutils2"
+- **Version**: choose "3rd party"
+- **Comment**: use the following template for versions, reproduction conditions:
+    * If some of the fields presented don't apply to the issue, feel free to pick only the ones you need.
+
+::
+
+    Operating System:
+    Version of Python:
+    Version of Distutils2:
+
+    How to reproduce:
+
+    What happens:
+
+    What should happen:
+
+- Filling in the fields:
+    * **How to reproduce**: indicate some test case to reproduce the issue.
+    * **What happens**: describe what is the error, paste tracebacks if you have any.
+    * **What should happen**: indicate what you think should be the result of the test case (wanted behaviour).
+    * **Versions**:
+        - If you're using a release of distutils2, you may want to test the latest version of the project (under developpment code).
+        - If the issue is present in the latest version, please indicate the tip commit of the version tested.
+        - Be careful to indicate the remote reference (12 characters, for instance c3cf81fc64db), not the local reference (rXXX).
+
+- If it is relevant, please join any file that will help reproducing the issue or logs to understand the problem (setup.cfg, strace ouptups, ...).
+
+Issues regarding PyPI display of the distutils2 projects
+========================================================
+
+- Please send a bug report to the catalog-sig at python.org mailing list.
+- You can include your setup.cfg, and a link to your project page.
diff --git a/docs/source/distutils/sourcedist.rst b/docs/source/distutils/sourcedist.rst
--- a/docs/source/distutils/sourcedist.rst
+++ b/docs/source/distutils/sourcedist.rst
@@ -86,8 +86,7 @@
   distributions, but in the future there will be a standard for testing Python
   module distributions)
 
-* :file:`README.txt` (or :file:`README`), :file:`setup.py` (or whatever  you
-  called your setup script), and :file:`setup.cfg`
+* The configuration file :file:`setup.cfg`
 
 * all files that matches the ``package_data`` metadata.
   See :ref:`distutils-installing-package-data`.
@@ -95,6 +94,10 @@
 * all files that matches the ``data_files`` metadata.
   See :ref:`distutils-additional-files`.
 
+.. Warning::
+    In Distutils2, setup.py and README (or README.txt) files are not more
+    included in source distribution by default
+
 Sometimes this is enough, but usually you will want to specify additional files
 to distribute.  The typical way to do this is to write a *manifest template*,
 called :file:`MANIFEST.in` by default.  The manifest template is just a list of
diff --git a/docs/source/setupcfg.rst b/docs/source/setupcfg.rst
--- a/docs/source/setupcfg.rst
+++ b/docs/source/setupcfg.rst
@@ -7,24 +7,35 @@
 
 Each section contains a description of its options.
 
-- Options that are marked *\*multi* can have multiple values, one value
-  per line.
+- Options that are marked *\*multi* can have multiple values, one value per
+  line.
 - Options that are marked *\*optional* can be omited.
-- Options that are marked *\*environ* can use environement markes, as described
-  in PEP 345.
+- Options that are marked *\*environ* can use environment markers, as described
+  in :PEP:`345`.
+
 
 The sections are:
 
-- global
-- metadata
-- files
-- command sections
+global
+    Global options for Distutils2.
+
+metadata
+    The metadata section contains the metadata for the project as described in
+    :PEP:`345`.
+
+files
+    Declaration of package files included in the project.
+
+`command` sections
+    Redefinition of user options for Distutils2 commands.
 
 
 global
 ======
 
-Contains global options for Distutils2. This section is shared with Distutils1.
+Contains global options for Distutils2. This section is shared with Distutils1
+(legacy version distributed in python 2.X standard library).
+
 
 - **commands**: Defined Distutils2 command. A command is defined by its fully
   qualified name.
@@ -38,13 +49,13 @@
   *\*optional* *\*multi*
 
 - **compilers**: Defined Distutils2 compiler. A compiler is defined by its fully
-  qualified name. 
+  qualified name.
 
   Example::
 
     [global]
     compiler =
-        package.compilers.CustomCCompiler
+        package.compiler.CustomCCompiler
 
   *\*optional* *\*multi*
 
@@ -52,21 +63,29 @@
   :file:`setup.cfg` file is read. The callable receives the configuration
   in form of a mapping and can make some changes to it. *\*optional*
 
+  Example::
+
+    [global]
+    setup_hook =
+        distutils2.tests.test_config.hook
+
 
 metadata
 ========
 
 The metadata section contains the metadata for the project as described in
-PEP 345.
+:PEP:`345`.
 
+.. Note::
+    Field names are case-insensitive.
 
 Fields:
 
 - **name**: Name of the project.
-- **version**: Version of the project. Must comply with PEP 386.
+- **version**: Version of the project. Must comply with :PEP:`386`.
 - **platform**: Platform specification describing an operating system supported
   by the distribution which is not listed in the "Operating System" Trove
-  classifiers. *\*multi* *\*optional*
+  classifiers (:PEP:`301`). *\*multi* *\*optional*
 - **supported-platform**: Binary distributions containing a PKG-INFO file will
   use the Supported-Platform field in their metadata to specify the OS and
   CPU for which the binary distribution was compiled.  The semantics of
@@ -113,14 +132,18 @@
     name = pypi2rpm
     version = 0.1
     author = Tarek Ziade
-    author_email = tarek at ziade.org
+    author-email = tarek at ziade.org
     summary = Script that transforms a sdist archive into a rpm archive
     description-file = README
-    home_page = http://bitbucket.org/tarek/pypi2rpm
+    home-page = http://bitbucket.org/tarek/pypi2rpm
+    project-url: RSS feed, https://bitbucket.org/tarek/pypi2rpm/rss
 
     classifier = Development Status :: 3 - Alpha
         License :: OSI Approved :: Mozilla Public License 1.1 (MPL 1.1)
 
+.. Note::
+    Some metadata fields seen in :PEP:`345` are automatically generated
+    (for instance Metadata-Version value).
 
 
 files
@@ -148,17 +171,30 @@
 
     extra_files =
             setup.py
+            README
 
+.. Note::
+    In Distutils2, setup.cfg will be implicitly included.
 
-command sections
-================
+.. Warning::
+    In Distutils2, setup.py and README (or README.txt) files are not more
+    included in source distribution by default
 
-Each command can have its options described in :file:`setup.cfg`
 
+`command` sections
+==================
+
+Each Distutils2 command can have its own user options defined in :file:`setup.cfg`
 
 Example::
 
     [sdist]
-    manifest_makers = package.module.Maker
+    manifest-builders = package.module.Maker
 
 
+To override the build class in order to generate Python3 code from your Python2 base::
+
+    [build_py]
+    use-2to3 = True
+
+

--
Repository URL: http://hg.python.org/distutils2


More information about the Python-checkins mailing list