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
):
294 os
.symlink(os
.readlink(src
), dest
)
297 return copy_func(dest
, os
.path
.realpath(src
))
299 elif os
.path
.isfile(src
):
300 shutil
.copy2(src
, dest
)
304 shutil
.copytree(src
, dest
, symlinks
)
308 def copy_strfunc(dest
, src
, symlinks
: bool=True) -> str:
309 """strfunction for the Copy action function."""
310 return f
'Copy({get_paths_str(dest)}, {get_paths_str(src)})'
313 Copy
= ActionFactory(copy_func
, copy_strfunc
)
316 def delete_func(dest
, must_exist
: bool=False) -> None:
317 """Implementation of the Delete action function.
319 Lets the Python :func:`os.unlink` raise an error if *dest* does not exist,
320 unless *must_exist* evaluates false (the default).
322 SCons
.Node
.FS
.invalidate_node_memos(dest
)
323 if not is_List(dest
):
327 # os.path.exists returns False with broken links that exist
328 entry_exists
= os
.path
.exists(entry
) or os
.path
.islink(entry
)
329 if not entry_exists
and not must_exist
:
331 # os.path.isdir returns True when entry is a link to a dir
332 if os
.path
.isdir(entry
) and not os
.path
.islink(entry
):
333 shutil
.rmtree(entry
, True)
338 def delete_strfunc(dest
, must_exist
: bool=False) -> str:
339 """strfunction for the Delete action function."""
340 return f
'Delete({get_paths_str(dest)})'
343 Delete
= ActionFactory(delete_func
, delete_strfunc
)
346 def mkdir_func(dest
) -> None:
347 """Implementation of the Mkdir action function."""
348 SCons
.Node
.FS
.invalidate_node_memos(dest
)
349 if not is_List(dest
):
352 os
.makedirs(str(entry
), exist_ok
=True)
355 Mkdir
= ActionFactory(mkdir_func
, lambda _dir
: f
'Mkdir({get_paths_str(_dir)})')
358 def move_func(dest
, src
) -> None:
359 """Implementation of the Move action function."""
360 SCons
.Node
.FS
.invalidate_node_memos(dest
)
361 SCons
.Node
.FS
.invalidate_node_memos(src
)
362 shutil
.move(src
, dest
)
365 Move
= ActionFactory(
366 move_func
, lambda dest
, src
: f
'Move("{dest}", "{src}")', convert
=str
370 def touch_func(dest
) -> None:
371 """Implementation of the Touch action function."""
372 SCons
.Node
.FS
.invalidate_node_memos(dest
)
373 if not is_List(dest
):
377 mtime
= int(time
.time())
378 if os
.path
.exists(file):
379 atime
= os
.path
.getatime(file)
381 with
open(file, 'w'):
383 os
.utime(file, (atime
, mtime
))
386 Touch
= ActionFactory(touch_func
, lambda file: f
'Touch({get_paths_str(file)})')
389 # Internal utility functions
391 # pylint: disable-msg=too-many-arguments
392 def _concat(prefix
, items_iter
, suffix
, env
, f
=lambda x
: x
, target
=None, source
=None, affect_signature
: bool=True):
394 Creates a new list from 'items_iter' by first interpolating each element
395 in the list using the 'env' dictionary and then calling f on the
396 list, and finally calling _concat_ixes to concatenate 'prefix' and
397 'suffix' onto each element of the list.
403 l
= f(SCons
.PathList
.PathList(items_iter
).subst_path(env
, target
, source
))
407 if not affect_signature
:
411 value
+= _concat_ixes(prefix
, items_iter
, suffix
, env
)
413 if not affect_signature
:
417 # pylint: enable-msg=too-many-arguments
420 def _concat_ixes(prefix
, items_iter
, suffix
, env
):
422 Creates a new list from 'items_iter' by concatenating the 'prefix' and
423 'suffix' arguments onto each element of the list. A trailing space
424 on 'prefix' or leading space on 'suffix' will cause them to be put
425 into separate list elements rather than being concatenated.
430 # ensure that prefix and suffix are strings
431 prefix
= str(env
.subst(prefix
, SCons
.Subst
.SUBST_RAW
))
432 suffix
= str(env
.subst(suffix
, SCons
.Subst
.SUBST_RAW
))
434 for x
in flatten(items_iter
):
435 if isinstance(x
, SCons
.Node
.FS
.File
):
442 if prefix
[-1] == ' ':
443 result
.append(prefix
[:-1])
444 elif x
[:len(prefix
)] != prefix
:
451 result
.append(suffix
[1:])
452 elif x
[-len(suffix
):] != suffix
:
453 result
[-1] = result
[-1] + suffix
462 stripprefixes
: List
[str],
463 stripsuffixes
: List
[str],
465 literal_prefix
: str = "",
466 c
: Callable
[[list], list] = None,
468 """Returns a list with text added to items after first stripping them.
470 A companion to :func:`_concat_ixes`, used by tools (like the GNU
471 linker) that need to turn something like ``libfoo.a`` into ``-lfoo``.
472 *stripprefixes* and *stripsuffixes* are stripped from *items*.
473 Calls function *c* to postprocess the result.
476 prefix: string to prepend to elements
477 items: string or iterable to transform
478 suffix: string to append to elements
479 stripprefixes: prefix string(s) to strip from elements
480 stripsuffixes: suffix string(s) to strip from elements
481 env: construction environment for variable interpolation
482 c: optional function to perform a transformation on the list.
483 The default is `None`, which will select :func:`_concat_ixes`.
489 env_c
= env
['_concat']
490 if env_c
!= _concat
and callable(env_c
):
491 # There's a custom _concat() method in the construction
492 # environment, and we've allowed people to set that in
493 # the past (see test/custom-concat.py), so preserve the
494 # backwards compatibility.
499 stripprefixes
= list(map(env
.subst
, flatten(stripprefixes
)))
500 stripsuffixes
= list(map(env
.subst
, flatten(stripsuffixes
)))
502 # This is a little funky: if literal_prefix is the same as os.pathsep
503 # (e.g. both ':'), the normal conversion to a PathList will drop the
504 # literal_prefix prefix. Tell it not to split in that case, which *should*
505 # be okay because if we come through here, we're normally processing
506 # library names and won't have strings like "path:secondpath:thirdpath"
507 # which is why PathList() otherwise wants to split strings.
508 do_split
= not literal_prefix
== os
.pathsep
511 for l
in SCons
.PathList
.PathList(items
, do_split
).subst_path(env
, None, None):
512 if isinstance(l
, SCons
.Node
.FS
.File
):
519 if literal_prefix
and l
.startswith(literal_prefix
):
523 for stripprefix
in stripprefixes
:
524 lsp
= len(stripprefix
)
525 if l
[:lsp
] == stripprefix
:
527 # Do not strip more than one prefix
530 for stripsuffix
in stripsuffixes
:
531 lss
= len(stripsuffix
)
532 if l
[-lss
:] == stripsuffix
:
534 # Do not strip more than one suffix
539 return c(prefix
, stripped
, suffix
, env
)
542 def processDefines(defs
) -> List
[str]:
543 """Return list of strings for preprocessor defines from *defs*.
545 Resolves the different forms ``CPPDEFINES`` can be assembled in:
546 if the Append/Prepend routines are used beyond a initial setting it
547 will be a deque, but if written to only once (Environment initializer,
548 or direct write) it can be a multitude of types.
550 Any prefix/suffix is handled elsewhere (usually :func:`_concat_ixes`).
552 .. versionchanged:: 4.5.0
553 Bare tuples are now treated the same as tuple-in-sequence, assumed
554 to describe a valued macro. Bare strings are now split on space.
555 A dictionary is no longer sorted before handling.
562 elif is_Sequence(define
):
564 raise SCons
.Errors
.UserError(
565 f
"Invalid tuple in CPPDEFINES: {define!r}, "
566 "must be a tuple with only two elements"
568 name
, *value
= define
569 if value
and value
[0] is not None:
570 # TODO: do we need to quote value if it contains space?
571 dlist
.append(f
"{name}={value[0]}")
573 dlist
.append(str(define
[0]))
574 elif is_Dict(define
):
575 for macro
, value
in define
.items():
576 if value
is not None:
577 # TODO: do we need to quote value if it contains space?
578 dlist
.append(f
"{macro}={value}")
580 dlist
.append(str(macro
))
581 elif is_String(define
):
582 dlist
.append(str(define
))
584 raise SCons
.Errors
.UserError(
585 f
"CPPDEFINES entry {define!r} is not a tuple, list, "
586 "dict, string or None."
590 raise SCons
.Errors
.UserError(
591 f
"Invalid tuple in CPPDEFINES: {defs!r}, "
592 "must be a tuple with only two elements"
595 if value
and value
[0] is not None:
596 # TODO: do we need to quote value if it contains space?
597 dlist
.append(f
"{name}={value[0]}")
599 dlist
.append(str(define
[0]))
601 for macro
, value
in defs
.items():
603 dlist
.append(str(macro
))
605 dlist
.append(f
"{macro}={value}")
606 elif is_String(defs
):
609 dlist
.append(str(defs
))
614 def _defines(prefix
, defs
, suffix
, env
, target
=None, source
=None, c
=_concat_ixes
):
615 """A wrapper around :func:`_concat_ixes` that turns a list or string
616 into a list of C preprocessor command-line definitions.
618 return c(prefix
, env
.subst_list(processDefines(defs
), target
=target
, source
=source
), suffix
, env
)
621 class NullCmdGenerator
:
622 """Callable class for use as a no-effect command generator.
624 The ``__call__`` method for this class simply returns the thing
625 you instantiated it with. Example usage::
627 env["DO_NOTHING"] = NullCmdGenerator
628 env["LINKCOM"] = "${DO_NOTHING('$LINK $SOURCES $TARGET')}"
631 def __init__(self
, cmd
) -> None:
634 def __call__(self
, target
, source
, env
, for_signature
=None):
638 class Variable_Method_Caller
:
639 """A class for finding a construction variable on the stack and
640 calling one of its methods.
642 Used to support "construction variables" appearing in string
643 ``eval``s that actually stand in for methods--specifically, the use
644 of "RDirs" in a call to :func:`_concat` that should actually execute the
645 ``TARGET.RDirs`` method.
647 Historical note: This was formerly supported by creating a little
648 "build dictionary" that mapped RDirs to the method, but this got
649 in the way of Memoizing construction environments, because we had to
650 create new environment objects to hold the variables.
653 def __init__(self
, variable
, method
) -> None:
654 self
.variable
= variable
657 def __call__(self
, *args
, **kw
):
660 except ZeroDivisionError:
661 # Don't start iterating with the current stack-frame to
662 # prevent creating reference cycles (f_back is safe).
663 frame
= sys
.exc_info()[2].tb_frame
.f_back
664 variable
= self
.variable
666 if variable
in frame
.f_locals
:
667 v
= frame
.f_locals
[variable
]
669 method
= getattr(v
, self
.method
)
670 return method(*args
, **kw
)
675 def __libversionflags(env
, version_var
, flags_var
):
677 if version_var is not empty, returns env[flags_var], otherwise returns None
684 if env
.subst('$' + version_var
):
685 return env
[flags_var
]
691 def __lib_either_version_flag(env
, version_var1
, version_var2
, flags_var
):
693 if $version_var1 or $version_var2 is not empty, returns env[flags_var], otherwise returns None
701 if env
.subst('$' + version_var1
) or env
.subst('$' + version_var2
):
702 return env
[flags_var
]
711 ConstructionEnvironment
= {
713 'SCANNERS': [SCons
.Tool
.SourceFileScanner
],
714 'CONFIGUREDIR': '#/.sconf_temp',
715 'CONFIGURELOG': '#/config.log',
716 'CPPSUFFIXES': SCons
.Tool
.CSuffixes
,
717 'DSUFFIXES': SCons
.Tool
.DSuffixes
,
719 'IDLSUFFIXES': SCons
.Tool
.IDLSuffixes
,
721 '_defines': _defines
,
722 '_stripixes': _stripixes
,
723 '_LIBFLAGS': '${_concat(LIBLINKPREFIX, LIBS, LIBLINKSUFFIX, __env__)}',
725 '_LIBDIRFLAGS': '${_concat(LIBDIRPREFIX, LIBPATH, LIBDIRSUFFIX, __env__, RDirs, TARGET, SOURCE, affect_signature=False)}',
726 '_CPPINCFLAGS': '${_concat(INCPREFIX, CPPPATH, INCSUFFIX, __env__, RDirs, TARGET, SOURCE, affect_signature=False)}',
728 '_CPPDEFFLAGS': '${_defines(CPPDEFPREFIX, CPPDEFINES, CPPDEFSUFFIX, __env__, TARGET, SOURCE)}',
730 '__libversionflags': __libversionflags
,
731 '__SHLIBVERSIONFLAGS': '${__libversionflags(__env__,"SHLIBVERSION","_SHLIBVERSIONFLAGS")}',
732 '__LDMODULEVERSIONFLAGS': '${__libversionflags(__env__,"LDMODULEVERSION","_LDMODULEVERSIONFLAGS")}',
733 '__DSHLIBVERSIONFLAGS': '${__libversionflags(__env__,"DSHLIBVERSION","_DSHLIBVERSIONFLAGS")}',
734 '__lib_either_version_flag': __lib_either_version_flag
,
736 'TEMPFILE': NullCmdGenerator
,
737 'TEMPFILEARGJOIN': ' ',
738 'TEMPFILEARGESCFUNC': SCons
.Subst
.quote_spaces
,
739 'Dir': Variable_Method_Caller('TARGET', 'Dir'),
740 'Dirs': Variable_Method_Caller('TARGET', 'Dirs'),
741 'File': Variable_Method_Caller('TARGET', 'File'),
742 'RDirs': Variable_Method_Caller('TARGET', 'RDirs'),
747 # indent-tabs-mode:nil
749 # vim: set expandtab tabstop=4 shiftwidth=4: