Skip to content

Commit 38125c0

Browse files
authored
Merge pull request SCons#4463 from Repiteo/complex-type-hint-framework
Implement framework for hinting complex types
2 parents 759ed8c + a3c76a2 commit 38125c0

File tree

15 files changed

+122
-62
lines changed

15 files changed

+122
-62
lines changed

CHANGES.txt

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,10 @@ RELEASE VERSION/DATE TO BE FILLED IN LATER
4343
method so that the generated function argument list matches the function's
4444
prototype when including a header file.
4545

46+
From Thaddeus Crews:
47+
- Implemented SCons.Util.sctyping as a safe means of hinting complex types. Currently
48+
only implemented for `Executor` as a proof-of-concept.
49+
4650
From William Deegan:
4751
- Fix sphinx config to handle SCons versions with post such as: 4.6.0.post1
4852

@@ -1437,12 +1441,12 @@ RELEASE 3.1.0 - Mon, 20 Jul 2019 16:59:23 -0700
14371441
scons: rebuilding `file3' because:
14381442
the dependency order changed:
14391443
->Sources
1440-
Old:xxx New:zzz
1441-
Old:yyy New:yyy
1442-
Old:zzz New:xxx
1444+
Old:xxx New:zzz
1445+
Old:yyy New:yyy
1446+
Old:zzz New:xxx
14431447
->Depends
14441448
->Implicit
1445-
Old:/usr/bin/python New:/usr/bin/python
1449+
Old:/usr/bin/python New:/usr/bin/python
14461450
- Fix Issue #3350 - SCons Exception EnvironmentError is conflicting with Python's EnvironmentError.
14471451
- Fix spurious rebuilds on second build for cases where builder has > 1 target and the source file
14481452
is generated. This was causing the > 1th target to not have it's implicit list cleared when the source

RELEASE.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,8 @@ DEVELOPMENT
9494
-----------
9595

9696
- Fix sphinx config to handle SCons versions with post such as: 4.6.0.post1
97+
- Created SCons.Util.sctyping to contain complex type information, allowing
98+
for repo-wide type hinting without causing cyclical dependencies.
9799

98100
Thanks to the following contributors listed below for their contributions to this release.
99101
==========================================================================================

SCons/Action.py

Lines changed: 38 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,7 @@
120120
from SCons.Debug import logInstanceCreation
121121
from SCons.Subst import SUBST_CMD, SUBST_RAW, SUBST_SIG
122122
from SCons.Util import is_String, is_List
123+
from SCons.Util.sctyping import ExecutorType
123124

124125
class _null:
125126
pass
@@ -528,7 +529,7 @@ def __call__(
528529
show=_null,
529530
execute=_null,
530531
chdir=_null,
531-
executor=None,
532+
executor: Optional[ExecutorType] = None,
532533
):
533534
raise NotImplementedError
534535

@@ -540,15 +541,15 @@ def no_batch_key(self, env, target, source):
540541

541542
batch_key = no_batch_key
542543

543-
def genstring(self, target, source, env, executor=None) -> str:
544+
def genstring(self, target, source, env, executor: Optional[ExecutorType] = None) -> str:
544545
return str(self)
545546

546547
@abstractmethod
547-
def get_presig(self, target, source, env, executor=None):
548+
def get_presig(self, target, source, env, executor: Optional[ExecutorType] = None):
548549
raise NotImplementedError
549550

550551
@abstractmethod
551-
def get_implicit_deps(self, target, source, env, executor=None):
552+
def get_implicit_deps(self, target, source, env, executor: Optional[ExecutorType] = None):
552553
raise NotImplementedError
553554

554555
def get_contents(self, target, source, env):
@@ -600,10 +601,10 @@ def presub_lines(self, env):
600601
self.presub_env = None # don't need this any more
601602
return lines
602603

603-
def get_varlist(self, target, source, env, executor=None):
604+
def get_varlist(self, target, source, env, executor: Optional[ExecutorType] = None):
604605
return self.varlist
605606

606-
def get_targets(self, env, executor):
607+
def get_targets(self, env, executor: Optional[ExecutorType]):
607608
"""
608609
Returns the type of targets ($TARGETS, $CHANGED_TARGETS) used
609610
by this action.
@@ -657,7 +658,7 @@ def __call__(self, target, source, env,
657658
show=_null,
658659
execute=_null,
659660
chdir=_null,
660-
executor=None):
661+
executor: Optional[ExecutorType] = None):
661662
if not is_List(target):
662663
target = [target]
663664
if not is_List(source):
@@ -741,10 +742,10 @@ def __call__(self, target, source, env,
741742
# an ABC like parent ActionBase, but things reach in and use it. It's
742743
# not just unittests or we could fix it up with a concrete subclass there.
743744

744-
def get_presig(self, target, source, env, executor=None):
745+
def get_presig(self, target, source, env, executor: Optional[ExecutorType] = None):
745746
raise NotImplementedError
746747

747-
def get_implicit_deps(self, target, source, env, executor=None):
748+
def get_implicit_deps(self, target, source, env, executor: Optional[ExecutorType] = None):
748749
raise NotImplementedError
749750

750751

@@ -1008,6 +1009,7 @@ def __str__(self) -> str:
10081009
return ' '.join(map(str, self.cmd_list))
10091010
return str(self.cmd_list)
10101011

1012+
10111013
def process(self, target, source, env, executor=None, overrides: Optional[dict] = None) -> Tuple[List, bool, bool]:
10121014
if executor:
10131015
result = env.subst_list(self.cmd_list, SUBST_CMD, executor=executor, overrides=overrides)
@@ -1029,7 +1031,7 @@ def process(self, target, source, env, executor=None, overrides: Optional[dict]
10291031
pass
10301032
return result, ignore, silent
10311033

1032-
def strfunction(self, target, source, env, executor=None, overrides: Optional[dict] = None) -> str:
1034+
def strfunction(self, target, source, env, executor: Optional[ExecutorType] = None, overrides: Optional[dict] = None) -> str:
10331035
if self.cmdstr is None:
10341036
return None
10351037
if self.cmdstr is not _null:
@@ -1044,7 +1046,7 @@ def strfunction(self, target, source, env, executor=None, overrides: Optional[di
10441046
return ''
10451047
return _string_from_cmd_list(cmd_list[0])
10461048

1047-
def execute(self, target, source, env, executor=None):
1049+
def execute(self, target, source, env, executor: Optional[ExecutorType] = None):
10481050
"""Execute a command action.
10491051
10501052
This will handle lists of commands as well as individual commands,
@@ -1106,7 +1108,7 @@ def execute(self, target, source, env, executor=None):
11061108
command=cmd_line)
11071109
return 0
11081110

1109-
def get_presig(self, target, source, env, executor=None):
1111+
def get_presig(self, target, source, env, executor: Optional[ExecutorType] = None):
11101112
"""Return the signature contents of this action's command line.
11111113
11121114
This strips $(-$) and everything in between the string,
@@ -1121,7 +1123,7 @@ def get_presig(self, target, source, env, executor=None):
11211123
return env.subst_target_source(cmd, SUBST_SIG, executor=executor)
11221124
return env.subst_target_source(cmd, SUBST_SIG, target, source)
11231125

1124-
def get_implicit_deps(self, target, source, env, executor=None):
1126+
def get_implicit_deps(self, target, source, env, executor: Optional[ExecutorType] = None):
11251127
"""Return the implicit dependencies of this action's command line."""
11261128
icd = env.get('IMPLICIT_COMMAND_DEPENDENCIES', True)
11271129
if is_String(icd) and icd[:1] == '$':
@@ -1143,7 +1145,7 @@ def get_implicit_deps(self, target, source, env, executor=None):
11431145
# lightweight dependency scanning.
11441146
return self._get_implicit_deps_lightweight(target, source, env, executor)
11451147

1146-
def _get_implicit_deps_lightweight(self, target, source, env, executor):
1148+
def _get_implicit_deps_lightweight(self, target, source, env, executor: Optional[ExecutorType]):
11471149
"""
11481150
Lightweight dependency scanning involves only scanning the first entry
11491151
in an action string, even if it contains &&.
@@ -1164,7 +1166,7 @@ def _get_implicit_deps_lightweight(self, target, source, env, executor):
11641166
res.append(env.fs.File(d))
11651167
return res
11661168

1167-
def _get_implicit_deps_heavyweight(self, target, source, env, executor,
1169+
def _get_implicit_deps_heavyweight(self, target, source, env, executor: Optional[ExecutorType],
11681170
icd_int):
11691171
"""
11701172
Heavyweight dependency scanning involves scanning more than just the
@@ -1232,7 +1234,7 @@ def __init__(self, generator, kw) -> None:
12321234
self.varlist = kw.get('varlist', ())
12331235
self.targets = kw.get('targets', '$TARGETS')
12341236

1235-
def _generate(self, target, source, env, for_signature, executor=None):
1237+
def _generate(self, target, source, env, for_signature, executor: Optional[ExecutorType] = None):
12361238
# ensure that target is a list, to make it easier to write
12371239
# generator functions:
12381240
if not is_List(target):
@@ -1263,11 +1265,11 @@ def __str__(self) -> str:
12631265
def batch_key(self, env, target, source):
12641266
return self._generate(target, source, env, 1).batch_key(env, target, source)
12651267

1266-
def genstring(self, target, source, env, executor=None) -> str:
1268+
def genstring(self, target, source, env, executor: Optional[ExecutorType] = None) -> str:
12671269
return self._generate(target, source, env, 1, executor).genstring(target, source, env)
12681270

12691271
def __call__(self, target, source, env, exitstatfunc=_null, presub=_null,
1270-
show=_null, execute=_null, chdir=_null, executor=None):
1272+
show=_null, execute=_null, chdir=_null, executor: Optional[ExecutorType] = None):
12711273
act = self._generate(target, source, env, 0, executor)
12721274
if act is None:
12731275
raise SCons.Errors.UserError(
@@ -1279,21 +1281,21 @@ def __call__(self, target, source, env, exitstatfunc=_null, presub=_null,
12791281
target, source, env, exitstatfunc, presub, show, execute, chdir, executor
12801282
)
12811283

1282-
def get_presig(self, target, source, env, executor=None):
1284+
def get_presig(self, target, source, env, executor: Optional[ExecutorType] = None):
12831285
"""Return the signature contents of this action's command line.
12841286
12851287
This strips $(-$) and everything in between the string,
12861288
since those parts don't affect signatures.
12871289
"""
12881290
return self._generate(target, source, env, 1, executor).get_presig(target, source, env)
12891291

1290-
def get_implicit_deps(self, target, source, env, executor=None):
1292+
def get_implicit_deps(self, target, source, env, executor: Optional[ExecutorType] = None):
12911293
return self._generate(target, source, env, 1, executor).get_implicit_deps(target, source, env)
12921294

1293-
def get_varlist(self, target, source, env, executor=None):
1295+
def get_varlist(self, target, source, env, executor: Optional[ExecutorType] = None):
12941296
return self._generate(target, source, env, 1, executor).get_varlist(target, source, env, executor)
12951297

1296-
def get_targets(self, env, executor):
1298+
def get_targets(self, env, executor: Optional[ExecutorType]):
12971299
return self._generate(None, None, env, 1, executor).get_targets(env, executor)
12981300

12991301

@@ -1339,22 +1341,22 @@ def _generate_cache(self, env):
13391341
raise SCons.Errors.UserError("$%s value %s cannot be used to create an Action." % (self.var, repr(c)))
13401342
return gen_cmd
13411343

1342-
def _generate(self, target, source, env, for_signature, executor=None):
1344+
def _generate(self, target, source, env, for_signature, executor: Optional[ExecutorType] = None):
13431345
return self._generate_cache(env)
13441346

13451347
def __call__(self, target, source, env, *args, **kw):
13461348
c = self.get_parent_class(env)
13471349
return c.__call__(self, target, source, env, *args, **kw)
13481350

1349-
def get_presig(self, target, source, env, executor=None):
1351+
def get_presig(self, target, source, env, executor: Optional[ExecutorType] = None):
13501352
c = self.get_parent_class(env)
13511353
return c.get_presig(self, target, source, env)
13521354

1353-
def get_implicit_deps(self, target, source, env, executor=None):
1355+
def get_implicit_deps(self, target, source, env, executor: Optional[ExecutorType] = None):
13541356
c = self.get_parent_class(env)
13551357
return c.get_implicit_deps(self, target, source, env)
13561358

1357-
def get_varlist(self, target, source, env, executor=None):
1359+
def get_varlist(self, target, source, env, executor: Optional[ExecutorType] = None):
13581360
c = self.get_parent_class(env)
13591361
return c.get_varlist(self, target, source, env, executor)
13601362

@@ -1387,7 +1389,7 @@ def function_name(self):
13871389
except AttributeError:
13881390
return "unknown_python_function"
13891391

1390-
def strfunction(self, target, source, env, executor=None):
1392+
def strfunction(self, target, source, env, executor: Optional[ExecutorType] = None):
13911393
if self.cmdstr is None:
13921394
return None
13931395
if self.cmdstr is not _null:
@@ -1428,7 +1430,7 @@ def __str__(self) -> str:
14281430
return str(self.execfunction)
14291431
return "%s(target, source, env)" % name
14301432

1431-
def execute(self, target, source, env, executor=None):
1433+
def execute(self, target, source, env, executor: Optional[ExecutorType] = None):
14321434
exc_info = (None,None,None)
14331435
try:
14341436
if executor:
@@ -1469,14 +1471,14 @@ def execute(self, target, source, env, executor=None):
14691471
# more information about this issue.
14701472
del exc_info
14711473

1472-
def get_presig(self, target, source, env, executor=None):
1474+
def get_presig(self, target, source, env, executor: Optional[ExecutorType] = None):
14731475
"""Return the signature contents of this callable action."""
14741476
try:
14751477
return self.gc(target, source, env)
14761478
except AttributeError:
14771479
return self.funccontents
14781480

1479-
def get_implicit_deps(self, target, source, env, executor=None):
1481+
def get_implicit_deps(self, target, source, env, executor: Optional[ExecutorType] = None):
14801482
return []
14811483

14821484
class ListAction(ActionBase):
@@ -1493,7 +1495,7 @@ def list_of_actions(x):
14931495
self.varlist = ()
14941496
self.targets = '$TARGETS'
14951497

1496-
def genstring(self, target, source, env, executor=None) -> str:
1498+
def genstring(self, target, source, env, executor: Optional[ExecutorType] = None) -> str:
14971499
return '\n'.join([a.genstring(target, source, env) for a in self.list])
14981500

14991501
def __str__(self) -> str:
@@ -1503,15 +1505,15 @@ def presub_lines(self, env):
15031505
return SCons.Util.flatten_sequence(
15041506
[a.presub_lines(env) for a in self.list])
15051507

1506-
def get_presig(self, target, source, env, executor=None):
1508+
def get_presig(self, target, source, env, executor: Optional[ExecutorType] = None):
15071509
"""Return the signature contents of this action list.
15081510
15091511
Simple concatenation of the signatures of the elements.
15101512
"""
15111513
return b"".join([bytes(x.get_contents(target, source, env)) for x in self.list])
15121514

15131515
def __call__(self, target, source, env, exitstatfunc=_null, presub=_null,
1514-
show=_null, execute=_null, chdir=_null, executor=None):
1516+
show=_null, execute=_null, chdir=_null, executor: Optional[ExecutorType] = None):
15151517
if executor:
15161518
target = executor.get_all_targets()
15171519
source = executor.get_all_sources()
@@ -1522,13 +1524,13 @@ def __call__(self, target, source, env, exitstatfunc=_null, presub=_null,
15221524
return stat
15231525
return 0
15241526

1525-
def get_implicit_deps(self, target, source, env, executor=None):
1527+
def get_implicit_deps(self, target, source, env, executor: Optional[ExecutorType] = None):
15261528
result = []
15271529
for act in self.list:
15281530
result.extend(act.get_implicit_deps(target, source, env))
15291531
return result
15301532

1531-
def get_varlist(self, target, source, env, executor=None):
1533+
def get_varlist(self, target, source, env, executor: Optional[ExecutorType] = None):
15321534
result = OrderedDict()
15331535
for act in self.list:
15341536
for var in act.get_varlist(target, source, env, executor):
@@ -1594,7 +1596,7 @@ def subst_kw(self, target, source, env):
15941596
kw[key] = self.subst(self.kw[key], target, source, env)
15951597
return kw
15961598

1597-
def __call__(self, target, source, env, executor=None):
1599+
def __call__(self, target, source, env, executor: Optional[ExecutorType] = None):
15981600
args = self.subst_args(target, source, env)
15991601
kw = self.subst_kw(target, source, env)
16001602
return self.parent.actfunc(*args, **kw)

SCons/ActionTests.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,11 +43,13 @@ def __call__(self) -> None:
4343
import unittest
4444
from unittest import mock
4545
from subprocess import PIPE
46+
from typing import Optional
4647

4748
import SCons.Action
4849
import SCons.Environment
4950
import SCons.Errors
5051
from SCons.Action import scons_subproc_run
52+
from SCons.Util.sctyping import ExecutorType
5153

5254
import TestCmd
5355

@@ -1699,11 +1701,11 @@ def __call__(self, target, source, env) -> int:
16991701
c = test.read(outfile, 'r')
17001702
assert c == "class1b\n", c
17011703

1702-
def build_it(target, source, env, executor=None, self=self) -> int:
1704+
def build_it(target, source, env, executor: Optional[ExecutorType] = None, self=self) -> int:
17031705
self.build_it = 1
17041706
return 0
17051707

1706-
def string_it(target, source, env, executor=None, self=self):
1708+
def string_it(target, source, env, executor: Optional[ExecutorType] = None, self=self):
17071709
self.string_it = 1
17081710
return None
17091711

SCons/Builder.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,7 @@
102102
import os
103103
from collections import UserDict, UserList
104104
from contextlib import suppress
105+
from typing import Optional
105106

106107
import SCons.Action
107108
import SCons.Debug
@@ -111,6 +112,7 @@
111112
import SCons.Warnings
112113
from SCons.Debug import logInstanceCreation
113114
from SCons.Errors import InternalError, UserError
115+
from SCons.Util.sctyping import ExecutorType
114116

115117
class _Null:
116118
pass
@@ -589,7 +591,7 @@ def _execute(self, env, target, source, overwarn={}, executor_kw={}):
589591
# build this particular list of targets from this particular list of
590592
# sources.
591593

592-
executor = None
594+
executor: Optional[ExecutorType] = None
593595
key = None
594596

595597
if self.multi:

0 commit comments

Comments
 (0)