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 ()
diff --git a/Lib/distutils/msvccompiler.py b/Lib/distutils/msvccompiler.py
index 2dd8dc1..847c611 100644
--- a/Lib/distutils/msvccompiler.py
+++ b/Lib/distutils/msvccompiler.py
@@ -5,12 +5,13 @@
 
 
 # created 1999/08/19, Perry Stoll
-# 
+# hacked by Robin Becker and Thomas Heller to do a better job of
+#   finding DevStudio (through the registry)
+
 __revision__ = "$Id$"
 
-import os
-import sys
-import string
+import sys, os, string
+from types import *
 from distutils.errors import *
 from distutils.ccompiler import \
      CCompiler, gen_preprocess_options, gen_lib_options
@@ -137,6 +138,20 @@
 
     compiler_type = 'msvc'
 
+    # Private class data (need to distinguish C from C++ source for compiler)
+    _c_extensions = ['.c']
+    _cpp_extensions = ['.cc','.cpp']
+
+    # Needed for the filename generation methods provided by the
+    # base class, CCompiler.
+    src_extensions = _c_extensions + _cpp_extensions
+    obj_extension = '.obj'
+    static_lib_extension = '.lib'
+    shared_lib_extension = '.dll'
+    static_lib_format = shared_lib_format = '%s%s'
+    exe_extension = '.exe'
+
+
     def __init__ (self,
                   verbose=0,
                   dry_run=0,
@@ -169,9 +184,7 @@
 
         self.preprocess_options = None
         self.compile_options = [ '/nologo', '/Ox', '/MD', '/W3' ]
-        self.compile_options_debug = [
-            '/nologo', '/Od', '/MDd', '/W3', '/Z7', '/D_DEBUG'
-            ]
+        self.compile_options_debug = ['/nologo', '/Od', '/MDd', '/W3', '/Z7', '/D_DEBUG']
 
         self.ldflags_shared = ['/DLL', '/nologo', '/INCREMENTAL:NO']
         self.ldflags_shared_debug = [
@@ -181,21 +194,7 @@
 
 
     # -- Worker methods ------------------------------------------------
-    # (must be implemented by subclasses)
 
-    _c_extensions = [ '.c' ]
-    _cpp_extensions = [ '.cc', '.cpp' ]
-
-    _obj_ext = '.obj'
-    _exe_ext = '.exe'
-    _shared_lib_ext = '.dll'
-    _static_lib_ext = '.lib'
-
-    # XXX the 'output_dir' parameter is ignored by the methods in this
-    # class!  I just put it in to be consistent with CCompiler and
-    # UnixCCompiler, but someone who actually knows Visual C++ will
-    # have to make it work...
-    
     def compile (self,
                  sources,
                  output_dir=None,
@@ -205,48 +204,43 @@
                  extra_preargs=None,
                  extra_postargs=None):
 
-        if macros is None:
-            macros = []
-        if include_dirs is None:
-            include_dirs = []
+        (output_dir, macros, include_dirs) = \
+            self._fix_compile_args (output_dir, macros, include_dirs)
+        (objects, skip_sources) = self._prep_compile (sources, output_dir)
 
-        objectFiles = []
+        if extra_postargs is None:
+            extra_postargs = []
 
-        base_pp_opts = \
-            gen_preprocess_options (self.macros + macros,
-                                    self.include_dirs + include_dirs)
-
-        base_pp_opts.append('/c')
-
+        pp_opts = gen_preprocess_options (macros, include_dirs)
+        compile_opts = extra_preargs or []
+        compile_opts.append ('/c')
         if debug:
-            compile_options = self.compile_options_debug
+            compile_opts.extend (self.compile_options_debug)
         else:
-            compile_options = self.compile_options
+            compile_opts.extend (self.compile_options)
         
-        for srcFile in sources:
-            base,ext = os.path.splitext(srcFile)
-            objFile = base + ".obj"
+        for i in range (len (sources)):
+            src = sources[i] ; obj = objects[i]
+            ext = (os.path.splitext (src))[1]
 
-            if ext in self._c_extensions:
-                fileOpt = "/Tc"
-            elif ext in self._cpp_extensions:
-                fileOpt = "/Tp"
+            if skip_sources[src]:
+                self.announce ("skipping %s (%s up-to-date)" % (src, obj))
+            else:
+                if ext in self._c_extensions:
+                    input_opt = "/Tc" + src
+                elif ext in self._cpp_extensions:
+                    input_opt = "/Tp" + src
 
-            inputOpt  = fileOpt + srcFile
-            outputOpt = "/Fo"   + objFile
+                output_opt = "/Fo" + obj
 
-            cc_args = compile_options + \
-                      base_pp_opts + \
-                      [outputOpt, inputOpt]
+                self.mkpath (os.path.dirname (obj))
+                self.spawn ([self.cc] + compile_opts + pp_opts +
+                            [input_opt, output_opt] +
+                            extra_postargs)
 
-            if extra_preargs:
-                cc_args[:0] = extra_preargs
-            if extra_postargs:
-                cc_args.extend (extra_postargs)
+        return objects
 
-            self.spawn ([self.cc] + cc_args)
-            objectFiles.append( objFile )
-        return objectFiles
+    # compile ()
 
 
     # XXX the signature of this method is different from CCompiler and
@@ -263,25 +257,30 @@
                          extra_preargs=None,
                          extra_postargs=None):
 
-        if libraries is None:
-            libraries = []
-        if library_dirs is None:
-            library_dirs = []
+        (objects, output_dir, libraries, library_dirs) = \
+            self._fix_link_args (objects, output_dir, takes_libs=1,
+                                 libraries=libraries,
+                                 library_dirs=library_dirs)
         
-        lib_opts = gen_lib_options (self.libraries + libraries,
-                                    self.library_dirs + library_dirs,
-                                    "%s.lib", "/LIBPATH:%s")
+        output_filename = \
+            self.library_filename (output_libname, output_dir=output_dir)
 
-        ld_args = self.ldflags_static + lib_opts + \
-                  objects + ['/OUT:' + output_filename]
-        if debug:
-            pass                        # XXX what goes here?
-        if extra_preargs:
-            ld_args[:0] = extra_preargs
-        if extra_postargs:
-            ld_args.extend (extra_postargs)
+        if self._need_link (objects, output_filename):
+            lib_opts = gen_lib_options (libraries, library_dirs,
+                                        "%s.lib", "/LIBPATH:%s")
+            ld_args = self.ldflags_static + lib_opts + \
+                      objects + ['/OUT:' + output_filename]
+            if debug:
+                pass                    # XXX what goes here?
+            if extra_preargs:
+                ld_args[:0] = extra_preargs
+            if extra_postargs:
+                ld_args.extend (extra_postargs)
+            self.spawn ([self.link] + ld_args)
+        else:
+            self.announce ("skipping %s (up-to-date)" % output_filename)
 
-        self.spawn ( [ self.link ] + ld_args )
+    # link_static_lib ()
     
 
     def link_shared_lib (self,
@@ -294,8 +293,6 @@
                          extra_preargs=None,
                          extra_postargs=None):
 
-        # XXX should we sanity check the library name? (eg. no
-        # slashes)
         self.link_shared_object (objects,
                                  self.shared_library_name(output_libname),
                                  output_dir=output_dir,
@@ -315,70 +312,48 @@
                             debug=0,
                             extra_preargs=None,
                             extra_postargs=None):
-        """Link a bunch of stuff together to create a shared object
-           file.  Much like 'link_shared_lib()', except the output
-           filename is explicitly supplied as 'output_filename'."""
-        if libraries is None:
-            libraries = []
-        if library_dirs is None:
-            library_dirs = []
+
+        (objects, output_dir, libraries, library_dirs) = \
+            self._fix_link_args (objects, output_dir, takes_libs=1,
+                                 libraries=libraries, library_dirs=library_dirs)
         
-        lib_opts = gen_lib_options (self,
-                                    self.library_dirs + library_dirs,
-                                    self.libraries + libraries)
+        lib_opts = gen_lib_options (self, library_dirs, libraries)
+        if type (output_dir) not in (StringType, NoneType):
+            raise TypeError, "'output_dir' must be a string or None"
+        if output_dir is not None:
+            output_filename = os.path.join (output_dir, output_filename)
 
-        if debug:
-            ldflags = self.ldflags_shared_debug
-            basename, ext = os.path.splitext (output_filename)
-            #XXX not sure this belongs here
-            # extensions in debug_mode are named 'module_d.pyd'
-            output_filename = basename + '_d' + ext
+        if self._need_link (objects, output_filename):
+
+            if debug:
+                ldflags = self.ldflags_shared_debug
+                # XXX not sure this belongs here
+                # extensions in debug_mode are named 'module_d.pyd'
+                basename, ext = os.path.splitext (output_filename)
+                output_filename = basename + '_d' + ext
+            else:
+                ldflags = self.ldflags_shared
+
+            ld_args = ldflags + lib_opts + \
+                      objects + ['/OUT:' + output_filename]
+
+            if extra_preargs:
+                ld_args[:0] = extra_preargs
+            if extra_postargs:
+                ld_args.extend (extra_postargs)
+
+            self.mkpath (os.path.dirname (output_filename))
+            self.spawn ([self.link] + ld_args)
+
         else:
-            ldflags = self.ldflags_shared
+            self.announce ("skipping %s (up-to-date)" % output_filename)
 
-        ld_args = ldflags + lib_opts + \
-                  objects + ['/OUT:' + output_filename]
+    # link_shared_object ()
+    
 
-        if extra_preargs:
-            ld_args[:0] = extra_preargs
-        if extra_postargs:
-            ld_args.extend (extra_postargs)
-
-        self.spawn ( [ self.link ] + ld_args )
-
-
-    # -- Filename mangling methods -------------------------------------
-
-    def _change_extensions( self, filenames, newExtension ):
-        object_filenames = []
-
-        for srcFile in filenames:
-            base,ext = os.path.splitext( srcFile )
-            # XXX should we strip off any existing path?
-            object_filenames.append( base + newExtension )
-
-        return object_filenames
-
-    def object_filenames (self, source_filenames):
-        """Return the list of object filenames corresponding to each
-           specified source filename."""
-        return self._change_extensions( source_filenames, self._obj_ext )
-
-    def shared_object_filename (self, source_filename):
-        """Return the shared object filename corresponding to a
-           specified source filename."""
-        return self._change_extensions( source_filenames, self._shared_lib_ext )
-
-    def library_filename (self, libname):
-        """Return the static library filename corresponding to the
-           specified library name."""
-        return "%s%s" %( libname, self._static_lib_ext )
-
-    def shared_library_filename (self, libname):
-        """Return the shared library filename corresponding to the
-           specified library name."""
-        return "%s%s" %( libname, self._shared_lib_ext )
-
+    # -- Miscellaneous methods -----------------------------------------
+    # These are all used by the 'gen_lib_options() function, in
+    # ccompiler.py.
 
     def library_dir_option (self, dir):
         return "/LIBPATH:" + dir
diff --git a/Lib/distutils/unixccompiler.py b/Lib/distutils/unixccompiler.py
index 7765faf..35390de 100644
--- a/Lib/distutils/unixccompiler.py
+++ b/Lib/distutils/unixccompiler.py
@@ -20,10 +20,9 @@
 import string, re, os
 from types import *
 from copy import copy
-from sysconfig import \
+from distutils.sysconfig import \
      CC, CCSHARED, CFLAGS, OPT, LDSHARED, LDFLAGS, RANLIB, AR, SO
-from ccompiler import CCompiler, gen_preprocess_options, gen_lib_options
-from util import move_file, newer_pairwise, newer_group
+from distutils.ccompiler import CCompiler, gen_preprocess_options, gen_lib_options
 
 # XXX Things not currently handled:
 #   * optimization/debug/warning flags; we just use whatever's in Python's
@@ -55,10 +54,13 @@
 
     compiler_type = 'unix'
 
-    _obj_ext = '.o'
-    _exe_ext = ''
-    _shared_lib_ext = SO
-    _static_lib_ext = '.a'
+    # Needed for the filename generation methods provided by the
+    # base class, CCompiler.
+    src_extensions = [".c",".C",".cc",".cxx",".cpp"]
+    obj_extension = ".o"
+    static_lib_extension = ".a"
+    shared_lib_extension = ".so"
+    static_lib_format = shared_lib_format = "lib%s%s"
 
     # Command to create a static library: seems to be pretty consistent
     # across the major Unices.  Might have to move down into the
@@ -96,66 +98,24 @@
 
         self.ld_exec = self.cc
 
+    # __init__ ()
+
 
     def compile (self,
                  sources,
                  output_dir=None,
-                 keep_dir=0,
                  macros=None,
                  include_dirs=None,
                  debug=0,
                  extra_preargs=None,
                  extra_postargs=None):
 
-        if type (output_dir) not in (StringType, NoneType):
-            raise TypeError, "'output_dir' must be a string or None"
-        if output_dir is None:
-            output_dir = self.output_dir
-        if macros is None:
-            macros = []
-        if include_dirs is None:
-            include_dirs = []
+        (output_dir, macros, include_dirs) = \
+            self._fix_compile_args (output_dir, macros, include_dirs)
+        (objects, skip_sources) = self._prep_compile (sources, output_dir)
 
-        if type (macros) is not ListType:
-            raise TypeError, \
-                  "'macros' (if supplied) must be a list of tuples"
-        if type (include_dirs) not in (ListType, TupleType):
-            raise TypeError, \
-                  "'include_dirs' (if supplied) must be a list of strings"
-        include_dirs = list (include_dirs)
-
-        pp_opts = gen_preprocess_options (self.macros + macros,
-                                          self.include_dirs + include_dirs)
-
-        # So we can mangle 'sources' without hurting the caller's data
-        orig_sources = sources
-        sources = copy (sources)
-
-        # Get the list of expected output (object) files and drop files we
-        # don't have to recompile.  (Simplistic check -- we just compare the
-        # source and object file, no deep dependency checking involving
-        # header files.  Hmmm.)
-        objects = self.object_filenames (sources,
-                                         output_dir=output_dir,
-                                         keep_dir=keep_dir)
-        all_objects = copy (objects)    # preserve full list to return
-
-        if not self.force:
-            skipped = newer_pairwise (sources, objects)
-            for skipped_pair in skipped:
-                self.announce ("skipping %s (%s up-to-date)" % skipped_pair)
-
-        # Build list of (source,object) tuples for convenience
-        srcobj = []
-        for i in range (len (sources)):
-            srcobj.append ((sources[i], objects[i]))
-
-        # Compile all source files that weren't eliminated by
-        # 'newer_pairwise()'.
-        # XXX use of ccflags_shared means we're blithely assuming
-        # that we're compiling for inclusion in a shared object!
-        # (will have to fix this when I add the ability to build a
-        # new Python)
+        # Figure out the options for the compiler command line.
+        pp_opts = gen_preprocess_options (macros, include_dirs)
         cc_args = ['-c'] + pp_opts + self.ccflags + self.ccflags_shared
         if debug:
             cc_args[:0] = ['-g']
@@ -164,44 +124,21 @@
         if extra_postargs is None:
             extra_postargs = []
 
-        if output_dir is not None:
-            self.mkpath (output_dir)
-        for (source,object) in srcobj:
-            self.spawn ([self.cc] + cc_args +
-                        [source, '-o', object] +
-                        extra_postargs)
+        # Compile all source files that weren't eliminated by
+        # '_prep_compile()'.        
+        for i in range (len (sources)):
+            src = sources[i] ; obj = objects[i]
+            if skip_sources[src]:
+                self.announce ("skipping %s (%s up-to-date)" % (src, obj))
+            else:
+                self.mkpath (os.path.dirname (obj))
+                self.spawn ([self.cc] + cc_args + [src, '-o', obj] + extra_postargs)
 
-        # Have to re-fetch list of object filenames, because we want to
-        # return *all* of them, including those that weren't recompiled on
-        # this call!
-        return all_objects
+        # Return *all* object filenames, not just the ones we just built.
+        return objects
 
-
-    def _fix_link_args (self, output_dir, libraries, library_dirs):
-        """Fixes up the arguments supplied to the 'link_*' methods:
-           if output_dir is None, use self.output_dir; ensure that
-           libraries and library_dirs are both lists (could be None or
-           tuples on input -- both are converted to lists).  Return
-           a tuple of the three input arguments."""
-
-        if output_dir is None:
-            output_dir = self.output_dir
-        if libraries is None:
-            libraries = []
-        if library_dirs is None:
-            library_dirs = []
-        
-        if type (libraries) not in (ListType, TupleType):
-            raise TypeError, \
-                  "'libraries' (if supplied) must be a list of strings"
-        if type (library_dirs) not in (ListType, TupleType):
-            raise TypeError, \
-                  "'library_dirs' (if supplied) must be a list of strings"
-        libraries = list (libraries)
-        library_dirs = list (library_dirs)
-
-        return (output_dir, libraries, library_dirs)
-
+    # compile ()
+    
 
     def link_static_lib (self,
                          objects,
@@ -209,35 +146,17 @@
                          output_dir=None,
                          debug=0):
 
-        if type (objects) not in (ListType, TupleType):
-            raise TypeError, \
-                  "'objects' must be a list or tuple of strings"
-        objects = list (objects)
-            
-        if type (output_dir) not in (StringType, NoneType):
-            raise TypeError, "'output_dir' must be a string or None"
-        if output_dir is None:
-            output_dir = self.output_dir
+        (objects, output_dir) = self._fix_link_args (objects, output_dir, takes_libs=0)
 
-        output_filename = self.library_filename (output_libname)
-        if output_dir is not None:
-            output_filename = os.path.join (output_dir, output_filename)
+        output_filename = \
+            self.library_filename (output_libname, output_dir=output_dir)
 
-        # Check timestamps: if any of the object files are newer than
-        # the library file, *or* if "force" is true, then we'll
-        # recreate the library.
-        if not self.force:
-            if self.dry_run:
-                newer = newer_group (objects, output_filename, missing='newer')
-            else:
-                newer = newer_group (objects, output_filename)
-
-        if self.force or newer:
+        if self._need_link (objects, output_filename):
             self.mkpath (os.path.dirname (output_filename))
             self.spawn ([self.archiver,
                          self.archiver_options,
                          output_filename] +
-                        objects)
+                        objects + self.objects)
         else:
             self.announce ("skipping %s (up-to-date)" % output_filename)
 
@@ -253,11 +172,9 @@
                          debug=0,
                          extra_preargs=None,
                          extra_postargs=None):
-        # XXX should we sanity check the library name? (eg. no
-        # slashes)
         self.link_shared_object (
             objects,
-            "lib%s%s" % (output_libname, self._shared_lib_ext),
+            self.shared_library_filename (output_libname),
             output_dir,
             libraries,
             library_dirs,
@@ -276,30 +193,19 @@
                             extra_preargs=None,
                             extra_postargs=None):
 
-        (output_dir, libraries, library_dirs) = \
-            self._fix_link_args (output_dir, libraries, library_dirs)
+        (objects, output_dir, libraries, library_dirs) = \
+            self._fix_link_args (objects, output_dir, takes_libs=1,
+                                 libraries=libraries, library_dirs=library_dirs)
 
-        lib_opts = gen_lib_options (self,
-                                    self.library_dirs + library_dirs,
-                                    self.libraries + libraries)
+        lib_opts = gen_lib_options (self, library_dirs, libraries)
         if type (output_dir) not in (StringType, NoneType):
             raise TypeError, "'output_dir' must be a string or None"
         if output_dir is not None:
             output_filename = os.path.join (output_dir, output_filename)
 
-        # If any of the input object files are newer than the output shared
-        # object, relink.  Again, this is a simplistic dependency check:
-        # doesn't look at any of the libraries we might be linking with.
-
-        if not self.force:
-            if self.dry_run:
-                newer = newer_group (objects, output_filename, missing='newer')
-            else:
-                newer = newer_group (objects, output_filename)
-
-        if self.force or newer:
-            ld_args = self.ldflags_shared + objects + \
-                      lib_opts + ['-o', output_filename]
+        if self._need_link (objects, output_filename):
+            ld_args = (self.ldflags_shared + objects + self.objects + 
+                       lib_opts + ['-o', output_filename])
             if debug:
                 ld_args[:0] = ['-g']
             if extra_preargs:
@@ -324,25 +230,17 @@
                          extra_preargs=None,
                          extra_postargs=None):
     
-        (output_dir, libraries, library_dirs) = \
-            self._fix_link_args (output_dir, libraries, library_dirs)
+        (objects, output_dir, libraries, library_dirs) = \
+            self._fix_link_args (objects, output_dir, takes_libs=1,
+                                 libraries=libraries, library_dirs=library_dirs)
 
-        lib_opts = gen_lib_options (self,
-                                    self.library_dirs + library_dirs,
-                                    self.libraries + libraries)
+        lib_opts = gen_lib_options (self, library_dirs, libraries)
         output_filename = output_progname # Unix-ism!
         if output_dir is not None:
             output_filename = os.path.join (output_dir, output_filename)
 
-        # Same ol' simplistic-but-still-useful dependency check.
-        if not self.force:
-            if self.dry_run:
-                newer = newer_group (objects, output_filename, missing='newer')
-            else:
-                newer = newer_group (objects, output_filename)
-
-        if self.force or newer:
-            ld_args = objects + lib_opts + ['-o', output_filename]
+        if self._need_link (objects, output_filename):
+            ld_args = objects + self.objects + lib_opts + ['-o', output_filename]
             if debug:
                 ld_args[:0] = ['-g']
             if extra_preargs:
@@ -357,41 +255,10 @@
     # link_executable ()
 
 
-    # -- Filename-mangling (etc.) methods ------------------------------
-
-    def object_filenames (self, source_filenames,
-                          keep_dir=0, output_dir=None):
-        outnames = []
-        for inname in source_filenames:
-            outname = re.sub (r'\.(c|C|cc|cxx|cpp)$', self._obj_ext, inname)
-            if not keep_dir:
-                outname = os.path.basename (outname)
-            if output_dir is not None:
-                outname = os.path.join (output_dir, outname)
-            outnames.append (outname)
-        return outnames
-
-    def shared_object_filename (self, source_filename,
-                                keep_dir=0, output_dir=None):
-        outname = re.sub (r'\.(c|C|cc|cxx|cpp)$', self._shared_lib_ext)
-        if not keep_dir:
-            outname = os.path.basename (outname)
-        if output_dir is not None:
-            outname = os.path.join (output_dir, outname)
-        return outname
-
-
-    def library_filename (self, libname):
-        (dirname, basename) = os.path.split (libname)
-        return os.path.join (dirname,
-                             "lib%s%s" % (basename, self._static_lib_ext))
-
-    def shared_library_filename (self, libname):
-        (dirname, basename) = os.path.split (libname)
-        return os.path.join (dirname,
-                             "lib%s%s" % (basename, self._shared_lib_ext))
-
-
+    # -- Miscellaneous methods -----------------------------------------
+    # These are all used by the 'gen_lib_options() function, in
+    # ccompiler.py.
+    
     def library_dir_option (self, dir):
         return "-L" + dir