diff options
author | Florian Bruhin <me@the-compiler.org> | 2020-01-09 21:14:41 +0100 |
---|---|---|
committer | Florian Bruhin <me@the-compiler.org> | 2020-01-10 17:06:20 +0100 |
commit | 30fdde8a450f72d40ded7db323fb99f4a066cec3 (patch) | |
tree | 2263c5ba88e4808b6491529eb5c1a25a495c955b /scripts/mkvenv.py | |
parent | 7dfb403478d7a1f27ffcd50263f1e9a43298a0dd (diff) | |
download | qutebrowser-30fdde8a450f72d40ded7db323fb99f4a066cec3.tar.gz qutebrowser-30fdde8a450f72d40ded7db323fb99f4a066cec3.zip |
Replace tox -e mkvenv by a mkvenv.py script
This was mainly needed because PyQt 5.14.1 is a manylinux2014 wheel and needs
an updated pip, see #5013
Diffstat (limited to 'scripts/mkvenv.py')
-rw-r--r-- | scripts/mkvenv.py | 218 |
1 files changed, 218 insertions, 0 deletions
diff --git a/scripts/mkvenv.py b/scripts/mkvenv.py new file mode 100644 index 000000000..55f08a618 --- /dev/null +++ b/scripts/mkvenv.py @@ -0,0 +1,218 @@ +#!/usr/bin/env python3 +# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: + +# Copyright 2020 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/>. + + +"""Create a local virtualenv with a PyQt install.""" + +import argparse +import pathlib +import sys +import os +import os.path +import typing +import shutil +import venv +import subprocess + +sys.path.insert(0, os.path.join(os.path.dirname(__file__), os.pardir)) +from scripts import utils, link_pyqt + + +REPO_ROOT = pathlib.Path(__file__).parent.parent + + +def parse_args() -> argparse.Namespace: + """Parse commandline arguments.""" + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument('--keep', + action='store_true', + help="Reuse an existing virtualenv.") + parser.add_argument('--venv-dir', + default='.venv', + help="Where to place the virtualenv.") + parser.add_argument('--pyqt-version', + choices=pyqt_versions(), + default='auto', + help="PyQt version to install") + parser.add_argument('--pyqt-type', + choices=['binary', 'source', 'link'], + default='binary', + help="How to install PyQt/Qt.") + parser.add_argument('--tox-error', + action='store_true', + help=argparse.SUPPRESS) + return parser.parse_args() + + +def pyqt_versions() -> typing.List[str]: + """Get a list of all available PyQt versions. + + The list is based on the filenames of misc/requirements/ files. + """ + version_set = set() + + requirements_dir = REPO_ROOT / 'misc' / 'requirements' + for req in requirements_dir.glob('requirements-pyqt-*.txt'): + version_set.add(req.stem.split('-')[-1]) + + versions = sorted(version_set, + key=lambda v: [int(c) for c in v.split('.')]) + return versions + ['auto'] + + +def run_venv(venv_dir: pathlib.Path, executable, *args: str) -> None: + """Runt he given command inside the virtualenv.""" + subdir = 'Scripts' if os.name == 'nt' else 'bin' + + try: + subprocess.run([str(venv_dir / subdir / executable)] + + [str(arg) for arg in args], check=True) + except subprocess.CalledProcessError as e: + utils.print_col("Subprocess failed, exiting", 'red') + sys.exit(e.returncode) + + +def pip_install(venv_dir: pathlib.Path, *args: str) -> None: + """Run a pip install command inside the virtualenv.""" + arg_str = ' '.join(str(arg) for arg in args) + utils.print_col('venv$ pip install {}'.format(arg_str), 'blue') + run_venv(venv_dir, 'python3', '-m', 'pip', 'install', *args) + + +def show_tox_error(pyqt_type: str) -> None: + """DIsplay an error when invoked from tox.""" + if pyqt_type == 'link': + env = 'mkvenv' + args = ' --pyqt-type link' + elif pyqt_type == 'binary': + env = 'mkvenv-pypi' + args = '' + else: + raise AssertionError + + print() + utils.print_col('tox -e {} is deprecated. Please use scripts/mkvenv.py{} ' + 'instead.'.format(env, args), 'red') + print() + + +def delete_old_venv(venv_dir: pathlib.Path) -> None: + """Remove an existing virtualenv directory.""" + if not venv_dir.exists(): + return + + markers = ['.tox-config1', 'pyvenv.cfg'] + if not any((venv_dir / m).exists() for m in markers): + utils.print_col('{} does not look like a virtualenv, ' + 'cowardly refusing to remove it.'.format(venv_dir), + 'red') + sys.exit(1) + + utils.print_col('$ rm -r {}'.format(venv_dir), 'blue') + shutil.rmtree(str(venv_dir)) + + +def create_venv(venv_dir: pathlib.Path) -> None: + """Create a new virtualenv.""" + utils.print_col('$ python3 -m venv {}'.format(venv_dir), 'blue') + venv.create(str(venv_dir), with_pip=True) + + +def upgrade_pip(venv_dir: pathlib.Path) -> None: + """Upgrade pip inside a virtualenv.""" + utils.print_title("Upgrading pip") + pip_install(venv_dir, '-U', 'pip') + + +def pyqt_requirements_file(version: str): + """Get the filename of the requirements file for the given PyQt version.""" + suffix = '' if version == 'auto' else '-{}'.format(version) + return (REPO_ROOT / 'misc' / 'requirements' / + 'requirements-pyqt{}.txt'.format(suffix)) + + +def install_pyqt_binary(venv_dir: pathlib.Path, version: str) -> None: + """Install PyQt from a binary wheel.""" + utils.print_title("Installing PyQt from binary") + utils.print_col("No proprietary codec support will be available in " + "qutebrowser.", 'bold') + pip_install(venv_dir, '-r', pyqt_requirements_file(version), + '--only-binary', 'PyQt5,PyQtWebEngine') + + +def install_pyqt_source(venv_dir: pathlib.Path, version: str) -> None: + """Install PyQt from the source tarball.""" + utils.print_title("Installing PyQt from sources") + pip_install(venv_dir, '-r', pyqt_requirements_file(version), + '--verbose', '--no-binary', 'PyQt5,PyQtWebEngine') + + +def install_pyqt_link(venv_dir: pathlib.Path) -> None: + """Install PyQt by linking a system-wide install.""" + utils.print_title("Linking system-wide PyQt") + lib_path = link_pyqt.get_venv_lib_path(str(venv_dir)) + link_pyqt.link_pyqt(sys.executable, lib_path) + + +def install_requirements(venv_dir: pathlib.Path) -> None: + """Install qutebrowser's requirement.txt.""" + utils.print_title("Installing other qutebrowser dependencies") + requirements_file = REPO_ROOT / 'requirements.txt' + pip_install(venv_dir, '-r', str(requirements_file)) + + +def install_qutebrowser(venv_dir: pathlib.Path) -> None: + """Install qutebrowser itself as an editable install.""" + utils.print_title("Installing qutebrowser") + pip_install(venv_dir, '-e', str(REPO_ROOT)) + + +def main() -> None: + """Install qutebrowser in a virtualenv..""" + args = parse_args() + venv_dir = pathlib.Path(args.venv_dir) + utils.change_cwd() + + if args.tox_error: + show_tox_error(args.pyqt_type) + sys.exit(1) + + if not args.keep: + utils.print_title("Creating virtual environment") + delete_old_venv(venv_dir) + create_venv(venv_dir) + + upgrade_pip(venv_dir) + + if args.pyqt_type == 'binary': + install_pyqt_binary(venv_dir, args.pyqt_version) + elif args.pyqt_type == 'source': + install_pyqt_source(venv_dir, args.pyqt_version) + elif args.pyqt_type == 'link': + install_pyqt_link(venv_dir) + else: + raise AssertionError + + install_requirements(venv_dir) + install_qutebrowser(venv_dir) + + +if __name__ == '__main__': + main() |