Commit 538305ba authored by Jeremy Hylton's avatar Jeremy Hylton

Extend compiler() method with optional depends argument.

This change is not backwards compatible.  If a compiler subclass
exists outside the distutils package, it may get called with the
unexpected keyword arg.  It's easy to extend that compiler by having
it ignore the argument, and not much harder to do the right thing.  If
this ends up being burdensome, we can change it before 2.3 final to
work harder at compatibility.

Also add _setup_compile() and _get_cc_args() helper functions that
factor out much of the boilerplate for each concrete compiler class.
parent 26e7e90c
...@@ -160,7 +160,6 @@ class CCompiler: ...@@ -160,7 +160,6 @@ class CCompiler:
setattr(self, key, value) setattr(self, key, value)
def _find_macro (self, name): def _find_macro (self, name):
i = 0 i = 0
for defn in self.macros: for defn in self.macros:
...@@ -321,6 +320,100 @@ class CCompiler: ...@@ -321,6 +320,100 @@ class CCompiler:
# -- Private utility methods -------------------------------------- # -- Private utility methods --------------------------------------
# (here for the convenience of subclasses) # (here for the convenience of subclasses)
# Helper method to prep compiler in subclass compile() methods
def _setup_compile(self, outdir, macros, incdirs, sources, depends,
extra):
"""Process arguments and decide which source files to compile.
Merges _fix_compile_args() and _prep_compile().
"""
if outdir is None:
outdir = self.output_dir
elif type(outdir) 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 incdirs is None:
incdirs = self.include_dirs
elif type(incdirs) in (ListType, TupleType):
incdirs = list(incdirs) + (self.include_dirs or [])
else:
raise TypeError, \
"'include_dirs' (if supplied) must be a list of strings"
if extra is None:
extra = []
# Get the list of expected output (object) files
objects = self.object_filenames(sources, 1, outdir)
assert len(objects) == len(sources)
# XXX should redo this code to eliminate skip_source entirely.
# XXX instead create build and issue skip messages inline
if self.force:
skip_source = {} # rebuild everything
for source in sources:
skip_source[source] = 0
elif depends is None:
# If depends is None, 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
skip_source[source] = 0 # out-of-date
else:
# If depends is a list of files, then do a different
# simplistic check. Assume that each object depends on
# its source and all files in the depends list.
skip_source = {}
# L contains all the depends plus a spot at the end for a
# particular source file
L = depends[:] + [None]
for i in range(len(objects)):
source = sources[i]
L[-1] = source
if newer_group(L, objects[i]):
skip_source[source] = 0
else:
skip_source[source] = 1
pp_opts = gen_preprocess_options(macros, incdirs)
build = {}
for i in range(len(sources)):
src = sources[i]
obj = objects[i]
ext = os.path.splitext(src)[1]
self.mkpath(os.path.dirname(obj))
if skip_source[src]:
log.debug("skipping %s (%s up-to-date)", src, obj)
else:
build[obj] = src, ext
return macros, objects, extra, pp_opts, build
def _get_cc_args(self, pp_opts, debug, before):
# works for unixccompiler, emxccompiler, cygwinccompiler
cc_args = pp_opts + ['-c']
if debug:
cc_args[:0] = ['-g']
if before:
cc_args[:0] = before
return cc_args
def _fix_compile_args (self, output_dir, macros, include_dirs): def _fix_compile_args (self, output_dir, macros, include_dirs):
"""Typecheck and fix-up some of the arguments to the 'compile()' """Typecheck and fix-up some of the arguments to the 'compile()'
method, and return fixed-up values. Specifically: if 'output_dir' method, and return fixed-up values. Specifically: if 'output_dir'
...@@ -341,8 +434,7 @@ class CCompiler: ...@@ -341,8 +434,7 @@ class CCompiler:
elif type (macros) is ListType: elif type (macros) is ListType:
macros = macros + (self.macros or []) macros = macros + (self.macros or [])
else: else:
raise TypeError, \ raise TypeError, "'macros' (if supplied) must be a list of tuples"
"'macros' (if supplied) must be a list of tuples"
if include_dirs is None: if include_dirs is None:
include_dirs = self.include_dirs include_dirs = self.include_dirs
...@@ -352,40 +444,57 @@ class CCompiler: ...@@ -352,40 +444,57 @@ class CCompiler:
raise TypeError, \ raise TypeError, \
"'include_dirs' (if supplied) must be a list of strings" "'include_dirs' (if supplied) must be a list of strings"
return (output_dir, macros, include_dirs) return output_dir, macros, include_dirs
# _fix_compile_args () # _fix_compile_args ()
def _prep_compile (self, sources, output_dir): def _prep_compile(self, sources, output_dir, depends=None):
"""Determine the list of object files corresponding to 'sources', """Decide which souce files must be recompiled.
and figure out which ones really need to be recompiled. Return a
list of all object files and a dictionary telling which source Determine the list of object files corresponding to 'sources',
files can be skipped. 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 # Get the list of expected output (object) files
objects = self.object_filenames (sources, objects = self.object_filenames(sources, strip_dir=1,
strip_dir=1, output_dir=output_dir)
output_dir=output_dir) assert len(objects) == len(sources)
if self.force: if self.force:
skip_source = {} # rebuild everything skip_source = {} # rebuild everything
for source in sources: for source in sources:
skip_source[source] = 0 skip_source[source] = 0
else: elif depends is None:
# Figure out which source files we have to recompile according # If depends is None, figure out which source files we
# to a simplistic check -- we just compare the source and # have to recompile according to a simplistic check. We
# object file, no deep dependency checking involving header # just compare the source and object file, no deep
# files. # dependency checking involving header files.
skip_source = {} # rebuild everything skip_source = {} # rebuild everything
for source in sources: # no wait, rebuild nothing for source in sources: # no wait, rebuild nothing
skip_source[source] = 1 skip_source[source] = 1
(n_sources, n_objects) = newer_pairwise (sources, objects) n_sources, n_objects = newer_pairwise(sources, objects)
for source in n_sources: # no really, only rebuild what's for source in n_sources: # no really, only rebuild what's
skip_source[source] = 0 # out-of-date skip_source[source] = 0 # out-of-date
else:
return (objects, skip_source) # If depends is a list of files, then do a different
# simplistic check. Assume that each object depends on
# its source and all files in the depends list.
skip_source = {}
# L contains all the depends plus a spot at the end for a
# particular source file
L = depends[:] + [None]
for i in range(len(objects)):
source = sources[i]
L[-1] = source
if newer_group(L, objects[i]):
skip_source[source] = 0
else:
skip_source[source] = 1
return objects, skip_source
# _prep_compile () # _prep_compile ()
...@@ -484,22 +593,19 @@ class CCompiler: ...@@ -484,22 +593,19 @@ class CCompiler:
""" """
pass pass
def compile (self, def compile(self, sources, output_dir=None, macros=None,
sources, include_dirs=None, debug=0, extra_preargs=None,
output_dir=None, extra_postargs=None, depends=None):
macros=None, """Compile one or more source files.
include_dirs=None,
debug=0, 'sources' must be a list of filenames, most likely C/C++
extra_preargs=None, files, but in reality anything that can be handled by a
extra_postargs=None): particular compiler and compiler class (eg. MSVCCompiler can
"""Compile one or more source files. 'sources' must be a list of handle resource files in 'sources'). Return a list of object
filenames, most likely C/C++ files, but in reality anything that filenames, one per source filename in 'sources'. Depending on
can be handled by a particular compiler and compiler class the implementation, not all source files will necessarily be
(eg. MSVCCompiler can handle resource files in 'sources'). Return compiled, but all corresponding object filenames will be
a list of object filenames, one per source filename in 'sources'. returned.
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 If 'output_dir' is given, object files will be put under it, while
retaining their original path component. That is, "foo/bar.c" retaining their original path component. That is, "foo/bar.c"
...@@ -530,6 +636,12 @@ class CCompiler: ...@@ -530,6 +636,12 @@ class CCompiler:
for those occasions when the abstract compiler framework doesn't for those occasions when the abstract compiler framework doesn't
cut the mustard. cut the mustard.
'depends', if given, is a list of filenames that all targets
depend on. If a source file is older than any file in
depends, then the source file will be recompiled. This
supports dependency tracking, but only at a coarse
granularity.
Raises CompileError on failure. Raises CompileError on failure.
""" """
pass pass
...@@ -710,7 +822,6 @@ class CCompiler: ...@@ -710,7 +822,6 @@ class CCompiler:
""" """
raise NotImplementedError raise NotImplementedError
# -- Filename generation methods ----------------------------------- # -- Filename generation methods -----------------------------------
# The default implementation of the filename generating methods are # The default implementation of the filename generating methods are
...@@ -745,63 +856,46 @@ class CCompiler: ...@@ -745,63 +856,46 @@ class CCompiler:
# * exe_extension - # * exe_extension -
# extension for executable files, eg. '' or '.exe' # extension for executable files, eg. '' or '.exe'
def object_filenames (self, def object_filenames(self, source_filenames, strip_dir=0, output_dir=''):
source_filenames, assert output_dir is not None
strip_dir=0,
output_dir=''):
if output_dir is None: output_dir = ''
obj_names = [] obj_names = []
for src_name in source_filenames: for src_name in source_filenames:
(base, ext) = os.path.splitext (src_name) base, ext = os.path.splitext(src_name)
if ext not in self.src_extensions: if ext not in self.src_extensions:
raise UnknownFileError, \ raise UnknownFileError, \
"unknown file type '%s' (from '%s')" % \ "unknown file type '%s' (from '%s')" % (ext, src_name)
(ext, src_name)
if strip_dir: if strip_dir:
base = os.path.basename (base) base = os.path.basename(base)
obj_names.append (os.path.join (output_dir, obj_names.append(os.path.join(output_dir,
base + self.obj_extension)) base + self.obj_extension))
return obj_names return obj_names
# object_filenames () def shared_object_filename(self, basename, strip_dir=0, output_dir=''):
assert output_dir is not None
def shared_object_filename (self,
basename,
strip_dir=0,
output_dir=''):
if output_dir is None: output_dir = ''
if strip_dir: if strip_dir:
basename = os.path.basename (basename) basename = os.path.basename (basename)
return os.path.join (output_dir, basename + self.shared_lib_extension) return os.path.join(output_dir, basename + self.shared_lib_extension)
def executable_filename (self, def executable_filename(self, basename, strip_dir=0, output_dir=''):
basename, assert output_dir is not None
strip_dir=0,
output_dir=''):
if output_dir is None: output_dir = ''
if strip_dir: if strip_dir:
basename = os.path.basename (basename) basename = os.path.basename (basename)
return os.path.join(output_dir, basename + (self.exe_extension or '')) return os.path.join(output_dir, basename + (self.exe_extension or ''))
def library_filename (self, def library_filename(self, libname, lib_type='static', # or 'shared'
libname, strip_dir=0, output_dir=''):
lib_type='static', # or 'shared' assert output_dir is not None
strip_dir=0, if lib_type not in ("static", "shared", "dylib"):
output_dir=''):
if output_dir is None: output_dir = ''
if lib_type not in ("static","shared","dylib"):
raise ValueError, "'lib_type' must be \"static\", \"shared\" or \"dylib\"" raise ValueError, "'lib_type' must be \"static\", \"shared\" or \"dylib\""
fmt = getattr (self, lib_type + "_lib_format") fmt = getattr(self, lib_type + "_lib_format")
ext = getattr (self, lib_type + "_lib_extension") ext = getattr(self, lib_type + "_lib_extension")
(dir, base) = os.path.split (libname) dir, base = os.path.split (libname)
filename = fmt % (base, ext) filename = fmt % (base, ext)
if strip_dir: if strip_dir:
dir = '' dir = ''
return os.path.join (output_dir, dir, filename) return os.path.join(output_dir, dir, filename)
# -- Utility methods ----------------------------------------------- # -- Utility methods -----------------------------------------------
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment