Serious overhaul of the C compiler interface and the two classes that
implement it (so far):
  * moved filename generation methods into CCompiler base class,
    driven by data supplied by implementation classes
  * moved a bunch of common code from UnixCCompiler to convenience
    methods in CCompiler
  * overhauled MSVCCompiler's compile/link methods to look and act
    as much as possible like UnixCCompiler's, in order to regularize
    both interface and behaviour (especially by using those new
    convenience methods)
diff --git a/Lib/distutils/ccompiler.py b/Lib/distutils/ccompiler.py
index 4819b23..2336e96 100644
--- a/Lib/distutils/ccompiler.py
+++ b/Lib/distutils/ccompiler.py
@@ -12,7 +12,7 @@
 from copy import copy
 from distutils.errors import *
 from distutils.spawn import spawn
-from distutils.util import move_file, mkpath
+from distutils.util import move_file, mkpath, newer_pairwise, newer_group
 
 
 class CCompiler:
@@ -65,6 +65,18 @@
     #     library search path anyways.
     
 
+    # Subclasses that rely on the standard filename generation methods
+    # implemented below should override these; see the comment near
+    # those methods ('object_filenames()' et. al.) for details:
+    src_extensions = None               # list of strings
+    obj_extension = None                # string
+    static_lib_extension = None
+    shared_lib_extension = None         # string
+    static_lib_format = None            # format string
+    shared_lib_format = None            # prob. same as static_lib_format
+    exe_extension = None                # string
+
+
     def __init__ (self,
                   verbose=0,
                   dry_run=0,
@@ -255,6 +267,138 @@
         self.objects = copy (objects)
 
 
+    # -- Priviate utility methods --------------------------------------
+    # (here for the convenience of subclasses)
+
+    def _fix_compile_args (self, output_dir, macros, include_dirs):
+        """Typecheck and fix-up some of the arguments to the 'compile()' method,
+           and return fixed-up values.  Specifically: if 'output_dir' is
+           None, replaces it with 'self.output_dir'; ensures that 'macros'
+           is a list, and augments it with 'self.macros'; ensures that
+           'include_dirs' is a list, and augments it with
+           'self.include_dirs'.  Guarantees that the returned values are of
+           the correct type, i.e. for 'output_dir' either string or None,
+           and for 'macros' and 'include_dirs' either list or None."""
+
+        if output_dir is None:
+            output_dir = self.output_dir
+        elif type (output_dir) is not StringType:
+            raise TypeError, "'output_dir' must be a string or None"
+
+        if macros is None:
+            macros = self.macros
+        elif type (macros) is ListType:
+            macros = macros + (self.macros or [])
+        else:
+            raise TypeError, \
+                  "'macros' (if supplied) must be a list of tuples"
+
+        if include_dirs is None:
+            include_dirs = self.include_dirs
+        elif type (include_dirs) in (ListType, TupleType):
+            include_dirs = list (include_dirs) + (self.include_dirs or [])
+        else:
+            raise TypeError, \
+                  "'include_dirs' (if supplied) must be a list of strings"
+                    
+        return (output_dir, macros, include_dirs)
+
+    # _fix_compile_args ()
+
+
+    def _prep_compile (self, sources, output_dir):
+        """Determine the list of object files corresponding to 'sources', and
+           figure out which ones really need to be recompiled.  Return a list
+           of all object files and a dictionary telling which source files can
+           be skipped."""
+
+        # Get the list of expected output (object) files 
+        objects = self.object_filenames (sources,
+                                         output_dir=output_dir)
+
+        if self.force:
+            skip_source = {}            # rebuild everything
+            for source in sources:
+                skip_source[source] = 0
+        else:
+            # Figure out which source files we have to recompile according
+            # to a simplistic check -- we just compare the source and
+            # object file, no deep dependency checking involving header
+            # files.
+            skip_source = {}            # rebuild everything
+            for source in sources:      # no wait, rebuild nothing
+                skip_source[source] = 1
+
+            (n_sources, n_objects) = newer_pairwise (sources, objects)
+            for source in n_sources:    # no really, only rebuild what's out-of-date
+                skip_source[source] = 0
+
+        return (objects, skip_source)
+
+    # _prep_compile ()
+
+
+    def _fix_link_args (self, objects, output_dir,
+                        takes_libs=0, libraries=None, library_dirs=None):
+        """Typecheck and fix up some of the arguments supplied to the
+           'link_*' methods and return the fixed values.  Specifically:
+           ensure that 'objects' is a list; if output_dir is None, use
+           self.output_dir; ensure that 'libraries' and 'library_dirs' are
+           both lists, and augment them with 'self.libraries' and
+           'self.library_dirs'.  If 'takes_libs' is true, return a tuple
+           (objects, output_dir, libraries, library_dirs; else return
+           (objects, output_dir)."""
+
+        if type (objects) not in (ListType, TupleType):
+            raise TypeError, \
+                  "'objects' must be a list or tuple of strings"
+        objects = list (objects)
+            
+        if output_dir is None:
+            output_dir = self.output_dir
+        elif type (output_dir) is not StringType:
+            raise TypeError, "'output_dir' must be a string or None"
+
+        if takes_libs:
+            if libraries is None:
+                libraries = self.libraries
+            elif type (libraries) in (ListType, TupleType):
+                libraries = list (libraries) + (self.libraries or [])
+            else:
+                raise TypeError, \
+                      "'libraries' (if supplied) must be a list of strings"
+
+            if library_dirs is None:
+                library_dirs = self.library_dirs
+            elif type (library_dirs) in (ListType, TupleType):
+                library_dirs = list (library_dirs) + (self.library_dirs or [])
+            else:
+                raise TypeError, \
+                      "'library_dirs' (if supplied) must be a list of strings"
+
+            return (objects, output_dir, libraries, library_dirs)
+        else:
+            return (objects, output_dir)
+
+    # _fix_link_args ()
+
+
+    def _need_link (self, objects, output_file):
+        """Return true if we need to relink the files listed in 'objects' to
+           recreate 'output_file'."""
+
+        if self.force:
+            return 1
+        else:
+            if self.dry_run:
+                newer = newer_group (objects, output_file, missing='newer')
+            else:
+                newer = newer_group (objects, output_file)
+            return newer
+
+    # _need_link ()
+
+
     # -- Worker methods ------------------------------------------------
     # (must be implemented by subclasses)
 
@@ -268,8 +412,16 @@
                  extra_postargs=None):
         """Compile one or more C/C++ source files.  'sources' must be
            a list of strings, each one the name of a C/C++ source
-           file.  Return a list of the object filenames generated
-           (one for each source filename in 'sources').
+           file.  Return a list of object filenames, one per source
+           filename in 'sources'.  Depending on the implementation,
+           not all source files will necessarily be compiled, but
+           all corresponding object filenames will be returned.
+
+           If 'output_dir' is given, object files will be put under it,
+           while retaining their original path component.  That is,
+           "foo/bar.c" normally compiles to "foo/bar.o" (for a Unix
+           implementation); if 'output_dir' is "build", then it would
+           compile to "build/foo/bar.o".
 
            'macros', if given, must be a list of macro definitions.  A
            macro definition is either a (name, value) 2-tuple or a (name,)
@@ -285,11 +437,12 @@
            'debug' is a boolean; if true, the compiler will be instructed
            to output debug symbols in (or alongside) the object file(s).
 
-           'extra_preargs' and 'extra_postargs' are optional lists of extra
-           command-line arguments that will be, respectively, prepended or
-           appended to the generated command line immediately before
-           execution.  These will most likely be peculiar to the particular
-           platform and compiler being worked with, but are a necessary
+           'extra_preargs' and 'extra_postargs' are implementation-
+           dependent.  On platforms that have the notion of a command-line
+           (e.g. Unix, DOS/Windows), they are most likely lists of strings:
+           extra command-line arguments to prepand/append to the compiler
+           command line.  On other platforms, consult the implementation
+           class documentation.  In any event, they are intended as an
            escape hatch for those occasions when the abstract compiler
            framework doesn't cut the mustard."""
            
@@ -398,45 +551,88 @@
 
 
 
-    # -- Filename mangling methods -------------------------------------
+    # -- Filename generation methods -----------------------------------
 
-    # General principle for the filename-mangling methods: by default,
-    # don't include a directory component, no matter what the caller
-    # supplies.  Eg. for UnixCCompiler, a source file of "foo/bar/baz.c"
-    # becomes "baz.o" or "baz.so", etc.  (That way, it's easiest for the
-    # caller to decide where it wants to put/find the output file.)  The
-    # 'output_dir' parameter overrides this, of course -- the directory
-    # component of the input filenames is replaced by 'output_dir'.
+    # The default implementation of the filename generating methods are
+    # prejudiced towards the Unix/DOS/Windows view of the world:
+    #   * object files are named by replacing the source file extension
+    #     (eg. .c/.cpp -> .o/.obj)
+    #   * library files (shared or static) are named by plugging the
+    #     library name and extension into a format string, eg.
+    #     "lib%s.%s" % (lib_name, ".a") for Unix static libraries
+    #   * executables are named by appending an extension (possibly
+    #     empty) to the program name: eg. progname + ".exe" for
+    #     Windows
+    #
+    # To reduce redundant code, these methods expect to find
+    # several attributes in the current object (presumably defined
+    # as class attributes):
+    #   * src_extensions -
+    #     list of C/C++ source file extensions, eg. ['.c', '.cpp']
+    #   * obj_extension -
+    #     object file extension, eg. '.o' or '.obj'
+    #   * static_lib_extension -
+    #     extension for static library files, eg. '.a' or '.lib'
+    #   * shared_lib_extension -
+    #     extension for shared library/object files, eg. '.so', '.dll'
+    #   * static_lib_format -
+    #     format string for generating static library filenames,
+    #     eg. 'lib%s.%s' or '%s.%s'
+    #   * shared_lib_format
+    #     format string for generating shared library filenames
+    #     (probably same as static_lib_format, since the extension
+    #     is one of the intended parameters to the format string)
+    #   * exe_extension -
+    #     extension for executable files, eg. '' or '.exe'
 
-    def object_filenames (self, source_filenames, output_dir=None):
-        """Return the list of object filenames corresponding to each
-           specified source filename."""
-        pass
+    def object_filenames (self,
+                          source_filenames,
+                          strip_dir=0,
+                          output_dir=''):
+        if output_dir is None: output_dir = ''
+        obj_names = []
+        for src_name in source_filenames:
+            (base, ext) = os.path.splitext (src_name)
+            if ext not in self.src_extensions:
+                continue
+            if strip_dir:
+                base = os.path.basename (base)
+            obj_names.append (os.path.join (output_dir,
+                                            base + self.obj_extension))
+        return obj_names
 
-    def shared_object_filename (self, source_filename):
-        """Return the shared object filename corresponding to a
-           specified source filename (assuming the same directory)."""
-        pass    
+    # object_filenames ()
 
-    def library_filename (self, libname):
-        """Return the static library filename corresponding to the
-           specified library name."""
-        
-        pass
 
-    def shared_library_filename (self, libname):
-        """Return the shared library filename corresponding to the
-           specified library name."""
-        pass
+    def shared_object_filename (self,
+                                basename,
+                                strip_dir=0,
+                                output_dir=''):
+        if output_dir is None: output_dir = ''
+        if strip_dir:
+            basename = os.path.basename (basename)
+        return os.path.join (output_dir, basename + self.shared_lib_extension)
 
-    # XXX ugh -- these should go!
-    def object_name (self, inname):
-        """Given a name with no extension, return the name + object extension"""
-        return inname + self._obj_ext
 
-    def shared_library_name (self, inname):
-        """Given a name with no extension, return the name + shared object extension"""
-        return inname + self._shared_lib_ext
+    def library_filename (self,
+                          libname,
+                          lib_type='static',     # or 'shared'
+                          strip_dir=0,
+                          output_dir=''):
+
+        if output_dir is None: output_dir = ''
+        if lib_type not in ("static","shared"):
+            raise ValueError, "'lib_type' must be \"static\" or \"shared\""
+        fmt = getattr (self, lib_type + "_lib_format")
+        ext = getattr (self, lib_type + "_lib_extension")
+
+        (dir, base) = os.path.split (libname)
+        filename = fmt % (base, ext)
+        if strip_dir:
+            dir = ''
+
+        return os.path.join (output_dir, dir, filename)
+
 
     # -- Utility methods -----------------------------------------------
 
@@ -606,4 +802,4 @@
 
     return lib_opts
 
-# _gen_lib_options ()
+# gen_lib_options ()