fixed a few typos and changed all references to c++ project file to be MSVC (entity...
[scons.git] / SCons / Action.py
blobafa1d91dd9ca49bc7495f1794ba35e7923216275
1 # MIT License
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.
24 """SCons Actions.
26 Information about executing any sort of action that
27 can build one or more target Nodes (typically files) from one or more
28 source Nodes (also typically files) given a specific Environment.
30 The base class here is ActionBase. The base class supplies just a few
31 utility methods and some generic methods for displaying information
32 about an Action in response to the various commands that control printing.
34 A second-level base class is _ActionAction. This extends ActionBase
35 by providing the methods that can be used to show and perform an
36 action. True Action objects will subclass _ActionAction; Action
37 factory class objects will subclass ActionBase.
39 The heavy lifting is handled by subclasses for the different types of
40 actions we might execute:
42 CommandAction
43 CommandGeneratorAction
44 FunctionAction
45 ListAction
47 The subclasses supply the following public interface methods used by
48 other modules:
50 __call__()
51 THE public interface, "calling" an Action object executes the
52 command or Python function. This also takes care of printing
53 a pre-substitution command for debugging purposes.
55 get_contents()
56 Fetches the "contents" of an Action for signature calculation
57 plus the varlist. This is what gets checksummed to decide
58 if a target needs to be rebuilt because its action changed.
60 genstring()
61 Returns a string representation of the Action *without*
62 command substitution, but allows a CommandGeneratorAction to
63 generate the right action based on the specified target,
64 source and env. This is used by the Signature subsystem
65 (through the Executor) to obtain an (imprecise) representation
66 of the Action operation for informative purposes.
69 Subclasses also supply the following methods for internal use within
70 this module:
72 __str__()
73 Returns a string approximation of the Action; no variable
74 substitution is performed.
76 execute()
77 The internal method that really, truly, actually handles the
78 execution of a command or Python function. This is used so
79 that the __call__() methods can take care of displaying any
80 pre-substitution representations, and *then* execute an action
81 without worrying about the specific Actions involved.
83 get_presig()
84 Fetches the "contents" of a subclass for signature calculation.
85 The varlist is added to this to produce the Action's contents.
86 TODO(?): Change this to always return bytes and not str?
88 strfunction()
89 Returns a substituted string representation of the Action.
90 This is used by the _ActionAction.show() command to display the
91 command/function that will be executed to generate the target(s).
93 There is a related independent ActionCaller class that looks like a
94 regular Action, and which serves as a wrapper for arbitrary functions
95 that we want to let the user specify the arguments to now, but actually
96 execute later (when an out-of-date check determines that it's needed to
97 be executed, for example). Objects of this class are returned by an
98 ActionFactory class that provides a __call__() method as a convenient
99 way for wrapping up the functions.
103 import inspect
104 import os
105 import pickle
106 import re
107 import subprocess
108 import sys
109 from abc import ABC, abstractmethod
110 from collections import OrderedDict
111 from subprocess import DEVNULL, PIPE
112 from typing import List, Optional, Tuple
114 import SCons.Debug
115 import SCons.Errors
116 import SCons.Subst
117 import SCons.Util
119 # we use these a lot, so try to optimize them
120 from SCons.Debug import logInstanceCreation
121 from SCons.Subst import SUBST_CMD, SUBST_RAW, SUBST_SIG
122 from SCons.Util import is_String, is_List
123 from SCons.Util.sctyping import ExecutorType
125 class _null:
126 pass
128 print_actions = True
129 execute_actions = True
130 print_actions_presub = False
132 # Use pickle protocol 4 when pickling functions for signature.
133 # This is the common format since Python 3.4
134 # TODO: use is commented out as not stable since 2017: e0bc3a04d5. Drop?
135 # ACTION_SIGNATURE_PICKLE_PROTOCOL = 4
138 def rfile(n):
139 try:
140 return n.rfile()
141 except AttributeError:
142 return n
145 def default_exitstatfunc(s):
146 return s
148 strip_quotes = re.compile(r'^[\'"](.*)[\'"]$')
151 def _callable_contents(obj) -> bytearray:
152 """Return the signature contents of a callable Python object."""
153 try:
154 # Test if obj is a method.
155 return _function_contents(obj.__func__)
157 except AttributeError:
158 try:
159 # Test if obj is a callable object.
160 return _function_contents(obj.__call__.__func__)
162 except AttributeError:
163 try:
164 # Test if obj is a code object.
165 return _code_contents(obj)
167 except AttributeError:
168 # Test if obj is a function object.
169 return _function_contents(obj)
172 def _object_contents(obj) -> bytearray:
173 """Return the signature contents of any Python object.
175 We have to handle the case where object contains a code object
176 since it can be pickled directly.
178 try:
179 # Test if obj is a method.
180 return _function_contents(obj.__func__)
182 except AttributeError:
183 try:
184 # Test if obj is a callable object.
185 return _function_contents(obj.__call__.__func__)
187 except AttributeError:
188 try:
189 # Test if obj is a code object.
190 return _code_contents(obj)
192 except AttributeError:
193 try:
194 # Test if obj is a function object.
195 return _function_contents(obj)
197 except AttributeError as ae:
198 # Should be a pickle-able Python object.
199 try:
200 return _object_instance_content(obj)
201 # pickling an Action instance or object doesn't yield a stable
202 # content as instance property may be dumped in different orders
203 # return pickle.dumps(obj, ACTION_SIGNATURE_PICKLE_PROTOCOL)
204 except (pickle.PicklingError, TypeError, AttributeError) as ex:
205 # This is weird, but it seems that nested classes
206 # are unpickable. The Python docs say it should
207 # always be a PicklingError, but some Python
208 # versions seem to return TypeError. Just do
209 # the best we can.
210 return bytearray(repr(obj), 'utf-8')
212 # TODO: docstrings for _code_contents and _function_contents
213 # do not render well with Sphinx. Consider reworking.
215 def _code_contents(code, docstring=None) -> bytearray:
216 r"""Return the signature contents of a code object.
218 By providing direct access to the code object of the
219 function, Python makes this extremely easy. Hooray!
221 Unfortunately, older versions of Python include line
222 number indications in the compiled byte code. Boo!
223 So we remove the line number byte codes to prevent
224 recompilations from moving a Python function.
226 See:
227 - https://docs.python.org/3/library/inspect.html
228 - http://python-reference.readthedocs.io/en/latest/docs/code/index.html
230 For info on what each co\_ variable provides
232 The signature is as follows (should be byte/chars):
233 co_argcount, len(co_varnames), len(co_cellvars), len(co_freevars),
234 ( comma separated signature for each object in co_consts ),
235 ( comma separated signature for each object in co_names ),
236 ( The bytecode with line number bytecodes removed from co_code )
238 co_argcount - Returns the number of positional arguments (including arguments with default values).
239 co_varnames - Returns a tuple containing the names of the local variables (starting with the argument names).
240 co_cellvars - Returns a tuple containing the names of local variables that are referenced by nested functions.
241 co_freevars - Returns a tuple containing the names of free variables. (?)
242 co_consts - Returns a tuple containing the literals used by the bytecode.
243 co_names - Returns a tuple containing the names used by the bytecode.
244 co_code - Returns a string representing the sequence of bytecode instructions.
247 # contents = []
249 # The code contents depends on the number of local variables
250 # but not their actual names.
251 contents = bytearray(f"{code.co_argcount}, {len(code.co_varnames)}", 'utf-8')
253 contents.extend(b", ")
254 contents.extend(bytearray(str(len(code.co_cellvars)), 'utf-8'))
255 contents.extend(b", ")
256 contents.extend(bytearray(str(len(code.co_freevars)), 'utf-8'))
258 # The code contents depends on any constants accessed by the
259 # function. Note that we have to call _object_contents on each
260 # constants because the code object of nested functions can
261 # show-up among the constants.
262 z = [_object_contents(cc) for cc in code.co_consts if cc != docstring]
263 contents.extend(b',(')
264 contents.extend(bytearray(',', 'utf-8').join(z))
265 contents.extend(b')')
267 # The code contents depends on the variable names used to
268 # accessed global variable, as changing the variable name changes
269 # the variable actually accessed and therefore changes the
270 # function result.
271 z= [bytearray(_object_contents(cc)) for cc in code.co_names]
272 contents.extend(b',(')
273 contents.extend(bytearray(',','utf-8').join(z))
274 contents.extend(b')')
276 # The code contents depends on its actual code!!!
277 contents.extend(b',(')
278 contents.extend(code.co_code)
279 contents.extend(b')')
281 return contents
284 def _function_contents(func) -> bytearray:
285 """Return the signature contents of a function.
287 The signature is as follows (should be byte/chars):
288 < _code_contents (see above) from func.__code__ >
289 ,( comma separated _object_contents for function argument defaults)
290 ,( comma separated _object_contents for any closure contents )
293 See also: https://docs.python.org/3/reference/datamodel.html
294 - func.__code__ - The code object representing the compiled function body.
295 - func.__defaults__ - A tuple containing default argument values for those arguments that have defaults, or None if no arguments have a default value
296 - func.__closure__ - None or a tuple of cells that contain bindings for the function's free variables.
298 contents = [_code_contents(func.__code__, func.__doc__)]
300 # The function contents depends on the value of defaults arguments
301 if func.__defaults__:
303 function_defaults_contents = [_object_contents(cc) for cc in func.__defaults__]
305 defaults = bytearray(b',(')
306 defaults.extend(bytearray(b',').join(function_defaults_contents))
307 defaults.extend(b')')
309 contents.append(defaults)
310 else:
311 contents.append(b',()')
313 # The function contents depends on the closure captured cell values.
314 closure = func.__closure__ or []
316 try:
317 closure_contents = [_object_contents(x.cell_contents) for x in closure]
318 except AttributeError:
319 closure_contents = []
321 contents.append(b',(')
322 contents.append(bytearray(b',').join(closure_contents))
323 contents.append(b')')
325 retval = bytearray(b'').join(contents)
326 return retval
329 def _object_instance_content(obj):
331 Returns consistant content for a action class or an instance thereof
333 :Parameters:
334 - `obj` Should be either and action class or an instance thereof
336 :Returns:
337 bytearray or bytes representing the obj suitable for generating a signature from.
339 retval = bytearray()
341 if obj is None:
342 return b'N.'
344 if isinstance(obj, SCons.Util.BaseStringTypes):
345 return SCons.Util.to_bytes(obj)
347 inst_class = obj.__class__
348 inst_class_name = bytearray(obj.__class__.__name__,'utf-8')
349 inst_class_module = bytearray(obj.__class__.__module__,'utf-8')
350 inst_class_hierarchy = bytearray(repr(inspect.getclasstree([obj.__class__,])),'utf-8')
351 # print("ICH:%s : %s"%(inst_class_hierarchy, repr(obj)))
353 properties = [(p, getattr(obj, p, "None")) for p in dir(obj) if not (p[:2] == '__' or inspect.ismethod(getattr(obj, p)) or inspect.isbuiltin(getattr(obj,p))) ]
354 properties.sort()
355 properties_str = ','.join(["%s=%s"%(p[0],p[1]) for p in properties])
356 properties_bytes = bytearray(properties_str,'utf-8')
358 methods = [p for p in dir(obj) if inspect.ismethod(getattr(obj, p))]
359 methods.sort()
361 method_contents = []
362 for m in methods:
363 # print("Method:%s"%m)
364 v = _function_contents(getattr(obj, m))
365 # print("[%s->]V:%s [%s]"%(m,v,type(v)))
366 method_contents.append(v)
368 retval = bytearray(b'{')
369 retval.extend(inst_class_name)
370 retval.extend(b":")
371 retval.extend(inst_class_module)
372 retval.extend(b'}[[')
373 retval.extend(inst_class_hierarchy)
374 retval.extend(b']]{{')
375 retval.extend(bytearray(b",").join(method_contents))
376 retval.extend(b"}}{{{")
377 retval.extend(properties_bytes)
378 retval.extend(b'}}}')
379 return retval
381 # print("class :%s"%inst_class)
382 # print("class_name :%s"%inst_class_name)
383 # print("class_module :%s"%inst_class_module)
384 # print("Class hier :\n%s"%pp.pformat(inst_class_hierarchy))
385 # print("Inst Properties:\n%s"%pp.pformat(properties))
386 # print("Inst Methods :\n%s"%pp.pformat(methods))
388 def _actionAppend(act1, act2):
389 """Joins two actions together.
391 Mainly, it handles ListActions by concatenating into a single ListAction.
393 a1 = Action(act1)
394 a2 = Action(act2)
395 if a1 is None:
396 return a2
397 if a2 is None:
398 return a1
399 if isinstance(a1, ListAction):
400 if isinstance(a2, ListAction):
401 return ListAction(a1.list + a2.list)
402 return ListAction(a1.list + [ a2 ])
404 if isinstance(a2, ListAction):
405 return ListAction([ a1 ] + a2.list)
407 return ListAction([ a1, a2 ])
410 def _do_create_keywords(args, kw):
411 """This converts any arguments after the action argument into
412 their equivalent keywords and adds them to the kw argument.
414 v = kw.get('varlist', ())
415 # prevent varlist="FOO" from being interpreted as ['F', 'O', 'O']
416 if is_String(v): v = (v,)
417 kw['varlist'] = tuple(v)
418 if args:
419 # turn positional args into equivalent keywords
420 cmdstrfunc = args[0]
421 if cmdstrfunc is None or is_String(cmdstrfunc):
422 kw['cmdstr'] = cmdstrfunc
423 elif callable(cmdstrfunc):
424 kw['strfunction'] = cmdstrfunc
425 else:
426 raise SCons.Errors.UserError(
427 'Invalid command display variable type. '
428 'You must either pass a string or a callback which '
429 'accepts (target, source, env) as parameters.')
430 if len(args) > 1:
431 kw['varlist'] = tuple(SCons.Util.flatten(args[1:])) + kw['varlist']
432 if kw.get('strfunction', _null) is not _null \
433 and kw.get('cmdstr', _null) is not _null:
434 raise SCons.Errors.UserError(
435 'Cannot have both strfunction and cmdstr args to Action()')
438 def _do_create_action(act, kw):
439 """The internal implementation for the Action factory method.
441 This handles the fact that passing lists to :func:`Action` itself has
442 different semantics than passing lists as elements of lists.
443 The former will create a :class:`ListAction`, the latter will create a
444 :class:`CommandAction` by converting the inner list elements to strings.
446 if isinstance(act, ActionBase):
447 return act
449 if is_String(act):
450 var = SCons.Util.get_environment_var(act)
451 if var:
452 # This looks like a string that is purely an Environment
453 # variable reference, like "$FOO" or "${FOO}". We do
454 # something special here...we lazily evaluate the contents
455 # of that Environment variable, so a user could put something
456 # like a function or a CommandGenerator in that variable
457 # instead of a string.
458 return LazyAction(var, kw)
459 commands = str(act).split('\n')
460 if len(commands) == 1:
461 return CommandAction(commands[0], **kw)
462 # The list of string commands may include a LazyAction, so we
463 # reprocess them via _do_create_list_action.
464 return _do_create_list_action(commands, kw)
466 if is_List(act):
467 return CommandAction(act, **kw)
469 if callable(act):
470 gen = kw.pop('generator', False)
471 if gen:
472 action_type = CommandGeneratorAction
473 else:
474 action_type = FunctionAction
475 return action_type(act, kw)
477 # Catch a common error case with a nice message:
478 if isinstance(act, (int, float)):
479 raise TypeError("Don't know how to create an Action from a number (%s)"%act)
480 # Else fail silently (???)
481 return None
484 # TODO: from __future__ import annotations once we get to Python 3.7 base,
485 # to avoid quoting the defined-later classname
486 def _do_create_list_action(act, kw) -> "ListAction":
487 """A factory for list actions.
489 Convert the input list *act* into Actions and then wrap them in a
490 :class:`ListAction`. If *act* has only a single member, return that
491 member, not a *ListAction*. This is intended to allow a contained
492 list to specify a command action without being processed into a
493 list action.
495 acts = []
496 for a in act:
497 aa = _do_create_action(a, kw)
498 if aa is not None:
499 acts.append(aa)
500 if not acts:
501 return ListAction([])
502 if len(acts) == 1:
503 return acts[0]
504 return ListAction(acts)
507 def Action(act, *args, **kw):
508 """A factory for action objects."""
509 # Really simple: the _do_create_* routines do the heavy lifting.
510 _do_create_keywords(args, kw)
511 if is_List(act):
512 return _do_create_list_action(act, kw)
513 return _do_create_action(act, kw)
516 class ActionBase(ABC):
517 """Base class for all types of action objects that can be held by
518 other objects (Builders, Executors, etc.) This provides the
519 common methods for manipulating and combining those actions."""
521 @abstractmethod
522 def __call__(
523 self,
524 target,
525 source,
526 env,
527 exitstatfunc=_null,
528 presub=_null,
529 show=_null,
530 execute=_null,
531 chdir=_null,
532 executor: Optional[ExecutorType] = None,
534 raise NotImplementedError
536 def __eq__(self, other):
537 return self.__dict__ == other
539 def no_batch_key(self, env, target, source):
540 return None
542 batch_key = no_batch_key
544 def genstring(self, target, source, env, executor: Optional[ExecutorType] = None) -> str:
545 return str(self)
547 @abstractmethod
548 def get_presig(self, target, source, env, executor: Optional[ExecutorType] = None):
549 raise NotImplementedError
551 @abstractmethod
552 def get_implicit_deps(self, target, source, env, executor: Optional[ExecutorType] = None):
553 raise NotImplementedError
555 def get_contents(self, target, source, env):
556 result = self.get_presig(target, source, env)
558 if not isinstance(result, (bytes, bytearray)):
559 result = bytearray(result, 'utf-8')
560 else:
561 # Make a copy and put in bytearray, without this the contents returned by get_presig
562 # can be changed by the logic below, appending with each call and causing very
563 # hard to track down issues...
564 result = bytearray(result)
566 # At this point everything should be a bytearray
568 # This should never happen, as the Action() factory should wrap
569 # the varlist, but just in case an action is created directly,
570 # we duplicate this check here.
571 vl = self.get_varlist(target, source, env)
572 if is_String(vl): vl = (vl,)
573 for v in vl:
574 # do the subst this way to ignore $(...$) parts:
575 if isinstance(result, bytearray):
576 result.extend(SCons.Util.to_bytes(env.subst_target_source('${'+v+'}', SUBST_SIG, target, source)))
577 else:
578 raise Exception("WE SHOULD NEVER GET HERE result should be bytearray not:%s"%type(result))
579 # result.append(SCons.Util.to_bytes(env.subst_target_source('${'+v+'}', SUBST_SIG, target, source)))
581 if isinstance(result, (bytes, bytearray)):
582 return result
584 raise Exception("WE SHOULD NEVER GET HERE - #2 result should be bytearray not:%s" % type(result))
586 def __add__(self, other):
587 return _actionAppend(self, other)
589 def __radd__(self, other):
590 return _actionAppend(other, self)
592 def presub_lines(self, env):
593 # CommandGeneratorAction needs a real environment
594 # in order to return the proper string here, since
595 # it may call LazyAction, which looks up a key
596 # in that env. So we temporarily remember the env here,
597 # and CommandGeneratorAction will use this env
598 # when it calls its _generate method.
599 self.presub_env = env
600 lines = str(self).split('\n')
601 self.presub_env = None # don't need this any more
602 return lines
604 def get_varlist(self, target, source, env, executor: Optional[ExecutorType] = None):
605 return self.varlist
607 def get_targets(self, env, executor: Optional[ExecutorType]):
609 Returns the type of targets ($TARGETS, $CHANGED_TARGETS) used
610 by this action.
612 return self.targets
615 class _ActionAction(ActionBase):
616 """Base class for actions that create output objects."""
617 def __init__(self, cmdstr=_null, strfunction=_null, varlist=(),
618 presub=_null, chdir=None, exitstatfunc=None,
619 batch_key=None, targets: str='$TARGETS',
620 **kw) -> None:
621 self.cmdstr = cmdstr
622 if strfunction is not _null:
623 if strfunction is None:
624 self.cmdstr = None
625 else:
626 self.strfunction = strfunction
627 self.varlist = varlist
628 self.presub = presub
629 self.chdir = chdir
630 if not exitstatfunc:
631 exitstatfunc = default_exitstatfunc
632 self.exitstatfunc = exitstatfunc
634 self.targets = targets
636 if batch_key:
637 if not callable(batch_key):
638 # They have set batch_key, but not to their own
639 # callable. The default behavior here will batch
640 # *all* targets+sources using this action, separated
641 # for each construction environment.
642 def default_batch_key(self, env, target, source):
643 return (id(self), id(env))
644 batch_key = default_batch_key
645 SCons.Util.AddMethod(self, batch_key, 'batch_key')
647 def print_cmd_line(self, s, target, source, env) -> None:
649 In python 3, and in some of our tests, sys.stdout is
650 a String io object, and it takes unicode strings only
651 This code assumes s is a regular string.
653 sys.stdout.write(s + "\n")
655 def __call__(self, target, source, env,
656 exitstatfunc=_null,
657 presub=_null,
658 show=_null,
659 execute=_null,
660 chdir=_null,
661 executor: Optional[ExecutorType] = None):
662 if not is_List(target):
663 target = [target]
664 if not is_List(source):
665 source = [source]
667 if presub is _null:
668 presub = self.presub
669 if presub is _null:
670 presub = print_actions_presub
671 if exitstatfunc is _null:
672 exitstatfunc = self.exitstatfunc
673 if show is _null:
674 show = print_actions
675 if execute is _null:
676 execute = execute_actions
677 if chdir is _null:
678 chdir = self.chdir
679 save_cwd = None
680 if chdir:
681 save_cwd = os.getcwd()
682 try:
683 chdir = str(chdir.get_abspath())
684 except AttributeError:
685 if not is_String(chdir):
686 if executor:
687 chdir = str(executor.batches[0].targets[0].dir)
688 else:
689 chdir = str(target[0].dir)
690 if presub:
691 if executor:
692 target = executor.get_all_targets()
693 source = executor.get_all_sources()
694 t = ' and '.join(map(str, target))
695 l = '\n '.join(self.presub_lines(env))
696 out = "Building %s with action:\n %s\n" % (t, l)
697 sys.stdout.write(out)
698 cmd = None
699 if show and self.strfunction:
700 if executor:
701 target = executor.get_all_targets()
702 source = executor.get_all_sources()
703 try:
704 cmd = self.strfunction(target, source, env, executor)
705 except TypeError:
706 cmd = self.strfunction(target, source, env)
707 if cmd:
708 if chdir:
709 cmd = ('os.chdir(%s)\n' % repr(chdir)) + cmd
710 try:
711 get = env.get
712 except AttributeError:
713 print_func = self.print_cmd_line
714 else:
715 print_func = get('PRINT_CMD_LINE_FUNC')
716 if not print_func:
717 print_func = self.print_cmd_line
718 print_func(cmd, target, source, env)
719 stat = 0
720 if execute:
721 if chdir:
722 os.chdir(chdir)
723 try:
724 stat = self.execute(target, source, env, executor=executor)
725 if isinstance(stat, SCons.Errors.BuildError):
726 s = exitstatfunc(stat.status)
727 if s:
728 stat.status = s
729 else:
730 stat = s
731 else:
732 stat = exitstatfunc(stat)
733 finally:
734 if save_cwd:
735 os.chdir(save_cwd)
736 if cmd and save_cwd:
737 print_func('os.chdir(%s)' % repr(save_cwd), target, source, env)
739 return stat
741 # Stub these two only so _ActionAction can be instantiated. It's really
742 # an ABC like parent ActionBase, but things reach in and use it. It's
743 # not just unittests or we could fix it up with a concrete subclass there.
745 def get_presig(self, target, source, env, executor: Optional[ExecutorType] = None):
746 raise NotImplementedError
748 def get_implicit_deps(self, target, source, env, executor: Optional[ExecutorType] = None):
749 raise NotImplementedError
752 def _string_from_cmd_list(cmd_list):
753 """Takes a list of command line arguments and returns a pretty
754 representation for printing."""
755 cl = []
756 for arg in map(str, cmd_list):
757 if ' ' in arg or '\t' in arg:
758 arg = '"' + arg + '"'
759 cl.append(arg)
760 return ' '.join(cl)
762 default_ENV = None
765 def get_default_ENV(env):
766 """Returns an execution environment.
768 If there is one in *env*, just use it, else return the Default
769 Environment, insantiated if necessary.
771 A fiddlin' little function that has an ``import SCons.Environment``
772 which cannot be moved to the top level without creating an import
773 loop. Since this import creates a local variable named ``SCons``,
774 it blocks access to the global variable, so we move it here to
775 prevent complaints about local variables being used uninitialized.
777 global default_ENV
779 try:
780 return env['ENV']
781 except KeyError:
782 if not default_ENV:
783 import SCons.Environment # pylint: disable=import-outside-toplevel,redefined-outer-name
784 # This is a hideously expensive way to get a default execution
785 # environment. What it really should do is run the platform
786 # setup to get the default ENV. Fortunately, it's incredibly
787 # rare for an Environment not to have an execution environment,
788 # so we're not going to worry about it overmuch.
789 default_ENV = SCons.Environment.Environment()['ENV']
790 return default_ENV
793 def _resolve_shell_env(env, target, source):
794 """Returns a resolved execution environment.
796 First get the execution environment. Then if ``SHELL_ENV_GENERATORS``
797 is set and is iterable, call each function to allow it to alter the
798 created execution environment, passing each the returned execution
799 environment from the previous call.
801 .. versionadded:: 4.4
803 ENV = get_default_ENV(env)
804 shell_gen = env.get('SHELL_ENV_GENERATORS')
805 if shell_gen:
806 try:
807 shell_gens = iter(shell_gen)
808 except TypeError:
809 raise SCons.Errors.UserError("SHELL_ENV_GENERATORS must be iteratable.")
811 ENV = ENV.copy()
812 for generator in shell_gens:
813 ENV = generator(env, target, source, ENV)
814 if not isinstance(ENV, dict):
815 raise SCons.Errors.UserError(f"SHELL_ENV_GENERATORS function: {generator} must return a dict.")
817 return ENV
820 def scons_subproc_run(scons_env, *args, **kwargs) -> subprocess.CompletedProcess:
821 """Run an external command using an SCons execution environment.
823 SCons normally runs external build commands using :mod:`subprocess`,
824 but does not harvest any output from such commands. This function
825 is a thin wrapper around :func:`subprocess.run` allowing running
826 a command in an SCons context (i.e. uses an "execution environment"
827 rather than the user's existing environment), and provides the ability
828 to return any output in a :class:`subprocess.CompletedProcess`
829 instance (this must be selected by setting ``stdout`` and/or
830 ``stderr`` to ``PIPE``, or setting ``capture_output=True`` - see
831 Keyword Arguments). Typical use case is to run a tool's "version"
832 option to find out the installed version.
834 If supplied, the ``env`` keyword argument provides an execution
835 environment to process into appropriate form before it is supplied
836 to :mod:`subprocess`; if omitted, *scons_env* is used to derive a
837 suitable default. The other keyword arguments are passed through,
838 except that the SCons legacy ``error`` keyword is remapped to the
839 subprocess ``check`` keyword; if both are omitted ``check=False``
840 will be passed. The caller is responsible for setting up the desired
841 arguments for :func:`subprocess.run`.
843 This function retains the legacy behavior of returning something
844 vaguely usable even in the face of complete failure, unless
845 ``check=True`` (in which case an error is allowed to be raised):
846 it synthesizes a :class:`~subprocess.CompletedProcess` instance in
847 this case.
849 A subset of interesting keyword arguments follows; see the Python
850 documentation of :mod:`subprocess` for the complete list.
852 Keyword Arguments:
853 stdout: (and *stderr*, *stdin*) if set to :const:`subprocess.PIPE`.
854 send input to or collect output from the relevant stream in
855 the subprocess; the default ``None`` does no redirection
856 (i.e. output or errors may go to the console or log file,
857 but is not captured); if set to :const:`subprocess.DEVNULL`
858 they are explicitly thrown away. ``capture_output=True`` is a
859 synonym for setting both ``stdout`` and ``stderr``
860 to :const:`~subprocess.PIPE`.
861 text: open *stdin*, *stdout*, *stderr* in text mode. Default
862 is binary mode. ``universal_newlines`` is a synonym.
863 encoding: specifies an encoding. Changes to text mode.
864 errors: specified error handling. Changes to text mode.
865 input: a byte sequence to be passed to *stdin*, unless text
866 mode is enabled, in which case it must be a string.
867 shell: if true, the command is executed through the shell.
868 check: if true and the subprocess exits with a non-zero exit
869 code, raise a :exc:`subprocess.CalledProcessError` exception.
870 Otherwise (the default) in case of an :exc:`OSError`, report the
871 exit code in the :class:`~Subprocess.CompletedProcess` instance.
873 .. versionadded:: 4.6
875 # Figure out the execution environment to use
876 env = kwargs.get('env', None)
877 if env is None:
878 env = get_default_ENV(scons_env)
879 kwargs['env'] = SCons.Util.sanitize_shell_env(env)
881 # Backwards-compat with _subproc: accept 'error', map to 'check',
882 # and remove, since subprocess.run does not recognize.
883 # 'error' isn't True/False, it takes a string value (see _subproc)
884 error = kwargs.get('error')
885 if error and 'check' in kwargs:
886 raise ValueError('error and check arguments may not both be used.')
887 check = kwargs.get('check', False) # always set a value for 'check'
888 if error is not None:
889 if error == 'raise':
890 check = True
891 del kwargs['error']
892 kwargs['check'] = check
894 # TODO: Python version-compat stuff: remap/remove too-new args if needed
895 if 'text' in kwargs and sys.version_info[:3] < (3, 7):
896 kwargs['universal_newlines'] = kwargs.pop('text')
898 if 'capture_output' in kwargs and sys.version_info[:3] < (3, 7):
899 capture_output = kwargs.pop('capture_output')
900 if capture_output:
901 kwargs['stdout'] = kwargs['stderr'] = PIPE
903 # Most SCons tools/tests expect not to fail on things like missing files.
904 # check=True (or error="raise") means we're okay to take an exception;
905 # else we catch the likely exception and construct a dummy
906 # CompletedProcess instance.
907 # Note pylint can't see we always include 'check' in kwargs: suppress.
908 if check:
909 cp = subprocess.run(*args, **kwargs) # pylint: disable=subprocess-run-check
910 else:
911 try:
912 cp = subprocess.run(*args, **kwargs) # pylint: disable=subprocess-run-check
913 except OSError as exc:
914 argline = ' '.join(*args)
915 cp = subprocess.CompletedProcess(
916 args=argline, returncode=1, stdout="", stderr=""
919 return cp
922 def _subproc(scons_env, cmd, error='ignore', **kw):
923 """Wrapper for subprocess.Popen which pulls from construction env.
925 Use for calls to subprocess which need to interpolate values from
926 an SCons construction environment into the environment passed to
927 subprocess. Adds an an error-handling argument. Adds ability
928 to specify std{in,out,err} with "'devnull'" tag.
930 .. deprecated:: 4.6
932 # TODO: just uses subprocess.DEVNULL now, we can drop the "devnull"
933 # string now - it is a holdover from Py2, which didn't have DEVNULL.
934 for stream in 'stdin', 'stdout', 'stderr':
935 io = kw.get(stream)
936 if is_String(io) and io == 'devnull':
937 kw[stream] = DEVNULL
939 # Figure out what execution environment to use
940 ENV = kw.get('env', None)
941 if ENV is None: ENV = get_default_ENV(scons_env)
943 kw['env'] = SCons.Util.sanitize_shell_env(ENV)
945 try:
946 pobj = subprocess.Popen(cmd, **kw)
947 except OSError as e:
948 if error == 'raise': raise
949 # return a dummy Popen instance that only returns error
950 class dummyPopen:
951 def __init__(self, e) -> None:
952 self.exception = e
953 # Add the following two to enable using the return value as a context manager
954 # for example
955 # with Action._subproc(...) as po:
956 # logic here which uses po
958 def __enter__(self):
959 return self
961 def __exit__(self, *args) -> None:
962 pass
964 def communicate(self, input=None):
965 return ('', '')
967 def wait(self):
968 return -self.exception.errno
970 stdin = None
971 class f:
972 def read(self) -> str: return ''
973 def readline(self) -> str: return ''
974 def __iter__(self): return iter(())
975 stdout = stderr = f()
976 pobj = dummyPopen(e)
977 finally:
978 # clean up open file handles stored in parent's kw
979 for k, v in kw.items():
980 if inspect.ismethod(getattr(v, 'close', None)):
981 v.close()
983 return pobj
986 class CommandAction(_ActionAction):
987 """Class for command-execution actions."""
988 def __init__(self, cmd, **kw) -> None:
989 # Cmd can actually be a list or a single item; if it's a
990 # single item it should be the command string to execute; if a
991 # list then it should be the words of the command string to
992 # execute. Only a single command should be executed by this
993 # object; lists of commands should be handled by embedding
994 # these objects in a ListAction object (which the Action()
995 # factory above does). cmd will be passed to
996 # Environment.subst_list() for substituting environment
997 # variables.
998 if SCons.Debug.track_instances: logInstanceCreation(self, 'Action.CommandAction')
1000 super().__init__(**kw)
1001 if is_List(cmd):
1002 if [c for c in cmd if is_List(c)]:
1003 raise TypeError("CommandAction should be given only "
1004 "a single command")
1005 self.cmd_list = cmd
1007 def __str__(self) -> str:
1008 if is_List(self.cmd_list):
1009 return ' '.join(map(str, self.cmd_list))
1010 return str(self.cmd_list)
1013 def process(self, target, source, env, executor=None, overrides: Optional[dict] = None) -> Tuple[List, bool, bool]:
1014 if executor:
1015 result = env.subst_list(self.cmd_list, SUBST_CMD, executor=executor, overrides=overrides)
1016 else:
1017 result = env.subst_list(self.cmd_list, SUBST_CMD, target, source, overrides=overrides)
1018 silent = False
1019 ignore = False
1020 while True:
1021 try: c = result[0][0][0]
1022 except IndexError: c = None
1023 if c == '@': silent = True
1024 elif c == '-': ignore = True
1025 else: break
1026 result[0][0] = result[0][0][1:]
1027 try:
1028 if not result[0][0]:
1029 result[0] = result[0][1:]
1030 except IndexError:
1031 pass
1032 return result, ignore, silent
1034 def strfunction(self, target, source, env, executor: Optional[ExecutorType] = None, overrides: Optional[dict] = None) -> str:
1035 if self.cmdstr is None:
1036 return None
1037 if self.cmdstr is not _null:
1038 if executor:
1039 c = env.subst(self.cmdstr, SUBST_RAW, executor=executor, overrides=overrides)
1040 else:
1041 c = env.subst(self.cmdstr, SUBST_RAW, target, source, overrides=overrides)
1042 if c:
1043 return c
1044 cmd_list, ignore, silent = self.process(target, source, env, executor, overrides=overrides)
1045 if silent:
1046 return ''
1047 return _string_from_cmd_list(cmd_list[0])
1049 def execute(self, target, source, env, executor: Optional[ExecutorType] = None):
1050 """Execute a command action.
1052 This will handle lists of commands as well as individual commands,
1053 because construction variable substitution may turn a single
1054 "command" into a list. This means that this class can actually
1055 handle lists of commands, even though that's not how we use it
1056 externally.
1058 escape_list = SCons.Subst.escape_list
1059 flatten_sequence = SCons.Util.flatten_sequence
1061 try:
1062 shell = env['SHELL']
1063 except KeyError:
1064 raise SCons.Errors.UserError('Missing SHELL construction variable.')
1066 try:
1067 spawn = env['SPAWN']
1068 except KeyError:
1069 raise SCons.Errors.UserError('Missing SPAWN construction variable.')
1071 if is_String(spawn):
1072 spawn = env.subst(spawn, raw=1, conv=lambda x: x)
1074 escape = env.get('ESCAPE', lambda x: x)
1075 ENV = _resolve_shell_env(env, target, source)
1077 # Ensure that the ENV values are all strings:
1078 for key, value in ENV.items():
1079 if not is_String(value):
1080 if is_List(value):
1081 # If the value is a list, then we assume it is a
1082 # path list, because that's a pretty common list-like
1083 # value to stick in an environment variable:
1084 value = flatten_sequence(value)
1085 ENV[key] = os.pathsep.join(map(str, value))
1086 else:
1087 # If it isn't a string or a list, then we just coerce
1088 # it to a string, which is the proper way to handle
1089 # Dir and File instances and will produce something
1090 # reasonable for just about everything else:
1091 ENV[key] = str(value)
1093 if executor:
1094 target = executor.get_all_targets()
1095 source = executor.get_all_sources()
1096 cmd_list, ignore, silent = self.process(target, list(map(rfile, source)), env, executor)
1098 # Use len() to filter out any "command" that's zero-length.
1099 for cmd_line in filter(len, cmd_list):
1100 # Escape the command line for the interpreter we are using.
1101 cmd_line = escape_list(cmd_line, escape)
1102 result = spawn(shell, escape, cmd_line[0], cmd_line, ENV)
1103 if not ignore and result:
1104 msg = "Error %s" % result
1105 return SCons.Errors.BuildError(errstr=msg,
1106 status=result,
1107 action=self,
1108 command=cmd_line)
1109 return 0
1111 def get_presig(self, target, source, env, executor: Optional[ExecutorType] = None):
1112 """Return the signature contents of this action's command line.
1114 This strips $(-$) and everything in between the string,
1115 since those parts don't affect signatures.
1117 cmd = self.cmd_list
1118 if is_List(cmd):
1119 cmd = ' '.join(map(str, cmd))
1120 else:
1121 cmd = str(cmd)
1122 if executor:
1123 return env.subst_target_source(cmd, SUBST_SIG, executor=executor)
1124 return env.subst_target_source(cmd, SUBST_SIG, target, source)
1126 def get_implicit_deps(self, target, source, env, executor: Optional[ExecutorType] = None):
1127 """Return the implicit dependencies of this action's command line."""
1128 icd = env.get('IMPLICIT_COMMAND_DEPENDENCIES', True)
1129 if is_String(icd) and icd[:1] == '$':
1130 icd = env.subst(icd)
1132 if not icd or str(icd).lower() in ('0', 'none', 'false', 'no', 'off'):
1133 return []
1135 try:
1136 icd_int = int(icd)
1137 except ValueError:
1138 icd_int = None
1140 if (icd_int and icd_int > 1) or str(icd).lower() == 'all':
1141 # An integer value greater than 1 specifies the number of entries
1142 # to scan. "all" means to scan all.
1143 return self._get_implicit_deps_heavyweight(target, source, env, executor, icd_int)
1144 # Everything else (usually 1 or True) means that we want
1145 # lightweight dependency scanning.
1146 return self._get_implicit_deps_lightweight(target, source, env, executor)
1148 def _get_implicit_deps_lightweight(self, target, source, env, executor: Optional[ExecutorType]):
1150 Lightweight dependency scanning involves only scanning the first entry
1151 in an action string, even if it contains &&.
1153 if executor:
1154 cmd_list = env.subst_list(self.cmd_list, SUBST_SIG, executor=executor)
1155 else:
1156 cmd_list = env.subst_list(self.cmd_list, SUBST_SIG, target, source)
1157 res = []
1158 for cmd_line in cmd_list:
1159 if cmd_line:
1160 d = str(cmd_line[0])
1161 m = strip_quotes.match(d)
1162 if m:
1163 d = m.group(1)
1164 d = env.WhereIs(d)
1165 if d:
1166 res.append(env.fs.File(d))
1167 return res
1169 def _get_implicit_deps_heavyweight(self, target, source, env, executor: Optional[ExecutorType],
1170 icd_int):
1172 Heavyweight dependency scanning involves scanning more than just the
1173 first entry in an action string. The exact behavior depends on the
1174 value of icd_int. Only files are taken as implicit dependencies;
1175 directories are ignored.
1177 If icd_int is an integer value, it specifies the number of entries to
1178 scan for implicit dependencies. Action strings are also scanned after
1179 a &&. So for example, if icd_int=2 and the action string is
1180 "cd <some_dir> && $PYTHON $SCRIPT_PATH <another_path>", the implicit
1181 dependencies would be the path to the python binary and the path to the
1182 script.
1184 If icd_int is None, all entries are scanned for implicit dependencies.
1187 # Avoid circular and duplicate dependencies by not providing source,
1188 # target, or executor to subst_list. This causes references to
1189 # $SOURCES, $TARGETS, and all related variables to disappear.
1190 cmd_list = env.subst_list(self.cmd_list, SUBST_SIG, conv=lambda x: x)
1191 res = []
1193 for cmd_line in cmd_list:
1194 if cmd_line:
1195 entry_count = 0
1196 for entry in cmd_line:
1197 d = str(entry)
1198 if ((icd_int is None or entry_count < icd_int) and
1199 not d.startswith(('&', '-', '/') if os.name == 'nt'
1200 else ('&', '-'))):
1201 m = strip_quotes.match(d)
1202 if m:
1203 d = m.group(1)
1205 if d:
1206 # Resolve the first entry in the command string using
1207 # PATH, which env.WhereIs() looks in.
1208 # For now, only match files, not directories.
1209 p = os.path.abspath(d) if os.path.isfile(d) else None
1210 if not p and entry_count == 0:
1211 p = env.WhereIs(d)
1213 if p:
1214 res.append(env.fs.File(p))
1216 entry_count = entry_count + 1
1217 else:
1218 entry_count = 0 if d == '&&' else entry_count + 1
1220 # Despite not providing source and target to env.subst() above, we
1221 # can still end up with sources in this list. For example, files in
1222 # LIBS will still resolve in env.subst(). This won't result in
1223 # circular dependencies, but it causes problems with cache signatures
1224 # changing between full and incremental builds.
1225 return [r for r in res if r not in target and r not in source]
1228 class CommandGeneratorAction(ActionBase):
1229 """Class for command-generator actions."""
1230 def __init__(self, generator, kw) -> None:
1231 if SCons.Debug.track_instances: logInstanceCreation(self, 'Action.CommandGeneratorAction')
1232 self.generator = generator
1233 self.gen_kw = kw
1234 self.varlist = kw.get('varlist', ())
1235 self.targets = kw.get('targets', '$TARGETS')
1237 def _generate(self, target, source, env, for_signature, executor: Optional[ExecutorType] = None):
1238 # ensure that target is a list, to make it easier to write
1239 # generator functions:
1240 if not is_List(target):
1241 target = [target]
1243 if executor:
1244 target = executor.get_all_targets()
1245 source = executor.get_all_sources()
1246 ret = self.generator(target=target,
1247 source=source,
1248 env=env,
1249 for_signature=for_signature)
1250 gen_cmd = Action(ret, **self.gen_kw)
1251 if not gen_cmd:
1252 raise SCons.Errors.UserError("Object returned from command generator: %s cannot be used to create an Action." % repr(ret))
1253 return gen_cmd
1255 def __str__(self) -> str:
1256 try:
1257 env = self.presub_env
1258 except AttributeError:
1259 env = None
1260 if env is None:
1261 env = SCons.Defaults.DefaultEnvironment()
1262 act = self._generate([], [], env, 1)
1263 return str(act)
1265 def batch_key(self, env, target, source):
1266 return self._generate(target, source, env, 1).batch_key(env, target, source)
1268 def genstring(self, target, source, env, executor: Optional[ExecutorType] = None) -> str:
1269 return self._generate(target, source, env, 1, executor).genstring(target, source, env)
1271 def __call__(self, target, source, env, exitstatfunc=_null, presub=_null,
1272 show=_null, execute=_null, chdir=_null, executor: Optional[ExecutorType] = None):
1273 act = self._generate(target, source, env, 0, executor)
1274 if act is None:
1275 raise SCons.Errors.UserError(
1276 "While building `%s': "
1277 "Cannot deduce file extension from source files: %s"
1278 % (repr(list(map(str, target))), repr(list(map(str, source))))
1280 return act(
1281 target, source, env, exitstatfunc, presub, show, execute, chdir, executor
1284 def get_presig(self, target, source, env, executor: Optional[ExecutorType] = None):
1285 """Return the signature contents of this action's command line.
1287 This strips $(-$) and everything in between the string,
1288 since those parts don't affect signatures.
1290 return self._generate(target, source, env, 1, executor).get_presig(target, source, env)
1292 def get_implicit_deps(self, target, source, env, executor: Optional[ExecutorType] = None):
1293 return self._generate(target, source, env, 1, executor).get_implicit_deps(target, source, env)
1295 def get_varlist(self, target, source, env, executor: Optional[ExecutorType] = None):
1296 return self._generate(target, source, env, 1, executor).get_varlist(target, source, env, executor)
1298 def get_targets(self, env, executor: Optional[ExecutorType]):
1299 return self._generate(None, None, env, 1, executor).get_targets(env, executor)
1302 class LazyAction(CommandGeneratorAction, CommandAction):
1304 A LazyAction is a kind of hybrid generator and command action for
1305 strings of the form "$VAR". These strings normally expand to other
1306 strings (think "$CCCOM" to "$CC -c -o $TARGET $SOURCE"), but we also
1307 want to be able to replace them with functions in the construction
1308 environment. Consequently, we want lazy evaluation and creation of
1309 an Action in the case of the function, but that's overkill in the more
1310 normal case of expansion to other strings.
1312 So we do this with a subclass that's both a generator *and*
1313 a command action. The overridden methods all do a quick check
1314 of the construction variable, and if it's a string we just call
1315 the corresponding CommandAction method to do the heavy lifting.
1316 If not, then we call the same-named CommandGeneratorAction method.
1317 The CommandGeneratorAction methods work by using the overridden
1318 _generate() method, that is, our own way of handling "generation" of
1319 an action based on what's in the construction variable.
1322 def __init__(self, var, kw) -> None:
1323 if SCons.Debug.track_instances: logInstanceCreation(self, 'Action.LazyAction')
1324 CommandAction.__init__(self, '${'+var+'}', **kw)
1325 self.var = SCons.Util.to_String(var)
1326 self.gen_kw = kw
1328 def get_parent_class(self, env):
1329 c = env.get(self.var)
1330 if is_String(c) and '\n' not in c:
1331 return CommandAction
1332 return CommandGeneratorAction
1334 def _generate_cache(self, env):
1335 if env:
1336 c = env.get(self.var, '')
1337 else:
1338 c = ''
1339 gen_cmd = Action(c, **self.gen_kw)
1340 if not gen_cmd:
1341 raise SCons.Errors.UserError("$%s value %s cannot be used to create an Action." % (self.var, repr(c)))
1342 return gen_cmd
1344 def _generate(self, target, source, env, for_signature, executor: Optional[ExecutorType] = None):
1345 return self._generate_cache(env)
1347 def __call__(self, target, source, env, *args, **kw):
1348 c = self.get_parent_class(env)
1349 return c.__call__(self, target, source, env, *args, **kw)
1351 def get_presig(self, target, source, env, executor: Optional[ExecutorType] = None):
1352 c = self.get_parent_class(env)
1353 return c.get_presig(self, target, source, env)
1355 def get_implicit_deps(self, target, source, env, executor: Optional[ExecutorType] = None):
1356 c = self.get_parent_class(env)
1357 return c.get_implicit_deps(self, target, source, env)
1359 def get_varlist(self, target, source, env, executor: Optional[ExecutorType] = None):
1360 c = self.get_parent_class(env)
1361 return c.get_varlist(self, target, source, env, executor)
1364 class FunctionAction(_ActionAction):
1365 """Class for Python function actions."""
1367 def __init__(self, execfunction, kw) -> None:
1368 if SCons.Debug.track_instances: logInstanceCreation(self, 'Action.FunctionAction')
1370 self.execfunction = execfunction
1371 try:
1372 self.funccontents = _callable_contents(execfunction)
1373 except AttributeError:
1374 try:
1375 # See if execfunction will do the heavy lifting for us.
1376 self.gc = execfunction.get_contents
1377 except AttributeError:
1378 # This is weird, just do the best we can.
1379 self.funccontents = _object_contents(execfunction)
1381 super().__init__(**kw)
1383 def function_name(self):
1384 try:
1385 return self.execfunction.__name__
1386 except AttributeError:
1387 try:
1388 return self.execfunction.__class__.__name__
1389 except AttributeError:
1390 return "unknown_python_function"
1392 def strfunction(self, target, source, env, executor: Optional[ExecutorType] = None):
1393 if self.cmdstr is None:
1394 return None
1395 if self.cmdstr is not _null:
1396 if executor:
1397 c = env.subst(self.cmdstr, SUBST_RAW, executor=executor)
1398 else:
1399 c = env.subst(self.cmdstr, SUBST_RAW, target, source)
1400 if c:
1401 return c
1403 def array(a):
1404 def quote(s):
1405 try:
1406 str_for_display = s.str_for_display
1407 except AttributeError:
1408 s = repr(s)
1409 else:
1410 s = str_for_display()
1411 return s
1412 return '[' + ", ".join(map(quote, a)) + ']'
1413 try:
1414 strfunc = self.execfunction.strfunction
1415 except AttributeError:
1416 pass
1417 else:
1418 if strfunc is None:
1419 return None
1420 if callable(strfunc):
1421 return strfunc(target, source, env)
1422 name = self.function_name()
1423 tstr = array(target)
1424 sstr = array(source)
1425 return "%s(%s, %s)" % (name, tstr, sstr)
1427 def __str__(self) -> str:
1428 name = self.function_name()
1429 if name == 'ActionCaller':
1430 return str(self.execfunction)
1431 return "%s(target, source, env)" % name
1433 def execute(self, target, source, env, executor: Optional[ExecutorType] = None):
1434 exc_info = (None,None,None)
1435 try:
1436 if executor:
1437 target = executor.get_all_targets()
1438 source = executor.get_all_sources()
1439 rsources = list(map(rfile, source))
1440 try:
1441 result = self.execfunction(target=target, source=rsources, env=env)
1442 except (KeyboardInterrupt, SystemExit):
1443 raise
1444 except Exception as e:
1445 result = e
1446 exc_info = sys.exc_info()
1448 if result:
1449 result = SCons.Errors.convert_to_BuildError(result, exc_info)
1450 result.node = target
1451 result.action = self
1452 try:
1453 result.command=self.strfunction(target, source, env, executor)
1454 except TypeError:
1455 result.command=self.strfunction(target, source, env)
1457 # FIXME: This maintains backward compatibility with respect to
1458 # which type of exceptions were returned by raising an
1459 # exception and which ones were returned by value. It would
1460 # probably be best to always return them by value here, but
1461 # some codes do not check the return value of Actions and I do
1462 # not have the time to modify them at this point.
1463 if (exc_info[1] and
1464 not isinstance(exc_info[1], EnvironmentError)):
1465 raise result
1467 return result
1468 finally:
1469 # Break the cycle between the traceback object and this
1470 # function stack frame. See the sys.exc_info() doc info for
1471 # more information about this issue.
1472 del exc_info
1474 def get_presig(self, target, source, env, executor: Optional[ExecutorType] = None):
1475 """Return the signature contents of this callable action."""
1476 try:
1477 return self.gc(target, source, env)
1478 except AttributeError:
1479 return self.funccontents
1481 def get_implicit_deps(self, target, source, env, executor: Optional[ExecutorType] = None):
1482 return []
1484 class ListAction(ActionBase):
1485 """Class for lists of other actions."""
1486 def __init__(self, actionlist) -> None:
1487 if SCons.Debug.track_instances: logInstanceCreation(self, 'Action.ListAction')
1488 def list_of_actions(x):
1489 if isinstance(x, ActionBase):
1490 return x
1491 return Action(x)
1492 self.list = list(map(list_of_actions, actionlist))
1493 # our children will have had any varlist
1494 # applied; we don't need to do it again
1495 self.varlist = ()
1496 self.targets = '$TARGETS'
1498 def genstring(self, target, source, env, executor: Optional[ExecutorType] = None) -> str:
1499 return '\n'.join([a.genstring(target, source, env) for a in self.list])
1501 def __str__(self) -> str:
1502 return '\n'.join(map(str, self.list))
1504 def presub_lines(self, env):
1505 return SCons.Util.flatten_sequence(
1506 [a.presub_lines(env) for a in self.list])
1508 def get_presig(self, target, source, env, executor: Optional[ExecutorType] = None):
1509 """Return the signature contents of this action list.
1511 Simple concatenation of the signatures of the elements.
1513 return b"".join([bytes(x.get_contents(target, source, env)) for x in self.list])
1515 def __call__(self, target, source, env, exitstatfunc=_null, presub=_null,
1516 show=_null, execute=_null, chdir=_null, executor: Optional[ExecutorType] = None):
1517 if executor:
1518 target = executor.get_all_targets()
1519 source = executor.get_all_sources()
1520 for act in self.list:
1521 stat = act(target, source, env, exitstatfunc, presub,
1522 show, execute, chdir, executor)
1523 if stat:
1524 return stat
1525 return 0
1527 def get_implicit_deps(self, target, source, env, executor: Optional[ExecutorType] = None):
1528 result = []
1529 for act in self.list:
1530 result.extend(act.get_implicit_deps(target, source, env))
1531 return result
1533 def get_varlist(self, target, source, env, executor: Optional[ExecutorType] = None):
1534 result = OrderedDict()
1535 for act in self.list:
1536 for var in act.get_varlist(target, source, env, executor):
1537 result[var] = True
1538 return list(result.keys())
1541 class ActionCaller:
1542 """A class for delaying calling an Action function with specific
1543 (positional and keyword) arguments until the Action is actually
1544 executed.
1546 This class looks to the rest of the world like a normal Action object,
1547 but what it's really doing is hanging on to the arguments until we
1548 have a target, source and env to use for the expansion.
1550 def __init__(self, parent, args, kw) -> None:
1551 self.parent = parent
1552 self.args = args
1553 self.kw = kw
1555 def get_contents(self, target, source, env):
1556 actfunc = self.parent.actfunc
1557 try:
1558 # "self.actfunc" is a function.
1559 contents = actfunc.__code__.co_code
1560 except AttributeError:
1561 # "self.actfunc" is a callable object.
1562 try:
1563 contents = actfunc.__call__.__func__.__code__.co_code
1564 except AttributeError:
1565 # No __call__() method, so it might be a builtin
1566 # or something like that. Do the best we can.
1567 contents = repr(actfunc)
1569 return contents
1571 def subst(self, s, target, source, env):
1572 # If s is a list, recursively apply subst()
1573 # to every element in the list
1574 if is_List(s):
1575 result = []
1576 for elem in s:
1577 result.append(self.subst(elem, target, source, env))
1578 return self.parent.convert(result)
1580 # Special-case hack: Let a custom function wrapped in an
1581 # ActionCaller get at the environment through which the action
1582 # was called by using this hard-coded value as a special return.
1583 if s == '$__env__':
1584 return env
1585 if is_String(s):
1586 return env.subst(s, 1, target, source)
1588 return self.parent.convert(s)
1590 def subst_args(self, target, source, env):
1591 return [self.subst(x, target, source, env) for x in self.args]
1593 def subst_kw(self, target, source, env):
1594 kw = {}
1595 for key in list(self.kw.keys()):
1596 kw[key] = self.subst(self.kw[key], target, source, env)
1597 return kw
1599 def __call__(self, target, source, env, executor: Optional[ExecutorType] = None):
1600 args = self.subst_args(target, source, env)
1601 kw = self.subst_kw(target, source, env)
1602 return self.parent.actfunc(*args, **kw)
1604 def strfunction(self, target, source, env):
1605 args = self.subst_args(target, source, env)
1606 kw = self.subst_kw(target, source, env)
1607 return self.parent.strfunc(*args, **kw)
1609 def __str__(self) -> str:
1610 return self.parent.strfunc(*self.args, **self.kw)
1613 class ActionFactory:
1614 """A factory class that will wrap up an arbitrary function
1615 as an SCons-executable Action object.
1617 The real heavy lifting here is done by the ActionCaller class.
1618 We just collect the (positional and keyword) arguments that we're
1619 called with and give them to the ActionCaller object we create,
1620 so it can hang onto them until it needs them.
1622 def __init__(self, actfunc, strfunc, convert=lambda x: x) -> None:
1623 self.actfunc = actfunc
1624 self.strfunc = strfunc
1625 self.convert = convert
1627 def __call__(self, *args, **kw):
1628 ac = ActionCaller(self, args, kw)
1629 action = Action(ac, strfunction=ac.strfunction)
1630 return action
1632 # Local Variables:
1633 # tab-width:4
1634 # indent-tabs-mode:nil
1635 # End:
1636 # vim: set expandtab tabstop=4 shiftwidth=4: