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