diff --git a/comfy_cli/cmdline.py b/comfy_cli/cmdline.py index 207cea8..563cdf1 100644 --- a/comfy_cli/cmdline.py +++ b/comfy_cli/cmdline.py @@ -372,6 +372,7 @@ def update( raise typer.Exit(code=1) comfy_path = workspace_manager.workspace_path + python_exe = workspace_manager.python_exe if "all" == target: custom_nodes.command.execute_cm_cli(["update", "all"]) @@ -383,7 +384,7 @@ def update( os.chdir(comfy_path) subprocess.run(["git", "pull"], check=True) subprocess.run( - [sys.executable, "-m", "pip", "install", "-r", "requirements.txt"], + [python_exe, "-m", "pip", "install", "-r", "requirements.txt"], check=True, ) diff --git a/comfy_cli/command/custom_nodes/cm_cli_util.py b/comfy_cli/command/custom_nodes/cm_cli_util.py index 347313c..883f16c 100644 --- a/comfy_cli/command/custom_nodes/cm_cli_util.py +++ b/comfy_cli/command/custom_nodes/cm_cli_util.py @@ -25,6 +25,7 @@ def execute_cm_cli(args, channel=None, fast_deps=False, no_deps=False, mode=None _config_manager = ConfigManager() workspace_path = workspace_manager.workspace_path + python_exe = workspace_manager.python_exe if not workspace_path: print("\n[bold red]ComfyUI path is not resolved.[/bold red]\n", file=sys.stderr) @@ -38,7 +39,7 @@ def execute_cm_cli(args, channel=None, fast_deps=False, no_deps=False, mode=None ) raise typer.Exit(code=1) - cmd = [sys.executable, cm_cli_path] + args + cmd = [python_exe, cm_cli_path] + args if channel is not None: cmd += ["--channel", channel] diff --git a/comfy_cli/command/custom_nodes/command.py b/comfy_cli/command/custom_nodes/command.py index 47d2a15..ab47fb4 100644 --- a/comfy_cli/command/custom_nodes/command.py +++ b/comfy_cli/command/custom_nodes/command.py @@ -69,10 +69,11 @@ def run_script(cmd, cwd="."): def get_installed_packages(): global pip_map + python_exe = workspace_manager.python_exe if pip_map is None: try: - result = subprocess.check_output([sys.executable, "-m", "pip", "list"], universal_newlines=True) + result = subprocess.check_output([python_exe, "-m", "pip", "list"], universal_newlines=True) pip_map = {} for line in result.split("\n"): @@ -137,6 +138,7 @@ def try_install_script(repo_path, install_cmd, instant_execution=False): def execute_install_script(repo_path): + python_exe = workspace_manager.python_exe install_script_path = os.path.join(repo_path, "install.py") requirements_path = os.path.join(repo_path, "requirements.txt") @@ -156,13 +158,13 @@ def execute_install_script(repo_path): # package_name = remap_pip_package(line.strip()) package_name = line.strip() if package_name and not package_name.startswith("#"): - install_cmd = [sys.executable, "-m", "pip", "install", package_name] + install_cmd = [python_exe, "-m", "pip", "install", package_name] if package_name.strip() != "": try_install_script(repo_path, install_cmd) if os.path.exists(install_script_path): print("Install: install script") - install_cmd = [sys.executable, "install.py"] + install_cmd = [python_exe, "install.py"] try_install_script(repo_path, install_cmd) @@ -510,6 +512,7 @@ def uninstall( def update_node_id_cache(): config_manager = ConfigManager() workspace_path = workspace_manager.workspace_path + python_exe = workspace_manager.python_exe cm_cli_path = os.path.join(workspace_path, "custom_nodes", "ComfyUI-Manager", "cm-cli.py") @@ -518,7 +521,7 @@ def update_node_id_cache(): os.makedirs(tmp_path) cache_path = os.path.join(tmp_path, "node-cache.list") - cmd = [sys.executable, cm_cli_path, "export-custom-node-ids", cache_path] + cmd = [python_exe, cm_cli_path, "export-custom-node-ids", cache_path] new_env = os.environ.copy() new_env["COMFYUI_PATH"] = workspace_path diff --git a/comfy_cli/command/install.py b/comfy_cli/command/install.py index ad8380a..bd94eec 100755 --- a/comfy_cli/command/install.py +++ b/comfy_cli/command/install.py @@ -40,6 +40,7 @@ def pip_install_comfyui_dependencies( skip_requirement: bool, ): os.chdir(repo_dir) + python_exe = workspace_manager.python_exe result = None if not skip_torch_or_directml: @@ -48,7 +49,7 @@ def pip_install_comfyui_dependencies( pip_url = ["--extra-index-url", "https://download.pytorch.org/whl/rocm6.0"] result = subprocess.run( [ - sys.executable, + python_exe, "-m", "pip", "install", @@ -63,7 +64,7 @@ def pip_install_comfyui_dependencies( # install torch for NVIDIA if gpu == GPU_OPTION.NVIDIA: base_command = [ - sys.executable, + python_exe, "-m", "pip", "install", @@ -112,7 +113,7 @@ def pip_install_comfyui_dependencies( # TODO: wrap pip install in a function result = subprocess.run( [ - sys.executable, + python_exe, "-m", "pip", "install", @@ -129,13 +130,13 @@ def pip_install_comfyui_dependencies( # install directml for AMD windows if gpu == GPU_OPTION.AMD and plat == constants.OS.WINDOWS: - result = subprocess.run([sys.executable, "-m", "pip", "install", "torch-directml"], check=True) + result = subprocess.run([python_exe, "-m", "pip", "install", "torch-directml"], check=True) # install torch for Mac M Series if gpu == GPU_OPTION.MAC_M_SERIES: result = subprocess.run( [ - sys.executable, + python_exe, "-m", "pip", "install", @@ -152,7 +153,7 @@ def pip_install_comfyui_dependencies( # install requirements.txt if skip_requirement: return - result = subprocess.run([sys.executable, "-m", "pip", "install", "-r", "requirements.txt"], check=False) + result = subprocess.run([python_exe, "-m", "pip", "install", "-r", "requirements.txt"], check=False) if result.returncode != 0: rprint("Failed to install ComfyUI dependencies. Please check your environment (`comfy env`) and try again.") sys.exit(1) @@ -160,8 +161,10 @@ def pip_install_comfyui_dependencies( # install requirements for manager def pip_install_manager_dependencies(repo_dir): + python_exe = workspace_manager.python_exe + os.chdir(os.path.join(repo_dir, "custom_nodes", "ComfyUI-Manager")) - subprocess.run([sys.executable, "-m", "pip", "install", "-r", "requirements.txt"], check=True) + subprocess.run([python_exe, "-m", "pip", "install", "-r", "requirements.txt"], check=True) def execute( diff --git a/comfy_cli/command/launch.py b/comfy_cli/command/launch.py index 30f92a8..981a683 100644 --- a/comfy_cli/command/launch.py +++ b/comfy_cli/command/launch.py @@ -34,6 +34,8 @@ def launch_comfyui(extra, frontend_pr=None): # To minimize the possibility of leaving residue in the tmp directory, use files instead of directories. reboot_path = os.path.join(session_path + ".reboot") + python_exe = workspace_manager.python_exe + extra = extra if extra is not None else [] # Handle temporary frontend PR @@ -55,7 +57,7 @@ def launch_comfyui(extra, frontend_pr=None): if "COMFY_CLI_BACKGROUND" not in os.environ: # If not running in background mode, there's no need to use popen. This can prevent the issue of linefeeds occurring with tqdm. while True: - res = subprocess.run([sys.executable, "main.py"] + extra, env=new_env, check=False) + res = subprocess.run([python_exe, "main.py"] + extra, env=new_env, check=False) if reboot_path is None: print("[bold red]ComfyUI is not installed.[/bold red]\n") @@ -84,7 +86,7 @@ def redirector_stdout(): while True: if sys.platform == "win32": process = subprocess.Popen( - [sys.executable, "main.py"] + extra, + [python_exe, "main.py"] + extra, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, @@ -95,7 +97,7 @@ def redirector_stdout(): ) else: process = subprocess.Popen( - [sys.executable, "main.py"] + extra, + [python_exe, "main.py"] + extra, text=True, env=new_env, encoding="utf-8", diff --git a/comfy_cli/workspace_manager.py b/comfy_cli/workspace_manager.py index 3c58272..9d9bb24 100644 --- a/comfy_cli/workspace_manager.py +++ b/comfy_cli/workspace_manager.py @@ -1,5 +1,7 @@ import concurrent.futures import os +import sys +import subprocess from dataclasses import dataclass, field from datetime import datetime from enum import Enum @@ -151,6 +153,7 @@ def __init__( self.use_recent = None self.workspace_path = None self.workspace_type = None + self.python_exe = None self.skip_prompting = None def setup_workspace_manager( @@ -164,6 +167,7 @@ def setup_workspace_manager( self.use_here = use_here self.use_recent = use_recent self.workspace_path, self.workspace_type = self.get_workspace_path() + self.python_exe = self.find_or_create_python_env() self.skip_prompting = skip_prompting def set_recent_workspace(self, path: str): @@ -310,5 +314,51 @@ def save_metadata(self): file_path = os.path.join(self.workspace_path, constants.COMFY_LOCK_YAML_FILE) save_yaml(file_path, self.metadata) + def find_or_create_python_env(self, default_name=".venv"): + """ + Locates the Conda/virtual environment to use, else creates `.venv`. + """ + # Case 1: Find if .venv or venv is present in install directory. + for name in [default_name, "venv"]: + if utils.get_os() == constants.OS.WINDOWS: + venv_path = os.path.join(self.workspace_path, name, "Scripts", "python.exe") + else: + venv_path = os.path.join(self.workspace_path, name, "bin", "python") + if os.path.exists(venv_path): + return venv_path + + # Case 2: If CONDA_PREFIX is present, use the conda environment. + conda_prefix = os.environ.get("CONDA_PREFIX") + if conda_prefix: + if utils.get_os() == constants.OS.WINDOWS: + return os.path.join(conda_prefix, "python.exe") + else: + return os.path.join(conda_prefix, "bin", "python") + + + # Case 3: Create standalone venv using currently active Python executable. + try: + print("[bold yellow]Virtual environment not found. Creating...[/bold yellow]") + # TODO: Should probably check here if Python version is compatible. + subprocess.check_call( + [ + sys.executable, + "-m", + "venv", + "--copies", + "--upgrade-deps", + "--prompt", + "comfyui", + os.path.join(self.workspace_path, default_name), + ] + ) + if utils.get_os() == constants.OS.WINDOWS: + return os.path.join(self.workspace_path, default_name, "Scripts", "python.exe") + else: + return os.path.join(self.workspace_path, default_name, "bin", "python") + except subprocess.CalledProcessError as e: + print(f"[bold red]Failed to create virtual environment: {e}[/bold red]") + raise typer.Exit(code=1) + def fill_print_table(self): return [("Current selected workspace", f"[bold green]→ {self.workspace_path}[/bold green]")]