Eliminate unnecessary WIN32/Win32/win32 references in tests, too.
[scons.git] / test / scan-once.py
1 #!/usr/bin/env python
2 #
3 # __COPYRIGHT__
4 #
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:
12 #
13 # The above copyright notice and this permission notice shall be included
14 # in all copies or substantial portions of the Software.
15 #
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.
23 #
24
25 """
26 This test verifies that Scanners are called just once.
27
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
34 generated .h files.
35
36 """
37
38 __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
39
40 import os.path
41 import sys
42
43 import TestCmd
44 import TestSCons
45
46 test = TestSCons.TestSCons()
47
48 test.subdir('simple',
49             'SLF',
50             ['SLF', 'reftree'], ['SLF', 'reftree', 'include'],
51             ['SLF', 'src'], ['SLF', 'src', 'lib_geng'])
52
53 test.write('SConstruct', """\
54 SConscript('simple/SConscript')
55 SConscript('SLF/SConscript')
56 """)
57
58 test.write(['simple', 'SConscript'], r"""
59 import os.path
60
61 def scan(node, env, envkey, arg):
62     print 'XScanner: node =', os.path.split(str(node))[1]
63     return []
64
65 def exists_check(node, env):
66     return os.path.exists(str(node))
67
68 XScanner = Scanner(name = 'XScanner',
69                    function = scan,
70                    argument = None,
71                    scan_check = exists_check,
72                    skeys = ['.x'])
73
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)
78
79 Echo = Builder(action = Action(echo, None),
80                src_suffix = '.x',
81                suffix = '.x')
82
83 env = Environment(BUILDERS = {'Echo':Echo}, SCANNERS = [XScanner])
84
85 f1 = env.Echo(source=['file1'], target=['file2'])
86 f2 = env.Echo(source=['file2'], target=['file3'])
87 f3 = env.Echo(source=['file3'], target=['file4'])
88 """)
89
90 test.write(['simple', 'file1.x'], 'simple/file1.x\n')
91
92 test.write(['SLF', 'SConscript'], """\
93 ###
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!!!!
97 ###
98
99 experimenttop = r"%s"
100
101 import os
102 import os.path
103 import string
104 import Mylib
105
106 BStaticLibMerge = Builder(generator = Mylib.Gen_StaticLibMerge)
107 builders = Environment().Dictionary('BUILDERS')
108 builders["StaticLibMerge"] = BStaticLibMerge
109
110 env = Environment(BUILDERS = builders)
111 e = env.Dictionary()    # Slightly easier to type
112
113 global_env = env
114 e["GlobalEnv"] = global_env
115
116 e["REF_INCLUDE"] = os.path.join(experimenttop, "reftree", "include")
117 e["REF_LIB"] = os.path.join(experimenttop, "reftree", "lib")
118 e["EXPORT_INCLUDE"] = os.path.join(experimenttop, "export", "include")
119 e["EXPORT_LIB"] = os.path.join(experimenttop, "export", "lib")
120 e["INSTALL_BIN"] = os.path.join(experimenttop, "install", "bin")
121
122 build_dir = os.path.join(experimenttop, "tmp-bld-dir")
123 src_dir = os.path.join(experimenttop, "src")
124
125 env.Append(CPPPATH = [e["EXPORT_INCLUDE"]])
126 env.Append(CPPPATH = [e["REF_INCLUDE"]])
127 Mylib.AddLibDirs(env, "/via/Mylib.AddLibPath")
128 env.Append(LIBPATH = [e["EXPORT_LIB"]])
129 env.Append(LIBPATH = [e["REF_LIB"]])
130
131 Mylib.Subdirs(env, "src")
132 """ % test.workpath('SLF'))
133
134 test.write(['SLF', 'Mylib.py'], """\
135 import os
136 import string
137 import re
138
139 def Subdirs(env, dirlist):
140     for file in _subconf_list(dirlist):
141         env.SConscript(file, "env")
142
143 def _subconf_list(dirlist):
144     return map(lambda x: os.path.join(x, "SConscript"), string.split(dirlist))
145
146 def StaticLibMergeMembers(local_env, libname, hackpath, files):
147     for file in string.split(files):
148         # QQQ Fix limits in grok'ed regexp
149         tmp = re.sub(".c$", ".o", file)
150         objname = re.sub(".cpp", ".o", tmp)
151         local_env.Object(target = objname, source = file)
152         e = 'local_env["GlobalEnv"].Append(%s = ["%s"])' % (libname, os.path.join(hackpath, objname))
153         exec(e)
154
155 def CreateMergedStaticLibrary(env, libname):
156     objpaths = env["GlobalEnv"][libname]
157     libname = "lib%s.a" % (libname)
158     env.StaticLibMerge(target = libname, source = objpaths)
159
160 # I put the main body of the generator code here to avoid
161 # namespace problems
162 def Gen_StaticLibMerge(source, target, env, for_signature):
163     target_string = ""
164     for t in target:
165         target_string = str(t)
166     subdir = os.path.dirname(target_string)
167     srclist = []
168     for src in source:
169         srclist.append(src)
170     return [["ar", "cq"] + target + srclist, ["ranlib"] + target]
171
172 def StaticLibrary(env, target, source):
173     env.StaticLibrary(target, string.split(source))
174
175 def SharedLibrary(env, target, source):
176     env.SharedLibrary(target, string.split(source))
177
178 def ExportHeader(env, headers):
179     env.Install(dir = env["EXPORT_INCLUDE"], source = string.split(headers))
180
181 def ExportLib(env, libs):
182     env.Install(dir = env["EXPORT_LIB"], source = string.split(libs))
183
184 def InstallBin(env, bins):
185     env.Install(dir = env["INSTALL_BIN"], source = string.split(bins))
186
187 def Program(env, target, source):
188     env.Program(target, string.split(source))
189
190 def AddCFlags(env, str):
191     env.Append(CPPFLAGS = " " + str)
192
193 # QQQ Synonym needed?
194 #def AddCFLAGS(env, str):
195 #    AddCFlags(env, str)
196
197 def AddIncludeDirs(env, str):
198     env.Append(CPPPATH = string.split(str))
199
200 def AddLibs(env, str):
201     env.Append(LIBS = string.split(str))
202
203 def AddLibDirs(env, str):
204     env.Append(LIBPATH = string.split(str))
205
206 """)
207
208 test.write(['SLF', 'reftree', 'include', 'lib_a.h'], """\
209 char *a_letter(void);
210 """)
211
212 test.write(['SLF', 'reftree', 'include', 'lib_b.h'], """\
213 char *b_letter(void);
214 """)
215
216 test.write(['SLF', 'reftree', 'include', 'lib_ja.h'], """\
217 char *j_letter_a(void);
218 """)
219
220 test.write(['SLF', 'reftree', 'include', 'lib_jb.h.intentionally-moved'], """\
221 char *j_letter_b(void);
222 """)
223
224 test.write(['SLF', 'src', 'SConscript'], """\
225 # --- Begin SConscript boilerplate ---
226 import Mylib
227 Import("env")
228
229 #env = env.Copy()    # Yes, clobber intentionally
230 #Make environment changes, such as: Mylib.AddCFlags(env, "-g -D_TEST")
231 #Mylib.Subdirs(env, "lib_a lib_b lib_mergej prog_x")
232 Mylib.Subdirs(env, "lib_geng")
233
234 env = env.Copy()    # Yes, clobber intentionally
235 # --- End SConscript boilerplate ---
236
237 """)
238
239 test.write(['SLF', 'src', 'lib_geng', 'SConscript'], """\
240 # --- Begin SConscript boilerplate ---
241 import string
242 import sys
243 import Mylib
244 Import("env")
245
246 #env = env.Copy()    # Yes, clobber intentionally
247 #Make environment changes, such as: Mylib.AddCFlags(env, "-g -D_TEST")
248 #Mylib.Subdirs(env, "foo_dir")
249
250 env = env.Copy()    # Yes, clobber intentionally
251 # --- End SConscript boilerplate ---
252
253 Mylib.AddCFlags(env, "-DGOOFY_DEMO")
254 Mylib.AddIncludeDirs(env, ".")
255
256 # Not part of SLF's original stuff: On Windows, it's import to use the
257 # original test environment when we invoke SCons recursively.
258 import os
259 recurse_env = env.Copy()
260 recurse_env["ENV"] = os.environ
261
262 # Icky code to set up process environment for "make"
263 # I really ought to drop this into Mylib....
264
265 fromdict = env.Dictionary()
266 todict = env["ENV"]
267 import SCons.Util
268 import re
269 for k in fromdict.keys():
270     if k != "ENV" and k != "SCANNERS" and k != "CFLAGS" and k != "CXXFLAGS" \
271     and not SCons.Util.is_Dict(fromdict[k]):
272         # The next line can fail on some systems because it would try to
273         # do env.subst on:
274         #       $RMIC $RMICFLAGS -d ${TARGET.attributes.java_lookupdir} ...
275         # When $TARGET is None, so $TARGET.attributes would throw an
276         # exception.
277         f = fromdict[k]
278         if SCons.Util.is_String(f) and string.find(f, "TARGET") == -1:
279              todict[k] = env.subst(f)
280 todict["CFLAGS"] = fromdict["CPPFLAGS"] + " " + \
281     string.join(map(lambda x: "-I" + x, env["CPPPATH"])) + " " + \
282     string.join(map(lambda x: "-L" + x, env["LIBPATH"])) 
283 todict["CXXFLAGS"] = todict["CFLAGS"]
284
285 generated_hdrs = "libg_gx.h libg_gy.h libg_gz.h"
286 static_hdrs = "libg_w.h"
287 #exported_hdrs = generated_hdrs + " " + static_hdrs
288 exported_hdrs = static_hdrs
289 lib_name = "g"
290 lib_fullname = env.subst("${LIBPREFIX}g${LIBSUFFIX}")
291 lib_srcs = string.split("libg_1.c libg_2.c libg_3.c")
292 import re
293 lib_objs = map(lambda x: re.sub("\.c$", ".o", x), lib_srcs)
294
295 Mylib.ExportHeader(env, exported_hdrs)
296 Mylib.ExportLib(env, lib_fullname)
297
298 # The following were the original commands from SLF, making use of
299 # a shell script and a Makefile to build the library.  These have
300 # been preserved, commented out below, but in order to make this
301 # test portable, we've replaced them with a Python script and a
302 # recursive invocation of SCons (!).
303 #cmd_both = "cd %s ; make generated ; make" % Dir(".")
304 #cmd_generated = "cd %s ; sh MAKE-HEADER.sh" % Dir(".")
305 #cmd_justlib = "cd %s ; make" % Dir(".")
306
307 _ws = re.compile('\s')
308
309 def escape(s):
310     if _ws.search(s):
311         s = '"' + s + '"'
312     return s
313
314 cmd_generated = "%s $SOURCE" % (escape(sys.executable),)
315 cmd_justlib = "%s %s -C ${SOURCES[0].dir}" % ((sys.executable),
316                                               escape(sys.argv[0]))
317
318 ##### Deps appear correct ... but wacky scanning?
319 # Why?
320 #
321 # SCons bug??
322
323 env.Command(string.split(generated_hdrs),
324             ["MAKE-HEADER.py"],
325             cmd_generated)
326 recurse_env.Command([lib_fullname] + lib_objs,
327                     lib_srcs + string.split(generated_hdrs + " " + static_hdrs),
328                     cmd_justlib) 
329 """)
330
331 test.write(['SLF', 'src', 'lib_geng', 'MAKE-HEADER.py'], """\
332 #!/usr/bin/env python
333
334 import os
335 import os.path
336 import sys
337
338 # chdir to the directory in which this script lives
339 os.chdir(os.path.split(sys.argv[0])[0])
340
341 for h in ['libg_gx.h', 'libg_gy.h', 'libg_gz.h']:
342     open(h, 'w').write('')
343 """)
344
345 test.write(['SLF', 'src', 'lib_geng', 'SConstruct'], """\
346 import os
347
348 Scanned = {}
349
350 def write_out(file, dict):
351     keys = dict.keys()
352     keys.sort()
353     f = open(file, 'wb')
354     for k in keys:
355         file = os.path.split(k)[1]
356         f.write(file + ": " + str(dict[k]) + "\\n")
357     f.close()
358
359 orig_function = CScan.scan
360
361 def MyCScan(node, paths, orig_function=orig_function):
362     deps = orig_function(node, paths)
363
364     global Scanned
365     n = str(node)
366     try:
367         Scanned[n] = Scanned[n] + 1
368     except KeyError:
369         Scanned[n] = 1
370     write_out(r'%s', Scanned)
371
372     return deps
373
374 CScan.scan = MyCScan
375
376 env = Environment(CPPPATH = ".")
377 l = env.StaticLibrary("g", Split("libg_1.c libg_2.c libg_3.c"))
378 Default(l)
379 """ % test.workpath('MyCScan.out'))
380
381 # These were the original shell script and Makefile from SLF's original
382 # bug report.  We're not using them--in order to make this script as
383 # portable as possible, we're using a Python script and a recursive
384 # invocation of SCons--but we're preserving them here for history.
385 #test.write(['SLF', 'src', 'lib_geng', 'MAKE-HEADER.sh'], """\
386 ##!/bin/sh
387 #
388 #exec touch $*
389 #""")
390 #
391 #test.write(['SLF', 'src', 'lib_geng', 'Makefile'], """\
392 #all: libg.a
393 #
394 #GEN_HDRS = libg_gx.h libg_gy.h libg_gz.h
395 #STATIC_HDRS = libg_w.h
396 #
397 #$(GEN_HDRS): generated
398 #
399 #generated: MAKE-HEADER.sh
400 #       sh ./MAKE-HEADER.sh $(GEN_HDRS)
401 #
402 #libg.a: libg_1.o libg_2.o libg_3.o
403 #       ar r libg.a libg_1.o libg_2.o libg_3.o
404 #
405 #libg_1.c: $(STATIC_HDRS) $(GEN_HDRS)
406 #libg_2.c: $(STATIC_HDRS) $(GEN_HDRS)
407 #libg_3.c: $(STATIC_HDRS) $(GEN_HDRS)
408 #
409 #clean:
410 #       -rm -f $(GEN_HDRS)
411 #       -rm -f libg.a *.o core core.*
412 #""")
413
414 test.write(['SLF', 'src', 'lib_geng', 'libg_w.h'], """\
415 """)
416
417 test.write(['SLF', 'src', 'lib_geng', 'libg_1.c'], """\
418 #include <libg_w.h>
419 #include <libg_gx.h>
420
421 int g_1()
422 {
423     return 1;
424 }
425 """)
426
427 test.write(['SLF', 'src', 'lib_geng', 'libg_2.c'], """\
428 #include <libg_w.h>
429 #include <libg_gx.h> 
430 #include <libg_gy.h>
431 #include <libg_gz.h>
432
433 int g_2()
434 {
435         return 2;
436 }
437 """)
438
439 test.write(['SLF', 'src', 'lib_geng', 'libg_3.c'], """\
440 #include <libg_w.h>
441 #include <libg_gx.h>
442
443 int g_3()
444 {
445     return 3;
446 }
447 """)
448
449 test.run(arguments = 'simple',
450          stdout = test.wrap_stdout("""\
451 XScanner: node = file1.x
452 create file2.x from file1.x
453 create file3.x from file2.x
454 create file4.x from file3.x
455 """))
456
457 test.write(['simple', 'file2.x'], 'simple/file2.x\n')
458
459 test.run(arguments = 'simple',
460          stdout = test.wrap_stdout("""\
461 XScanner: node = file1.x
462 XScanner: node = file2.x
463 create file3.x from file2.x
464 create file4.x from file3.x
465 """))
466
467 test.write(['simple', 'file3.x'], 'simple/file3.x\n')
468
469 test.run(arguments = 'simple',
470          stdout = test.wrap_stdout("""\
471 XScanner: node = file1.x
472 XScanner: node = file2.x
473 XScanner: node = file3.x
474 create file4.x from file3.x
475 """))
476
477 test.run(arguments = 'SLF',
478          stderr=TestSCons.noisy_ar,
479          match=TestSCons.match_re_dotall)
480
481 # XXX Note that the generated .h files still get scanned twice,
482 # once before they're generated and once after.  That's the
483 # next thing to fix here.
484
485 test.must_match("MyCScan.out", """\
486 libg_1.c: 1
487 libg_2.c: 1
488 libg_3.c: 1
489 libg_gx.h: 1
490 libg_gy.h: 1
491 libg_gz.h: 1
492 libg_w.h: 1
493 """)
494
495 test.pass_test()