Skip to content

Commit 452e6e5

Browse files
Require pyperformance to be installed before running (#151)
A number of things get simpler if we require pyperformance to always be installed. In particular, we no longer need to create a top-level venv all the time. This change also includes a convenience script, dev.py, to create a venv if needed.
1 parent 7ee113a commit 452e6e5

File tree

12 files changed

+199
-104
lines changed

12 files changed

+199
-104
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ pyperformance.egg-info/
1414

1515
# Created by the pyperformance script
1616
venv/
17+
.venvs/
1718

1819
# Created by the tox program
1920
.tox/

MANIFEST.in

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,6 @@ include README.rst
55
include TODO.rst
66
include requirements.in
77
include requirements.txt
8-
include runtests.py
9-
include tox.ini
108

119
include doc/*.rst doc/images/*.png doc/images/*.jpg
1210
include doc/conf.py doc/Makefile doc/make.bat

dev.py

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
# A script for running pyperformance out of the repo in dev-mode.
2+
3+
import os.path
4+
import sys
5+
6+
7+
REPO_ROOT = os.path.dirname(os.path.abspath(__file__))
8+
VENVS = os.path.join(REPO_ROOT, '.venvs')
9+
10+
11+
def ensure_venv_ready(venvroot=None, kind='dev'):
12+
if sys.prefix != sys.base_prefix:
13+
assert os.path.exists(os.path.join(sys.prefix, 'pyvenv.cfg'))
14+
venvroot = sys.prefix
15+
python = sys.executable
16+
readyfile = os.path.join(sys.prefix, 'READY')
17+
isready = os.path.exists(readyfile)
18+
else:
19+
import venv
20+
if not venvroot:
21+
import sysconfig
22+
if sysconfig.is_python_build():
23+
sys.exit('please install your built Python first (or pass it using --python)')
24+
# XXX Handle other implementations too?
25+
base = os.path.join(VENVS, kind or 'dev')
26+
major, minor = sys.version_info[:2]
27+
pyloc = ((os.path.abspath(sys.executable)
28+
).partition(os.path.sep)[2].lstrip(os.path.sep)
29+
).replace(os.path.sep, '-')
30+
venvroot = f'{base}-{major}.{minor}-{pyloc}'
31+
# Make sure the venv exists.
32+
readyfile = os.path.join(venvroot, 'READY')
33+
isready = os.path.exists(readyfile)
34+
if not isready:
35+
relroot = os.path.relpath(venvroot)
36+
if not os.path.exists(venvroot):
37+
print(f'creating venv at {relroot}...')
38+
else:
39+
print(f'venv {relroot} not ready, re-creating...')
40+
venv.create(venvroot, with_pip=True, clear=True)
41+
else:
42+
assert os.path.exists(os.path.join(venvroot, 'pyvenv.cfg'))
43+
# Return the venv's Python executable.
44+
binname = 'Scripts' if os.name == 'nt' else 'bin'
45+
exename = os.path.basename(sys.executable)
46+
python = os.path.join(venvroot, binname, exename)
47+
48+
# Now make sure the venv has pyperformance installed.
49+
if not isready:
50+
import subprocess
51+
relroot = os.path.relpath(venvroot)
52+
print(f'venv {relroot} not ready, installing dependencies...')
53+
proc = subprocess.run(
54+
[python, '-m', 'pip', 'install',
55+
'--upgrade',
56+
'--editable', REPO_ROOT],
57+
)
58+
if proc.returncode != 0:
59+
sys.exit('ERROR: install failed')
60+
with open(readyfile, 'w'):
61+
pass
62+
print('...venv {relroot} ready!')
63+
64+
return python
65+
66+
67+
def main(venvroot=None):
68+
python = ensure_venv_ready(venvroot)
69+
if python != sys.executable:
70+
# Now re-run using the venv.
71+
os.execv(python, [python, *sys.argv])
72+
# <unreachable>
73+
74+
# Now run pyperformance.
75+
import pyperformance.cli
76+
pyperformance.cli.main()
77+
78+
79+
if __name__ == '__main__':
80+
main()

pyperformance/__init__.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,18 @@
1111

1212

1313
def is_installed():
14-
parent = os.path.dirname(PKG_ROOT)
15-
if not os.path.exists(os.path.join(parent, 'setup.py')):
14+
if not is_dev():
1615
return True
1716
if _is_venv():
1817
return True
1918
return _is_devel_install()
2019

2120

21+
def is_dev():
22+
parent = os.path.dirname(PKG_ROOT)
23+
return os.path.exists(os.path.join(parent, 'setup.py'))
24+
25+
2226
def _is_venv():
2327
if sys.base_prefix == sys.prefix:
2428
return False

pyperformance/cli.py

Lines changed: 24 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
11
import argparse
2-
import contextlib
32
import logging
43
import os.path
54
import sys
65

7-
from pyperformance import _utils, is_installed
8-
from pyperformance.venv import exec_in_virtualenv, cmd_venv
6+
from pyperformance import _utils, is_installed, is_dev
7+
from pyperformance.venv import cmd_venv
98

109

1110
def comma_separated(values):
@@ -136,19 +135,21 @@ def parse_args():
136135
cmds.append(cmd)
137136

138137
# venv
139-
cmd = subparsers.add_parser('venv',
138+
venv_common = argparse.ArgumentParser(add_help=False)
139+
venv_common.add_argument("--venv", help="Path to the virtual environment")
140+
cmd = subparsers.add_parser('venv', parents=[venv_common],
140141
help='Actions on the virtual environment')
141142
cmd.set_defaults(venv_action='show')
142143
venvsubs = cmd.add_subparsers(dest="venv_action")
143-
cmd = venvsubs.add_parser('show')
144+
cmd = venvsubs.add_parser('show', parents=[venv_common])
144145
cmds.append(cmd)
145-
cmd = venvsubs.add_parser('create')
146+
cmd = venvsubs.add_parser('create', parents=[venv_common])
146147
filter_opts(cmd, allow_no_benchmarks=True)
147148
cmds.append(cmd)
148-
cmd = venvsubs.add_parser('recreate')
149+
cmd = venvsubs.add_parser('recreate', parents=[venv_common])
149150
filter_opts(cmd, allow_no_benchmarks=True)
150151
cmds.append(cmd)
151-
cmd = venvsubs.add_parser('remove')
152+
cmd = venvsubs.add_parser('remove', parents=[venv_common])
152153
cmds.append(cmd)
153154

154155
for cmd in cmds:
@@ -158,15 +159,9 @@ def parse_args():
158159
"names that are inherited from the parent "
159160
"environment when running benchmarking "
160161
"subprocesses."))
161-
cmd.add_argument("--inside-venv", action="store_true",
162-
help=("Option for internal usage only, don't use "
163-
"it directly. Notice that we are already "
164-
"inside the virtual environment."))
165162
cmd.add_argument("-p", "--python",
166163
help="Python executable (default: use running Python)",
167164
default=sys.executable)
168-
cmd.add_argument("--venv",
169-
help="Path to the virtual environment")
170165

171166
options = parser.parse_args()
172167

@@ -198,21 +193,6 @@ def parse_args():
198193
return (parser, options)
199194

200195

201-
@contextlib.contextmanager
202-
def _might_need_venv(options):
203-
try:
204-
if not is_installed():
205-
# Always force a local checkout to be installed.
206-
assert not options.inside_venv
207-
raise ModuleNotFoundError
208-
yield
209-
except ModuleNotFoundError:
210-
if not options.inside_venv:
211-
print('switching to a venv.', flush=True)
212-
exec_in_virtualenv(options)
213-
raise # re-raise
214-
215-
216196
def _manifest_from_options(options):
217197
from pyperformance import _manifest
218198
return _manifest.load_manifest(options.manifest)
@@ -253,12 +233,18 @@ def _select_benchmarks(raw, manifest):
253233

254234

255235
def _main():
236+
if not is_installed():
237+
# Always require a local checkout to be installed.
238+
print('ERROR: pyperformance should not be run without installing first')
239+
if is_dev():
240+
print('(consider using the dev.py script)')
241+
sys.exit(1)
242+
256243
parser, options = parse_args()
257244

258245
if options.action == 'venv':
259246
if options.venv_action in ('create', 'recreate'):
260-
with _might_need_venv(options):
261-
benchmarks = _benchmarks_from_options(options)
247+
benchmarks = _benchmarks_from_options(options)
262248
else:
263249
benchmarks = None
264250
cmd_venv(options, benchmarks)
@@ -280,23 +266,19 @@ def _main():
280266
cmd_show(options)
281267
sys.exit()
282268
elif options.action == 'run':
283-
with _might_need_venv(options):
284-
from pyperformance.cli_run import cmd_run
285-
benchmarks = _benchmarks_from_options(options)
269+
from pyperformance.cli_run import cmd_run
270+
benchmarks = _benchmarks_from_options(options)
286271
cmd_run(options, benchmarks)
287272
elif options.action == 'compare':
288-
with _might_need_venv(options):
289-
from pyperformance.compare import cmd_compare
273+
from pyperformance.compare import cmd_compare
290274
cmd_compare(options)
291275
elif options.action == 'list':
292-
with _might_need_venv(options):
293-
from pyperformance.cli_run import cmd_list
294-
benchmarks = _benchmarks_from_options(options)
276+
from pyperformance.cli_run import cmd_list
277+
benchmarks = _benchmarks_from_options(options)
295278
cmd_list(options, benchmarks)
296279
elif options.action == 'list_groups':
297-
with _might_need_venv(options):
298-
from pyperformance.cli_run import cmd_list_groups
299-
manifest = _manifest_from_options(options)
280+
from pyperformance.cli_run import cmd_list_groups
281+
manifest = _manifest_from_options(options)
300282
cmd_list_groups(manifest)
301283
else:
302284
parser.print_help()

pyperformance/compile.py

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -506,7 +506,6 @@ def compile_install(self):
506506
# First: remove everything
507507
self.safe_rmdir(self.conf.build_dir)
508508
self.safe_rmdir(self.conf.prefix)
509-
self.safe_rmdir(self.conf.venv)
510509

511510
self.python.patch(self.patch)
512511
self.python.compile_install()
@@ -522,18 +521,22 @@ def create_venv(self):
522521
if not python or not exists:
523522
python = sys.executable
524523
cmd = [python, '-u', '-m', 'pyperformance', 'venv', 'recreate',
525-
'--benchmarks', '<NONE>']
526-
if self.conf.venv:
527-
cmd.extend(('--venv', self.conf.venv))
524+
'--venv', self.conf.venv,
525+
'--benchmarks', '<NONE>',
526+
]
528527
if self.options.inherit_environ:
529528
cmd.append('--inherit-environ=%s' % ','.join(self.options.inherit_environ))
530529
exitcode = self.run_nocheck(*cmd)
531530
if exitcode:
532531
sys.exit(EXIT_VENV_ERROR)
532+
binname = 'Scripts' if os.name == 'nt' else 'bin'
533+
base = os.path.basename(python)
534+
return os.path.join(self.conf.venv, binname, base)
533535

534-
def run_benchmark(self):
536+
def run_benchmark(self, python=None):
535537
self.safe_makedirs(os.path.dirname(self.filename))
536-
python = self.python.program
538+
if not python:
539+
python = self.python.program
537540
if self._dryrun:
538541
python = sys.executable
539542
cmd = [python, '-u',
@@ -549,8 +552,6 @@ def run_benchmark(self):
549552
cmd.append('--benchmarks=%s' % self.conf.benchmarks)
550553
if self.conf.affinity:
551554
cmd.extend(('--affinity', self.conf.affinity))
552-
if self.conf.venv:
553-
cmd.extend(('--venv', self.conf.venv))
554555
if self.conf.debug:
555556
cmd.append('--debug-single-value')
556557
exitcode = self.run_nocheck(*cmd)
@@ -709,9 +710,12 @@ def compile_bench(self):
709710
except SystemExit:
710711
sys.exit(EXIT_COMPILE_ERROR)
711712

712-
self.create_venv()
713+
if self.conf.venv:
714+
python = self.create_venv()
715+
else:
716+
python = None
713717

714-
failed = self.run_benchmark()
718+
failed = self.run_benchmark(python)
715719
if not failed and self.conf.upload:
716720
self.upload()
717721
return failed
@@ -992,8 +996,6 @@ def cmd_compile(options):
992996
conf.update = False
993997
if options.no_tune:
994998
conf.system_tune = False
995-
if options.venv:
996-
conf.venv = options.venv
997999
bench = BenchmarkRevision(conf, options.revision, options.branch,
9981000
patch=options.patch, options=options)
9991001
bench.main()

pyperformance/run.py

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -60,20 +60,15 @@ def run_benchmarks(should_run, python, options):
6060

6161
benchmarks = {}
6262
venvs = set()
63-
if options.venv:
64-
venv = _venv.VirtualEnvironment(
65-
options.python,
66-
options.venv,
67-
inherit_environ=options.inherit_environ,
68-
)
69-
venv.ensure(refresh=False)
70-
venvs.add(venv.get_path())
63+
if sys.prefix != sys.base_prefix:
64+
venvs.add(sys.prefix)
65+
common_venv = None # XXX Add the ability to combine venvs.
7166
for i, bench in enumerate(to_run):
7267
bench_runid = runid._replace(bench=bench)
7368
assert bench_runid.name, (bench, bench_runid)
7469
venv = _venv.VirtualEnvironment(
7570
options.python,
76-
options.venv,
71+
common_venv,
7772
inherit_environ=options.inherit_environ,
7873
name=bench_runid.name,
7974
usebase=True,

pyperformance/tests/__init__.py

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -116,10 +116,7 @@ def tearDownClass(cls):
116116
def venv_python(self):
117117
return resolve_venv_python(self._VENV)
118118

119-
def run_pyperformance(self, cmd, *args, invenv=True):
120-
if invenv:
121-
assert self._VENV
122-
args += ('--venv', self._VENV)
119+
def run_pyperformance(self, cmd, *args):
123120
run_cmd(
124121
sys.executable, '-u', '-m', 'pyperformance',
125122
cmd, *args,

0 commit comments

Comments
 (0)