diff options
author | Steven Knight <knight@baldmt.com> | 2001-12-15 00:23:46 (GMT) |
---|---|---|
committer | Steven Knight <knight@baldmt.com> | 2001-12-15 00:23:46 (GMT) |
commit | af3987ad4663708a4f9d10b661971fd7b29a9f0d (patch) | |
tree | 9de48645f9d229266078ede22a78fcd740f705b6 /src | |
parent | 0d14a2ff1774d42dc855aa9aab94791df3f55157 (diff) | |
download | SCons-af3987ad4663708a4f9d10b661971fd7b29a9f0d.zip SCons-af3987ad4663708a4f9d10b661971fd7b29a9f0d.tar.gz SCons-af3987ad4663708a4f9d10b661971fd7b29a9f0d.tar.bz2 |
Add BuildDir(), Export(), and Install() functionality (courtesy Charles Crain).
Diffstat (limited to 'src')
-rw-r--r-- | src/CHANGES.txt | 6 | ||||
-rw-r--r-- | src/engine/SCons/Environment.py | 48 | ||||
-rw-r--r-- | src/engine/SCons/EnvironmentTests.py | 12 | ||||
-rw-r--r-- | src/engine/SCons/Node/FS.py | 85 | ||||
-rw-r--r-- | src/engine/SCons/Node/FSTests.py | 54 | ||||
-rw-r--r-- | src/engine/SCons/Node/NodeTests.py | 28 | ||||
-rw-r--r-- | src/engine/SCons/Node/__init__.py | 22 | ||||
-rw-r--r-- | src/engine/SCons/Scanner/C.py | 6 | ||||
-rw-r--r-- | src/engine/SCons/Scanner/CTests.py | 6 | ||||
-rw-r--r-- | src/engine/SCons/Script.py | 34 |
10 files changed, 263 insertions, 38 deletions
diff --git a/src/CHANGES.txt b/src/CHANGES.txt index 3d47e84..2aa1525 100644 --- a/src/CHANGES.txt +++ b/src/CHANGES.txt @@ -13,6 +13,12 @@ RELEASE 0.02 - - Fixed the version comment in the scons.bat (the UNIX geek used # instead of @rem). + - Added support for the Install method (courtesy Charles Crain). + + - Added support for the BuildDir method (courtesy Charles Crain). + + - Added the Export method (courtesy Charles Crain). + RELEASE 0.01 - Thu Dec 13 19:25:23 CST 2001 diff --git a/src/engine/SCons/Environment.py b/src/engine/SCons/Environment.py index 9f78a65..ad92dfd 100644 --- a/src/engine/SCons/Environment.py +++ b/src/engine/SCons/Environment.py @@ -30,7 +30,7 @@ XXX __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__" - +import os import copy import os.path import re @@ -40,12 +40,32 @@ import SCons.Builder import SCons.Defaults from SCons.Errors import UserError from UserList import UserList - -def Command(): - pass # XXX - -def Install(): - pass # XXX +import SCons.Node.FS +import sys +import shutil + +def installFunc(env, target, source): + try: + os.unlink(target) + except OSError: + pass + + try: + SCons.Node.FS.file_link(source[0], target) + print 'Install file: "%s" as "%s"' % \ + (source[0], target) + return 0 + except IOError, e: + sys.stderr.write('Unable to install "%s" as "%s"\n%s\n' % \ + (source[0], target, str(e))) + return -1 + except OSError, e: + sys.stderr.write('Unable to install "%s" as "%s"\n%s\n' % \ + (source[0], target, str(e))) + return -1 + +InstallBuilder = SCons.Builder.Builder(name='Install', + action=installFunc) def InstallAs(): pass # XXX @@ -171,6 +191,20 @@ class Environment: bld = SCons.Builder.Builder(name="Command", action=action) return bld(self, target, source) + def Install(self, dir, source): + """Install specified files in the given directory.""" + sources = SCons.Util.scons_str2nodes(source) + dnodes = SCons.Util.scons_str2nodes(dir, + SCons.Node.FS.default_fs.Dir) + tgt = [] + for dnode in dnodes: + for src in sources: + target = SCons.Node.FS.default_fs.File(src.name, dnode) + tgt.append(InstallBuilder(self, target, src)) + if len(tgt) == 1: + tgt = tgt[0] + return tgt + def subst(self, string): """Recursively interpolates construction variables from the Environment into the specified string, returning the expanded diff --git a/src/engine/SCons/EnvironmentTests.py b/src/engine/SCons/EnvironmentTests.py index 65eb510..c2faa35 100644 --- a/src/engine/SCons/EnvironmentTests.py +++ b/src/engine/SCons/EnvironmentTests.py @@ -139,9 +139,6 @@ class EnvironmentTestCase(unittest.TestCase): s = env3.get_scanner(".cxx") assert s == None, s - def test_Command(self): - pass # XXX - def test_Copy(self): """Test construction Environment copying @@ -218,7 +215,14 @@ class EnvironmentTestCase(unittest.TestCase): assert env1 == env2 def test_Install(self): - pass # XXX + """Test Install method""" + env=Environment() + tgt = env.Install('export', [ 'build/foo1', 'build/foo2' ]) + paths = map(str, tgt) + paths.sort() + assert paths == [ 'export/foo1', 'export/foo2' ], paths + for tnode in tgt: + assert tnode.builder == InstallBuilder def test_InstallAs(self): pass # XXX diff --git a/src/engine/SCons/Node/FS.py b/src/engine/SCons/Node/FS.py index 648e1f9..303db61 100644 --- a/src/engine/SCons/Node/FS.py +++ b/src/engine/SCons/Node/FS.py @@ -39,6 +39,18 @@ import types import SCons.Node from UserDict import UserDict import sys +from SCons.Errors import UserError + +try: + import os + file_link = os.link +except AttributeError: + import shutil + import stat + def file_link(src, dest): + shutil.copyfile(src, dest) + st=os.stat(src) + os.chmod(dest, stat.S_IMODE(st[stat.ST_MODE])) class PathName: """This is a string like object with limited capabilities (i.e., @@ -119,6 +131,7 @@ class FS: self.Root = PathDict() self.Top = self.__doLookup(Dir, path) self.Top.path = '.' + self.Top.srcpath = '.' self.Top.path_ = os.path.join('.', '') self.cwd = self.Top @@ -242,7 +255,17 @@ class FS: name, directory = self.__transformPath(name, directory) return self.__doLookup(Dir, name, directory) - + def BuildDir(self, build_dir, src_dir): + """Link the supplied build directory to the source directory + for purposes of building files.""" + dirSrc = self.Dir(src_dir) + dirBuild = self.Dir(build_dir) + if not dirSrc.is_under(self.Top) or not dirBuild.is_under(self.Top): + raise UserError, "Both source and build directories must be under top of build tree." + if dirSrc.is_under(dirBuild): + raise UserError, "Source directory cannot be under build directory." + dirBuild.link(dirSrc) + class Entry(SCons.Node.Node): """A generic class for file system entries. This class if for @@ -272,6 +295,19 @@ class Entry(SCons.Node.Node): self.abspath_ = self.abspath self.dir = directory self.use_signature = 1 + self.__doSrcpath() + + def adjust_srcpath(self): + self.__doSrcpath() + + def __doSrcpath(self): + if self.dir: + if str(self.dir.srcpath) == '.': + self.srcpath = self.name + else: + self.srcpath = os.path.join(self.dir.srcpath, self.name) + else: + self.srcpath = self.name def __str__(self): """A FS node's string representation is its path name.""" @@ -301,6 +337,13 @@ class Entry(SCons.Node.Node): return 0 return None + def is_under(self, dir): + if self is dir: + return 1 + if not self.dir: + return 0 + return self.dir.is_under(dir) + # XXX TODO? @@ -343,6 +386,21 @@ class Dir(Entry): self.builder = 1 self._sconsign = None + def __doReparent(self): + for ent in self.entries.values(): + if not ent is self and not ent is self.dir: + ent.adjust_srcpath() + + def adjust_srcpath(self): + Entry.adjust_srcpath(self) + self.__doReparent() + + def link(self, srcdir): + """Set this directory as the build directory for the + supplied source directory.""" + self.srcpath = srcdir.path + self.__doReparent() + def up(self): return self.entries['..'] @@ -425,17 +483,21 @@ class Dir(Entry): class File(Entry): """A class for files in a file system. """ + def __init__(self, name, directory = None): + Entry.__init__(self, name, directory) + self._morph() + def _morph(self): - """Turn a file system node into a File object. Nothing - to be done, actually, because all of the info we need - is handled by our base Entry class initialization.""" - pass + """Turn a file system node into a File object.""" + self.created = 0 def root(self): return self.dir.root() def get_contents(self): - return open(self.path, "r").read() + if not self.exists(): + return '' + return open(str(self), "r").read() def get_timestamp(self): if self.exists(): @@ -472,6 +534,17 @@ class File(Entry): self.add_implicit(scn.scan(self.path, self.env), scn) self.scanned[scn] = 1 + + def exists(self): + if not self.created: + self.created = 1 + if self.srcpath != self.path and \ + os.path.exists(self.srcpath): + if os.path.exists(self.path): + os.unlink(self.path) + self.__createDir() + file_link(self.srcpath, self.path) + return Entry.exists(self) def __createDir(self): # ensure that the directories for this node are diff --git a/src/engine/SCons/Node/FSTests.py b/src/engine/SCons/Node/FSTests.py index 45f8e64..0f6b8b7 100644 --- a/src/engine/SCons/Node/FSTests.py +++ b/src/engine/SCons/Node/FSTests.py @@ -29,7 +29,8 @@ import string import sys import unittest import SCons.Node.FS - +from TestCmd import TestCmd +from SCons.Errors import UserError built_it = None @@ -59,6 +60,54 @@ class Environment: def get_scanner(self, skey): return self.scanner +class BuildDirTestCase(unittest.TestCase): + def runTest(self): + """Test build dir functionality""" + fs = SCons.Node.FS.FS() + f1 = fs.File('build/test1') + fs.BuildDir('build', 'src') + f2 = fs.File('build/test2') + assert f1.srcpath == 'src/test1', f1.srcpath + assert f2.srcpath == 'src/test2', f2.srcpath + + fs = SCons.Node.FS.FS() + f1 = fs.File('build/test1') + fs.BuildDir('build', '.') + f2 = fs.File('build/test2') + assert f1.srcpath == 'test1', f1.srcpath + assert f2.srcpath == 'test2', f2.srcpath + + fs = SCons.Node.FS.FS() + fs.BuildDir('build/var1', 'src') + fs.BuildDir('build/var2', 'src') + f1 = fs.File('build/var1/test1') + f2 = fs.File('build/var2/test1') + assert f1.srcpath == 'src/test1', f1.srcpath + assert f2.srcpath == 'src/test1', f2.srcpath + + exc_caught = 0 + try: + fs = SCons.Node.FS.FS() + fs.BuildDir('/test/foo', '.') + except UserError: + exc_caught = 1 + assert exc_caught, "Should have caught a UserError." + + exc_caught = 0 + try: + fs = SCons.Node.FS.FS() + fs.BuildDir('build', '/test/foo') + except UserError: + exc_caught = 1 + assert exc_caught, "Should have caught a UserError." + + exc_caught = 0 + try: + fs = SCons.Node.FS.FS() + fs.BuildDir('build', 'build/src') + except UserError: + exc_caught = 1 + assert exc_caught, "Should have caught a UserError." class FSTestCase(unittest.TestCase): def runTest(self): @@ -68,8 +117,6 @@ class FSTestCase(unittest.TestCase): tests in one environment, so we don't have to set up a complicated directory structure for each test individually. """ - from TestCmd import TestCmd - test = TestCmd(workdir = '') test.subdir('sub', ['sub', 'dir']) @@ -393,5 +440,6 @@ class FSTestCase(unittest.TestCase): if __name__ == "__main__": suite = unittest.TestSuite() suite.addTest(FSTestCase()) + suite.addTest(BuildDirTestCase()) if not unittest.TextTestRunner().run(suite).wasSuccessful(): sys.exit(1) diff --git a/src/engine/SCons/Node/NodeTests.py b/src/engine/SCons/Node/NodeTests.py index 673ab25..f55659e 100644 --- a/src/engine/SCons/Node/NodeTests.py +++ b/src/engine/SCons/Node/NodeTests.py @@ -415,8 +415,32 @@ class NodeTestCase(unittest.TestCase): assert n2.children_are_executed() assert n1.children_are_executed() - - + def test_rescan(self): + """Test that built nodes are rescanned.""" + class DummyScanner: + pass + + class TestNode(SCons.Node.Node): + def scan(self): + for scn in self.scanners: + if not self.scanned.has_key(scn): + n=SCons.Node.Node() + n.scanner_set(scn) + self.add_implicit([ n ], scn) + self.scanned[scn] = 1 + tn=TestNode() + tn.builder_set(Builder()) + tn.env_set(Environment()) + ds = DummyScanner() + tn.scanner_set(ds) + tn.scan() + map(lambda x: x.scan(), tn.depends) + assert tn.scanned[ds] + assert len(tn.implicit[ds]) == 1, tn.implicit + tn.build() + assert len(tn.implicit[ds]) == 2, tn.implicit + for dep in tn.implicit[ds]: + assert dep.scanned[ds] == 1 if __name__ == "__main__": suite = unittest.makeSuite(NodeTestCase, 'test_') diff --git a/src/engine/SCons/Node/__init__.py b/src/engine/SCons/Node/__init__.py index ddcddfb..0682c7f 100644 --- a/src/engine/SCons/Node/__init__.py +++ b/src/engine/SCons/Node/__init__.py @@ -76,6 +76,28 @@ class Node: target = self, source = self.sources) if stat != 0: raise BuildError(node = self, stat = stat) + + # If we succesfully build a node, then we need to rescan for + # implicit dependencies, since it might have changed on us. + + # XXX Modify this so we only rescan using the scanner(s) relevant + # to this build. + for scn in self.scanners: + try: + del self.scanned[scn] + except KeyError: + pass + + self.scan() + + for scn in self.scanners: + try: + for dep in self.implicit[scn]: + w=Walker(dep) + while not w.is_done(): + w.next().scan() + except KeyError: + pass return stat def builder_set(self, builder): diff --git a/src/engine/SCons/Scanner/C.py b/src/engine/SCons/Scanner/C.py index 8ad7e00..a5aa02d 100644 --- a/src/engine/SCons/Scanner/C.py +++ b/src/engine/SCons/Scanner/C.py @@ -96,7 +96,7 @@ def scan(filename, env, args = [SCons.Node.FS.default_fs, ()]): fs, cpppath = args - try: + if fs.File(filename, fs.Top).exists(): file = open(filename) contents = file.read() file.close() @@ -109,9 +109,9 @@ def scan(filename, env, args = [SCons.Node.FS.default_fs, ()]): source_dir = (fs.Dir(dir, fs.Top),) else: source_dir = ( fs.Top, ) + return (SCons.Util.find_files(angle_includes, cpppath + source_dir, fs.File) + SCons.Util.find_files(quote_includes, source_dir + cpppath, fs.File)) - except (IOError, OSError): - return [] + return [] diff --git a/src/engine/SCons/Scanner/CTests.py b/src/engine/SCons/Scanner/CTests.py index bc30fa4..c9cc5ea 100644 --- a/src/engine/SCons/Scanner/CTests.py +++ b/src/engine/SCons/Scanner/CTests.py @@ -29,6 +29,7 @@ import unittest import sys import os import os.path +import SCons.Node.FS test = TestCmd.TestCmd(workdir = '') @@ -176,6 +177,11 @@ class CScannerTestCase5(unittest.TestCase): env = DummyEnvironment([]) s = SCons.Scanner.C.CScan() deps = s.instance(env).scan(test.workpath('f3.cpp'), env) + + # Make sure exists() gets called on the file node being + # scanned, essential for cooperation with BuildDir functionality. + assert SCons.Node.FS.default_fs.File(test.workpath('f3.cpp')).created + headers = ['f1.h', 'f2.h', 'f3.h', 'fi.h', 'fj.h', 'd1/f1.h', 'd1/f2.h', 'd1/f3.h'] deps_match(self, deps, map(test.workpath, headers)) diff --git a/src/engine/SCons/Script.py b/src/engine/SCons/Script.py index 836fb1a..086e470 100644 --- a/src/engine/SCons/Script.py +++ b/src/engine/SCons/Script.py @@ -42,6 +42,7 @@ import os.path import string import sys import traceback +import copy # Strip the script directory from sys.path() so on case-insensitive # (WIN32) systems Python doesn't think that the "scons" script is the @@ -159,9 +160,9 @@ def _scons_other_errors(): -def SConscript(filename): +def SConscript(sconscript, export={}): global scripts - scripts.append(SCons.Node.FS.default_fs.File(filename)) + scripts.append( (SCons.Node.FS.default_fs.File(sconscript), export) ) def Default(*targets): for t in targets: @@ -175,7 +176,12 @@ def Help(text): print "Use scons -H for help about command-line options." sys.exit(0) +def BuildDir(build_dir, src_dir): + SCons.Node.FS.default_fs.BuildDir(build_dir, src_dir) +def Export(**kw): + # A convenient shorthand to pass exports to the SConscript function. + return kw # # After options are initialized, the following variables are @@ -374,9 +380,9 @@ def options_init(): def opt_f(opt, arg): global scripts if arg == '-': - scripts.append(arg) + scripts.append( ( arg, {} ) ) else: - scripts.append(SCons.Node.FS.default_fs.File(arg)) + scripts.append( (SCons.Node.FS.default_fs.File(arg), {}) ) Option(func = opt_f, short = 'f', long = ['file', 'makefile', 'sconstruct'], arg = 'FILE', @@ -599,7 +605,7 @@ def _main(): if not scripts: for file in ['SConstruct', 'Sconstruct', 'sconstruct']: if os.path.isfile(file): - scripts.append(SCons.Node.FS.default_fs.File(file)) + scripts.append( (SCons.Node.FS.default_fs.File(file), {}) ) break if help_option == 'H': @@ -619,17 +625,19 @@ def _main(): sys.path = include_dirs + sys.path while scripts: - f, scripts = scripts[0], scripts[1:] + f, exports = scripts.pop(0) + script_env = copy.copy(globals()) + script_env.update(exports) if f == "-": - exec sys.stdin in globals() + exec sys.stdin in script_env else: - try: - file = open(f.path, "r") - except IOError, s: - sys.stderr.write("Ignoring missing SConscript '%s'\n" % f.path) - else: + if f.exists(): + file = open(str(f), "r") SCons.Node.FS.default_fs.chdir(f.dir) - exec file in globals() + exec file in script_env + else: + sys.stderr.write("Ignoring missing SConscript '%s'\n" % f.path) + SCons.Node.FS.default_fs.chdir(SCons.Node.FS.default_fs.Top) if help_option == 'h': |