[Python-checkins] distutils2: now distutils2 uses set_command to set its own commands

tarek.ziade python-checkins at python.org
Sat Nov 13 01:30:19 CET 2010


tarek.ziade pushed e44df92acbdf to distutils2:

http://hg.python.org/distutils2/rev/e44df92acbdf
changeset:   813:e44df92acbdf
tag:         tip
user:        Tarek Ziade <tarek at ziade.org>
date:        Sat Nov 13 01:29:47 2010 +0100
summary:     now distutils2 uses set_command to set its own commands
files:       distutils2/command/__init__.py, distutils2/command/cmd.py, distutils2/command/sdist.py, distutils2/compiler/__init__.py, distutils2/config.py, distutils2/dist.py, distutils2/tests/test_command_test.py, distutils2/tests/test_config.py, distutils2/tests/test_dist.py

diff --git a/distutils2/command/__init__.py b/distutils2/command/__init__.py
--- a/distutils2/command/__init__.py
+++ b/distutils2/command/__init__.py
@@ -2,27 +2,53 @@
 
 Package containing implementation of all the standard Distutils
 commands."""
+from distutils2.errors import DistutilsModuleError
+from distutils2.util import resolve_name
 
+_COMMANDS = {
+    'check': 'distutils2.command.check.check',
+    'test': 'distutils2.command.test.test',
+    'build': 'distutils2.command.build.build',
+    'build_py': 'distutils2.command.build_py.build_py',
+    'build_ext': 'distutils2.command.build_ext.build_ext',
+    'build_clib': 'distutils2.command.build_clib.build_clib',
+    'build_scripts': 'distutils2.command.build_scripts.build_scripts',
+    'clean': 'distutils2.command.clean.clean',
+    'install_dist': 'distutils2.command.install_dist.install_dist',
+    'install_lib': 'distutils2.command.install_lib.install_lib',
+    'install_headers': 'distutils2.command.install_headers.install_headers',
+    'install_scripts': 'distutils2.command.install_scripts.install_scripts',
+    'install_data': 'distutils2.command.install_data.install_data',
+    'install_distinfo':
+        'distutils2.command.install_distinfo.install_distinfo',
+    'sdist': 'distutils2.command.sdist.sdist',
+    'bdist': 'distutils2.command.bdist.bdist',
+    'bdist_dumb': 'distutils2.command.bdist_dumb.bdist_dumb',
+    'bdist_wininst': 'distutils2.command.bdist_wininst.bdist_wininst',
+    'register': 'distutils2.command.register.register',
+    'upload': 'distutils2.command.upload.upload',
+    'upload_docs': 'distutils2.command.upload_docs.upload_docs'}
 
-__all__ = ['check',
-           'test',
-           'build',
-           'build_py',
-           'build_ext',
-           'build_clib',
-           'build_scripts',
-           'clean',
-           'install_dist',
-           'install_lib',
-           'install_headers',
-           'install_scripts',
-           'install_data',
-           'install_distinfo',
-           'sdist',
-           'bdist',
-           'bdist_dumb',
-           'bdist_wininst',
-           'register',
-           'upload',
-           'upload_docs'
-          ]
+
+def get_command_names():
+    """Returns registered commands"""
+    return sorted(_COMMANDS.keys())
+
+
+def set_command(location):
+    klass = resolve_name(location)
+    # we want to do the duck-type checking here
+    # XXX
+    _COMMANDS[klass.get_command_name()] = klass
+
+
+def get_command_class(name):
+    """Return the registered command"""
+    try:
+        klass = _COMMANDS[name]
+        if isinstance(klass, str):
+            klass = resolve_name(klass)
+            _COMMANDS[name] = klass
+        return klass
+    except KeyError:
+        raise DistutilsModuleError("Invalid command %s" % name)
diff --git a/distutils2/command/cmd.py b/distutils2/command/cmd.py
--- a/distutils2/command/cmd.py
+++ b/distutils2/command/cmd.py
@@ -296,11 +296,12 @@
 
     # -- Convenience methods for commands ------------------------------
 
-    def get_command_name(self):
-        if hasattr(self, 'command_name'):
-            return self.command_name
+    @classmethod
+    def get_command_name(cls):
+        if hasattr(cls, 'command_name'):
+            return cls.command_name
         else:
-            return self.__class__.__name__
+            return cls.__name__
 
     def set_undefined_options(self, src_cmd, *options):
         """Set values of undefined options from another command.
diff --git a/distutils2/command/sdist.py b/distutils2/command/sdist.py
--- a/distutils2/command/sdist.py
+++ b/distutils2/command/sdist.py
@@ -15,6 +15,7 @@
 except ImportError:
     from distutils2._backport.shutil import get_archive_formats
 
+from distutils2.command import get_command_names
 from distutils2.command.cmd import Command
 from distutils2.errors import (DistutilsPlatformError, DistutilsOptionError,
                                DistutilsTemplateError, DistutilsModuleError)
@@ -250,7 +251,7 @@
             if files:
                 self.filelist.extend(files)
 
-        for cmd_name in self.distribution.get_command_names():
+        for cmd_name in get_command_names():
             try:
                 cmd_obj = self.get_finalized_command(cmd_name)
             except DistutilsOptionError:
diff --git a/distutils2/compiler/__init__.py b/distutils2/compiler/__init__.py
--- a/distutils2/compiler/__init__.py
+++ b/distutils2/compiler/__init__.py
@@ -105,16 +105,19 @@
     return 'unix'
 
 
-_COMPILERS = {'unix': 'distutils2.compiler.unixccompiler.UnixCCompiler',
-              'msvc': 'distutils2.compiler.msvccompiler.MSVCCompiler',
-              'cygwin': 'distutils2.compiler.cygwinccompiler.CygWinCCompiler',
-              'mingw32': 'distutils2.compiler.cygwinccompiler.Mingw32CCompiler',
-              'bcpp': 'distutils2.compilers.bcppcompiler.BCPPCompiler'}
+_COMPILERS = {
+    'unix': 'distutils2.compiler.unixccompiler.UnixCCompiler',
+    'msvc': 'distutils2.compiler.msvccompiler.MSVCCompiler',
+    'cygwin': 'distutils2.compiler.cygwinccompiler.CygWinCCompiler',
+    'mingw32': 'distutils2.compiler.cygwinccompiler.Mingw32CCompiler',
+    'bcpp': 'distutils2.compilers.bcppcompiler.BCPPCompiler'}
 
 
-def set_compiler(name, location):
+def set_compiler(location):
     """Add or change a compiler"""
-    _COMPILERS[name] = location
+    klass = resolve_name(location)
+    # XXX we want to check teh class here
+    _COMPILERS[klass.compiler_type] = klass
 
 
 def show_compilers():
@@ -124,8 +127,11 @@
     from distutils2.fancy_getopt import FancyGetopt
     compilers = []
 
-    for compiler, location in _COMPILERS.items():
-        klass = resolve_name(location)
+    for name, klass in _COMPILERS.items():
+        if isinstance(klass, str):
+            klass = resolve_name(klass)
+            _COMPILERS[name] = klass
+
         compilers.append(("compiler=" + compiler, None, klass.description))
 
     compilers.sort()
@@ -151,24 +157,22 @@
         if compiler is None:
             compiler = get_default_compiler(plat)
 
-        location = _COMPILERS[compiler]
+        klass = _COMPILERS[compiler]
     except KeyError:
         msg = "don't know how to compile C/C++ code on platform '%s'" % plat
         if compiler is not None:
             msg = msg + " with '%s' compiler" % compiler
         raise DistutilsPlatformError(msg)
 
-    try:
-        cls = resolve_name(location)
-    except ImportError:
-        raise DistutilsModuleError(
-              "can't compile C/C++ code: unable to load '%s'" % \
-              location)
+    if isinstance(klass, str):
+        klass = resolve_name(klass)
+        _COMPILERS[compiler] = klass
+
 
     # XXX The None is necessary to preserve backwards compatibility
     # with classes that expect verbose to be the first positional
     # argument.
-    return cls(None, dry_run, force)
+    return klass(None, dry_run, force)
 
 
 def gen_preprocess_options(macros, include_dirs):
diff --git a/distutils2/config.py b/distutils2/config.py
--- a/distutils2/config.py
+++ b/distutils2/config.py
@@ -9,7 +9,7 @@
 from distutils2 import logger
 from distutils2.util import check_environ, resolve_name
 from distutils2.compiler import set_compiler
-
+from distutils2.command import set_command
 
 class Config(object):
     """Reads configuration files and work with the Distribution instance
@@ -94,20 +94,6 @@
                 self.setup_hook = resolve_name(setup_hook)
                 self.run_hook(content)
 
-            if 'commands' in content['global']:
-                commands = self._multiline(content['global']['commands'])
-                if isinstance(commands, str):
-                    commands = [commands]
-
-                for command in commands:
-                    command = command.split('=')
-                    if len(command) != 2:
-                        # Issue XXX a warning
-                        continue
-                    name, location = command[0].strip(), command[1].strip()
-                    self.dist.cmdclass[name] = resolve_name(location)
-
-
         metadata = self.dist.metadata
 
         # setting the metadata values
@@ -195,9 +181,12 @@
                 self._read_setup_cfg(parser)
 
             for section in parser.sections():
-                if section == 'compilers':
-                    self._load_compilers(parser.items(section))
-                    continue
+                if section == 'global':
+                    if parser.has_option('global', 'compilers'):
+                        self._load_compilers(parser.get('global', 'compilers'))
+
+                    if parser.has_option('global', 'commands'):
+                        self._load_commands(parser.get('global', 'commands'))
 
                 options = parser.options(section)
                 opt_dict = self.dist.get_option_dict(section)
@@ -247,5 +236,15 @@
                     raise DistutilsOptionError(msg)
 
     def _load_compilers(self, compilers):
-        for name, location in compilers:
-            set_compiler(name, location)
+        compilers = self._multiline(compilers)
+        if isinstance(compilers, str):
+            compilers = [compilers]
+        for compiler in compilers:
+            set_compiler(compiler.strip())
+
+    def _load_commands(self, commands):
+        commands = self._multiline(commands)
+        if isinstance(commands, str):
+            commands = [commands]
+        for command in commands:
+            set_command(command.strip())
diff --git a/distutils2/dist.py b/distutils2/dist.py
--- a/distutils2/dist.py
+++ b/distutils2/dist.py
@@ -18,6 +18,7 @@
 from distutils2 import logger
 from distutils2.metadata import DistributionMetadata
 from distutils2.config import Config
+from distutils2.command import get_command_class
 
 # Regex to define acceptable Distutils command names.  This is not *quite*
 # the same as a Python NAME -- I don't allow leading underscores.  The fact
@@ -153,14 +154,6 @@
         # for the setup script to override command classes
         self.cmdclass = {}
 
-        # 'command_packages' is a list of packages in which commands
-        # are searched for.  The factory for command 'foo' is expected
-        # to be named 'foo' in the module 'foo' in one of the packages
-        # named here.  This list is searched from the left; an error
-        # is raised if no named package provides the command being
-        # searched for.  (Always access using get_command_packages().)
-        self.command_packages = None
-
         # 'script_name' and 'script_args' are usually set to sys.argv[0]
         # and sys.argv[1:], but they can be overridden when the caller is
         # not necessarily a setup script run from the command line.
@@ -388,11 +381,6 @@
                             commands=self.commands)
             return
 
-        # Oops, no commands found -- an end-user error
-        if not self.commands:
-            raise DistutilsArgError("no commands supplied")
-
-        # All is well: return true
         return 1
 
     def _get_toplevel_options(self):
@@ -425,10 +413,12 @@
         # 1) know that it's a valid command, and 2) know which options
         # it takes.
         try:
-            cmd_class = self.get_command_class(command)
+            cmd_class = get_command_class(command)
         except DistutilsModuleError, msg:
             raise DistutilsArgError(msg)
 
+        # XXX We want to push this in distutils.command
+        #
         # Require that the command class be derived from Command -- want
         # to be sure that the basic "command" interface is implemented.
         for meth in ('initialize_options', 'finalize_options', 'run'):
@@ -545,7 +535,7 @@
             if isinstance(command, type) and issubclass(command, Command):
                 cls = command
             else:
-                cls = self.get_command_class(command)
+                cls = get_command_class(command)
             if (hasattr(cls, 'help_options') and
                 isinstance(cls.help_options, list)):
                 parser.set_option_table(cls.user_options +
@@ -606,7 +596,7 @@
         for cmd in commands:
             cls = self.cmdclass.get(cmd)
             if not cls:
-                cls = self.get_command_class(cmd)
+                cls = get_command_class(cmd)
             try:
                 description = cls.description
             except AttributeError:
@@ -648,94 +638,9 @@
                                     "Extra commands",
                                     max_length)
 
-    def get_command_list(self):
-        """Get a list of (command, description) tuples.
-
-        The list is divided into standard commands (listed in
-        distutils2.command.__all__) and extra commands (given in
-        self.cmdclass and not standard commands).  The descriptions come
-        from the command class attribute 'description'.
-        """
-        # Currently this is only used on Mac OS, for the Mac-only GUI
-        # Distutils interface (by Jack Jansen)
-
-        rv = []
-        for cls in self.get_command_classes():
-            try:
-                description = cls.description
-            except AttributeError:
-                description = "(no description available)"
-            rv.append((cls, description))
-        return rv
 
     # -- Command class/object methods ----------------------------------
 
-    def get_command_packages(self):
-        """Return a list of packages from which commands are loaded."""
-        pkgs = self.command_packages
-        if not isinstance(pkgs, list):
-            if pkgs is None:
-                pkgs = ''
-            pkgs = [pkg.strip() for pkg in pkgs.split(',') if pkg != '']
-            if "distutils2.command" not in pkgs:
-                pkgs.insert(0, "distutils2.command")
-            self.command_packages = pkgs
-        return pkgs
-
-    def get_command_names(self):
-        """Return a list of all command names."""
-        return [getattr(cls, 'command_name', cls.__name__)
-                for cls in self.get_command_classes()]
-
-    def get_command_classes(self):
-        """Return a list of all command classes."""
-        std_commands, extra_commands = self._get_command_groups()
-        classes = []
-        for cmd in (std_commands + extra_commands):
-            try:
-                cls = self.cmdclass[cmd]
-            except KeyError:
-                cls = self.get_command_class(cmd)
-            classes.append(cls)
-        return classes
-
-    def get_command_class(self, command):
-        """Return the class that implements the Distutils command named by
-        'command'.  First we check the 'cmdclass' dictionary; if the
-        command is mentioned there, we fetch the class object from the
-        dictionary and return it.  Otherwise we load the command module
-        ("distutils.command." + command) and fetch the command class from
-        the module.  The loaded class is also stored in 'cmdclass'
-        to speed future calls to 'get_command_class()'.
-
-        Raises DistutilsModuleError if the expected module could not be
-        found, or if that module does not define the expected class.
-        """
-        cls = self.cmdclass.get(command)
-        if cls:
-            return cls
-
-        for pkgname in self.get_command_packages():
-            module_name = "%s.%s" % (pkgname, command)
-            class_name = command
-
-            try:
-                __import__(module_name)
-                module = sys.modules[module_name]
-            except ImportError:
-                continue
-
-            try:
-                cls = getattr(module, class_name)
-            except AttributeError:
-                raise DistutilsModuleError(
-                      "invalid command '%s' (no class '%s' in module '%s')" %
-                      (command, class_name, module_name))
-
-            self.cmdclass[command] = cls
-            return cls
-
-        raise DistutilsModuleError("invalid command '%s'" % command)
 
     def get_command_obj(self, command, create=1):
         """Return the command object for 'command'.  Normally this object
@@ -748,7 +653,7 @@
             logger.debug("Distribution.get_command_obj(): " \
                          "creating '%s' command object" % command)
 
-            cls = self.get_command_class(command)
+            cls = get_command_class(command)
             cmd_obj = self.command_obj[command] = cls(self)
             self.have_run[command] = 0
 
diff --git a/distutils2/tests/test_command_test.py b/distutils2/tests/test_command_test.py
--- a/distutils2/tests/test_command_test.py
+++ b/distutils2/tests/test_command_test.py
@@ -9,10 +9,11 @@
 from operator import getitem, setitem, delitem
 from StringIO import StringIO
 
-from distutils2.command.cmd import Command
+from distutils2.command.build import build
 from distutils2.tests import unittest
 from distutils2.tests.support import TempdirManager, LoggingCatcher
 from distutils2.command.test import test
+from distutils2.command import set_command
 from distutils2.dist import Distribution
 from distutils2._backport import pkgutil
 
@@ -31,6 +32,17 @@
 
 here = os.path.dirname(os.path.abspath(__file__))
 
+_RECORD = []
+
+class MockBuildCmd(build):
+    build_lib = "mock build lib"
+    command_name = 'build'
+    plat_name = 'whatever'
+    def initialize_options(self): pass
+    def finalize_options(self): pass
+    def run(self): _RECORD.append("build run")
+
+
 class TestTest(TempdirManager,
                LoggingCatcher,
                unittest.TestCase):
@@ -111,20 +123,17 @@
         self.assertEqual(record, ["suite", "run"])
 
     def test_builds_before_running_tests(self):
-        dist = Distribution()
-        cmd = test(dist)
-        cmd.runner = self.prepare_named_function(lambda: None)
-        record = []
-        class MockBuildCmd(Command):
-            build_lib = "mock build lib"
-            def initialize_options(self): pass
-            def finalize_options(self): pass
-            def run(self): record.append("build run")
-        dist.cmdclass['build'] = MockBuildCmd
-
-        cmd.ensure_finalized()
-        cmd.run()
-        self.assertEqual(record, ['build run'])
+        set_command('distutils2.tests.test_command_test.MockBuildCmd')
+        try:
+            dist = Distribution()
+            cmd = test(dist)
+            cmd.runner = self.prepare_named_function(lambda: None)
+            _RECORD[:] = []
+            cmd.ensure_finalized()
+            cmd.run()
+            self.assertEqual(_RECORD, ['build run'])
+        finally:
+            set_command('distutils2.command.build.build')
 
     def _test_works_with_2to3(self):
         pass
@@ -157,7 +166,6 @@
     def test_custom_runner(self):
         dist = Distribution()
         cmd = test(dist)
-
         record = []
         cmd.runner = self.prepare_named_function(lambda: record.append("runner called"))
         cmd.ensure_finalized()
@@ -189,12 +197,12 @@
     def test_calls_discover(self):
         self.safely_replace(ut1.TestLoader, "discover", delete=True)
         mock_ut2 = self.prepare_mock_ut2()
-        record = []
-        mock_ut2.TestLoader.discover = lambda self, path: record.append(path)
+        _RECORD[:] = []
+        mock_ut2.TestLoader.discover = lambda self, path: _RECORD.append(path)
         dist = Distribution()
         cmd = test(dist)
         cmd.run()
-        self.assertEqual(record, [os.curdir])
+        self.assertEqual(_RECORD, [os.curdir])
 
 def test_suite():
     return unittest.makeSuite(TestTest)
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
@@ -74,15 +74,17 @@
 
 [global]
 commands =
-    foo = distutils2.tests.test_config.Foo
+    distutils2.tests.test_config.Foo
+
+compilers =
+    distutils2.tests.test_config.DCompiler
 
 setup_hook = distutils2.tests.test_config.hook
 
+
+
 [install_dist]
 sub_commands = foo
-
-[compilers]
-d = distutils2.tests.test_config.DCompiler
 """
 
 
@@ -102,12 +104,19 @@
     def __init__(self, dist):
         self.distribution = dist
 
+    @classmethod
+    def get_command_name(self):
+        return 'foo'
+
     def run(self):
         self.distribution.foo_was_here = 1
 
     def nothing(self):
         pass
 
+    def get_source_files(self):
+        return []
+
     ensure_finalized = finalize_options = initialize_options = nothing
 
 
@@ -184,7 +193,7 @@
         self.assertEqual(dist.package_dir['two'], 'src')
 
         # make sure we get the foo command loaded !
-        self.assertEquals(dist.cmdclass['foo'], Foo)
+        self.assertTrue(isinstance(dist.get_command_obj('foo'), Foo))
 
         # did the README got loaded ?
         self.assertEquals(dist.metadata['description'], 'yeah')
diff --git a/distutils2/tests/test_dist.py b/distutils2/tests/test_dist.py
--- a/distutils2/tests/test_dist.py
+++ b/distutils2/tests/test_dist.py
@@ -8,6 +8,7 @@
 
 import distutils2.dist
 from distutils2.dist import Distribution, fix_help_options
+from distutils2.command import set_command
 from distutils2.command.cmd import Command
 from distutils2.errors import DistutilsModuleError, DistutilsOptionError
 from distutils2.tests import TESTFN, captured_stdout
@@ -66,52 +67,6 @@
         finally:
             distutils2.dist.DEBUG = False
 
-    def test_command_packages_unspecified(self):
-        sys.argv.append("build")
-        d = create_distribution()
-        self.assertEqual(d.get_command_packages(), ["distutils2.command"])
-
-    def test_command_packages_cmdline(self):
-        from distutils2.tests.test_dist import test_dist
-        sys.argv.extend(["--command-packages",
-                         "foo.bar,distutils2.tests",
-                         "test_dist",
-                         "-Ssometext",
-                         ])
-        d = create_distribution()
-        # let's actually try to load our test command:
-        self.assertEqual(d.get_command_packages(),
-                         ["distutils2.command", "foo.bar", "distutils2.tests"])
-        cmd = d.get_command_obj("test_dist")
-        self.assertTrue(isinstance(cmd, test_dist))
-        self.assertEqual(cmd.sample_option, "sometext")
-
-    def test_command_packages_configfile(self):
-        sys.argv.append("build")
-        f = open(TESTFN, "w")
-        try:
-            print >> f, "[global]"
-            print >> f, "command_packages = foo.bar, splat"
-            f.close()
-            d = create_distribution([TESTFN])
-            self.assertEqual(d.get_command_packages(),
-                             ["distutils2.command", "foo.bar", "splat"])
-
-            # ensure command line overrides config:
-            sys.argv[1:] = ["--command-packages", "spork", "build"]
-            d = create_distribution([TESTFN])
-            self.assertEqual(d.get_command_packages(),
-                             ["distutils2.command", "spork"])
-
-            # Setting --command-packages to '' should cause the default to
-            # be used even if a config file specified something else:
-            sys.argv[1:] = ["--command-packages", "", "build"]
-            d = create_distribution([TESTFN])
-            self.assertEqual(d.get_command_packages(), ["distutils2.command"])
-
-        finally:
-            os.unlink(TESTFN)
-
     def test_write_pkg_file(self):
         # Check DistributionMetadata handling of Unicode fields
         tmp_dir = self.mkdtemp()
@@ -188,18 +143,6 @@
         self.assertEqual(dist.metadata['platform'], ['one', 'two'])
         self.assertEqual(dist.metadata['keywords'], ['one', 'two'])
 
-    def test_get_command_packages(self):
-        dist = Distribution()
-        self.assertEqual(dist.command_packages, None)
-        cmds = dist.get_command_packages()
-        self.assertEqual(cmds, ['distutils2.command'])
-        self.assertEqual(dist.command_packages,
-                         ['distutils2.command'])
-
-        dist.command_packages = 'one,two'
-        cmds = dist.get_command_packages()
-        self.assertEqual(cmds, ['distutils2.command', 'one', 'two'])
-
     def test_announce(self):
         # make sure the level is known
         dist = Distribution()
@@ -250,12 +193,9 @@
         self.write_file((temp_home, "config2.cfg"),
                         '[test_dist]\npre-hook.b = type')
 
-        sys.argv.extend(["--command-packages",
-                         "distutils2.tests",
-                         "test_dist"])
+        set_command('distutils2.tests.test_dist.test_dist')
         dist = create_distribution(config_files)
         cmd = dist.get_command_obj("test_dist")
-
         self.assertEqual(cmd.pre_hook, {"a": 'type', "b": 'type'})
 
     def test_hooks_get_run(self):
@@ -278,10 +218,7 @@
             record.append('post-%s' % cmd.get_command_name())
         '''))
 
-        sys.argv.extend(["--command-packages",
-                         "distutils2.tests",
-                         "test_dist"])
-
+        set_command('distutils2.tests.test_dist.test_dist')
         d = create_distribution([config_file])
         cmd = d.get_command_obj("test_dist")
 
@@ -329,10 +266,7 @@
             [test_dist]
             pre-hook.test = distutils2.tests.test_dist.__doc__'''))
 
-        sys.argv.extend(["--command-packages",
-                         "distutils2.tests",
-                         "test_dist"])
-
+        set_command('distutils2.tests.test_dist.test_dist')
         d = create_distribution([config_file])
         cmd = d.get_command_obj("test_dist")
         cmd.ensure_finalized()

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


More information about the Python-checkins mailing list