Fix how BUILDERS are updated.
authorstevenknight <stevenknight@fdb21ef1-2011-0410-befe-b5e4ea1792b1>
Sat, 25 Jan 2003 05:49:32 +0000 (05:49 +0000)
committerstevenknight <stevenknight@fdb21ef1-2011-0410-befe-b5e4ea1792b1>
Sat, 25 Jan 2003 05:49:32 +0000 (05:49 +0000)
git-svn-id: http://scons.tigris.org/svn/scons/trunk@563 fdb21ef1-2011-0410-befe-b5e4ea1792b1

doc/man/scons.1
src/CHANGES.txt
src/RELEASE.txt
src/engine/SCons/Environment.py
src/engine/SCons/EnvironmentTests.py
test/Alias-scanner.py

index c10b7295faeec44aa096e4a8a28ad86b24565def..b196fac45429a9e1ef3e1e6e54fb80a246e83649 100644 (file)
@@ -1517,6 +1517,23 @@ to underlying Builder objects.
 Builders named
 Alias, CFile, CXXFile, DVI, Library, Object, PDF, PostScript, and Program
 are available by default.
+If you initialize this variable when an
+Environment is created:
+.ES
+env = Environment(BUILDERS = {'NewBuilder' : foo})
+.EE
+the default Builders will no longer be available.
+To use a new Builder object in addition to the default Builders,
+add your new Builder object like this:
+.ES
+env = Environment()
+env.Append(BUILDERS = {'NewBuilder' : foo})
+.EE
+or this:
+.ES
+env = Environment()
+env['BUILDERS]['NewBuilder'] = foo
+.EE
 
 .IP CC 
 The C compiler.
@@ -3576,6 +3593,32 @@ env.PDFBuilder(target = 'foo.pdf', source = 'foo.tex')
 env.PDFBuilder(target = 'bar', source = 'bar')
 .EE
 
+Note also that the above initialization
+overwrites the default Builder objects,
+so the Environment created above
+can not be used call Builders like env.Program(),
+env.Object(), env.StaticLibrary(), etc.
+
+.SS Adding Your Own Builder Object to an Environment
+
+.ES
+bld = Builder(action = 'pdftex < $SOURCES > $TARGET'
+              suffix = '.pdf',
+              src_suffix = '.tex')
+env = Environment()
+env.Append(BUILDERS = {'PDFBuilder' : bld})
+env.PDFBuilder(target = 'foo.pdf', source = 'foo.tex')
+env.Program(target = 'bar', source = 'bar.c')
+.EE
+
+You also can use other Pythonic techniques to add
+to the BUILDERS construction variable, such as:
+
+.ES
+env = Environment()
+env.['BUILDERS]['PDFBuilder'] = bld
+.EE
+
 .SS Defining Your Own Scanner Object
 
 .ES
index 1f5a6d514511c4780666f80540b6b9b44e49f33a..391ddf8836d5a178ea9b0501f76cce9b69095c57 100644 (file)
@@ -32,6 +32,13 @@ RELEASE 0.11 - XXX
   - Doc changes:  Eliminate description of deprecated "name" keyword
     argument from Builder definition (reported by Gary Ruben).
 
+  - Support using env.Append() on BUILDERS (and other dictionaries).
+    (Bug reported by Bj=F6rn Bylander.)
+
+  - Setting the BUILDERS construction variable now properly clears
+    the previous Builder attributes from the construction Environment.
+    (Bug reported by Bj=F6rn Bylander.)
+
   From Anthony Roach:
 
   - Use a different static object suffix (.os) when using gcc so shared
index d6e145ed2499d8bda6ce59e10a4ffa07df0c21ab..cc3adc8b8dd1b3381e50d1cf56d7006c6513a0aa 100644 (file)
@@ -37,6 +37,25 @@ RELEASE 0.10 - Thu, 16 Jan 2003 04:11:46 -0600
 
         env = Environment(SHOBJSUFFIX = '.o')
 
+  - Setting the BUILDERS construction variable now properly clears
+    the previous Builder attributes from the construction Environment.
+    Before, you could initialize BUILDERS like so:
+
+        env = Environment(BUILDERS = {'NewBuilder' : foo})
+
+    And still use the canned default Builders like env.Program(),
+    env.Object(), env.StaticLibrary(), etc.  No more.  Beginning with
+    SCons 0.10, an initialization like that above will create an
+    Environment with only the env.NewBuilder() Builder.
+
+    So now, if you want to use a new builder in addition to the default
+    builders, you should explicitly append your new builder to the
+    existing ones using techniques like the following:
+
+        env.Append(BUILDERS = {'NewBuilder' : foo})
+
+        env['BUILDERS']['newbuilder'] = foo
+
   Please note the following important changes since release 0.09:
 
     - The Scanner interface has been changed to make it easier to
index 7609a72c2bd25d1407d7a8a2098d58098875e1db..f9448bd3dd1d061e064faa02578d36058da748ab 100644 (file)
@@ -67,28 +67,69 @@ def our_deepcopy(x):
        copy = x
    return copy
 
+class BuilderWrapper:
+    """Wrapper class that associates an environment with a Builder at
+    instantiation."""
+    def __init__(self, env, builder):
+        self.env = env
+        self.builder = builder
+
+    def __call__(self, *args, **kw):
+        return apply(self.builder, (self.env,) + args, kw)
+
+    # This allows a Builder to be executed directly
+    # through the Environment to which it's attached.
+    # In practice, we shouldn't need this, because
+    # builders actually get executed through a Node.
+    # But we do have a unit test for this, and can't
+    # yet rule out that it would be useful in the
+    # future, so leave it for now.
+    def execute(self, **kw):
+        kw['env'] = self.env
+        apply(self.builder.execute, (), kw)
+
 class BuilderDict(UserDict):
-    """This is a dictionary-like class used by Environment
-    to hold Builders.  We need to do this, because every time
-    someone changes the Builders in the Environment's BUILDERS
-    dictionary, we need to update the Environment's attributes."""
-    def setEnvironment(self, env):
+    """This is a dictionary-like class used by an Environment to hold
+    the Builders.  We need to do this because every time someone changes
+    the Builders in the Environment's BUILDERS dictionary, we must
+    update the Environment's attributes."""
+    def __init__(self, dict, env):
+        # Set self.env before calling the superclass initialization,
+        # because it will end up calling our other methods, which will
+        # need to point the values in this dictionary to self.env.
         self.env = env
-        
+        UserDict.__init__(self, dict)
+
     def __setitem__(self, item, val):
         UserDict.__setitem__(self, item, val)
         try:
-            self.env.Replace() # re-compute Builders
+            self.setenvattr(item, val)
         except AttributeError:
-            # Have to catch this because sometimes
-            # __setitem__ gets called out of __init__, when
-            # we don't have an env attribute yet, nor do
-            # we want one!
+            # Have to catch this because sometimes __setitem__ gets
+            # called out of __init__, when we don't have an env
+            # attribute yet, nor do we want one!
             pass
 
+    def setenvattr(self, item, val):
+        """Set the corresponding environment attribute for this Builder.
+
+        If the value is already a BuilderWrapper, we pull the builder
+        out of it and make another one, so that making a copy of an
+        existing BuilderDict is guaranteed separate wrappers for each
+        Builder + Environment pair."""
+        try:
+            builder = val.builder
+        except AttributeError:
+            builder = val
+        setattr(self.env, item, BuilderWrapper(self.env, builder))
+
     def __delitem__(self, item):
         UserDict.__delitem__(self, item)
-        self.env.Replace()
+        delattr(self.env, item)
+
+    def update(self, dict):
+        for i, v in dict.items():
+            self.__setitem__(i, v)
 
 _rm = re.compile(r'\$[()]')
 
@@ -110,6 +151,8 @@ class Environment:
         self.fs = SCons.Node.FS.default_fs
         self._dict = our_deepcopy(SCons.Defaults.ConstructionEnvironment)
 
+        self._dict['BUILDERS'] = BuilderDict(self._dict['BUILDERS'], self)
+
         if SCons.Util.is_String(platform):
             platform = SCons.Platform.Platform(platform)
         platform(self)
@@ -147,17 +190,22 @@ class Environment:
        pass    # XXX
 
     def Copy(self, **kw):
-       """Return a copy of a construction Environment.  The
-       copy is like a Python "deep copy"--that is, independent
-       copies are made recursively of each objects--except that
-       a reference is copied when an object is not deep-copyable
-       (like a function).  There are no references to any mutable
-       objects in the original Environment.
-       """
+        """Return a copy of a construction Environment.  The
+        copy is like a Python "deep copy"--that is, independent
+        copies are made recursively of each objects--except that
+        a reference is copied when an object is not deep-copyable
+        (like a function).  There are no references to any mutable
+        objects in the original Environment.
+        """
         clone = copy.copy(self)
         clone._dict = our_deepcopy(self._dict)
+        try:
+            cbd = clone._dict['BUILDERS']
+            clone._dict['BUILDERS'] = BuilderDict(cbd, clone)
+        except KeyError:
+            pass
         apply(clone.Replace, (), kw)
-       return clone
+        return clone
 
     def Scanners(self):
        pass    # XXX
@@ -166,64 +214,18 @@ class Environment:
         """A deprecated synonym for Replace().
         """
         apply(self.Replace, (), kw)
-
-    def __updateBuildersAndScanners(self):
-        """Update attributes for builders and scanners.
-
-        Have to be careful in this function...we can't
-        call functions like __setitem__() or Replace(), or
-        we will have infinite recursion."""
-        
-        if self._dict.has_key('SCANNERS') and \
-           not SCons.Util.is_List(self._dict['SCANNERS']):
-            self._dict['SCANNERS'] = [self._dict['SCANNERS']]
-
-        class BuilderWrapper:
-            """Wrapper class that allows an environment to
-            be associated with a Builder at instantiation.
-            """
-            def __init__(self, env, builder):
-                self.env = env
-                self.builder = builder
-
-            def __call__(self, *args, **kw):
-                return apply(self.builder, (self.env,) + args, kw)
-
-            # This allows a Builder to be executed directly
-            # through the Environment to which it's attached.
-            # In practice, we shouldn't need this, because
-            # builders actually get executed through a Node.
-            # But we do have a unit test for this, and can't
-            # yet rule out that it would be useful in the
-            # future, so leave it for now.
-            def execute(self, **kw):
-                kw['env'] = self.env
-                apply(self.builder.execute, (), kw)
-
-        if self._dict.has_key('BUILDERS'):
-            if SCons.Util.is_Dict(self._dict['BUILDERS']):
-                bd = self._dict['BUILDERS']
-                if not isinstance(bd, BuilderDict):
-                    # Convert it to a BuilderDict.  This class
-                    # Updates our builder attributes every time
-                    # someone changes it.
-                    bd = BuilderDict(bd)
-                    bd.setEnvironment(self)
-                    self._dict['BUILDERS'] = bd
-                for name, builder in bd.items():
-                    setattr(self, name, BuilderWrapper(self, builder))
-            else:
-                raise SCons.Errors.UserError, "The use of the BUILDERS Environment variable as a list or Builder instance is deprecated.  BUILDERS should be a dictionary of name->Builder instead."
-        for s in self._dict['SCANNERS']:
-            setattr(self, s.name, s)
         
-
     def Replace(self, **kw):
         """Replace existing construction variables in an Environment
         with new construction variables and/or values.
         """
+        try:
+            kwbd = our_deepcopy(kw['BUILDERS'])
+            del kw['BUILDERS']
+            self.__setitem__('BUILDERS', kwbd)
+        except KeyError:
+            pass
         self._dict.update(our_deepcopy(kw))
-        self.__updateBuildersAndScanners()
 
     def Append(self, **kw):
         """Append values to existing construction variables
@@ -239,9 +241,11 @@ class Environment:
             elif SCons.Util.is_List(kw[key]) and not \
                  SCons.Util.is_List(self._dict[key]):
                 self._dict[key] = [ self._dict[key] ] + kw[key]
+            elif SCons.Util.is_Dict(self._dict[key]) and \
+                 SCons.Util.is_Dict(kw[key]):
+                self._dict[key].update(kw[key])
             else:
                 self._dict[key] = self._dict[key] + kw[key]
-        self.__updateBuildersAndScanners()
 
     def Prepend(self, **kw):
         """Prepend values to existing construction variables
@@ -257,9 +261,11 @@ class Environment:
             elif SCons.Util.is_List(kw[key]) and not \
                  SCons.Util.is_List(self._dict[key]):
                 self._dict[key] = kw[key] + [ self._dict[key] ]
+            elif SCons.Util.is_Dict(self._dict[key]) and \
+                 SCons.Util.is_Dict(kw[key]):
+                self._dict[key].update(kw[key])
             else:
                 self._dict[key] = kw[key] + self._dict[key]
-        self.__updateBuildersAndScanners()
 
     def        Depends(self, target, dependency):
        """Explicity specify that 'target's depend on 'dependency'."""
@@ -304,8 +310,16 @@ class Environment:
        return dlist
 
     def __setitem__(self, key, value):
-        self._dict[key] = value
-        self.__updateBuildersAndScanners()
+        if key == 'BUILDERS':
+            try:
+                bd = self._dict[key]
+                for k in bd.keys():
+                    del bd[k]
+            except KeyError:
+                self._dict[key] = BuilderDict(kwbd, self)
+            self._dict[key].update(value)
+        else:
+            self._dict[key] = value
 
     def __getitem__(self, key):
         return self._dict[key]
index a93b08fe2cb69f167cb6f1fc51844cfe0d1bb881..657f190340ae7e0e81dab4e2b066525a349e0ced 100644 (file)
@@ -135,7 +135,6 @@ class EnvironmentTestCase(unittest.TestCase):
         assert not called_it.has_key('target')
         assert not called_it.has_key('source')
 
-
     def test_Builder_execs(self):
        """Test Builder execution through different environments
 
@@ -160,8 +159,8 @@ class EnvironmentTestCase(unittest.TestCase):
        assert built_it['out3']
 
         env4 = env3.Copy()
-        assert env4.builder1.env is env4
-        assert env4.builder2.env is env4
+        assert env4.builder1.env is env4, "builder1.env (%s) == env3 (%s)?" % (env4.builder1.env, env3)
+        assert env4.builder2.env is env4, "builder2.env (%s) == env3 (%s)?" % (env4.builder1.env, env3)
 
         # Now test BUILDERS as a dictionary.
         built_it = {}
@@ -188,37 +187,37 @@ class EnvironmentTestCase(unittest.TestCase):
         Scanner object, one with a list of a single Scanner
         object, and one with a list of two Scanner objects.
         """
-        global scanned_it
-
-       s1 = Scanner(name = 'scanner1', skeys = [".c", ".cc"])
-       s2 = Scanner(name = 'scanner2', skeys = [".m4"])
-
-       scanned_it = {}
-       env1 = Environment(SCANNERS = s1)
-        env1.scanner1(filename = 'out1')
-       assert scanned_it['out1']
-
-       scanned_it = {}
-       env2 = Environment(SCANNERS = [s1])
-        env1.scanner1(filename = 'out1')
-       assert scanned_it['out1']
-
-       scanned_it = {}
-        env3 = Environment()
-        env3.Replace(SCANNERS = [s1, s2])
-        env3.scanner1(filename = 'out1')
-        env3.scanner2(filename = 'out2')
-        env3.scanner1(filename = 'out3')
-       assert scanned_it['out1']
-       assert scanned_it['out2']
-       assert scanned_it['out3']
-
-       s = env3.get_scanner(".c")
-       assert s == s1, s
-       s = env3.get_scanner(skey=".m4")
-       assert s == s2, s
-       s = env3.get_scanner(".cxx")
-       assert s == None, s
+#        global scanned_it
+#
+#      s1 = Scanner(name = 'scanner1', skeys = [".c", ".cc"])
+#      s2 = Scanner(name = 'scanner2', skeys = [".m4"])
+#
+#      scanned_it = {}
+#      env1 = Environment(SCANNERS = s1)
+#        env1.scanner1(filename = 'out1')
+#      assert scanned_it['out1']
+#
+#      scanned_it = {}
+#      env2 = Environment(SCANNERS = [s1])
+#        env1.scanner1(filename = 'out1')
+#      assert scanned_it['out1']
+#
+#      scanned_it = {}
+#        env3 = Environment()
+#        env3.Replace(SCANNERS = [s1, s2])
+#        env3.scanner1(filename = 'out1')
+#        env3.scanner2(filename = 'out2')
+#        env3.scanner1(filename = 'out3')
+#      assert scanned_it['out1']
+#      assert scanned_it['out2']
+#      assert scanned_it['out3']
+#
+#      s = env3.get_scanner(".c")
+#      assert s == s1, s
+#      s = env3.get_scanner(skey=".m4")
+#      assert s == s2, s
+#      s = env3.get_scanner(".cxx")
+#      assert s == None, s
 
     def test_Copy(self):
        """Test construction Environment copying
@@ -258,6 +257,17 @@ class EnvironmentTestCase(unittest.TestCase):
         assert env2.Dictionary('ZZZ').has_key(5)
         assert not env1.Dictionary('ZZZ').has_key(5)
 
+        #
+        env1 = Environment(BUILDERS = {'b1' : 1})
+        assert hasattr(env1, 'b1'), "env1.b1 was not set"
+        assert env1.b1.env == env1, "b1.env doesn't point to env1"
+        env2 = env1.Copy(BUILDERS = {'b2' : 2})
+        assert hasattr(env1, 'b1'), "b1 was mistakenly cleared from env1"
+        assert env1.b1.env == env1, "b1.env was changed"
+        assert not hasattr(env2, 'b1'), "b1 was not cleared from env2"
+        assert hasattr(env2, 'b2'), "env2.b2 was not set"
+        assert env2.b2.env == env2, "b2.env doesn't point to env2"
+
     def test_Dictionary(self):
        """Test retrieval of known construction variables
 
@@ -329,9 +339,16 @@ class EnvironmentTestCase(unittest.TestCase):
         """
         env1 = Environment(AAA = 'a', BBB = 'b')
         env1.Replace(BBB = 'bbb', CCC = 'ccc')
+
         env2 = Environment(AAA = 'a', BBB = 'bbb', CCC = 'ccc')
         assert env1 == env2, diff_env(env1, env2)
 
+        env3 = Environment(BUILDERS = {'b1' : 1})
+        assert hasattr(env3, 'b1'), "b1 was not set"
+        env3.Replace(BUILDERS = {'b2' : 2})
+        assert not hasattr(env3, 'b1'), "b1 was not cleared"
+        assert hasattr(env3, 'b2'), "b2 was not set"
+
     def test_Append(self):
         """Test appending to construction variables in an Environment
         """
@@ -352,13 +369,16 @@ class EnvironmentTestCase(unittest.TestCase):
                            KKK = UL(['k', 'K']), LLL = UL(['l', 'L']))
         assert env1 == env2, diff_env(env1, env2)
 
-        env3 = Environment(X = {'x' : 7})
-        try:
-            env3.Append(X = {'x' : 8})
-        except TypeError:
-            pass
-        except:
-            raise
+        env3 = Environment(X = {'x1' : 7})
+        env3.Append(X = {'x1' : 8, 'x2' : 9}, Y = {'y1' : 10})
+        assert env3['X'] == {'x1': 8, 'x2': 9}, env3['X']
+        assert env3['Y'] == {'y1': 10}, env3['Y']
+
+        env4 = Environment(BUILDERS = {'z1' : 11})
+        env4.Append(BUILDERS = {'z2' : 12})
+        assert env4['BUILDERS'] == {'z1' : 11, 'z2' : 12}, env4['BUILDERS']
+        assert hasattr(env4, 'z1')
+        assert hasattr(env4, 'z2')
 
     def test_Prepend(self):
         """Test prepending to construction variables in an Environment
@@ -380,13 +400,16 @@ class EnvironmentTestCase(unittest.TestCase):
                            KKK = UL(['K', 'k']), LLL = UL(['L', 'l']))
         assert env1 == env2, diff_env(env1, env2)
 
-        env3 = Environment(X = {'x' : 7})
-        try:
-            env3.Prepend(X = {'x' : 8})
-        except TypeError:
-            pass
-        except:
-            raise
+        env3 = Environment(X = {'x1' : 7})
+        env3.Prepend(X = {'x1' : 8, 'x2' : 9}, Y = {'y1' : 10})
+        assert env3['X'] == {'x1': 8, 'x2' : 9}, env3['X']
+        assert env3['Y'] == {'y1': 10}, env3['Y']
+
+        env4 = Environment(BUILDERS = {'z1' : 11})
+        env4.Prepend(BUILDERS = {'z2' : 12})
+        assert env4['BUILDERS'] == {'z1' : 11, 'z2' : 12}, env4['BUILDERS']
+        assert hasattr(env4, 'z1')
+        assert hasattr(env4, 'z2')
 
     def test_Depends(self):
        """Test the explicit Depends method."""
index 3b0ffa0cb82a3e125ec63551c69a0a62bfcb7df3..3ce14dde62780009c6b596874982374aae0f9f4a 100644 (file)
@@ -45,7 +45,8 @@ def cat(env, source, target):
     f.close()
 
 XBuilder = Builder(action = cat, src_suffix = '.x', suffix = '.c')
-env = Environment(BUILDERS = { 'XBuilder': XBuilder })
+env = Environment()
+env.Append(BUILDERS = { 'XBuilder': XBuilder })
 f = env.XBuilder(source = ['file.x'], target = ['file.c'])
 env.Alias(target = ['cfiles'], source = f)
 Default(['cfiles'])