From b2f29911d2c2f21ab9f68410543378be3273a8f7 Mon Sep 17 00:00:00 2001 From: vladmandic Date: Thu, 5 Mar 2026 17:57:34 +0100 Subject: [PATCH 01/14] update diffusers and changelog Signed-off-by: vladmandic --- CHANGELOG.md | 7 ++++--- installer.py | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 95ddafdcd..aead910b7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,8 @@ # Change Log for SD.Next -## Update for 2026-03-04 +## Update for 2026-03-05 -### Highlights for 2026-03-04 +### Highlights for 2026-03-05 This release brings massive code refactoring to modernize codebase and removal of some obsolete features. Leaner & Faster! And since its a bit quieter period when it comes to new models, so we have two deep fine-tunes: *FireRed-Image-Edit* and *SkyWorks-UniPic-3* @@ -11,7 +11,7 @@ But also many smaller quality-of-life improvements - for full details, see [Chan [ReadMe](https://github.com/vladmandic/automatic/blob/master/README.md) | [ChangeLog](https://github.com/vladmandic/automatic/blob/master/CHANGELOG.md) | [Docs](https://vladmandic.github.io/sdnext-docs/) | [WiKi](https://github.com/vladmandic/automatic/wiki) | [Discord](https://discord.com/invite/sd-next-federal-batch-inspectors-1101998836328697867) | [Sponsor](https://github.com/sponsors/vladmandic) -### Details for 2026-03-04 +### Details for 2026-03-05 - **Models** - [Google Flash 3.1 Image](https://ai.google.dev/gemini-api/docs/models/gemini-3-flash-preview) a.k.a. *Nano Banana 2* @@ -102,6 +102,7 @@ But also many smaller quality-of-life improvements - for full details, see [Chan - ui connection monitor - kandinsky 5 t2i/i2i model type detection - kanvas notify core on image size change + - lora additional z-image types ## Update for 2026-02-04 diff --git a/installer.py b/installer.py index 3e4504858..e7e005742 100644 --- a/installer.py +++ b/installer.py @@ -469,7 +469,7 @@ def check_diffusers(): t_start = time.time() if args.skip_all: return - target_commit = '4a2833c1c226362677e165a363fecb4da331e122' # diffusers commit hash + target_commit = '8ec0a5ccad96957c10388d2d2acc7fdd8e0fab84' # diffusers commit hash # if args.use_rocm or args.use_zluda or args.use_directml: # sha = '043ab2520f6a19fce78e6e060a68dbc947edb9f9' # lock diffusers versions for now pkg = package_spec('diffusers') From 7c09e3dfe12033452b00ded2b0071811668f1631 Mon Sep 17 00:00:00 2001 From: awsr <43862868+awsr@users.noreply.github.com> Date: Thu, 5 Mar 2026 16:16:01 -0800 Subject: [PATCH 02/14] Avoid using `args` as a variable name --- installer.py | 204 ++++++++++++++++++------------------- launch.py | 2 +- modules/zluda_installer.py | 6 +- 3 files changed, 106 insertions(+), 106 deletions(-) diff --git a/installer.py b/installer.py index e7e005742..18a0819e8 100644 --- a/installer.py +++ b/installer.py @@ -40,7 +40,7 @@ first_call = True quick_allowed = True errors = [] opts = {} -args = Dot({ +arguments = Dot({ 'debug': False, 'reset': False, 'profile': False, @@ -89,7 +89,7 @@ except Exception: @lru_cache def get_logfile(): log_size = os.path.getsize(log_file) if os.path.exists(log_file) else 0 - log.info(f'Logger: file="{os.path.abspath(log_file)}" level={logging.getLevelName(logging.DEBUG if args.debug else logging.INFO)} host="{hostname}" size={log_size} mode={"append" if not log_rolled else "create"}') + log.info(f'Logger: file="{os.path.abspath(log_file)}" level={logging.getLevelName(logging.DEBUG if arguments.debug else logging.INFO)} host="{hostname}" size={log_size} mode={"append" if not log_rolled else "create"}') return log_file @@ -169,13 +169,13 @@ def installed(package, friendly: str = None, quiet = False): # pylint: disable=r if len(p) > 1: exact = pkg_version == p[1] if not exact and not quiet: - if args.experimental: + if arguments.experimental: log.warning(f'Install: package="{p[0]}" installed={pkg_version} required={p[1]} allowing experimental') else: log.warning(f'Install: package="{p[0]}" installed={pkg_version} required={p[1]} version mismatch') global restart_required # pylint: disable=global-statement restart_required = True - ok = ok and (exact or args.experimental) + ok = ok and (exact or arguments.experimental) else: if not quiet: log.debug(f'Install: package="{p[0]}" install required') @@ -238,7 +238,7 @@ def pip(arg: str, ignore: bool = False, quiet: bool = True, uv = True): log.warning('Offline mode enabled') return 'offline' package = arg.replace("install", "").replace("--upgrade", "").replace("--no-deps", "").replace("--force-reinstall", "").replace(" ", " ").strip() - uv = uv and args.uv and not package.startswith('git+') + uv = uv and arguments.uv and not package.startswith('git+') pipCmd = "uv pip" if uv else "pip" if not quiet and '-r ' not in arg: log.info(f'Install: package="{package}" mode={"uv" if uv else "pip"}') @@ -271,13 +271,13 @@ def pip(arg: str, ignore: bool = False, quiet: bool = True, uv = True): def install(package, friendly: str = None, ignore: bool = False, reinstall: bool = False, no_deps: bool = False, quiet: bool = False, force: bool = False, no_build_isolation: bool = False): t_start = time.time() res = '' - if args.reinstall or args.upgrade: + if arguments.reinstall or arguments.upgrade: global quick_allowed # pylint: disable=global-statement quick_allowed = False - if (args.reinstall) or (reinstall) or (not installed(package, friendly, quiet=quiet)): + if (arguments.reinstall) or (reinstall) or (not installed(package, friendly, quiet=quiet)): deps = '' if not no_deps else '--no-deps ' isolation = '' if not no_build_isolation else '--no-build-isolation ' - cmd = f"install{' --upgrade' if not args.uv else ''}{' --force-reinstall' if force else ''} {deps}{isolation}{package}" + cmd = f"install{' --upgrade' if not arguments.uv else ''}{' --force-reinstall' if force else ''} {deps}{isolation}{package}" res = pip(cmd, ignore=ignore, uv=package != "uv" and not package.startswith('git+')) ts('install', t_start) return res @@ -286,7 +286,7 @@ def install(package, friendly: str = None, ignore: bool = False, reinstall: bool # execute git command def git(arg: str, folder: str = None, ignore: bool = False, optional: bool = False): # pylint: disable=unused-argument t_start = time.time() - if args.skip_git: + if arguments.skip_git: return '' if 'google.colab' in sys.modules: return '' @@ -370,7 +370,7 @@ def update(folder, keep_branch = False, rebase = True): else: res = git(f'pull origin {b} {arg}', folder) debug(f'Install update: folder={folder} branch={b} args={arg} {res}') - if not args.experimental: + if not arguments.experimental: commit = extensions_commit.get(os.path.basename(folder), None) if commit is not None: res = git(f'checkout {commit}', folder) @@ -432,7 +432,7 @@ def check_python(supported_minors=None, experimental_minors=None, reason=None): supported_minors = [10, 11, 12, 13] experimental_minors = [14] t_start = time.time() - if args.quick: + if arguments.quick: return log.info(f'Python: version={platform.python_version()} platform={platform.system()} bin="{sys.executable}" venv="{sys.prefix}"') if int(sys.version_info.minor) == 9: @@ -450,13 +450,13 @@ def check_python(supported_minors=None, experimental_minors=None, reason=None): log.error(f"Python incompatible: current {sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro} required 3.{supported_minors}") if reason is not None: log.error(reason) - if not args.ignore and not args.experimental: + if not arguments.ignore and not arguments.experimental: sys.exit(1) - if not args.skip_git: + if not arguments.skip_git: git_cmd = os.environ.get('GIT', "git") if shutil.which(git_cmd) is None: log.error('Git not found') - if not args.ignore: + if not arguments.ignore: sys.exit(1) else: git_version = git('--version', folder=None, ignore=False) @@ -467,7 +467,7 @@ def check_python(supported_minors=None, experimental_minors=None, reason=None): # check diffusers version def check_diffusers(): t_start = time.time() - if args.skip_all: + if arguments.skip_all: return target_commit = '8ec0a5ccad96957c10388d2d2acc7fdd8e0fab84' # diffusers commit hash # if args.use_rocm or args.use_zluda or args.use_directml: @@ -475,13 +475,13 @@ def check_diffusers(): pkg = package_spec('diffusers') minor = int(pkg.version.split('.')[1] if pkg is not None else -1) current = opts.get('diffusers_version', '') if minor > -1 else '' - if (minor == -1) or ((current != target_commit) and (not args.experimental)): + if (minor == -1) or ((current != target_commit) and (not arguments.experimental)): if minor == -1: log.info(f'Install: package="diffusers" commit={target_commit}') else: log.info(f'Update: package="diffusers" current={pkg.version} hash={current} target={target_commit}') pip('uninstall --yes diffusers', ignore=True, quiet=True, uv=False) - if args.skip_git: + if arguments.skip_git: log.warning('Git: marked as not available but required for diffusers installation') pip(f'install --upgrade git+https://github.com/huggingface/diffusers@{target_commit}', ignore=False, quiet=True, uv=False) global diffusers_commit # pylint: disable=global-statement @@ -492,12 +492,12 @@ def check_diffusers(): # check transformers version def check_transformers(): t_start = time.time() - if args.skip_all or args.skip_git or args.experimental: + if arguments.skip_all or arguments.skip_git or arguments.experimental: return pkg_transformers = package_spec('transformers') pkg_tokenizers = package_spec('tokenizers') target_commit = '753d61104116eefc8ffc977327b441ee0c8d599f' # transformers commit hash == 4.57.6 - if args.use_directml: + if arguments.use_directml: target_transformers = '4.52.4' target_tokenizers = '0.21.4' else: @@ -505,7 +505,7 @@ def check_transformers(): target_tokenizers = '0.22.2' if target_transformers is not None: # Pinned release version (e.g. DirectML) - if (pkg_transformers is None) or ((pkg_transformers.version != target_transformers) or (pkg_tokenizers is None) or ((pkg_tokenizers.version != target_tokenizers) and (not args.experimental))): + if (pkg_transformers is None) or ((pkg_transformers.version != target_transformers) or (pkg_tokenizers is None) or ((pkg_tokenizers.version != target_tokenizers) and (not arguments.experimental))): if pkg_transformers is None: log.info(f'Install: package="transformers" version={target_transformers}') else: @@ -532,7 +532,7 @@ def check_transformers(): # check onnx version def check_onnx(): t_start = time.time() - if args.skip_all or args.skip_requirements: + if arguments.skip_all or arguments.skip_requirements: return if not installed('onnx', quiet=True): install('onnx', 'onnx', ignore=True) @@ -545,7 +545,7 @@ def install_cuda(): t_start = time.time() log.info('CUDA: nVidia toolkit detected') ts('cuda', t_start) - if args.use_nightly: + if arguments.use_nightly: cmd = os.environ.get('TORCH_COMMAND', '--upgrade --pre torch torchvision --index-url https://download.pytorch.org/whl/nightly/cu128 --extra-index-url https://download.pytorch.org/whl/nightly/cu130') else: cmd = os.environ.get('TORCH_COMMAND', 'torch==2.10.0+cu128 torchvision==0.25.0+cu128 --index-url https://download.pytorch.org/whl/cu128') @@ -555,7 +555,7 @@ def install_cuda(): def install_rocm_zluda(): torch_command = '' t_start = time.time() - if args.skip_all or args.skip_requirements: + if arguments.skip_all or arguments.skip_requirements: return torch_command from modules import rocm @@ -572,7 +572,7 @@ def install_rocm_zluda(): log.warning('ROCm: no agent was found') else: log.info(f'ROCm: agents={[gpu.name for gpu in amd_gpus]}') - if args.device_id is None: + if arguments.device_id is None: index = 0 for idx, gpu in enumerate(amd_gpus): index = idx @@ -583,11 +583,11 @@ def install_rocm_zluda(): os.environ.setdefault('HIP_VISIBLE_DEVICES', str(index)) device = amd_gpus[index] else: - device_id = int(args.device_id) + device_id = int(arguments.device_id) if device_id < len(amd_gpus): device = amd_gpus[device_id] - if sys.platform == "win32" and not args.use_zluda and device is not None and device.therock is not None and not installed("rocm"): + if sys.platform == "win32" and not arguments.use_zluda and device is not None and device.therock is not None and not installed("rocm"): check_python(supported_minors=[11, 12, 13], reason='ROCm backend requires a Python version between 3.11 and 3.13') install(f"rocm[devel,libraries] --index-url https://rocm.nightlies.amd.com/{device.therock}") rocm.refresh() @@ -598,18 +598,18 @@ def install_rocm_zluda(): log.info(msg) if sys.platform == "win32": - if args.use_zluda: + if arguments.use_zluda: torch_command = os.environ.get('TORCH_COMMAND', 'torch==2.7.1+cu118 torchvision==0.22.1+cu118 --index-url https://download.pytorch.org/whl/cu118') - if args.device_id is not None: + if arguments.device_id is not None: if os.environ.get('HIP_VISIBLE_DEVICES', None) is not None: log.warning('Setting HIP_VISIBLE_DEVICES and --device-id at the same time may be mistake.') - os.environ['HIP_VISIBLE_DEVICES'] = args.device_id - del args.device_id + os.environ['HIP_VISIBLE_DEVICES'] = arguments.device_id + del arguments.device_id from modules import zluda_installer try: - if args.reinstall or zluda_installer.is_reinstall_needed(): + if arguments.reinstall or zluda_installer.is_reinstall_needed(): zluda_installer.uninstall() zluda_installer.install() zluda_installer.set_default_agent(device) @@ -632,7 +632,7 @@ def install_rocm_zluda(): torch_command = os.environ.get('TORCH_COMMAND', '--no-cache-dir https://repo.radeon.com/rocm/windows/rocm-rel-6.4.4/torch-2.8.0a0%2Bgitfc14c65-cp312-cp312-win_amd64.whl https://repo.radeon.com/rocm/windows/rocm-rel-6.4.4/torchvision-0.24.0a0%2Bc85f008-cp312-cp312-win_amd64.whl') else: #check_python(supported_minors=[10, 11, 12, 13, 14], reason='ROCm backend requires a Python version between 3.10 and 3.13') - if args.use_nightly: + if arguments.use_nightly: if rocm.version is None or float(rocm.version) >= 7.1: # assume the latest if version check fails torch_command = os.environ.get('TORCH_COMMAND', '--upgrade --pre torch torchvision --index-url https://download.pytorch.org/whl/nightly/rocm7.1') else: # oldest rocm version on nightly is 7.0 @@ -673,10 +673,10 @@ def install_rocm_zluda(): def install_ipex(): t_start = time.time() #check_python(supported_minors=[10, 11, 12, 13, 14], reason='IPEX backend requires a Python version between 3.10 and 3.13') - args.use_ipex = True # pylint: disable=attribute-defined-outside-init + arguments.use_ipex = True # pylint: disable=attribute-defined-outside-init log.info('IPEX: Intel OneAPI toolkit detected') - if args.use_nightly: + if arguments.use_nightly: torch_command = os.environ.get('TORCH_COMMAND', '--upgrade --pre torch torchvision --index-url https://download.pytorch.org/whl/nightly/xpu') else: torch_command = os.environ.get('TORCH_COMMAND', 'torch==2.10.0+xpu torchvision==0.25.0+xpu --index-url https://download.pytorch.org/whl/xpu') @@ -696,7 +696,7 @@ def install_openvino(): else: torch_command = os.environ.get('TORCH_COMMAND', 'torch==2.10.0+cpu torchvision==0.25.0 --index-url https://download.pytorch.org/whl/cpu') - if not (args.skip_all or args.skip_requirements): + if not (arguments.skip_all or arguments.skip_requirements): install(os.environ.get('OPENVINO_COMMAND', 'openvino==2025.4.1'), 'openvino') install(os.environ.get('NNCF_COMMAND', 'nncf==2.19.0'), 'nncf') ts('openvino', t_start) @@ -708,13 +708,13 @@ def install_torch_addons(): triton_command = os.environ.get('TRITON_COMMAND', None) if triton_command is not None and triton_command != 'skip': install(triton_command, 'triton', quiet=True) - xformers_package = os.environ.get('XFORMERS_PACKAGE', '--pre xformers') if opts.get('cross_attention_optimization', '') == 'xFormers' or args.use_xformers else 'none' + xformers_package = os.environ.get('XFORMERS_PACKAGE', '--pre xformers') if opts.get('cross_attention_optimization', '') == 'xFormers' or arguments.use_xformers else 'none' if 'xformers' in xformers_package: try: install(xformers_package, ignore=True, no_deps=True) except Exception as e: log.debug(f'xFormers cannot install: {e}') - elif not args.experimental and not args.use_xformers and opts.get('cross_attention_optimization', '') != 'xFormers': + elif not arguments.experimental and not arguments.use_xformers and opts.get('cross_attention_optimization', '') != 'xFormers': uninstall('xformers') if opts.get('cuda_compile_backend', '') == 'hidet': install('hidet', 'hidet') @@ -728,7 +728,7 @@ def install_torch_addons(): install('torchao==0.10.0', 'torchao') if opts.get('samples_format', 'jpg') == 'jxl' or opts.get('grid_format', 'jpg') == 'jxl': install('pillow-jxl-plugin==1.3.5', 'pillow-jxl-plugin') - if not args.experimental: + if not arguments.experimental: uninstall('wandb', quiet=True) uninstall('pynvml', quiet=True) ts('addons', t_start) @@ -753,25 +753,25 @@ def check_cudnn(): def check_torch(): log.info('Torch: verifying installation') t_start = time.time() - if args.skip_torch: + if arguments.skip_torch: log.info('Torch: skip tests') return - if args.profile: + if arguments.profile: pr = cProfile.Profile() pr.enable() - allow_cuda = not (args.use_rocm or args.use_directml or args.use_ipex or args.use_openvino) - allow_rocm = not (args.use_cuda or args.use_directml or args.use_ipex or args.use_openvino) - allow_ipex = not (args.use_cuda or args.use_rocm or args.use_directml or args.use_openvino) - allow_directml = not (args.use_cuda or args.use_rocm or args.use_ipex or args.use_openvino) - allow_openvino = not (args.use_cuda or args.use_rocm or args.use_ipex or args.use_directml) - log.debug(f'Torch overrides: cuda={args.use_cuda} rocm={args.use_rocm} ipex={args.use_ipex} directml={args.use_directml} openvino={args.use_openvino} zluda={args.use_zluda}') + allow_cuda = not (arguments.use_rocm or arguments.use_directml or arguments.use_ipex or arguments.use_openvino) + allow_rocm = not (arguments.use_cuda or arguments.use_directml or arguments.use_ipex or arguments.use_openvino) + allow_ipex = not (arguments.use_cuda or arguments.use_rocm or arguments.use_directml or arguments.use_openvino) + allow_directml = not (arguments.use_cuda or arguments.use_rocm or arguments.use_ipex or arguments.use_openvino) + allow_openvino = not (arguments.use_cuda or arguments.use_rocm or arguments.use_ipex or arguments.use_directml) + log.debug(f'Torch overrides: cuda={arguments.use_cuda} rocm={arguments.use_rocm} ipex={arguments.use_ipex} directml={arguments.use_directml} openvino={arguments.use_openvino} zluda={arguments.use_zluda}') # log.debug(f'Torch allowed: cuda={allow_cuda} rocm={allow_rocm} ipex={allow_ipex} diml={allow_directml} openvino={allow_openvino}') torch_command = os.environ.get('TORCH_COMMAND', '') if sys.platform != 'win32': - if args.use_zluda: + if arguments.use_zluda: log.error('ZLUDA is only supported on Windows') - if args.use_directml: + if arguments.use_directml: log.error('DirectML is only supported on Windows') if torch_command != '': @@ -779,21 +779,21 @@ def check_torch(): is_ipex_available = False is_rocm_available = False else: - is_cuda_available = allow_cuda and (args.use_cuda or shutil.which('nvidia-smi') is not None or os.path.exists(os.path.join(os.environ.get('SystemRoot') or r'C:\Windows', 'System32', 'nvidia-smi.exe'))) - is_ipex_available = allow_ipex and (args.use_ipex or shutil.which('sycl-ls') is not None or shutil.which('sycl-ls.exe') is not None or os.environ.get('ONEAPI_ROOT') is not None or os.path.exists('/opt/intel/oneapi') or os.path.exists("C:/Program Files (x86)/Intel/oneAPI") or os.path.exists("C:/oneAPI") or os.path.exists("C:/Program Files/Intel/Intel Graphics Software")) + is_cuda_available = allow_cuda and (arguments.use_cuda or shutil.which('nvidia-smi') is not None or os.path.exists(os.path.join(os.environ.get('SystemRoot') or r'C:\Windows', 'System32', 'nvidia-smi.exe'))) + is_ipex_available = allow_ipex and (arguments.use_ipex or shutil.which('sycl-ls') is not None or shutil.which('sycl-ls.exe') is not None or os.environ.get('ONEAPI_ROOT') is not None or os.path.exists('/opt/intel/oneapi') or os.path.exists("C:/Program Files (x86)/Intel/oneAPI") or os.path.exists("C:/oneAPI") or os.path.exists("C:/Program Files/Intel/Intel Graphics Software")) is_rocm_available = False if not is_cuda_available and not is_ipex_available and allow_rocm: from modules import rocm - is_rocm_available = allow_rocm and (args.use_rocm or args.use_zluda or rocm.is_installed) # late eval to avoid unnecessary import + is_rocm_available = allow_rocm and (arguments.use_rocm or arguments.use_zluda or rocm.is_installed) # late eval to avoid unnecessary import - if is_cuda_available and args.use_cuda: # prioritize cuda + if is_cuda_available and arguments.use_cuda: # prioritize cuda torch_command = install_cuda() - elif is_rocm_available and (args.use_rocm or args.use_zluda): # prioritize rocm + elif is_rocm_available and (arguments.use_rocm or arguments.use_zluda): # prioritize rocm torch_command = install_rocm_zluda() - elif allow_ipex and args.use_ipex: # prioritize ipex + elif allow_ipex and arguments.use_ipex: # prioritize ipex torch_command = install_ipex() - elif allow_openvino and args.use_openvino: # prioritize openvino + elif allow_openvino and arguments.use_openvino: # prioritize openvino torch_command = install_openvino() elif is_cuda_available: torch_command = install_cuda() @@ -805,17 +805,17 @@ def check_torch(): machine = platform.machine() if sys.platform == 'darwin': torch_command = os.environ.get('TORCH_COMMAND', 'torch torchvision') - elif allow_directml and args.use_directml and ('arm' not in machine and 'aarch' not in machine): + elif allow_directml and arguments.use_directml and ('arm' not in machine and 'aarch' not in machine): log.info('DirectML: selected') torch_command = os.environ.get('TORCH_COMMAND', 'torch==2.4.1 torchvision torch-directml==0.2.4.dev240913') - if 'torch' in torch_command and not args.version: + if 'torch' in torch_command and not arguments.version: install(torch_command, 'torch torchvision') install('onnxruntime-directml', 'onnxruntime-directml', ignore=True) else: log.warning('Torch: CPU-only version installed') torch_command = os.environ.get('TORCH_COMMAND', 'torch torchvision') - if args.version: + if arguments.version: return if 'torch' in torch_command: @@ -833,13 +833,13 @@ def check_torch(): pass if 'cpu' in torch.__version__: if is_cuda_available: - if args.use_cuda: + if arguments.use_cuda: log.warning(f'Torch: version="{torch.__version__}" CPU version installed and CUDA is selected - reinstalling') install(torch_command, 'torch torchvision', quiet=True, reinstall=True, force=True) # foce reinstall else: log.warning(f'Torch: version="{torch.__version__}" CPU version installed and CUDA is available - consider reinstalling') elif is_rocm_available: - if args.use_rocm: + if arguments.use_rocm: log.warning(f'Torch: version="{torch.__version__}" CPU version installed and ROCm is selected - reinstalling') install(torch_command, 'torch torchvision', quiet=True, reinstall=True, force=True) # foce reinstall else: @@ -860,7 +860,7 @@ def check_torch(): log.info(f'Torch detected: gpu="{torch.cuda.get_device_name(device)}" vram={round(torch.cuda.get_device_properties(device).total_memory / 1024 / 1024)} arch={torch.cuda.get_device_capability(device)} cores={torch.cuda.get_device_properties(device).multi_processor_count}') else: try: - if args.use_directml and allow_directml: + if arguments.use_directml and allow_directml: import torch_directml # pylint: disable=import-error dml_ver = package_version("torch-directml") log.warning(f'Torch backend: DirectML ({dml_ver})') @@ -871,15 +871,15 @@ def check_torch(): log.warning("Torch reports CUDA not available") except Exception as e: log.error(f'Torch cannot load: {e}') - if not args.ignore: + if not arguments.ignore: sys.exit(1) if is_rocm_available: rocm.postinstall() - if not args.skip_all: + if not arguments.skip_all: install_torch_addons() check_cudnn() - if args.profile: + if arguments.profile: pr.disable() print_profile(pr, 'Torch') ts('torch', t_start) @@ -888,9 +888,9 @@ def check_torch(): # check modified files def check_modified_files(): t_start = time.time() - if args.quick: + if arguments.quick: return - if args.skip_git: + if arguments.skip_git: return try: res = git('status --porcelain') @@ -952,7 +952,7 @@ def list_extensions_folder(folder, quiet=False): # run installer for each installed and enabled extension and optionally update them def install_extensions(force=False): - if args.profile: + if arguments.profile: pr = cProfile.Profile() pr.enable() pkgs = [f"{d.metadata['Name']}=={d.version}" for d in importlib.metadata.distributions()] @@ -961,7 +961,7 @@ def install_extensions(force=False): extensions_duplicates = [] extensions_enabled = [] extensions_disabled = [e.lower() for e in opts.get('disabled_extensions', [])] - extension_folders = [extensions_builtin_dir] if args.safe else [extensions_builtin_dir, extensions_dir] + extension_folders = [extensions_builtin_dir] if arguments.safe else [extensions_builtin_dir, extensions_dir] res = [] for folder in extension_folders: if not os.path.isdir(folder): @@ -976,13 +976,13 @@ def install_extensions(force=False): extensions_duplicates.append(ext) continue extensions_enabled.append(ext) - if args.upgrade or force: + if arguments.upgrade or force: try: res.append(update(os.path.join(folder, ext))) except Exception: res.append(f'Extension update error: {os.path.join(folder, ext)}') log.error(f'Extension update error: {os.path.join(folder, ext)}') - if not args.skip_extensions: + if not arguments.skip_extensions: commit = extensions_commit.get(os.path.basename(ext), None) if commit is not None: log.debug(f'Extension force: name="{ext}" commit={commit}') @@ -1000,7 +1000,7 @@ def install_extensions(force=False): log.info(f'Extensions enabled: {extensions_enabled}') if len(extensions_duplicates) > 0: log.warning(f'Extensions duplicates: {extensions_duplicates}') - if args.profile: + if arguments.profile: pr.disable() print_profile(pr, 'Extensions') # ts('extensions', t_start) @@ -1010,7 +1010,7 @@ def install_extensions(force=False): # initialize and optionally update submodules def install_submodules(force=True): t_start = time.time() - if args.profile: + if arguments.profile: pr = cProfile.Profile() pr.enable() log.info('Verifying submodules') @@ -1027,14 +1027,14 @@ def install_submodules(force=True): for submodule in submodules: try: name = submodule.split()[1].strip() - if args.upgrade: + if arguments.upgrade: res.append(update(name)) else: branch(name) except Exception: log.error(f'Submodule update error: {submodule}') setup_logging() - if args.profile: + if arguments.profile: pr.disable() print_profile(pr, 'Submodule') ts('submodules', t_start) @@ -1083,7 +1083,7 @@ def install_pydantic(): def install_scipy(): - if args.new or (sys.version_info >= (3, 14)): + if arguments.new or (sys.version_info >= (3, 14)): install('scipy==1.17.0', ignore=True, quiet=True) else: install('scipy==1.14.1', ignore=True, quiet=True) @@ -1142,9 +1142,9 @@ def install_optional(): def install_requirements(): t_start = time.time() - if args.skip_requirements and not args.requirements: + if arguments.skip_requirements and not arguments.requirements: return - if args.profile: + if arguments.profile: pr = cProfile.Profile() pr.enable() if int(sys.version_info.minor) >= 13: @@ -1154,11 +1154,11 @@ def install_requirements(): quick_allowed = False log.info('Install requirements: this may take a while...') pip('install -r requirements.txt') - if args.optional: + if arguments.optional: quick_allowed = False install_optional() log.info('Install: verifying requirements') - if args.new: + if arguments.new: log.debug('Install: flag=new') with open('requirements.txt', encoding='utf8') as f: lines = [line.strip() for line in f.readlines() if line.strip() != '' and not line.startswith('#') and line is not None] @@ -1168,7 +1168,7 @@ def install_requirements(): install_pydantic() install_opencv() install_scipy() - if args.profile: + if arguments.profile: pr.disable() print_profile(pr, 'Requirements') ts('requirements', t_start) @@ -1226,7 +1226,7 @@ def set_environment(): def check_extensions(): newest_all = os.path.getmtime('requirements.txt') from modules.paths import extensions_builtin_dir, extensions_dir - extension_folders = [extensions_builtin_dir] if args.safe else [extensions_builtin_dir, extensions_dir] + extension_folders = [extensions_builtin_dir] if arguments.safe else [extensions_builtin_dir, extensions_dir] disabled_extensions_all = opts.get('disable_all_extensions', 'none') if disabled_extensions_all != 'none': log.info(f'Extensions: disabled={disabled_extensions_all}') @@ -1360,27 +1360,27 @@ def check_venv(): def check_version(reset=True): # pylint: disable=unused-argument if opts.get('offline_mode', False): log.warning('Offline mode enabled') - args.skip_git = True # pylint: disable=attribute-defined-outside-init - args.skip_all = True # pylint: disable=attribute-defined-outside-init + arguments.skip_git = True # pylint: disable=attribute-defined-outside-init + arguments.skip_all = True # pylint: disable=attribute-defined-outside-init return t_start = time.time() - if args.skip_all: + if arguments.skip_all: return if not os.path.exists('.git'): log.warning('Not a git repository') - args.skip_git = True # pylint: disable=attribute-defined-outside-init + arguments.skip_git = True # pylint: disable=attribute-defined-outside-init ver = get_version() log.info(f'Version: {print_dict(ver)}') branch_name = ver.get('branch', None) if ver is not None else 'master' if branch_name is None or branch_name == 'unknown': branch_name = 'master' - if args.version or args.skip_git: + if arguments.version or arguments.skip_git: return check_ui(ver) commit = git('rev-parse HEAD') global git_commit # pylint: disable=global-statement git_commit = commit[:7] - if args.quick: + if arguments.quick: return try: import requests @@ -1409,7 +1409,7 @@ def check_version(reset=True): # pylint: disable=unused-argument latest = commits['commit']['sha'] if len(latest) != 40: log.error(f'Repository error: commit={latest} invalid') - elif latest != commit and args.upgrade: + elif latest != commit and arguments.upgrade: global quick_allowed # pylint: disable=global-statement quick_allowed = False log.info('Updating main repository') @@ -1441,7 +1441,7 @@ def check_version(reset=True): # pylint: disable=unused-argument def update_wiki(): t_start = time.time() - if args.upgrade: + if arguments.upgrade: log.info('Updating Wiki') try: update(os.path.join(os.path.dirname(__file__), "wiki")) @@ -1472,7 +1472,7 @@ def get_state(): try: from concurrent.futures.thread import ThreadPoolExecutor from modules.paths import extensions_builtin_dir, extensions_dir - extension_folders = [extensions_builtin_dir] if args.safe else [extensions_builtin_dir, extensions_dir] + extension_folders = [extensions_builtin_dir] if arguments.safe else [extensions_builtin_dir, extensions_dir] ext_dirs = [] for folder in extension_folders: if not os.path.isdir(folder): @@ -1526,9 +1526,9 @@ def update_state(): def check_timestamp(): if not quick_allowed or not os.path.isfile(log_file): return False - if args.quick: + if arguments.quick: return True - if args.skip_git: + if arguments.skip_git: return True return check_state() @@ -1552,28 +1552,28 @@ def add_args(parser): def parse_args(parser): # command line args - global args # pylint: disable=global-statement + global arguments # pylint: disable=global-statement if "USED_VSCODE_COMMAND_PICKARGS" in os.environ: import shlex argv = shlex.split(" ".join(sys.argv[1:])) if "USED_VSCODE_COMMAND_PICKARGS" in os.environ else sys.argv[1:] log.debug('VSCode Launch') - args = parser.parse_args(argv) + arguments = parser.parse_args(argv) else: - args = parser.parse_args() - return args + arguments = parser.parse_args() + return arguments def extensions_preload(parser): t_start = time.time() - if args.profile: + if arguments.profile: pr = cProfile.Profile() pr.enable() - if args.safe: + if arguments.safe: log.info('Running in safe mode without user extensions') try: from modules.script_loading import preload_extensions from modules.paths import extensions_builtin_dir, extensions_dir - extension_folders = [extensions_builtin_dir] if args.safe else [extensions_builtin_dir, extensions_dir] + extension_folders = [extensions_builtin_dir] if arguments.safe else [extensions_builtin_dir, extensions_dir] preload_time = {} for ext_dir in extension_folders: t0 = time.time() @@ -1583,7 +1583,7 @@ def extensions_preload(parser): log.debug(f'Extension preload: {preload_time}') except Exception: log.error('Error running extension preloading') - if args.profile: + if arguments.profile: pr.disable() print_profile(pr, 'Preload') ts('preload', t_start) @@ -1612,8 +1612,8 @@ def git_reset(folder='.'): def read_options(): t_start = time.time() global opts # pylint: disable=global-statement - if os.path.isfile(args.config): - with open(args.config, encoding="utf8") as file: + if os.path.isfile(arguments.config): + with open(arguments.config, encoding="utf8") as file: try: opts = json.load(file) if type(opts) is str: diff --git a/launch.py b/launch.py index 67b146146..6664be77b 100755 --- a/launch.py +++ b/launch.py @@ -233,7 +233,7 @@ def start_server(immediate=True, server=None): def main(): global args # pylint: disable=global-statement init_args() # setup argparser and default folders - installer.args = args + installer.arguments = args installer.setup_logging(debug=args.debug, trace=args.trace, filename=args.log) log.info('Starting SD.Next') installer.get_logfile() diff --git a/modules/zluda_installer.py b/modules/zluda_installer.py index 1d9a18890..09b702550 100644 --- a/modules/zluda_installer.py +++ b/modules/zluda_installer.py @@ -6,7 +6,7 @@ import ctypes import shutil import zipfile import urllib.request -from installer import args +from installer import arguments from modules.logger import log from modules import rocm @@ -82,8 +82,8 @@ def install(): commit = os.environ.get("ZLUDA_HASH", "5e717459179dc272b7d7d23391f0fad66c7459cf") if os.environ.get("ZLUDA_NIGHTLY", "0") == "1": log.warning("Environment variable 'ZLUDA_NIGHTLY' will be removed. Please use command-line argument '--use-nightly' instead.") - args.use_nightly = True - if args.use_nightly: + arguments.use_nightly = True + if arguments.use_nightly: platform = "nightly-" + platform log.debug(f'Install ZLUDA: rocm={rocm.version} platform={platform} commit={commit}') ssl._create_default_https_context = ssl._create_unverified_context # pylint: disable=protected-access From 3f8812a76f7c578e0da2885de160c1ad192bf8e4 Mon Sep 17 00:00:00 2001 From: awsr <43862868+awsr@users.noreply.github.com> Date: Thu, 5 Mar 2026 18:07:55 -0800 Subject: [PATCH 03/14] Shared function for running `subprocess.run` --- installer.py | 35 +++++++++++++++++++++++++++-------- 1 file changed, 27 insertions(+), 8 deletions(-) diff --git a/installer.py b/installer.py index 18a0819e8..76991406a 100644 --- a/installer.py +++ b/installer.py @@ -200,14 +200,33 @@ def uninstall(package, quiet = False): return res -def run(cmd: str, arg: str): - result = subprocess.run(f'"{cmd}" {arg}', shell=True, check=False, env=os.environ, capture_output=True) - txt = result.stdout.decode(encoding="utf8", errors="ignore") - if len(result.stderr) > 0: - txt += ('\n' if len(txt) > 0 else '') + result.stderr.decode(encoding="utf8", errors="ignore") - txt = txt.strip() - debug(f'Exec {cmd}: {txt}') - return txt +def sub_run(cmd: str, *args: str, **kwargs): + """Run command and arguments with `subprocess.run`. + + Default run options are `shell=True, check=False, env=os.environ`. + + Args: + cmd (str): Main command to run. + *args (str): Additional command arguments. + **kwargs: `subprocess.run` option overrides. + + Returns: + tuple[CompletedProcess[str], str]: Tuple with the results and the combined `stdout` and `stderr` values. + """ + options = { + "shell": True, + "check": False, + "env": os.environ, + } + options |= kwargs # Override defaults with passed kwargs + result = subprocess.run(f'"{cmd}" {" ".join(args)}', **options, capture_output=True, text=True) + result.stdout = result.stdout.strip() + result.stderr = result.stderr.strip() + txt = result.stdout + if result.stderr: + # Put newline between outputs only if stdout isn't empty + txt += "\n" + result.stderr if txt else result.stderr + return result, txt def cleanup_broken_packages(): From 61415ca30558440d82e91176bff1f9b0b95c78a0 Mon Sep 17 00:00:00 2001 From: awsr <43862868+awsr@users.noreply.github.com> Date: Thu, 5 Mar 2026 19:18:30 -0800 Subject: [PATCH 04/14] Use new `sub_run` function --- installer.py | 56 +++++++++++++++++----------------------------------- 1 file changed, 18 insertions(+), 38 deletions(-) diff --git a/installer.py b/installer.py index 76991406a..39227aeb5 100644 --- a/installer.py +++ b/installer.py @@ -265,18 +265,13 @@ def pip(arg: str, ignore: bool = False, quiet: bool = True, uv = True): all_args = f'{pip_log}{arg} {env_args}'.strip() if not quiet: log.debug(f'Running: {pipCmd}="{all_args}"') - result = subprocess.run(f'"{sys.executable}" -m {pipCmd} {all_args}', shell=True, check=False, env=os.environ, capture_output=True) - txt = result.stdout.decode(encoding="utf8", errors="ignore") + result, txt = sub_run(sys.executable, "-m", pipCmd, all_args) if len(result.stderr) > 0: if uv and result.returncode != 0: - err = result.stderr.decode(encoding="utf8", errors="ignore") log.warning(f'Install: cmd="{pipCmd}" args="{all_args}" cannot use uv, fallback to pip') - debug(f'Install: uv pip error: {err}') + debug(f'Install: uv pip error: {result.stderr}') cleanup_broken_packages() return pip(originalArg, ignore, quiet, uv=False) - else: - txt += ('\n' if len(txt) > 0 else '') + result.stderr.decode(encoding="utf8", errors="ignore") - txt = txt.strip() debug(f'Install {pipCmd}: {txt}') if result.returncode != 0 and not ignore: errors.append(f'pip: {package}') @@ -312,25 +307,21 @@ def git(arg: str, folder: str = None, ignore: bool = False, optional: bool = Fal git_cmd = os.environ.get('GIT', "git") if git_cmd != "git": git_cmd = os.path.abspath(git_cmd) - result = subprocess.run(f'"{git_cmd}" {arg}', check=False, shell=True, env=os.environ, capture_output=True, cwd=folder or '.') - stdout = result.stdout.decode(encoding="utf8", errors="ignore") - if len(result.stderr) > 0: - stdout += ('\n' if len(stdout) > 0 else '') + result.stderr.decode(encoding="utf8", errors="ignore") - stdout = stdout.strip() + result, txt = sub_run(git_cmd, arg, cwd=folder or ".") if result.returncode != 0 and not ignore: if folder is None: folder = 'root' - if "couldn't find remote ref" in stdout: # not a git repo + if "couldn't find remote ref" in txt: # not a git repo log.error(f'Git: folder="{folder}" could not identify repository') - elif "no submodule mapping found" in stdout: + elif "no submodule mapping found" in txt: log.warning(f'Git: folder="{folder}" submodules changed') - elif 'or stash them' in stdout: + elif 'or stash them' in txt: log.error(f'Git: folder="{folder}" local changes detected') else: - log.error(f'Git: folder="{folder}" arg="{arg}" output={stdout}') + log.error(f'Git: folder="{folder}" arg="{arg}" output={txt}') errors.append(f'git: {folder}') ts('git', t_start) - return stdout + return txt # reattach as needed as head can get detached @@ -944,13 +935,10 @@ def run_extension_installer(folder): if os.environ.get('PYTHONPATH', None) is not None: seperator = ';' if sys.platform == 'win32' else ':' env['PYTHONPATH'] += seperator + os.environ.get('PYTHONPATH', None) - result = subprocess.run(f'"{sys.executable}" "{path_installer}"', shell=True, env=env, check=False, capture_output=True, cwd=folder) - txt = result.stdout.decode(encoding="utf8", errors="ignore") - debug(f'Extension installer: file="{path_installer}" {txt}') + result, txt = sub_run(sys.executable, path_installer, env=env, cwd=folder) + debug(f'Extension installer: file="{path_installer}" {result.stdout}') if result.returncode != 0: errors.append(f'ext: {os.path.basename(folder)}') - if len(result.stderr) > 0: - txt = txt + '\n' + result.stderr.decode(encoding="utf8", errors="ignore") log.error(f'Extension installer error: {path_installer}') log.debug(txt) except Exception as e: @@ -1279,17 +1267,14 @@ def get_version(force=False): except Exception: pass try: - res = subprocess.run('git log --pretty=format:"%h %ad" -1 --date=short', capture_output=True, shell=True, check=True) - ver = res.stdout.decode(encoding = 'utf8', errors='ignore') if len(res.stdout) > 0 else ' ' + ver = sub_run('git log --pretty=format:"%h %ad" -1 --date=short', check=True)[0].stdout or ' ' commit, updated = ver.split(' ') version['commit'], version['updated'] = commit, updated except Exception as e: log.warning(f'Version: where=commit {e}') try: - res = subprocess.run('git remote get-url origin', capture_output=True, shell=True, check=True) - origin = res.stdout.decode(encoding = 'utf8', errors='ignore') if len(res.stdout) > 0 else '' - res = subprocess.run('git rev-parse --abbrev-ref HEAD', capture_output=True, shell=True, check=True) - branch_name = res.stdout.decode(encoding = 'utf8', errors='ignore') if len(res.stdout) > 0 else '' + origin = sub_run('git remote get-url origin', check=True)[0].stdout + branch_name = sub_run('git rev-parse --abbrev-ref HEAD', check=True)[0].stdout version['url'] = origin.replace('\n', '').removesuffix('.git') + '/tree/' + branch_name.replace('\n', '') version['branch'] = branch_name.replace('\n', '') if version['branch'] == 'HEAD': @@ -1298,10 +1283,8 @@ def get_version(force=False): log.warning(f'Version: where=branch {e}') try: if os.path.exists('extensions-builtin/sdnext-modernui'): - res = subprocess.run('git rev-parse --abbrev-ref HEAD', capture_output=True, shell=True, check=True, cwd='extensions-builtin/sdnext-modernui') - branch_ui = res.stdout.decode(encoding = 'utf8', errors='ignore') if len(res.stdout) > 0 else '' - branch_ui = 'dev' if 'dev' in branch_ui else 'main' - version['ui'] = branch_ui + branch_ui = sub_run('git rev-parse --abbrev-ref HEAD', check=True, cwd='extensions-builtin/sdnext-modernui')[0].stdout + version['ui'] = 'dev' if 'dev' in branch_ui else 'main' else: version['ui'] = 'unavailable' except Exception as e: @@ -1311,10 +1294,8 @@ def get_version(force=False): if os.environ.get('SD_KANVAS_DISABLE', None) is not None: version['kanvas'] = 'disabled' elif os.path.exists('extensions-builtin/sdnext-kanvas'): - res = subprocess.run('git rev-parse --abbrev-ref HEAD', capture_output=True, shell=True, check=True, cwd='extensions-builtin/sdnext-kanvas') - branch_kanvas = res.stdout.decode(encoding = 'utf8', errors='ignore') if len(res.stdout) > 0 else '' - branch_kanvas = 'dev' if 'dev' in branch_kanvas else 'main' - version['kanvas'] = branch_kanvas + branch_kanvas = sub_run('git rev-parse --abbrev-ref HEAD', check=True, cwd='extensions-builtin/sdnext-kanvas')[0].stdout + version['kanvas'] = 'dev' if 'dev' in branch_kanvas else 'main' else: version['kanvas'] = 'unavailable' except Exception as e: @@ -1502,8 +1483,7 @@ def get_state(): def _get_commit(item): ext, ext_dir = item try: - res = subprocess.run('git rev-parse HEAD', capture_output=True, shell=True, check=False, cwd=ext_dir) - return ext, res.stdout.decode(encoding='utf8', errors='ignore').strip() + return ext, sub_run('git rev-parse HEAD', cwd=ext_dir)[0].stdout except Exception: return ext, '' From af8622240c4c936dd4902350bd359b10550b1c2b Mon Sep 17 00:00:00 2001 From: awsr <43862868+awsr@users.noreply.github.com> Date: Thu, 5 Mar 2026 19:20:05 -0800 Subject: [PATCH 05/14] Remove redundant formatting --- installer.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/installer.py b/installer.py index 39227aeb5..c13f751b1 100644 --- a/installer.py +++ b/installer.py @@ -1275,8 +1275,8 @@ def get_version(force=False): try: origin = sub_run('git remote get-url origin', check=True)[0].stdout branch_name = sub_run('git rev-parse --abbrev-ref HEAD', check=True)[0].stdout - version['url'] = origin.replace('\n', '').removesuffix('.git') + '/tree/' + branch_name.replace('\n', '') - version['branch'] = branch_name.replace('\n', '') + version['url'] = origin.removesuffix('.git') + '/tree/' + branch_name + version['branch'] = branch_name if version['branch'] == 'HEAD': log.warning('Version: detached state detected') except Exception as e: From feffbe08510fcad786345e53eb27c9a0dde3d709 Mon Sep 17 00:00:00 2001 From: awsr <43862868+awsr@users.noreply.github.com> Date: Thu, 5 Mar 2026 20:06:09 -0800 Subject: [PATCH 06/14] Update `subprocess.run` usage --- cli/nvidia-smi.py | 5 ++--- launch.py | 8 ++++---- modules/devices.py | 4 ++-- modules/rocm.py | 4 ++-- 4 files changed, 10 insertions(+), 11 deletions(-) diff --git a/cli/nvidia-smi.py b/cli/nvidia-smi.py index 37d6390c5..298fc562e 100755 --- a/cli/nvidia-smi.py +++ b/cli/nvidia-smi.py @@ -13,9 +13,8 @@ def get_nvidia_smi(output='dict'): if smi is None: log.error("nvidia-smi not found") return None - result = subprocess.run(f'"{smi}" -q -x', shell=True, check=False, env=os.environ, stdout=subprocess.PIPE, stderr=subprocess.PIPE) - xml = result.stdout.decode(encoding="utf8", errors="ignore") - d = xmltodict.parse(xml) + result = subprocess.run(f'"{smi}" -q -x', shell=True, check=False, env=os.environ, capture_output=True, text=True) + d = xmltodict.parse(result.stdout) if 'nvidia_smi_log' in d: d = d['nvidia_smi_log'] if 'gpu' in d and 'supported_clocks' in d['gpu']: diff --git a/launch.py b/launch.py index 6664be77b..a3d503293 100755 --- a/launch.py +++ b/launch.py @@ -97,13 +97,13 @@ def run(command, desc=None, errdesc=None, custom_env=None, live=False): # compat if result.returncode != 0: raise RuntimeError(f"""{errdesc or 'Error running command'} Command: {command} Error code: {result.returncode}""") return '' - result = subprocess.run(command, capture_output=True, check=False, shell=True, env=os.environ if custom_env is None else custom_env) + result = subprocess.run(command, capture_output=True, check=False, shell=True, env=os.environ if custom_env is None else custom_env, text=True) if result.returncode != 0: raise RuntimeError(f"""{errdesc or 'Error running command'}: {command} code: {result.returncode} -{result.stdout.decode(encoding="utf8", errors="ignore") if len(result.stdout)>0 else ''} -{result.stderr.decode(encoding="utf8", errors="ignore") if len(result.stderr)>0 else ''} +{result.stdout} +{result.stderr} """) - return result.stdout.decode(encoding="utf8", errors="ignore") + return result.stdout def check_run(command): # compatbility function diff --git a/modules/devices.py b/modules/devices.py index 99276e30e..90f86ffcb 100644 --- a/modules/devices.py +++ b/modules/devices.py @@ -104,8 +104,8 @@ def get_gpu_info(): elif torch.cuda.is_available() and torch.version.cuda: try: import subprocess - result = subprocess.run('nvidia-smi --query-gpu=driver_version --format=csv,noheader', shell=True, check=False, env=os.environ, capture_output=True) - version = result.stdout.decode(encoding="utf8", errors="ignore").strip() + result = subprocess.run('nvidia-smi --query-gpu=driver_version --format=csv,noheader', shell=True, check=False, env=os.environ, capture_output=True, text=True) + version = result.stdout.strip() return version except Exception: return '' diff --git a/modules/rocm.py b/modules/rocm.py index 7295b4a52..576686d18 100644 --- a/modules/rocm.py +++ b/modules/rocm.py @@ -31,8 +31,8 @@ def dirname(path_: str, r: int = 1) -> str: def spawn(command: str | list[str], cwd: os.PathLike = '.') -> str: - process = subprocess.run(command, cwd=cwd, shell=True, check=False, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL) - return process.stdout.decode(encoding="utf8", errors="ignore") + process = subprocess.run(command, cwd=cwd, shell=True, check=False, capture_output=True, text=True) + return process.stdout def load_library_global(path_: str): From 1b4a45e2a6fb78abd04ab25952b48792220050a7 Mon Sep 17 00:00:00 2001 From: awsr <43862868+awsr@users.noreply.github.com> Date: Thu, 5 Mar 2026 20:17:44 -0800 Subject: [PATCH 07/14] Revert "Avoid using `args` as a variable name" This reverts commit 7c09e3dfe12033452b00ded2b0071811668f1631. --- installer.py | 204 ++++++++++++++++++------------------- launch.py | 2 +- modules/zluda_installer.py | 6 +- 3 files changed, 106 insertions(+), 106 deletions(-) diff --git a/installer.py b/installer.py index c13f751b1..84551483c 100644 --- a/installer.py +++ b/installer.py @@ -40,7 +40,7 @@ first_call = True quick_allowed = True errors = [] opts = {} -arguments = Dot({ +args = Dot({ 'debug': False, 'reset': False, 'profile': False, @@ -89,7 +89,7 @@ except Exception: @lru_cache def get_logfile(): log_size = os.path.getsize(log_file) if os.path.exists(log_file) else 0 - log.info(f'Logger: file="{os.path.abspath(log_file)}" level={logging.getLevelName(logging.DEBUG if arguments.debug else logging.INFO)} host="{hostname}" size={log_size} mode={"append" if not log_rolled else "create"}') + log.info(f'Logger: file="{os.path.abspath(log_file)}" level={logging.getLevelName(logging.DEBUG if args.debug else logging.INFO)} host="{hostname}" size={log_size} mode={"append" if not log_rolled else "create"}') return log_file @@ -169,13 +169,13 @@ def installed(package, friendly: str = None, quiet = False): # pylint: disable=r if len(p) > 1: exact = pkg_version == p[1] if not exact and not quiet: - if arguments.experimental: + if args.experimental: log.warning(f'Install: package="{p[0]}" installed={pkg_version} required={p[1]} allowing experimental') else: log.warning(f'Install: package="{p[0]}" installed={pkg_version} required={p[1]} version mismatch') global restart_required # pylint: disable=global-statement restart_required = True - ok = ok and (exact or arguments.experimental) + ok = ok and (exact or args.experimental) else: if not quiet: log.debug(f'Install: package="{p[0]}" install required') @@ -257,7 +257,7 @@ def pip(arg: str, ignore: bool = False, quiet: bool = True, uv = True): log.warning('Offline mode enabled') return 'offline' package = arg.replace("install", "").replace("--upgrade", "").replace("--no-deps", "").replace("--force-reinstall", "").replace(" ", " ").strip() - uv = uv and arguments.uv and not package.startswith('git+') + uv = uv and args.uv and not package.startswith('git+') pipCmd = "uv pip" if uv else "pip" if not quiet and '-r ' not in arg: log.info(f'Install: package="{package}" mode={"uv" if uv else "pip"}') @@ -285,13 +285,13 @@ def pip(arg: str, ignore: bool = False, quiet: bool = True, uv = True): def install(package, friendly: str = None, ignore: bool = False, reinstall: bool = False, no_deps: bool = False, quiet: bool = False, force: bool = False, no_build_isolation: bool = False): t_start = time.time() res = '' - if arguments.reinstall or arguments.upgrade: + if args.reinstall or args.upgrade: global quick_allowed # pylint: disable=global-statement quick_allowed = False - if (arguments.reinstall) or (reinstall) or (not installed(package, friendly, quiet=quiet)): + if (args.reinstall) or (reinstall) or (not installed(package, friendly, quiet=quiet)): deps = '' if not no_deps else '--no-deps ' isolation = '' if not no_build_isolation else '--no-build-isolation ' - cmd = f"install{' --upgrade' if not arguments.uv else ''}{' --force-reinstall' if force else ''} {deps}{isolation}{package}" + cmd = f"install{' --upgrade' if not args.uv else ''}{' --force-reinstall' if force else ''} {deps}{isolation}{package}" res = pip(cmd, ignore=ignore, uv=package != "uv" and not package.startswith('git+')) ts('install', t_start) return res @@ -300,7 +300,7 @@ def install(package, friendly: str = None, ignore: bool = False, reinstall: bool # execute git command def git(arg: str, folder: str = None, ignore: bool = False, optional: bool = False): # pylint: disable=unused-argument t_start = time.time() - if arguments.skip_git: + if args.skip_git: return '' if 'google.colab' in sys.modules: return '' @@ -380,7 +380,7 @@ def update(folder, keep_branch = False, rebase = True): else: res = git(f'pull origin {b} {arg}', folder) debug(f'Install update: folder={folder} branch={b} args={arg} {res}') - if not arguments.experimental: + if not args.experimental: commit = extensions_commit.get(os.path.basename(folder), None) if commit is not None: res = git(f'checkout {commit}', folder) @@ -442,7 +442,7 @@ def check_python(supported_minors=None, experimental_minors=None, reason=None): supported_minors = [10, 11, 12, 13] experimental_minors = [14] t_start = time.time() - if arguments.quick: + if args.quick: return log.info(f'Python: version={platform.python_version()} platform={platform.system()} bin="{sys.executable}" venv="{sys.prefix}"') if int(sys.version_info.minor) == 9: @@ -460,13 +460,13 @@ def check_python(supported_minors=None, experimental_minors=None, reason=None): log.error(f"Python incompatible: current {sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro} required 3.{supported_minors}") if reason is not None: log.error(reason) - if not arguments.ignore and not arguments.experimental: + if not args.ignore and not args.experimental: sys.exit(1) - if not arguments.skip_git: + if not args.skip_git: git_cmd = os.environ.get('GIT', "git") if shutil.which(git_cmd) is None: log.error('Git not found') - if not arguments.ignore: + if not args.ignore: sys.exit(1) else: git_version = git('--version', folder=None, ignore=False) @@ -477,7 +477,7 @@ def check_python(supported_minors=None, experimental_minors=None, reason=None): # check diffusers version def check_diffusers(): t_start = time.time() - if arguments.skip_all: + if args.skip_all: return target_commit = '8ec0a5ccad96957c10388d2d2acc7fdd8e0fab84' # diffusers commit hash # if args.use_rocm or args.use_zluda or args.use_directml: @@ -485,13 +485,13 @@ def check_diffusers(): pkg = package_spec('diffusers') minor = int(pkg.version.split('.')[1] if pkg is not None else -1) current = opts.get('diffusers_version', '') if minor > -1 else '' - if (minor == -1) or ((current != target_commit) and (not arguments.experimental)): + if (minor == -1) or ((current != target_commit) and (not args.experimental)): if minor == -1: log.info(f'Install: package="diffusers" commit={target_commit}') else: log.info(f'Update: package="diffusers" current={pkg.version} hash={current} target={target_commit}') pip('uninstall --yes diffusers', ignore=True, quiet=True, uv=False) - if arguments.skip_git: + if args.skip_git: log.warning('Git: marked as not available but required for diffusers installation') pip(f'install --upgrade git+https://github.com/huggingface/diffusers@{target_commit}', ignore=False, quiet=True, uv=False) global diffusers_commit # pylint: disable=global-statement @@ -502,12 +502,12 @@ def check_diffusers(): # check transformers version def check_transformers(): t_start = time.time() - if arguments.skip_all or arguments.skip_git or arguments.experimental: + if args.skip_all or args.skip_git or args.experimental: return pkg_transformers = package_spec('transformers') pkg_tokenizers = package_spec('tokenizers') target_commit = '753d61104116eefc8ffc977327b441ee0c8d599f' # transformers commit hash == 4.57.6 - if arguments.use_directml: + if args.use_directml: target_transformers = '4.52.4' target_tokenizers = '0.21.4' else: @@ -515,7 +515,7 @@ def check_transformers(): target_tokenizers = '0.22.2' if target_transformers is not None: # Pinned release version (e.g. DirectML) - if (pkg_transformers is None) or ((pkg_transformers.version != target_transformers) or (pkg_tokenizers is None) or ((pkg_tokenizers.version != target_tokenizers) and (not arguments.experimental))): + if (pkg_transformers is None) or ((pkg_transformers.version != target_transformers) or (pkg_tokenizers is None) or ((pkg_tokenizers.version != target_tokenizers) and (not args.experimental))): if pkg_transformers is None: log.info(f'Install: package="transformers" version={target_transformers}') else: @@ -542,7 +542,7 @@ def check_transformers(): # check onnx version def check_onnx(): t_start = time.time() - if arguments.skip_all or arguments.skip_requirements: + if args.skip_all or args.skip_requirements: return if not installed('onnx', quiet=True): install('onnx', 'onnx', ignore=True) @@ -555,7 +555,7 @@ def install_cuda(): t_start = time.time() log.info('CUDA: nVidia toolkit detected') ts('cuda', t_start) - if arguments.use_nightly: + if args.use_nightly: cmd = os.environ.get('TORCH_COMMAND', '--upgrade --pre torch torchvision --index-url https://download.pytorch.org/whl/nightly/cu128 --extra-index-url https://download.pytorch.org/whl/nightly/cu130') else: cmd = os.environ.get('TORCH_COMMAND', 'torch==2.10.0+cu128 torchvision==0.25.0+cu128 --index-url https://download.pytorch.org/whl/cu128') @@ -565,7 +565,7 @@ def install_cuda(): def install_rocm_zluda(): torch_command = '' t_start = time.time() - if arguments.skip_all or arguments.skip_requirements: + if args.skip_all or args.skip_requirements: return torch_command from modules import rocm @@ -582,7 +582,7 @@ def install_rocm_zluda(): log.warning('ROCm: no agent was found') else: log.info(f'ROCm: agents={[gpu.name for gpu in amd_gpus]}') - if arguments.device_id is None: + if args.device_id is None: index = 0 for idx, gpu in enumerate(amd_gpus): index = idx @@ -593,11 +593,11 @@ def install_rocm_zluda(): os.environ.setdefault('HIP_VISIBLE_DEVICES', str(index)) device = amd_gpus[index] else: - device_id = int(arguments.device_id) + device_id = int(args.device_id) if device_id < len(amd_gpus): device = amd_gpus[device_id] - if sys.platform == "win32" and not arguments.use_zluda and device is not None and device.therock is not None and not installed("rocm"): + if sys.platform == "win32" and not args.use_zluda and device is not None and device.therock is not None and not installed("rocm"): check_python(supported_minors=[11, 12, 13], reason='ROCm backend requires a Python version between 3.11 and 3.13') install(f"rocm[devel,libraries] --index-url https://rocm.nightlies.amd.com/{device.therock}") rocm.refresh() @@ -608,18 +608,18 @@ def install_rocm_zluda(): log.info(msg) if sys.platform == "win32": - if arguments.use_zluda: + if args.use_zluda: torch_command = os.environ.get('TORCH_COMMAND', 'torch==2.7.1+cu118 torchvision==0.22.1+cu118 --index-url https://download.pytorch.org/whl/cu118') - if arguments.device_id is not None: + if args.device_id is not None: if os.environ.get('HIP_VISIBLE_DEVICES', None) is not None: log.warning('Setting HIP_VISIBLE_DEVICES and --device-id at the same time may be mistake.') - os.environ['HIP_VISIBLE_DEVICES'] = arguments.device_id - del arguments.device_id + os.environ['HIP_VISIBLE_DEVICES'] = args.device_id + del args.device_id from modules import zluda_installer try: - if arguments.reinstall or zluda_installer.is_reinstall_needed(): + if args.reinstall or zluda_installer.is_reinstall_needed(): zluda_installer.uninstall() zluda_installer.install() zluda_installer.set_default_agent(device) @@ -642,7 +642,7 @@ def install_rocm_zluda(): torch_command = os.environ.get('TORCH_COMMAND', '--no-cache-dir https://repo.radeon.com/rocm/windows/rocm-rel-6.4.4/torch-2.8.0a0%2Bgitfc14c65-cp312-cp312-win_amd64.whl https://repo.radeon.com/rocm/windows/rocm-rel-6.4.4/torchvision-0.24.0a0%2Bc85f008-cp312-cp312-win_amd64.whl') else: #check_python(supported_minors=[10, 11, 12, 13, 14], reason='ROCm backend requires a Python version between 3.10 and 3.13') - if arguments.use_nightly: + if args.use_nightly: if rocm.version is None or float(rocm.version) >= 7.1: # assume the latest if version check fails torch_command = os.environ.get('TORCH_COMMAND', '--upgrade --pre torch torchvision --index-url https://download.pytorch.org/whl/nightly/rocm7.1') else: # oldest rocm version on nightly is 7.0 @@ -683,10 +683,10 @@ def install_rocm_zluda(): def install_ipex(): t_start = time.time() #check_python(supported_minors=[10, 11, 12, 13, 14], reason='IPEX backend requires a Python version between 3.10 and 3.13') - arguments.use_ipex = True # pylint: disable=attribute-defined-outside-init + args.use_ipex = True # pylint: disable=attribute-defined-outside-init log.info('IPEX: Intel OneAPI toolkit detected') - if arguments.use_nightly: + if args.use_nightly: torch_command = os.environ.get('TORCH_COMMAND', '--upgrade --pre torch torchvision --index-url https://download.pytorch.org/whl/nightly/xpu') else: torch_command = os.environ.get('TORCH_COMMAND', 'torch==2.10.0+xpu torchvision==0.25.0+xpu --index-url https://download.pytorch.org/whl/xpu') @@ -706,7 +706,7 @@ def install_openvino(): else: torch_command = os.environ.get('TORCH_COMMAND', 'torch==2.10.0+cpu torchvision==0.25.0 --index-url https://download.pytorch.org/whl/cpu') - if not (arguments.skip_all or arguments.skip_requirements): + if not (args.skip_all or args.skip_requirements): install(os.environ.get('OPENVINO_COMMAND', 'openvino==2025.4.1'), 'openvino') install(os.environ.get('NNCF_COMMAND', 'nncf==2.19.0'), 'nncf') ts('openvino', t_start) @@ -718,13 +718,13 @@ def install_torch_addons(): triton_command = os.environ.get('TRITON_COMMAND', None) if triton_command is not None and triton_command != 'skip': install(triton_command, 'triton', quiet=True) - xformers_package = os.environ.get('XFORMERS_PACKAGE', '--pre xformers') if opts.get('cross_attention_optimization', '') == 'xFormers' or arguments.use_xformers else 'none' + xformers_package = os.environ.get('XFORMERS_PACKAGE', '--pre xformers') if opts.get('cross_attention_optimization', '') == 'xFormers' or args.use_xformers else 'none' if 'xformers' in xformers_package: try: install(xformers_package, ignore=True, no_deps=True) except Exception as e: log.debug(f'xFormers cannot install: {e}') - elif not arguments.experimental and not arguments.use_xformers and opts.get('cross_attention_optimization', '') != 'xFormers': + elif not args.experimental and not args.use_xformers and opts.get('cross_attention_optimization', '') != 'xFormers': uninstall('xformers') if opts.get('cuda_compile_backend', '') == 'hidet': install('hidet', 'hidet') @@ -738,7 +738,7 @@ def install_torch_addons(): install('torchao==0.10.0', 'torchao') if opts.get('samples_format', 'jpg') == 'jxl' or opts.get('grid_format', 'jpg') == 'jxl': install('pillow-jxl-plugin==1.3.5', 'pillow-jxl-plugin') - if not arguments.experimental: + if not args.experimental: uninstall('wandb', quiet=True) uninstall('pynvml', quiet=True) ts('addons', t_start) @@ -763,25 +763,25 @@ def check_cudnn(): def check_torch(): log.info('Torch: verifying installation') t_start = time.time() - if arguments.skip_torch: + if args.skip_torch: log.info('Torch: skip tests') return - if arguments.profile: + if args.profile: pr = cProfile.Profile() pr.enable() - allow_cuda = not (arguments.use_rocm or arguments.use_directml or arguments.use_ipex or arguments.use_openvino) - allow_rocm = not (arguments.use_cuda or arguments.use_directml or arguments.use_ipex or arguments.use_openvino) - allow_ipex = not (arguments.use_cuda or arguments.use_rocm or arguments.use_directml or arguments.use_openvino) - allow_directml = not (arguments.use_cuda or arguments.use_rocm or arguments.use_ipex or arguments.use_openvino) - allow_openvino = not (arguments.use_cuda or arguments.use_rocm or arguments.use_ipex or arguments.use_directml) - log.debug(f'Torch overrides: cuda={arguments.use_cuda} rocm={arguments.use_rocm} ipex={arguments.use_ipex} directml={arguments.use_directml} openvino={arguments.use_openvino} zluda={arguments.use_zluda}') + allow_cuda = not (args.use_rocm or args.use_directml or args.use_ipex or args.use_openvino) + allow_rocm = not (args.use_cuda or args.use_directml or args.use_ipex or args.use_openvino) + allow_ipex = not (args.use_cuda or args.use_rocm or args.use_directml or args.use_openvino) + allow_directml = not (args.use_cuda or args.use_rocm or args.use_ipex or args.use_openvino) + allow_openvino = not (args.use_cuda or args.use_rocm or args.use_ipex or args.use_directml) + log.debug(f'Torch overrides: cuda={args.use_cuda} rocm={args.use_rocm} ipex={args.use_ipex} directml={args.use_directml} openvino={args.use_openvino} zluda={args.use_zluda}') # log.debug(f'Torch allowed: cuda={allow_cuda} rocm={allow_rocm} ipex={allow_ipex} diml={allow_directml} openvino={allow_openvino}') torch_command = os.environ.get('TORCH_COMMAND', '') if sys.platform != 'win32': - if arguments.use_zluda: + if args.use_zluda: log.error('ZLUDA is only supported on Windows') - if arguments.use_directml: + if args.use_directml: log.error('DirectML is only supported on Windows') if torch_command != '': @@ -789,21 +789,21 @@ def check_torch(): is_ipex_available = False is_rocm_available = False else: - is_cuda_available = allow_cuda and (arguments.use_cuda or shutil.which('nvidia-smi') is not None or os.path.exists(os.path.join(os.environ.get('SystemRoot') or r'C:\Windows', 'System32', 'nvidia-smi.exe'))) - is_ipex_available = allow_ipex and (arguments.use_ipex or shutil.which('sycl-ls') is not None or shutil.which('sycl-ls.exe') is not None or os.environ.get('ONEAPI_ROOT') is not None or os.path.exists('/opt/intel/oneapi') or os.path.exists("C:/Program Files (x86)/Intel/oneAPI") or os.path.exists("C:/oneAPI") or os.path.exists("C:/Program Files/Intel/Intel Graphics Software")) + is_cuda_available = allow_cuda and (args.use_cuda or shutil.which('nvidia-smi') is not None or os.path.exists(os.path.join(os.environ.get('SystemRoot') or r'C:\Windows', 'System32', 'nvidia-smi.exe'))) + is_ipex_available = allow_ipex and (args.use_ipex or shutil.which('sycl-ls') is not None or shutil.which('sycl-ls.exe') is not None or os.environ.get('ONEAPI_ROOT') is not None or os.path.exists('/opt/intel/oneapi') or os.path.exists("C:/Program Files (x86)/Intel/oneAPI") or os.path.exists("C:/oneAPI") or os.path.exists("C:/Program Files/Intel/Intel Graphics Software")) is_rocm_available = False if not is_cuda_available and not is_ipex_available and allow_rocm: from modules import rocm - is_rocm_available = allow_rocm and (arguments.use_rocm or arguments.use_zluda or rocm.is_installed) # late eval to avoid unnecessary import + is_rocm_available = allow_rocm and (args.use_rocm or args.use_zluda or rocm.is_installed) # late eval to avoid unnecessary import - if is_cuda_available and arguments.use_cuda: # prioritize cuda + if is_cuda_available and args.use_cuda: # prioritize cuda torch_command = install_cuda() - elif is_rocm_available and (arguments.use_rocm or arguments.use_zluda): # prioritize rocm + elif is_rocm_available and (args.use_rocm or args.use_zluda): # prioritize rocm torch_command = install_rocm_zluda() - elif allow_ipex and arguments.use_ipex: # prioritize ipex + elif allow_ipex and args.use_ipex: # prioritize ipex torch_command = install_ipex() - elif allow_openvino and arguments.use_openvino: # prioritize openvino + elif allow_openvino and args.use_openvino: # prioritize openvino torch_command = install_openvino() elif is_cuda_available: torch_command = install_cuda() @@ -815,17 +815,17 @@ def check_torch(): machine = platform.machine() if sys.platform == 'darwin': torch_command = os.environ.get('TORCH_COMMAND', 'torch torchvision') - elif allow_directml and arguments.use_directml and ('arm' not in machine and 'aarch' not in machine): + elif allow_directml and args.use_directml and ('arm' not in machine and 'aarch' not in machine): log.info('DirectML: selected') torch_command = os.environ.get('TORCH_COMMAND', 'torch==2.4.1 torchvision torch-directml==0.2.4.dev240913') - if 'torch' in torch_command and not arguments.version: + if 'torch' in torch_command and not args.version: install(torch_command, 'torch torchvision') install('onnxruntime-directml', 'onnxruntime-directml', ignore=True) else: log.warning('Torch: CPU-only version installed') torch_command = os.environ.get('TORCH_COMMAND', 'torch torchvision') - if arguments.version: + if args.version: return if 'torch' in torch_command: @@ -843,13 +843,13 @@ def check_torch(): pass if 'cpu' in torch.__version__: if is_cuda_available: - if arguments.use_cuda: + if args.use_cuda: log.warning(f'Torch: version="{torch.__version__}" CPU version installed and CUDA is selected - reinstalling') install(torch_command, 'torch torchvision', quiet=True, reinstall=True, force=True) # foce reinstall else: log.warning(f'Torch: version="{torch.__version__}" CPU version installed and CUDA is available - consider reinstalling') elif is_rocm_available: - if arguments.use_rocm: + if args.use_rocm: log.warning(f'Torch: version="{torch.__version__}" CPU version installed and ROCm is selected - reinstalling') install(torch_command, 'torch torchvision', quiet=True, reinstall=True, force=True) # foce reinstall else: @@ -870,7 +870,7 @@ def check_torch(): log.info(f'Torch detected: gpu="{torch.cuda.get_device_name(device)}" vram={round(torch.cuda.get_device_properties(device).total_memory / 1024 / 1024)} arch={torch.cuda.get_device_capability(device)} cores={torch.cuda.get_device_properties(device).multi_processor_count}') else: try: - if arguments.use_directml and allow_directml: + if args.use_directml and allow_directml: import torch_directml # pylint: disable=import-error dml_ver = package_version("torch-directml") log.warning(f'Torch backend: DirectML ({dml_ver})') @@ -881,15 +881,15 @@ def check_torch(): log.warning("Torch reports CUDA not available") except Exception as e: log.error(f'Torch cannot load: {e}') - if not arguments.ignore: + if not args.ignore: sys.exit(1) if is_rocm_available: rocm.postinstall() - if not arguments.skip_all: + if not args.skip_all: install_torch_addons() check_cudnn() - if arguments.profile: + if args.profile: pr.disable() print_profile(pr, 'Torch') ts('torch', t_start) @@ -898,9 +898,9 @@ def check_torch(): # check modified files def check_modified_files(): t_start = time.time() - if arguments.quick: + if args.quick: return - if arguments.skip_git: + if args.skip_git: return try: res = git('status --porcelain') @@ -959,7 +959,7 @@ def list_extensions_folder(folder, quiet=False): # run installer for each installed and enabled extension and optionally update them def install_extensions(force=False): - if arguments.profile: + if args.profile: pr = cProfile.Profile() pr.enable() pkgs = [f"{d.metadata['Name']}=={d.version}" for d in importlib.metadata.distributions()] @@ -968,7 +968,7 @@ def install_extensions(force=False): extensions_duplicates = [] extensions_enabled = [] extensions_disabled = [e.lower() for e in opts.get('disabled_extensions', [])] - extension_folders = [extensions_builtin_dir] if arguments.safe else [extensions_builtin_dir, extensions_dir] + extension_folders = [extensions_builtin_dir] if args.safe else [extensions_builtin_dir, extensions_dir] res = [] for folder in extension_folders: if not os.path.isdir(folder): @@ -983,13 +983,13 @@ def install_extensions(force=False): extensions_duplicates.append(ext) continue extensions_enabled.append(ext) - if arguments.upgrade or force: + if args.upgrade or force: try: res.append(update(os.path.join(folder, ext))) except Exception: res.append(f'Extension update error: {os.path.join(folder, ext)}') log.error(f'Extension update error: {os.path.join(folder, ext)}') - if not arguments.skip_extensions: + if not args.skip_extensions: commit = extensions_commit.get(os.path.basename(ext), None) if commit is not None: log.debug(f'Extension force: name="{ext}" commit={commit}') @@ -1007,7 +1007,7 @@ def install_extensions(force=False): log.info(f'Extensions enabled: {extensions_enabled}') if len(extensions_duplicates) > 0: log.warning(f'Extensions duplicates: {extensions_duplicates}') - if arguments.profile: + if args.profile: pr.disable() print_profile(pr, 'Extensions') # ts('extensions', t_start) @@ -1017,7 +1017,7 @@ def install_extensions(force=False): # initialize and optionally update submodules def install_submodules(force=True): t_start = time.time() - if arguments.profile: + if args.profile: pr = cProfile.Profile() pr.enable() log.info('Verifying submodules') @@ -1034,14 +1034,14 @@ def install_submodules(force=True): for submodule in submodules: try: name = submodule.split()[1].strip() - if arguments.upgrade: + if args.upgrade: res.append(update(name)) else: branch(name) except Exception: log.error(f'Submodule update error: {submodule}') setup_logging() - if arguments.profile: + if args.profile: pr.disable() print_profile(pr, 'Submodule') ts('submodules', t_start) @@ -1090,7 +1090,7 @@ def install_pydantic(): def install_scipy(): - if arguments.new or (sys.version_info >= (3, 14)): + if args.new or (sys.version_info >= (3, 14)): install('scipy==1.17.0', ignore=True, quiet=True) else: install('scipy==1.14.1', ignore=True, quiet=True) @@ -1149,9 +1149,9 @@ def install_optional(): def install_requirements(): t_start = time.time() - if arguments.skip_requirements and not arguments.requirements: + if args.skip_requirements and not args.requirements: return - if arguments.profile: + if args.profile: pr = cProfile.Profile() pr.enable() if int(sys.version_info.minor) >= 13: @@ -1161,11 +1161,11 @@ def install_requirements(): quick_allowed = False log.info('Install requirements: this may take a while...') pip('install -r requirements.txt') - if arguments.optional: + if args.optional: quick_allowed = False install_optional() log.info('Install: verifying requirements') - if arguments.new: + if args.new: log.debug('Install: flag=new') with open('requirements.txt', encoding='utf8') as f: lines = [line.strip() for line in f.readlines() if line.strip() != '' and not line.startswith('#') and line is not None] @@ -1175,7 +1175,7 @@ def install_requirements(): install_pydantic() install_opencv() install_scipy() - if arguments.profile: + if args.profile: pr.disable() print_profile(pr, 'Requirements') ts('requirements', t_start) @@ -1233,7 +1233,7 @@ def set_environment(): def check_extensions(): newest_all = os.path.getmtime('requirements.txt') from modules.paths import extensions_builtin_dir, extensions_dir - extension_folders = [extensions_builtin_dir] if arguments.safe else [extensions_builtin_dir, extensions_dir] + extension_folders = [extensions_builtin_dir] if args.safe else [extensions_builtin_dir, extensions_dir] disabled_extensions_all = opts.get('disable_all_extensions', 'none') if disabled_extensions_all != 'none': log.info(f'Extensions: disabled={disabled_extensions_all}') @@ -1360,27 +1360,27 @@ def check_venv(): def check_version(reset=True): # pylint: disable=unused-argument if opts.get('offline_mode', False): log.warning('Offline mode enabled') - arguments.skip_git = True # pylint: disable=attribute-defined-outside-init - arguments.skip_all = True # pylint: disable=attribute-defined-outside-init + args.skip_git = True # pylint: disable=attribute-defined-outside-init + args.skip_all = True # pylint: disable=attribute-defined-outside-init return t_start = time.time() - if arguments.skip_all: + if args.skip_all: return if not os.path.exists('.git'): log.warning('Not a git repository') - arguments.skip_git = True # pylint: disable=attribute-defined-outside-init + args.skip_git = True # pylint: disable=attribute-defined-outside-init ver = get_version() log.info(f'Version: {print_dict(ver)}') branch_name = ver.get('branch', None) if ver is not None else 'master' if branch_name is None or branch_name == 'unknown': branch_name = 'master' - if arguments.version or arguments.skip_git: + if args.version or args.skip_git: return check_ui(ver) commit = git('rev-parse HEAD') global git_commit # pylint: disable=global-statement git_commit = commit[:7] - if arguments.quick: + if args.quick: return try: import requests @@ -1409,7 +1409,7 @@ def check_version(reset=True): # pylint: disable=unused-argument latest = commits['commit']['sha'] if len(latest) != 40: log.error(f'Repository error: commit={latest} invalid') - elif latest != commit and arguments.upgrade: + elif latest != commit and args.upgrade: global quick_allowed # pylint: disable=global-statement quick_allowed = False log.info('Updating main repository') @@ -1441,7 +1441,7 @@ def check_version(reset=True): # pylint: disable=unused-argument def update_wiki(): t_start = time.time() - if arguments.upgrade: + if args.upgrade: log.info('Updating Wiki') try: update(os.path.join(os.path.dirname(__file__), "wiki")) @@ -1472,7 +1472,7 @@ def get_state(): try: from concurrent.futures.thread import ThreadPoolExecutor from modules.paths import extensions_builtin_dir, extensions_dir - extension_folders = [extensions_builtin_dir] if arguments.safe else [extensions_builtin_dir, extensions_dir] + extension_folders = [extensions_builtin_dir] if args.safe else [extensions_builtin_dir, extensions_dir] ext_dirs = [] for folder in extension_folders: if not os.path.isdir(folder): @@ -1525,9 +1525,9 @@ def update_state(): def check_timestamp(): if not quick_allowed or not os.path.isfile(log_file): return False - if arguments.quick: + if args.quick: return True - if arguments.skip_git: + if args.skip_git: return True return check_state() @@ -1551,28 +1551,28 @@ def add_args(parser): def parse_args(parser): # command line args - global arguments # pylint: disable=global-statement + global args # pylint: disable=global-statement if "USED_VSCODE_COMMAND_PICKARGS" in os.environ: import shlex argv = shlex.split(" ".join(sys.argv[1:])) if "USED_VSCODE_COMMAND_PICKARGS" in os.environ else sys.argv[1:] log.debug('VSCode Launch') - arguments = parser.parse_args(argv) + args = parser.parse_args(argv) else: - arguments = parser.parse_args() - return arguments + args = parser.parse_args() + return args def extensions_preload(parser): t_start = time.time() - if arguments.profile: + if args.profile: pr = cProfile.Profile() pr.enable() - if arguments.safe: + if args.safe: log.info('Running in safe mode without user extensions') try: from modules.script_loading import preload_extensions from modules.paths import extensions_builtin_dir, extensions_dir - extension_folders = [extensions_builtin_dir] if arguments.safe else [extensions_builtin_dir, extensions_dir] + extension_folders = [extensions_builtin_dir] if args.safe else [extensions_builtin_dir, extensions_dir] preload_time = {} for ext_dir in extension_folders: t0 = time.time() @@ -1582,7 +1582,7 @@ def extensions_preload(parser): log.debug(f'Extension preload: {preload_time}') except Exception: log.error('Error running extension preloading') - if arguments.profile: + if args.profile: pr.disable() print_profile(pr, 'Preload') ts('preload', t_start) @@ -1611,8 +1611,8 @@ def git_reset(folder='.'): def read_options(): t_start = time.time() global opts # pylint: disable=global-statement - if os.path.isfile(arguments.config): - with open(arguments.config, encoding="utf8") as file: + if os.path.isfile(args.config): + with open(args.config, encoding="utf8") as file: try: opts = json.load(file) if type(opts) is str: diff --git a/launch.py b/launch.py index a3d503293..306b95f8e 100755 --- a/launch.py +++ b/launch.py @@ -233,7 +233,7 @@ def start_server(immediate=True, server=None): def main(): global args # pylint: disable=global-statement init_args() # setup argparser and default folders - installer.arguments = args + installer.args = args installer.setup_logging(debug=args.debug, trace=args.trace, filename=args.log) log.info('Starting SD.Next') installer.get_logfile() diff --git a/modules/zluda_installer.py b/modules/zluda_installer.py index 09b702550..1d9a18890 100644 --- a/modules/zluda_installer.py +++ b/modules/zluda_installer.py @@ -6,7 +6,7 @@ import ctypes import shutil import zipfile import urllib.request -from installer import arguments +from installer import args from modules.logger import log from modules import rocm @@ -82,8 +82,8 @@ def install(): commit = os.environ.get("ZLUDA_HASH", "5e717459179dc272b7d7d23391f0fad66c7459cf") if os.environ.get("ZLUDA_NIGHTLY", "0") == "1": log.warning("Environment variable 'ZLUDA_NIGHTLY' will be removed. Please use command-line argument '--use-nightly' instead.") - arguments.use_nightly = True - if arguments.use_nightly: + args.use_nightly = True + if args.use_nightly: platform = "nightly-" + platform log.debug(f'Install ZLUDA: rocm={rocm.version} platform={platform} commit={commit}') ssl._create_default_https_context = ssl._create_unverified_context # pylint: disable=protected-access From c2087de473c52a6ba25fb8241483adc8aa650de2 Mon Sep 17 00:00:00 2001 From: awsr <43862868+awsr@users.noreply.github.com> Date: Fri, 6 Mar 2026 03:07:27 -0800 Subject: [PATCH 08/14] Fix `sub_run` usage --- installer.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/installer.py b/installer.py index 84551483c..03fd38dec 100644 --- a/installer.py +++ b/installer.py @@ -1267,14 +1267,14 @@ def get_version(force=False): except Exception: pass try: - ver = sub_run('git log --pretty=format:"%h %ad" -1 --date=short', check=True)[0].stdout or ' ' + ver = sub_run('git', 'log --pretty=format:"%h %ad" -1 --date=short', check=True)[0].stdout or ' ' commit, updated = ver.split(' ') version['commit'], version['updated'] = commit, updated except Exception as e: log.warning(f'Version: where=commit {e}') try: - origin = sub_run('git remote get-url origin', check=True)[0].stdout - branch_name = sub_run('git rev-parse --abbrev-ref HEAD', check=True)[0].stdout + origin = sub_run('git', 'remote get-url origin', check=True)[0].stdout + branch_name = sub_run('git', 'rev-parse --abbrev-ref HEAD', check=True)[0].stdout version['url'] = origin.removesuffix('.git') + '/tree/' + branch_name version['branch'] = branch_name if version['branch'] == 'HEAD': @@ -1283,7 +1283,7 @@ def get_version(force=False): log.warning(f'Version: where=branch {e}') try: if os.path.exists('extensions-builtin/sdnext-modernui'): - branch_ui = sub_run('git rev-parse --abbrev-ref HEAD', check=True, cwd='extensions-builtin/sdnext-modernui')[0].stdout + branch_ui = sub_run('git', 'rev-parse --abbrev-ref HEAD', check=True, cwd='extensions-builtin/sdnext-modernui')[0].stdout version['ui'] = 'dev' if 'dev' in branch_ui else 'main' else: version['ui'] = 'unavailable' @@ -1294,7 +1294,7 @@ def get_version(force=False): if os.environ.get('SD_KANVAS_DISABLE', None) is not None: version['kanvas'] = 'disabled' elif os.path.exists('extensions-builtin/sdnext-kanvas'): - branch_kanvas = sub_run('git rev-parse --abbrev-ref HEAD', check=True, cwd='extensions-builtin/sdnext-kanvas')[0].stdout + branch_kanvas = sub_run('git', 'rev-parse --abbrev-ref HEAD', check=True, cwd='extensions-builtin/sdnext-kanvas')[0].stdout version['kanvas'] = 'dev' if 'dev' in branch_kanvas else 'main' else: version['kanvas'] = 'unavailable' @@ -1483,7 +1483,7 @@ def get_state(): def _get_commit(item): ext, ext_dir = item try: - return ext, sub_run('git rev-parse HEAD', cwd=ext_dir)[0].stdout + return ext, sub_run('git', 'rev-parse HEAD', cwd=ext_dir)[0].stdout except Exception: return ext, '' From 2f30e466e1bc7f460ad98bd7d0bd1580a30f47cf Mon Sep 17 00:00:00 2001 From: CalamitousFelicitousness Date: Sat, 7 Mar 2026 02:20:10 +0000 Subject: [PATCH 09/14] fix(caption): tagger batch only processes first uploaded file Align tagger batch file collection with the working VQA/OpenCLIP pattern. The previous implementation used Path wrapping and resolve() deduplication which broke multi-file uploads from the Gradio File component. Now all four batch modes (VQA, OpenCLIP, WaifuDiffusion, DeepBooru) use the same f.name file collection approach. --- modules/caption/deepbooru.py | 72 ++++++++----------------------- modules/caption/waifudiffusion.py | 72 ++++++++----------------------- 2 files changed, 34 insertions(+), 110 deletions(-) diff --git a/modules/caption/deepbooru.py b/modules/caption/deepbooru.py index 19ff58afc..6ccb848b1 100644 --- a/modules/caption/deepbooru.py +++ b/modules/caption/deepbooru.py @@ -205,6 +205,7 @@ def batch( Returns: Combined tag results """ + import os import time from pathlib import Path import rich.progress as rp @@ -214,55 +215,15 @@ def batch( # Collect image files image_files = [] - image_extensions = {'.jpg', '.jpeg', '.png', '.webp', '.bmp', '.gif'} - - # From file picker - if batch_files: - for f in batch_files: - if isinstance(f, dict): - image_files.append(Path(f['name'])) - elif hasattr(f, 'name'): - image_files.append(Path(f.name)) - else: - image_files.append(Path(f)) - - # From folder picker - if batch_folder: - folder_path = None - if isinstance(batch_folder, list) and len(batch_folder) > 0: - f = batch_folder[0] - if isinstance(f, dict): - folder_path = Path(f['name']).parent - elif hasattr(f, 'name'): - folder_path = Path(f.name).parent - if folder_path and folder_path.is_dir(): - if recursive: - for ext in image_extensions: - image_files.extend(folder_path.rglob(f'*{ext}')) - else: - for ext in image_extensions: - image_files.extend(folder_path.glob(f'*{ext}')) - - # From string path - if batch_str and batch_str.strip(): + if batch_files is not None: + image_files += [f.name for f in batch_files] + if batch_folder is not None: + image_files += [f.name for f in batch_folder] + if batch_str is not None and len(batch_str) > 0 and os.path.exists(batch_str) and os.path.isdir(batch_str): + image_extensions = {'.jpg', '.jpeg', '.png', '.webp', '.bmp', '.gif'} folder_path = Path(batch_str.strip()) - if folder_path.is_dir(): - if recursive: - for ext in image_extensions: - image_files.extend(folder_path.rglob(f'*{ext}')) - else: - for ext in image_extensions: - image_files.extend(folder_path.glob(f'*{ext}')) - - # Remove duplicates while preserving order - seen = set() - unique_files = [] - for f in image_files: - f_resolved = f.resolve() - if f_resolved not in seen: - seen.add(f_resolved) - unique_files.append(f) - image_files = unique_files + for ext in image_extensions: + image_files.extend(str(p) for p in (folder_path.rglob(f'*{ext}') if recursive else folder_path.glob(f'*{ext}'))) if not image_files: log.warning('DeepBooru batch: no images found') @@ -280,25 +241,26 @@ def batch( with pbar: task = pbar.add_task(total=len(image_files), description='starting...') - for img_path in image_files: - pbar.update(task, advance=1, description=str(img_path.name)) + for file in image_files: + file_name = os.path.basename(file) + pbar.update(task, advance=1, description=file_name) try: if shared.state.interrupted: log.info('DeepBooru batch: interrupted') break - image = Image.open(img_path) + image = Image.open(file) tags_str = model.tag_multi(image, **kwargs) if save_output: from modules.caption import tagger - tagger.save_tags_to_file(img_path, tags_str, save_append) + tagger.save_tags_to_file(Path(file), tags_str, save_append) - results.append(f'{img_path.name}: {tags_str[:100]}...' if len(tags_str) > 100 else f'{img_path.name}: {tags_str}') + results.append(f'{file_name}: {tags_str[:100]}...' if len(tags_str) > 100 else f'{file_name}: {tags_str}') except Exception as e: - log.error(f'DeepBooru batch: file="{img_path}" error={e}') - results.append(f'{img_path.name}: ERROR - {e}') + log.error(f'DeepBooru batch: file="{file}" error={e}') + results.append(f'{file_name}: ERROR - {e}') model.stop() elapsed = time.time() - t0 diff --git a/modules/caption/waifudiffusion.py b/modules/caption/waifudiffusion.py index ba7077dac..3e284db75 100644 --- a/modules/caption/waifudiffusion.py +++ b/modules/caption/waifudiffusion.py @@ -419,6 +419,7 @@ def batch( Returns: Combined tag results """ + import os from pathlib import Path # Load model @@ -429,55 +430,15 @@ def batch( # Collect image files image_files = [] - image_extensions = {'.jpg', '.jpeg', '.png', '.webp', '.bmp', '.gif'} - - # From file picker - if batch_files: - for f in batch_files: - if isinstance(f, dict): - image_files.append(Path(f['name'])) - elif hasattr(f, 'name'): - image_files.append(Path(f.name)) - else: - image_files.append(Path(f)) - - # From folder picker - if batch_folder: - folder_path = None - if isinstance(batch_folder, list) and len(batch_folder) > 0: - f = batch_folder[0] - if isinstance(f, dict): - folder_path = Path(f['name']).parent - elif hasattr(f, 'name'): - folder_path = Path(f.name).parent - if folder_path and folder_path.is_dir(): - if recursive: - for ext in image_extensions: - image_files.extend(folder_path.rglob(f'*{ext}')) - else: - for ext in image_extensions: - image_files.extend(folder_path.glob(f'*{ext}')) - - # From string path - if batch_str and batch_str.strip(): + if batch_files is not None: + image_files += [f.name for f in batch_files] + if batch_folder is not None: + image_files += [f.name for f in batch_folder] + if batch_str is not None and len(batch_str) > 0 and os.path.exists(batch_str) and os.path.isdir(batch_str): + image_extensions = {'.jpg', '.jpeg', '.png', '.webp', '.bmp', '.gif'} folder_path = Path(batch_str.strip()) - if folder_path.is_dir(): - if recursive: - for ext in image_extensions: - image_files.extend(folder_path.rglob(f'*{ext}')) - else: - for ext in image_extensions: - image_files.extend(folder_path.glob(f'*{ext}')) - - # Remove duplicates while preserving order - seen = set() - unique_files = [] - for f in image_files: - f_resolved = f.resolve() - if f_resolved not in seen: - seen.add(f_resolved) - unique_files.append(f) - image_files = unique_files + for ext in image_extensions: + image_files.extend(str(p) for p in (folder_path.rglob(f'*{ext}') if recursive else folder_path.glob(f'*{ext}'))) if not image_files: log.warning('WaifuDiffusion batch: no images found') @@ -496,25 +457,26 @@ def batch( with pbar: task = pbar.add_task(total=len(image_files), description='starting...') - for img_path in image_files: - pbar.update(task, advance=1, description=str(img_path.name)) + for file in image_files: + file_name = os.path.basename(file) + pbar.update(task, advance=1, description=file_name) try: if shared.state.interrupted: log.info('WaifuDiffusion batch: interrupted') break - image = Image.open(img_path) + image = Image.open(file) tags_str = tagger.predict(image, **kwargs) if save_output: from modules.caption import tagger as tagger_module - tagger_module.save_tags_to_file(img_path, tags_str, save_append) + tagger_module.save_tags_to_file(Path(file), tags_str, save_append) - results.append(f'{img_path.name}: {tags_str[:100]}...' if len(tags_str) > 100 else f'{img_path.name}: {tags_str}') + results.append(f'{file_name}: {tags_str[:100]}...' if len(tags_str) > 100 else f'{file_name}: {tags_str}') except Exception as e: - log.error(f'WaifuDiffusion batch: file="{img_path}" error={e}') - results.append(f'{img_path.name}: ERROR - {e}') + log.error(f'WaifuDiffusion batch: file="{file}" error={e}') + results.append(f'{file_name}: ERROR - {e}') elapsed = time.time() - t0 log.info(f'WaifuDiffusion batch: complete images={len(results)} time={elapsed:.1f}s') From c68ab0f75eab7aee5c77c888e5a423060997ad20 Mon Sep 17 00:00:00 2001 From: CalamitousFelicitousness Date: Sat, 7 Mar 2026 02:50:23 +0000 Subject: [PATCH 10/14] fix(ui): constrain batch file list height in caption tab Add max-height and overflow-y scroll to batch file upload components so uploading many files doesn't push the entire UI down. --- modules/ui_caption.py | 1 + 1 file changed, 1 insertion(+) diff --git a/modules/ui_caption.py b/modules/ui_caption.py index 33ee8132f..ceb2c2d31 100644 --- a/modules/ui_caption.py +++ b/modules/ui_caption.py @@ -270,6 +270,7 @@ def create_ui(): with gr.Row(): wd_show_scores = gr.Checkbox(label='Show confidence scores', value=shared.opts.tagger_show_scores, elem_id='wd_show_scores') gr.HTML('') + gr.HTML('') with gr.Accordion(label='Caption: Batch', open=False, visible=True): with gr.Row(): wd_batch_files = gr.File(label="Files", show_label=True, file_count='multiple', file_types=['image'], interactive=True, height=100, elem_id='wd_batch_files') From 7657c5af293e6be1b7a23d2e0af997fb80b54e6b Mon Sep 17 00:00:00 2001 From: awsr <43862868+awsr@users.noreply.github.com> Date: Fri, 6 Mar 2026 20:24:51 -0800 Subject: [PATCH 11/14] Ensure `shell=True` to work with single-string cmd --- installer.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/installer.py b/installer.py index 03fd38dec..9e4e38f4c 100644 --- a/installer.py +++ b/installer.py @@ -214,12 +214,11 @@ def sub_run(cmd: str, *args: str, **kwargs): tuple[CompletedProcess[str], str]: Tuple with the results and the combined `stdout` and `stderr` values. """ options = { - "shell": True, "check": False, "env": os.environ, } options |= kwargs # Override defaults with passed kwargs - result = subprocess.run(f'"{cmd}" {" ".join(args)}', **options, capture_output=True, text=True) + result = subprocess.run(f'"{cmd}" {" ".join(args)}', **options, shell=True, capture_output=True, text=True) result.stdout = result.stdout.strip() result.stderr = result.stderr.strip() txt = result.stdout From 952fdedd73dfe7c750948d7061cdaeb7604c5525 Mon Sep 17 00:00:00 2001 From: awsr <43862868+awsr@users.noreply.github.com> Date: Fri, 6 Mar 2026 19:46:56 -0800 Subject: [PATCH 12/14] Use `wslview` on WSL + minor rewrite `wslview` has been included by default in WSL for a while now. --- modules/ui_common.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/modules/ui_common.py b/modules/ui_common.py index 48c399a9e..59833f8aa 100644 --- a/modules/ui_common.py +++ b/modules/ui_common.py @@ -249,12 +249,14 @@ def open_folder(result_gallery, gallery_index = 0): path = os.path.normpath(folder) if platform.system() == "Windows": os.startfile(path) # pylint: disable=no-member - elif platform.system() == "Darwin": - subprocess.Popen(["open", path]) # pylint: disable=consider-using-with + return + if platform.system() == "Darwin": + opener = "open" elif "microsoft-standard-WSL2" in platform.uname().release: - subprocess.Popen(["wsl-open", path]) # pylint: disable=consider-using-with + opener = "wslview" else: - subprocess.Popen(["xdg-open", path]) # pylint: disable=consider-using-with + opener = "xdg-open" + subprocess.Popen([opener, path]) # pylint: disable=consider-using-with def create_output_panel(tabname, preview=True, prompt=None, height=None, transfer=True, scale=1, result_info=None): From 609c2bed44bbee96556a90fe9b9d9f441d89171e Mon Sep 17 00:00:00 2001 From: awsr <43862868+awsr@users.noreply.github.com> Date: Sat, 7 Mar 2026 13:47:53 -0800 Subject: [PATCH 13/14] Revert name `sub_run` back to `run` --- installer.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/installer.py b/installer.py index 9e4e38f4c..a269548bb 100644 --- a/installer.py +++ b/installer.py @@ -200,7 +200,7 @@ def uninstall(package, quiet = False): return res -def sub_run(cmd: str, *args: str, **kwargs): +def run(cmd: str, *args: str, **kwargs): """Run command and arguments with `subprocess.run`. Default run options are `shell=True, check=False, env=os.environ`. @@ -264,7 +264,7 @@ def pip(arg: str, ignore: bool = False, quiet: bool = True, uv = True): all_args = f'{pip_log}{arg} {env_args}'.strip() if not quiet: log.debug(f'Running: {pipCmd}="{all_args}"') - result, txt = sub_run(sys.executable, "-m", pipCmd, all_args) + result, txt = run(sys.executable, "-m", pipCmd, all_args) if len(result.stderr) > 0: if uv and result.returncode != 0: log.warning(f'Install: cmd="{pipCmd}" args="{all_args}" cannot use uv, fallback to pip') @@ -306,7 +306,7 @@ def git(arg: str, folder: str = None, ignore: bool = False, optional: bool = Fal git_cmd = os.environ.get('GIT', "git") if git_cmd != "git": git_cmd = os.path.abspath(git_cmd) - result, txt = sub_run(git_cmd, arg, cwd=folder or ".") + result, txt = run(git_cmd, arg, cwd=folder or ".") if result.returncode != 0 and not ignore: if folder is None: folder = 'root' @@ -934,7 +934,7 @@ def run_extension_installer(folder): if os.environ.get('PYTHONPATH', None) is not None: seperator = ';' if sys.platform == 'win32' else ':' env['PYTHONPATH'] += seperator + os.environ.get('PYTHONPATH', None) - result, txt = sub_run(sys.executable, path_installer, env=env, cwd=folder) + result, txt = run(sys.executable, path_installer, env=env, cwd=folder) debug(f'Extension installer: file="{path_installer}" {result.stdout}') if result.returncode != 0: errors.append(f'ext: {os.path.basename(folder)}') @@ -1266,14 +1266,14 @@ def get_version(force=False): except Exception: pass try: - ver = sub_run('git', 'log --pretty=format:"%h %ad" -1 --date=short', check=True)[0].stdout or ' ' + ver = run('git', 'log --pretty=format:"%h %ad" -1 --date=short', check=True)[0].stdout or ' ' commit, updated = ver.split(' ') version['commit'], version['updated'] = commit, updated except Exception as e: log.warning(f'Version: where=commit {e}') try: - origin = sub_run('git', 'remote get-url origin', check=True)[0].stdout - branch_name = sub_run('git', 'rev-parse --abbrev-ref HEAD', check=True)[0].stdout + origin = run('git', 'remote get-url origin', check=True)[0].stdout + branch_name = run('git', 'rev-parse --abbrev-ref HEAD', check=True)[0].stdout version['url'] = origin.removesuffix('.git') + '/tree/' + branch_name version['branch'] = branch_name if version['branch'] == 'HEAD': @@ -1282,7 +1282,7 @@ def get_version(force=False): log.warning(f'Version: where=branch {e}') try: if os.path.exists('extensions-builtin/sdnext-modernui'): - branch_ui = sub_run('git', 'rev-parse --abbrev-ref HEAD', check=True, cwd='extensions-builtin/sdnext-modernui')[0].stdout + branch_ui = run('git', 'rev-parse --abbrev-ref HEAD', check=True, cwd='extensions-builtin/sdnext-modernui')[0].stdout version['ui'] = 'dev' if 'dev' in branch_ui else 'main' else: version['ui'] = 'unavailable' @@ -1293,7 +1293,7 @@ def get_version(force=False): if os.environ.get('SD_KANVAS_DISABLE', None) is not None: version['kanvas'] = 'disabled' elif os.path.exists('extensions-builtin/sdnext-kanvas'): - branch_kanvas = sub_run('git', 'rev-parse --abbrev-ref HEAD', check=True, cwd='extensions-builtin/sdnext-kanvas')[0].stdout + branch_kanvas = run('git', 'rev-parse --abbrev-ref HEAD', check=True, cwd='extensions-builtin/sdnext-kanvas')[0].stdout version['kanvas'] = 'dev' if 'dev' in branch_kanvas else 'main' else: version['kanvas'] = 'unavailable' @@ -1482,7 +1482,7 @@ def get_state(): def _get_commit(item): ext, ext_dir = item try: - return ext, sub_run('git', 'rev-parse HEAD', cwd=ext_dir)[0].stdout + return ext, run('git', 'rev-parse HEAD', cwd=ext_dir)[0].stdout except Exception: return ext, '' From 61b98de9e2a2eb4f9af1b80bfda70948f2bd88ac Mon Sep 17 00:00:00 2001 From: awsr <43862868+awsr@users.noreply.github.com> Date: Sat, 7 Mar 2026 14:52:10 -0800 Subject: [PATCH 14/14] Keep `wsl-open` as a fallback Very unlikely to be needed, but just in case... --- modules/ui_common.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/ui_common.py b/modules/ui_common.py index 59833f8aa..3b7530e08 100644 --- a/modules/ui_common.py +++ b/modules/ui_common.py @@ -253,7 +253,7 @@ def open_folder(result_gallery, gallery_index = 0): if platform.system() == "Darwin": opener = "open" elif "microsoft-standard-WSL2" in platform.uname().release: - opener = "wslview" + opener = "wslview" if shutil.which("wslview") is not None else "wsl-open" else: opener = "xdg-open" subprocess.Popen([opener, path]) # pylint: disable=consider-using-with