3 # Copyright The SCons Foundation
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.
35 import SCons
.Scanner
.C
37 test
= TestCmd
.TestCmd(workdir
= '')
39 os
.chdir(test
.workpath(''))
41 # create some source files and headers:
43 test
.write('f1.cpp',"""
44 #ifdef INCLUDE_F2 /* multi-line comment */
56 test
.write('f2.cpp',"""
59 #if 5UL < 10 && !defined(DUMMY_MACRO) // some comment
75 test
.write('f3.cpp',"""
78 # \t include "f3-test.h"
82 # \t include <d1/f3-test.h>
86 const char* x = "#include <never.h>"
94 # include using a macro, defined in source file
95 test
.write('f9a.c', """\
105 # include using a macro, not defined in source file
106 test
.write('f9b.c', """\
118 test
.subdir('d1', ['d1', 'd2'])
120 headers
= ['f1.h','f2.h', 'f3-test.h', 'fi.h', 'fj.h', 'never.h',
121 'd1/f1.h', 'd1/f2.h', 'd1/f3-test.h', 'd1/fi.h', 'd1/fj.h',
122 'd1/d2/f1.h', 'd1/d2/f2.h', 'd1/d2/f3-test.h',
123 'd1/d2/f4.h', 'd1/d2/fi.h', 'd1/d2/fj.h', 'f9.h']
128 test
.write('f2.h',"""
132 test
.write('f3-test.h',"""
137 test
.subdir('include', 'subdir', ['subdir', 'include'])
139 test
.write('fa.cpp',"""
149 test
.write(['include', 'fa.h'], "\n")
150 test
.write(['include', 'fb.h'], "\n")
151 test
.write(['subdir', 'include', 'fa.h'], "\n")
152 test
.write(['subdir', 'include', 'fb.h'], "\n")
155 test
.subdir('repository', ['repository', 'include'],
156 ['repository', 'src' ])
157 test
.subdir('work', ['work', 'src'])
159 test
.write(['repository', 'include', 'iii.h'], "\n")
161 test
.write(['work', 'src', 'fff.c'], """
171 test
.write([ 'work', 'src', 'aaa.c'], """
180 test
.write([ 'work', 'src', 'bbb.h'], "\n")
182 test
.write([ 'repository', 'src', 'ccc.c'], """
191 test
.write([ 'repository', 'src', 'ddd.h'], "\n")
193 test
.write('f5.c', """\
198 test
.write("f5a.h", "\n")
199 test
.write("f5b.h", "\n")
201 # define some helpers:
203 class DummyEnvironment(collections
.UserDict
):
204 def __init__(self
, **kwargs
) -> None:
206 self
.data
.update(kwargs
)
207 self
.fs
= SCons
.Node
.FS
.FS(test
.workpath(''))
209 def Dictionary(self
, *args
):
212 def subst(self
, strSubst
, target
=None, source
=None, conv
=None):
213 if strSubst
[0] == '$':
214 return self
.data
[strSubst
[1:]]
217 def subst_list(self
, strSubst
, target
=None, source
=None, conv
=None):
218 if strSubst
[0] == '$':
219 return [self
.data
[strSubst
[1:]]]
222 def subst_path(self
, path
, target
=None, source
=None, conv
=None):
223 if not isinstance(path
, list):
225 return list(map(self
.subst
, path
))
227 def get_calculator(self
):
230 def get_factory(self
, factory
):
231 return factory
or self
.fs
.File
233 def Dir(self
, filename
):
234 return self
.fs
.Dir(filename
)
236 def File(self
, filename
):
237 return self
.fs
.File(filename
)
239 if os
.path
.normcase('foo') == os
.path
.normcase('FOO'):
240 my_normpath
= os
.path
.normcase
242 my_normpath
= os
.path
.normpath
244 def deps_match(self
, deps
, headers
) -> None:
246 scanned
= list(map(my_normpath
, list(map(str, deps
))))
247 expect
= list(map(my_normpath
, headers
))
248 self
.assertTrue(scanned
== expect
, f
"expect {expect} != scanned {scanned}")
252 class CScannerTestCase1(unittest
.TestCase
):
253 def runTest(self
) -> None:
254 """Find local files with no CPPPATH"""
255 env
= DummyEnvironment(CPPPATH
=[])
256 s
= SCons
.Scanner
.C
.CScanner()
258 deps
= s(env
.File('f1.cpp'), env
, path
)
259 headers
= ['f1.h', 'f2.h']
260 deps_match(self
, deps
, headers
)
262 class CScannerTestCase2(unittest
.TestCase
):
263 def runTest(self
) -> None:
264 """Find a file in a CPPPATH directory"""
265 env
= DummyEnvironment(CPPPATH
=[test
.workpath("d1")])
266 s
= SCons
.Scanner
.C
.CScanner()
268 deps
= s(env
.File('f1.cpp'), env
, path
)
269 headers
= ['f1.h', 'd1/f2.h']
270 deps_match(self
, deps
, headers
)
272 class CScannerTestCase3(unittest
.TestCase
):
273 def runTest(self
) -> None:
274 """Find files in explicit subdirectories, ignore missing file"""
275 env
= DummyEnvironment(CPPPATH
=[test
.workpath("d1")])
276 s
= SCons
.Scanner
.C
.CScanner()
278 deps
= s(env
.File('f2.cpp'), env
, path
)
279 headers
= ['d1/f1.h', 'f1.h', 'd1/d2/f1.h']
280 deps_match(self
, deps
, headers
)
282 class CScannerTestCase4(unittest
.TestCase
):
283 def runTest(self
) -> None:
284 """Find files in explicit subdirectories"""
285 env
= DummyEnvironment(CPPPATH
=[test
.workpath("d1"), test
.workpath("d1/d2")])
286 s
= SCons
.Scanner
.C
.CScanner()
288 deps
= s(env
.File('f2.cpp'), env
, path
)
289 headers
= ['d1/f1.h', 'f1.h', 'd1/d2/f1.h', 'd1/d2/f4.h']
290 deps_match(self
, deps
, headers
)
292 class CScannerTestCase5(unittest
.TestCase
):
293 def runTest(self
) -> None:
294 """Make sure files in repositories will get scanned"""
295 env
= DummyEnvironment(CPPPATH
=[])
296 s
= SCons
.Scanner
.C
.CScanner()
299 n
= env
.File('f3.cpp')
301 s
.Tag('rexists_called', 1)
302 return SCons
.Node
._rexists
_map
[s
.GetTag('old_rexists')](s
)
303 n
.Tag('old_rexists', n
._func
_rexists
)
304 SCons
.Node
._rexists
_map
[3] = my_rexists
307 deps
= s(n
, env
, path
)
309 # Make sure rexists() got called on the file node being
310 # scanned, essential for cooperation with VariantDir functionality.
311 assert n
.GetTag('rexists_called')
313 headers
= ['f1.h', 'f2.h', 'f3-test.h',
314 'd1/f1.h', 'd1/f2.h', 'd1/f3-test.h']
315 deps_match(self
, deps
, headers
)
317 class CScannerTestCase6(unittest
.TestCase
):
318 def runTest(self
) -> None:
319 """Find a same-named file in different directories when CPPPATH changes"""
320 env1
= DummyEnvironment(CPPPATH
=[test
.workpath("d1")])
321 env2
= DummyEnvironment(CPPPATH
=[test
.workpath("d1/d2")])
322 s
= SCons
.Scanner
.C
.CScanner()
325 deps1
= s(env1
.File('f1.cpp'), env1
, path1
)
326 deps2
= s(env2
.File('f1.cpp'), env2
, path2
)
327 headers1
= ['f1.h', 'd1/f2.h']
328 headers2
= ['f1.h', 'd1/d2/f2.h']
329 deps_match(self
, deps1
, headers1
)
330 deps_match(self
, deps2
, headers2
)
332 class CScannerTestCase8(unittest
.TestCase
):
333 def runTest(self
) -> None:
334 """Find files in a subdirectory relative to the current directory"""
335 env
= DummyEnvironment(CPPPATH
=["include"])
336 s
= SCons
.Scanner
.C
.CScanner()
338 deps1
= s(env
.File('fa.cpp'), env
, path
)
339 env
.fs
.chdir(env
.Dir('subdir'))
340 dir = env
.fs
.getcwd()
341 env
.fs
.chdir(env
.Dir(''))
342 path
= s
.path(env
, dir)
343 deps2
= s(env
.File('#fa.cpp'), env
, path
)
344 headers1
= list(map(test
.workpath
, ['include/fa.h', 'include/fb.h']))
345 headers2
= ['include/fa.h', 'include/fb.h']
346 deps_match(self
, deps1
, headers1
)
347 deps_match(self
, deps2
, headers2
)
349 class CScannerTestCase9(unittest
.TestCase
):
350 def runTest(self
) -> None:
351 """Generate a warning when we can't find a #included file"""
352 SCons
.Warnings
.enableWarningClass(SCons
.Warnings
.DependencyWarning
)
354 def __call__(self
, x
) -> None:
359 SCons
.Warnings
._warningOut
= to
360 test
.write('fa.h','\n')
361 fs
= SCons
.Node
.FS
.FS(test
.workpath(''))
362 env
= DummyEnvironment(CPPPATH
=[])
364 s
= SCons
.Scanner
.C
.CScanner()
366 deps
= s(fs
.File('fa.cpp'), env
, path
)
368 # Did we catch the warning associated with not finding fb.h?
371 deps_match(self
, deps
, [ 'fa.h' ])
374 class CScannerTestCase10(unittest
.TestCase
):
375 def runTest(self
) -> None:
376 """Find files in the local directory when the scanned file is elsewhere"""
377 fs
= SCons
.Node
.FS
.FS(test
.workpath(''))
378 fs
.chdir(fs
.Dir('include'))
379 env
= DummyEnvironment(CPPPATH
=[])
381 s
= SCons
.Scanner
.C
.CScanner()
383 test
.write('include/fa.cpp', test
.read('fa.cpp'))
384 fs
.chdir(fs
.Dir('..'))
385 deps
= s(fs
.File('#include/fa.cpp'), env
, path
)
386 deps_match(self
, deps
, [ 'include/fa.h', 'include/fb.h' ])
387 test
.unlink('include/fa.cpp')
389 class CScannerTestCase11(unittest
.TestCase
):
390 def runTest(self
) -> None:
391 """Handle dependencies on a derived .h file in a non-existent directory"""
392 os
.chdir(test
.workpath('work'))
393 fs
= SCons
.Node
.FS
.FS(test
.workpath('work'))
394 fs
.Repository(test
.workpath('repository'))
396 # Create a derived file in a directory that does not exist yet.
397 # This was a bug at one time.
398 f1
=fs
.File('include2/jjj.h')
400 env
= DummyEnvironment(CPPPATH
=['include', 'include2'])
402 s
= SCons
.Scanner
.C
.CScanner()
404 deps
= s(fs
.File('src/fff.c'), env
, path
)
405 deps_match(self
, deps
, [ test
.workpath('repository/include/iii.h'),
407 os
.chdir(test
.workpath(''))
409 class CScannerTestCase12(unittest
.TestCase
):
410 def runTest(self
) -> None:
411 """Find files in VariantDir() directories"""
412 os
.chdir(test
.workpath('work'))
413 fs
= SCons
.Node
.FS
.FS(test
.workpath('work'))
414 fs
.VariantDir('build1', 'src', 1)
415 fs
.VariantDir('build2', 'src', 0)
416 fs
.Repository(test
.workpath('repository'))
417 env
= DummyEnvironment(CPPPATH
=[])
419 s
= SCons
.Scanner
.C
.CScanner()
421 deps1
= s(fs
.File('build1/aaa.c'), env
, path
)
422 deps_match(self
, deps1
, [ 'build1/bbb.h' ])
423 deps2
= s(fs
.File('build2/aaa.c'), env
, path
)
424 deps_match(self
, deps2
, [ 'src/bbb.h' ])
425 deps3
= s(fs
.File('build1/ccc.c'), env
, path
)
426 deps_match(self
, deps3
, [ 'build1/ddd.h' ])
427 deps4
= s(fs
.File('build2/ccc.c'), env
, path
)
428 deps_match(self
, deps4
, [ test
.workpath('repository/src/ddd.h') ])
429 os
.chdir(test
.workpath(''))
431 class CScannerTestCase13(unittest
.TestCase
):
432 def runTest(self
) -> None:
433 """Find files in directories named in a substituted environment variable"""
434 class SubstEnvironment(DummyEnvironment
):
435 def subst(self
, arg
, target
=None, source
=None, conv
=None, test
=test
):
437 return test
.workpath("d1")
440 env
= SubstEnvironment(CPPPATH
=["$blah"])
441 s
= SCons
.Scanner
.C
.CScanner()
443 deps
= s(env
.File('f1.cpp'), env
, path
)
444 headers
= ['f1.h', 'd1/f2.h']
445 deps_match(self
, deps
, headers
)
447 class CScannerTestCase14(unittest
.TestCase
):
448 def runTest(self
) -> None:
449 """Find files when there's no space between "#include" and the name"""
450 env
= DummyEnvironment(CPPPATH
=[])
451 s
= SCons
.Scanner
.C
.CScanner()
453 deps
= s(env
.File('f5.c'), env
, path
)
454 headers
= ['f5a.h', 'f5b.h']
455 deps_match(self
, deps
, headers
)
457 class CScannerTestCase15(unittest
.TestCase
):
458 def runTest(self
) -> None:
459 """Verify scanner initialization with the suffixes in $CPPSUFFIXES"""
460 suffixes
= [".c", ".C", ".cxx", ".cpp", ".c++", ".cc",
461 ".h", ".H", ".hxx", ".hpp", ".hh",
462 ".F", ".fpp", ".FPP",
463 ".S", ".spp", ".SPP"]
464 env
= DummyEnvironment(CPPSUFFIXES
= suffixes
)
465 s
= SCons
.Scanner
.C
.CScanner()
466 for suffix
in suffixes
:
467 assert suffix
in s
.get_skeys(env
), f
"{suffix} not in skeys"
470 class CConditionalScannerTestCase1(unittest
.TestCase
):
471 def runTest(self
) -> None:
472 """Find local files with no CPPPATH"""
473 env
= DummyEnvironment(CPPPATH
=[])
474 s
= SCons
.Scanner
.C
.CConditionalScanner()
476 deps
= s(env
.File('f1.cpp'), env
, path
)
478 deps_match(self
, deps
, headers
)
481 class CConditionalScannerTestCase2(unittest
.TestCase
):
482 def runTest(self
) -> None:
483 """Find local files with no CPPPATH based on #ifdef"""
484 env
= DummyEnvironment(CPPPATH
=[], CPPDEFINES
=["INCLUDE_F2"])
485 s
= SCons
.Scanner
.C
.CConditionalScanner()
487 deps
= s(env
.File('f1.cpp'), env
, path
)
488 headers
= ['f2.h', 'fi.h']
489 deps_match(self
, deps
, headers
)
492 class CConditionalScannerTestCase3(unittest
.TestCase
):
493 def runTest(self
) -> None:
494 """Find files in explicit subdirectories, ignore missing file"""
495 env
= DummyEnvironment(
496 CPPPATH
=[test
.workpath("d1")],
497 CPPDEFINES
=[("NESTED_CONDITION", 1)]
499 s
= SCons
.Scanner
.C
.CConditionalScanner()
500 deps
= s(env
.File('f2.cpp'), env
, s
.path(env
))
501 headers
= ['d1/f1.h', 'd1/d2/f1.h']
502 deps_match(self
, deps
, headers
)
504 # disable nested conditions
505 env
= DummyEnvironment(
506 CPPPATH
=[test
.workpath("d1")],
507 CPPDEFINES
=[("NESTED_CONDITION", 0)]
509 s
= SCons
.Scanner
.C
.CConditionalScanner()
510 deps
= s(env
.File('f2.cpp'), env
, s
.path(env
))
511 headers
= ['d1/f1.h']
512 deps_match(self
, deps
, headers
)
515 class CConditionalScannerTestCase4(unittest
.TestCase
):
516 def runTest(self
) -> None:
517 """Test that dependency is detected if #include uses a macro."""
519 with self
.subTest("macro defined in the source file"):
520 env
= DummyEnvironment()
521 s
= SCons
.Scanner
.C
.CConditionalScanner()
522 deps
= s(env
.File('f9a.c'), env
, s
.path(env
))
524 deps_match(self
, deps
, headers
)
526 with self
.subTest("macro defined on the command line"):
527 env
= DummyEnvironment(CPPDEFINES
='HEADER=\\"f9.h\\"')
528 #env = DummyEnvironment(CPPDEFINES=['HEADER=\\"f9.h\\"'])
529 s
= SCons
.Scanner
.C
.CConditionalScanner()
530 deps
= s(env
.File('f9b.c'), env
, s
.path(env
))
532 deps_match(self
, deps
, headers
)
535 class dictify_CPPDEFINESTestCase(unittest
.TestCase
):
536 def runTest(self
) -> None:
537 """Make sure CPPDEFINES converts correctly.
539 Various types and combinations of types could fail if not handled
540 specifically by dictify_CPPDEFINES - this is a regression test.
542 with self
.subTest("tuples in a sequence, including one without value"):
543 env
= DummyEnvironment(CPPDEFINES
=[("VALUED", 1), ("UNVALUED",)])
544 d
= SCons
.Scanner
.C
.dictify_CPPDEFINES(env
)
545 expect
= {"VALUED": 1, "UNVALUED": None}
546 self
.assertEqual(d
, expect
)
548 with self
.subTest("tuple-valued define by itself"):
549 env
= DummyEnvironment(CPPDEFINES
=("STRING", "VALUE"))
550 d
= SCons
.Scanner
.C
.dictify_CPPDEFINES(env
)
551 expect
= {"STRING": "VALUE"}
552 self
.assertEqual(d
, expect
)
554 with self
.subTest("string-valued define in a sequence"):
555 env
= DummyEnvironment(CPPDEFINES
=[("STRING=VALUE")])
556 d
= SCons
.Scanner
.C
.dictify_CPPDEFINES(env
)
557 expect
= {"STRING": "VALUE"}
558 self
.assertEqual(d
, expect
)
560 with self
.subTest("string-valued define by itself"):
561 env
= DummyEnvironment(CPPDEFINES
="STRING=VALUE")
562 d
= SCons
.Scanner
.C
.dictify_CPPDEFINES(env
)
563 expect
= {"STRING": "VALUE"}
564 self
.assertEqual(d
, expect
)
566 from collections
import deque
567 with self
.subTest("compound CPPDEFINES in internal format"):
568 env
= DummyEnvironment(
569 CPPDEFINES
=deque([("STRING", "VALUE"), ("UNVALUED",)])
571 d
= SCons
.Scanner
.C
.dictify_CPPDEFINES(env
)
572 expect
= {"STRING": "VALUE", "UNVALUED": None}
573 self
.assertEqual(d
, expect
)
575 with self
.subTest("CPPDEFINES with macro replacement"):
576 env
= DummyEnvironment(
579 ("REPLACEABLE", "RVALUE"),
580 ("RVALUE", "AVALUE"),
583 d
= SCons
.Scanner
.C
.dictify_CPPDEFINES(env
, replace
=True)
584 expect
= {"STRING": "VALUE", "REPLACEABLE": "AVALUE", "RVALUE": "AVALUE"}
585 self
.assertEqual(d
, expect
)
588 if __name__
== "__main__":
593 # indent-tabs-mode:nil
595 # vim: set expandtab tabstop=4 shiftwidth=4: