Merge pull request #7761 from SomberNight/202204_versioninfo_cmd
add `version_info` command
This commit is contained in:
@@ -11,6 +11,10 @@ if is_local and os.name == 'nt':
|
||||
os.add_dll_directory(os.path.dirname(__file__))
|
||||
|
||||
|
||||
class GuiImportError(ImportError):
|
||||
pass
|
||||
|
||||
|
||||
from .version import ELECTRUM_VERSION
|
||||
from .util import format_satoshis
|
||||
from .wallet import Wallet
|
||||
|
||||
@@ -38,6 +38,7 @@ from functools import wraps, partial
|
||||
from itertools import repeat
|
||||
from decimal import Decimal
|
||||
from typing import Optional, TYPE_CHECKING, Dict, List
|
||||
import os
|
||||
|
||||
from .import util, ecc
|
||||
from .util import (bfh, bh2u, format_satoshis, json_decode, json_normalize,
|
||||
@@ -57,11 +58,13 @@ from .lnutil import SENT, RECEIVED
|
||||
from .lnutil import LnFeatures
|
||||
from .lnutil import extract_nodeid
|
||||
from .lnpeer import channel_id_from_funding_tx
|
||||
from .plugin import run_hook
|
||||
from .plugin import run_hook, DeviceMgr
|
||||
from .version import ELECTRUM_VERSION
|
||||
from .simple_config import SimpleConfig
|
||||
from .invoices import Invoice
|
||||
from . import submarine_swaps
|
||||
from . import GuiImportError
|
||||
from . import crypto
|
||||
|
||||
|
||||
if TYPE_CHECKING:
|
||||
@@ -546,9 +549,45 @@ class Commands:
|
||||
@command('')
|
||||
async def version(self):
|
||||
"""Return the version of Electrum."""
|
||||
from .version import ELECTRUM_VERSION
|
||||
return ELECTRUM_VERSION
|
||||
|
||||
@command('')
|
||||
async def version_info(self):
|
||||
"""Return information about dependencies, such as their version and path."""
|
||||
ret = {
|
||||
"electrum.version": ELECTRUM_VERSION,
|
||||
"electrum.path": os.path.dirname(os.path.realpath(__file__)),
|
||||
}
|
||||
# add currently running GUI
|
||||
if self.daemon and self.daemon.gui_object:
|
||||
ret.update(self.daemon.gui_object.version_info())
|
||||
# always add Qt GUI, so we get info even when running this from CLI
|
||||
try:
|
||||
from .gui.qt import ElectrumGui as QtElectrumGui
|
||||
ret.update(QtElectrumGui.version_info())
|
||||
except GuiImportError:
|
||||
pass
|
||||
# Add shared libs (.so/.dll), and non-pure-python dependencies.
|
||||
# Such deps can be installed in various ways - often via the Linux distro's pkg manager,
|
||||
# instead of using pip, hence it is useful to list them for debugging.
|
||||
from . import ecc_fast
|
||||
ret.update(ecc_fast.version_info())
|
||||
from . import qrscanner
|
||||
ret.update(qrscanner.version_info())
|
||||
ret.update(DeviceMgr.version_info())
|
||||
ret.update(crypto.version_info())
|
||||
# add some special cases
|
||||
import aiohttp
|
||||
ret["aiohttp.version"] = aiohttp.__version__
|
||||
import aiorpcx
|
||||
ret["aiorpcx.version"] = aiorpcx._version_str
|
||||
import certifi
|
||||
ret["certifi.version"] = certifi.__version__
|
||||
import dns
|
||||
ret["dnspython.version"] = dns.__version__
|
||||
|
||||
return ret
|
||||
|
||||
@command('w')
|
||||
async def getmpk(self, wallet: Abstract_Wallet = None):
|
||||
"""Get master public key. Return your wallet\'s master public key"""
|
||||
|
||||
@@ -28,7 +28,7 @@ import os
|
||||
import sys
|
||||
import hashlib
|
||||
import hmac
|
||||
from typing import Union
|
||||
from typing import Union, Mapping, Optional
|
||||
|
||||
from .util import assert_bytes, InvalidPassword, to_bytes, to_string, WalletFileException, versiontuple
|
||||
from .i18n import _
|
||||
@@ -84,6 +84,27 @@ if not (HAS_CRYPTODOME or HAS_CRYPTOGRAPHY):
|
||||
sys.exit(f"Error: at least one of ('pycryptodomex', 'cryptography') needs to be installed.")
|
||||
|
||||
|
||||
def version_info() -> Mapping[str, Optional[str]]:
|
||||
ret = {}
|
||||
if HAS_PYAES:
|
||||
ret["pyaes.version"] = ".".join(map(str, pyaes.VERSION[:3]))
|
||||
else:
|
||||
ret["pyaes.version"] = None
|
||||
if HAS_CRYPTODOME:
|
||||
ret["cryptodome.version"] = Cryptodome.__version__
|
||||
if hasattr(Cryptodome, "__path__"):
|
||||
ret["cryptodome.path"] = ", ".join(Cryptodome.__path__ or [])
|
||||
else:
|
||||
ret["cryptodome.version"] = None
|
||||
if HAS_CRYPTOGRAPHY:
|
||||
ret["cryptography.version"] = cryptography.__version__
|
||||
if hasattr(cryptography, "__path__"):
|
||||
ret["cryptography.path"] = ", ".join(cryptography.__path__ or [])
|
||||
else:
|
||||
ret["cryptography.version"] = None
|
||||
return ret
|
||||
|
||||
|
||||
class InvalidPadding(Exception):
|
||||
pass
|
||||
|
||||
|
||||
@@ -51,6 +51,7 @@ from .commands import known_commands, Commands
|
||||
from .simple_config import SimpleConfig
|
||||
from .exchange_rate import FxThread
|
||||
from .logging import get_logger, Logger
|
||||
from . import GuiImportError
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from electrum import gui
|
||||
@@ -622,7 +623,10 @@ class Daemon(Logger):
|
||||
gui_name = 'qt'
|
||||
self.logger.info(f'launching GUI: {gui_name}')
|
||||
try:
|
||||
gui = __import__('electrum.gui.' + gui_name, fromlist=['electrum'])
|
||||
try:
|
||||
gui = __import__('electrum.gui.' + gui_name, fromlist=['electrum'])
|
||||
except GuiImportError as e:
|
||||
sys.exit(str(e))
|
||||
self.gui_object = gui.ElectrumGui(config=config, daemon=self, plugins=plugins)
|
||||
if not self._stop_entered:
|
||||
self.gui_object.main()
|
||||
|
||||
@@ -138,3 +138,9 @@ except BaseException as e:
|
||||
if _libsecp256k1 is None:
|
||||
# hard fail:
|
||||
sys.exit(f"Error: Failed to load libsecp256k1.")
|
||||
|
||||
|
||||
def version_info() -> dict:
|
||||
return {
|
||||
"libsecp256k1.path": _libsecp256k1._name if _libsecp256k1 else None,
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
# Notifications about network events are sent to the GUI by using network.register_callback()
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
from typing import TYPE_CHECKING, Mapping, Optional
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from . import qt
|
||||
@@ -28,3 +28,7 @@ class BaseElectrumGui:
|
||||
This method must be thread-safe.
|
||||
"""
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def version_info(cls) -> Mapping[str, Optional[str]]:
|
||||
return {}
|
||||
|
||||
@@ -29,16 +29,19 @@ import sys
|
||||
import os
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from electrum import GuiImportError
|
||||
|
||||
KIVY_GUI_PATH = os.path.abspath(os.path.dirname(__file__))
|
||||
os.environ['KIVY_DATA_DIR'] = os.path.join(KIVY_GUI_PATH, 'data')
|
||||
|
||||
try:
|
||||
sys.argv = ['']
|
||||
import kivy
|
||||
except ImportError:
|
||||
except ImportError as e:
|
||||
# This error ideally shouldn't be raised with pre-built packages
|
||||
sys.exit("Error: Could not import kivy. Please install it using the "
|
||||
"instructions mentioned here `https://kivy.org/#download` .")
|
||||
raise GuiImportError(
|
||||
"Error: Could not import kivy. Please install it using the "
|
||||
"instructions mentioned here `https://kivy.org/#download` .") from e
|
||||
|
||||
# minimum required version for kivy
|
||||
kivy.require('1.8.0')
|
||||
@@ -77,3 +80,12 @@ class ElectrumGui(BaseElectrumGui, Logger):
|
||||
if not app:
|
||||
return
|
||||
Clock.schedule_once(lambda dt: app.stop())
|
||||
|
||||
@classmethod
|
||||
def version_info(cls):
|
||||
ret = {
|
||||
"kivy.version": kivy.__version__,
|
||||
}
|
||||
if hasattr(kivy, "__path__"):
|
||||
ret["kivy.path"] = ", ".join(kivy.__path__ or [])
|
||||
return ret
|
||||
|
||||
@@ -30,12 +30,15 @@ import traceback
|
||||
import threading
|
||||
from typing import Optional, TYPE_CHECKING, List
|
||||
|
||||
from electrum import GuiImportError
|
||||
|
||||
try:
|
||||
import PyQt5
|
||||
import PyQt5.QtGui
|
||||
except Exception:
|
||||
sys.exit("Error: Could not import PyQt5 on Linux systems, you may try 'sudo apt-get install python3-pyqt5'")
|
||||
except Exception as e:
|
||||
raise GuiImportError(
|
||||
"Error: Could not import PyQt5 on Linux systems, "
|
||||
"you may try 'sudo apt-get install python3-pyqt5'") from e
|
||||
|
||||
from PyQt5.QtGui import QGuiApplication
|
||||
from PyQt5.QtWidgets import (QApplication, QSystemTrayIcon, QWidget, QMenu,
|
||||
@@ -469,3 +472,13 @@ class ElectrumGui(BaseElectrumGui, Logger):
|
||||
def stop(self):
|
||||
self.logger.info('closing GUI')
|
||||
self.app.quit_signal.emit()
|
||||
|
||||
@classmethod
|
||||
def version_info(cls):
|
||||
ret = {
|
||||
"qt.version": QtCore.QT_VERSION_STR,
|
||||
"pyqt.version": QtCore.PYQT_VERSION_STR,
|
||||
}
|
||||
if hasattr(PyQt5, "__path__"):
|
||||
ret["pyqt.path"] = ", ".join(PyQt5.__path__ or [])
|
||||
return ret
|
||||
|
||||
@@ -29,7 +29,7 @@ import time
|
||||
import threading
|
||||
import sys
|
||||
from typing import (NamedTuple, Any, Union, TYPE_CHECKING, Optional, Tuple,
|
||||
Dict, Iterable, List, Sequence, Callable, TypeVar)
|
||||
Dict, Iterable, List, Sequence, Callable, TypeVar, Mapping)
|
||||
import concurrent
|
||||
from concurrent import futures
|
||||
from functools import wraps, partial
|
||||
@@ -749,3 +749,25 @@ class DeviceMgr(ThreadJob):
|
||||
client.handler.update_status(False)
|
||||
|
||||
return devices
|
||||
|
||||
@classmethod
|
||||
def version_info(cls) -> Mapping[str, Optional[str]]:
|
||||
ret = {}
|
||||
# add libusb
|
||||
try:
|
||||
import usb1
|
||||
except Exception as e:
|
||||
ret["libusb.version"] = None
|
||||
else:
|
||||
ret["libusb.version"] = ".".join(map(str, usb1.getVersion()[:4]))
|
||||
try:
|
||||
ret["libusb.path"] = usb1.libusb1.libusb._name
|
||||
except AttributeError:
|
||||
ret["libusb.path"] = None
|
||||
# add hidapi
|
||||
from importlib.metadata import version
|
||||
try:
|
||||
ret["hidapi.version"] = version("hidapi")
|
||||
except ImportError:
|
||||
ret["hidapi.version"] = None
|
||||
return ret
|
||||
|
||||
@@ -102,5 +102,11 @@ def find_system_cameras() -> Mapping[str, str]:
|
||||
return devices
|
||||
|
||||
|
||||
def version_info() -> Mapping[str, Optional[str]]:
|
||||
return {
|
||||
"libzbar.path": libzbar._name if libzbar else None,
|
||||
}
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
print(scan_barcode())
|
||||
|
||||
Reference in New Issue
Block a user