Merge pull request #4642 from Repiteo/remove-python-3.6
[scons.git] / SCons / Action.py
blob18a488a78da2664a61135815b291199efeb392ab
1 # MIT License
3 # Copyright The SCons Foundation
5 # Permission is hereby granted, free of charge, to any person obtaining
6 # a copy of this software and associated documentation files (the
7 # "Software"), to deal in the Software without restriction, including
8 # without limitation the rights to use, copy, modify, merge, publish,
9 # distribute, sublicense, and/or sell copies of the Software, and to
10 # permit persons to whom the Software is furnished to do so, subject to
11 # the following conditions:
13 # The above copyright notice and this permission notice shall be included
14 # in all copies or substantial portions of the Software.
16 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
17 # KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
18 # WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19 # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20 # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21 # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22 # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
24 """SCons Actions.
26 Information about executing any sort of action that
27 can build one or more target Nodes (typically files) from one or more
28 source Nodes (also typically files) given a specific Environment.
30 The base class here is ActionBase. The base class supplies just a few
31 utility methods and some generic methods for displaying information
32 about an Action in response to the various commands that control printing.
34 A second-level base class is _ActionAction. This extends ActionBase
35 by providing the methods that can be used to show and perform an
36 action. True Action objects will subclass _ActionAction; Action
37 factory class objects will subclass ActionBase.
39 The heavy lifting is handled by subclasses for the different types of
40 actions we might execute:
42 CommandAction
43 CommandGeneratorAction
44 FunctionAction
45 ListAction
47 The subclasses supply the following public interface methods used by
48 other modules:
50 __call__()
51 THE public interface, "calling" an Action object executes the
52 command or Python function. This also takes care of printing
53 a pre-substitution command for debugging purposes.
55 get_contents()
56 Fetches the "contents" of an Action for signature calculation
57 plus the varlist. This is what gets checksummed to decide
58 if a target needs to be rebuilt because its action changed.
60 genstring()
61 Returns a string representation of the Action *without*
62 command substitution, but allows a CommandGeneratorAction to
63 generate the right action based on the specified target,
64 source and env. This is used by the Signature subsystem
65 (through the Executor) to obtain an (imprecise) representation
66 of the Action operation for informative purposes.
69 Subclasses also supply the following methods for internal use within
70 this module:
72 __str__()
73 Returns a string approximation of the Action; no variable
74 substitution is performed.
76 execute()
77 The internal method that really, truly, actually handles the
78 execution of a command or Python function. This is used so
79 that the __call__() methods can take care of displaying any
80 pre-substitution representations, and *then* execute an action
81 without worrying about the specific Actions involved.
83 get_presig()
84 Fetches the "contents" of a subclass for signature calculation.
85 The varlist is added to this to produce the Action's contents.
86 TODO(?): Change this to always return bytes and not str?
88 strfunction()
89 Returns a substituted string representation of the Action.
90 This is used by the _ActionAction.show() command to display the
91 command/function that will be executed to generate the target(s).
93 There is a related independent ActionCaller class that looks like a
94 regular Action, and which serves as a wrapper for arbitrary functions
95 that we want to let the user specify the arguments to now, but actually
96 execute later (when an out-of-date check determines that it's needed to
97 be executed, for example). Objects of this class are returned by an
98 ActionFactory class that provides a __call__() method as a convenient
99 way for wrapping up the functions.
103 from __future__ import annotations
105 import inspect
106 import os
107 import pickle
108 import re
109 import subprocess
110 import sys
111 from abc import ABC, abstractmethod
112 from collections import OrderedDict
113 from subprocess import DEVNULL, PIPE
114 from typing import TYPE_CHECKING
116 import SCons.Debug
117 import SCons.Errors
118 import SCons.Subst
119 import SCons.Util
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
126 if TYPE_CHECKING:
127 from SCons.Executor import Executor
129 class _null:
130 pass
132 print_actions = True
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
142 def rfile(n):
143 try:
144 return n.rfile()
145 except AttributeError:
146 return n
149 def default_exitstatfunc(s):
150 return s
152 strip_quotes = re.compile(r'^[\'"](.*)[\'"]$')
155 def _callable_contents(obj) -> bytearray:
156 """Return the signature contents of a callable Python object."""
157 try:
158 # Test if obj is a method.
159 return _function_contents(obj.__func__)
161 except AttributeError:
162 try:
163 # Test if obj is a callable object.
164 return _function_contents(obj.__call__.__func__)
166 except AttributeError:
167 try:
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.
182 try:
183 # Test if obj is a method.
184 return _function_contents(obj.__func__)
186 except AttributeError:
187 try:
188 # Test if obj is a callable object.
189 return _function_contents(obj.__call__.__func__)
191 except AttributeError:
192 try:
193 # Test if obj is a code object.
194 return _code_contents(obj)
196 except AttributeError:
197 try:
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.
203 try:
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
213 # the best we can.
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.
230 See:
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.
251 # contents = []
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
274 # function result.
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')')
285 return contents
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)
314 else:
315 contents.append(b',()')
317 # The function contents depends on the closure captured cell values.
318 closure = func.__closure__ or []
320 try:
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)
330 return retval
333 def _object_instance_content(obj):
335 Returns consistant content for a action class or an instance thereof
337 :Parameters:
338 - `obj` Should be either and action class or an instance thereof
340 :Returns:
341 bytearray or bytes representing the obj suitable for generating a signature from.
343 retval = bytearray()
345 if obj is None:
346 return b'N.'
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))) ]
358 properties.sort()
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))]
363 methods.sort()
365 method_contents = []
366 for m in methods:
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)
374 retval.extend(b":")
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'}}}')
383 return retval
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.
397 a1 = Action(act1)
398 a2 = Action(act2)
399 if a1 is None:
400 return a2
401 if a2 is None:
402 return a1
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)
422 if args:
423 # turn positional args into equivalent keywords
424 cmdstrfunc = args[0]
425 if cmdstrfunc is None or is_String(cmdstrfunc):
426 kw['cmdstr'] = cmdstrfunc
427 elif callable(cmdstrfunc):
428 kw['strfunction'] = cmdstrfunc
429 else:
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.')
434 if len(args) > 1:
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):
451 return act
453 if is_String(act):
454 var = SCons.Util.get_environment_var(act)
455 if var:
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)
470 if is_List(act):
471 return CommandAction(act, **kw)
473 if callable(act):
474 gen = kw.pop('generator', False)
475 if gen:
476 action_type = CommandGeneratorAction
477 else:
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 (???)
485 return None
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
495 list action.
497 acts = []
498 for a in act:
499 aa = _do_create_action(a, kw)
500 if aa is not None:
501 acts.append(aa)
502 if not acts:
503 return ListAction([])
504 if len(acts) == 1:
505 return acts[0]
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)
513 if is_List(act):
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."""
523 @abstractmethod
524 def __call__(
525 self,
526 target,
527 source,
528 env,
529 exitstatfunc=_null,
530 presub=_null,
531 show=_null,
532 execute=_null,
533 chdir=_null,
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):
542 return None
544 batch_key = no_batch_key
546 def genstring(self, target, source, env, executor: Executor | None = None) -> str:
547 return str(self)
549 @abstractmethod
550 def get_presig(self, target, source, env, executor: Executor | None = None):
551 raise NotImplementedError
553 @abstractmethod
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')
562 else:
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,)
575 for v in 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)))
579 else:
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)):
584 return result
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
604 return lines
606 def get_varlist(self, target, source, env, executor: Executor | None = None):
607 return self.varlist
609 def get_targets(self, env, executor: Executor | None):
611 Returns the type of targets ($TARGETS, $CHANGED_TARGETS) used
612 by this action.
614 return self.targets
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',
622 **kw) -> None:
623 self.cmdstr = cmdstr
624 if strfunction is not _null:
625 if strfunction is None:
626 self.cmdstr = None
627 else:
628 self.strfunction = strfunction
629 self.varlist = varlist
630 self.presub = presub
631 self.chdir = chdir
632 if not exitstatfunc:
633 exitstatfunc = default_exitstatfunc
634 self.exitstatfunc = exitstatfunc
636 self.targets = targets
638 if batch_key:
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,
658 exitstatfunc=_null,
659 presub=_null,
660 show=_null,
661 execute=_null,
662 chdir=_null,
663 executor: Executor | None = None):
664 if not is_List(target):
665 target = [target]
666 if not is_List(source):
667 source = [source]
669 if presub is _null:
670 presub = self.presub
671 if presub is _null:
672 presub = print_actions_presub
673 if exitstatfunc is _null:
674 exitstatfunc = self.exitstatfunc
675 if show is _null:
676 show = print_actions
677 if execute is _null:
678 execute = execute_actions
679 if chdir is _null:
680 chdir = self.chdir
681 save_cwd = None
682 if chdir:
683 save_cwd = os.getcwd()
684 try:
685 chdir = str(chdir.get_abspath())
686 except AttributeError:
687 if not is_String(chdir):
688 if executor:
689 chdir = str(executor.batches[0].targets[0].dir)
690 else:
691 chdir = str(target[0].dir)
692 if presub:
693 if executor:
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)
700 cmd = None
701 if show and self.strfunction:
702 if executor:
703 target = executor.get_all_targets()
704 source = executor.get_all_sources()
705 try:
706 cmd = self.strfunction(target, source, env, executor)
707 except TypeError:
708 cmd = self.strfunction(target, source, env)
709 if cmd:
710 if chdir:
711 cmd = ('os.chdir(%s)\n' % repr(chdir)) + cmd
712 try:
713 get = env.get
714 except AttributeError:
715 print_func = self.print_cmd_line
716 else:
717 print_func = get('PRINT_CMD_LINE_FUNC')
718 if not print_func:
719 print_func = self.print_cmd_line
720 print_func(cmd, target, source, env)
721 stat = 0
722 if execute:
723 if chdir:
724 os.chdir(chdir)
725 try:
726 stat = self.execute(target, source, env, executor=executor)
727 if isinstance(stat, SCons.Errors.BuildError):
728 s = exitstatfunc(stat.status)
729 if s:
730 stat.status = s
731 else:
732 stat = s
733 else:
734 stat = exitstatfunc(stat)
735 finally:
736 if save_cwd:
737 os.chdir(save_cwd)
738 if cmd and save_cwd:
739 print_func('os.chdir(%s)' % repr(save_cwd), target, source, env)
741 return stat
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."""
757 cl = []
758 for arg in map(str, cmd_list):
759 if ' ' in arg or '\t' in arg:
760 arg = '"' + arg + '"'
761 cl.append(arg)
762 return ' '.join(cl)
764 default_ENV = None
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.
779 global default_ENV
781 try:
782 return env['ENV']
783 except KeyError:
784 if not default_ENV:
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']
792 return default_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')
807 if shell_gen:
808 try:
809 shell_gens = iter(shell_gen)
810 except TypeError:
811 raise SCons.Errors.UserError("SHELL_ENV_GENERATORS must be iteratable.")
813 ENV = ENV.copy()
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.")
819 return ENV
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
849 this case.
851 A subset of interesting keyword arguments follows; see the Python
852 documentation of :mod:`subprocess` for the complete list.
854 Keyword Arguments:
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)
879 if env is 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:
891 if error == 'raise':
892 check = True
893 del kwargs['error']
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.
901 if check:
902 cp = subprocess.run(*args, **kwargs) # pylint: disable=subprocess-run-check
903 else:
904 try:
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=""
912 return cp
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.
923 .. deprecated:: 4.6
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':
928 io = kw.get(stream)
929 if is_String(io) and io == 'devnull':
930 kw[stream] = 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)
938 try:
939 pobj = subprocess.Popen(cmd, **kw)
940 except OSError as e:
941 if error == 'raise': raise
942 # return a dummy Popen instance that only returns error
943 class dummyPopen:
944 def __init__(self, e) -> None:
945 self.exception = e
946 # Add the following two to enable using the return value as a context manager
947 # for example
948 # with Action._subproc(...) as po:
949 # logic here which uses po
951 def __enter__(self):
952 return self
954 def __exit__(self, *args) -> None:
955 pass
957 def communicate(self, input=None):
958 return ('', '')
960 def wait(self):
961 return -self.exception.errno
963 stdin = None
964 class f:
965 def read(self) -> str: return ''
966 def readline(self) -> str: return ''
967 def __iter__(self): return iter(())
968 stdout = stderr = f()
969 pobj = dummyPopen(e)
970 finally:
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)):
974 v.close()
976 return pobj
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
990 # variables.
991 if SCons.Debug.track_instances: logInstanceCreation(self, 'Action.CommandAction')
993 super().__init__(**kw)
994 if is_List(cmd):
995 if [c for c in cmd if is_List(c)]:
996 raise TypeError("CommandAction should be given only "
997 "a single command")
998 self.cmd_list = cmd
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]:
1007 if executor:
1008 result = env.subst_list(self.cmd_list, SUBST_CMD, executor=executor, overrides=overrides)
1009 else:
1010 result = env.subst_list(self.cmd_list, SUBST_CMD, target, source, overrides=overrides)
1011 silent = False
1012 ignore = False
1013 while True:
1014 try: c = result[0][0][0]
1015 except IndexError: c = None
1016 if c == '@': silent = True
1017 elif c == '-': ignore = True
1018 else: break
1019 result[0][0] = result[0][0][1:]
1020 try:
1021 if not result[0][0]:
1022 result[0] = result[0][1:]
1023 except IndexError:
1024 pass
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:
1029 return None
1030 if self.cmdstr is not _null:
1031 if executor:
1032 c = env.subst(self.cmdstr, SUBST_RAW, executor=executor, overrides=overrides)
1033 else:
1034 c = env.subst(self.cmdstr, SUBST_RAW, target, source, overrides=overrides)
1035 if c:
1036 return c
1037 cmd_list, ignore, silent = self.process(target, source, env, executor, overrides=overrides)
1038 if silent:
1039 return ''
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
1049 externally.
1051 escape_list = SCons.Subst.escape_list
1052 flatten_sequence = SCons.Util.flatten_sequence
1054 try:
1055 shell = env['SHELL']
1056 except KeyError:
1057 raise SCons.Errors.UserError('Missing SHELL construction variable.')
1059 try:
1060 spawn = env['SPAWN']
1061 except KeyError:
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):
1073 if is_List(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))
1079 else:
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)
1086 if executor:
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,
1099 status=result,
1100 action=self,
1101 command=cmd_line)
1102 return 0
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.
1110 cmd = self.cmd_list
1111 if is_List(cmd):
1112 cmd = ' '.join(map(str, cmd))
1113 else:
1114 cmd = str(cmd)
1115 if executor:
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'):
1126 return []
1128 try:
1129 icd_int = int(icd)
1130 except ValueError:
1131 icd_int = None
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 &&.
1146 if executor:
1147 cmd_list = env.subst_list(self.cmd_list, SUBST_SIG, executor=executor)
1148 else:
1149 cmd_list = env.subst_list(self.cmd_list, SUBST_SIG, target, source)
1150 res = []
1151 for cmd_line in cmd_list:
1152 if cmd_line:
1153 d = str(cmd_line[0])
1154 m = strip_quotes.match(d)
1155 if m:
1156 d = m.group(1)
1157 d = env.WhereIs(d)
1158 if d:
1159 res.append(env.fs.File(d))
1160 return res
1162 def _get_implicit_deps_heavyweight(self, target, source, env, executor: Executor | None,
1163 icd_int):
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
1175 script.
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)
1184 res = []
1186 for cmd_line in cmd_list:
1187 if cmd_line:
1188 entry_count = 0
1189 for entry in cmd_line:
1190 d = str(entry)
1191 if ((icd_int is None or entry_count < icd_int) and
1192 not d.startswith(('&', '-', '/') if os.name == 'nt'
1193 else ('&', '-'))):
1194 m = strip_quotes.match(d)
1195 if m:
1196 d = m.group(1)
1198 if 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:
1204 p = env.WhereIs(d)
1206 if p:
1207 res.append(env.fs.File(p))
1209 entry_count = entry_count + 1
1210 else:
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
1226 self.gen_kw = kw
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):
1234 target = [target]
1236 if executor:
1237 target = executor.get_all_targets()
1238 source = executor.get_all_sources()
1239 ret = self.generator(target=target,
1240 source=source,
1241 env=env,
1242 for_signature=for_signature)
1243 gen_cmd = Action(ret, **self.gen_kw)
1244 if not gen_cmd:
1245 raise SCons.Errors.UserError("Object returned from command generator: %s cannot be used to create an Action." % repr(ret))
1246 return gen_cmd
1248 def __str__(self) -> str:
1249 try:
1250 env = self.presub_env
1251 except AttributeError:
1252 env = None
1253 if env is None:
1254 env = SCons.Defaults.DefaultEnvironment()
1255 act = self._generate([], [], env, 1)
1256 return str(act)
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)
1267 if act is None:
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))))
1273 return act(
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)
1319 self.gen_kw = kw
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):
1328 if env:
1329 c = env.get(self.var, '')
1330 else:
1331 c = ''
1332 gen_cmd = Action(c, **self.gen_kw)
1333 if not gen_cmd:
1334 raise SCons.Errors.UserError("$%s value %s cannot be used to create an Action." % (self.var, repr(c)))
1335 return gen_cmd
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
1364 try:
1365 self.funccontents = _callable_contents(execfunction)
1366 except AttributeError:
1367 try:
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):
1377 try:
1378 return self.execfunction.__name__
1379 except AttributeError:
1380 try:
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:
1387 return None
1388 if self.cmdstr is not _null:
1389 if executor:
1390 c = env.subst(self.cmdstr, SUBST_RAW, executor=executor)
1391 else:
1392 c = env.subst(self.cmdstr, SUBST_RAW, target, source)
1393 if c:
1394 return c
1396 def array(a):
1397 def quote(s):
1398 try:
1399 str_for_display = s.str_for_display
1400 except AttributeError:
1401 s = repr(s)
1402 else:
1403 s = str_for_display()
1404 return s
1405 return '[' + ", ".join(map(quote, a)) + ']'
1406 try:
1407 strfunc = self.execfunction.strfunction
1408 except AttributeError:
1409 pass
1410 else:
1411 if strfunc is None:
1412 return None
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)
1428 try:
1429 if executor:
1430 target = executor.get_all_targets()
1431 source = executor.get_all_sources()
1432 rsources = list(map(rfile, source))
1433 try:
1434 result = self.execfunction(target=target, source=rsources, env=env)
1435 except (KeyboardInterrupt, SystemExit):
1436 raise
1437 except Exception as e:
1438 result = e
1439 exc_info = sys.exc_info()
1441 if result:
1442 result = SCons.Errors.convert_to_BuildError(result, exc_info)
1443 result.node = target
1444 result.action = self
1445 try:
1446 result.command=self.strfunction(target, source, env, executor)
1447 except TypeError:
1448 result.command=self.strfunction(target, source, env)
1450 return result
1451 finally:
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.
1455 del exc_info
1457 def get_presig(self, target, source, env, executor: Executor | None = None):
1458 """Return the signature contents of this callable action."""
1459 try:
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):
1465 return []
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):
1473 return x
1474 return Action(x)
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
1478 self.varlist = ()
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):
1500 if executor:
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)
1506 if stat:
1507 return stat
1508 return 0
1510 def get_implicit_deps(self, target, source, env, executor: Executor | None = None):
1511 result = []
1512 for act in self.list:
1513 result.extend(act.get_implicit_deps(target, source, env))
1514 return result
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):
1520 result[var] = True
1521 return list(result.keys())
1524 class ActionCaller:
1525 """A class for delaying calling an Action function with specific
1526 (positional and keyword) arguments until the Action is actually
1527 executed.
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
1535 self.args = args
1536 self.kw = kw
1538 def get_contents(self, target, source, env):
1539 actfunc = self.parent.actfunc
1540 try:
1541 # "self.actfunc" is a function.
1542 contents = actfunc.__code__.co_code
1543 except AttributeError:
1544 # "self.actfunc" is a callable object.
1545 try:
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)
1552 return contents
1554 def subst(self, s, target, source, env):
1555 # If s is a list, recursively apply subst()
1556 # to every element in the list
1557 if is_List(s):
1558 result = []
1559 for elem in s:
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.
1566 if s == '$__env__':
1567 return env
1568 if is_String(s):
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):
1577 kw = {}
1578 for key in list(self.kw.keys()):
1579 kw[key] = self.subst(self.kw[key], target, source, env)
1580 return kw
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)
1613 return action
1615 # Local Variables:
1616 # tab-width:4
1617 # indent-tabs-mode:nil
1618 # End:
1619 # vim: set expandtab tabstop=4 shiftwidth=4: