First approach with the "components" package shipped with qutebrowser:
From 8dca7e07e98cb4e724ac1f82c7e3de0be8592c3b Mon Sep 17 00:00:00 2001
From: Florian Bruhin <me@the-compiler.org>
Date: Mon, 10 Dec 2018 09:38:23 +0100
Subject: [PATCH 1/3] Load components dynamically
---
qutebrowser/app.py | 4 ++--
qutebrowser/extensions/__init__.py | 0
qutebrowser/extensions/loader.py | 35 ++++++++++++++++++++++++++++++
qutebrowser/utils/log.py | 3 ++-
scripts/dev/run_vulture.py | 3 +++
scripts/dev/src2asciidoc.py | 2 ++
6 files changed, 44 insertions(+), 3 deletions(-)
create mode 100644 qutebrowser/extensions/__init__.py
create mode 100644 qutebrowser/extensions/loader.py
diff --git a/qutebrowser/app.py b/qutebrowser/app.py
index 6c948e10c8..65c7395eb0 100644
--- a/qutebrowser/app.py
+++ b/qutebrowser/app.py
@@ -68,6 +68,7 @@
from qutebrowser.browser.network import proxy
from qutebrowser.browser.webkit import cookies, cache
from qutebrowser.browser.webkit.network import networkmanager
+from qutebrowser.extensions import loader
from qutebrowser.keyinput import macros
from qutebrowser.mainwindow import mainwindow, prompt
from qutebrowser.misc import (readline, ipc, savemanager, sessions,
@@ -77,8 +78,6 @@
usertypes, standarddir, error, qtutils)
# pylint: disable=unused-import
# We import those to run the cmdutils.register decorators.
-from qutebrowser.components import (scrollcommands, caretcommands,
- zoomcommands, misccommands)
from qutebrowser.mainwindow.statusbar import command
from qutebrowser.misc import utilcmds
# pylint: enable=unused-import
@@ -166,6 +165,7 @@ def init(args, crash_handler):
qApp.setQuitOnLastWindowClosed(False)
_init_icon()
+ loader.load_components()
try:
_init_modules(args, crash_handler)
except (OSError, UnicodeDecodeError, browsertab.WebTabError) as e:
diff --git a/qutebrowser/extensions/__init__.py b/qutebrowser/extensions/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/qutebrowser/extensions/loader.py b/qutebrowser/extensions/loader.py
new file mode 100644
index 0000000000..9b5aadd25c
--- /dev/null
+++ b/qutebrowser/extensions/loader.py
@@ -0,0 +1,35 @@
+# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
+
+# Copyright 2018 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
+#
+# This file is part of qutebrowser.
+#
+# qutebrowser is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# qutebrowser is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with qutebrowser. If not, see <http://www.gnu.org/licenses/>.
+
+"""Loader for qutebrowser extensions."""
+
+import pkgutil
+
+from qutebrowser import components
+from qutebrowser.utils import log
+
+
+def load_components() -> None:
+ """Load everything from qutebrowser.components."""
+ for info in pkgutil.walk_packages(components.__path__):
+ if info.ispkg:
+ continue
+ log.extensions.debug("Importing {}".format(info.name))
+ loader = info.module_finder.find_module(info.name)
+ loader.load_module(info.name)
diff --git a/qutebrowser/utils/log.py b/qutebrowser/utils/log.py
index bbc0255158..115c53352f 100644
--- a/qutebrowser/utils/log.py
+++ b/qutebrowser/utils/log.py
@@ -137,6 +137,7 @@ def vdebug(self, msg, *args, **kwargs):
network = logging.getLogger('network')
sql = logging.getLogger('sql')
greasemonkey = logging.getLogger('greasemonkey')
+extensions = logging.getLogger('extensions')
LOGGER_NAMES = [
'statusbar', 'completion', 'init', 'url',
@@ -146,7 +147,7 @@ def vdebug(self, msg, *args, **kwargs):
'js', 'qt', 'rfc6266', 'ipc', 'shlexer',
'save', 'message', 'config', 'sessions',
'webelem', 'prompt', 'network', 'sql',
- 'greasemonkey'
+ 'greasemonkey', 'extensions',
]
diff --git a/scripts/dev/run_vulture.py b/scripts/dev/run_vulture.py
index f3217694ee..7874f6a796 100755
--- a/scripts/dev/run_vulture.py
+++ b/scripts/dev/run_vulture.py
@@ -30,6 +30,7 @@
import vulture
import qutebrowser.app # pylint: disable=unused-import
+from qutebrowser.extensions import loader
from qutebrowser.misc import objects
from qutebrowser.utils import utils
from qutebrowser.browser.webkit import rfc6266
@@ -43,6 +44,8 @@
def whitelist_generator(): # noqa
"""Generator which yields lines to add to a vulture whitelist."""
+ loader.load_components()
+
# qutebrowser commands
for cmd in objects.commands.values():
yield utils.qualname(cmd.handler)
diff --git a/scripts/dev/src2asciidoc.py b/scripts/dev/src2asciidoc.py
index ba4e9b69c7..f0536c045b 100755
--- a/scripts/dev/src2asciidoc.py
+++ b/scripts/dev/src2asciidoc.py
@@ -35,6 +35,7 @@
# We import qutebrowser.app so all @cmdutils-register decorators are run.
import qutebrowser.app
from qutebrowser import qutebrowser, commands
+from qutebrowser.extensions import loader
from qutebrowser.commands import argparser
from qutebrowser.config import configdata, configtypes
from qutebrowser.utils import docutils, usertypes
@@ -549,6 +550,7 @@ def regenerate_cheatsheet():
def main():
"""Regenerate all documentation."""
utils.change_cwd()
+ loader.load_components()
print("Generating manpage...")
regenerate_manpage('doc/qutebrowser.1.asciidoc')
print("Generating settings help...")
From f0ea11b8e2f8f0307a32627e4b5614965996e3ae Mon Sep 17 00:00:00 2001
From: Florian Bruhin <me@the-compiler.org>
Date: Mon, 10 Dec 2018 10:06:07 +0100
Subject: [PATCH 2/3] Add types to extensions.loader
---
mypy.ini | 4 ++++
qutebrowser/extensions/loader.py | 18 +++++++++++++-----
2 files changed, 17 insertions(+), 5 deletions(-)
diff --git a/mypy.ini b/mypy.ini
index 4526e4e48c..8fb8d89ae3 100644
--- a/mypy.ini
+++ b/mypy.ini
@@ -73,3 +73,7 @@ disallow_incomplete_defs = True
[mypy-qutebrowser.components.*]
disallow_untyped_defs = True
disallow_incomplete_defs = True
+
+[mypy-qutebrowser.extensions.*]
+disallow_untyped_defs = True
+disallow_incomplete_defs = True
diff --git a/qutebrowser/extensions/loader.py b/qutebrowser/extensions/loader.py
index 9b5aadd25c..9674ad7079 100644
--- a/qutebrowser/extensions/loader.py
+++ b/qutebrowser/extensions/loader.py
@@ -19,7 +19,9 @@
"""Loader for qutebrowser extensions."""
+import importlib.abc
import pkgutil
+import types
from qutebrowser import components
from qutebrowser.utils import log
@@ -27,9 +29,15 @@
def load_components() -> None:
"""Load everything from qutebrowser.components."""
- for info in pkgutil.walk_packages(components.__path__):
- if info.ispkg:
+ for finder, name, ispkg in pkgutil.walk_packages(components.__path__):
+ if ispkg:
continue
- log.extensions.debug("Importing {}".format(info.name))
- loader = info.module_finder.find_module(info.name)
- loader.load_module(info.name)
+ _load_module(finder, name)
+
+
+def _load_module(finder: importlib.abc.PathEntryFinder,
+ name: str) -> types.ModuleType:
+ log.extensions.debug("Importing {}".format(name))
+ loader = finder.find_module(name)
+ assert loader is not None
+ return loader.load_module(name)
From a17dde31964c3c81cbeb4c33c036d9a86303d81e Mon Sep 17 00:00:00 2001
From: Florian Bruhin <me@the-compiler.org>
Date: Mon, 10 Dec 2018 10:26:25 +0100
Subject: [PATCH 3/3] Add components to pyinstaller hiddenimports
---
misc/qutebrowser.spec | 11 ++++++++++-
qutebrowser/extensions/loader.py | 27 +++++++++++++++++++++------
2 files changed, 31 insertions(+), 7 deletions(-)
diff --git a/misc/qutebrowser.spec b/misc/qutebrowser.spec
index ff1b10577a..b40172754f 100644
--- a/misc/qutebrowser.spec
+++ b/misc/qutebrowser.spec
@@ -6,6 +6,8 @@ import os
sys.path.insert(0, os.getcwd())
from scripts import setupcommon
+from qutebrowser.extensions import loader
+
block_cipher = None
@@ -27,6 +29,13 @@ def get_data_files():
return data_files
+def get_hidden_imports():
+ imports = ['PyQt5.QtOpenGL', 'PyQt5._QOpenGLFunctions_2_0']
+ for info in loader.walk_components():
+ imports.append('qutebrowser.components.' + info.name)
+ return imports
+
+
setupcommon.write_git_file()
@@ -42,7 +51,7 @@ a = Analysis(['../qutebrowser/__main__.py'],
pathex=['misc'],
binaries=None,
datas=get_data_files(),
- hiddenimports=['PyQt5.QtOpenGL', 'PyQt5._QOpenGLFunctions_2_0'],
+ hiddenimports=get_hidden_imports(),
hookspath=[],
runtime_hooks=[],
excludes=['tkinter'],
diff --git a/qutebrowser/extensions/loader.py b/qutebrowser/extensions/loader.py
index 9674ad7079..d6fdc675ee 100644
--- a/qutebrowser/extensions/loader.py
+++ b/qutebrowser/extensions/loader.py
@@ -22,22 +22,37 @@
import importlib.abc
import pkgutil
import types
+import typing
+
+import attr
from qutebrowser import components
from qutebrowser.utils import log
+@attr.s
+class ComponentInfo:
+
+ name = attr.ib() # type: str
+ finder = attr.ib() # type: importlib.abc.PathEntryFinder
+
+
def load_components() -> None:
"""Load everything from qutebrowser.components."""
+ for info in walk_components():
+ _load_component(info)
+
+
+def walk_components() -> typing.Iterator[ComponentInfo]:
+ """Yield ComponentInfo objects for all modules."""
for finder, name, ispkg in pkgutil.walk_packages(components.__path__):
if ispkg:
continue
- _load_module(finder, name)
+ yield ComponentInfo(name=name, finder=finder)
-def _load_module(finder: importlib.abc.PathEntryFinder,
- name: str) -> types.ModuleType:
- log.extensions.debug("Importing {}".format(name))
- loader = finder.find_module(name)
+def _load_component(info: ComponentInfo) -> types.ModuleType:
+ log.extensions.debug("Importing {}".format(info.name))
+ loader = info.finder.find_module(info.name)
assert loader is not None
- return loader.load_module(name)
+ return loader.load_module(info.name)
However, that fails to find any modules on macOS/Windows with the frozen package. On macOS, components.__path__ shows /Volumes/qutebrowser/qutebrowser.app/Contents/MacOS/qutebrowser/components with qutebrowser being the executable (i.e., that path doesn't exist at all). As a result, pkgutil.walk_packages(components.__path__) doesn't yield anything.
First approach with the "components" package shipped with qutebrowser:
However, that fails to find any modules on macOS/Windows with the frozen package. On macOS,
components.__path__
shows/Volumes/qutebrowser/qutebrowser.app/Contents/MacOS/qutebrowser/components
withqutebrowser
being the executable (i.e., that path doesn't exist at all). As a result,pkgutil.walk_packages(components.__path__)
doesn't yield anything.