aboutsummaryrefslogtreecommitdiff
path: root/modules/extensions.py
diff options
context:
space:
mode:
authorAUTOMATIC1111 <16777216c@gmail.com>2023-11-20 14:49:46 +0300
committerGitHub <noreply@github.com>2023-11-20 14:49:46 +0300
commit73a0b4bba6a533966c58724e31bc0b2b49e9e041 (patch)
treed14d2543623f91b91308c4cc0beb45b73ce71f91 /modules/extensions.py
parent411da7c281703fd61fbac715d0ed90b67a085ea8 (diff)
parentbde439ef67776be126d6a8c569a23d54dbc3e707 (diff)
Merge pull request #13944 from wfjsw/dag
implementing script metadata and DAG sorting mechanism
Diffstat (limited to 'modules/extensions.py')
-rw-r--r--modules/extensions.py84
1 files changed, 74 insertions, 10 deletions
diff --git a/modules/extensions.py b/modules/extensions.py
index bf9a1878..f3988d02 100644
--- a/modules/extensions.py
+++ b/modules/extensions.py
@@ -1,5 +1,8 @@
+import configparser
+import functools
import os
import threading
+import re
from modules import shared, errors, cache, scripts
from modules.gitpython_hack import Repo
@@ -23,8 +26,9 @@ class Extension:
lock = threading.Lock()
cached_fields = ['remote', 'commit_date', 'branch', 'commit_hash', 'version']
- def __init__(self, name, path, enabled=True, is_builtin=False):
+ def __init__(self, name, path, enabled=True, is_builtin=False, canonical_name=None):
self.name = name
+ self.canonical_name = canonical_name or name.lower()
self.path = path
self.enabled = enabled
self.status = ''
@@ -37,6 +41,18 @@ class Extension:
self.remote = None
self.have_info_from_repo = False
+ @functools.cached_property
+ def metadata(self):
+ if os.path.isfile(os.path.join(self.path, "metadata.ini")):
+ try:
+ config = configparser.ConfigParser()
+ config.read(os.path.join(self.path, "metadata.ini"))
+ return config
+ except Exception:
+ errors.report(f"Error reading metadata.ini for extension {self.canonical_name}.",
+ exc_info=True)
+ return None
+
def to_dict(self):
return {x: getattr(self, x) for x in self.cached_fields}
@@ -56,6 +72,7 @@ class Extension:
self.do_read_info_from_repo()
return self.to_dict()
+
try:
d = cache.cached_data_for_file('extensions-git', self.name, os.path.join(self.path, ".git"), read_from_repo)
self.from_dict(d)
@@ -136,9 +153,6 @@ class Extension:
def list_extensions():
extensions.clear()
- if not os.path.isdir(extensions_dir):
- return
-
if shared.cmd_opts.disable_all_extensions:
print("*** \"--disable-all-extensions\" arg was used, will not load any extensions ***")
elif shared.opts.disable_all_extensions == "all":
@@ -148,18 +162,68 @@ def list_extensions():
elif shared.opts.disable_all_extensions == "extra":
print("*** \"Disable all extensions\" option was set, will only load built-in extensions ***")
- extension_paths = []
- for dirname in [extensions_dir, extensions_builtin_dir]:
+ extension_dependency_map = {}
+
+ # scan through extensions directory and load metadata
+ for dirname in [extensions_builtin_dir, extensions_dir]:
if not os.path.isdir(dirname):
- return
+ continue
for extension_dirname in sorted(os.listdir(dirname)):
path = os.path.join(dirname, extension_dirname)
if not os.path.isdir(path):
continue
- extension_paths.append((extension_dirname, path, dirname == extensions_builtin_dir))
+ canonical_name = extension_dirname
+ requires = None
+
+ if os.path.isfile(os.path.join(path, "metadata.ini")):
+ try:
+ config = configparser.ConfigParser()
+ config.read(os.path.join(path, "metadata.ini"))
+ canonical_name = config.get("Extension", "Name", fallback=canonical_name)
+ requires = config.get("Extension", "Requires", fallback=None)
+ except Exception:
+ errors.report(f"Error reading metadata.ini for extension {extension_dirname}. "
+ f"Will load regardless.", exc_info=True)
+
+ canonical_name = canonical_name.lower().strip()
+
+ # check for duplicated canonical names
+ if canonical_name in extension_dependency_map:
+ errors.report(f"Duplicate canonical name \"{canonical_name}\" found in extensions "
+ f"\"{extension_dirname}\" and \"{extension_dependency_map[canonical_name]['dirname']}\". "
+ f"The current loading extension will be discarded.", exc_info=False)
+ continue
- for dirname, path, is_builtin in extension_paths:
- extension = Extension(name=dirname, path=path, enabled=dirname not in shared.opts.disabled_extensions, is_builtin=is_builtin)
+ # both "," and " " are accepted as separator
+ requires = list(filter(None, re.split(r"[,\s]+", requires.lower()))) if requires else []
+
+ extension_dependency_map[canonical_name] = {
+ "dirname": extension_dirname,
+ "path": path,
+ "requires": requires,
+ }
+
+ # check for requirements
+ for (_, extension_data) in extension_dependency_map.items():
+ dirname, path, requires = extension_data['dirname'], extension_data['path'], extension_data['requires']
+ requirement_met = True
+ for req in requires:
+ if req not in extension_dependency_map:
+ errors.report(f"Extension \"{dirname}\" requires \"{req}\" which is not installed. "
+ f"The current loading extension will be discarded.", exc_info=False)
+ requirement_met = False
+ break
+ dep_dirname = extension_dependency_map[req]['dirname']
+ if dep_dirname in shared.opts.disabled_extensions:
+ errors.report(f"Extension \"{dirname}\" requires \"{dep_dirname}\" which is disabled. "
+ f"The current loading extension will be discarded.", exc_info=False)
+ requirement_met = False
+ break
+
+ is_builtin = dirname == extensions_builtin_dir
+ extension = Extension(name=dirname, path=path,
+ enabled=dirname not in shared.opts.disabled_extensions and requirement_met,
+ is_builtin=is_builtin)
extensions.append(extension)