Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Isolate dependencies from other add-ons #284

Merged
merged 5 commits into from
Jun 5, 2024
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,8 @@ CMakeCache.txt
Makefile
lib

#Selenium
__hubs_selenium_profile
# Selenium
__hubs_selenium_profile

# Dependencies
addons/io_hubs_addon/.__deps__
2 changes: 0 additions & 2 deletions addons/io_hubs_addon/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
from .utils import create_prefs_dir
from .utils import get_user_python_path
import sys
import bpy
from .io import gltf_exporter, gltf_importer, panels
Expand All @@ -22,7 +21,6 @@
"category": "Generic"
}

sys.path.insert(0, get_user_python_path())

create_prefs_dir()

Expand Down
14 changes: 7 additions & 7 deletions addons/io_hubs_addon/debugger.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from bpy.types import Context

from .preferences import EXPORT_TMP_FILE_NAME, EXPORT_TMP_SCREENSHOT_FILE_NAME
from .utils import isModuleAvailable, save_prefs, find_area, image_type_to_file_ext
from .utils import is_module_available, save_prefs, find_area, image_type_to_file_ext
from .icons import get_hubs_icons
from .hubs_session import HubsSession, PARAMS_TO_STRING
from . import api
Expand Down Expand Up @@ -238,7 +238,7 @@ class HUBS_PT_ToolsSceneDebuggerCreatePanel(bpy.types.Panel):

@classmethod
def poll(cls, context: Context):
return isModuleAvailable("selenium")
return is_module_available("selenium")

def draw(self, context: Context):
prefs = context.window_manager.hubs_scene_debugger_prefs
Expand Down Expand Up @@ -270,7 +270,7 @@ class HUBS_PT_ToolsSceneDebuggerOpenPanel(bpy.types.Panel):

@classmethod
def poll(cls, context: Context):
return isModuleAvailable("selenium")
return is_module_available("selenium")

def draw(self, context: Context):
box = self.layout.box()
Expand Down Expand Up @@ -303,7 +303,7 @@ class HUBS_PT_ToolsSceneDebuggerUpdatePanel(bpy.types.Panel):

@classmethod
def poll(cls, context: Context):
return isModuleAvailable("selenium")
return is_module_available("selenium")

def draw(self, context: Context):
box = self.layout.box()
Expand Down Expand Up @@ -379,7 +379,7 @@ class HUBS_PT_ToolsSceneSessionPanel(bpy.types.Panel):
def draw(self, context):
main_box = self.layout.box()

if isModuleAvailable("selenium"):
if is_module_available("selenium"):
row = main_box.row(align=True)
row.alignment = "CENTER"
col = row.column()
Expand Down Expand Up @@ -454,7 +454,7 @@ class HUBS_PT_ToolsSceneDebuggerPanel(bpy.types.Panel):

@classmethod
def poll(cls, context: Context):
return isModuleAvailable("selenium")
return is_module_available("selenium")

def draw(self, context):
params_icons = {}
Expand Down Expand Up @@ -860,7 +860,7 @@ class HUBS_PT_ToolsSceneDebuggerPublishScenePanel(bpy.types.Panel):

@classmethod
def poll(cls, context: Context):
return isModuleAvailable("selenium")
return is_module_available("selenium")

def draw(self, context: Context):
if not hubs_session.is_alive() or not hubs_session.user_logged_in:
Expand Down
11 changes: 11 additions & 0 deletions addons/io_hubs_addon/dependencies/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
from ..utils import load_dependency

selenium = None


def get_selenium():
global selenium
if selenium is None:
selenium = load_dependency("selenium.webdriver")

return selenium
19 changes: 10 additions & 9 deletions addons/io_hubs_addon/hubs_session.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import bpy
from .preferences import get_addon_pref, EXPORT_TMP_FILE_NAME
from .utils import isModuleAvailable, get_browser_profile_directory
from .utils import is_module_available, get_browser_profile_directory

PARAMS_TO_STRING = {
"newLoader": {
Expand Down Expand Up @@ -121,26 +121,27 @@ def __create_instance(self, context):
file_path = get_browser_profile_directory(browser)
if not os.path.exists(file_path):
os.mkdir(file_path)

from .dependencies import get_selenium
selenium = get_selenium()
if browser == "Firefox":
from selenium import webdriver
options = webdriver.FirefoxOptions()
options = selenium.FirefoxOptions()
override_ff_path = get_addon_pref(
context).override_firefox_path
ff_path = get_addon_pref(context).firefox_path
if override_ff_path and ff_path:
options.binary_location = ff_path
# This should work but it doesn't https://github.com/SeleniumHQ/selenium/issues/11028 so using arguments instead
# firefox_profile = webdriver.FirefoxProfile(file_path)
# firefox_profile = selenium.FirefoxProfile(file_path)
# firefox_profile.accept_untrusted_certs = True
# firefox_profile.assume_untrusted_cert_issuer = True
# options.profile = firefox_profile
options.add_argument("-profile")
options.add_argument(file_path)
options.set_preference("javascript.options.shared_memory", True)
self._web_driver = webdriver.Firefox(options=options)
self._web_driver = selenium.Firefox(options=options)
else:
from selenium import webdriver
options = webdriver.ChromeOptions()
options = selenium.ChromeOptions()
options.add_argument('--enable-features=SharedArrayBuffer')
options.add_argument('--ignore-certificate-errors')
options.add_argument(
Expand All @@ -150,7 +151,7 @@ def __create_instance(self, context):
chrome_path = get_addon_pref(context).chrome_path
if override_chrome_path and chrome_path:
options.binary_location = chrome_path
self._web_driver = webdriver.Chrome(options=options)
self._web_driver = selenium.Chrome(options=options)

def update_session_state(self):
if self.is_alive():
Expand Down Expand Up @@ -201,7 +202,7 @@ def bring_to_front(self, context):

def is_alive(self):
try:
if not self._web_driver or not isModuleAvailable("selenium"):
if not self._web_driver or not is_module_available("selenium"):
return False
else:
return bool(self._web_driver.current_url)
Expand Down
90 changes: 19 additions & 71 deletions addons/io_hubs_addon/preferences.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import bpy
from bpy.types import AddonPreferences, Context
from bpy.props import IntProperty, StringProperty, EnumProperty, BoolProperty, CollectionProperty
from .utils import get_addon_package, isModuleAvailable, get_browser_profile_directory
from bpy.props import IntProperty, StringProperty, EnumProperty, BoolProperty, CollectionProperty, PointerProperty
from .utils import get_addon_package, is_module_available, get_browser_profile_directory
import platform
from os.path import join, dirname, realpath

Expand Down Expand Up @@ -39,37 +39,24 @@ class InstallDepsOperator(bpy.types.Operator):
bl_property = "dep_names"
keianhzo marked this conversation as resolved.
Show resolved Hide resolved
bl_options = {'REGISTER', 'UNDO'}

dep_names: CollectionProperty(type=DepsProperty)
dep_config: PointerProperty(type=DepsProperty)

def execute(self, context):
import subprocess
import sys

result = subprocess.run([sys.executable, '-m', 'ensurepip'],
capture_output=False, text=True, input="y")
if result.returncode < 0:
print(result.stderr)
bpy.ops.wm.hubs_report_viewer('INVOKE_DEFAULT', title="Hubs scene debugger report",
report_string='\n\n'.join(["Dependencies install has failed",
f'{result.stderr}']))
return {'CANCELLED'}

deps = []
for _, dep in self.dep_names.items():
if dep.version:
deps.append(f'{dep.name}=={dep.version}')
else:
deps.append(dep.name)
from .utils import get_or_create_deps_path
dep = self.dep_config.name
if self.dep_config.version:
dep = f'{self.dep_config.name}=={self.dep_config.version}'

from .utils import get_user_python_path
result = subprocess.run(
[sys.executable, '-m', 'pip', 'install', *deps,
'-t', get_user_python_path()],
[sys.executable, '-m', 'pip', 'install', dep,
'-t', get_or_create_deps_path(self.dep_config.name)],
capture_output=True, text=True, input="y")
failed = False
for _, dep in self.dep_names.items():
if not isModuleAvailable(dep.name):
failed = True
if not is_module_available(self.dep_config.name):
failed = True
if result.returncode != 0 or failed:
print(result.stderr)
bpy.ops.wm.hubs_report_viewer('INVOKE_DEFAULT', title="Hubs scene debugger report",
Expand All @@ -88,52 +75,14 @@ class UninstallDepsOperator(bpy.types.Operator):
bl_property = "dep_names"
keianhzo marked this conversation as resolved.
Show resolved Hide resolved
bl_options = {'REGISTER', 'UNDO'}

dep_names: CollectionProperty(type=DepsProperty)
dep_config: PointerProperty(type=DepsProperty)
force: BoolProperty(default=False)
keianhzo marked this conversation as resolved.
Show resolved Hide resolved

def execute(self, context):
import subprocess
import sys

result = subprocess.run([sys.executable, '-m', 'ensurepip'],
capture_output=False, text=True, input="y")
if result.returncode < 0:
print(result.stderr)
bpy.ops.wm.hubs_report_viewer('INVOKE_DEFAULT', title="Hubs scene debugger report",
report_string='\n\n'.join(["Dependencies uninstall has failed",
f'{result.stderr}']))
return {'CANCELLED'}

for name, _ in self.dep_names.items():
del name

result = subprocess.run(
[sys.executable, '-m', 'pip', 'uninstall', *
[name for name, _ in self.dep_names.items()]],
capture_output=True, text=True, input="y")

failed = False
for name, _ in self.dep_names.items():
if isModuleAvailable(name):
failed = True
if result.returncode != 0 or failed:
print(result.stderr)
bpy.ops.wm.hubs_report_viewer('INVOKE_DEFAULT', title="Hubs scene debugger report",
report_string='\n\n'.join(["Dependencies install has failed",
f'{result.stderr}']))
return {'CANCELLED'}

if self.force:
import os
from .utils import get_user_python_path
deps_paths = [os.path.join(get_user_python_path(), name)
for name, _ in self.dep_names.items()]
import shutil
for dep_path in deps_paths:
shutil.rmtree(dep_path)
from .utils import get_or_create_deps_path
import shutil
shutil.rmtree(get_or_create_deps_path(self.dep_config.name))

bpy.ops.wm.hubs_report_viewer('INVOKE_DEFAULT', title="Hubs scene debugger report",
report_string="Dependencies uninstalled successfully")
return {'FINISHED'}


Expand Down Expand Up @@ -209,7 +158,7 @@ def draw(self, context):
box.row().prop(self, "row_length")
box.row().prop(self, "recast_lib_path")

selenium_available = isModuleAvailable("selenium")
selenium_available = is_module_available("selenium")
modules_available = selenium_available
box = layout.box()
box.label(text="Scene debugger configuration")
Expand Down Expand Up @@ -267,13 +216,12 @@ def draw(self, context):
row.prop(self, "force_uninstall")
op = row.operator(UninstallDepsOperator.bl_idname,
text="Uninstall dependencies (selenium)")
op.dep_names.add().name = "selenium"
op.dep_config.name = "selenium"
else:
op = row.operator(InstallDepsOperator.bl_idname,
text="Install dependencies (selenium)")
dep = op.dep_names.add()
dep.name = "selenium"
dep.version = "4.15.2"
op.dep_config.name = "selenium"
op.dep_config.version = "4.15.2"


def register():
Expand Down
76 changes: 59 additions & 17 deletions addons/io_hubs_addon/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,27 +26,69 @@ def gather():
return wrapper_delayed_gather


user_python_path = None
def get_or_create_deps_path(name):
import os
deps_path = os.path.abspath(os.path.join(
__file__, "..", ".__deps__", name))
if not os.path.exists(deps_path):
os.makedirs(deps_path, exist_ok=True)
return deps_path


def is_module_available(name):
import sys
old_syspath = sys.path[:]
old_sysmod = sys.modules.copy()

def get_user_python_path():
global user_python_path
if not user_python_path:
import sys
import subprocess
result = subprocess.run([sys.executable, '-m', 'site',
'--user-site'], capture_output=True, text=True, input="y")
user_python_path = result.stdout.strip("\n")
return user_python_path
try:
path = get_or_create_deps_path(name)

import importlib
sys.path.insert(0, str(path))

def isModuleAvailable(name):
import importlib
loader = importlib.util.find_spec(name)
import os
from .utils import get_user_python_path
path = os.path.join(get_user_python_path(), name)
return loader and os.path.exists(path)
try:
loader = importlib.util.find_spec(name)
except ImportError as ex:
print(f'{name} not found')

import os
path = os.path.join(path, name)
return loader and os.path.exists(path)

finally:
# Restore without assigning a new list instance. That way references
# held by other code will stay valid.
sys.path[:] = old_syspath
sys.modules.clear()
sys.modules.update(old_sysmod)


def load_dependency(name):
import sys
old_syspath = sys.path[:]
old_sysmod = sys.modules.copy()

module = None
try:
modules = name.split(".")
path = get_or_create_deps_path(modules[0])

import importlib
sys.path.insert(0, str(path))

try:
module = importlib.import_module(name)
except ImportError as ex:
print(f'Unable to load {name}')

finally:
# Restore without assigning a new list instance. That way references
# held by other code will stay valid.
sys.path[:] = old_syspath
sys.modules.clear()
sys.modules.update(old_sysmod)

return module


HUBS_PREFS_DIR = ".__hubs_blender_addon_preferences"
Expand Down
Loading