diff --git a/SConscript b/SConscript index 56cb640ed..cb2f1b48f 100644 --- a/SConscript +++ b/SConscript @@ -52,9 +52,9 @@ if((not GetOption('lin')) and (not GetOption('win')) and (not GetOption('macosx' raise SystemExit(1) if(GetOption('win')): - env = Environment(tools = ['mingw', 'gch'], ENV = os.environ) + env = Environment(tools = ['mingw', 'gch', 'mfprogram'], ENV = os.environ) else: - env = Environment(tools = ['default', 'gch'], ENV = os.environ) + env = Environment(tools = ['default', 'gch', 'mfprogram'], ENV = os.environ) if GetOption("toolprefix"): env['CC'] = GetOption("toolprefix")+env['CC'] @@ -166,9 +166,6 @@ if(GetOption('save-version')): if(GetOption('minor-version')): env.Append(CPPDEFINES=['MINOR_VERSION=' + GetOption('minor-version')]) -if(GetOption('release')): - env.Append(CCFLAGS=['-O3', '-ftree-vectorize', '-funsafe-math-optimizations', '-ffast-math', '-fomit-frame-pointer', '-funsafe-loop-optimizations', '-Wunsafe-loop-optimizations']) - if(GetOption('x86')): env.Append(CPPDEFINES='X86') @@ -231,11 +228,20 @@ if(GetOption('macosx')): if(GetOption('win')): programName += ".exe" -env.Command(['generated/ElementClasses.cpp', 'generated/ElementClasses.h'], Glob('src/simulation/elements/*.cpp'), "python generator.py elements $TARGETS $SOURCES") -env.Command(['generated/ToolClasses.cpp', 'generated/ToolClasses.h'], Glob('src/simulation/tools/*.cpp'), "python generator.py tools $TARGETS $SOURCES") -env.Decider('MD5') -t=env.Program(target=programName, source=sources) -Default(t) +if(GetOption('release')): + env.Append(CCFLAGS=['-O3', '-ftree-vectorize', '-funsafe-math-optimizations', '-ffast-math', '-fomit-frame-pointer', '-funsafe-loop-optimizations', '-Wunsafe-loop-optimizations']) + #env.Command(programName, sources, "gcc -o $TARGETS $SOURCES") + env.Decider('MD5') + env.Command(['generated/ElementClasses.cpp', 'generated/ElementClasses.h'], Glob('src/simulation/elements/*.cpp'), "python generator.py elements $TARGETS $SOURCES") + env.Command(['generated/ToolClasses.cpp', 'generated/ToolClasses.h'], Glob('src/simulation/tools/*.cpp'), "python generator.py tools $TARGETS $SOURCES") + t=env.MFProgram(target=programName, source=sources) + Default(t) +else: + env.Command(['generated/ElementClasses.cpp', 'generated/ElementClasses.h'], Glob('src/simulation/elements/*.cpp'), "python generator.py elements $TARGETS $SOURCES") + env.Command(['generated/ToolClasses.cpp', 'generated/ToolClasses.h'], Glob('src/simulation/tools/*.cpp'), "python generator.py tools $TARGETS $SOURCES") + env.Decider('MD5') + t=env.Program(target=programName, source=sources) + Default(t) #if(GetOption('release')): # StripExecutable(t); diff --git a/site_scons/site_tools/mfprogram/__init__.py b/site_scons/site_tools/mfprogram/__init__.py new file mode 100644 index 000000000..2d94ac29c --- /dev/null +++ b/site_scons/site_tools/mfprogram/__init__.py @@ -0,0 +1,236 @@ +# +# 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)) + _CPPINCFLAGS = ['-I' + i for i in _CPPPATH] + _CCOMCOM = '$CPPFLAGS $_CPPDEFFLAGS %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}) \ No newline at end of file diff --git a/site_scons/site_tools/mfprogram/__init__.pyc b/site_scons/site_tools/mfprogram/__init__.pyc new file mode 100644 index 000000000..edc9a1b4b Binary files /dev/null and b/site_scons/site_tools/mfprogram/__init__.pyc differ diff --git a/site_scons/site_tools/mfprogram/relpath.py b/site_scons/site_tools/mfprogram/relpath.py new file mode 100644 index 000000000..59e19710f --- /dev/null +++ b/site_scons/site_tools/mfprogram/relpath.py @@ -0,0 +1,73 @@ +#PYTHON SOFTWARE FOUNDATION LICENSE VERSION 2 +#-------------------------------------------- +# +#1. This LICENSE AGREEMENT is between the Python Software Foundation +#("PSF"), and the Individual or Organization ("Licensee") accessing and +#otherwise using this software ("Python") in source or binary form and +#its associated documentation. +# +#2. Subject to the terms and conditions of this License Agreement, PSF hereby +#grants Licensee a nonexclusive, royalty-free, world-wide license to reproduce, +#analyze, test, perform and/or display publicly, prepare derivative works, +#distribute, and otherwise use Python alone or in any derivative version, +#provided, however, that PSF's License Agreement and PSF's notice of copyright, +#i.e., "Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010 +#Python Software Foundation; All Rights Reserved" are retained in Python alone or +#in any derivative version prepared by Licensee. +# +#3. In the event Licensee prepares a derivative work that is based on +#or incorporates Python or any part thereof, and wants to make +#the derivative work available to others as provided herein, then +#Licensee hereby agrees to include in any such work a brief summary of +#the changes made to Python. +# +#4. PSF is making Python available to Licensee on an "AS IS" +#basis. PSF MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR +#IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, PSF MAKES NO AND +#DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS +#FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON WILL NOT +#INFRINGE ANY THIRD PARTY RIGHTS. +# +#5. PSF SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON +#FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS +#A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON, +#OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF. +# +#6. This License Agreement will automatically terminate upon a material +#breach of its terms and conditions. +# +#7. Nothing in this License Agreement shall be deemed to create any +#relationship of agency, partnership, or joint venture between PSF and +#Licensee. This License Agreement does not grant permission to use PSF +#trademarks or trade name in a trademark sense to endorse or promote +#products or services of Licensee, or any third party. +# +#8. By copying, installing or otherwise using Python, Licensee +#agrees to be bound by the terms and conditions of this License +#Agreement. + + + +# Changelog: +# 3-12-2010 Alan Somers Copied verbatim from posixpath.py in Python 2.6.4 + + + +from os.path import * + +def relpath(path, start=curdir): + """Return a relative version of a path""" + + if not path: + raise ValueError("no path specified") + + start_list = abspath(start).split(sep) + path_list = abspath(path).split(sep) + + # Work out how much of the filepath is shared by start and path. + i = len(commonprefix([start_list, path_list])) + + rel_list = [pardir] * (len(start_list)-i) + path_list[i:] + if not rel_list: + return curdir + return join(*rel_list) \ No newline at end of file