import datetime
import os
import subprocess
import sys
import atexit
import threading
import re
import locale
import platform
import json
glob_path = os . path . join ( os . path . dirname ( __file__ ) , " glob " )
sys . path . append ( glob_path )
from manager_util import *
import cm_global
cm_global . pip_downgrade_blacklist = [ ' torch ' , ' torchsde ' , ' torchvision ' , ' transformers ' , ' safetensors ' , ' kornia ' ]
def skip_pip_spam ( x ) :
return ( ' Requirement already satisfied: ' in x ) or ( " DEPRECATION: Loading egg at " in x )
message_collapses = [ skip_pip_spam ]
import_failed_extensions = set ( )
cm_global . variables [ ' cm.on_revision_detected_handler ' ] = [ ]
enable_file_logging = True
def register_message_collapse ( f ) :
global message_collapses
message_collapses . append ( f )
def is_import_failed_extension ( name ) :
global import_failed_extensions
return name in import_failed_extensions
def check_file_logging ( ) :
global enable_file_logging
try :
import configparser
config_path = os . path . join ( os . path . dirname ( __file__ ) , " config.ini " )
config = configparser . ConfigParser ( )
config . read ( config_path )
default_conf = config [ ' default ' ]
if ' file_logging ' in default_conf and not default_conf [ ' file_logging ' ] :
enable_file_logging = False
except Exception :
pass
check_file_logging ( )
sys . __comfyui_manager_register_message_collapse = register_message_collapse
sys . __comfyui_manager_is_import_failed_extension = is_import_failed_extension
cm_global . register_api ( ' cm.register_message_collapse ' , register_message_collapse )
cm_global . register_api ( ' cm.is_import_failed_extension ' , is_import_failed_extension )
comfyui_manager_path = os . path . dirname ( __file__ )
custom_nodes_path = os . path . abspath ( os . path . join ( comfyui_manager_path , " .. " ) )
startup_script_path = os . path . join ( comfyui_manager_path , " startup-scripts " )
restore_snapshot_path = os . path . join ( startup_script_path , " restore-snapshot.json " )
git_script_path = os . path . join ( comfyui_manager_path , " git_helper.py " )
pip_overrides_path = os . path . join ( comfyui_manager_path , " pip_overrides.json " )
cm_global . pip_overrides = { }
if os . path . exists ( pip_overrides_path ) :
with open ( pip_overrides_path , ' r ' , encoding = " UTF-8 " , errors = " ignore " ) as json_file :
cm_global . pip_overrides = json . load ( json_file )
def remap_pip_package ( pkg ) :
if pkg in cm_global . pip_overrides :
res = cm_global . pip_overrides [ pkg ]
print ( f " [ComfyUI-Manager] ' { pkg } ' is remapped to ' { res } ' " )
return res
else :
return pkg
std_log_lock = threading . Lock ( )
class TerminalHook :
def __init__ ( self ) :
self . hooks = { }
def add_hook ( self , k , v ) :
self . hooks [ k ] = v
def remove_hook ( self , k ) :
if k in self . hooks :
del self . hooks [ k ]
def write_stderr ( self , msg ) :
for v in self . hooks . values ( ) :
try :
v . write_stderr ( msg )
except Exception :
pass
def write_stdout ( self , msg ) :
for v in self . hooks . values ( ) :
try :
v . write_stdout ( msg )
except Exception :
pass
terminal_hook = TerminalHook ( )
sys . __comfyui_manager_terminal_hook = terminal_hook
def handle_stream ( stream , prefix ) :
stream . reconfigure ( encoding = locale . getpreferredencoding ( ) , errors = ' replace ' )
for msg in stream :
if prefix == ' [!] ' and ( ' it/s] ' in msg or ' s/it] ' in msg ) and ( ' % | ' in msg or ' it [ ' in msg ) :
if msg . startswith ( ' 100 % ' ) :
print ( ' \r ' + msg , end = " " , file = sys . stderr ) ,
else :
print ( ' \r ' + msg [ : - 1 ] , end = " " , file = sys . stderr ) ,
else :
if prefix == ' [!] ' :
print ( prefix , msg , end = " " , file = sys . stderr )
else :
print ( prefix , msg , end = " " )
def process_wrap ( cmd_str , cwd_path , handler = None ) :
process = subprocess . Popen ( cmd_str , cwd = cwd_path , stdout = subprocess . PIPE , stderr = subprocess . PIPE , text = True , bufsize = 1 )
if handler is None :
handler = handle_stream
stdout_thread = threading . Thread ( target = handler , args = ( process . stdout , " " ) )
stderr_thread = threading . Thread ( target = handler , args = ( process . stderr , " [!] " ) )
stdout_thread . start ( )
stderr_thread . start ( )
stdout_thread . join ( )
stderr_thread . join ( )
return process . wait ( )
try :
if ' --port ' in sys . argv :
port_index = sys . argv . index ( ' --port ' )
if port_index + 1 < len ( sys . argv ) :
port = int ( sys . argv [ port_index + 1 ] )
postfix = f " _ { port } "
else :
postfix = " "
# Logger setup
if enable_file_logging :
if os . path . exists ( f " comfyui { postfix } .log " ) :
if os . path . exists ( f " comfyui { postfix } .prev.log " ) :
if os . path . exists ( f " comfyui { postfix } .prev2.log " ) :
os . remove ( f " comfyui { postfix } .prev2.log " )
os . rename ( f " comfyui { postfix } .prev.log " , f " comfyui { postfix } .prev2.log " )
os . rename ( f " comfyui { postfix } .log " , f " comfyui { postfix } .prev.log " )
log_file = open ( f " comfyui { postfix } .log " , " w " , encoding = " utf-8 " , errors = " ignore " )
log_lock = threading . Lock ( )
original_stdout = sys . stdout
original_stderr = sys . stderr
if original_stdout . encoding . lower ( ) == ' utf-8 ' :
write_stdout = original_stdout . write
write_stderr = original_stderr . write
else :
def wrapper_stdout ( msg ) :
original_stdout . write ( msg . encode ( ' utf-8 ' ) . decode ( original_stdout . encoding , errors = " ignore " ) )
def wrapper_stderr ( msg ) :
original_stderr . write ( msg . encode ( ' utf-8 ' ) . decode ( original_stderr . encoding , errors = " ignore " ) )
write_stdout = wrapper_stdout
write_stderr = wrapper_stderr
pat_tqdm = r ' \ d+ % .* \ [(.*?) \ ] '
pat_import_fail = r ' seconds \ (IMPORT FAILED \ ):.*[/ \\ ]custom_nodes[/ \\ ](.*)$ '
is_start_mode = True
class ComfyUIManagerLogger :
def __init__ ( self , is_stdout ) :
self . is_stdout = is_stdout
self . encoding = " utf-8 "
self . last_char = ' '
def fileno ( self ) :
try :
if self . is_stdout :
return original_stdout . fileno ( )
else :
return original_stderr . fileno ( )
except AttributeError :
# Handle error
raise ValueError ( " The object does not have a fileno method " )
def write ( self , message ) :
global is_start_mode
if any ( f ( message ) for f in message_collapses ) :
return
if is_start_mode :
match = re . search ( pat_import_fail , message )
if match :
import_failed_extensions . add ( match . group ( 1 ) )
if ' Starting server ' in message :
is_start_mode = False
if not self . is_stdout :
match = re . search ( pat_tqdm , message )
if match :
message = re . sub ( r ' ([#|]) \ d ' , r ' \ 1▌ ' , message )
message = re . sub ( ' # ' , ' █ ' , message )
if ' 100 % ' in message :
self . sync_write ( message )
else :
write_stderr ( message )
original_stderr . flush ( )
else :
self . sync_write ( message )
else :
self . sync_write ( message )
def sync_write ( self , message ) :
with log_lock :
timestamp = datetime . datetime . now ( ) . strftime ( ' % Y- % m- %d % H: % M: % S ' ) [ : - 3 ]
if self . last_char != ' \n ' :
log_file . write ( message )
else :
log_file . write ( f " [ { timestamp } ] { message } " )
log_file . flush ( )
self . last_char = message if message == ' ' else message [ - 1 ]
with std_log_lock :
if self . is_stdout :
write_stdout ( message )
original_stdout . flush ( )
terminal_hook . write_stderr ( message )
else :
write_stderr ( message )
original_stderr . flush ( )
terminal_hook . write_stdout ( message )
def flush ( self ) :
log_file . flush ( )
with std_log_lock :
if self . is_stdout :
original_stdout . flush ( )
else :
original_stderr . flush ( )
def close ( self ) :
self . flush ( )
def reconfigure ( self , * args , * * kwargs ) :
pass
# You can close through sys.stderr.close_log()
def close_log ( self ) :
sys . stderr = original_stderr
sys . stdout = original_stdout
log_file . close ( )
def close_log ( ) :
sys . stderr = original_stderr
sys . stdout = original_stdout
log_file . close ( )
if enable_file_logging :
sys . stdout = ComfyUIManagerLogger ( True )
sys . stderr = ComfyUIManagerLogger ( False )
atexit . register ( close_log )
else :
sys . stdout . close_log = lambda : None
except Exception as e :
print ( f " [ComfyUI-Manager] Logging failed: { e } " )
try :
import git
except ModuleNotFoundError :
my_path = os . path . dirname ( __file__ )
requirements_path = os . path . join ( my_path , " requirements.txt " )
print ( f " ## ComfyUI-Manager: installing dependencies. (GitPython) " )
try :
result = subprocess . check_output ( [ sys . executable , ' -s ' , ' -m ' , ' pip ' , ' install ' , ' -r ' , requirements_path ] )
except subprocess . CalledProcessError as e :
print ( f " ## [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 ] )
except subprocess . CalledProcessError as e :
print ( f " ## [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) " )
try :
import git
print ( f " ## ComfyUI-Manager: installing dependencies done. " )
except :
# maybe we should sys.exit() here? there is at least two screens worth of error messages still being pumped after our error messages
print ( f " ## [ERROR] ComfyUI-Manager: GitPython package seems to be installed, but failed to load somehow. Make sure you have a working git client installed " )
print ( " ** ComfyUI startup time: " , datetime . datetime . now ( ) )
print ( " ** Platform: " , platform . system ( ) )
print ( " ** Python version: " , sys . version )
print ( " ** Python executable: " , sys . executable )
if enable_file_logging :
print ( " ** Log path: " , os . path . abspath ( ' comfyui.log ' ) )
else :
print ( " ** Log path: file logging is disabled " )
def read_downgrade_blacklist ( ) :
try :
import configparser
config_path = os . path . join ( os . path . dirname ( __file__ ) , " config.ini " )
config = configparser . ConfigParser ( )
config . read ( 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 != ' ' ]
cm_global . pip_downgrade_blacklist + = items
cm_global . pip_downgrade_blacklist = list ( set ( cm_global . pip_downgrade_blacklist ) )
except :
pass
read_downgrade_blacklist ( )
def check_bypass_ssl ( ) :
try :
import configparser
import ssl
config_path = os . path . join ( os . path . dirname ( __file__ ) , " config.ini " )
config = configparser . ConfigParser ( )
config . read ( config_path )
default_conf = config [ ' default ' ]
if ' bypass_ssl ' in default_conf and default_conf [ ' bypass_ssl ' ] :
print ( f " [ComfyUI-Manager] WARN: Unsafe - SSL verification bypass option is Enabled. (see ComfyUI-Manager/config.ini) " )
ssl . _create_default_https_context = ssl . _create_unverified_context # SSL certificate error fix.
except Exception :
pass
check_bypass_ssl ( )
# Perform install
processed_install = set ( )
script_list_path = os . path . join ( os . path . dirname ( os . path . realpath ( __file__ ) ) , " startup-scripts " , " install-scripts.txt " )
pip_map = None
def get_installed_packages ( ) :
global pip_map
if pip_map is None :
try :
result = subprocess . check_output ( [ sys . executable , ' -m ' , ' pip ' , ' list ' ] , universal_newlines = True )
pip_map = { }
for line in result . split ( ' \n ' ) :
x = line . strip ( )
if x :
y = line . split ( )
if y [ 0 ] == ' Package ' or y [ 0 ] . startswith ( ' - ' ) :
continue
pip_map [ y [ 0 ] ] = y [ 1 ]
except subprocess . CalledProcessError as e :
print ( f " [ComfyUI-Manager] Failed to retrieve the information of installed pip packages. " )
return set ( )
return pip_map
def is_installed ( name ) :
name = name . strip ( )
if name . startswith ( ' # ' ) :
return True
pattern = r ' ([^<>!=]+)([<>!=]=?)(.*) '
match = re . search ( pattern , name )
if match :
name = match . group ( 1 )
if name in cm_global . pip_downgrade_blacklist :
pips = get_installed_packages ( )
if match is None :
if name in pips :
return True
elif match . group ( 2 ) in [ ' <= ' , ' == ' , ' < ' ] :
if name in pips :
if StrictVersion ( pips [ name ] ) > = StrictVersion ( match . group ( 3 ) ) :
print ( f " [ComfyUI-Manager] skip black listed pip installation: ' { name } ' " )
return True
return name . lower ( ) in get_installed_packages ( )
if os . path . exists ( restore_snapshot_path ) :
try :
cloned_repos = [ ]
def msg_capture ( stream , prefix ) :
stream . reconfigure ( encoding = locale . getpreferredencoding ( ) , errors = ' replace ' )
for msg in stream :
if msg . startswith ( " CLONE: " ) :
cloned_repos . append ( msg [ 7 : ] )
if prefix == ' [!] ' :
print ( prefix , msg , end = " " , file = sys . stderr )
else :
print ( prefix , msg , end = " " )
elif prefix == ' [!] ' and ( ' it/s] ' in msg or ' s/it] ' in msg ) and ( ' % | ' in msg or ' it [ ' in msg ) :
if msg . startswith ( ' 100 % ' ) :
print ( ' \r ' + msg , end = " " , file = sys . stderr ) ,
else :
print ( ' \r ' + msg [ : - 1 ] , end = " " , file = sys . stderr ) ,
else :
if prefix == ' [!] ' :
print ( prefix , msg , end = " " , file = sys . stderr )
else :
print ( prefix , msg , end = " " )
print ( f " [ComfyUI-Manager] Restore snapshot. " )
cmd_str = [ sys . executable , git_script_path , ' --apply-snapshot ' , restore_snapshot_path ]
exit_code = process_wrap ( cmd_str , custom_nodes_path , handler = msg_capture )
repository_name = ' '
for url in cloned_repos :
try :
repository_name = url . split ( " / " ) [ - 1 ] . strip ( )
repo_path = os . path . join ( custom_nodes_path , repository_name )
repo_path = os . path . abspath ( repo_path )
requirements_path = os . path . join ( repo_path , ' requirements.txt ' )
install_script_path = os . path . join ( repo_path , ' install.py ' )
this_exit_code = 0
if os . path . exists ( requirements_path ) :
with open ( requirements_path , ' r ' , encoding = " UTF-8 " , errors = " ignore " ) as file :
for line in file :
package_name = remap_pip_package ( line . strip ( ) )
if package_name and not is_installed ( package_name ) :
install_cmd = [ sys . executable , " -m " , " pip " , " install " , package_name ]
this_exit_code + = process_wrap ( install_cmd , repo_path )
if os . path . exists ( install_script_path ) and f ' { repo_path } /install.py ' not in processed_install :
processed_install . add ( f ' { repo_path } /install.py ' )
install_cmd = [ sys . executable , install_script_path ]
print ( f " >>> { install_cmd } / { repo_path } " )
this_exit_code + = process_wrap ( install_cmd , repo_path )
if this_exit_code != 0 :
print ( f " [ComfyUI-Manager] Restoring ' { repository_name } ' is failed. " )
except Exception as e :
print ( e )
print ( f " [ComfyUI-Manager] Restoring ' { repository_name } ' is failed. " )
if exit_code != 0 :
print ( f " [ComfyUI-Manager] Restore snapshot failed. " )
else :
print ( f " [ComfyUI-Manager] Restore snapshot done. " )
except Exception as e :
print ( e )
print ( f " [ComfyUI-Manager] Restore snapshot failed. " )
os . remove ( restore_snapshot_path )
def execute_lazy_install_script ( repo_path , executable ) :
global processed_install
install_script_path = os . path . join ( repo_path , " install.py " )
requirements_path = os . path . join ( repo_path , " requirements.txt " )
if os . path . exists ( requirements_path ) :
print ( f " Install: pip packages for ' { repo_path } ' " )
with open ( requirements_path , " r " ) as requirements_file :
for line in requirements_file :
package_name = remap_pip_package ( line . strip ( ) )
if package_name and not is_installed ( package_name ) :
install_cmd = [ executable , " -m " , " pip " , " install " , package_name ]
process_wrap ( install_cmd , repo_path )
if os . path . exists ( install_script_path ) and f ' { repo_path } /install.py ' not in processed_install :
processed_install . add ( f ' { repo_path } /install.py ' )
print ( f " Install: install script for ' { repo_path } ' " )
install_cmd = [ executable , " install.py " ]
process_wrap ( install_cmd , repo_path )
# Check if script_list_path exists
if os . path . exists ( script_list_path ) :
print ( " \n ####################################################################### " )
print ( " [ComfyUI-Manager] Starting dependency installation/(de)activation for the extension \n " )
executed = set ( )
# Read each line from the file and convert it to a list using eval
with open ( script_list_path , ' r ' , encoding = " UTF-8 " , errors = " ignore " ) as file :
for line in file :
if line in executed :
continue
executed . add ( line )
try :
script = eval ( line )
if script [ 1 ] . startswith ( ' # ' ) and script [ 1 ] != ' #FORCE ' :
if script [ 1 ] == " #LAZY-INSTALL-SCRIPT " :
execute_lazy_install_script ( script [ 0 ] , script [ 2 ] )
elif os . path . exists ( script [ 0 ] ) :
if script [ 1 ] == " #FORCE " :
del script [ 1 ]
else :
if ' pip ' in script [ 1 : ] and ' install ' in script [ 1 : ] and is_installed ( script [ - 1 ] ) :
continue
print ( f " \n ## ComfyUI-Manager: EXECUTE => { script [ 1 : ] } " )
print ( f " \n ## Execute install/(de)activation script for ' { script [ 0 ] } ' " )
exit_code = process_wrap ( script [ 1 : ] , script [ 0 ] )
if exit_code != 0 :
print ( f " install/(de)activation script failed: { script [ 0 ] } " )
else :
print ( f " \n ## ComfyUI-Manager: CANCELED => { script [ 1 : ] } " )
except Exception as e :
print ( f " [ERROR] Failed to execute install/(de)activation script: { line } / { e } " )
# Remove the script_list_path file
if os . path . exists ( script_list_path ) :
os . remove ( script_list_path )
print ( " \n [ComfyUI-Manager] Startup script completed. " )
print ( " ####################################################################### \n " )
del processed_install
del pip_map
def check_windows_event_loop_policy ( ) :
try :
import configparser
config_path = os . path . join ( os . path . dirname ( __file__ ) , " config.ini " )
config = configparser . ConfigParser ( )
config . read ( config_path )
default_conf = config [ ' default ' ]
if ' windows_selector_event_loop_policy ' in default_conf and default_conf [ ' windows_selector_event_loop_policy ' ] :
try :
import asyncio
import asyncio . windows_events
asyncio . set_event_loop_policy ( asyncio . windows_events . WindowsSelectorEventLoopPolicy ( ) )
print ( f " [ComfyUI-Manager] Windows event loop policy mode enabled " )
except Exception as e :
print ( f " [ComfyUI-Manager] WARN: Windows initialization fail: { e } " )
except Exception :
pass
if platform . system ( ) == ' Windows ' :
check_windows_event_loop_policy ( )