3 # Copyright The SCons Foundation
5 # Permission is hereby granted, free of charge, to any person obtaining
6 # a copy of this software and associated documentation files (the
7 # "Software"), to deal in the Software without restriction, including
8 # without limitation the rights to use, copy, modify, merge, publish,
9 # distribute, sublicense, and/or sell copies of the Software, and to
10 # permit persons to whom the Software is furnished to do so, subject to
11 # the following conditions:
13 # The above copyright notice and this permission notice shall be included
14 # in all copies or substantial portions of the Software.
16 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
17 # KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
18 # WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19 # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20 # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21 # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22 # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
25 """Builders and other things for the local site.
27 Here's where we'll duplicate the functionality of autoconf until we
28 move it into the installation procedure or use something like qmconf.
30 The code that reads the registry to find MSVC components was borrowed
31 from distutils.msvccompiler.
39 from typing
import List
44 import SCons
.Environment
47 import SCons
.Scanner
.Dir
50 from SCons
.Util
import is_List
, is_String
, is_Sequence
, is_Tuple
, is_Dict
, flatten
52 # A placeholder for a default Environment (for fetching source files
53 # from source code management systems and the like). This must be
54 # initialized later, after the top-level directory is set by the calling
59 # Lazily instantiate the default environment so the overhead of creating
60 # it doesn't apply when it's not needed.
61 def _fetch_DefaultEnvironment(*args
, **kw
):
62 """Returns the already-created default construction environment."""
67 def DefaultEnvironment(*args
, **kw
):
69 Initial public entry point for creating the default construction
72 After creating the environment, we overwrite our name
73 (DefaultEnvironment) with the _fetch_DefaultEnvironment() function,
74 which more efficiently returns the initialized default construction
75 environment without checking for its existence.
77 (This function still exists with its _default_check because someone
78 else (*cough* Script/__init__.py *cough*) may keep a reference
79 to this function. So we can't use the fully functional idiom of
80 having the name originally be a something that *only* creates the
81 construction environment and then overwrites the name.)
85 _default_env
= SCons
.Environment
.Environment(*args
, **kw
)
86 _default_env
.Decider('content')
87 global DefaultEnvironment
88 DefaultEnvironment
= _fetch_DefaultEnvironment
89 _default_env
._CacheDir
_path
= None
93 # Emitters for setting the shared attribute on object files,
94 # and an action for checking that all of the source files
95 # going into a shared library are, in fact, shared.
96 def StaticObjectEmitter(target
, source
, env
):
98 tgt
.attributes
.shared
= None
102 def SharedObjectEmitter(target
, source
, env
):
104 tgt
.attributes
.shared
= 1
105 return target
, source
108 def SharedFlagChecker(source
, target
, env
):
109 same
= env
.subst('$STATIC_AND_SHARED_OBJECTS_ARE_THE_SAME')
110 if same
== '0' or same
== '' or same
== 'False':
113 shared
= src
.attributes
.shared
114 except AttributeError:
117 raise SCons
.Errors
.UserError(
118 "Source file: %s is static and is not compatible with shared target: %s" % (src
, target
[0]))
121 SharedCheck
= SCons
.Action
.Action(SharedFlagChecker
, None)
123 # Some people were using these variable name before we made
124 # SourceFileScanner part of the public interface. Don't break their
125 # SConscript files until we've given them some fair warning and a
127 CScan
= SCons
.Tool
.CScanner
128 DScan
= SCons
.Tool
.DScanner
129 LaTeXScan
= SCons
.Tool
.LaTeXScanner
130 ObjSourceScan
= SCons
.Tool
.SourceFileScanner
131 ProgScan
= SCons
.Tool
.ProgramScanner
133 # These aren't really tool scanners, so they don't quite belong with
134 # the rest of those in Tool/__init__.py, but I'm not sure where else
135 # they should go. Leave them here for now.
137 DirScanner
= SCons
.Scanner
.Dir
.DirScanner()
138 DirEntryScanner
= SCons
.Scanner
.Dir
.DirEntryScanner()
140 # Actions for common languages.
141 CAction
= SCons
.Action
.Action("$CCCOM", "$CCCOMSTR")
142 ShCAction
= SCons
.Action
.Action("$SHCCCOM", "$SHCCCOMSTR")
143 CXXAction
= SCons
.Action
.Action("$CXXCOM", "$CXXCOMSTR")
144 ShCXXAction
= SCons
.Action
.Action("$SHCXXCOM", "$SHCXXCOMSTR")
146 DAction
= SCons
.Action
.Action("$DCOM", "$DCOMSTR")
147 ShDAction
= SCons
.Action
.Action("$SHDCOM", "$SHDCOMSTR")
149 ASAction
= SCons
.Action
.Action("$ASCOM", "$ASCOMSTR")
150 ASPPAction
= SCons
.Action
.Action("$ASPPCOM", "$ASPPCOMSTR")
152 LinkAction
= SCons
.Action
.Action("$LINKCOM", "$LINKCOMSTR")
153 ShLinkAction
= SCons
.Action
.Action("$SHLINKCOM", "$SHLINKCOMSTR")
155 LdModuleLinkAction
= SCons
.Action
.Action("$LDMODULECOM", "$LDMODULECOMSTR")
157 # Common tasks that we allow users to perform in platform-independent
158 # ways by creating ActionFactory instances.
159 ActionFactory
= SCons
.Action
.ActionFactory
162 def get_paths_str(dest
) -> str:
163 """Generates a string from *dest* for use in a strfunction.
165 If *dest* is a list, manually converts each elem to a string.
167 def quote(arg
) -> str:
171 elem_strs
= [quote(d
) for d
in dest
]
172 return f
'[{", ".join(elem_strs)}]'
196 def chmod_func(dest
, mode
) -> None:
197 """Implementation of the Chmod action function.
199 *mode* can be either an integer (normally expressed in octal mode,
200 as in 0o755) or a string following the syntax of the POSIX chmod
201 command (for example "ugo+w"). The latter must be converted, since
202 the underlying Python only takes the numeric form.
204 from string
import digits
205 SCons
.Node
.FS
.invalidate_node_memos(dest
)
206 if not is_List(dest
):
208 if is_String(mode
) and 0 not in [i
in digits
for i
in mode
]:
210 if not is_String(mode
):
212 os
.chmod(str(element
), mode
)
215 for operation
in mode
.split(","):
218 elif "+" in operation
:
220 elif "-" in operation
:
223 raise SyntaxError("Could not find +, - or =")
224 operation_list
= operation
.split(operator
)
225 if len(operation_list
) != 2:
226 raise SyntaxError("More than one operator found")
227 user
= operation_list
[0].strip().replace("a", "ugo")
228 permission
= operation_list
[1].strip()
233 new_perm
= new_perm | permission_dic
[u
][p
]
235 raise SyntaxError("Unrecognized user or permission format")
237 curr_perm
= os
.stat(str(element
)).st_mode
239 os
.chmod(str(element
), new_perm
)
240 elif operator
== "+":
241 os
.chmod(str(element
), curr_perm | new_perm
)
242 elif operator
== "-":
243 os
.chmod(str(element
), curr_perm
& ~new_perm
)
246 def chmod_strfunc(dest
, mode
) -> str:
247 """strfunction for the Chmod action function."""
248 if not is_String(mode
):
249 return f
'Chmod({get_paths_str(dest)}, {mode:#o})'
251 return f
'Chmod({get_paths_str(dest)}, "{mode}")'
255 Chmod
= ActionFactory(chmod_func
, chmod_strfunc
)
259 def copy_func(dest
, src
, symlinks
: bool=True) -> int:
260 """Implementation of the Copy action function.
262 Copies *src* to *dest*. If *src* is a list, *dest* must be
263 a directory, or not exist (will be created).
265 Since Python :mod:`shutil` methods, which know nothing about
266 SCons Nodes, will be called to perform the actual copying,
267 args are converted to strings first.
269 If *symlinks* evaluates true, then a symbolic link will be
270 shallow copied and recreated as a symbolic link; otherwise, copying
271 a symbolic link will be equivalent to copying the symbolic link's
272 final target regardless of symbolic link depth.
276 src
= [str(n
) for n
in src
] if is_List(src
) else str(src
)
278 SCons
.Node
.FS
.invalidate_node_memos(dest
)
280 # this fails only if dest exists and is not a dir
282 os
.makedirs(dest
, exist_ok
=True)
283 except FileExistsError
:
284 raise SCons
.Errors
.BuildError(
286 'Error: Copy() called with a list of sources, '
287 'which requires target to be a directory, '
288 f
'but "{dest}" is not a directory.'
292 shutil
.copy2(file, dest
)
295 elif os
.path
.islink(src
):
297 os
.symlink(os
.readlink(src
), dest
)
300 return copy_func(dest
, os
.path
.realpath(src
))
302 elif os
.path
.isfile(src
):
303 shutil
.copy2(src
, dest
)
307 shutil
.copytree(src
, dest
, symlinks
)
311 def copy_strfunc(dest
, src
, symlinks
: bool=True) -> str:
312 """strfunction for the Copy action function."""
313 return f
'Copy({get_paths_str(dest)}, {get_paths_str(src)})'
316 Copy
= ActionFactory(copy_func
, copy_strfunc
)
319 def delete_func(dest
, must_exist
: bool=False) -> None:
320 """Implementation of the Delete action function.
322 Lets the Python :func:`os.unlink` raise an error if *dest* does not exist,
323 unless *must_exist* evaluates false (the default).
325 SCons
.Node
.FS
.invalidate_node_memos(dest
)
326 if not is_List(dest
):
330 # os.path.exists returns False with broken links that exist
331 entry_exists
= os
.path
.exists(entry
) or os
.path
.islink(entry
)
332 if not entry_exists
and not must_exist
:
334 # os.path.isdir returns True when entry is a link to a dir
335 if os
.path
.isdir(entry
) and not os
.path
.islink(entry
):
336 shutil
.rmtree(entry
, True)
341 def delete_strfunc(dest
, must_exist
: bool=False) -> str:
342 """strfunction for the Delete action function."""
343 return f
'Delete({get_paths_str(dest)})'
346 Delete
= ActionFactory(delete_func
, delete_strfunc
)
349 def mkdir_func(dest
) -> None:
350 """Implementation of the Mkdir action function."""
351 SCons
.Node
.FS
.invalidate_node_memos(dest
)
352 if not is_List(dest
):
355 os
.makedirs(str(entry
), exist_ok
=True)
358 Mkdir
= ActionFactory(mkdir_func
, lambda _dir
: f
'Mkdir({get_paths_str(_dir)})')
361 def move_func(dest
, src
) -> None:
362 """Implementation of the Move action function."""
363 SCons
.Node
.FS
.invalidate_node_memos(dest
)
364 SCons
.Node
.FS
.invalidate_node_memos(src
)
365 shutil
.move(src
, dest
)
368 Move
= ActionFactory(
369 move_func
, lambda dest
, src
: f
'Move("{dest}", "{src}")', convert
=str
373 def touch_func(dest
) -> None:
374 """Implementation of the Touch action function."""
375 SCons
.Node
.FS
.invalidate_node_memos(dest
)
376 if not is_List(dest
):
380 mtime
= int(time
.time())
381 if os
.path
.exists(file):
382 atime
= os
.path
.getatime(file)
384 with
open(file, 'w'):
386 os
.utime(file, (atime
, mtime
))
389 Touch
= ActionFactory(touch_func
, lambda file: f
'Touch({get_paths_str(file)})')
392 # Internal utility functions
394 # pylint: disable-msg=too-many-arguments
395 def _concat(prefix
, items_iter
, suffix
, env
, f
=lambda x
: x
, target
=None, source
=None, affect_signature
: bool=True):
397 Creates a new list from 'items_iter' by first interpolating each element
398 in the list using the 'env' dictionary and then calling f on the
399 list, and finally calling _concat_ixes to concatenate 'prefix' and
400 'suffix' onto each element of the list.
406 l
= f(SCons
.PathList
.PathList(items_iter
).subst_path(env
, target
, source
))
410 if not affect_signature
:
414 value
+= _concat_ixes(prefix
, items_iter
, suffix
, env
)
416 if not affect_signature
:
420 # pylint: enable-msg=too-many-arguments
423 def _concat_ixes(prefix
, items_iter
, suffix
, env
):
425 Creates a new list from 'items_iter' by concatenating the 'prefix' and
426 'suffix' arguments onto each element of the list. A trailing space
427 on 'prefix' or leading space on 'suffix' will cause them to be put
428 into separate list elements rather than being concatenated.
433 # ensure that prefix and suffix are strings
434 prefix
= str(env
.subst(prefix
, SCons
.Subst
.SUBST_RAW
))
435 suffix
= str(env
.subst(suffix
, SCons
.Subst
.SUBST_RAW
))
437 for x
in flatten(items_iter
):
438 if isinstance(x
, SCons
.Node
.FS
.File
):
445 if prefix
[-1] == ' ':
446 result
.append(prefix
[:-1])
447 elif x
[:len(prefix
)] != prefix
:
454 result
.append(suffix
[1:])
455 elif x
[-len(suffix
):] != suffix
:
456 result
[-1] = result
[-1] + suffix
461 def _stripixes(prefix
, itms
, suffix
, stripprefixes
, stripsuffixes
, env
, c
=None):
463 This is a wrapper around _concat()/_concat_ixes() that checks for
464 the existence of prefixes or suffixes on list items and strips them
465 where it finds them. This is used by tools (like the GNU linker)
466 that need to turn something like 'libfoo.a' into '-lfoo'.
473 env_c
= env
['_concat']
474 if env_c
!= _concat
and callable(env_c
):
475 # There's a custom _concat() method in the construction
476 # environment, and we've allowed people to set that in
477 # the past (see test/custom-concat.py), so preserve the
478 # backwards compatibility.
483 stripprefixes
= list(map(env
.subst
, flatten(stripprefixes
)))
484 stripsuffixes
= list(map(env
.subst
, flatten(stripsuffixes
)))
487 for l
in SCons
.PathList
.PathList(itms
).subst_path(env
, None, None):
488 if isinstance(l
, SCons
.Node
.FS
.File
):
495 for stripprefix
in stripprefixes
:
496 lsp
= len(stripprefix
)
497 if l
[:lsp
] == stripprefix
:
499 # Do not strip more than one prefix
502 for stripsuffix
in stripsuffixes
:
503 lss
= len(stripsuffix
)
504 if l
[-lss
:] == stripsuffix
:
506 # Do not strip more than one suffix
511 return c(prefix
, stripped
, suffix
, env
)
514 def processDefines(defs
) -> List
[str]:
515 """Return list of strings for preprocessor defines from *defs*.
517 Resolves the different forms ``CPPDEFINES`` can be assembled in:
518 if the Append/Prepend routines are used beyond a initial setting it
519 will be a deque, but if written to only once (Environment initializer,
520 or direct write) it can be a multitude of types.
522 Any prefix/suffix is handled elsewhere (usually :func:`_concat_ixes`).
524 .. versionchanged:: 4.5.0
525 Bare tuples are now treated the same as tuple-in-sequence, assumed
526 to describe a valued macro. Bare strings are now split on space.
527 A dictionary is no longer sorted before handling.
534 elif is_Sequence(define
):
536 raise SCons
.Errors
.UserError(
537 f
"Invalid tuple in CPPDEFINES: {define!r}, "
538 "must be a tuple with only two elements"
540 name
, *value
= define
541 if value
and value
[0] is not None:
542 # TODO: do we need to quote value if it contains space?
543 dlist
.append(f
"{name}={value[0]}")
545 dlist
.append(str(define
[0]))
546 elif is_Dict(define
):
547 for macro
, value
in define
.items():
548 if value
is not None:
549 # TODO: do we need to quote value if it contains space?
550 dlist
.append(f
"{macro}={value}")
552 dlist
.append(str(macro
))
553 elif is_String(define
):
554 dlist
.append(str(define
))
556 raise SCons
.Errors
.UserError(
557 f
"CPPDEFINES entry {define!r} is not a tuple, list, "
558 "dict, string or None."
562 raise SCons
.Errors
.UserError(
563 f
"Invalid tuple in CPPDEFINES: {defs!r}, "
564 "must be a tuple with only two elements"
567 if value
and value
[0] is not None:
568 # TODO: do we need to quote value if it contains space?
569 dlist
.append(f
"{name}={value[0]}")
571 dlist
.append(str(define
[0]))
573 for macro
, value
in defs
.items():
575 dlist
.append(str(macro
))
577 dlist
.append(f
"{macro}={value}")
578 elif is_String(defs
):
581 dlist
.append(str(defs
))
586 def _defines(prefix
, defs
, suffix
, env
, target
=None, source
=None, c
=_concat_ixes
):
587 """A wrapper around :func:`_concat_ixes` that turns a list or string
588 into a list of C preprocessor command-line definitions.
590 return c(prefix
, env
.subst_list(processDefines(defs
), target
=target
, source
=source
), suffix
, env
)
593 class NullCmdGenerator
:
594 """This is a callable class that can be used in place of other
595 command generators if you don't want them to do anything.
597 The __call__ method for this class simply returns the thing
598 you instantiated it with.
601 env["DO_NOTHING"] = NullCmdGenerator
602 env["LINKCOM"] = "${DO_NOTHING('$LINK $SOURCES $TARGET')}"
605 def __init__(self
, cmd
) -> None:
608 def __call__(self
, target
, source
, env
, for_signature
=None):
612 class Variable_Method_Caller
:
613 """A class for finding a construction variable on the stack and
614 calling one of its methods.
616 We use this to support "construction variables" in our string
617 eval()s that actually stand in for methods--specifically, use
618 of "RDirs" in call to _concat that should actually execute the
619 "TARGET.RDirs" method. (We used to support this by creating a little
620 "build dictionary" that mapped RDirs to the method, but this got in
621 the way of Memoizing construction environments, because we had to
622 create new environment objects to hold the variables.)
625 def __init__(self
, variable
, method
) -> None:
626 self
.variable
= variable
629 def __call__(self
, *args
, **kw
):
632 except ZeroDivisionError:
633 # Don't start iterating with the current stack-frame to
634 # prevent creating reference cycles (f_back is safe).
635 frame
= sys
.exc_info()[2].tb_frame
.f_back
636 variable
= self
.variable
638 if variable
in frame
.f_locals
:
639 v
= frame
.f_locals
[variable
]
641 method
= getattr(v
, self
.method
)
642 return method(*args
, **kw
)
647 def __libversionflags(env
, version_var
, flags_var
):
649 if version_var is not empty, returns env[flags_var], otherwise returns None
656 if env
.subst('$' + version_var
):
657 return env
[flags_var
]
663 def __lib_either_version_flag(env
, version_var1
, version_var2
, flags_var
):
665 if $version_var1 or $version_var2 is not empty, returns env[flags_var], otherwise returns None
673 if env
.subst('$' + version_var1
) or env
.subst('$' + version_var2
):
674 return env
[flags_var
]
683 ConstructionEnvironment
= {
685 'SCANNERS': [SCons
.Tool
.SourceFileScanner
],
686 'CONFIGUREDIR': '#/.sconf_temp',
687 'CONFIGURELOG': '#/config.log',
688 'CPPSUFFIXES': SCons
.Tool
.CSuffixes
,
689 'DSUFFIXES': SCons
.Tool
.DSuffixes
,
691 'IDLSUFFIXES': SCons
.Tool
.IDLSuffixes
,
693 '_defines': _defines
,
694 '_stripixes': _stripixes
,
695 '_LIBFLAGS': '${_concat(LIBLINKPREFIX, LIBS, LIBLINKSUFFIX, __env__)}',
697 '_LIBDIRFLAGS': '${_concat(LIBDIRPREFIX, LIBPATH, LIBDIRSUFFIX, __env__, RDirs, TARGET, SOURCE, affect_signature=False)}',
698 '_CPPINCFLAGS': '${_concat(INCPREFIX, CPPPATH, INCSUFFIX, __env__, RDirs, TARGET, SOURCE, affect_signature=False)}',
700 '_CPPDEFFLAGS': '${_defines(CPPDEFPREFIX, CPPDEFINES, CPPDEFSUFFIX, __env__, TARGET, SOURCE)}',
702 '__libversionflags': __libversionflags
,
703 '__SHLIBVERSIONFLAGS': '${__libversionflags(__env__,"SHLIBVERSION","_SHLIBVERSIONFLAGS")}',
704 '__LDMODULEVERSIONFLAGS': '${__libversionflags(__env__,"LDMODULEVERSION","_LDMODULEVERSIONFLAGS")}',
705 '__DSHLIBVERSIONFLAGS': '${__libversionflags(__env__,"DSHLIBVERSION","_DSHLIBVERSIONFLAGS")}',
706 '__lib_either_version_flag': __lib_either_version_flag
,
708 'TEMPFILE': NullCmdGenerator
,
709 'TEMPFILEARGJOIN': ' ',
710 'TEMPFILEARGESCFUNC': SCons
.Subst
.quote_spaces
,
711 'Dir': Variable_Method_Caller('TARGET', 'Dir'),
712 'Dirs': Variable_Method_Caller('TARGET', 'Dirs'),
713 'File': Variable_Method_Caller('TARGET', 'File'),
714 'RDirs': Variable_Method_Caller('TARGET', 'RDirs'),
719 # indent-tabs-mode:nil
721 # vim: set expandtab tabstop=4 shiftwidth=4: