diff --git a/README.md b/README.md index ca28ec11..51415fd3 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,7 @@ ![menu](https://raw.githubusercontent.com/ltdrdata/ComfyUI-extension-tutorials/refs/heads/Main/ComfyUI-Manager/images/dialog.jpg) ## NOTICE +* V3.16: Support for `uv` has been added. Set `use_uv` in `config.ini`. * V3.10: `double-click feature` is removed * This feature has been moved to https://github.com/ltdrdata/comfyui-connection-helper * V3.3.2: Overhauled. Officially supports [https://comfyregistry.org/](https://comfyregistry.org/). @@ -246,6 +247,24 @@ The following settings are applied based on the section marked as `is_default`. ![missing-list](https://raw.githubusercontent.com/ltdrdata/ComfyUI-extension-tutorials/Main/ComfyUI-Manager/images/missing-list.jpg) +# Config +* You can modify the `config.ini` file to apply the settings for ComfyUI-Manager. + * The path to the `config.ini` used by ComfyUI-Manager is displayed in the startup log messages. + * See also: [https://github.com/ltdrdata/ComfyUI-Manager#paths] +* Configuration options: + ``` + [default] + git_exe = + use_uv = + channel_url = https://raw.githubusercontent.com/ltdrdata/ComfyUI-Manager/main + bypass_ssl = + file_logging = + windows_selector_event_loop_policy = + model_download_by_agent = + downgrade_blacklist = + security_level = + ``` + ## Additional Feature * Logging to file feature * This feature is enabled by default and can be disabled by setting `file_logging = False` in the `config.ini`. diff --git a/glob/manager_core.py b/glob/manager_core.py index 346107b2..11d9ef10 100644 --- a/glob/manager_core.py +++ b/glob/manager_core.py @@ -42,7 +42,7 @@ import manager_downloader from node_package import InstalledNodePackage -version_code = [3, 15] +version_code = [3, 16] version_str = f"V{version_code[0]}.{version_code[1]}" + (f'.{version_code[2]}' if len(version_code) > 2 else '') @@ -809,7 +809,7 @@ class UnifiedManager: package_name = remap_pip_package(line.strip()) if package_name and not package_name.startswith('#') and package_name not in self.processed_install: self.processed_install.add(package_name) - install_cmd = [sys.executable, "-m", "pip", "install", package_name] + install_cmd = manager_util.make_pip_cmd(["install", package_name]) if package_name.strip() != "" and not package_name.startswith('#'): res = res and try_install_script(url, repo_path, install_cmd, instant_execution=instant_execution) @@ -819,7 +819,7 @@ class UnifiedManager: if os.path.exists(install_script_path) and install_script_path not in self.processed_install: self.processed_install.add(install_script_path) print("Install: install script") - install_cmd = [sys.executable, "install.py"] + install_cmd = manager_util.make_pip_cmd(["install.py"]) return try_install_script(url, repo_path, install_cmd, instant_execution=instant_execution) return True @@ -1545,7 +1545,8 @@ def write_config(): config = configparser.ConfigParser() config['default'] = { 'preview_method': manager_funcs.get_current_preview_method(), - 'git_exe': get_config()['git_exe'], + 'git_exe': get_config()['git_exe'], + 'use_uv': get_config()['use_uv'], 'channel_url': get_config()['channel_url'], 'share_option': get_config()['share_option'], 'bypass_ssl': get_config()['bypass_ssl'], @@ -1581,9 +1582,12 @@ def read_config(): else: security_level = default_conf['security_level'] if 'security_level' in default_conf else 'normal' + manager_util.use_uv = default_conf['use_uv'].lower() == 'true' if 'use_uv' in default_conf else False + return { 'preview_method': default_conf['preview_method'] if 'preview_method' in default_conf else manager_funcs.get_current_preview_method(), 'git_exe': default_conf['git_exe'] if 'git_exe' in default_conf else '', + 'use_uv': default_conf['use_uv'].lower() == 'true' if 'use_uv' in default_conf else False, 'channel_url': default_conf['channel_url'] if 'channel_url' in default_conf else DEFAULT_CHANNEL, 'share_option': default_conf['share_option'] if 'share_option' in default_conf else 'all', 'bypass_ssl': default_conf['bypass_ssl'].lower() == 'true' if 'bypass_ssl' in default_conf else False, @@ -1593,13 +1597,15 @@ def read_config(): 'model_download_by_agent': default_conf['model_download_by_agent'].lower() == 'true' if 'model_download_by_agent' in default_conf else False, 'downgrade_blacklist': default_conf['downgrade_blacklist'] if 'downgrade_blacklist' in default_conf else '', 'skip_migration_check': default_conf['skip_migration_check'].lower() == 'true' if 'skip_migration_check' in default_conf else False, - 'security_level': security_level + 'security_level': security_level, } except Exception: + manager_util.use_uv = False return { 'preview_method': manager_funcs.get_current_preview_method(), 'git_exe': '', + 'use_uv': False, 'channel_url': DEFAULT_CHANNEL, 'share_option': 'all', 'bypass_ssl': False, @@ -1681,6 +1687,10 @@ def try_install_script(url, repo_path, install_cmd, instant_execution=False): if is_blacklisted(install_cmd[4]): print(f"[ComfyUI-Manager] skip black listed pip installation: '{install_cmd[4]}'") return True + elif len(install_cmd) == 6 and install_cmd[3:5] == ['pip', 'install']: # uv mode + if is_blacklisted(install_cmd[5]): + print(f"[ComfyUI-Manager] skip black listed pip installation: '{install_cmd[5]}'") + return True print(f"\n## ComfyUI-Manager: EXECUTE => {install_cmd}") code = manager_funcs.run_script(install_cmd, cwd=repo_path) @@ -1797,9 +1807,9 @@ def execute_install_script(url, repo_path, lazy_mode=False, instant_execution=Fa if package_name and not package_name.startswith('#'): if '--index-url' in package_name: s = package_name.split('--index-url') - install_cmd = [sys.executable, "-m", "pip", "install", s[0].strip(), '--index-url', s[1].strip()] + install_cmd = manager_util.make_pip_cmd(["install", s[0].strip(), '--index-url', s[1].strip()]) else: - install_cmd = [sys.executable, "-m", "pip", "install", package_name] + install_cmd = manager_util.make_pip_cmd(["install", package_name]) if package_name.strip() != "" and not package_name.startswith('#'): try_install_script(url, repo_path, install_cmd, instant_execution=instant_execution) @@ -2120,7 +2130,7 @@ def gitclone_fix(files, instant_execution=False, no_deps=False): def pip_install(packages): - install_cmd = ['#FORCE', sys.executable, "-m", "pip", "install", '-U'] + packages + install_cmd = ['#FORCE'] + manager_util.make_pip_cmd(["install", '-U']) + packages try_install_script('pip install via manager', '..', install_cmd) @@ -2417,7 +2427,8 @@ def check_state_of_git_node_pack_single(item, do_fetch=False, do_update_check=Tr def get_installed_pip_packages(): # extract pip package infos - pips = subprocess.check_output([sys.executable, '-m', 'pip', 'freeze'], text=True).split('\n') + cmd = manager_util.make_pip_cmd(['pip', 'freeze']) + pips = subprocess.check_output(cmd, text=True).split('\n') res = {} for x in pips: diff --git a/glob/manager_util.py b/glob/manager_util.py index c7be467f..63d3c022 100644 --- a/glob/manager_util.py +++ b/glob/manager_util.py @@ -19,6 +19,14 @@ cache_lock = threading.Lock() comfyui_manager_path = os.path.abspath(os.path.join(os.path.dirname(__file__), '..')) cache_dir = os.path.join(comfyui_manager_path, '.cache') # This path is also updated together in **manager_core.update_user_directory**. +use_uv = False + +def make_pip_cmd(cmd): + if use_uv: + return [sys.executable, '-m', 'uv', 'pip'] + cmd + else: + return [sys.executable, '-m', 'pip'] + cmd + # DON'T USE StrictVersion - cannot handle pre_release version # try: @@ -209,7 +217,7 @@ def get_installed_packages(renew=False): if renew or pip_map is None: try: - result = subprocess.check_output([sys.executable, '-m', 'pip', 'list'], universal_newlines=True) + result = subprocess.check_output(make_pip_cmd(['list']), universal_newlines=True) pip_map = {} for line in result.split('\n'): @@ -260,7 +268,7 @@ class PIPFixer: if len(spec) > 0: platform = spec[1] else: - cmd = [sys.executable, '-m', 'pip', 'install', '--force', 'torch', 'torchvision', 'torchaudio'] + cmd = make_pip_cmd(['install', '--force', 'torch', 'torchvision', 'torchaudio']) subprocess.check_output(cmd, universal_newlines=True) logging.error(cmd) return @@ -270,15 +278,13 @@ class PIPFixer: torch_torchvision_torchaudio_ver = torch_torchvision_torchaudio_version_map.get(torch_ver) if torch_torchvision_torchaudio_ver is None: - cmd = [sys.executable, '-m', 'pip', 'install', '--pre', - 'torch', 'torchvision', 'torchaudio', - '--index-url', f"https://download.pytorch.org/whl/nightly/{platform}"] + cmd = make_pip_cmd(['install', '--pre', 'torch', 'torchvision', 'torchaudio', + '--index-url', f"https://download.pytorch.org/whl/nightly/{platform}"]) logging.info("[ComfyUI-Manager] restore PyTorch to nightly version") else: torchvision_ver, torchaudio_ver = torch_torchvision_torchaudio_ver - cmd = [sys.executable, '-m', 'pip', 'install', - f'torch=={torch_ver}', f'torchvision=={torchvision_ver}', f"torchaudio=={torchaudio_ver}", - '--index-url', f"https://download.pytorch.org/whl/{platform}"] + cmd = make_pip_cmd(['install', f'torch=={torch_ver}', f'torchvision=={torchvision_ver}', f"torchaudio=={torchaudio_ver}", + '--index-url', f"https://download.pytorch.org/whl/{platform}"]) logging.info(f"[ComfyUI-Manager] restore PyTorch to {torch_ver}+{platform}") subprocess.check_output(cmd, universal_newlines=True) @@ -289,7 +295,7 @@ class PIPFixer: # remove `comfy` python package try: if 'comfy' in new_pip_versions: - cmd = [sys.executable, '-m', 'pip', 'uninstall', 'comfy'] + cmd = make_pip_cmd(['uninstall', 'comfy']) subprocess.check_output(cmd, universal_newlines=True) logging.warning("[ComfyUI-Manager] 'comfy' python package is uninstalled.\nWARN: The 'comfy' package is completely unrelated to ComfyUI and should never be installed as it causes conflicts with ComfyUI.") @@ -335,7 +341,7 @@ class PIPFixer: if len(targets) > 0: for x in targets: - cmd = [sys.executable, '-m', 'pip', 'install', f"{x}=={versions[0].version_string}"] + cmd = make_pip_cmd(['install', f"{x}=={versions[0].version_string}"]) subprocess.check_output(cmd, universal_newlines=True) logging.info(f"[ComfyUI-Manager] 'opencv' dependencies were fixed: {targets}") @@ -348,7 +354,8 @@ class PIPFixer: np = new_pip_versions.get('numpy') if np is not None: if StrictVersion(np) >= StrictVersion('2'): - subprocess.check_output([sys.executable, '-m', 'pip', 'install', "numpy<2"], universal_newlines=True) + cmd = make_pip_cmd(['install', "numpy<2"]) + subprocess.check_output(cmd , universal_newlines=True) except Exception as e: logging.error("[ComfyUI-Manager] Failed to restore numpy") logging.error(e) diff --git a/js/custom-nodes-manager.js b/js/custom-nodes-manager.js index 5ac8b418..d391bf02 100644 --- a/js/custom-nodes-manager.js +++ b/js/custom-nodes-manager.js @@ -1446,7 +1446,7 @@ export class CustomNodesManager { let v = result[hash]; if(v != 'success') - errorMsg += v; + errorMsg += v+'\n'; } for(let k in self.install_context.targets) { diff --git a/js/model-manager.js b/js/model-manager.js index a577deb9..b86f1219 100644 --- a/js/model-manager.js +++ b/js/model-manager.js @@ -738,7 +738,7 @@ export class ModelManager { let v = result[hash]; if(v != 'success') - errorMsg += v; + errorMsg += v + '\n'; } for(let k in self.install_context.targets) { diff --git a/prestartup_script.py b/prestartup_script.py index c2cce7bb..4539018b 100644 --- a/prestartup_script.py +++ b/prestartup_script.py @@ -103,6 +103,24 @@ manager_config_path = os.path.join(manager_files_path, 'config.ini') cm_cli_path = os.path.join(comfyui_manager_path, "cm-cli.py") +default_conf = {} + +def read_config(): + global default_conf + import configparser + config = configparser.ConfigParser() + config.read(manager_config_path) + default_conf = config['default'] + +def read_uv_mode(): + if 'use_uv' in default_conf: + manager_util.use_uv = default_conf['use_uv'] + + +read_config() +read_uv_mode() + + cm_global.pip_overrides = {'numpy': 'numpy<2', 'ultralytics': 'ultralytics==8.3.40'} if os.path.exists(manager_pip_overrides_path): with open(manager_pip_overrides_path, 'r', encoding="UTF-8", errors="ignore") as json_file: @@ -419,11 +437,11 @@ except ModuleNotFoundError: print("## ComfyUI-Manager: installing dependencies. (GitPython)") try: - result = subprocess.check_output([sys.executable, '-s', '-m', 'pip', 'install', '-r', requirements_path]) + result = subprocess.check_output(manager_util.make_pip_cmd(['install', '-r', requirements_path])) except subprocess.CalledProcessError: print("## [ERROR] ComfyUI-Manager: Attempting to reinstall dependencies using an alternative method.") try: - result = subprocess.check_output([sys.executable, '-s', '-m', 'pip', 'install', '--user', '-r', requirements_path]) + result = subprocess.check_output(manager_util.make_pip_cmd(['install', '--user', '-r', requirements_path])) except subprocess.CalledProcessError: print("## [ERROR] ComfyUI-Manager: Failed to install the GitPython package in the correct Python environment. Please install it manually in the appropriate environment. (You can seek help at https://app.element.io/#/room/%23comfyui_space%3Amatrix.org)") @@ -452,11 +470,6 @@ else: def read_downgrade_blacklist(): try: - import configparser - config = configparser.ConfigParser() - config.read(manager_config_path) - default_conf = config['default'] - if 'downgrade_blacklist' in default_conf: items = default_conf['downgrade_blacklist'].split(',') items = [x.strip() for x in items if x != ''] @@ -471,19 +484,13 @@ read_downgrade_blacklist() def check_bypass_ssl(): try: - import configparser import ssl - config = configparser.ConfigParser() - config.read(manager_config_path) - default_conf = config['default'] - if 'bypass_ssl' in default_conf and default_conf['bypass_ssl'].lower() == 'true': print(f"[ComfyUI-Manager] WARN: Unsafe - SSL verification bypass option is Enabled. (see {manager_config_path})") ssl._create_default_https_context = ssl._create_unverified_context # SSL certificate error fix. except Exception: pass - check_bypass_ssl() @@ -603,9 +610,9 @@ def execute_lazy_install_script(repo_path, executable): if package_name and not is_installed(package_name): if '--index-url' in package_name: s = package_name.split('--index-url') - install_cmd = [sys.executable, "-m", "pip", "install", s[0].strip(), '--index-url', s[1].strip()] + install_cmd = manager_util.make_pip_cmd(["install", s[0].strip(), '--index-url', s[1].strip()]) else: - install_cmd = [sys.executable, "-m", "pip", "install", package_name] + install_cmd = manager_util.make_pip_cmd(["install", package_name]) process_wrap(install_cmd, repo_path) diff --git a/pyproject.toml b/pyproject.toml index aa020bc2..5829b2cc 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,7 @@ [project] name = "comfyui-manager" description = "ComfyUI-Manager provides features to install and manage custom nodes for ComfyUI, as well as various functionalities to assist with ComfyUI." -version = "3.15" +version = "3.16" license = { file = "LICENSE.txt" } dependencies = ["GitPython", "PyGithub", "matrix-client==0.4.0", "transformers", "huggingface-hub>0.20", "typer", "rich", "typing-extensions"]