From: stevenknight Date: Wed, 26 Feb 2003 17:40:22 +0000 (+0000) Subject: Fix str(Node.FS) in an SConscript file, and add a separate src_dir argument to SConsc... X-Git-Url: http://git.tremily.us/?a=commitdiff_plain;h=8042aef8918e65606c7268e5e3a28a8e32e334b0;p=scons.git Fix str(Node.FS) in an SConscript file, and add a separate src_dir argument to SConscript(). (Charles Crain) git-svn-id: http://scons.tigris.org/svn/scons/trunk@601 fdb21ef1-2011-0410-befe-b5e4ea1792b1 --- diff --git a/doc/man/scons.1 b/doc/man/scons.1 index dd4b548d..0bfb13b4 100644 --- a/doc/man/scons.1 +++ b/doc/man/scons.1 @@ -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) diff --git a/src/CHANGES.txt b/src/CHANGES.txt index a9133147..ccdf1595 100644 --- a/src/CHANGES.txt +++ b/src/CHANGES.txt @@ -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 diff --git a/src/engine/SCons/Node/FS.py b/src/engine/SCons/Node/FS.py index 8d5a45f3..a6dd7ae6 100644 --- a/src/engine/SCons/Node/FS.py +++ b/src/engine/SCons/Node/FS.py @@ -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 diff --git a/src/engine/SCons/Node/FSTests.py b/src/engine/SCons/Node/FSTests.py index 1e8bc922..5e4bb528 100644 --- a/src/engine/SCons/Node/FSTests.py +++ b/src/engine/SCons/Node/FSTests.py @@ -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() diff --git a/src/engine/SCons/Scanner/CTests.py b/src/engine/SCons/Scanner/CTests.py index 332f7e13..c865e6e2 100644 --- a/src/engine/SCons/Scanner/CTests.py +++ b/src/engine/SCons/Scanner/CTests.py @@ -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') diff --git a/src/engine/SCons/Scanner/FortranTests.py b/src/engine/SCons/Scanner/FortranTests.py index b2cf14cd..7564c1e2 100644 --- a/src/engine/SCons/Scanner/FortranTests.py +++ b/src/engine/SCons/Scanner/FortranTests.py @@ -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') diff --git a/src/engine/SCons/Script/SConscript.py b/src/engine/SCons/Script/SConscript.py index 4c54879f..dfcfe5b7 100644 --- a/src/engine/SCons/Script/SConscript.py +++ b/src/engine/SCons/Script/SConscript.py @@ -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) diff --git a/src/engine/SCons/Script/__init__.py b/src/engine/SCons/Script/__init__.py index 41b95dcc..3716d2ee 100644 --- a/src/engine/SCons/Script/__init__.py +++ b/src/engine/SCons/Script/__init__.py @@ -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)) diff --git a/test/SConscript-build_dir.py b/test/SConscript-build_dir.py index c0b92f39..165619a1 100644 --- a/test/SConscript-build_dir.py +++ b/test/SConscript-build_dir.py @@ -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 diff --git a/test/scan-once.py b/test/scan-once.py index 4355998f..b9104384 100644 --- a/test/scan-once.py +++ b/test/scan-once.py @@ -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?