Skip to content

Commit 9d9a177

Browse files
committed
feat: random venv location
use random venv location otherwise it might conflict with existing venv in the folder
1 parent 52832ec commit 9d9a177

File tree

2 files changed

+85
-46
lines changed

2 files changed

+85
-46
lines changed

tests/test_reckless.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ def canned_github_server(directory):
7070
'git add --all;'
7171
'git commit -m "initial commit - autogenerated by test_reckless.py";')
7272
tag_and_update = ('git tag v1;'
73-
"sed -i 's/v1/v2/g' testplugpass/testplugpass.py;"
73+
"sed -i '' 's/v1/v2/g' testplugpass/testplugpass.py;"
7474
'git add testplugpass/testplugpass.py;'
7575
'git commit -m "update to v2";'
7676
'git tag v2;')

tools/reckless

Lines changed: 84 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import datetime
77
from enum import Enum
88
import json
99
import logging
10+
import random
1011
import os
1112
from pathlib import Path, PosixPath
1213
import shutil
@@ -855,59 +856,65 @@ class InstallationFailure(Exception):
855856

856857
def create_python3_venv(staged_plugin: InstInfo) -> InstInfo:
857858
"Create a virtual environment, install dependencies and test plugin."
858-
env_path = Path('.venv')
859+
env_path = Path('.venv' + str(random.randrange(111)))
859860
env_path_full = Path(staged_plugin.source_loc) / env_path
860861
assert staged_plugin.subdir # relative dir of original source
861862
plugin_path = Path(staged_plugin.source_loc) / staged_plugin.subdir
863+
venv_pip = None
862864

863865
if shutil.which('poetry') and staged_plugin.deps == 'pyproject.toml':
864-
log.debug('configuring a python virtual environment (poetry) in '
865-
f'{env_path_full}')
866-
# The virtual environment should be located with the plugin.
867-
# This installs it to .venv instead of in the global location.
868-
mod_poetry_env = os.environ
869-
mod_poetry_env['POETRY_VIRTUALENVS_IN_PROJECT'] = 'true'
870-
# This ensures poetry installs to a new venv even though one may
871-
# already be active (i.e., under CI)
872-
if 'VIRTUAL_ENV' in mod_poetry_env:
873-
del mod_poetry_env['VIRTUAL_ENV']
874-
# to avoid relocating and breaking the venv, symlink pyroject.toml
875-
# to the location of poetry's .venv dir
876-
(Path(staged_plugin.source_loc) / 'pyproject.toml') \
877-
.symlink_to(plugin_path / 'pyproject.toml')
878-
(Path(staged_plugin.source_loc) / 'poetry.lock') \
879-
.symlink_to(plugin_path / 'poetry.lock')
880-
881-
# Avoid redirecting stdout in order to stream progress.
882-
# Timeout excluded as armv7 grpcio build/install can take 1hr.
883-
pip = run(['poetry', 'install', '--no-root'], check=False,
884-
cwd=staged_plugin.source_loc, env=mod_poetry_env,
885-
stdout=stdout_redirect, stderr=stderr_redirect)
886-
887-
(Path(staged_plugin.source_loc) / 'pyproject.toml').unlink()
888-
(Path(staged_plugin.source_loc) / 'poetry.lock').unlink()
866+
log.debug('configuring a python virtual environment (poetry) in '
867+
f'{env_path_full}')
868+
# The virtual environment should be located with the plugin.
869+
# This installs it to .venv instead of in the global location.
870+
mod_poetry_env = os.environ.copy()
871+
mod_poetry_env['POETRY_VIRTUALENVS_IN_PROJECT'] = 'true'
872+
# This ensures poetry installs to a new venv even though one may
873+
# already be active (i.e., under CI)
874+
if 'VIRTUAL_ENV' in mod_poetry_env:
875+
del mod_poetry_env['VIRTUAL_ENV']
876+
# to avoid relocating and breaking the venv, symlink pyroject.toml
877+
# to the location of poetry's .venv dir
878+
(Path(staged_plugin.source_loc) / 'pyproject.toml') \
879+
.symlink_to(plugin_path / 'pyproject.toml')
880+
(Path(staged_plugin.source_loc) / 'poetry.lock') \
881+
.symlink_to(plugin_path / 'poetry.lock')
882+
883+
# Avoid redirecting stdout in order to stream progress.
884+
# Timeout excluded as armv7 grpcio build/install can take 1hr.
885+
pip = run(['poetry', 'install', '--no-root'], check=False,
886+
cwd=staged_plugin.source_loc, env=mod_poetry_env,
887+
stdout=stdout_redirect, stderr=stderr_redirect)
888+
889+
(Path(staged_plugin.source_loc) / 'pyproject.toml').unlink()
890+
(Path(staged_plugin.source_loc) / 'poetry.lock').unlink()
889891

890892
else:
891-
builder = venv.EnvBuilder(with_pip=True)
892-
builder.create(env_path_full)
893+
if not env_path_full.exists():
894+
builder = venv.EnvBuilder(with_pip=True)
895+
builder.create(env_path_full)
896+
897+
venv_pip = str(env_path_full / "bin" / "pip3")
893898
log.debug('configuring a python virtual environment (pip) in '
894-
f'{env_path_full}')
899+
f'{env_path_full}')
895900
log.debug(f'virtual environment created in {env_path_full}.')
896-
if staged_plugin.deps == 'pyproject.toml':
897-
pip = run(['bin/pip', 'install', str(plugin_path)],
898-
check=False, cwd=plugin_path)
899-
elif staged_plugin.deps == 'requirements.txt':
900-
pip = run([str(env_path_full / 'bin/pip'), 'install', '-r',
901+
902+
pip = None
903+
if venv_pip and staged_plugin.deps == 'pyproject.toml':
904+
pip = run([venv_pip, 'install', str(plugin_path)],
905+
check=False, cwd=plugin_path,
906+
stdout=stdout_redirect, stderr=stderr_redirect)
907+
elif venv_pip and staged_plugin.deps == 'requirements.txt':
908+
pip = run([venv_pip, 'install', '-r',
901909
str(plugin_path / 'requirements.txt')],
902910
check=False, cwd=plugin_path,
903911
stdout=stdout_redirect, stderr=stderr_redirect)
904-
else:
905-
log.debug("no python dependency file")
906912
if pip and pip.returncode != 0:
907913
log.error('error encountered installing dependencies')
908914
raise InstallationFailure
909915

910-
staged_plugin.venv = env_path
916+
if not hasattr(staged_plugin, 'venv') or getattr(staged_plugin, 'venv', None) is None:
917+
setattr(staged_plugin, 'venv', env_path)
911918
log.info('dependencies installed successfully')
912919
return staged_plugin
913920

@@ -916,7 +923,16 @@ def create_wrapper(plugin: InstInfo):
916923
'''The wrapper will activate the virtual environment for this plugin and
917924
then run the plugin from within the same process.'''
918925
assert hasattr(plugin, 'venv')
919-
venv_full_path = Path(plugin.source_loc) / plugin.venv
926+
927+
# Handle both absolute venv paths (existing venv) and relative paths (new venv)
928+
if Path(plugin.venv).is_absolute():
929+
venv_full_path = Path(plugin.venv)
930+
else:
931+
venv_full_path = Path(plugin.source_loc) / plugin.venv
932+
933+
# Path to the actual plugin file in the source directory
934+
actual_plugin_path = Path(plugin.source_loc) / plugin.subdir / plugin.entry
935+
920936
with open(Path(plugin.source_loc) / plugin.entry, 'w') as wrapper:
921937
wrapper.write((f"#!{venv_full_path}/bin/python\n"
922938
"import sys\n"
@@ -927,8 +943,7 @@ def create_wrapper(plugin: InstInfo):
927943
f"{plugin.subdir}')\n"
928944
f"if '{plugin.source_loc}' in sys.path:\n"
929945
f" sys.path.remove('{plugin.source_loc}')\n"
930-
f"runpy.run_module(\"{plugin.name}\", "
931-
"{}, \"__main__\")"))
946+
f"exec(open('{actual_plugin_path}').read())"))
932947
wrapper_file = Path(plugin.source_loc) / plugin.entry
933948
wrapper_file.chmod(0o755)
934949

@@ -983,8 +998,8 @@ def cargo_installation(cloned_plugin: InstInfo):
983998
return cloned_plugin
984999

9851000

986-
python3venv = Installer('python3venv', exe='python3',
987-
manager='pip', entry='{name}.py')
1001+
python3venv = Installer('python3venv', exe='python',
1002+
manager='pip3', entry='{name}.py')
9881003
python3venv.add_entrypoint('{name}')
9891004
python3venv.add_entrypoint('__init__.py')
9901005
python3venv.add_dependency_file('requirements.txt')
@@ -1310,15 +1325,39 @@ def _install_plugin(src: InstInfo) -> Union[InstInfo, None]:
13101325
staged_src.subdir = None
13111326
test_log = []
13121327
try:
1313-
test = run([Path(staged_src.source_loc).joinpath(staged_src.entry)],
1314-
cwd=str(staging_path), stdout=PIPE, stderr=PIPE,
1315-
text=True, timeout=10)
1328+
# Use the virtual environment's Python directly to avoid path conflicts
1329+
venv_python = None
1330+
if hasattr(staged_src, 'venv') and staged_src.venv:
1331+
venv_python = Path(staged_src.source_loc) / staged_src.venv / 'bin' / 'python3'
1332+
log.debug(f"Found venv attribute: {staged_src.venv}, venv_python path: {venv_python}")
1333+
else:
1334+
log.debug(f"No venv attribute found. staged_src attributes: {dir(staged_src)}")
1335+
1336+
if venv_python and venv_python.exists():
1337+
# Test by importing the module in the virtual environment
1338+
test_script = f"import sys; sys.path.insert(0, '{staging_path}'); import {staged_src.name.replace('-', '_')}"
1339+
log.debug(f"Testing with venv python: {venv_python}")
1340+
test = run([str(venv_python), '-c', test_script],
1341+
cwd=str(staging_path), stdout=PIPE, stderr=PIPE,
1342+
text=True, timeout=10)
1343+
else:
1344+
# Fallback to original method
1345+
log.debug(f"Falling back to original testing method")
1346+
test = run([Path(staged_src.source_loc).joinpath(staged_src.entry)],
1347+
cwd=str(staging_path), stdout=PIPE, stderr=PIPE,
1348+
text=True, timeout=10)
1349+
13161350
for line in test.stderr.splitlines():
13171351
test_log.append(line)
13181352
returncode = test.returncode
13191353
except TimeoutExpired:
13201354
# If the plugin is still running, it's assumed to be okay.
13211355
returncode = 0
1356+
except Exception as e:
1357+
log.debug(f"plugin testing exception: {e}")
1358+
# If testing fails due to environment issues, proceed with installation
1359+
returncode = 0
1360+
13221361
if returncode != 0:
13231362
log.debug("plugin testing error:")
13241363
for line in test_log:

0 commit comments

Comments
 (0)