[Python-checkins] distutils2: Merge

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


tarek.ziade pushed 7d859dd56e92 to distutils2:

http://hg.python.org/distutils2/rev/7d859dd56e92
changeset:   1062:7d859dd56e92
parent:      1061:84ae7ec1d532
parent:      978:301ad616c208
user:        FELD Boris <lothiraldan at gmail.com>
date:        Sun Jan 30 15:09:13 2011 +0100
summary:
  Merge

files:
  distutils2/_backport/pkgutil.py
  distutils2/config.py
  distutils2/tests/test_config.py
  docs/source/setupcfg.rst

diff --git a/distutils2/_backport/pkgutil.py b/distutils2/_backport/pkgutil.py
--- a/distutils2/_backport/pkgutil.py
+++ b/distutils2/_backport/pkgutil.py
@@ -922,10 +922,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:
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
@@ -8,6 +8,7 @@
 import sys
 import re
 from ConfigParser import RawConfigParser
+from shlex import split
 
 from distutils2 import logger
 from distutils2.errors import DistutilsOptionError
@@ -20,15 +21,9 @@
 
 def _pop_values(values_dct, key):
     """Remove values from the dictionary and convert them as a list"""
-    vals_str = values_dct.pop(key, None)
-    if not vals_str:
-        return
+    vals_str = values_dct.pop(key, '')
     # Get bash options like `gcc -print-file-name=libgcc.a`
-    vals = re.search('(`.*?`)', vals_str) or []
-    if vals:
-        vals = list(vals.groups())
-        vals_str = re.sub('`.*?`', '', vals_str)
-    vals.extend(vals_str.split())
+    vals = split(vals_str)
     if vals:
         return vals
 
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,4 +1,11 @@
-from tempfile import mkdtemp
+"""Provides installations scripts.
+
+The goal of this script is to install a release from the indexes (eg.
+PyPI), including the dependencies of the releases if needed.
+
+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
@@ -17,14 +24,9 @@
 from distutils2.errors import DistutilsError
 from distutils2.version import get_version_predicate
 
-"""Provides installations scripts.
 
-The goal of this script is to install a release from the indexes (eg.
-PyPI), including the dependencies of the releases if needed.
-
-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.
-"""
+__all__ = ['install_dists', 'install_from_infos', 'get_infos', 'remove',
+           'install']
 
 
 class InstallationException(Exception):
@@ -35,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.
 
@@ -43,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:
@@ -60,7 +60,7 @@
             else:
                 raise e
         os.rename(old, new)
-        yield (old, new)
+        yield old, new
 
 
 def _run_d1_install(archive_dir, path):
@@ -93,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
@@ -119,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
@@ -129,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.
@@ -168,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
 
@@ -204,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):
@@ -265,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)
 
@@ -284,6 +295,8 @@
     else:
         deps = metadata['requires_dist']
 
+    # XXX deps not used
+
     distributions = itertools.chain(installed, [release])
     depgraph = generate_graph(distributions)
 
@@ -315,16 +328,19 @@
             infos[key].extend(new_infos[key])
 
 
+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"""
-    tmp = tempfile.mkdtemp(prefix=project_name+'-uninstall')
     dist = get_distribution(project_name, 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):
@@ -339,8 +355,8 @@
                     rmfiles.append(file)
                 if dirname not in rmdirs:
                     rmdirs.append(dirname)
-    except OSError:
-        os.rmdir(tmp)
+    finally:
+        shutil.rmtree(tmp)
 
     for file in rmfiles:
         os.remove(file)
@@ -351,14 +367,6 @@
                 os.rmdir(dirname)
 
 
-
-def main(**attrs):
-    if 'script_args' not in attrs:
-        import sys
-        attrs['requirements'] = sys.argv[1]
-    get_infos(**attrs)
-
-
 def install(project):
     logger.info('Getting information about "%s".' % project)
     try:
@@ -373,13 +381,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
@@ -266,23 +266,25 @@
             data['packages'].extend(dist.packages or [])
             data['modules'].extend(dist.py_modules or [])
             # 2.1 data_files -> resources.
-            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
+            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 []:
diff --git a/distutils2/run.py b/distutils2/run.py
--- a/distutils2/run.py
+++ b/distutils2/run.py
@@ -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,25 @@
             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 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/test_config.py b/distutils2/tests/test_config.py
--- a/distutils2/tests/test_config.py
+++ b/distutils2/tests/test_config.py
@@ -103,7 +103,7 @@
 [extension=speed_coconuts]
 name = one.speed_coconuts
 sources = c_src/speed_coconuts.c
-extra_link_args = `gcc -print-file-name=libgcc.a` -shared
+extra_link_args = "`gcc -print-file-name=libgcc.a`" -shared
 define_macros = HAVE_CAIRO HAVE_GTK2
 
 [extension=fast_taunt]
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,24 @@
                           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)
+
+    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/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/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,6 +171,10 @@
 
     extra_files =
             setup.py
+            README
+
+.. Note::
+    In Distutils2, setup.cfg will be implicitly included.
 
 data-files
 ==========
@@ -369,17 +396,27 @@
   doc/ * = {doc}
   doc/ man = {man}
 
-We use brace expansion syntax to place all the bash and batch scripts into {scripts} category.    
+We use brace expansion syntax to place all the bash and batch scripts into {scripts} category.
 
-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