GP-6641 - Extensions - UI Updates to show more extension details for

contained ExtensionPoints
This commit is contained in:
dragonmacher
2026-04-02 14:52:51 -04:00
parent 42b3b1d3da
commit e92c29bd1f
33 changed files with 769 additions and 357 deletions
@@ -14,8 +14,8 @@
into a Ghidra distribution. This allows users to create and share new plugins and scripts.
Ghidra ships with some pre-built extensions that not installed by default.
</P>
<P>Ghidra Extensions can be installed and uninstalled at runtime, with the changes taking effect
when Ghidra is restarted. The extension installation dialog can
<P>Ghidra Extensions can be installed and uninstalled at runtime, <U>with the changes taking effect
when Ghidra is restarted</U>. The extension installation dialog can
be opened by selecting the <B>Install Extensions</B> option on the project <B>File</B> menu.</P>
<BLOCKQUOTE>
@@ -32,11 +32,61 @@
</BLOCKQUOTE>
<BLOCKQUOTE>
<P>Installed extensions provide new functionality to Ghidra, such as Plugins, Analyzers and
other Extension Points. Non-plugin Extension Points will be automatically discovered and
loaded at startup. This is <B>not</B> the case for Plugins. Plugins must be manually
enabled inside of the tool that is being used. For example, when running the Code Browser,
use the <A href="help/topics/Tool/Configure_Tool.htm">
<B>File</B><IMG src="help/shared/arrow.gif" border="0"><B>Congfigure</B></A> menu to
enable any plugins added by newly installed extensions.
</P>
<BLOCKQUOTE>
<P><IMG src="help/shared/tip.png" alt="" border="0">The easiest way to find the plugins
for a given extension is to use the table view of all known plugins. To see all plugins
for an extension:</P>
<OL>
<LI>
Configure plugins via <B>File</B><IMG src="help/shared/arrow.gif" border="0">
<B>Congfigure</B>
</LI>
<LI>
Click the <img src="images/plugin.png" /> icon to show all plugins
</LI>
<LI>
If not already visible, add the <B>Module</B> table column via the right-click
menu of the table header. (Extensions are modules.)
</LI>
<LI>
Sort on the <B>Module</B> table column
</LI>
<LI>
Optionally, you can type the name of the extension into the filter to hide other modules
</LI>
</OL>
</BLOCKQUOTE>
</BLOCKQUOTE>
<H2>Dialog Components</H2>
<H3>Extensions Table</H3>
<BLOCKQUOTE>
<P>To install an extension, select the extension's checkbox. To unininstall, deselect the
checkbox.</P>
<BLOCKQUOTE>
<P><IMG src="help/shared/note.png" alt="" border="0">If the checkbox is not editable,
that means that means the extension is installed and canont be uninistalled. This can
happen in development mode with extensions that live in source control.</P>
</BLOCKQUOTE>
<P>The list of extensions is populated when the dialog is launched. To build the list, Ghidra
looks in several locations:</P>
@@ -371,8 +371,8 @@
<P class="relatedtopic">Related Topics</P>
<UL>
<LI><A href="help/topics/Tool/ToolOptions_Dialog.htm#KeyBindings_Option">Key
Bindings</A></LI>
<LI><A href="../BundleManager/BundleManager.htm">Ghidra Bundles</A>
<LI><A href="help/topics/Tool/ToolOptions_Dialog.htm#KeyBindings_Option">Key Bindings</A></LI>
</UL>
<P>&nbsp;</P>
@@ -27,7 +27,8 @@
<UL>
<LI>
<B>Version</B> - Ghidra, Operating System, and Java version information. Clicking the
<B><I>Copy</I></B> button will copy this information to the clipboard for easy transfer into a bug report.
<B><I>Copy</I></B> button will copy this information to the clipboard for easy transfer into
a bug report.
</LI>
<LI>
<B>Memory</B> - JVM memory usage. Clicking the <B><I>Collect Garbage</I></B> button will
@@ -47,7 +48,11 @@
<B>Modules</B> - A list of discovered Ghidra Modules.
</LI>
<LI>
<B>Extension Points</B> - A list of discovered Ghidra Extension Points.
<B>Extension Points</B> - A list of discovered Ghidra Extension Points.<BR><BR>
Note: Extension Points differ from Extensions. Extension Points are classes that Ghidra
will find at startup. Extensions are additional Ghidra Modules that can be installed
by the user. Further, Extensions may contain Extension Points.
<BR><BR>
</LI>
<LI>
<B>Classpath</B> - The ordered classpath for the active Ghidra application.
@@ -24,6 +24,7 @@ import java.util.*;
import org.junit.AfterClass;
import docking.test.AbstractDockingTest;
import generic.jar.ResourceFile;
import ghidra.GhidraTestApplicationLayout;
import ghidra.app.events.ProgramLocationPluginEvent;
import ghidra.app.events.ProgramSelectionPluginEvent;
@@ -659,9 +660,16 @@ public abstract class AbstractGhidraHeadlessIntegrationTest extends AbstractDock
Set<ClassFileInfo> serviceSet = extensionPointSuffixToInfoMap.get(suffix);
assertNotNull(serviceSet);
serviceSet.clear();
ClassFileInfo info = new ClassFileInfo("", replacement.getClass().getName(), suffix);
Class<? extends Object> clazz = replacement.getClass();
ResourceFile module = Application.getModuleContainingClass(clazz);
String modulePath = "";
if (module != null) {
modulePath = module.getAbsolutePath();
}
String name = clazz.getName();
ClassFileInfo info = new ClassFileInfo("", name, suffix, modulePath);
serviceSet.add(info);
loadedCache.put(info, replacement.getClass());
loadedCache.put(info, clazz);
}
T instance = tool.getService(service);
@@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -263,7 +263,7 @@ public class AnalysisOptions2Test extends AbstractGhidraHeadedIntegrationTest {
assertNotNull(analyzerSet);
analyzerSet.removeIf(c -> c.name().contains("TestStubAnalyzer"));
ClassFileInfo info = new ClassFileInfo("", analyzer.getName(), "Analyzer");
ClassFileInfo info = new ClassFileInfo("", analyzer.getName(), "Analyzer", "");
analyzerSet.add(info);
loadedCache.put(info, analyzer);
}
@@ -31,13 +31,14 @@ import ghidra.framework.Application;
import ghidra.framework.project.tool.testplugins.TestExtensionHello2Plugin;
import ghidra.framework.project.tool.testplugins.TestExtensionHelloPlugin;
import ghidra.test.AbstractGhidraHeadedIntegrationTest;
import ghidra.util.classfinder.ClassFileInfo;
import ghidra.util.extensions.ExtensionUtils;
import ghidra.util.xml.GenericXMLOutputter;
import ghidra.util.xml.XmlUtilities;
import utilities.util.FileUtilities;
import utility.application.ApplicationLayout;
public class ExtensionManagerTest extends AbstractGhidraHeadedIntegrationTest {
public class ToolExtensionStatusManagerTest extends AbstractGhidraHeadedIntegrationTest {
private ApplicationLayout appLayout;
private FakeToolExtensionsEnabledState extensionsState;
@@ -130,6 +131,7 @@ public class ExtensionManagerTest extends AbstractGhidraHeadedIntegrationTest {
// verify user is prompted to add new plugins
extensionManager.checkForNewExtensions();
waitForSwing();
waitForSwing();
assertTrue(extensionsState.didPrompt());
}
@@ -274,35 +276,47 @@ public class ExtensionManagerTest extends AbstractGhidraHeadedIntegrationTest {
private class FakeToolExtensionsEnabledState implements ExtensionsEnabledState {
private Map<String, Set<Class<?>>> extensions = new HashMap<>();
private Set<Class<?>> installedPlugins = new HashSet<>();
private Map<String, Set<ClassFileInfo>> extensions = new HashMap<>();
private Set<ClassFileInfo> installedPlugins = new HashSet<>();
private boolean didPrompt = false;
@Override
public Map<String, Set<Class<?>>> getAllKnownExtensions() {
public Map<String, Set<ClassFileInfo>> getAllKnownExtensions() {
return extensions;
}
@Override
public void removeInstalledPlugins(Set<Class<?>> plugins) {
public void removeInstalledPlugins(Set<ClassFileInfo> plugins) {
plugins.removeAll(installedPlugins);
}
@Override
public void propmtToConfigureNewPlugins(Set<Class<?>> plugins) {
public void propmtToConfigureNewPlugins(Set<ClassFileInfo> plugins) {
didPrompt = true;
}
void addExtension(String name, Class<?>... classes) {
void addExtension(String name, ClassFileInfo... classes) {
extensions.put(name, Set.of(classes));
}
void addExtension(String name, Class<?>... classes) {
for (Class<?> c : classes) {
String className = c.getName();
ClassFileInfo info = new ClassFileInfo("/fake/path", className, "Plugin", "");
addExtension(name, info);
}
}
boolean didPrompt() {
return didPrompt;
}
void setInstalled(Class<TestExtensionHelloPlugin> c) {
installedPlugins.add(c);
String name = c.getName();
ClassFileInfo info = new ClassFileInfo("/fake/path", name, "Plugin", "");
installedPlugins.add(info);
}
}