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
, Callable
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
, **kwargs
):
62 """Returns the already-created default construction environment."""
66 def DefaultEnvironment(*args
, **kwargs
):
67 """Construct the global ("default") construction environment.
69 The environment is provisioned with the values from *kwargs*.
71 After the environment is created, this function is replaced with
72 a reference to :func:`_fetch_DefaultEnvironment` which efficiently
73 returns the initialized default construction environment without
74 checking for its existence.
76 Historically, some parts of the code held references to this function.
77 Thus it still has the existence check for :data:`_default_env` rather
78 than just blindly creating the environment and overwriting itself.
82 _default_env
= SCons
.Environment
.Environment(*args
, **kwargs
)
83 _default_env
.Decider('content')
84 global DefaultEnvironment
85 DefaultEnvironment
= _fetch_DefaultEnvironment
86 _default_env
._CacheDir
_path
= None
90 # Emitters for setting the shared attribute on object files,
91 # and an action for checking that all of the source files
92 # going into a shared library are, in fact, shared.
93 def StaticObjectEmitter(target
, source
, env
):
95 tgt
.attributes
.shared
= False
99 def SharedObjectEmitter(target
, source
, env
):
101 tgt
.attributes
.shared
= 1
102 return target
, source
105 def SharedFlagChecker(source
, target
, env
):
106 same
= env
.subst('$STATIC_AND_SHARED_OBJECTS_ARE_THE_SAME')
107 if same
== '0' or same
== '' or same
== 'False':
110 shared
= src
.attributes
.shared
111 except AttributeError:
114 raise SCons
.Errors
.UserError(
115 "Source file: %s is static and is not compatible with shared target: %s" % (src
, target
[0]))
118 SharedCheck
= SCons
.Action
.Action(SharedFlagChecker
, None)
120 # Some people were using these variable name before we made
121 # SourceFileScanner part of the public interface. Don't break their
122 # SConscript files until we've given them some fair warning and a
124 CScan
= SCons
.Tool
.CScanner
125 DScan
= SCons
.Tool
.DScanner
126 LaTeXScan
= SCons
.Tool
.LaTeXScanner
127 ObjSourceScan
= SCons
.Tool
.SourceFileScanner
128 ProgScan
= SCons
.Tool
.ProgramScanner
130 # These aren't really tool scanners, so they don't quite belong with
131 # the rest of those in Tool/__init__.py, but I'm not sure where else
132 # they should go. Leave them here for now.
134 DirScanner
= SCons
.Scanner
.Dir
.DirScanner()
135 DirEntryScanner
= SCons
.Scanner
.Dir
.DirEntryScanner()
137 # Actions for common languages.
138 CAction
= SCons
.Action
.Action("$CCCOM", "$CCCOMSTR")
139 ShCAction
= SCons
.Action
.Action("$SHCCCOM", "$SHCCCOMSTR")
140 CXXAction
= SCons
.Action
.Action("$CXXCOM", "$CXXCOMSTR")
141 ShCXXAction
= SCons
.Action
.Action("$SHCXXCOM", "$SHCXXCOMSTR")
143 DAction
= SCons
.Action
.Action("$DCOM", "$DCOMSTR")
144 ShDAction
= SCons
.Action
.Action("$SHDCOM", "$SHDCOMSTR")
146 ASAction
= SCons
.Action
.Action("$ASCOM", "$ASCOMSTR")
147 ASPPAction
= SCons
.Action
.Action("$ASPPCOM", "$ASPPCOMSTR")
149 LinkAction
= SCons
.Action
.Action("$LINKCOM", "$LINKCOMSTR")
150 ShLinkAction
= SCons
.Action
.Action("$SHLINKCOM", "$SHLINKCOMSTR")
152 LdModuleLinkAction
= SCons
.Action
.Action("$LDMODULECOM", "$LDMODULECOMSTR")
154 # Common tasks that we allow users to perform in platform-independent
155 # ways by creating ActionFactory instances.
156 ActionFactory
= SCons
.Action
.ActionFactory
159 def get_paths_str(dest
) -> str:
160 """Generates a string from *dest* for use in a strfunction.
162 If *dest* is a list, manually converts each elem to a string.
164 def quote(arg
) -> str:
168 elem_strs
= [quote(d
) for d
in dest
]
169 return f
'[{", ".join(elem_strs)}]'
193 def chmod_func(dest
, mode
) -> None:
194 """Implementation of the Chmod action function.
196 *mode* can be either an integer (normally expressed in octal mode,
197 as in 0o755) or a string following the syntax of the POSIX chmod
198 command (for example "ugo+w"). The latter must be converted, since
199 the underlying Python only takes the numeric form.
201 from string
import digits
202 SCons
.Node
.FS
.invalidate_node_memos(dest
)
203 if not is_List(dest
):
205 if is_String(mode
) and 0 not in [i
in digits
for i
in mode
]:
207 if not is_String(mode
):
209 os
.chmod(str(element
), mode
)
212 for operation
in mode
.split(","):
215 elif "+" in operation
:
217 elif "-" in operation
:
220 raise SyntaxError("Could not find +, - or =")
221 operation_list
= operation
.split(operator
)
222 if len(operation_list
) != 2:
223 raise SyntaxError("More than one operator found")
224 user
= operation_list
[0].strip().replace("a", "ugo")
225 permission
= operation_list
[1].strip()
230 new_perm
= new_perm | permission_dic
[u
][p
]
232 raise SyntaxError("Unrecognized user or permission format")
234 curr_perm
= os
.stat(str(element
)).st_mode
236 os
.chmod(str(element
), new_perm
)
237 elif operator
== "+":
238 os
.chmod(str(element
), curr_perm | new_perm
)
239 elif operator
== "-":
240 os
.chmod(str(element
), curr_perm
& ~new_perm
)
243 def chmod_strfunc(dest
, mode
) -> str:
244 """strfunction for the Chmod action function."""
245 if not is_String(mode
):
246 return f
'Chmod({get_paths_str(dest)}, {mode:#o})'
248 return f
'Chmod({get_paths_str(dest)}, "{mode}")'
252 Chmod
= ActionFactory(chmod_func
, chmod_strfunc
)
256 def copy_func(dest
, src
, symlinks
: bool=True) -> int:
257 """Implementation of the Copy action function.
259 Copies *src* to *dest*. If *src* is a list, *dest* must be
260 a directory, or not exist (will be created).
262 Since Python :mod:`shutil` methods, which know nothing about
263 SCons Nodes, will be called to perform the actual copying,
264 args are converted to strings first.
266 If *symlinks* evaluates true, then a symbolic link will be
267 shallow copied and recreated as a symbolic link; otherwise, copying
268 a symbolic link will be equivalent to copying the symbolic link's
269 final target regardless of symbolic link depth.
273 src
= [str(n
) for n
in src
] if is_List(src
) else str(src
)
275 SCons
.Node
.FS
.invalidate_node_memos(dest
)
277 # this fails only if dest exists and is not a dir
279 os
.makedirs(dest
, exist_ok
=True)
280 except FileExistsError
:
281 raise SCons
.Errors
.BuildError(
283 'Error: Copy() called with a list of sources, '
284 'which requires target to be a directory, '
285 f
'but "{dest}" is not a directory.'
289 shutil
.copy2(file, dest
)
292 elif os
.path
.islink(src
):
295 os
.symlink(os
.readlink(src
), dest
)
296 except FileExistsError
:
297 raise SCons
.Errors
.BuildError(
299 f
'Error: Copy() called to create symlink at "{dest}",'
300 ' but a file already exists at that location.'
305 return copy_func(dest
, os
.path
.realpath(src
))
307 elif os
.path
.isfile(src
):
308 shutil
.copy2(src
, dest
)
312 shutil
.copytree(src
, dest
, symlinks
)
316 def copy_strfunc(dest
, src
, symlinks
: bool=True) -> str:
317 """strfunction for the Copy action function."""
318 return f
'Copy({get_paths_str(dest)}, {get_paths_str(src)})'
321 Copy
= ActionFactory(copy_func
, copy_strfunc
)
324 def delete_func(dest
, must_exist
: bool=False) -> None:
325 """Implementation of the Delete action function.
327 Lets the Python :func:`os.unlink` raise an error if *dest* does not exist,
328 unless *must_exist* evaluates false (the default).
330 SCons
.Node
.FS
.invalidate_node_memos(dest
)
331 if not is_List(dest
):
335 # os.path.exists returns False with broken links that exist
336 entry_exists
= os
.path
.exists(entry
) or os
.path
.islink(entry
)
337 if not entry_exists
and not must_exist
:
339 # os.path.isdir returns True when entry is a link to a dir
340 if os
.path
.isdir(entry
) and not os
.path
.islink(entry
):
341 shutil
.rmtree(entry
, True)
346 def delete_strfunc(dest
, must_exist
: bool=False) -> str:
347 """strfunction for the Delete action function."""
348 return f
'Delete({get_paths_str(dest)})'
351 Delete
= ActionFactory(delete_func
, delete_strfunc
)
354 def mkdir_func(dest
) -> None:
355 """Implementation of the Mkdir action function."""
356 SCons
.Node
.FS
.invalidate_node_memos(dest
)
357 if not is_List(dest
):
360 os
.makedirs(str(entry
), exist_ok
=True)
363 Mkdir
= ActionFactory(mkdir_func
, lambda _dir
: f
'Mkdir({get_paths_str(_dir)})')
366 def move_func(dest
, src
) -> None:
367 """Implementation of the Move action function."""
368 SCons
.Node
.FS
.invalidate_node_memos(dest
)
369 SCons
.Node
.FS
.invalidate_node_memos(src
)
370 shutil
.move(src
, dest
)
373 Move
= ActionFactory(
374 move_func
, lambda dest
, src
: f
'Move("{dest}", "{src}")', convert
=str
378 def touch_func(dest
) -> None:
379 """Implementation of the Touch action function."""
380 SCons
.Node
.FS
.invalidate_node_memos(dest
)
381 if not is_List(dest
):
385 mtime
= int(time
.time())
386 if os
.path
.exists(file):
387 atime
= os
.path
.getatime(file)
389 with
open(file, 'w'):
391 os
.utime(file, (atime
, mtime
))
394 Touch
= ActionFactory(touch_func
, lambda file: f
'Touch({get_paths_str(file)})')
397 # Internal utility functions
399 # pylint: disable-msg=too-many-arguments
400 def _concat(prefix
, items_iter
, suffix
, env
, f
=lambda x
: x
, target
=None, source
=None, affect_signature
: bool=True):
402 Creates a new list from 'items_iter' by first interpolating each element
403 in the list using the 'env' dictionary and then calling f on the
404 list, and finally calling _concat_ixes to concatenate 'prefix' and
405 'suffix' onto each element of the list.
411 l
= f(SCons
.PathList
.PathList(items_iter
).subst_path(env
, target
, source
))
415 if not affect_signature
:
419 value
+= _concat_ixes(prefix
, items_iter
, suffix
, env
)
421 if not affect_signature
:
425 # pylint: enable-msg=too-many-arguments
428 def _concat_ixes(prefix
, items_iter
, suffix
, env
):
430 Creates a new list from 'items_iter' by concatenating the 'prefix' and
431 'suffix' arguments onto each element of the list. A trailing space
432 on 'prefix' or leading space on 'suffix' will cause them to be put
433 into separate list elements rather than being concatenated.
438 # ensure that prefix and suffix are strings
439 prefix
= str(env
.subst(prefix
, SCons
.Subst
.SUBST_RAW
))
440 suffix
= str(env
.subst(suffix
, SCons
.Subst
.SUBST_RAW
))
442 for x
in flatten(items_iter
):
443 if isinstance(x
, SCons
.Node
.FS
.File
):
450 if prefix
[-1] == ' ':
451 result
.append(prefix
[:-1])
452 elif x
[:len(prefix
)] != prefix
:
459 result
.append(suffix
[1:])
460 elif x
[-len(suffix
):] != suffix
:
461 result
[-1] = result
[-1] + suffix
470 stripprefixes
: List
[str],
471 stripsuffixes
: List
[str],
473 literal_prefix
: str = "",
474 c
: Callable
[[list], list] = None,
476 """Returns a list with text added to items after first stripping them.
478 A companion to :func:`_concat_ixes`, used by tools (like the GNU
479 linker) that need to turn something like ``libfoo.a`` into ``-lfoo``.
480 *stripprefixes* and *stripsuffixes* are stripped from *items*.
481 Calls function *c* to postprocess the result.
484 prefix: string to prepend to elements
485 items: string or iterable to transform
486 suffix: string to append to elements
487 stripprefixes: prefix string(s) to strip from elements
488 stripsuffixes: suffix string(s) to strip from elements
489 env: construction environment for variable interpolation
490 c: optional function to perform a transformation on the list.
491 The default is `None`, which will select :func:`_concat_ixes`.
497 env_c
= env
['_concat']
498 if env_c
!= _concat
and callable(env_c
):
499 # There's a custom _concat() method in the construction
500 # environment, and we've allowed people to set that in
501 # the past (see test/custom-concat.py), so preserve the
502 # backwards compatibility.
507 stripprefixes
= list(map(env
.subst
, flatten(stripprefixes
)))
508 stripsuffixes
= list(map(env
.subst
, flatten(stripsuffixes
)))
510 # This is a little funky: if literal_prefix is the same as os.pathsep
511 # (e.g. both ':'), the normal conversion to a PathList will drop the
512 # literal_prefix prefix. Tell it not to split in that case, which *should*
513 # be okay because if we come through here, we're normally processing
514 # library names and won't have strings like "path:secondpath:thirdpath"
515 # which is why PathList() otherwise wants to split strings.
516 do_split
= not literal_prefix
== os
.pathsep
519 for l
in SCons
.PathList
.PathList(items
, do_split
).subst_path(env
, None, None):
520 if isinstance(l
, SCons
.Node
.FS
.File
):
527 if literal_prefix
and l
.startswith(literal_prefix
):
531 for stripprefix
in stripprefixes
:
532 lsp
= len(stripprefix
)
533 if l
[:lsp
] == stripprefix
:
535 # Do not strip more than one prefix
538 for stripsuffix
in stripsuffixes
:
539 lss
= len(stripsuffix
)
540 if l
[-lss
:] == stripsuffix
:
542 # Do not strip more than one suffix
547 return c(prefix
, stripped
, suffix
, env
)
550 def processDefines(defs
) -> List
[str]:
551 """Return list of strings for preprocessor defines from *defs*.
553 Resolves the different forms ``CPPDEFINES`` can be assembled in:
554 if the Append/Prepend routines are used beyond a initial setting it
555 will be a deque, but if written to only once (Environment initializer,
556 or direct write) it can be a multitude of types.
558 Any prefix/suffix is handled elsewhere (usually :func:`_concat_ixes`).
560 .. versionchanged:: 4.5.0
561 Bare tuples are now treated the same as tuple-in-sequence, assumed
562 to describe a valued macro. Bare strings are now split on space.
563 A dictionary is no longer sorted before handling.
570 elif is_Sequence(define
):
572 raise SCons
.Errors
.UserError(
573 f
"Invalid tuple in CPPDEFINES: {define!r}, "
574 "must be a tuple with only two elements"
576 name
, *value
= define
577 if value
and value
[0] is not None:
578 # TODO: do we need to quote value if it contains space?
579 dlist
.append(f
"{name}={value[0]}")
581 dlist
.append(str(define
[0]))
582 elif is_Dict(define
):
583 for macro
, value
in define
.items():
584 if value
is not None:
585 # TODO: do we need to quote value if it contains space?
586 dlist
.append(f
"{macro}={value}")
588 dlist
.append(str(macro
))
589 elif is_String(define
):
590 dlist
.append(str(define
))
592 raise SCons
.Errors
.UserError(
593 f
"CPPDEFINES entry {define!r} is not a tuple, list, "
594 "dict, string or None."
598 raise SCons
.Errors
.UserError(
599 f
"Invalid tuple in CPPDEFINES: {defs!r}, "
600 "must be a tuple with only two elements"
603 if value
and value
[0] is not None:
604 # TODO: do we need to quote value if it contains space?
605 dlist
.append(f
"{name}={value[0]}")
607 dlist
.append(str(define
[0]))
609 for macro
, value
in defs
.items():
611 dlist
.append(str(macro
))
613 dlist
.append(f
"{macro}={value}")
614 elif is_String(defs
):
617 dlist
.append(str(defs
))
622 def _defines(prefix
, defs
, suffix
, env
, target
=None, source
=None, c
=_concat_ixes
):
623 """A wrapper around :func:`_concat_ixes` that turns a list or string
624 into a list of C preprocessor command-line definitions.
626 return c(prefix
, env
.subst_list(processDefines(defs
), target
=target
, source
=source
), suffix
, env
)
629 class NullCmdGenerator
:
630 """Callable class for use as a no-effect command generator.
632 The ``__call__`` method for this class simply returns the thing
633 you instantiated it with. Example usage::
635 env["DO_NOTHING"] = NullCmdGenerator
636 env["LINKCOM"] = "${DO_NOTHING('$LINK $SOURCES $TARGET')}"
639 def __init__(self
, cmd
) -> None:
642 def __call__(self
, target
, source
, env
, for_signature
=None):
646 class Variable_Method_Caller
:
647 """A class for finding a construction variable on the stack and
648 calling one of its methods.
650 Used to support "construction variables" appearing in string
651 ``eval``s that actually stand in for methods--specifically, the use
652 of "RDirs" in a call to :func:`_concat` that should actually execute the
653 ``TARGET.RDirs`` method.
655 Historical note: This was formerly supported by creating a little
656 "build dictionary" that mapped RDirs to the method, but this got
657 in the way of Memoizing construction environments, because we had to
658 create new environment objects to hold the variables.
661 def __init__(self
, variable
, method
) -> None:
662 self
.variable
= variable
665 def __call__(self
, *args
, **kw
):
668 except ZeroDivisionError:
669 # Don't start iterating with the current stack-frame to
670 # prevent creating reference cycles (f_back is safe).
671 frame
= sys
.exc_info()[2].tb_frame
.f_back
672 variable
= self
.variable
674 if variable
in frame
.f_locals
:
675 v
= frame
.f_locals
[variable
]
677 method
= getattr(v
, self
.method
)
678 return method(*args
, **kw
)
683 def __libversionflags(env
, version_var
, flags_var
):
685 if version_var is not empty, returns env[flags_var], otherwise returns None
692 if env
.subst('$' + version_var
):
693 return env
[flags_var
]
699 def __lib_either_version_flag(env
, version_var1
, version_var2
, flags_var
):
701 if $version_var1 or $version_var2 is not empty, returns env[flags_var], otherwise returns None
709 if env
.subst('$' + version_var1
) or env
.subst('$' + version_var2
):
710 return env
[flags_var
]
719 ConstructionEnvironment
= {
721 'SCANNERS': [SCons
.Tool
.SourceFileScanner
],
722 'CONFIGUREDIR': '#/.sconf_temp',
723 'CONFIGURELOG': '#/config.log',
724 'CPPSUFFIXES': SCons
.Tool
.CSuffixes
,
725 'DSUFFIXES': SCons
.Tool
.DSuffixes
,
727 'IDLSUFFIXES': SCons
.Tool
.IDLSuffixes
,
729 '_defines': _defines
,
730 '_stripixes': _stripixes
,
731 '_LIBFLAGS': '${_concat(LIBLINKPREFIX, LIBS, LIBLINKSUFFIX, __env__)}',
733 '_LIBDIRFLAGS': '${_concat(LIBDIRPREFIX, LIBPATH, LIBDIRSUFFIX, __env__, RDirs, TARGET, SOURCE, affect_signature=False)}',
734 '_CPPINCFLAGS': '${_concat(INCPREFIX, CPPPATH, INCSUFFIX, __env__, RDirs, TARGET, SOURCE, affect_signature=False)}',
736 '_CPPDEFFLAGS': '${_defines(CPPDEFPREFIX, CPPDEFINES, CPPDEFSUFFIX, __env__, TARGET, SOURCE)}',
738 '__libversionflags': __libversionflags
,
739 '__SHLIBVERSIONFLAGS': '${__libversionflags(__env__,"SHLIBVERSION","_SHLIBVERSIONFLAGS")}',
740 '__LDMODULEVERSIONFLAGS': '${__libversionflags(__env__,"LDMODULEVERSION","_LDMODULEVERSIONFLAGS")}',
741 '__DSHLIBVERSIONFLAGS': '${__libversionflags(__env__,"DSHLIBVERSION","_DSHLIBVERSIONFLAGS")}',
742 '__lib_either_version_flag': __lib_either_version_flag
,
744 'TEMPFILE': NullCmdGenerator
,
745 'TEMPFILEARGJOIN': ' ',
746 'TEMPFILEARGESCFUNC': SCons
.Subst
.quote_spaces
,
747 'Dir': Variable_Method_Caller('TARGET', 'Dir'),
748 'Dirs': Variable_Method_Caller('TARGET', 'Dirs'),
749 'File': Variable_Method_Caller('TARGET', 'File'),
750 'RDirs': Variable_Method_Caller('TARGET', 'RDirs'),
755 # indent-tabs-mode:nil
757 # vim: set expandtab tabstop=4 shiftwidth=4: