[Python-checkins] distutils2: branch merge

tarek.ziade python-checkins at python.org
Sun Dec 26 14:21:44 CET 2010


tarek.ziade pushed 7eee7b1bb382 to distutils2:

http://hg.python.org/distutils2/rev/7eee7b1bb382
changeset:   838:7eee7b1bb382
parent:      837:001d7d14b6c7
parent:      836:a8eaafda8fd3
user:        Alexis Metaireau <ametaireau at gmail.com>
date:        Sun Nov 21 01:07:26 2010 +0000
summary:
  branch merge

files:
  distutils2/extension.py

diff --git a/CHANGES.txt b/CHANGES.txt
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -6,6 +6,8 @@
 ---------
 
 - The setup runner supports more options:
+- XXX fill changes done in commands + compilers
+- Issue 10409: Fixed the Licence selector in mkcfg
 
 1.0a3 - 2010-10-08
 ------------------
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,52 @@
 
 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():
+    return sorted(_COMMANDS.keys())
+    """Return registered commands"""
+
+
+def set_command(location):
+    cls = resolve_name(location)
+    # XXX we want to do the duck-type checking here
+    _COMMANDS[cls.get_command_name()] = cls
+
+
+def get_command_class(name):
+    """Return the registered command"""
+    try:
+        cls = _COMMANDS[name]
+        if isinstance(cls, str):
+            cls = resolve_name(cls)
+            _COMMANDS[name] = cls
+        return cls
+    except KeyError:
+        raise DistutilsModuleError("Invalid command %s" % name)
diff --git a/distutils2/command/build.py b/distutils2/command/build.py
--- a/distutils2/command/build.py
+++ b/distutils2/command/build.py
@@ -127,27 +127,27 @@
         # Run all relevant sub-commands.  This will be some subset of:
         #  - build_py      - pure Python modules
         #  - build_clib    - standalone C libraries
-        #  - build_ext     - Python extensions
-        #  - build_scripts - (Python) scripts
+        #  - build_ext     - Python extension modules
+        #  - build_scripts - Python scripts
         for cmd_name in self.get_sub_commands():
             self.run_command(cmd_name)
 
     # -- Predicates for the sub-command list ---------------------------
 
-    def has_pure_modules (self):
+    def has_pure_modules(self):
         return self.distribution.has_pure_modules()
 
-    def has_c_libraries (self):
+    def has_c_libraries(self):
         return self.distribution.has_c_libraries()
 
-    def has_ext_modules (self):
+    def has_ext_modules(self):
         return self.distribution.has_ext_modules()
 
-    def has_scripts (self):
+    def has_scripts(self):
         return self.distribution.has_scripts()
 
-    sub_commands = [('build_py',      has_pure_modules),
-                    ('build_clib',    has_c_libraries),
-                    ('build_ext',     has_ext_modules),
+    sub_commands = [('build_py', has_pure_modules),
+                    ('build_clib', has_c_libraries),
+                    ('build_ext', has_ext_modules),
                     ('build_scripts', has_scripts),
                    ]
diff --git a/distutils2/command/build_ext.py b/distutils2/command/build_ext.py
--- a/distutils2/command/build_ext.py
+++ b/distutils2/command/build_ext.py
@@ -14,12 +14,9 @@
                                DistutilsPlatformError, DistutilsSetupError)
 from distutils2.compiler import customize_compiler, show_compilers
 from distutils2.util import newer_group
-from distutils2.extension import Extension
+from distutils2.compiler.extension import Extension
 from distutils2 import logger
-try:
-    import sysconfig
-except ImportError:
-    from distutils2._backport import sysconfig
+from distutils2._backport import sysconfig
 
 # this keeps compatibility from 2.3 to 2.5
 if sys.version < "2.6":
diff --git a/distutils2/command/build_scripts.py b/distutils2/command/build_scripts.py
--- a/distutils2/command/build_scripts.py
+++ b/distutils2/command/build_scripts.py
@@ -9,10 +9,7 @@
 from distutils2.command.cmd import Command
 from distutils2.util import convert_path, newer
 from distutils2 import logger
-try:
-    import sysconfig
-except ImportError:
-    from distutils2._backport import sysconfig
+from distutils2._backport import sysconfig
 from distutils2.compat import Mixin2to3
 
 # check if Python is called on the first line with this expression
diff --git a/distutils2/command/cmd.py b/distutils2/command/cmd.py
--- a/distutils2/command/cmd.py
+++ b/distutils2/command/cmd.py
@@ -19,6 +19,7 @@
 except ImportError:
     from distutils2._backport.shutil import make_archive
 
+
 class Command(object):
     """Abstract base class for defining command classes, the "worker bees"
     of the Distutils.  A useful analogy for command classes is to think of
@@ -57,7 +58,6 @@
     pre_hook = None
     post_hook = None
 
-
     # -- Creation/initialization methods -------------------------------
 
     def __init__(self, dist):
@@ -69,9 +69,9 @@
         from distutils2.dist import Distribution
 
         if not isinstance(dist, Distribution):
-            raise TypeError, "dist must be a Distribution instance"
+            raise TypeError("dist must be a Distribution instance")
         if self.__class__ is Command:
-            raise RuntimeError, "Command is an abstract class"
+            raise RuntimeError("Command is an abstract class")
 
         self.distribution = dist
         self.initialize_options()
@@ -143,8 +143,8 @@
 
         This method must be implemented by all command classes.
         """
-        raise RuntimeError, \
-              "abstract method -- subclass %s must override" % self.__class__
+        raise RuntimeError(
+            "abstract method -- subclass %s must override" % self.__class__)
 
     def finalize_options(self):
         """Set final values for all the options that this command supports.
@@ -157,9 +157,8 @@
 
         This method must be implemented by all command classes.
         """
-        raise RuntimeError, \
-              "abstract method -- subclass %s must override" % self.__class__
-
+        raise RuntimeError(
+            "abstract method -- subclass %s must override" % self.__class__)
 
     def dump_options(self, header=None, indent=""):
         if header is None:
@@ -184,8 +183,8 @@
 
         This method must be implemented by all command classes.
         """
-        raise RuntimeError, \
-              "abstract method -- subclass %s must override" % self.__class__
+        raise RuntimeError(
+            "abstract method -- subclass %s must override" % self.__class__)
 
     def announce(self, msg, level=logging.INFO):
         """If the current verbosity level is of greater than or equal to
@@ -237,8 +236,8 @@
             setattr(self, option, default)
             return default
         elif not isinstance(val, str):
-            raise DistutilsOptionError, \
-                  "'%s' must be a %s (got `%s`)" % (option, what, val)
+            raise DistutilsOptionError("'%s' must be a %s (got `%s`)" %
+                                       (option, what, val))
         return val
 
     def ensure_string(self, option, default=None):
@@ -248,7 +247,7 @@
         self._ensure_stringlike(option, "string", default)
 
     def ensure_string_list(self, option):
-        """Ensure that 'option' is a list of strings.  If 'option' is
+        r"""Ensure that 'option' is a list of strings.  If 'option' is
         currently a string, we split it either on /,\s*/ or /\s+/, so
         "foo bar baz", "foo,bar,baz", and "foo,   bar baz" all become
         ["foo", "bar", "baz"].
@@ -270,17 +269,15 @@
                 ok = 0
 
             if not ok:
-                raise DistutilsOptionError, \
-                    "'%s' must be a list of strings (got %r)" % \
-                        (option, val)
-
+                raise DistutilsOptionError(
+                    "'%s' must be a list of strings (got %r)" % (option, val))
 
     def _ensure_tested_string(self, option, tester,
                               what, error_fmt, default=None):
         val = self._ensure_stringlike(option, what, default)
         if val is not None and not tester(val):
-            raise DistutilsOptionError, \
-                  ("error in '%s' option: " + error_fmt) % (option, val)
+            raise DistutilsOptionError(
+                ("error in '%s' option: " + error_fmt) % (option, val))
 
     def ensure_filename(self, option):
         """Ensure that 'option' is the name of an existing file."""
@@ -293,14 +290,14 @@
                                    "directory name",
                                    "'%s' does not exist or is not a directory")
 
-
     # -- 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.
@@ -368,12 +365,10 @@
                 commands.append(sub_command)
         return commands
 
-
     # -- External world manipulation -----------------------------------
 
     def warn(self, msg):
-        logger.warning("warning: %s: %s\n" %
-                (self.get_command_name(), msg))
+        logger.warning("warning: %s: %s\n" % (self.get_command_name(), msg))
 
     def execute(self, func, args, msg=None, level=1):
         util.execute(func, args, msg, dry_run=self.dry_run)
@@ -412,19 +407,19 @@
         and force flags.
         """
         if self.dry_run:
-            return # see if we want to display something
+            return  # see if we want to display something
         return copytree(infile, outfile, preserve_symlinks)
 
     def move_file(self, src, dst, level=1):
         """Move a file respectin dry-run flag."""
         if self.dry_run:
-            return # XXX log ?
+            return  # XXX log ?
         return move(src, dst)
 
     def spawn(self, cmd, search_path=1, level=1):
         """Spawn an external command respecting dry-run flag."""
         from distutils2.util import spawn
-        spawn(cmd, search_path, dry_run= self.dry_run)
+        spawn(cmd, search_path, dry_run=self.dry_run)
 
     def make_archive(self, base_name, format, root_dir=None, base_dir=None,
                      owner=None, group=None):
@@ -449,12 +444,11 @@
         if isinstance(infiles, str):
             infiles = (infiles,)
         elif not isinstance(infiles, (list, tuple)):
-            raise TypeError, \
-                  "'infiles' must be a string, or a list or tuple of strings"
+            raise TypeError(
+                "'infiles' must be a string, or a list or tuple of strings")
 
         if exec_msg is None:
-            exec_msg = "generating %s from %s" % \
-                       (outfile, ', '.join(infiles))
+            exec_msg = "generating %s from %s" % (outfile, ', '.join(infiles))
 
         # If 'outfile' must be regenerated (either because it doesn't
         # exist, is out-of-date, or the 'force' flag is true) then
diff --git a/distutils2/command/install_dist.py b/distutils2/command/install_dist.py
--- a/distutils2/command/install_dist.py
+++ b/distutils2/command/install_dist.py
@@ -520,7 +520,6 @@
                 raise DistutilsPlatformError("Can't install when "
                                              "cross-compiling")
 
-
         # Run all sub-commands (at least those that need to be run)
         for cmd_name in self.get_sub_commands():
             self.run_command(cmd_name)
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
@@ -4,7 +4,7 @@
 
 from distutils2._backport import sysconfig
 from distutils2.util import resolve_name
-from distutils2.errors import DistutilsModuleError, DistutilsPlatformError
+from distutils2.errors import DistutilsPlatformError
 
 
 def customize_compiler(compiler):
@@ -13,7 +13,7 @@
     Mainly needed on Unix, so we can plug in the information that
     varies across Unices and is stored in Python's Makefile.
     """
-    if compiler.compiler_type == "unix":
+    if compiler.name == "unix":
         (cc, cxx, opt, cflags, ccshared, ldshared, so_ext, ar, ar_flags) = \
             sysconfig.get_config_vars('CC', 'CXX', 'OPT', 'CFLAGS',
                                        'CCSHARED', 'LDSHARED', 'SO', 'AR',
@@ -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
+    cls = resolve_name(location)
+    # XXX we want to check the class here
+    _COMPILERS[cls.name] = cls
 
 
 def show_compilers():
@@ -124,9 +127,12 @@
     from distutils2.fancy_getopt import FancyGetopt
     compilers = []
 
-    for compiler, location in _COMPILERS.items():
-        klass = resolve_name(location)
-        compilers.append(("compiler=" + compiler, None, klass.description))
+    for name, cls in _COMPILERS.items():
+        if isinstance(cls, str):
+            cls = resolve_name(cls)
+            _COMPILERS[name] = cls
+
+        compilers.append(("compiler=" + name, None, cls.description))
 
     compilers.sort()
     pretty_printer = FancyGetopt(compilers)
@@ -151,19 +157,17 @@
         if compiler is None:
             compiler = get_default_compiler(plat)
 
-        location = _COMPILERS[compiler]
+        cls = _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(cls, str):
+        cls = resolve_name(cls)
+        _COMPILERS[compiler] = cls
+
 
     # XXX The None is necessary to preserve backwards compatibility
     # with classes that expect verbose to be the first positional
diff --git a/distutils2/compiler/bcppcompiler.py b/distutils2/compiler/bcppcompiler.py
--- a/distutils2/compiler/bcppcompiler.py
+++ b/distutils2/compiler/bcppcompiler.py
@@ -26,7 +26,7 @@
     compiler, as defined by the CCompiler abstract class.
     """
 
-    compiler_type = 'bcpp'
+    name = 'bcpp'
     description = 'Borland C++ Compiler'
 
     # Just set this so CCompiler's constructor doesn't barf.  We currently
diff --git a/distutils2/compiler/ccompiler.py b/distutils2/compiler/ccompiler.py
--- a/distutils2/compiler/ccompiler.py
+++ b/distutils2/compiler/ccompiler.py
@@ -15,6 +15,7 @@
 from distutils2 import logger
 from distutils2.compiler import gen_preprocess_options
 
+
 class CCompiler(object):
     """Abstract base class to define the interface that must be implemented
     by real compiler classes.  Also has some utility methods used by
@@ -29,11 +30,11 @@
     attributes may be varied on a per-compilation or per-link basis.
     """
 
-    # 'compiler_type' is a class attribute that identifies this class.  It
+    # 'name' is a class attribute that identifies this class.  It
     # keeps code that wants to know what kind of compiler it's dealing with
     # from having to import all possible compiler classes just to do an
     # 'isinstance'.
-    compiler_type = None
+    name = None
     description = None
 
     # XXX things not handled by this compiler abstraction model:
diff --git a/distutils2/compiler/cygwinccompiler.py b/distutils2/compiler/cygwinccompiler.py
--- a/distutils2/compiler/cygwinccompiler.py
+++ b/distutils2/compiler/cygwinccompiler.py
@@ -85,7 +85,7 @@
 class CygwinCCompiler(UnixCCompiler):
     """ Handles the Cygwin port of the GNU C compiler to Windows.
     """
-    compiler_type = 'cygwin'
+    name = 'cygwin'
     description = 'Cygwin port of GNU C Compiler for Win32'
     obj_extension = ".o"
     static_lib_extension = ".a"
@@ -110,7 +110,7 @@
 
         self.gcc_version, self.ld_version, self.dllwrap_version = \
             get_compiler_versions()
-        self.debug_print(self.compiler_type + ": gcc %s, ld %s, dllwrap %s\n" %
+        self.debug_print(self.name + ": gcc %s, ld %s, dllwrap %s\n" %
                          (self.gcc_version,
                           self.ld_version,
                           self.dllwrap_version) )
@@ -272,7 +272,8 @@
 class Mingw32CCompiler(CygwinCCompiler):
     """ Handles the Mingw32 port of the GNU C compiler to Windows.
     """
-    compiler_type = 'mingw32'
+    name = 'mingw32'
+    description = 'MinGW32 compiler'
 
     def __init__(self, verbose=0, dry_run=0, force=0):
 
diff --git a/distutils2/extension.py b/distutils2/compiler/extension.py
rename from distutils2/extension.py
rename to distutils2/compiler/extension.py
diff --git a/distutils2/compiler/msvc9compiler.py b/distutils2/compiler/msvc9compiler.py
--- a/distutils2/compiler/msvc9compiler.py
+++ b/distutils2/compiler/msvc9compiler.py
@@ -285,7 +285,7 @@
     """Concrete class that implements an interface to Microsoft Visual C++,
        as defined by the CCompiler abstract class."""
 
-    compiler_type = 'msvc'
+    name = 'msvc'
     description = 'Microsoft Visual C++'
 
     # Just set this so CCompiler's constructor doesn't barf.  We currently
diff --git a/distutils2/compiler/msvccompiler.py b/distutils2/compiler/msvccompiler.py
--- a/distutils2/compiler/msvccompiler.py
+++ b/distutils2/compiler/msvccompiler.py
@@ -205,7 +205,7 @@
     """Concrete class that implements an interface to Microsoft Visual C++,
        as defined by the CCompiler abstract class."""
 
-    compiler_type = 'msvc'
+    name = 'msvc'
     description = "Microsoft Visual C++"
 
     # Just set this so CCompiler's constructor doesn't barf.  We currently
diff --git a/distutils2/compiler/unixccompiler.py b/distutils2/compiler/unixccompiler.py
--- a/distutils2/compiler/unixccompiler.py
+++ b/distutils2/compiler/unixccompiler.py
@@ -106,7 +106,7 @@
 
 class UnixCCompiler(CCompiler):
 
-    compiler_type = 'unix'
+    name = 'unix'
     description = 'Standard UNIX-style compiler'
 
     # These are used by CCompiler in two places: the constructor sets
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):
@@ -401,10 +389,7 @@
         This includes options that are recognized *only* at the top
         level as well as options recognized for commands.
         """
-        return self.global_options + [
-            ("command-packages=", None,
-             "list of packages that provide distutils commands"),
-            ]
+        return self.global_options
 
     def _parse_command_opts(self, parser, args):
         """Parse the command-line options for a single command.
@@ -425,17 +410,19 @@
         # 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'):
             if hasattr(cmd_class, meth):
                 continue
             raise DistutilsClassError(
-                  'command "%s" must implement "%s"' % (cmd_class. meth))
+                  'command "%s" must implement "%s"' % (cmd_class, meth))
 
         # Also make sure that the command object provides a list of its
         # known options.
@@ -545,7 +532,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 +593,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 +635,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 +650,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/mkcfg.py b/distutils2/mkcfg.py
--- a/distutils2/mkcfg.py
+++ b/distutils2/mkcfg.py
@@ -148,6 +148,16 @@
 
 CLASSIFIERS = _build_classifiers_dict(_CLASSIFIERS_LIST)
 
+def _build_licences(classifiers):
+    res = []
+    for index, item in enumerate(classifiers):
+        if not item.startswith('License :: '):
+            continue
+        res.append((index, item.split(' :: ')[-1].lower()))
+    return res
+
+LICENCES = _build_licences(_CLASSIFIERS_LIST)
+
 
 class MainProgram(object):
     def __init__(self):
@@ -378,45 +388,44 @@
             if not license:
                 return
 
-            licenseWords = license.lower().split(' ')
+            license_words = license.lower().split(' ')
+            found_list = []
 
-            foundList = []
-            # TODO use enumerate
-            for index in range(len(_CLASSIFIERS_LIST)):
-                troveItem = _CLASSIFIERS_LIST[index]
-                if not troveItem.startswith('License :: '):
-                    continue
-                troveItem = troveItem[11:].lower()
+            for index, licence in LICENCES:
+                for word in license_words:
+                    if word in licence:
+                        found_list.append(index)
+                        break
 
-                allMatch = True
-                for word in licenseWords:
-                    if not word in troveItem:
-                        allMatch = False
-                        break
-                if allMatch:
-                    foundList.append(index)
+            if len(found_list) == 0:
+                print('ERROR: Could not find a matching license for "%s"' % \
+                      license)
+                continue
 
             question = 'Matching licenses:\n\n'
-            # TODO use enumerate?
-            for i in xrange(1, len(foundList) + 1):
-                question += '   %s) %s\n' % (i, _CLASSIFIERS_LIST[foundList[i - 1]])
+
+            for index, list_index in enumerate(found_list):
+                question += '   %s) %s\n' % (index + 1,
+                                             _CLASSIFIERS_LIST[list_index])
+
             question += ('\nType the number of the license you wish to use or '
                          '? to try again:')
-            troveLicense = ask(question, required=False)
+            choice = ask(question, required=False)
 
-            if troveLicense == '?':
+            if choice == '?':
                 continue
-            if troveLicense == '':
+            if choice == '':
                 return
-            # FIXME the int conversion can fail
-            foundIndex = foundList[int(troveLicense) - 1]
-            classifiers[_CLASSIFIERS_LIST[foundIndex]] = 1
+
             try:
-                return
-            except IndexError:
+                index = found_list[int(choice) - 1]
+            except ValueError:
                 print ("ERROR: Invalid selection, type a number from the list "
                        "above.")
 
+            classifiers[_CLASSIFIERS_LIST[index]] = 1
+            return
+
     def set_devel_status(self, classifiers):
         while True:
             choice = ask(dedent('''\
diff --git a/distutils2/run.py b/distutils2/run.py
--- a/distutils2/run.py
+++ b/distutils2/run.py
@@ -1,10 +1,12 @@
 import os
 import sys
+from optparse import OptionParser
 
 from distutils2.util import grok_environment_error
 from distutils2.errors import (DistutilsSetupError, DistutilsArgError,
                                DistutilsError, CCompilerError)
 from distutils2.dist import Distribution
+from distutils2 import __version__
 
 # This is a barebones help message generated displayed when the user
 # runs the setup script with no arguments at all.  More useful help
@@ -23,7 +25,7 @@
     return USAGE % {'script': script}
 
 
-def main(**attrs):
+def commands_main(**attrs):
     """The gateway to the Distutils: do everything your setup script needs
     to do, in a highly flexible and user-driven way.  Briefly: create a
     Distribution instance; find and parse config files; parse the command
@@ -110,5 +112,24 @@
     return dist
 
 
+def main():
+    """Main entry point for Distutils2"""
+    parser = OptionParser()
+    parser.disable_interspersed_args()
+    parser.add_option("-v", "--version",
+                  action="store_true", dest="version", default=False,
+                  help="Prints out the version of Distutils2 and exits.")
+
+    options, args = parser.parse_args()
+    if options.version:
+        print('Distutils2 %s' % __version__)
+        sys.exit(0)
+
+    if len(args) == 0:
+        parser.print_help()
+
+    commands_main()
+    sys.exit(0)
+
 if __name__ == '__main__':
     main()
diff --git a/distutils2/tests/test_command_build.py b/distutils2/tests/test_command_build.py
--- a/distutils2/tests/test_command_build.py
+++ b/distutils2/tests/test_command_build.py
@@ -4,10 +4,7 @@
 
 from distutils2.command.build import build
 from distutils2.tests import unittest, support
-try:
-    from sysconfig import get_platform
-except ImportError:
-    from distutils2._backport.sysconfig import get_platform
+from distutils2._backport.sysconfig import get_platform
 
 class BuildTestCase(support.TempdirManager,
                     support.LoggingCatcher,
diff --git a/distutils2/tests/test_command_build_ext.py b/distutils2/tests/test_command_build_ext.py
--- a/distutils2/tests/test_command_build_ext.py
+++ b/distutils2/tests/test_command_build_ext.py
@@ -5,17 +5,13 @@
 
 import distutils2.tests
 from distutils2.tests import unittest
-from distutils2.extension import Extension
+from distutils2.compiler.extension import Extension
 from distutils2.dist import Distribution
 from distutils2.command.build_ext import build_ext
 from distutils2.tests import support
-from distutils2.extension import Extension
 from distutils2.errors import (UnknownFileError, DistutilsSetupError,
                                CompileError)
-try:
-    import sysconfig
-except ImportError:
-    from distutils2._backport import sysconfig
+from distutils2._backport import sysconfig
 
 
 # http://bugs.python.org/issue4373
@@ -107,10 +103,7 @@
         old = sys.platform
 
         sys.platform = 'sunos' # fooling finalize_options
-        try:
-            from sysconfig import _CONFIG_VARS
-        except ImportError:
-            from distutils2._backport.sysconfig import _CONFIG_VARS
+        from distutils2._backport.sysconfig import _CONFIG_VARS
 
         old_var = _CONFIG_VARS.get('Py_ENABLE_SHARED')
         _CONFIG_VARS['Py_ENABLE_SHARED'] = 1
diff --git a/distutils2/tests/test_command_build_scripts.py b/distutils2/tests/test_command_build_scripts.py
--- a/distutils2/tests/test_command_build_scripts.py
+++ b/distutils2/tests/test_command_build_scripts.py
@@ -4,10 +4,7 @@
 
 from distutils2.command.build_scripts import build_scripts
 from distutils2.dist import Distribution
-try:
-    import sysconfig
-except ImportError:
-    from distutils2._backport import sysconfig
+from distutils2._backport import sysconfig
 
 from distutils2.tests import unittest, support
 
diff --git a/distutils2/tests/test_command_install_dist.py b/distutils2/tests/test_command_install_dist.py
--- a/distutils2/tests/test_command_install_dist.py
+++ b/distutils2/tests/test_command_install_dist.py
@@ -19,6 +19,7 @@
 
 from distutils2.tests import unittest, support
 
+
 class InstallTestCase(support.TempdirManager,
                       support.EnvironGuard,
                       support.LoggingCatcher,
@@ -83,10 +84,12 @@
         _CONFIG_VARS['userbase'] = self.user_base
         scheme = '%s_user' % os.name
         _SCHEMES.set(scheme, 'purelib', self.user_site)
+
         def _expanduser(path):
             if path[0] == '~':
                 path = os.path.normpath(self.tmpdir) + path[1:]
             return path
+
         self.old_expand = os.path.expanduser
         os.path.expanduser = _expanduser
 
@@ -212,6 +215,7 @@
             install_module.DEBUG = False
         self.assertTrue(len(self.logs) > old_logs_len)
 
+
 def test_suite():
     return unittest.makeSuite(InstallTestCase)
 
diff --git a/distutils2/tests/test_command_install_lib.py b/distutils2/tests/test_command_install_lib.py
--- a/distutils2/tests/test_command_install_lib.py
+++ b/distutils2/tests/test_command_install_lib.py
@@ -3,7 +3,7 @@
 import os
 
 from distutils2.command.install_lib import install_lib
-from distutils2.extension import Extension
+from distutils2.compiler.extension import Extension
 from distutils2.tests import unittest, support
 from distutils2.errors import DistutilsOptionError
 
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_compiler.py b/distutils2/tests/test_compiler.py
--- a/distutils2/tests/test_compiler.py
+++ b/distutils2/tests/test_compiler.py
@@ -7,6 +7,10 @@
 
 
 class FakeCompiler(object):
+
+    name = 'fake'
+    description = 'Fake'
+
     def library_dir_option(self, dir):
         return "-L" + dir
 
@@ -33,7 +37,7 @@
 
         # make sure AR gets caught
         class compiler:
-            compiler_type = 'unix'
+            name = 'unix'
 
             def set_executables(self, **kw):
                 self.exes = kw
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,20 +74,22 @@
 
 [global]
 commands =
-    foo = distutils2.tests.test_config.Foo
+    distutils2.tests.test_config.FooBarBazTest
+
+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
 """
 
 
 class DCompiler(object):
-    compiler_type = 'd'
+    name = 'd'
     description = 'D Compiler'
 
     def __init__(self, *args):
@@ -97,21 +99,29 @@
     content['metadata']['version'] += '.dev1'
 
 
-class Foo(object):
+class FooBarBazTest(object):
 
     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
 
 
 class ConfigTestCase(support.TempdirManager,
+                     support.LoggingCatcher,
                      unittest.TestCase):
 
     def setUp(self):
@@ -183,8 +193,15 @@
              ('/etc/init.d ', ['init-script'])])
         self.assertEqual(dist.package_dir['two'], 'src')
 
-        # make sure we get the foo command loaded !
-        self.assertEquals(dist.cmdclass['foo'], Foo)
+        # Make sure we get the foo command loaded.  We use a string comparison
+        # instead of assertIsInstance because the class is not the same when
+        # this test is run directly: foo is distutils2.tests.test_config.Foo
+        # because get_command_class uses the full name, but a bare "Foo" in
+        # this file would be __main__.Foo when run as "python test_config.py".
+        # The name FooBarBazTest should be unique enough to prevent
+        # collisions.
+        self.assertEqual(dist.get_command_obj('foo').__class__.__name__,
+                         'FooBarBazTest')
 
         # did the README got loaded ?
         self.assertEquals(dist.metadata['description'], 'yeah')
diff --git a/distutils2/tests/test_cygwinccompiler.py b/distutils2/tests/test_cygwinccompiler.py
--- a/distutils2/tests/test_cygwinccompiler.py
+++ b/distutils2/tests/test_cygwinccompiler.py
@@ -1,10 +1,8 @@
 """Tests for distutils.cygwinccompiler."""
 import sys
 import os
-try:
-    import sysconfig
-except ImportError:
-    from distutils2._backport import sysconfig
+
+from distutils2._backport import sysconfig
 
 from distutils2.tests import run_unittest
 from distutils2.tests import captured_stdout
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")
 
@@ -311,10 +248,7 @@
             [test_dist]
             pre-hook.test = nonexistent.dotted.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")
         cmd.ensure_finalized()
@@ -329,10 +263,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()
diff --git a/distutils2/tests/test_extension.py b/distutils2/tests/test_extension.py
--- a/distutils2/tests/test_extension.py
+++ b/distutils2/tests/test_extension.py
@@ -1,7 +1,7 @@
 """Tests for distutils.extension."""
 import os
 
-from distutils2.extension import Extension
+from distutils2.compiler.extension import Extension
 from distutils2.tests import unittest
 
 class ExtensionTestCase(unittest.TestCase):
diff --git a/distutils2/tests/test_index_simple.py b/distutils2/tests/test_index_simple.py
--- a/distutils2/tests/test_index_simple.py
+++ b/distutils2/tests/test_index_simple.py
@@ -250,7 +250,7 @@
 
         # Test that the simple link matcher yield the good links.
         generator = crawler._simple_link_matcher(content, crawler.index_url)
-        self.assertEqual(('%stest/foobar-1.tar.gz#md5=abcdef' % crawler.index_url, 
+        self.assertEqual(('%stest/foobar-1.tar.gz#md5=abcdef' % crawler.index_url,
                          True), generator.next())
         self.assertEqual(('http://dl-link1', True), generator.next())
         self.assertEqual(('%stest' % crawler.index_url, False),
@@ -260,7 +260,7 @@
         # Follow the external links is possible (eg. homepages)
         crawler.follow_externals = True
         generator = crawler._simple_link_matcher(content, crawler.index_url)
-        self.assertEqual(('%stest/foobar-1.tar.gz#md5=abcdef' % crawler.index_url, 
+        self.assertEqual(('%stest/foobar-1.tar.gz#md5=abcdef' % crawler.index_url,
                          True), generator.next())
         self.assertEqual(('http://dl-link1', True), generator.next())
         self.assertEqual(('http://dl-link2', False), generator.next())
@@ -304,8 +304,8 @@
         # we can search the index for some projects, on their names
         # the case used no matters here
         crawler = self._get_simple_crawler(server)
-        tests = (('Foobar', ['FooBar-bar', 'Foobar-baz', 'Baz-FooBar']), 
-                 ('foobar*', ['FooBar-bar', 'Foobar-baz']), 
+        tests = (('Foobar', ['FooBar-bar', 'Foobar-baz', 'Baz-FooBar']),
+                 ('foobar*', ['FooBar-bar', 'Foobar-baz']),
                  ('*foobar', ['Baz-FooBar',]))
 
         for search, expected in tests:
diff --git a/distutils2/tests/test_metadata.py b/distutils2/tests/test_metadata.py
--- a/distutils2/tests/test_metadata.py
+++ b/distutils2/tests/test_metadata.py
@@ -11,6 +11,7 @@
 from distutils2.errors import (MetadataConflictError,
                                MetadataUnrecognizedVersionError)
 
+
 class DistributionMetadataTestCase(LoggingCatcher, WarningsCatcher,
                                    unittest.TestCase):
 
@@ -95,7 +96,6 @@
                                    {'python_version': '0.1'}))
 
     def test_metadata_read_write(self):
-
         PKG_INFO = os.path.join(os.path.dirname(__file__), 'PKG-INFO')
         metadata = DistributionMetadata(PKG_INFO)
         out = StringIO()
diff --git a/distutils2/tests/test_unixccompiler.py b/distutils2/tests/test_unixccompiler.py
--- a/distutils2/tests/test_unixccompiler.py
+++ b/distutils2/tests/test_unixccompiler.py
@@ -1,11 +1,7 @@
 """Tests for distutils.unixccompiler."""
 import sys
 
-try:
-    import sysconfig
-except ImportError:
-    from distutils2._backport import sysconfig
-
+from distutils2._backport import sysconfig
 from distutils2.compiler.unixccompiler import UnixCCompiler
 from distutils2.tests import unittest
 
diff --git a/distutils2/util.py b/distutils2/util.py
--- a/distutils2/util.py
+++ b/distutils2/util.py
@@ -659,8 +659,8 @@
     for part in parts[1:]:
         try:
             ret = getattr(ret, part)
-        except AttributeError:
-            raise ImportError
+        except AttributeError, exc:
+            raise ImportError(exc)
 
     return ret
 
diff --git a/docs/source/setupcfg.rst b/docs/source/setupcfg.rst
new file mode 100644
--- /dev/null
+++ b/docs/source/setupcfg.rst
@@ -0,0 +1,161 @@
+==================
+The setup.cfg file
+==================
+
+This document describes the :file:`setup.cfg`, a ini-like file used by
+Distutils2 to replace the :file:`setup.py` file.
+
+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 *\*optional* can be omited.
+- Options that are marked *\*environ* can use environement markes, as described
+  in PEP 345.
+
+The sections are:
+
+- global
+- metadata
+- files
+- command sections
+
+
+global
+======
+
+Contains global options for Distutils2. This section is shared with Distutils1.
+
+- **commands**: Defined Distutils2 command. A command is defined by its fully
+  qualified name.
+
+  Examples::
+
+    [global]
+    commands =
+        package.sdist.CustomSdistCommand
+
+  *\*optional* *\*multi*
+
+- **compilers**: Defined Distutils2 compiler. A compiler is defined by its fully
+  qualified name. 
+
+  Example::
+
+    [global]
+    compiler =
+        package.compilers.CustomCCompiler
+
+  *\*optional* *\*multi*
+
+- **setup_hook**: defines a callable that will be called right after the
+  :file:`setup.cfg` file is read. The callable receives the configuration
+  in form of a mapping and can make some changes to it. *\*optional*
+
+
+metadata
+========
+
+The metadata section contains the metadata for the project as described in
+PEP 345.
+
+
+Fields:
+
+- **name**: Name of the project.
+- **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*
+- **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
+  the Supported-Platform field are freeform. *\*multi* *\*optional*
+- **summary**: A one-line summary of what the distribution does.
+  (Used to be called *description* in Distutils1.)
+- **description**: A longer description. (Used to be called *long_description*
+  in Distutils1.) A file can be provided in the *description-file* field.
+  *\*optional*
+- **description-file**: path to a text file that will be used for the
+  **description** field. *\*optional*
+- **keywords**: A list of additional keywords to be used to assist searching
+  for the distribution in a larger catalog. Comma or space-separated. *\*optional*
+- **home-page**: The URL for the distribution's home page.
+- **download-url**: The URL from which this version of the distribution
+  can be downloaded. *\*optional*
+- **author**: Author's name. *\*optional*
+- **author-email**: Author's e-mail. *\*optional*
+- **maintainer**: Maintainer's name. *\*optional*
+- **maintainer-email**: Maintainer's e-mail. *\*optional*
+- **license**: A text indicating the term of uses, when a trove classifier does
+  not match. *\*optional*.
+- **classifiers**: Classification for the distribution, as described in PEP 301.
+  *\*optional* *\*multi* *\*environ*
+- **requires-dist**: name of another distutils project required as a dependency.
+  The format is *name (version)* where version is an optional
+  version declaration, as described in PEP 345. *\*optional* *\*multi* *\*environ*
+- **provides-dist**: name of another distutils project contained whithin this
+  distribution. Same format than *requires-dist*. *\*optional* *\*multi* *\*environ*
+- **obsoletes-dist**: name of another distutils project this version obsoletes.
+  Same format than *requires-dist*. *\*optional* *\*multi* *\*environ*
+- **requires-python**: Specifies the Python version the distribution requires.
+  The value is a version number, as described in PEP 345.
+  *\*optional* *\*multi* *\*environ*
+- **requires-externals**: a dependency in the system. This field is free-form,
+  and just a hint for downstream maintainers. *\*optional* *\*multi* *\*environ*
+- **project-url**: A label, followed by a browsable URL for the project.
+  "label, url". The label is limited to 32 signs. *\*optional* *\*multi*
+
+
+Example::
+
+    [metadata]
+    name = pypi2rpm
+    version = 0.1
+    author = Tarek Ziade
+    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
+
+    classifier = Development Status :: 3 - Alpha
+        License :: OSI Approved :: Mozilla Public License 1.1 (MPL 1.1)
+
+
+
+files
+=====
+
+This section describes the files included in the project.
+
+- **packages**: a list of packages the project includes *\*optional* *\*multi*
+- **modules**: a list of packages the project includes *\*optional* *\*multi*
+- **scripts**: a list of scripts the project includes *\*optional* *\*multi*
+- **extra_files**: a list of patterns to include extra files *\*optional* *\*multi*
+
+Example::
+
+    [files]
+    packages =
+            pypi2rpm
+            pypi2rpm.command
+
+    scripts =
+            pypi2rpm/pypi2rpm.py
+
+    extra_files =
+            setup.py
+
+
+command sections
+================
+
+Each command can have its options described in :file:`setup.cfg`
+
+
+Example::
+
+    [sdist]
+    manifest_makers = package.module.Maker
+
+

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


More information about the Python-checkins mailing list