comfy-env 0.1.14__py3-none-any.whl → 0.1.16__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 +115 -62
- comfy_env/cli.py +89 -319
- comfy_env/config/__init__.py +18 -8
- comfy_env/config/parser.py +21 -122
- comfy_env/config/types.py +37 -70
- comfy_env/detection/__init__.py +77 -0
- comfy_env/detection/cuda.py +61 -0
- comfy_env/detection/gpu.py +230 -0
- comfy_env/detection/platform.py +70 -0
- comfy_env/detection/runtime.py +103 -0
- comfy_env/environment/__init__.py +53 -0
- comfy_env/environment/cache.py +141 -0
- comfy_env/environment/libomp.py +41 -0
- comfy_env/environment/paths.py +38 -0
- comfy_env/environment/setup.py +88 -0
- comfy_env/install.py +163 -249
- comfy_env/isolation/__init__.py +33 -2
- comfy_env/isolation/tensor_utils.py +83 -0
- comfy_env/isolation/workers/__init__.py +16 -0
- comfy_env/{workers → isolation/workers}/mp.py +1 -1
- comfy_env/{workers → isolation/workers}/subprocess.py +2 -2
- comfy_env/isolation/wrap.py +149 -409
- comfy_env/packages/__init__.py +60 -0
- comfy_env/packages/apt.py +36 -0
- comfy_env/packages/cuda_wheels.py +97 -0
- comfy_env/packages/node_dependencies.py +77 -0
- comfy_env/packages/pixi.py +85 -0
- comfy_env/packages/toml_generator.py +88 -0
- comfy_env-0.1.16.dist-info/METADATA +279 -0
- comfy_env-0.1.16.dist-info/RECORD +36 -0
- comfy_env/cache.py +0 -331
- comfy_env/errors.py +0 -293
- comfy_env/nodes.py +0 -187
- comfy_env/pixi/__init__.py +0 -48
- comfy_env/pixi/core.py +0 -588
- comfy_env/pixi/cuda_detection.py +0 -303
- comfy_env/pixi/platform/__init__.py +0 -21
- comfy_env/pixi/platform/base.py +0 -96
- comfy_env/pixi/platform/darwin.py +0 -53
- comfy_env/pixi/platform/linux.py +0 -68
- comfy_env/pixi/platform/windows.py +0 -284
- comfy_env/pixi/resolver.py +0 -198
- comfy_env/prestartup.py +0 -192
- comfy_env/workers/__init__.py +0 -38
- comfy_env/workers/tensor_utils.py +0 -188
- comfy_env-0.1.14.dist-info/METADATA +0 -291
- comfy_env-0.1.14.dist-info/RECORD +0 -33
- /comfy_env/{workers → isolation/workers}/base.py +0 -0
- {comfy_env-0.1.14.dist-info → comfy_env-0.1.16.dist-info}/WHEEL +0 -0
- {comfy_env-0.1.14.dist-info → comfy_env-0.1.16.dist-info}/entry_points.txt +0 -0
- {comfy_env-0.1.14.dist-info → comfy_env-0.1.16.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
"""Platform detection - OS, architecture, platform tags."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import platform as platform_module
|
|
6
|
+
import sys
|
|
7
|
+
from dataclasses import dataclass
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@dataclass
|
|
11
|
+
class PlatformInfo:
|
|
12
|
+
os_name: str # linux, windows, darwin
|
|
13
|
+
arch: str # x86_64, aarch64, arm64
|
|
14
|
+
platform_tag: str # linux_x86_64, win_amd64, macosx_11_0_arm64
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def detect_platform() -> PlatformInfo:
|
|
18
|
+
return PlatformInfo(
|
|
19
|
+
os_name=_get_os_name(),
|
|
20
|
+
arch=platform_module.machine().lower(),
|
|
21
|
+
platform_tag=get_platform_tag(),
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def _get_os_name() -> str:
|
|
26
|
+
if sys.platform.startswith('linux'): return 'linux'
|
|
27
|
+
if sys.platform == 'win32': return 'windows'
|
|
28
|
+
if sys.platform == 'darwin': return 'darwin'
|
|
29
|
+
return sys.platform
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
_PLATFORM_TAGS = {
|
|
33
|
+
('linux', 'x86_64'): 'linux_x86_64', ('linux', 'amd64'): 'linux_x86_64',
|
|
34
|
+
('linux', 'aarch64'): 'linux_aarch64',
|
|
35
|
+
('win32', 'amd64'): 'win_amd64', ('win32', 'x86_64'): 'win_amd64',
|
|
36
|
+
('darwin', 'arm64'): 'macosx_11_0_arm64',
|
|
37
|
+
('darwin', 'x86_64'): 'macosx_10_9_x86_64',
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def get_platform_tag() -> str:
|
|
42
|
+
key = (sys.platform if sys.platform != 'linux' else 'linux', platform_module.machine().lower())
|
|
43
|
+
return _PLATFORM_TAGS.get(key, f'{sys.platform}_{platform_module.machine().lower()}')
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
_PIXI_PLATFORMS = {
|
|
47
|
+
('linux', 'x86_64'): 'linux-64', ('linux', 'amd64'): 'linux-64',
|
|
48
|
+
('linux', 'aarch64'): 'linux-aarch64',
|
|
49
|
+
('windows', 'amd64'): 'win-64', ('windows', 'x86_64'): 'win-64',
|
|
50
|
+
('darwin', 'arm64'): 'osx-arm64',
|
|
51
|
+
('darwin', 'x86_64'): 'osx-64',
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def get_pixi_platform() -> str:
|
|
56
|
+
key = (_get_os_name(), platform_module.machine().lower())
|
|
57
|
+
return _PIXI_PLATFORMS.get(key, f'{key[0]}-{key[1]}')
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def get_library_extension() -> str:
|
|
61
|
+
return {'.dll': 'windows', '.dylib': 'darwin'}.get(_get_os_name(), '.so')
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def get_executable_suffix() -> str:
|
|
65
|
+
return '.exe' if _get_os_name() == 'windows' else ''
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def is_linux() -> bool: return _get_os_name() == 'linux'
|
|
69
|
+
def is_windows() -> bool: return _get_os_name() == 'windows'
|
|
70
|
+
def is_macos() -> bool: return _get_os_name() == 'darwin'
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
"""Runtime environment detection - combines all detection into a single snapshot."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import sys
|
|
6
|
+
from dataclasses import dataclass
|
|
7
|
+
from typing import Dict, Optional
|
|
8
|
+
|
|
9
|
+
from .cuda import detect_cuda_version
|
|
10
|
+
from .gpu import detect_cuda_environment
|
|
11
|
+
from .platform import get_platform_tag, _get_os_name
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@dataclass
|
|
15
|
+
class RuntimeEnv:
|
|
16
|
+
"""Detected runtime environment for wheel resolution."""
|
|
17
|
+
os_name: str
|
|
18
|
+
platform_tag: str
|
|
19
|
+
python_version: str
|
|
20
|
+
python_short: str
|
|
21
|
+
cuda_version: Optional[str]
|
|
22
|
+
cuda_short: Optional[str]
|
|
23
|
+
torch_version: Optional[str]
|
|
24
|
+
torch_short: Optional[str]
|
|
25
|
+
torch_mm: Optional[str]
|
|
26
|
+
gpu_name: Optional[str] = None
|
|
27
|
+
gpu_compute: Optional[str] = None
|
|
28
|
+
|
|
29
|
+
@classmethod
|
|
30
|
+
def detect(cls, torch_version: Optional[str] = None) -> "RuntimeEnv":
|
|
31
|
+
"""Detect runtime environment from current system."""
|
|
32
|
+
py_version = f"{sys.version_info.major}.{sys.version_info.minor}"
|
|
33
|
+
cuda_version = detect_cuda_version()
|
|
34
|
+
torch_version = torch_version or _detect_torch_version()
|
|
35
|
+
|
|
36
|
+
gpu_name, gpu_compute = None, None
|
|
37
|
+
try:
|
|
38
|
+
env = detect_cuda_environment()
|
|
39
|
+
if env.gpus:
|
|
40
|
+
gpu_name, gpu_compute = env.gpus[0].name, env.gpus[0].sm_version()
|
|
41
|
+
except Exception:
|
|
42
|
+
pass
|
|
43
|
+
|
|
44
|
+
return cls(
|
|
45
|
+
os_name=_get_os_name(),
|
|
46
|
+
platform_tag=get_platform_tag(),
|
|
47
|
+
python_version=py_version,
|
|
48
|
+
python_short=py_version.replace(".", ""),
|
|
49
|
+
cuda_version=cuda_version,
|
|
50
|
+
cuda_short=cuda_version.replace(".", "") if cuda_version else None,
|
|
51
|
+
torch_version=torch_version,
|
|
52
|
+
torch_short=torch_version.replace(".", "") if torch_version else None,
|
|
53
|
+
torch_mm="".join(torch_version.split(".")[:2]) if torch_version else None,
|
|
54
|
+
gpu_name=gpu_name,
|
|
55
|
+
gpu_compute=gpu_compute,
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
def as_dict(self) -> Dict[str, str]:
|
|
59
|
+
"""Convert to dict for template substitution."""
|
|
60
|
+
result = {
|
|
61
|
+
"os": self.os_name,
|
|
62
|
+
"platform": self.platform_tag,
|
|
63
|
+
"python_version": self.python_version,
|
|
64
|
+
"py_version": self.python_version,
|
|
65
|
+
"py_short": self.python_short,
|
|
66
|
+
"py_minor": self.python_version.split(".")[-1],
|
|
67
|
+
"py_tag": f"cp{self.python_short}",
|
|
68
|
+
}
|
|
69
|
+
if self.cuda_version:
|
|
70
|
+
result.update(cuda_version=self.cuda_version, cuda_short=self.cuda_short,
|
|
71
|
+
cuda_major=self.cuda_version.split(".")[0])
|
|
72
|
+
if self.torch_version:
|
|
73
|
+
result.update(torch_version=self.torch_version, torch_short=self.torch_short,
|
|
74
|
+
torch_mm=self.torch_mm, torch_dotted_mm=".".join(self.torch_version.split(".")[:2]))
|
|
75
|
+
return result
|
|
76
|
+
|
|
77
|
+
def __str__(self) -> str:
|
|
78
|
+
parts = [f"Python {self.python_version}",
|
|
79
|
+
f"CUDA {self.cuda_version}" if self.cuda_version else "CPU"]
|
|
80
|
+
if self.torch_version: parts.append(f"PyTorch {self.torch_version}")
|
|
81
|
+
if self.gpu_name: parts.append(f"GPU: {self.gpu_name}")
|
|
82
|
+
return ", ".join(parts)
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def detect_runtime(torch_version: Optional[str] = None) -> RuntimeEnv:
|
|
86
|
+
return RuntimeEnv.detect(torch_version)
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def _detect_torch_version() -> Optional[str]:
|
|
90
|
+
try:
|
|
91
|
+
import torch
|
|
92
|
+
return torch.__version__.split('+')[0]
|
|
93
|
+
except ImportError:
|
|
94
|
+
return None
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def parse_wheel_requirement(req: str) -> tuple[str, Optional[str]]:
|
|
98
|
+
"""Parse 'pkg==1.0' -> ('pkg', '1.0')"""
|
|
99
|
+
for op in ['==', '>=', '<=', '~=', '!=', '>', '<']:
|
|
100
|
+
if op in req:
|
|
101
|
+
parts = req.split(op, 1)
|
|
102
|
+
return (parts[0].strip(), parts[1].strip())
|
|
103
|
+
return (req.strip(), None)
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Environment layer - Environment management with side effects.
|
|
3
|
+
|
|
4
|
+
Handles environment caching, path resolution, and runtime setup.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from .cache import (
|
|
8
|
+
CACHE_DIR,
|
|
9
|
+
MARKER_FILE,
|
|
10
|
+
get_cache_dir,
|
|
11
|
+
get_env_name,
|
|
12
|
+
get_env_path,
|
|
13
|
+
cleanup_orphaned_envs,
|
|
14
|
+
write_marker_file,
|
|
15
|
+
read_marker_file,
|
|
16
|
+
)
|
|
17
|
+
from .paths import (
|
|
18
|
+
resolve_env_path,
|
|
19
|
+
get_site_packages_path,
|
|
20
|
+
get_lib_path,
|
|
21
|
+
copy_files,
|
|
22
|
+
)
|
|
23
|
+
from .setup import (
|
|
24
|
+
setup_env,
|
|
25
|
+
load_env_vars,
|
|
26
|
+
inject_site_packages,
|
|
27
|
+
)
|
|
28
|
+
from .libomp import (
|
|
29
|
+
dedupe_libomp,
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
__all__ = [
|
|
33
|
+
# Cache management
|
|
34
|
+
"CACHE_DIR",
|
|
35
|
+
"MARKER_FILE",
|
|
36
|
+
"get_cache_dir",
|
|
37
|
+
"get_env_name",
|
|
38
|
+
"get_env_path",
|
|
39
|
+
"cleanup_orphaned_envs",
|
|
40
|
+
"write_marker_file",
|
|
41
|
+
"read_marker_file",
|
|
42
|
+
# Path resolution
|
|
43
|
+
"resolve_env_path",
|
|
44
|
+
"get_site_packages_path",
|
|
45
|
+
"get_lib_path",
|
|
46
|
+
"copy_files",
|
|
47
|
+
# Setup helpers
|
|
48
|
+
"setup_env",
|
|
49
|
+
"load_env_vars",
|
|
50
|
+
"inject_site_packages",
|
|
51
|
+
# macOS workaround
|
|
52
|
+
"dedupe_libomp",
|
|
53
|
+
]
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
"""Central environment cache at ~/.comfy-env/envs/"""
|
|
2
|
+
|
|
3
|
+
import glob
|
|
4
|
+
import hashlib
|
|
5
|
+
import shutil
|
|
6
|
+
import sys
|
|
7
|
+
from datetime import datetime
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from typing import Callable, Optional, Tuple
|
|
10
|
+
|
|
11
|
+
import tomli
|
|
12
|
+
import tomli_w
|
|
13
|
+
|
|
14
|
+
try:
|
|
15
|
+
from .. import __version__
|
|
16
|
+
except ImportError:
|
|
17
|
+
__version__ = "0.0.0-dev"
|
|
18
|
+
|
|
19
|
+
CACHE_DIR = Path.home() / ".comfy-env" / "envs"
|
|
20
|
+
MARKER_FILE = ".comfy-env-marker.toml"
|
|
21
|
+
METADATA_FILE = ".comfy-env-metadata.toml"
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def get_cache_dir() -> Path:
|
|
25
|
+
CACHE_DIR.mkdir(parents=True, exist_ok=True)
|
|
26
|
+
return CACHE_DIR
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def compute_config_hash(config_path: Path) -> str:
|
|
30
|
+
return hashlib.sha256(config_path.read_bytes()).hexdigest()[:8]
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def sanitize_name(name: str) -> str:
|
|
34
|
+
name = name.lower()
|
|
35
|
+
for prefix in ("comfyui-", "comfyui_"):
|
|
36
|
+
if name.startswith(prefix): name = name[len(prefix):]
|
|
37
|
+
return name.replace("-", "_").replace(" ", "_")
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def get_env_name(node_dir: Path, config_path: Path) -> str:
|
|
41
|
+
"""Generate env name: <nodename>_<subfolder>_<hash>"""
|
|
42
|
+
node_name = sanitize_name(node_dir.name)
|
|
43
|
+
config_parent = config_path.parent
|
|
44
|
+
if config_parent == node_dir:
|
|
45
|
+
subfolder = ""
|
|
46
|
+
else:
|
|
47
|
+
try:
|
|
48
|
+
subfolder = config_parent.relative_to(node_dir).as_posix().replace("/", "_")
|
|
49
|
+
except ValueError:
|
|
50
|
+
subfolder = sanitize_name(config_parent.name)
|
|
51
|
+
return f"{node_name}_{subfolder}_{compute_config_hash(config_path)}"
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def get_env_path(node_dir: Path, config_path: Path) -> Path:
|
|
55
|
+
return get_cache_dir() / get_env_name(node_dir, config_path)
|
|
56
|
+
|
|
57
|
+
get_central_env_path = get_env_path
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def write_marker_file(config_path: Path, env_path: Path) -> None:
|
|
61
|
+
marker_path = config_path.parent / MARKER_FILE
|
|
62
|
+
marker_path.write_text(tomli_w.dumps({
|
|
63
|
+
"env": {"name": env_path.name, "path": str(env_path),
|
|
64
|
+
"config_hash": compute_config_hash(config_path),
|
|
65
|
+
"created": datetime.now().isoformat(), "comfy_env_version": __version__}
|
|
66
|
+
}))
|
|
67
|
+
|
|
68
|
+
write_marker = write_marker_file
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def write_env_metadata(env_path: Path, marker_path: Path) -> None:
|
|
72
|
+
(env_path / METADATA_FILE).write_text(tomli_w.dumps({
|
|
73
|
+
"marker_path": str(marker_path), "created": datetime.now().isoformat()
|
|
74
|
+
}))
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def read_marker_file(marker_path: Path) -> Optional[dict]:
|
|
78
|
+
if not marker_path.exists(): return None
|
|
79
|
+
try:
|
|
80
|
+
with open(marker_path, "rb") as f: return tomli.load(f)
|
|
81
|
+
except Exception: return None
|
|
82
|
+
|
|
83
|
+
read_marker = read_marker_file
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def read_env_metadata(env_path: Path) -> Optional[dict]:
|
|
87
|
+
metadata_path = env_path / METADATA_FILE
|
|
88
|
+
if not metadata_path.exists(): return None
|
|
89
|
+
try:
|
|
90
|
+
with open(metadata_path, "rb") as f: return tomli.load(f)
|
|
91
|
+
except Exception: return None
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def resolve_env_path(node_dir: Path) -> Tuple[Optional[Path], Optional[Path], Optional[Path]]:
|
|
95
|
+
"""Resolve env with fallback: marker -> _env_<name> -> .pixi -> .venv"""
|
|
96
|
+
# Marker
|
|
97
|
+
marker = read_marker_file(node_dir / MARKER_FILE)
|
|
98
|
+
if marker and "env" in marker:
|
|
99
|
+
env_path = Path(marker["env"]["path"])
|
|
100
|
+
if env_path.exists(): return _get_env_paths(env_path)
|
|
101
|
+
|
|
102
|
+
# Local _env_<name>
|
|
103
|
+
local_env = node_dir / f"_env_{sanitize_name(node_dir.name)}"
|
|
104
|
+
if local_env.exists(): return _get_env_paths(local_env)
|
|
105
|
+
|
|
106
|
+
# .pixi
|
|
107
|
+
pixi_env = node_dir / ".pixi" / "envs" / "default"
|
|
108
|
+
if pixi_env.exists(): return _get_env_paths(pixi_env)
|
|
109
|
+
|
|
110
|
+
# .venv
|
|
111
|
+
venv = node_dir / ".venv"
|
|
112
|
+
if venv.exists(): return _get_env_paths(venv)
|
|
113
|
+
|
|
114
|
+
return None, None, None
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def _get_env_paths(env_path: Path) -> Tuple[Path, Optional[Path], Optional[Path]]:
|
|
118
|
+
if sys.platform == "win32":
|
|
119
|
+
return env_path, env_path / "Lib" / "site-packages", env_path / "Library" / "bin"
|
|
120
|
+
matches = glob.glob(str(env_path / "lib" / "python*" / "site-packages"))
|
|
121
|
+
return env_path, Path(matches[0]) if matches else None, env_path / "lib"
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def cleanup_orphaned_envs(log: Callable[[str], None] = print) -> int:
|
|
125
|
+
"""Remove envs whose marker files no longer exist."""
|
|
126
|
+
cache_dir = get_cache_dir()
|
|
127
|
+
if not cache_dir.exists(): return 0
|
|
128
|
+
|
|
129
|
+
cleaned = 0
|
|
130
|
+
for env_dir in cache_dir.iterdir():
|
|
131
|
+
if not env_dir.is_dir(): continue
|
|
132
|
+
metadata = read_env_metadata(env_dir)
|
|
133
|
+
if not metadata: continue
|
|
134
|
+
marker_path = metadata.get("marker_path", "")
|
|
135
|
+
if marker_path and not Path(marker_path).exists():
|
|
136
|
+
log(f"[comfy-env] Cleaning: {env_dir.name}")
|
|
137
|
+
try:
|
|
138
|
+
shutil.rmtree(env_dir)
|
|
139
|
+
cleaned += 1
|
|
140
|
+
except Exception: pass
|
|
141
|
+
return cleaned
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"""macOS: Dedupe libomp.dylib to prevent OpenMP runtime conflicts."""
|
|
2
|
+
|
|
3
|
+
import glob
|
|
4
|
+
import os
|
|
5
|
+
import sys
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def dedupe_libomp() -> None:
|
|
9
|
+
"""Symlink all libomp copies to torch's to prevent dual-runtime crashes."""
|
|
10
|
+
if sys.platform != "darwin":
|
|
11
|
+
return
|
|
12
|
+
|
|
13
|
+
try:
|
|
14
|
+
import torch
|
|
15
|
+
torch_libomp = os.path.join(os.path.dirname(torch.__file__), 'lib', 'libomp.dylib')
|
|
16
|
+
if not os.path.exists(torch_libomp):
|
|
17
|
+
return
|
|
18
|
+
except ImportError:
|
|
19
|
+
return
|
|
20
|
+
|
|
21
|
+
site_packages = os.path.dirname(os.path.dirname(torch.__file__))
|
|
22
|
+
patterns = [
|
|
23
|
+
os.path.join(site_packages, '*', 'Frameworks', 'libomp.dylib'),
|
|
24
|
+
os.path.join(site_packages, '*', '.dylibs', 'libomp.dylib'),
|
|
25
|
+
os.path.join(site_packages, '*', 'lib', 'libomp.dylib'),
|
|
26
|
+
]
|
|
27
|
+
|
|
28
|
+
for pattern in patterns:
|
|
29
|
+
for libomp in glob.glob(pattern):
|
|
30
|
+
if 'torch' in libomp:
|
|
31
|
+
continue
|
|
32
|
+
if os.path.islink(libomp) and os.path.realpath(libomp) == os.path.realpath(torch_libomp):
|
|
33
|
+
continue
|
|
34
|
+
try:
|
|
35
|
+
if os.path.islink(libomp):
|
|
36
|
+
os.unlink(libomp)
|
|
37
|
+
else:
|
|
38
|
+
os.rename(libomp, libomp + '.bak')
|
|
39
|
+
os.symlink(torch_libomp, libomp)
|
|
40
|
+
except OSError:
|
|
41
|
+
pass
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"""Environment path utilities."""
|
|
2
|
+
|
|
3
|
+
import shutil
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import Optional, Tuple
|
|
6
|
+
|
|
7
|
+
from .cache import resolve_env_path as _resolve_env_path
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def get_site_packages_path(node_dir: Path) -> Optional[Path]:
|
|
11
|
+
_, site_packages, _ = _resolve_env_path(node_dir)
|
|
12
|
+
return site_packages
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def get_lib_path(node_dir: Path) -> Optional[Path]:
|
|
16
|
+
_, _, lib_dir = _resolve_env_path(node_dir)
|
|
17
|
+
return lib_dir
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def resolve_env_path(node_dir: Path) -> Tuple[Optional[Path], Optional[Path], Optional[Path]]:
|
|
21
|
+
return _resolve_env_path(node_dir)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def copy_files(src, dst, pattern: str = "*", overwrite: bool = False) -> int:
|
|
25
|
+
"""Copy files matching pattern from src to dst."""
|
|
26
|
+
src, dst = Path(src), Path(dst)
|
|
27
|
+
if not src.exists(): return 0
|
|
28
|
+
|
|
29
|
+
dst.mkdir(parents=True, exist_ok=True)
|
|
30
|
+
copied = 0
|
|
31
|
+
for f in src.glob(pattern):
|
|
32
|
+
if f.is_file():
|
|
33
|
+
target = dst / f.relative_to(src)
|
|
34
|
+
target.parent.mkdir(parents=True, exist_ok=True)
|
|
35
|
+
if overwrite or not target.exists():
|
|
36
|
+
shutil.copy2(f, target)
|
|
37
|
+
copied += 1
|
|
38
|
+
return copied
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
"""Environment setup for ComfyUI prestartup."""
|
|
2
|
+
|
|
3
|
+
import glob
|
|
4
|
+
import os
|
|
5
|
+
import sys
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import Dict, Optional
|
|
8
|
+
|
|
9
|
+
from .cache import MARKER_FILE, sanitize_name
|
|
10
|
+
from .libomp import dedupe_libomp
|
|
11
|
+
|
|
12
|
+
USE_COMFY_ENV_VAR = "USE_COMFY_ENV"
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def is_comfy_env_enabled() -> bool:
|
|
16
|
+
return os.environ.get(USE_COMFY_ENV_VAR, "1").lower() not in ("0", "false", "no", "off")
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def load_env_vars(config_path: str) -> Dict[str, str]:
|
|
20
|
+
"""Load [env_vars] from comfy-env.toml."""
|
|
21
|
+
if not os.path.exists(config_path): return {}
|
|
22
|
+
try:
|
|
23
|
+
import tomli
|
|
24
|
+
with open(config_path, "rb") as f:
|
|
25
|
+
return {str(k): str(v) for k, v in tomli.load(f).get("env_vars", {}).items()}
|
|
26
|
+
except Exception:
|
|
27
|
+
return {}
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def inject_site_packages(env_path: str) -> Optional[str]:
|
|
31
|
+
"""Add site-packages to sys.path."""
|
|
32
|
+
if sys.platform == "win32":
|
|
33
|
+
site_packages = os.path.join(env_path, "Lib", "site-packages")
|
|
34
|
+
else:
|
|
35
|
+
matches = glob.glob(os.path.join(env_path, "lib", "python*", "site-packages"))
|
|
36
|
+
site_packages = matches[0] if matches else None
|
|
37
|
+
|
|
38
|
+
if site_packages and os.path.exists(site_packages) and site_packages not in sys.path:
|
|
39
|
+
sys.path.insert(0, site_packages)
|
|
40
|
+
return site_packages
|
|
41
|
+
return None
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def setup_env(node_dir: Optional[str] = None) -> None:
|
|
45
|
+
"""Set up env for pixi libraries. Call in prestartup_script.py before native imports."""
|
|
46
|
+
if not is_comfy_env_enabled(): return
|
|
47
|
+
dedupe_libomp()
|
|
48
|
+
|
|
49
|
+
if node_dir is None:
|
|
50
|
+
import inspect
|
|
51
|
+
node_dir = str(Path(inspect.stack()[1].filename).parent)
|
|
52
|
+
|
|
53
|
+
# Apply env vars
|
|
54
|
+
for k, v in load_env_vars(os.path.join(node_dir, "comfy-env.toml")).items():
|
|
55
|
+
os.environ[k] = v
|
|
56
|
+
|
|
57
|
+
# Find env: marker -> _env_<name> -> .pixi
|
|
58
|
+
pixi_env = None
|
|
59
|
+
marker_path = os.path.join(node_dir, MARKER_FILE)
|
|
60
|
+
if os.path.exists(marker_path):
|
|
61
|
+
try:
|
|
62
|
+
import tomli
|
|
63
|
+
with open(marker_path, "rb") as f:
|
|
64
|
+
env_path = tomli.load(f).get("env", {}).get("path")
|
|
65
|
+
if env_path and os.path.exists(env_path):
|
|
66
|
+
pixi_env = env_path
|
|
67
|
+
except Exception: pass
|
|
68
|
+
|
|
69
|
+
if not pixi_env:
|
|
70
|
+
local_env = os.path.join(node_dir, f"_env_{sanitize_name(os.path.basename(node_dir))}")
|
|
71
|
+
if os.path.exists(local_env): pixi_env = local_env
|
|
72
|
+
|
|
73
|
+
if not pixi_env:
|
|
74
|
+
old_pixi = os.path.join(node_dir, ".pixi", "envs", "default")
|
|
75
|
+
if os.path.exists(old_pixi): pixi_env = old_pixi
|
|
76
|
+
|
|
77
|
+
if not pixi_env: return
|
|
78
|
+
|
|
79
|
+
# Set library paths
|
|
80
|
+
if sys.platform == "win32":
|
|
81
|
+
lib_dir = os.path.join(pixi_env, "Library", "bin")
|
|
82
|
+
if os.path.exists(lib_dir): os.environ["PATH"] = lib_dir + ";" + os.environ.get("PATH", "")
|
|
83
|
+
else:
|
|
84
|
+
lib_dir = os.path.join(pixi_env, "lib")
|
|
85
|
+
var = "DYLD_LIBRARY_PATH" if sys.platform == "darwin" else "LD_LIBRARY_PATH"
|
|
86
|
+
if os.path.exists(lib_dir): os.environ[var] = lib_dir + ":" + os.environ.get(var, "")
|
|
87
|
+
|
|
88
|
+
inject_site_packages(pixi_env)
|