-
Notifications
You must be signed in to change notification settings - Fork 10
/
microvenv.py
126 lines (107 loc) · 3.94 KB
/
microvenv.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
"""A minimal, self-contained implementation of `venv`.
Because this module is self-contained from the rest of the package, you can
execute this module directly. It has its own CLI (which can be explored via
`--help`).
"""
import argparse
import os
import pathlib
import sys
import sysconfig
# This should not change during execution, so it's reasonable as a global.
# The path is purposefully unresolved for pyvenv.cfg purposes.
_BASE_EXECUTABLE = pathlib.Path(getattr(sys, "_base_executable", sys.executable))
_PYVENVCFG_TEMPLATE = f"""\
home = {_BASE_EXECUTABLE.parent}
include-system-site-packages = false
version = {'.'.join(map(str, sys.version_info[:3]))}
executable = {_BASE_EXECUTABLE.resolve()}
command = {{command}}
"""
def _sysconfig_path(name, env_dir):
variables = {
"base": env_dir,
"platbase": env_dir,
"installed_base": env_dir,
"installed_platbase": env_dir,
}
return pathlib.Path(sysconfig.get_path(name, "venv", variables))
DEFAULT_ENV_DIR = ".venv"
# Analogous to `venv.create()`.
def create(env_dir=DEFAULT_ENV_DIR, *, scm_ignore_files=frozenset(["git"])):
"""Create a minimal virtual environment.
Analogous to `venv.create(env_dir, symlinks=True, with_pip=False)`.
"""
env_path = pathlib.Path(env_dir)
# sysconfig scheme support introduced in Python 3.11.
try:
scripts_dir = _sysconfig_path("scripts", env_path)
include_dir = _sysconfig_path("include", env_path)
purelib_dir = _sysconfig_path("purelib", env_path)
except KeyError:
scripts_dir = env_path / "bin"
include_dir = env_path / "include"
purelib_dir = (
env_path
/ "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":
lib_path = env_path / "lib"
lib64_path = env_path / "lib64"
# There is no guarantee the sysconfig scheme will produce a `lib`
# directory.
if lib_path.is_dir() and not lib64_path.exists():
lib64_path.symlink_to("lib", target_is_directory=True)
for executable_name in (
"python",
f"python{sys.version_info.major}",
f"python{sys.version_info.major}.{sys.version_info.minor}",
):
(scripts_dir / executable_name).symlink_to(_BASE_EXECUTABLE)
if __spec__ is None:
command = f"{sys.executable} -c '...'"
else:
module_path = pathlib.Path(__spec__.origin).resolve()
command = f"{sys.executable} {module_path} {env_path.resolve()}"
(env_path / "pyvenv.cfg").write_text(
_PYVENVCFG_TEMPLATE.format(command=command),
encoding="utf-8",
)
scm_ignore_files = frozenset(scm_ignore_files)
if scm_ignore_files == {"git"}:
(env_path / ".gitignore").write_text("*\n", encoding="utf-8")
elif scm_ignore_files:
unexpected = scm_ignore_files - {"git"}
raise NotImplementedError(
f"Only `git` is supported as a SCM ignore file, not {unexpected}."
)
def main():
parser = argparse.ArgumentParser(
description="Create a minimal virtual environment."
)
parser.add_argument(
"--without-scm-ignore-files",
dest="scm_ignore_files",
action="store_const",
const=frozenset(),
default=frozenset(["git"]),
help="Skips adding SCM ignore files to the environment "
"directory (otherwise a `.gitignore` file is added).",
)
parser.add_argument(
"env_dir",
default=DEFAULT_ENV_DIR,
nargs="?",
help=(
"Directory to create virtual environment in "
f"(default: {DEFAULT_ENV_DIR!r}"
),
)
args = parser.parse_args()
create(args.env_dir, scm_ignore_files=args.scm_ignore_files)
if __name__ == "__main__":
main()