1 """SCons.Environment
2
3 Base class for construction Environments. These are
4 the primary objects used to communicate dependency and
5 construction information to the build engine.
6
7 Keyword arguments supplied when the construction Environment
8 is created are construction variables used to initialize the
9 Environment
10 """
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35 __revision__ = "src/engine/SCons/Environment.py 3266 2008/08/12 07:31:01 knight"
36
37
38 import copy
39 import os
40 import os.path
41 import re
42 import shlex
43 import string
44 from UserDict import UserDict
45
46 import SCons.Action
47 import SCons.Builder
48 from SCons.Debug import logInstanceCreation
49 import SCons.Defaults
50 import SCons.Errors
51 import SCons.Memoize
52 import SCons.Node
53 import SCons.Node.Alias
54 import SCons.Node.FS
55 import SCons.Node.Python
56 import SCons.Platform
57 import SCons.SConsign
58 import SCons.Subst
59 import SCons.Tool
60 import SCons.Util
61 import SCons.Warnings
62
65
66 _null = _Null
67
68 _warn_copy_deprecated = True
69 _warn_source_signatures_deprecated = True
70 _warn_target_signatures_deprecated = True
71
72 CleanTargets = {}
73 CalculatorArgs = {}
74
75 semi_deepcopy = SCons.Util.semi_deepcopy
76
77
78
79
80 UserError = SCons.Errors.UserError
81
84
85 AliasBuilder = SCons.Builder.Builder(action = alias_builder,
86 target_factory = SCons.Node.Alias.default_ans.Alias,
87 source_factory = SCons.Node.FS.Entry,
88 multi = 1,
89 is_explicit = None,
90 name='AliasBuilder')
91
107
108
109
110
111 reserved_construction_var_names = \
112 ['TARGET', 'TARGETS', 'SOURCE', 'SOURCES']
113
122
126
128 try:
129 bd = env._dict[key]
130 for k in bd.keys():
131 del bd[k]
132 except KeyError:
133 bd = BuilderDict(kwbd, env)
134 env._dict[key] = bd
135 bd.update(value)
136
140
144
145
146
147
148
149
150
151
152
153
154
155
156
157
159 """
160 A generic Wrapper class that associates a method (which can
161 actually be any callable) with an object. As part of creating this
162 MethodWrapper object an attribute with the specified (by default,
163 the name of the supplied method) is added to the underlying object.
164 When that new "method" is called, our __call__() method adds the
165 object as the first argument, simulating the Python behavior of
166 supplying "self" on method calls.
167
168 We hang on to the name by which the method was added to the underlying
169 base class so that we can provide a method to "clone" ourselves onto
170 a new underlying object being copied (without which we wouldn't need
171 to save that info).
172 """
173 - def __init__(self, object, method, name=None):
180
182 nargs = (self.object,) + args
183 return apply(self.method, nargs, kwargs)
184
185 - def clone(self, new_object):
186 """
187 Returns an object that re-binds the underlying "method" to
188 the specified new object.
189 """
190 return self.__class__(new_object, self.method, self.name)
191
193 """
194 A MethodWrapper subclass that that associates an environment with
195 a Builder.
196
197 This mainly exists to wrap the __call__() function so that all calls
198 to Builders can have their argument lists massaged in the same way
199 (treat a lone argument as the source, treat two arguments as target
200 then source, make sure both target and source are lists) without
201 having to have cut-and-paste code to do it.
202
203 As a bit of obsessive backwards compatibility, we also intercept
204 attempts to get or set the "env" or "builder" attributes, which were
205 the names we used before we put the common functionality into the
206 MethodWrapper base class. We'll keep this around for a while in case
207 people shipped Tool modules that reached into the wrapper (like the
208 Tool/qt.py module does, or did). There shouldn't be a lot attribute
209 fetching or setting on these, so a little extra work shouldn't hurt.
210 """
212 if source is _null:
213 source = target
214 target = None
215 if not target is None and not SCons.Util.is_List(target):
216 target = [target]
217 if not source is None and not SCons.Util.is_List(source):
218 source = [source]
219 return apply(MethodWrapper.__call__, (self, target, source) + args, kw)
220
222 return '<BuilderWrapper %s>' % repr(self.name)
223
226
228 if name == 'env':
229 return self.object
230 elif name == 'builder':
231 return self.method
232 else:
233 return self.__dict__[name]
234
236 if name == 'env':
237 self.object = value
238 elif name == 'builder':
239 self.method = value
240 else:
241 self.__dict__[name] = value
242
243
244
245
246
247
248
249
250
251
252
253
255 """This is a dictionary-like class used by an Environment to hold
256 the Builders. We need to do this because every time someone changes
257 the Builders in the Environment's BUILDERS dictionary, we must
258 update the Environment's attributes."""
260
261
262
263 self.env = env
264 UserDict.__init__(self, dict)
265
267 return self.__class__(self.data, self.env)
268
270 try:
271 method = getattr(self.env, item).method
272 except AttributeError:
273 pass
274 else:
275 self.env.RemoveMethod(method)
276 UserDict.__setitem__(self, item, val)
277 BuilderWrapper(self.env, val, item)
278
280 UserDict.__delitem__(self, item)
281 delattr(self.env, item)
282
286
287
288
289 _is_valid_var = re.compile(r'[_a-zA-Z]\w*$')
290
292 """Return if the specified string is a legitimate construction
293 variable.
294 """
295 return _is_valid_var.match(varstr)
296
297
298
300 """Base class for different flavors of construction environments.
301
302 This class contains a minimal set of methods that handle contruction
303 variable expansion and conversion of strings to Nodes, which may or
304 may not be actually useful as a stand-alone class. Which methods
305 ended up in this class is pretty arbitrary right now. They're
306 basically the ones which we've empirically determined are common to
307 the different construction environment subclasses, and most of the
308 others that use or touch the underlying dictionary of construction
309 variables.
310
311 Eventually, this class should contain all the methods that we
312 determine are necessary for a "minimal" interface to the build engine.
313 A full "native Python" SCons environment has gotten pretty heavyweight
314 with all of the methods and Tools and construction variables we've
315 jammed in there, so it would be nice to have a lighter weight
316 alternative for interfaces that don't need all of the bells and
317 whistles. (At some point, we'll also probably rename this class
318 "Base," since that more reflects what we want this class to become,
319 but because we've released comments that tell people to subclass
320 Environment.Base to create their own flavors of construction
321 environment, we'll save that for a future refactoring when this
322 class actually becomes useful.)
323 """
324
325 if SCons.Memoize.use_memoizer:
326 __metaclass__ = SCons.Memoize.Memoized_Metaclass
327
338
339
341 """Initial the dispatch tables for special handling of
342 special construction variables."""
343 self._special_del = {}
344 self._special_del['SCANNERS'] = _del_SCANNERS
345
346 self._special_set = {}
347 for key in reserved_construction_var_names:
348 self._special_set[key] = _set_reserved
349 self._special_set['BUILDERS'] = _set_BUILDERS
350 self._special_set['SCANNERS'] = _set_SCANNERS
351
352
353
354
355 self._special_set_keys = self._special_set.keys()
356
358 return cmp(self._dict, other._dict)
359
361 special = self._special_del.get(key)
362 if special:
363 special(self, key)
364 else:
365 del self._dict[key]
366
368 return self._dict[key]
369
371
372
373
374
375
376
377
378
379
380
381
382
383 if key in self._special_set_keys:
384 self._special_set[key](self, key, value)
385 else:
386
387
388
389
390 if not self._dict.has_key(key) \
391 and not _is_valid_var.match(key):
392 raise SCons.Errors.UserError, "Illegal construction variable `%s'" % key
393 self._dict[key] = value
394
395 - def get(self, key, default=None):
396 "Emulates the get() method of dictionaries."""
397 return self._dict.get(key, default)
398
401
403 return self._dict.items()
404
406 if node_factory is _null:
407 node_factory = self.fs.File
408 if lookup_list is _null:
409 lookup_list = self.lookup_list
410
411 if not args:
412 return []
413
414 args = SCons.Util.flatten(args)
415
416 nodes = []
417 for v in args:
418 if SCons.Util.is_String(v):
419 n = None
420 for l in lookup_list:
421 n = l(v)
422 if not n is None:
423 break
424 if not n is None:
425 if SCons.Util.is_String(n):
426
427 kw['raw'] = 1
428 n = apply(self.subst, (n,), kw)
429 if node_factory:
430 n = node_factory(n)
431 if SCons.Util.is_List(n):
432 nodes.extend(n)
433 else:
434 nodes.append(n)
435 elif node_factory:
436
437 kw['raw'] = 1
438 v = node_factory(apply(self.subst, (v,), kw))
439 if SCons.Util.is_List(v):
440 nodes.extend(v)
441 else:
442 nodes.append(v)
443 else:
444 nodes.append(v)
445
446 return nodes
447
450
453
454 - def subst(self, string, raw=0, target=None, source=None, conv=None):
455 """Recursively interpolates construction variables from the
456 Environment into the specified string, returning the expanded
457 result. Construction variables are specified by a $ prefix
458 in the string and begin with an initial underscore or
459 alphabetic character followed by any number of underscores
460 or alphanumeric characters. The construction variable names
461 may be surrounded by curly braces to separate the name from
462 trailing characters.
463 """
464 gvars = self.gvars()
465 lvars = self.lvars()
466 lvars['__env__'] = self
467 return SCons.Subst.scons_subst(string, self, raw, target, source, gvars, lvars, conv)
468
469 - def subst_kw(self, kw, raw=0, target=None, source=None):
470 nkw = {}
471 for k, v in kw.items():
472 k = self.subst(k, raw, target, source)
473 if SCons.Util.is_String(v):
474 v = self.subst(v, raw, target, source)
475 nkw[k] = v
476 return nkw
477
478 - def subst_list(self, string, raw=0, target=None, source=None, conv=None):
485
486 - def subst_path(self, path, target=None, source=None):
487 """Substitute a path list, turning EntryProxies into Nodes
488 and leaving Nodes (and other objects) as-is."""
489
490 if not SCons.Util.is_List(path):
491 path = [path]
492
493 def s(obj):
494 """This is the "string conversion" routine that we have our
495 substitutions use to return Nodes, not strings. This relies
496 on the fact that an EntryProxy object has a get() method that
497 returns the underlying Node that it wraps, which is a bit of
498 architectural dependence that we might need to break or modify
499 in the future in response to additional requirements."""
500 try:
501 get = obj.get
502 except AttributeError:
503 obj = SCons.Util.to_String_for_subst(obj)
504 else:
505 obj = get()
506 return obj
507
508 r = []
509 for p in path:
510 if SCons.Util.is_String(p):
511 p = self.subst(p, target=target, source=source, conv=s)
512 if SCons.Util.is_List(p):
513 if len(p) == 1:
514 p = p[0]
515 else:
516
517
518
519 p = string.join(map(SCons.Util.to_String_for_subst, p), '')
520 else:
521 p = s(p)
522 r.append(p)
523 return r
524
525 subst_target_source = subst
526
528 import subprocess
529 if SCons.Util.is_List(command):
530 p = subprocess.Popen(command,
531 stdout=subprocess.PIPE,
532 stderr=subprocess.PIPE,
533 universal_newlines=True)
534 else:
535 p = subprocess.Popen(command,
536 stdout=subprocess.PIPE,
537 stderr=subprocess.PIPE,
538 universal_newlines=True,
539 shell=True)
540 out = p.stdout.read()
541 p.stdout.close()
542 err = p.stderr.read()
543 p.stderr.close()
544 status = p.wait()
545 if err:
546 import sys
547 sys.stderr.write(err)
548 if status:
549 raise OSError("'%s' exited %d" % (command, status))
550 return out
551
553 """
554 Adds the specified function as a method of this construction
555 environment with the specified name. If the name is omitted,
556 the default name is the name of the function itself.
557 """
558 method = MethodWrapper(self, function, name)
559 self.added_methods.append(method)
560
562 """
563 Removes the specified function's MethodWrapper from the
564 added_methods list, so we don't re-bind it when making a clone.
565 """
566 is_not_func = lambda dm, f=function: not dm.method is f
567 self.added_methods = filter(is_not_func, self.added_methods)
568
570 """
571 Produce a modified environment whose variables are overriden by
572 the overrides dictionaries. "overrides" is a dictionary that
573 will override the variables of this environment.
574
575 This function is much more efficient than Clone() or creating
576 a new Environment because it doesn't copy the construction
577 environment dictionary, it just wraps the underlying construction
578 environment, and doesn't even create a wrapper object if there
579 are no overrides.
580 """
581 if not overrides: return self
582 o = copy_non_reserved_keywords(overrides)
583 if not o: return self
584 overrides = {}
585 merges = None
586 for key, value in o.items():
587 if key == 'parse_flags':
588 merges = value
589 else:
590 overrides[key] = SCons.Subst.scons_subst_once(value, self, key)
591 env = OverrideEnvironment(self, overrides)
592 if merges: env.MergeFlags(merges)
593 return env
594
596 """
597 Parse the set of flags and return a dict with the flags placed
598 in the appropriate entry. The flags are treated as a typical
599 set of command-line flags for a GNU-like toolchain and used to
600 populate the entries in the dict immediately below. If one of
601 the flag strings begins with a bang (exclamation mark), it is
602 assumed to be a command and the rest of the string is executed;
603 the result of that evaluation is then added to the dict.
604 """
605 dict = {
606 'ASFLAGS' : SCons.Util.CLVar(''),
607 'CFLAGS' : SCons.Util.CLVar(''),
608 'CCFLAGS' : SCons.Util.CLVar(''),
609 'CPPDEFINES' : [],
610 'CPPFLAGS' : SCons.Util.CLVar(''),
611 'CPPPATH' : [],
612 'FRAMEWORKPATH' : SCons.Util.CLVar(''),
613 'FRAMEWORKS' : SCons.Util.CLVar(''),
614 'LIBPATH' : [],
615 'LIBS' : [],
616 'LINKFLAGS' : SCons.Util.CLVar(''),
617 'RPATH' : [],
618 }
619
620
621
622 def do_parse(arg, me, self = self, dict = dict):
623
624 if not arg:
625 return
626
627 if not SCons.Util.is_String(arg):
628 for t in arg: me(t, me)
629 return
630
631
632 if arg[0] == '!':
633 arg = self.backtick(arg[1:])
634
635
636 def append_define(name, dict = dict):
637 t = string.split(name, '=')
638 if len(t) == 1:
639 dict['CPPDEFINES'].append(name)
640 else:
641 dict['CPPDEFINES'].append([t[0], string.join(t[1:], '=')])
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663 params = shlex.split(arg)
664 append_next_arg_to = None
665 for arg in params:
666 if append_next_arg_to:
667 if append_next_arg_to == 'CPPDEFINES':
668 append_define(arg)
669 elif append_next_arg_to == '-include':
670 t = ('-include', self.fs.File(arg))
671 dict['CCFLAGS'].append(t)
672 elif append_next_arg_to == '-isysroot':
673 t = ('-isysroot', arg)
674 dict['CCFLAGS'].append(t)
675 dict['LINKFLAGS'].append(t)
676 elif append_next_arg_to == '-arch':
677 t = ('-arch', arg)
678 dict['CCFLAGS'].append(t)
679 dict['LINKFLAGS'].append(t)
680 else:
681 dict[append_next_arg_to].append(arg)
682 append_next_arg_to = None
683 elif not arg[0] in ['-', '+']:
684 dict['LIBS'].append(self.fs.File(arg))
685 elif arg[:2] == '-L':
686 if arg[2:]:
687 dict['LIBPATH'].append(arg[2:])
688 else:
689 append_next_arg_to = 'LIBPATH'
690 elif arg[:2] == '-l':
691 if arg[2:]:
692 dict['LIBS'].append(arg[2:])
693 else:
694 append_next_arg_to = 'LIBS'
695 elif arg[:2] == '-I':
696 if arg[2:]:
697 dict['CPPPATH'].append(arg[2:])
698 else:
699 append_next_arg_to = 'CPPPATH'
700 elif arg[:4] == '-Wa,':
701 dict['ASFLAGS'].append(arg[4:])
702 dict['CCFLAGS'].append(arg)
703 elif arg[:4] == '-Wl,':
704 if arg[:11] == '-Wl,-rpath=':
705 dict['RPATH'].append(arg[11:])
706 elif arg[:7] == '-Wl,-R,':
707 dict['RPATH'].append(arg[7:])
708 elif arg[:6] == '-Wl,-R':
709 dict['RPATH'].append(arg[6:])
710 else:
711 dict['LINKFLAGS'].append(arg)
712 elif arg[:4] == '-Wp,':
713 dict['CPPFLAGS'].append(arg)
714 elif arg[:2] == '-D':
715 if arg[2:]:
716 append_define(arg[2:])
717 else:
718 append_next_arg_to = 'CPPDEFINES'
719 elif arg == '-framework':
720 append_next_arg_to = 'FRAMEWORKS'
721 elif arg[:14] == '-frameworkdir=':
722 dict['FRAMEWORKPATH'].append(arg[14:])
723 elif arg[:2] == '-F':
724 if arg[2:]:
725 dict['FRAMEWORKPATH'].append(arg[2:])
726 else:
727 append_next_arg_to = 'FRAMEWORKPATH'
728 elif arg == '-mno-cygwin':
729 dict['CCFLAGS'].append(arg)
730 dict['LINKFLAGS'].append(arg)
731 elif arg == '-mwindows':
732 dict['LINKFLAGS'].append(arg)
733 elif arg == '-pthread':
734 dict['CCFLAGS'].append(arg)
735 dict['LINKFLAGS'].append(arg)
736 elif arg[:5] == '-std=':
737 dict['CFLAGS'].append(arg)
738 elif arg[0] == '+':
739 dict['CCFLAGS'].append(arg)
740 dict['LINKFLAGS'].append(arg)
741 elif arg in ['-include', '-isysroot', '-arch']:
742 append_next_arg_to = arg
743 else:
744 dict['CCFLAGS'].append(arg)
745
746 for arg in flags:
747 do_parse(arg, do_parse)
748 return dict
749
751 """
752 Merge the dict in args into the construction variables. If args
753 is not a dict, it is converted into a dict using ParseFlags.
754 If unique is not set, the flags are appended rather than merged.
755 """
756
757 if not SCons.Util.is_Dict(args):
758 args = self.ParseFlags(args)
759 if not unique:
760 apply(self.Append, (), args)
761 return self
762 for key, value in args.items():
763 if not value:
764 continue
765 try:
766 orig = self[key]
767 except KeyError:
768 orig = value
769 else:
770 if not orig:
771 orig = value
772 elif value:
773
774
775
776
777
778
779 try:
780 orig = orig + value
781 except (KeyError, TypeError):
782 try:
783 add_to_orig = orig.append
784 except AttributeError:
785 value.insert(0, orig)
786 orig = value
787 else:
788 add_to_orig(value)
789 t = []
790 if key[-4:] == 'PATH':
791
792 for v in orig:
793 if v not in t:
794 t.append(v)
795 else:
796
797 orig.reverse()
798 for v in orig:
799 if v not in t:
800 t.insert(0, v)
801 self[key] = t
802 return self
803
804
805
814
818
822
824 f = SCons.Defaults.DefaultEnvironment().copy_from_cache
825 return f(src, dst)
826
827 -class Base(SubstitutionEnvironment):
828 """Base class for "real" construction Environments. These are the
829 primary objects used to communicate dependency and construction
830 information to the build engine.
831
832 Keyword arguments supplied when the construction Environment
833 is created are construction variables used to initialize the
834 Environment.
835 """
836
837 if SCons.Memoize.use_memoizer:
838 __metaclass__ = SCons.Memoize.Memoized_Metaclass
839
840 memoizer_counters = []
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856 - def __init__(self,
857 platform=None,
858 tools=None,
859 toolpath=None,
860 variables=None,
861 parse_flags = None,
862 **kw):
863 """
864 Initialization of a basic SCons construction environment,
865 including setting up special construction variables like BUILDER,
866 PLATFORM, etc., and searching for and applying available Tools.
867
868 Note that we do *not* call the underlying base class
869 (SubsitutionEnvironment) initialization, because we need to
870 initialize things in a very specific order that doesn't work
871 with the much simpler base class initialization.
872 """
873 if __debug__: logInstanceCreation(self, 'Environment.Base')
874 self._memo = {}
875 self.fs = SCons.Node.FS.get_default_fs()
876 self.ans = SCons.Node.Alias.default_ans
877 self.lookup_list = SCons.Node.arg2nodes_lookups
878 self._dict = semi_deepcopy(SCons.Defaults.ConstructionEnvironment)
879 self._init_special()
880 self.added_methods = []
881
882
883
884
885
886
887
888 self.decide_target = default_decide_target
889 self.decide_source = default_decide_source
890
891 self.copy_from_cache = default_copy_from_cache
892
893 self._dict['BUILDERS'] = BuilderDict(self._dict['BUILDERS'], self)
894
895 if platform is None:
896 platform = self._dict.get('PLATFORM', None)
897 if platform is None:
898 platform = SCons.Platform.Platform()
899 if SCons.Util.is_String(platform):
900 platform = SCons.Platform.Platform(platform)
901 self._dict['PLATFORM'] = str(platform)
902 platform(self)
903
904
905
906
907 if kw.has_key('options'):
908
909
910 variables = kw['options']
911 del kw['options']
912 apply(self.Replace, (), kw)
913 keys = kw.keys()
914 if variables:
915 keys = keys + variables.keys()
916 variables.Update(self)
917
918 save = {}
919 for k in keys:
920 try:
921 save[k] = self._dict[k]
922 except KeyError:
923
924
925 pass
926
927 SCons.Tool.Initializers(self)
928
929 if tools is None:
930 tools = self._dict.get('TOOLS', None)
931 if tools is None:
932 tools = ['default']
933 apply_tools(self, tools, toolpath)
934
935
936
937
938 for key, val in save.items():
939 self._dict[key] = val
940
941
942 if parse_flags: self.MergeFlags(parse_flags)
943
944
945
946
947
948
950 """Fetch the builder with the specified name from the environment.
951 """
952 try:
953 return self._dict['BUILDERS'][name]
954 except KeyError:
955 return None
956
958 try:
959 path = self._CacheDir_path
960 except AttributeError:
961 path = SCons.Defaults.DefaultEnvironment()._CacheDir_path
962 try:
963 if path == self._last_CacheDir_path:
964 return self._last_CacheDir
965 except AttributeError:
966 pass
967 cd = SCons.CacheDir.CacheDir(path)
968 self._last_CacheDir_path = path
969 self._last_CacheDir = cd
970 return cd
971
973 """Return a factory function for creating Nodes for this
974 construction environment.
975 """
976 name = default
977 try:
978 is_node = issubclass(factory, SCons.Node.Node)
979 except TypeError:
980
981
982 pass
983 else:
984 if is_node:
985
986
987
988
989 try: name = factory.__name__
990 except AttributeError: pass
991 else: factory = None
992 if not factory:
993
994
995
996
997
998 factory = getattr(self.fs, name)
999 return factory
1000
1001 memoizer_counters.append(SCons.Memoize.CountValue('_gsm'))
1002
1004 try:
1005 return self._memo['_gsm']
1006 except KeyError:
1007 pass
1008
1009 result = {}
1010
1011 try:
1012 scanners = self._dict['SCANNERS']
1013 except KeyError:
1014 pass
1015 else:
1016
1017
1018
1019
1020 if not SCons.Util.is_List(scanners):
1021 scanners = [scanners]
1022 else:
1023 scanners = scanners[:]
1024 scanners.reverse()
1025 for scanner in scanners:
1026 for k in scanner.get_skeys(self):
1027 result[k] = scanner
1028
1029 self._memo['_gsm'] = result
1030
1031 return result
1032
1034 """Find the appropriate scanner given a key (usually a file suffix).
1035 """
1036 return self._gsm().get(skey)
1037
1039 """Delete the cached scanner map (if we need to).
1040 """
1041 try:
1042 del self._memo['_gsm']
1043 except KeyError:
1044 pass
1045
1047 """Update an environment's values directly, bypassing the normal
1048 checks that occur when users try to set items.
1049 """
1050 self._dict.update(dict)
1051
1053 try:
1054 return self.src_sig_type
1055 except AttributeError:
1056 t = SCons.Defaults.DefaultEnvironment().src_sig_type
1057 self.src_sig_type = t
1058 return t
1059
1061 try:
1062 return self.tgt_sig_type
1063 except AttributeError:
1064 t = SCons.Defaults.DefaultEnvironment().tgt_sig_type
1065 self.tgt_sig_type = t
1066 return t
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1078 """Append values to existing construction variables
1079 in an Environment.
1080 """
1081 kw = copy_non_reserved_keywords(kw)
1082 for key, val in kw.items():
1083
1084
1085
1086
1087 try:
1088 orig = self._dict[key]
1089 except KeyError:
1090
1091
1092 self._dict[key] = val
1093 else:
1094 try:
1095
1096
1097
1098
1099
1100 update_dict = orig.update
1101 except AttributeError:
1102 try:
1103
1104
1105
1106 self._dict[key] = orig + val
1107 except (KeyError, TypeError):
1108 try:
1109
1110 add_to_orig = orig.append
1111 except AttributeError:
1112
1113
1114
1115
1116
1117 if orig:
1118 val.insert(0, orig)
1119 self._dict[key] = val
1120 else:
1121
1122
1123 if val:
1124 add_to_orig(val)
1125 else:
1126
1127
1128 if SCons.Util.is_List(val):
1129 for v in val:
1130 orig[v] = None
1131 else:
1132 try:
1133 update_dict(val)
1134 except (AttributeError, TypeError, ValueError):
1135 if SCons.Util.is_Dict(val):
1136 for k, v in val.items():
1137 orig[k] = v
1138 else:
1139 orig[val] = None
1140 self.scanner_map_delete(kw)
1141
1143 """Append path elements to the path 'name' in the 'ENV'
1144 dictionary for this environment. Will only add any particular
1145 path once, and will normpath and normcase all paths to help
1146 assure this. This can also handle the case where the env
1147 variable is a list instead of a string.
1148 """
1149
1150 orig = ''
1151 if self._dict.has_key(envname) and self._dict[envname].has_key(name):
1152 orig = self._dict[envname][name]
1153
1154 nv = SCons.Util.AppendPath(orig, newpath, sep)
1155
1156 if not self._dict.has_key(envname):
1157 self._dict[envname] = {}
1158
1159 self._dict[envname][name] = nv
1160
1162 """Append values to existing construction variables
1163 in an Environment, if they're not already there.
1164 """
1165 kw = copy_non_reserved_keywords(kw)
1166 for key, val in kw.items():
1167 if not self._dict.has_key(key) or self._dict[key] in ('', None):
1168 self._dict[key] = val
1169 elif SCons.Util.is_Dict(self._dict[key]) and \
1170 SCons.Util.is_Dict(val):
1171 self._dict[key].update(val)
1172 elif SCons.Util.is_List(val):
1173 dk = self._dict[key]
1174 if not SCons.Util.is_List(dk):
1175 dk = [dk]
1176 val = filter(lambda x, dk=dk: x not in dk, val)
1177 self._dict[key] = dk + val
1178 else:
1179 dk = self._dict[key]
1180 if SCons.Util.is_List(dk):
1181
1182
1183 if not val in dk:
1184 self._dict[key] = dk + [val]
1185 else:
1186 self._dict[key] = self._dict[key] + val
1187 self.scanner_map_delete(kw)
1188
1189 - def Clone(self, tools=[], toolpath=None, parse_flags = None, **kw):
1190 """Return a copy of a construction Environment. The
1191 copy is like a Python "deep copy"--that is, independent
1192 copies are made recursively of each objects--except that
1193 a reference is copied when an object is not deep-copyable
1194 (like a function). There are no references to any mutable
1195 objects in the original Environment.
1196 """
1197 clone = copy.copy(self)
1198 clone._dict = semi_deepcopy(self._dict)
1199
1200 try:
1201 cbd = clone._dict['BUILDERS']
1202 except KeyError:
1203 pass
1204 else:
1205 clone._dict['BUILDERS'] = BuilderDict(cbd, clone)
1206
1207
1208
1209
1210
1211 clone.added_methods = []
1212 for mw in self.added_methods:
1213 if mw == getattr(self, mw.name):
1214 clone.added_methods.append(mw.clone(clone))
1215
1216 clone._memo = {}
1217
1218
1219
1220 kw = copy_non_reserved_keywords(kw)
1221 new = {}
1222 for key, value in kw.items():
1223 new[key] = SCons.Subst.scons_subst_once(value, self, key)
1224 apply(clone.Replace, (), new)
1225
1226 apply_tools(clone, tools, toolpath)
1227
1228
1229 apply(clone.Replace, (), new)
1230
1231
1232 if parse_flags: clone.MergeFlags(parse_flags)
1233
1234 if __debug__: logInstanceCreation(self, 'Environment.EnvironmentClone')
1235 return clone
1236
1237 - def Copy(self, *args, **kw):
1244
1249
1250 - def _changed_content(self, dependency, target, prev_ni):
1251 return dependency.changed_content(target, prev_ni)
1252
1260
1261 - def _changed_timestamp_then_content(self, dependency, target, prev_ni):
1262 return dependency.changed_timestamp_then_content(target, prev_ni)
1263
1266
1269
1271 return self.fs.copy(src, dst)
1272
1274 return self.fs.copy2(src, dst)
1275
1299
1301 """Return the first available program in progs.
1302 """
1303 if not SCons.Util.is_List(progs):
1304 progs = [ progs ]
1305 for prog in progs:
1306 path = self.WhereIs(prog)
1307 if path: return prog
1308 return None
1309
1311 if not args:
1312 return self._dict
1313 dlist = map(lambda x, s=self: s._dict[x], args)
1314 if len(dlist) == 1:
1315 dlist = dlist[0]
1316 return dlist
1317
1318 - def Dump(self, key = None):
1319 """
1320 Using the standard Python pretty printer, dump the contents of the
1321 scons build environment to stdout.
1322
1323 If the key passed in is anything other than None, then that will
1324 be used as an index into the build environment dictionary and
1325 whatever is found there will be fed into the pretty printer. Note
1326 that this key is case sensitive.
1327 """
1328 import pprint
1329 pp = pprint.PrettyPrinter(indent=2)
1330 if key:
1331 dict = self.Dictionary(key)
1332 else:
1333 dict = self.Dictionary()
1334 return pp.pformat(dict)
1335
1336 - def FindIxes(self, paths, prefix, suffix):
1337 """
1338 Search a list of paths for something that matches the prefix and suffix.
1339
1340 paths - the list of paths or nodes.
1341 prefix - construction variable for the prefix.
1342 suffix - construction variable for the suffix.
1343 """
1344
1345 suffix = self.subst('$'+suffix)
1346 prefix = self.subst('$'+prefix)
1347
1348 for path in paths:
1349 dir,name = os.path.split(str(path))
1350 if name[:len(prefix)] == prefix and name[-len(suffix):] == suffix:
1351 return path
1352
1353 - def ParseConfig(self, command, function=None, unique=1):
1354 """
1355 Use the specified function to parse the output of the command
1356 in order to modify the current environment. The 'command' can
1357 be a string or a list of strings representing a command and
1358 its arguments. 'Function' is an optional argument that takes
1359 the environment, the output of the command, and the unique flag.
1360 If no function is specified, MergeFlags, which treats the output
1361 as the result of a typical 'X-config' command (i.e. gtk-config),
1362 will merge the output into the appropriate variables.
1363 """
1364 if function is None:
1365 def parse_conf(env, cmd, unique=unique):
1366 return env.MergeFlags(cmd, unique)
1367 function = parse_conf
1368 if SCons.Util.is_List(command):
1369 command = string.join(command)
1370 command = self.subst(command)
1371 return function(self, self.backtick(command))
1372
1373 - def ParseDepends(self, filename, must_exist=None, only_one=0):
1374 """
1375 Parse a mkdep-style file for explicit dependencies. This is
1376 completely abusable, and should be unnecessary in the "normal"
1377 case of proper SCons configuration, but it may help make
1378 the transition from a Make hierarchy easier for some people
1379 to swallow. It can also be genuinely useful when using a tool
1380 that can write a .d file, but for which writing a scanner would
1381 be too complicated.
1382 """
1383 filename = self.subst(filename)
1384 try:
1385 fp = open(filename, 'r')
1386 except IOError:
1387 if must_exist:
1388 raise
1389 return
1390 lines = SCons.Util.LogicalLines(fp).readlines()
1391 lines = filter(lambda l: l[0] != '#', lines)
1392 tdlist = []
1393 for line in lines:
1394 try:
1395 target, depends = string.split(line, ':', 1)
1396 except (AttributeError, TypeError, ValueError):
1397
1398
1399
1400
1401 pass
1402 else:
1403 tdlist.append((string.split(target), string.split(depends)))
1404 if only_one:
1405 targets = reduce(lambda x, y: x+y, map(lambda p: p[0], tdlist))
1406 if len(targets) > 1:
1407 raise SCons.Errors.UserError, "More than one dependency target found in `%s': %s" % (filename, targets)
1408 for target, depends in tdlist:
1409 self.Depends(target, depends)
1410
1414
1416 """Prepend values to existing construction variables
1417 in an Environment.
1418 """
1419 kw = copy_non_reserved_keywords(kw)
1420 for key, val in kw.items():
1421
1422
1423
1424
1425 try:
1426 orig = self._dict[key]
1427 except KeyError:
1428
1429
1430 self._dict[key] = val
1431 else:
1432 try:
1433
1434
1435
1436
1437
1438 update_dict = orig.update
1439 except AttributeError:
1440 try:
1441
1442
1443
1444 self._dict[key] = val + orig
1445 except (KeyError, TypeError):
1446 try:
1447
1448 add_to_val = val.append
1449 except AttributeError:
1450
1451
1452
1453
1454 if val:
1455 orig.insert(0, val)
1456 else:
1457
1458
1459
1460 if orig:
1461 add_to_val(orig)
1462 self._dict[key] = val
1463 else:
1464
1465
1466 if SCons.Util.is_List(val):
1467 for v in val:
1468 orig[v] = None
1469 else:
1470 try:
1471 update_dict(val)
1472 except (AttributeError, TypeError, ValueError):
1473 if SCons.Util.is_Dict(val):
1474 for k, v in val.items():
1475 orig[k] = v
1476 else:
1477 orig[val] = None
1478 self.scanner_map_delete(kw)
1479
1481 """Prepend path elements to the path 'name' in the 'ENV'
1482 dictionary for this environment. Will only add any particular
1483 path once, and will normpath and normcase all paths to help
1484 assure this. This can also handle the case where the env
1485 variable is a list instead of a string.
1486 """
1487
1488 orig = ''
1489 if self._dict.has_key(envname) and self._dict[envname].has_key(name):
1490 orig = self._dict[envname][name]
1491
1492 nv = SCons.Util.PrependPath(orig, newpath, sep)
1493
1494 if not self._dict.has_key(envname):
1495 self._dict[envname] = {}
1496
1497 self._dict[envname][name] = nv
1498
1500 """Append values to existing construction variables
1501 in an Environment, if they're not already there.
1502 """
1503 kw = copy_non_reserved_keywords(kw)
1504 for key, val in kw.items():
1505 if not self._dict.has_key(key) or self._dict[key] in ('', None):
1506 self._dict[key] = val
1507 elif SCons.Util.is_Dict(self._dict[key]) and \
1508 SCons.Util.is_Dict(val):
1509 self._dict[key].update(val)
1510 elif SCons.Util.is_List(val):
1511 dk = self._dict[key]
1512 if not SCons.Util.is_List(dk):
1513 dk = [dk]
1514 val = filter(lambda x, dk=dk: x not in dk, val)
1515 self._dict[key] = val + dk
1516 else:
1517 dk = self._dict[key]
1518 if SCons.Util.is_List(dk):
1519
1520
1521 if not val in dk:
1522 self._dict[key] = [val] + dk
1523 else:
1524 self._dict[key] = val + dk
1525 self.scanner_map_delete(kw)
1526
1528 """Replace existing construction variables in an Environment
1529 with new construction variables and/or values.
1530 """
1531 try:
1532 kwbd = kw['BUILDERS']
1533 except KeyError:
1534 pass
1535 else:
1536 kwbd = semi_deepcopy(kwbd)
1537 del kw['BUILDERS']
1538 self.__setitem__('BUILDERS', kwbd)
1539 kw = copy_non_reserved_keywords(kw)
1540 self._update(semi_deepcopy(kw))
1541 self.scanner_map_delete(kw)
1542
1543 - def ReplaceIxes(self, path, old_prefix, old_suffix, new_prefix, new_suffix):
1544 """
1545 Replace old_prefix with new_prefix and old_suffix with new_suffix.
1546
1547 env - Environment used to interpolate variables.
1548 path - the path that will be modified.
1549 old_prefix - construction variable for the old prefix.
1550 old_suffix - construction variable for the old suffix.
1551 new_prefix - construction variable for the new prefix.
1552 new_suffix - construction variable for the new suffix.
1553 """
1554 old_prefix = self.subst('$'+old_prefix)
1555 old_suffix = self.subst('$'+old_suffix)
1556
1557 new_prefix = self.subst('$'+new_prefix)
1558 new_suffix = self.subst('$'+new_suffix)
1559
1560 dir,name = os.path.split(str(path))
1561 if name[:len(old_prefix)] == old_prefix:
1562 name = name[len(old_prefix):]
1563 if name[-len(old_suffix):] == old_suffix:
1564 name = name[:-len(old_suffix)]
1565 return os.path.join(dir, new_prefix+name+new_suffix)
1566
1568 for k in kw.keys():
1569 if self._dict.has_key(k):
1570 del kw[k]
1571 apply(self.Replace, (), kw)
1572
1575
1584
1585 - def WhereIs(self, prog, path=None, pathext=None, reject=[]):
1605
1606
1607
1608
1609
1610
1611
1612
1613
1614 - def Action(self, *args, **kw):
1619 nargs = map(subst_string, args)
1620 nkw = self.subst_kw(kw)
1621 return apply(SCons.Action.Action, nargs, nkw)
1622
1632
1633 - def AddPostAction(self, files, action):
1634 nodes = self.arg2nodes(files, self.fs.Entry)
1635 action = SCons.Action.Action(action)
1636 uniq = {}
1637 for executor in map(lambda n: n.get_executor(), nodes):
1638 uniq[executor] = 1
1639 for executor in uniq.keys():
1640 executor.add_post_action(action)
1641 return nodes
1642
1643 - def Alias(self, target, source=[], action=None, **kw):
1644 tlist = self.arg2nodes(target, self.ans.Alias)
1645 if not SCons.Util.is_List(source):
1646 source = [source]
1647 source = filter(None, source)
1648
1649 if not action:
1650 if not source:
1651
1652
1653
1654
1655
1656
1657 return tlist
1658
1659
1660
1661 result = []
1662 for t in tlist:
1663 bld = t.get_builder(AliasBuilder)
1664 result.extend(bld(self, t, source))
1665 return result
1666
1667 nkw = self.subst_kw(kw)
1668 nkw.update({
1669 'action' : SCons.Action.Action(action),
1670 'source_factory' : self.fs.Entry,
1671 'multi' : 1,
1672 'is_explicit' : None,
1673 })
1674 bld = apply(SCons.Builder.Builder, (), nkw)
1675
1676
1677
1678
1679
1680 result = []
1681 for t in tlist:
1682
1683
1684
1685
1686 b = t.get_builder()
1687 if b is None or b is AliasBuilder:
1688 b = bld
1689 else:
1690 nkw['action'] = b.action + action
1691 b = apply(SCons.Builder.Builder, (), nkw)
1692 t.convert()
1693 result.extend(b(self, t, t.sources + source))
1694 return result
1695
1703
1705 if kw.has_key('build_dir'):
1706 kw['variant_dir'] = kw['build_dir']
1707 del kw['build_dir']
1708 return apply(self.VariantDir, args, kw)
1709
1713
1719
1720 - def Clean(self, targets, files):
1729
1741
1742 - def Command(self, target, source, action, **kw):
1743 """Builds the supplied target files from the supplied
1744 source files using the supplied action. Action may
1745 be any type that the Builder constructor will accept
1746 for an action."""
1747 bkw = {
1748 'action' : action,
1749 'target_factory' : self.fs.Entry,
1750 'source_factory' : self.fs.Entry,
1751 }
1752 try: bkw['source_scanner'] = kw['source_scanner']
1753 except KeyError: pass
1754 else: del kw['source_scanner']
1755 bld = apply(SCons.Builder.Builder, (), bkw)
1756 return apply(bld, (self, target, source), kw)
1757
1758 - def Depends(self, target, dependency):
1759 """Explicity specify that 'target's depend on 'dependency'."""
1760 tlist = self.arg2nodes(target, self.fs.Entry)
1761 dlist = self.arg2nodes(dependency, self.fs.Entry)
1762 for t in tlist:
1763 t.add_dependency(dlist)
1764 return tlist
1765
1766 - def Dir(self, name, *args, **kw):
1776
1778 """Tags a target so that it will not be cleaned by -c"""
1779 tlist = []
1780 for t in targets:
1781 tlist.extend(self.arg2nodes(t, self.fs.Entry))
1782 for t in tlist:
1783 t.set_noclean()
1784 return tlist
1785
1787 """Tags a target so that it will not be cached"""
1788 tlist = []
1789 for t in targets:
1790 tlist.extend(self.arg2nodes(t, self.fs.Entry))
1791 for t in tlist:
1792 t.set_nocache()
1793 return tlist
1794
1795 - def Entry(self, name, *args, **kw):
1796 """
1797 """
1798 s = self.subst(name)
1799 if SCons.Util.is_Sequence(s):
1800 result=[]
1801 for e in s:
1802 result.append(apply(self.fs.Entry, (e,) + args, kw))
1803 return result
1804 return apply(self.fs.Entry, (s,) + args, kw)
1805
1808
1809 - def Execute(self, action, *args, **kw):
1810 """Directly execute an action through an Environment
1811 """
1812 action = apply(self.Action, (action,) + args, kw)
1813 result = action([], [], self)
1814 if isinstance(result, SCons.Errors.BuildError):
1815 return result.status
1816 else:
1817 return result
1818
1819 - def File(self, name, *args, **kw):
1829
1834
1837
1844
1847
1848 - def Ignore(self, target, dependency):
1855
1858
1859 - def Local(self, *targets):
1870
1878
1882
1883 - def Requires(self, target, prerequisite):
1884 """Specify that 'prerequisite' must be built before 'target',
1885 (but 'target' does not actually depend on 'prerequisite'
1886 and need not be rebuilt if it changes)."""
1887 tlist = self.arg2nodes(target, self.fs.Entry)
1888 plist = self.arg2nodes(prerequisite, self.fs.Entry)
1889 for t in tlist:
1890 t.add_prerequisite(plist)
1891 return tlist
1892
1901
1902 - def SConsignFile(self, name=".sconsign", dbm_module=None):
1908
1910 """Tell scons that side_effects are built as side
1911 effects of building targets."""
1912 side_effects = self.arg2nodes(side_effect, self.fs.Entry)
1913 targets = self.arg2nodes(target, self.fs.Entry)
1914
1915 for side_effect in side_effects:
1916 if side_effect.multiple_side_effect_has_builder():
1917 raise SCons.Errors.UserError, "Multiple ways to build the same target were specified for: %s" % str(side_effect)
1918 side_effect.add_source(targets)
1919 side_effect.side_effect = 1
1920 self.Precious(side_effect)
1921 for target in targets:
1922 target.side_effects.append(side_effect)
1923 return side_effects
1924
1926 """Arrange for a source code builder for (part of) a tree."""
1927 entries = self.arg2nodes(entry, self.fs.Entry)
1928 for entry in entries:
1929 entry.set_src_builder(builder)
1930 return entries
1931
1949
1951 """This function converts a string or list into a list of strings
1952 or Nodes. This makes things easier for users by allowing files to
1953 be specified as a white-space separated list to be split.
1954 The input rules are:
1955 - A single string containing names separated by spaces. These will be
1956 split apart at the spaces.
1957 - A single Node instance
1958 - A list containing either strings or Node instances. Any strings
1959 in the list are not split at spaces.
1960 In all cases, the function returns a list of Nodes and strings."""
1961 if SCons.Util.is_List(arg):
1962 return map(self.subst, arg)
1963 elif SCons.Util.is_String(arg):
1964 return string.split(self.subst(arg))
1965 else:
1966 return [self.subst(arg)]
1967
1989
1990 - def Value(self, value, built_value=None):
1994
1995 - def VariantDir(self, variant_dir, src_dir, duplicate=1):
1999
2025
2026
2027
2028 map( get_final_srcnode, sources )
2029
2030
2031 return list(set(sources))
2032
2034 """ returns the list of all targets of the Install and InstallAs Builder.
2035 """
2036 from SCons.Tool import install
2037 if install._UNIQUE_INSTALLED_FILES is None:
2038 install._UNIQUE_INSTALLED_FILES = SCons.Util.uniquer_hashables(install._INSTALLED_FILES)
2039 return install._UNIQUE_INSTALLED_FILES
2040
2042 """A proxy that overrides variables in a wrapped construction
2043 environment by returning values from an overrides dictionary in
2044 preference to values from the underlying subject environment.
2045
2046 This is a lightweight (I hope) proxy that passes through most use of
2047 attributes to the underlying Environment.Base class, but has just
2048 enough additional methods defined to act like a real construction
2049 environment with overridden values. It can wrap either a Base
2050 construction environment, or another OverrideEnvironment, which
2051 can in turn nest arbitrary OverrideEnvironments...
2052
2053 Note that we do *not* call the underlying base class
2054 (SubsitutionEnvironment) initialization, because we get most of those
2055 from proxying the attributes of the subject construction environment.
2056 But because we subclass SubstitutionEnvironment, this class also
2057 has inherited arg2nodes() and subst*() methods; those methods can't
2058 be proxied because they need *this* object's methods to fetch the
2059 values from the overrides dictionary.
2060 """
2061
2062 if SCons.Memoize.use_memoizer:
2063 __metaclass__ = SCons.Memoize.Memoized_Metaclass
2064
2065 - def __init__(self, subject, overrides={}):
2066 if __debug__: logInstanceCreation(self, 'Environment.OverrideEnvironment')
2067 self.__dict__['__subject'] = subject
2068 self.__dict__['overrides'] = overrides
2069
2070
2072 return getattr(self.__dict__['__subject'], name)
2074 setattr(self.__dict__['__subject'], name, value)
2075
2076
2078 try:
2079 return self.__dict__['overrides'][key]
2080 except KeyError:
2081 return self.__dict__['__subject'].__getitem__(key)
2087 try:
2088 del self.__dict__['overrides'][key]
2089 except KeyError:
2090 deleted = 0
2091 else:
2092 deleted = 1
2093 try:
2094 result = self.__dict__['__subject'].__delitem__(key)
2095 except KeyError:
2096 if not deleted:
2097 raise
2098 result = None
2099 return result
2100 - def get(self, key, default=None):
2101 """Emulates the get() method of dictionaries."""
2102 try:
2103 return self.__dict__['overrides'][key]
2104 except KeyError:
2105 return self.__dict__['__subject'].get(key, default)
2107 try:
2108 self.__dict__['overrides'][key]
2109 return 1
2110 except KeyError:
2111 return self.__dict__['__subject'].has_key(key)
2113 """Emulates the items() method of dictionaries."""
2114 d = self.__dict__['__subject'].Dictionary().copy()
2115 d.update(self.__dict__['overrides'])
2116 return d
2118 """Emulates the items() method of dictionaries."""
2119 return self.Dictionary().items()
2120
2121
2123 """Update an environment's values directly, bypassing the normal
2124 checks that occur when users try to set items.
2125 """
2126 self.__dict__['overrides'].update(dict)
2127
2129 return self.__dict__['__subject'].gvars()
2130
2135
2136
2140
2141
2142
2143
2144
2145
2146
2147 Environment = Base
2148
2149
2150
2151
2152
2153
2154
2155
2156
2157
2158
2159
2160
2162 class _NoSubstitutionProxy(Environment):
2163 def __init__(self, subject):
2164 self.__dict__['__subject'] = subject
2165 def __getattr__(self, name):
2166 return getattr(self.__dict__['__subject'], name)
2167 def __setattr__(self, name, value):
2168 return setattr(self.__dict__['__subject'], name, value)
2169 def raw_to_mode(self, dict):
2170 try:
2171 raw = dict['raw']
2172 except KeyError:
2173 pass
2174 else:
2175 del dict['raw']
2176 dict['mode'] = raw
2177 def subst(self, string, *args, **kwargs):
2178 return string
2179 def subst_kw(self, kw, *args, **kwargs):
2180 return kw
2181 def subst_list(self, string, *args, **kwargs):
2182 nargs = (string, self,) + args
2183 nkw = kwargs.copy()
2184 nkw['gvars'] = {}
2185 self.raw_to_mode(nkw)
2186 return apply(SCons.Subst.scons_subst_list, nargs, nkw)
2187 def subst_target_source(self, string, *args, **kwargs):
2188 nargs = (string, self,) + args
2189 nkw = kwargs.copy()
2190 nkw['gvars'] = {}
2191 self.raw_to_mode(nkw)
2192 return apply(SCons.Subst.scons_subst, nargs, nkw)
2193 return _NoSubstitutionProxy(subject)
2194