Use tox to manage virtualenvs.
Obsoletes #463. Fixes #558. Fixes part of #474. Closes #479. Closes #452.
This commit is contained in:
parent
07da31e2a0
commit
1d29e3462f
7
requirements.txt
Normal file
7
requirements.txt
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
Jinja2==2.7.3
|
||||||
|
MarkupSafe==0.23
|
||||||
|
Pygments==2.0.2
|
||||||
|
pyPEG2==2.15.1
|
||||||
|
PyYAML==3.11
|
||||||
|
colorama==0.3.3
|
||||||
|
colorlog==2.6.0
|
142
scripts/link_pyqt.py
Normal file
142
scripts/link_pyqt.py
Normal file
@ -0,0 +1,142 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||||
|
|
||||||
|
# Copyright 2014-2015 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/>.
|
||||||
|
|
||||||
|
"""Symlink PyQt into a given virtualenv."""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import argparse
|
||||||
|
import shutil
|
||||||
|
import os.path
|
||||||
|
import sys
|
||||||
|
import glob
|
||||||
|
import subprocess
|
||||||
|
|
||||||
|
|
||||||
|
class Error(Exception):
|
||||||
|
|
||||||
|
"""Exception raised when linking fails."""
|
||||||
|
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def verbose_copy(src, dst, *, follow_symlinks=True):
|
||||||
|
"""Copy function for shutil.copytree which prints copied files."""
|
||||||
|
print('{} -> {}'.format(src, dst))
|
||||||
|
shutil.copy(src, dst, follow_symlinks=follow_symlinks)
|
||||||
|
|
||||||
|
|
||||||
|
def get_ignored_files(directory, files):
|
||||||
|
"""Get the files which should be ignored for link_pyqt() on Windows."""
|
||||||
|
needed_exts = ('.py', '.dll', '.pyd', '.so')
|
||||||
|
ignored_dirs = ('examples', 'qml', 'uic', 'doc')
|
||||||
|
filtered = []
|
||||||
|
for f in files:
|
||||||
|
ext = os.path.splitext(f)[1]
|
||||||
|
full_path = os.path.join(directory, f)
|
||||||
|
if os.path.isdir(full_path) and f in ignored_dirs:
|
||||||
|
filtered.append(f)
|
||||||
|
elif (ext not in needed_exts) and os.path.isfile(full_path):
|
||||||
|
filtered.append(f)
|
||||||
|
return filtered
|
||||||
|
|
||||||
|
|
||||||
|
def link_pyqt(sys_path, venv_path):
|
||||||
|
"""Symlink the systemwide PyQt/sip into the venv.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
sys_path: The path to the system-wide site-packages.
|
||||||
|
venv_path: The path to the virtualenv site-packages.
|
||||||
|
"""
|
||||||
|
globbed_sip = (glob.glob(os.path.join(sys_path, 'sip*.so')) +
|
||||||
|
glob.glob(os.path.join(sys_path, 'sip*.pyd')))
|
||||||
|
if not globbed_sip:
|
||||||
|
raise Error("Did not find sip in {}!".format(sys_path))
|
||||||
|
|
||||||
|
files = ['PyQt5']
|
||||||
|
files += [os.path.basename(e) for e in globbed_sip]
|
||||||
|
for fn in files:
|
||||||
|
source = os.path.join(sys_path, fn)
|
||||||
|
dest = os.path.join(venv_path, fn)
|
||||||
|
if not os.path.exists(source):
|
||||||
|
raise FileNotFoundError(source)
|
||||||
|
if os.path.exists(dest):
|
||||||
|
if os.path.isdir(dest) and not os.path.islink(dest):
|
||||||
|
shutil.rmtree(dest)
|
||||||
|
else:
|
||||||
|
os.unlink(dest)
|
||||||
|
if os.name == 'nt':
|
||||||
|
if os.path.isdir(source):
|
||||||
|
shutil.copytree(source, dest, ignore=get_ignored_files,
|
||||||
|
copy_function=verbose_copy)
|
||||||
|
else:
|
||||||
|
print('{} -> {}'.format(source, dest))
|
||||||
|
shutil.copy(source, dest)
|
||||||
|
else:
|
||||||
|
print('{} -> {}'.format(source, dest))
|
||||||
|
os.symlink(source, dest)
|
||||||
|
|
||||||
|
|
||||||
|
def get_python_lib(executable):
|
||||||
|
"""Get the python site-packages directory for the given executable."""
|
||||||
|
return subprocess.check_output(
|
||||||
|
[executable, '-c', 'from distutils.sysconfig import get_python_lib\n'
|
||||||
|
'print(get_python_lib())'], universal_newlines=True).rstrip()
|
||||||
|
|
||||||
|
|
||||||
|
def get_tox_syspython(venv_path):
|
||||||
|
"""Get the system python based on a virtualenv created by tox."""
|
||||||
|
path = os.path.join(venv_path, '.tox-config1')
|
||||||
|
with open(path, encoding='ascii') as f:
|
||||||
|
line = f.readline()
|
||||||
|
_md5, sys_python = line.rstrip().split(' ')
|
||||||
|
return sys_python
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
"""Main entry point."""
|
||||||
|
parser = argparse.ArgumentParser()
|
||||||
|
parser.add_argument('path', help="Base path to the venv.")
|
||||||
|
parser.add_argument('site_path', help="Path to the venv site-packages "
|
||||||
|
"folder (for tox).", nargs='?')
|
||||||
|
parser.add_argument('--tox', help="Add when called via tox.",
|
||||||
|
action='store_true')
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
if args.tox:
|
||||||
|
sys_path = get_python_lib(get_tox_syspython(args.path))
|
||||||
|
else:
|
||||||
|
sys_path = get_python_lib(sys.executable)
|
||||||
|
|
||||||
|
if args.site_path is None:
|
||||||
|
subdir = 'Scripts' if os.name == 'nt' else 'bin'
|
||||||
|
executable = os.path.join(args.path, subdir, 'python')
|
||||||
|
venv_path = get_python_lib(executable)
|
||||||
|
else:
|
||||||
|
venv_path = args.site_path
|
||||||
|
|
||||||
|
link_pyqt(sys_path, venv_path)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
try:
|
||||||
|
main()
|
||||||
|
except Error as e:
|
||||||
|
print(str(e), file=sys.stderr)
|
||||||
|
sys.exit(1)
|
104
scripts/misc_checks.py
Normal file
104
scripts/misc_checks.py
Normal file
@ -0,0 +1,104 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||||
|
|
||||||
|
# Copyright 2014-2015 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/>.
|
||||||
|
|
||||||
|
"""Various small code checkers."""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import os.path
|
||||||
|
import argparse
|
||||||
|
import subprocess
|
||||||
|
import tokenize
|
||||||
|
import traceback
|
||||||
|
|
||||||
|
sys.path.insert(0, os.path.join(os.path.dirname(__file__), os.pardir))
|
||||||
|
|
||||||
|
from scripts import utils
|
||||||
|
|
||||||
|
|
||||||
|
def check_git():
|
||||||
|
"""Check for uncommited git files.."""
|
||||||
|
if not os.path.isdir(".git"):
|
||||||
|
print("No .git dir, ignoring")
|
||||||
|
print()
|
||||||
|
return False
|
||||||
|
untracked = []
|
||||||
|
changed = []
|
||||||
|
gitst = subprocess.check_output(['git', 'status', '--porcelain'])
|
||||||
|
gitst = gitst.decode('UTF-8').strip()
|
||||||
|
for line in gitst.splitlines():
|
||||||
|
s, name = line.split(maxsplit=1)
|
||||||
|
if s == '??' and name != '.venv/':
|
||||||
|
untracked.append(name)
|
||||||
|
elif s == 'M':
|
||||||
|
changed.append(name)
|
||||||
|
status = True
|
||||||
|
if untracked:
|
||||||
|
status = False
|
||||||
|
utils.print_col("Untracked files:", 'red')
|
||||||
|
print('\n'.join(untracked))
|
||||||
|
if changed:
|
||||||
|
status = False
|
||||||
|
utils.print_col("Uncommited changes:", 'red')
|
||||||
|
print('\n'.join(changed))
|
||||||
|
print()
|
||||||
|
return status
|
||||||
|
|
||||||
|
|
||||||
|
def check_vcs_conflict(target):
|
||||||
|
"""Check VCS conflict markers."""
|
||||||
|
try:
|
||||||
|
ok = True
|
||||||
|
for (dirpath, _dirnames, filenames) in os.walk(target):
|
||||||
|
for name in (e for e in filenames if e.endswith('.py')):
|
||||||
|
fn = os.path.join(dirpath, name)
|
||||||
|
with tokenize.open(fn) as f:
|
||||||
|
for line in f:
|
||||||
|
if any(line.startswith(c * 7) for c in '<>=|'):
|
||||||
|
print("Found conflict marker in {}".format(fn))
|
||||||
|
ok = False
|
||||||
|
print()
|
||||||
|
return ok
|
||||||
|
except Exception:
|
||||||
|
traceback.print_exc()
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
"""Main entry point."""
|
||||||
|
parser = argparse.ArgumentParser()
|
||||||
|
parser.add_argument('checker', choices=('git', 'vcs'),
|
||||||
|
help="Which checker to run.")
|
||||||
|
parser.add_argument('target', help="What to check", nargs='*')
|
||||||
|
args = parser.parse_args()
|
||||||
|
if args.checker == 'git':
|
||||||
|
ok = check_git()
|
||||||
|
return 0 if ok else 1
|
||||||
|
elif args.checker == 'vcs':
|
||||||
|
is_ok = True
|
||||||
|
for target in args.target:
|
||||||
|
ok = check_vcs_conflict(target)
|
||||||
|
if not ok:
|
||||||
|
is_ok = False
|
||||||
|
return 0 if is_ok else 1
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
sys.exit(main())
|
61
scripts/print_coverage.py
Normal file
61
scripts/print_coverage.py
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||||
|
|
||||||
|
# Copyright 2014-2015 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/>.
|
||||||
|
|
||||||
|
"""Print the unittest coverage based on the HTML output.
|
||||||
|
|
||||||
|
This can probably be deleted when switching to py.test
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
import os.path
|
||||||
|
import html.parser
|
||||||
|
|
||||||
|
|
||||||
|
class Parser(html.parser.HTMLParser):
|
||||||
|
|
||||||
|
"""HTML parser to get the percentage from coverage's HTML output."""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__(convert_charrefs=True)
|
||||||
|
self._active = False
|
||||||
|
self.percentage = None
|
||||||
|
|
||||||
|
def handle_starttag(self, tag, attrs):
|
||||||
|
if tag == 'span' and dict(attrs).get('class', None) == 'pc_cov':
|
||||||
|
self._active = True
|
||||||
|
|
||||||
|
def handle_endtag(self, _tag):
|
||||||
|
self._active = False
|
||||||
|
|
||||||
|
def handle_data(self, data):
|
||||||
|
if self._active:
|
||||||
|
self.percentage = data
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
"""Main entry point."""
|
||||||
|
p = Parser()
|
||||||
|
with open(os.path.join('htmlcov', 'index.html'), encoding='utf-8') as f:
|
||||||
|
p.feed(f.read())
|
||||||
|
print('COVERAGE: {}'.format(p.percentage))
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
78
tox.ini
Normal file
78
tox.ini
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
# Tox (http://tox.testrun.org/) is a tool for running tests
|
||||||
|
# in multiple virtualenvs. This configuration file will run the
|
||||||
|
# test suite on all supported python versions. To use it, "pip install tox"
|
||||||
|
# and then run "tox" from this directory.
|
||||||
|
|
||||||
|
[tox]
|
||||||
|
envlist = unittests,misc,pep257,flake8,pylint,pyroma,check-manifest
|
||||||
|
|
||||||
|
[testenv]
|
||||||
|
basepython = python3
|
||||||
|
|
||||||
|
[testenv:mkvenv]
|
||||||
|
commands = {envpython} scripts/link_pyqt.py --tox {envdir} {envsitepackagesdir}
|
||||||
|
envdir = {toxinidir}/.venv
|
||||||
|
usedevelop = true
|
||||||
|
|
||||||
|
[testenv:unittests]
|
||||||
|
deps =
|
||||||
|
coverage==3.7.1
|
||||||
|
commands =
|
||||||
|
{[testenv:mkvenv]commands}
|
||||||
|
{envpython} -m coverage erase
|
||||||
|
{envpython} -m coverage run --branch --source=qutebrowser -m unittest
|
||||||
|
{envpython} -m coverage html
|
||||||
|
{envpython} scripts/print_coverage.py
|
||||||
|
|
||||||
|
[testenv:misc]
|
||||||
|
commands =
|
||||||
|
{envpython} scripts/misc_checks.py git
|
||||||
|
{envpython} scripts/misc_checks.py vcs qutebrowser scripts
|
||||||
|
|
||||||
|
[testenv:pylint]
|
||||||
|
skip_install = true
|
||||||
|
setenv = PYTHONPATH={toxinidir}/scripts:{env:PYTHONPATH:}
|
||||||
|
deps =
|
||||||
|
-rrequirements.txt
|
||||||
|
astroid==1.3.6
|
||||||
|
beautifulsoup4==4.3.2
|
||||||
|
pylint==1.4.3
|
||||||
|
logilab-common==0.63.2
|
||||||
|
six==1.9.0
|
||||||
|
commands =
|
||||||
|
{[testenv:mkvenv]commands}
|
||||||
|
{envdir}/bin/pylint scripts qutebrowser --rcfile=.pylintrc --output-format=colorized --reports=no
|
||||||
|
|
||||||
|
[testenv:pep257]
|
||||||
|
skip_install = true
|
||||||
|
deps = pep257==0.5.0
|
||||||
|
# Disabled checks:
|
||||||
|
# D102: Docstring missing, will be handled by others
|
||||||
|
# D209: Blank line before closing """ (removed from PEP257)
|
||||||
|
# D402: First line should not be function's signature (false-positives)
|
||||||
|
commands = {envdir}/bin/pep257 scripts qutebrowser --ignore=D102,D209,D402 '--match=(?!resources|test_content_disposition).*\.py'
|
||||||
|
|
||||||
|
[testenv:flake8]
|
||||||
|
deps =
|
||||||
|
-rrequirements.txt
|
||||||
|
pyflakes==0.8.1
|
||||||
|
pep8==1.5.7 # rq.filter: <=1.5.7
|
||||||
|
flake8==2.4.0
|
||||||
|
commands =
|
||||||
|
{[testenv:mkvenv]commands}
|
||||||
|
{envdir}/bin/flake8 scripts qutebrowser --config=.flake8
|
||||||
|
|
||||||
|
[testenv:pyroma]
|
||||||
|
deps =
|
||||||
|
pyroma==1.7
|
||||||
|
docutils==0.12
|
||||||
|
commands =
|
||||||
|
{[testenv:mkvenv]commands}
|
||||||
|
{envdir}/bin/pyroma .
|
||||||
|
|
||||||
|
[testenv:check-manifest]
|
||||||
|
deps =
|
||||||
|
check-manifest==0.23
|
||||||
|
commands =
|
||||||
|
{[testenv:mkvenv]commands}
|
||||||
|
{envdir}/bin/check-manifest --ignore 'qutebrowser/git-commit-id,qutebrowser/html/doc,qutebrowser/html/doc/*,*/__pycache__'
|
Loading…
Reference in New Issue
Block a user