Fix str(Node.FS) in an SConscript file, and add a separate src_dir argument to SConsc...
authorstevenknight <stevenknight@fdb21ef1-2011-0410-befe-b5e4ea1792b1>
Wed, 26 Feb 2003 17:40:22 +0000 (17:40 +0000)
committerstevenknight <stevenknight@fdb21ef1-2011-0410-befe-b5e4ea1792b1>
Wed, 26 Feb 2003 17:40:22 +0000 (17:40 +0000)
git-svn-id: http://scons.tigris.org/svn/scons/trunk@601 fdb21ef1-2011-0410-befe-b5e4ea1792b1

doc/man/scons.1
src/CHANGES.txt
src/engine/SCons/Node/FS.py
src/engine/SCons/Node/FSTests.py
src/engine/SCons/Scanner/CTests.py
src/engine/SCons/Scanner/FortranTests.py
src/engine/SCons/Script/SConscript.py
src/engine/SCons/Script/__init__.py
test/SConscript-build_dir.py
test/scan-once.py

index dd4b548dfcb964e66cd2a0db99dd472a19339e2a..0bfb13b4c078819b3dc8e6f597931741c825020d 100644 (file)
@@ -3281,7 +3281,7 @@ Return(["foo", "bar"])
 .EE
 
 .TP
-.RI SConscript( script ", [" exports ", " build_dir ", " duplicate ])
+.RI SConscript( script ", [" exports ", " build_dir ", " src_dir ", " duplicate ])
 This tells
 .B scons
 to execute
@@ -3304,6 +3304,13 @@ that would normally be built in the subdirectory in which
 resides should actually
 be built in
 .IR build_dir .
+The optional
+.I src_dir
+argument specifies that the
+source files from which
+the target files should be built
+can be found in
+.IR src_dir .
 By default,
 .B scons
 will link or copy (depending on the platform)
index a9133147ffec8c679df9abf2821c4cf8e45313bf..ccdf1595e564e016ad8e8ec525417f3c68327929 100644 (file)
@@ -14,6 +14,13 @@ RELEASE 0.12 - XXX
 
   - Added support for the Perforce source code management system.
 
+  - Fix str(Node.FS) so that it returns a path relative to the calling
+    SConscript file's directory, not the top-level directory.
+
+  - Added support for a separate src_dir argument to SConscript()
+    that allows explicit specification of where the source files
+    for an SConscript file can be found.
+
   From Steven Knight:
 
   - Added an INSTALL construction variable that can be set to a function
index 8d5a45f3127e3e13d415be6dcda5a6ce591d4582..a6dd7ae648c618ccaa659d6a8cffce2b5761285d 100644 (file)
@@ -168,6 +168,9 @@ class ParentOfRoot:
     def get_dir(self):
         return None
 
+    def recurse_get_path(self, dir, path_elems):
+        return path_elems
+
     def src_builder(self):
         return None
 
@@ -201,6 +204,7 @@ class Entry(SCons.Node.Node):
 
         self.name = name
         self.fs = fs
+        self.relpath = {}
 
         assert directory, "A directory must be provided"
 
@@ -222,8 +226,8 @@ class Entry(SCons.Node.Node):
     def __str__(self):
         """A FS node's string representation is its path name."""
         if self.duplicate or self.has_builder():
-            return self.path
-        return self.srcnode().path
+            return self.get_path()
+        return self.srcnode().get_path()
 
     def get_contents(self):
         """Fetch the contents of the entry.
@@ -297,6 +301,32 @@ class Entry(SCons.Node.Node):
             self._srcnode = self
             return self._srcnode
 
+    def recurse_get_path(self, dir, path_elems):
+        """Recursively build a path relative to a supplied directory
+        node."""
+        if self != dir:
+            path_elems.append(self.name)
+            path_elems = self.dir.recurse_get_path(dir, path_elems)
+        return path_elems
+
+    def get_path(self, dir=None):
+        """Return path relative to the current working directory of the
+        FS object that owns us."""
+        if not dir:
+            dir = self.fs.getcwd()
+        try:
+            return self.relpath[dir]
+        except KeyError:
+            if self == dir:
+                # Special case, return "." as the path
+                ret = '.'
+            else:
+                path_elems = self.recurse_get_path(dir, [])
+                path_elems.reverse()
+                ret = string.join(path_elems, os.sep)
+            self.relpath[dir] = ret
+            return ret
+            
     def set_src_builder(self, builder):
         """Set the source code builder for this node."""
         self.sbuilder = builder
@@ -474,12 +504,16 @@ class FS:
             directory = self._cwd
         return (os.path.normpath(name), directory)
 
-    def chdir(self, dir):
+    def chdir(self, dir, change_os_dir=0):
         """Change the current working directory for lookups.
+        If change_os_dir is true, we will also change the "real" cwd
+        to match.
         """
         self.__setTopLevelDir()
         if not dir is None:
             self._cwd = dir
+            if change_os_dir:
+                os.chdir(dir.abspath)
 
     def Entry(self, name, directory = None, create = 1, klass=None):
         """Lookup or create a generic Entry node with the specified name.
@@ -863,11 +897,11 @@ class File(Entry):
     def get_contents(self):
         if not self.rexists():
             return ''
-        return open(self.rstr(), "rb").read()
+        return open(self.rfile().abspath, "rb").read()
 
     def get_timestamp(self):
         if self.rexists():
-            return os.path.getmtime(self.rstr())
+            return os.path.getmtime(self.rfile().abspath)
         else:
             return 0
 
index 1e8bc922755c4943734883e703683d68cf7cbeb4..5e4bb528d25a8fd79cb28f7264d7ba015c82342d 100644 (file)
@@ -190,8 +190,8 @@ class BuildDirTestCase(unittest.TestCase):
         assert str(f1) == os.path.normpath('src/test.in'), str(f1)
         # Build path does not exist
         assert not f1.exists()
-        # But source path does
-        assert f1.srcnode().exists()
+        # ...but the actual file is not there...
+        assert not os.path.exists(f1.abspath)
         # And duplicate=0 should also work just like a Repository
         assert f1.rexists()
         # rfile() should point to the source path
@@ -270,6 +270,16 @@ class BuildDirTestCase(unittest.TestCase):
         # Verify the Mkdir and Link actions are called
         f9 = fs.File('build/var2/new_dir/test9.out')
 
+        # Test for an interesting pathological case...we have a source
+        # file in a build path, but not in a source path.  This can
+        # happen if you switch from duplicate=1 to duplicate=0, then
+        # delete a source file.  At one time, this would cause exists()
+        # to return a 1 but get_contents() to throw.
+        test.write([ 'work', 'build', 'var1', 'asourcefile' ], 'stuff')
+        f10 = fs.File('build/var1/asourcefile')
+        assert f10.exists()
+        assert f10.get_contents() == 'stuff', f10.get_contents()
+
         save_Mkdir = SCons.Node.FS.Mkdir
         dir_made = []
         def mkdir_func(target, source, env, dir_made=dir_made):
@@ -801,9 +811,11 @@ class FSTestCase(unittest.TestCase):
         fs = SCons.Node.FS.FS()
         assert str(fs.getcwd()) == ".", str(fs.getcwd())
         fs.chdir(fs.Dir('subdir'))
-        assert str(fs.getcwd()) == "subdir", str(fs.getcwd())
+        # The cwd's path is always "."
+        assert str(fs.getcwd()) == ".", str(fs.getcwd())
+        assert fs.getcwd().path == 'subdir', fs.getcwd().path
         fs.chdir(fs.Dir('../..'))
-        assert str(fs.getcwd()) == test.workdir, str(fs.getcwd())
+        assert fs.getcwd().path == test.workdir, fs.getcwd().path
         
         f1 = fs.File(test.workpath("do_i_exist"))
         assert not f1.exists()
index 332f7e134e8362912a95272410f07868e34e838a..c865e6e22a4121b9792ef7241d2259d3bafaef50 100644 (file)
@@ -324,6 +324,7 @@ class CScannerTestCase10(unittest.TestCase):
         path = s.path(env)
         test.write('include/fa.cpp', test.read('fa.cpp'))
         deps = s(fs.File('#include/fa.cpp'), env, path)
+        fs.chdir(fs.Dir('..'))
         deps_match(self, deps, [ 'include/fa.h', 'include/fb.h' ])
         test.unlink('include/fa.cpp')
 
index b2cf14cd28ea7879b655099b7f1b2c52616b329d..7564c1e2b804253e039757c212b9ea99cf56daa4 100644 (file)
@@ -338,6 +338,7 @@ class FortranScannerTestCase12(unittest.TestCase):
         path = s.path(env)
         test.write('include/fff4.f', test.read('fff4.f'))
         deps = s(fs.File('#include/fff4.f'), env, path)
+        fs.chdir(fs.Dir('..'))
         deps_match(self, deps, ['include/f4.f'])
         test.unlink('include/fff4.f')
 
index 4c54879f1389b093a29154a3952fdec3a5fb8121..dfcfe5b7d4c4868b5ed148681e68bcc193bd47ac 100644 (file)
@@ -163,7 +163,17 @@ def GetSConscriptFilenames(ls, kw):
         if not src_dir:
             src_dir, fname = os.path.split(str(files[0]))
         else:
-            fname = os.path.split(files[0])[1]
+            if not isinstance(src_dir, SCons.Node.Node):
+                src_dir = SCons.Node.FS.default_fs.Dir(src_dir)
+            fn = files[0]
+            if not isinstance(fn, SCons.Node.Node):
+                fn = SCons.Node.FS.default_fs.File(fn)
+            if fn.is_under(src_dir):
+                # Get path relative to the source directory.
+                fname = fn.get_path(src_dir)
+            else:
+                # Fast way to only get the terminal path component of a Node.
+                fname = fn.get_path(fn.dir)
         BuildDir(build_dir, src_dir, duplicate)
         files = [os.path.join(str(build_dir), fname)]
 
@@ -187,7 +197,13 @@ def SConscript(*ls, **kw):
                 else:
                     f = SCons.Node.FS.default_fs.File(str(fn))
                 _file_ = None
+                old_dir = SCons.Node.FS.default_fs.getcwd()
+                SCons.Node.FS.default_fs.chdir(SCons.Node.FS.default_fs.Dir('#'),
+                                               change_os_dir=1)
                 if f.rexists():
+                    # Change directory to top of source tree to make sure
+                    # the os's cwd and the cwd of SCons.Node.FS.default_fs
+                    # match so we can open the SConscript.
                     _file_ = open(f.rstr(), "r")
                 elif f.has_builder():
                     # The SConscript file apparently exists in a source
@@ -199,17 +215,13 @@ def SConscript(*ls, **kw):
                     s = str(f)
                     if os.path.exists(s):
                         _file_ = open(s, "r")
-
                 if _file_:
-                    SCons.Node.FS.default_fs.chdir(f.dir)
-                    if sconscript_chdir:
-                        old_dir = os.getcwd()
-                        os.chdir(str(f.dir))
-
+                    SCons.Node.FS.default_fs.chdir(f.dir,
+                                                   change_os_dir=sconscript_chdir)
                     # prepend the SConscript directory to sys.path so
                     # that Python modules in the SConscript directory can
                     # be easily imported
-                    sys.path = [os.path.abspath(str(f.dir))] + sys.path
+                    sys.path = [ f.dir.abspath ] + sys.path
 
                     # This is the magic line that actually reads up and
                     # executes the stuff in the SConscript file.  We
@@ -227,7 +239,8 @@ def SConscript(*ls, **kw):
             frame = stack.pop()
             SCons.Node.FS.default_fs.chdir(frame.prev_dir)
             if old_dir:
-                os.chdir(old_dir)
+                SCons.Node.FS.default_fs.chdir(old_dir,
+                                               change_os_dir=sconscript_chdir)
 
             results.append(frame.retval)
 
@@ -390,11 +403,10 @@ def Clean(target, files):
         else:
             nodes.extend(SCons.Node.arg2nodes(f, SCons.Node.FS.default_fs.Entry))
 
-    s = str(target)
-    if clean_targets.has_key(s):
-        clean_targets[s].extend(nodes)
-    else:
-        clean_targets[s] = nodes
+    try:
+        clean_targets[target].extend(nodes)
+    except KeyError:
+        clean_targets[target] = nodes
 
 def AddPreAction(files, action):
     nodes = SCons.Node.arg2nodes(files, SCons.Node.FS.default_fs.Entry)
index 41b95dcccc5dc8f595bf164bc31201cd257b9fe0..3716d2ee3d6b42b2c5b096f917adbd1c0d642009 100644 (file)
@@ -153,8 +153,8 @@ class CleanTask(SCons.Taskmaster.Task):
         if (self.targets[0].has_builder() or self.targets[0].side_effect) \
            and not os.path.isdir(str(self.targets[0])):
             display("Removed " + str(self.targets[0]))
-        if SCons.Script.SConscript.clean_targets.has_key(str(self.targets[0])):
-            files = SCons.Script.SConscript.clean_targets[str(self.targets[0])]
+        if SCons.Script.SConscript.clean_targets.has_key(self.targets[0]):
+            files = SCons.Script.SConscript.clean_targets[self.targets[0]]
             for f in files:
                 SCons.Util.fs_delete(str(f), 0)
 
@@ -168,8 +168,8 @@ class CleanTask(SCons.Taskmaster.Task):
                 else:
                     if removed:
                         display("Removed " + str(t))
-        if SCons.Script.SConscript.clean_targets.has_key(str(self.targets[0])):
-            files = SCons.Script.SConscript.clean_targets[str(self.targets[0])]
+        if SCons.Script.SConscript.clean_targets.has_key(self.targets[0]):
+            files = SCons.Script.SConscript.clean_targets[self.targets[0]]
             for f in files:
                 SCons.Util.fs_delete(str(f))
 
index c0b92f39caed45acf48f087beaf97c1e51d2c669..165619a1148a1c0adc7db089e213a4ed39eb1501 100644 (file)
@@ -40,6 +40,7 @@ all5 = test.workpath('build', 'var5', 'all')
 all6 = test.workpath('build', 'var6', 'all')
 all7 = test.workpath('build', 'var7', 'all')
 all8 = test.workpath('build', 'var8', 'all')
+all9 = test.workpath('test', 'build', 'var9', 'src', 'all')
 
 test.subdir('test')
 
@@ -54,6 +55,7 @@ var5 = Dir('../build/var5')
 var6 = Dir('../build/var6')
 var7 = Dir('../build/var7')
 var8 = Dir('../build/var8')
+var9 = Dir('../build/var9')
 
 def cat(env, source, target):
     target = str(target[0])
@@ -83,6 +85,15 @@ SConscript('src/SConscript', build_dir=var6)
 
 SConscript('src/SConscript', build_dir=var7, src_dir=src, duplicate=0)
 SConscript('src/SConscript', build_dir='../build/var8', duplicate=0)
+
+# This tests the fact that if you specify a src_dir that is above
+# the dir a SConscript is in, that we do the intuitive thing, i.e.,
+# we set the path of the SConscript accordingly.  The below is
+# equivalent to saying:
+#
+# BuildDir('build/var9', '.')
+# SConscript('build/var9/src/SConscript')
+SConscript('src/SConscript', build_dir='build/var9', src_dir='.')
 """) 
 
 test.subdir(['test', 'src'], ['test', 'alt'])
@@ -120,6 +131,7 @@ test.fail_test(test.read(all3) != all_src)
 test.fail_test(test.read(all6) != all_src)
 test.fail_test(test.read(all7) != all_src)
 test.fail_test(test.read(all8) != all_src)
+test.fail_test(test.read(all9) != all_src)
 
 import os
 import stat
index 4355998f2e68e46607ea43a3bd84715d30ed9959..b9104384ba217b7ef43f3403905e205245fb016e 100644 (file)
@@ -332,7 +332,7 @@ Mylib.ExportLib(env, lib_fullname)
 #cmd_justlib = "cd %s ; make" % Dir(".")
 
 cmd_generated = "%s $SOURCE" % (sys.executable,)
-cmd_justlib = "%s %s -C %s" % (sys.executable, sys.argv[0], Dir("."))
+cmd_justlib = "%s %s -C ${SOURCE[0].dir}" % (sys.executable, sys.argv[0])
 
 ##### Deps appear correct ... but wacky scanning?
 # Why?