Skip to content

Commit

Permalink
feat: automatic dependency download/install (#8)
Browse files Browse the repository at this point in the history
* feat: automatic dependency download

* feat: untested macos auto downloading
  • Loading branch information
alanpq authored Aug 17, 2024
1 parent 2280282 commit 5cfbd26
Show file tree
Hide file tree
Showing 7 changed files with 120 additions and 95 deletions.
20 changes: 19 additions & 1 deletion addons/lol_blender/dependencies.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,29 @@
Dependency = namedtuple("Dependency", ["module", "package", "name"])

dependencies = (Dependency(module="league_toolkit", package=None, name=None),)
dependencies_installed = [False]

modules = {}

dependencies_installed = [False]
default_dep_path = os.path.join(os.path.dirname(__file__), "deps")

def get_modules(wheel_path: str):
if len(modules.keys()) != len(dependencies):
install_dependencies(wheel_path)
return modules

def install_dependencies(wheel_path: str):
try:
install_pip()
except:
pass
for dependency in dependencies:
pkg = dependency.package
if dependency.module == "league_toolkit":
pkg = wheel_path
install_and_import_module(module_name=dependency.module,
package_name=pkg,
global_name=dependency.name)

def import_module(module_name, global_name=None, reload=True):
"""
Expand Down
Empty file.
73 changes: 73 additions & 0 deletions addons/lol_blender/operators/DownloadDeps.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import bpy
import requests
import platform

from addons.lol_blender.dependencies import *

system_mapping = {
'darwin': "macosx",
'windows': "win"
}

class LOL_OT_download_dependencies(bpy.types.Operator):
bl_idname = "lolblender.download_dependencies"
bl_label = "Automatically download & install dependencies"
bl_description = ("Downloads and installs the required python packages for this add-on. "
"Internet connection is required. Blender may have to be started with "
"elevated permissions in order to install the package")
bl_options = {"REGISTER", "INTERNAL"}

@classmethod
def poll(self, context):
# Deactivate when dependencies have been installed
# return not dependencies_installed[0]
return True

def execute(self, context):
try:
resp = requests.get("https://api.github.com/repos/alanpq/lol-blender/releases/latest").json()

system = platform.system().lower()
if len(system) == 0:
raise ValueError("Could not determine operating system.")
if system in system_mapping:
system = system_mapping[system]
machine = platform.machine().lower()
if len(machine) == 0:
raise ValueError("Could not determine machine architecture.")

matches = []
for asset in resp["assets"]:
name: str = asset["name"].lower()
if not name.startswith("league_toolkit"):
continue
if not (system in name and machine in name):
continue
matches.append((name, asset["browser_download_url"]))

if len(matches) <= 0:
raise ValueError(f"Could not find toolkit wheel for {system}/{machine}.")
elif len(matches) > 1:
self.report({"WARN"}, ">1 matching wheel found!")
# TODO: what should we do when >1 wheel matches our os/arch

asset = matches[0]
wheel = requests.get(asset[1])
download_path = bpy.context.preferences.addons["lol_blender"].preferences.wheel_download_path
if not os.path.exists(download_path):
os.makedirs(download_path)
wheel_path = os.path.join(download_path, asset[0])
with open(wheel_path, mode="wb") as f:
f.write(wheel.content)
bpy.context.preferences.addons["lol_blender"].preferences.wheel_path = wheel_path

install_dependencies(wheel_path)
except (subprocess.CalledProcessError, ImportError, ValueError) as err:
self.report({"ERROR"}, str(err))
return {"CANCELLED"}

# Register the panels, operators, etc. since dependencies are installed
# for cls in classes:
# bpy.utils.register_class(cls)

return {"FINISHED"}
18 changes: 4 additions & 14 deletions addons/lol_blender/operators/InstallDeps.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@


class LOL_OT_install_dependencies(bpy.types.Operator):
bl_idname = "example.install_dependencies"
bl_label = "Install dependencies"
bl_description = ("Downloads and installs the required python packages for this add-on. "
bl_idname = "lolblender.install_dependencies"
bl_label = "Manually install dependencies"
bl_description = ("Installs the league-toolkit dependency for this add-on. "
"Internet connection is required. Blender may have to be started with "
"elevated permissions in order to install the package")
bl_options = {"REGISTER", "INTERNAL"}
Expand All @@ -19,17 +19,7 @@ def poll(self, context):

def execute(self, context):
try:
try:
install_pip()
except:
pass
for dependency in dependencies:
pkg = dependency.package
if dependency.module == "league_toolkit":
pkg = bpy.context.preferences.addons["lol_blender"].preferences.wheel_path
install_and_import_module(module_name=dependency.module,
package_name=pkg,
global_name=dependency.name)
install_dependencies(bpy.context.preferences.addons["lol_blender"].preferences.wheel_path)
except (subprocess.CalledProcessError, ImportError) as err:
self.report({"ERROR"}, str(err))
return {"CANCELLED"}
Expand Down
30 changes: 11 additions & 19 deletions addons/lol_blender/panels/AddonPanels.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
import bpy

from addons.lol_blender.config import __addon_name__
from addons.lol_blender.dependencies import dependencies, modules
from addons.lol_blender.dependencies import dependencies, get_modules
from addons.lol_blender.operators.AddonOperators import ExportSkinned
from addons.lol_blender.operators.InstallDeps import LOL_OT_install_dependencies
from addons.lol_blender.operators.DownloadDeps import LOL_OT_download_dependencies
from common.i18n.i18n import i18n

from addons.lol_blender.toolkit import get_toolkit


class ToolkitPanel(bpy.types.Panel):
bl_label = "League Toolkit"
Expand All @@ -25,21 +23,15 @@ def draw(self, context: bpy.types.Context):

layout = self.layout

layout.operator(LOL_OT_install_dependencies.bl_idname, icon="CONSOLE")
for dependency in dependencies:
if not dependency.module in modules:
layout.label(text=f"Dependencies not installed!")
return

layout.label(text=i18n("Toolkit Library") + ": ")
layout.prop(addon_prefs, "toolkit_path")
toolkit = get_toolkit()
if len(addon_prefs.toolkit_version) <= 1:
layout.label(text="Invalid toolkit library")
else:
layout.label(text=f"Toolkit version: {toolkit.version}")
league_toolkit = modules["league_toolkit"]
layout.label(text=f"Real toolkit version: {league_toolkit.version()}")
modules = get_modules(addon_prefs.wheel_path)
league_toolkit = modules.get("league_toolkit", None)

if league_toolkit is None:
layout.operator(LOL_OT_download_dependencies.bl_idname, icon="CONSOLE")
layout.label(text=f"Dependencies not installed!")
return

layout.label(text=f"Toolkit version: {league_toolkit.version()}")
layout.separator()

row = layout.row()
Expand Down
38 changes: 13 additions & 25 deletions addons/lol_blender/preference/AddonPreferences.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import os

import bpy
from bpy.props import StringProperty, IntProperty, BoolProperty
from bpy.types import AddonPreferences

from addons.lol_blender.config import __addon_name__
from addons.lol_blender.operators.DownloadDeps import LOL_OT_download_dependencies
from addons.lol_blender.operators.InstallDeps import LOL_OT_install_dependencies
from addons.lol_blender.toolkit import load_toolkit_lib, Toolkit, get_toolkit
from addons.lol_blender.dependencies import default_dep_path


def factory_update_addon_category(cls, prop):
Expand All @@ -19,42 +19,30 @@ def func(self, context):
return func


def switch_toolkit_lib(self, context):
load_toolkit_lib(self.toolkit_path)


def get_toolkit_lib(self):
toolkit = get_toolkit()
if toolkit is None:
return ""
return str(toolkit.version)


class LOLPrefs(AddonPreferences):
# this must match the add-on name (the folder name of the unzipped file)
bl_idname = __addon_name__

# https://docs.blender.org/api/current/bpy.props.html
# The name can't be dynamically translated during blender programming running as they are defined
# when the class is registered, i.e. we need to restart blender for the property name to be correctly translated.
toolkit_path: StringProperty(
name="Library Path",
subtype='FILE_PATH',
default='X:\Documents\Projects\lol-blender\lib.dll',
update=switch_toolkit_lib
)

wheel_path: StringProperty(
wheel_path: bpy.props.StringProperty(
name="Wheel Path",
subtype='FILE_PATH',
default=os.environ.get("__LOL_WHEEL_PATH", 'X:\\Documents\\Projects\\lol-blender\\bindings\\target\\wheels\\league_toolkit-0.1.0-cp311-none-win_amd64.whl'),
)
default=os.environ.get("__LOL_WHEEL_PATH", ''),
) # type: ignore

toolkit_version: StringProperty(name="Toolkit Version", get=get_toolkit_lib)
wheel_download_path: bpy.props.StringProperty(
name="Dependency Download Path",
subtype='DIR_PATH',
default=default_dep_path
) # type: ignore

def draw(self, context: bpy.types.Context):
layout = self.layout
layout.label(text="Add-on Preferences View")
layout.prop(self, "toolkit_path")
layout.operator(LOL_OT_download_dependencies.bl_idname, icon="IMPORT")
layout.prop(self, "wheel_download_path")
layout.prop(self, "wheel_path")
layout.operator(LOL_OT_install_dependencies.bl_idname, icon="CONSOLE")

36 changes: 0 additions & 36 deletions addons/lol_blender/toolkit.py

This file was deleted.

0 comments on commit 5cfbd26

Please sign in to comment.