Created
June 7, 2025 14:14
-
-
Save ahrm/639eae21f715d7d2c6bf3101cc667dc9 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # 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