comfy-env 0.1.13__tar.gz → 0.1.14__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {comfy_env-0.1.13 → comfy_env-0.1.14}/PKG-INFO +1 -1
- {comfy_env-0.1.13 → comfy_env-0.1.14}/pyproject.toml +1 -1
- {comfy_env-0.1.13 → comfy_env-0.1.14}/src/comfy_env/__init__.py +25 -0
- comfy_env-0.1.14/src/comfy_env/cache.py +331 -0
- {comfy_env-0.1.13 → comfy_env-0.1.14}/src/comfy_env/isolation/wrap.py +76 -20
- {comfy_env-0.1.13 → comfy_env-0.1.14}/src/comfy_env/pixi/core.py +43 -8
- {comfy_env-0.1.13 → comfy_env-0.1.14}/src/comfy_env/prestartup.py +37 -9
- {comfy_env-0.1.13 → comfy_env-0.1.14}/.github/workflows/ci.yml +0 -0
- {comfy_env-0.1.13 → comfy_env-0.1.14}/.github/workflows/publish.yml +0 -0
- {comfy_env-0.1.13 → comfy_env-0.1.14}/.gitignore +0 -0
- {comfy_env-0.1.13 → comfy_env-0.1.14}/LICENSE +0 -0
- {comfy_env-0.1.13 → comfy_env-0.1.14}/README.md +0 -0
- {comfy_env-0.1.13 → comfy_env-0.1.14}/src/comfy_env/cli.py +0 -0
- {comfy_env-0.1.13 → comfy_env-0.1.14}/src/comfy_env/config/__init__.py +0 -0
- {comfy_env-0.1.13 → comfy_env-0.1.14}/src/comfy_env/config/parser.py +0 -0
- {comfy_env-0.1.13 → comfy_env-0.1.14}/src/comfy_env/config/types.py +0 -0
- {comfy_env-0.1.13 → comfy_env-0.1.14}/src/comfy_env/errors.py +0 -0
- {comfy_env-0.1.13 → comfy_env-0.1.14}/src/comfy_env/install.py +0 -0
- {comfy_env-0.1.13 → comfy_env-0.1.14}/src/comfy_env/isolation/__init__.py +0 -0
- {comfy_env-0.1.13 → comfy_env-0.1.14}/src/comfy_env/nodes.py +0 -0
- {comfy_env-0.1.13 → comfy_env-0.1.14}/src/comfy_env/pixi/__init__.py +0 -0
- {comfy_env-0.1.13 → comfy_env-0.1.14}/src/comfy_env/pixi/cuda_detection.py +0 -0
- {comfy_env-0.1.13 → comfy_env-0.1.14}/src/comfy_env/pixi/platform/__init__.py +0 -0
- {comfy_env-0.1.13 → comfy_env-0.1.14}/src/comfy_env/pixi/platform/base.py +0 -0
- {comfy_env-0.1.13 → comfy_env-0.1.14}/src/comfy_env/pixi/platform/darwin.py +0 -0
- {comfy_env-0.1.13 → comfy_env-0.1.14}/src/comfy_env/pixi/platform/linux.py +0 -0
- {comfy_env-0.1.13 → comfy_env-0.1.14}/src/comfy_env/pixi/platform/windows.py +0 -0
- {comfy_env-0.1.13 → comfy_env-0.1.14}/src/comfy_env/pixi/resolver.py +0 -0
- {comfy_env-0.1.13 → comfy_env-0.1.14}/src/comfy_env/templates/comfy-env-instructions.txt +0 -0
- {comfy_env-0.1.13 → comfy_env-0.1.14}/src/comfy_env/templates/comfy-env.toml +0 -0
- {comfy_env-0.1.13 → comfy_env-0.1.14}/src/comfy_env/workers/__init__.py +0 -0
- {comfy_env-0.1.13 → comfy_env-0.1.14}/src/comfy_env/workers/base.py +0 -0
- {comfy_env-0.1.13 → comfy_env-0.1.14}/src/comfy_env/workers/mp.py +0 -0
- {comfy_env-0.1.13 → comfy_env-0.1.14}/src/comfy_env/workers/subprocess.py +0 -0
- {comfy_env-0.1.13 → comfy_env-0.1.14}/src/comfy_env/workers/tensor_utils.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: comfy-env
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.14
|
|
4
4
|
Summary: Environment management for ComfyUI custom nodes - CUDA wheel resolution and process isolation
|
|
5
5
|
Project-URL: Homepage, https://github.com/PozzettiAndrea/comfy-env
|
|
6
6
|
Project-URL: Repository, https://github.com/PozzettiAndrea/comfy-env
|
|
@@ -56,6 +56,15 @@ from .install import install, verify_installation
|
|
|
56
56
|
# Prestartup helpers
|
|
57
57
|
from .prestartup import setup_env
|
|
58
58
|
|
|
59
|
+
# Cache management
|
|
60
|
+
from .cache import (
|
|
61
|
+
get_cache_dir,
|
|
62
|
+
cleanup_orphaned_envs,
|
|
63
|
+
resolve_env_path,
|
|
64
|
+
CACHE_DIR,
|
|
65
|
+
MARKER_FILE,
|
|
66
|
+
)
|
|
67
|
+
|
|
59
68
|
# Errors
|
|
60
69
|
from .errors import (
|
|
61
70
|
EnvManagerError,
|
|
@@ -105,4 +114,20 @@ __all__ = [
|
|
|
105
114
|
"DependencyError",
|
|
106
115
|
"CUDANotFoundError",
|
|
107
116
|
"InstallError",
|
|
117
|
+
# Cache
|
|
118
|
+
"get_cache_dir",
|
|
119
|
+
"cleanup_orphaned_envs",
|
|
120
|
+
"resolve_env_path",
|
|
121
|
+
"CACHE_DIR",
|
|
122
|
+
"MARKER_FILE",
|
|
108
123
|
]
|
|
124
|
+
|
|
125
|
+
# Run orphan cleanup once on module load (silently)
|
|
126
|
+
def _run_startup_cleanup():
|
|
127
|
+
"""Clean orphaned envs on startup. Runs silently, never fails startup."""
|
|
128
|
+
try:
|
|
129
|
+
cleanup_orphaned_envs(log=lambda x: None) # Silent
|
|
130
|
+
except Exception:
|
|
131
|
+
pass # Never fail startup due to cleanup
|
|
132
|
+
|
|
133
|
+
_run_startup_cleanup()
|
|
@@ -0,0 +1,331 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Central environment cache management for comfy-env.
|
|
3
|
+
|
|
4
|
+
Stores environments in ~/.comfy-env/envs/<nodename>_<subfolder>_<hash>/
|
|
5
|
+
to avoid Windows MAX_PATH (260 char) issues.
|
|
6
|
+
|
|
7
|
+
Marker files in node folders link to central envs and enable orphan cleanup.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
import hashlib
|
|
11
|
+
import os
|
|
12
|
+
import shutil
|
|
13
|
+
import sys
|
|
14
|
+
from datetime import datetime
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
from typing import Optional, Tuple, Callable
|
|
17
|
+
|
|
18
|
+
# Import version
|
|
19
|
+
try:
|
|
20
|
+
from . import __version__
|
|
21
|
+
except ImportError:
|
|
22
|
+
__version__ = "0.0.0-dev"
|
|
23
|
+
|
|
24
|
+
# Lazy import tomli/tomllib
|
|
25
|
+
def _get_tomli():
|
|
26
|
+
if sys.version_info >= (3, 11):
|
|
27
|
+
import tomllib
|
|
28
|
+
return tomllib
|
|
29
|
+
else:
|
|
30
|
+
try:
|
|
31
|
+
import tomli
|
|
32
|
+
return tomli
|
|
33
|
+
except ImportError:
|
|
34
|
+
return None
|
|
35
|
+
|
|
36
|
+
def _get_tomli_w():
|
|
37
|
+
try:
|
|
38
|
+
import tomli_w
|
|
39
|
+
return tomli_w
|
|
40
|
+
except ImportError:
|
|
41
|
+
return None
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
# Constants
|
|
45
|
+
CACHE_DIR = Path.home() / ".comfy-env" / "envs"
|
|
46
|
+
MARKER_FILE = ".comfy-env-marker.toml"
|
|
47
|
+
METADATA_FILE = ".comfy-env-metadata.toml"
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def get_cache_dir() -> Path:
|
|
51
|
+
"""Get central cache directory, create if needed."""
|
|
52
|
+
CACHE_DIR.mkdir(parents=True, exist_ok=True)
|
|
53
|
+
return CACHE_DIR
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def compute_config_hash(config_path: Path) -> str:
|
|
57
|
+
"""Compute hash of comfy-env.toml content (first 8 chars of SHA256)."""
|
|
58
|
+
content = config_path.read_bytes()
|
|
59
|
+
return hashlib.sha256(content).hexdigest()[:8]
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def sanitize_name(name: str) -> str:
|
|
63
|
+
"""Sanitize a name for use in filesystem paths."""
|
|
64
|
+
# Lowercase and replace problematic chars
|
|
65
|
+
name = name.lower()
|
|
66
|
+
for prefix in ("comfyui-", "comfyui_"):
|
|
67
|
+
if name.startswith(prefix):
|
|
68
|
+
name = name[len(prefix):]
|
|
69
|
+
return name.replace("-", "_").replace(" ", "_")
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def get_env_name(node_dir: Path, config_path: Path) -> str:
|
|
73
|
+
"""
|
|
74
|
+
Generate env name: <nodename>_<subfolder>_<hash>
|
|
75
|
+
|
|
76
|
+
Args:
|
|
77
|
+
node_dir: The custom node directory (e.g., custom_nodes/ComfyUI-UniRig)
|
|
78
|
+
config_path: Path to comfy-env.toml
|
|
79
|
+
|
|
80
|
+
Examples:
|
|
81
|
+
- ComfyUI-UniRig/nodes/comfy-env.toml -> unirig_nodes_a1b2c3d4
|
|
82
|
+
- ComfyUI-Pack/comfy-env.toml -> pack__f5e6d7c8 (double underscore = no subfolder)
|
|
83
|
+
"""
|
|
84
|
+
# Get node name
|
|
85
|
+
node_name = sanitize_name(node_dir.name)
|
|
86
|
+
|
|
87
|
+
# Get subfolder (relative path from node_dir to config parent)
|
|
88
|
+
config_parent = config_path.parent
|
|
89
|
+
if config_parent == node_dir:
|
|
90
|
+
subfolder = ""
|
|
91
|
+
else:
|
|
92
|
+
try:
|
|
93
|
+
rel_path = config_parent.relative_to(node_dir)
|
|
94
|
+
subfolder = rel_path.as_posix().replace("/", "_")
|
|
95
|
+
except ValueError:
|
|
96
|
+
# config_path not under node_dir - use parent folder name
|
|
97
|
+
subfolder = sanitize_name(config_parent.name)
|
|
98
|
+
|
|
99
|
+
# Compute hash
|
|
100
|
+
config_hash = compute_config_hash(config_path)
|
|
101
|
+
|
|
102
|
+
return f"{node_name}_{subfolder}_{config_hash}"
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def get_central_env_path(node_dir: Path, config_path: Path) -> Path:
|
|
106
|
+
"""Get path to central environment for this config."""
|
|
107
|
+
env_name = get_env_name(node_dir, config_path)
|
|
108
|
+
return get_cache_dir() / env_name
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def write_marker(config_path: Path, env_path: Path) -> None:
|
|
112
|
+
"""
|
|
113
|
+
Write marker file linking node to central env.
|
|
114
|
+
|
|
115
|
+
Args:
|
|
116
|
+
config_path: Path to comfy-env.toml
|
|
117
|
+
env_path: Path to central environment
|
|
118
|
+
"""
|
|
119
|
+
tomli_w = _get_tomli_w()
|
|
120
|
+
if not tomli_w:
|
|
121
|
+
# Fallback to manual TOML writing
|
|
122
|
+
marker_path = config_path.parent / MARKER_FILE
|
|
123
|
+
content = f'''[env]
|
|
124
|
+
name = "{env_path.name}"
|
|
125
|
+
path = "{env_path}"
|
|
126
|
+
config_hash = "{compute_config_hash(config_path)}"
|
|
127
|
+
created = "{datetime.now().isoformat()}"
|
|
128
|
+
comfy_env_version = "{__version__}"
|
|
129
|
+
'''
|
|
130
|
+
marker_path.write_text(content)
|
|
131
|
+
return
|
|
132
|
+
|
|
133
|
+
marker_path = config_path.parent / MARKER_FILE
|
|
134
|
+
marker_data = {
|
|
135
|
+
"env": {
|
|
136
|
+
"name": env_path.name,
|
|
137
|
+
"path": str(env_path),
|
|
138
|
+
"config_hash": compute_config_hash(config_path),
|
|
139
|
+
"created": datetime.now().isoformat(),
|
|
140
|
+
"comfy_env_version": __version__,
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
marker_path.write_text(tomli_w.dumps(marker_data))
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def write_env_metadata(env_path: Path, marker_path: Path) -> None:
|
|
147
|
+
"""
|
|
148
|
+
Write metadata file in env for orphan detection.
|
|
149
|
+
|
|
150
|
+
Args:
|
|
151
|
+
env_path: Path to central environment
|
|
152
|
+
marker_path: Path to marker file in node folder
|
|
153
|
+
"""
|
|
154
|
+
tomli_w = _get_tomli_w()
|
|
155
|
+
metadata_path = env_path / METADATA_FILE
|
|
156
|
+
|
|
157
|
+
if not tomli_w:
|
|
158
|
+
# Fallback to manual TOML writing
|
|
159
|
+
content = f'''marker_path = "{marker_path}"
|
|
160
|
+
created = "{datetime.now().isoformat()}"
|
|
161
|
+
'''
|
|
162
|
+
metadata_path.write_text(content)
|
|
163
|
+
return
|
|
164
|
+
|
|
165
|
+
metadata = {
|
|
166
|
+
"marker_path": str(marker_path),
|
|
167
|
+
"created": datetime.now().isoformat(),
|
|
168
|
+
}
|
|
169
|
+
metadata_path.write_text(tomli_w.dumps(metadata))
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
def read_marker(marker_path: Path) -> Optional[dict]:
|
|
173
|
+
"""
|
|
174
|
+
Read marker file, return None if invalid/missing.
|
|
175
|
+
|
|
176
|
+
Args:
|
|
177
|
+
marker_path: Path to .comfy-env-marker.toml
|
|
178
|
+
|
|
179
|
+
Returns:
|
|
180
|
+
Parsed marker data or None
|
|
181
|
+
"""
|
|
182
|
+
if not marker_path.exists():
|
|
183
|
+
return None
|
|
184
|
+
|
|
185
|
+
tomli = _get_tomli()
|
|
186
|
+
if not tomli:
|
|
187
|
+
return None
|
|
188
|
+
|
|
189
|
+
try:
|
|
190
|
+
with open(marker_path, "rb") as f:
|
|
191
|
+
return tomli.load(f)
|
|
192
|
+
except Exception:
|
|
193
|
+
return None
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
def read_env_metadata(env_path: Path) -> Optional[dict]:
|
|
197
|
+
"""
|
|
198
|
+
Read metadata file from env, return None if invalid/missing.
|
|
199
|
+
|
|
200
|
+
Args:
|
|
201
|
+
env_path: Path to central environment
|
|
202
|
+
|
|
203
|
+
Returns:
|
|
204
|
+
Parsed metadata or None
|
|
205
|
+
"""
|
|
206
|
+
metadata_path = env_path / METADATA_FILE
|
|
207
|
+
if not metadata_path.exists():
|
|
208
|
+
return None
|
|
209
|
+
|
|
210
|
+
tomli = _get_tomli()
|
|
211
|
+
if not tomli:
|
|
212
|
+
return None
|
|
213
|
+
|
|
214
|
+
try:
|
|
215
|
+
with open(metadata_path, "rb") as f:
|
|
216
|
+
return tomli.load(f)
|
|
217
|
+
except Exception:
|
|
218
|
+
return None
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
def resolve_env_path(node_dir: Path) -> Tuple[Optional[Path], Optional[Path], Optional[Path]]:
|
|
222
|
+
"""
|
|
223
|
+
Resolve environment path with fallback chain.
|
|
224
|
+
|
|
225
|
+
Args:
|
|
226
|
+
node_dir: Directory containing comfy-env.toml or marker file
|
|
227
|
+
|
|
228
|
+
Returns:
|
|
229
|
+
(env_path, site_packages, lib_dir) or (None, None, None)
|
|
230
|
+
|
|
231
|
+
Fallback order:
|
|
232
|
+
1. Marker file -> central cache
|
|
233
|
+
2. _env_<name> (current location)
|
|
234
|
+
3. .pixi/envs/default (old pixi)
|
|
235
|
+
4. .venv (venv support)
|
|
236
|
+
"""
|
|
237
|
+
# 1. Check marker file -> central cache
|
|
238
|
+
marker_path = node_dir / MARKER_FILE
|
|
239
|
+
marker = read_marker(marker_path)
|
|
240
|
+
if marker and "env" in marker:
|
|
241
|
+
env_path = Path(marker["env"]["path"])
|
|
242
|
+
if env_path.exists():
|
|
243
|
+
return _get_env_paths(env_path)
|
|
244
|
+
|
|
245
|
+
# 2. Check _env_<name>
|
|
246
|
+
node_name = sanitize_name(node_dir.name)
|
|
247
|
+
env_name = f"_env_{node_name}"
|
|
248
|
+
local_env = node_dir / env_name
|
|
249
|
+
if local_env.exists():
|
|
250
|
+
return _get_env_paths(local_env)
|
|
251
|
+
|
|
252
|
+
# 3. Check .pixi/envs/default
|
|
253
|
+
pixi_env = node_dir / ".pixi" / "envs" / "default"
|
|
254
|
+
if pixi_env.exists():
|
|
255
|
+
return _get_env_paths(pixi_env)
|
|
256
|
+
|
|
257
|
+
# 4. Check .venv
|
|
258
|
+
venv_dir = node_dir / ".venv"
|
|
259
|
+
if venv_dir.exists():
|
|
260
|
+
return _get_env_paths(venv_dir)
|
|
261
|
+
|
|
262
|
+
return None, None, None
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
def _get_env_paths(env_path: Path) -> Tuple[Path, Optional[Path], Optional[Path]]:
|
|
266
|
+
"""
|
|
267
|
+
Get site-packages and lib paths from an environment.
|
|
268
|
+
|
|
269
|
+
Args:
|
|
270
|
+
env_path: Path to environment root
|
|
271
|
+
|
|
272
|
+
Returns:
|
|
273
|
+
(env_path, site_packages, lib_dir)
|
|
274
|
+
"""
|
|
275
|
+
import glob
|
|
276
|
+
|
|
277
|
+
if sys.platform == "win32":
|
|
278
|
+
site_packages = env_path / "Lib" / "site-packages"
|
|
279
|
+
lib_dir = env_path / "Library" / "bin"
|
|
280
|
+
else:
|
|
281
|
+
# Linux/Mac: lib/python*/site-packages
|
|
282
|
+
matches = glob.glob(str(env_path / "lib" / "python*" / "site-packages"))
|
|
283
|
+
site_packages = Path(matches[0]) if matches else None
|
|
284
|
+
lib_dir = env_path / "lib"
|
|
285
|
+
|
|
286
|
+
return env_path, site_packages, lib_dir
|
|
287
|
+
|
|
288
|
+
|
|
289
|
+
def cleanup_orphaned_envs(log: Callable[[str], None] = print) -> int:
|
|
290
|
+
"""
|
|
291
|
+
Scan central cache and remove orphaned environments.
|
|
292
|
+
|
|
293
|
+
An env is orphaned if its marker file no longer exists
|
|
294
|
+
(meaning the node was deleted).
|
|
295
|
+
|
|
296
|
+
Args:
|
|
297
|
+
log: Logging function
|
|
298
|
+
|
|
299
|
+
Returns:
|
|
300
|
+
Number of envs cleaned up
|
|
301
|
+
"""
|
|
302
|
+
cache_dir = get_cache_dir()
|
|
303
|
+
if not cache_dir.exists():
|
|
304
|
+
return 0
|
|
305
|
+
|
|
306
|
+
cleaned = 0
|
|
307
|
+
for env_dir in cache_dir.iterdir():
|
|
308
|
+
if not env_dir.is_dir():
|
|
309
|
+
continue
|
|
310
|
+
|
|
311
|
+
# Skip if no metadata (might be manually created or old format)
|
|
312
|
+
metadata = read_env_metadata(env_dir)
|
|
313
|
+
if not metadata:
|
|
314
|
+
continue
|
|
315
|
+
|
|
316
|
+
# Check if marker file still exists
|
|
317
|
+
marker_path_str = metadata.get("marker_path", "")
|
|
318
|
+
if not marker_path_str:
|
|
319
|
+
continue
|
|
320
|
+
|
|
321
|
+
marker_path = Path(marker_path_str)
|
|
322
|
+
if not marker_path.exists():
|
|
323
|
+
# Marker gone = node was deleted = orphan
|
|
324
|
+
log(f"[comfy-env] Cleaning orphaned env: {env_dir.name}")
|
|
325
|
+
try:
|
|
326
|
+
shutil.rmtree(env_dir)
|
|
327
|
+
cleaned += 1
|
|
328
|
+
except Exception as e:
|
|
329
|
+
log(f"[comfy-env] Failed to cleanup {env_dir.name}: {e}")
|
|
330
|
+
|
|
331
|
+
return cleaned
|
|
@@ -145,15 +145,19 @@ def _find_env_paths(node_dir: Path) -> tuple[Optional[Path], Optional[Path]]:
|
|
|
145
145
|
"""
|
|
146
146
|
Find site-packages and lib directories for the isolated environment.
|
|
147
147
|
|
|
148
|
+
Fallback order:
|
|
149
|
+
1. Marker file -> central cache
|
|
150
|
+
2. _env_<name> (local)
|
|
151
|
+
3. .pixi/envs/default (old pixi)
|
|
152
|
+
4. .venv
|
|
153
|
+
|
|
148
154
|
Returns:
|
|
149
155
|
(site_packages, lib_dir) - lib_dir is for LD_LIBRARY_PATH
|
|
150
156
|
"""
|
|
151
157
|
import glob
|
|
152
158
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
env_dir = node_dir / env_name
|
|
156
|
-
if env_dir.exists():
|
|
159
|
+
def _get_paths_from_env(env_dir: Path) -> tuple[Optional[Path], Optional[Path]]:
|
|
160
|
+
"""Extract site-packages and lib_dir from an env directory."""
|
|
157
161
|
if sys.platform == "win32":
|
|
158
162
|
site_packages = env_dir / "Lib" / "site-packages"
|
|
159
163
|
lib_dir = env_dir / "Library" / "bin"
|
|
@@ -163,23 +167,45 @@ def _find_env_paths(node_dir: Path) -> tuple[Optional[Path], Optional[Path]]:
|
|
|
163
167
|
site_packages = Path(matches[0]) if matches else None
|
|
164
168
|
lib_dir = env_dir / "lib"
|
|
165
169
|
if site_packages and site_packages.exists():
|
|
166
|
-
return site_packages, lib_dir if lib_dir.exists() else None
|
|
170
|
+
return site_packages, lib_dir if lib_dir and lib_dir.exists() else None
|
|
171
|
+
return None, None
|
|
167
172
|
|
|
168
|
-
#
|
|
173
|
+
# 1. Check marker file -> central cache
|
|
174
|
+
marker_path = node_dir / ".comfy-env-marker.toml"
|
|
175
|
+
if marker_path.exists():
|
|
176
|
+
try:
|
|
177
|
+
if sys.version_info >= (3, 11):
|
|
178
|
+
import tomllib
|
|
179
|
+
else:
|
|
180
|
+
import tomli as tomllib
|
|
181
|
+
with open(marker_path, "rb") as f:
|
|
182
|
+
marker = tomllib.load(f)
|
|
183
|
+
env_path = marker.get("env", {}).get("path")
|
|
184
|
+
if env_path:
|
|
185
|
+
env_dir = Path(env_path)
|
|
186
|
+
if env_dir.exists():
|
|
187
|
+
result = _get_paths_from_env(env_dir)
|
|
188
|
+
if result[0]:
|
|
189
|
+
return result
|
|
190
|
+
except Exception:
|
|
191
|
+
pass # Fall through to other options
|
|
192
|
+
|
|
193
|
+
# 2. Check _env_<name> directory (local)
|
|
194
|
+
env_name = get_env_name(node_dir.name)
|
|
195
|
+
env_dir = node_dir / env_name
|
|
196
|
+
if env_dir.exists():
|
|
197
|
+
result = _get_paths_from_env(env_dir)
|
|
198
|
+
if result[0]:
|
|
199
|
+
return result
|
|
200
|
+
|
|
201
|
+
# 3. Fallback: Check old .pixi/envs/default (for backward compat)
|
|
169
202
|
pixi_env = node_dir / ".pixi" / "envs" / "default"
|
|
170
203
|
if pixi_env.exists():
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
else:
|
|
175
|
-
pattern = str(pixi_env / "lib" / "python*" / "site-packages")
|
|
176
|
-
matches = glob.glob(pattern)
|
|
177
|
-
site_packages = Path(matches[0]) if matches else None
|
|
178
|
-
lib_dir = pixi_env / "lib"
|
|
179
|
-
if site_packages and site_packages.exists():
|
|
180
|
-
return site_packages, lib_dir if lib_dir.exists() else None
|
|
204
|
+
result = _get_paths_from_env(pixi_env)
|
|
205
|
+
if result[0]:
|
|
206
|
+
return result
|
|
181
207
|
|
|
182
|
-
# Check .venv directory
|
|
208
|
+
# 4. Check .venv directory
|
|
183
209
|
venv_dir = node_dir / ".venv"
|
|
184
210
|
if venv_dir.exists():
|
|
185
211
|
if sys.platform == "win32":
|
|
@@ -195,19 +221,49 @@ def _find_env_paths(node_dir: Path) -> tuple[Optional[Path], Optional[Path]]:
|
|
|
195
221
|
|
|
196
222
|
|
|
197
223
|
def _find_env_dir(node_dir: Path) -> Optional[Path]:
|
|
198
|
-
"""
|
|
199
|
-
|
|
224
|
+
"""
|
|
225
|
+
Find the environment directory (for cache key).
|
|
226
|
+
|
|
227
|
+
Fallback order:
|
|
228
|
+
1. Marker file -> central cache
|
|
229
|
+
2. _env_<name> (local)
|
|
230
|
+
3. .pixi/envs/default (old pixi)
|
|
231
|
+
4. .venv
|
|
232
|
+
"""
|
|
233
|
+
# 1. Check marker file -> central cache
|
|
234
|
+
marker_path = node_dir / ".comfy-env-marker.toml"
|
|
235
|
+
if marker_path.exists():
|
|
236
|
+
try:
|
|
237
|
+
if sys.version_info >= (3, 11):
|
|
238
|
+
import tomllib
|
|
239
|
+
else:
|
|
240
|
+
import tomli as tomllib
|
|
241
|
+
with open(marker_path, "rb") as f:
|
|
242
|
+
marker = tomllib.load(f)
|
|
243
|
+
env_path = marker.get("env", {}).get("path")
|
|
244
|
+
if env_path:
|
|
245
|
+
env_dir = Path(env_path)
|
|
246
|
+
if env_dir.exists():
|
|
247
|
+
return env_dir
|
|
248
|
+
except Exception:
|
|
249
|
+
pass
|
|
250
|
+
|
|
251
|
+
# 2. Check _env_<name> first
|
|
200
252
|
env_name = get_env_name(node_dir.name)
|
|
201
253
|
env_dir = node_dir / env_name
|
|
202
254
|
if env_dir.exists():
|
|
203
255
|
return env_dir
|
|
204
|
-
|
|
256
|
+
|
|
257
|
+
# 3. Fallback to old .pixi path
|
|
205
258
|
pixi_env = node_dir / ".pixi" / "envs" / "default"
|
|
206
259
|
if pixi_env.exists():
|
|
207
260
|
return pixi_env
|
|
261
|
+
|
|
262
|
+
# 4. Check .venv
|
|
208
263
|
venv_dir = node_dir / ".venv"
|
|
209
264
|
if venv_dir.exists():
|
|
210
265
|
return venv_dir
|
|
266
|
+
|
|
211
267
|
return None
|
|
212
268
|
|
|
213
269
|
|
|
@@ -534,20 +534,55 @@ def pixi_install(
|
|
|
534
534
|
|
|
535
535
|
log("CUDA packages installed")
|
|
536
536
|
|
|
537
|
-
# Move environment from .pixi/envs/default to
|
|
537
|
+
# Move environment from .pixi/envs/default to central cache
|
|
538
|
+
from ..cache import (
|
|
539
|
+
get_central_env_path, write_marker, write_env_metadata,
|
|
540
|
+
MARKER_FILE, get_cache_dir
|
|
541
|
+
)
|
|
542
|
+
|
|
538
543
|
old_env = node_dir / ".pixi" / "envs" / "default"
|
|
539
|
-
|
|
540
|
-
|
|
544
|
+
config_path = node_dir / "comfy-env.toml"
|
|
545
|
+
|
|
546
|
+
# Determine the main node directory (for naming)
|
|
547
|
+
# If node_dir is custom_nodes/NodeName/subdir, main_node_dir is custom_nodes/NodeName
|
|
548
|
+
# If node_dir is custom_nodes/NodeName, main_node_dir is custom_nodes/NodeName
|
|
549
|
+
if node_dir.parent.name == "custom_nodes":
|
|
550
|
+
main_node_dir = node_dir
|
|
551
|
+
else:
|
|
552
|
+
# Walk up to find custom_nodes parent
|
|
553
|
+
main_node_dir = node_dir
|
|
554
|
+
for parent in node_dir.parents:
|
|
555
|
+
if parent.parent.name == "custom_nodes":
|
|
556
|
+
main_node_dir = parent
|
|
557
|
+
break
|
|
558
|
+
|
|
559
|
+
# Get central env path
|
|
560
|
+
central_env = get_central_env_path(main_node_dir, config_path)
|
|
541
561
|
|
|
542
562
|
if old_env.exists():
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
#
|
|
563
|
+
# Ensure cache directory exists
|
|
564
|
+
get_cache_dir()
|
|
565
|
+
|
|
566
|
+
# Remove old central env if exists
|
|
567
|
+
if central_env.exists():
|
|
568
|
+
shutil.rmtree(central_env)
|
|
569
|
+
|
|
570
|
+
# Move to central cache
|
|
571
|
+
shutil.move(str(old_env), str(central_env))
|
|
572
|
+
|
|
573
|
+
# Write marker file in node directory
|
|
574
|
+
write_marker(config_path, central_env)
|
|
575
|
+
|
|
576
|
+
# Write metadata in env for orphan detection
|
|
577
|
+
marker_path = config_path.parent / MARKER_FILE
|
|
578
|
+
write_env_metadata(central_env, marker_path)
|
|
579
|
+
|
|
580
|
+
# Clean up .pixi directory
|
|
547
581
|
pixi_dir = node_dir / ".pixi"
|
|
548
582
|
if pixi_dir.exists():
|
|
549
583
|
shutil.rmtree(pixi_dir)
|
|
550
|
-
|
|
584
|
+
|
|
585
|
+
log(f"Environment created at: {central_env}")
|
|
551
586
|
|
|
552
587
|
log("Installation complete!")
|
|
553
588
|
return True
|
|
@@ -127,15 +127,43 @@ def setup_env(node_dir: Optional[str] = None) -> None:
|
|
|
127
127
|
for key, value in env_vars.items():
|
|
128
128
|
os.environ[key] = value
|
|
129
129
|
|
|
130
|
-
#
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
130
|
+
# Resolve environment path with fallback chain:
|
|
131
|
+
# 1. Marker file -> central cache
|
|
132
|
+
# 2. _env_<name> (current local)
|
|
133
|
+
# 3. .pixi/envs/default (old pixi)
|
|
134
|
+
pixi_env = None
|
|
135
|
+
|
|
136
|
+
# 1. Check marker file -> central cache
|
|
137
|
+
marker_path = os.path.join(node_dir, ".comfy-env-marker.toml")
|
|
138
|
+
if os.path.exists(marker_path):
|
|
139
|
+
try:
|
|
140
|
+
if sys.version_info >= (3, 11):
|
|
141
|
+
import tomllib
|
|
142
|
+
else:
|
|
143
|
+
import tomli as tomllib
|
|
144
|
+
with open(marker_path, "rb") as f:
|
|
145
|
+
marker = tomllib.load(f)
|
|
146
|
+
env_path = marker.get("env", {}).get("path")
|
|
147
|
+
if env_path and os.path.exists(env_path):
|
|
148
|
+
pixi_env = env_path
|
|
149
|
+
except Exception:
|
|
150
|
+
pass # Fall through to other options
|
|
151
|
+
|
|
152
|
+
# 2. Check _env_<name> (local)
|
|
153
|
+
if not pixi_env:
|
|
154
|
+
env_name = get_env_name(os.path.basename(node_dir))
|
|
155
|
+
local_env = os.path.join(node_dir, env_name)
|
|
156
|
+
if os.path.exists(local_env):
|
|
157
|
+
pixi_env = local_env
|
|
158
|
+
|
|
159
|
+
# 3. Fallback to old .pixi path
|
|
160
|
+
if not pixi_env:
|
|
161
|
+
old_pixi = os.path.join(node_dir, ".pixi", "envs", "default")
|
|
162
|
+
if os.path.exists(old_pixi):
|
|
163
|
+
pixi_env = old_pixi
|
|
164
|
+
|
|
165
|
+
if not pixi_env:
|
|
166
|
+
return # No environment found
|
|
139
167
|
|
|
140
168
|
if sys.platform == "win32":
|
|
141
169
|
# Windows: add to PATH for DLL loading
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|