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.
109 from abc
import ABC
, abstractmethod
110 from collections
import OrderedDict
111 from subprocess
import DEVNULL
, PIPE
112 from typing
import List
, Optional
, Tuple
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
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
141 except AttributeError:
145 def default_exitstatfunc(s
):
148 strip_quotes
= re
.compile(r
'^[\'"](.*)[\'"]$
')
151 def _callable_contents(obj) -> bytearray:
152 """Return the signature contents of a callable Python object."""
154 # Test if obj is a method.
155 return _function_contents(obj.__func__)
157 except AttributeError:
159 # Test if obj is a callable object.
160 return _function_contents(obj.__call__.__func__)
162 except AttributeError:
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.
179 # Test if obj is a method.
180 return _function_contents(obj.__func__)
182 except AttributeError:
184 # Test if obj is a callable object.
185 return _function_contents(obj.__call__.__func__)
187 except AttributeError:
189 # Test if obj is a code object.
190 return _code_contents(obj)
192 except AttributeError:
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.
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
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.
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.
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
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
')')
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
)
311 contents
.append(b
',()')
313 # The function contents depends on the closure captured cell values.
314 closure
= func
.__closure
__ or []
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
)
329 def _object_instance_content(obj
):
331 Returns consistant content for a action class or an instance thereof
334 - `obj` Should be either and action class or an instance thereof
337 bytearray or bytes representing the obj suitable for generating a signature from.
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
))) ]
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
))]
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
)
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
'}}}')
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.
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
)
419 # turn positional args into equivalent keywords
421 if cmdstrfunc
is None or is_String(cmdstrfunc
):
422 kw
['cmdstr'] = cmdstrfunc
423 elif callable(cmdstrfunc
):
424 kw
['strfunction'] = cmdstrfunc
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.')
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
):
450 var
= SCons
.Util
.get_environment_var(act
)
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
)
467 return CommandAction(act
, **kw
)
470 gen
= kw
.pop('generator', False)
472 action_type
= CommandGeneratorAction
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 (???)
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
497 aa
= _do_create_action(a
, kw
)
501 return ListAction([])
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
)
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."""
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
):
542 batch_key
= no_batch_key
544 def genstring(self
, target
, source
, env
, executor
: Optional
[ExecutorType
] = None) -> str:
548 def get_presig(self
, target
, source
, env
, executor
: Optional
[ExecutorType
] = None):
549 raise NotImplementedError
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')
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
,)
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
)))
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
)):
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
604 def get_varlist(self
, target
, source
, env
, executor
: Optional
[ExecutorType
] = None):
607 def get_targets(self
, env
, executor
: Optional
[ExecutorType
]):
609 Returns the type of targets ($TARGETS, $CHANGED_TARGETS) used
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',
622 if strfunction
is not _null
:
623 if strfunction
is None:
626 self
.strfunction
= strfunction
627 self
.varlist
= varlist
631 exitstatfunc
= default_exitstatfunc
632 self
.exitstatfunc
= exitstatfunc
634 self
.targets
= targets
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
,
661 executor
: Optional
[ExecutorType
] = None):
662 if not is_List(target
):
664 if not is_List(source
):
670 presub
= print_actions_presub
671 if exitstatfunc
is _null
:
672 exitstatfunc
= self
.exitstatfunc
676 execute
= execute_actions
681 save_cwd
= os
.getcwd()
683 chdir
= str(chdir
.get_abspath())
684 except AttributeError:
685 if not is_String(chdir
):
687 chdir
= str(executor
.batches
[0].targets
[0].dir)
689 chdir
= str(target
[0].dir)
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
)
699 if show
and self
.strfunction
:
701 target
= executor
.get_all_targets()
702 source
= executor
.get_all_sources()
704 cmd
= self
.strfunction(target
, source
, env
, executor
)
706 cmd
= self
.strfunction(target
, source
, env
)
709 cmd
= ('os.chdir(%s)\n' % repr(chdir
)) + cmd
712 except AttributeError:
713 print_func
= self
.print_cmd_line
715 print_func
= get('PRINT_CMD_LINE_FUNC')
717 print_func
= self
.print_cmd_line
718 print_func(cmd
, target
, source
, env
)
724 stat
= self
.execute(target
, source
, env
, executor
=executor
)
725 if isinstance(stat
, SCons
.Errors
.BuildError
):
726 s
= exitstatfunc(stat
.status
)
732 stat
= exitstatfunc(stat
)
737 print_func('os.chdir(%s)' % repr(save_cwd
), target
, source
, env
)
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."""
756 for arg
in map(str, cmd_list
):
757 if ' ' in arg
or '\t' in arg
:
758 arg
= '"' + arg
+ '"'
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.
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']
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')
807 shell_gens
= iter(shell_gen
)
809 raise SCons
.Errors
.UserError("SHELL_ENV_GENERATORS must be iteratable.")
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.")
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
849 A subset of interesting keyword arguments follows; see the Python
850 documentation of :mod:`subprocess` for the complete list.
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)
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:
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')
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.
909 cp
= subprocess
.run(*args
, **kwargs
) # pylint: disable=subprocess-run-check
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
=""
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.
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':
936 if is_String(io
) and io
== '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
)
946 pobj
= subprocess
.Popen(cmd
, **kw
)
948 if error
== 'raise': raise
949 # return a dummy Popen instance that only returns error
951 def __init__(self
, e
) -> None:
953 # Add the following two to enable using the return value as a context manager
955 # with Action._subproc(...) as po:
956 # logic here which uses po
961 def __exit__(self
, *args
) -> None:
964 def communicate(self
, input=None):
968 return -self
.exception
.errno
972 def read(self
) -> str: return ''
973 def readline(self
) -> str: return ''
974 def __iter__(self
): return iter(())
975 stdout
= stderr
= f()
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)):
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
998 if SCons
.Debug
.track_instances
: logInstanceCreation(self
, 'Action.CommandAction')
1000 super().__init
__(**kw
)
1002 if [c
for c
in cmd
if is_List(c
)]:
1003 raise TypeError("CommandAction should be given only "
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]:
1015 result
= env
.subst_list(self
.cmd_list
, SUBST_CMD
, executor
=executor
, overrides
=overrides
)
1017 result
= env
.subst_list(self
.cmd_list
, SUBST_CMD
, target
, source
, overrides
=overrides
)
1021 try: c
= result
[0][0][0]
1022 except IndexError: c
= None
1023 if c
== '@': silent
= True
1024 elif c
== '-': ignore
= True
1026 result
[0][0] = result
[0][0][1:]
1028 if not result
[0][0]:
1029 result
[0] = result
[0][1:]
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:
1037 if self
.cmdstr
is not _null
:
1039 c
= env
.subst(self
.cmdstr
, SUBST_RAW
, executor
=executor
, overrides
=overrides
)
1041 c
= env
.subst(self
.cmdstr
, SUBST_RAW
, target
, source
, overrides
=overrides
)
1044 cmd_list
, ignore
, silent
= self
.process(target
, source
, env
, executor
, overrides
=overrides
)
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
1058 escape_list
= SCons
.Subst
.escape_list
1059 flatten_sequence
= SCons
.Util
.flatten_sequence
1062 shell
= env
['SHELL']
1064 raise SCons
.Errors
.UserError('Missing SHELL construction variable.')
1067 spawn
= env
['SPAWN']
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
):
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
))
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
)
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
,
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.
1119 cmd
= ' '.join(map(str, cmd
))
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'):
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 &&.
1154 cmd_list
= env
.subst_list(self
.cmd_list
, SUBST_SIG
, executor
=executor
)
1156 cmd_list
= env
.subst_list(self
.cmd_list
, SUBST_SIG
, target
, source
)
1158 for cmd_line
in cmd_list
:
1160 d
= str(cmd_line
[0])
1161 m
= strip_quotes
.match(d
)
1166 res
.append(env
.fs
.File(d
))
1169 def _get_implicit_deps_heavyweight(self
, target
, source
, env
, executor
: Optional
[ExecutorType
],
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
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
)
1193 for cmd_line
in cmd_list
:
1196 for entry
in cmd_line
:
1198 if ((icd_int
is None or entry_count
< icd_int
) and
1199 not d
.startswith(('&', '-', '/') if os
.name
== 'nt'
1201 m
= strip_quotes
.match(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:
1214 res
.append(env
.fs
.File(p
))
1216 entry_count
= entry_count
+ 1
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
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
):
1244 target
= executor
.get_all_targets()
1245 source
= executor
.get_all_sources()
1246 ret
= self
.generator(target
=target
,
1249 for_signature
=for_signature
)
1250 gen_cmd
= Action(ret
, **self
.gen_kw
)
1252 raise SCons
.Errors
.UserError("Object returned from command generator: %s cannot be used to create an Action." % repr(ret
))
1255 def __str__(self
) -> str:
1257 env
= self
.presub_env
1258 except AttributeError:
1261 env
= SCons
.Defaults
.DefaultEnvironment()
1262 act
= self
._generate
([], [], env
, 1)
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
)
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
))))
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
)
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
):
1336 c
= env
.get(self
.var
, '')
1339 gen_cmd
= Action(c
, **self
.gen_kw
)
1341 raise SCons
.Errors
.UserError("$%s value %s cannot be used to create an Action." % (self
.var
, repr(c
)))
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
1372 self
.funccontents
= _callable_contents(execfunction
)
1373 except AttributeError:
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
):
1385 return self
.execfunction
.__name
__
1386 except AttributeError:
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:
1395 if self
.cmdstr
is not _null
:
1397 c
= env
.subst(self
.cmdstr
, SUBST_RAW
, executor
=executor
)
1399 c
= env
.subst(self
.cmdstr
, SUBST_RAW
, target
, source
)
1406 str_for_display
= s
.str_for_display
1407 except AttributeError:
1410 s
= str_for_display()
1412 return '[' + ", ".join(map(quote
, a
)) + ']'
1414 strfunc
= self
.execfunction
.strfunction
1415 except AttributeError:
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)
1437 target
= executor
.get_all_targets()
1438 source
= executor
.get_all_sources()
1439 rsources
= list(map(rfile
, source
))
1441 result
= self
.execfunction(target
=target
, source
=rsources
, env
=env
)
1442 except (KeyboardInterrupt, SystemExit):
1444 except Exception as e
:
1446 exc_info
= sys
.exc_info()
1449 result
= SCons
.Errors
.convert_to_BuildError(result
, exc_info
)
1450 result
.node
= target
1451 result
.action
= self
1453 result
.command
=self
.strfunction(target
, source
, env
, executor
)
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.
1464 not isinstance(exc_info
[1], EnvironmentError)):
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.
1474 def get_presig(self
, target
, source
, env
, executor
: Optional
[ExecutorType
] = None):
1475 """Return the signature contents of this callable action."""
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):
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
):
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
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):
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
)
1527 def get_implicit_deps(self
, target
, source
, env
, executor
: Optional
[ExecutorType
] = None):
1529 for act
in self
.list:
1530 result
.extend(act
.get_implicit_deps(target
, source
, env
))
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
):
1538 return list(result
.keys())
1542 """A class for delaying calling an Action function with specific
1543 (positional and keyword) arguments until the Action is actually
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
1555 def get_contents(self
, target
, source
, env
):
1556 actfunc
= self
.parent
.actfunc
1558 # "self.actfunc" is a function.
1559 contents
= actfunc
.__code
__.co_code
1560 except AttributeError:
1561 # "self.actfunc" is a callable object.
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
)
1571 def subst(self
, s
, target
, source
, env
):
1572 # If s is a list, recursively apply subst()
1573 # to every element in the list
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.
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
):
1595 for key
in list(self
.kw
.keys()):
1596 kw
[key
] = self
.subst(self
.kw
[key
], target
, source
, env
)
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
)
1634 # indent-tabs-mode:nil
1636 # vim: set expandtab tabstop=4 shiftwidth=4: