Package SCons :: Module Action
[hide private]
[frames] | no frames]

Source Code for Module SCons.Action

   1  """SCons.Action 
   2   
   3  This encapsulates information about executing any sort of action that 
   4  can build one or more target Nodes (typically files) from one or more 
   5  source Nodes (also typically files) given a specific Environment. 
   6   
   7  The base class here is ActionBase.  The base class supplies just a few 
   8  OO utility methods and some generic methods for displaying information 
   9  about an Action in response to the various commands that control printing. 
  10   
  11  A second-level base class is _ActionAction.  This extends ActionBase 
  12  by providing the methods that can be used to show and perform an 
  13  action.  True Action objects will subclass _ActionAction; Action 
  14  factory class objects will subclass ActionBase. 
  15   
  16  The heavy lifting is handled by subclasses for the different types of 
  17  actions we might execute: 
  18   
  19      CommandAction 
  20      CommandGeneratorAction 
  21      FunctionAction 
  22      ListAction 
  23   
  24  The subclasses supply the following public interface methods used by 
  25  other modules: 
  26   
  27      __call__() 
  28          THE public interface, "calling" an Action object executes the 
  29          command or Python function.  This also takes care of printing 
  30          a pre-substitution command for debugging purposes. 
  31   
  32      get_contents() 
  33          Fetches the "contents" of an Action for signature calculation 
  34          plus the varlist.  This is what gets MD5 checksummed to decide 
  35          if a target needs to be rebuilt because its action changed. 
  36   
  37      genstring() 
  38          Returns a string representation of the Action *without* 
  39          command substitution, but allows a CommandGeneratorAction to 
  40          generate the right action based on the specified target, 
  41          source and env.  This is used by the Signature subsystem 
  42          (through the Executor) to obtain an (imprecise) representation 
  43          of the Action operation for informative purposes. 
  44   
  45   
  46  Subclasses also supply the following methods for internal use within 
  47  this module: 
  48   
  49      __str__() 
  50          Returns a string approximation of the Action; no variable 
  51          substitution is performed. 
  52   
  53      execute() 
  54          The internal method that really, truly, actually handles the 
  55          execution of a command or Python function.  This is used so 
  56          that the __call__() methods can take care of displaying any 
  57          pre-substitution representations, and *then* execute an action 
  58          without worrying about the specific Actions involved. 
  59   
  60      get_presig() 
  61          Fetches the "contents" of a subclass for signature calculation. 
  62          The varlist is added to this to produce the Action's contents. 
  63          TODO(?): Change this to always return ascii/bytes and not unicode (or py3 strings) 
  64   
  65      strfunction() 
  66          Returns a substituted string representation of the Action. 
  67          This is used by the _ActionAction.show() command to display the 
  68          command/function that will be executed to generate the target(s). 
  69   
  70  There is a related independent ActionCaller class that looks like a 
  71  regular Action, and which serves as a wrapper for arbitrary functions 
  72  that we want to let the user specify the arguments to now, but actually 
  73  execute later (when an out-of-date check determines that it's needed to 
  74  be executed, for example).  Objects of this class are returned by an 
  75  ActionFactory class that provides a __call__() method as a convenient 
  76  way for wrapping up the functions. 
  77   
  78  """ 
  79   
  80  # Copyright (c) 2001 - 2017 The SCons Foundation 
  81  # 
  82  # Permission is hereby granted, free of charge, to any person obtaining 
  83  # a copy of this software and associated documentation files (the 
  84  # "Software"), to deal in the Software without restriction, including 
  85  # without limitation the rights to use, copy, modify, merge, publish, 
  86  # distribute, sublicense, and/or sell copies of the Software, and to 
  87  # permit persons to whom the Software is furnished to do so, subject to 
  88  # the following conditions: 
  89  # 
  90  # The above copyright notice and this permission notice shall be included 
  91  # in all copies or substantial portions of the Software. 
  92  # 
  93  # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY 
  94  # KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE 
  95  # WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 
  96  # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 
  97  # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 
  98  # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 
  99  # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 
 100   
 101  __revision__ = "src/engine/SCons/Action.py 74b2c53bc42290e911b334a6b44f187da698a668 2017/11/14 13:16:53 bdbaddog" 
 102   
 103  import os 
 104  import pickle 
 105  import re 
 106  import sys 
 107  import subprocess 
 108  import itertools 
 109  import inspect 
 110   
 111  import SCons.Debug 
 112  from SCons.Debug import logInstanceCreation 
 113  import SCons.Errors 
 114  import SCons.Util 
 115  import SCons.Subst 
 116   
 117  # we use these a lot, so try to optimize them 
 118  is_String = SCons.Util.is_String 
 119  is_List = SCons.Util.is_List 
 120   
121 -class _null(object):
122 pass
123 124 print_actions = 1 125 execute_actions = 1 126 print_actions_presub = 0 127 128 # Use pickle protocol 1 when pickling functions for signature 129 # otherwise python3 and python2 will yield different pickles 130 # for the same object. 131 # This is due to default being 1 for python 2.7, and 3 for 3.x 132 # TODO: We can roll this forward to 2 (if it has value), but not 133 # before a deprecation cycle as the sconsigns will change 134 ACTION_SIGNATURE_PICKLE_PROTOCOL = 1 135 136
137 -def rfile(n):
138 try: 139 return n.rfile() 140 except AttributeError: 141 return n
142 143
144 -def default_exitstatfunc(s):
145 return s
146 147 strip_quotes = re.compile('^[\'"](.*)[\'"]$') 148 149
150 -def _callable_contents(obj):
151 """Return the signature contents of a callable Python object. 152 """ 153 try: 154 # Test if obj is a method. 155 return _function_contents(obj.__func__) 156 157 except AttributeError: 158 try: 159 # Test if obj is a callable object. 160 return _function_contents(obj.__call__.__func__) 161 162 except AttributeError: 163 try: 164 # Test if obj is a code object. 165 return _code_contents(obj) 166 167 except AttributeError: 168 # Test if obj is a function object. 169 return _function_contents(obj)
170 171
172 -def _object_contents(obj):
173 """Return the signature contents of any Python object. 174 175 We have to handle the case where object contains a code object 176 since it can be pickled directly. 177 """ 178 try: 179 # Test if obj is a method. 180 return _function_contents(obj.__func__) 181 182 except AttributeError: 183 try: 184 # Test if obj is a callable object. 185 return _function_contents(obj.__call__.__func__) 186 187 except AttributeError: 188 try: 189 # Test if obj is a code object. 190 return _code_contents(obj) 191 192 except AttributeError: 193 try: 194 # Test if obj is a function object. 195 return _function_contents(obj) 196 197 except AttributeError as ae: 198 # Should be a pickle-able Python object. 199 try: 200 return _object_instance_content(obj) 201 # pickling an Action instance or object doesn't yield a stable 202 # content as instance property may be dumped in different orders 203 # return pickle.dumps(obj, ACTION_SIGNATURE_PICKLE_PROTOCOL) 204 except (pickle.PicklingError, TypeError, AttributeError) as ex: 205 # This is weird, but it seems that nested classes 206 # are unpickable. The Python docs say it should 207 # always be a PicklingError, but some Python 208 # versions seem to return TypeError. Just do 209 # the best we can. 210 return bytearray(repr(obj), 'utf-8')
211 212
213 -def _code_contents(code, docstring=None):
214 """Return the signature contents of a code object. 215 216 By providing direct access to the code object of the 217 function, Python makes this extremely easy. Hooray! 218 219 Unfortunately, older versions of Python include line 220 number indications in the compiled byte code. Boo! 221 So we remove the line number byte codes to prevent 222 recompilations from moving a Python function. 223 224 See: 225 - https://docs.python.org/2/library/inspect.html 226 - http://python-reference.readthedocs.io/en/latest/docs/code/index.html 227 228 For info on what each co\_ variable provides 229 230 The signature is as follows (should be byte/chars): 231 co_argcount, len(co_varnames), len(co_cellvars), len(co_freevars), 232 ( comma separated signature for each object in co_consts ), 233 ( comma separated signature for each object in co_names ), 234 ( The bytecode with line number bytecodes removed from co_code ) 235 236 co_argcount - Returns the number of positional arguments (including arguments with default values). 237 co_varnames - Returns a tuple containing the names of the local variables (starting with the argument names). 238 co_cellvars - Returns a tuple containing the names of local variables that are referenced by nested functions. 239 co_freevars - Returns a tuple containing the names of free variables. (?) 240 co_consts - Returns a tuple containing the literals used by the bytecode. 241 co_names - Returns a tuple containing the names used by the bytecode. 242 co_code - Returns a string representing the sequence of bytecode instructions. 243 244 """ 245 246 # contents = [] 247 248 # The code contents depends on the number of local variables 249 # but not their actual names. 250 contents = bytearray("{}, {}".format(code.co_argcount, len(code.co_varnames)), 'utf-8') 251 252 contents.extend(b", ") 253 contents.extend(bytearray(str(len(code.co_cellvars)), 'utf-8')) 254 contents.extend(b", ") 255 contents.extend(bytearray(str(len(code.co_freevars)), 'utf-8')) 256 257 # The code contents depends on any constants accessed by the 258 # function. Note that we have to call _object_contents on each 259 # constants because the code object of nested functions can 260 # show-up among the constants. 261 262 z = [_object_contents(cc) for cc in code.co_consts[1:]] 263 contents.extend(b',(') 264 contents.extend(bytearray(',', 'utf-8').join(z)) 265 contents.extend(b')') 266 267 # The code contents depends on the variable names used to 268 # accessed global variable, as changing the variable name changes 269 # the variable actually accessed and therefore changes the 270 # function result. 271 z= [bytearray(_object_contents(cc)) for cc in code.co_names] 272 contents.extend(b',(') 273 contents.extend(bytearray(',','utf-8').join(z)) 274 contents.extend(b')') 275 276 # The code contents depends on its actual code!!! 277 contents.extend(b',(') 278 contents.extend(code.co_code) 279 contents.extend(b')') 280 281 return contents
282 283
284 -def _function_contents(func):
285 """ 286 The signature is as follows (should be byte/chars): 287 < _code_contents (see above) from func.__code__ > 288 ,( comma separated _object_contents for function argument defaults) 289 ,( comma separated _object_contents for any closure contents ) 290 291 292 See also: https://docs.python.org/3/reference/datamodel.html 293 - func.__code__ - The code object representing the compiled function body. 294 - func.__defaults__ - A tuple containing default argument values for those arguments that have defaults, or None if no arguments have a default value 295 - func.__closure__ - None or a tuple of cells that contain bindings for the function's free variables. 296 297 :Returns: 298 Signature contents of a function. (in bytes) 299 """ 300 301 contents = [_code_contents(func.__code__, func.__doc__)] 302 303 # The function contents depends on the value of defaults arguments 304 if func.__defaults__: 305 306 function_defaults_contents = [_object_contents(cc) for cc in func.__defaults__] 307 308 defaults = bytearray(b',(') 309 defaults.extend(bytearray(b',').join(function_defaults_contents)) 310 defaults.extend(b')') 311 312 contents.append(defaults) 313 else: 314 contents.append(b',()') 315 316 # The function contents depends on the closure captured cell values. 317 closure = func.__closure__ or [] 318 319 try: 320 closure_contents = [_object_contents(x.cell_contents) for x in closure] 321 except AttributeError: 322 closure_contents = [] 323 324 contents.append(b',(') 325 contents.append(bytearray(b',').join(closure_contents)) 326 contents.append(b')') 327 328 retval = bytearray(b'').join(contents) 329 return retval
330 331
332 -def _object_instance_content(obj):
333 """ 334 Returns consistant content for a action class or an instance thereof 335 336 :Parameters: 337 - `obj` Should be either and action class or an instance thereof 338 339 :Returns: 340 bytearray or bytes representing the obj suitable for generating a signature from. 341 """ 342 retval = bytearray() 343 344 if obj is None: 345 return b'N.' 346 347 if isinstance(obj, SCons.Util.BaseStringTypes): 348 return SCons.Util.to_bytes(obj) 349 350 inst_class = obj.__class__ 351 inst_class_name = bytearray(obj.__class__.__name__,'utf-8') 352 inst_class_module = bytearray(obj.__class__.__module__,'utf-8') 353 inst_class_hierarchy = bytearray(repr(inspect.getclasstree([obj.__class__,])),'utf-8') 354 # print("ICH:%s : %s"%(inst_class_hierarchy, repr(obj))) 355 356 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))) ] 357 properties.sort() 358 properties_str = ','.join(["%s=%s"%(p[0],p[1]) for p in properties]) 359 properties_bytes = bytearray(properties_str,'utf-8') 360 361 methods = [p for p in dir(obj) if inspect.ismethod(getattr(obj, p))] 362 methods.sort() 363 364 method_contents = [] 365 for m in methods: 366 # print("Method:%s"%m) 367 v = _function_contents(getattr(obj, m)) 368 # print("[%s->]V:%s [%s]"%(m,v,type(v))) 369 method_contents.append(v) 370 371 retval = bytearray(b'{') 372 retval.extend(inst_class_name) 373 retval.extend(b":") 374 retval.extend(inst_class_module) 375 retval.extend(b'}[[') 376 retval.extend(inst_class_hierarchy) 377 retval.extend(b']]{{') 378 retval.extend(bytearray(b",").join(method_contents)) 379 retval.extend(b"}}{{{") 380 retval.extend(properties_bytes) 381 retval.extend(b'}}}') 382 return retval
383 384 # print("class :%s"%inst_class) 385 # print("class_name :%s"%inst_class_name) 386 # print("class_module :%s"%inst_class_module) 387 # print("Class hier :\n%s"%pp.pformat(inst_class_hierarchy)) 388 # print("Inst Properties:\n%s"%pp.pformat(properties)) 389 # print("Inst Methods :\n%s"%pp.pformat(methods)) 390
391 -def _actionAppend(act1, act2):
392 # This function knows how to slap two actions together. 393 # Mainly, it handles ListActions by concatenating into 394 # a single ListAction. 395 a1 = Action(act1) 396 a2 = Action(act2) 397 if a1 is None: 398 return a2 399 if a2 is None: 400 return a1 401 if isinstance(a1, ListAction): 402 if isinstance(a2, ListAction): 403 return ListAction(a1.list + a2.list) 404 else: 405 return ListAction(a1.list + [ a2 ]) 406 else: 407 if isinstance(a2, ListAction): 408 return ListAction([ a1 ] + a2.list) 409 else: 410 return ListAction([ a1, a2 ])
411 412
413 -def _do_create_keywords(args, kw):
414 """This converts any arguments after the action argument into 415 their equivalent keywords and adds them to the kw argument. 416 """ 417 v = kw.get('varlist', ()) 418 # prevent varlist="FOO" from being interpreted as ['F', 'O', 'O'] 419 if is_String(v): v = (v,) 420 kw['varlist'] = tuple(v) 421 if args: 422 # turn positional args into equivalent keywords 423 cmdstrfunc = args[0] 424 if cmdstrfunc is None or is_String(cmdstrfunc): 425 kw['cmdstr'] = cmdstrfunc 426 elif callable(cmdstrfunc): 427 kw['strfunction'] = cmdstrfunc 428 else: 429 raise SCons.Errors.UserError( 430 'Invalid command display variable type. ' 431 'You must either pass a string or a callback which ' 432 'accepts (target, source, env) as parameters.') 433 if len(args) > 1: 434 kw['varlist'] = tuple(SCons.Util.flatten(args[1:])) + kw['varlist'] 435 if kw.get('strfunction', _null) is not _null \ 436 and kw.get('cmdstr', _null) is not _null: 437 raise SCons.Errors.UserError( 438 'Cannot have both strfunction and cmdstr args to Action()')
439 440
441 -def _do_create_action(act, kw):
442 """This is the actual "implementation" for the 443 Action factory method, below. This handles the 444 fact that passing lists to Action() itself has 445 different semantics than passing lists as elements 446 of lists. 447 448 The former will create a ListAction, the latter 449 will create a CommandAction by converting the inner 450 list elements to strings.""" 451 452 if isinstance(act, ActionBase): 453 return act 454 455 if is_String(act): 456 var=SCons.Util.get_environment_var(act) 457 if var: 458 # This looks like a string that is purely an Environment 459 # variable reference, like "$FOO" or "${FOO}". We do 460 # something special here...we lazily evaluate the contents 461 # of that Environment variable, so a user could put something 462 # like a function or a CommandGenerator in that variable 463 # instead of a string. 464 return LazyAction(var, kw) 465 commands = str(act).split('\n') 466 if len(commands) == 1: 467 return CommandAction(commands[0], **kw) 468 # The list of string commands may include a LazyAction, so we 469 # reprocess them via _do_create_list_action. 470 return _do_create_list_action(commands, kw) 471 472 if is_List(act): 473 return CommandAction(act, **kw) 474 475 if callable(act): 476 try: 477 gen = kw['generator'] 478 del kw['generator'] 479 except KeyError: 480 gen = 0 481 if gen: 482 action_type = CommandGeneratorAction 483 else: 484 action_type = FunctionAction 485 return action_type(act, kw) 486 487 # Catch a common error case with a nice message: 488 if isinstance(act, int) or isinstance(act, float): 489 raise TypeError("Don't know how to create an Action from a number (%s)"%act) 490 # Else fail silently (???) 491 return None
492 493
494 -def _do_create_list_action(act, kw):
495 """A factory for list actions. Convert the input list into Actions 496 and then wrap them in a ListAction.""" 497 acts = [] 498 for a in act: 499 aa = _do_create_action(a, kw) 500 if aa is not None: acts.append(aa) 501 if not acts: 502 return ListAction([]) 503 elif len(acts) == 1: 504 return acts[0] 505 else: 506 return ListAction(acts)
507 508
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)
516 517
518 -class ActionBase(object):
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.""" 522
523 - def __eq__(self, other):
524 return self.__dict__ == other
525
526 - def no_batch_key(self, env, target, source):
527 return None
528 529 batch_key = no_batch_key 530
531 - def genstring(self, target, source, env):
532 return str(self)
533
534 - def get_contents(self, target, source, env):
535 result = self.get_presig(target, source, env) 536 537 if not isinstance(result,(bytes, bytearray)): 538 result = bytearray("",'utf-8').join([ SCons.Util.to_bytes(r) for r in result ]) 539 else: 540 # Make a copy and put in bytearray, without this the contents returned by get_presig 541 # can be changed by the logic below, appending with each call and causing very 542 # hard to track down issues... 543 result = bytearray(result) 544 545 # At this point everything should be a bytearray 546 547 # This should never happen, as the Action() factory should wrap 548 # the varlist, but just in case an action is created directly, 549 # we duplicate this check here. 550 vl = self.get_varlist(target, source, env) 551 if is_String(vl): vl = (vl,) 552 for v in vl: 553 # do the subst this way to ignore $(...$) parts: 554 if isinstance(result, bytearray): 555 result.extend(SCons.Util.to_bytes(env.subst_target_source('${'+v+'}', SCons.Subst.SUBST_SIG, target, source))) 556 else: 557 raise Exception("WE SHOULD NEVER GET HERE result should be bytearray not:%s"%type(result)) 558 # result.append(SCons.Util.to_bytes(env.subst_target_source('${'+v+'}', SCons.Subst.SUBST_SIG, target, source))) 559 560 561 if isinstance(result, (bytes,bytearray)): 562 return result 563 else: 564 raise Exception("WE SHOULD NEVER GET HERE - #2 result should be bytearray not:%s" % type(result))
565 # return b''.join(result) 566
567 - def __add__(self, other):
568 return _actionAppend(self, other)
569
570 - def __radd__(self, other):
571 return _actionAppend(other, self)
572
573 - def presub_lines(self, env):
574 # CommandGeneratorAction needs a real environment 575 # in order to return the proper string here, since 576 # it may call LazyAction, which looks up a key 577 # in that env. So we temporarily remember the env here, 578 # and CommandGeneratorAction will use this env 579 # when it calls its _generate method. 580 self.presub_env = env 581 lines = str(self).split('\n') 582 self.presub_env = None # don't need this any more 583 return lines
584
585 - def get_varlist(self, target, source, env, executor=None):
586 return self.varlist
587
588 - def get_targets(self, env, executor):
589 """ 590 Returns the type of targets ($TARGETS, $CHANGED_TARGETS) used 591 by this action. 592 """ 593 return self.targets
594 595
596 -class _ActionAction(ActionBase):
597 """Base class for actions that create output objects."""
598 - def __init__(self, cmdstr=_null, strfunction=_null, varlist=(), 599 presub=_null, chdir=None, exitstatfunc=None, 600 batch_key=None, targets='$TARGETS', 601 **kw):
602 self.cmdstr = cmdstr 603 if strfunction is not _null: 604 if strfunction is None: 605 self.cmdstr = None 606 else: 607 self.strfunction = strfunction 608 self.varlist = varlist 609 self.presub = presub 610 self.chdir = chdir 611 if not exitstatfunc: 612 exitstatfunc = default_exitstatfunc 613 self.exitstatfunc = exitstatfunc 614 615 self.targets = targets 616 617 if batch_key: 618 if not callable(batch_key): 619 # They have set batch_key, but not to their own 620 # callable. The default behavior here will batch 621 # *all* targets+sources using this action, separated 622 # for each construction environment. 623 def default_batch_key(self, env, target, source): 624 return (id(self), id(env))
625 batch_key = default_batch_key 626 SCons.Util.AddMethod(self, batch_key, 'batch_key')
627
628 - def print_cmd_line(self, s, target, source, env):
629 """ 630 In python 3, and in some of our tests, sys.stdout is 631 a String io object, and it takes unicode strings only 632 In other cases it's a regular Python 2.x file object 633 which takes strings (bytes), and if you pass those a 634 unicode object they try to decode with 'ascii' codec 635 which fails if the cmd line has any hi-bit-set chars. 636 This code assumes s is a regular string, but should 637 work if it's unicode too. 638 """ 639 try: 640 sys.stdout.write(s + u"\n") 641 except UnicodeDecodeError: 642 sys.stdout.write(s + "\n")
643
644 - def __call__(self, target, source, env, 645 exitstatfunc=_null, 646 presub=_null, 647 show=_null, 648 execute=_null, 649 chdir=_null, 650 executor=None):
651 if not is_List(target): 652 target = [target] 653 if not is_List(source): 654 source = [source] 655 656 if presub is _null: 657 presub = self.presub 658 if presub is _null: 659 presub = print_actions_presub 660 if exitstatfunc is _null: exitstatfunc = self.exitstatfunc 661 if show is _null: show = print_actions 662 if execute is _null: execute = execute_actions 663 if chdir is _null: chdir = self.chdir 664 save_cwd = None 665 if chdir: 666 save_cwd = os.getcwd() 667 try: 668 chdir = str(chdir.get_abspath()) 669 except AttributeError: 670 if not is_String(chdir): 671 if executor: 672 chdir = str(executor.batches[0].targets[0].dir) 673 else: 674 chdir = str(target[0].dir) 675 if presub: 676 if executor: 677 target = executor.get_all_targets() 678 source = executor.get_all_sources() 679 t = ' and '.join(map(str, target)) 680 l = '\n '.join(self.presub_lines(env)) 681 out = u"Building %s with action:\n %s\n" % (t, l) 682 sys.stdout.write(out) 683 cmd = None 684 if show and self.strfunction: 685 if executor: 686 target = executor.get_all_targets() 687 source = executor.get_all_sources() 688 try: 689 cmd = self.strfunction(target, source, env, executor) 690 except TypeError: 691 cmd = self.strfunction(target, source, env) 692 if cmd: 693 if chdir: 694 cmd = ('os.chdir(%s)\n' % repr(chdir)) + cmd 695 try: 696 get = env.get 697 except AttributeError: 698 print_func = self.print_cmd_line 699 else: 700 print_func = get('PRINT_CMD_LINE_FUNC') 701 if not print_func: 702 print_func = self.print_cmd_line 703 print_func(cmd, target, source, env) 704 stat = 0 705 if execute: 706 if chdir: 707 os.chdir(chdir) 708 try: 709 stat = self.execute(target, source, env, executor=executor) 710 if isinstance(stat, SCons.Errors.BuildError): 711 s = exitstatfunc(stat.status) 712 if s: 713 stat.status = s 714 else: 715 stat = s 716 else: 717 stat = exitstatfunc(stat) 718 finally: 719 if save_cwd: 720 os.chdir(save_cwd) 721 if cmd and save_cwd: 722 print_func('os.chdir(%s)' % repr(save_cwd), target, source, env) 723 724 return stat
725 726
727 -def _string_from_cmd_list(cmd_list):
728 """Takes a list of command line arguments and returns a pretty 729 representation for printing.""" 730 cl = [] 731 for arg in map(str, cmd_list): 732 if ' ' in arg or '\t' in arg: 733 arg = '"' + arg + '"' 734 cl.append(arg) 735 return ' '.join(cl)
736 737 default_ENV = None 738 739
740 -def get_default_ENV(env):
741 """ 742 A fiddlin' little function that has an 'import SCons.Environment' which 743 can't be moved to the top level without creating an import loop. Since 744 this import creates a local variable named 'SCons', it blocks access to 745 the global variable, so we move it here to prevent complaints about local 746 variables being used uninitialized. 747 """ 748 global default_ENV 749 try: 750 return env['ENV'] 751 except KeyError: 752 if not default_ENV: 753 import SCons.Environment 754 # This is a hideously expensive way to get a default shell 755 # environment. What it really should do is run the platform 756 # setup to get the default ENV. Fortunately, it's incredibly 757 # rare for an Environment not to have a shell environment, so 758 # we're not going to worry about it overmuch. 759 default_ENV = SCons.Environment.Environment()['ENV'] 760 return default_ENV
761 762
763 -def _subproc(scons_env, cmd, error = 'ignore', **kw):
764 """Do common setup for a subprocess.Popen() call 765 766 This function is still in draft mode. We're going to need something like 767 it in the long run as more and more places use subprocess, but I'm sure 768 it'll have to be tweaked to get the full desired functionality. 769 one special arg (so far?), 'error', to tell what to do with exceptions. 770 """ 771 # allow std{in,out,err} to be "'devnull'" 772 io = kw.get('stdin') 773 if is_String(io) and io == 'devnull': 774 kw['stdin'] = open(os.devnull) 775 io = kw.get('stdout') 776 if is_String(io) and io == 'devnull': 777 kw['stdout'] = open(os.devnull, 'w') 778 io = kw.get('stderr') 779 if is_String(io) and io == 'devnull': 780 kw['stderr'] = open(os.devnull, 'w') 781 782 # Figure out what shell environment to use 783 ENV = kw.get('env', None) 784 if ENV is None: ENV = get_default_ENV(scons_env) 785 786 # Ensure that the ENV values are all strings: 787 new_env = {} 788 for key, value in ENV.items(): 789 if is_List(value): 790 # If the value is a list, then we assume it is a path list, 791 # because that's a pretty common list-like value to stick 792 # in an environment variable: 793 value = SCons.Util.flatten_sequence(value) 794 new_env[key] = os.pathsep.join(map(str, value)) 795 else: 796 # It's either a string or something else. If it's a string, 797 # we still want to call str() because it might be a *Unicode* 798 # string, which makes subprocess.Popen() gag. If it isn't a 799 # string or a list, then we just coerce it to a string, which 800 # is the proper way to handle Dir and File instances and will 801 # produce something reasonable for just about everything else: 802 new_env[key] = str(value) 803 kw['env'] = new_env 804 805 try: 806 return subprocess.Popen(cmd, **kw) 807 except EnvironmentError as e: 808 if error == 'raise': raise 809 # return a dummy Popen instance that only returns error 810 class dummyPopen(object): 811 def __init__(self, e): self.exception = e 812 def communicate(self, input=None): return ('', '') 813 def wait(self): return -self.exception.errno 814 stdin = None 815 class f(object): 816 def read(self): return '' 817 def readline(self): return '' 818 def __iter__(self): return iter(())
819 stdout = stderr = f() 820 return dummyPopen(e) 821 822
823 -class CommandAction(_ActionAction):
824 """Class for command-execution actions."""
825 - def __init__(self, cmd, **kw):
826 # Cmd can actually be a list or a single item; if it's a 827 # single item it should be the command string to execute; if a 828 # list then it should be the words of the command string to 829 # execute. Only a single command should be executed by this 830 # object; lists of commands should be handled by embedding 831 # these objects in a ListAction object (which the Action() 832 # factory above does). cmd will be passed to 833 # Environment.subst_list() for substituting environment 834 # variables. 835 if SCons.Debug.track_instances: logInstanceCreation(self, 'Action.CommandAction') 836 837 _ActionAction.__init__(self, **kw) 838 if is_List(cmd): 839 if [c for c in cmd if is_List(c)]: 840 raise TypeError("CommandAction should be given only " \ 841 "a single command") 842 self.cmd_list = cmd
843
844 - def __str__(self):
845 if is_List(self.cmd_list): 846 return ' '.join(map(str, self.cmd_list)) 847 return str(self.cmd_list)
848
849 - def process(self, target, source, env, executor=None):
850 if executor: 851 result = env.subst_list(self.cmd_list, 0, executor=executor) 852 else: 853 result = env.subst_list(self.cmd_list, 0, target, source) 854 silent = None 855 ignore = None 856 while True: 857 try: c = result[0][0][0] 858 except IndexError: c = None 859 if c == '@': silent = 1 860 elif c == '-': ignore = 1 861 else: break 862 result[0][0] = result[0][0][1:] 863 try: 864 if not result[0][0]: 865 result[0] = result[0][1:] 866 except IndexError: 867 pass 868 return result, ignore, silent
869
870 - def strfunction(self, target, source, env, executor=None):
871 if self.cmdstr is None: 872 return None 873 if self.cmdstr is not _null: 874 from SCons.Subst import SUBST_RAW 875 if executor: 876 c = env.subst(self.cmdstr, SUBST_RAW, executor=executor) 877 else: 878 c = env.subst(self.cmdstr, SUBST_RAW, target, source) 879 if c: 880 return c 881 cmd_list, ignore, silent = self.process(target, source, env, executor) 882 if silent: 883 return '' 884 return _string_from_cmd_list(cmd_list[0])
885
886 - def execute(self, target, source, env, executor=None):
887 """Execute a command action. 888 889 This will handle lists of commands as well as individual commands, 890 because construction variable substitution may turn a single 891 "command" into a list. This means that this class can actually 892 handle lists of commands, even though that's not how we use it 893 externally. 894 """ 895 escape_list = SCons.Subst.escape_list 896 flatten_sequence = SCons.Util.flatten_sequence 897 898 try: 899 shell = env['SHELL'] 900 except KeyError: 901 raise SCons.Errors.UserError('Missing SHELL construction variable.') 902 903 try: 904 spawn = env['SPAWN'] 905 except KeyError: 906 raise SCons.Errors.UserError('Missing SPAWN construction variable.') 907 else: 908 if is_String(spawn): 909 spawn = env.subst(spawn, raw=1, conv=lambda x: x) 910 911 escape = env.get('ESCAPE', lambda x: x) 912 913 ENV = get_default_ENV(env) 914 915 # Ensure that the ENV values are all strings: 916 for key, value in ENV.items(): 917 if not is_String(value): 918 if is_List(value): 919 # If the value is a list, then we assume it is a 920 # path list, because that's a pretty common list-like 921 # value to stick in an environment variable: 922 value = flatten_sequence(value) 923 ENV[key] = os.pathsep.join(map(str, value)) 924 else: 925 # If it isn't a string or a list, then we just coerce 926 # it to a string, which is the proper way to handle 927 # Dir and File instances and will produce something 928 # reasonable for just about everything else: 929 ENV[key] = str(value) 930 931 if executor: 932 target = executor.get_all_targets() 933 source = executor.get_all_sources() 934 cmd_list, ignore, silent = self.process(target, list(map(rfile, source)), env, executor) 935 936 # Use len() to filter out any "command" that's zero-length. 937 for cmd_line in filter(len, cmd_list): 938 # Escape the command line for the interpreter we are using. 939 cmd_line = escape_list(cmd_line, escape) 940 result = spawn(shell, escape, cmd_line[0], cmd_line, ENV) 941 if not ignore and result: 942 msg = "Error %s" % result 943 return SCons.Errors.BuildError(errstr=msg, 944 status=result, 945 action=self, 946 command=cmd_line) 947 return 0
948
949 - def get_presig(self, target, source, env, executor=None):
950 """Return the signature contents of this action's command line. 951 952 This strips $(-$) and everything in between the string, 953 since those parts don't affect signatures. 954 """ 955 from SCons.Subst import SUBST_SIG 956 cmd = self.cmd_list 957 if is_List(cmd): 958 cmd = ' '.join(map(str, cmd)) 959 else: 960 cmd = str(cmd) 961 if executor: 962 return env.subst_target_source(cmd, SUBST_SIG, executor=executor) 963 else: 964 return env.subst_target_source(cmd, SUBST_SIG, target, source)
965
966 - def get_implicit_deps(self, target, source, env, executor=None):
967 icd = env.get('IMPLICIT_COMMAND_DEPENDENCIES', True) 968 if is_String(icd) and icd[:1] == '$': 969 icd = env.subst(icd) 970 if not icd or icd in ('0', 'None'): 971 return [] 972 from SCons.Subst import SUBST_SIG 973 if executor: 974 cmd_list = env.subst_list(self.cmd_list, SUBST_SIG, executor=executor) 975 else: 976 cmd_list = env.subst_list(self.cmd_list, SUBST_SIG, target, source) 977 res = [] 978 for cmd_line in cmd_list: 979 if cmd_line: 980 d = str(cmd_line[0]) 981 m = strip_quotes.match(d) 982 if m: 983 d = m.group(1) 984 d = env.WhereIs(d) 985 if d: 986 res.append(env.fs.File(d)) 987 return res
988 989
990 -class CommandGeneratorAction(ActionBase):
991 """Class for command-generator actions."""
992 - def __init__(self, generator, kw):
993 if SCons.Debug.track_instances: logInstanceCreation(self, 'Action.CommandGeneratorAction') 994 self.generator = generator 995 self.gen_kw = kw 996 self.varlist = kw.get('varlist', ()) 997 self.targets = kw.get('targets', '$TARGETS')
998
999 - def _generate(self, target, source, env, for_signature, executor=None):
1000 # ensure that target is a list, to make it easier to write 1001 # generator functions: 1002 if not is_List(target): 1003 target = [target] 1004 1005 if executor: 1006 target = executor.get_all_targets() 1007 source = executor.get_all_sources() 1008 ret = self.generator(target=target, 1009 source=source, 1010 env=env, 1011 for_signature=for_signature) 1012 gen_cmd = Action(ret, **self.gen_kw) 1013 if not gen_cmd: 1014 raise SCons.Errors.UserError("Object returned from command generator: %s cannot be used to create an Action." % repr(ret)) 1015 return gen_cmd
1016
1017 - def __str__(self):
1018 try: 1019 env = self.presub_env 1020 except AttributeError: 1021 env = None 1022 if env is None: 1023 env = SCons.Defaults.DefaultEnvironment() 1024 act = self._generate([], [], env, 1) 1025 return str(act)
1026
1027 - def batch_key(self, env, target, source):
1028 return self._generate(target, source, env, 1).batch_key(env, target, source)
1029
1030 - def genstring(self, target, source, env, executor=None):
1031 return self._generate(target, source, env, 1, executor).genstring(target, source, env)
1032
1033 - def __call__(self, target, source, env, exitstatfunc=_null, presub=_null, 1034 show=_null, execute=_null, chdir=_null, executor=None):
1035 act = self._generate(target, source, env, 0, executor) 1036 if act is None: 1037 raise SCons.Errors.UserError("While building `%s': " 1038 "Cannot deduce file extension from source files: %s" 1039 % (repr(list(map(str, target))), repr(list(map(str, source))))) 1040 return act(target, source, env, exitstatfunc, presub, 1041 show, execute, chdir, executor)
1042
1043 - def get_presig(self, target, source, env, executor=None):
1044 """Return the signature contents of this action's command line. 1045 1046 This strips $(-$) and everything in between the string, 1047 since those parts don't affect signatures. 1048 """ 1049 return self._generate(target, source, env, 1, executor).get_presig(target, source, env)
1050
1051 - def get_implicit_deps(self, target, source, env, executor=None):
1052 return self._generate(target, source, env, 1, executor).get_implicit_deps(target, source, env)
1053
1054 - def get_varlist(self, target, source, env, executor=None):
1055 return self._generate(target, source, env, 1, executor).get_varlist(target, source, env, executor)
1056
1057 - def get_targets(self, env, executor):
1058 return self._generate(None, None, env, 1, executor).get_targets(env, executor)
1059 1060
1061 -class LazyAction(CommandGeneratorAction, CommandAction):
1062 """ 1063 A LazyAction is a kind of hybrid generator and command action for 1064 strings of the form "$VAR". These strings normally expand to other 1065 strings (think "$CCCOM" to "$CC -c -o $TARGET $SOURCE"), but we also 1066 want to be able to replace them with functions in the construction 1067 environment. Consequently, we want lazy evaluation and creation of 1068 an Action in the case of the function, but that's overkill in the more 1069 normal case of expansion to other strings. 1070 1071 So we do this with a subclass that's both a generator *and* 1072 a command action. The overridden methods all do a quick check 1073 of the construction variable, and if it's a string we just call 1074 the corresponding CommandAction method to do the heavy lifting. 1075 If not, then we call the same-named CommandGeneratorAction method. 1076 The CommandGeneratorAction methods work by using the overridden 1077 _generate() method, that is, our own way of handling "generation" of 1078 an action based on what's in the construction variable. 1079 """ 1080
1081 - def __init__(self, var, kw):
1082 if SCons.Debug.track_instances: logInstanceCreation(self, 'Action.LazyAction') 1083 CommandAction.__init__(self, '${'+var+'}', **kw) 1084 self.var = SCons.Util.to_String(var) 1085 self.gen_kw = kw
1086
1087 - def get_parent_class(self, env):
1088 c = env.get(self.var) 1089 if is_String(c) and not '\n' in c: 1090 return CommandAction 1091 return CommandGeneratorAction
1092
1093 - def _generate_cache(self, env):
1094 if env: 1095 c = env.get(self.var, '') 1096 else: 1097 c = '' 1098 gen_cmd = Action(c, **self.gen_kw) 1099 if not gen_cmd: 1100 raise SCons.Errors.UserError("$%s value %s cannot be used to create an Action." % (self.var, repr(c))) 1101 return gen_cmd
1102
1103 - def _generate(self, target, source, env, for_signature, executor=None):
1104 return self._generate_cache(env)
1105
1106 - def __call__(self, target, source, env, *args, **kw):
1107 c = self.get_parent_class(env) 1108 return c.__call__(self, target, source, env, *args, **kw)
1109
1110 - def get_presig(self, target, source, env):
1111 c = self.get_parent_class(env) 1112 return c.get_presig(self, target, source, env)
1113
1114 - def get_varlist(self, target, source, env, executor=None):
1115 c = self.get_parent_class(env) 1116 return c.get_varlist(self, target, source, env, executor)
1117 1118
1119 -class FunctionAction(_ActionAction):
1120 """Class for Python function actions.""" 1121
1122 - def __init__(self, execfunction, kw):
1123 if SCons.Debug.track_instances: logInstanceCreation(self, 'Action.FunctionAction') 1124 1125 self.execfunction = execfunction 1126 try: 1127 self.funccontents = _callable_contents(execfunction) 1128 except AttributeError: 1129 try: 1130 # See if execfunction will do the heavy lifting for us. 1131 self.gc = execfunction.get_contents 1132 except AttributeError: 1133 # This is weird, just do the best we can. 1134 self.funccontents = _object_contents(execfunction) 1135 1136 _ActionAction.__init__(self, **kw)
1137
1138 - def function_name(self):
1139 try: 1140 return self.execfunction.__name__ 1141 except AttributeError: 1142 try: 1143 return self.execfunction.__class__.__name__ 1144 except AttributeError: 1145 return "unknown_python_function"
1146
1147 - def strfunction(self, target, source, env, executor=None):
1148 if self.cmdstr is None: 1149 return None 1150 if self.cmdstr is not _null: 1151 from SCons.Subst import SUBST_RAW 1152 if executor: 1153 c = env.subst(self.cmdstr, SUBST_RAW, executor=executor) 1154 else: 1155 c = env.subst(self.cmdstr, SUBST_RAW, target, source) 1156 if c: 1157 return c 1158 1159 def array(a): 1160 def quote(s): 1161 try: 1162 str_for_display = s.str_for_display 1163 except AttributeError: 1164 s = repr(s) 1165 else: 1166 s = str_for_display() 1167 return s
1168 return '[' + ", ".join(map(quote, a)) + ']'
1169 try: 1170 strfunc = self.execfunction.strfunction 1171 except AttributeError: 1172 pass 1173 else: 1174 if strfunc is None: 1175 return None 1176 if callable(strfunc): 1177 return strfunc(target, source, env) 1178 name = self.function_name() 1179 tstr = array(target) 1180 sstr = array(source) 1181 return "%s(%s, %s)" % (name, tstr, sstr) 1182
1183 - def __str__(self):
1184 name = self.function_name() 1185 if name == 'ActionCaller': 1186 return str(self.execfunction) 1187 return "%s(target, source, env)" % name
1188
1189 - def execute(self, target, source, env, executor=None):
1190 exc_info = (None,None,None) 1191 try: 1192 if executor: 1193 target = executor.get_all_targets() 1194 source = executor.get_all_sources() 1195 rsources = list(map(rfile, source)) 1196 try: 1197 result = self.execfunction(target=target, source=rsources, env=env) 1198 except KeyboardInterrupt as e: 1199 raise 1200 except SystemExit as e: 1201 raise 1202 except Exception as e: 1203 result = e 1204 exc_info = sys.exc_info() 1205 1206 if result: 1207 result = SCons.Errors.convert_to_BuildError(result, exc_info) 1208 result.node=target 1209 result.action=self 1210 try: 1211 result.command=self.strfunction(target, source, env, executor) 1212 except TypeError: 1213 result.command=self.strfunction(target, source, env) 1214 1215 # FIXME: This maintains backward compatibility with respect to 1216 # which type of exceptions were returned by raising an 1217 # exception and which ones were returned by value. It would 1218 # probably be best to always return them by value here, but 1219 # some codes do not check the return value of Actions and I do 1220 # not have the time to modify them at this point. 1221 if (exc_info[1] and 1222 not isinstance(exc_info[1],EnvironmentError)): 1223 raise result 1224 1225 return result 1226 finally: 1227 # Break the cycle between the traceback object and this 1228 # function stack frame. See the sys.exc_info() doc info for 1229 # more information about this issue. 1230 del exc_info
1231
1232 - def get_presig(self, target, source, env):
1233 """Return the signature contents of this callable action.""" 1234 try: 1235 return self.gc(target, source, env) 1236 except AttributeError: 1237 return self.funccontents
1238
1239 - def get_implicit_deps(self, target, source, env):
1240 return []
1241
1242 -class ListAction(ActionBase):
1243 """Class for lists of other actions."""
1244 - def __init__(self, actionlist):
1245 if SCons.Debug.track_instances: logInstanceCreation(self, 'Action.ListAction') 1246 def list_of_actions(x): 1247 if isinstance(x, ActionBase): 1248 return x 1249 return Action(x)
1250 self.list = list(map(list_of_actions, actionlist)) 1251 # our children will have had any varlist 1252 # applied; we don't need to do it again 1253 self.varlist = () 1254 self.targets = '$TARGETS'
1255
1256 - def genstring(self, target, source, env):
1257 return '\n'.join([a.genstring(target, source, env) for a in self.list])
1258
1259 - def __str__(self):
1260 return '\n'.join(map(str, self.list))
1261
1262 - def presub_lines(self, env):
1263 return SCons.Util.flatten_sequence( 1264 [a.presub_lines(env) for a in self.list])
1265
1266 - def get_presig(self, target, source, env):
1267 """Return the signature contents of this action list. 1268 1269 Simple concatenation of the signatures of the elements. 1270 """ 1271 return b"".join([bytes(x.get_contents(target, source, env)) for x in self.list])
1272
1273 - def __call__(self, target, source, env, exitstatfunc=_null, presub=_null, 1274 show=_null, execute=_null, chdir=_null, executor=None):
1275 if executor: 1276 target = executor.get_all_targets() 1277 source = executor.get_all_sources() 1278 for act in self.list: 1279 stat = act(target, source, env, exitstatfunc, presub, 1280 show, execute, chdir, executor) 1281 if stat: 1282 return stat 1283 return 0
1284
1285 - def get_implicit_deps(self, target, source, env):
1286 result = [] 1287 for act in self.list: 1288 result.extend(act.get_implicit_deps(target, source, env)) 1289 return result
1290
1291 - def get_varlist(self, target, source, env, executor=None):
1292 result = SCons.Util.OrderedDict() 1293 for act in self.list: 1294 for var in act.get_varlist(target, source, env, executor): 1295 result[var] = True 1296 return list(result.keys())
1297 1298
1299 -class ActionCaller(object):
1300 """A class for delaying calling an Action function with specific 1301 (positional and keyword) arguments until the Action is actually 1302 executed. 1303 1304 This class looks to the rest of the world like a normal Action object, 1305 but what it's really doing is hanging on to the arguments until we 1306 have a target, source and env to use for the expansion. 1307 """
1308 - def __init__(self, parent, args, kw):
1309 self.parent = parent 1310 self.args = args 1311 self.kw = kw
1312
1313 - def get_contents(self, target, source, env):
1314 actfunc = self.parent.actfunc 1315 try: 1316 # "self.actfunc" is a function. 1317 contents = actfunc.__code__.co_code 1318 except AttributeError: 1319 # "self.actfunc" is a callable object. 1320 try: 1321 contents = actfunc.__call__.__func__.__code__.co_code 1322 except AttributeError: 1323 # No __call__() method, so it might be a builtin 1324 # or something like that. Do the best we can. 1325 contents = repr(actfunc) 1326 1327 return contents
1328
1329 - def subst(self, s, target, source, env):
1330 # If s is a list, recursively apply subst() 1331 # to every element in the list 1332 if is_List(s): 1333 result = [] 1334 for elem in s: 1335 result.append(self.subst(elem, target, source, env)) 1336 return self.parent.convert(result) 1337 1338 # Special-case hack: Let a custom function wrapped in an 1339 # ActionCaller get at the environment through which the action 1340 # was called by using this hard-coded value as a special return. 1341 if s == '$__env__': 1342 return env 1343 elif is_String(s): 1344 return env.subst(s, 1, target, source) 1345 return self.parent.convert(s)
1346
1347 - def subst_args(self, target, source, env):
1348 return [self.subst(x, target, source, env) for x in self.args]
1349
1350 - def subst_kw(self, target, source, env):
1351 kw = {} 1352 for key in list(self.kw.keys()): 1353 kw[key] = self.subst(self.kw[key], target, source, env) 1354 return kw
1355
1356 - def __call__(self, target, source, env, executor=None):
1357 args = self.subst_args(target, source, env) 1358 kw = self.subst_kw(target, source, env) 1359 return self.parent.actfunc(*args, **kw)
1360
1361 - def strfunction(self, target, source, env):
1362 args = self.subst_args(target, source, env) 1363 kw = self.subst_kw(target, source, env) 1364 return self.parent.strfunc(*args, **kw)
1365
1366 - def __str__(self):
1367 return self.parent.strfunc(*self.args, **self.kw)
1368 1369
1370 -class ActionFactory(object):
1371 """A factory class that will wrap up an arbitrary function 1372 as an SCons-executable Action object. 1373 1374 The real heavy lifting here is done by the ActionCaller class. 1375 We just collect the (positional and keyword) arguments that we're 1376 called with and give them to the ActionCaller object we create, 1377 so it can hang onto them until it needs them. 1378 """
1379 - def __init__(self, actfunc, strfunc, convert=lambda x: x):
1380 self.actfunc = actfunc 1381 self.strfunc = strfunc 1382 self.convert = convert
1383
1384 - def __call__(self, *args, **kw):
1385 ac = ActionCaller(self, args, kw) 1386 action = Action(ac, strfunction=ac.strfunction) 1387 return action
1388 1389 # Local Variables: 1390 # tab-width:4 1391 # indent-tabs-mode:nil 1392 # End: 1393 # vim: set expandtab tabstop=4 shiftwidth=4: 1394