comfy-env 0.0.72__tar.gz → 0.0.74__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.0.72 → comfy_env-0.0.74}/PKG-INFO +1 -1
- {comfy_env-0.0.72 → comfy_env-0.0.74}/pyproject.toml +1 -1
- comfy_env-0.0.74/src/comfy_env/install.py +335 -0
- comfy_env-0.0.72/src/comfy_env/install.py +0 -171
- {comfy_env-0.0.72 → comfy_env-0.0.74}/.github/workflows/ci.yml +0 -0
- {comfy_env-0.0.72 → comfy_env-0.0.74}/.github/workflows/publish.yml +0 -0
- {comfy_env-0.0.72 → comfy_env-0.0.74}/.gitignore +0 -0
- {comfy_env-0.0.72 → comfy_env-0.0.74}/LICENSE +0 -0
- {comfy_env-0.0.72 → comfy_env-0.0.74}/README.md +0 -0
- {comfy_env-0.0.72 → comfy_env-0.0.74}/src/comfy_env/__init__.py +0 -0
- {comfy_env-0.0.72 → comfy_env-0.0.74}/src/comfy_env/cli.py +0 -0
- {comfy_env-0.0.72 → comfy_env-0.0.74}/src/comfy_env/config/__init__.py +0 -0
- {comfy_env-0.0.72 → comfy_env-0.0.74}/src/comfy_env/config/parser.py +0 -0
- {comfy_env-0.0.72 → comfy_env-0.0.74}/src/comfy_env/config/types.py +0 -0
- {comfy_env-0.0.72 → comfy_env-0.0.74}/src/comfy_env/errors.py +0 -0
- {comfy_env-0.0.72 → comfy_env-0.0.74}/src/comfy_env/isolation/__init__.py +0 -0
- {comfy_env-0.0.72 → comfy_env-0.0.74}/src/comfy_env/isolation/wrap.py +0 -0
- {comfy_env-0.0.72 → comfy_env-0.0.74}/src/comfy_env/nodes.py +0 -0
- {comfy_env-0.0.72 → comfy_env-0.0.74}/src/comfy_env/pixi/__init__.py +0 -0
- {comfy_env-0.0.72 → comfy_env-0.0.74}/src/comfy_env/pixi/core.py +0 -0
- {comfy_env-0.0.72 → comfy_env-0.0.74}/src/comfy_env/pixi/cuda_detection.py +0 -0
- {comfy_env-0.0.72 → comfy_env-0.0.74}/src/comfy_env/pixi/platform/__init__.py +0 -0
- {comfy_env-0.0.72 → comfy_env-0.0.74}/src/comfy_env/pixi/platform/base.py +0 -0
- {comfy_env-0.0.72 → comfy_env-0.0.74}/src/comfy_env/pixi/platform/darwin.py +0 -0
- {comfy_env-0.0.72 → comfy_env-0.0.74}/src/comfy_env/pixi/platform/linux.py +0 -0
- {comfy_env-0.0.72 → comfy_env-0.0.74}/src/comfy_env/pixi/platform/windows.py +0 -0
- {comfy_env-0.0.72 → comfy_env-0.0.74}/src/comfy_env/pixi/resolver.py +0 -0
- {comfy_env-0.0.72 → comfy_env-0.0.74}/src/comfy_env/prestartup.py +0 -0
- {comfy_env-0.0.72 → comfy_env-0.0.74}/src/comfy_env/templates/comfy-env-instructions.txt +0 -0
- {comfy_env-0.0.72 → comfy_env-0.0.74}/src/comfy_env/templates/comfy-env.toml +0 -0
- {comfy_env-0.0.72 → comfy_env-0.0.74}/src/comfy_env/workers/__init__.py +0 -0
- {comfy_env-0.0.72 → comfy_env-0.0.74}/src/comfy_env/workers/base.py +0 -0
- {comfy_env-0.0.72 → comfy_env-0.0.74}/src/comfy_env/workers/mp.py +0 -0
- {comfy_env-0.0.72 → comfy_env-0.0.74}/src/comfy_env/workers/subprocess.py +0 -0
- {comfy_env-0.0.72 → comfy_env-0.0.74}/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.0.
|
|
3
|
+
Version: 0.0.74
|
|
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
|
|
@@ -0,0 +1,335 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Installation API for comfy-env.
|
|
3
|
+
|
|
4
|
+
Example:
|
|
5
|
+
from comfy_env import install
|
|
6
|
+
install() # Auto-discovers comfy-env.toml and installs
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import inspect
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
from typing import Callable, List, Optional, Set, Union
|
|
12
|
+
|
|
13
|
+
from .config.types import ComfyEnvConfig, NodeReq
|
|
14
|
+
from .config.parser import load_config, discover_config
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def install(
|
|
18
|
+
config: Optional[Union[str, Path]] = None,
|
|
19
|
+
node_dir: Optional[Path] = None,
|
|
20
|
+
log_callback: Optional[Callable[[str], None]] = None,
|
|
21
|
+
dry_run: bool = False,
|
|
22
|
+
) -> bool:
|
|
23
|
+
"""
|
|
24
|
+
Install dependencies from comfy-env.toml.
|
|
25
|
+
|
|
26
|
+
Args:
|
|
27
|
+
config: Optional path to comfy-env.toml. Auto-discovered if not provided.
|
|
28
|
+
node_dir: Optional node directory. Auto-discovered from caller if not provided.
|
|
29
|
+
log_callback: Optional callback for logging. Defaults to print.
|
|
30
|
+
dry_run: If True, show what would be installed without installing.
|
|
31
|
+
|
|
32
|
+
Returns:
|
|
33
|
+
True if installation succeeded.
|
|
34
|
+
"""
|
|
35
|
+
# Auto-discover caller's directory if not provided
|
|
36
|
+
if node_dir is None:
|
|
37
|
+
frame = inspect.stack()[1]
|
|
38
|
+
caller_file = frame.filename
|
|
39
|
+
node_dir = Path(caller_file).parent.resolve()
|
|
40
|
+
|
|
41
|
+
log = log_callback or print
|
|
42
|
+
|
|
43
|
+
# Load config
|
|
44
|
+
if config is not None:
|
|
45
|
+
config_path = Path(config)
|
|
46
|
+
if not config_path.is_absolute():
|
|
47
|
+
config_path = node_dir / config_path
|
|
48
|
+
cfg = load_config(config_path)
|
|
49
|
+
else:
|
|
50
|
+
cfg = discover_config(node_dir)
|
|
51
|
+
|
|
52
|
+
if cfg is None:
|
|
53
|
+
raise FileNotFoundError(
|
|
54
|
+
f"No comfy-env.toml found in {node_dir}. "
|
|
55
|
+
"Create comfy-env.toml to define dependencies."
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
# Install apt packages first (Linux only)
|
|
59
|
+
if cfg.apt_packages:
|
|
60
|
+
_install_apt_packages(cfg.apt_packages, log, dry_run)
|
|
61
|
+
|
|
62
|
+
# Set persistent env vars (for OpenMP settings, etc.)
|
|
63
|
+
if cfg.env_vars:
|
|
64
|
+
_set_persistent_env_vars(cfg.env_vars, log, dry_run)
|
|
65
|
+
|
|
66
|
+
# Install node dependencies
|
|
67
|
+
if cfg.node_reqs:
|
|
68
|
+
_install_node_dependencies(cfg.node_reqs, node_dir, log, dry_run)
|
|
69
|
+
|
|
70
|
+
# Install everything via pixi
|
|
71
|
+
_install_via_pixi(cfg, node_dir, log, dry_run)
|
|
72
|
+
|
|
73
|
+
# Auto-discover and install isolated subdirectory environments
|
|
74
|
+
_install_isolated_subdirs(node_dir, log, dry_run)
|
|
75
|
+
|
|
76
|
+
log("\nInstallation complete!")
|
|
77
|
+
return True
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def _install_apt_packages(
|
|
81
|
+
packages: List[str],
|
|
82
|
+
log: Callable[[str], None],
|
|
83
|
+
dry_run: bool,
|
|
84
|
+
) -> None:
|
|
85
|
+
"""Install apt packages (Linux only)."""
|
|
86
|
+
import os
|
|
87
|
+
import platform
|
|
88
|
+
import shutil
|
|
89
|
+
import subprocess
|
|
90
|
+
|
|
91
|
+
if platform.system() != "Linux":
|
|
92
|
+
log(f"[apt] Skipping apt packages (not Linux)")
|
|
93
|
+
return
|
|
94
|
+
|
|
95
|
+
log(f"\n[apt] Installing {len(packages)} system package(s):")
|
|
96
|
+
for pkg in packages:
|
|
97
|
+
log(f" - {pkg}")
|
|
98
|
+
|
|
99
|
+
if dry_run:
|
|
100
|
+
log(" (dry run - no changes made)")
|
|
101
|
+
return
|
|
102
|
+
|
|
103
|
+
# Determine if we need sudo
|
|
104
|
+
is_root = os.geteuid() == 0
|
|
105
|
+
has_sudo = shutil.which("sudo") is not None
|
|
106
|
+
use_sudo = not is_root and has_sudo
|
|
107
|
+
prefix = ["sudo"] if use_sudo else []
|
|
108
|
+
|
|
109
|
+
if not is_root and not has_sudo:
|
|
110
|
+
log(f"[apt] Warning: No root access. Install manually:")
|
|
111
|
+
log(f" sudo apt-get update && sudo apt-get install -y {' '.join(packages)}")
|
|
112
|
+
return
|
|
113
|
+
|
|
114
|
+
# Run apt-get update (suppress output, just show errors)
|
|
115
|
+
log("[apt] Updating package lists...")
|
|
116
|
+
result = subprocess.run(
|
|
117
|
+
prefix + ["apt-get", "update"],
|
|
118
|
+
capture_output=True,
|
|
119
|
+
text=True,
|
|
120
|
+
)
|
|
121
|
+
if result.returncode != 0:
|
|
122
|
+
log(f"[apt] Warning: apt-get update failed: {result.stderr.strip()}")
|
|
123
|
+
|
|
124
|
+
# Install each package individually (some may not exist on all distros)
|
|
125
|
+
log("[apt] Installing packages...")
|
|
126
|
+
installed = []
|
|
127
|
+
skipped = []
|
|
128
|
+
for pkg in packages:
|
|
129
|
+
result = subprocess.run(
|
|
130
|
+
prefix + ["apt-get", "install", "-y", pkg],
|
|
131
|
+
capture_output=True,
|
|
132
|
+
text=True,
|
|
133
|
+
)
|
|
134
|
+
if result.returncode == 0:
|
|
135
|
+
installed.append(pkg)
|
|
136
|
+
log(f" [apt] Installed {pkg}")
|
|
137
|
+
else:
|
|
138
|
+
skipped.append(pkg)
|
|
139
|
+
log(f" [apt] Skipped {pkg} (not available)")
|
|
140
|
+
|
|
141
|
+
if installed:
|
|
142
|
+
log(f"[apt] Installed {len(installed)} package(s)")
|
|
143
|
+
if skipped:
|
|
144
|
+
log(f"[apt] Skipped {len(skipped)} unavailable package(s)")
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def _set_persistent_env_vars(
|
|
148
|
+
env_vars: dict,
|
|
149
|
+
log: Callable[[str], None],
|
|
150
|
+
dry_run: bool,
|
|
151
|
+
) -> None:
|
|
152
|
+
"""Set env vars permanently (survives restarts)."""
|
|
153
|
+
import os
|
|
154
|
+
import platform
|
|
155
|
+
import subprocess
|
|
156
|
+
from pathlib import Path
|
|
157
|
+
|
|
158
|
+
if not env_vars:
|
|
159
|
+
return
|
|
160
|
+
|
|
161
|
+
system = platform.system()
|
|
162
|
+
log(f"\n[env] Setting {len(env_vars)} persistent environment variable(s)...")
|
|
163
|
+
|
|
164
|
+
for key, value in env_vars.items():
|
|
165
|
+
log(f" - {key}={value}")
|
|
166
|
+
|
|
167
|
+
if dry_run:
|
|
168
|
+
log(" (dry run - no changes made)")
|
|
169
|
+
return
|
|
170
|
+
|
|
171
|
+
if system == "Windows":
|
|
172
|
+
# Windows: use setx (writes to registry)
|
|
173
|
+
for key, value in env_vars.items():
|
|
174
|
+
result = subprocess.run(
|
|
175
|
+
["setx", key, value],
|
|
176
|
+
capture_output=True, text=True
|
|
177
|
+
)
|
|
178
|
+
if result.returncode == 0:
|
|
179
|
+
log(f" [env] Set {key} (Windows registry)")
|
|
180
|
+
else:
|
|
181
|
+
log(f" [env] Warning: Failed to set {key}: {result.stderr.strip()}")
|
|
182
|
+
log("[env] Restart terminal/ComfyUI for changes to take effect")
|
|
183
|
+
|
|
184
|
+
elif system == "Darwin": # macOS
|
|
185
|
+
# macOS: launchctl for GUI apps + zshrc for terminal
|
|
186
|
+
for key, value in env_vars.items():
|
|
187
|
+
subprocess.run(["launchctl", "setenv", key, value], capture_output=True)
|
|
188
|
+
log(f" [env] Set {key} (launchctl)")
|
|
189
|
+
|
|
190
|
+
# Also add to zshrc for terminal (zsh is default on macOS)
|
|
191
|
+
_add_to_shell_profile(env_vars, log)
|
|
192
|
+
|
|
193
|
+
else: # Linux
|
|
194
|
+
_add_to_shell_profile(env_vars, log)
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
def _add_to_shell_profile(
|
|
198
|
+
env_vars: dict,
|
|
199
|
+
log: Callable[[str], None],
|
|
200
|
+
) -> None:
|
|
201
|
+
"""Add env vars to shell profile (Linux/macOS)."""
|
|
202
|
+
import os
|
|
203
|
+
from pathlib import Path
|
|
204
|
+
|
|
205
|
+
# Determine shell profile
|
|
206
|
+
shell = os.environ.get("SHELL", "/bin/bash")
|
|
207
|
+
if "zsh" in shell:
|
|
208
|
+
rc_file = Path.home() / ".zshrc"
|
|
209
|
+
else:
|
|
210
|
+
rc_file = Path.home() / ".bashrc"
|
|
211
|
+
|
|
212
|
+
profile_file = Path.home() / ".comfy-env-profile"
|
|
213
|
+
|
|
214
|
+
# Write env vars to our dedicated file
|
|
215
|
+
with open(profile_file, "w") as f:
|
|
216
|
+
f.write("# Generated by comfy-env - do not edit manually\n")
|
|
217
|
+
for key, value in env_vars.items():
|
|
218
|
+
f.write(f'export {key}="{value}"\n')
|
|
219
|
+
log(f" [env] Wrote {profile_file}")
|
|
220
|
+
|
|
221
|
+
# Add source line to shell rc (only once)
|
|
222
|
+
source_line = f'source "{profile_file}"'
|
|
223
|
+
existing = rc_file.read_text() if rc_file.exists() else ""
|
|
224
|
+
|
|
225
|
+
if source_line not in existing and str(profile_file) not in existing:
|
|
226
|
+
with open(rc_file, "a") as f:
|
|
227
|
+
f.write(f'\n# comfy-env environment variables\n')
|
|
228
|
+
f.write(f'{source_line}\n')
|
|
229
|
+
log(f" [env] Added source line to {rc_file}")
|
|
230
|
+
else:
|
|
231
|
+
log(f" [env] Already configured in {rc_file}")
|
|
232
|
+
|
|
233
|
+
log("[env] Restart terminal/ComfyUI for changes to take effect")
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
def _install_node_dependencies(
|
|
237
|
+
node_reqs: List[NodeReq],
|
|
238
|
+
node_dir: Path,
|
|
239
|
+
log: Callable[[str], None],
|
|
240
|
+
dry_run: bool,
|
|
241
|
+
) -> None:
|
|
242
|
+
"""Install node dependencies (other ComfyUI custom nodes)."""
|
|
243
|
+
from .nodes import install_node_deps
|
|
244
|
+
|
|
245
|
+
custom_nodes_dir = node_dir.parent
|
|
246
|
+
log(f"\nInstalling {len(node_reqs)} node dependencies...")
|
|
247
|
+
|
|
248
|
+
if dry_run:
|
|
249
|
+
for req in node_reqs:
|
|
250
|
+
node_path = custom_nodes_dir / req.name
|
|
251
|
+
status = "exists" if node_path.exists() else "would clone"
|
|
252
|
+
log(f" {req.name}: {status}")
|
|
253
|
+
return
|
|
254
|
+
|
|
255
|
+
visited: Set[str] = {node_dir.name}
|
|
256
|
+
install_node_deps(node_reqs, custom_nodes_dir, log, visited)
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
def _install_via_pixi(
|
|
260
|
+
cfg: ComfyEnvConfig,
|
|
261
|
+
node_dir: Path,
|
|
262
|
+
log: Callable[[str], None],
|
|
263
|
+
dry_run: bool,
|
|
264
|
+
) -> None:
|
|
265
|
+
"""Install all packages via pixi."""
|
|
266
|
+
from .pixi import pixi_install
|
|
267
|
+
|
|
268
|
+
# Count what we're installing
|
|
269
|
+
cuda_count = len(cfg.cuda_packages)
|
|
270
|
+
|
|
271
|
+
# Count from passthrough (pixi-native format)
|
|
272
|
+
deps = cfg.pixi_passthrough.get("dependencies", {})
|
|
273
|
+
pypi_deps = cfg.pixi_passthrough.get("pypi-dependencies", {})
|
|
274
|
+
|
|
275
|
+
if cuda_count == 0 and not deps and not pypi_deps:
|
|
276
|
+
log("No packages to install")
|
|
277
|
+
return
|
|
278
|
+
|
|
279
|
+
log(f"\nInstalling via pixi:")
|
|
280
|
+
if cuda_count:
|
|
281
|
+
log(f" CUDA packages: {', '.join(cfg.cuda_packages)}")
|
|
282
|
+
if deps:
|
|
283
|
+
log(f" Conda packages: {len(deps)}")
|
|
284
|
+
if pypi_deps:
|
|
285
|
+
log(f" PyPI packages: {len(pypi_deps)}")
|
|
286
|
+
|
|
287
|
+
if dry_run:
|
|
288
|
+
log("\n(dry run - no changes made)")
|
|
289
|
+
return
|
|
290
|
+
|
|
291
|
+
pixi_install(cfg, node_dir, log)
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
def _install_isolated_subdirs(
|
|
295
|
+
node_dir: Path,
|
|
296
|
+
log: Callable[[str], None],
|
|
297
|
+
dry_run: bool,
|
|
298
|
+
) -> None:
|
|
299
|
+
"""Find and install comfy-env.toml in subdirectories."""
|
|
300
|
+
from .pixi import pixi_install
|
|
301
|
+
from .config.parser import CONFIG_FILE_NAME
|
|
302
|
+
|
|
303
|
+
# Find all comfy-env.toml files in subdirectories (not root)
|
|
304
|
+
for config_file in node_dir.rglob(CONFIG_FILE_NAME):
|
|
305
|
+
if config_file.parent == node_dir:
|
|
306
|
+
continue # Skip root (already installed)
|
|
307
|
+
|
|
308
|
+
sub_dir = config_file.parent
|
|
309
|
+
relative = sub_dir.relative_to(node_dir)
|
|
310
|
+
|
|
311
|
+
log(f"\n[isolated] Installing: {relative}")
|
|
312
|
+
sub_cfg = load_config(config_file)
|
|
313
|
+
|
|
314
|
+
if dry_run:
|
|
315
|
+
log(f" (dry run)")
|
|
316
|
+
continue
|
|
317
|
+
|
|
318
|
+
pixi_install(sub_cfg, sub_dir, log)
|
|
319
|
+
|
|
320
|
+
|
|
321
|
+
def verify_installation(
|
|
322
|
+
packages: List[str],
|
|
323
|
+
log: Callable[[str], None] = print,
|
|
324
|
+
) -> bool:
|
|
325
|
+
"""Verify that packages are importable."""
|
|
326
|
+
all_ok = True
|
|
327
|
+
for package in packages:
|
|
328
|
+
import_name = package.replace("-", "_").split("[")[0]
|
|
329
|
+
try:
|
|
330
|
+
__import__(import_name)
|
|
331
|
+
log(f" {package}: OK")
|
|
332
|
+
except ImportError as e:
|
|
333
|
+
log(f" {package}: FAILED ({e})")
|
|
334
|
+
all_ok = False
|
|
335
|
+
return all_ok
|
|
@@ -1,171 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Installation API for comfy-env.
|
|
3
|
-
|
|
4
|
-
Example:
|
|
5
|
-
from comfy_env import install
|
|
6
|
-
install() # Auto-discovers comfy-env.toml and installs
|
|
7
|
-
"""
|
|
8
|
-
|
|
9
|
-
import inspect
|
|
10
|
-
from pathlib import Path
|
|
11
|
-
from typing import Callable, List, Optional, Set, Union
|
|
12
|
-
|
|
13
|
-
from .config.types import ComfyEnvConfig, NodeReq
|
|
14
|
-
from .config.parser import load_config, discover_config
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
def install(
|
|
18
|
-
config: Optional[Union[str, Path]] = None,
|
|
19
|
-
node_dir: Optional[Path] = None,
|
|
20
|
-
log_callback: Optional[Callable[[str], None]] = None,
|
|
21
|
-
dry_run: bool = False,
|
|
22
|
-
) -> bool:
|
|
23
|
-
"""
|
|
24
|
-
Install dependencies from comfy-env.toml.
|
|
25
|
-
|
|
26
|
-
Args:
|
|
27
|
-
config: Optional path to comfy-env.toml. Auto-discovered if not provided.
|
|
28
|
-
node_dir: Optional node directory. Auto-discovered from caller if not provided.
|
|
29
|
-
log_callback: Optional callback for logging. Defaults to print.
|
|
30
|
-
dry_run: If True, show what would be installed without installing.
|
|
31
|
-
|
|
32
|
-
Returns:
|
|
33
|
-
True if installation succeeded.
|
|
34
|
-
"""
|
|
35
|
-
# Auto-discover caller's directory if not provided
|
|
36
|
-
if node_dir is None:
|
|
37
|
-
frame = inspect.stack()[1]
|
|
38
|
-
caller_file = frame.filename
|
|
39
|
-
node_dir = Path(caller_file).parent.resolve()
|
|
40
|
-
|
|
41
|
-
log = log_callback or print
|
|
42
|
-
|
|
43
|
-
# Load config
|
|
44
|
-
if config is not None:
|
|
45
|
-
config_path = Path(config)
|
|
46
|
-
if not config_path.is_absolute():
|
|
47
|
-
config_path = node_dir / config_path
|
|
48
|
-
cfg = load_config(config_path)
|
|
49
|
-
else:
|
|
50
|
-
cfg = discover_config(node_dir)
|
|
51
|
-
|
|
52
|
-
if cfg is None:
|
|
53
|
-
raise FileNotFoundError(
|
|
54
|
-
f"No comfy-env.toml found in {node_dir}. "
|
|
55
|
-
"Create comfy-env.toml to define dependencies."
|
|
56
|
-
)
|
|
57
|
-
|
|
58
|
-
# Install node dependencies first
|
|
59
|
-
if cfg.node_reqs:
|
|
60
|
-
_install_node_dependencies(cfg.node_reqs, node_dir, log, dry_run)
|
|
61
|
-
|
|
62
|
-
# Install everything via pixi
|
|
63
|
-
_install_via_pixi(cfg, node_dir, log, dry_run)
|
|
64
|
-
|
|
65
|
-
# Auto-discover and install isolated subdirectory environments
|
|
66
|
-
_install_isolated_subdirs(node_dir, log, dry_run)
|
|
67
|
-
|
|
68
|
-
log("\nInstallation complete!")
|
|
69
|
-
return True
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
def _install_node_dependencies(
|
|
73
|
-
node_reqs: List[NodeReq],
|
|
74
|
-
node_dir: Path,
|
|
75
|
-
log: Callable[[str], None],
|
|
76
|
-
dry_run: bool,
|
|
77
|
-
) -> None:
|
|
78
|
-
"""Install node dependencies (other ComfyUI custom nodes)."""
|
|
79
|
-
from .nodes import install_node_deps
|
|
80
|
-
|
|
81
|
-
custom_nodes_dir = node_dir.parent
|
|
82
|
-
log(f"\nInstalling {len(node_reqs)} node dependencies...")
|
|
83
|
-
|
|
84
|
-
if dry_run:
|
|
85
|
-
for req in node_reqs:
|
|
86
|
-
node_path = custom_nodes_dir / req.name
|
|
87
|
-
status = "exists" if node_path.exists() else "would clone"
|
|
88
|
-
log(f" {req.name}: {status}")
|
|
89
|
-
return
|
|
90
|
-
|
|
91
|
-
visited: Set[str] = {node_dir.name}
|
|
92
|
-
install_node_deps(node_reqs, custom_nodes_dir, log, visited)
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
def _install_via_pixi(
|
|
96
|
-
cfg: ComfyEnvConfig,
|
|
97
|
-
node_dir: Path,
|
|
98
|
-
log: Callable[[str], None],
|
|
99
|
-
dry_run: bool,
|
|
100
|
-
) -> None:
|
|
101
|
-
"""Install all packages via pixi."""
|
|
102
|
-
from .pixi import pixi_install
|
|
103
|
-
|
|
104
|
-
# Count what we're installing
|
|
105
|
-
cuda_count = len(cfg.cuda_packages)
|
|
106
|
-
|
|
107
|
-
# Count from passthrough (pixi-native format)
|
|
108
|
-
deps = cfg.pixi_passthrough.get("dependencies", {})
|
|
109
|
-
pypi_deps = cfg.pixi_passthrough.get("pypi-dependencies", {})
|
|
110
|
-
|
|
111
|
-
if cuda_count == 0 and not deps and not pypi_deps:
|
|
112
|
-
log("No packages to install")
|
|
113
|
-
return
|
|
114
|
-
|
|
115
|
-
log(f"\nInstalling via pixi:")
|
|
116
|
-
if cuda_count:
|
|
117
|
-
log(f" CUDA packages: {', '.join(cfg.cuda_packages)}")
|
|
118
|
-
if deps:
|
|
119
|
-
log(f" Conda packages: {len(deps)}")
|
|
120
|
-
if pypi_deps:
|
|
121
|
-
log(f" PyPI packages: {len(pypi_deps)}")
|
|
122
|
-
|
|
123
|
-
if dry_run:
|
|
124
|
-
log("\n(dry run - no changes made)")
|
|
125
|
-
return
|
|
126
|
-
|
|
127
|
-
pixi_install(cfg, node_dir, log)
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
def _install_isolated_subdirs(
|
|
131
|
-
node_dir: Path,
|
|
132
|
-
log: Callable[[str], None],
|
|
133
|
-
dry_run: bool,
|
|
134
|
-
) -> None:
|
|
135
|
-
"""Find and install comfy-env.toml in subdirectories."""
|
|
136
|
-
from .pixi import pixi_install
|
|
137
|
-
from .config.parser import CONFIG_FILE_NAME
|
|
138
|
-
|
|
139
|
-
# Find all comfy-env.toml files in subdirectories (not root)
|
|
140
|
-
for config_file in node_dir.rglob(CONFIG_FILE_NAME):
|
|
141
|
-
if config_file.parent == node_dir:
|
|
142
|
-
continue # Skip root (already installed)
|
|
143
|
-
|
|
144
|
-
sub_dir = config_file.parent
|
|
145
|
-
relative = sub_dir.relative_to(node_dir)
|
|
146
|
-
|
|
147
|
-
log(f"\n[isolated] Installing: {relative}")
|
|
148
|
-
sub_cfg = load_config(config_file)
|
|
149
|
-
|
|
150
|
-
if dry_run:
|
|
151
|
-
log(f" (dry run)")
|
|
152
|
-
continue
|
|
153
|
-
|
|
154
|
-
pixi_install(sub_cfg, sub_dir, log)
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
def verify_installation(
|
|
158
|
-
packages: List[str],
|
|
159
|
-
log: Callable[[str], None] = print,
|
|
160
|
-
) -> bool:
|
|
161
|
-
"""Verify that packages are importable."""
|
|
162
|
-
all_ok = True
|
|
163
|
-
for package in packages:
|
|
164
|
-
import_name = package.replace("-", "_").split("[")[0]
|
|
165
|
-
try:
|
|
166
|
-
__import__(import_name)
|
|
167
|
-
log(f" {package}: OK")
|
|
168
|
-
except ImportError as e:
|
|
169
|
-
log(f" {package}: FAILED ({e})")
|
|
170
|
-
all_ok = False
|
|
171
|
-
return all_ok
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|