5 # Permission is hereby granted, free of charge, to any person obtaining
6 # a copy of this software and associated documentation files (the
7 # "Software"), to deal in the Software without restriction, including
8 # without limitation the rights to use, copy, modify, merge, publish,
9 # distribute, sublicense, and/or sell copies of the Software, and to
10 # permit persons to whom the Software is furnished to do so, subject to
11 # the following conditions:
13 # The above copyright notice and this permission notice shall be included
14 # in all copies or substantial portions of the Software.
16 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
17 # KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
18 # WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19 # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20 # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21 # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22 # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
26 This test verifies that Scanners are called just once.
28 This is actually a shotgun marriage of two separate tests, the simple
29 test originally created for this, plus a more complicated test based
30 on a real-life bug report submitted by Scott Lystig Fritchie. Both
31 have value: the simple test will be easier to debug if there are basic
32 scanning problems, while Scott's test has a lot of cool real-world
33 complexity that is valuable in its own right, including scanning of
38 __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
46 test = TestSCons.TestSCons()
50 ['SLF', 'reftree'], ['SLF', 'reftree', 'include'],
51 ['SLF', 'src'], ['SLF', 'src', 'lib_geng'])
53 test.write('SConstruct', """\
54 SConscript('simple/SConscript')
55 SConscript('SLF/SConscript')
58 test.write(['simple', 'SConscript'], r"""
61 def scan(node, env, envkey, arg):
62 print 'XScanner: node =', os.path.split(str(node))[1]
65 def exists_check(node):
66 return os.path.exists(str(node))
68 XScanner = Scanner(name = 'XScanner',
71 scan_check = exists_check,
74 def echo(env, target, source):
75 t = os.path.split(str(target[0]))[1]
76 s = os.path.split(str(source[0]))[1]
77 print 'create %s from %s' % (t, s)
79 Echo = Builder(action = Action(echo, None),
83 env = Environment(BUILDERS = {'Echo':Echo}, SCANNERS = [XScanner])
85 f1 = env.Echo(source=['file1'], target=['file2'])
86 f2 = env.Echo(source=['file2'], target=['file3'])
87 f3 = env.Echo(source=['file3'], target=['file4'])
90 test.write(['simple', 'file1.x'], 'simple/file1.x\n')
92 test.write(['SLF', 'SConscript'], """\
94 ### QQQ !@#$!@#$! I need to move the SConstruct file to be "above"
95 ### both the source and install dirs, or the install dependencies
96 ### don't seem to work well! ARRGH!!!!
106 BStaticLibMerge = Builder(generator = Mylib.Gen_StaticLibMerge)
107 builders = Environment().Dictionary('BUILDERS')
108 builders["StaticLibMerge"] = BStaticLibMerge
110 env = Environment(BUILDERS = builders)
111 e = env.Dictionary() # Slightly easier to type
115 def write_out(file, dict):
120 file = os.path.split(k)[1]
121 f.write(file + ": " + str(dict[k]) + "\\n")
124 import SCons.Scanner.C
125 c_scanner = SCons.Scanner.C.CScan()
126 def MyCScan(node, env, target):
127 deps = c_scanner(node, env, target)
132 Scanned[n] = Scanned[n] + 1
135 write_out('MyCScan.out', Scanned)
138 S_MyCScan = SCons.Scanner.Current(skeys = [".c", ".C", ".cxx", ".cpp", ".c++", ".cc",
139 ".h", ".H", ".hxx", ".hpp", ".h++", ".hh"],
142 # QQQ Yes, this is manner of fixing the SCANNERS list is fragile.
143 env["SCANNERS"] = [S_MyCScan] + env["SCANNERS"][1:]
146 e["GlobalEnv"] = global_env
148 e["REF_INCLUDE"] = os.path.join(experimenttop, "reftree", "include")
149 e["REF_LIB"] = os.path.join(experimenttop, "reftree", "lib")
150 e["EXPORT_INCLUDE"] = os.path.join(experimenttop, "export", "include")
151 e["EXPORT_LIB"] = os.path.join(experimenttop, "export", "lib")
152 e["INSTALL_BIN"] = os.path.join(experimenttop, "install", "bin")
154 build_dir = os.path.join(experimenttop, "tmp-bld-dir")
155 src_dir = os.path.join(experimenttop, "src")
157 env.Append(CPPPATH = [e["EXPORT_INCLUDE"]])
158 env.Append(CPPPATH = [e["REF_INCLUDE"]])
159 Mylib.AddLibDirs(env, "/via/Mylib.AddLibPath")
160 env.Append(LIBPATH = [e["EXPORT_LIB"]])
161 env.Append(LIBPATH = [e["REF_LIB"]])
163 Mylib.Subdirs(env, "src")
164 """ % test.workpath('SLF'))
166 test.write(['SLF', 'Mylib.py'], """\
170 import SCons.Environment
172 def Subdirs(env, dirlist):
173 for file in _subconf_list(dirlist):
174 env.SConscript(file, "env")
176 def _subconf_list(dirlist):
177 return map(lambda x: os.path.join(x, "SConscript"), string.split(dirlist))
179 def StaticLibMergeMembers(local_env, libname, hackpath, files):
180 for file in string.split(files):
181 # QQQ Fix limits in grok'ed regexp
182 tmp = re.sub(".c$", ".o", file)
183 objname = re.sub(".cpp", ".o", tmp)
184 local_env.Object(target = objname, source = file)
185 e = 'local_env["GlobalEnv"].Append(%s = ["%s"])' % (libname, os.path.join(hackpath, objname))
188 def CreateMergedStaticLibrary(env, libname):
189 objpaths = env["GlobalEnv"][libname]
190 libname = "lib%s.a" % (libname)
191 env.StaticLibMerge(target = libname, source = objpaths)
193 # I put the main body of the generator code here to avoid
195 def Gen_StaticLibMerge(source, target, env, for_signature):
198 target_string = str(t)
199 subdir = os.path.dirname(target_string)
203 return [["ar", "cq"] + target + srclist, ["ranlib"] + target]
205 def StaticLibrary(env, target, source):
206 env.StaticLibrary(target, string.split(source))
208 def SharedLibrary(env, target, source):
209 env.SharedLibrary(target, string.split(source))
211 def ExportHeader(env, headers):
212 env.Install(dir = env["EXPORT_INCLUDE"], source = string.split(headers))
214 def ExportLib(env, libs):
215 env.Install(dir = env["EXPORT_LIB"], source = string.split(libs))
217 def InstallBin(env, bins):
218 env.Install(dir = env["INSTALL_BIN"], source = string.split(bins))
220 def Program(env, target, source):
221 env.Program(target, string.split(source))
223 def AddCFlags(env, str):
224 env.Append(CPPFLAGS = " " + str)
226 # QQQ Synonym needed?
227 #def AddCFLAGS(env, str):
228 # AddCFlags(env, str)
230 def AddIncludeDirs(env, str):
231 env.Append(CPPPATH = string.split(str))
233 def AddLibs(env, str):
234 env.Append(LIBS = string.split(str))
236 def AddLibDirs(env, str):
237 env.Append(LIBPATH = string.split(str))
241 test.write(['SLF', 'reftree', 'include', 'lib_a.h'], """\
242 char *a_letter(void);
245 test.write(['SLF', 'reftree', 'include', 'lib_b.h'], """\
246 char *b_letter(void);
249 test.write(['SLF', 'reftree', 'include', 'lib_ja.h'], """\
250 char *j_letter_a(void);
253 test.write(['SLF', 'reftree', 'include', 'lib_jb.h.intentionally-moved'], """\
254 char *j_letter_b(void);
257 test.write(['SLF', 'src', 'SConscript'], """\
258 # --- Begin SConscript boilerplate ---
262 #env = env.Copy() # Yes, clobber intentionally
263 #Make environment changes, such as: Mylib.AddCFlags(env, "-g -D_TEST")
264 #Mylib.Subdirs(env, "lib_a lib_b lib_mergej prog_x")
265 Mylib.Subdirs(env, "lib_geng")
267 env = env.Copy() # Yes, clobber intentionally
268 # --- End SConscript boilerplate ---
272 test.write(['SLF', 'src', 'lib_geng', 'SConscript'], """\
273 # --- Begin SConscript boilerplate ---
279 #env = env.Copy() # Yes, clobber intentionally
280 #Make environment changes, such as: Mylib.AddCFlags(env, "-g -D_TEST")
281 #Mylib.Subdirs(env, "foo_dir")
283 env = env.Copy() # Yes, clobber intentionally
284 # --- End SConscript boilerplate ---
286 Mylib.AddCFlags(env, "-DGOOFY_DEMO")
287 Mylib.AddIncludeDirs(env, ".")
289 # Not part of SLF's original stuff: On Win32, it's import to use the
290 # original test environment when we invoke SCons recursively.
292 recurse_env = env.Copy()
293 recurse_env["ENV"] = os.environ
295 # Icky code to set up process environment for "make"
296 # I really ought to drop this into Mylib....
298 fromdict = env.Dictionary()
302 for k in fromdict.keys():
303 if k != "ENV" and k != "SCANNERS" and k != "CFLAGS" and k != "CXXFLAGS" \
304 and not SCons.Util.is_Dict(fromdict[k]):
305 # The next line can fail on some systems because it would try to
307 # $RMIC $RMICFLAGS -d ${TARGET.attributes.java_lookupdir} ...
308 # When $TARGET is None, so $TARGET.attributes would throw an
311 if SCons.Util.is_String(f) and string.find(f, "TARGET") == -1:
312 todict[k] = env.subst(f)
313 todict["CFLAGS"] = fromdict["CPPFLAGS"] + " " + \
314 string.join(map(lambda x: "-I" + x, env["CPPPATH"])) + " " + \
315 string.join(map(lambda x: "-L" + x, env["LIBPATH"]))
316 todict["CXXFLAGS"] = todict["CFLAGS"]
318 generated_hdrs = "libg_gx.h libg_gy.h libg_gz.h"
319 static_hdrs = "libg_w.h"
320 #exported_hdrs = generated_hdrs + " " + static_hdrs
321 exported_hdrs = static_hdrs
323 lib_fullname = "libg.a"
324 lib_srcs = string.split("libg_1.c libg_2.c libg_3.c")
326 lib_objs = map(lambda x: re.sub("\.c$", ".o", x), lib_srcs)
328 Mylib.ExportHeader(env, exported_hdrs)
329 Mylib.ExportLib(env, lib_fullname)
331 # The following were the original commands from SLF, making use of
332 # a shell script and a Makefile to build the library. These have
333 # been preserved, commented out below, but in order to make this
334 # test portable, we've replaced them with a Python script and a
335 # recursive invocation of SCons (!).
336 #cmd_both = "cd %s ; make generated ; make" % Dir(".")
337 #cmd_generated = "cd %s ; sh MAKE-HEADER.sh" % Dir(".")
338 #cmd_justlib = "cd %s ; make" % Dir(".")
340 _ws = re.compile('\s')
347 cmd_generated = "%s $SOURCE" % (escape(sys.executable),)
348 cmd_justlib = "%s %s -C ${SOURCES[0].dir}" % ((sys.executable),
351 ##### Deps appear correct ... but wacky scanning?
356 env.Command(string.split(generated_hdrs),
359 recurse_env.Command([lib_fullname] + lib_objs,
360 lib_srcs + string.split(generated_hdrs + " " + static_hdrs),
364 test.write(['SLF', 'src', 'lib_geng', 'MAKE-HEADER.py'], """\
365 #!/usr/bin/env python
371 # chdir to the directory in which this script lives
372 os.chdir(os.path.split(sys.argv[0])[0])
374 for h in ['libg_gx.h', 'libg_gy.h', 'libg_gz.h']:
375 open(h, 'w').write('')
378 test.write(['SLF', 'src', 'lib_geng', 'SConstruct'], """\
379 env = Environment(CPPPATH = ".")
380 l = env.StaticLibrary("g", Split("libg_1.c libg_2.c libg_3.c"))
384 # These were the original shell script and Makefile from SLF's original
385 # bug report. We're not using them--in order to make this script as
386 # portable as possible, we're using a Python script and a recursive
387 # invocation of SCons--but we're preserving them here for history.
388 #test.write(['SLF', 'src', 'lib_geng', 'MAKE-HEADER.sh'], """\
394 #test.write(['SLF', 'src', 'lib_geng', 'Makefile'], """\
397 #GEN_HDRS = libg_gx.h libg_gy.h libg_gz.h
398 #STATIC_HDRS = libg_w.h
400 #$(GEN_HDRS): generated
402 #generated: MAKE-HEADER.sh
403 # sh ./MAKE-HEADER.sh $(GEN_HDRS)
405 #libg.a: libg_1.o libg_2.o libg_3.o
406 # ar r libg.a libg_1.o libg_2.o libg_3.o
408 #libg_1.c: $(STATIC_HDRS) $(GEN_HDRS)
409 #libg_2.c: $(STATIC_HDRS) $(GEN_HDRS)
410 #libg_3.c: $(STATIC_HDRS) $(GEN_HDRS)
414 # -rm -f libg.a *.o core core.*
417 test.write(['SLF', 'src', 'lib_geng', 'libg_w.h'], """\
420 test.write(['SLF', 'src', 'lib_geng', 'libg_1.c'], """\
430 test.write(['SLF', 'src', 'lib_geng', 'libg_2.c'], """\
442 test.write(['SLF', 'src', 'lib_geng', 'libg_3.c'], """\
452 test.run(arguments = 'simple',
453 stdout = test.wrap_stdout("""\
454 XScanner: node = file1.x
455 create file2.x from file1.x
456 create file3.x from file2.x
457 create file4.x from file3.x
460 test.write(['simple', 'file2.x'], 'simple/file2.x\n')
462 test.run(arguments = 'simple',
463 stdout = test.wrap_stdout("""\
464 XScanner: node = file1.x
465 XScanner: node = file2.x
466 create file3.x from file2.x
467 create file4.x from file3.x
470 test.write(['simple', 'file3.x'], 'simple/file3.x\n')
472 test.run(arguments = 'simple',
473 stdout = test.wrap_stdout("""\
474 XScanner: node = file1.x
475 XScanner: node = file2.x
476 XScanner: node = file3.x
477 create file4.x from file3.x
480 test.run(arguments = 'SLF')
482 # XXX Note that the generated .h files still get scanned twice,
483 # once before they're generated and once after. That's the
484 # next thing to fix here.
485 test.fail_test(test.read("MyCScan.out", "rb") != """\