mirror of
https://github.com/mapnik/mapnik.git
synced 2025-12-08 20:13:09 +00:00
Upgrade to SCons v4.9.1
This commit is contained in:
parent
2525147a68
commit
659bf720d3
2
scons/scons-LICENSE
vendored
2
scons/scons-LICENSE
vendored
@ -5,7 +5,7 @@
|
||||
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2001 - 2024 The SCons Foundation
|
||||
Copyright (c) 2001 - 2025 The SCons Foundation
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
|
||||
12
scons/scons-configure-cache.py
vendored
12
scons/scons-configure-cache.py
vendored
@ -32,15 +32,15 @@ The files are split into directories named by the first few
|
||||
digits of the signature. The prefix length used for directory
|
||||
names can be changed by this script.
|
||||
"""
|
||||
__revision__ = "scripts/scons-configure-cache.py 08661ed4c552323ef3a7f0ff1af38868cbabb05e Tue, 03 Sep 2024 17:46:32 -0700 bdbaddog"
|
||||
__revision__ = "scripts/scons-configure-cache.py 39a12f34d532ab2493e78a7b73aeab2250852790 Thu, 27 Mar 2025 11:44:24 -0700 bdbaddog"
|
||||
|
||||
__version__ = "4.8.1"
|
||||
__version__ = "4.9.1"
|
||||
|
||||
__build__ = "08661ed4c552323ef3a7f0ff1af38868cbabb05e"
|
||||
__build__ = "39a12f34d532ab2493e78a7b73aeab2250852790"
|
||||
|
||||
__buildsys__ = "M1Dog2021"
|
||||
|
||||
__date__ = "Tue, 03 Sep 2024 17:46:32 -0700"
|
||||
__date__ = "Thu, 27 Mar 2025 11:44:24 -0700"
|
||||
|
||||
__developer__ = "bdbaddog"
|
||||
|
||||
@ -49,9 +49,9 @@ import os
|
||||
import sys
|
||||
|
||||
# python compatibility check
|
||||
if sys.version_info < (3, 6, 0):
|
||||
if sys.version_info < (3, 7, 0):
|
||||
msg = "scons: *** SCons version %s does not run under Python version %s.\n\
|
||||
Python >= 3.6.0 is required.\n"
|
||||
Python >= 3.7.0 is required.\n"
|
||||
sys.stderr.write(msg % (__version__, sys.version.split()[0]))
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
33
scons/scons-local-4.8.1/SCons/Util/sctyping.py
vendored
33
scons/scons-local-4.8.1/SCons/Util/sctyping.py
vendored
@ -1,33 +0,0 @@
|
||||
# SPDX-License-Identifier: MIT
|
||||
#
|
||||
# Copyright The SCons Foundation
|
||||
|
||||
"""Various SCons type aliases.
|
||||
|
||||
For representing complex types across the entire repo without risking
|
||||
circular dependencies, we take advantage of TYPE_CHECKING to import
|
||||
modules in an tool-only environment. This allows us to introduce
|
||||
hinting that resolves as expected in IDEs without clashing at runtime.
|
||||
|
||||
For consistency, it's recommended to ALWAYS use these aliases in a
|
||||
type-hinting context, even if the type is actually expected to be
|
||||
resolved in a given file.
|
||||
"""
|
||||
|
||||
from typing import Union, TYPE_CHECKING
|
||||
|
||||
if TYPE_CHECKING:
|
||||
import SCons.Executor
|
||||
|
||||
|
||||
# Because we don't have access to TypeAlias until 3.10, we have to utilize
|
||||
# 'Union' for all aliases. As it expects at least two entries, anything that
|
||||
# is only represented with a single type needs to list itself twice.
|
||||
ExecutorType = Union["SCons.Executor.Executor", "SCons.Executor.Executor"]
|
||||
|
||||
|
||||
# Local Variables:
|
||||
# tab-width:4
|
||||
# indent-tabs-mode:nil
|
||||
# End:
|
||||
# vim: set expandtab tabstop=4 shiftwidth=4:
|
||||
9
scons/scons-local-4.8.1/SCons/__init__.py
vendored
9
scons/scons-local-4.8.1/SCons/__init__.py
vendored
@ -1,9 +0,0 @@
|
||||
__version__="4.8.1"
|
||||
__copyright__="Copyright (c) 2001 - 2024 The SCons Foundation"
|
||||
__developer__="bdbaddog"
|
||||
__date__="Tue, 03 Sep 2024 17:46:32 -0700"
|
||||
__buildsys__="M1Dog2021"
|
||||
__revision__="08661ed4c552323ef3a7f0ff1af38868cbabb05e"
|
||||
__build__="08661ed4c552323ef3a7f0ff1af38868cbabb05e"
|
||||
# make sure compatibility is always in place
|
||||
import SCons.compat # noqa
|
||||
@ -100,6 +100,8 @@ way for wrapping up the functions.
|
||||
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import inspect
|
||||
import os
|
||||
import pickle
|
||||
@ -109,7 +111,7 @@ import sys
|
||||
from abc import ABC, abstractmethod
|
||||
from collections import OrderedDict
|
||||
from subprocess import DEVNULL, PIPE
|
||||
from typing import List, Optional, Tuple
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
import SCons.Debug
|
||||
import SCons.Errors
|
||||
@ -120,7 +122,9 @@ import SCons.Util
|
||||
from SCons.Debug import logInstanceCreation
|
||||
from SCons.Subst import SUBST_CMD, SUBST_RAW, SUBST_SIG
|
||||
from SCons.Util import is_String, is_List
|
||||
from SCons.Util.sctyping import ExecutorType
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from SCons.Executor import Executor
|
||||
|
||||
class _null:
|
||||
pass
|
||||
@ -481,9 +485,7 @@ def _do_create_action(act, kw):
|
||||
return None
|
||||
|
||||
|
||||
# TODO: from __future__ import annotations once we get to Python 3.7 base,
|
||||
# to avoid quoting the defined-later classname
|
||||
def _do_create_list_action(act, kw) -> "ListAction":
|
||||
def _do_create_list_action(act, kw) -> ListAction:
|
||||
"""A factory for list actions.
|
||||
|
||||
Convert the input list *act* into Actions and then wrap them in a
|
||||
@ -529,7 +531,7 @@ class ActionBase(ABC):
|
||||
show=_null,
|
||||
execute=_null,
|
||||
chdir=_null,
|
||||
executor: Optional[ExecutorType] = None,
|
||||
executor: Executor | None = None,
|
||||
):
|
||||
raise NotImplementedError
|
||||
|
||||
@ -541,15 +543,15 @@ class ActionBase(ABC):
|
||||
|
||||
batch_key = no_batch_key
|
||||
|
||||
def genstring(self, target, source, env, executor: Optional[ExecutorType] = None) -> str:
|
||||
def genstring(self, target, source, env, executor: Executor | None = None) -> str:
|
||||
return str(self)
|
||||
|
||||
@abstractmethod
|
||||
def get_presig(self, target, source, env, executor: Optional[ExecutorType] = None):
|
||||
def get_presig(self, target, source, env, executor: Executor | None = None):
|
||||
raise NotImplementedError
|
||||
|
||||
@abstractmethod
|
||||
def get_implicit_deps(self, target, source, env, executor: Optional[ExecutorType] = None):
|
||||
def get_implicit_deps(self, target, source, env, executor: Executor | None = None):
|
||||
raise NotImplementedError
|
||||
|
||||
def get_contents(self, target, source, env):
|
||||
@ -601,10 +603,10 @@ class ActionBase(ABC):
|
||||
self.presub_env = None # don't need this any more
|
||||
return lines
|
||||
|
||||
def get_varlist(self, target, source, env, executor: Optional[ExecutorType] = None):
|
||||
def get_varlist(self, target, source, env, executor: Executor | None = None):
|
||||
return self.varlist
|
||||
|
||||
def get_targets(self, env, executor: Optional[ExecutorType]):
|
||||
def get_targets(self, env, executor: Executor | None):
|
||||
"""
|
||||
Returns the type of targets ($TARGETS, $CHANGED_TARGETS) used
|
||||
by this action.
|
||||
@ -658,7 +660,7 @@ class _ActionAction(ActionBase):
|
||||
show=_null,
|
||||
execute=_null,
|
||||
chdir=_null,
|
||||
executor: Optional[ExecutorType] = None):
|
||||
executor: Executor | None = None):
|
||||
if not is_List(target):
|
||||
target = [target]
|
||||
if not is_List(source):
|
||||
@ -742,10 +744,10 @@ class _ActionAction(ActionBase):
|
||||
# an ABC like parent ActionBase, but things reach in and use it. It's
|
||||
# not just unittests or we could fix it up with a concrete subclass there.
|
||||
|
||||
def get_presig(self, target, source, env, executor: Optional[ExecutorType] = None):
|
||||
def get_presig(self, target, source, env, executor: Executor | None = None):
|
||||
raise NotImplementedError
|
||||
|
||||
def get_implicit_deps(self, target, source, env, executor: Optional[ExecutorType] = None):
|
||||
def get_implicit_deps(self, target, source, env, executor: Executor | None = None):
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
@ -891,15 +893,6 @@ def scons_subproc_run(scons_env, *args, **kwargs) -> subprocess.CompletedProcess
|
||||
del kwargs['error']
|
||||
kwargs['check'] = check
|
||||
|
||||
# TODO: Python version-compat stuff: remap/remove too-new args if needed
|
||||
if 'text' in kwargs and sys.version_info < (3, 7):
|
||||
kwargs['universal_newlines'] = kwargs.pop('text')
|
||||
|
||||
if 'capture_output' in kwargs and sys.version_info < (3, 7):
|
||||
capture_output = kwargs.pop('capture_output')
|
||||
if capture_output:
|
||||
kwargs['stdout'] = kwargs['stderr'] = PIPE
|
||||
|
||||
# Most SCons tools/tests expect not to fail on things like missing files.
|
||||
# check=True (or error="raise") means we're okay to take an exception;
|
||||
# else we catch the likely exception and construct a dummy
|
||||
@ -1010,7 +1003,7 @@ class CommandAction(_ActionAction):
|
||||
return str(self.cmd_list)
|
||||
|
||||
|
||||
def process(self, target, source, env, executor=None, overrides: Optional[dict] = None) -> Tuple[List, bool, bool]:
|
||||
def process(self, target, source, env, executor: Executor | None = None, overrides: dict | None = None) -> tuple[list, bool, bool]:
|
||||
if executor:
|
||||
result = env.subst_list(self.cmd_list, SUBST_CMD, executor=executor, overrides=overrides)
|
||||
else:
|
||||
@ -1031,7 +1024,7 @@ class CommandAction(_ActionAction):
|
||||
pass
|
||||
return result, ignore, silent
|
||||
|
||||
def strfunction(self, target, source, env, executor: Optional[ExecutorType] = None, overrides: Optional[dict] = None) -> str:
|
||||
def strfunction(self, target, source, env, executor: Executor | None = None, overrides: dict | None = None) -> str:
|
||||
if self.cmdstr is None:
|
||||
return None
|
||||
if self.cmdstr is not _null:
|
||||
@ -1046,7 +1039,7 @@ class CommandAction(_ActionAction):
|
||||
return ''
|
||||
return _string_from_cmd_list(cmd_list[0])
|
||||
|
||||
def execute(self, target, source, env, executor: Optional[ExecutorType] = None):
|
||||
def execute(self, target, source, env, executor: Executor | None = None):
|
||||
"""Execute a command action.
|
||||
|
||||
This will handle lists of commands as well as individual commands,
|
||||
@ -1108,7 +1101,7 @@ class CommandAction(_ActionAction):
|
||||
command=cmd_line)
|
||||
return 0
|
||||
|
||||
def get_presig(self, target, source, env, executor: Optional[ExecutorType] = None):
|
||||
def get_presig(self, target, source, env, executor: Executor | None = None):
|
||||
"""Return the signature contents of this action's command line.
|
||||
|
||||
This strips $(-$) and everything in between the string,
|
||||
@ -1123,7 +1116,7 @@ class CommandAction(_ActionAction):
|
||||
return env.subst_target_source(cmd, SUBST_SIG, executor=executor)
|
||||
return env.subst_target_source(cmd, SUBST_SIG, target, source)
|
||||
|
||||
def get_implicit_deps(self, target, source, env, executor: Optional[ExecutorType] = None):
|
||||
def get_implicit_deps(self, target, source, env, executor: Executor | None = None):
|
||||
"""Return the implicit dependencies of this action's command line."""
|
||||
icd = env.get('IMPLICIT_COMMAND_DEPENDENCIES', True)
|
||||
if is_String(icd) and icd[:1] == '$':
|
||||
@ -1145,7 +1138,7 @@ class CommandAction(_ActionAction):
|
||||
# lightweight dependency scanning.
|
||||
return self._get_implicit_deps_lightweight(target, source, env, executor)
|
||||
|
||||
def _get_implicit_deps_lightweight(self, target, source, env, executor: Optional[ExecutorType]):
|
||||
def _get_implicit_deps_lightweight(self, target, source, env, executor: Executor | None):
|
||||
"""
|
||||
Lightweight dependency scanning involves only scanning the first entry
|
||||
in an action string, even if it contains &&.
|
||||
@ -1166,7 +1159,7 @@ class CommandAction(_ActionAction):
|
||||
res.append(env.fs.File(d))
|
||||
return res
|
||||
|
||||
def _get_implicit_deps_heavyweight(self, target, source, env, executor: Optional[ExecutorType],
|
||||
def _get_implicit_deps_heavyweight(self, target, source, env, executor: Executor | None,
|
||||
icd_int):
|
||||
"""
|
||||
Heavyweight dependency scanning involves scanning more than just the
|
||||
@ -1234,7 +1227,7 @@ class CommandGeneratorAction(ActionBase):
|
||||
self.varlist = kw.get('varlist', ())
|
||||
self.targets = kw.get('targets', '$TARGETS')
|
||||
|
||||
def _generate(self, target, source, env, for_signature, executor: Optional[ExecutorType] = None):
|
||||
def _generate(self, target, source, env, for_signature, executor: Executor | None = None):
|
||||
# ensure that target is a list, to make it easier to write
|
||||
# generator functions:
|
||||
if not is_List(target):
|
||||
@ -1265,11 +1258,11 @@ class CommandGeneratorAction(ActionBase):
|
||||
def batch_key(self, env, target, source):
|
||||
return self._generate(target, source, env, 1).batch_key(env, target, source)
|
||||
|
||||
def genstring(self, target, source, env, executor: Optional[ExecutorType] = None) -> str:
|
||||
def genstring(self, target, source, env, executor: Executor | None = None) -> str:
|
||||
return self._generate(target, source, env, 1, executor).genstring(target, source, env)
|
||||
|
||||
def __call__(self, target, source, env, exitstatfunc=_null, presub=_null,
|
||||
show=_null, execute=_null, chdir=_null, executor: Optional[ExecutorType] = None):
|
||||
show=_null, execute=_null, chdir=_null, executor: Executor | None = None):
|
||||
act = self._generate(target, source, env, 0, executor)
|
||||
if act is None:
|
||||
raise SCons.Errors.UserError(
|
||||
@ -1281,7 +1274,7 @@ class CommandGeneratorAction(ActionBase):
|
||||
target, source, env, exitstatfunc, presub, show, execute, chdir, executor
|
||||
)
|
||||
|
||||
def get_presig(self, target, source, env, executor: Optional[ExecutorType] = None):
|
||||
def get_presig(self, target, source, env, executor: Executor | None = None):
|
||||
"""Return the signature contents of this action's command line.
|
||||
|
||||
This strips $(-$) and everything in between the string,
|
||||
@ -1289,13 +1282,13 @@ class CommandGeneratorAction(ActionBase):
|
||||
"""
|
||||
return self._generate(target, source, env, 1, executor).get_presig(target, source, env)
|
||||
|
||||
def get_implicit_deps(self, target, source, env, executor: Optional[ExecutorType] = None):
|
||||
def get_implicit_deps(self, target, source, env, executor: Executor | None = None):
|
||||
return self._generate(target, source, env, 1, executor).get_implicit_deps(target, source, env)
|
||||
|
||||
def get_varlist(self, target, source, env, executor: Optional[ExecutorType] = None):
|
||||
def get_varlist(self, target, source, env, executor: Executor | None = None):
|
||||
return self._generate(target, source, env, 1, executor).get_varlist(target, source, env, executor)
|
||||
|
||||
def get_targets(self, env, executor: Optional[ExecutorType]):
|
||||
def get_targets(self, env, executor: Executor | None):
|
||||
return self._generate(None, None, env, 1, executor).get_targets(env, executor)
|
||||
|
||||
|
||||
@ -1341,22 +1334,22 @@ class LazyAction(CommandGeneratorAction, CommandAction):
|
||||
raise SCons.Errors.UserError("$%s value %s cannot be used to create an Action." % (self.var, repr(c)))
|
||||
return gen_cmd
|
||||
|
||||
def _generate(self, target, source, env, for_signature, executor: Optional[ExecutorType] = None):
|
||||
def _generate(self, target, source, env, for_signature, executor: Executor | None = None):
|
||||
return self._generate_cache(env)
|
||||
|
||||
def __call__(self, target, source, env, *args, **kw):
|
||||
c = self.get_parent_class(env)
|
||||
return c.__call__(self, target, source, env, *args, **kw)
|
||||
|
||||
def get_presig(self, target, source, env, executor: Optional[ExecutorType] = None):
|
||||
def get_presig(self, target, source, env, executor: Executor | None = None):
|
||||
c = self.get_parent_class(env)
|
||||
return c.get_presig(self, target, source, env)
|
||||
|
||||
def get_implicit_deps(self, target, source, env, executor: Optional[ExecutorType] = None):
|
||||
def get_implicit_deps(self, target, source, env, executor: Executor | None = None):
|
||||
c = self.get_parent_class(env)
|
||||
return c.get_implicit_deps(self, target, source, env)
|
||||
|
||||
def get_varlist(self, target, source, env, executor: Optional[ExecutorType] = None):
|
||||
def get_varlist(self, target, source, env, executor: Executor | None = None):
|
||||
c = self.get_parent_class(env)
|
||||
return c.get_varlist(self, target, source, env, executor)
|
||||
|
||||
@ -1389,7 +1382,7 @@ class FunctionAction(_ActionAction):
|
||||
except AttributeError:
|
||||
return "unknown_python_function"
|
||||
|
||||
def strfunction(self, target, source, env, executor: Optional[ExecutorType] = None):
|
||||
def strfunction(self, target, source, env, executor: Executor | None = None):
|
||||
if self.cmdstr is None:
|
||||
return None
|
||||
if self.cmdstr is not _null:
|
||||
@ -1430,7 +1423,7 @@ class FunctionAction(_ActionAction):
|
||||
return str(self.execfunction)
|
||||
return "%s(target, source, env)" % name
|
||||
|
||||
def execute(self, target, source, env, executor: Optional[ExecutorType] = None):
|
||||
def execute(self, target, source, env, executor: Executor | None = None):
|
||||
exc_info = (None,None,None)
|
||||
try:
|
||||
if executor:
|
||||
@ -1461,14 +1454,14 @@ class FunctionAction(_ActionAction):
|
||||
# more information about this issue.
|
||||
del exc_info
|
||||
|
||||
def get_presig(self, target, source, env, executor: Optional[ExecutorType] = None):
|
||||
def get_presig(self, target, source, env, executor: Executor | None = None):
|
||||
"""Return the signature contents of this callable action."""
|
||||
try:
|
||||
return self.gc(target, source, env)
|
||||
except AttributeError:
|
||||
return self.funccontents
|
||||
|
||||
def get_implicit_deps(self, target, source, env, executor: Optional[ExecutorType] = None):
|
||||
def get_implicit_deps(self, target, source, env, executor: Executor | None = None):
|
||||
return []
|
||||
|
||||
class ListAction(ActionBase):
|
||||
@ -1485,7 +1478,7 @@ class ListAction(ActionBase):
|
||||
self.varlist = ()
|
||||
self.targets = '$TARGETS'
|
||||
|
||||
def genstring(self, target, source, env, executor: Optional[ExecutorType] = None) -> str:
|
||||
def genstring(self, target, source, env, executor: Executor | None = None) -> str:
|
||||
return '\n'.join([a.genstring(target, source, env) for a in self.list])
|
||||
|
||||
def __str__(self) -> str:
|
||||
@ -1495,7 +1488,7 @@ class ListAction(ActionBase):
|
||||
return SCons.Util.flatten_sequence(
|
||||
[a.presub_lines(env) for a in self.list])
|
||||
|
||||
def get_presig(self, target, source, env, executor: Optional[ExecutorType] = None):
|
||||
def get_presig(self, target, source, env, executor: Executor | None = None):
|
||||
"""Return the signature contents of this action list.
|
||||
|
||||
Simple concatenation of the signatures of the elements.
|
||||
@ -1503,7 +1496,7 @@ class ListAction(ActionBase):
|
||||
return b"".join([bytes(x.get_contents(target, source, env)) for x in self.list])
|
||||
|
||||
def __call__(self, target, source, env, exitstatfunc=_null, presub=_null,
|
||||
show=_null, execute=_null, chdir=_null, executor: Optional[ExecutorType] = None):
|
||||
show=_null, execute=_null, chdir=_null, executor: Executor | None = None):
|
||||
if executor:
|
||||
target = executor.get_all_targets()
|
||||
source = executor.get_all_sources()
|
||||
@ -1514,13 +1507,13 @@ class ListAction(ActionBase):
|
||||
return stat
|
||||
return 0
|
||||
|
||||
def get_implicit_deps(self, target, source, env, executor: Optional[ExecutorType] = None):
|
||||
def get_implicit_deps(self, target, source, env, executor: Executor | None = None):
|
||||
result = []
|
||||
for act in self.list:
|
||||
result.extend(act.get_implicit_deps(target, source, env))
|
||||
return result
|
||||
|
||||
def get_varlist(self, target, source, env, executor: Optional[ExecutorType] = None):
|
||||
def get_varlist(self, target, source, env, executor: Executor | None = None):
|
||||
result = OrderedDict()
|
||||
for act in self.list:
|
||||
for var in act.get_varlist(target, source, env, executor):
|
||||
@ -1586,7 +1579,7 @@ class ActionCaller:
|
||||
kw[key] = self.subst(self.kw[key], target, source, env)
|
||||
return kw
|
||||
|
||||
def __call__(self, target, source, env, executor: Optional[ExecutorType] = None):
|
||||
def __call__(self, target, source, env, executor: Executor | None = None):
|
||||
args = self.subst_args(target, source, env)
|
||||
kw = self.subst_kw(target, source, env)
|
||||
return self.parent.actfunc(*args, **kw)
|
||||
@ -99,10 +99,11 @@ There are the following methods for internal use within this module:
|
||||
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
from collections import UserDict, UserList
|
||||
from contextlib import suppress
|
||||
from typing import Optional
|
||||
|
||||
import SCons.Action
|
||||
import SCons.Debug
|
||||
@ -112,7 +113,7 @@ import SCons.Util
|
||||
import SCons.Warnings
|
||||
from SCons.Debug import logInstanceCreation
|
||||
from SCons.Errors import InternalError, UserError
|
||||
from SCons.Util.sctyping import ExecutorType
|
||||
from SCons.Executor import Executor
|
||||
|
||||
class _Null:
|
||||
pass
|
||||
@ -591,7 +592,7 @@ class BuilderBase:
|
||||
# build this particular list of targets from this particular list of
|
||||
# sources.
|
||||
|
||||
executor: Optional[ExecutorType] = None
|
||||
executor: Executor | None = None
|
||||
key = None
|
||||
|
||||
if self.multi:
|
||||
@ -27,8 +27,10 @@
|
||||
import atexit
|
||||
import json
|
||||
import os
|
||||
import shutil
|
||||
import stat
|
||||
import sys
|
||||
import tempfile
|
||||
import uuid
|
||||
|
||||
import SCons.Action
|
||||
@ -36,6 +38,12 @@ import SCons.Errors
|
||||
import SCons.Warnings
|
||||
import SCons.Util
|
||||
|
||||
CACHE_PREFIX_LEN = 2 # first two characters used as subdirectory name
|
||||
CACHE_TAG = (
|
||||
b"Signature: 8a477f597d28d172789f06886806bc55\n"
|
||||
b"# SCons cache directory - see https://bford.info/cachedir/\n"
|
||||
)
|
||||
|
||||
cache_enabled = True
|
||||
cache_debug = False
|
||||
cache_force = False
|
||||
@ -64,23 +72,22 @@ def CacheRetrieveFunc(target, source, env) -> int:
|
||||
except OSError:
|
||||
pass
|
||||
st = fs.stat(cachefile)
|
||||
fs.chmod(t.get_internal_path(), stat.S_IMODE(st[stat.ST_MODE]) | stat.S_IWRITE)
|
||||
fs.chmod(t.get_internal_path(), stat.S_IMODE(st.st_mode) | stat.S_IWRITE)
|
||||
return 0
|
||||
|
||||
def CacheRetrieveString(target, source, env) -> None:
|
||||
def CacheRetrieveString(target, source, env) -> str:
|
||||
t = target[0]
|
||||
fs = t.fs
|
||||
cd = env.get_CacheDir()
|
||||
cachedir, cachefile = cd.cachepath(t)
|
||||
if t.fs.exists(cachefile):
|
||||
return "Retrieved `%s' from cache" % t.get_internal_path()
|
||||
return None
|
||||
return ""
|
||||
|
||||
CacheRetrieve = SCons.Action.Action(CacheRetrieveFunc, CacheRetrieveString)
|
||||
|
||||
CacheRetrieveSilent = SCons.Action.Action(CacheRetrieveFunc, None)
|
||||
|
||||
def CachePushFunc(target, source, env):
|
||||
def CachePushFunc(target, source, env) -> None:
|
||||
if cache_readonly:
|
||||
return
|
||||
|
||||
@ -134,8 +141,7 @@ CachePush = SCons.Action.Action(CachePushFunc, None)
|
||||
class CacheDir:
|
||||
|
||||
def __init__(self, path) -> None:
|
||||
"""
|
||||
Initialize a CacheDir object.
|
||||
"""Initialize a CacheDir object.
|
||||
|
||||
The cache configuration is stored in the object. It
|
||||
is read from the config file in the supplied path if
|
||||
@ -147,53 +153,120 @@ class CacheDir:
|
||||
self.path = path
|
||||
self.current_cache_debug = None
|
||||
self.debugFP = None
|
||||
self.config = dict()
|
||||
if path is None:
|
||||
return
|
||||
self.config = {}
|
||||
if path is not None:
|
||||
self._readconfig(path)
|
||||
|
||||
self._readconfig(path)
|
||||
def _add_config(self, path: str) -> None:
|
||||
"""Create the cache config file in *path*.
|
||||
|
||||
|
||||
def _readconfig(self, path):
|
||||
"""
|
||||
Read the cache config.
|
||||
|
||||
If directory or config file do not exist, create. Take advantage
|
||||
of Py3 capability in os.makedirs() and in file open(): just try
|
||||
the operation and handle failure appropriately.
|
||||
|
||||
Omit the check for old cache format, assume that's old enough
|
||||
there will be none of those left to worry about.
|
||||
|
||||
:param path: path to the cache directory
|
||||
Locking isn't necessary in the normal case - when the cachedir is
|
||||
being created - because it's written to a unique directory first,
|
||||
before the directory is renamed. But it is legal to call CacheDir
|
||||
with an existing directory, which may be missing the config file,
|
||||
and in that case we do need locking. Simpler to always lock.
|
||||
"""
|
||||
config_file = os.path.join(path, 'config')
|
||||
# TODO: this breaks the "unserializable config object" test which
|
||||
# does some crazy stuff, so for now don't use setdefault. It does
|
||||
# seem like it would be better to preserve an exisiting value.
|
||||
# self.config.setdefault('prefix_len', CACHE_PREFIX_LEN)
|
||||
self.config['prefix_len'] = CACHE_PREFIX_LEN
|
||||
with SCons.Util.FileLock(config_file, timeout=5, writer=True), open(
|
||||
config_file, "x"
|
||||
) as config:
|
||||
try:
|
||||
json.dump(self.config, config)
|
||||
except Exception:
|
||||
msg = "Failed to write cache configuration for " + path
|
||||
raise SCons.Errors.SConsEnvironmentError(msg)
|
||||
|
||||
# Add the tag file "carelessly" - the contents are not used by SCons
|
||||
# so we don't care about the chance of concurrent writes.
|
||||
try:
|
||||
# still use a try block even with exist_ok, might have other fails
|
||||
os.makedirs(path, exist_ok=True)
|
||||
except OSError:
|
||||
msg = "Failed to create cache directory " + path
|
||||
raise SCons.Errors.SConsEnvironmentError(msg)
|
||||
tagfile = os.path.join(path, "CACHEDIR.TAG")
|
||||
with open(tagfile, 'xb') as cachedir_tag:
|
||||
cachedir_tag.write(CACHE_TAG)
|
||||
except FileExistsError:
|
||||
pass
|
||||
|
||||
def _mkdir_atomic(self, path: str) -> bool:
|
||||
"""Create cache directory at *path*.
|
||||
|
||||
Uses directory renaming to avoid races. If we are actually
|
||||
creating the dir, populate it with the metadata files at the
|
||||
same time as that's the safest way. But it's not illegal to point
|
||||
CacheDir at an existing directory that wasn't a cache previously,
|
||||
so we may have to do that elsewhere, too.
|
||||
|
||||
Returns:
|
||||
``True`` if it we created the dir, ``False`` if already existed,
|
||||
|
||||
Raises:
|
||||
SConsEnvironmentError: if we tried and failed to create the cache.
|
||||
"""
|
||||
directory = os.path.abspath(path)
|
||||
if os.path.exists(directory):
|
||||
return False
|
||||
|
||||
try:
|
||||
with SCons.Util.FileLock(config_file, timeout=5, writer=True), open(
|
||||
config_file, "x"
|
||||
) as config:
|
||||
self.config['prefix_len'] = 2
|
||||
# TODO: Python 3.7. See comment below.
|
||||
# tempdir = tempfile.TemporaryDirectory(dir=os.path.dirname(directory))
|
||||
tempdir = tempfile.mkdtemp(dir=os.path.dirname(directory))
|
||||
except OSError as e:
|
||||
msg = "Failed to create cache directory " + path
|
||||
raise SCons.Errors.SConsEnvironmentError(msg) from e
|
||||
|
||||
# TODO: Python 3.7: the context manager raises exception on cleanup
|
||||
# if the temporary was moved successfully (File Not Found).
|
||||
# Fixed in 3.8+. In the replacement below we manually clean up if
|
||||
# the move failed as mkdtemp() does not. TemporaryDirectory's
|
||||
# cleanup is more sophisitcated so prefer when we can use it.
|
||||
# self._add_config(tempdir.name)
|
||||
# with tempdir:
|
||||
# try:
|
||||
# os.replace(tempdir.name, directory)
|
||||
# return True
|
||||
# except OSError as e:
|
||||
# # did someone else get there first?
|
||||
# if os.path.isdir(directory):
|
||||
# return False # context manager cleans up
|
||||
# msg = "Failed to create cache directory " + path
|
||||
# raise SCons.Errors.SConsEnvironmentError(msg) from e
|
||||
|
||||
self._add_config(tempdir)
|
||||
try:
|
||||
os.replace(tempdir, directory)
|
||||
return True
|
||||
except OSError as e:
|
||||
# did someone else get there first? attempt cleanup.
|
||||
if os.path.isdir(directory):
|
||||
try:
|
||||
json.dump(self.config, config)
|
||||
except Exception:
|
||||
msg = "Failed to write cache configuration for " + path
|
||||
raise SCons.Errors.SConsEnvironmentError(msg)
|
||||
except FileExistsError:
|
||||
try:
|
||||
with SCons.Util.FileLock(config_file, timeout=5, writer=False), open(
|
||||
config_file
|
||||
) as config:
|
||||
self.config = json.load(config)
|
||||
except (ValueError, json.decoder.JSONDecodeError):
|
||||
msg = "Failed to read cache configuration for " + path
|
||||
raise SCons.Errors.SConsEnvironmentError(msg)
|
||||
shutil.rmtree(tempdir)
|
||||
except Exception: # we tried, don't worry about it
|
||||
pass
|
||||
return False
|
||||
msg = "Failed to create cache directory " + path
|
||||
raise SCons.Errors.SConsEnvironmentError(msg) from e
|
||||
|
||||
def _readconfig(self, path: str) -> None:
|
||||
"""Read the cache config from *path*.
|
||||
|
||||
If directory or config file do not exist, create and populate.
|
||||
"""
|
||||
config_file = os.path.join(path, 'config')
|
||||
created = self._mkdir_atomic(path)
|
||||
if not created and not os.path.isfile(config_file):
|
||||
# Could have been passed an empty directory
|
||||
self._add_config(path)
|
||||
try:
|
||||
with SCons.Util.FileLock(config_file, timeout=5, writer=False), open(
|
||||
config_file
|
||||
) as config:
|
||||
self.config = json.load(config)
|
||||
except (ValueError, json.decoder.JSONDecodeError):
|
||||
msg = "Failed to read cache configuration for " + path
|
||||
raise SCons.Errors.SConsEnvironmentError(msg)
|
||||
|
||||
def CacheDebug(self, fmt, target, cachefile) -> None:
|
||||
if cache_debug != self.current_cache_debug:
|
||||
@ -252,7 +325,7 @@ class CacheDir:
|
||||
def is_readonly(self) -> bool:
|
||||
return cache_readonly
|
||||
|
||||
def get_cachedir_csig(self, node):
|
||||
def get_cachedir_csig(self, node) -> str:
|
||||
cachedir, cachefile = self.cachepath(node)
|
||||
if cachefile and os.path.exists(cachefile):
|
||||
return SCons.Util.hash_file_signature(cachefile, SCons.Node.FS.File.hash_chunksize)
|
||||
@ -31,12 +31,14 @@ The code that reads the registry to find MSVC components was borrowed
|
||||
from distutils.msvccompiler.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import shutil
|
||||
import stat
|
||||
import sys
|
||||
import time
|
||||
from typing import List, Callable
|
||||
from typing import Callable
|
||||
|
||||
import SCons.Action
|
||||
import SCons.Builder
|
||||
@ -467,8 +469,8 @@ def _stripixes(
|
||||
prefix: str,
|
||||
items,
|
||||
suffix: str,
|
||||
stripprefixes: List[str],
|
||||
stripsuffixes: List[str],
|
||||
stripprefixes: list[str],
|
||||
stripsuffixes: list[str],
|
||||
env,
|
||||
literal_prefix: str = "",
|
||||
c: Callable[[list], list] = None,
|
||||
@ -547,7 +549,7 @@ def _stripixes(
|
||||
return c(prefix, stripped, suffix, env)
|
||||
|
||||
|
||||
def processDefines(defs) -> List[str]:
|
||||
def processDefines(defs) -> list[str]:
|
||||
"""Return list of strings for preprocessor defines from *defs*.
|
||||
|
||||
Resolves the different forms ``CPPDEFINES`` can be assembled in:
|
||||
@ -30,6 +30,8 @@ Keyword arguments supplied when the construction Environment is created
|
||||
are construction variables used to initialize the Environment.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import copy
|
||||
import os
|
||||
import sys
|
||||
@ -37,7 +39,7 @@ import re
|
||||
import shlex
|
||||
from collections import UserDict, UserList, deque
|
||||
from subprocess import PIPE, DEVNULL
|
||||
from typing import Callable, Collection, Optional, Sequence, Union
|
||||
from typing import TYPE_CHECKING, Callable, Collection, Sequence
|
||||
|
||||
import SCons.Action
|
||||
import SCons.Builder
|
||||
@ -76,7 +78,9 @@ from SCons.Util import (
|
||||
to_String_for_subst,
|
||||
uniquer_hashables,
|
||||
)
|
||||
from SCons.Util.sctyping import ExecutorType
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from SCons.Executor import Executor
|
||||
|
||||
class _Null:
|
||||
pass
|
||||
@ -534,6 +538,11 @@ class SubstitutionEnvironment:
|
||||
Environment.Base to create their own flavors of construction
|
||||
environment, we'll save that for a future refactoring when this
|
||||
class actually becomes useful.)
|
||||
|
||||
Special note: methods here and in actual child classes might be called
|
||||
via proxy from an :class:`OverrideEnvironment`, which isn't in the
|
||||
class inheritance chain. Take care that methods called with a *self*
|
||||
that's really an ``OverrideEnvironment`` don't make bad assumptions.
|
||||
"""
|
||||
|
||||
def __init__(self, **kw) -> None:
|
||||
@ -567,6 +576,20 @@ class SubstitutionEnvironment:
|
||||
self._special_set_keys = list(self._special_set.keys())
|
||||
|
||||
def __eq__(self, other):
|
||||
"""Compare two environments.
|
||||
|
||||
This is used by checks in Builder to determine if duplicate
|
||||
targets have environments that would cause the same result.
|
||||
The more reliable way (respecting the admonition to avoid poking
|
||||
at :attr:`_dict` directly) would be to use ``Dictionary`` so this
|
||||
is sure to work even if one or both are are instances of
|
||||
:class:`OverrideEnvironment`. However an actual
|
||||
``SubstitutionEnvironment`` doesn't have a ``Dictionary`` method
|
||||
That causes problems for unit tests written to excercise
|
||||
``SubsitutionEnvironment`` directly, although nobody else seems
|
||||
to ever instantiate one. We count on :class:`OverrideEnvironment`
|
||||
to fake the :attr:`_dict` to make things work.
|
||||
"""
|
||||
return self._dict == other._dict
|
||||
|
||||
def __delitem__(self, key) -> None:
|
||||
@ -679,7 +702,7 @@ class SubstitutionEnvironment:
|
||||
def lvars(self):
|
||||
return {}
|
||||
|
||||
def subst(self, string, raw: int=0, target=None, source=None, conv=None, executor: Optional[ExecutorType] = None, overrides: Optional[dict] = None):
|
||||
def subst(self, string, raw: int=0, target=None, source=None, conv=None, executor: Executor | None = None, overrides: dict | None = None):
|
||||
"""Recursively interpolates construction variables from the
|
||||
Environment into the specified string, returning the expanded
|
||||
result. Construction variables are specified by a $ prefix
|
||||
@ -705,7 +728,7 @@ class SubstitutionEnvironment:
|
||||
nkw[k] = v
|
||||
return nkw
|
||||
|
||||
def subst_list(self, string, raw: int=0, target=None, source=None, conv=None, executor: Optional[ExecutorType] = None, overrides: Optional[dict] = None):
|
||||
def subst_list(self, string, raw: int=0, target=None, source=None, conv=None, executor: Executor | None = None, overrides: dict | None = None):
|
||||
"""Calls through to SCons.Subst.scons_subst_list().
|
||||
|
||||
See the documentation for that function.
|
||||
@ -811,16 +834,30 @@ class SubstitutionEnvironment:
|
||||
self.added_methods = [dm for dm in self.added_methods if dm.method is not function]
|
||||
|
||||
def Override(self, overrides):
|
||||
"""
|
||||
Produce a modified environment whose variables are overridden by
|
||||
the overrides dictionaries. "overrides" is a dictionary that
|
||||
will override the variables of this environment.
|
||||
"""Create an override environment from the current environment.
|
||||
|
||||
This function is much more efficient than Clone() or creating
|
||||
a new Environment because it doesn't copy the construction
|
||||
Produces a modified environment where the current variables are
|
||||
overridden by any same-named variables from the *overrides* dict.
|
||||
|
||||
An override is much more efficient than doing :meth:`~Base.Clone`
|
||||
or creating a new Environment because it doesn't copy the construction
|
||||
environment dictionary, it just wraps the underlying construction
|
||||
environment, and doesn't even create a wrapper object if there
|
||||
are no overrides.
|
||||
|
||||
Using this method is preferred over directly instantiating an
|
||||
:class:`OverrideEnvirionment` because extra checks are performed,
|
||||
substitution takes place, and there is special handling for a
|
||||
*parse_flags* keyword argument.
|
||||
|
||||
This method is not currently exposed as part of the public API,
|
||||
but is invoked internally when things like builder calls have
|
||||
keyword arguments, which are then passed as *overrides* here.
|
||||
Some tools also call this explicitly.
|
||||
|
||||
Returns:
|
||||
A proxy environment of type :class:`OverrideEnvironment`.
|
||||
or the current environment if *overrides* is empty.
|
||||
"""
|
||||
if not overrides: return self
|
||||
o = copy_non_reserved_keywords(overrides)
|
||||
@ -868,7 +905,7 @@ class SubstitutionEnvironment:
|
||||
'RPATH' : [],
|
||||
}
|
||||
|
||||
def do_parse(arg: Union[str, Sequence]) -> None:
|
||||
def do_parse(arg: str | Sequence) -> None:
|
||||
if not arg:
|
||||
return
|
||||
|
||||
@ -946,7 +983,7 @@ class SubstitutionEnvironment:
|
||||
else:
|
||||
mapping[append_next_arg_to].append(arg)
|
||||
append_next_arg_to = None
|
||||
elif not arg[0] in ['-', '+']:
|
||||
elif arg[0] not in ['-', '+']:
|
||||
mapping['LIBS'].append(self.fs.File(arg))
|
||||
elif arg == '-dylib_file':
|
||||
mapping['LINKFLAGS'].append(arg)
|
||||
@ -1414,7 +1451,6 @@ class Base(SubstitutionEnvironment):
|
||||
|
||||
The variable is created if it is not already present.
|
||||
"""
|
||||
|
||||
kw = copy_non_reserved_keywords(kw)
|
||||
for key, val in kw.items():
|
||||
if key == 'CPPDEFINES':
|
||||
@ -1676,26 +1712,35 @@ class Base(SubstitutionEnvironment):
|
||||
return None
|
||||
|
||||
|
||||
def Dictionary(self, *args):
|
||||
r"""Return construction variables from an environment.
|
||||
def Dictionary(self, *args: str, as_dict: bool = False):
|
||||
"""Return construction variables from an environment.
|
||||
|
||||
Args:
|
||||
\*args (optional): variable names to look up
|
||||
args (optional): construction variable names to select.
|
||||
If omitted, all variables are selected and returned
|
||||
as a dict.
|
||||
as_dict: if true, and *args* is supplied, return the
|
||||
variables and their values in a dict. If false
|
||||
(the default), return a single value as a scalar,
|
||||
or multiple values in a list.
|
||||
|
||||
Returns:
|
||||
If `args` omitted, the dictionary of all construction variables.
|
||||
If one arg, the corresponding value is returned.
|
||||
If more than one arg, a list of values is returned.
|
||||
A dictionary of construction variables, or a single value
|
||||
or list of values.
|
||||
|
||||
Raises:
|
||||
KeyError: if any of `args` is not in the construction environment.
|
||||
KeyError: if any of *args* is not in the construction environment.
|
||||
|
||||
.. versionchanged:: 4.9.0
|
||||
Added the *as_dict* keyword arg to specify always returning a dict.
|
||||
"""
|
||||
if not args:
|
||||
return self._dict
|
||||
dlist = [self._dict[x] for x in args]
|
||||
if as_dict:
|
||||
return {key: self._dict[key] for key in args}
|
||||
dlist = [self._dict[key] for key in args]
|
||||
if len(dlist) == 1:
|
||||
dlist = dlist[0]
|
||||
return dlist[0]
|
||||
return dlist
|
||||
|
||||
|
||||
@ -1718,18 +1763,15 @@ class Base(SubstitutionEnvironment):
|
||||
Raises:
|
||||
ValueError: *format* is not a recognized serialization format.
|
||||
|
||||
.. versionchanged:: NEXT_VERSION
|
||||
.. versionchanged:: 4.9.0
|
||||
*key* is no longer limited to a single construction variable name.
|
||||
If *key* is supplied, a formatted dictionary is generated like the
|
||||
no-arg case - previously a single *key* displayed just the value.
|
||||
"""
|
||||
if not key:
|
||||
cvars = self.Dictionary()
|
||||
elif len(key) == 1:
|
||||
dkey = key[0]
|
||||
cvars = {dkey: self[dkey]}
|
||||
if len(key):
|
||||
cvars = self.Dictionary(*key, as_dict=True)
|
||||
else:
|
||||
cvars = dict(zip(key, self.Dictionary(*key)))
|
||||
cvars = self.Dictionary()
|
||||
|
||||
fmt = format.lower()
|
||||
|
||||
@ -1760,7 +1802,7 @@ class Base(SubstitutionEnvironment):
|
||||
raise ValueError("Unsupported serialization format: %s." % fmt)
|
||||
|
||||
|
||||
def FindIxes(self, paths: Sequence[str], prefix: str, suffix: str) -> Optional[str]:
|
||||
def FindIxes(self, paths: Sequence[str], prefix: str, suffix: str) -> str | None:
|
||||
"""Search *paths* for a path that has *prefix* and *suffix*.
|
||||
|
||||
Returns on first match.
|
||||
@ -1857,7 +1899,6 @@ class Base(SubstitutionEnvironment):
|
||||
|
||||
The variable is created if it is not already present.
|
||||
"""
|
||||
|
||||
kw = copy_non_reserved_keywords(kw)
|
||||
for key, val in kw.items():
|
||||
if key == 'CPPDEFINES':
|
||||
@ -2042,7 +2083,7 @@ class Base(SubstitutionEnvironment):
|
||||
return self.fs.Dir(self.subst(tp)).srcnode().get_abspath()
|
||||
|
||||
def Tool(
|
||||
self, tool: Union[str, Callable], toolpath: Optional[Collection[str]] = None, **kwargs
|
||||
self, tool: str | Callable, toolpath: Collection[str] | None = None, **kwargs
|
||||
) -> Callable:
|
||||
"""Find and run tool module *tool*.
|
||||
|
||||
@ -2207,6 +2248,16 @@ class Base(SubstitutionEnvironment):
|
||||
self.get_CacheDir()
|
||||
|
||||
def Clean(self, targets, files) -> None:
|
||||
"""Mark additional files for cleaning.
|
||||
|
||||
*files* will be removed if any of *targets* are selected
|
||||
for cleaning - that is, the combination of target selection
|
||||
and -c clean mode.
|
||||
|
||||
Args:
|
||||
targets (files or nodes): targets to associate *files* with.
|
||||
files (files or nodes): items to remove if *targets* are selected.
|
||||
"""
|
||||
global CleanTargets
|
||||
tlist = self.arg2nodes(targets, self.fs.Entry)
|
||||
flist = self.arg2nodes(files, self.fs.Entry)
|
||||
@ -2293,8 +2344,8 @@ class Base(SubstitutionEnvironment):
|
||||
return result
|
||||
return self.fs.PyPackageDir(s)
|
||||
|
||||
def NoClean(self, *targets):
|
||||
"""Tag target(s) so that it will not be cleaned by -c."""
|
||||
def NoClean(self, *targets) -> list:
|
||||
"""Tag *targets* to not be removed in clean mode."""
|
||||
tlist = []
|
||||
for t in targets:
|
||||
tlist.extend(self.arg2nodes(t, self.fs.Entry))
|
||||
@ -2537,45 +2588,75 @@ class Base(SubstitutionEnvironment):
|
||||
|
||||
|
||||
class OverrideEnvironment(Base):
|
||||
"""A proxy that overrides variables in a wrapped construction
|
||||
environment by returning values from an overrides dictionary in
|
||||
preference to values from the underlying subject environment.
|
||||
"""A proxy that implements override environments.
|
||||
|
||||
This is a lightweight (I hope) proxy that passes through most use of
|
||||
attributes to the underlying Environment.Base class, but has just
|
||||
enough additional methods defined to act like a real construction
|
||||
environment with overridden values. It can wrap either a Base
|
||||
construction environment, or another OverrideEnvironment, which
|
||||
can in turn nest arbitrary OverrideEnvironments...
|
||||
Returns attributes/methods and construction variables from the
|
||||
base environment *subject*, except that same-named construction
|
||||
variables from *overrides* are returned on read access; assignment
|
||||
to a construction variable creates an override entry - *subject* is
|
||||
not modified. This is a much lighter weight approach for limited-use
|
||||
setups than cloning an environment, for example to handle a builder
|
||||
call with keyword arguments that make a temporary change to the
|
||||
current environment::
|
||||
|
||||
Note that we do *not* call the underlying base class
|
||||
(SubsitutionEnvironment) initialization, because we get most of those
|
||||
from proxying the attributes of the subject construction environment.
|
||||
But because we subclass SubstitutionEnvironment, this class also
|
||||
has inherited arg2nodes() and subst*() methods; those methods can't
|
||||
be proxied because they need *this* object's methods to fetch the
|
||||
values from the overrides dictionary.
|
||||
env.Program(target="foo", source=sources, DEBUG=True)
|
||||
|
||||
While the majority of methods are proxied from the underlying environment
|
||||
class, enough plumbing is defined in this class for it to behave
|
||||
like an ordinary Environment without the caller needing to know it is
|
||||
"special" in some way. We don't call the initializer of the class
|
||||
we're proxying, rather depend on it already being properly set up.
|
||||
|
||||
Deletion is handled specially, if a variable was explicitly deleted,
|
||||
it should no longer appear to be in the env, but we also don't want to
|
||||
modify the subject environment.
|
||||
|
||||
:class:`OverrideEnvironment` can nest arbitratily, *subject*
|
||||
can be an existing instance. Although instances can be
|
||||
instantiated directly, the expected use is to call the
|
||||
:meth:`~SubstitutionEnvironment.Override` method as a factory.
|
||||
|
||||
Note Python does not give us a way to assure the subject environment
|
||||
is not modified. Assigning to a variable creates a new entry in
|
||||
the override, but moditying a variable will first fetch the one
|
||||
from the subject, and if mutable, it will just be modified in place.
|
||||
For example: ``over_env.Append(CPPDEFINES="-O")``, where ``CPPDEFINES``
|
||||
is an existing list or :class:`~SCons.Util.CLVar`, will successfully
|
||||
append to ``CPPDEFINES`` in the subject env. To avoid such leakage,
|
||||
clients such as Scanners, Emitters and Action functions called by a
|
||||
Builder using override syntax must take care if modifying an env
|
||||
(which is not advised anyway) in case they were passed an
|
||||
``OverrideEnvironment``.
|
||||
"""
|
||||
|
||||
def __init__(self, subject, overrides=None) -> None:
|
||||
def __init__(self, subject, overrides: dict | None = None) -> None:
|
||||
if SCons.Debug.track_instances: logInstanceCreation(self, 'Environment.OverrideEnvironment')
|
||||
overrides = {} if overrides is None else overrides
|
||||
# set these directly via __dict__ to avoid trapping by __setattr__
|
||||
self.__dict__['__subject'] = subject
|
||||
if overrides is None:
|
||||
self.__dict__['overrides'] = {}
|
||||
else:
|
||||
self.__dict__['overrides'] = overrides
|
||||
self.__dict__['overrides'] = overrides
|
||||
self.__dict__['__deleted'] = []
|
||||
|
||||
# Methods that make this class act like a proxy.
|
||||
|
||||
def __getattr__(self, name):
|
||||
# Proxied environment methods don't know (nor should they have to) that
|
||||
# they could be called with an OverrideEnvironment as 'self' and may
|
||||
# access the _dict construction variable dict directly, so we need to
|
||||
# pretend to have one, and not serve up the one from the subject, or it
|
||||
# will miss the overridden values (and possibly modify the base). Use
|
||||
# ourselves and hope the dict-like methods below are sufficient.
|
||||
if name == '_dict':
|
||||
return self
|
||||
|
||||
attr = getattr(self.__dict__['__subject'], name)
|
||||
# Here we check if attr is one of the Wrapper classes. For
|
||||
# example when a pseudo-builder is being called from an
|
||||
# OverrideEnvironment.
|
||||
#
|
||||
# These wrappers when they're constructed capture the
|
||||
# Environment they are being constructed with and so will not
|
||||
# have access to overrided values. So we rebuild them with the
|
||||
# OverrideEnvironment so they have access to overrided values.
|
||||
|
||||
# Check first if attr is one of the Wrapper classes, for example
|
||||
# when a pseudo-builder is being called from an OverrideEnvironment.
|
||||
# These wrappers, when they're constructed, capture the Environment
|
||||
# they are being constructed with and so will not have access to
|
||||
# overridden values. So we rebuild them with the OverrideEnvironment
|
||||
# so they have access to overridden values.
|
||||
if isinstance(attr, MethodWrapper):
|
||||
return attr.clone(self)
|
||||
else:
|
||||
@ -2585,13 +2666,21 @@ class OverrideEnvironment(Base):
|
||||
setattr(self.__dict__['__subject'], name, value)
|
||||
|
||||
# Methods that make this class act like a dictionary.
|
||||
|
||||
def __getitem__(self, key):
|
||||
"""Return the visible value of *key*.
|
||||
|
||||
Backfills from the subject env if *key* doesn't have an entry in
|
||||
the override, and is not explicity deleted.
|
||||
"""
|
||||
try:
|
||||
return self.__dict__['overrides'][key]
|
||||
except KeyError:
|
||||
if key in self.__dict__['__deleted']:
|
||||
raise
|
||||
return self.__dict__['__subject'].__getitem__(key)
|
||||
|
||||
def __setitem__(self, key, value):
|
||||
def __setitem__(self, key, value) -> None:
|
||||
# This doesn't have the same performance equation as a "real"
|
||||
# environment: in an override you're basically just writing
|
||||
# new stuff; it's not a common case to be changing values already
|
||||
@ -2599,58 +2688,94 @@ class OverrideEnvironment(Base):
|
||||
if not key.isidentifier():
|
||||
raise UserError(f"Illegal construction variable {key!r}")
|
||||
self.__dict__['overrides'][key] = value
|
||||
if key in self.__dict__['__deleted']:
|
||||
# it's no longer "deleted" if we set it
|
||||
self.__dict__['__deleted'].remove(key)
|
||||
|
||||
def __delitem__(self, key):
|
||||
def __delitem__(self, key) -> None:
|
||||
"""Delete *key* from override.
|
||||
|
||||
Makes *key* not visible in the override. Previously implemented
|
||||
by deleting from ``overrides`` and from ``__subject``, which
|
||||
keeps :meth:`__getitem__` from filling it back in next time.
|
||||
However, that approach was a form of leak, as the subject
|
||||
environment was modified. So instead we log that it's deleted
|
||||
and use that to make decisions elsewhere.
|
||||
"""
|
||||
try:
|
||||
del self.__dict__['overrides'][key]
|
||||
except KeyError:
|
||||
deleted = 0
|
||||
deleted = False
|
||||
else:
|
||||
deleted = 1
|
||||
try:
|
||||
result = self.__dict__['__subject'].__delitem__(key)
|
||||
except KeyError:
|
||||
if not deleted:
|
||||
raise
|
||||
result = None
|
||||
return result
|
||||
deleted = True
|
||||
if not deleted and key not in self.__dict__['__subject']:
|
||||
raise KeyError(key)
|
||||
self.__dict__['__deleted'].append(key)
|
||||
|
||||
def get(self, key, default=None):
|
||||
"""Emulates the get() method of dictionaries."""
|
||||
"""Emulates the ``get`` method of dictionaries.
|
||||
|
||||
Backfills from the subject environment if *key* is not in the override
|
||||
and not deleted.
|
||||
"""
|
||||
try:
|
||||
return self.__dict__['overrides'][key]
|
||||
except KeyError:
|
||||
if key in self.__dict__['__deleted']:
|
||||
return default
|
||||
return self.__dict__['__subject'].get(key, default)
|
||||
|
||||
def __contains__(self, key) -> bool:
|
||||
"""Emulates the ``contains`` method of dictionaries.
|
||||
|
||||
Backfills from the subject environment if *key* is not in the override
|
||||
and not deleted.
|
||||
"""
|
||||
if key in self.__dict__['overrides']:
|
||||
return True
|
||||
if key in self.__dict__['__deleted']:
|
||||
return False
|
||||
return key in self.__dict__['__subject']
|
||||
|
||||
def Dictionary(self, *args):
|
||||
d = self.__dict__['__subject'].Dictionary().copy()
|
||||
def Dictionary(self, *args, as_dict: bool = False):
|
||||
"""Return construction variables from an environment.
|
||||
|
||||
Behavior is as described for :class:`SubstitutionEnvironment.Dictionary`
|
||||
but understanda about the override.
|
||||
|
||||
Raises:
|
||||
KeyError: if any of *args* is not in the construction environment.
|
||||
|
||||
.. versionchanged: 4.9.0
|
||||
Added the *as_dict* keyword arg to always return a dict.
|
||||
"""
|
||||
d = {}
|
||||
d.update(self.__dict__['__subject'])
|
||||
d.update(self.__dict__['overrides'])
|
||||
d = {k: v for k, v in d.items() if k not in self.__dict__['__deleted']}
|
||||
if not args:
|
||||
return d
|
||||
dlist = [d[x] for x in args]
|
||||
if as_dict:
|
||||
return {key: d[key] for key in args}
|
||||
dlist = [d[key] for key in args]
|
||||
if len(dlist) == 1:
|
||||
dlist = dlist[0]
|
||||
return dlist[0]
|
||||
return dlist
|
||||
|
||||
def items(self):
|
||||
"""Emulates the items() method of dictionaries."""
|
||||
"""Emulates the ``items`` method of dictionaries."""
|
||||
return self.Dictionary().items()
|
||||
|
||||
def keys(self):
|
||||
"""Emulates the keys() method of dictionaries."""
|
||||
"""Emulates the ``keys`` method of dictionaries."""
|
||||
return self.Dictionary().keys()
|
||||
|
||||
def values(self):
|
||||
"""Emulates the values() method of dictionaries."""
|
||||
"""Emulates the ``values`` method of dictionaries."""
|
||||
return self.Dictionary().values()
|
||||
|
||||
def setdefault(self, key, default=None):
|
||||
"""Emulates the setdefault() method of dictionaries."""
|
||||
"""Emulates the ``setdefault`` method of dictionaries."""
|
||||
try:
|
||||
return self.__getitem__(key)
|
||||
except KeyError:
|
||||
@ -2658,6 +2783,7 @@ class OverrideEnvironment(Base):
|
||||
return default
|
||||
|
||||
# Overridden private construction environment methods.
|
||||
|
||||
def _update(self, other) -> None:
|
||||
self.__dict__['overrides'].update(other)
|
||||
|
||||
@ -2680,6 +2806,7 @@ class OverrideEnvironment(Base):
|
||||
return lvars
|
||||
|
||||
# Overridden public construction environment methods.
|
||||
|
||||
def Replace(self, **kw) -> None:
|
||||
kw = copy_non_reserved_keywords(kw)
|
||||
self.__dict__['overrides'].update(semi_deepcopy(kw))
|
||||
@ -26,11 +26,15 @@
|
||||
Used to handle internal and user errors in SCons.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import shutil
|
||||
from typing import Optional
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from SCons.Util.sctypes import to_String, is_String
|
||||
from SCons.Util.sctyping import ExecutorType
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from SCons.Executor import Executor
|
||||
|
||||
# Note that not all Errors are defined here, some are at the point of use
|
||||
|
||||
@ -75,7 +79,7 @@ class BuildError(Exception):
|
||||
|
||||
def __init__(self,
|
||||
node=None, errstr: str="Unknown error", status: int=2, exitstatus: int=2,
|
||||
filename=None, executor: Optional[ExecutorType] = None, action=None, command=None,
|
||||
filename=None, executor: Executor | None = None, action=None, command=None,
|
||||
exc_info=(None, None, None)) -> None:
|
||||
|
||||
# py3: errstr should be string and not bytes.
|
||||
@ -23,8 +23,9 @@
|
||||
|
||||
"""Execute actions with specific lists of target and source Nodes."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import collections
|
||||
from typing import Dict
|
||||
|
||||
import SCons.Errors
|
||||
import SCons.Memoize
|
||||
@ -32,7 +33,6 @@ import SCons.Util
|
||||
from SCons.compat import NoSlotsPyPy
|
||||
import SCons.Debug
|
||||
from SCons.Debug import logInstanceCreation
|
||||
from SCons.Util.sctyping import ExecutorType
|
||||
|
||||
class Batch:
|
||||
"""Remembers exact association between targets
|
||||
@ -550,12 +550,12 @@ class Executor(metaclass=NoSlotsPyPy):
|
||||
|
||||
|
||||
|
||||
_batch_executors: Dict[str, ExecutorType] = {}
|
||||
_batch_executors: dict[str, Executor] = {}
|
||||
|
||||
def GetBatchExecutor(key: str) -> ExecutorType:
|
||||
def GetBatchExecutor(key: str) -> Executor:
|
||||
return _batch_executors[key]
|
||||
|
||||
def AddBatchExecutor(key: str, executor: ExecutorType) -> None:
|
||||
def AddBatchExecutor(key: str, executor: Executor) -> None:
|
||||
assert key not in _batch_executors
|
||||
_batch_executors[key] = executor
|
||||
|
||||
@ -104,9 +104,15 @@ class Alias(SCons.Node.Node):
|
||||
#
|
||||
#
|
||||
|
||||
def build(self) -> None:
|
||||
def build(self, **kw) -> None:
|
||||
"""A "builder" for aliases."""
|
||||
pass
|
||||
if len(self.executor.post_actions) + len(self.executor.pre_actions) > 0:
|
||||
# Only actually call Node's build() if there are any
|
||||
# pre or post actions.
|
||||
# Alias nodes will get 1 action and Alias.build()
|
||||
# This fixes GH Issue #2281
|
||||
return self.really_build(**kw)
|
||||
|
||||
|
||||
def convert(self) -> None:
|
||||
try: del self.builder
|
||||
@ -30,6 +30,8 @@ This holds a "default_fs" variable that should be initialized with an FS
|
||||
that can be used by scripts or modules looking for the canonical default.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import codecs
|
||||
import fnmatch
|
||||
import importlib.util
|
||||
@ -40,7 +42,6 @@ import stat
|
||||
import sys
|
||||
import time
|
||||
from itertools import chain
|
||||
from typing import Optional
|
||||
|
||||
import SCons.Action
|
||||
import SCons.Debug
|
||||
@ -761,6 +762,8 @@ class Base(SCons.Node.Node):
|
||||
st = self.stat()
|
||||
|
||||
if st:
|
||||
# TODO: switch to st.st_mtime, however this changes granularity
|
||||
# (ST_MTIME is an int for backwards compat, st_mtime is float)
|
||||
return st[stat.ST_MTIME]
|
||||
else:
|
||||
return None
|
||||
@ -1492,7 +1495,7 @@ class FS(LocalFS):
|
||||
d = self.Dir(d)
|
||||
self.Top.addRepository(d)
|
||||
|
||||
def PyPackageDir(self, modulename) -> Optional[Dir]:
|
||||
def PyPackageDir(self, modulename) -> Dir | None:
|
||||
r"""Locate the directory of Python module *modulename*.
|
||||
|
||||
For example 'SCons' might resolve to
|
||||
@ -3190,7 +3193,7 @@ class File(Base):
|
||||
# SIGNATURE SUBSYSTEM
|
||||
#
|
||||
|
||||
def get_max_drift_csig(self) -> Optional[str]:
|
||||
def get_max_drift_csig(self) -> str | None:
|
||||
"""
|
||||
Returns the content signature currently stored for this node
|
||||
if it's been unmodified longer than the max_drift value, or the
|
||||
@ -40,18 +40,26 @@ be able to depend on any other type of "thing."
|
||||
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import collections
|
||||
import copy
|
||||
from itertools import chain, zip_longest
|
||||
from typing import Optional
|
||||
from typing import Any, Callable, TYPE_CHECKING
|
||||
|
||||
import SCons.Debug
|
||||
import SCons.Executor
|
||||
import SCons.Memoize
|
||||
from SCons.compat import NoSlotsPyPy
|
||||
from SCons.Debug import logInstanceCreation, Trace
|
||||
from SCons.Executor import Executor
|
||||
from SCons.Util import hash_signature, is_List, UniqueList, render_tree
|
||||
from SCons.Util.sctyping import ExecutorType
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from SCons.Builder import BuilderBase
|
||||
from SCons.Environment import Base as Environment
|
||||
from SCons.Scanner import ScannerBase
|
||||
from SCons.SConsign import SConsignEntry
|
||||
|
||||
print_duplicate = 0
|
||||
|
||||
@ -101,7 +109,7 @@ def do_nothing_node(node) -> None: pass
|
||||
Annotate = do_nothing_node
|
||||
|
||||
# global set for recording all processed SContruct/SConscript nodes
|
||||
SConscriptNodes = set()
|
||||
SConscriptNodes: set[Node] = set()
|
||||
|
||||
# Gets set to 'True' if we're running in interactive mode. Is
|
||||
# currently used to release parts of a target's info during
|
||||
@ -188,7 +196,7 @@ def get_contents_entry(node):
|
||||
"""Fetch the contents of the entry. Returns the exact binary
|
||||
contents of the file."""
|
||||
try:
|
||||
node = node.disambiguate(must_exist=1)
|
||||
node = node.disambiguate(must_exist=True)
|
||||
except SCons.Errors.UserError:
|
||||
# There was nothing on disk with which to disambiguate
|
||||
# this entry. Leave it as an Entry, but return a null
|
||||
@ -355,7 +363,7 @@ class NodeInfoBase:
|
||||
__slots__ = ('__weakref__',)
|
||||
current_version_id = 2
|
||||
|
||||
def update(self, node) -> None:
|
||||
def update(self, node: Node) -> None:
|
||||
try:
|
||||
field_list = self.field_list
|
||||
except AttributeError:
|
||||
@ -375,7 +383,7 @@ class NodeInfoBase:
|
||||
def convert(self, node, val) -> None:
|
||||
pass
|
||||
|
||||
def merge(self, other) -> None:
|
||||
def merge(self, other: NodeInfoBase) -> None:
|
||||
"""
|
||||
Merge the fields of another object into this object. Already existing
|
||||
information is overwritten by the other instance's data.
|
||||
@ -385,7 +393,7 @@ class NodeInfoBase:
|
||||
state = other.__getstate__()
|
||||
self.__setstate__(state)
|
||||
|
||||
def format(self, field_list=None, names: int=0):
|
||||
def format(self, field_list: list[str] | None = None, names: bool = False):
|
||||
if field_list is None:
|
||||
try:
|
||||
field_list = self.field_list
|
||||
@ -408,7 +416,7 @@ class NodeInfoBase:
|
||||
fields.append(f)
|
||||
return fields
|
||||
|
||||
def __getstate__(self):
|
||||
def __getstate__(self) -> dict[str, Any]:
|
||||
"""
|
||||
Return all fields that shall be pickled. Walk the slots in the class
|
||||
hierarchy and add those to the state dictionary. If a '__dict__' slot is
|
||||
@ -428,7 +436,7 @@ class NodeInfoBase:
|
||||
pass
|
||||
return state
|
||||
|
||||
def __setstate__(self, state) -> None:
|
||||
def __setstate__(self, state: dict[str, Any]) -> None:
|
||||
"""
|
||||
Restore the attributes from a pickled state. The version is discarded.
|
||||
"""
|
||||
@ -457,12 +465,12 @@ class BuildInfoBase:
|
||||
def __init__(self) -> None:
|
||||
# Create an object attribute from the class attribute so it ends up
|
||||
# in the pickled data in the .sconsign file.
|
||||
self.bsourcesigs = []
|
||||
self.bdependsigs = []
|
||||
self.bimplicitsigs = []
|
||||
self.bactsig = None
|
||||
self.bsourcesigs: list[BuildInfoBase] = []
|
||||
self.bdependsigs: list[BuildInfoBase] = []
|
||||
self.bimplicitsigs: list[BuildInfoBase] = []
|
||||
self.bactsig: str | None = None
|
||||
|
||||
def merge(self, other) -> None:
|
||||
def merge(self, other: BuildInfoBase) -> None:
|
||||
"""
|
||||
Merge the fields of another object into this object. Already existing
|
||||
information is overwritten by the other instance's data.
|
||||
@ -472,7 +480,7 @@ class BuildInfoBase:
|
||||
state = other.__getstate__()
|
||||
self.__setstate__(state)
|
||||
|
||||
def __getstate__(self):
|
||||
def __getstate__(self) -> dict[str, Any]:
|
||||
"""
|
||||
Return all fields that shall be pickled. Walk the slots in the class
|
||||
hierarchy and add those to the state dictionary. If a '__dict__' slot is
|
||||
@ -492,7 +500,7 @@ class BuildInfoBase:
|
||||
pass
|
||||
return state
|
||||
|
||||
def __setstate__(self, state) -> None:
|
||||
def __setstate__(self, state: dict[str, Any]) -> None:
|
||||
"""
|
||||
Restore the attributes from a pickled state.
|
||||
"""
|
||||
@ -570,42 +578,42 @@ class Node(metaclass=NoSlotsPyPy):
|
||||
# this way, instead of wrapping up each list+dictionary pair in
|
||||
# a class. (Of course, we could always still do that in the
|
||||
# future if we had a good reason to...).
|
||||
self.sources = [] # source files used to build node
|
||||
self.sources_set = set()
|
||||
self.sources: list[Node] = [] # source files used to build node
|
||||
self.sources_set: set[Node] = set()
|
||||
self._specific_sources = False
|
||||
self.depends = [] # explicit dependencies (from Depends)
|
||||
self.depends_set = set()
|
||||
self.ignore = [] # dependencies to ignore
|
||||
self.ignore_set = set()
|
||||
self.prerequisites = None
|
||||
self.implicit = None # implicit (scanned) dependencies (None means not scanned yet)
|
||||
self.waiting_parents = set()
|
||||
self.waiting_s_e = set()
|
||||
self.depends: list[Node] = [] # explicit dependencies (from Depends)
|
||||
self.depends_set: set[Node] = set()
|
||||
self.ignore: list[Node] = [] # dependencies to ignore
|
||||
self.ignore_set: set[Node] = set()
|
||||
self.prerequisites: UniqueList | None = None
|
||||
self.implicit: list[Node] | None = None # implicit (scanned) dependencies (None means not scanned yet)
|
||||
self.waiting_parents: set[Node] = set()
|
||||
self.waiting_s_e: set[Node] = set()
|
||||
self.ref_count = 0
|
||||
self.wkids = None # Kids yet to walk, when it's an array
|
||||
self.wkids: list[Node] | None = None # Kids yet to walk, when it's an array
|
||||
|
||||
self.env = None
|
||||
self.env: Environment | None = None
|
||||
self.state = no_state
|
||||
self.precious = None
|
||||
self.precious = False
|
||||
self.pseudo = False
|
||||
self.noclean = 0
|
||||
self.nocache = 0
|
||||
self.cached = 0 # is this node pulled from cache?
|
||||
self.always_build = None
|
||||
self.includes = None
|
||||
self.noclean = False
|
||||
self.nocache = False
|
||||
self.cached = False # is this node pulled from cache?
|
||||
self.always_build = False
|
||||
self.includes: list[str] | None = None
|
||||
self.attributes = self.Attrs() # Generic place to stick information about the Node.
|
||||
self.side_effect = 0 # true iff this node is a side effect
|
||||
self.side_effects = [] # the side effects of building this target
|
||||
self.linked = 0 # is this node linked to the variant directory?
|
||||
self.changed_since_last_build = 0
|
||||
self.store_info = 0
|
||||
self._tags = None
|
||||
self._func_is_derived = 1
|
||||
self._func_exists = 1
|
||||
self._func_rexists = 1
|
||||
self._func_get_contents = 0
|
||||
self._func_target_from_source = 0
|
||||
self.ninfo = None
|
||||
self.side_effect = False # true iff this node is a side effect
|
||||
self.side_effects: list[Node] = [] # the side effects of building this target
|
||||
self.linked = False # is this node linked to the variant directory?
|
||||
self.changed_since_last_build = 0 # Index for "_decider_map".
|
||||
self.store_info = 0 # Index for "store_info_map".
|
||||
self._tags: dict[str, Any] | None = None
|
||||
self._func_is_derived = 1 # Index for "_is_derived_map".
|
||||
self._func_exists = 1 # Index for "_exists_map"
|
||||
self._func_rexists = 1 # Index for "_rexists_map"
|
||||
self._func_get_contents = 0 # Index for "_get_contents_map"
|
||||
self._func_target_from_source = 0 # Index for "_target_from_source_map"
|
||||
self.ninfo: NodeInfoBase | None = None
|
||||
|
||||
self.clear_memoized_values()
|
||||
|
||||
@ -614,14 +622,14 @@ class Node(metaclass=NoSlotsPyPy):
|
||||
# what line in what file created the node, for example).
|
||||
Annotate(self)
|
||||
|
||||
def disambiguate(self, must_exist=None):
|
||||
def disambiguate(self, must_exist: bool = False):
|
||||
return self
|
||||
|
||||
def get_suffix(self) -> str:
|
||||
return ''
|
||||
|
||||
@SCons.Memoize.CountMethodCall
|
||||
def get_build_env(self):
|
||||
def get_build_env(self) -> Environment:
|
||||
"""Fetch the appropriate Environment to build this node.
|
||||
"""
|
||||
try:
|
||||
@ -632,15 +640,15 @@ class Node(metaclass=NoSlotsPyPy):
|
||||
self._memo['get_build_env'] = result
|
||||
return result
|
||||
|
||||
def get_build_scanner_path(self, scanner):
|
||||
def get_build_scanner_path(self, scanner: ScannerBase):
|
||||
"""Fetch the appropriate scanner path for this node."""
|
||||
return self.get_executor().get_build_scanner_path(scanner)
|
||||
|
||||
def set_executor(self, executor: ExecutorType) -> None:
|
||||
def set_executor(self, executor: Executor) -> None:
|
||||
"""Set the action executor for this node."""
|
||||
self.executor = executor
|
||||
|
||||
def get_executor(self, create: int=1) -> ExecutorType:
|
||||
def get_executor(self, create: bool = True) -> Executor:
|
||||
"""Fetch the action executor for this node. Create one if
|
||||
there isn't already one, and requested to do so."""
|
||||
try:
|
||||
@ -651,7 +659,7 @@ class Node(metaclass=NoSlotsPyPy):
|
||||
try:
|
||||
act = self.builder.action
|
||||
except AttributeError:
|
||||
executor = SCons.Executor.Null(targets=[self]) # type: ignore
|
||||
executor = SCons.Executor.Null(targets=[self]) # type: ignore[assignment]
|
||||
else:
|
||||
executor = SCons.Executor.Executor(act,
|
||||
self.env or self.builder.env,
|
||||
@ -664,7 +672,7 @@ class Node(metaclass=NoSlotsPyPy):
|
||||
def executor_cleanup(self) -> None:
|
||||
"""Let the executor clean up any cached information."""
|
||||
try:
|
||||
executor = self.get_executor(create=None)
|
||||
executor = self.get_executor(create=False)
|
||||
except AttributeError:
|
||||
pass
|
||||
else:
|
||||
@ -681,7 +689,7 @@ class Node(metaclass=NoSlotsPyPy):
|
||||
def push_to_cache(self) -> bool:
|
||||
"""Try to push a node into a cache
|
||||
"""
|
||||
pass
|
||||
return False
|
||||
|
||||
def retrieve_from_cache(self) -> bool:
|
||||
"""Try to retrieve the node's content from a cache
|
||||
@ -708,7 +716,7 @@ class Node(metaclass=NoSlotsPyPy):
|
||||
"""
|
||||
pass
|
||||
|
||||
def prepare(self):
|
||||
def prepare(self) -> None:
|
||||
"""Prepare for this Node to be built.
|
||||
|
||||
This is called after the Taskmaster has decided that the Node
|
||||
@ -741,7 +749,7 @@ class Node(metaclass=NoSlotsPyPy):
|
||||
raise SCons.Errors.StopError(msg % (i, self))
|
||||
self.binfo = self.get_binfo()
|
||||
|
||||
def build(self, **kw):
|
||||
def build(self, **kw) -> None:
|
||||
"""Actually build the node.
|
||||
|
||||
This is called by the Taskmaster after it's decided that the
|
||||
@ -826,10 +834,10 @@ class Node(metaclass=NoSlotsPyPy):
|
||||
"""
|
||||
pass
|
||||
|
||||
def add_to_waiting_s_e(self, node) -> None:
|
||||
def add_to_waiting_s_e(self, node: Node) -> None:
|
||||
self.waiting_s_e.add(node)
|
||||
|
||||
def add_to_waiting_parents(self, node) -> int:
|
||||
def add_to_waiting_parents(self, node: Node) -> int:
|
||||
"""
|
||||
Returns the number of nodes added to our waiting parents list:
|
||||
1 if we add a unique waiting parent, 0 if not. (Note that the
|
||||
@ -866,13 +874,13 @@ class Node(metaclass=NoSlotsPyPy):
|
||||
delattr(self, attr)
|
||||
except AttributeError:
|
||||
pass
|
||||
self.cached = 0
|
||||
self.cached = False
|
||||
self.includes = None
|
||||
|
||||
def clear_memoized_values(self) -> None:
|
||||
self._memo = {}
|
||||
|
||||
def builder_set(self, builder) -> None:
|
||||
def builder_set(self, builder: BuilderBase | None) -> None:
|
||||
self.builder = builder
|
||||
try:
|
||||
del self.executor
|
||||
@ -898,7 +906,7 @@ class Node(metaclass=NoSlotsPyPy):
|
||||
b = self.builder = None
|
||||
return b is not None
|
||||
|
||||
def set_explicit(self, is_explicit) -> None:
|
||||
def set_explicit(self, is_explicit: bool) -> None:
|
||||
self.is_explicit = is_explicit
|
||||
|
||||
def has_explicit_builder(self) -> bool:
|
||||
@ -914,7 +922,7 @@ class Node(metaclass=NoSlotsPyPy):
|
||||
self.is_explicit = False
|
||||
return False
|
||||
|
||||
def get_builder(self, default_builder=None):
|
||||
def get_builder(self, default_builder: BuilderBase | None = None) -> BuilderBase | None:
|
||||
"""Return the set builder, or a specified default value"""
|
||||
try:
|
||||
return self.builder
|
||||
@ -947,7 +955,7 @@ class Node(metaclass=NoSlotsPyPy):
|
||||
return False
|
||||
return True
|
||||
|
||||
def check_attributes(self, name):
|
||||
def check_attributes(self, name: str) -> Any | None:
|
||||
""" Simple API to check if the node.attributes for name has been set"""
|
||||
return getattr(getattr(self, "attributes", None), name, None)
|
||||
|
||||
@ -957,7 +965,7 @@ class Node(metaclass=NoSlotsPyPy):
|
||||
"""
|
||||
return [], None
|
||||
|
||||
def get_found_includes(self, env, scanner, path):
|
||||
def get_found_includes(self, env: Environment, scanner: ScannerBase | None, path) -> list[Node]:
|
||||
"""Return the scanned include lines (implicit dependencies)
|
||||
found in this node.
|
||||
|
||||
@ -967,7 +975,7 @@ class Node(metaclass=NoSlotsPyPy):
|
||||
"""
|
||||
return []
|
||||
|
||||
def get_implicit_deps(self, env, initial_scanner, path_func, kw = {}):
|
||||
def get_implicit_deps(self, env: Environment, initial_scanner: ScannerBase | None, path_func, kw = {}) -> list[Node]:
|
||||
"""Return a list of implicit dependencies for this node.
|
||||
|
||||
This method exists to handle recursive invocation of the scanner
|
||||
@ -1002,7 +1010,7 @@ class Node(metaclass=NoSlotsPyPy):
|
||||
|
||||
return dependencies
|
||||
|
||||
def _get_scanner(self, env, initial_scanner, root_node_scanner, kw):
|
||||
def _get_scanner(self, env: Environment, initial_scanner: ScannerBase | None, root_node_scanner: ScannerBase | None, kw: dict[str, Any] | None) -> ScannerBase | None:
|
||||
if initial_scanner:
|
||||
# handle explicit scanner case
|
||||
scanner = initial_scanner.select(self)
|
||||
@ -1019,13 +1027,13 @@ class Node(metaclass=NoSlotsPyPy):
|
||||
|
||||
return scanner
|
||||
|
||||
def get_env_scanner(self, env, kw={}):
|
||||
def get_env_scanner(self, env: Environment, kw: dict[str, Any] | None = {}) -> ScannerBase | None:
|
||||
return env.get_scanner(self.scanner_key())
|
||||
|
||||
def get_target_scanner(self):
|
||||
def get_target_scanner(self) -> ScannerBase | None:
|
||||
return self.builder.target_scanner
|
||||
|
||||
def get_source_scanner(self, node):
|
||||
def get_source_scanner(self, node: Node) -> ScannerBase | None:
|
||||
"""Fetch the source scanner for the specified node
|
||||
|
||||
NOTE: "self" is the target being built, "node" is
|
||||
@ -1051,10 +1059,10 @@ class Node(metaclass=NoSlotsPyPy):
|
||||
scanner = scanner.select(node)
|
||||
return scanner
|
||||
|
||||
def add_to_implicit(self, deps) -> None:
|
||||
def add_to_implicit(self, deps: list[Node]) -> None:
|
||||
if not hasattr(self, 'implicit') or self.implicit is None:
|
||||
self.implicit = []
|
||||
self.implicit_set = set()
|
||||
self.implicit_set: set[Node] = set()
|
||||
self._children_reset()
|
||||
self._add_child(self.implicit, self.implicit_set, deps)
|
||||
|
||||
@ -1113,10 +1121,10 @@ class Node(metaclass=NoSlotsPyPy):
|
||||
if scanner:
|
||||
executor.scan_targets(scanner)
|
||||
|
||||
def scanner_key(self):
|
||||
def scanner_key(self) -> str | None:
|
||||
return None
|
||||
|
||||
def select_scanner(self, scanner):
|
||||
def select_scanner(self, scanner: ScannerBase) -> ScannerBase | None:
|
||||
"""Selects a scanner for this Node.
|
||||
|
||||
This is a separate method so it can be overridden by Node
|
||||
@ -1126,7 +1134,7 @@ class Node(metaclass=NoSlotsPyPy):
|
||||
"""
|
||||
return scanner.select(self)
|
||||
|
||||
def env_set(self, env, safe: bool=False) -> None:
|
||||
def env_set(self, env: Environment, safe: bool = False) -> None:
|
||||
if safe and self.env:
|
||||
return
|
||||
self.env = env
|
||||
@ -1138,21 +1146,21 @@ class Node(metaclass=NoSlotsPyPy):
|
||||
NodeInfo = NodeInfoBase
|
||||
BuildInfo = BuildInfoBase
|
||||
|
||||
def new_ninfo(self):
|
||||
def new_ninfo(self) -> NodeInfoBase:
|
||||
ninfo = self.NodeInfo()
|
||||
return ninfo
|
||||
|
||||
def get_ninfo(self):
|
||||
def get_ninfo(self) -> NodeInfoBase:
|
||||
if self.ninfo is not None:
|
||||
return self.ninfo
|
||||
self.ninfo = self.new_ninfo()
|
||||
return self.ninfo
|
||||
|
||||
def new_binfo(self):
|
||||
def new_binfo(self) -> BuildInfoBase:
|
||||
binfo = self.BuildInfo()
|
||||
return binfo
|
||||
|
||||
def get_binfo(self):
|
||||
def get_binfo(self) -> BuildInfoBase:
|
||||
"""
|
||||
Fetch a node's build information.
|
||||
|
||||
@ -1211,7 +1219,7 @@ class Node(metaclass=NoSlotsPyPy):
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
def get_csig(self):
|
||||
def get_csig(self) -> str:
|
||||
try:
|
||||
return self.ninfo.csig
|
||||
except AttributeError:
|
||||
@ -1219,13 +1227,13 @@ class Node(metaclass=NoSlotsPyPy):
|
||||
ninfo.csig = hash_signature(self.get_contents())
|
||||
return self.ninfo.csig
|
||||
|
||||
def get_cachedir_csig(self):
|
||||
def get_cachedir_csig(self) -> str:
|
||||
return self.get_csig()
|
||||
|
||||
def get_stored_info(self):
|
||||
def get_stored_info(self) -> SConsignEntry | None:
|
||||
return None
|
||||
|
||||
def get_stored_implicit(self):
|
||||
def get_stored_implicit(self) -> list[Node] | None:
|
||||
"""Fetch the stored implicit dependencies"""
|
||||
return None
|
||||
|
||||
@ -1233,7 +1241,7 @@ class Node(metaclass=NoSlotsPyPy):
|
||||
#
|
||||
#
|
||||
|
||||
def set_precious(self, precious: int = 1) -> None:
|
||||
def set_precious(self, precious: bool = True) -> None:
|
||||
"""Set the Node's precious value."""
|
||||
self.precious = precious
|
||||
|
||||
@ -1241,19 +1249,15 @@ class Node(metaclass=NoSlotsPyPy):
|
||||
"""Set the Node's pseudo value."""
|
||||
self.pseudo = pseudo
|
||||
|
||||
def set_noclean(self, noclean: int = 1) -> None:
|
||||
def set_noclean(self, noclean: bool = True) -> None:
|
||||
"""Set the Node's noclean value."""
|
||||
# Make sure noclean is an integer so the --debug=stree
|
||||
# output in Util.py can use it as an index.
|
||||
self.noclean = noclean and 1 or 0
|
||||
self.noclean = noclean
|
||||
|
||||
def set_nocache(self, nocache: int = 1) -> None:
|
||||
def set_nocache(self, nocache: bool = True) -> None:
|
||||
"""Set the Node's nocache value."""
|
||||
# Make sure nocache is an integer so the --debug=stree
|
||||
# output in Util.py can use it as an index.
|
||||
self.nocache = nocache and 1 or 0
|
||||
self.nocache = nocache
|
||||
|
||||
def set_always_build(self, always_build: int = 1) -> None:
|
||||
def set_always_build(self, always_build: bool = True) -> None:
|
||||
"""Set the Node's always_build value."""
|
||||
self.always_build = always_build
|
||||
|
||||
@ -1261,12 +1265,12 @@ class Node(metaclass=NoSlotsPyPy):
|
||||
"""Reports whether node exists."""
|
||||
return _exists_map[self._func_exists](self)
|
||||
|
||||
def rexists(self):
|
||||
def rexists(self) -> bool:
|
||||
"""Does this node exist locally or in a repository?"""
|
||||
# There are no repositories by default:
|
||||
return _rexists_map[self._func_rexists](self)
|
||||
|
||||
def get_contents(self):
|
||||
def get_contents(self) -> bytes | str:
|
||||
"""Fetch the contents of the entry."""
|
||||
return _get_contents_map[self._func_get_contents](self)
|
||||
|
||||
@ -1275,11 +1279,11 @@ class Node(metaclass=NoSlotsPyPy):
|
||||
not self.linked and \
|
||||
not self.rexists()
|
||||
|
||||
def remove(self):
|
||||
def remove(self) -> None:
|
||||
"""Remove this Node: no-op by default."""
|
||||
return None
|
||||
|
||||
def add_dependency(self, depend):
|
||||
def add_dependency(self, depend: list[Node]) -> None:
|
||||
"""Adds dependencies."""
|
||||
try:
|
||||
self._add_child(self.depends, self.depends_set, depend)
|
||||
@ -1291,14 +1295,14 @@ class Node(metaclass=NoSlotsPyPy):
|
||||
s = str(e)
|
||||
raise SCons.Errors.UserError("attempted to add a non-Node dependency to %s:\n\t%s is a %s, not a Node" % (str(self), s, type(e)))
|
||||
|
||||
def add_prerequisite(self, prerequisite) -> None:
|
||||
def add_prerequisite(self, prerequisite: list[Node]) -> None:
|
||||
"""Adds prerequisites"""
|
||||
if self.prerequisites is None:
|
||||
self.prerequisites = UniqueList()
|
||||
self.prerequisites.extend(prerequisite)
|
||||
self._children_reset()
|
||||
|
||||
def add_ignore(self, depend):
|
||||
def add_ignore(self, depend: list[Node]) -> None:
|
||||
"""Adds dependencies to ignore."""
|
||||
try:
|
||||
self._add_child(self.ignore, self.ignore_set, depend)
|
||||
@ -1310,7 +1314,7 @@ class Node(metaclass=NoSlotsPyPy):
|
||||
s = str(e)
|
||||
raise SCons.Errors.UserError("attempted to ignore a non-Node dependency of %s:\n\t%s is a %s, not a Node" % (str(self), s, type(e)))
|
||||
|
||||
def add_source(self, source):
|
||||
def add_source(self, source: list[Node]) -> None:
|
||||
"""Adds sources."""
|
||||
if self._specific_sources:
|
||||
return
|
||||
@ -1324,7 +1328,7 @@ class Node(metaclass=NoSlotsPyPy):
|
||||
s = str(e)
|
||||
raise SCons.Errors.UserError("attempted to add a non-Node as source of %s:\n\t%s is a %s, not a Node" % (str(self), s, type(e)))
|
||||
|
||||
def _add_child(self, collection, set, child) -> None:
|
||||
def _add_child(self, collection: list[Node], set: set[Node], child: list[Node]) -> None:
|
||||
"""Adds 'child' to 'collection', first checking 'set' to see if it's
|
||||
already present."""
|
||||
added = None
|
||||
@ -1336,11 +1340,11 @@ class Node(metaclass=NoSlotsPyPy):
|
||||
if added:
|
||||
self._children_reset()
|
||||
|
||||
def set_specific_source(self, source) -> None:
|
||||
def set_specific_source(self, source: list[Node]) -> None:
|
||||
self.add_source(source)
|
||||
self._specific_sources = True
|
||||
|
||||
def add_wkid(self, wkid) -> None:
|
||||
def add_wkid(self, wkid: Node) -> None:
|
||||
"""Add a node to the list of kids waiting to be evaluated"""
|
||||
if self.wkids is not None:
|
||||
self.wkids.append(wkid)
|
||||
@ -1352,7 +1356,7 @@ class Node(metaclass=NoSlotsPyPy):
|
||||
self.executor_cleanup()
|
||||
|
||||
@SCons.Memoize.CountMethodCall
|
||||
def _children_get(self):
|
||||
def _children_get(self) -> list[Node]:
|
||||
try:
|
||||
return self._memo['_children_get']
|
||||
except KeyError:
|
||||
@ -1383,12 +1387,12 @@ class Node(metaclass=NoSlotsPyPy):
|
||||
if i not in self.ignore_set:
|
||||
children.append(i)
|
||||
else:
|
||||
children = self.all_children(scan=0)
|
||||
children = self.all_children(scan=False)
|
||||
|
||||
self._memo['_children_get'] = children
|
||||
return children
|
||||
|
||||
def all_children(self, scan: int=1):
|
||||
def all_children(self, scan: bool = True) -> list[Node]:
|
||||
"""Return a list of all the node's direct children."""
|
||||
if scan:
|
||||
self.scan()
|
||||
@ -1412,27 +1416,27 @@ class Node(metaclass=NoSlotsPyPy):
|
||||
# internally anyway...)
|
||||
return list(chain.from_iterable([_f for _f in [self.sources, self.depends, self.implicit] if _f]))
|
||||
|
||||
def children(self, scan: int=1):
|
||||
def children(self, scan: bool = True) -> list[Node]:
|
||||
"""Return a list of the node's direct children, minus those
|
||||
that are ignored by this node."""
|
||||
if scan:
|
||||
self.scan()
|
||||
return self._children_get()
|
||||
|
||||
def set_state(self, state) -> None:
|
||||
def set_state(self, state: int) -> None:
|
||||
self.state = state
|
||||
|
||||
def get_state(self):
|
||||
def get_state(self) -> int:
|
||||
return self.state
|
||||
|
||||
def get_env(self):
|
||||
def get_env(self) -> Environment:
|
||||
env = self.env
|
||||
if not env:
|
||||
import SCons.Defaults
|
||||
env = SCons.Defaults.DefaultEnvironment()
|
||||
return env
|
||||
|
||||
def Decider(self, function) -> None:
|
||||
def Decider(self, function: Callable[[Node, Node, NodeInfoBase, Node | None], bool]) -> None:
|
||||
foundkey = None
|
||||
for k, v in _decider_map.items():
|
||||
if v == function:
|
||||
@ -1443,19 +1447,19 @@ class Node(metaclass=NoSlotsPyPy):
|
||||
_decider_map[foundkey] = function
|
||||
self.changed_since_last_build = foundkey
|
||||
|
||||
def Tag(self, key, value) -> None:
|
||||
def Tag(self, key: str, value: Any | None) -> None:
|
||||
""" Add a user-defined tag. """
|
||||
if not self._tags:
|
||||
self._tags = {}
|
||||
self._tags[key] = value
|
||||
|
||||
def GetTag(self, key):
|
||||
def GetTag(self, key: str) -> Any | None:
|
||||
""" Return a user-defined tag. """
|
||||
if not self._tags:
|
||||
return None
|
||||
return self._tags.get(key, None)
|
||||
|
||||
def changed(self, node=None, allowcache: bool=False):
|
||||
def changed(self, node: Node | None = None, allowcache: bool = False) -> bool:
|
||||
"""
|
||||
Returns if the node is up-to-date with respect to the BuildInfo
|
||||
stored last time it was built. The default behavior is to compare
|
||||
@ -1534,7 +1538,7 @@ class Node(metaclass=NoSlotsPyPy):
|
||||
if self.always_build:
|
||||
return False
|
||||
state = 0
|
||||
for kid in self.children(None):
|
||||
for kid in self.children(False):
|
||||
s = kid.get_state()
|
||||
if s and (not state or s > state):
|
||||
state = s
|
||||
@ -1559,13 +1563,13 @@ class Node(metaclass=NoSlotsPyPy):
|
||||
path = self.get_build_scanner_path(scanner)
|
||||
else:
|
||||
path = None
|
||||
def f(node, env=env, scanner=scanner, path=path):
|
||||
def f(node: Node, env: Environment = env, scanner: ScannerBase = scanner, path=path):
|
||||
return node.get_found_includes(env, scanner, path)
|
||||
return render_tree(s, f, 1)
|
||||
else:
|
||||
return None
|
||||
|
||||
def get_abspath(self):
|
||||
def get_abspath(self) -> str:
|
||||
"""
|
||||
Return an absolute path to the Node. This will return simply
|
||||
str(Node) by default, but for Node types that have a concept of
|
||||
@ -1573,7 +1577,7 @@ class Node(metaclass=NoSlotsPyPy):
|
||||
"""
|
||||
return str(self)
|
||||
|
||||
def for_signature(self):
|
||||
def for_signature(self) -> str:
|
||||
"""
|
||||
Return a string representation of the Node that will always
|
||||
be the same for this particular Node, no matter what. This
|
||||
@ -1588,7 +1592,7 @@ class Node(metaclass=NoSlotsPyPy):
|
||||
"""
|
||||
return str(self)
|
||||
|
||||
def get_string(self, for_signature):
|
||||
def get_string(self, for_signature: bool) -> str:
|
||||
"""This is a convenience function designed primarily to be
|
||||
used in command generators (i.e., CommandGeneratorActions or
|
||||
Environment variables that are callable), which are called
|
||||
@ -1719,9 +1723,9 @@ class NodeList(collections.UserList):
|
||||
def __str__(self) -> str:
|
||||
return str(list(map(str, self.data)))
|
||||
|
||||
def get_children(node, parent): return node.children()
|
||||
def ignore_cycle(node, stack) -> None: pass
|
||||
def do_nothing(node, parent) -> None: pass
|
||||
def get_children(node: Node, parent: Node | None) -> list[Node]: return node.children()
|
||||
def ignore_cycle(node: Node, stack: list[Node]) -> None: pass
|
||||
def do_nothing(node: Node, parent: Node | None) -> None: pass
|
||||
|
||||
class Walker:
|
||||
"""An iterator for walking a Node tree.
|
||||
@ -1736,15 +1740,19 @@ class Walker:
|
||||
This class does not get caught in node cycles caused, for example,
|
||||
by C header file include loops.
|
||||
"""
|
||||
def __init__(self, node, kids_func=get_children,
|
||||
cycle_func=ignore_cycle,
|
||||
eval_func=do_nothing) -> None:
|
||||
def __init__(
|
||||
self,
|
||||
node: Node,
|
||||
kids_func: Callable[[Node, Node | None], list[Node]] = get_children,
|
||||
cycle_func: Callable[[Node, list[Node]], None] = ignore_cycle,
|
||||
eval_func: Callable[[Node, Node | None], None] = do_nothing,
|
||||
) -> None:
|
||||
self.kids_func = kids_func
|
||||
self.cycle_func = cycle_func
|
||||
self.eval_func = eval_func
|
||||
node.wkids = copy.copy(kids_func(node, None))
|
||||
self.stack = [node]
|
||||
self.history = {} # used to efficiently detect and avoid cycles
|
||||
self.history: dict[Node, Any | None] = {} # used to efficiently detect and avoid cycles
|
||||
self.history[node] = None
|
||||
|
||||
def get_next(self):
|
||||
@ -42,6 +42,7 @@ their own platform definition.
|
||||
|
||||
import SCons.compat
|
||||
|
||||
import atexit
|
||||
import importlib
|
||||
import os
|
||||
import sys
|
||||
@ -150,7 +151,7 @@ class TempFileMunge:
|
||||
the length of command lines. Example::
|
||||
|
||||
env["TEMPFILE"] = TempFileMunge
|
||||
env["LINKCOM"] = "${TEMPFILE('$LINK $TARGET $SOURCES','$LINKCOMSTR')}"
|
||||
env["LINKCOM"] = "${TEMPFILE('$LINK $TARGET $SOURCES', '$LINKCOMSTR')}"
|
||||
|
||||
By default, the name of the temporary file used begins with a
|
||||
prefix of '@'. This may be configured for other tool chains by
|
||||
@ -258,31 +259,27 @@ class TempFileMunge:
|
||||
fd, tmp = tempfile.mkstemp(suffix, dir=tempfile_dir, text=True)
|
||||
native_tmp = SCons.Util.get_native_path(tmp)
|
||||
|
||||
# arrange for cleanup on exit:
|
||||
|
||||
def tmpfile_cleanup(file) -> None:
|
||||
os.remove(file)
|
||||
|
||||
atexit.register(tmpfile_cleanup, tmp)
|
||||
|
||||
if env.get('SHELL', None) == 'sh':
|
||||
# The sh shell will try to escape the backslashes in the
|
||||
# path, so unescape them.
|
||||
native_tmp = native_tmp.replace('\\', r'\\\\')
|
||||
# In Cygwin, we want to use rm to delete the temporary
|
||||
# file, because del does not exist in the sh shell.
|
||||
rm = env.Detect('rm') or 'del'
|
||||
else:
|
||||
# Don't use 'rm' if the shell is not sh, because rm won't
|
||||
# work with the Windows shells (cmd.exe or command.com) or
|
||||
# Windows path names.
|
||||
rm = 'del'
|
||||
|
||||
if 'TEMPFILEPREFIX' in env:
|
||||
prefix = env.subst('$TEMPFILEPREFIX')
|
||||
else:
|
||||
prefix = '@'
|
||||
prefix = "@"
|
||||
|
||||
tempfile_esc_func = env.get('TEMPFILEARGESCFUNC', SCons.Subst.quote_spaces)
|
||||
args = [
|
||||
tempfile_esc_func(arg)
|
||||
for arg in cmd[1:]
|
||||
]
|
||||
args = [tempfile_esc_func(arg) for arg in cmd[1:]]
|
||||
join_char = env.get('TEMPFILEARGJOIN', ' ')
|
||||
os.write(fd, bytearray(join_char.join(args) + "\n", 'utf-8'))
|
||||
os.write(fd, bytearray(join_char.join(args) + "\n", encoding="utf-8"))
|
||||
os.close(fd)
|
||||
|
||||
# XXX Using the SCons.Action.print_actions value directly
|
||||
@ -301,15 +298,20 @@ class TempFileMunge:
|
||||
# purity get in the way of just being helpful, so we'll
|
||||
# reach into SCons.Action directly.
|
||||
if SCons.Action.print_actions:
|
||||
cmdstr = env.subst(self.cmdstr, SCons.Subst.SUBST_RAW, target,
|
||||
source) if self.cmdstr is not None else ''
|
||||
cmdstr = (
|
||||
env.subst(self.cmdstr, SCons.Subst.SUBST_RAW, target, source)
|
||||
if self.cmdstr is not None
|
||||
else ''
|
||||
)
|
||||
# Print our message only if XXXCOMSTR returns an empty string
|
||||
if len(cmdstr) == 0 :
|
||||
cmdstr = ("Using tempfile "+native_tmp+" for command line:\n"+
|
||||
str(cmd[0]) + " " + " ".join(args))
|
||||
if not cmdstr:
|
||||
cmdstr = (
|
||||
f"Using tempfile {native_tmp} for command line:\n"
|
||||
f'{cmd[0]} {" ".join(args)}'
|
||||
)
|
||||
self._print_cmd_str(target, source, env, cmdstr)
|
||||
|
||||
cmdlist = [cmd[0], prefix + native_tmp + '\n' + rm, native_tmp]
|
||||
cmdlist = [cmd[0], prefix + native_tmp]
|
||||
|
||||
# Store the temporary file command list into the target Node.attributes
|
||||
# to avoid creating two temporary files one for print and one for execute.
|
||||
@ -46,7 +46,7 @@ def generate(env) -> None:
|
||||
# make sure this works on Macs with Tiger or earlier
|
||||
try:
|
||||
dirlist = os.listdir('/etc/paths.d')
|
||||
except FileNotFoundError:
|
||||
except (FileNotFoundError, PermissionError):
|
||||
dirlist = []
|
||||
|
||||
for file in dirlist:
|
||||
@ -31,6 +31,8 @@ Tests on the build system can detect if compiler sees header files, if
|
||||
libraries are installed, if some command line options are supported etc.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import SCons.compat
|
||||
|
||||
import atexit
|
||||
@ -39,7 +41,6 @@ import os
|
||||
import re
|
||||
import sys
|
||||
import traceback
|
||||
from typing import Tuple
|
||||
|
||||
import SCons.Action
|
||||
import SCons.Builder
|
||||
@ -265,7 +266,7 @@ class SConfBuildTask(SCons.Taskmaster.AlwaysTask):
|
||||
sys.excepthook(*self.exc_info())
|
||||
return SCons.Taskmaster.Task.failed(self)
|
||||
|
||||
def collect_node_states(self) -> Tuple[bool, bool, bool]:
|
||||
def collect_node_states(self) -> tuple[bool, bool, bool]:
|
||||
# returns (is_up_to_date, cached_error, cachable)
|
||||
# where is_up_to_date is True if the node(s) are up_to_date
|
||||
# cached_error is True if the node(s) are up_to_date, but the
|
||||
@ -1100,12 +1101,17 @@ def CheckCXXHeader(context, header, include_quotes: str = '""'):
|
||||
|
||||
|
||||
def CheckLib(context, library = None, symbol: str = "main",
|
||||
header = None, language = None, autoadd: bool=True,
|
||||
append: bool=True, unique: bool=False) -> bool:
|
||||
header = None, language = None, extra_libs = None,
|
||||
autoadd: bool=True, append: bool=True, unique: bool=False) -> bool:
|
||||
"""
|
||||
A test for a library. See also CheckLibWithHeader.
|
||||
A test for a library. See also :func:`CheckLibWithHeader`.
|
||||
Note that library may also be None to test whether the given symbol
|
||||
compiles without flags.
|
||||
|
||||
.. versionchanged:: 4.9.0
|
||||
Added the *extra_libs* keyword parameter. The actual implementation
|
||||
is in :func:`SCons.Conftest.CheckLib` which already accepted this
|
||||
parameter, so this is only exposing existing functionality.
|
||||
"""
|
||||
|
||||
if not library:
|
||||
@ -1115,9 +1121,9 @@ def CheckLib(context, library = None, symbol: str = "main",
|
||||
library = [library]
|
||||
|
||||
# ToDo: accept path for the library
|
||||
res = SCons.Conftest.CheckLib(context, library, symbol, header = header,
|
||||
language = language, autoadd = autoadd,
|
||||
append=append, unique=unique)
|
||||
res = SCons.Conftest.CheckLib(context, library, symbol, header=header,
|
||||
language=language, extra_libs=extra_libs,
|
||||
autoadd=autoadd, append=append, unique=unique)
|
||||
context.did_show_result = True
|
||||
return not res
|
||||
|
||||
@ -1125,15 +1131,21 @@ def CheckLib(context, library = None, symbol: str = "main",
|
||||
# Bram: Can only include one header and can't use #ifdef HAVE_HEADER_H.
|
||||
|
||||
def CheckLibWithHeader(context, libs, header, language,
|
||||
call = None, autoadd: bool=True, append: bool=True, unique: bool=False) -> bool:
|
||||
# ToDo: accept path for library. Support system header files.
|
||||
extra_libs = None, call = None, autoadd: bool=True,
|
||||
append: bool=True, unique: bool=False) -> bool:
|
||||
"""
|
||||
Another (more sophisticated) test for a library.
|
||||
Checks, if library and header is available for language (may be 'C'
|
||||
or 'CXX'). Call maybe be a valid expression _with_ a trailing ';'.
|
||||
As in CheckLib, we support library=None, to test if the call compiles
|
||||
As in :func:`CheckLib`, we support library=None, to test if the call compiles
|
||||
without extra link flags.
|
||||
|
||||
.. versionchanged:: 4.9.0
|
||||
Added the *extra_libs* keyword parameter. The actual implementation
|
||||
is in :func:`SCons.Conftest.CheckLib` which already accepted this
|
||||
parameter, so this is only exposing existing functionality.
|
||||
"""
|
||||
# ToDo: accept path for library. Support system header files.
|
||||
prog_prefix, dummy = createIncludesFromHeaders(header, 0)
|
||||
if not libs:
|
||||
libs = [None]
|
||||
@ -1142,8 +1154,8 @@ def CheckLibWithHeader(context, libs, header, language,
|
||||
libs = [libs]
|
||||
|
||||
res = SCons.Conftest.CheckLib(context, libs, None, prog_prefix,
|
||||
call = call, language = language, autoadd=autoadd,
|
||||
append=append, unique=unique)
|
||||
extra_libs = extra_libs, call = call, language = language,
|
||||
autoadd=autoadd, append=append, unique=unique)
|
||||
context.did_show_result = 1
|
||||
return not res
|
||||
|
||||
@ -28,6 +28,8 @@ CConditionalScanner, which must be explicitly selected by calling
|
||||
add_scanner() for each affected suffix.
|
||||
"""
|
||||
|
||||
from typing import Dict
|
||||
|
||||
import SCons.Node.FS
|
||||
import SCons.cpp
|
||||
import SCons.Util
|
||||
@ -65,32 +67,85 @@ class SConsCPPScanner(SCons.cpp.PreProcessor):
|
||||
self.missing.append((file, self.current_file))
|
||||
return ''
|
||||
|
||||
def dictify_CPPDEFINES(env) -> dict:
|
||||
"""Returns CPPDEFINES converted to a dict.
|
||||
def dictify_CPPDEFINES(env, replace: bool = False) -> dict:
|
||||
"""Return CPPDEFINES converted to a dict for preprocessor emulation.
|
||||
|
||||
This should be similar to :func:`~SCons.Defaults.processDefines`.
|
||||
Unfortunately, we can't do the simple thing of calling that routine and
|
||||
passing the result to the dict() constructor, because it turns the defines
|
||||
into a list of "name=value" pairs, which the dict constructor won't
|
||||
consume correctly. Also cannot just call dict on CPPDEFINES itself - it's
|
||||
fine if it's stored in the converted form (currently deque of tuples), but
|
||||
CPPDEFINES could be in other formats too.
|
||||
The concept is similar to :func:`~SCons.Defaults.processDefines`:
|
||||
turn the values stored in an internal form in ``env['CPPDEFINES']``
|
||||
into one needed for a specific context - in this case the cpp-like
|
||||
work the C/C++ scanner will do. We can't reuse ``processDefines``
|
||||
output as that's a list of strings for the command line. We also can't
|
||||
pass the ``CPPDEFINES`` variable directly to the ``dict`` constructor,
|
||||
as SCons allows it to be stored in several different ways - it's only
|
||||
after ``Append`` and relatives has been called we know for sure it will
|
||||
be a deque of tuples.
|
||||
|
||||
So we have to do all the work here - keep concepts in sync with
|
||||
``processDefines``.
|
||||
If requested (*replace* is true), simulate some of the macro
|
||||
replacement that would take place if an actual preprocessor ran,
|
||||
to avoid some conditional inclusions comeing out wrong. A bit
|
||||
of an edge case, but does happen (GH #4623). See 6.10.5 in the C
|
||||
standard and 15.6 in the C++ standard).
|
||||
|
||||
Args:
|
||||
replace: if true, simulate macro replacement
|
||||
|
||||
.. versionchanged:: 4.9.0
|
||||
Simple macro replacement added, and *replace* arg to enable it.
|
||||
"""
|
||||
def _replace(mapping: Dict) -> Dict:
|
||||
"""Simplistic macro replacer for dictify_CPPDEFINES.
|
||||
|
||||
Scan *mapping* for a value that is the same as a key in the dict,
|
||||
and replace with the value of that key; the process is repeated a few
|
||||
times, but not forever in case someone left a case that can't be
|
||||
fully resolved. This is a cheap approximation of the preprocessor's
|
||||
macro replacement rules with no smarts - it doesn't "look inside"
|
||||
the values, so only triggers on object-like macros, not on
|
||||
function-like macros, and will not work on complex values, e.g.
|
||||
a value like ``(1UL << PR_MTE_TCF_SHIFT)`` would not have
|
||||
``PR_MTE_TCF_SHIFT`` replaced if that was also a key in ``CPPDEFINES``.
|
||||
|
||||
Args:
|
||||
mapping: a dictionary representing macro names and replacements.
|
||||
|
||||
Returns:
|
||||
a dictionary with replacements made.
|
||||
"""
|
||||
old_ns = mapping
|
||||
loops = 0
|
||||
while loops < 5: # don't recurse forever in case there's circular data
|
||||
# this was originally written as a dict comprehension, but unrolling
|
||||
# lets us add a finer-grained check for whether another loop is
|
||||
# needed, rather than comparing two dicts to see if one changed.
|
||||
again = False
|
||||
ns = {}
|
||||
for k, v in old_ns.items():
|
||||
if v in old_ns:
|
||||
ns[k] = old_ns[v]
|
||||
if not again and ns[k] != v:
|
||||
again = True
|
||||
else:
|
||||
ns[k] = v
|
||||
if not again:
|
||||
break
|
||||
old_ns = ns
|
||||
loops += 1
|
||||
return ns
|
||||
|
||||
cppdefines = env.get('CPPDEFINES', {})
|
||||
result = {}
|
||||
if cppdefines is None:
|
||||
return result
|
||||
if not cppdefines:
|
||||
return {}
|
||||
|
||||
if SCons.Util.is_Tuple(cppdefines):
|
||||
# single macro defined in a tuple
|
||||
try:
|
||||
return {cppdefines[0]: cppdefines[1]}
|
||||
except IndexError:
|
||||
return {cppdefines[0]: None}
|
||||
|
||||
if SCons.Util.is_Sequence(cppdefines):
|
||||
# multiple (presumably) macro defines in a deque, list, etc.
|
||||
result = {}
|
||||
for c in cppdefines:
|
||||
if SCons.Util.is_Sequence(c):
|
||||
try:
|
||||
@ -107,9 +162,12 @@ def dictify_CPPDEFINES(env) -> dict:
|
||||
else:
|
||||
# don't really know what to do here
|
||||
result[c] = None
|
||||
return result
|
||||
if replace:
|
||||
return _replace(result)
|
||||
return(result)
|
||||
|
||||
if SCons.Util.is_String(cppdefines):
|
||||
# single macro define in a string
|
||||
try:
|
||||
name, value = cppdefines.split('=')
|
||||
return {name: value}
|
||||
@ -117,6 +175,9 @@ def dictify_CPPDEFINES(env) -> dict:
|
||||
return {cppdefines: None}
|
||||
|
||||
if SCons.Util.is_Dict(cppdefines):
|
||||
# already in the desired form
|
||||
if replace:
|
||||
return _replace(cppdefines)
|
||||
return cppdefines
|
||||
|
||||
return {cppdefines: None}
|
||||
@ -136,7 +197,9 @@ class SConsCPPScannerWrapper:
|
||||
|
||||
def __call__(self, node, env, path=()):
|
||||
cpp = SConsCPPScanner(
|
||||
current=node.get_dir(), cpppath=path, dict=dictify_CPPDEFINES(env)
|
||||
current=node.get_dir(),
|
||||
cpppath=path,
|
||||
dict=dictify_CPPDEFINES(env, replace=True),
|
||||
)
|
||||
result = cpp(node)
|
||||
for included, includer in cpp.missing:
|
||||
@ -149,6 +212,7 @@ class SConsCPPScannerWrapper:
|
||||
|
||||
def recurse_nodes(self, nodes):
|
||||
return nodes
|
||||
|
||||
def select(self, node):
|
||||
return self
|
||||
|
||||
@ -169,6 +169,11 @@ class LaTeX(ScannerBase):
|
||||
'addsectionbib': 'BIBINPUTS',
|
||||
'makeindex': 'INDEXSTYLE',
|
||||
'usepackage': 'TEXINPUTS',
|
||||
'usetheme': 'TEXINPUTS',
|
||||
'usecolortheme': 'TEXINPUTS',
|
||||
'usefonttheme': 'TEXINPUTS',
|
||||
'useinnertheme': 'TEXINPUTS',
|
||||
'useoutertheme': 'TEXINPUTS',
|
||||
'lstinputlisting': 'TEXINPUTS'}
|
||||
env_variables = SCons.Util.unique(list(keyword_paths.values()))
|
||||
two_arg_commands = ['import', 'subimport',
|
||||
@ -193,6 +198,7 @@ class LaTeX(ScannerBase):
|
||||
| addglobalbib
|
||||
| addsectionbib
|
||||
| usepackage
|
||||
| use(?:|color|font|inner|outer)theme(?:\s*\[[^\]]+\])?
|
||||
)
|
||||
\s*{([^}]*)} # first arg
|
||||
(?: \s*{([^}]*)} )? # maybe another arg
|
||||
@ -362,6 +368,9 @@ class LaTeX(ScannerBase):
|
||||
if inc_type in self.two_arg_commands:
|
||||
inc_subdir = os.path.join(subdir, include[1])
|
||||
inc_list = include[2].split(',')
|
||||
elif re.match('use(|color|font|inner|outer)theme', inc_type):
|
||||
inc_list = [re.sub('use', 'beamer', inc_type) + _ + '.sty' for _ in
|
||||
include[1].split(',')]
|
||||
else:
|
||||
inc_list = include[1].split(',')
|
||||
for inc in inc_list:
|
||||
@ -411,7 +420,7 @@ class LaTeX(ScannerBase):
|
||||
if n is None:
|
||||
# Do not bother with 'usepackage' warnings, as they most
|
||||
# likely refer to system-level files
|
||||
if inc_type != 'usepackage':
|
||||
if inc_type != 'usepackage' or re.match("use(|color|font|inner|outer)theme", inc_type):
|
||||
SCons.Warnings.warn(
|
||||
SCons.Warnings.DependencyWarning,
|
||||
"No dependency generated for file: %s "
|
||||
@ -31,9 +31,12 @@ some other module. If it's specific to the "scons" script invocation,
|
||||
it goes here.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import SCons.compat
|
||||
|
||||
import importlib.util
|
||||
import optparse
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
@ -41,7 +44,6 @@ import time
|
||||
import traceback
|
||||
import platform
|
||||
import threading
|
||||
from typing import Optional, List, TYPE_CHECKING
|
||||
|
||||
import SCons.CacheDir
|
||||
import SCons.Debug
|
||||
@ -59,14 +61,13 @@ import SCons.Taskmaster
|
||||
import SCons.Util
|
||||
import SCons.Warnings
|
||||
import SCons.Script.Interactive
|
||||
if TYPE_CHECKING:
|
||||
from SCons.Script import SConsOption
|
||||
from .SConsOptions import SConsOption
|
||||
from SCons.Util.stats import count_stats, memory_stats, time_stats, ENABLE_JSON, write_scons_stats_file, JSON_OUTPUT_FILE
|
||||
|
||||
from SCons import __version__ as SConsVersion
|
||||
|
||||
# these define the range of versions SCons supports
|
||||
minimum_python_version = (3, 6, 0)
|
||||
minimum_python_version = (3, 7, 0)
|
||||
deprecated_python_version = (3, 7, 0)
|
||||
|
||||
# ordered list of SConstruct names to look for if there is no -f flag
|
||||
@ -508,29 +509,41 @@ class FakeOptionParser:
|
||||
# TODO: to quiet checkers, FakeOptionParser should also define
|
||||
# raise_exception_on_error, preserve_unknown_options, largs and parse_args
|
||||
|
||||
def add_local_option(self, *args, **kw) -> "SConsOption":
|
||||
def add_local_option(self, *args, **kw) -> SConsOption:
|
||||
pass
|
||||
|
||||
|
||||
OptionsParser = FakeOptionParser()
|
||||
|
||||
def AddOption(*args, settable: bool = False, **kw) -> "SConsOption":
|
||||
def AddOption(*args, **kw) -> SConsOption:
|
||||
"""Add a local option to the option parser - Public API.
|
||||
|
||||
If the *settable* parameter is true, the option will be included in the
|
||||
list of settable options; all other keyword arguments are passed on to
|
||||
:meth:`~SCons.Script.SConsOptions.SConsOptionParser.add_local_option`.
|
||||
If the SCons-specific *settable* kwarg is true (default ``False``),
|
||||
the option will allow calling :func:``SetOption`.
|
||||
|
||||
.. versionchanged:: 4.8.0
|
||||
The *settable* parameter added to allow including the new option
|
||||
to the table of options eligible to use :func:`SetOption`.
|
||||
|
||||
in the table of options eligible to use :func:`SetOption`.
|
||||
"""
|
||||
settable = kw.get('settable', False)
|
||||
if len(args) == 1 and isinstance(args[0], SConsOption):
|
||||
# If they passed an SConsOption object, ignore kw - the underlying
|
||||
# add_option method relies on seeing zero kwargs to recognize this.
|
||||
# Since we don't support an omitted default, overrwrite optparse's
|
||||
# marker to get the same effect as setting it in kw otherwise.
|
||||
optobj = args[0]
|
||||
if optobj.default is optparse.NO_DEFAULT:
|
||||
optobj.default = None
|
||||
# make sure settable attribute exists; positive setting wins
|
||||
attr_settable = getattr(optobj, "settable")
|
||||
if attr_settable is None or settable > attr_settable:
|
||||
optobj.settable = settable
|
||||
return OptionsParser.add_local_option(*args)
|
||||
|
||||
if 'default' not in kw:
|
||||
kw['default'] = None
|
||||
kw['settable'] = settable
|
||||
result = OptionsParser.add_local_option(*args, **kw)
|
||||
return result
|
||||
kw['settable'] = settable # just to make sure it gets set
|
||||
return OptionsParser.add_local_option(*args, **kw)
|
||||
|
||||
def GetOption(name: str):
|
||||
"""Get the value from an option - Public API."""
|
||||
@ -540,7 +553,7 @@ def SetOption(name: str, value):
|
||||
"""Set the value of an option - Public API."""
|
||||
return OptionsParser.values.set_option(name, value)
|
||||
|
||||
def DebugOptions(json: Optional[str] = None) -> None:
|
||||
def DebugOptions(json: str | None = None) -> None:
|
||||
"""Specify options to SCons debug logic - Public API.
|
||||
|
||||
Currently only *json* is supported, which changes the JSON file
|
||||
@ -669,8 +682,8 @@ def _scons_internal_error() -> None:
|
||||
sys.exit(2)
|
||||
|
||||
def _SConstruct_exists(
|
||||
dirname: str, repositories: List[str], filelist: List[str]
|
||||
) -> Optional[str]:
|
||||
dirname: str, repositories: list[str], filelist: list[str]
|
||||
) -> str | None:
|
||||
"""Check that an SConstruct file exists in a directory.
|
||||
|
||||
Arguments:
|
||||
@ -1344,12 +1357,6 @@ def _build_targets(fs, options, targets, target_top):
|
||||
# various print_* settings, tree_printer list, etc.
|
||||
BuildTask.options = options
|
||||
|
||||
is_pypy = platform.python_implementation() == 'PyPy'
|
||||
# As of 3.7, python removed support for threadless platforms.
|
||||
# See https://www.python.org/dev/peps/pep-0011/
|
||||
is_37_or_later = sys.version_info >= (3, 7)
|
||||
# python_has_threads = sysconfig.get_config_var('WITH_THREAD') or is_pypy or is_37_or_later
|
||||
|
||||
# As of python 3.4 threading has a dummy_threading module for use when there is no threading
|
||||
# it's get_ident() will allways return -1, while real threading modules get_ident() will
|
||||
# always return a positive integer
|
||||
@ -1412,7 +1419,7 @@ def _exec_main(parser, values) -> None:
|
||||
class SConsPdb(pdb.Pdb):
|
||||
"""Specialization of Pdb to help find SConscript files."""
|
||||
|
||||
def lookupmodule(self, filename: str) -> Optional[str]:
|
||||
def lookupmodule(self, filename: str) -> str | None:
|
||||
"""Helper function for break/clear parsing -- SCons version.
|
||||
|
||||
Translates (possibly incomplete) file or module name
|
||||
@ -21,6 +21,8 @@
|
||||
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import gettext
|
||||
import optparse
|
||||
import re
|
||||
@ -73,7 +75,7 @@ class SConsValues(optparse.Values):
|
||||
1. set on the command line.
|
||||
2. set in an SConscript file via :func:`~SCons.Script.Main.SetOption`.
|
||||
3. the default setting (from the the ``op.add_option()``
|
||||
calls in the :func:`Parser` function, below).
|
||||
calls in the :func:`Parser` function.
|
||||
|
||||
The command line always overrides a value set in a SConscript file,
|
||||
which in turn always overrides default settings. Because we want
|
||||
@ -144,16 +146,14 @@ class SConsValues(optparse.Values):
|
||||
]
|
||||
|
||||
def set_option(self, name: str, value) -> None:
|
||||
"""Sets an option *name* from an SConscript file.
|
||||
"""Set an option value from a :func:`~SCons.Script.Main.SetOption` call.
|
||||
|
||||
Vvalidation steps for known (that is, defined in SCons itself)
|
||||
options are in-line here. Validation should be along the same
|
||||
lines as for options processed from the command line -
|
||||
it's kind of a pain to have to duplicate. Project-defined options
|
||||
can specify callbacks for the command-line version, but will have
|
||||
no inbuilt validation here. It's up to the build system maintainer
|
||||
to make sure :func:`~SCons.Script.Main.SetOption` is being used
|
||||
correctly, we can't really do any better here.
|
||||
Validation steps for settable options (those defined in SCons
|
||||
itself) are in-line here. Duplicates the logic for the matching
|
||||
command-line options in :func:`Parse` - these need to be kept
|
||||
in sync. Cannot provide validation for options added via
|
||||
:func:`~SCons.Script.Main.AddOption` since we don't know about those
|
||||
ahead of time - it is up to the developer to figure that out.
|
||||
|
||||
Raises:
|
||||
UserError: the option is not settable.
|
||||
@ -233,13 +233,37 @@ class SConsValues(optparse.Values):
|
||||
|
||||
|
||||
class SConsOption(optparse.Option):
|
||||
def convert_value(self, opt, value):
|
||||
"""SCons added option.
|
||||
|
||||
Changes :attr:`CHECK_METHODS` and :attr:`CONST_ACTIONS` settings from
|
||||
:class:`optparse.Option` base class to tune for our usage.
|
||||
|
||||
New function :meth:`_check_nargs_optional` implements the ``nargs=?``
|
||||
syntax from :mod:`argparse`, and is added to the ``CHECK_METHODS`` list.
|
||||
Overridden :meth:`convert_value` supports this usage.
|
||||
|
||||
.. versionchanged:: 4.9.0
|
||||
The *settable* attribute is added to ``ATTRS``, allowing it to be
|
||||
set in the option. A parameter to mark the option settable was added
|
||||
in 4.8.0, but was not initially made part of the option object itself.
|
||||
"""
|
||||
# can uncomment to have a place to trap SConsOption creation for debugging:
|
||||
# def __init__(self, *args, **kwargs):
|
||||
# super().__init__(*args, **kwargs)
|
||||
|
||||
def convert_value(self, opt: str, value):
|
||||
"""SCons override: recognize nargs="?"."""
|
||||
if value is not None:
|
||||
if self.nargs in (1, '?'):
|
||||
return self.check_value(opt, value)
|
||||
return tuple([self.check_value(opt, v) for v in value])
|
||||
|
||||
def process(self, opt, value, values, parser):
|
||||
"""Process a value.
|
||||
|
||||
Direct copy of optparse version including the comments -
|
||||
we don't change anything so this could just be dropped.
|
||||
"""
|
||||
# First, convert the value(s) to the right type. Howl if any
|
||||
# value(s) are bogus.
|
||||
value = self.convert_value(opt, value)
|
||||
@ -250,15 +274,17 @@ class SConsOption(optparse.Option):
|
||||
return self.take_action(
|
||||
self.action, self.dest, opt, value, values, parser)
|
||||
|
||||
def _check_nargs_optional(self):
|
||||
def _check_nargs_optional(self) -> None:
|
||||
"""SCons added: deal with optional option-arguments."""
|
||||
if self.nargs == '?' and self._short_opts:
|
||||
fmt = "option %s: nargs='?' is incompatible with short options"
|
||||
raise SCons.Errors.UserError(fmt % self._short_opts[0])
|
||||
|
||||
ATTRS = optparse.Option.ATTRS + ['settable'] # added for SCons
|
||||
CHECK_METHODS = optparse.Option.CHECK_METHODS
|
||||
if CHECK_METHODS is None:
|
||||
CHECK_METHODS = []
|
||||
CHECK_METHODS = CHECK_METHODS + [_check_nargs_optional]
|
||||
CHECK_METHODS += [_check_nargs_optional] # added for SCons
|
||||
CONST_ACTIONS = optparse.Option.CONST_ACTIONS + optparse.Option.TYPED_ACTIONS
|
||||
|
||||
|
||||
@ -270,8 +296,8 @@ class SConsOptionGroup(optparse.OptionGroup):
|
||||
lined up with the normal "SCons Options".
|
||||
"""
|
||||
|
||||
def format_help(self, formatter):
|
||||
""" Format an option group's help text.
|
||||
def format_help(self, formatter) -> str:
|
||||
"""SCons-specific formatting of an option group's help text.
|
||||
|
||||
The title is dedented so it's flush with the "SCons Options"
|
||||
title we print at the top.
|
||||
@ -285,14 +311,15 @@ class SConsOptionGroup(optparse.OptionGroup):
|
||||
|
||||
|
||||
class SConsBadOptionError(optparse.BadOptionError):
|
||||
"""Exception used to indicate that invalid command line options were specified
|
||||
|
||||
:ivar str opt_str: The offending option specified on command line which is not recognized
|
||||
:ivar OptionParser parser: The active argument parser
|
||||
"""Raised if an invalid option value is encountered on the command line.
|
||||
|
||||
Attributes:
|
||||
opt_str: The unrecognized command-line option.
|
||||
parser: The active argument parser.
|
||||
"""
|
||||
# TODO why is 'parser' needed? Not called in current code base.
|
||||
|
||||
def __init__(self, opt_str, parser=None) -> None:
|
||||
def __init__(self, opt_str: str, parser: SConsOptionParser | None = None) -> None:
|
||||
self.opt_str = opt_str
|
||||
self.parser = parser
|
||||
|
||||
@ -304,8 +331,8 @@ class SConsOptionParser(optparse.OptionParser):
|
||||
preserve_unknown_options = False
|
||||
raise_exception_on_error = False
|
||||
|
||||
def error(self, msg):
|
||||
"""Overridden OptionValueError exception handler."""
|
||||
def error(self, msg: str) -> None:
|
||||
"""SCons-specific handling of option errors."""
|
||||
if self.raise_exception_on_error:
|
||||
raise SConsBadOptionError(msg, self)
|
||||
else:
|
||||
@ -313,15 +340,16 @@ class SConsOptionParser(optparse.OptionParser):
|
||||
sys.stderr.write("SCons Error: %s\n" % msg)
|
||||
sys.exit(2)
|
||||
|
||||
def _process_long_opt(self, rargs, values):
|
||||
""" SCons-specific processing of long options.
|
||||
def _process_long_opt(self, rargs, values) -> None:
|
||||
"""SCons-specific processing of long options.
|
||||
|
||||
This is copied directly from the normal
|
||||
``optparse._process_long_opt()`` method, except that, if configured
|
||||
to do so, we catch the exception thrown when an unknown option
|
||||
is encountered and just stick it back on the "leftover" arguments
|
||||
for later (re-)processing. This is because we may see the option
|
||||
definition later, while processing SConscript files.
|
||||
This is copied directly from the normal Optparse
|
||||
:meth:`~optparse.OptionParser._process_long_opt` method, except
|
||||
that, if configured to do so, we catch the exception thrown
|
||||
when an unknown option is encountered and just stick it back
|
||||
on the "leftover" arguments for later (re-)processing. This is
|
||||
because we may see the option definition later, while processing
|
||||
SConscript files.
|
||||
"""
|
||||
arg = rargs.pop(0)
|
||||
|
||||
@ -341,9 +369,9 @@ class SConsOptionParser(optparse.OptionParser):
|
||||
% (opt, self._match_long_opt(opt))
|
||||
)
|
||||
except optparse.BadOptionError:
|
||||
# SCons addition: if requested, add unknown options to
|
||||
# the "leftover arguments" list for later processing.
|
||||
if self.preserve_unknown_options:
|
||||
# SCons-specific: if requested, add unknown options to
|
||||
# the "leftover arguments" list for later processing.
|
||||
self.largs.append(arg)
|
||||
if had_explicit_value:
|
||||
# The unknown option will be re-processed later,
|
||||
@ -355,6 +383,7 @@ class SConsOptionParser(optparse.OptionParser):
|
||||
option = self._long_opt[opt]
|
||||
if option.takes_value():
|
||||
nargs = option.nargs
|
||||
# SCons addition: recognize '?' for nargs
|
||||
if nargs == '?':
|
||||
if had_explicit_value:
|
||||
value = rargs.pop(0)
|
||||
@ -362,6 +391,7 @@ class SConsOptionParser(optparse.OptionParser):
|
||||
value = option.const
|
||||
elif len(rargs) < nargs:
|
||||
if nargs == 1:
|
||||
# SCons addition: nicer msg if option had choices
|
||||
if not option.choices:
|
||||
self.error(_("%s option requires an argument") % opt)
|
||||
else:
|
||||
@ -386,6 +416,66 @@ class SConsOptionParser(optparse.OptionParser):
|
||||
|
||||
option.process(opt, value, values, self)
|
||||
|
||||
|
||||
def _process_short_opts(self, rargs, values) -> None:
|
||||
"""SCons-specific processing of short options.
|
||||
|
||||
This is copied directly from the normal Optparse
|
||||
:meth:`~optparse.OptionParser._process_short_opts` method, except
|
||||
that, if configured to do so, we catch the exception thrown
|
||||
when an unknown option is encountered and just stick it back
|
||||
on the "leftover" arguments for later (re-)processing. This is
|
||||
because we may see the option definition later, while processing
|
||||
SConscript files.
|
||||
"""
|
||||
arg = rargs.pop(0)
|
||||
stop = False
|
||||
i = 1
|
||||
for ch in arg[1:]:
|
||||
opt = "-" + ch
|
||||
option = self._short_opt.get(opt)
|
||||
i += 1 # we have consumed a character
|
||||
|
||||
try:
|
||||
if not option:
|
||||
raise optparse.BadOptionError(opt)
|
||||
except optparse.BadOptionError:
|
||||
# SCons addition: if requested, add unknown options to
|
||||
# the "leftover arguments" list for later processing.
|
||||
if self.preserve_unknown_options:
|
||||
self.largs.append(arg)
|
||||
return
|
||||
raise
|
||||
|
||||
if option.takes_value():
|
||||
# Any characters left in arg? Pretend they're the
|
||||
# next arg, and stop consuming characters of arg.
|
||||
if i < len(arg):
|
||||
rargs.insert(0, arg[i:])
|
||||
stop = True
|
||||
|
||||
nargs = option.nargs
|
||||
if len(rargs) < nargs:
|
||||
if nargs == 1:
|
||||
self.error(_("%s option requires an argument") % opt)
|
||||
else:
|
||||
self.error(_("%s option requires %d arguments")
|
||||
% (opt, nargs))
|
||||
elif nargs == 1:
|
||||
value = rargs.pop(0)
|
||||
else:
|
||||
value = tuple(rargs[0:nargs])
|
||||
del rargs[0:nargs]
|
||||
|
||||
else: # option doesn't take a value
|
||||
value = None
|
||||
|
||||
option.process(opt, value, values, self)
|
||||
|
||||
if stop:
|
||||
break
|
||||
|
||||
|
||||
def reparse_local_options(self) -> None:
|
||||
"""Re-parse the leftover command-line options.
|
||||
|
||||
@ -399,10 +489,8 @@ class SConsOptionParser(optparse.OptionParser):
|
||||
allow exact matches for long-opts only (no partial argument names!).
|
||||
Otherwise there could be problems in :meth:`add_local_option`
|
||||
below. When called from there, we try to reparse the
|
||||
command-line arguments that
|
||||
|
||||
1. haven't been processed so far (`self.largs`), but
|
||||
2. are possibly not added to the list of options yet.
|
||||
command-line arguments that haven't been processed so far
|
||||
(``self.largs``), but are possibly not added to the options list yet.
|
||||
|
||||
So, when we only have a value for ``--myargument`` so far,
|
||||
a command-line argument of ``--myarg=test`` would set it,
|
||||
@ -450,29 +538,31 @@ class SConsOptionParser(optparse.OptionParser):
|
||||
self.largs = self.largs + largs_restore
|
||||
|
||||
def add_local_option(self, *args, **kw) -> SConsOption:
|
||||
""" Adds a local option to the parser.
|
||||
"""Add a local option to the parser.
|
||||
|
||||
This is initiated by an :func:`~SCons.Script.Main.AddOption` call to
|
||||
add a user-defined command-line option. Add the option to a separate
|
||||
option group for the local options, creating the group if necessary.
|
||||
This is the implementation of :func:`~SCons.Script.Main.AddOption`,
|
||||
to add a project-defined command-line option. Local options
|
||||
are added to a separate option group, which is created if necessary.
|
||||
|
||||
The keyword argument *settable* is recognized specially (and
|
||||
removed from *kw*). If true, the option is marked as modifiable;
|
||||
by default "local" (project-added) options are not eligible for
|
||||
for :func:`~SCons.Script.Main.SetOption` calls.
|
||||
|
||||
.. versionchanged:: 4.8.0
|
||||
Added special handling of *settable*.
|
||||
:func:`~SCons.Script.Main.SetOption` calls.
|
||||
|
||||
.. versionchanged:: NEXT_VERSION
|
||||
If the option's *settable* attribute is true, it is added to
|
||||
the :attr:`SConsValues.settable` list. *settable* handling was
|
||||
added in 4.8.0, but was not made an option attribute at the time.
|
||||
"""
|
||||
group: SConsOptionGroup
|
||||
try:
|
||||
group = self.local_option_group
|
||||
except AttributeError:
|
||||
group = SConsOptionGroup(self, 'Local Options')
|
||||
group = self.add_option_group(group)
|
||||
self.add_option_group(group)
|
||||
self.local_option_group = group
|
||||
|
||||
settable = kw.pop('settable')
|
||||
# this gives us an SConsOption due to the setting of self.option_class
|
||||
result = group.add_option(*args, **kw)
|
||||
if result:
|
||||
# The option was added successfully. We now have to add the
|
||||
@ -483,15 +573,16 @@ class SConsOptionParser(optparse.OptionParser):
|
||||
# any value overridden on the command line is immediately
|
||||
# available if the user turns around and does a GetOption()
|
||||
# right away.
|
||||
# TODO: what if dest is None?
|
||||
setattr(self.values.__defaults__, result.dest, result.default)
|
||||
self.reparse_local_options()
|
||||
if settable:
|
||||
if result.settable:
|
||||
SConsValues.settable.append(result.dest)
|
||||
|
||||
return result
|
||||
|
||||
def format_local_option_help(self, formatter=None, file=None):
|
||||
"""Return the help for the project-level ("local") options.
|
||||
"""Return the help for the project-level ("local") SCons options.
|
||||
|
||||
.. versionadded:: 4.6.0
|
||||
"""
|
||||
@ -514,7 +605,7 @@ class SConsOptionParser(optparse.OptionParser):
|
||||
return local_help
|
||||
|
||||
def print_local_option_help(self, file=None):
|
||||
"""Print help for just project-defined options.
|
||||
"""Print help for just local SCons options.
|
||||
|
||||
Writes to *file* (default stdout).
|
||||
|
||||
@ -527,11 +618,11 @@ class SConsOptionParser(optparse.OptionParser):
|
||||
|
||||
class SConsIndentedHelpFormatter(optparse.IndentedHelpFormatter):
|
||||
def format_usage(self, usage) -> str:
|
||||
""" Formats the usage message. """
|
||||
"""Format the usage message for SCons."""
|
||||
return "usage: %s\n" % usage
|
||||
|
||||
def format_heading(self, heading):
|
||||
""" Translates heading to "SCons Options"
|
||||
"""Translate heading to "SCons Options"
|
||||
|
||||
Heading of "Options" changed to "SCons Options."
|
||||
Unfortunately, we have to do this here, because those titles
|
||||
@ -542,11 +633,10 @@ class SConsIndentedHelpFormatter(optparse.IndentedHelpFormatter):
|
||||
return super().format_heading(heading)
|
||||
|
||||
def format_option(self, option):
|
||||
""" Customized option formatter.
|
||||
"""SCons-specific option formatter.
|
||||
|
||||
A copy of the normal ``optparse.IndentedHelpFormatter.format_option()``
|
||||
method. This has been snarfed so we can modify text wrapping to
|
||||
our liking:
|
||||
A copy of the :meth:`optparse.IndentedHelpFormatter.format_option`
|
||||
method. Overridden so we can modify text wrapping to our liking:
|
||||
|
||||
* add our own regular expression that doesn't break on hyphens
|
||||
(so things like ``--no-print-directory`` don't get broken).
|
||||
@ -556,21 +646,25 @@ class SConsIndentedHelpFormatter(optparse.IndentedHelpFormatter):
|
||||
|
||||
The help for each option consists of two parts:
|
||||
|
||||
* the opt strings and metavars e.g. ("-x", or
|
||||
"-fFILENAME, --file=FILENAME")
|
||||
* the opt strings and metavars e.g. (``-x``, or
|
||||
``-fFILENAME, --file=FILENAME``)
|
||||
* the user-supplied help string e.g.
|
||||
("turn on expert mode", "read data from FILENAME")
|
||||
(``turn on expert mode``, ``read data from FILENAME``)
|
||||
|
||||
If possible, we write both of these on the same line::
|
||||
|
||||
-x turn on expert mode
|
||||
|
||||
But if the opt string list is too long, we put the help
|
||||
If the opt string list is too long, we put the help
|
||||
string on a second line, indented to the same column it would
|
||||
start in if it fit on the first line::
|
||||
|
||||
-fFILENAME, --file=FILENAME
|
||||
read data from FILENAME
|
||||
|
||||
Help strings are wrapped for terminal width and do not preserve
|
||||
any hand-made formatting that may have been used in the ``AddOption``
|
||||
call, so don't attempt prettying up a list of choices (for example).
|
||||
"""
|
||||
result = []
|
||||
opts = self.option_strings[option]
|
||||
@ -23,6 +23,8 @@
|
||||
|
||||
"""This module defines the Python API provided to SConscript files."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import SCons
|
||||
import SCons.Action
|
||||
import SCons.Builder
|
||||
@ -45,7 +47,6 @@ import re
|
||||
import sys
|
||||
import traceback
|
||||
import time
|
||||
from typing import Tuple
|
||||
|
||||
class SConscriptReturn(Exception):
|
||||
pass
|
||||
@ -386,7 +387,7 @@ class SConsEnvironment(SCons.Environment.Base):
|
||||
# Private methods of an SConsEnvironment.
|
||||
#
|
||||
@staticmethod
|
||||
def _get_major_minor_revision(version_string: str) -> Tuple[int, int, int]:
|
||||
def _get_major_minor_revision(version_string: str) -> tuple[int, int, int]:
|
||||
"""Split a version string into major, minor and (optionally)
|
||||
revision parts.
|
||||
|
||||
@ -485,7 +486,7 @@ class SConsEnvironment(SCons.Environment.Base):
|
||||
SCons.Script._Set_Default_Targets(self, targets)
|
||||
|
||||
@staticmethod
|
||||
def GetSConsVersion() -> Tuple[int, int, int]:
|
||||
def GetSConsVersion() -> tuple[int, int, int]:
|
||||
"""Return the current SCons version.
|
||||
|
||||
.. versionadded:: 4.8.0
|
||||
@ -535,25 +536,27 @@ class SConsEnvironment(SCons.Environment.Base):
|
||||
name = self.subst(name)
|
||||
return SCons.Script.Main.GetOption(name)
|
||||
|
||||
def Help(self, text, append: bool = False, keep_local: bool = False) -> None:
|
||||
def Help(self, text, append: bool = False, local_only: bool = False) -> None:
|
||||
"""Update the help text.
|
||||
|
||||
The previous help text has *text* appended to it, except on the
|
||||
first call. On first call, the values of *append* and *keep_local*
|
||||
first call. On first call, the values of *append* and *local_only*
|
||||
are considered to determine what is appended to.
|
||||
|
||||
Arguments:
|
||||
text: string to add to the help text.
|
||||
append: on first call, if true, keep the existing help text
|
||||
(default False).
|
||||
keep_local: on first call, if true and *append* is also true,
|
||||
local_only: on first call, if true and *append* is also true,
|
||||
keep only the help text from AddOption calls.
|
||||
|
||||
.. versionchanged:: 4.6.0
|
||||
The *keep_local* parameter was added.
|
||||
.. versionchanged:: 4.9.0
|
||||
The *keep_local* parameter was renamed *local_only* to match manpage
|
||||
"""
|
||||
text = self.subst(text, raw=1)
|
||||
SCons.Script.HelpFunction(text, append=append, keep_local=keep_local)
|
||||
SCons.Script.HelpFunction(text, append=append, local_only=local_only)
|
||||
|
||||
def Import(self, *vars):
|
||||
try:
|
||||
@ -35,6 +35,7 @@ import time
|
||||
start_time = time.time()
|
||||
|
||||
import collections
|
||||
import itertools
|
||||
import os
|
||||
from io import StringIO
|
||||
|
||||
@ -53,9 +54,17 @@ import sys
|
||||
# to not add the shims. So we use a special-case, up-front check for
|
||||
# the "--debug=memoizer" flag and enable Memoizer before we import any
|
||||
# of the other modules that use it.
|
||||
# Update: this breaks if the option isn't exactly "--debug=memoizer",
|
||||
# like if there is more than one debug option as a csv. Do a bit more work.
|
||||
|
||||
_args = sys.argv + os.environ.get('SCONSFLAGS', '').split()
|
||||
if "--debug=memoizer" in _args:
|
||||
_args = sys.argv + os.environ.get("SCONSFLAGS", "").split()
|
||||
_args = (
|
||||
arg[len("--debug=") :].split(",")
|
||||
for arg in _args
|
||||
if arg.startswith("--debug=")
|
||||
)
|
||||
_args = list(itertools.chain.from_iterable(_args))
|
||||
if "memoizer" in _args:
|
||||
import SCons.Memoize
|
||||
import SCons.Warnings
|
||||
try:
|
||||
@ -251,19 +260,21 @@ def _Set_Default_Targets(env, tlist) -> None:
|
||||
help_text = None
|
||||
|
||||
|
||||
def HelpFunction(text, append: bool = False, keep_local: bool = False) -> None:
|
||||
def HelpFunction(text, append: bool = False, local_only: bool = False) -> None:
|
||||
"""The implementaion of the the ``Help`` method.
|
||||
|
||||
See :meth:`~SCons.Script.SConscript.Help`.
|
||||
|
||||
.. versionchanged:: 4.6.0
|
||||
The *keep_local* parameter was added.
|
||||
.. versionchanged:: 4.9.0
|
||||
The *keep_local* parameter was renamed *local_only* to match manpage
|
||||
"""
|
||||
global help_text
|
||||
if help_text is None:
|
||||
if append:
|
||||
with StringIO() as s:
|
||||
PrintHelp(s, local_only=keep_local)
|
||||
PrintHelp(s, local_only=local_only)
|
||||
help_text = s.getvalue()
|
||||
else:
|
||||
help_text = ""
|
||||
@ -23,10 +23,11 @@
|
||||
|
||||
"""SCons string substitution."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import collections
|
||||
import re
|
||||
from inspect import signature, Parameter
|
||||
from typing import Optional
|
||||
|
||||
import SCons.Errors
|
||||
from SCons.Util import is_String, is_Sequence
|
||||
@ -807,7 +808,7 @@ _separate_args = re.compile(r'(%s|\s+|[^\s$]+|\$)' % _dollar_exps_str)
|
||||
_space_sep = re.compile(r'[\t ]+(?![^{]*})')
|
||||
|
||||
|
||||
def scons_subst(strSubst, env, mode=SUBST_RAW, target=None, source=None, gvars={}, lvars={}, conv=None, overrides: Optional[dict] = None):
|
||||
def scons_subst(strSubst, env, mode=SUBST_RAW, target=None, source=None, gvars={}, lvars={}, conv=None, overrides: dict | None = None):
|
||||
"""Expand a string or list containing construction variable
|
||||
substitutions.
|
||||
|
||||
@ -889,7 +890,7 @@ def scons_subst(strSubst, env, mode=SUBST_RAW, target=None, source=None, gvars={
|
||||
|
||||
return result
|
||||
|
||||
def scons_subst_list(strSubst, env, mode=SUBST_RAW, target=None, source=None, gvars={}, lvars={}, conv=None, overrides: Optional[dict] = None):
|
||||
def scons_subst_list(strSubst, env, mode=SUBST_RAW, target=None, source=None, gvars={}, lvars={}, conv=None, overrides: dict | None = None):
|
||||
"""Substitute construction variables in a string (or list or other
|
||||
object) and separate the arguments into a command list.
|
||||
|
||||
@ -23,9 +23,10 @@
|
||||
|
||||
"""Routines for setting up Fortran, common to all dialects."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
import os.path
|
||||
from typing import Tuple, List
|
||||
|
||||
import SCons.Scanner.Fortran
|
||||
import SCons.Tool
|
||||
@ -96,7 +97,7 @@ def ShFortranEmitter(target, source, env) -> Tuple:
|
||||
return SharedObjectEmitter(target, source, env)
|
||||
|
||||
|
||||
def ComputeFortranSuffixes(suffixes: List[str], ppsuffixes: List[str]) -> None:
|
||||
def ComputeFortranSuffixes(suffixes: list[str], ppsuffixes: list[str]) -> None:
|
||||
"""Update the suffix lists to reflect the platform requirements.
|
||||
|
||||
If upper-cased suffixes can be distinguished from lower, those are
|
||||
@ -119,7 +120,7 @@ def ComputeFortranSuffixes(suffixes: List[str], ppsuffixes: List[str]) -> None:
|
||||
|
||||
def CreateDialectActions(
|
||||
dialect: str,
|
||||
) -> Tuple[CommandAction, CommandAction, CommandAction, CommandAction]:
|
||||
) -> tuple[CommandAction, CommandAction, CommandAction, CommandAction]:
|
||||
"""Create dialect specific actions."""
|
||||
CompAction = Action(f'${dialect}COM ', cmdstr=f'${dialect}COMSTR')
|
||||
CompPPAction = Action(f'${dialect}PPCOM ', cmdstr=f'${dialect}PPCOMSTR')
|
||||
@ -131,8 +132,8 @@ def CreateDialectActions(
|
||||
def DialectAddToEnv(
|
||||
env,
|
||||
dialect: str,
|
||||
suffixes: List[str],
|
||||
ppsuffixes: List[str],
|
||||
suffixes: list[str],
|
||||
ppsuffixes: list[str],
|
||||
support_mods: bool = False,
|
||||
) -> None:
|
||||
"""Add dialect specific construction variables.
|
||||
@ -23,11 +23,12 @@
|
||||
|
||||
"""Common routines for processing Java. """
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import re
|
||||
import glob
|
||||
from pathlib import Path
|
||||
from typing import List
|
||||
|
||||
import SCons.Util
|
||||
|
||||
@ -491,7 +492,7 @@ else:
|
||||
return os.path.split(fn)
|
||||
|
||||
|
||||
def get_java_install_dirs(platform, version=None) -> List[str]:
|
||||
def get_java_install_dirs(platform, version=None) -> list[str]:
|
||||
""" Find possible java jdk installation directories.
|
||||
|
||||
Returns a list for use as `default_paths` when looking up actual
|
||||
@ -540,7 +541,7 @@ def get_java_install_dirs(platform, version=None) -> List[str]:
|
||||
return []
|
||||
|
||||
|
||||
def get_java_include_paths(env, javac, version) -> List[str]:
|
||||
def get_java_include_paths(env, javac, version) -> list[str]:
|
||||
"""Find java include paths for JNI building.
|
||||
|
||||
Cannot be called in isolation - `javac` refers to an already detected
|
||||
@ -34,15 +34,25 @@ from contextlib import suppress
|
||||
from subprocess import DEVNULL, PIPE
|
||||
from pathlib import Path
|
||||
|
||||
import SCons.Errors
|
||||
import SCons.Util
|
||||
import SCons.Warnings
|
||||
|
||||
class MSVCCacheInvalidWarning(SCons.Warnings.WarningOnByDefault):
|
||||
pass
|
||||
|
||||
def _check_logfile(logfile):
|
||||
if logfile and '"' in logfile:
|
||||
err_msg = (
|
||||
"SCONS_MSCOMMON_DEBUG value contains double quote character(s)\n"
|
||||
f" SCONS_MSCOMMON_DEBUG={logfile}"
|
||||
)
|
||||
raise SCons.Errors.UserError(err_msg)
|
||||
return logfile
|
||||
|
||||
# SCONS_MSCOMMON_DEBUG is internal-use so undocumented:
|
||||
# set to '-' to print to console, else set to filename to log to
|
||||
LOGFILE = os.environ.get('SCONS_MSCOMMON_DEBUG')
|
||||
LOGFILE = _check_logfile(os.environ.get('SCONS_MSCOMMON_DEBUG'))
|
||||
if LOGFILE:
|
||||
import logging
|
||||
|
||||
@ -129,7 +139,15 @@ if LOGFILE:
|
||||
log_handler = logging.StreamHandler(sys.stdout)
|
||||
else:
|
||||
log_prefix = ''
|
||||
log_handler = logging.FileHandler(filename=LOGFILE)
|
||||
try:
|
||||
log_handler = logging.FileHandler(filename=LOGFILE)
|
||||
except (OSError, FileNotFoundError) as e:
|
||||
err_msg = (
|
||||
"Could not create logfile, check SCONS_MSCOMMON_DEBUG\n"
|
||||
f" SCONS_MSCOMMON_DEBUG={LOGFILE}\n"
|
||||
f" {e.__class__.__name__}: {str(e)}"
|
||||
)
|
||||
raise SCons.Errors.UserError(err_msg)
|
||||
log_formatter = _CustomFormatter(log_prefix)
|
||||
log_handler.setFormatter(log_formatter)
|
||||
logger = logging.getLogger(name=__name__)
|
||||
@ -33,10 +33,11 @@ one needs to use or tie in to this subsystem in order to roll their own
|
||||
tool specifications.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
import os
|
||||
import importlib.util
|
||||
from typing import Optional
|
||||
|
||||
import SCons.Builder
|
||||
import SCons.Errors
|
||||
@ -101,6 +102,7 @@ TOOL_ALIASES = {
|
||||
'gettext': 'gettext_tool',
|
||||
'clang++': 'clangxx',
|
||||
'as': 'asm',
|
||||
'ninja' : 'ninja_tool'
|
||||
}
|
||||
|
||||
|
||||
@ -691,8 +693,8 @@ def tool_list(platform, env):
|
||||
if str(platform) == 'win32':
|
||||
"prefer Microsoft tools on Windows"
|
||||
linkers = ['mslink', 'gnulink', 'ilink', 'linkloc', 'ilink32']
|
||||
c_compilers = ['msvc', 'mingw', 'gcc', 'intelc', 'icl', 'icc', 'cc', 'bcc32']
|
||||
cxx_compilers = ['msvc', 'intelc', 'icc', 'g++', 'cxx', 'bcc32']
|
||||
c_compilers = ['msvc', 'mingw', 'gcc', 'clang', 'intelc', 'icl', 'icc', 'cc', 'bcc32']
|
||||
cxx_compilers = ['msvc', 'intelc', 'icc', 'g++', 'clang++', 'cxx', 'bcc32']
|
||||
assemblers = ['masm', 'nasm', 'gas', '386asm']
|
||||
fortran_compilers = ['gfortran', 'g77', 'ifl', 'cvf', 'f95', 'f90', 'fortran']
|
||||
ars = ['mslib', 'ar', 'tlib']
|
||||
@ -757,8 +759,8 @@ def tool_list(platform, env):
|
||||
else:
|
||||
"prefer GNU tools on all other platforms"
|
||||
linkers = ['gnulink', 'ilink']
|
||||
c_compilers = ['gcc', 'intelc', 'icc', 'cc']
|
||||
cxx_compilers = ['g++', 'intelc', 'icc', 'cxx']
|
||||
c_compilers = ['gcc', 'clang', 'intelc', 'icc', 'cc']
|
||||
cxx_compilers = ['g++', 'clang++', 'intelc', 'icc', 'cxx']
|
||||
assemblers = ['gas', 'nasm', 'masm']
|
||||
fortran_compilers = ['gfortran', 'g77', 'ifort', 'ifl', 'f95', 'f90', 'f77']
|
||||
ars = ['ar', ]
|
||||
@ -824,7 +826,7 @@ def tool_list(platform, env):
|
||||
return [x for x in tools if x]
|
||||
|
||||
|
||||
def find_program_path(env, key_program, default_paths=None, add_path: bool=False) -> Optional[str]:
|
||||
def find_program_path(env, key_program, default_paths=None, add_path: bool=False) -> str | None:
|
||||
"""
|
||||
Find the location of a tool using various means.
|
||||
|
||||
@ -43,6 +43,8 @@ from .cxx import CXXSuffixes
|
||||
from .cc import CSuffixes
|
||||
from .asm import ASSuffixes, ASPPSuffixes
|
||||
|
||||
DEFAULT_DB_NAME = 'compile_commands.json'
|
||||
|
||||
# TODO: Is there a better way to do this than this global? Right now this exists so that the
|
||||
# emitter we add can record all of the things it emits, so that the scanner for the top level
|
||||
# compilation database can access the complete list, and also so that the writer has easy
|
||||
@ -189,9 +191,8 @@ def compilation_db_emitter(target, source, env):
|
||||
if not target and len(source) == 1:
|
||||
target = source
|
||||
|
||||
# Default target name is compilation_db.json
|
||||
if not target:
|
||||
target = ['compile_commands.json', ]
|
||||
target = [DEFAULT_DB_NAME]
|
||||
|
||||
# No source should have been passed. Drop it.
|
||||
if source:
|
||||
@ -224,13 +225,17 @@ def generate(env, **kwargs) -> None:
|
||||
),
|
||||
itertools.product(
|
||||
ASSuffixes,
|
||||
[(static_obj, SCons.Defaults.StaticObjectEmitter, "$ASCOM")],
|
||||
[(shared_obj, SCons.Defaults.SharedObjectEmitter, "$ASCOM")],
|
||||
[
|
||||
(static_obj, SCons.Defaults.StaticObjectEmitter, "$ASCOM"),
|
||||
(shared_obj, SCons.Defaults.SharedObjectEmitter, "$ASCOM")
|
||||
],
|
||||
),
|
||||
itertools.product(
|
||||
ASPPSuffixes,
|
||||
[(static_obj, SCons.Defaults.StaticObjectEmitter, "$ASPPCOM")],
|
||||
[(shared_obj, SCons.Defaults.SharedObjectEmitter, "$ASPPCOM")],
|
||||
[
|
||||
(static_obj, SCons.Defaults.StaticObjectEmitter, "$ASPPCOM"),
|
||||
(shared_obj, SCons.Defaults.SharedObjectEmitter, "$ASPPCOM")
|
||||
],
|
||||
),
|
||||
)
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user