scons: make a flexible system for guarding source files
authorNathan Binkert <nate@binkert.org>
Fri, 15 Apr 2011 17:44:44 +0000 (10:44 -0700)
committerNathan Binkert <nate@binkert.org>
Fri, 15 Apr 2011 17:44:44 +0000 (10:44 -0700)
This is similar to guards on mercurial queues and they're used for selecting
which files are compiled into some given object.  We already do something
similar, but it's mostly hard coded for the m5 binary and the m5 library
and I'd like to make it more flexible to better support the unittests

src/SConscript
src/sim/SConscript

index 77dec89a9cfb421d77c381c590ca27bac04fbb75..52de673dcdf54d957100a60ef4b2178202350cbf 100755 (executable)
@@ -56,42 +56,92 @@ from m5.util import code_formatter
 ########################################################################
 # Code for adding source files of various types
 #
+# When specifying a source file of some type, a set of guards can be
+# specified for that file.  When get() is used to find the files, if
+# get specifies a set of filters, only files that match those filters
+# will be accepted (unspecified filters on files are assumed to be
+# false).  Current filters are:
+#     main -- specifies the m5 main() function
+#     skip_lib -- do not put this file into the m5 library
+#     <unittest> -- unit tests use filters based on the unit test name
+#
+# A parent can now be specified for a source file and default filter
+# values will be retrieved recursively from parents (children override
+# parents).
+#
 class SourceMeta(type):
+    '''Meta class for source files that keeps track of all files of a
+    particular type and has a get function for finding all functions
+    of a certain type that match a set of guards'''
     def __init__(cls, name, bases, dict):
         super(SourceMeta, cls).__init__(name, bases, dict)
         cls.all = []
         
-    def get(cls, **kwargs):
+    def get(cls, **guards):
+        '''Find all files that match the specified guards.  If a source
+        file does not specify a flag, the default is False'''
         for src in cls.all:
-            for attr,value in kwargs.iteritems():
-                if getattr(src, attr) != value:
+            for flag,value in guards.iteritems():
+                # if the flag is found and has a different value, skip
+                # this file
+                if src.all_guards.get(flag, False) != value:
                     break
             else:
                 yield src
 
 class SourceFile(object):
+    '''Base object that encapsulates the notion of a source file.
+    This includes, the source node, target node, various manipulations
+    of those.  A source file also specifies a set of guards which
+    describing which builds the source file applies to.  A parent can
+    also be specified to get default guards from'''
     __metaclass__ = SourceMeta
-    def __init__(self, source):
+    def __init__(self, source, parent=None, **guards):
+        self.guards = guards
+        self.parent = parent
+
         tnode = source
         if not isinstance(source, SCons.Node.FS.File):
             tnode = File(source)
 
         self.tnode = tnode
         self.snode = tnode.srcnode()
-        self.filename = str(tnode)
-        self.dirname = dirname(self.filename)
-        self.basename = basename(self.filename)
-        index = self.basename.rfind('.')
-        if index <= 0:
-            # dot files aren't extensions
-            self.extname = self.basename, None
-        else:
-            self.extname = self.basename[:index], self.basename[index+1:]
 
         for base in type(self).__mro__:
             if issubclass(base, SourceFile):
                 base.all.append(self)
 
+    @property
+    def filename(self):
+        return str(self.tnode)
+
+    @property
+    def dirname(self):
+        return dirname(self.filename)
+
+    @property
+    def basename(self):
+        return basename(self.filename)
+
+    @property
+    def extname(self):
+        index = self.basename.rfind('.')
+        if index <= 0:
+            # dot files aren't extensions
+            return self.basename, None
+
+        return self.basename[:index], self.basename[index+1:]
+
+    @property
+    def all_guards(self):
+        '''find all guards for this object getting default values
+        recursively from its parents'''
+        guards = {}
+        if self.parent:
+            guards.update(self.parent.guards)
+        guards.update(self.guards)
+        return guards
+
     def __lt__(self, other): return self.filename < other.filename
     def __le__(self, other): return self.filename <= other.filename
     def __gt__(self, other): return self.filename > other.filename
@@ -101,14 +151,12 @@ class SourceFile(object):
         
 class Source(SourceFile):
     '''Add a c/c++ source file to the build'''
-    def __init__(self, source, Werror=True, swig=False, bin_only=False,
-                 skip_lib=False):
-        super(Source, self).__init__(source)
+    def __init__(self, source, Werror=True, swig=False, **guards):
+        '''specify the source file, and any guards'''
+        super(Source, self).__init__(source, **guards)
 
         self.Werror = Werror
         self.swig = swig
-        self.bin_only = bin_only
-        self.skip_lib = bin_only or skip_lib
 
 class PySource(SourceFile):
     '''Add a python source file to the named package'''
@@ -117,8 +165,9 @@ class PySource(SourceFile):
     tnodes = {}
     symnames = {}
     
-    def __init__(self, package, source):
-        super(PySource, self).__init__(source)
+    def __init__(self, package, source, **guards):
+        '''specify the python package, the source file, and any guards'''
+        super(PySource, self).__init__(source, **guards)
 
         modname,ext = self.extname
         assert ext == 'py'
@@ -158,8 +207,10 @@ class SimObject(PySource):
     fixed = False
     modnames = []
 
-    def __init__(self, source):
-        super(SimObject, self).__init__('m5.objects', source)
+    def __init__(self, source, **guards):
+        '''Specify the source file and any guards (automatically in
+        the m5.objects package)'''
+        super(SimObject, self).__init__('m5.objects', source, **guards)
         if self.fixed:
             raise AttributeError, "Too late to call SimObject now."
 
@@ -168,8 +219,9 @@ class SimObject(PySource):
 class SwigSource(SourceFile):
     '''Add a swig file to build'''
 
-    def __init__(self, package, source):
-        super(SwigSource, self).__init__(source)
+    def __init__(self, package, source, **guards):
+        '''Specify the python package, the source file, and any guards'''
+        super(SwigSource, self).__init__(source, **guards)
 
         modname,ext = self.extname
         assert ext == 'i'
@@ -178,11 +230,13 @@ class SwigSource(SourceFile):
         cc_file = joinpath(self.dirname, modname + '_wrap.cc')
         py_file = joinpath(self.dirname, modname + '.py')
 
-        self.cc_source = Source(cc_file, swig=True)
-        self.py_source = PySource(package, py_file)
+        self.cc_source = Source(cc_file, swig=True, parent=self)
+        self.py_source = PySource(package, py_file, parent=self)
 
 unit_tests = []
 def UnitTest(target, sources):
+    '''Create a unit test, specify the target name and a source or
+    list of sources'''
     if not isinstance(sources, (list, tuple)):
         sources = [ sources ]
 
@@ -835,8 +889,9 @@ def makeEnv(label, objsfx, strip = False, **kwargs):
 
         return obj
 
-    static_objs = [ make_obj(s, True) for s in Source.get(skip_lib=False)]
-    shared_objs = [ make_obj(s, False) for s in Source.get(skip_lib=False)]
+    sources = Source.get(main=False, skip_lib=False)
+    static_objs = [ make_obj(s, True) for s in sources ]
+    shared_objs = [ make_obj(s, False) for s in sources ]
 
     static_date = make_obj(date_source, static=True, extra_deps=static_objs)
     static_objs.append(static_date)
@@ -854,12 +909,13 @@ def makeEnv(label, objsfx, strip = False, **kwargs):
         new_env.Program("unittest/%s.%s" % (target, label), objs + static_objs)
 
     # Now link a stub with main() and the static library.
-    bin_objs = [make_obj(s, True) for s in Source.get(bin_only=True) ]
+    main_objs = [ make_obj(s, True) for s in Source.get(main=True) ]
+
     progname = exename
     if strip:
         progname += '.unstripped'
 
-    targets = new_env.Program(progname, bin_objs + static_objs)
+    targets = new_env.Program(progname, main_objs + static_objs)
 
     if strip:
         if sys.platform == 'sunos5':
index 5c22e328ec68722fd31b447c909188390067fbb8..b3065374bbf7d9bf52bde5596b0dcc223d452dae 100644 (file)
@@ -39,7 +39,7 @@ Source('core.cc')
 Source('debug.cc')
 Source('eventq.cc')
 Source('init.cc')
-Source('main.cc', bin_only=True)
+Source('main.cc', main=True, skip_lib=True)
 Source('root.cc')
 Source('serialize.cc')
 Source('sim_events.cc')