summaryrefslogtreecommitdiff
path: root/python
diff options
context:
space:
mode:
author0cyn <kat@vector35.com>2026-04-14 15:45:28 -0400
committer0cyn <kat@vector35.com>2026-04-14 15:47:03 -0400
commit9fddd2a54eaa640888c656cdfb490690ec6f519b (patch)
treed2379dd29e8c17dd16dda72980273a30763c8b8a /python
parent544b7ee320dd8dd69f7b368a36ee72e3faf7e640 (diff)
Plugin Manifest V2 Support
Diffstat (limited to 'python')
-rw-r--r--python/__init__.py1
-rw-r--r--python/pluginmanager.py52
-rw-r--r--python/scriptingprovider.py18
3 files changed, 45 insertions, 26 deletions
diff --git a/python/__init__.py b/python/__init__.py
index 8790ac8f..cafea2e8 100644
--- a/python/__init__.py
+++ b/python/__init__.py
@@ -263,7 +263,6 @@ def _init_plugins():
if _enable_default_log and is_headless_init_once and min_level in LogLevel.__members__ and not core_ui_enabled(
) and sys.stderr.isatty():
log_to_stderr(LogLevel[min_level])
- core.BNInitRepoPlugins()
if core.BNIsLicenseValidated():
_plugin_init = True
else:
diff --git a/python/pluginmanager.py b/python/pluginmanager.py
index 27360dbe..5e32a79f 100644
--- a/python/pluginmanager.py
+++ b/python/pluginmanager.py
@@ -29,9 +29,9 @@ from . import deprecation
from .enums import PluginType
-class RepoPlugin:
+class Extension:
"""
- ``RepoPlugin`` is mostly read-only, however you can install/uninstall enable/disable plugins. RepoPlugins are
+ ``Extension`` is mostly read-only, however you can install/uninstall enable/disable plugins. Extensions are
created by parsing the plugins.json in a plugin repository.
"""
def __init__(self, handle: 'core.BNRepoPluginHandle'):
@@ -70,10 +70,10 @@ class RepoPlugin:
"""Boolean True if the plugin is installed, False otherwise"""
return core.BNPluginIsInstalled(self.handle)
- def install(self) -> bool:
+ def install(self, version_id=None) -> bool:
"""Attempt to install the given plugin"""
self.install_dependencies()
- return core.BNPluginInstall(self.handle)
+ return core.BNPluginInstall(self.handle, version_id)
def uninstall(self) -> bool:
"""Attempt to uninstall the given plugin"""
@@ -83,7 +83,7 @@ class RepoPlugin:
def installed(self, state: bool):
if state:
self.install_dependencies()
- core.BNPluginInstall(self.handle)
+ core.BNPluginInstall(self.handle, None)
else:
core.BNPluginUninstall(self.handle)
@@ -126,14 +126,16 @@ class RepoPlugin:
core.BNFreePluginPlatforms(platforms, count.value)
@property
+ @deprecation.deprecated(deprecated_in="5.3", details='Use :py:attr:`current_version` in combination with :py:attr:`versions` instead.')
def description(self) -> Optional[str]:
"""String short description of the plugin"""
return core.BNPluginGetDescription(self.handle)
@property
+ @deprecation.deprecated(deprecated_in="5.3", details='This field will be removed.')
def license_text(self) -> Optional[str]:
"""String complete license text for the given plugin"""
- return core.BNPluginGetLicenseText(self.handle)
+ return ''
@property
def long_description(self) -> Optional[str]:
@@ -185,6 +187,7 @@ class RepoPlugin:
return core.BNPluginGetProjectUrl(self.handle)
@property
+ @deprecation.deprecated(deprecated_in="5.3", details='Use :py:attr:`current_version` in combination with :py:attr:`versions` instead.')
def package_url(self) -> Optional[str]:
"""String URL of the plugin's zip file"""
return core.BNPluginGetPackageUrl(self.handle)
@@ -200,9 +203,17 @@ class RepoPlugin:
return core.BNPluginGetAuthor(self.handle)
@property
+ @deprecation.deprecated(deprecated_in="5.3", details='Use :py:attr:`current_version` in combination with :py:attr:`versions` instead.')
def version(self) -> Optional[str]:
"""String version of the plugin"""
- return core.BNPluginGetVersion(self.handle)
+ version: core.BNPluginVersion = core.BNPluginGetCurrentVersion(self.handle)
+ try:
+ version_string = version.versionString
+ except AttributeError:
+ version_string = ""
+ finally:
+ core.BNPluginFreeVersion(version)
+ return version_string
@property
def install_platforms(self) -> List[str]:
@@ -259,6 +270,7 @@ class RepoPlugin:
return core.BNPluginAreDependenciesBeingInstalled(self.handle)
@property
+ @deprecation.deprecated(deprecated_in="5.3", details='This field will be removed.')
def project_data(self) -> Dict:
"""Gets a json object of the project data field"""
data = core.BNPluginGetProjectData(self.handle)
@@ -266,11 +278,18 @@ class RepoPlugin:
return json.loads(data)
@property
+ @deprecation.deprecated(deprecated_in="5.3", details='Use :py:attr:`versions` in combination with :py:attr:`current_version` to check for updates instead.')
def last_update(self) -> date:
"""Returns a datetime object representing the plugins last update"""
return datetime.fromtimestamp(core.BNPluginGetLastUpdate(self.handle))
+@deprecation.deprecated(deprecated_in="5.3", details='Use :py:class:`binaryninja.Extension` instead.')
+class RepoPlugin(Extension):
+ def __init__(self, handle: 'core.BNRepoPluginHandle'):
+ super().__init__(handle)
+
+
class Repository:
"""
``Repository`` is a read-only class. Use RepositoryManager to Enable/Disable/Install/Uninstall plugins.
@@ -313,8 +332,8 @@ class Repository:
return result
@property
- def plugins(self) -> List[RepoPlugin]:
- """List of RepoPlugin objects contained within this repository"""
+ def plugins(self) -> List[Extension]:
+ """List of Extension objects contained within this repository"""
pluginlist = []
count = ctypes.c_ulonglong(0)
result = core.BNRepositoryGetPlugins(self.handle, count)
@@ -323,7 +342,7 @@ class Repository:
for i in range(count.value):
plugin_ref = core.BNNewPluginReference(result[i])
assert plugin_ref is not None, "core.BNNewPluginReference returned None"
- pluginlist.append(RepoPlugin(plugin_ref))
+ pluginlist.append(Extension(plugin_ref))
return pluginlist
finally:
core.BNFreeRepositoryPluginList(result)
@@ -337,7 +356,6 @@ class RepositoryManager:
"""
def __init__(self):
binaryninja._init_plugins()
- self.handle = core.BNGetRepositoryManager()
def __getitem__(self, repo_path: str) -> Repository:
for repo in self.repositories:
@@ -347,14 +365,14 @@ class RepositoryManager:
def check_for_updates(self) -> bool:
"""Check for updates for all managed Repository objects"""
- return core.BNRepositoryManagerCheckForUpdates(self.handle)
+ return core.BNRepositoryManagerCheckForUpdates()
@property
def repositories(self) -> List[Repository]:
"""List of Repository objects being managed"""
result = []
count = ctypes.c_ulonglong(0)
- repos = core.BNRepositoryManagerGetRepositories(self.handle, count)
+ repos = core.BNRepositoryManagerGetRepositories(count)
assert repos is not None, "core.BNRepositoryManagerGetRepositories returned None"
try:
for i in range(count.value):
@@ -366,8 +384,8 @@ class RepositoryManager:
core.BNFreeRepositoryManagerRepositoriesList(repos)
@property
- def plugins(self) -> Dict[str, List[RepoPlugin]]:
- """List of all RepoPlugins in each repository"""
+ def plugins(self) -> Dict[str, List[Extension]]:
+ """List of all Extensions in each repository"""
plugin_list = {}
for repo in self.repositories:
plugin_list[repo.path] = repo.plugins
@@ -376,7 +394,7 @@ class RepositoryManager:
@property
def default_repository(self) -> Repository:
"""Gets the default Repository"""
- repo_handle = core.BNRepositoryManagerGetDefaultRepository(self.handle)
+ repo_handle = core.BNRepositoryManagerGetDefaultRepository()
assert repo_handle is not None, "core.BNRepositoryManagerGetDefaultRepository returned None"
repo_handle_ref = core.BNNewRepositoryReference(repo_handle)
assert repo_handle_ref is not None, "core.BNNewRepositoryReference returned None"
@@ -406,4 +424,4 @@ class RepositoryManager:
if not isinstance(url, str) or not isinstance(repopath, str):
raise ValueError("Expected url or repopath to be of type str.")
- return core.BNRepositoryManagerAddRepository(self.handle, url, repopath)
+ return core.BNRepositoryManagerAddRepository(url, repopath)
diff --git a/python/scriptingprovider.py b/python/scriptingprovider.py
index dbeef1cb..3b6718af 100644
--- a/python/scriptingprovider.py
+++ b/python/scriptingprovider.py
@@ -53,6 +53,7 @@ from .pluginmanager import RepositoryManager
from .enums import ScriptingProviderExecuteResult, ScriptingProviderInputReadyState
from .settings import Settings
from .enums import SettingsScope
+import json
_WARNING_REGEX = re.compile(r'^\S+:\d+: \w+Warning: ')
@@ -1086,12 +1087,8 @@ class PythonScriptingProvider(ScriptingProvider):
plugin = repo[module]
if not force and self.apiName not in plugin.api:
- raise ValueError(f"Plugin API name is not {self.name}")
+ raise ValueError(f"Plugin '{plugin.name}' API name '{plugin.api}' is not {self.name}")
- if not force and core.core_platform not in plugin.install_platforms:
- raise ValueError(
- f"Current platform {core.core_platform} isn't in list of valid platforms for this plugin {plugin.install_platforms}"
- )
if not plugin.installed:
plugin.installed = True
@@ -1218,8 +1215,13 @@ class PythonScriptingProvider(ScriptingProvider):
def _install_modules(self, ctx, _modules: bytes) -> bool:
# This callback should not be called directly
- modules = _modules.decode("utf-8")
- if len(modules.strip()) == 0:
+ dependencies_json = json.loads(_modules.decode("utf-8"))
+ modules = ""
+ if "pip" in dependencies_json:
+ if len(dependencies_json["pip"].strip()) == 0:
+ return True
+ modules = [line.split('#', 1)[0].strip() for line in dependencies_json["pip"].split('\n') if line.split('#', 1)[0].strip()]
+ if len(modules) == 0:
return True
python_lib = settings.Settings().get_string("python.interpreter")
python_bin_override = settings.Settings().get_string("python.binaryOverride")
@@ -1267,7 +1269,7 @@ class PythonScriptingProvider(ScriptingProvider):
) / f"python{sys.version_info.major}{sys.version_info.minor}" / "site-packages"
site_package_dir.mkdir(parents=True, exist_ok=True)
args.extend(["--target", str(site_package_dir)])
- args.extend(list(filter(len, modules.split("\n"))))
+ args.extend(list(filter(len, modules)))
logger.log_info(f"Running pip {args}")
status, result = self._run_args(args, env=python_env)
if status: