comfy-env 0.0.40__py3-none-any.whl → 0.0.42__py3-none-any.whl
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/__init__.py +8 -4
- comfy_env/cli.py +77 -147
- comfy_env/env/config.py +2 -0
- comfy_env/env/config_file.py +27 -3
- comfy_env/env/manager.py +30 -129
- comfy_env/install.py +115 -497
- comfy_env/registry.py +48 -9
- comfy_env/resolver.py +10 -187
- comfy_env/wheel_sources.yml +30 -85
- {comfy_env-0.0.40.dist-info → comfy_env-0.0.42.dist-info}/METADATA +118 -46
- {comfy_env-0.0.40.dist-info → comfy_env-0.0.42.dist-info}/RECORD +14 -15
- comfy_env/index_resolver.py +0 -132
- {comfy_env-0.0.40.dist-info → comfy_env-0.0.42.dist-info}/WHEEL +0 -0
- {comfy_env-0.0.40.dist-info → comfy_env-0.0.42.dist-info}/entry_points.txt +0 -0
- {comfy_env-0.0.40.dist-info → comfy_env-0.0.42.dist-info}/licenses/LICENSE +0 -0
comfy_env/__init__.py
CHANGED
|
@@ -1,4 +1,9 @@
|
|
|
1
|
-
|
|
1
|
+
from importlib.metadata import version, PackageNotFoundError
|
|
2
|
+
|
|
3
|
+
try:
|
|
4
|
+
__version__ = version("comfy-env")
|
|
5
|
+
except PackageNotFoundError:
|
|
6
|
+
__version__ = "0.0.0-dev" # Fallback for editable installs
|
|
2
7
|
|
|
3
8
|
from .env.config import IsolatedEnv, EnvManagerConfig, LocalConfig, NodeReq, CondaConfig
|
|
4
9
|
from .env.config_file import (
|
|
@@ -31,8 +36,8 @@ from .ipc.worker import BaseWorker, register
|
|
|
31
36
|
from .decorator import isolated, shutdown_all_processes
|
|
32
37
|
|
|
33
38
|
# New in-place installation API
|
|
34
|
-
from .install import install, verify_installation
|
|
35
|
-
from .resolver import RuntimeEnv
|
|
39
|
+
from .install import install, verify_installation
|
|
40
|
+
from .resolver import RuntimeEnv
|
|
36
41
|
|
|
37
42
|
# Pixi integration (for conda packages)
|
|
38
43
|
from .pixi import (
|
|
@@ -82,7 +87,6 @@ __all__ = [
|
|
|
82
87
|
"install",
|
|
83
88
|
"verify_installation",
|
|
84
89
|
"RuntimeEnv",
|
|
85
|
-
"WheelResolver",
|
|
86
90
|
# Pixi integration (for conda packages)
|
|
87
91
|
"ensure_pixi",
|
|
88
92
|
"get_pixi_path",
|
comfy_env/cli.py
CHANGED
|
@@ -10,7 +10,6 @@ Provides the `comfy-env` command with subcommands:
|
|
|
10
10
|
|
|
11
11
|
Usage:
|
|
12
12
|
comfy-env install
|
|
13
|
-
comfy-env install --isolated
|
|
14
13
|
comfy-env install --dry-run
|
|
15
14
|
|
|
16
15
|
comfy-env info
|
|
@@ -54,21 +53,11 @@ def main(args: Optional[List[str]] = None) -> int:
|
|
|
54
53
|
type=str,
|
|
55
54
|
help="Path to config file (default: auto-discover)",
|
|
56
55
|
)
|
|
57
|
-
install_parser.add_argument(
|
|
58
|
-
"--isolated",
|
|
59
|
-
action="store_true",
|
|
60
|
-
help="Create isolated venv instead of installing in-place",
|
|
61
|
-
)
|
|
62
56
|
install_parser.add_argument(
|
|
63
57
|
"--dry-run",
|
|
64
58
|
action="store_true",
|
|
65
59
|
help="Show what would be installed without installing",
|
|
66
60
|
)
|
|
67
|
-
install_parser.add_argument(
|
|
68
|
-
"--verify",
|
|
69
|
-
action="store_true",
|
|
70
|
-
help="Verify wheel URLs exist before installing",
|
|
71
|
-
)
|
|
72
61
|
install_parser.add_argument(
|
|
73
62
|
"--dir", "-d",
|
|
74
63
|
type=str,
|
|
@@ -108,11 +97,6 @@ def main(args: Optional[List[str]] = None) -> int:
|
|
|
108
97
|
type=str,
|
|
109
98
|
help="Path to config file",
|
|
110
99
|
)
|
|
111
|
-
resolve_parser.add_argument(
|
|
112
|
-
"--verify",
|
|
113
|
-
action="store_true",
|
|
114
|
-
help="Verify URLs exist (HTTP HEAD check)",
|
|
115
|
-
)
|
|
116
100
|
|
|
117
101
|
# doctor command
|
|
118
102
|
doctor_parser = subparsers.add_parser(
|
|
@@ -175,16 +159,13 @@ def cmd_install(args) -> int:
|
|
|
175
159
|
"""Handle install command."""
|
|
176
160
|
from .install import install
|
|
177
161
|
|
|
178
|
-
mode = "isolated" if args.isolated else "inplace"
|
|
179
162
|
node_dir = Path(args.dir) if args.dir else Path.cwd()
|
|
180
163
|
|
|
181
164
|
try:
|
|
182
165
|
install(
|
|
183
166
|
config=args.config,
|
|
184
|
-
mode=mode,
|
|
185
167
|
node_dir=node_dir,
|
|
186
168
|
dry_run=args.dry_run,
|
|
187
|
-
verify_wheels=args.verify,
|
|
188
169
|
)
|
|
189
170
|
return 0
|
|
190
171
|
except FileNotFoundError as e:
|
|
@@ -233,18 +214,15 @@ def cmd_info(args) -> int:
|
|
|
233
214
|
|
|
234
215
|
def cmd_resolve(args) -> int:
|
|
235
216
|
"""Handle resolve command."""
|
|
236
|
-
from .resolver import RuntimeEnv,
|
|
237
|
-
from .registry import PACKAGE_REGISTRY
|
|
217
|
+
from .resolver import RuntimeEnv, parse_wheel_requirement
|
|
218
|
+
from .registry import PACKAGE_REGISTRY, get_cuda_short2
|
|
238
219
|
from .env.config_file import discover_env_config, load_env_from_file
|
|
239
220
|
|
|
240
221
|
env = RuntimeEnv.detect()
|
|
241
|
-
resolver = WheelResolver()
|
|
242
|
-
|
|
243
222
|
packages = []
|
|
244
223
|
|
|
245
224
|
# Get packages from args or config
|
|
246
225
|
if args.all or (not args.packages and args.config):
|
|
247
|
-
# Load from config
|
|
248
226
|
if args.config:
|
|
249
227
|
config = load_env_from_file(Path(args.config))
|
|
250
228
|
else:
|
|
@@ -264,52 +242,57 @@ def cmd_resolve(args) -> int:
|
|
|
264
242
|
print(f"Resolving wheels for: {env}")
|
|
265
243
|
print("=" * 60)
|
|
266
244
|
|
|
245
|
+
# Build template variables
|
|
246
|
+
vars_dict = env.as_dict()
|
|
247
|
+
if env.cuda_version:
|
|
248
|
+
vars_dict["cuda_short2"] = get_cuda_short2(env.cuda_version)
|
|
249
|
+
|
|
267
250
|
all_ok = True
|
|
268
251
|
for pkg_spec in packages:
|
|
269
252
|
package, version = parse_wheel_requirement(pkg_spec)
|
|
270
|
-
if version is None:
|
|
271
|
-
print(f" {package}: No version specified, skipping")
|
|
272
|
-
continue
|
|
273
|
-
|
|
274
253
|
pkg_lower = package.lower()
|
|
254
|
+
|
|
275
255
|
try:
|
|
276
|
-
# Check if package is in registry with github_release method
|
|
277
256
|
if pkg_lower in PACKAGE_REGISTRY:
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
257
|
+
config = PACKAGE_REGISTRY[pkg_lower]
|
|
258
|
+
|
|
259
|
+
if "wheel_template" in config:
|
|
260
|
+
# Direct wheel URL template
|
|
261
|
+
effective_version = version or config.get("default_version")
|
|
262
|
+
if not effective_version:
|
|
263
|
+
print(f" {package}: No version specified (no default in registry)")
|
|
264
|
+
all_ok = False
|
|
265
|
+
continue
|
|
266
|
+
|
|
267
|
+
vars_dict["version"] = effective_version
|
|
268
|
+
url = _substitute_template(config["wheel_template"], vars_dict)
|
|
269
|
+
print(f" {package}=={effective_version}: resolved")
|
|
286
270
|
print(f" {url}")
|
|
271
|
+
|
|
272
|
+
elif "package_name" in config:
|
|
273
|
+
# PyPI variant (e.g., spconv-cu124)
|
|
274
|
+
pkg_name = _substitute_template(config["package_name"], vars_dict)
|
|
275
|
+
pkg_spec = f"{pkg_name}=={version}" if version else pkg_name
|
|
276
|
+
print(f" {package}: installs as {pkg_spec} from PyPI")
|
|
277
|
+
|
|
287
278
|
else:
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
if "index_url" in registry_config:
|
|
291
|
-
index_url = _substitute_template(registry_config["index_url"], env)
|
|
292
|
-
print(f" index: {index_url}")
|
|
293
|
-
elif "package_template" in registry_config:
|
|
294
|
-
pkg_name = _substitute_template(registry_config["package_template"], env)
|
|
295
|
-
print(f" installs as: {pkg_name}")
|
|
279
|
+
print(f" {package}: no wheel_template or package_name in registry")
|
|
280
|
+
all_ok = False
|
|
296
281
|
else:
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
282
|
+
print(f" {package}: NOT in registry")
|
|
283
|
+
print(f" Add to [wheel_sources] in comfy-env.toml:")
|
|
284
|
+
print(f' {package} = "https://example.com/{package}-{{version}}+cu{{cuda_short}}-{{py_tag}}-{{platform}}.whl"')
|
|
285
|
+
all_ok = False
|
|
286
|
+
|
|
302
287
|
except Exception as e:
|
|
303
|
-
print(f" {package}
|
|
304
|
-
_print_wheel_not_found_error(package, version, env, e)
|
|
288
|
+
print(f" {package}: FAILED - {e}")
|
|
305
289
|
all_ok = False
|
|
306
290
|
|
|
307
291
|
return 0 if all_ok else 1
|
|
308
292
|
|
|
309
293
|
|
|
310
|
-
def _substitute_template(template: str,
|
|
311
|
-
"""Substitute
|
|
312
|
-
vars_dict = env.as_dict()
|
|
294
|
+
def _substitute_template(template: str, vars_dict: dict) -> str:
|
|
295
|
+
"""Substitute {var} placeholders in template."""
|
|
313
296
|
result = template
|
|
314
297
|
for key, value in vars_dict.items():
|
|
315
298
|
if value is not None:
|
|
@@ -317,70 +300,6 @@ def _substitute_template(template: str, env) -> str:
|
|
|
317
300
|
return result
|
|
318
301
|
|
|
319
302
|
|
|
320
|
-
def _resolve_github_release_url(package: str, version: str, env, config: dict) -> str:
|
|
321
|
-
"""Resolve URL for github_release method packages."""
|
|
322
|
-
sources = config.get("sources", [])
|
|
323
|
-
if not sources:
|
|
324
|
-
raise ValueError(f"No sources configured for {package}")
|
|
325
|
-
|
|
326
|
-
# Build template variables
|
|
327
|
-
vars_dict = env.as_dict()
|
|
328
|
-
vars_dict["version"] = version
|
|
329
|
-
vars_dict["py_tag"] = f"cp{env.python_short}"
|
|
330
|
-
if env.cuda_version:
|
|
331
|
-
vars_dict["cuda_major"] = env.cuda_version.split(".")[0]
|
|
332
|
-
|
|
333
|
-
# Filter sources by platform
|
|
334
|
-
current_platform = env.platform_tag
|
|
335
|
-
compatible_sources = [
|
|
336
|
-
s for s in sources
|
|
337
|
-
if current_platform in s.get("platforms", [])
|
|
338
|
-
]
|
|
339
|
-
|
|
340
|
-
if not compatible_sources:
|
|
341
|
-
available = set()
|
|
342
|
-
for s in sources:
|
|
343
|
-
available.update(s.get("platforms", []))
|
|
344
|
-
raise ValueError(
|
|
345
|
-
f"No {package} wheels for platform {current_platform}. "
|
|
346
|
-
f"Available: {', '.join(sorted(available))}"
|
|
347
|
-
)
|
|
348
|
-
|
|
349
|
-
# Return URL from first compatible source
|
|
350
|
-
source = compatible_sources[0]
|
|
351
|
-
url_template = source.get("url_template", "")
|
|
352
|
-
url = url_template
|
|
353
|
-
for key, value in vars_dict.items():
|
|
354
|
-
if value is not None:
|
|
355
|
-
url = url.replace(f"{{{key}}}", str(value))
|
|
356
|
-
|
|
357
|
-
return url
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
def _print_wheel_not_found_error(package: str, version: str, env, error: Exception) -> None:
|
|
361
|
-
"""Print a formatted error message for wheel not found."""
|
|
362
|
-
from .errors import WheelNotFoundError
|
|
363
|
-
|
|
364
|
-
if isinstance(error, WheelNotFoundError):
|
|
365
|
-
print(f" CUDA wheel not found: {package}=={version}")
|
|
366
|
-
print()
|
|
367
|
-
print("+------------------------------------------------------------------+")
|
|
368
|
-
print("| CUDA Wheel Not Found |")
|
|
369
|
-
print("+------------------------------------------------------------------+")
|
|
370
|
-
print(f"| Package: {package}=={version:<46} |")
|
|
371
|
-
print(f"| Requested: cu{env.cuda_short}-torch{env.torch_mm}-{env.python_short}-{env.platform_tag:<17} |")
|
|
372
|
-
print("| |")
|
|
373
|
-
print(f"| Reason: {error.reason:<54} |")
|
|
374
|
-
print("| |")
|
|
375
|
-
print("| Suggestions: |")
|
|
376
|
-
print(f"| 1. Check if wheel exists: comfy-env resolve {package:<15} |")
|
|
377
|
-
print(f"| 2. Build wheel locally: comfy-env build {package:<18} |")
|
|
378
|
-
print("| |")
|
|
379
|
-
print("+------------------------------------------------------------------+")
|
|
380
|
-
else:
|
|
381
|
-
print(f" {error}")
|
|
382
|
-
|
|
383
|
-
|
|
384
303
|
def cmd_doctor(args) -> int:
|
|
385
304
|
"""Handle doctor command."""
|
|
386
305
|
from .install import verify_installation
|
|
@@ -409,7 +328,6 @@ def cmd_doctor(args) -> int:
|
|
|
409
328
|
packages = (config.requirements or []) + (config.no_deps_requirements or [])
|
|
410
329
|
|
|
411
330
|
if packages:
|
|
412
|
-
# Extract package names from specs
|
|
413
331
|
pkg_names = []
|
|
414
332
|
for pkg in packages:
|
|
415
333
|
name = pkg.split("==")[0].split(">=")[0].split("[")[0]
|
|
@@ -429,55 +347,67 @@ def cmd_doctor(args) -> int:
|
|
|
429
347
|
|
|
430
348
|
def cmd_list_packages(args) -> int:
|
|
431
349
|
"""Handle list-packages command."""
|
|
432
|
-
from .registry import PACKAGE_REGISTRY
|
|
350
|
+
from .registry import PACKAGE_REGISTRY
|
|
433
351
|
|
|
434
352
|
if args.json:
|
|
435
353
|
import json
|
|
436
354
|
result = {}
|
|
437
355
|
for name, config in PACKAGE_REGISTRY.items():
|
|
438
356
|
result[name] = {
|
|
439
|
-
"method": config["method"],
|
|
440
357
|
"description": config.get("description", ""),
|
|
441
358
|
}
|
|
442
|
-
if "
|
|
443
|
-
result[name]["
|
|
444
|
-
if "
|
|
445
|
-
result[name]["
|
|
359
|
+
if "wheel_template" in config:
|
|
360
|
+
result[name]["wheel_template"] = config["wheel_template"]
|
|
361
|
+
if "package_name" in config:
|
|
362
|
+
result[name]["package_name"] = config["package_name"]
|
|
363
|
+
if "default_version" in config:
|
|
364
|
+
result[name]["default_version"] = config["default_version"]
|
|
446
365
|
print(json.dumps(result, indent=2))
|
|
447
366
|
return 0
|
|
448
367
|
|
|
449
368
|
print("Built-in CUDA Package Registry")
|
|
450
369
|
print("=" * 60)
|
|
451
370
|
print()
|
|
452
|
-
print("These packages can be installed
|
|
453
|
-
print("Just add them to your comfy-env.toml:")
|
|
371
|
+
print("These packages can be installed by adding them to comfy-env.toml:")
|
|
454
372
|
print()
|
|
455
373
|
print(" [cuda]")
|
|
456
|
-
print(
|
|
457
|
-
print(
|
|
374
|
+
print(' nvdiffrast = "0.4.0"')
|
|
375
|
+
print(' torch-scatter = "2.1.2"')
|
|
376
|
+
print()
|
|
377
|
+
print("Or override with custom wheel source:")
|
|
378
|
+
print()
|
|
379
|
+
print(" [wheel_sources]")
|
|
380
|
+
print(' nvdiffrast = "https://my-mirror.com/nvdiffrast-{version}+cu{cuda_short}-{py_tag}-{platform}.whl"')
|
|
458
381
|
print()
|
|
459
382
|
print("-" * 60)
|
|
460
383
|
|
|
461
|
-
# Group
|
|
462
|
-
|
|
384
|
+
# Group by type
|
|
385
|
+
wheel_template_packages = []
|
|
386
|
+
package_name_packages = []
|
|
387
|
+
|
|
463
388
|
for name, config in PACKAGE_REGISTRY.items():
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
"
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
389
|
+
desc = config.get("description", "")
|
|
390
|
+
default = config.get("default_version", "")
|
|
391
|
+
if "wheel_template" in config:
|
|
392
|
+
wheel_template_packages.append((name, desc, default))
|
|
393
|
+
elif "package_name" in config:
|
|
394
|
+
package_name_packages.append((name, desc, config["package_name"]))
|
|
395
|
+
|
|
396
|
+
if wheel_template_packages:
|
|
397
|
+
print("\nDirect wheel URL packages:")
|
|
398
|
+
for name, desc, default in sorted(wheel_template_packages):
|
|
399
|
+
version_info = f" (default: {default})" if default else ""
|
|
400
|
+
print(f" {name:20} - {desc}{version_info}")
|
|
401
|
+
|
|
402
|
+
if package_name_packages:
|
|
403
|
+
print("\nPyPI variant packages:")
|
|
404
|
+
for name, desc, pkg_template in sorted(package_name_packages):
|
|
479
405
|
print(f" {name:20} - {desc}")
|
|
406
|
+
print(f" installs as: {pkg_template}")
|
|
480
407
|
|
|
408
|
+
print()
|
|
409
|
+
print("Template variables: {version}, {cuda_short}, {torch_mm}, {py_tag}, {platform}")
|
|
410
|
+
print("See README for full documentation on writing wheel templates.")
|
|
481
411
|
print()
|
|
482
412
|
return 0
|
|
483
413
|
|
comfy_env/env/config.py
CHANGED
|
@@ -68,12 +68,14 @@ class EnvManagerConfig:
|
|
|
68
68
|
[envname.packages] - Regular packages for isolated env
|
|
69
69
|
[node_reqs] - Node dependencies
|
|
70
70
|
[tools] - External tools (e.g., blender = "4.2")
|
|
71
|
+
[wheel_sources] - Custom wheel URL templates (override registry)
|
|
71
72
|
"""
|
|
72
73
|
system: SystemConfig = field(default_factory=SystemConfig)
|
|
73
74
|
local: LocalConfig = field(default_factory=LocalConfig)
|
|
74
75
|
envs: Dict[str, "IsolatedEnv"] = field(default_factory=dict)
|
|
75
76
|
node_reqs: List[NodeReq] = field(default_factory=list)
|
|
76
77
|
tools: Dict[str, ToolConfig] = field(default_factory=dict)
|
|
78
|
+
wheel_sources: Dict[str, str] = field(default_factory=dict) # package -> wheel_template URL
|
|
77
79
|
|
|
78
80
|
@property
|
|
79
81
|
def has_system(self) -> bool:
|
comfy_env/env/config_file.py
CHANGED
|
@@ -219,7 +219,7 @@ def _parse_config(data: Dict[str, Any], base_dir: Path) -> IsolatedEnv:
|
|
|
219
219
|
|
|
220
220
|
# Handle pytorch version - auto-derive if "auto" or not specified
|
|
221
221
|
pytorch_version = env_section.get("pytorch_version") or env_section.get("pytorch")
|
|
222
|
-
if pytorch_version == "auto" or
|
|
222
|
+
if pytorch_version == "auto" or pytorch_version is None:
|
|
223
223
|
pytorch_version = _get_default_pytorch_version(cuda)
|
|
224
224
|
|
|
225
225
|
if pytorch_version:
|
|
@@ -332,7 +332,7 @@ def _substitute_vars(s: str, variables: Dict[str, str]) -> str:
|
|
|
332
332
|
# =============================================================================
|
|
333
333
|
|
|
334
334
|
# Reserved table names that are NOT isolated environments
|
|
335
|
-
RESERVED_TABLES = {"local", "node_reqs", "env", "packages", "sources", "cuda", "variables", "worker", "tools", "system"}
|
|
335
|
+
RESERVED_TABLES = {"local", "node_reqs", "env", "packages", "sources", "cuda", "variables", "worker", "tools", "system", "wheel_sources"}
|
|
336
336
|
|
|
337
337
|
|
|
338
338
|
def load_config(
|
|
@@ -415,6 +415,7 @@ def _parse_full_config(data: Dict[str, Any], base_dir: Path) -> EnvManagerConfig
|
|
|
415
415
|
[envname.cuda] - CUDA packages for env
|
|
416
416
|
[envname.packages] - Packages for env
|
|
417
417
|
[node_reqs] - Node dependencies
|
|
418
|
+
[wheel_sources] - Custom wheel URL templates
|
|
418
419
|
|
|
419
420
|
Also supports simple format ([env] + [packages]) for backward compatibility.
|
|
420
421
|
"""
|
|
@@ -431,6 +432,7 @@ def _parse_full_config(data: Dict[str, Any], base_dir: Path) -> EnvManagerConfig
|
|
|
431
432
|
envs = _parse_env_sections(data, base_dir)
|
|
432
433
|
node_reqs = _parse_node_reqs(data.get("node_reqs", {}))
|
|
433
434
|
tools = _parse_tools_section(data.get("tools", {}))
|
|
435
|
+
wheel_sources = _parse_wheel_sources_section(data.get("wheel_sources", {}))
|
|
434
436
|
|
|
435
437
|
return EnvManagerConfig(
|
|
436
438
|
system=system,
|
|
@@ -438,6 +440,7 @@ def _parse_full_config(data: Dict[str, Any], base_dir: Path) -> EnvManagerConfig
|
|
|
438
440
|
envs=envs,
|
|
439
441
|
node_reqs=node_reqs,
|
|
440
442
|
tools=tools,
|
|
443
|
+
wheel_sources=wheel_sources,
|
|
441
444
|
)
|
|
442
445
|
|
|
443
446
|
|
|
@@ -656,6 +659,24 @@ def _parse_system_section(system_data: Dict[str, Any]) -> SystemConfig:
|
|
|
656
659
|
)
|
|
657
660
|
|
|
658
661
|
|
|
662
|
+
def _parse_wheel_sources_section(wheel_sources_data: Dict[str, Any]) -> Dict[str, str]:
|
|
663
|
+
"""Parse [wheel_sources] section.
|
|
664
|
+
|
|
665
|
+
Supports:
|
|
666
|
+
[wheel_sources]
|
|
667
|
+
nvdiffrast = "https://example.com/nvdiffrast-{version}+cu{cuda_short}-{py_tag}-{platform}.whl"
|
|
668
|
+
my-package = "https://my-server.com/my-package-{version}.whl"
|
|
669
|
+
|
|
670
|
+
Returns:
|
|
671
|
+
Dict mapping package name (lowercase) to wheel URL template
|
|
672
|
+
"""
|
|
673
|
+
wheel_sources = {}
|
|
674
|
+
for name, url_template in wheel_sources_data.items():
|
|
675
|
+
if isinstance(url_template, str):
|
|
676
|
+
wheel_sources[name.lower()] = url_template
|
|
677
|
+
return wheel_sources
|
|
678
|
+
|
|
679
|
+
|
|
659
680
|
def _convert_simple_to_full(data: Dict[str, Any], base_dir: Path) -> EnvManagerConfig:
|
|
660
681
|
"""Convert simple config format to full EnvManagerConfig.
|
|
661
682
|
|
|
@@ -665,9 +686,10 @@ def _convert_simple_to_full(data: Dict[str, Any], base_dir: Path) -> EnvManagerC
|
|
|
665
686
|
# Parse using simple parser to get IsolatedEnv
|
|
666
687
|
simple_env = _parse_config(data, base_dir)
|
|
667
688
|
|
|
668
|
-
# Parse tools and
|
|
689
|
+
# Parse tools, system, and wheel_sources sections (shared between simple and full format)
|
|
669
690
|
tools = _parse_tools_section(data.get("tools", {}))
|
|
670
691
|
system = _parse_system_section(data.get("system", {}))
|
|
692
|
+
wheel_sources = _parse_wheel_sources_section(data.get("wheel_sources", {}))
|
|
671
693
|
|
|
672
694
|
# Check if this has explicit env settings (isolated venv) vs just CUDA packages (local install)
|
|
673
695
|
env_section = data.get("env", {})
|
|
@@ -681,6 +703,7 @@ def _convert_simple_to_full(data: Dict[str, Any], base_dir: Path) -> EnvManagerC
|
|
|
681
703
|
envs={simple_env.name: simple_env},
|
|
682
704
|
node_reqs=[],
|
|
683
705
|
tools=tools,
|
|
706
|
+
wheel_sources=wheel_sources,
|
|
684
707
|
)
|
|
685
708
|
else:
|
|
686
709
|
# Local CUDA packages only (no isolated venv)
|
|
@@ -701,4 +724,5 @@ def _convert_simple_to_full(data: Dict[str, Any], base_dir: Path) -> EnvManagerC
|
|
|
701
724
|
envs={},
|
|
702
725
|
node_reqs=[],
|
|
703
726
|
tools=tools,
|
|
727
|
+
wheel_sources=wheel_sources,
|
|
704
728
|
)
|
comfy_env/env/manager.py
CHANGED
|
@@ -22,7 +22,6 @@ from .security import (
|
|
|
22
22
|
)
|
|
23
23
|
from ..registry import PACKAGE_REGISTRY, is_registered, get_cuda_short2
|
|
24
24
|
from ..resolver import RuntimeEnv, parse_wheel_requirement
|
|
25
|
-
from ..index_resolver import resolve_wheel_from_index
|
|
26
25
|
|
|
27
26
|
|
|
28
27
|
class IsolatedEnvManager:
|
|
@@ -373,7 +372,6 @@ class IsolatedEnvManager:
|
|
|
373
372
|
vars_dict = runtime_env.as_dict()
|
|
374
373
|
if env.cuda:
|
|
375
374
|
vars_dict["cuda_short2"] = get_cuda_short2(env.cuda)
|
|
376
|
-
# Add py_tag for wheel filename templates (e.g., cp310)
|
|
377
375
|
vars_dict["py_tag"] = f"cp{env.python.replace('.', '')}"
|
|
378
376
|
|
|
379
377
|
for req in env.no_deps_requirements:
|
|
@@ -382,142 +380,45 @@ class IsolatedEnvManager:
|
|
|
382
380
|
|
|
383
381
|
if pkg_lower in PACKAGE_REGISTRY:
|
|
384
382
|
config = PACKAGE_REGISTRY[pkg_lower]
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
wheel_url =
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
self.log(f" Wheel: {wheel_url}")
|
|
397
|
-
result = subprocess.run(
|
|
398
|
-
pip_args + ["--no-deps", wheel_url],
|
|
399
|
-
capture_output=True, text=True,
|
|
400
|
-
)
|
|
401
|
-
else:
|
|
402
|
-
# Fallback to index-based resolution
|
|
403
|
-
self.log(f" Index: {index_url}")
|
|
404
|
-
self.log(f" Package: {pkg_spec}")
|
|
405
|
-
result = subprocess.run(
|
|
406
|
-
pip_args + ["--extra-index-url", index_url, "--no-deps", pkg_spec],
|
|
407
|
-
capture_output=True, text=True,
|
|
408
|
-
)
|
|
409
|
-
|
|
410
|
-
elif method in ("github_index", "find_links"):
|
|
411
|
-
# GitHub Pages or generic find-links
|
|
412
|
-
index_url = self._substitute_template(config["index_url"], vars_dict)
|
|
413
|
-
pkg_spec = f"{package}=={version}" if version else package
|
|
414
|
-
# Try to resolve exact wheel URL from find-links page
|
|
415
|
-
wheel_url = resolve_wheel_from_index(index_url, package, vars_dict, version)
|
|
416
|
-
if wheel_url:
|
|
417
|
-
# Install from resolved URL directly (guarantees we get what we resolved)
|
|
418
|
-
self.log(f" Wheel: {wheel_url}")
|
|
419
|
-
result = subprocess.run(
|
|
420
|
-
pip_args + ["--no-deps", wheel_url],
|
|
421
|
-
capture_output=True, text=True,
|
|
422
|
-
)
|
|
423
|
-
else:
|
|
424
|
-
# Fallback to find-links based resolution
|
|
425
|
-
self.log(f" Find-links: {index_url}")
|
|
426
|
-
self.log(f" Package: {pkg_spec}")
|
|
427
|
-
result = subprocess.run(
|
|
428
|
-
pip_args + ["--find-links", index_url, "--no-deps", pkg_spec],
|
|
429
|
-
capture_output=True, text=True,
|
|
430
|
-
)
|
|
431
|
-
|
|
432
|
-
elif method == "pypi_variant":
|
|
433
|
-
# Transform package name based on CUDA version
|
|
434
|
-
actual_package = self._substitute_template(config["package_template"], vars_dict)
|
|
435
|
-
pkg_spec = f"{actual_package}=={version}" if version else actual_package
|
|
436
|
-
self.log(f" PyPI variant: {pkg_spec}")
|
|
383
|
+
|
|
384
|
+
if "wheel_template" in config:
|
|
385
|
+
# Direct wheel URL from template
|
|
386
|
+
effective_version = version or config.get("default_version")
|
|
387
|
+
if not effective_version:
|
|
388
|
+
raise RuntimeError(f"Package {package} requires version (no default in registry)")
|
|
389
|
+
|
|
390
|
+
vars_dict["version"] = effective_version
|
|
391
|
+
wheel_url = self._substitute_template(config["wheel_template"], vars_dict)
|
|
392
|
+
self.log(f" Installing {package}=={effective_version}...")
|
|
393
|
+
self.log(f" URL: {wheel_url}")
|
|
437
394
|
result = subprocess.run(
|
|
438
|
-
pip_args + ["--no-deps",
|
|
395
|
+
pip_args + ["--no-deps", wheel_url],
|
|
439
396
|
capture_output=True, text=True,
|
|
440
397
|
)
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
self.
|
|
447
|
-
|
|
448
|
-
)
|
|
449
|
-
continue # Already handled
|
|
450
|
-
|
|
451
|
-
else:
|
|
452
|
-
# Unknown method - try regular install
|
|
453
|
-
self.log(f" Unknown method '{method}', trying regular install")
|
|
454
|
-
pkg_spec = f"{package}=={version}" if version else package
|
|
398
|
+
if result.returncode != 0:
|
|
399
|
+
raise RuntimeError(f"Failed to install {package}: {result.stderr}")
|
|
400
|
+
|
|
401
|
+
elif "package_name" in config:
|
|
402
|
+
# PyPI variant (e.g., spconv-cu124)
|
|
403
|
+
pkg_name = self._substitute_template(config["package_name"], vars_dict)
|
|
404
|
+
pkg_spec = f"{pkg_name}=={version}" if version else pkg_name
|
|
405
|
+
self.log(f" Installing {package} as {pkg_spec}...")
|
|
455
406
|
result = subprocess.run(
|
|
456
|
-
pip_args + [
|
|
407
|
+
pip_args + [pkg_spec],
|
|
457
408
|
capture_output=True, text=True,
|
|
458
409
|
)
|
|
410
|
+
if result.returncode != 0:
|
|
411
|
+
raise RuntimeError(f"Failed to install {package}: {result.stderr}")
|
|
412
|
+
|
|
413
|
+
else:
|
|
414
|
+
raise RuntimeError(f"Package {package} in registry but missing wheel_template or package_name")
|
|
459
415
|
|
|
460
|
-
if result.returncode != 0:
|
|
461
|
-
raise RuntimeError(f"Failed to install {package}: {result.stderr}")
|
|
462
416
|
else:
|
|
463
|
-
# Not in registry -
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
pip_args + ["--no-deps", pkg_spec],
|
|
468
|
-
capture_output=True, text=True,
|
|
417
|
+
# Not in registry - error
|
|
418
|
+
raise RuntimeError(
|
|
419
|
+
f"Package {package} not found in registry. "
|
|
420
|
+
f"Add wheel_template to [wheel_sources] in comfy-env.toml"
|
|
469
421
|
)
|
|
470
|
-
if result.returncode != 0:
|
|
471
|
-
raise RuntimeError(f"Failed to install {package}: {result.stderr}")
|
|
472
|
-
|
|
473
|
-
def _install_from_github_release(
|
|
474
|
-
self,
|
|
475
|
-
package: str,
|
|
476
|
-
version: str,
|
|
477
|
-
vars_dict: dict,
|
|
478
|
-
config: dict,
|
|
479
|
-
pip_args: list,
|
|
480
|
-
) -> None:
|
|
481
|
-
"""Install package from GitHub release wheels with fallback sources."""
|
|
482
|
-
import platform as plat
|
|
483
|
-
import sys
|
|
484
|
-
# Use consistent platform tags (win_amd64, linux_x86_64, etc.)
|
|
485
|
-
if sys.platform == 'win32':
|
|
486
|
-
machine = plat.machine().lower()
|
|
487
|
-
current_platform = 'win_amd64' if machine in ('amd64', 'x86_64') else f'win_{machine}'
|
|
488
|
-
elif sys.platform == 'darwin':
|
|
489
|
-
current_platform = f"macosx_{plat.machine()}"
|
|
490
|
-
else:
|
|
491
|
-
current_platform = f"linux_{plat.machine()}"
|
|
492
|
-
|
|
493
|
-
sources = config.get("sources", [])
|
|
494
|
-
errors = []
|
|
495
|
-
|
|
496
|
-
for source in sources:
|
|
497
|
-
# Check platform compatibility
|
|
498
|
-
platforms = source.get("platforms", [])
|
|
499
|
-
if platforms and not any(p in current_platform for p in platforms):
|
|
500
|
-
continue
|
|
501
|
-
|
|
502
|
-
url_template = source["url_template"]
|
|
503
|
-
url = self._substitute_template(url_template, vars_dict)
|
|
504
|
-
|
|
505
|
-
self.log(f" Trying {source.get('name', 'unknown')}: {url}")
|
|
506
|
-
result = subprocess.run(
|
|
507
|
-
pip_args + ["--no-deps", url],
|
|
508
|
-
capture_output=True, text=True,
|
|
509
|
-
)
|
|
510
|
-
|
|
511
|
-
if result.returncode == 0:
|
|
512
|
-
return # Success!
|
|
513
|
-
|
|
514
|
-
errors.append(f"{source.get('name', 'unknown')}: {result.stderr.strip()}")
|
|
515
|
-
|
|
516
|
-
# All sources failed
|
|
517
|
-
raise RuntimeError(
|
|
518
|
-
f"Failed to install {package}=={version} from any source:\n"
|
|
519
|
-
+ "\n".join(errors)
|
|
520
|
-
)
|
|
521
422
|
|
|
522
423
|
def _substitute_template(self, template: str, vars_dict: dict) -> str:
|
|
523
424
|
"""Substitute template variables with environment values."""
|