summaryrefslogtreecommitdiffstats
path: root/Lib/distutils/ccompiler.py
diff options
context:
space:
mode:
Diffstat (limited to 'Lib/distutils/ccompiler.py')
-rw-r--r--Lib/distutils/ccompiler.py290
1 files changed, 243 insertions, 47 deletions
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 types import *
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 @@ class CCompiler:
# 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 @@ class CCompiler:
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 @@ class CCompiler:
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 @@ class CCompiler:
'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 @@ class CCompiler:
- # -- Filename mangling 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'.
-
- def object_filenames (self, source_filenames, output_dir=None):
- """Return the list of object filenames corresponding to each
- specified source filename."""
- pass
-
- def shared_object_filename (self, source_filename):
- """Return the shared object filename corresponding to a
- specified source filename (assuming the same directory)."""
- pass
-
- 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
-
- # 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
+ # -- Filename generation methods -----------------------------------
+
+ # 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,
+ 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
+
+ # object_filenames ()
+
+
+ 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)
+
+
+ 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)
- def shared_library_name (self, inname):
- """Given a name with no extension, return the name + shared object extension"""
- return inname + self._shared_lib_ext
# -- Utility methods -----------------------------------------------
@@ -606,4 +802,4 @@ def gen_lib_options (compiler, library_dirs, libraries):
return lib_opts
-# _gen_lib_options ()
+# gen_lib_options ()