Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
ninjaneural
GitHub Repository: ninjaneural/webui
Path: blob/master/misc/direct/forge/launch_utils.py
3275 views
1
# this scripts installs necessary requirements and launches main program in webui.py
2
import logging
3
import re
4
import subprocess
5
import os
6
import shutil
7
import sys
8
import importlib.util
9
import importlib.metadata
10
import platform
11
import json
12
from functools import lru_cache
13
14
from modules import cmd_args, errors
15
from modules.paths_internal import script_path, extensions_dir, extensions_builtin_dir
16
from modules.timer import startup_timer
17
from modules import logging_config
18
from modules_forge import forge_version
19
from modules_forge.config import always_disabled_extensions
20
21
22
args, _ = cmd_args.parser.parse_known_args()
23
logging_config.setup_logging(args.loglevel)
24
25
python = sys.executable
26
git = os.environ.get('GIT', "git")
27
index_url = os.environ.get('INDEX_URL', "")
28
dir_repos = "repositories"
29
30
# Whether to default to printing command output
31
default_command_live = (os.environ.get('WEBUI_LAUNCH_LIVE_OUTPUT') == "1")
32
33
os.environ.setdefault('GRADIO_ANALYTICS_ENABLED', 'False')
34
35
36
def check_python_version():
37
is_windows = platform.system() == "Windows"
38
major = sys.version_info.major
39
minor = sys.version_info.minor
40
micro = sys.version_info.micro
41
42
if is_windows:
43
supported_minors = [10]
44
else:
45
supported_minors = [7, 8, 9, 10, 11]
46
47
if not (major == 3 and minor in supported_minors):
48
import modules.errors
49
50
modules.errors.print_error_explanation(f"""
51
INCOMPATIBLE PYTHON VERSION
52
53
This program is tested with 3.10.6 Python, but you have {major}.{minor}.{micro}.
54
If you encounter an error with "RuntimeError: Couldn't install torch." message,
55
or any other error regarding unsuccessful package (library) installation,
56
please downgrade (or upgrade) to the latest version of 3.10 Python
57
and delete current Python and "venv" folder in WebUI's directory.
58
59
You can download 3.10 Python from here: https://www.python.org/downloads/release/python-3106/
60
61
{"Alternatively, use a binary release of WebUI: https://github.com/AUTOMATIC1111/stable-diffusion-webui/releases" if is_windows else ""}
62
63
Use --skip-python-version-check to suppress this warning.
64
""")
65
66
67
@lru_cache()
68
def commit_hash():
69
try:
70
return subprocess.check_output([git, "-C", script_path, "rev-parse", "HEAD"], shell=False, encoding='utf8').strip()
71
except Exception:
72
return "<none>"
73
74
75
@lru_cache()
76
def git_tag_a1111():
77
try:
78
return subprocess.check_output([git, "-C", script_path, "describe", "--tags"], shell=False, encoding='utf8').strip()
79
except Exception:
80
try:
81
82
changelog_md = os.path.join(os.path.dirname(os.path.dirname(__file__)), "CHANGELOG.md")
83
with open(changelog_md, "r", encoding="utf-8") as file:
84
line = next((line.strip() for line in file if line.strip()), "<none>")
85
line = line.replace("## ", "")
86
return line
87
except Exception:
88
return "<none>"
89
90
91
def git_tag():
92
return 'f' + forge_version.version + '-' + git_tag_a1111()
93
94
95
def run(command, desc=None, errdesc=None, custom_env=None, live: bool = default_command_live) -> str:
96
if desc is not None:
97
print(desc)
98
99
run_kwargs = {
100
"args": command,
101
"shell": True,
102
"env": os.environ if custom_env is None else custom_env,
103
"encoding": 'utf8',
104
"errors": 'ignore',
105
}
106
107
if not live:
108
run_kwargs["stdout"] = run_kwargs["stderr"] = subprocess.PIPE
109
110
result = subprocess.run(**run_kwargs)
111
112
if result.returncode != 0:
113
error_bits = [
114
f"{errdesc or 'Error running command'}.",
115
f"Command: {command}",
116
f"Error code: {result.returncode}",
117
]
118
if result.stdout:
119
error_bits.append(f"stdout: {result.stdout}")
120
if result.stderr:
121
error_bits.append(f"stderr: {result.stderr}")
122
raise RuntimeError("\n".join(error_bits))
123
124
return (result.stdout or "")
125
126
127
def is_installed(package):
128
try:
129
dist = importlib.metadata.distribution(package)
130
except importlib.metadata.PackageNotFoundError:
131
try:
132
spec = importlib.util.find_spec(package)
133
except ModuleNotFoundError:
134
return False
135
136
return spec is not None
137
138
return dist is not None
139
140
141
def repo_dir(name):
142
return os.path.join(script_path, dir_repos, name)
143
144
145
def run_pip(command, desc=None, live=default_command_live):
146
if args.skip_install:
147
return
148
149
index_url_line = f' --index-url {index_url}' if index_url != '' else ''
150
return run(f'"{python}" -m pip {command} --prefer-binary{index_url_line}', desc=f"Installing {desc}", errdesc=f"Couldn't install {desc}", live=live)
151
152
153
def check_run_python(code: str) -> bool:
154
result = subprocess.run([python, "-c", code], capture_output=True, shell=False)
155
return result.returncode == 0
156
157
158
def git_fix_workspace(dir, name):
159
run(f'"{git}" -C "{dir}" fetch --refetch --no-auto-gc', f"Fetching all contents for {name}", f"Couldn't fetch {name}", live=True)
160
run(f'"{git}" -C "{dir}" gc --aggressive --prune=now', f"Pruning {name}", f"Couldn't prune {name}", live=True)
161
return
162
163
164
def run_git(dir, name, command, desc=None, errdesc=None, custom_env=None, live: bool = default_command_live, autofix=True):
165
try:
166
return run(f'"{git}" -C "{dir}" {command}', desc=desc, errdesc=errdesc, custom_env=custom_env, live=live)
167
except RuntimeError:
168
if not autofix:
169
raise
170
171
print(f"{errdesc}, attempting autofix...")
172
git_fix_workspace(dir, name)
173
174
return run(f'"{git}" -C "{dir}" {command}', desc=desc, errdesc=errdesc, custom_env=custom_env, live=live)
175
176
177
def git_clone(url, dir, name, commithash=None):
178
# TODO clone into temporary dir and move if successful
179
180
if os.path.exists(dir):
181
if commithash is None:
182
return
183
184
current_hash = run_git(dir, name, 'rev-parse HEAD', None, f"Couldn't determine {name}'s hash: {commithash}", live=False).strip()
185
if current_hash == commithash:
186
return
187
188
if run_git(dir, name, 'config --get remote.origin.url', None, f"Couldn't determine {name}'s origin URL", live=False).strip() != url:
189
run_git(dir, name, f'remote set-url origin "{url}"', None, f"Failed to set {name}'s origin URL", live=False)
190
191
run_git(dir, name, 'fetch', f"Fetching updates for {name}...", f"Couldn't fetch {name}", autofix=False)
192
193
run_git(dir, name, f'checkout {commithash}', f"Checking out commit for {name} with hash: {commithash}...", f"Couldn't checkout commit {commithash} for {name}", live=True)
194
195
return
196
197
try:
198
run(f'"{git}" clone "{url}" "{dir}"', f"Cloning {name} into {dir}...", f"Couldn't clone {name}", live=True)
199
except RuntimeError:
200
shutil.rmtree(dir, ignore_errors=True)
201
raise
202
203
if commithash is not None:
204
run(f'"{git}" -C "{dir}" checkout {commithash}', None, "Couldn't checkout {name}'s hash: {commithash}")
205
206
207
def git_pull_recursive(dir):
208
for subdir, _, _ in os.walk(dir):
209
if os.path.exists(os.path.join(subdir, '.git')):
210
try:
211
output = subprocess.check_output([git, '-C', subdir, 'pull', '--autostash'])
212
print(f"Pulled changes for repository in '{subdir}':\n{output.decode('utf-8').strip()}\n")
213
except subprocess.CalledProcessError as e:
214
print(f"Couldn't perform 'git pull' on repository in '{subdir}':\n{e.output.decode('utf-8').strip()}\n")
215
216
217
def version_check(commit):
218
try:
219
import requests
220
commits = requests.get('https://api.github.com/repos/AUTOMATIC1111/stable-diffusion-webui/branches/master').json()
221
if commit != "<none>" and commits['commit']['sha'] != commit:
222
print("--------------------------------------------------------")
223
print("| You are not up to date with the most recent release. |")
224
print("| Consider running `git pull` to update. |")
225
print("--------------------------------------------------------")
226
elif commits['commit']['sha'] == commit:
227
print("You are up to date with the most recent release.")
228
else:
229
print("Not a git clone, can't perform version check.")
230
except Exception as e:
231
print("version check failed", e)
232
233
234
def run_extension_installer(extension_dir):
235
path_installer = os.path.join(extension_dir, "install.py")
236
if not os.path.isfile(path_installer):
237
return
238
239
try:
240
env = os.environ.copy()
241
env['PYTHONPATH'] = f"{os.path.abspath('.')}{os.pathsep}{env.get('PYTHONPATH', '')}"
242
243
stdout = run(f'"{python}" "{path_installer}"', errdesc=f"Error running install.py for extension {extension_dir}", custom_env=env).strip()
244
if stdout:
245
print(stdout)
246
except Exception as e:
247
errors.report(str(e))
248
249
250
def list_extensions(settings_file):
251
settings = {}
252
253
try:
254
with open(settings_file, "r", encoding="utf8") as file:
255
settings = json.load(file)
256
except FileNotFoundError:
257
pass
258
except Exception:
259
errors.report(f'\nCould not load settings\nThe config file "{settings_file}" is likely corrupted\nIt has been moved to the "tmp/config.json"\nReverting config to default\n\n''', exc_info=True)
260
os.replace(settings_file, os.path.join(script_path, "tmp", "config.json"))
261
262
disabled_extensions = set(settings.get('disabled_extensions', []) + always_disabled_extensions)
263
disable_all_extensions = settings.get('disable_all_extensions', 'none')
264
265
if disable_all_extensions != 'none' or args.disable_extra_extensions or args.disable_all_extensions or not os.path.isdir(extensions_dir):
266
return []
267
268
return [x for x in os.listdir(extensions_dir) if x not in disabled_extensions]
269
270
271
def list_extensions_builtin(settings_file):
272
settings = {}
273
274
try:
275
with open(settings_file, "r", encoding="utf8") as file:
276
settings = json.load(file)
277
except FileNotFoundError:
278
pass
279
except Exception:
280
errors.report(f'\nCould not load settings\nThe config file "{settings_file}" is likely corrupted\nIt has been moved to the "tmp/config.json"\nReverting config to default\n\n''', exc_info=True)
281
os.replace(settings_file, os.path.join(script_path, "tmp", "config.json"))
282
283
disabled_extensions = set(settings.get('disabled_extensions', []))
284
disable_all_extensions = settings.get('disable_all_extensions', 'none')
285
286
if disable_all_extensions != 'none' or args.disable_extra_extensions or args.disable_all_extensions or not os.path.isdir(extensions_builtin_dir):
287
return []
288
289
return [x for x in os.listdir(extensions_builtin_dir) if x not in disabled_extensions]
290
291
292
def run_extensions_installers(settings_file):
293
if not os.path.isdir(extensions_dir):
294
return
295
296
with startup_timer.subcategory("run extensions installers"):
297
for dirname_extension in list_extensions(settings_file):
298
logging.debug(f"Installing {dirname_extension}")
299
300
path = os.path.join(extensions_dir, dirname_extension)
301
302
if os.path.isdir(path):
303
run_extension_installer(path)
304
startup_timer.record(dirname_extension)
305
306
if not os.path.isdir(extensions_builtin_dir):
307
return
308
309
with startup_timer.subcategory("run extensions_builtin installers"):
310
for dirname_extension in list_extensions_builtin(settings_file):
311
logging.debug(f"Installing {dirname_extension}")
312
313
path = os.path.join(extensions_builtin_dir, dirname_extension)
314
315
if os.path.isdir(path):
316
run_extension_installer(path)
317
startup_timer.record(dirname_extension)
318
319
return
320
321
322
re_requirement = re.compile(r"\s*([-_a-zA-Z0-9]+)\s*(?:==\s*([-+_.a-zA-Z0-9]+))?\s*")
323
324
325
def requirements_met(requirements_file):
326
"""
327
Does a simple parse of a requirements.txt file to determine if all rerqirements in it
328
are already installed. Returns True if so, False if not installed or parsing fails.
329
"""
330
331
import importlib.metadata
332
import packaging.version
333
334
with open(requirements_file, "r", encoding="utf8") as file:
335
for line in file:
336
if line.strip() == "":
337
continue
338
339
m = re.match(re_requirement, line)
340
if m is None:
341
return False
342
343
package = m.group(1).strip()
344
version_required = (m.group(2) or "").strip()
345
346
if version_required == "":
347
continue
348
349
try:
350
version_installed = importlib.metadata.version(package)
351
except Exception:
352
return False
353
354
if packaging.version.parse(version_required) != packaging.version.parse(version_installed):
355
return False
356
357
return True
358
359
360
def prepare_environment():
361
torch_index_url = os.environ.get('TORCH_INDEX_URL', "https://download.pytorch.org/whl/cu121")
362
torch_command = os.environ.get('TORCH_COMMAND', f"pip install torch==2.1.2 torchvision==0.16.2 --extra-index-url {torch_index_url}")
363
if args.use_ipex:
364
if platform.system() == "Windows":
365
# The "Nuullll/intel-extension-for-pytorch" wheels were built from IPEX source for Intel Arc GPU: https://github.com/intel/intel-extension-for-pytorch/tree/xpu-main
366
# This is NOT an Intel official release so please use it at your own risk!!
367
# See https://github.com/Nuullll/intel-extension-for-pytorch/releases/tag/v2.0.110%2Bxpu-master%2Bdll-bundle for details.
368
#
369
# Strengths (over official IPEX 2.0.110 windows release):
370
# - AOT build (for Arc GPU only) to eliminate JIT compilation overhead: https://github.com/intel/intel-extension-for-pytorch/issues/399
371
# - Bundles minimal oneAPI 2023.2 dependencies into the python wheels, so users don't need to install oneAPI for the whole system.
372
# - Provides a compatible torchvision wheel: https://github.com/intel/intel-extension-for-pytorch/issues/465
373
# Limitation:
374
# - Only works for python 3.10
375
url_prefix = "https://github.com/Nuullll/intel-extension-for-pytorch/releases/download/v2.0.110%2Bxpu-master%2Bdll-bundle"
376
torch_command = os.environ.get('TORCH_COMMAND', f"pip install {url_prefix}/torch-2.0.0a0+gite9ebda2-cp310-cp310-win_amd64.whl {url_prefix}/torchvision-0.15.2a0+fa99a53-cp310-cp310-win_amd64.whl {url_prefix}/intel_extension_for_pytorch-2.0.110+gitc6ea20b-cp310-cp310-win_amd64.whl")
377
else:
378
# Using official IPEX release for linux since it's already an AOT build.
379
# However, users still have to install oneAPI toolkit and activate oneAPI environment manually.
380
# See https://intel.github.io/intel-extension-for-pytorch/index.html#installation for details.
381
torch_index_url = os.environ.get('TORCH_INDEX_URL', "https://pytorch-extension.intel.com/release-whl/stable/xpu/us/")
382
torch_command = os.environ.get('TORCH_COMMAND', f"pip install torch==2.0.0a0 intel-extension-for-pytorch==2.0.110+gitba7f6c1 --extra-index-url {torch_index_url}")
383
requirements_file = os.environ.get('REQS_FILE', "requirements_versions.txt")
384
requirements_file_for_npu = os.environ.get('REQS_FILE_FOR_NPU', "requirements_npu.txt")
385
386
xformers_package = os.environ.get('XFORMERS_PACKAGE', 'xformers==0.0.23.post1')
387
clip_package = os.environ.get('CLIP_PACKAGE', "https://github.com/openai/CLIP/archive/d50d76daa670286dd6cacf3bcd80b5e4823fc8e1.zip")
388
openclip_package = os.environ.get('OPENCLIP_PACKAGE', "https://github.com/mlfoundations/open_clip/archive/bb6e834e9c70d9c27d0dc3ecedeebeaeb1ffad6b.zip")
389
390
assets_repo = os.environ.get('ASSETS_REPO', "https://github.com/AUTOMATIC1111/stable-diffusion-webui-assets.git")
391
stable_diffusion_repo = os.environ.get('STABLE_DIFFUSION_REPO', "https://github.com/Stability-AI/stablediffusion.git")
392
stable_diffusion_xl_repo = os.environ.get('STABLE_DIFFUSION_XL_REPO', "https://github.com/Stability-AI/generative-models.git")
393
k_diffusion_repo = os.environ.get('K_DIFFUSION_REPO', 'https://github.com/crowsonkb/k-diffusion.git')
394
blip_repo = os.environ.get('BLIP_REPO', 'https://github.com/salesforce/BLIP.git')
395
396
assets_commit_hash = os.environ.get('ASSETS_COMMIT_HASH', "6f7db241d2f8ba7457bac5ca9753331f0c266917")
397
stable_diffusion_commit_hash = os.environ.get('STABLE_DIFFUSION_COMMIT_HASH', "cf1d67a6fd5ea1aa600c4df58e5b47da45f6bdbf")
398
stable_diffusion_xl_commit_hash = os.environ.get('STABLE_DIFFUSION_XL_COMMIT_HASH', "45c443b316737a4ab6e40413d7794a7f5657c19f")
399
k_diffusion_commit_hash = os.environ.get('K_DIFFUSION_COMMIT_HASH', "ab527a9a6d347f364e3d185ba6d714e22d80cb3c")
400
blip_commit_hash = os.environ.get('BLIP_COMMIT_HASH', "48211a1594f1321b00f14c9f7a5b4813144b2fb9")
401
402
try:
403
# the existence of this file is a signal to webui.sh/bat that webui needs to be restarted when it stops execution
404
os.remove(os.path.join(script_path, "tmp", "restart"))
405
os.environ.setdefault('SD_WEBUI_RESTARTING', '1')
406
except OSError:
407
pass
408
409
if not args.skip_python_version_check:
410
check_python_version()
411
412
startup_timer.record("checks")
413
414
commit = commit_hash()
415
tag = git_tag()
416
startup_timer.record("git version info")
417
418
print(f"Python {sys.version}")
419
print(f"Version: {tag}")
420
print(f"Commit hash: {commit}")
421
422
if args.reinstall_torch or not is_installed("torch") or not is_installed("torchvision"):
423
run(f'"{python}" -m {torch_command}', "Installing torch and torchvision", "Couldn't install torch", live=True)
424
startup_timer.record("install torch")
425
426
if args.use_ipex:
427
args.skip_torch_cuda_test = True
428
if not args.skip_torch_cuda_test and not check_run_python("import torch; assert torch.cuda.is_available()"):
429
raise RuntimeError(
430
'Torch is not able to use GPU; '
431
'add --skip-torch-cuda-test to COMMANDLINE_ARGS variable to disable this check'
432
)
433
startup_timer.record("torch GPU test")
434
435
if not is_installed("clip"):
436
run_pip(f"install {clip_package}", "clip")
437
startup_timer.record("install clip")
438
439
if not is_installed("open_clip"):
440
run_pip(f"install {openclip_package}", "open_clip")
441
startup_timer.record("install open_clip")
442
443
if (not is_installed("xformers") or args.reinstall_xformers) and args.xformers:
444
run_pip(f"install -U -I --no-deps {xformers_package}", "xformers")
445
startup_timer.record("install xformers")
446
447
if not is_installed("ngrok") and args.ngrok:
448
run_pip("install ngrok", "ngrok")
449
startup_timer.record("install ngrok")
450
451
os.makedirs(os.path.join(script_path, dir_repos), exist_ok=True)
452
453
git_clone(assets_repo, repo_dir('stable-diffusion-webui-assets'), "assets", assets_commit_hash)
454
git_clone(stable_diffusion_repo, repo_dir('stable-diffusion-stability-ai'), "Stable Diffusion", stable_diffusion_commit_hash)
455
git_clone(stable_diffusion_xl_repo, repo_dir('generative-models'), "Stable Diffusion XL", stable_diffusion_xl_commit_hash)
456
git_clone(k_diffusion_repo, repo_dir('k-diffusion'), "K-diffusion", k_diffusion_commit_hash)
457
git_clone(blip_repo, repo_dir('BLIP'), "BLIP", blip_commit_hash)
458
459
startup_timer.record("clone repositores")
460
461
if not os.path.isfile(requirements_file):
462
requirements_file = os.path.join(script_path, requirements_file)
463
464
if not requirements_met(requirements_file):
465
run_pip(f"install -r \"{requirements_file}\"", "requirements")
466
startup_timer.record("install requirements")
467
468
if not os.path.isfile(requirements_file_for_npu):
469
requirements_file_for_npu = os.path.join(script_path, requirements_file_for_npu)
470
471
if "torch_npu" in torch_command and not requirements_met(requirements_file_for_npu):
472
run_pip(f"install -r \"{requirements_file_for_npu}\"", "requirements_for_npu")
473
startup_timer.record("install requirements_for_npu")
474
475
if not args.skip_install:
476
run_extensions_installers(settings_file=args.ui_settings_file)
477
478
if args.update_check:
479
version_check(commit)
480
startup_timer.record("check version")
481
482
if args.update_all_extensions:
483
git_pull_recursive(extensions_dir)
484
startup_timer.record("update extensions")
485
486
if "--exit" in sys.argv:
487
print("Exiting because of --exit argument")
488
exit(0)
489
490
491
492
def configure_for_tests():
493
if "--api" not in sys.argv:
494
sys.argv.append("--api")
495
if "--ckpt" not in sys.argv:
496
sys.argv.append("--ckpt")
497
sys.argv.append(os.path.join(script_path, "test/test_files/empty.pt"))
498
if "--skip-torch-cuda-test" not in sys.argv:
499
sys.argv.append("--skip-torch-cuda-test")
500
if "--disable-nan-check" not in sys.argv:
501
sys.argv.append("--disable-nan-check")
502
503
os.environ['COMMANDLINE_ARGS'] = ""
504
505
506
def start():
507
print(f"Launching {'API server' if '--nowebui' in sys.argv else 'Web UI'} with arguments: {' '.join(sys.argv[1:])}")
508
import webui
509
if '--nowebui' in sys.argv:
510
webui.api_only()
511
else:
512
webui.webui()
513
514
515
def dump_sysinfo():
516
from modules import sysinfo
517
import datetime
518
519
text = sysinfo.get()
520
filename = f"sysinfo-{datetime.datetime.utcnow().strftime('%Y-%m-%d-%H-%M')}.json"
521
522
with open(filename, "w", encoding="utf8") as file:
523
file.write(text)
524
525
return filename
526
527