Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make com interfaces ide friendly #12201

Merged
merged 13 commits into from
Mar 25, 2021
Merged
Show file tree
Hide file tree
Changes from 10 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
5 changes: 3 additions & 2 deletions source/_UIAHandler.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,9 @@
import eventHandler
from logHandler import log
import UIAUtils
from comtypes.gen import UIAutomationClient as UIA
from comtypes.gen.UIAutomationClient import *
from comInterfaces import UIAutomationClient as UIA
# F403: unable to detect undefined names
from comInterfaces .UIAutomationClient import * # noqa: F403
import textInfos
from typing import Dict
from queue import Queue
Expand Down
6 changes: 5 additions & 1 deletion source/comInterfaces/UIAutomationClient.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
from comtypes.gen import _944DE083_8FB8_45CF_BCB7_C477ACB2F897_0_1_0
try:
from comtypes.gen import _944DE083_8FB8_45CF_BCB7_C477ACB2F897_0_1_0
except ModuleNotFoundError:
import _944DE083_8FB8_45CF_BCB7_C477ACB2F897_0_1_0
from _944DE083_8FB8_45CF_BCB7_C477ACB2F897_0_1_0 import *
globals().update(_944DE083_8FB8_45CF_BCB7_C477ACB2F897_0_1_0.__dict__)
__name__ = 'comtypes.gen.UIAutomationClient'
16 changes: 16 additions & 0 deletions source/comInterfaces/readme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
The comInterfaces package is generated via SCons.
The logic for this is in `comInterfaces_sconscript`, which uses `comtypes.gen` to read `*.tlb`
files or via interface IDs.

The interface files have an ID named file (a GUID, followed by a version number) as well as a
"friendly name" file.

The "friendly name" file generated by comtypes is not consumed easily by tools and IDEs,
runtime logic is used to expose symbols.
To remedy this, the file is then processed by `comInterfaces_sconscript` to extract the module
name and replace the import statement with a more elaborate approach which includes a fallback
for the purposes of IDEs and tools.

Only UIAutomation.py is not generated, UIA has historically been updated regularly and the version on
Appveyor build servers could not be guaranteed.

80 changes: 67 additions & 13 deletions source/comInterfaces_sconscript
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@
#This license can be found at:
#http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
###
from typing import List
import fileinput
import re
from SCons.Node.FS import File

Import(
'env',
Expand All @@ -26,18 +30,49 @@ def new_my_import(fullname):
return old_my_import(fullname)
comtypes.client._generate._my_import = new_my_import

def interfaceAction(target,source,env):
clsid=env.get('clsid')
def makeIDEFriendly(path:str) -> None:
"""
Add a local import of * so that tools and IDE's can find definitions.
Prefer to import from comtypes.gen, at runtime behavior will not have changed.
@param path: Path to the friendly name comInterfaces module.
"""
importTemplate = (
"""try:
from comtypes.gen import {libIdentifier}
except ModuleNotFoundError:
import {libIdentifier}
from {libIdentifier} import *
""")

importPattern = re.compile(r"from comtypes\.gen import ([\w]+)\n")
for line in fileinput.input(path, inplace=True):
feerrenrut marked this conversation as resolved.
Show resolved Hide resolved
# Note: The fileinput module temporarily redirects stdout to the file.
# So print is used to write the file.
match = importPattern.match(line)
if match:
libId = match.group(1)
print(importTemplate.format(libIdentifier=libId), end='')
else:
print(line, end='')

def interfaceAction(target:List[File], source, env):
clsid = env.get('clsid')
if clsid:
source=(clsid,env['majorVersion'],env['minorVersion'])
source=(clsid, env['majorVersion'], env['minorVersion'])
else:
source=str(source[0])
comtypes.client.GetModule(source)
# re-write the the "friendlyNameFile" so that tools/IDEs can find the
# definitions
for t in target:
path: str = t.abspath
if path.endswith(".py"):
makeIDEFriendly(path)

interfaceBuilder=env.Builder(
action=env.Action(interfaceAction),
)
env['BUILDERS']['comtypesInterface']=interfaceBuilder
env['BUILDERS']['comtypesInterface'] = interfaceBuilder

# Force comtypes generated interfaces in to our directory
import comtypes.client
Expand All @@ -47,21 +82,40 @@ COM_INTERFACES = {
"IAccessible2Lib.py": "typelibs/ia2.tlb",
"ISimpleDOM.py": "typelibs/ISimpleDOMNode.tlb",
"mathPlayer.py": "typelibs/mathPlayerDLL.tlb",
#"Accessibility.py": ('{1EA4DBF0-3C3B-11CF-810C-00AA00389B71}',1,0),
"Accessibility.py": ('{1EA4DBF0-3C3B-11CF-810C-00AA00389B71}',1,0),
"tom.py": ('{8CC497C9-A1DF-11CE-8098-00AA0047BE5D}',1,0),
"SpeechLib.py": ('{C866CA3A-32F7-11D2-9602-00C04F8EE628}',5,0),
"AcrobatAccessLib.py": "typelibs/AcrobatAccess.tlb",
# We don't generate UIAutomationClient (and _944DE083_8FB8_45CF_BCB7_C477ACB2F897_0_1_0.py)
# because we don't know what version is available on Appveyor.
# Instead we locally generate the file and include it in the repository.
# Ensuring that adjustments performed by 'makeIDEFriendly' are incorporated.
# "UIAutomationClient.py": ('{944DE083-8FB8-45CF-BCB7-C477ACB2F897}', 1, 0),
}

for k,v in COM_INTERFACES.items():
targets=[Dir('comInterfaces').File(k),
# This buillds a .pyc file as well.
Dir('comInterfaces').File(importlib.util.cache_from_source(k))]
source=clsid=majorVersion=None
targets=[
Dir('comInterfaces').File(k),
# This builds a .pyc file as well.
Dir('comInterfaces').File(importlib.util.cache_from_source(k))
]
source = clsid = majorVersion = None
if isinstance(v, str):
env.comtypesInterface(targets,v)
env.comtypesInterface(targets, v)
else:
env.comtypesInterface(targets,Dir('comInterfaces').File('__init__.py'),clsid=v[0],majorVersion=v[1],minorVersion=v[2])
env.comtypesInterface(
targets,
Dir('comInterfaces').File('__init__.py'),
clsid=v[0],
majorVersion=v[1],
minorVersion=v[2]
)


#When cleaning comInterfaces get rid of everything except for things starting with __ (e.g. __init__.py)
env.Clean(Dir('comInterfaces'),Glob('comInterfaces/[!_]*')+Glob('comInterfaces/_[!_]*'))

# When cleaning comInterfaces get rid of everything
# except for things starting with __ (e.g. __init__.py)
env.Clean(
Dir('comInterfaces'),
Glob('comInterfaces/[!_]*') + Glob('comInterfaces/_[!_]*')
)