From 5298d14084ca5a3cda7ef931146750a9394d8a8d Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Mon, 18 Sep 2017 19:22:54 +0200 Subject: [PATCH] Move backend initialization to config.py --- qutebrowser/app.py | 2 +- qutebrowser/config/config.py | 28 ++++++- qutebrowser/misc/earlyinit.py | 137 +++++++++++++++---------------- qutebrowser/qutebrowser.py | 2 +- tests/helpers/fixtures.py | 3 +- tests/unit/config/test_config.py | 16 ++-- 6 files changed, 107 insertions(+), 81 deletions(-) diff --git a/qutebrowser/app.py b/qutebrowser/app.py index 11b38d07a..9278e0a0c 100644 --- a/qutebrowser/app.py +++ b/qutebrowser/app.py @@ -77,7 +77,7 @@ def run(args): standarddir.init(args) log.init.debug("Initializing config...") - config.early_init() + config.early_init(args) global qApp qApp = Application(args) diff --git a/qutebrowser/config/config.py b/qutebrowser/config/config.py index ff954486b..7eec9d988 100644 --- a/qutebrowser/config/config.py +++ b/qutebrowser/config/config.py @@ -29,7 +29,7 @@ from PyQt5.QtWidgets import QMessageBox from qutebrowser.config import configdata, configexc, configtypes, configfiles from qutebrowser.utils import utils, objreg, message, log, usertypes, jinja -from qutebrowser.misc import objects, msgbox +from qutebrowser.misc import objects, msgbox, earlyinit from qutebrowser.commands import cmdexc, cmdutils, runners from qutebrowser.completion.models import configmodel @@ -641,7 +641,7 @@ class StyleSheetObserver(QObject): instance.changed.connect(self._update_stylesheet) -def early_init(): +def early_init(args): """Initialize the part of the config which works without a QApplication.""" configdata.init() @@ -686,6 +686,30 @@ def early_init(): configfiles.init() + objects.backend = get_backend(args) + earlyinit.init_with_backend(objects.backend) + + +def get_backend(args): + """Find out what backend to use based on available libraries.""" + from qutebrowser.utils import usertypes + try: + import PyQt5.QtWebKit # pylint: disable=unused-variable + webkit_available = True + except ImportError: + webkit_available = False + + if args.backend is not None: + backends = { + 'webkit': usertypes.Backend.QtWebKit, + 'webengine': usertypes.Backend.QtWebEngine, + } + return backends[args.backend] + elif webkit_available: + return usertypes.Backend.QtWebKit + else: + return usertypes.Backend.QtWebEngine + def late_init(save_manager): """Initialize the rest of the config after the QApplication is created.""" diff --git a/qutebrowser/misc/earlyinit.py b/qutebrowser/misc/earlyinit.py index 422560609..69f52d0b5 100644 --- a/qutebrowser/misc/earlyinit.py +++ b/qutebrowser/misc/earlyinit.py @@ -163,26 +163,6 @@ def check_pyqt_core(): sys.exit(1) -def get_backend(args): - """Find out what backend to use based on available libraries. - - Note this function returns the backend as a string so we don't have to - import qutebrowser.utils.usertypes yet. - """ - try: - import PyQt5.QtWebKit # pylint: disable=unused-variable - webkit_available = True - except ImportError: - webkit_available = False - - if args.backend is not None: - return args.backend - elif webkit_available: - return 'webkit' - else: - return 'webengine' - - def qt_version(qversion=None, qt_version_str=None): """Get a Qt version string based on the runtime/compiled versions.""" if qversion is None: @@ -210,31 +190,52 @@ def check_qt_version(): _die(text) -def check_ssl_support(backend): +def check_ssl_support(): """Check if SSL support is available.""" - from qutebrowser.utils import log - try: from PyQt5.QtNetwork import QSslSocket except ImportError: _die("Fatal error: Your Qt is built without SSL support.") + +def check_backend_ssl_support(backend): + """Check for full SSL availability when we know the backend.""" + from PyQt5.QtNetwork import QSslSocket + from qutebrowser.utils import log, usertypes text = ("Could not initialize QtNetwork SSL support. If you use " "OpenSSL 1.1 with a PyQt package from PyPI (e.g. on Archlinux " "or Debian Stretch), you need to set LD_LIBRARY_PATH to the path " - "of OpenSSL 1.0.") - if backend == 'webengine': - text += " This only affects downloads." + "of OpenSSL 1.0. This only affects downloads.") if not QSslSocket.supportsSsl(): - if backend == 'webkit': + if backend == usertypes.Backend.QtWebKit: _die("Could not initialize SSL support.") else: - assert backend == 'webengine' + assert backend == usertypes.Backend.QtWebEngine log.init.warning(text) -def check_libraries(backend): +def _check_modules(modules): + """Make sure the given modules are available.""" + from qutebrowser.utils import log + + for name, text in modules.items(): + try: + # https://github.com/pallets/jinja/pull/628 + # https://bitbucket.org/birkenfeld/pygments-main/issues/1314/ + # https://github.com/pallets/jinja/issues/646 + # https://bitbucket.org/fdik/pypeg/commits/dd15ca462b532019c0a3be1d39b8ee2f3fa32f4e + messages = ['invalid escape sequence', + 'Flags not at the start of the expression'] + with log.ignore_py_warnings( + category=DeprecationWarning, + message=r'({})'.format('|'.join(messages))): + importlib.import_module(name) + except ImportError as e: + _die(text, e) + + +def check_libraries(): """Check if all needed Python libraries are installed.""" modules = { 'pkg_resources': @@ -262,32 +263,29 @@ def check_libraries(backend): 'PyQt5.QtQml': _missing_str("PyQt5.QtQml"), 'PyQt5.QtSql': _missing_str("PyQt5.QtSql"), } - if backend == 'webengine': - modules['PyQt5.QtWebEngineWidgets'] = _missing_str("QtWebEngine", - webengine=True) - modules['PyQt5.QtOpenGL'] = _missing_str("PyQt5.QtOpenGL") + _check_modules(modules) + + +def check_backend_libraries(backend): + """Make sure the libraries needed by the given backend are available. + + Args: + backend: The backend as usertypes.Backend member. + """ + from qutebrowser.utils import usertypes + if backend == usertypes.Backend.QtWebEngine: + modules = { + 'PyQt5.QtWebEngineWidgets': + _missing_str("QtWebEngine", webengine=True), + 'PyQt5.QtOpenGL': _missing_str("PyQt5.QtOpenGL"), + } else: - assert backend == 'webkit' - modules['PyQt5.QtWebKit'] = _missing_str("PyQt5.QtWebKit") - modules['PyQt5.QtWebKitWidgets'] = _missing_str( - "PyQt5.QtWebKitWidgets") - - from qutebrowser.utils import log - - for name, text in modules.items(): - try: - # https://github.com/pallets/jinja/pull/628 - # https://bitbucket.org/birkenfeld/pygments-main/issues/1314/ - # https://github.com/pallets/jinja/issues/646 - # https://bitbucket.org/fdik/pypeg/commits/dd15ca462b532019c0a3be1d39b8ee2f3fa32f4e - messages = ['invalid escape sequence', - 'Flags not at the start of the expression'] - with log.ignore_py_warnings( - category=DeprecationWarning, - message=r'({})'.format('|'.join(messages))): - importlib.import_module(name) - except ImportError as e: - _die(text, e) + assert backend == usertypes.Backend.QtWebKit, backend + modules = { + 'PyQt5.QtWebKit': _missing_str("PyQt5.QtWebKit"), + 'PyQt5.QtWebKitWidgets': _missing_str("PyQt5.QtWebKitWidgets"), + } + _check_modules(modules) def remove_inputhook(): @@ -318,18 +316,7 @@ def check_optimize_flag(): "unexpected behavior may occur.") -def set_backend(backend): - """Set the objects.backend global to the given backend (as string).""" - from qutebrowser.misc import objects - from qutebrowser.utils import usertypes - backends = { - 'webkit': usertypes.Backend.QtWebKit, - 'webengine': usertypes.Backend.QtWebEngine, - } - objects.backend = backends[backend] - - -def earlyinit(args): +def early_init(args): """Do all needed early initialization. Note that it's vital the other earlyinit functions get called in the right @@ -348,10 +335,20 @@ def earlyinit(args): init_log(args) # Now we can be sure QtCore is available, so we can print dialogs on # errors, so people only using the GUI notice them as well. - backend = get_backend(args) - check_libraries(backend) + check_libraries() check_qt_version() remove_inputhook() - check_ssl_support(backend) + check_ssl_support() check_optimize_flag() - set_backend(backend) + + +def init_with_backend(backend): + """Do later stages of init when we know the backend. + + Args: + backend: The backend as usertypes.Backend member. + """ + assert not isinstance(backend, str), backend + assert backend is not None + check_backend_libraries(backend) + check_backend_ssl_support(backend) diff --git a/qutebrowser/qutebrowser.py b/qutebrowser/qutebrowser.py index 844898c0a..1ae4ce192 100644 --- a/qutebrowser/qutebrowser.py +++ b/qutebrowser/qutebrowser.py @@ -167,7 +167,7 @@ def main(): # from json. data = json.loads(args.json_args) args = argparse.Namespace(**data) - earlyinit.earlyinit(args) + earlyinit.early_init(args) # We do this imports late as earlyinit needs to be run first (because of # version checking and other early initialization) from qutebrowser import app diff --git a/tests/helpers/fixtures.py b/tests/helpers/fixtures.py index d8958f235..08aea97f6 100644 --- a/tests/helpers/fixtures.py +++ b/tests/helpers/fixtures.py @@ -415,8 +415,9 @@ def fake_save_manager(): @pytest.fixture -def fake_args(): +def fake_args(request): ns = types.SimpleNamespace() + ns.backend = 'webengine' if request.config.webengine else 'webkit' objreg.register('args', ns) yield ns objreg.delete('args') diff --git a/tests/unit/config/test_config.py b/tests/unit/config/test_config.py index 3a151a939..459745438 100644 --- a/tests/unit/config/test_config.py +++ b/tests/unit/config/test_config.py @@ -877,6 +877,9 @@ def init_patch(qapp, fake_save_manager, monkeypatch, config_tmpdir, monkeypatch.setattr(config, 'key_instance', None) monkeypatch.setattr(config, '_change_filters', []) monkeypatch.setattr(config, '_init_errors', []) + # Make sure we get no SSL warning + monkeypatch.setattr(config.earlyinit, 'check_backend_ssl_support', + lambda _backend: None) yield try: objreg.delete('config-commands') @@ -888,7 +891,7 @@ def init_patch(qapp, fake_save_manager, monkeypatch, config_tmpdir, @pytest.mark.parametrize('config_py', [True, 'error', False]) @pytest.mark.parametrize('invalid_yaml', ['42', 'unknown', False]) # pylint: disable=too-many-branches -def test_early_init(init_patch, config_tmpdir, caplog, +def test_early_init(init_patch, config_tmpdir, caplog, fake_args, load_autoconfig, config_py, invalid_yaml): # Prepare files autoconfig_file = config_tmpdir / 'autoconfig.yml' @@ -914,7 +917,7 @@ def test_early_init(init_patch, config_tmpdir, caplog, 'utf-8', ensure=True) with caplog.at_level(logging.ERROR): - config.early_init() + config.early_init(fake_args) # Check error messages expected_errors = [] @@ -954,15 +957,16 @@ def test_early_init(init_patch, config_tmpdir, caplog, assert config.instance._values == {'colors.hints.fg': 'magenta'} -def test_early_init_invalid_change_filter(init_patch): +def test_early_init_invalid_change_filter(init_patch, fake_args): config.change_filter('foobar') with pytest.raises(configexc.NoOptionError): - config.early_init() + config.early_init(fake_args) @pytest.mark.parametrize('errors', [True, False]) -def test_late_init(init_patch, monkeypatch, fake_save_manager, mocker, errors): - config.early_init() +def test_late_init(init_patch, monkeypatch, fake_save_manager, fake_args, + mocker, errors): + config.early_init(fake_args) if errors: err = configexc.ConfigErrorDesc("Error text", Exception("Exception")) errs = configexc.ConfigFileErrors("config.py", [err])