Skip to content

Commit 531cbfd

Browse files
committed
Implement framework for hinting complex types
• Showcase potential with `ExecutorType`
1 parent 7255c54 commit 531cbfd

File tree

15 files changed

+123
-63
lines changed

15 files changed

+123
-63
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

@@ -1402,12 +1406,12 @@ RELEASE 3.1.0 - Mon, 20 Jul 2019 16:59:23 -0700
14021406
scons: rebuilding `file3' because:
14031407
the dependency order changed:
14041408
->Sources
1405-
Old:xxx New:zzz
1406-
Old:yyy New:yyy
1407-
Old:zzz New:xxx
1409+
Old:xxx New:zzz
1410+
Old:yyy New:yyy
1411+
Old:zzz New:xxx
14081412
->Depends
14091413
->Implicit
1410-
Old:/usr/bin/python New:/usr/bin/python
1414+
Old:/usr/bin/python New:/usr/bin/python
14111415
- Fix Issue #3350 - SCons Exception EnvironmentError is conflicting with Python's EnvironmentError.
14121416
- Fix spurious rebuilds on second build for cases where builder has > 1 target and the source file
14131417
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
@@ -72,6 +72,8 @@ DEVELOPMENT
7272
-----------
7373

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

7678
Thanks to the following contributors listed below for their contributions to this release.
7779
==========================================================================================

SCons/Action.py

Lines changed: 39 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,7 @@
109109
from abc import ABC, abstractmethod
110110
from collections import OrderedDict
111111
from subprocess import DEVNULL, PIPE
112+
from typing import Optional
112113

113114
import SCons.Debug
114115
import SCons.Errors
@@ -119,6 +120,7 @@
119120
from SCons.Debug import logInstanceCreation
120121
from SCons.Subst import SUBST_SIG, SUBST_RAW
121122
from SCons.Util import is_String, is_List
123+
from SCons.Util.sctyping import ExecutorType
122124

123125
class _null:
124126
pass
@@ -530,7 +532,7 @@ def __call__(
530532
show=_null,
531533
execute=_null,
532534
chdir=_null,
533-
executor=None,
535+
executor: Optional[ExecutorType] = None,
534536
):
535537
raise NotImplementedError
536538

@@ -542,15 +544,15 @@ def no_batch_key(self, env, target, source):
542544

543545
batch_key = no_batch_key
544546

545-
def genstring(self, target, source, env, executor=None) -> str:
547+
def genstring(self, target, source, env, executor: Optional[ExecutorType] = None) -> str:
546548
return str(self)
547549

548550
@abstractmethod
549-
def get_presig(self, target, source, env, executor=None):
551+
def get_presig(self, target, source, env, executor: Optional[ExecutorType] = None):
550552
raise NotImplementedError
551553

552554
@abstractmethod
553-
def get_implicit_deps(self, target, source, env, executor=None):
555+
def get_implicit_deps(self, target, source, env, executor: Optional[ExecutorType] = None):
554556
raise NotImplementedError
555557

556558
def get_contents(self, target, source, env):
@@ -602,10 +604,10 @@ def presub_lines(self, env):
602604
self.presub_env = None # don't need this any more
603605
return lines
604606

605-
def get_varlist(self, target, source, env, executor=None):
607+
def get_varlist(self, target, source, env, executor: Optional[ExecutorType] = None):
606608
return self.varlist
607609

608-
def get_targets(self, env, executor):
610+
def get_targets(self, env, executor: Optional[ExecutorType]):
609611
"""
610612
Returns the type of targets ($TARGETS, $CHANGED_TARGETS) used
611613
by this action.
@@ -659,7 +661,7 @@ def __call__(self, target, source, env,
659661
show=_null,
660662
execute=_null,
661663
chdir=_null,
662-
executor=None):
664+
executor: Optional[ExecutorType] = None):
663665
if not is_List(target):
664666
target = [target]
665667
if not is_List(source):
@@ -743,10 +745,10 @@ def __call__(self, target, source, env,
743745
# an ABC like parent ActionBase, but things reach in and use it. It's
744746
# not just unittests or we could fix it up with a concrete subclass there.
745747

746-
def get_presig(self, target, source, env, executor=None):
748+
def get_presig(self, target, source, env, executor: Optional[ExecutorType] = None):
747749
raise NotImplementedError
748750

749-
def get_implicit_deps(self, target, source, env, executor=None):
751+
def get_implicit_deps(self, target, source, env, executor: Optional[ExecutorType] = None):
750752
raise NotImplementedError
751753

752754

@@ -1010,7 +1012,7 @@ def __str__(self) -> str:
10101012
return ' '.join(map(str, self.cmd_list))
10111013
return str(self.cmd_list)
10121014

1013-
def process(self, target, source, env, executor=None, overrides: bool=False):
1015+
def process(self, target, source, env, executor: Optional[ExecutorType] = None, overrides: bool=False):
10141016
if executor:
10151017
result = env.subst_list(self.cmd_list, 0, executor=executor, overrides=overrides)
10161018
else:
@@ -1031,7 +1033,7 @@ def process(self, target, source, env, executor=None, overrides: bool=False):
10311033
pass
10321034
return result, ignore, silent
10331035

1034-
def strfunction(self, target, source, env, executor=None, overrides: bool=False):
1036+
def strfunction(self, target, source, env, executor: Optional[ExecutorType] = None, overrides: bool=False):
10351037
if self.cmdstr is None:
10361038
return None
10371039
if self.cmdstr is not _null:
@@ -1046,7 +1048,7 @@ def strfunction(self, target, source, env, executor=None, overrides: bool=False)
10461048
return ''
10471049
return _string_from_cmd_list(cmd_list[0])
10481050

1049-
def execute(self, target, source, env, executor=None):
1051+
def execute(self, target, source, env, executor: Optional[ExecutorType] = None):
10501052
"""Execute a command action.
10511053
10521054
This will handle lists of commands as well as individual commands,
@@ -1108,7 +1110,7 @@ def execute(self, target, source, env, executor=None):
11081110
command=cmd_line)
11091111
return 0
11101112

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

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

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

1169-
def _get_implicit_deps_heavyweight(self, target, source, env, executor,
1171+
def _get_implicit_deps_heavyweight(self, target, source, env, executor: Optional[ExecutorType],
11701172
icd_int):
11711173
"""
11721174
Heavyweight dependency scanning involves scanning more than just the
@@ -1234,7 +1236,7 @@ def __init__(self, generator, kw) -> None:
12341236
self.varlist = kw.get('varlist', ())
12351237
self.targets = kw.get('targets', '$TARGETS')
12361238

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

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

12711273
def __call__(self, target, source, env, exitstatfunc=_null, presub=_null,
1272-
show=_null, execute=_null, chdir=_null, executor=None):
1274+
show=_null, execute=_null, chdir=_null, executor: Optional[ExecutorType] = None):
12731275
act = self._generate(target, source, env, 0, executor)
12741276
if act is None:
12751277
raise SCons.Errors.UserError(
@@ -1281,21 +1283,21 @@ def __call__(self, target, source, env, exitstatfunc=_null, presub=_null,
12811283
target, source, env, exitstatfunc, presub, show, execute, chdir, executor
12821284
)
12831285

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

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

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

1298-
def get_targets(self, env, executor):
1300+
def get_targets(self, env, executor: Optional[ExecutorType]):
12991301
return self._generate(None, None, env, 1, executor).get_targets(env, executor)
13001302

13011303

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

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

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

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

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

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

@@ -1389,7 +1391,7 @@ def function_name(self):
13891391
except AttributeError:
13901392
return "unknown_python_function"
13911393

1392-
def strfunction(self, target, source, env, executor=None):
1394+
def strfunction(self, target, source, env, executor: Optional[ExecutorType] = None):
13931395
if self.cmdstr is None:
13941396
return None
13951397
if self.cmdstr is not _null:
@@ -1430,7 +1432,7 @@ def __str__(self) -> str:
14301432
return str(self.execfunction)
14311433
return "%s(target, source, env)" % name
14321434

1433-
def execute(self, target, source, env, executor=None):
1435+
def execute(self, target, source, env, executor: Optional[ExecutorType] = None):
14341436
exc_info = (None,None,None)
14351437
try:
14361438
if executor:
@@ -1471,14 +1473,14 @@ def execute(self, target, source, env, executor=None):
14711473
# more information about this issue.
14721474
del exc_info
14731475

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

1481-
def get_implicit_deps(self, target, source, env, executor=None):
1483+
def get_implicit_deps(self, target, source, env, executor: Optional[ExecutorType] = None):
14821484
return []
14831485

14841486
class ListAction(ActionBase):
@@ -1495,7 +1497,7 @@ def list_of_actions(x):
14951497
self.varlist = ()
14961498
self.targets = '$TARGETS'
14971499

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

15011503
def __str__(self) -> str:
@@ -1505,15 +1507,15 @@ def presub_lines(self, env):
15051507
return SCons.Util.flatten_sequence(
15061508
[a.presub_lines(env) for a in self.list])
15071509

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

15151517
def __call__(self, target, source, env, exitstatfunc=_null, presub=_null,
1516-
show=_null, execute=_null, chdir=_null, executor=None):
1518+
show=_null, execute=_null, chdir=_null, executor: Optional[ExecutorType] = None):
15171519
if executor:
15181520
target = executor.get_all_targets()
15191521
source = executor.get_all_sources()
@@ -1524,13 +1526,13 @@ def __call__(self, target, source, env, exitstatfunc=_null, presub=_null,
15241526
return stat
15251527
return 0
15261528

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

1533-
def get_varlist(self, target, source, env, executor=None):
1535+
def get_varlist(self, target, source, env, executor: Optional[ExecutorType] = None):
15341536
result = OrderedDict()
15351537
for act in self.list:
15361538
for var in act.get_varlist(target, source, env, executor):
@@ -1596,7 +1598,7 @@ def subst_kw(self, target, source, env):
15961598
kw[key] = self.subst(self.kw[key], target, source, env)
15971599
return kw
15981600

1599-
def __call__(self, target, source, env, executor=None):
1601+
def __call__(self, target, source, env, executor: Optional[ExecutorType] = None):
16001602
args = self.subst_args(target, source, env)
16011603
kw = self.subst_kw(target, source, env)
16021604
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)