Skip to content

Instantly share code, notes, and snippets.

@ahrm
Created June 7, 2025 14:14
Show Gist options
  • Select an option

  • Save ahrm/639eae21f715d7d2c6bf3101cc667dc9 to your computer and use it in GitHub Desktop.

Select an option

Save ahrm/639eae21f715d7d2c6bf3101cc667dc9 to your computer and use it in GitHub Desktop.
# Copyright (C) 2017 The Qt Company Ltd.
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
import os
import sys
# import imp
import importlib.util
import re
# from distutils.version import LooseVersion
# from packaging.version import Version
class LooseVersion(object):
"""Version numbering for anarchists and software realists.
Implements the standard interface for version number classes as
described above. A version number consists of a series of numbers,
separated by either periods or strings of letters. When comparing
version numbers, the numeric components will be compared
numerically, and the alphabetic components lexically. The following
are all valid version numbers, in no particular order:
1.5.1
1.5.2b2
161
3.10a
8.02
3.4j
1996.07.12
3.2.pl0
3.1.1.6
2g6
11g
0.960923
2.2beta29
1.13++
5.5.kw
2.0b1pl0
In fact, there is no such thing as an invalid version number under
this scheme; the rules for comparison are simple and predictable,
but may not always give the results you want (for some definition
of "want").
"""
component_re = re.compile(r"(\d+ | [a-z]+ | \.)", re.VERBOSE)
def __init__(self, vstring=None):
if vstring:
self.parse(vstring)
def __eq__(self, other):
c = self._cmp(other)
if c is NotImplemented:
return NotImplemented
return c == 0
def __lt__(self, other):
c = self._cmp(other)
if c is NotImplemented:
return NotImplemented
return c < 0
def __le__(self, other):
c = self._cmp(other)
if c is NotImplemented:
return NotImplemented
return c <= 0
def __gt__(self, other):
c = self._cmp(other)
if c is NotImplemented:
return NotImplemented
return c > 0
def __ge__(self, other):
c = self._cmp(other)
if c is NotImplemented:
return NotImplemented
return c >= 0
def parse(self, vstring):
# I've given up on thinking I can reconstruct the version string
# from the parsed tuple -- so I just store the string here for
# use by __str__
self.vstring = vstring
components = [x for x in self.component_re.split(vstring) if x and x != "."]
for i, obj in enumerate(components):
try:
components[i] = int(obj)
except ValueError:
pass
self.version = components
def __str__(self):
return self.vstring
def __repr__(self):
return "LooseVersion ('%s')" % str(self)
def _cmp(self, other):
other = self._coerce(other)
if other is NotImplemented:
return NotImplemented
if self.version == other.version:
return 0
if self.version < other.version:
return -1
if self.version > other.version:
return 1
return NotImplemented
@classmethod
def _coerce(cls, other):
if isinstance(other, cls):
return other
elif isinstance(other, str):
return cls(other)
elif "distutils" in sys.modules:
# Using this check to avoid importing distutils and suppressing the warning
try:
from distutils.version import LooseVersion as deprecated
except ImportError:
return NotImplemented
if isinstance(other, deprecated):
return cls(str(other))
return NotImplemented
MODULE_NAME = 'qt'
debug = print if 'QT_LLDB_SUMMARY_PROVIDER_DEBUG' in os.environ \
else lambda *a, **k: None
def import_bridge_old(path, debugger, session_dict, reload_module=False):
if not reload_module and MODULE_NAME in sys.modules:
del sys.modules[MODULE_NAME]
if sys.version_info[0] >= 3:
sys.path.append(os.path.dirname(path))
debug(f"Loading source of Qt Creator bridge from '{path}'")
bridge = imp.load_source(MODULE_NAME, path)
if not hasattr(bridge, '__lldb_init_module'):
print("Could not find '__lldb_init_module'. Ignoring.")
return None
# Make available for the current LLDB session, so that LLDB
# can find the functions when initializing the module.
session_dict[MODULE_NAME] = bridge
# Initialize the module now that it's available globally
debug(f"Initializing Qt Creator bridge by calling __lldb_init_module(): {bridge}")
bridge.__lldb_init_module(debugger, session_dict)
if not debugger.GetCategory('Qt'):
debug("Could not find Qt debugger category. Qt Creator summary providers not loaded.")
# Summary provider failed for some reason
del session_dict[MODULE_NAME]
return None
debug("Bridge loaded successfully")
return bridge
def import_bridge(path, debugger, session_dict, reload_module=False):
# If we’re *not* reloading, but the module is already in sys.modules, remove it
if not reload_module and MODULE_NAME in sys.modules:
del sys.modules[MODULE_NAME]
# Optionally add the folder to sys.path so any relative imports inside the bridge work
bridge_dir = os.path.dirname(path)
if bridge_dir not in sys.path:
sys.path.append(bridge_dir)
debug(f"Loading source of Qt Creator bridge from '{path}'")
# ————————————————————————————————————————————————————
# NEW: load module via importlib
spec = importlib.util.spec_from_file_location(MODULE_NAME, path)
if spec is None or spec.loader is None:
raise ImportError(f"Cannot load spec for module {MODULE_NAME} at {path}")
bridge = importlib.util.module_from_spec(spec)
# execute the module in its own namespace
spec.loader.exec_module(bridge)
# (Optionally) register it in sys.modules so imports by name will find it
sys.modules[MODULE_NAME] = bridge
# ————————————————————————————————————————————————————
if not hasattr(bridge, '__lldb_init_module'):
print("Could not find '__lldb_init_module'. Ignoring.")
return None
# Make available for the current LLDB session so LLDB can see it by name
session_dict[MODULE_NAME] = bridge
debug(f"Initializing Qt Creator bridge by calling __lldb_init_module(): {bridge}")
bridge.__lldb_init_module(debugger, session_dict)
if not debugger.GetCategory('Qt'):
debug("Could not find Qt debugger category. Qt Creator summary providers not loaded.")
# clean up if initialization didn’t actually install
del session_dict[MODULE_NAME]
return None
debug("Bridge loaded successfully")
return bridge
def __lldb_init_module(debugger, session_dict):
qtc_env_vars = ['QTC_DEBUGGER_PROCESS', 'QT_CREATOR_LLDB_PROCESS']
if any(v in os.environ for v in qtc_env_vars) and \
not 'QT_FORCE_LOAD_LLDB_SUMMARY_PROVIDER' in os.environ:
debug("Qt Creator lldb bridge not loaded because we're already in a debugging session.")
return
# Check if the module has already been imported globally. This ensures
# that the Qt Creator application search is only performed once per
# LLDB process invocation, while still reloading for each session.
if MODULE_NAME in sys.modules:
module = sys.modules[MODULE_NAME]
debug(f"Module '{module.__file__}' already imported. Reloading for this session.")
# Reload module for this sessions
bridge = import_bridge(module.__file__, debugger, session_dict,
reload_module = True)
if bridge:
debug("Qt summary providers successfully reloaded.")
return
else:
print("Bridge reload failed. Trying to find other Qt Creator bridges.")
versions = {}
for path in os.popen('mdfind kMDItemCFBundleIdentifier=org.qt-project.qtcreator'):
path = path.strip()
file = open(os.path.join(path, 'Contents', 'Info.plist'), "rb")
import plistlib
plist = plistlib.load(file)
version = None
for key in ["CFBundleVersion", "CFBundleShortVersionString"]:
if key in plist:
version = plist[key]
break
if not version:
print(f"Could not resolve version for '{path}'. Ignoring.")
continue
versions[version] = path
if not len(versions):
print("Could not find Qt Creator installation. No Qt summary providers installed.")
return
for version in sorted(versions, key=LooseVersion, reverse=True):
path = versions[version]
debug(f"Loading Qt summary providers from Creator Qt {version} in '{path}'")
bridge_path = '{}/Contents/Resources/debugger/lldbbridge.py'.format(path)
bridge = import_bridge(bridge_path, debugger, session_dict)
if bridge:
debug(f"Qt summary providers successfully loaded.")
return
if 'QT_LLDB_SUMMARY_PROVIDER_DEBUG' not in os.environ:
print("Could not find any valid Qt Creator bridges with summary providers. "
"Launch lldb or Qt Creator with the QT_LLDB_SUMMARY_PROVIDER_DEBUG environment "
"variable to debug.")
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment