253 lines
8.7 KiB
Python
253 lines
8.7 KiB
Python
#!/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, asciidoc2html
|
|
|
|
|
|
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('--virtualenv',
|
|
action='store_true',
|
|
help="Use virtualenv instead of venv.")
|
|
parser.add_argument('--asciidoc', help="Full path to python and "
|
|
"asciidoc.py. If not given, it's searched in PATH.",
|
|
nargs=2, required=False,
|
|
metavar=('PYTHON', 'ASCIIDOC'))
|
|
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:
|
|
"""Run the 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 "python3 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 = [
|
|
venv_dir / '.tox-config1', # tox
|
|
venv_dir / 'pyvenv.cfg', # venv
|
|
venv_dir / 'Scripts', # Windows
|
|
venv_dir / 'bin', # Linux
|
|
]
|
|
|
|
if not any(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, use_virtualenv: bool = False) -> None:
|
|
"""Create a new virtualenv."""
|
|
if use_virtualenv:
|
|
utils.print_col('$ python3 -m virtualenv {}'.format(venv_dir), 'blue')
|
|
try:
|
|
subprocess.run([sys.executable, '-m', 'virtualenv', venv_dir],
|
|
check=True)
|
|
except subprocess.CalledProcessError as e:
|
|
utils.print_col("virtualenv failed, exiting", 'red')
|
|
sys.exit(e.returncode)
|
|
else:
|
|
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 regenerate_docs(venv_dir: pathlib.Path, asciidoc: typing.Tuple[str, str]):
|
|
"""Regenerate docs using asciidoc."""
|
|
utils.print_title("Generating documentation")
|
|
asciidoc2html.run(website=False, asciidoc=asciidoc)
|
|
|
|
|
|
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)
|
|
elif args.pyqt_type == 'link' and args.pyqt_version != 'auto':
|
|
utils.print_col('The --pyqt-version option is not available when '
|
|
'linking a system-wide install.', 'red')
|
|
sys.exit(1)
|
|
|
|
if not args.keep:
|
|
utils.print_title("Creating virtual environment")
|
|
delete_old_venv(venv_dir)
|
|
create_venv(venv_dir, use_virtualenv=args.virtualenv)
|
|
|
|
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)
|
|
regenerate_docs(venv_dir, args.asciidoc)
|
|
|
|
|
|
if __name__ == '__main__':
|
|
main()
|