241 lines
10 KiB
Python
241 lines
10 KiB
Python
#
|
|
# Copyright (c) 2010 Western Digital Corporation
|
|
# Alan Somers asomers (at) gmail (dot) com
|
|
#
|
|
# Permission is hereby granted, free of charge, to any person obtaining
|
|
# a copy of this software and associated documentation files (the
|
|
# "Software"), to deal in the Software without restriction, including
|
|
# without limitation the rights to use, copy, modify, merge, publish,
|
|
# distribute, sublicense, and/or sell copies of the Software, and to
|
|
# permit persons to whom the Software is furnished to do so, subject to
|
|
# the following conditions:
|
|
#
|
|
# The above copyright notice and this permission notice shall be included
|
|
# in all copies or substantial portions of the Software.
|
|
#
|
|
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
|
|
# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
|
|
# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
|
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
#
|
|
|
|
####
|
|
import os
|
|
import new
|
|
import sys
|
|
##
|
|
import SCons
|
|
|
|
if sys.version_info < (2,6,0):
|
|
from relpath import relpath
|
|
else:
|
|
from os.path import relpath
|
|
|
|
|
|
def sc_relpath(src, destdir):
|
|
"""Like relpath but aware of SCons convention regarding '#' in pathnames"""
|
|
if src[0] == '#':
|
|
return relpath(src[1:], destdir)
|
|
else:
|
|
return relpath(os.path.join(destdir, src), destdir)
|
|
|
|
|
|
|
|
class MF_Executor(SCons.Executor.Executor):
|
|
"""Custom Executor that can scan each target file for its dependencies
|
|
individually, rather than giving every target the same deps.
|
|
Assumes that there is a one-to-one relationship between sources and targets
|
|
and the targets have the same basenames as their respective sources
|
|
ie. [[foo.o, bar.o], [foo.c, bar.c]]"""
|
|
def scan(self, scanner, node_list):
|
|
tgt_names = [os.path.splitext(
|
|
os.path.basename(str(i)))[0] for i in self.targets]
|
|
env = self.get_build_env()
|
|
|
|
if scanner:
|
|
for node in node_list:
|
|
tgt = \
|
|
self.targets[tgt_names.index(
|
|
os.path.splitext(
|
|
os.path.basename(str(node)))[0])]
|
|
node.disambiguate()
|
|
s = scanner.select(node)
|
|
if not s:
|
|
continue
|
|
path = self.get_build_scanner_path(s)
|
|
tgt.add_to_implicit(node.get_implicit_deps(env, s, path))
|
|
else:
|
|
kw = self.get_kw()
|
|
for node in node_list:
|
|
tgt = \
|
|
self.targets[tgt_names.index(
|
|
os.path.splitext(
|
|
os.path.basename(str(node)))[0])]
|
|
node.disambiguate()
|
|
scanner = node.get_env_scanner(env, kw)
|
|
if not scanner:
|
|
continue
|
|
scanner = scanner.select(node)
|
|
if not scanner:
|
|
continue
|
|
path = self.get_build_scanner_path(scanner)
|
|
tgt.add_to_implicit(node.get_implicit_deps(env, scanner, path))
|
|
|
|
|
|
def MF_get_single_executor(self, env, tlist, slist, executor_kw):
|
|
if not self.action:
|
|
raise UserError, "Builder %s must have an action to build %s." % \
|
|
(self.get_name(env or self.env), map(str,tlist))
|
|
return MF_Executor(self.action, env, [], tlist, slist, executor_kw)
|
|
|
|
def exists(env):
|
|
return env.WhereIs(env.subst['$CC']) or env.WhereIs(env.subst['$CXX'])
|
|
|
|
def MFProgramEmitter(target, source, env):
|
|
"""Ensures that target list is complete, and does validity checking. Sets precious"""
|
|
if len(target) == 1 and len(source) > 1:
|
|
#Looks like the user specified many sources and SCons created 1 target
|
|
#targets are implicit, but the builder doesn't know how to handle
|
|
#suffixes for multiple target files, so we'll do it here
|
|
objdir = env.get('OBJDIR', '')
|
|
#target = [os.path.join(
|
|
# objdir,
|
|
# os.path.splitext(
|
|
# os.path.basename(str(i)))[0] + '.o' ) for i in source]
|
|
elif len(source) == 1 and 'OBJDIR' in env:
|
|
target = os.path.join(
|
|
env['OBJDIR'],
|
|
os.path.splitext(
|
|
os.path.basename(str(source[0])))[0] + '.o' )
|
|
else:
|
|
#targets are explicit, we need to check their validity
|
|
tgt_names = [os.path.splitext(
|
|
os.path.basename(str(i)))[0] for i in target]
|
|
src_names = [os.path.splitext(
|
|
os.path.basename(str(i)))[0] for i in source]
|
|
tgt_dirs = [os.path.dirname(str(i)) for i in target]
|
|
if sorted(tgt_names) != sorted(src_names):
|
|
raise ValueError, "target files do not have obvious one-one relationship to source files"
|
|
if len(set(src_names)) != len(src_names):
|
|
raise ValueError, "source files may not include identically named files in different directories"
|
|
if len(set(tgt_dirs)) != 1:
|
|
raise ValueError, "Target files must all be in same directory"
|
|
|
|
for t in target:
|
|
env.Precious(t)
|
|
return target, source
|
|
|
|
def MFProgramGenerator(source, target, env, for_signature):
|
|
#Rebuild everything if
|
|
# a) the number of dependencies has changed
|
|
# b) any target does not exist
|
|
# c) the build command has changed
|
|
#Else rebuild only those c files that have changed_since_last_build
|
|
#The signature of this builder should always be the same, because the
|
|
#multifile compile is always functionally equivalent to rebuilding
|
|
#everything
|
|
|
|
if for_signature:
|
|
pared_sources = source
|
|
else:
|
|
#First a sanity check
|
|
assert len(set([os.path.splitext(str(i))[1] for i in source])) == 1, \
|
|
"All source files must have the same extension."
|
|
pared_sources = []
|
|
src_names = [os.path.splitext(os.path.basename(str(i)))[0]
|
|
for i in source]
|
|
tgt_names = [os.path.splitext(os.path.basename(str(t)))[0]
|
|
for t in target]
|
|
ni = target[0].get_binfo()
|
|
oi = target[0].get_stored_info().binfo
|
|
if ni.bactsig != oi.bactsig:
|
|
#Command line has changed
|
|
pared_sources = source
|
|
else:
|
|
for i in range(len(tgt_names)):
|
|
t = target[i]
|
|
tgt_name = tgt_names[i]
|
|
if not t.exists():
|
|
#a target does not exist
|
|
pared_sources = source
|
|
break
|
|
bi = t.get_stored_info().binfo
|
|
then = bi.bsourcesigs + bi.bdependsigs + bi.bimplicitsigs
|
|
children = t.children()
|
|
if len(children) != len(then):
|
|
#the number of dependencies has changed
|
|
pared_sources = source
|
|
break
|
|
for child, prev_ni in zip(children, then):
|
|
if child.changed_since_last_build(t, prev_ni) and \
|
|
not t in pared_sources:
|
|
#If child is a source file, not an explicit or implicit
|
|
#dependency, then it is not truly a dependency of any target
|
|
#except that with the same basename. This is a limitation
|
|
#of SCons.node, which assumes that all sources of a Node
|
|
#are dependencies of all targets. So we check for that case
|
|
#here and only rebuild as necessary.
|
|
src_name = os.path.splitext(os.path.basename(str(child)))[0]
|
|
if src_name not in tgt_names or src_name == tgt_name:
|
|
s = source[src_names.index(tgt_name)]
|
|
pared_sources.append(s)
|
|
assert len(pared_sources) > 0
|
|
destdir = str(target[0].dir)
|
|
#finding sconscript_dir is a bit of a hack. It assumes that the source
|
|
#files are always going to be in the same directory as the SConscript file
|
|
#which is not necessarily true. BUG BY Alan Somers
|
|
sconscript_dir = os.path.dirname(str(pared_sources[0]))
|
|
prefixed_sources = [relpath(str(i), destdir) for i in pared_sources]
|
|
prefixed_sources_str = ' '.join([str(i) for i in prefixed_sources])
|
|
lang_ext = os.path.splitext(prefixed_sources[0])[1]
|
|
tgt_names2 = [os.path.splitext(os.path.basename(str(t)))[0]
|
|
for t in target]
|
|
|
|
_CPPPATH = []
|
|
if 'CPPPATH' in env:
|
|
for i in env['CPPPATH']:
|
|
#if i[0] == '#':
|
|
##_CPPPATH.append(relpath(i[1:], destdir))
|
|
_CPPPATH.append(i)
|
|
#else:
|
|
# _CPPPATH.append(relpath(os.path.join(sconscript_dir, i),
|
|
# destdir))
|
|
|
|
defines = ""
|
|
for t in env['CPPDEFINES']:
|
|
defines += ("-D"+str(t)+" ")
|
|
|
|
_CPPINCFLAGS = ['-I' + i for i in _CPPPATH]
|
|
_CCOMCOM = '$CPPFLAGS $_CPPDEFFLAGS $defines %s' % ' '.join(_CPPINCFLAGS)
|
|
|
|
libstr = ""
|
|
for t in env['LIBS']:
|
|
libstr += ("-l"+t+" ")
|
|
|
|
if lang_ext == '.c' :
|
|
_CCCOM = 'cd %s && $CC $CFLAGS $CCFLAGS %s %s $LINKFLAGS %s -o %s' % \
|
|
(destdir, _CCOMCOM, prefixed_sources_str, libstr, tgt_names2[0])
|
|
#XXX BUG BY Alan Somers. $CCCOMSTR gets substituted using the full list of target files,
|
|
#not prefixed_sources
|
|
cmd = SCons.Script.Action(env.subst(_CCCOM), "$CCCOMSTR")
|
|
elif lang_ext in ['.cc', '.cpp']:
|
|
_CXXCOM = 'cd %s && $CXX $CXXFLAGS $CCFLAGS %s %s $LINKFLAGS %s -o %s' % \
|
|
(destdir, _CCOMCOM, prefixed_sources_str, libstr, tgt_names2[0])
|
|
cmd = SCons.Script.Action(env.subst(_CXXCOM), "$CXXCOMSTR")
|
|
else:
|
|
assert False, "Unknown source file extension %s" % lang_ext
|
|
return cmd
|
|
|
|
def generate(env):
|
|
"""Adds the MFObject builder to your environment"""
|
|
MFProgramBld = env.Builder(generator = MFProgramGenerator,
|
|
emitter = MFProgramEmitter,
|
|
suffix = '.o',
|
|
source_scanner=SCons.Tool.SourceFileScanner)
|
|
MFProgramBld.get_single_executor = new.instancemethod(MF_get_single_executor,
|
|
MFProgramBld, MFProgramBld.__class__)
|
|
|
|
env.Append(BUILDERS = {'MFProgram': MFProgramBld}) |