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 25"""Builders and other things for the local site. 26 27Here's where we'll duplicate the functionality of autoconf until we 28move it into the installation procedure or use something like qmconf. 29 30The code that reads the registry to find MSVC components was borrowed 31from distutils.msvccompiler. 32""" 33 34import os 35import shutil 36import stat 37import sys 38import time 39 40import SCons.Action 41import SCons.Builder 42import SCons.CacheDir 43import SCons.Environment 44import SCons.PathList 45import SCons.Scanner.Dir 46import SCons.Subst 47import SCons.Tool 48 49# A placeholder for a default Environment (for fetching source files 50# from source code management systems and the like). This must be 51# initialized later, after the top-level directory is set by the calling 52# interface. 53_default_env = None 54 55 56# Lazily instantiate the default environment so the overhead of creating 57# it doesn't apply when it's not needed. 58def _fetch_DefaultEnvironment(*args, **kw): 59 """Returns the already-created default construction environment.""" 60 global _default_env 61 return _default_env 62 63 64def DefaultEnvironment(*args, **kw): 65 """ 66 Initial public entry point for creating the default construction 67 Environment. 68 69 After creating the environment, we overwrite our name 70 (DefaultEnvironment) with the _fetch_DefaultEnvironment() function, 71 which more efficiently returns the initialized default construction 72 environment without checking for its existence. 73 74 (This function still exists with its _default_check because someone 75 else (*cough* Script/__init__.py *cough*) may keep a reference 76 to this function. So we can't use the fully functional idiom of 77 having the name originally be a something that *only* creates the 78 construction environment and then overwrites the name.) 79 """ 80 global _default_env 81 if not _default_env: 82 import SCons.Util 83 _default_env = SCons.Environment.Environment(*args, **kw) 84 _default_env.Decider('content') 85 global DefaultEnvironment 86 DefaultEnvironment = _fetch_DefaultEnvironment 87 _default_env._CacheDir_path = None 88 return _default_env 89 90 91# Emitters for setting the shared attribute on object files, 92# and an action for checking that all of the source files 93# going into a shared library are, in fact, shared. 94def StaticObjectEmitter(target, source, env): 95 for tgt in target: 96 tgt.attributes.shared = None 97 return target, source 98 99 100def SharedObjectEmitter(target, source, env): 101 for tgt in target: 102 tgt.attributes.shared = 1 103 return target, source 104 105 106def SharedFlagChecker(source, target, env): 107 same = env.subst('$STATIC_AND_SHARED_OBJECTS_ARE_THE_SAME') 108 if same == '0' or same == '' or same == 'False': 109 for src in source: 110 try: 111 shared = src.attributes.shared 112 except AttributeError: 113 shared = None 114 if not shared: 115 raise SCons.Errors.UserError( 116 "Source file: %s is static and is not compatible with shared target: %s" % (src, target[0])) 117 118 119SharedCheck = SCons.Action.Action(SharedFlagChecker, None) 120 121# Some people were using these variable name before we made 122# SourceFileScanner part of the public interface. Don't break their 123# SConscript files until we've given them some fair warning and a 124# transition period. 125CScan = SCons.Tool.CScanner 126DScan = SCons.Tool.DScanner 127LaTeXScan = SCons.Tool.LaTeXScanner 128ObjSourceScan = SCons.Tool.SourceFileScanner 129ProgScan = SCons.Tool.ProgramScanner 130 131# These aren't really tool scanners, so they don't quite belong with 132# the rest of those in Tool/__init__.py, but I'm not sure where else 133# they should go. Leave them here for now. 134 135DirScanner = SCons.Scanner.Dir.DirScanner() 136DirEntryScanner = SCons.Scanner.Dir.DirEntryScanner() 137 138# Actions for common languages. 139CAction = SCons.Action.Action("$CCCOM", "$CCCOMSTR") 140ShCAction = SCons.Action.Action("$SHCCCOM", "$SHCCCOMSTR") 141CXXAction = SCons.Action.Action("$CXXCOM", "$CXXCOMSTR") 142ShCXXAction = SCons.Action.Action("$SHCXXCOM", "$SHCXXCOMSTR") 143 144DAction = SCons.Action.Action("$DCOM", "$DCOMSTR") 145ShDAction = SCons.Action.Action("$SHDCOM", "$SHDCOMSTR") 146 147ASAction = SCons.Action.Action("$ASCOM", "$ASCOMSTR") 148ASPPAction = SCons.Action.Action("$ASPPCOM", "$ASPPCOMSTR") 149 150LinkAction = SCons.Action.Action("$LINKCOM", "$LINKCOMSTR") 151ShLinkAction = SCons.Action.Action("$SHLINKCOM", "$SHLINKCOMSTR") 152 153LdModuleLinkAction = SCons.Action.Action("$LDMODULECOM", "$LDMODULECOMSTR") 154 155# Common tasks that we allow users to perform in platform-independent 156# ways by creating ActionFactory instances. 157ActionFactory = SCons.Action.ActionFactory 158 159 160def get_paths_str(dest): 161 # If dest is a list, we need to manually call str() on each element 162 if SCons.Util.is_List(dest): 163 elem_strs = [] 164 for element in dest: 165 elem_strs.append('"' + str(element) + '"') 166 return '[' + ', '.join(elem_strs) + ']' 167 else: 168 return '"' + str(dest) + '"' 169 170 171permission_dic = { 172 'u': { 173 'r': stat.S_IRUSR, 174 'w': stat.S_IWUSR, 175 'x': stat.S_IXUSR 176 }, 177 'g': { 178 'r': stat.S_IRGRP, 179 'w': stat.S_IWGRP, 180 'x': stat.S_IXGRP 181 }, 182 'o': { 183 'r': stat.S_IROTH, 184 'w': stat.S_IWOTH, 185 'x': stat.S_IXOTH 186 } 187} 188 189 190def chmod_func(dest, mode): 191 import SCons.Util 192 from string import digits 193 SCons.Node.FS.invalidate_node_memos(dest) 194 if not SCons.Util.is_List(dest): 195 dest = [dest] 196 if SCons.Util.is_String(mode) and 0 not in [i in digits for i in mode]: 197 mode = int(mode, 8) 198 if not SCons.Util.is_String(mode): 199 for element in dest: 200 os.chmod(str(element), mode) 201 else: 202 mode = str(mode) 203 for operation in mode.split(","): 204 if "=" in operation: 205 operator = "=" 206 elif "+" in operation: 207 operator = "+" 208 elif "-" in operation: 209 operator = "-" 210 else: 211 raise SyntaxError("Could not find +, - or =") 212 operation_list = operation.split(operator) 213 if len(operation_list) != 2: 214 raise SyntaxError("More than one operator found") 215 user = operation_list[0].strip().replace("a", "ugo") 216 permission = operation_list[1].strip() 217 new_perm = 0 218 for u in user: 219 for p in permission: 220 try: 221 new_perm = new_perm | permission_dic[u][p] 222 except KeyError: 223 raise SyntaxError("Unrecognized user or permission format") 224 for element in dest: 225 curr_perm = os.stat(str(element)).st_mode 226 if operator == "=": 227 os.chmod(str(element), new_perm) 228 elif operator == "+": 229 os.chmod(str(element), curr_perm | new_perm) 230 elif operator == "-": 231 os.chmod(str(element), curr_perm & ~new_perm) 232 233 234def chmod_strfunc(dest, mode): 235 import SCons.Util 236 if not SCons.Util.is_String(mode): 237 return 'Chmod(%s, 0%o)' % (get_paths_str(dest), mode) 238 else: 239 return 'Chmod(%s, "%s")' % (get_paths_str(dest), str(mode)) 240 241 242Chmod = ActionFactory(chmod_func, chmod_strfunc) 243 244 245def copy_func(dest, src, symlinks=True): 246 """ 247 If symlinks (is true), then a symbolic link will be 248 shallow copied and recreated as a symbolic link; otherwise, copying 249 a symbolic link will be equivalent to copying the symbolic link's 250 final target regardless of symbolic link depth. 251 """ 252 253 dest = str(dest) 254 src = str(src) 255 256 SCons.Node.FS.invalidate_node_memos(dest) 257 if SCons.Util.is_List(src) and os.path.isdir(dest): 258 for file in src: 259 shutil.copy2(file, dest) 260 return 0 261 elif os.path.islink(src): 262 if symlinks: 263 return os.symlink(os.readlink(src), dest) 264 else: 265 return copy_func(dest, os.path.realpath(src)) 266 elif os.path.isfile(src): 267 shutil.copy2(src, dest) 268 return 0 269 else: 270 shutil.copytree(src, dest, symlinks) 271 # copytree returns None in python2 and destination string in python3 272 # A error is raised in both cases, so we can just return 0 for success 273 return 0 274 275 276Copy = ActionFactory( 277 copy_func, 278 lambda dest, src, symlinks=True: 'Copy("%s", "%s")' % (dest, src) 279) 280 281 282def delete_func(dest, must_exist=0): 283 SCons.Node.FS.invalidate_node_memos(dest) 284 if not SCons.Util.is_List(dest): 285 dest = [dest] 286 for entry in dest: 287 entry = str(entry) 288 # os.path.exists returns False with broken links that exist 289 entry_exists = os.path.exists(entry) or os.path.islink(entry) 290 if not entry_exists and not must_exist: 291 continue 292 # os.path.isdir returns True when entry is a link to a dir 293 if os.path.isdir(entry) and not os.path.islink(entry): 294 shutil.rmtree(entry, True) 295 continue 296 os.unlink(entry) 297 298 299def delete_strfunc(dest, must_exist=0): 300 return 'Delete(%s)' % get_paths_str(dest) 301 302 303Delete = ActionFactory(delete_func, delete_strfunc) 304 305 306def mkdir_func(dest): 307 SCons.Node.FS.invalidate_node_memos(dest) 308 if not SCons.Util.is_List(dest): 309 dest = [dest] 310 for entry in dest: 311 os.makedirs(str(entry), exist_ok=True) 312 313 314Mkdir = ActionFactory(mkdir_func, 315 lambda _dir: 'Mkdir(%s)' % get_paths_str(_dir)) 316 317 318def move_func(dest, src): 319 SCons.Node.FS.invalidate_node_memos(dest) 320 SCons.Node.FS.invalidate_node_memos(src) 321 shutil.move(src, dest) 322 323 324Move = ActionFactory(move_func, 325 lambda dest, src: 'Move("%s", "%s")' % (dest, src), 326 convert=str) 327 328 329def touch_func(dest): 330 SCons.Node.FS.invalidate_node_memos(dest) 331 if not SCons.Util.is_List(dest): 332 dest = [dest] 333 for file in dest: 334 file = str(file) 335 mtime = int(time.time()) 336 if os.path.exists(file): 337 atime = os.path.getatime(file) 338 else: 339 with open(file, 'w'): 340 atime = mtime 341 os.utime(file, (atime, mtime)) 342 343 344Touch = ActionFactory(touch_func, 345 lambda file: 'Touch(%s)' % get_paths_str(file)) 346 347 348# Internal utility functions 349 350# pylint: disable-msg=too-many-arguments 351def _concat(prefix, items_iter, suffix, env, f=lambda x: x, target=None, source=None, affect_signature=True): 352 """ 353 Creates a new list from 'items_iter' by first interpolating each element 354 in the list using the 'env' dictionary and then calling f on the 355 list, and finally calling _concat_ixes to concatenate 'prefix' and 356 'suffix' onto each element of the list. 357 """ 358 359 if not items_iter: 360 return items_iter 361 362 l = f(SCons.PathList.PathList(items_iter).subst_path(env, target, source)) 363 if l is not None: 364 items_iter = l 365 366 if not affect_signature: 367 value = ['$('] 368 else: 369 value = [] 370 value += _concat_ixes(prefix, items_iter, suffix, env) 371 372 if not affect_signature: 373 value += ["$)"] 374 375 return value 376# pylint: enable-msg=too-many-arguments 377 378 379def _concat_ixes(prefix, items_iter, suffix, env): 380 """ 381 Creates a new list from 'items_iter' by concatenating the 'prefix' and 382 'suffix' arguments onto each element of the list. A trailing space 383 on 'prefix' or leading space on 'suffix' will cause them to be put 384 into separate list elements rather than being concatenated. 385 """ 386 387 result = [] 388 389 # ensure that prefix and suffix are strings 390 prefix = str(env.subst(prefix, SCons.Subst.SUBST_RAW)) 391 suffix = str(env.subst(suffix, SCons.Subst.SUBST_RAW)) 392 393 for x in SCons.Util.flatten(items_iter): 394 if isinstance(x, SCons.Node.FS.File): 395 result.append(x) 396 continue 397 x = str(x) 398 if x: 399 400 if prefix: 401 if prefix[-1] == ' ': 402 result.append(prefix[:-1]) 403 elif x[:len(prefix)] != prefix: 404 x = prefix + x 405 406 result.append(x) 407 408 if suffix: 409 if suffix[0] == ' ': 410 result.append(suffix[1:]) 411 elif x[-len(suffix):] != suffix: 412 result[-1] = result[-1] + suffix 413 414 return result 415 416 417def _stripixes(prefix, itms, suffix, stripprefixes, stripsuffixes, env, c=None): 418 """ 419 This is a wrapper around _concat()/_concat_ixes() that checks for 420 the existence of prefixes or suffixes on list items and strips them 421 where it finds them. This is used by tools (like the GNU linker) 422 that need to turn something like 'libfoo.a' into '-lfoo'. 423 """ 424 425 if not itms: 426 return itms 427 428 if not callable(c): 429 env_c = env['_concat'] 430 if env_c != _concat and callable(env_c): 431 # There's a custom _concat() method in the construction 432 # environment, and we've allowed people to set that in 433 # the past (see test/custom-concat.py), so preserve the 434 # backwards compatibility. 435 c = env_c 436 else: 437 c = _concat_ixes 438 439 stripprefixes = list(map(env.subst, SCons.Util.flatten(stripprefixes))) 440 stripsuffixes = list(map(env.subst, SCons.Util.flatten(stripsuffixes))) 441 442 stripped = [] 443 for l in SCons.PathList.PathList(itms).subst_path(env, None, None): 444 if isinstance(l, SCons.Node.FS.File): 445 stripped.append(l) 446 continue 447 448 if not SCons.Util.is_String(l): 449 l = str(l) 450 451 for stripprefix in stripprefixes: 452 lsp = len(stripprefix) 453 if l[:lsp] == stripprefix: 454 l = l[lsp:] 455 # Do not strip more than one prefix 456 break 457 458 for stripsuffix in stripsuffixes: 459 lss = len(stripsuffix) 460 if l[-lss:] == stripsuffix: 461 l = l[:-lss] 462 # Do not strip more than one suffix 463 break 464 465 stripped.append(l) 466 467 return c(prefix, stripped, suffix, env) 468 469 470def processDefines(defs): 471 """process defines, resolving strings, lists, dictionaries, into a list of 472 strings 473 """ 474 if SCons.Util.is_List(defs): 475 l = [] 476 for d in defs: 477 if d is None: 478 continue 479 elif SCons.Util.is_List(d) or isinstance(d, tuple): 480 if len(d) >= 2: 481 l.append(str(d[0]) + '=' + str(d[1])) 482 else: 483 l.append(str(d[0])) 484 elif SCons.Util.is_Dict(d): 485 for macro, value in d.items(): 486 if value is not None: 487 l.append(str(macro) + '=' + str(value)) 488 else: 489 l.append(str(macro)) 490 elif SCons.Util.is_String(d): 491 l.append(str(d)) 492 else: 493 raise SCons.Errors.UserError("DEFINE %s is not a list, dict, string or None." % repr(d)) 494 elif SCons.Util.is_Dict(defs): 495 # The items in a dictionary are stored in random order, but 496 # if the order of the command-line options changes from 497 # invocation to invocation, then the signature of the command 498 # line will change and we'll get random unnecessary rebuilds. 499 # Consequently, we have to sort the keys to ensure a 500 # consistent order... 501 l = [] 502 for k, v in sorted(defs.items()): 503 if v is None: 504 l.append(str(k)) 505 else: 506 l.append(str(k) + '=' + str(v)) 507 else: 508 l = [str(defs)] 509 return l 510 511 512def _defines(prefix, defs, suffix, env, target, source, c=_concat_ixes): 513 """A wrapper around _concat_ixes that turns a list or string 514 into a list of C preprocessor command-line definitions. 515 """ 516 517 return c(prefix, env.subst_list(processDefines(defs), target=target, source=source), suffix, env) 518 519 520class NullCmdGenerator: 521 """This is a callable class that can be used in place of other 522 command generators if you don't want them to do anything. 523 524 The __call__ method for this class simply returns the thing 525 you instantiated it with. 526 527 Example usage: 528 env["DO_NOTHING"] = NullCmdGenerator 529 env["LINKCOM"] = "${DO_NOTHING('$LINK $SOURCES $TARGET')}" 530 """ 531 532 def __init__(self, cmd): 533 self.cmd = cmd 534 535 def __call__(self, target, source, env, for_signature=None): 536 return self.cmd 537 538 539class Variable_Method_Caller: 540 """A class for finding a construction variable on the stack and 541 calling one of its methods. 542 543 We use this to support "construction variables" in our string 544 eval()s that actually stand in for methods--specifically, use 545 of "RDirs" in call to _concat that should actually execute the 546 "TARGET.RDirs" method. (We used to support this by creating a little 547 "build dictionary" that mapped RDirs to the method, but this got in 548 the way of Memoizing construction environments, because we had to 549 create new environment objects to hold the variables.) 550 """ 551 552 def __init__(self, variable, method): 553 self.variable = variable 554 self.method = method 555 556 def __call__(self, *args, **kw): 557 try: 558 1 // 0 559 except ZeroDivisionError: 560 # Don't start iterating with the current stack-frame to 561 # prevent creating reference cycles (f_back is safe). 562 frame = sys.exc_info()[2].tb_frame.f_back 563 variable = self.variable 564 while frame: 565 if variable in frame.f_locals: 566 v = frame.f_locals[variable] 567 if v: 568 method = getattr(v, self.method) 569 return method(*args, **kw) 570 frame = frame.f_back 571 return None 572 573 574def __libversionflags(env, version_var, flags_var): 575 """ 576 if version_var is not empty, returns env[flags_var], otherwise returns None 577 :param env: 578 :param version_var: 579 :param flags_var: 580 :return: 581 """ 582 try: 583 if env.subst('$' + version_var): 584 return env[flags_var] 585 except KeyError: 586 pass 587 return None 588 589 590def __lib_either_version_flag(env, version_var1, version_var2, flags_var): 591 """ 592 if $version_var1 or $version_var2 is not empty, returns env[flags_var], otherwise returns None 593 :param env: 594 :param version_var1: 595 :param version_var2: 596 :param flags_var: 597 :return: 598 """ 599 try: 600 if env.subst('$' + version_var1) or env.subst('$' + version_var2): 601 return env[flags_var] 602 except KeyError: 603 pass 604 return None 605 606 607ConstructionEnvironment = { 608 'BUILDERS': {}, 609 'SCANNERS': [SCons.Tool.SourceFileScanner], 610 'CONFIGUREDIR': '#/.sconf_temp', 611 'CONFIGURELOG': '#/config.log', 612 'CPPSUFFIXES': SCons.Tool.CSuffixes, 613 'DSUFFIXES': SCons.Tool.DSuffixes, 614 'ENV': {}, 615 'IDLSUFFIXES': SCons.Tool.IDLSuffixes, 616 '_concat': _concat, 617 '_defines': _defines, 618 '_stripixes': _stripixes, 619 '_LIBFLAGS': '${_concat(LIBLINKPREFIX, LIBS, LIBLINKSUFFIX, __env__)}', 620 621 '_LIBDIRFLAGS': '${_concat(LIBDIRPREFIX, LIBPATH, LIBDIRSUFFIX, __env__, RDirs, TARGET, SOURCE, affect_signature=False)}', 622 '_CPPINCFLAGS': '${_concat(INCPREFIX, CPPPATH, INCSUFFIX, __env__, RDirs, TARGET, SOURCE, affect_signature=False)}', 623 624 '_CPPDEFFLAGS': '${_defines(CPPDEFPREFIX, CPPDEFINES, CPPDEFSUFFIX, __env__, TARGET, SOURCE)}', 625 626 '__libversionflags': __libversionflags, 627 '__SHLIBVERSIONFLAGS': '${__libversionflags(__env__,"SHLIBVERSION","_SHLIBVERSIONFLAGS")}', 628 '__LDMODULEVERSIONFLAGS': '${__libversionflags(__env__,"LDMODULEVERSION","_LDMODULEVERSIONFLAGS")}', 629 '__DSHLIBVERSIONFLAGS': '${__libversionflags(__env__,"DSHLIBVERSION","_DSHLIBVERSIONFLAGS")}', 630 '__lib_either_version_flag': __lib_either_version_flag, 631 632 'TEMPFILE': NullCmdGenerator, 633 'TEMPFILEARGJOIN': ' ', 634 'TEMPFILEARGESCFUNC': SCons.Subst.quote_spaces, 635 'Dir': Variable_Method_Caller('TARGET', 'Dir'), 636 'Dirs': Variable_Method_Caller('TARGET', 'Dirs'), 637 'File': Variable_Method_Caller('TARGET', 'File'), 638 'RDirs': Variable_Method_Caller('TARGET', 'RDirs'), 639} 640 641# Local Variables: 642# tab-width:4 643# indent-tabs-mode:nil 644# End: 645# vim: set expandtab tabstop=4 shiftwidth=4: 646