1# MIT License 2# 3# Copyright The SCons Foundation 4# 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: 12# 13# The above copyright notice and this permission notice shall be included 14# in all copies or substantial portions of the Software. 15# 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. 23 24"""Base class for construction Environments. 25 26These are the primary objects used to communicate dependency and 27construction information to the build engine. 28 29Keyword arguments supplied when the construction Environment is created 30are construction variables used to initialize the Environment. 31""" 32 33import copy 34import os 35import sys 36import re 37import shlex 38from collections import UserDict 39 40import SCons.Action 41import SCons.Builder 42import SCons.Debug 43from SCons.Debug import logInstanceCreation 44import SCons.Defaults 45from SCons.Errors import UserError, BuildError 46import SCons.Memoize 47import SCons.Node 48import SCons.Node.Alias 49import SCons.Node.FS 50import SCons.Node.Python 51import SCons.Platform 52import SCons.SConf 53import SCons.SConsign 54import SCons.Subst 55import SCons.Tool 56import SCons.Warnings 57from SCons.Util import ( 58 AppendPath, 59 CLVar, 60 LogicalLines, 61 MethodWrapper, 62 PrependPath, 63 Split, 64 WhereIs, 65 flatten, 66 is_Dict, 67 is_List, 68 is_Sequence, 69 is_String, 70 is_Tuple, 71 semi_deepcopy, 72 semi_deepcopy_dict, 73 to_String_for_subst, 74 uniquer_hashables, 75) 76 77class _Null: 78 pass 79 80_null = _Null 81 82_warn_copy_deprecated = True 83_warn_source_signatures_deprecated = True 84_warn_target_signatures_deprecated = True 85 86CleanTargets = {} 87CalculatorArgs = {} 88 89def alias_builder(env, target, source): 90 pass 91 92AliasBuilder = SCons.Builder.Builder( 93 action=alias_builder, 94 target_factory=SCons.Node.Alias.default_ans.Alias, 95 source_factory=SCons.Node.FS.Entry, 96 multi=True, 97 is_explicit=None, 98 name='AliasBuilder', 99) 100 101def apply_tools(env, tools, toolpath): 102 # Store the toolpath in the Environment. 103 # This is expected to work even if no tools are given, so do this first. 104 if toolpath is not None: 105 env['toolpath'] = toolpath 106 if not tools: 107 return 108 109 # Filter out null tools from the list. 110 for tool in [_f for _f in tools if _f]: 111 if is_List(tool) or is_Tuple(tool): 112 # toolargs should be a dict of kw args 113 toolname, toolargs, *rest = tool 114 _ = env.Tool(toolname, **toolargs) 115 else: 116 _ = env.Tool(tool) 117 118# These names are (or will be) controlled by SCons; users should never 119# set or override them. The warning can optionally be turned off, 120# but scons will still ignore the illegal variable names even if it's off. 121reserved_construction_var_names = [ 122 'CHANGED_SOURCES', 123 'CHANGED_TARGETS', 124 'SOURCE', 125 'SOURCES', 126 'TARGET', 127 'TARGETS', 128 'UNCHANGED_SOURCES', 129 'UNCHANGED_TARGETS', 130] 131 132future_reserved_construction_var_names = [ 133 #'HOST_OS', 134 #'HOST_ARCH', 135 #'HOST_CPU', 136] 137 138def copy_non_reserved_keywords(dict): 139 result = semi_deepcopy(dict) 140 for k in result.copy().keys(): 141 if k in reserved_construction_var_names: 142 msg = "Ignoring attempt to set reserved variable `$%s'" 143 SCons.Warnings.warn(SCons.Warnings.ReservedVariableWarning, msg % k) 144 del result[k] 145 return result 146 147def _set_reserved(env, key, value): 148 msg = "Ignoring attempt to set reserved variable `$%s'" 149 SCons.Warnings.warn(SCons.Warnings.ReservedVariableWarning, msg % key) 150 151def _set_future_reserved(env, key, value): 152 env._dict[key] = value 153 msg = "`$%s' will be reserved in a future release and setting it will become ignored" 154 SCons.Warnings.warn(SCons.Warnings.FutureReservedVariableWarning, msg % key) 155 156def _set_BUILDERS(env, key, value): 157 try: 158 bd = env._dict[key] 159 for k in bd.copy().keys(): 160 del bd[k] 161 except KeyError: 162 bd = BuilderDict(bd, env) 163 env._dict[key] = bd 164 for k, v in value.items(): 165 if not SCons.Builder.is_a_Builder(v): 166 raise UserError('%s is not a Builder.' % repr(v)) 167 bd.update(value) 168 169def _del_SCANNERS(env, key): 170 del env._dict[key] 171 env.scanner_map_delete() 172 173def _set_SCANNERS(env, key, value): 174 env._dict[key] = value 175 env.scanner_map_delete() 176 177def _delete_duplicates(l, keep_last): 178 """Delete duplicates from a sequence, keeping the first or last.""" 179 seen=set() 180 result=[] 181 if keep_last: # reverse in & out, then keep first 182 l.reverse() 183 for i in l: 184 try: 185 if i not in seen: 186 result.append(i) 187 seen.add(i) 188 except TypeError: 189 # probably unhashable. Just keep it. 190 result.append(i) 191 if keep_last: 192 result.reverse() 193 return result 194 195 196 197# The following is partly based on code in a comment added by Peter 198# Shannon at the following page (there called the "transplant" class): 199# 200# ASPN : Python Cookbook : Dynamically added methods to a class 201# https://code.activestate.com/recipes/81732/ 202# 203# We had independently been using the idiom as BuilderWrapper, but 204# factoring out the common parts into this base class, and making 205# BuilderWrapper a subclass that overrides __call__() to enforce specific 206# Builder calling conventions, simplified some of our higher-layer code. 207# 208# Note: MethodWrapper moved to SCons.Util as it was needed there 209# and otherwise we had a circular import problem. 210 211class BuilderWrapper(MethodWrapper): 212 """ 213 A MethodWrapper subclass that that associates an environment with 214 a Builder. 215 216 This mainly exists to wrap the __call__() function so that all calls 217 to Builders can have their argument lists massaged in the same way 218 (treat a lone argument as the source, treat two arguments as target 219 then source, make sure both target and source are lists) without 220 having to have cut-and-paste code to do it. 221 222 As a bit of obsessive backwards compatibility, we also intercept 223 attempts to get or set the "env" or "builder" attributes, which were 224 the names we used before we put the common functionality into the 225 MethodWrapper base class. We'll keep this around for a while in case 226 people shipped Tool modules that reached into the wrapper (like the 227 Tool/qt.py module does, or did). There shouldn't be a lot attribute 228 fetching or setting on these, so a little extra work shouldn't hurt. 229 """ 230 def __call__(self, target=None, source=_null, *args, **kw): 231 if source is _null: 232 source = target 233 target = None 234 if target is not None and not is_List(target): 235 target = [target] 236 if source is not None and not is_List(source): 237 source = [source] 238 return super().__call__(target, source, *args, **kw) 239 240 def __repr__(self): 241 return '<BuilderWrapper %s>' % repr(self.name) 242 243 def __str__(self): 244 return self.__repr__() 245 246 def __getattr__(self, name): 247 if name == 'env': 248 return self.object 249 elif name == 'builder': 250 return self.method 251 else: 252 raise AttributeError(name) 253 254 def __setattr__(self, name, value): 255 if name == 'env': 256 self.object = value 257 elif name == 'builder': 258 self.method = value 259 else: 260 self.__dict__[name] = value 261 262 # This allows a Builder to be executed directly 263 # through the Environment to which it's attached. 264 # In practice, we shouldn't need this, because 265 # builders actually get executed through a Node. 266 # But we do have a unit test for this, and can't 267 # yet rule out that it would be useful in the 268 # future, so leave it for now. 269 #def execute(self, **kw): 270 # kw['env'] = self.env 271 # self.builder.execute(**kw) 272 273class BuilderDict(UserDict): 274 """This is a dictionary-like class used by an Environment to hold 275 the Builders. We need to do this because every time someone changes 276 the Builders in the Environment's BUILDERS dictionary, we must 277 update the Environment's attributes.""" 278 def __init__(self, dict, env): 279 # Set self.env before calling the superclass initialization, 280 # because it will end up calling our other methods, which will 281 # need to point the values in this dictionary to self.env. 282 self.env = env 283 UserDict.__init__(self, dict) 284 285 def __semi_deepcopy__(self): 286 # These cannot be copied since they would both modify the same builder object, and indeed 287 # just copying would modify the original builder 288 raise TypeError( 'cannot semi_deepcopy a BuilderDict' ) 289 290 def __setitem__(self, item, val): 291 try: 292 method = getattr(self.env, item).method 293 except AttributeError: 294 pass 295 else: 296 self.env.RemoveMethod(method) 297 UserDict.__setitem__(self, item, val) 298 BuilderWrapper(self.env, val, item) 299 300 def __delitem__(self, item): 301 UserDict.__delitem__(self, item) 302 delattr(self.env, item) 303 304 def update(self, dict): 305 for i, v in dict.items(): 306 self.__setitem__(i, v) 307 308 309 310_is_valid_var = re.compile(r'[_a-zA-Z]\w*$') 311 312def is_valid_construction_var(varstr): 313 """Return if the specified string is a legitimate construction 314 variable. 315 """ 316 return _is_valid_var.match(varstr) 317 318 319 320class SubstitutionEnvironment: 321 """Base class for different flavors of construction environments. 322 323 This class contains a minimal set of methods that handle construction 324 variable expansion and conversion of strings to Nodes, which may or 325 may not be actually useful as a stand-alone class. Which methods 326 ended up in this class is pretty arbitrary right now. They're 327 basically the ones which we've empirically determined are common to 328 the different construction environment subclasses, and most of the 329 others that use or touch the underlying dictionary of construction 330 variables. 331 332 Eventually, this class should contain all the methods that we 333 determine are necessary for a "minimal" interface to the build engine. 334 A full "native Python" SCons environment has gotten pretty heavyweight 335 with all of the methods and Tools and construction variables we've 336 jammed in there, so it would be nice to have a lighter weight 337 alternative for interfaces that don't need all of the bells and 338 whistles. (At some point, we'll also probably rename this class 339 "Base," since that more reflects what we want this class to become, 340 but because we've released comments that tell people to subclass 341 Environment.Base to create their own flavors of construction 342 environment, we'll save that for a future refactoring when this 343 class actually becomes useful.) 344 """ 345 346 def __init__(self, **kw): 347 """Initialization of an underlying SubstitutionEnvironment class. 348 """ 349 if SCons.Debug.track_instances: logInstanceCreation(self, 'Environment.SubstitutionEnvironment') 350 self.fs = SCons.Node.FS.get_default_fs() 351 self.ans = SCons.Node.Alias.default_ans 352 self.lookup_list = SCons.Node.arg2nodes_lookups 353 self._dict = kw.copy() 354 self._init_special() 355 self.added_methods = [] 356 #self._memo = {} 357 358 def _init_special(self): 359 """Initial the dispatch tables for special handling of 360 special construction variables.""" 361 self._special_del = {} 362 self._special_del['SCANNERS'] = _del_SCANNERS 363 364 self._special_set = {} 365 for key in reserved_construction_var_names: 366 self._special_set[key] = _set_reserved 367 for key in future_reserved_construction_var_names: 368 self._special_set[key] = _set_future_reserved 369 self._special_set['BUILDERS'] = _set_BUILDERS 370 self._special_set['SCANNERS'] = _set_SCANNERS 371 372 # Freeze the keys of self._special_set in a list for use by 373 # methods that need to check. 374 self._special_set_keys = list(self._special_set.keys()) 375 376 def __eq__(self, other): 377 return self._dict == other._dict 378 379 def __delitem__(self, key): 380 special = self._special_del.get(key) 381 if special: 382 special(self, key) 383 else: 384 del self._dict[key] 385 386 def __getitem__(self, key): 387 return self._dict[key] 388 389 def __setitem__(self, key, value): 390 # This is heavily used. This implementation is the best we have 391 # according to the timings in bench/env.__setitem__.py. 392 # 393 # The "key in self._special_set_keys" test here seems to perform 394 # pretty well for the number of keys we have. A hard-coded 395 # list worked a little better in Python 2.5, but that has the 396 # disadvantage of maybe getting out of sync if we ever add more 397 # variable names. 398 # So right now it seems like a good trade-off, but feel free to 399 # revisit this with bench/env.__setitem__.py as needed (and 400 # as newer versions of Python come out). 401 if key in self._special_set_keys: 402 self._special_set[key](self, key, value) 403 else: 404 # If we already have the entry, then it's obviously a valid 405 # key and we don't need to check. If we do check, using a 406 # global, pre-compiled regular expression directly is more 407 # efficient than calling another function or a method. 408 if key not in self._dict and not _is_valid_var.match(key): 409 raise UserError("Illegal construction variable `%s'" % key) 410 self._dict[key] = value 411 412 def get(self, key, default=None): 413 """Emulates the get() method of dictionaries.""" 414 return self._dict.get(key, default) 415 416 def __contains__(self, key): 417 return key in self._dict 418 419 def keys(self): 420 """Emulates the keys() method of dictionaries.""" 421 return self._dict.keys() 422 423 def values(self): 424 """Emulates the values() method of dictionaries.""" 425 return self._dict.values() 426 427 def items(self): 428 """Emulates the items() method of dictionaries.""" 429 return self._dict.items() 430 431 def setdefault(self, key, default=None): 432 """Emulates the setdefault() method of dictionaries.""" 433 return self._dict.setdefault(key, default) 434 435 def arg2nodes(self, args, node_factory=_null, lookup_list=_null, **kw): 436 if node_factory is _null: 437 node_factory = self.fs.File 438 if lookup_list is _null: 439 lookup_list = self.lookup_list 440 441 if not args: 442 return [] 443 444 args = flatten(args) 445 446 nodes = [] 447 for v in args: 448 if is_String(v): 449 n = None 450 for l in lookup_list: 451 n = l(v) 452 if n is not None: 453 break 454 if n is not None: 455 if is_String(n): 456 # n = self.subst(n, raw=1, **kw) 457 kw['raw'] = 1 458 n = self.subst(n, **kw) 459 if node_factory: 460 n = node_factory(n) 461 if is_List(n): 462 nodes.extend(n) 463 else: 464 nodes.append(n) 465 elif node_factory: 466 # v = node_factory(self.subst(v, raw=1, **kw)) 467 kw['raw'] = 1 468 v = node_factory(self.subst(v, **kw)) 469 if is_List(v): 470 nodes.extend(v) 471 else: 472 nodes.append(v) 473 else: 474 nodes.append(v) 475 476 return nodes 477 478 def gvars(self): 479 return self._dict 480 481 def lvars(self): 482 return {} 483 484 def subst(self, string, raw=0, target=None, source=None, conv=None, executor=None): 485 """Recursively interpolates construction variables from the 486 Environment into the specified string, returning the expanded 487 result. Construction variables are specified by a $ prefix 488 in the string and begin with an initial underscore or 489 alphabetic character followed by any number of underscores 490 or alphanumeric characters. The construction variable names 491 may be surrounded by curly braces to separate the name from 492 trailing characters. 493 """ 494 gvars = self.gvars() 495 lvars = self.lvars() 496 lvars['__env__'] = self 497 if executor: 498 lvars.update(executor.get_lvars()) 499 return SCons.Subst.scons_subst(string, self, raw, target, source, gvars, lvars, conv) 500 501 def subst_kw(self, kw, raw=0, target=None, source=None): 502 nkw = {} 503 for k, v in kw.items(): 504 k = self.subst(k, raw, target, source) 505 if is_String(v): 506 v = self.subst(v, raw, target, source) 507 nkw[k] = v 508 return nkw 509 510 def subst_list(self, string, raw=0, target=None, source=None, conv=None, executor=None): 511 """Calls through to SCons.Subst.scons_subst_list(). See 512 the documentation for that function.""" 513 gvars = self.gvars() 514 lvars = self.lvars() 515 lvars['__env__'] = self 516 if executor: 517 lvars.update(executor.get_lvars()) 518 return SCons.Subst.scons_subst_list(string, self, raw, target, source, gvars, lvars, conv) 519 520 def subst_path(self, path, target=None, source=None): 521 """Substitute a path list, turning EntryProxies into Nodes 522 and leaving Nodes (and other objects) as-is.""" 523 524 if not is_List(path): 525 path = [path] 526 527 def s(obj): 528 """This is the "string conversion" routine that we have our 529 substitutions use to return Nodes, not strings. This relies 530 on the fact that an EntryProxy object has a get() method that 531 returns the underlying Node that it wraps, which is a bit of 532 architectural dependence that we might need to break or modify 533 in the future in response to additional requirements.""" 534 try: 535 get = obj.get 536 except AttributeError: 537 obj = to_String_for_subst(obj) 538 else: 539 obj = get() 540 return obj 541 542 r = [] 543 for p in path: 544 if is_String(p): 545 p = self.subst(p, target=target, source=source, conv=s) 546 if is_List(p): 547 if len(p) == 1: 548 p = p[0] 549 else: 550 # We have an object plus a string, or multiple 551 # objects that we need to smush together. No choice 552 # but to make them into a string. 553 p = ''.join(map(to_String_for_subst, p)) 554 else: 555 p = s(p) 556 r.append(p) 557 return r 558 559 subst_target_source = subst 560 561 def backtick(self, command): 562 import subprocess 563 # common arguments 564 kw = { 'stdin' : 'devnull', 565 'stdout' : subprocess.PIPE, 566 'stderr' : subprocess.PIPE, 567 'universal_newlines' : True, 568 } 569 # if the command is a list, assume it's been quoted 570 # othewise force a shell 571 if not is_List(command): kw['shell'] = True 572 # run constructed command 573 p = SCons.Action._subproc(self, command, **kw) 574 out,err = p.communicate() 575 status = p.wait() 576 if err: 577 sys.stderr.write("" + err) 578 if status: 579 raise OSError("'%s' exited %d" % (command, status)) 580 return out 581 582 def AddMethod(self, function, name=None): 583 """ 584 Adds the specified function as a method of this construction 585 environment with the specified name. If the name is omitted, 586 the default name is the name of the function itself. 587 """ 588 method = MethodWrapper(self, function, name) 589 self.added_methods.append(method) 590 591 def RemoveMethod(self, function): 592 """ 593 Removes the specified function's MethodWrapper from the 594 added_methods list, so we don't re-bind it when making a clone. 595 """ 596 self.added_methods = [dm for dm in self.added_methods if dm.method is not function] 597 598 def Override(self, overrides): 599 """ 600 Produce a modified environment whose variables are overridden by 601 the overrides dictionaries. "overrides" is a dictionary that 602 will override the variables of this environment. 603 604 This function is much more efficient than Clone() or creating 605 a new Environment because it doesn't copy the construction 606 environment dictionary, it just wraps the underlying construction 607 environment, and doesn't even create a wrapper object if there 608 are no overrides. 609 """ 610 if not overrides: return self 611 o = copy_non_reserved_keywords(overrides) 612 if not o: return self 613 overrides = {} 614 merges = None 615 for key, value in o.items(): 616 if key == 'parse_flags': 617 merges = value 618 else: 619 overrides[key] = SCons.Subst.scons_subst_once(value, self, key) 620 env = OverrideEnvironment(self, overrides) 621 if merges: 622 env.MergeFlags(merges) 623 return env 624 625 def ParseFlags(self, *flags): 626 """Return a dict of parsed flags. 627 628 Parse ``flags`` and return a dict with the flags distributed into 629 the appropriate construction variable names. The flags are treated 630 as a typical set of command-line flags for a GNU-like toolchain, 631 such as might have been generated by one of the {foo}-config scripts, 632 and used to populate the entries based on knowledge embedded in 633 this method - the choices are not expected to be portable to other 634 toolchains. 635 636 If one of the ``flags`` strings begins with a bang (exclamation mark), 637 it is assumed to be a command and the rest of the string is executed; 638 the result of that evaluation is then added to the dict. 639 """ 640 dict = { 641 'ASFLAGS' : CLVar(''), 642 'CFLAGS' : CLVar(''), 643 'CCFLAGS' : CLVar(''), 644 'CXXFLAGS' : CLVar(''), 645 'CPPDEFINES' : [], 646 'CPPFLAGS' : CLVar(''), 647 'CPPPATH' : [], 648 'FRAMEWORKPATH' : CLVar(''), 649 'FRAMEWORKS' : CLVar(''), 650 'LIBPATH' : [], 651 'LIBS' : [], 652 'LINKFLAGS' : CLVar(''), 653 'RPATH' : [], 654 } 655 656 def do_parse(arg): 657 # if arg is a sequence, recurse with each element 658 if not arg: 659 return 660 661 if not is_String(arg): 662 for t in arg: do_parse(t) 663 return 664 665 # if arg is a command, execute it 666 if arg[0] == '!': 667 arg = self.backtick(arg[1:]) 668 669 # utility function to deal with -D option 670 def append_define(name, dict = dict): 671 t = name.split('=') 672 if len(t) == 1: 673 dict['CPPDEFINES'].append(name) 674 else: 675 dict['CPPDEFINES'].append([t[0], '='.join(t[1:])]) 676 677 # Loop through the flags and add them to the appropriate option. 678 # This tries to strike a balance between checking for all possible 679 # flags and keeping the logic to a finite size, so it doesn't 680 # check for some that don't occur often. It particular, if the 681 # flag is not known to occur in a config script and there's a way 682 # of passing the flag to the right place (by wrapping it in a -W 683 # flag, for example) we don't check for it. Note that most 684 # preprocessor options are not handled, since unhandled options 685 # are placed in CCFLAGS, so unless the preprocessor is invoked 686 # separately, these flags will still get to the preprocessor. 687 # Other options not currently handled: 688 # -iqoutedir (preprocessor search path) 689 # -u symbol (linker undefined symbol) 690 # -s (linker strip files) 691 # -static* (linker static binding) 692 # -shared* (linker dynamic binding) 693 # -symbolic (linker global binding) 694 # -R dir (deprecated linker rpath) 695 # IBM compilers may also accept -qframeworkdir=foo 696 697 params = shlex.split(arg) 698 append_next_arg_to = None # for multi-word args 699 for arg in params: 700 if append_next_arg_to: 701 if append_next_arg_to == 'CPPDEFINES': 702 append_define(arg) 703 elif append_next_arg_to == '-include': 704 t = ('-include', self.fs.File(arg)) 705 dict['CCFLAGS'].append(t) 706 elif append_next_arg_to == '-imacros': 707 t = ('-imacros', self.fs.File(arg)) 708 dict['CCFLAGS'].append(t) 709 elif append_next_arg_to == '-isysroot': 710 t = ('-isysroot', arg) 711 dict['CCFLAGS'].append(t) 712 dict['LINKFLAGS'].append(t) 713 elif append_next_arg_to == '-isystem': 714 t = ('-isystem', arg) 715 dict['CCFLAGS'].append(t) 716 elif append_next_arg_to == '-iquote': 717 t = ('-iquote', arg) 718 dict['CCFLAGS'].append(t) 719 elif append_next_arg_to == '-idirafter': 720 t = ('-idirafter', arg) 721 dict['CCFLAGS'].append(t) 722 elif append_next_arg_to == '-arch': 723 t = ('-arch', arg) 724 dict['CCFLAGS'].append(t) 725 dict['LINKFLAGS'].append(t) 726 elif append_next_arg_to == '--param': 727 t = ('--param', arg) 728 dict['CCFLAGS'].append(t) 729 else: 730 dict[append_next_arg_to].append(arg) 731 append_next_arg_to = None 732 elif not arg[0] in ['-', '+']: 733 dict['LIBS'].append(self.fs.File(arg)) 734 elif arg == '-dylib_file': 735 dict['LINKFLAGS'].append(arg) 736 append_next_arg_to = 'LINKFLAGS' 737 elif arg[:2] == '-L': 738 if arg[2:]: 739 dict['LIBPATH'].append(arg[2:]) 740 else: 741 append_next_arg_to = 'LIBPATH' 742 elif arg[:2] == '-l': 743 if arg[2:]: 744 dict['LIBS'].append(arg[2:]) 745 else: 746 append_next_arg_to = 'LIBS' 747 elif arg[:2] == '-I': 748 if arg[2:]: 749 dict['CPPPATH'].append(arg[2:]) 750 else: 751 append_next_arg_to = 'CPPPATH' 752 elif arg[:4] == '-Wa,': 753 dict['ASFLAGS'].append(arg[4:]) 754 dict['CCFLAGS'].append(arg) 755 elif arg[:4] == '-Wl,': 756 if arg[:11] == '-Wl,-rpath=': 757 dict['RPATH'].append(arg[11:]) 758 elif arg[:7] == '-Wl,-R,': 759 dict['RPATH'].append(arg[7:]) 760 elif arg[:6] == '-Wl,-R': 761 dict['RPATH'].append(arg[6:]) 762 else: 763 dict['LINKFLAGS'].append(arg) 764 elif arg[:4] == '-Wp,': 765 dict['CPPFLAGS'].append(arg) 766 elif arg[:2] == '-D': 767 if arg[2:]: 768 append_define(arg[2:]) 769 else: 770 append_next_arg_to = 'CPPDEFINES' 771 elif arg == '-framework': 772 append_next_arg_to = 'FRAMEWORKS' 773 elif arg[:14] == '-frameworkdir=': 774 dict['FRAMEWORKPATH'].append(arg[14:]) 775 elif arg[:2] == '-F': 776 if arg[2:]: 777 dict['FRAMEWORKPATH'].append(arg[2:]) 778 else: 779 append_next_arg_to = 'FRAMEWORKPATH' 780 elif arg in [ 781 '-mno-cygwin', 782 '-pthread', 783 '-openmp', 784 '-fmerge-all-constants', 785 '-fopenmp', 786 ]: 787 dict['CCFLAGS'].append(arg) 788 dict['LINKFLAGS'].append(arg) 789 elif arg == '-mwindows': 790 dict['LINKFLAGS'].append(arg) 791 elif arg[:5] == '-std=': 792 if '++' in arg[5:]: 793 key='CXXFLAGS' 794 else: 795 key='CFLAGS' 796 dict[key].append(arg) 797 elif arg[0] == '+': 798 dict['CCFLAGS'].append(arg) 799 dict['LINKFLAGS'].append(arg) 800 elif arg in [ 801 '-include', 802 '-imacros', 803 '-isysroot', 804 '-isystem', 805 '-iquote', 806 '-idirafter', 807 '-arch', 808 '--param', 809 ]: 810 append_next_arg_to = arg 811 else: 812 dict['CCFLAGS'].append(arg) 813 814 for arg in flags: 815 do_parse(arg) 816 return dict 817 818 def MergeFlags(self, args, unique=True): 819 """Merge flags into construction variables. 820 821 Merges the flags from ``args`` into this construction environent. 822 If ``args`` is not a dict, it is first converted to a dictionary with 823 flags distributed into appropriate construction variables. 824 See :meth:`ParseFlags`. 825 826 Args: 827 args: flags to merge 828 unique: merge flags rather than appending (default: True) 829 830 """ 831 if not is_Dict(args): 832 args = self.ParseFlags(args) 833 834 if not unique: 835 self.Append(**args) 836 return 837 838 for key, value in args.items(): 839 if not value: 840 continue 841 value = Split(value) 842 try: 843 orig = self[key] 844 except KeyError: 845 orig = value 846 else: 847 if not orig: 848 orig = value 849 elif value: 850 # Add orig and value. The logic here was lifted from 851 # part of env.Append() (see there for a lot of comments 852 # about the order in which things are tried) and is 853 # used mainly to handle coercion of strings to CLVar to 854 # "do the right thing" given (e.g.) an original CCFLAGS 855 # string variable like '-pipe -Wall'. 856 try: 857 orig = orig + value 858 except (KeyError, TypeError): 859 try: 860 add_to_orig = orig.append 861 except AttributeError: 862 value.insert(0, orig) 863 orig = value 864 else: 865 add_to_orig(value) 866 t = [] 867 if key[-4:] == 'PATH': 868 ### keep left-most occurence 869 for v in orig: 870 if v not in t: 871 t.append(v) 872 else: 873 ### keep right-most occurence 874 for v in orig[::-1]: 875 if v not in t: 876 t.insert(0, v) 877 self[key] = t 878 879 880def default_decide_source(dependency, target, prev_ni, repo_node=None): 881 f = SCons.Defaults.DefaultEnvironment().decide_source 882 return f(dependency, target, prev_ni, repo_node) 883 884 885def default_decide_target(dependency, target, prev_ni, repo_node=None): 886 f = SCons.Defaults.DefaultEnvironment().decide_target 887 return f(dependency, target, prev_ni, repo_node) 888 889 890def default_copy_from_cache(env, src, dst): 891 return SCons.CacheDir.CacheDir.copy_from_cache(env, src, dst) 892 893 894def default_copy_to_cache(env, src, dst): 895 return SCons.CacheDir.CacheDir.copy_to_cache(env, src, dst) 896 897 898class Base(SubstitutionEnvironment): 899 """Base class for "real" construction Environments. 900 901 These are the primary objects used to communicate dependency 902 and construction information to the build engine. 903 904 Keyword arguments supplied when the construction Environment 905 is created are construction variables used to initialize the 906 Environment. 907 """ 908 909 ####################################################################### 910 # This is THE class for interacting with the SCons build engine, 911 # and it contains a lot of stuff, so we're going to try to keep this 912 # a little organized by grouping the methods. 913 ####################################################################### 914 915 ####################################################################### 916 # Methods that make an Environment act like a dictionary. These have 917 # the expected standard names for Python mapping objects. Note that 918 # we don't actually make an Environment a subclass of UserDict for 919 # performance reasons. Note also that we only supply methods for 920 # dictionary functionality that we actually need and use. 921 ####################################################################### 922 923 def __init__( 924 self, 925 platform=None, 926 tools=None, 927 toolpath=None, 928 variables=None, 929 parse_flags=None, 930 **kw 931 ): 932 """Initialization of a basic SCons construction environment. 933 934 Sets up special construction variables like BUILDER, 935 PLATFORM, etc., and searches for and applies available Tools. 936 937 Note that we do *not* call the underlying base class 938 (SubsitutionEnvironment) initialization, because we need to 939 initialize things in a very specific order that doesn't work 940 with the much simpler base class initialization. 941 """ 942 if SCons.Debug.track_instances: logInstanceCreation(self, 'Environment.Base') 943 self._memo = {} 944 self.fs = SCons.Node.FS.get_default_fs() 945 self.ans = SCons.Node.Alias.default_ans 946 self.lookup_list = SCons.Node.arg2nodes_lookups 947 self._dict = semi_deepcopy(SCons.Defaults.ConstructionEnvironment) 948 self._init_special() 949 self.added_methods = [] 950 951 # We don't use AddMethod, or define these as methods in this 952 # class, because we *don't* want these functions to be bound 953 # methods. They need to operate independently so that the 954 # settings will work properly regardless of whether a given 955 # target ends up being built with a Base environment or an 956 # OverrideEnvironment or what have you. 957 self.decide_target = default_decide_target 958 self.decide_source = default_decide_source 959 960 self.cache_timestamp_newer = False 961 962 self._dict['BUILDERS'] = BuilderDict(self._dict['BUILDERS'], self) 963 964 if platform is None: 965 platform = self._dict.get('PLATFORM', None) 966 if platform is None: 967 platform = SCons.Platform.Platform() 968 if is_String(platform): 969 platform = SCons.Platform.Platform(platform) 970 self._dict['PLATFORM'] = str(platform) 971 platform(self) 972 973 self._dict['HOST_OS'] = self._dict.get('HOST_OS',None) 974 self._dict['HOST_ARCH'] = self._dict.get('HOST_ARCH',None) 975 976 # Now set defaults for TARGET_{OS|ARCH} 977 self._dict['TARGET_OS'] = self._dict.get('TARGET_OS',None) 978 self._dict['TARGET_ARCH'] = self._dict.get('TARGET_ARCH',None) 979 980 981 # Apply the passed-in and customizable variables to the 982 # environment before calling the tools, because they may use 983 # some of them during initialization. 984 if 'options' in kw: 985 # Backwards compatibility: they may stll be using the 986 # old "options" keyword. 987 variables = kw['options'] 988 del kw['options'] 989 self.Replace(**kw) 990 keys = list(kw.keys()) 991 if variables: 992 keys = keys + list(variables.keys()) 993 variables.Update(self) 994 995 save = {} 996 for k in keys: 997 try: 998 save[k] = self._dict[k] 999 except KeyError: 1000 # No value may have been set if they tried to pass in a 1001 # reserved variable name like TARGETS. 1002 pass 1003 1004 SCons.Tool.Initializers(self) 1005 1006 if tools is None: 1007 tools = self._dict.get('TOOLS', None) 1008 if tools is None: 1009 tools = ['default'] 1010 apply_tools(self, tools, toolpath) 1011 1012 # Now restore the passed-in and customized variables 1013 # to the environment, since the values the user set explicitly 1014 # should override any values set by the tools. 1015 for key, val in save.items(): 1016 self._dict[key] = val 1017 1018 # Finally, apply any flags to be merged in 1019 if parse_flags: 1020 self.MergeFlags(parse_flags) 1021 1022 ####################################################################### 1023 # Utility methods that are primarily for internal use by SCons. 1024 # These begin with lower-case letters. 1025 ####################################################################### 1026 1027 def get_builder(self, name): 1028 """Fetch the builder with the specified name from the environment. 1029 """ 1030 try: 1031 return self._dict['BUILDERS'][name] 1032 except KeyError: 1033 return None 1034 1035 def validate_CacheDir_class(self, custom_class=None): 1036 """Validate the passed custom CacheDir class, or if no args are passed, 1037 validate the custom CacheDir class from the environment. 1038 """ 1039 1040 if custom_class is None: 1041 custom_class = self.get("CACHEDIR_CLASS", SCons.CacheDir.CacheDir) 1042 if not issubclass(custom_class, SCons.CacheDir.CacheDir): 1043 raise UserError("Custom CACHEDIR_CLASS %s not derived from CacheDir" % str(custom_class)) 1044 return custom_class 1045 1046 def get_CacheDir(self): 1047 try: 1048 path = self._CacheDir_path 1049 except AttributeError: 1050 path = SCons.Defaults.DefaultEnvironment()._CacheDir_path 1051 1052 cachedir_class = self.validate_CacheDir_class() 1053 try: 1054 if (path == self._last_CacheDir_path 1055 # this checks if the cachedir class type has changed from what the 1056 # instantiated cache dir type is. If the are exactly the same we 1057 # can just keep using the existing one, otherwise the user is requesting 1058 # something new, so we will re-instantiate below. 1059 and type(self._last_CacheDir) is cachedir_class): 1060 return self._last_CacheDir 1061 except AttributeError: 1062 pass 1063 1064 cd = cachedir_class(path) 1065 self._last_CacheDir_path = path 1066 self._last_CacheDir = cd 1067 return cd 1068 1069 def get_factory(self, factory, default='File'): 1070 """Return a factory function for creating Nodes for this 1071 construction environment. 1072 """ 1073 name = default 1074 try: 1075 is_node = issubclass(factory, SCons.Node.FS.Base) 1076 except TypeError: 1077 # The specified factory isn't a Node itself--it's 1078 # most likely None, or possibly a callable. 1079 pass 1080 else: 1081 if is_node: 1082 # The specified factory is a Node (sub)class. Try to 1083 # return the FS method that corresponds to the Node's 1084 # name--that is, we return self.fs.Dir if they want a Dir, 1085 # self.fs.File for a File, etc. 1086 try: name = factory.__name__ 1087 except AttributeError: pass 1088 else: factory = None 1089 if not factory: 1090 # They passed us None, or we picked up a name from a specified 1091 # class, so return the FS method. (Note that we *don't* 1092 # use our own self.{Dir,File} methods because that would 1093 # cause env.subst() to be called twice on the file name, 1094 # interfering with files that have $$ in them.) 1095 factory = getattr(self.fs, name) 1096 return factory 1097 1098 @SCons.Memoize.CountMethodCall 1099 def _gsm(self): 1100 try: 1101 return self._memo['_gsm'] 1102 except KeyError: 1103 pass 1104 1105 result = {} 1106 1107 try: 1108 scanners = self._dict['SCANNERS'] 1109 except KeyError: 1110 pass 1111 else: 1112 # Reverse the scanner list so that, if multiple scanners 1113 # claim they can scan the same suffix, earlier scanners 1114 # in the list will overwrite later scanners, so that 1115 # the result looks like a "first match" to the user. 1116 if not is_List(scanners): 1117 scanners = [scanners] 1118 else: 1119 scanners = scanners[:] # copy so reverse() doesn't mod original 1120 scanners.reverse() 1121 for scanner in scanners: 1122 for k in scanner.get_skeys(self): 1123 if k and self['PLATFORM'] == 'win32': 1124 k = k.lower() 1125 result[k] = scanner 1126 1127 self._memo['_gsm'] = result 1128 1129 return result 1130 1131 def get_scanner(self, skey): 1132 """Find the appropriate scanner given a key (usually a file suffix). 1133 """ 1134 if skey and self['PLATFORM'] == 'win32': 1135 skey = skey.lower() 1136 return self._gsm().get(skey) 1137 1138 def scanner_map_delete(self, kw=None): 1139 """Delete the cached scanner map (if we need to). 1140 """ 1141 try: 1142 del self._memo['_gsm'] 1143 except KeyError: 1144 pass 1145 1146 def _update(self, other): 1147 """Private method to update an environment's consvar dict directly. 1148 1149 Bypasses the normal checks that occur when users try to set items. 1150 """ 1151 self._dict.update(other) 1152 1153 def _update_onlynew(self, other): 1154 """Private method to add new items to an environment's consvar dict. 1155 1156 Only adds items from `other` whose keys do not already appear in 1157 the existing dict; values from `other` are not used for replacement. 1158 Bypasses the normal checks that occur when users try to set items. 1159 """ 1160 for k, v in other.items(): 1161 if k not in self._dict: 1162 self._dict[k] = v 1163 1164 1165 def get_src_sig_type(self): 1166 try: 1167 return self.src_sig_type 1168 except AttributeError: 1169 t = SCons.Defaults.DefaultEnvironment().src_sig_type 1170 self.src_sig_type = t 1171 return t 1172 1173 def get_tgt_sig_type(self): 1174 try: 1175 return self.tgt_sig_type 1176 except AttributeError: 1177 t = SCons.Defaults.DefaultEnvironment().tgt_sig_type 1178 self.tgt_sig_type = t 1179 return t 1180 1181 ####################################################################### 1182 # Public methods for manipulating an Environment. These begin with 1183 # upper-case letters. The essential characteristic of methods in 1184 # this section is that they do *not* have corresponding same-named 1185 # global functions. For example, a stand-alone Append() function 1186 # makes no sense, because Append() is all about appending values to 1187 # an Environment's construction variables. 1188 ####################################################################### 1189 1190 def Append(self, **kw): 1191 """Append values to construction variables in an Environment. 1192 1193 The variable is created if it is not already present. 1194 """ 1195 1196 kw = copy_non_reserved_keywords(kw) 1197 for key, val in kw.items(): 1198 try: 1199 if key == 'CPPDEFINES' and is_String(self._dict[key]): 1200 self._dict[key] = [self._dict[key]] 1201 orig = self._dict[key] 1202 except KeyError: 1203 # No existing var in the environment, so set to the new value. 1204 if key == 'CPPDEFINES' and is_String(val): 1205 self._dict[key] = [val] 1206 else: 1207 self._dict[key] = val 1208 continue 1209 1210 try: 1211 # Check if the original looks like a dict: has .update? 1212 update_dict = orig.update 1213 except AttributeError: 1214 try: 1215 # Just try to add them together. This will work 1216 # in most cases, when the original and new values 1217 # are compatible types. 1218 self._dict[key] = orig + val 1219 except (KeyError, TypeError): 1220 try: 1221 # Check if the original is a list: has .append? 1222 add_to_orig = orig.append 1223 except AttributeError: 1224 # The original isn't a list, but the new 1225 # value is (by process of elimination), 1226 # so insert the original in the new value 1227 # (if there's one to insert) and replace 1228 # the variable with it. 1229 if orig: 1230 val.insert(0, orig) 1231 self._dict[key] = val 1232 else: 1233 # The original is a list, so append the new 1234 # value to it (if there's a value to append). 1235 if val: 1236 add_to_orig(val) 1237 continue 1238 1239 # The original looks like a dictionary, so update it 1240 # based on what we think the value looks like. 1241 # We can't just try adding the value because 1242 # dictionaries don't have __add__() methods, and 1243 # things like UserList will incorrectly coerce the 1244 # original dict to a list (which we don't want). 1245 if is_List(val): 1246 if key == 'CPPDEFINES': 1247 tmp = [] 1248 for (k, v) in orig.items(): 1249 if v is not None: 1250 tmp.append((k, v)) 1251 else: 1252 tmp.append((k,)) 1253 orig = tmp 1254 orig += val 1255 self._dict[key] = orig 1256 else: 1257 for v in val: 1258 orig[v] = None 1259 else: 1260 try: 1261 update_dict(val) 1262 except (AttributeError, TypeError, ValueError): 1263 if is_Dict(val): 1264 for k, v in val.items(): 1265 orig[k] = v 1266 else: 1267 orig[val] = None 1268 1269 self.scanner_map_delete(kw) 1270 1271 def _canonicalize(self, path): 1272 """Allow Dirs and strings beginning with # for top-relative. 1273 1274 Note this uses the current env's fs (in self). 1275 """ 1276 if not is_String(path): # typically a Dir 1277 path = str(path) 1278 if path and path[0] == '#': 1279 path = str(self.fs.Dir(path)) 1280 return path 1281 1282 def AppendENVPath(self, name, newpath, envname = 'ENV', 1283 sep = os.pathsep, delete_existing=0): 1284 """Append path elements to the path 'name' in the 'ENV' 1285 dictionary for this environment. Will only add any particular 1286 path once, and will normpath and normcase all paths to help 1287 assure this. This can also handle the case where the env 1288 variable is a list instead of a string. 1289 1290 If delete_existing is 0, a newpath which is already in the path 1291 will not be moved to the end (it will be left where it is). 1292 """ 1293 1294 orig = '' 1295 if envname in self._dict and name in self._dict[envname]: 1296 orig = self._dict[envname][name] 1297 1298 nv = AppendPath(orig, newpath, sep, delete_existing, canonicalize=self._canonicalize) 1299 1300 if envname not in self._dict: 1301 self._dict[envname] = {} 1302 1303 self._dict[envname][name] = nv 1304 1305 def AppendUnique(self, delete_existing=0, **kw): 1306 """Append values to existing construction variables 1307 in an Environment, if they're not already there. 1308 If delete_existing is 1, removes existing values first, so 1309 values move to end. 1310 """ 1311 kw = copy_non_reserved_keywords(kw) 1312 for key, val in kw.items(): 1313 if is_List(val): 1314 val = _delete_duplicates(val, delete_existing) 1315 if key not in self._dict or self._dict[key] in ('', None): 1316 self._dict[key] = val 1317 elif is_Dict(self._dict[key]) and is_Dict(val): 1318 self._dict[key].update(val) 1319 elif is_List(val): 1320 dk = self._dict[key] 1321 if key == 'CPPDEFINES': 1322 tmp = [] 1323 for i in val: 1324 if is_List(i): 1325 if len(i) >= 2: 1326 tmp.append((i[0], i[1])) 1327 else: 1328 tmp.append((i[0],)) 1329 elif is_Tuple(i): 1330 tmp.append(i) 1331 else: 1332 tmp.append((i,)) 1333 val = tmp 1334 # Construct a list of (key, value) tuples. 1335 if is_Dict(dk): 1336 tmp = [] 1337 for (k, v) in dk.items(): 1338 if v is not None: 1339 tmp.append((k, v)) 1340 else: 1341 tmp.append((k,)) 1342 dk = tmp 1343 elif is_String(dk): 1344 dk = [(dk,)] 1345 else: 1346 tmp = [] 1347 for i in dk: 1348 if is_List(i): 1349 if len(i) >= 2: 1350 tmp.append((i[0], i[1])) 1351 else: 1352 tmp.append((i[0],)) 1353 elif is_Tuple(i): 1354 tmp.append(i) 1355 else: 1356 tmp.append((i,)) 1357 dk = tmp 1358 else: 1359 if not is_List(dk): 1360 dk = [dk] 1361 if delete_existing: 1362 dk = [x for x in dk if x not in val] 1363 else: 1364 val = [x for x in val if x not in dk] 1365 self._dict[key] = dk + val 1366 else: 1367 dk = self._dict[key] 1368 if is_List(dk): 1369 if key == 'CPPDEFINES': 1370 tmp = [] 1371 for i in dk: 1372 if is_List(i): 1373 if len(i) >= 2: 1374 tmp.append((i[0], i[1])) 1375 else: 1376 tmp.append((i[0],)) 1377 elif is_Tuple(i): 1378 tmp.append(i) 1379 else: 1380 tmp.append((i,)) 1381 dk = tmp 1382 # Construct a list of (key, value) tuples. 1383 if is_Dict(val): 1384 tmp = [] 1385 for (k, v) in val.items(): 1386 if v is not None: 1387 tmp.append((k, v)) 1388 else: 1389 tmp.append((k,)) 1390 val = tmp 1391 elif is_String(val): 1392 val = [(val,)] 1393 if delete_existing: 1394 dk = list(filter(lambda x, val=val: x not in val, dk)) 1395 self._dict[key] = dk + val 1396 else: 1397 dk = [x for x in dk if x not in val] 1398 self._dict[key] = dk + val 1399 else: 1400 # By elimination, val is not a list. Since dk is a 1401 # list, wrap val in a list first. 1402 if delete_existing: 1403 dk = list(filter(lambda x, val=val: x not in val, dk)) 1404 self._dict[key] = dk + [val] 1405 else: 1406 if val not in dk: 1407 self._dict[key] = dk + [val] 1408 else: 1409 if key == 'CPPDEFINES': 1410 if is_String(dk): 1411 dk = [dk] 1412 elif is_Dict(dk): 1413 tmp = [] 1414 for (k, v) in dk.items(): 1415 if v is not None: 1416 tmp.append((k, v)) 1417 else: 1418 tmp.append((k,)) 1419 dk = tmp 1420 if is_String(val): 1421 if val in dk: 1422 val = [] 1423 else: 1424 val = [val] 1425 elif is_Dict(val): 1426 tmp = [] 1427 for i,j in val.items(): 1428 if j is not None: 1429 tmp.append((i,j)) 1430 else: 1431 tmp.append(i) 1432 val = tmp 1433 if delete_existing: 1434 dk = [x for x in dk if x not in val] 1435 self._dict[key] = dk + val 1436 self.scanner_map_delete(kw) 1437 1438 def Clone(self, tools=[], toolpath=None, parse_flags = None, **kw): 1439 """Return a copy of a construction Environment. 1440 1441 The copy is like a Python "deep copy"--that is, independent 1442 copies are made recursively of each objects--except that 1443 a reference is copied when an object is not deep-copyable 1444 (like a function). There are no references to any mutable 1445 objects in the original Environment. 1446 """ 1447 1448 builders = self._dict.get('BUILDERS', {}) 1449 1450 clone = copy.copy(self) 1451 # BUILDERS is not safe to do a simple copy 1452 clone._dict = semi_deepcopy_dict(self._dict, ['BUILDERS']) 1453 clone._dict['BUILDERS'] = BuilderDict(builders, clone) 1454 1455 # Check the methods added via AddMethod() and re-bind them to 1456 # the cloned environment. Only do this if the attribute hasn't 1457 # been overwritten by the user explicitly and still points to 1458 # the added method. 1459 clone.added_methods = [] 1460 for mw in self.added_methods: 1461 if mw == getattr(self, mw.name): 1462 clone.added_methods.append(mw.clone(clone)) 1463 1464 clone._memo = {} 1465 1466 # Apply passed-in variables before the tools 1467 # so the tools can use the new variables 1468 kw = copy_non_reserved_keywords(kw) 1469 new = {} 1470 for key, value in kw.items(): 1471 new[key] = SCons.Subst.scons_subst_once(value, self, key) 1472 clone.Replace(**new) 1473 1474 apply_tools(clone, tools, toolpath) 1475 1476 # apply them again in case the tools overwrote them 1477 clone.Replace(**new) 1478 1479 # Finally, apply any flags to be merged in 1480 if parse_flags: 1481 clone.MergeFlags(parse_flags) 1482 1483 if SCons.Debug.track_instances: logInstanceCreation(self, 'Environment.EnvironmentClone') 1484 return clone 1485 1486 def _changed_build(self, dependency, target, prev_ni, repo_node=None): 1487 if dependency.changed_state(target, prev_ni, repo_node): 1488 return 1 1489 return self.decide_source(dependency, target, prev_ni, repo_node) 1490 1491 def _changed_content(self, dependency, target, prev_ni, repo_node=None): 1492 return dependency.changed_content(target, prev_ni, repo_node) 1493 1494 def _changed_source(self, dependency, target, prev_ni, repo_node=None): 1495 target_env = dependency.get_build_env() 1496 type = target_env.get_tgt_sig_type() 1497 if type == 'source': 1498 return target_env.decide_source(dependency, target, prev_ni, repo_node) 1499 else: 1500 return target_env.decide_target(dependency, target, prev_ni, repo_node) 1501 1502 def _changed_timestamp_then_content(self, dependency, target, prev_ni, repo_node=None): 1503 return dependency.changed_timestamp_then_content(target, prev_ni, repo_node) 1504 1505 def _changed_timestamp_newer(self, dependency, target, prev_ni, repo_node=None): 1506 return dependency.changed_timestamp_newer(target, prev_ni, repo_node) 1507 1508 def _changed_timestamp_match(self, dependency, target, prev_ni, repo_node=None): 1509 return dependency.changed_timestamp_match(target, prev_ni, repo_node) 1510 1511 def Decider(self, function): 1512 self.cache_timestamp_newer = False 1513 if function in ('MD5', 'content'): 1514 # TODO: Handle if user requests MD5 and not content with deprecation notice 1515 function = self._changed_content 1516 elif function in ('MD5-timestamp', 'content-timestamp'): 1517 function = self._changed_timestamp_then_content 1518 elif function in ('timestamp-newer', 'make'): 1519 function = self._changed_timestamp_newer 1520 self.cache_timestamp_newer = True 1521 elif function == 'timestamp-match': 1522 function = self._changed_timestamp_match 1523 elif not callable(function): 1524 raise UserError("Unknown Decider value %s" % repr(function)) 1525 1526 # We don't use AddMethod because we don't want to turn the 1527 # function, which only expects three arguments, into a bound 1528 # method, which would add self as an initial, fourth argument. 1529 self.decide_target = function 1530 self.decide_source = function 1531 1532 1533 def Detect(self, progs): 1534 """Return the first available program from one or more possibilities. 1535 1536 Args: 1537 progs (str or list): one or more command names to check for 1538 1539 """ 1540 if not is_List(progs): 1541 progs = [progs] 1542 for prog in progs: 1543 path = self.WhereIs(prog) 1544 if path: return prog 1545 return None 1546 1547 1548 def Dictionary(self, *args): 1549 r"""Return construction variables from an environment. 1550 1551 Args: 1552 \*args (optional): variable names to look up 1553 1554 Returns: 1555 If `args` omitted, the dictionary of all construction variables. 1556 If one arg, the corresponding value is returned. 1557 If more than one arg, a list of values is returned. 1558 1559 Raises: 1560 KeyError: if any of `args` is not in the construction environment. 1561 1562 """ 1563 if not args: 1564 return self._dict 1565 dlist = [self._dict[x] for x in args] 1566 if len(dlist) == 1: 1567 dlist = dlist[0] 1568 return dlist 1569 1570 1571 def Dump(self, key=None, format='pretty'): 1572 """ Return construction variables serialized to a string. 1573 1574 Args: 1575 key (optional): if None, format the whole dict of variables. 1576 Else format the value of `key` (Default value = None) 1577 format (str, optional): specify the format to serialize to. 1578 `"pretty"` generates a pretty-printed string, 1579 `"json"` a JSON-formatted string. 1580 (Default value = `"pretty"`) 1581 1582 """ 1583 if key: 1584 cvars = self.Dictionary(key) 1585 else: 1586 cvars = self.Dictionary() 1587 1588 fmt = format.lower() 1589 1590 if fmt == 'pretty': 1591 import pprint 1592 pp = pprint.PrettyPrinter(indent=2) 1593 1594 # TODO: pprint doesn't do a nice job on path-style values 1595 # if the paths contain spaces (i.e. Windows), because the 1596 # algorithm tries to break lines on spaces, while breaking 1597 # on the path-separator would be more "natural". Is there 1598 # a better way to format those? 1599 return pp.pformat(cvars) 1600 1601 elif fmt == 'json': 1602 import json 1603 def non_serializable(obj): 1604 return str(type(obj).__qualname__) 1605 return json.dumps(cvars, indent=4, default=non_serializable) 1606 else: 1607 raise ValueError("Unsupported serialization format: %s." % fmt) 1608 1609 1610 def FindIxes(self, paths, prefix, suffix): 1611 """Search a list of paths for something that matches the prefix and suffix. 1612 1613 Args: 1614 paths: the list of paths or nodes. 1615 prefix: construction variable for the prefix. 1616 suffix: construction variable for the suffix. 1617 1618 Returns: the matched path or None 1619 1620 """ 1621 1622 suffix = self.subst('$'+suffix) 1623 prefix = self.subst('$'+prefix) 1624 1625 for path in paths: 1626 name = os.path.basename(str(path)) 1627 if name[:len(prefix)] == prefix and name[-len(suffix):] == suffix: 1628 return path 1629 1630 def ParseConfig(self, command, function=None, unique=True): 1631 """ 1632 Use the specified function to parse the output of the command 1633 in order to modify the current environment. The 'command' can 1634 be a string or a list of strings representing a command and 1635 its arguments. 'Function' is an optional argument that takes 1636 the environment, the output of the command, and the unique flag. 1637 If no function is specified, MergeFlags, which treats the output 1638 as the result of a typical 'X-config' command (i.e. gtk-config), 1639 will merge the output into the appropriate variables. 1640 """ 1641 if function is None: 1642 def parse_conf(env, cmd, unique=unique): 1643 return env.MergeFlags(cmd, unique) 1644 function = parse_conf 1645 if is_List(command): 1646 command = ' '.join(command) 1647 command = self.subst(command) 1648 return function(self, self.backtick(command)) 1649 1650 def ParseDepends(self, filename, must_exist=None, only_one=False): 1651 """ 1652 Parse a mkdep-style file for explicit dependencies. This is 1653 completely abusable, and should be unnecessary in the "normal" 1654 case of proper SCons configuration, but it may help make 1655 the transition from a Make hierarchy easier for some people 1656 to swallow. It can also be genuinely useful when using a tool 1657 that can write a .d file, but for which writing a scanner would 1658 be too complicated. 1659 """ 1660 filename = self.subst(filename) 1661 try: 1662 with open(filename, 'r') as fp: 1663 lines = LogicalLines(fp).readlines() 1664 except IOError: 1665 if must_exist: 1666 raise 1667 return 1668 lines = [l for l in lines if l[0] != '#'] 1669 tdlist = [] 1670 for line in lines: 1671 try: 1672 target, depends = line.split(':', 1) 1673 except (AttributeError, ValueError): 1674 # Throws AttributeError if line isn't a string. Can throw 1675 # ValueError if line doesn't split into two or more elements. 1676 pass 1677 else: 1678 tdlist.append((target.split(), depends.split())) 1679 if only_one: 1680 targets = [] 1681 for td in tdlist: 1682 targets.extend(td[0]) 1683 if len(targets) > 1: 1684 raise UserError( 1685 "More than one dependency target found in `%s': %s" 1686 % (filename, targets)) 1687 for target, depends in tdlist: 1688 self.Depends(target, depends) 1689 1690 def Platform(self, platform): 1691 platform = self.subst(platform) 1692 return SCons.Platform.Platform(platform)(self) 1693 1694 def Prepend(self, **kw): 1695 """Prepend values to construction variables in an Environment. 1696 1697 The variable is created if it is not already present. 1698 """ 1699 1700 kw = copy_non_reserved_keywords(kw) 1701 for key, val in kw.items(): 1702 try: 1703 orig = self._dict[key] 1704 except KeyError: 1705 # No existing var in the environment so set to the new value. 1706 self._dict[key] = val 1707 continue 1708 1709 try: 1710 # Check if the original looks like a dict: has .update? 1711 update_dict = orig.update 1712 except AttributeError: 1713 try: 1714 # Just try to add them together. This will work 1715 # in most cases, when the original and new values 1716 # are compatible types. 1717 self._dict[key] = val + orig 1718 except (KeyError, TypeError): 1719 try: 1720 # Check if the added value is a list: has .append? 1721 add_to_val = val.append 1722 except AttributeError: 1723 # The added value isn't a list, but the 1724 # original is (by process of elimination), 1725 # so insert the the new value in the original 1726 # (if there's one to insert). 1727 if val: 1728 orig.insert(0, val) 1729 else: 1730 # The added value is a list, so append 1731 # the original to it (if there's a value 1732 # to append) and replace the original. 1733 if orig: 1734 add_to_val(orig) 1735 self._dict[key] = val 1736 continue 1737 1738 # The original looks like a dictionary, so update it 1739 # based on what we think the value looks like. 1740 # We can't just try adding the value because 1741 # dictionaries don't have __add__() methods, and 1742 # things like UserList will incorrectly coerce the 1743 # original dict to a list (which we don't want). 1744 if is_List(val): 1745 for v in val: 1746 orig[v] = None 1747 else: 1748 try: 1749 update_dict(val) 1750 except (AttributeError, TypeError, ValueError): 1751 if is_Dict(val): 1752 for k, v in val.items(): 1753 orig[k] = v 1754 else: 1755 orig[val] = None 1756 1757 self.scanner_map_delete(kw) 1758 1759 def PrependENVPath(self, name, newpath, envname = 'ENV', sep = os.pathsep, 1760 delete_existing=1): 1761 """Prepend path elements to the path 'name' in the 'ENV' 1762 dictionary for this environment. Will only add any particular 1763 path once, and will normpath and normcase all paths to help 1764 assure this. This can also handle the case where the env 1765 variable is a list instead of a string. 1766 1767 If delete_existing is 0, a newpath which is already in the path 1768 will not be moved to the front (it will be left where it is). 1769 """ 1770 1771 orig = '' 1772 if envname in self._dict and name in self._dict[envname]: 1773 orig = self._dict[envname][name] 1774 1775 nv = PrependPath(orig, newpath, sep, delete_existing, 1776 canonicalize=self._canonicalize) 1777 1778 if envname not in self._dict: 1779 self._dict[envname] = {} 1780 1781 self._dict[envname][name] = nv 1782 1783 def PrependUnique(self, delete_existing=0, **kw): 1784 """Prepend values to existing construction variables 1785 in an Environment, if they're not already there. 1786 If delete_existing is 1, removes existing values first, so 1787 values move to front. 1788 """ 1789 kw = copy_non_reserved_keywords(kw) 1790 for key, val in kw.items(): 1791 if is_List(val): 1792 val = _delete_duplicates(val, not delete_existing) 1793 if key not in self._dict or self._dict[key] in ('', None): 1794 self._dict[key] = val 1795 elif is_Dict(self._dict[key]) and is_Dict(val): 1796 self._dict[key].update(val) 1797 elif is_List(val): 1798 dk = self._dict[key] 1799 if not is_List(dk): 1800 dk = [dk] 1801 if delete_existing: 1802 dk = [x for x in dk if x not in val] 1803 else: 1804 val = [x for x in val if x not in dk] 1805 self._dict[key] = val + dk 1806 else: 1807 dk = self._dict[key] 1808 if is_List(dk): 1809 # By elimination, val is not a list. Since dk is a 1810 # list, wrap val in a list first. 1811 if delete_existing: 1812 dk = [x for x in dk if x not in val] 1813 self._dict[key] = [val] + dk 1814 else: 1815 if val not in dk: 1816 self._dict[key] = [val] + dk 1817 else: 1818 if delete_existing: 1819 dk = [x for x in dk if x not in val] 1820 self._dict[key] = val + dk 1821 self.scanner_map_delete(kw) 1822 1823 def Replace(self, **kw): 1824 """Replace existing construction variables in an Environment 1825 with new construction variables and/or values. 1826 """ 1827 try: 1828 kwbd = kw['BUILDERS'] 1829 except KeyError: 1830 pass 1831 else: 1832 kwbd = BuilderDict(kwbd,self) 1833 del kw['BUILDERS'] 1834 self.__setitem__('BUILDERS', kwbd) 1835 kw = copy_non_reserved_keywords(kw) 1836 self._update(semi_deepcopy(kw)) 1837 self.scanner_map_delete(kw) 1838 1839 def ReplaceIxes(self, path, old_prefix, old_suffix, new_prefix, new_suffix): 1840 """ 1841 Replace old_prefix with new_prefix and old_suffix with new_suffix. 1842 1843 env - Environment used to interpolate variables. 1844 path - the path that will be modified. 1845 old_prefix - construction variable for the old prefix. 1846 old_suffix - construction variable for the old suffix. 1847 new_prefix - construction variable for the new prefix. 1848 new_suffix - construction variable for the new suffix. 1849 """ 1850 old_prefix = self.subst('$'+old_prefix) 1851 old_suffix = self.subst('$'+old_suffix) 1852 1853 new_prefix = self.subst('$'+new_prefix) 1854 new_suffix = self.subst('$'+new_suffix) 1855 1856 dir,name = os.path.split(str(path)) 1857 if name[:len(old_prefix)] == old_prefix: 1858 name = name[len(old_prefix):] 1859 if name[-len(old_suffix):] == old_suffix: 1860 name = name[:-len(old_suffix)] 1861 return os.path.join(dir, new_prefix+name+new_suffix) 1862 1863 def SetDefault(self, **kw): 1864 for k in list(kw.keys()): 1865 if k in self._dict: 1866 del kw[k] 1867 self.Replace(**kw) 1868 1869 def _find_toolpath_dir(self, tp): 1870 return self.fs.Dir(self.subst(tp)).srcnode().get_abspath() 1871 1872 def Tool(self, tool, toolpath=None, **kwargs) -> SCons.Tool.Tool: 1873 if is_String(tool): 1874 tool = self.subst(tool) 1875 if toolpath is None: 1876 toolpath = self.get('toolpath', []) 1877 toolpath = list(map(self._find_toolpath_dir, toolpath)) 1878 tool = SCons.Tool.Tool(tool, toolpath, **kwargs) 1879 tool(self) 1880 return tool 1881 1882 def WhereIs(self, prog, path=None, pathext=None, reject=None): 1883 """Find prog in the path. """ 1884 if not prog: # nothing to search for, just give up 1885 return None 1886 if path is None: 1887 try: 1888 path = self['ENV']['PATH'] 1889 except KeyError: 1890 pass 1891 elif is_String(path): 1892 path = self.subst(path) 1893 if pathext is None: 1894 try: 1895 pathext = self['ENV']['PATHEXT'] 1896 except KeyError: 1897 pass 1898 elif is_String(pathext): 1899 pathext = self.subst(pathext) 1900 prog = CLVar(self.subst(prog)) # support "program --with-args" 1901 path = WhereIs(prog[0], path, pathext, reject) 1902 if path: 1903 return path 1904 return None 1905 1906 ####################################################################### 1907 # Public methods for doing real "SCons stuff" (manipulating 1908 # dependencies, setting attributes on targets, etc.). These begin 1909 # with upper-case letters. The essential characteristic of methods 1910 # in this section is that they all *should* have corresponding 1911 # same-named global functions. 1912 ####################################################################### 1913 1914 def Action(self, *args, **kw): 1915 def subst_string(a, self=self): 1916 if is_String(a): 1917 a = self.subst(a) 1918 return a 1919 nargs = list(map(subst_string, args)) 1920 nkw = self.subst_kw(kw) 1921 return SCons.Action.Action(*nargs, **nkw) 1922 1923 def AddPreAction(self, files, action): 1924 nodes = self.arg2nodes(files, self.fs.Entry) 1925 action = SCons.Action.Action(action) 1926 uniq = {} 1927 for executor in [n.get_executor() for n in nodes]: 1928 uniq[executor] = 1 1929 for executor in uniq.keys(): 1930 executor.add_pre_action(action) 1931 return nodes 1932 1933 def AddPostAction(self, files, action): 1934 nodes = self.arg2nodes(files, self.fs.Entry) 1935 action = SCons.Action.Action(action) 1936 uniq = {} 1937 for executor in [n.get_executor() for n in nodes]: 1938 uniq[executor] = 1 1939 for executor in uniq.keys(): 1940 executor.add_post_action(action) 1941 return nodes 1942 1943 def Alias(self, target, source=[], action=None, **kw): 1944 tlist = self.arg2nodes(target, self.ans.Alias) 1945 if not is_List(source): 1946 source = [source] 1947 source = [_f for _f in source if _f] 1948 1949 if not action: 1950 if not source: 1951 # There are no source files and no action, so just 1952 # return a target list of classic Alias Nodes, without 1953 # any builder. The externally visible effect is that 1954 # this will make the wrapping Script.BuildTask class 1955 # say that there's "Nothing to be done" for this Alias, 1956 # instead of that it's "up to date." 1957 return tlist 1958 1959 # No action, but there are sources. Re-call all the target 1960 # builders to add the sources to each target. 1961 result = [] 1962 for t in tlist: 1963 bld = t.get_builder(AliasBuilder) 1964 result.extend(bld(self, t, source)) 1965 return result 1966 1967 nkw = self.subst_kw(kw) 1968 nkw.update({ 1969 'action' : SCons.Action.Action(action), 1970 'source_factory' : self.fs.Entry, 1971 'multi' : 1, 1972 'is_explicit' : None, 1973 }) 1974 bld = SCons.Builder.Builder(**nkw) 1975 1976 # Apply the Builder separately to each target so that the Aliases 1977 # stay separate. If we did one "normal" Builder call with the 1978 # whole target list, then all of the target Aliases would be 1979 # associated under a single Executor. 1980 result = [] 1981 for t in tlist: 1982 # Calling the convert() method will cause a new Executor to be 1983 # created from scratch, so we have to explicitly initialize 1984 # it with the target's existing sources, plus our new ones, 1985 # so nothing gets lost. 1986 b = t.get_builder() 1987 if b is None or b is AliasBuilder: 1988 b = bld 1989 else: 1990 nkw['action'] = b.action + action 1991 b = SCons.Builder.Builder(**nkw) 1992 t.convert() 1993 result.extend(b(self, t, t.sources + source)) 1994 return result 1995 1996 def AlwaysBuild(self, *targets): 1997 tlist = [] 1998 for t in targets: 1999 tlist.extend(self.arg2nodes(t, self.fs.Entry)) 2000 for t in tlist: 2001 t.set_always_build() 2002 return tlist 2003 2004 def Builder(self, **kw): 2005 nkw = self.subst_kw(kw) 2006 return SCons.Builder.Builder(**nkw) 2007 2008 def CacheDir(self, path, custom_class=None): 2009 if path is not None: 2010 path = self.subst(path) 2011 self._CacheDir_path = path 2012 2013 if custom_class: 2014 self['CACHEDIR_CLASS'] = self.validate_CacheDir_class(custom_class) 2015 2016 if SCons.Action.execute_actions: 2017 # Only initialize the CacheDir if -n/-no_exec was NOT specified. 2018 # Now initialized the CacheDir and prevent a race condition which can 2019 # happen when there's no existing cache dir and you are building with 2020 # multiple threads, but initializing it before the task walk starts 2021 self.get_CacheDir() 2022 2023 def Clean(self, targets, files): 2024 global CleanTargets 2025 tlist = self.arg2nodes(targets, self.fs.Entry) 2026 flist = self.arg2nodes(files, self.fs.Entry) 2027 for t in tlist: 2028 try: 2029 CleanTargets[t].extend(flist) 2030 except KeyError: 2031 CleanTargets[t] = flist 2032 2033 def Configure(self, *args, **kw): 2034 nargs = [self] 2035 if args: 2036 nargs = nargs + self.subst_list(args)[0] 2037 nkw = self.subst_kw(kw) 2038 nkw['_depth'] = kw.get('_depth', 0) + 1 2039 try: 2040 nkw['custom_tests'] = self.subst_kw(nkw['custom_tests']) 2041 except KeyError: 2042 pass 2043 return SCons.SConf.SConf(*nargs, **nkw) 2044 2045 def Command(self, target, source, action, **kw): 2046 """Builds the supplied target files from the supplied 2047 source files using the supplied action. Action may 2048 be any type that the Builder constructor will accept 2049 for an action.""" 2050 bkw = { 2051 'action': action, 2052 'target_factory': self.fs.Entry, 2053 'source_factory': self.fs.Entry, 2054 } 2055 # source scanner 2056 try: 2057 bkw['source_scanner'] = kw['source_scanner'] 2058 except KeyError: 2059 pass 2060 else: 2061 del kw['source_scanner'] 2062 2063 # target scanner 2064 try: 2065 bkw['target_scanner'] = kw['target_scanner'] 2066 except KeyError: 2067 pass 2068 else: 2069 del kw['target_scanner'] 2070 2071 # source factory 2072 try: 2073 bkw['source_factory'] = kw['source_factory'] 2074 except KeyError: 2075 pass 2076 else: 2077 del kw['source_factory'] 2078 2079 # target factory 2080 try: 2081 bkw['target_factory'] = kw['target_factory'] 2082 except KeyError: 2083 pass 2084 else: 2085 del kw['target_factory'] 2086 2087 bld = SCons.Builder.Builder(**bkw) 2088 return bld(self, target, source, **kw) 2089 2090 def Depends(self, target, dependency): 2091 """Explicity specify that 'target's depend on 'dependency'.""" 2092 tlist = self.arg2nodes(target, self.fs.Entry) 2093 dlist = self.arg2nodes(dependency, self.fs.Entry) 2094 for t in tlist: 2095 t.add_dependency(dlist) 2096 return tlist 2097 2098 def Dir(self, name, *args, **kw): 2099 """ 2100 """ 2101 s = self.subst(name) 2102 if is_Sequence(s): 2103 result=[] 2104 for e in s: 2105 result.append(self.fs.Dir(e, *args, **kw)) 2106 return result 2107 return self.fs.Dir(s, *args, **kw) 2108 2109 def PyPackageDir(self, modulename): 2110 s = self.subst(modulename) 2111 if is_Sequence(s): 2112 result=[] 2113 for e in s: 2114 result.append(self.fs.PyPackageDir(e)) 2115 return result 2116 return self.fs.PyPackageDir(s) 2117 2118 def NoClean(self, *targets): 2119 """Tags a target so that it will not be cleaned by -c""" 2120 tlist = [] 2121 for t in targets: 2122 tlist.extend(self.arg2nodes(t, self.fs.Entry)) 2123 for t in tlist: 2124 t.set_noclean() 2125 return tlist 2126 2127 def NoCache(self, *targets): 2128 """Tags a target so that it will not be cached""" 2129 tlist = [] 2130 for t in targets: 2131 tlist.extend(self.arg2nodes(t, self.fs.Entry)) 2132 for t in tlist: 2133 t.set_nocache() 2134 return tlist 2135 2136 def Entry(self, name, *args, **kw): 2137 """ 2138 """ 2139 s = self.subst(name) 2140 if is_Sequence(s): 2141 result=[] 2142 for e in s: 2143 result.append(self.fs.Entry(e, *args, **kw)) 2144 return result 2145 return self.fs.Entry(s, *args, **kw) 2146 2147 def Environment(self, **kw): 2148 return SCons.Environment.Environment(**self.subst_kw(kw)) 2149 2150 def Execute(self, action, *args, **kw): 2151 """Directly execute an action through an Environment 2152 """ 2153 action = self.Action(action, *args, **kw) 2154 result = action([], [], self) 2155 if isinstance(result, BuildError): 2156 errstr = result.errstr 2157 if result.filename: 2158 errstr = result.filename + ': ' + errstr 2159 sys.stderr.write("scons: *** %s\n" % errstr) 2160 return result.status 2161 else: 2162 return result 2163 2164 def File(self, name, *args, **kw): 2165 """ 2166 """ 2167 s = self.subst(name) 2168 if is_Sequence(s): 2169 result=[] 2170 for e in s: 2171 result.append(self.fs.File(e, *args, **kw)) 2172 return result 2173 return self.fs.File(s, *args, **kw) 2174 2175 def FindFile(self, file, dirs): 2176 file = self.subst(file) 2177 nodes = self.arg2nodes(dirs, self.fs.Dir) 2178 return SCons.Node.FS.find_file(file, tuple(nodes)) 2179 2180 def Flatten(self, sequence): 2181 return flatten(sequence) 2182 2183 def GetBuildPath(self, files): 2184 result = list(map(str, self.arg2nodes(files, self.fs.Entry))) 2185 if is_List(files): 2186 return result 2187 else: 2188 return result[0] 2189 2190 def Glob(self, pattern, ondisk=True, source=False, strings=False, exclude=None): 2191 return self.fs.Glob(self.subst(pattern), ondisk, source, strings, exclude) 2192 2193 def Ignore(self, target, dependency): 2194 """Ignore a dependency.""" 2195 tlist = self.arg2nodes(target, self.fs.Entry) 2196 dlist = self.arg2nodes(dependency, self.fs.Entry) 2197 for t in tlist: 2198 t.add_ignore(dlist) 2199 return tlist 2200 2201 def Literal(self, string): 2202 return SCons.Subst.Literal(string) 2203 2204 def Local(self, *targets): 2205 ret = [] 2206 for targ in targets: 2207 if isinstance(targ, SCons.Node.Node): 2208 targ.set_local() 2209 ret.append(targ) 2210 else: 2211 for t in self.arg2nodes(targ, self.fs.Entry): 2212 t.set_local() 2213 ret.append(t) 2214 return ret 2215 2216 def Precious(self, *targets): 2217 tlist = [] 2218 for t in targets: 2219 tlist.extend(self.arg2nodes(t, self.fs.Entry)) 2220 for t in tlist: 2221 t.set_precious() 2222 return tlist 2223 2224 def Pseudo(self, *targets): 2225 tlist = [] 2226 for t in targets: 2227 tlist.extend(self.arg2nodes(t, self.fs.Entry)) 2228 for t in tlist: 2229 t.set_pseudo() 2230 return tlist 2231 2232 def Repository(self, *dirs, **kw): 2233 dirs = self.arg2nodes(list(dirs), self.fs.Dir) 2234 self.fs.Repository(*dirs, **kw) 2235 2236 def Requires(self, target, prerequisite): 2237 """Specify that 'prerequisite' must be built before 'target', 2238 (but 'target' does not actually depend on 'prerequisite' 2239 and need not be rebuilt if it changes).""" 2240 tlist = self.arg2nodes(target, self.fs.Entry) 2241 plist = self.arg2nodes(prerequisite, self.fs.Entry) 2242 for t in tlist: 2243 t.add_prerequisite(plist) 2244 return tlist 2245 2246 def Scanner(self, *args, **kw): 2247 nargs = [] 2248 for arg in args: 2249 if is_String(arg): 2250 arg = self.subst(arg) 2251 nargs.append(arg) 2252 nkw = self.subst_kw(kw) 2253 return SCons.Scanner.Base(*nargs, **nkw) 2254 2255 def SConsignFile(self, name=".sconsign", dbm_module=None): 2256 if name is not None: 2257 name = self.subst(name) 2258 if not os.path.isabs(name): 2259 name = os.path.join(str(self.fs.SConstruct_dir), name) 2260 if name: 2261 name = os.path.normpath(name) 2262 sconsign_dir = os.path.dirname(name) 2263 if sconsign_dir and not os.path.exists(sconsign_dir): 2264 self.Execute(SCons.Defaults.Mkdir(sconsign_dir)) 2265 SCons.SConsign.File(name, dbm_module) 2266 2267 def SideEffect(self, side_effect, target): 2268 """Tell scons that side_effects are built as side 2269 effects of building targets.""" 2270 side_effects = self.arg2nodes(side_effect, self.fs.Entry) 2271 targets = self.arg2nodes(target, self.fs.Entry) 2272 2273 added_side_effects = [] 2274 for side_effect in side_effects: 2275 if side_effect.multiple_side_effect_has_builder(): 2276 raise UserError("Multiple ways to build the same target were specified for: %s" % str(side_effect)) 2277 side_effect.add_source(targets) 2278 side_effect.side_effect = 1 2279 self.Precious(side_effect) 2280 added = False 2281 for target in targets: 2282 if side_effect not in target.side_effects: 2283 target.side_effects.append(side_effect) 2284 added = True 2285 if added: 2286 added_side_effects.append(side_effect) 2287 return added_side_effects 2288 2289 def Split(self, arg): 2290 """This function converts a string or list into a list of strings 2291 or Nodes. This makes things easier for users by allowing files to 2292 be specified as a white-space separated list to be split. 2293 2294 The input rules are: 2295 - A single string containing names separated by spaces. These will be 2296 split apart at the spaces. 2297 - A single Node instance 2298 - A list containing either strings or Node instances. Any strings 2299 in the list are not split at spaces. 2300 2301 In all cases, the function returns a list of Nodes and strings.""" 2302 2303 if is_List(arg): 2304 return list(map(self.subst, arg)) 2305 elif is_String(arg): 2306 return self.subst(arg).split() 2307 else: 2308 return [self.subst(arg)] 2309 2310 def Value(self, value, built_value=None, name=None): 2311 """ 2312 """ 2313 return SCons.Node.Python.ValueWithMemo(value, built_value, name) 2314 2315 def VariantDir(self, variant_dir, src_dir, duplicate=1): 2316 variant_dir = self.arg2nodes(variant_dir, self.fs.Dir)[0] 2317 src_dir = self.arg2nodes(src_dir, self.fs.Dir)[0] 2318 self.fs.VariantDir(variant_dir, src_dir, duplicate) 2319 2320 def FindSourceFiles(self, node='.'): 2321 """ returns a list of all source files. 2322 """ 2323 node = self.arg2nodes(node, self.fs.Entry)[0] 2324 2325 sources = [] 2326 def build_source(ss): 2327 for s in ss: 2328 if isinstance(s, SCons.Node.FS.Dir): 2329 build_source(s.all_children()) 2330 elif s.has_builder(): 2331 build_source(s.sources) 2332 elif isinstance(s.disambiguate(), SCons.Node.FS.File): 2333 sources.append(s) 2334 build_source(node.all_children()) 2335 2336 def final_source(node): 2337 while node != node.srcnode(): 2338 node = node.srcnode() 2339 return node 2340 sources = list(map(final_source, sources)) 2341 # remove duplicates 2342 return list(set(sources)) 2343 2344 def FindInstalledFiles(self): 2345 """ returns the list of all targets of the Install and InstallAs Builder. 2346 """ 2347 from SCons.Tool import install 2348 if install._UNIQUE_INSTALLED_FILES is None: 2349 install._UNIQUE_INSTALLED_FILES = uniquer_hashables(install._INSTALLED_FILES) 2350 return install._UNIQUE_INSTALLED_FILES 2351 2352 2353class OverrideEnvironment(Base): 2354 """A proxy that overrides variables in a wrapped construction 2355 environment by returning values from an overrides dictionary in 2356 preference to values from the underlying subject environment. 2357 2358 This is a lightweight (I hope) proxy that passes through most use of 2359 attributes to the underlying Environment.Base class, but has just 2360 enough additional methods defined to act like a real construction 2361 environment with overridden values. It can wrap either a Base 2362 construction environment, or another OverrideEnvironment, which 2363 can in turn nest arbitrary OverrideEnvironments... 2364 2365 Note that we do *not* call the underlying base class 2366 (SubsitutionEnvironment) initialization, because we get most of those 2367 from proxying the attributes of the subject construction environment. 2368 But because we subclass SubstitutionEnvironment, this class also 2369 has inherited arg2nodes() and subst*() methods; those methods can't 2370 be proxied because they need *this* object's methods to fetch the 2371 values from the overrides dictionary. 2372 """ 2373 2374 def __init__(self, subject, overrides=None): 2375 if SCons.Debug.track_instances: logInstanceCreation(self, 'Environment.OverrideEnvironment') 2376 self.__dict__['__subject'] = subject 2377 if overrides is None: 2378 self.__dict__['overrides'] = dict() 2379 else: 2380 self.__dict__['overrides'] = overrides 2381 2382 # Methods that make this class act like a proxy. 2383 def __getattr__(self, name): 2384 attr = getattr(self.__dict__['__subject'], name) 2385 # Here we check if attr is one of the Wrapper classes. For 2386 # example when a pseudo-builder is being called from an 2387 # OverrideEnvironment. 2388 # 2389 # These wrappers when they're constructed capture the 2390 # Environment they are being constructed with and so will not 2391 # have access to overrided values. So we rebuild them with the 2392 # OverrideEnvironment so they have access to overrided values. 2393 if isinstance(attr, MethodWrapper): 2394 return attr.clone(self) 2395 else: 2396 return attr 2397 2398 def __setattr__(self, name, value): 2399 setattr(self.__dict__['__subject'], name, value) 2400 2401 # Methods that make this class act like a dictionary. 2402 def __getitem__(self, key): 2403 try: 2404 return self.__dict__['overrides'][key] 2405 except KeyError: 2406 return self.__dict__['__subject'].__getitem__(key) 2407 2408 def __setitem__(self, key, value): 2409 if not is_valid_construction_var(key): 2410 raise UserError("Illegal construction variable `%s'" % key) 2411 self.__dict__['overrides'][key] = value 2412 2413 def __delitem__(self, key): 2414 try: 2415 del self.__dict__['overrides'][key] 2416 except KeyError: 2417 deleted = 0 2418 else: 2419 deleted = 1 2420 try: 2421 result = self.__dict__['__subject'].__delitem__(key) 2422 except KeyError: 2423 if not deleted: 2424 raise 2425 result = None 2426 return result 2427 2428 def get(self, key, default=None): 2429 """Emulates the get() method of dictionaries.""" 2430 try: 2431 return self.__dict__['overrides'][key] 2432 except KeyError: 2433 return self.__dict__['__subject'].get(key, default) 2434 2435 def __contains__(self, key): 2436 if key in self.__dict__['overrides']: 2437 return True 2438 return key in self.__dict__['__subject'] 2439 2440 def Dictionary(self, *args): 2441 d = self.__dict__['__subject'].Dictionary().copy() 2442 d.update(self.__dict__['overrides']) 2443 if not args: 2444 return d 2445 dlist = [d[x] for x in args] 2446 if len(dlist) == 1: 2447 dlist = dlist[0] 2448 return dlist 2449 2450 def items(self): 2451 """Emulates the items() method of dictionaries.""" 2452 return self.Dictionary().items() 2453 2454 def keys(self): 2455 """Emulates the keys() method of dictionaries.""" 2456 return self.Dictionary().keys() 2457 2458 def values(self): 2459 """Emulates the values() method of dictionaries.""" 2460 return self.Dictionary().values() 2461 2462 def setdefault(self, key, default=None): 2463 """Emulates the setdefault() method of dictionaries.""" 2464 try: 2465 return self.__getitem__(key) 2466 except KeyError: 2467 self.__dict__['overrides'][key] = default 2468 return default 2469 2470 # Overridden private construction environment methods. 2471 def _update(self, other): 2472 self.__dict__['overrides'].update(other) 2473 2474 def _update_onlynew(self, other): 2475 for k, v in other.items(): 2476 if k not in self.__dict__['overrides']: 2477 self.__dict__['overrides'][k] = v 2478 2479 def gvars(self): 2480 return self.__dict__['__subject'].gvars() 2481 2482 def lvars(self): 2483 lvars = self.__dict__['__subject'].lvars() 2484 lvars.update(self.__dict__['overrides']) 2485 return lvars 2486 2487 # Overridden public construction environment methods. 2488 def Replace(self, **kw): 2489 kw = copy_non_reserved_keywords(kw) 2490 self.__dict__['overrides'].update(semi_deepcopy(kw)) 2491 2492 2493# The entry point that will be used by the external world 2494# to refer to a construction environment. This allows the wrapper 2495# interface to extend a construction environment for its own purposes 2496# by subclassing SCons.Environment.Base and then assigning the 2497# class to SCons.Environment.Environment. 2498 2499Environment = Base 2500 2501 2502def NoSubstitutionProxy(subject): 2503 """ 2504 An entry point for returning a proxy subclass instance that overrides 2505 the subst*() methods so they don't actually perform construction 2506 variable substitution. This is specifically intended to be the shim 2507 layer in between global function calls (which don't want construction 2508 variable substitution) and the DefaultEnvironment() (which would 2509 substitute variables if left to its own devices). 2510 2511 We have to wrap this in a function that allows us to delay definition of 2512 the class until it's necessary, so that when it subclasses Environment 2513 it will pick up whatever Environment subclass the wrapper interface 2514 might have assigned to SCons.Environment.Environment. 2515 """ 2516 class _NoSubstitutionProxy(Environment): 2517 def __init__(self, subject): 2518 self.__dict__['__subject'] = subject 2519 def __getattr__(self, name): 2520 return getattr(self.__dict__['__subject'], name) 2521 def __setattr__(self, name, value): 2522 return setattr(self.__dict__['__subject'], name, value) 2523 def executor_to_lvars(self, kwdict): 2524 if 'executor' in kwdict: 2525 kwdict['lvars'] = kwdict['executor'].get_lvars() 2526 del kwdict['executor'] 2527 else: 2528 kwdict['lvars'] = {} 2529 def raw_to_mode(self, dict): 2530 try: 2531 raw = dict['raw'] 2532 except KeyError: 2533 pass 2534 else: 2535 del dict['raw'] 2536 dict['mode'] = raw 2537 def subst(self, string, *args, **kwargs): 2538 return string 2539 def subst_kw(self, kw, *args, **kwargs): 2540 return kw 2541 def subst_list(self, string, *args, **kwargs): 2542 nargs = (string, self,) + args 2543 nkw = kwargs.copy() 2544 nkw['gvars'] = {} 2545 self.executor_to_lvars(nkw) 2546 self.raw_to_mode(nkw) 2547 return SCons.Subst.scons_subst_list(*nargs, **nkw) 2548 def subst_target_source(self, string, *args, **kwargs): 2549 nargs = (string, self,) + args 2550 nkw = kwargs.copy() 2551 nkw['gvars'] = {} 2552 self.executor_to_lvars(nkw) 2553 self.raw_to_mode(nkw) 2554 return SCons.Subst.scons_subst(*nargs, **nkw) 2555 return _NoSubstitutionProxy(subject) 2556 2557# Local Variables: 2558# tab-width:4 2559# indent-tabs-mode:nil 2560# End: 2561# vim: set expandtab tabstop=4 shiftwidth=4: 2562