Issue with Bundling Add-on Dependencies in extensions

Hello,

I think there is an issue with reusing the same class names when bundling add-on dependencies in extensions.

From the Doc
Bundle Dependencies > Bundle other add-ons together.

Make sure that both the individual and the combined add-on check for already registered types (Operators, Panels, …). This avoids duplication of operators and panels on the interface if the add-ons are installed as a bundle and individually.

Doc link:
https://docs.blender.org/manual/en/dev/advanced/extensions/addons.html#bundle-dependencies

Avoiding duplication of operators and panels is great, but this will open the possibility of users breaking an add-on that depends on another add-on by disabling (unregistering) the individually installed add-on.

Let me give a repro of the issue.

I have an example add-on that extends Node Arrange and bundles it. The add-on is called Node Arrange Plus and it creates a copy of the Node Arrange panel and adds a button. The add-on has code to avoid duplication of operators and panels.
Note: this is a contrived example add-on

User steps to repro

  1. Install the Node Arrange Add-on extension
  2. Make sure the add-on is enabled
  3. Install the Node Arrange Plus extension
  4. Make sure the add-on is enabled
  5. Open a Node editor and navigate to the Arrange panel
  6. Disabled the Node Arrange add-on
  7. Now the Node Arrange Plus panel is broken since it was using the same classes/ops that were deregistered in step 6

link to code: GitHub - CGArtPython/Example-Combined-Add-on-Extension: This is an example Blender 4.2 Extension add-on that bundles another add-on

1 Like

I might be misunderstanding how the bundling of add-on dependencies in extensions works.

In any case, I think it would be great to add some clarification in the docs on how the Blender developers envision this to work.

1 Like

A custom reference counting system for registered classes

One way to solve this is to write logic that will know that the registered classes are being used by the bundle or individually installed add-on. When deregistering the classes, the code would check if anything else is using these classes.

It would be great if this system was part of bpy.utils.register_class(c)
bpy.utils.unregister_class(c).

Bumping. I like how you set it up that it checks classes upon (un)registration.

Does the “Reload Scripts” operator fix this issue, after your reproduction steps?

image

That might be the easiest workaround. Because apart from reloading add-ons by the operator, we’d have to get into a reference system like you described or a similar dependency/notification tree so add-ons could notify others upon being enabled or disabled. We could add a warning to the panel if “existing” classes are gone via a try/except, maybe add the reload scripts as a button for users to try.

“Make sure that both the individual and the combined add-on check for already registered types”

This also seems to imply that the original Node Arrange add-on should check for bundles too? Or does this mean the bundled version should be checking? If either, seeing an example from other devs would be nice.

Yes, it does fix the issue.

1 Like

We could add a warning to the panel if “existing” classes are gone via a try/except, maybe add the reload scripts as a button for users to try.

That is a good idea!

1 Like

If I was the owner of the individual and bundled versions I would be checking for the other version in each of them.

Maybe it would be some global property that the other add-on would check for and skip the unregistering steps.

Maybe it would be some global property that the other add-on would check for and skip the unregistering steps.

My worry about that would be that the number of these properties could compound with multiple add-ons using each other. Complexity could go up fast.

In the meantime, I’d make it clear in documentation that users either: enable the dependency add-on before enabling mine or don’t enable it at all and use mine only. They need to know the add-on’s dependencies so they don’t mess with them while mine is still enabled. Some add-ons aren’t built for Blender’s reload functionality and will break, so exposing that would be more of a last resort for me.

My suggestion for the devs would be (and they can take this with a grain of salt): define dependencies by id in the extension manifests.

# blender_manifest.toml
dependencies = [ 'node_arrange_plus', 'light_painter']

Upon enabling the extension, it would attempt to find those dependencies by name and enable them first. Then it enables itself. Upon disabling the add-on, nothing extra is done (just in case the user still wants to use the dependencies, without your extension).

That way, there’s no new data apart from the the new line in the manifest, and it scales well. The new add-on respects already existing dependencies and variations of them (e.g. a studio has their own modified version of node_arrange_plus but would still be properly overridden).

1 Like

Yeah, this is a great point.

As you pointed out, the complexity/dependency graph of add-ons depending on each other can get out of hand quickly.
The users would have trouble figuring out such a dependency graph.

For example, what would users do if two primary add-ons need different versions of the same secondary add-on?

1 Like

In this scenario, do we still bundle the dependencies?
If we don’t, wouldn’t this make exertions not self-contained?

Defining dependencies would break the rule that extensions are to be self-contained.
Surely this adds complexity in the way how add-ons are to be organized if they want to be bundled together, but there is no easy solution to this. The namespacing capabilities are quite limited in the way Python is used in Blender.

3 Likes

Thank you both for your great points Victor and Sergey, I stand corrected.

In that case, if there’s always a possibility of clashes, then maybe it’s better to just “un-clash” one’s dependency contained in the extension. Renaming its bl_idnames and such. Then, if your dependency must be registered, it would never clash with the original add-on, and be unaffected if the original add-on is turned off.

I actually did something similar for a product owner last week. If self-contained is the higher priority, then it’s the extension dev’s responsibility to make sure their dependencies cannot clash upon registration, even if that means modifying the dependency (so long as that’s allowed in the license).

1 Like

Yeah, this would be like vendorizing the bundled add-ons.
This would also allow developers to bundle/support different versions of the same add-on.

As I think about this more, it seems that if you have bundled add-ons, your extension is not entirely self-contained since a user can break it by manipulating the individually installed add-on.