Separate addon bl_info from __init__.py

The reasoning here is to decouple the actual code from the manifest aka meta data.
That way, tooling can be made much simpler by just replacing the existing meta data without having to wade through the existing _init_.py code, which in turn can be arbitrarily complex and with bl_info located in arbitrary positions in the code.

My proposal would be to include a manifest.py or bl_info.py file with each addon, along with its main entry point, which is _init_.py.

Or better yet, make it a json or yaml file so that we can use arbitrary build systems, not necessarily aware of python, or having to write out python where the most native/naive solution would be json or yaml.

Ideally, however, the manifest will be interpreted dynamically, so that we can read the data from a VERSION_INFO file in the addon, from where we will fill out the blanks in the bl_info, say, documentation link that will link to a specific version and changelog thereof and a warning that is based on the current release state and whatnot. Which again will reduce our tooling overhead during ci builds to a minimum as it is all in code and we would just replace the VERSION_INFO file.
However, the specifics are unknown to blender and it will just interpret the manifest and get the bl_info export from that module.

For now I have settled on a solution in _init_.py that will work, but it feels a bit off, especially with blender first parsing the bl_info structure and with blender version initially being 0.0.0 and the plugin version initially being 0.0.0, both of which are to be replaced later on. However, this will break blender from performing any version checks, so disabling the addon might fail due to a version mismatch.

bl_info = {
    'name': 'Foobar',
    'author': 'Some Foe',
    'description': '',
    'support': 'COMMUNITY',
    'location': 'View3D > Sidebar > Foobar',
    'wiki_url': '...'
    'tracker_url': '...',
    'category': 'Some Category',
    'version': (0, 0, 0),
    'warning': '',
    'blender': (0, 0, 0),
    # 'version': will be filled in later
    # 'warning': will be filled in later
    # 'blender': will be filled in later
    # 'wiki_url': will be filled in later
    # 'tracker_url': will be filled in later
}

import addon_utils

from .utils.addon import load_version, version_warning

version = load_version(os.path.dirname(os.path.realpath(__file__)))

def find_addon_info(name):
    for addon in addon_utils.modules():
        if addon.bl_info['name'] == name:
            return addon.bl_info
    return None

addon_bl_info = find_addon_info(bl_info['name'])
addon_bl_info['version'] = version.to_tuple()
addon_bl_info['warning'] = version_warning(version)
addon_bl_info['blender'] = (2, 93, 0)
addon_bl_info['wiki_url'] = 'https://example.org'
addon_bl_info['tracker_url'] = 'https://example.org'
1 Like

Just use your editor’s search function to search for bl_info.

Although having a manifest or alike is a good thing there are some stuff to be aware off.
We also support single file addons. Having a manifest.py is to limited perhaps it should then be a <add-on-name>.manifest.py

The idea of the bl_info is that it can be parsed without loading the add-on. Your second solution can only work after loading the add-on in the python interpreter. That leads to overhead when showing the list of add-ons.

I normally have a template file and let CI generate the actual __init__.py. My register/unregister functions are located in different modules and just imported in the init.

Not sure why the bl_info isn’t on the top in your cases. Clean code standards I follow organizes code to be read as a book. (bl_info should then be at the top of the file). In python the reading as a book isn’t always possible but is a good guideline.