Skip to content

Commit

Permalink
Match path resolution in pyvenv.cfg (#24)
Browse files Browse the repository at this point in the history
  • Loading branch information
brettcannon authored Mar 9, 2023
1 parent 486198d commit 0c57a44
Show file tree
Hide file tree
Showing 3 changed files with 62 additions and 49 deletions.
65 changes: 36 additions & 29 deletions microvenv.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,43 +3,46 @@
import sys
import sysconfig

EXECUTABLE = pathlib.Path(sys.executable).resolve()

# We don't resolve `sys.executable` on purpose.
pyvenvcfg_template = f"""home = {EXECUTABLE.parent}
PYVENVCFG_TEMPLATE = f"""home = {{base_executable_parent}}
include-system-site-packages = false
version = {sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro}
executable = {EXECUTABLE}
executable = {{resolved_base_executable}}
command = {{command}}
"""


def create(venv_dir):
def _sysconfig_path(name, venv_dir):
variables = {
"base": venv_dir,
"platbase": venv_dir,
"installed_base": venv_dir,
"installed_platbase": venv_dir,
}

return pathlib.Path(sysconfig.get_path(name, "venv", variables))


def create(venv_dir):
executable = pathlib.Path(sys.executable)
try:
base_executable = pathlib.Path(sys._base_executable)
except AttributeError:
base_executable = executable

try:
paths = [
pathlib.Path(sysconfig.get_path(name, "venv", variables))
for name in ("scripts", "include", "purelib")
]
scripts_dir = _sysconfig_path("scripts", venv_dir)
include_dir = _sysconfig_path("include", venv_dir)
purelib_dir = _sysconfig_path("purelib", venv_dir)
except KeyError:
paths = [
venv_dir / subdir
for subdir in (
"bin",
"include",
pathlib.Path(
"lib",
f"python{sys.version_info.major}.{sys.version_info.minor}",
"site-packages",
),
)
]
for dir in paths:
scripts_dir = venv_dir / "bin"
include_dir = venv_dir / "include"
purelib_dir = (
venv_dir
/ "lib"
/ f"python{sys.version_info.major}.{sys.version_info.minor}"
/ "site-packages"
)
for dir in (scripts_dir, include_dir, purelib_dir):
dir.mkdir(parents=True)

if sys.maxsize > 2**32 and os.name == "posix" and sys.platform != "darwin":
Expand All @@ -50,24 +53,28 @@ def create(venv_dir):
f"python{sys.version_info.major}",
f"python{sys.version_info.major}.{sys.version_info.minor}",
):
(venv_dir / "bin" / executable_name).symlink_to(EXECUTABLE)
(scripts_dir / executable_name).symlink_to(base_executable)

try:
script_path = pathlib.Path(__file__).resolve()
module_path = pathlib.Path(__file__).resolve()
except NameError:
command = f"{EXECUTABLE} -c '...'"
command = f"{executable} -c '...'"
else:
command = f"{EXECUTABLE} {script_path} {venv_dir.resolve()}"
command = f"{executable} {module_path} {venv_dir.resolve()}"
(venv_dir / "pyvenv.cfg").write_text(
pyvenvcfg_template.format(venv_dir=venv_dir, command=command), encoding="utf-8"
PYVENVCFG_TEMPLATE.format(
base_executable_parent=base_executable.parent,
resolved_base_executable=base_executable.resolve(),
command=command,
),
encoding="utf-8",
)


if __name__ == "__main__":
if len(sys.argv) > 2:
print("Usage: microvenv.py [venv_dir='.venv']", file=sys.stderr)
sys.exit(1)

try:
venv_dir = sys.argv[1]
except IndexError:
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ build-backend = "flit_core.buildapi"

[project]
name = "microvenv"
version = "2023.0.0"
version = "2023.0.1"
description = "A minimal re-implementation of Python's venv module"
keywords = ["virtual environments", "venv"]
readme = "README.md"
Expand Down
44 changes: 25 additions & 19 deletions test_microvenv.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,19 @@
import microvenv


@pytest.fixture
def base_executable():
try:
return pathlib.Path(sys._base_executable)
except AttributeError:
return pathlib.Path(sys.executable)


@pytest.fixture
def executable():
return pathlib.Path(sys.executable)


@pytest.fixture(scope="session")
def full_venv(tmp_path_factory):
venv_path = tmp_path_factory.mktemp("venvs") / "full_venv"
Expand Down Expand Up @@ -82,53 +95,46 @@ def test_pyvenvcfg_data(full_venv, micro_venv, key):
assert full_config[key] == micro_config[key]


def test_pyvenvcfg_home(full_venv, micro_venv):
raw_path = pathlib.Path(sys.executable)
raw_dir = raw_path.parent
resolved_dir = raw_path.resolve().parent
dir_options = frozenset(map(os.fsdecode, (raw_dir, resolved_dir)))
def test_pyvenvcfg_home(base_executable, full_venv, micro_venv):
full_config = pyvenvcfg(full_venv)
micro_config = pyvenvcfg(micro_venv)

assert full_config["home"] in dir_options # Sanity check.
assert micro_config["home"] in dir_options
assert full_config["home"] == os.fsdecode(base_executable.parent) # Sanity check.
assert micro_config["home"] == os.fsdecode(base_executable.parent)


def test_pyvenvcfg_executable(full_venv, micro_venv):
def test_pyvenvcfg_executable(base_executable, full_venv, micro_venv):
resolved_base_executable = base_executable.resolve()
executable_path = os.fsdecode(resolved_base_executable)
full_config = pyvenvcfg(full_venv)
if "executable" not in full_config:
# Introduced in Python 3.11.
pytest.skip("`executable` key not in pyvenv.cfg")

micro_config = pyvenvcfg(micro_venv)
raw_path = pathlib.Path(sys.executable)
resolved_path = raw_path.resolve()
path_options = frozenset(map(os.fsdecode, (raw_path, resolved_path)))

assert full_config["executable"] in path_options # Sanity check.
assert micro_config["executable"] in path_options
assert full_config["executable"] == executable_path # Sanity check.
assert micro_config["executable"] == executable_path


def test_pyvenvfg_command(micro_venv):
def test_pyvenvfg_command(executable, micro_venv):
config = pyvenvcfg(micro_venv)
executable = pathlib.Path(sys.executable).resolve()
script_path = pathlib.Path(microvenv.__file__).resolve()
assert config["command"] == f"{executable} {script_path} {micro_venv.resolve()}"


def test_pyvencfg_command_relative(monkeypatch, tmp_path):
def test_pyvencfg_command_relative(executable, monkeypatch, tmp_path):
monkeypatch.chdir(tmp_path)
venv_path = tmp_path / "venv"
microvenv.create(pathlib.Path(venv_path.name))
executable = pathlib.Path(sys.executable).resolve()
script_path = pathlib.Path(microvenv.__file__).resolve()
config = pyvenvcfg(venv_path)
assert config["command"] == f"{executable} {script_path} {venv_path.resolve()}"


def test_code_size(monkeypatch, tmp_path):
def test_code_size(executable, monkeypatch, tmp_path):
"""Make sure the source code can fit into `argv` for use with `-c`."""
with open(microvenv.__file__, "r", encoding="utf-8") as file:
source = file.read()
monkeypatch.chdir(tmp_path)
subprocess.check_call([sys.executable, "-c", source])
subprocess.check_call([os.fsdecode(executable), "-c", source])

0 comments on commit 0c57a44

Please sign in to comment.