Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions build.py
Original file line number Diff line number Diff line change
Expand Up @@ -1144,6 +1144,17 @@ def cmd_etg(options, args):
etgfiles.remove(core_file)
etgfiles.insert(0, core_file)

# We need two loops:
# 1. Cache all type auto conversion rules which get created in etgtools/tweaker_tools.FixWxPrefix
# 2. Actually create the sip files
# This is needed because each etg file is run in its own python invocation, so
# the data is lost between them.

# First delete the old cache if found
cacheFile = opj(cfg.ROOT_DIR, 'sip', 'gen', '__auto_conversion_cache__.json')
if os.path.isfile(cacheFile):
os.remove(cacheFile)
is_newer = {}
for script in etgfiles:
sipfile = etg2sip(script)
deps = [script]
Expand All @@ -1157,7 +1168,16 @@ def cmd_etg(options, args):

# run the script only if any dependencies are newer
if newer_group(deps, sipfile):
is_newer[script] = True
runcmd('"%s" %s %s' % (PYTHON, script, flags + " --only-cache-auto-conversions"))
else:
is_newer[script] = False
for script in etgfiles:
if is_newer[script]:
runcmd('"%s" %s %s' % (PYTHON, script, flags))
# Delete the cache
if os.path.isfile(cacheFile):
os.remove(cacheFile)


def cmd_sphinx(options, args):
Expand Down
38 changes: 32 additions & 6 deletions etgtools/pi_generator.py
Original file line number Diff line number Diff line change
Expand Up @@ -571,27 +571,53 @@ def generatePyProperty(self, klass, prop, stream, indent):
def _generateProperty(self, klass: extractors.ClassDef, prop: Union[extractors.PyPropertyDef, extractors.PropertyDef], stream, indent: str):
if prop.ignored or piIgnored(prop):
return
# Track separate value and return types.
# This prevents us from emitting Union types on the getter, which do not make sense in practice,
# as exactly one type will be returned, even if multiple types are allowed by the setter.
value_type = ''
return_type = ''
if prop.getter:
getter = self.find_method(klass, prop.getter)
if getter and getter.signature:
value_type = getter.signature.return_type
return_type = getter.signature.return_type
if prop.setter:
setter = self.find_method(klass, prop.setter)
if setter and setter.signature:
value_type = setter.signature[0].type_hint
# The setter can be overloaded and we should find all setters which have exactly one argument and Union their types.
# We need to do this, because the property-setter only has one argument, so we assume that all overloads which
# take more than one argument are not applicable here.
if setter:
value_types =[]
if setter.signature and len(setter.signature._parameters) == 1:
value_types.append(setter.signature[0].type_hint)
if setter.hasOverloads():
for overload in setter.overloads:
if not overload.ignored and overload.signature and len(overload.signature._parameters) == 1:
value_types.append(overload.signature[0].type_hint)
if len(value_types) == 1:
value_type = value_types[0]
elif value_types:
# This does not flatten already existing Unions, but this is allowed
value_type = f'Union[{", ".join(value_types)}]'
# We choose some default values if no types were found above. This may lead to wrong info, if the underlying getter and setter types are wrong to begin with
if value_type or return_type:
if not value_type:
value_type = return_type
elif not return_type:
# We probably should not choose the value_type as default if it contains a Union,
# but this might still be better than using Any.
return_type = value_type
if prop.setter and prop.getter:
if value_type:
if value_type and return_type:
stream.write(f'{indent}@property\n')
stream.write(f'{indent}def {prop.name}(self) -> {value_type}: ...\n')
stream.write(f'{indent}def {prop.name}(self) -> {return_type}: ...\n')
stream.write(f'{indent}@{prop.name}.setter\n')
stream.write(f'{indent}def {prop.name}(self, value: {value_type}, /) -> None: ...\n')
else:
stream.write(f'{indent}{prop.name} = property({prop.getter}, {prop.setter})\n')
elif prop.getter:
if value_type:
stream.write(f'{indent}@property\n')
stream.write(f'{indent}def {prop.name}(self) -> {value_type}: ...\n')
stream.write(f'{indent}def {prop.name}(self) -> {return_type}: ...\n')
else:
stream.write(f'{indent}{prop.name} = property({prop.getter})\n')
elif prop.setter:
Expand Down
41 changes: 39 additions & 2 deletions etgtools/tweaker_tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
import sys, os
import copy
import textwrap
from typing import NamedTuple, Optional, Tuple, Union
from typing import Final, NamedTuple, Optional, Tuple, Union


isWindows = sys.platform.startswith('win')
Expand Down Expand Up @@ -246,6 +246,24 @@ def removeWxPrefix(name):
return name


# Filename for type auto conversion cache file
_AUTO_CONVERSION_CACHE_FILE: Final = '__auto_conversion_cache__.json'


def load_auto_conversions(destFile: Optional[str] = None) -> dict[str, Tuple[str, ...]]:
"""Load FixWxPrefix auto conversions from cache file if it exists."""
import json
if not destFile:
from buildtools.config import Config

cfg = Config(noWxConfig=True)
destFile = os.path.join(cfg.ROOT_DIR, 'sip', 'gen', _AUTO_CONVERSION_CACHE_FILE)
# Need to merge existing data with current data
if os.path.isfile(destFile):
with open(destFile, 'r', encoding='utf-8') as f:
return json.load(f)
return {}


class FixWxPrefix(object):
"""
Expand All @@ -254,7 +272,22 @@ class FixWxPrefix(object):
"""

_coreTopLevelNames = None
_auto_conversions: dict[str, Tuple[str, ...]] = {}
_auto_conversions: dict[str, Tuple[str, ...]] = load_auto_conversions()

@classmethod
def cache_auto_conversions(cls, destFile: Optional[str] = None) -> None:
"""Save current auto conversions to a cache file."""
import json

if not destFile:
from buildtools.config import Config

cfg = Config(noWxConfig=True)
destFile = os.path.join(cfg.ROOT_DIR, 'sip', 'gen', _AUTO_CONVERSION_CACHE_FILE)
# We overwrite the cache file, as we should have loaded the existing values when
# initializing the class.
with textfile_open(destFile, 'wt') as f:
json.dump(FixWxPrefix._auto_conversions, f)

@classmethod
def register_autoconversion(cls, class_name: str, convertables: Tuple[str, ...]) -> None:
Expand Down Expand Up @@ -371,6 +404,7 @@ def cleanType(self, type_name: str, is_input: bool = False) -> str:
'time_t': 'int',
'size_t': 'int',
'Int32': 'int',
'Uint32': 'int',
'long': long_type,
'unsignedlong': long_type,
'ulong': long_type,
Expand Down Expand Up @@ -950,6 +984,9 @@ def runGenerators(module):
checkForUnitTestModule(module)

generators = list()
if '--only-cache-auto-conversions' in sys.argv:
FixWxPrefix.cache_auto_conversions()
return

# Create the code generator selected from command line args
generators.append(getWrapperGenerator())
Expand Down
Loading