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.
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:
43 CommandGeneratorAction
47 The subclasses supply the following public interface methods used by
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.
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.
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
73 Returns a string approximation of the Action; no variable
74 substitution is performed.
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.
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?
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 from __future__
import annotations
111 from abc
import ABC
, abstractmethod
112 from collections
import OrderedDict
113 from subprocess
import DEVNULL
, PIPE
114 from typing
import TYPE_CHECKING
121 # we use these a lot, so try to optimize them
122 from SCons
.Debug
import logInstanceCreation
123 from SCons
.Subst
import SUBST_CMD
, SUBST_RAW
, SUBST_SIG
124 from SCons
.Util
import is_String
, is_List
127 from SCons
.Executor
import Executor
133 execute_actions
= True
134 print_actions_presub
= False
136 # Use pickle protocol 4 when pickling functions for signature.
137 # This is the common format since Python 3.4
138 # TODO: use is commented out as not stable since 2017: e0bc3a04d5. Drop?
139 # ACTION_SIGNATURE_PICKLE_PROTOCOL = 4
145 except AttributeError:
149 def default_exitstatfunc(s
):
152 strip_quotes
= re
.compile(r
'^[\'"](.*)[\'"]$
')
155 def _callable_contents(obj) -> bytearray:
156 """Return the signature contents of a callable Python object."""
158 # Test if obj is a method.
159 return _function_contents(obj.__func__)
161 except AttributeError:
163 # Test if obj is a callable object.
164 return _function_contents(obj.__call__.__func__)
166 except AttributeError:
168 # Test if obj is a code object.
169 return _code_contents(obj)
171 except AttributeError:
172 # Test if obj is a function object.
173 return _function_contents(obj)
176 def _object_contents(obj) -> bytearray:
177 """Return the signature contents of any Python object.
179 We have to handle the case where object contains a code object
180 since it can be pickled directly.
183 # Test if obj is a method.
184 return _function_contents(obj.__func__)
186 except AttributeError:
188 # Test if obj is a callable object.
189 return _function_contents(obj.__call__.__func__)
191 except AttributeError:
193 # Test if obj is a code object.
194 return _code_contents(obj)
196 except AttributeError:
198 # Test if obj is a function object.
199 return _function_contents(obj)
201 except AttributeError as ae:
202 # Should be a pickle-able Python object.
204 return _object_instance_content(obj)
205 # pickling an Action instance or object doesn't
yield a stable
206 # content as instance property may be dumped in different orders
207 # return pickle.dumps(obj, ACTION_SIGNATURE_PICKLE_PROTOCOL)
208 except (pickle
.PicklingError
, TypeError, AttributeError) as ex
:
209 # This is weird, but it seems that nested classes
210 # are unpickable. The Python docs say it should
211 # always be a PicklingError, but some Python
212 # versions seem to return TypeError. Just do
214 return bytearray(repr(obj
), 'utf-8')
216 # TODO: docstrings for _code_contents and _function_contents
217 # do not render well with Sphinx. Consider reworking.
219 def _code_contents(code
, docstring
=None) -> bytearray
:
220 r
"""Return the signature contents of a code object.
222 By providing direct access to the code object of the
223 function, Python makes this extremely easy. Hooray!
225 Unfortunately, older versions of Python include line
226 number indications in the compiled byte code. Boo!
227 So we remove the line number byte codes to prevent
228 recompilations from moving a Python function.
231 - https://docs.python.org/3/library/inspect.html
232 - http://python-reference.readthedocs.io/en/latest/docs/code/index.html
234 For info on what each co\_ variable provides
236 The signature is as follows (should be byte/chars):
237 co_argcount, len(co_varnames), len(co_cellvars), len(co_freevars),
238 ( comma separated signature for each object in co_consts ),
239 ( comma separated signature for each object in co_names ),
240 ( The bytecode with line number bytecodes removed from co_code )
242 co_argcount - Returns the number of positional arguments (including arguments with default values).
243 co_varnames - Returns a tuple containing the names of the local variables (starting with the argument names).
244 co_cellvars - Returns a tuple containing the names of local variables that are referenced by nested functions.
245 co_freevars - Returns a tuple containing the names of free variables. (?)
246 co_consts - Returns a tuple containing the literals used by the bytecode.
247 co_names - Returns a tuple containing the names used by the bytecode.
248 co_code - Returns a string representing the sequence of bytecode instructions.
253 # The code contents depends on the number of local variables
254 # but not their actual names.
255 contents
= bytearray(f
"{code.co_argcount}, {len(code.co_varnames)}", 'utf-8')
257 contents
.extend(b
", ")
258 contents
.extend(bytearray(str(len(code
.co_cellvars
)), 'utf-8'))
259 contents
.extend(b
", ")
260 contents
.extend(bytearray(str(len(code
.co_freevars
)), 'utf-8'))
262 # The code contents depends on any constants accessed by the
263 # function. Note that we have to call _object_contents on each
264 # constants because the code object of nested functions can
265 # show-up among the constants.
266 z
= [_object_contents(cc
) for cc
in code
.co_consts
if cc
!= docstring
]
267 contents
.extend(b
',(')
268 contents
.extend(bytearray(',', 'utf-8').join(z
))
269 contents
.extend(b
')')
271 # The code contents depends on the variable names used to
272 # accessed global variable, as changing the variable name changes
273 # the variable actually accessed and therefore changes the
275 z
= [bytearray(_object_contents(cc
)) for cc
in code
.co_names
]
276 contents
.extend(b
',(')
277 contents
.extend(bytearray(',','utf-8').join(z
))
278 contents
.extend(b
')')
280 # The code contents depends on its actual code!!!
281 contents
.extend(b
',(')
282 contents
.extend(code
.co_code
)
283 contents
.extend(b
')')
288 def _function_contents(func
) -> bytearray
:
289 """Return the signature contents of a function.
291 The signature is as follows (should be byte/chars):
292 < _code_contents (see above) from func.__code__ >
293 ,( comma separated _object_contents for function argument defaults)
294 ,( comma separated _object_contents for any closure contents )
297 See also: https://docs.python.org/3/reference/datamodel.html
298 - func.__code__ - The code object representing the compiled function body.
299 - func.__defaults__ - A tuple containing default argument values for those arguments that have defaults, or None if no arguments have a default value
300 - func.__closure__ - None or a tuple of cells that contain bindings for the function's free variables.
302 contents
= [_code_contents(func
.__code
__, func
.__doc
__)]
304 # The function contents depends on the value of defaults arguments
305 if func
.__defaults
__:
307 function_defaults_contents
= [_object_contents(cc
) for cc
in func
.__defaults
__]
309 defaults
= bytearray(b
',(')
310 defaults
.extend(bytearray(b
',').join(function_defaults_contents
))
311 defaults
.extend(b
')')
313 contents
.append(defaults
)
315 contents
.append(b
',()')
317 # The function contents depends on the closure captured cell values.
318 closure
= func
.__closure
__ or []
321 closure_contents
= [_object_contents(x
.cell_contents
) for x
in closure
]
322 except AttributeError:
323 closure_contents
= []
325 contents
.append(b
',(')
326 contents
.append(bytearray(b
',').join(closure_contents
))
327 contents
.append(b
')')
329 retval
= bytearray(b
'').join(contents
)
333 def _object_instance_content(obj
):
335 Returns consistant content for a action class or an instance thereof
338 - `obj` Should be either and action class or an instance thereof
341 bytearray or bytes representing the obj suitable for generating a signature from.
348 if isinstance(obj
, SCons
.Util
.BaseStringTypes
):
349 return SCons
.Util
.to_bytes(obj
)
351 inst_class
= obj
.__class
__
352 inst_class_name
= bytearray(obj
.__class
__.__name
__,'utf-8')
353 inst_class_module
= bytearray(obj
.__class
__.__module
__,'utf-8')
354 inst_class_hierarchy
= bytearray(repr(inspect
.getclasstree([obj
.__class
__,])),'utf-8')
355 # print("ICH:%s : %s"%(inst_class_hierarchy, repr(obj)))
357 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
))) ]
359 properties_str
= ','.join(["%s=%s"%(p
[0],p
[1]) for p
in properties
])
360 properties_bytes
= bytearray(properties_str
,'utf-8')
362 methods
= [p
for p
in dir(obj
) if inspect
.ismethod(getattr(obj
, p
))]
367 # print("Method:%s"%m)
368 v
= _function_contents(getattr(obj
, m
))
369 # print("[%s->]V:%s [%s]"%(m,v,type(v)))
370 method_contents
.append(v
)
372 retval
= bytearray(b
'{')
373 retval
.extend(inst_class_name
)
375 retval
.extend(inst_class_module
)
376 retval
.extend(b
'}[[')
377 retval
.extend(inst_class_hierarchy
)
378 retval
.extend(b
']]{{')
379 retval
.extend(bytearray(b
",").join(method_contents
))
380 retval
.extend(b
"}}{{{")
381 retval
.extend(properties_bytes
)
382 retval
.extend(b
'}}}')
385 # print("class :%s"%inst_class)
386 # print("class_name :%s"%inst_class_name)
387 # print("class_module :%s"%inst_class_module)
388 # print("Class hier :\n%s"%pp.pformat(inst_class_hierarchy))
389 # print("Inst Properties:\n%s"%pp.pformat(properties))
390 # print("Inst Methods :\n%s"%pp.pformat(methods))
392 def _actionAppend(act1
, act2
):
393 """Joins two actions together.
395 Mainly, it handles ListActions by concatenating into a single ListAction.
403 if isinstance(a1
, ListAction
):
404 if isinstance(a2
, ListAction
):
405 return ListAction(a1
.list + a2
.list)
406 return ListAction(a1
.list + [ a2
])
408 if isinstance(a2
, ListAction
):
409 return ListAction([ a1
] + a2
.list)
411 return ListAction([ a1
, a2
])
414 def _do_create_keywords(args
, kw
):
415 """This converts any arguments after the action argument into
416 their equivalent keywords and adds them to the kw argument.
418 v
= kw
.get('varlist', ())
419 # prevent varlist="FOO" from being interpreted as ['F', 'O', 'O']
420 if is_String(v
): v
= (v
,)
421 kw
['varlist'] = tuple(v
)
423 # turn positional args into equivalent keywords
425 if cmdstrfunc
is None or is_String(cmdstrfunc
):
426 kw
['cmdstr'] = cmdstrfunc
427 elif callable(cmdstrfunc
):
428 kw
['strfunction'] = cmdstrfunc
430 raise SCons
.Errors
.UserError(
431 'Invalid command display variable type. '
432 'You must either pass a string or a callback which '
433 'accepts (target, source, env) as parameters.')
435 kw
['varlist'] = tuple(SCons
.Util
.flatten(args
[1:])) + kw
['varlist']
436 if kw
.get('strfunction', _null
) is not _null \
437 and kw
.get('cmdstr', _null
) is not _null
:
438 raise SCons
.Errors
.UserError(
439 'Cannot have both strfunction and cmdstr args to Action()')
442 def _do_create_action(act
, kw
):
443 """The internal implementation for the Action factory method.
445 This handles the fact that passing lists to :func:`Action` itself has
446 different semantics than passing lists as elements of lists.
447 The former will create a :class:`ListAction`, the latter will create a
448 :class:`CommandAction` by converting the inner list elements to strings.
450 if isinstance(act
, ActionBase
):
454 var
= SCons
.Util
.get_environment_var(act
)
456 # This looks like a string that is purely an Environment
457 # variable reference, like "$FOO" or "${FOO}". We do
458 # something special here...we lazily evaluate the contents
459 # of that Environment variable, so a user could put something
460 # like a function or a CommandGenerator in that variable
461 # instead of a string.
462 return LazyAction(var
, kw
)
463 commands
= str(act
).split('\n')
464 if len(commands
) == 1:
465 return CommandAction(commands
[0], **kw
)
466 # The list of string commands may include a LazyAction, so we
467 # reprocess them via _do_create_list_action.
468 return _do_create_list_action(commands
, kw
)
471 return CommandAction(act
, **kw
)
474 gen
= kw
.pop('generator', False)
476 action_type
= CommandGeneratorAction
478 action_type
= FunctionAction
479 return action_type(act
, kw
)
481 # Catch a common error case with a nice message:
482 if isinstance(act
, (int, float)):
483 raise TypeError("Don't know how to create an Action from a number (%s)"%act
)
484 # Else fail silently (???)
488 def _do_create_list_action(act
, kw
) -> ListAction
:
489 """A factory for list actions.
491 Convert the input list *act* into Actions and then wrap them in a
492 :class:`ListAction`. If *act* has only a single member, return that
493 member, not a *ListAction*. This is intended to allow a contained
494 list to specify a command action without being processed into a
499 aa
= _do_create_action(a
, kw
)
503 return ListAction([])
506 return ListAction(acts
)
509 def Action(act
, *args
, **kw
):
510 """A factory for action objects."""
511 # Really simple: the _do_create_* routines do the heavy lifting.
512 _do_create_keywords(args
, kw
)
514 return _do_create_list_action(act
, kw
)
515 return _do_create_action(act
, kw
)
518 class ActionBase(ABC
):
519 """Base class for all types of action objects that can be held by
520 other objects (Builders, Executors, etc.) This provides the
521 common methods for manipulating and combining those actions."""
534 executor
: Executor |
None = None,
536 raise NotImplementedError
538 def __eq__(self
, other
):
539 return self
.__dict
__ == other
541 def no_batch_key(self
, env
, target
, source
):
544 batch_key
= no_batch_key
546 def genstring(self
, target
, source
, env
, executor
: Executor |
None = None) -> str:
550 def get_presig(self
, target
, source
, env
, executor
: Executor |
None = None):
551 raise NotImplementedError
554 def get_implicit_deps(self
, target
, source
, env
, executor
: Executor |
None = None):
555 raise NotImplementedError
557 def get_contents(self
, target
, source
, env
):
558 result
= self
.get_presig(target
, source
, env
)
560 if not isinstance(result
, (bytes
, bytearray
)):
561 result
= bytearray(result
, 'utf-8')
563 # Make a copy and put in bytearray, without this the contents returned by get_presig
564 # can be changed by the logic below, appending with each call and causing very
565 # hard to track down issues...
566 result
= bytearray(result
)
568 # At this point everything should be a bytearray
570 # This should never happen, as the Action() factory should wrap
571 # the varlist, but just in case an action is created directly,
572 # we duplicate this check here.
573 vl
= self
.get_varlist(target
, source
, env
)
574 if is_String(vl
): vl
= (vl
,)
576 # do the subst this way to ignore $(...$) parts:
577 if isinstance(result
, bytearray
):
578 result
.extend(SCons
.Util
.to_bytes(env
.subst_target_source('${'+v
+'}', SUBST_SIG
, target
, source
)))
580 raise Exception("WE SHOULD NEVER GET HERE result should be bytearray not:%s"%type(result
))
581 # result.append(SCons.Util.to_bytes(env.subst_target_source('${'+v+'}', SUBST_SIG, target, source)))
583 if isinstance(result
, (bytes
, bytearray
)):
586 raise Exception("WE SHOULD NEVER GET HERE - #2 result should be bytearray not:%s" % type(result
))
588 def __add__(self
, other
):
589 return _actionAppend(self
, other
)
591 def __radd__(self
, other
):
592 return _actionAppend(other
, self
)
594 def presub_lines(self
, env
):
595 # CommandGeneratorAction needs a real environment
596 # in order to return the proper string here, since
597 # it may call LazyAction, which looks up a key
598 # in that env. So we temporarily remember the env here,
599 # and CommandGeneratorAction will use this env
600 # when it calls its _generate method.
601 self
.presub_env
= env
602 lines
= str(self
).split('\n')
603 self
.presub_env
= None # don't need this any more
606 def get_varlist(self
, target
, source
, env
, executor
: Executor |
None = None):
609 def get_targets(self
, env
, executor
: Executor |
None):
611 Returns the type of targets ($TARGETS, $CHANGED_TARGETS) used
617 class _ActionAction(ActionBase
):
618 """Base class for actions that create output objects."""
619 def __init__(self
, cmdstr
=_null
, strfunction
=_null
, varlist
=(),
620 presub
=_null
, chdir
=None, exitstatfunc
=None,
621 batch_key
=None, targets
: str='$TARGETS',
624 if strfunction
is not _null
:
625 if strfunction
is None:
628 self
.strfunction
= strfunction
629 self
.varlist
= varlist
633 exitstatfunc
= default_exitstatfunc
634 self
.exitstatfunc
= exitstatfunc
636 self
.targets
= targets
639 if not callable(batch_key
):
640 # They have set batch_key, but not to their own
641 # callable. The default behavior here will batch
642 # *all* targets+sources using this action, separated
643 # for each construction environment.
644 def default_batch_key(self
, env
, target
, source
):
645 return (id(self
), id(env
))
646 batch_key
= default_batch_key
647 SCons
.Util
.AddMethod(self
, batch_key
, 'batch_key')
649 def print_cmd_line(self
, s
, target
, source
, env
) -> None:
651 In python 3, and in some of our tests, sys.stdout is
652 a String io object, and it takes unicode strings only
653 This code assumes s is a regular string.
655 sys
.stdout
.write(s
+ "\n")
657 def __call__(self
, target
, source
, env
,
663 executor
: Executor |
None = None):
664 if not is_List(target
):
666 if not is_List(source
):
672 presub
= print_actions_presub
673 if exitstatfunc
is _null
:
674 exitstatfunc
= self
.exitstatfunc
678 execute
= execute_actions
683 save_cwd
= os
.getcwd()
685 chdir
= str(chdir
.get_abspath())
686 except AttributeError:
687 if not is_String(chdir
):
689 chdir
= str(executor
.batches
[0].targets
[0].dir)
691 chdir
= str(target
[0].dir)
694 target
= executor
.get_all_targets()
695 source
= executor
.get_all_sources()
696 t
= ' and '.join(map(str, target
))
697 l
= '\n '.join(self
.presub_lines(env
))
698 out
= "Building %s with action:\n %s\n" % (t
, l
)
699 sys
.stdout
.write(out
)
701 if show
and self
.strfunction
:
703 target
= executor
.get_all_targets()
704 source
= executor
.get_all_sources()
706 cmd
= self
.strfunction(target
, source
, env
, executor
)
708 cmd
= self
.strfunction(target
, source
, env
)
711 cmd
= ('os.chdir(%s)\n' % repr(chdir
)) + cmd
714 except AttributeError:
715 print_func
= self
.print_cmd_line
717 print_func
= get('PRINT_CMD_LINE_FUNC')
719 print_func
= self
.print_cmd_line
720 print_func(cmd
, target
, source
, env
)
726 stat
= self
.execute(target
, source
, env
, executor
=executor
)
727 if isinstance(stat
, SCons
.Errors
.BuildError
):
728 s
= exitstatfunc(stat
.status
)
734 stat
= exitstatfunc(stat
)
739 print_func('os.chdir(%s)' % repr(save_cwd
), target
, source
, env
)
743 # Stub these two only so _ActionAction can be instantiated. It's really
744 # an ABC like parent ActionBase, but things reach in and use it. It's
745 # not just unittests or we could fix it up with a concrete subclass there.
747 def get_presig(self
, target
, source
, env
, executor
: Executor |
None = None):
748 raise NotImplementedError
750 def get_implicit_deps(self
, target
, source
, env
, executor
: Executor |
None = None):
751 raise NotImplementedError
754 def _string_from_cmd_list(cmd_list
):
755 """Takes a list of command line arguments and returns a pretty
756 representation for printing."""
758 for arg
in map(str, cmd_list
):
759 if ' ' in arg
or '\t' in arg
:
760 arg
= '"' + arg
+ '"'
767 def get_default_ENV(env
):
768 """Returns an execution environment.
770 If there is one in *env*, just use it, else return the Default
771 Environment, insantiated if necessary.
773 A fiddlin' little function that has an ``import SCons.Environment``
774 which cannot be moved to the top level without creating an import
775 loop. Since this import creates a local variable named ``SCons``,
776 it blocks access to the global variable, so we move it here to
777 prevent complaints about local variables being used uninitialized.
785 import SCons
.Environment
# pylint: disable=import-outside-toplevel,redefined-outer-name
786 # This is a hideously expensive way to get a default execution
787 # environment. What it really should do is run the platform
788 # setup to get the default ENV. Fortunately, it's incredibly
789 # rare for an Environment not to have an execution environment,
790 # so we're not going to worry about it overmuch.
791 default_ENV
= SCons
.Environment
.Environment()['ENV']
795 def _resolve_shell_env(env
, target
, source
):
796 """Returns a resolved execution environment.
798 First get the execution environment. Then if ``SHELL_ENV_GENERATORS``
799 is set and is iterable, call each function to allow it to alter the
800 created execution environment, passing each the returned execution
801 environment from the previous call.
803 .. versionadded:: 4.4
805 ENV
= get_default_ENV(env
)
806 shell_gen
= env
.get('SHELL_ENV_GENERATORS')
809 shell_gens
= iter(shell_gen
)
811 raise SCons
.Errors
.UserError("SHELL_ENV_GENERATORS must be iteratable.")
814 for generator
in shell_gens
:
815 ENV
= generator(env
, target
, source
, ENV
)
816 if not isinstance(ENV
, dict):
817 raise SCons
.Errors
.UserError(f
"SHELL_ENV_GENERATORS function: {generator} must return a dict.")
822 def scons_subproc_run(scons_env
, *args
, **kwargs
) -> subprocess
.CompletedProcess
:
823 """Run an external command using an SCons execution environment.
825 SCons normally runs external build commands using :mod:`subprocess`,
826 but does not harvest any output from such commands. This function
827 is a thin wrapper around :func:`subprocess.run` allowing running
828 a command in an SCons context (i.e. uses an "execution environment"
829 rather than the user's existing environment), and provides the ability
830 to return any output in a :class:`subprocess.CompletedProcess`
831 instance (this must be selected by setting ``stdout`` and/or
832 ``stderr`` to ``PIPE``, or setting ``capture_output=True`` - see
833 Keyword Arguments). Typical use case is to run a tool's "version"
834 option to find out the installed version.
836 If supplied, the ``env`` keyword argument provides an execution
837 environment to process into appropriate form before it is supplied
838 to :mod:`subprocess`; if omitted, *scons_env* is used to derive a
839 suitable default. The other keyword arguments are passed through,
840 except that the SCons legacy ``error`` keyword is remapped to the
841 subprocess ``check`` keyword; if both are omitted ``check=False``
842 will be passed. The caller is responsible for setting up the desired
843 arguments for :func:`subprocess.run`.
845 This function retains the legacy behavior of returning something
846 vaguely usable even in the face of complete failure, unless
847 ``check=True`` (in which case an error is allowed to be raised):
848 it synthesizes a :class:`~subprocess.CompletedProcess` instance in
851 A subset of interesting keyword arguments follows; see the Python
852 documentation of :mod:`subprocess` for the complete list.
855 stdout: (and *stderr*, *stdin*) if set to :const:`subprocess.PIPE`.
856 send input to or collect output from the relevant stream in
857 the subprocess; the default ``None`` does no redirection
858 (i.e. output or errors may go to the console or log file,
859 but is not captured); if set to :const:`subprocess.DEVNULL`
860 they are explicitly thrown away. ``capture_output=True`` is a
861 synonym for setting both ``stdout`` and ``stderr``
862 to :const:`~subprocess.PIPE`.
863 text: open *stdin*, *stdout*, *stderr* in text mode. Default
864 is binary mode. ``universal_newlines`` is a synonym.
865 encoding: specifies an encoding. Changes to text mode.
866 errors: specified error handling. Changes to text mode.
867 input: a byte sequence to be passed to *stdin*, unless text
868 mode is enabled, in which case it must be a string.
869 shell: if true, the command is executed through the shell.
870 check: if true and the subprocess exits with a non-zero exit
871 code, raise a :exc:`subprocess.CalledProcessError` exception.
872 Otherwise (the default) in case of an :exc:`OSError`, report the
873 exit code in the :class:`~Subprocess.CompletedProcess` instance.
875 .. versionadded:: 4.6
877 # Figure out the execution environment to use
878 env
= kwargs
.get('env', None)
880 env
= get_default_ENV(scons_env
)
881 kwargs
['env'] = SCons
.Util
.sanitize_shell_env(env
)
883 # Backwards-compat with _subproc: accept 'error', map to 'check',
884 # and remove, since subprocess.run does not recognize.
885 # 'error' isn't True/False, it takes a string value (see _subproc)
886 error
= kwargs
.get('error')
887 if error
and 'check' in kwargs
:
888 raise ValueError('error and check arguments may not both be used.')
889 check
= kwargs
.get('check', False) # always set a value for 'check'
890 if error
is not None:
894 kwargs
['check'] = check
896 # Most SCons tools/tests expect not to fail on things like missing files.
897 # check=True (or error="raise") means we're okay to take an exception;
898 # else we catch the likely exception and construct a dummy
899 # CompletedProcess instance.
900 # Note pylint can't see we always include 'check' in kwargs: suppress.
902 cp
= subprocess
.run(*args
, **kwargs
) # pylint: disable=subprocess-run-check
905 cp
= subprocess
.run(*args
, **kwargs
) # pylint: disable=subprocess-run-check
906 except OSError as exc
:
907 argline
= ' '.join(*args
)
908 cp
= subprocess
.CompletedProcess(
909 args
=argline
, returncode
=1, stdout
="", stderr
=""
915 def _subproc(scons_env
, cmd
, error
='ignore', **kw
):
916 """Wrapper for subprocess.Popen which pulls from construction env.
918 Use for calls to subprocess which need to interpolate values from
919 an SCons construction environment into the environment passed to
920 subprocess. Adds an an error-handling argument. Adds ability
921 to specify std{in,out,err} with "'devnull'" tag.
925 # TODO: just uses subprocess.DEVNULL now, we can drop the "devnull"
926 # string now - it is a holdover from Py2, which didn't have DEVNULL.
927 for stream
in 'stdin', 'stdout', 'stderr':
929 if is_String(io
) and io
== 'devnull':
932 # Figure out what execution environment to use
933 ENV
= kw
.get('env', None)
934 if ENV
is None: ENV
= get_default_ENV(scons_env
)
936 kw
['env'] = SCons
.Util
.sanitize_shell_env(ENV
)
939 pobj
= subprocess
.Popen(cmd
, **kw
)
941 if error
== 'raise': raise
942 # return a dummy Popen instance that only returns error
944 def __init__(self
, e
) -> None:
946 # Add the following two to enable using the return value as a context manager
948 # with Action._subproc(...) as po:
949 # logic here which uses po
954 def __exit__(self
, *args
) -> None:
957 def communicate(self
, input=None):
961 return -self
.exception
.errno
965 def read(self
) -> str: return ''
966 def readline(self
) -> str: return ''
967 def __iter__(self
): return iter(())
968 stdout
= stderr
= f()
971 # clean up open file handles stored in parent's kw
972 for k
, v
in kw
.items():
973 if inspect
.ismethod(getattr(v
, 'close', None)):
979 class CommandAction(_ActionAction
):
980 """Class for command-execution actions."""
981 def __init__(self
, cmd
, **kw
) -> None:
982 # Cmd can actually be a list or a single item; if it's a
983 # single item it should be the command string to execute; if a
984 # list then it should be the words of the command string to
985 # execute. Only a single command should be executed by this
986 # object; lists of commands should be handled by embedding
987 # these objects in a ListAction object (which the Action()
988 # factory above does). cmd will be passed to
989 # Environment.subst_list() for substituting environment
991 if SCons
.Debug
.track_instances
: logInstanceCreation(self
, 'Action.CommandAction')
993 super().__init
__(**kw
)
995 if [c
for c
in cmd
if is_List(c
)]:
996 raise TypeError("CommandAction should be given only "
1000 def __str__(self
) -> str:
1001 if is_List(self
.cmd_list
):
1002 return ' '.join(map(str, self
.cmd_list
))
1003 return str(self
.cmd_list
)
1006 def process(self
, target
, source
, env
, executor
: Executor |
None = None, overrides
: dict |
None = None) -> tuple[list, bool, bool]:
1008 result
= env
.subst_list(self
.cmd_list
, SUBST_CMD
, executor
=executor
, overrides
=overrides
)
1010 result
= env
.subst_list(self
.cmd_list
, SUBST_CMD
, target
, source
, overrides
=overrides
)
1014 try: c
= result
[0][0][0]
1015 except IndexError: c
= None
1016 if c
== '@': silent
= True
1017 elif c
== '-': ignore
= True
1019 result
[0][0] = result
[0][0][1:]
1021 if not result
[0][0]:
1022 result
[0] = result
[0][1:]
1025 return result
, ignore
, silent
1027 def strfunction(self
, target
, source
, env
, executor
: Executor |
None = None, overrides
: dict |
None = None) -> str:
1028 if self
.cmdstr
is None:
1030 if self
.cmdstr
is not _null
:
1032 c
= env
.subst(self
.cmdstr
, SUBST_RAW
, executor
=executor
, overrides
=overrides
)
1034 c
= env
.subst(self
.cmdstr
, SUBST_RAW
, target
, source
, overrides
=overrides
)
1037 cmd_list
, ignore
, silent
= self
.process(target
, source
, env
, executor
, overrides
=overrides
)
1040 return _string_from_cmd_list(cmd_list
[0])
1042 def execute(self
, target
, source
, env
, executor
: Executor |
None = None):
1043 """Execute a command action.
1045 This will handle lists of commands as well as individual commands,
1046 because construction variable substitution may turn a single
1047 "command" into a list. This means that this class can actually
1048 handle lists of commands, even though that's not how we use it
1051 escape_list
= SCons
.Subst
.escape_list
1052 flatten_sequence
= SCons
.Util
.flatten_sequence
1055 shell
= env
['SHELL']
1057 raise SCons
.Errors
.UserError('Missing SHELL construction variable.')
1060 spawn
= env
['SPAWN']
1062 raise SCons
.Errors
.UserError('Missing SPAWN construction variable.')
1064 if is_String(spawn
):
1065 spawn
= env
.subst(spawn
, raw
=1, conv
=lambda x
: x
)
1067 escape
= env
.get('ESCAPE', lambda x
: x
)
1068 ENV
= _resolve_shell_env(env
, target
, source
)
1070 # Ensure that the ENV values are all strings:
1071 for key
, value
in ENV
.items():
1072 if not is_String(value
):
1074 # If the value is a list, then we assume it is a
1075 # path list, because that's a pretty common list-like
1076 # value to stick in an environment variable:
1077 value
= flatten_sequence(value
)
1078 ENV
[key
] = os
.pathsep
.join(map(str, value
))
1080 # If it isn't a string or a list, then we just coerce
1081 # it to a string, which is the proper way to handle
1082 # Dir and File instances and will produce something
1083 # reasonable for just about everything else:
1084 ENV
[key
] = str(value
)
1087 target
= executor
.get_all_targets()
1088 source
= executor
.get_all_sources()
1089 cmd_list
, ignore
, silent
= self
.process(target
, list(map(rfile
, source
)), env
, executor
)
1091 # Use len() to filter out any "command" that's zero-length.
1092 for cmd_line
in filter(len, cmd_list
):
1093 # Escape the command line for the interpreter we are using.
1094 cmd_line
= escape_list(cmd_line
, escape
)
1095 result
= spawn(shell
, escape
, cmd_line
[0], cmd_line
, ENV
)
1096 if not ignore
and result
:
1097 msg
= "Error %s" % result
1098 return SCons
.Errors
.BuildError(errstr
=msg
,
1104 def get_presig(self
, target
, source
, env
, executor
: Executor |
None = None):
1105 """Return the signature contents of this action's command line.
1107 This strips $(-$) and everything in between the string,
1108 since those parts don't affect signatures.
1112 cmd
= ' '.join(map(str, cmd
))
1116 return env
.subst_target_source(cmd
, SUBST_SIG
, executor
=executor
)
1117 return env
.subst_target_source(cmd
, SUBST_SIG
, target
, source
)
1119 def get_implicit_deps(self
, target
, source
, env
, executor
: Executor |
None = None):
1120 """Return the implicit dependencies of this action's command line."""
1121 icd
= env
.get('IMPLICIT_COMMAND_DEPENDENCIES', True)
1122 if is_String(icd
) and icd
[:1] == '$':
1123 icd
= env
.subst(icd
)
1125 if not icd
or str(icd
).lower() in ('0', 'none', 'false', 'no', 'off'):
1133 if (icd_int
and icd_int
> 1) or str(icd
).lower() == 'all':
1134 # An integer value greater than 1 specifies the number of entries
1135 # to scan. "all" means to scan all.
1136 return self
._get
_implicit
_deps
_heavyweight
(target
, source
, env
, executor
, icd_int
)
1137 # Everything else (usually 1 or True) means that we want
1138 # lightweight dependency scanning.
1139 return self
._get
_implicit
_deps
_lightweight
(target
, source
, env
, executor
)
1141 def _get_implicit_deps_lightweight(self
, target
, source
, env
, executor
: Executor |
None):
1143 Lightweight dependency scanning involves only scanning the first entry
1144 in an action string, even if it contains &&.
1147 cmd_list
= env
.subst_list(self
.cmd_list
, SUBST_SIG
, executor
=executor
)
1149 cmd_list
= env
.subst_list(self
.cmd_list
, SUBST_SIG
, target
, source
)
1151 for cmd_line
in cmd_list
:
1153 d
= str(cmd_line
[0])
1154 m
= strip_quotes
.match(d
)
1159 res
.append(env
.fs
.File(d
))
1162 def _get_implicit_deps_heavyweight(self
, target
, source
, env
, executor
: Executor |
None,
1165 Heavyweight dependency scanning involves scanning more than just the
1166 first entry in an action string. The exact behavior depends on the
1167 value of icd_int. Only files are taken as implicit dependencies;
1168 directories are ignored.
1170 If icd_int is an integer value, it specifies the number of entries to
1171 scan for implicit dependencies. Action strings are also scanned after
1172 a &&. So for example, if icd_int=2 and the action string is
1173 "cd <some_dir> && $PYTHON $SCRIPT_PATH <another_path>", the implicit
1174 dependencies would be the path to the python binary and the path to the
1177 If icd_int is None, all entries are scanned for implicit dependencies.
1180 # Avoid circular and duplicate dependencies by not providing source,
1181 # target, or executor to subst_list. This causes references to
1182 # $SOURCES, $TARGETS, and all related variables to disappear.
1183 cmd_list
= env
.subst_list(self
.cmd_list
, SUBST_SIG
, conv
=lambda x
: x
)
1186 for cmd_line
in cmd_list
:
1189 for entry
in cmd_line
:
1191 if ((icd_int
is None or entry_count
< icd_int
) and
1192 not d
.startswith(('&', '-', '/') if os
.name
== 'nt'
1194 m
= strip_quotes
.match(d
)
1199 # Resolve the first entry in the command string using
1200 # PATH, which env.WhereIs() looks in.
1201 # For now, only match files, not directories.
1202 p
= os
.path
.abspath(d
) if os
.path
.isfile(d
) else None
1203 if not p
and entry_count
== 0:
1207 res
.append(env
.fs
.File(p
))
1209 entry_count
= entry_count
+ 1
1211 entry_count
= 0 if d
== '&&' else entry_count
+ 1
1213 # Despite not providing source and target to env.subst() above, we
1214 # can still end up with sources in this list. For example, files in
1215 # LIBS will still resolve in env.subst(). This won't result in
1216 # circular dependencies, but it causes problems with cache signatures
1217 # changing between full and incremental builds.
1218 return [r
for r
in res
if r
not in target
and r
not in source
]
1221 class CommandGeneratorAction(ActionBase
):
1222 """Class for command-generator actions."""
1223 def __init__(self
, generator
, kw
) -> None:
1224 if SCons
.Debug
.track_instances
: logInstanceCreation(self
, 'Action.CommandGeneratorAction')
1225 self
.generator
= generator
1227 self
.varlist
= kw
.get('varlist', ())
1228 self
.targets
= kw
.get('targets', '$TARGETS')
1230 def _generate(self
, target
, source
, env
, for_signature
, executor
: Executor |
None = None):
1231 # ensure that target is a list, to make it easier to write
1232 # generator functions:
1233 if not is_List(target
):
1237 target
= executor
.get_all_targets()
1238 source
= executor
.get_all_sources()
1239 ret
= self
.generator(target
=target
,
1242 for_signature
=for_signature
)
1243 gen_cmd
= Action(ret
, **self
.gen_kw
)
1245 raise SCons
.Errors
.UserError("Object returned from command generator: %s cannot be used to create an Action." % repr(ret
))
1248 def __str__(self
) -> str:
1250 env
= self
.presub_env
1251 except AttributeError:
1254 env
= SCons
.Defaults
.DefaultEnvironment()
1255 act
= self
._generate
([], [], env
, 1)
1258 def batch_key(self
, env
, target
, source
):
1259 return self
._generate
(target
, source
, env
, 1).batch_key(env
, target
, source
)
1261 def genstring(self
, target
, source
, env
, executor
: Executor |
None = None) -> str:
1262 return self
._generate
(target
, source
, env
, 1, executor
).genstring(target
, source
, env
)
1264 def __call__(self
, target
, source
, env
, exitstatfunc
=_null
, presub
=_null
,
1265 show
=_null
, execute
=_null
, chdir
=_null
, executor
: Executor |
None = None):
1266 act
= self
._generate
(target
, source
, env
, 0, executor
)
1268 raise SCons
.Errors
.UserError(
1269 "While building `%s': "
1270 "Cannot deduce file extension from source files: %s"
1271 % (repr(list(map(str, target
))), repr(list(map(str, source
))))
1274 target
, source
, env
, exitstatfunc
, presub
, show
, execute
, chdir
, executor
1277 def get_presig(self
, target
, source
, env
, executor
: Executor |
None = None):
1278 """Return the signature contents of this action's command line.
1280 This strips $(-$) and everything in between the string,
1281 since those parts don't affect signatures.
1283 return self
._generate
(target
, source
, env
, 1, executor
).get_presig(target
, source
, env
)
1285 def get_implicit_deps(self
, target
, source
, env
, executor
: Executor |
None = None):
1286 return self
._generate
(target
, source
, env
, 1, executor
).get_implicit_deps(target
, source
, env
)
1288 def get_varlist(self
, target
, source
, env
, executor
: Executor |
None = None):
1289 return self
._generate
(target
, source
, env
, 1, executor
).get_varlist(target
, source
, env
, executor
)
1291 def get_targets(self
, env
, executor
: Executor |
None):
1292 return self
._generate
(None, None, env
, 1, executor
).get_targets(env
, executor
)
1295 class LazyAction(CommandGeneratorAction
, CommandAction
):
1297 A LazyAction is a kind of hybrid generator and command action for
1298 strings of the form "$VAR". These strings normally expand to other
1299 strings (think "$CCCOM" to "$CC -c -o $TARGET $SOURCE"), but we also
1300 want to be able to replace them with functions in the construction
1301 environment. Consequently, we want lazy evaluation and creation of
1302 an Action in the case of the function, but that's overkill in the more
1303 normal case of expansion to other strings.
1305 So we do this with a subclass that's both a generator *and*
1306 a command action. The overridden methods all do a quick check
1307 of the construction variable, and if it's a string we just call
1308 the corresponding CommandAction method to do the heavy lifting.
1309 If not, then we call the same-named CommandGeneratorAction method.
1310 The CommandGeneratorAction methods work by using the overridden
1311 _generate() method, that is, our own way of handling "generation" of
1312 an action based on what's in the construction variable.
1315 def __init__(self
, var
, kw
) -> None:
1316 if SCons
.Debug
.track_instances
: logInstanceCreation(self
, 'Action.LazyAction')
1317 CommandAction
.__init
__(self
, '${'+var
+'}', **kw
)
1318 self
.var
= SCons
.Util
.to_String(var
)
1321 def get_parent_class(self
, env
):
1322 c
= env
.get(self
.var
)
1323 if is_String(c
) and '\n' not in c
:
1324 return CommandAction
1325 return CommandGeneratorAction
1327 def _generate_cache(self
, env
):
1329 c
= env
.get(self
.var
, '')
1332 gen_cmd
= Action(c
, **self
.gen_kw
)
1334 raise SCons
.Errors
.UserError("$%s value %s cannot be used to create an Action." % (self
.var
, repr(c
)))
1337 def _generate(self
, target
, source
, env
, for_signature
, executor
: Executor |
None = None):
1338 return self
._generate
_cache
(env
)
1340 def __call__(self
, target
, source
, env
, *args
, **kw
):
1341 c
= self
.get_parent_class(env
)
1342 return c
.__call
__(self
, target
, source
, env
, *args
, **kw
)
1344 def get_presig(self
, target
, source
, env
, executor
: Executor |
None = None):
1345 c
= self
.get_parent_class(env
)
1346 return c
.get_presig(self
, target
, source
, env
)
1348 def get_implicit_deps(self
, target
, source
, env
, executor
: Executor |
None = None):
1349 c
= self
.get_parent_class(env
)
1350 return c
.get_implicit_deps(self
, target
, source
, env
)
1352 def get_varlist(self
, target
, source
, env
, executor
: Executor |
None = None):
1353 c
= self
.get_parent_class(env
)
1354 return c
.get_varlist(self
, target
, source
, env
, executor
)
1357 class FunctionAction(_ActionAction
):
1358 """Class for Python function actions."""
1360 def __init__(self
, execfunction
, kw
) -> None:
1361 if SCons
.Debug
.track_instances
: logInstanceCreation(self
, 'Action.FunctionAction')
1363 self
.execfunction
= execfunction
1365 self
.funccontents
= _callable_contents(execfunction
)
1366 except AttributeError:
1368 # See if execfunction will do the heavy lifting for us.
1369 self
.gc
= execfunction
.get_contents
1370 except AttributeError:
1371 # This is weird, just do the best we can.
1372 self
.funccontents
= _object_contents(execfunction
)
1374 super().__init
__(**kw
)
1376 def function_name(self
):
1378 return self
.execfunction
.__name
__
1379 except AttributeError:
1381 return self
.execfunction
.__class
__.__name
__
1382 except AttributeError:
1383 return "unknown_python_function"
1385 def strfunction(self
, target
, source
, env
, executor
: Executor |
None = None):
1386 if self
.cmdstr
is None:
1388 if self
.cmdstr
is not _null
:
1390 c
= env
.subst(self
.cmdstr
, SUBST_RAW
, executor
=executor
)
1392 c
= env
.subst(self
.cmdstr
, SUBST_RAW
, target
, source
)
1399 str_for_display
= s
.str_for_display
1400 except AttributeError:
1403 s
= str_for_display()
1405 return '[' + ", ".join(map(quote
, a
)) + ']'
1407 strfunc
= self
.execfunction
.strfunction
1408 except AttributeError:
1413 if callable(strfunc
):
1414 return strfunc(target
, source
, env
)
1415 name
= self
.function_name()
1416 tstr
= array(target
)
1417 sstr
= array(source
)
1418 return "%s(%s, %s)" % (name
, tstr
, sstr
)
1420 def __str__(self
) -> str:
1421 name
= self
.function_name()
1422 if name
== 'ActionCaller':
1423 return str(self
.execfunction
)
1424 return "%s(target, source, env)" % name
1426 def execute(self
, target
, source
, env
, executor
: Executor |
None = None):
1427 exc_info
= (None,None,None)
1430 target
= executor
.get_all_targets()
1431 source
= executor
.get_all_sources()
1432 rsources
= list(map(rfile
, source
))
1434 result
= self
.execfunction(target
=target
, source
=rsources
, env
=env
)
1435 except (KeyboardInterrupt, SystemExit):
1437 except Exception as e
:
1439 exc_info
= sys
.exc_info()
1442 result
= SCons
.Errors
.convert_to_BuildError(result
, exc_info
)
1443 result
.node
= target
1444 result
.action
= self
1446 result
.command
=self
.strfunction(target
, source
, env
, executor
)
1448 result
.command
=self
.strfunction(target
, source
, env
)
1452 # Break the cycle between the traceback object and this
1453 # function stack frame. See the sys.exc_info() doc info for
1454 # more information about this issue.
1457 def get_presig(self
, target
, source
, env
, executor
: Executor |
None = None):
1458 """Return the signature contents of this callable action."""
1460 return self
.gc(target
, source
, env
)
1461 except AttributeError:
1462 return self
.funccontents
1464 def get_implicit_deps(self
, target
, source
, env
, executor
: Executor |
None = None):
1467 class ListAction(ActionBase
):
1468 """Class for lists of other actions."""
1469 def __init__(self
, actionlist
) -> None:
1470 if SCons
.Debug
.track_instances
: logInstanceCreation(self
, 'Action.ListAction')
1471 def list_of_actions(x
):
1472 if isinstance(x
, ActionBase
):
1475 self
.list = list(map(list_of_actions
, actionlist
))
1476 # our children will have had any varlist
1477 # applied; we don't need to do it again
1479 self
.targets
= '$TARGETS'
1481 def genstring(self
, target
, source
, env
, executor
: Executor |
None = None) -> str:
1482 return '\n'.join([a
.genstring(target
, source
, env
) for a
in self
.list])
1484 def __str__(self
) -> str:
1485 return '\n'.join(map(str, self
.list))
1487 def presub_lines(self
, env
):
1488 return SCons
.Util
.flatten_sequence(
1489 [a
.presub_lines(env
) for a
in self
.list])
1491 def get_presig(self
, target
, source
, env
, executor
: Executor |
None = None):
1492 """Return the signature contents of this action list.
1494 Simple concatenation of the signatures of the elements.
1496 return b
"".join([bytes(x
.get_contents(target
, source
, env
)) for x
in self
.list])
1498 def __call__(self
, target
, source
, env
, exitstatfunc
=_null
, presub
=_null
,
1499 show
=_null
, execute
=_null
, chdir
=_null
, executor
: Executor |
None = None):
1501 target
= executor
.get_all_targets()
1502 source
= executor
.get_all_sources()
1503 for act
in self
.list:
1504 stat
= act(target
, source
, env
, exitstatfunc
, presub
,
1505 show
, execute
, chdir
, executor
)
1510 def get_implicit_deps(self
, target
, source
, env
, executor
: Executor |
None = None):
1512 for act
in self
.list:
1513 result
.extend(act
.get_implicit_deps(target
, source
, env
))
1516 def get_varlist(self
, target
, source
, env
, executor
: Executor |
None = None):
1517 result
= OrderedDict()
1518 for act
in self
.list:
1519 for var
in act
.get_varlist(target
, source
, env
, executor
):
1521 return list(result
.keys())
1525 """A class for delaying calling an Action function with specific
1526 (positional and keyword) arguments until the Action is actually
1529 This class looks to the rest of the world like a normal Action object,
1530 but what it's really doing is hanging on to the arguments until we
1531 have a target, source and env to use for the expansion.
1533 def __init__(self
, parent
, args
, kw
) -> None:
1534 self
.parent
= parent
1538 def get_contents(self
, target
, source
, env
):
1539 actfunc
= self
.parent
.actfunc
1541 # "self.actfunc" is a function.
1542 contents
= actfunc
.__code
__.co_code
1543 except AttributeError:
1544 # "self.actfunc" is a callable object.
1546 contents
= actfunc
.__call
__.__func
__.__code
__.co_code
1547 except AttributeError:
1548 # No __call__() method, so it might be a builtin
1549 # or something like that. Do the best we can.
1550 contents
= repr(actfunc
)
1554 def subst(self
, s
, target
, source
, env
):
1555 # If s is a list, recursively apply subst()
1556 # to every element in the list
1560 result
.append(self
.subst(elem
, target
, source
, env
))
1561 return self
.parent
.convert(result
)
1563 # Special-case hack: Let a custom function wrapped in an
1564 # ActionCaller get at the environment through which the action
1565 # was called by using this hard-coded value as a special return.
1569 return env
.subst(s
, 1, target
, source
)
1571 return self
.parent
.convert(s
)
1573 def subst_args(self
, target
, source
, env
):
1574 return [self
.subst(x
, target
, source
, env
) for x
in self
.args
]
1576 def subst_kw(self
, target
, source
, env
):
1578 for key
in list(self
.kw
.keys()):
1579 kw
[key
] = self
.subst(self
.kw
[key
], target
, source
, env
)
1582 def __call__(self
, target
, source
, env
, executor
: Executor |
None = None):
1583 args
= self
.subst_args(target
, source
, env
)
1584 kw
= self
.subst_kw(target
, source
, env
)
1585 return self
.parent
.actfunc(*args
, **kw
)
1587 def strfunction(self
, target
, source
, env
):
1588 args
= self
.subst_args(target
, source
, env
)
1589 kw
= self
.subst_kw(target
, source
, env
)
1590 return self
.parent
.strfunc(*args
, **kw
)
1592 def __str__(self
) -> str:
1593 return self
.parent
.strfunc(*self
.args
, **self
.kw
)
1596 class ActionFactory
:
1597 """A factory class that will wrap up an arbitrary function
1598 as an SCons-executable Action object.
1600 The real heavy lifting here is done by the ActionCaller class.
1601 We just collect the (positional and keyword) arguments that we're
1602 called with and give them to the ActionCaller object we create,
1603 so it can hang onto them until it needs them.
1605 def __init__(self
, actfunc
, strfunc
, convert
=lambda x
: x
) -> None:
1606 self
.actfunc
= actfunc
1607 self
.strfunc
= strfunc
1608 self
.convert
= convert
1610 def __call__(self
, *args
, **kw
):
1611 ac
= ActionCaller(self
, args
, kw
)
1612 action
= Action(ac
, strfunction
=ac
.strfunction
)
1617 # indent-tabs-mode:nil
1619 # vim: set expandtab tabstop=4 shiftwidth=4: