introduce GhidraBundle, cleanup packages, rename path->status

This commit is contained in:
Jason P. Leasure 2020-04-02 17:19:49 -04:00
parent 7873bb32fa
commit 431d7ac752
22 changed files with 466 additions and 441 deletions

View file

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package ghidra.app.script.osgi; package ghidra.app.plugin.core.osgi;
import java.io.*; import java.io.*;
import java.nio.charset.Charset; import java.nio.charset.Charset;
@ -38,33 +38,33 @@ import generic.jar.ResourceFile;
import ghidra.app.script.*; import ghidra.app.script.*;
public class BundleCompiler { public class BundleCompiler {
public static final String GENERATED_ACTIVATOR_CLASSNAME = "GeneratedActivator";
private BundleHost bh; private BundleHost bh;
public BundleCompiler(BundleHost bh) { static final String GENERATED_ACTIVATOR_CLASSNAME = "GeneratedActivator";
BundleCompiler(BundleHost bh) {
this.bh = bh; this.bh = bh;
} }
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); private JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
/** /**
* compile a source directory to an exploded bundle * compile a source directory to an exploded bundle
* *
* @param sbi the bundle info to build * @param sb the bundle info to build
* @param writer for updating the user during compilation * @param writer for updating the user during compilation
* @throws IOException for source/manifest file reading/generation and binary deletion/creation * @throws IOException for source/manifest file reading/generation and binary deletion/creation
* @throws OSGiException if generation of bundle metadata fails * @throws OSGiException if generation of bundle metadata fails
*/ */
public void compileToExplodedBundle(SourceBundleInfo sbi, PrintWriter writer) void compileToExplodedBundle(GhidraSourceBundle sb, PrintWriter writer)
throws IOException, OSGiException { throws IOException, OSGiException {
sbi.compileAttempted(); sb.compileAttempted();
sbi.setSummary(String.format("build %d files, skipping %d%s", sbi.getNewSourcesCount(), sb.setSummary(String.format("build %d files, skipping %d%s", sb.getNewSourcesCount(),
sbi.getFailingSourcesCount(), sbi.newManifestFile() ? ", new manifest" : "")); sb.getFailingSourcesCount(), sb.newManifestFile() ? ", new manifest" : ""));
ResourceFile srcdir = sbi.getSourceDir(); ResourceFile srcdir = sb.getSourceDir();
Path bindir = sbi.getBinDir(); Path bindir = sb.getBinDir();
Files.createDirectories(bindir); Files.createDirectories(bindir);
List<String> options = new ArrayList<>(); List<String> options = new ArrayList<>();
@ -79,7 +79,7 @@ public class BundleCompiler {
// final JavaFileManager rfm = new ResourceFileJavaFileManager(Collections.singletonList(bi.getSourceDir())); // final JavaFileManager rfm = new ResourceFileJavaFileManager(Collections.singletonList(bi.getSourceDir()));
final JavaFileManager rfm = final JavaFileManager rfm =
new ResourceFileJavaFileManager(Collections.singletonList(sbi.getSourceDir())); new ResourceFileJavaFileManager(Collections.singletonList(sb.getSourceDir()));
BundleJavaManager bjm = new BundleJavaManager(bh.getHostFramework(), rfm, options); BundleJavaManager bjm = new BundleJavaManager(bh.getHostFramework(), rfm, options);
// The phidias BundleJavaManager is for compiling from within a bundle -- it makes the // The phidias BundleJavaManager is for compiling from within a bundle -- it makes the
@ -88,7 +88,7 @@ public class BundleCompiler {
// XXX skip this if there's a source manifest, emit warnings about @imports // XXX skip this if there's a source manifest, emit warnings about @imports
// get wires for currently active bundles to satisfy all requirements // get wires for currently active bundles to satisfy all requirements
List<BundleRequirement> reqs = sbi.getAllReqs(); List<BundleRequirement> reqs = sb.getAllReqs();
List<BundleWiring> bundleWirings = bh.resolve(reqs); List<BundleWiring> bundleWirings = bh.resolve(reqs);
if (!reqs.isEmpty()) { if (!reqs.isEmpty()) {
@ -98,19 +98,19 @@ public class BundleCompiler {
writer.printf(" %s\n", req.toString()); writer.printf(" %s\n", req.toString());
} }
sbi.setSummary( sb.setSummary(
String.format("%d missing @import%s:", reqs.size(), reqs.size() > 1 ? "s" : "", String.format("%d missing @import%s:", reqs.size(), reqs.size() > 1 ? "s" : "",
reqs.stream().flatMap( reqs.stream().flatMap(
r -> OSGiUtils.extractPackages(r.toString()).stream()).distinct().collect( r -> OSGiUtils.extractPackages(r.toString()).stream()).distinct().collect(
Collectors.joining(",")))); Collectors.joining(","))));
} }
else { else {
sbi.setSummary(""); sb.setSummary("");
} }
// XXX add sources that will fail to call attention // XXX add sources that will fail to call attention
List<ResourceFile> newSource = sbi.getNewSources(); List<ResourceFile> newSource = sb.getNewSources();
for (BundleRequirement req : reqs) { for (BundleRequirement req : reqs) {
newSource.addAll(sbi.req2file.get(req.toString())); newSource.addAll(sb.req2file.get(req.toString()));
} }
// send the capabilities to phidias // send the capabilities to phidias
@ -142,7 +142,7 @@ public class BundleCompiler {
writer.write(err); writer.write(err);
ResourceFileJavaFileObject sf = (ResourceFileJavaFileObject) d.getSource(); ResourceFileJavaFileObject sf = (ResourceFileJavaFileObject) d.getSource();
ResourceFile rf = sf.getFile(); ResourceFile rf = sf.getFile();
sbi.buildError(rf, err); // remember all errors for this file sb.buildError(rf, err); // remember all errors for this file
if (sourceFiles.remove(sf)) { if (sourceFiles.remove(sf)) {
writer.printf("skipping %s\n", sf.toString()); writer.printf("skipping %s\n", sf.toString());
// if it's a script, mark it for having compile errors // if it's a script, mark it for having compile errors
@ -162,9 +162,8 @@ public class BundleCompiler {
} }
} }
// buildErrors is now up to date, set status // buildErrors is now up to date, set status
if (sbi.getFailingSourcesCount() > 0) { if (sb.getFailingSourcesCount() > 0) {
sbi.appendSummary( sb.appendSummary(String.format("%d failing source files", sb.getFailingSourcesCount()));
String.format("%d failing source files", sbi.getFailingSourcesCount()));
} }
ResourceFile smf = new ResourceFile(srcdir, "META-INF" + File.separator + "MANIFEST.MF"); ResourceFile smf = new ResourceFile(srcdir, "META-INF" + File.separator + "MANIFEST.MF");
@ -203,7 +202,7 @@ public class BundleCompiler {
manifest = analyzer.calcManifest(); manifest = analyzer.calcManifest();
} }
catch (Exception e) { catch (Exception e) {
sbi.appendSummary("bad manifest"); sb.appendSummary("bad manifest");
throw new OSGiException("failed to calculate manifest by analyzing code", e); throw new OSGiException("failed to calculate manifest by analyzing code", e);
} }
Attributes ma = manifest.getMainAttributes(); Attributes ma = manifest.getMainAttributes();
@ -219,22 +218,24 @@ public class BundleCompiler {
} }
} }
catch (Exception e) { catch (Exception e) {
sbi.appendSummary("failed bnd analysis"); sb.appendSummary("failed bnd analysis");
throw new OSGiException("failed to query classes while searching for activator", e); throw new OSGiException("failed to query classes while searching for activator", e);
} }
if (activator_classname == null) { if (activator_classname == null) {
activator_classname = GENERATED_ACTIVATOR_CLASSNAME; activator_classname = GENERATED_ACTIVATOR_CLASSNAME;
if (!buildDefaultActivator(bindir, activator_classname, writer)) { if (!buildDefaultActivator(bindir, activator_classname, writer)) {
sbi.appendSummary("failed to build generated activator"); sb.appendSummary("failed to build generated activator");
return; return;
} }
// since we add the activator after bndtools built the imports, we should add its imports too // since we add the activator after bndtools built the imports, we should add its imports too
String imps = ma.getValue(Constants.IMPORT_PACKAGE); String imps = ma.getValue(Constants.IMPORT_PACKAGE);
if (imps == null) { if (imps == null) {
ma.putValue(Constants.IMPORT_PACKAGE, "ghidra.app.script.osgi"); ma.putValue(Constants.IMPORT_PACKAGE,
GhidraBundleActivator.class.getPackageName());
} }
else { else {
ma.putValue(Constants.IMPORT_PACKAGE, imps + ",ghidra.app.script.osgi"); ma.putValue(Constants.IMPORT_PACKAGE,
imps + "," + GhidraBundleActivator.class.getPackageName());
} }
} }
ma.putValue(Constants.BUNDLE_ACTIVATOR, activator_classname); ma.putValue(Constants.BUNDLE_ACTIVATOR, activator_classname);
@ -265,7 +266,7 @@ public class BundleCompiler {
try (PrintWriter out = try (PrintWriter out =
new PrintWriter(Files.newBufferedWriter(activator_dest, Charset.forName("UTF-8")))) { new PrintWriter(Files.newBufferedWriter(activator_dest, Charset.forName("UTF-8")))) {
out.println("import ghidra.app.script.osgi.GhidraBundleActivator;"); out.println("import " + GhidraBundleActivator.class.getName() + ";");
out.println("import org.osgi.framework.BundleActivator;"); out.println("import org.osgi.framework.BundleActivator;");
out.println("import org.osgi.framework.BundleContext;"); out.println("import org.osgi.framework.BundleContext;");
out.println("public class " + GENERATED_ACTIVATOR_CLASSNAME + out.println("public class " + GENERATED_ACTIVATOR_CLASSNAME +

View file

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package ghidra.app.script.osgi; package ghidra.app.plugin.core.osgi;
import static java.util.stream.Collectors.*; import static java.util.stream.Collectors.*;
@ -35,15 +35,13 @@ import org.osgi.framework.launch.Framework;
import org.osgi.framework.wiring.*; import org.osgi.framework.wiring.*;
import org.osgi.service.log.*; import org.osgi.service.log.*;
import generic.io.NullPrintWriter;
import generic.jar.ResourceFile; import generic.jar.ResourceFile;
import ghidra.app.script.GhidraScriptUtil; import ghidra.app.script.GhidraScriptUtil;
import ghidra.util.exception.CancelledException; import ghidra.util.exception.CancelledException;
import ghidra.util.task.*; import ghidra.util.task.*;
// XXX this class should be part of a service/plugin
public class BundleHost { public class BundleHost {
// XXX embedded OSGi should be a service, but ScriptProviders don't have any way to access services // XXX ScriptProviders don't have any way to access services or other way to access system wide resources
static private BundleHost _instance; static private BundleHost _instance;
static public BundleHost getInstance() { static public BundleHost getInstance() {
@ -69,16 +67,28 @@ public class BundleHost {
} }
} }
// XXX this should be remembered in bundlehosts's savestate // XXX this should be remembered by bundlehosts's savestate
HashMap<ResourceFile, SourceBundleInfo> file2sbi = new HashMap<>(); HashMap<ResourceFile, GhidraBundle> file2sbi = new HashMap<>();
public SourceBundleInfo getSourceBundleInfo(ResourceFile sourceDir) { public GhidraBundle getGhidraBundle(ResourceFile path) {
return file2sbi.computeIfAbsent(sourceDir, sd -> new SourceBundleInfo(this, sd)); return file2sbi.computeIfAbsent(path, this::createGhidraBundle);
}
public GhidraBundle createGhidraBundle(ResourceFile path) {
switch (GhidraBundle.getType(path)) {
case SourceDir:
return new GhidraSourceBundle(this, path);
case BndScript:
case Jar:
default:
break;
}
return null;
} }
// XXX consumers must clean up after themselves // XXX consumers must clean up after themselves
public void removeSourceBundleInfo(ResourceFile sourceDir) { public void removeSourceBundleInfo(ResourceFile path) {
file2sbi.remove(sourceDir); file2sbi.remove(path);
} }
/** /**
@ -86,21 +96,16 @@ public class BundleHost {
* *
* @param imports Import-Package value * @param imports Import-Package value
* @return deduced requirements or null if there was an error * @return deduced requirements or null if there was an error
* @throws OSGiException on parse failure * @throws BundleException on parse failure
*/ */
static List<BundleRequirement> parseImports(String imports) throws OSGiException { static List<BundleRequirement> parseImports(String imports) throws BundleException {
// parse it with Felix's ManifestParser to a list of BundleRequirement objects // parse it with Felix's ManifestParser to a list of BundleRequirement objects
Map<String, Object> headerMap = new HashMap<>(); Map<String, Object> headerMap = new HashMap<>();
headerMap.put(Constants.IMPORT_PACKAGE, imports); headerMap.put(Constants.IMPORT_PACKAGE, imports);
ManifestParser mp; ManifestParser mp;
try { mp = new ManifestParser(null, null, null, headerMap);
mp = new ManifestParser(null, null, null, headerMap); return mp.getRequirements();
return mp.getRequirements();
}
catch (org.osgi.framework.BundleException e) {
throw new OSGiException("parsing Import-Package: " + imports, e);
}
} }
/** /**
@ -415,6 +420,14 @@ public class BundleHost {
} }
} }
public void deactivateSynchronously(String bundleLoc)
throws GhidraBundleException, InterruptedException {
Bundle b = getBundle(bundleLoc);
if (b != null) {
deactivateSynchronously(b);
}
}
void forceStopFelix() { void forceStopFelix() {
Task t = new Task("killing felix", false, false, true, true) { Task t = new Task("killing felix", false, false, true, true) {
@Override @Override
@ -568,76 +581,9 @@ public class BundleHost {
} }
} }
/**
* compile a source bundle if it's out of sync
*
* @param sbi the bundle info
* @param writer where to write issues
* @throws OSGiException if bundle operations fail
* @throws IOException if there are issues with the contents of the bundle
* @return the activated bundle
* @throws InterruptedException if interrupted while waiting for bundle state change
*/
public boolean compileSourceBundle(SourceBundleInfo sbi, PrintWriter writer)
throws OSGiException, IOException, InterruptedException {
if (writer == null) {
writer = new NullPrintWriter();
}
boolean needsCompile = false;
sbi.updateFromFilesystem(writer);
int failing = sbi.getFailingSourcesCount();
int newSourcecount = sbi.getNewSourcesCount();
long lastBundleActivation = 0; // XXX record last bundle activation in bundlestatusmodel
if (failing > 0 && (lastBundleActivation > sbi.getLastCompileAttempt())) {
needsCompile = true;
}
if (newSourcecount == 0) {
if (failing > 0) {
writer.printf("%s hasn't changed, with %d file%s failing in previous build(s):\n",
sbi.getSourceDir().toString(), failing, failing > 1 ? "s" : "");
writer.printf("%s\n", sbi.getPreviousBuildErrors());
}
if (sbi.newManifestFile()) {
needsCompile = true;
}
}
else {
needsCompile = true;
}
if (needsCompile) {
writer.printf("%d new files, %d skipped, %s\n", newSourcecount, failing,
sbi.newManifestFile() ? ", new manifest" : "");
// if there a bundle is currently active, uninstall it
Bundle b = sbi.getBundle();
if (b != null) {
deactivateSynchronously(b);
}
// once we've committed to recompile and regenerate generated classes, delete the old stuff
sbi.deleteOldBinaries();
BundleCompiler bundleCompiler = new BundleCompiler(this);
long startTime = System.nanoTime();
bundleCompiler.compileToExplodedBundle(sbi, writer);
long endTime = System.nanoTime();
writer.printf("%3.2f seconds compile time.\n", (endTime - startTime) / 1e9);
fireSourceBundleCompiled(sbi);
return true;
}
return false;
}
List<OSGiListener> osgiListeners = new ArrayList<>(); List<OSGiListener> osgiListeners = new ArrayList<>();
void fireSourceBundleCompiled(SourceBundleInfo sbi) { void fireSourceBundleCompiled(GhidraSourceBundle sbi) {
synchronized (osgiListeners) { synchronized (osgiListeners) {
for (OSGiListener l : osgiListeners) { for (OSGiListener l : osgiListeners) {
l.sourceBundleCompiled(sbi); l.sourceBundleCompiled(sbi);

View file

@ -13,67 +13,33 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package ghidra.app.plugin.core.script.osgi; package ghidra.app.plugin.core.osgi;
import java.io.File;
import generic.jar.ResourceFile; import generic.jar.ResourceFile;
import generic.util.Path; import generic.util.Path;
/** /**
* The BundlePath class represents the runtime state and user preferences for OSGi bundles in Ghidra. * The BundleStatus class represents the runtime state and user preferences for OSGi bundles in Ghidra.
*/ */
public class BundlePath extends Path { public class BundleStatus extends Path {
final Type type; final GhidraBundle.Type type;
boolean active = false; boolean active = false;
boolean busy = false; boolean busy = false;
String summary; String summary;
public static enum Type { public GhidraBundle.Type getType() {
BndScript, Jar, SourceDir, INVALID
}
static public Type getType(File f) {
if (f.isDirectory()) {
return Type.SourceDir;
}
String n = f.getName().toLowerCase();
if (n.endsWith(".bnd")) {
return Type.BndScript;
}
if (n.endsWith(".jar")) {
return Type.Jar;
}
return Type.INVALID;
}
static public Type getType(ResourceFile rf) {
if (rf.isDirectory()) {
return Type.SourceDir;
}
String n = rf.getName().toLowerCase();
if (n.endsWith(".bnd")) {
return Type.BndScript;
}
if (n.endsWith(".jar")) {
return Type.Jar;
}
return Type.INVALID;
}
public Type getType() {
return type; return type;
} }
BundlePath(String path, boolean enabled, boolean readonly) { BundleStatus(String path, boolean enabled, boolean readonly) {
super(path, enabled, false /*editable */, readonly); super(path, enabled, false /*editable */, readonly);
type = getType(getPath()); type = GhidraBundle.getType(getPath());
} }
BundlePath(ResourceFile path, boolean enabled, boolean readonly) { BundleStatus(ResourceFile path, boolean enabled, boolean readonly) {
super(path, enabled, false /* editable */, readonly); super(path, enabled, false /* editable */, readonly);
type = getType(getPath()); type = GhidraBundle.getType(getPath());
} }
public boolean isDirectory() { public boolean isDirectory() {
@ -109,4 +75,8 @@ public class BundlePath extends Path {
return summary; return summary;
} }
public boolean pathExists() {
return exists();
}
} }

View file

@ -14,16 +14,23 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package ghidra.app.plugin.core.script.osgi; package ghidra.app.plugin.core.osgi;
public interface BundleStatusListener { public interface BundleStatusListener {
/** /**
* Called when the list of bundle paths changes * Called when the list of bundle paths changes
*/ */
public void bundlesChanged(); default public void bundlesChanged() {
//
}
default public void bundleEnablementChanged(BundleStatus status, boolean newValue) {
//
}
default public void bundleActivationChanged(BundleStatus status, boolean newValue) {
//
}
public void bundleEnablementChanged(BundlePath path, boolean newValue);
public void bundleActivationChanged(BundlePath path, boolean newValue);
} }

View file

@ -14,7 +14,7 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package ghidra.app.plugin.core.script.osgi; package ghidra.app.plugin.core.osgi;
import java.io.File; import java.io.File;
import java.util.*; import java.util.*;
@ -25,12 +25,11 @@ import org.osgi.framework.Bundle;
import docking.widgets.table.AbstractSortedTableModel; import docking.widgets.table.AbstractSortedTableModel;
import generic.jar.ResourceFile; import generic.jar.ResourceFile;
import ghidra.app.script.GhidraScriptUtil; import ghidra.app.script.GhidraScriptUtil;
import ghidra.app.script.osgi.*;
import ghidra.framework.options.SaveState; import ghidra.framework.options.SaveState;
import ghidra.framework.preferences.Preferences; import ghidra.framework.preferences.Preferences;
import ghidra.util.Msg; import ghidra.util.Msg;
public class BundleStatusModel extends AbstractSortedTableModel<BundlePath> { public class BundleStatusModel extends AbstractSortedTableModel<BundleStatus> {
List<Column> columns = new ArrayList<>(); List<Column> columns = new ArrayList<>();
class Column { class Column {
@ -45,15 +44,15 @@ public class BundleStatusModel extends AbstractSortedTableModel<BundlePath> {
this.clazz = clazz; this.clazz = clazz;
} }
boolean editable(BundlePath path) { boolean editable(BundleStatus status) {
return false; return false;
} }
Object getValue(BundlePath path) { Object getValue(BundleStatus status) {
return null; return null;
} }
void setValue(BundlePath path, Object aValue) { void setValue(BundleStatus status, Object aValue) {
throw new RuntimeException(name + " is not editable!"); throw new RuntimeException(name + " is not editable!");
} }
@ -61,55 +60,55 @@ public class BundleStatusModel extends AbstractSortedTableModel<BundlePath> {
Column enabledColumn = new Column("Enabled", Boolean.class) { Column enabledColumn = new Column("Enabled", Boolean.class) {
@Override @Override
boolean editable(BundlePath path) { boolean editable(BundleStatus status) {
return path.exists(); return status.pathExists();
} }
@Override @Override
Object getValue(BundlePath path) { Object getValue(BundleStatus status) {
return path.isEnabled(); return status.isEnabled();
} }
@Override @Override
void setValue(BundlePath path, Object newValue) { void setValue(BundleStatus status, Object newValue) {
path.setEnabled((Boolean) newValue); status.setEnabled((Boolean) newValue);
fireBundleEnablementChanged(path, (Boolean) newValue); fireBundleEnablementChanged(status, (Boolean) newValue);
} }
}; };
Column activeColumn = new Column("Active", Boolean.class) { Column activeColumn = new Column("Active", Boolean.class) {
@Override @Override
boolean editable(BundlePath path) { boolean editable(BundleStatus status) {
return path.exists(); // XXX maybe only if it's already enabled return status.pathExists(); // XXX maybe only if it's already enabled
} }
@Override @Override
Object getValue(BundlePath path) { Object getValue(BundleStatus status) {
return path.isActive(); return status.isActive();
} }
@Override @Override
void setValue(BundlePath path, Object newValue) { void setValue(BundleStatus status, Object newValue) {
path.setActive((Boolean) newValue); status.setActive((Boolean) newValue);
fireBundleActivationChanged(path, (Boolean) newValue); fireBundleActivationChanged(status, (Boolean) newValue);
} }
}; };
Column typeColumn = new Column("Type", String.class) { Column typeColumn = new Column("Type", String.class) {
@Override @Override
Object getValue(BundlePath path) { Object getValue(BundleStatus status) {
return path.getType().toString(); return status.getType().toString();
} }
}; };
Column pathColumn = new Column("Path", BundlePath.class) { Column pathColumn = new Column("Path", String.class) {
@Override @Override
Object getValue(BundlePath path) { Object getValue(BundleStatus status) {
return path; return status.getPath().toString();
} }
}; };
Column summaryColumn = new Column("Summary", BundlePath.class) { Column summaryColumn = new Column("Summary", String.class) {
@Override @Override
Object getValue(BundlePath path) { Object getValue(BundleStatus status) {
return path.getSummary(); return status.getSummary();
} }
}; };
@ -127,41 +126,29 @@ public class BundleStatusModel extends AbstractSortedTableModel<BundlePath> {
} }
private BundleStatusProvider provider; private BundleStatusProvider provider;
private List<BundlePath> paths; private List<BundleStatus> statuses;
private BundleHost bundleHost; private BundleHost bundleHost;
OSGiListener bundleListener; OSGiListener bundleListener;
private Map<String, BundlePath> loc2bp = new HashMap<>(); private Map<String, BundleStatus> loc2status = new HashMap<>();
BundlePath getPath(String bundleLocation) { BundleStatus getStatus(String bundleLocation) {
return loc2bp.get(bundleLocation); return loc2status.get(bundleLocation);
} }
public String getBundleLoc(BundlePath bp) { public String getBundleLoc(BundleStatus status) {
switch (bp.getType()) { return bundleHost.getGhidraBundle(status.getPath()).getBundleLoc();
case Jar:
return bp.getPath().getAbsolutePath();
case SourceDir:
SourceBundleInfo bi = bundleHost.getSourceBundleInfo(bp.getPath());
return bi.getBundleLoc();
case BndScript:
// XXX
case INVALID:
default:
break;
}
return null;
} }
/** /**
* (re)compute cached mapping from bundleloc to bundlepath * (re)compute cached mapping from bundleloc to bundlepath
*/ */
private void computeCache() { private void computeCache() {
loc2bp.clear(); loc2status.clear();
for (BundlePath bp : paths) { for (BundleStatus status : statuses) {
String loc = getBundleLoc(bp); String loc = getBundleLoc(status);
if (loc != null) { if (loc != null) {
loc2bp.put(loc, bp); loc2status.put(loc, status);
} }
} }
} }
@ -172,18 +159,20 @@ public class BundleStatusModel extends AbstractSortedTableModel<BundlePath> {
this.bundleHost = bundleHost; this.bundleHost = bundleHost;
// add unmodifiable paths // add unmodifiable paths
this.paths = GhidraScriptUtil.getSystemScriptPaths().stream().distinct().map( this.statuses = GhidraScriptUtil.getSystemScriptPaths().stream().distinct().map(
f -> new BundlePath(f, true, true)).collect(Collectors.toList()); f -> new BundleStatus(f, true, true)).collect(Collectors.toList());
// add user path // add user path
this.paths.add(0, new BundlePath(GhidraScriptUtil.getUserScriptDirectory(), true, false)); this.statuses.add(0,
new BundleStatus(GhidraScriptUtil.getUserScriptDirectory(), true, false));
computeCache(); computeCache();
bundleHost.addListener(bundleListener = new OSGiListener() { bundleHost.addListener(bundleListener = new OSGiListener() {
@Override @Override
public void sourceBundleCompiled(SourceBundleInfo sbi) { public void sourceBundleCompiled(GhidraSourceBundle sb) {
BundlePath bp = getPath(sbi.getBundleLoc()); BundleStatus bp = getStatus(sb.getBundleLoc());
if (bp != null) { if (bp != null) {
bp.setSummary(sbi.getSummary()); bp.setSummary(sb.getSummary());
int row = getRowIndex(bp); int row = getRowIndex(bp);
fireTableRowsUpdated(row, row); fireTableRowsUpdated(row, row);
} }
@ -191,7 +180,7 @@ public class BundleStatusModel extends AbstractSortedTableModel<BundlePath> {
@Override @Override
public void bundleActivationChange(Bundle b, boolean newActivation) { public void bundleActivationChange(Bundle b, boolean newActivation) {
BundlePath bp = getPath(b.getLocation()); BundleStatus bp = getStatus(b.getLocation());
if (newActivation) { if (newActivation) {
if (bp != null) { if (bp != null) {
bp.setActive(true); bp.setActive(true);
@ -219,71 +208,70 @@ public class BundleStatusModel extends AbstractSortedTableModel<BundlePath> {
bundleHost.removeListener(bundleListener); bundleHost.removeListener(bundleListener);
} }
void clear() { public List<ResourceFile> getEnabledPaths() {
paths.clear();
}
List<BundlePath> getAllPaths() {
return new ArrayList<BundlePath>(paths);
}
public List<ResourceFile> getPaths() {
List<ResourceFile> list = new ArrayList<>(); List<ResourceFile> list = new ArrayList<>();
for (BundlePath path : paths) { for (BundleStatus status : statuses) {
if (path.isEnabled()) { if (status.isEnabled()) {
list.add(path.getPath()); list.add(status.getPath());
} }
} }
return list; return list;
} }
private void addPath(BundlePath path) { private void addStatus(BundleStatus path) {
if (paths.contains(path)) { if (statuses.contains(path)) {
return; return;
} }
String loc = getBundleLoc(path); String loc = getBundleLoc(path);
if (loc != null) { if (loc != null) {
loc2bp.put(loc, path); loc2status.put(loc, path);
} }
int index = paths.size(); int index = statuses.size();
paths.add(path); statuses.add(path);
fireTableRowsInserted(index, index); fireTableRowsInserted(index, index);
} }
private BundlePath addNewPath(ResourceFile path, boolean enabled, boolean readonly) { private BundleStatus addNewStatus(ResourceFile path, boolean enabled, boolean readonly) {
BundlePath p = new BundlePath(path, enabled, readonly); BundleStatus p = new BundleStatus(path, enabled, readonly);
addPath(p); addStatus(p);
return p; return p;
} }
private BundlePath addNewPath(String path, boolean enabled, boolean readonly) { private BundleStatus addNewStatus(String path, boolean enabled, boolean readonly) {
BundlePath p = new BundlePath(path, enabled, readonly); BundleStatus p = new BundleStatus(path, enabled, readonly);
addPath(p); addStatus(p);
return p; return p;
} }
/**
* create new BundleStatus objects for each of the given files
*
* @param files the files.. given...
* @param enabled mark them all as enabled
* @param readonly mark them all as readonly
*/
void addNewPaths(List<File> files, boolean enabled, boolean readonly) { void addNewPaths(List<File> files, boolean enabled, boolean readonly) {
for (File f : files) { for (File f : files) {
BundlePath p = new BundlePath(new ResourceFile(f), enabled, readonly); BundleStatus status = new BundleStatus(new ResourceFile(f), enabled, readonly);
addPath(p); addStatus(status);
} }
fireBundlesChanged(); fireBundlesChanged();
} }
void remove(int[] selectedRows) { void remove(int[] selectedRows) {
List<BundlePath> list = new ArrayList<>(); List<BundleStatus> toRemove = new ArrayList<>();
for (int selectedRow : selectedRows) { for (int selectedRow : selectedRows) {
list.add(paths.get(selectedRow)); toRemove.add(statuses.get(selectedRow));
} }
for (BundlePath path : list) { for (BundleStatus status : toRemove) {
if (!path.isReadOnly()) { if (!status.isReadOnly()) {
paths.remove(path); statuses.remove(status);
loc2bp.remove(getBundleLoc(path)); loc2status.remove(getBundleLoc(status));
} }
else { else {
Msg.showInfo(this, this.provider.getComponent(), "Unabled to remove path", Msg.showInfo(this, this.provider.getComponent(), "Unabled to remove path",
"System path cannot be removed: " + path.toString()); "System path cannot be removed: " + status.toString());
} }
} }
fireTableDataChanged(); fireTableDataChanged();
@ -299,7 +287,7 @@ public class BundleStatusModel extends AbstractSortedTableModel<BundlePath> {
@Override @Override
public int getRowCount() { public int getRowCount() {
return paths.size(); return statuses.size();
} }
@Override @Override
@ -309,8 +297,8 @@ public class BundleStatusModel extends AbstractSortedTableModel<BundlePath> {
@Override @Override
public boolean isCellEditable(int rowIndex, int columnIndex) { public boolean isCellEditable(int rowIndex, int columnIndex) {
BundlePath path = paths.get(rowIndex); BundleStatus status = statuses.get(rowIndex);
return getColumn(columnIndex).editable(path); return getColumn(columnIndex).editable(status);
} }
@Override @Override
@ -320,15 +308,15 @@ public class BundleStatusModel extends AbstractSortedTableModel<BundlePath> {
@Override @Override
public void setValueAt(Object aValue, int rowIndex, int columnIndex) { public void setValueAt(Object aValue, int rowIndex, int columnIndex) {
BundlePath path = paths.get(rowIndex); BundleStatus status = statuses.get(rowIndex);
getColumn(columnIndex).setValue(path, aValue); getColumn(columnIndex).setValue(status, aValue);
// XXX: preclude RowObjectSelectionManager.repairSelection // XXX: avoid RowObjectSelectionManager.repairSelection by selecting the row we're editing
provider.selectRow(rowIndex); provider.selectRow(rowIndex);
} }
@Override @Override
public Object getColumnValueForRow(BundlePath path, int columnIndex) { public Object getColumnValueForRow(BundleStatus status, int columnIndex) {
return getColumn(columnIndex).getValue(path); return getColumn(columnIndex).getValue(status);
} }
@Override @Override
@ -338,12 +326,12 @@ public class BundleStatusModel extends AbstractSortedTableModel<BundlePath> {
@Override @Override
public String getName() { public String getName() {
return "BundlePathManagerModel"; return BundleStatusModel.class.getSimpleName();
} }
@Override @Override
public List<BundlePath> getModelData() { public List<BundleStatus> getModelData() {
return paths; return statuses;
} }
/** /**
@ -353,7 +341,7 @@ public class BundleStatusModel extends AbstractSortedTableModel<BundlePath> {
*/ */
public boolean enablePath(ResourceFile file) { public boolean enablePath(ResourceFile file) {
ResourceFile dir = file.isDirectory() ? file : file.getParentFile(); ResourceFile dir = file.isDirectory() ? file : file.getParentFile();
for (BundlePath path : getAllPaths()) { for (BundleStatus path : statuses) {
if (path.getPath().equals(dir)) { if (path.getPath().equals(dir)) {
if (!path.isEnabled()) { if (!path.isEnabled()) {
path.setEnabled(true); path.setEnabled(true);
@ -364,7 +352,7 @@ public class BundleStatusModel extends AbstractSortedTableModel<BundlePath> {
return false; return false;
} }
} }
addNewPath(dir, true, false); addNewStatus(dir, true, false);
Preferences.setProperty(BundleStatusProvider.preferenceForLastSelectedBundle, Preferences.setProperty(BundleStatusProvider.preferenceForLastSelectedBundle,
dir.getAbsolutePath()); dir.getAbsolutePath());
fireBundlesChanged(); fireBundlesChanged();
@ -377,8 +365,8 @@ public class BundleStatusModel extends AbstractSortedTableModel<BundlePath> {
* @return true if the bundle is managed and not marked readonly * @return true if the bundle is managed and not marked readonly
*/ */
public boolean isWriteable(ResourceFile bundle) { public boolean isWriteable(ResourceFile bundle) {
Optional<BundlePath> o = paths.stream().filter( Optional<BundleStatus> o = statuses.stream().filter(
bp -> bp.isDirectory() && bp.getPath().equals(bundle)).findFirst(); status -> status.isDirectory() && status.getPath().equals(bundle)).findFirst();
return o.isPresent() && !o.get().isReadOnly(); return o.isPresent() && !o.get().isReadOnly();
} }
@ -387,10 +375,10 @@ public class BundleStatusModel extends AbstractSortedTableModel<BundlePath> {
* *
* each path is marked editable and non-readonly * each path is marked editable and non-readonly
* *
* @param testingPaths the paths to use * @param testingPaths the statuses to use
*/ */
public void setPathsForTesting(List<String> testingPaths) { public void setPathsForTesting(List<String> testingPaths) {
this.paths = testingPaths.stream().map(f -> new BundlePath(f, true, false)).collect( this.statuses = testingPaths.stream().map(f -> new BundleStatus(f, true, false)).collect(
Collectors.toList()); Collectors.toList());
computeCache(); computeCache();
fireTableDataChanged(); fireTableDataChanged();
@ -403,7 +391,7 @@ public class BundleStatusModel extends AbstractSortedTableModel<BundlePath> {
* @param path the path to insert * @param path the path to insert
*/ */
public void insertPathForTesting(String path) { public void insertPathForTesting(String path) {
addNewPath(path, true, false); addNewStatus(path, true, false);
} }
private ArrayList<BundleStatusListener> listeners = new ArrayList<>(); private ArrayList<BundleStatusListener> listeners = new ArrayList<>();
@ -430,7 +418,7 @@ public class BundleStatusModel extends AbstractSortedTableModel<BundlePath> {
} }
} }
void fireBundleEnablementChanged(BundlePath path, boolean newValue) { void fireBundleEnablementChanged(BundleStatus path, boolean newValue) {
synchronized (listeners) { synchronized (listeners) {
for (BundleStatusListener listener : listeners) { for (BundleStatusListener listener : listeners) {
listener.bundleEnablementChanged(path, newValue); listener.bundleEnablementChanged(path, newValue);
@ -438,7 +426,7 @@ public class BundleStatusModel extends AbstractSortedTableModel<BundlePath> {
} }
} }
void fireBundleActivationChanged(BundlePath path, boolean newValue) { void fireBundleActivationChanged(BundleStatus path, boolean newValue) {
synchronized (listeners) { synchronized (listeners) {
for (BundleStatusListener listener : listeners) { for (BundleStatusListener listener : listeners) {
listener.bundleActivationChanged(path, newValue); listener.bundleActivationChanged(path, newValue);
@ -447,7 +435,7 @@ public class BundleStatusModel extends AbstractSortedTableModel<BundlePath> {
} }
/** /**
* Restores the paths from the specified SaveState object. * Restores the statuses from the specified SaveState object.
* @param ss the SaveState object * @param ss the SaveState object
*/ */
public void restoreState(SaveState ss) { public void restoreState(SaveState ss) {
@ -460,49 +448,47 @@ public class BundleStatusModel extends AbstractSortedTableModel<BundlePath> {
boolean[] enableArr = ss.getBooleans("BundleStatus_ENABLE", new boolean[pathArr.length]); boolean[] enableArr = ss.getBooleans("BundleStatus_ENABLE", new boolean[pathArr.length]);
boolean[] readonlyArr = ss.getBooleans("BundleStatus_READ", new boolean[pathArr.length]); boolean[] readonlyArr = ss.getBooleans("BundleStatus_READ", new boolean[pathArr.length]);
List<BundlePath> currentPaths = getAllPaths(); List<BundleStatus> currentPaths = new ArrayList<>(statuses);
clear(); statuses.clear();
for (int i = 0; i < pathArr.length; i++) { for (int i = 0; i < pathArr.length; i++) {
BundlePath currentPath = getPath(pathArr[i], currentPaths); BundleStatus currentStatus = getStatus(pathArr[i], currentPaths);
if (currentPath != null) { if (currentStatus != null) {
currentPaths.remove(currentPath); currentPaths.remove(currentStatus);
addNewPath(pathArr[i], enableArr[i], readonlyArr[i]); addNewStatus(pathArr[i], enableArr[i], readonlyArr[i]);
} }
else if (!readonlyArr[i]) { else if (!readonlyArr[i]) {
// skip read-only paths which are not present in the current config // skip read-only statuses which are not present in the current config
// This is needed to thin-out old default entries // This is needed to thin-out old default entries
addNewPath(pathArr[i], enableArr[i], readonlyArr[i]); addNewStatus(pathArr[i], enableArr[i], readonlyArr[i]);
} }
} }
fireBundlesChanged(); fireBundlesChanged();
} }
private static BundlePath getPath(String filepath, List<BundlePath> paths) { private static BundleStatus getStatus(String filepath, List<BundleStatus> statuses) {
for (BundlePath path : paths) { for (BundleStatus status : statuses) {
if (filepath.equals(path.getPathAsString())) { if (filepath.equals(status.getPathAsString())) {
return path; return status;
} }
} }
return null; return null;
} }
/** /**
* Saves the paths to the specified SaveState object. * Saves the statuses to the specified SaveState object.
* @param ss the SaveState object * @param ss the SaveState object
*/ */
public void saveState(SaveState ss) { public void saveState(SaveState ss) {
List<BundlePath> currentPaths = getAllPaths(); String[] pathArr = new String[statuses.size()];
boolean[] enableArr = new boolean[statuses.size()];
String[] pathArr = new String[currentPaths.size()]; boolean[] readonlyArr = new boolean[statuses.size()];
boolean[] enableArr = new boolean[currentPaths.size()];
boolean[] readonlyArr = new boolean[currentPaths.size()];
int index = 0; int index = 0;
for (BundlePath path : currentPaths) { for (BundleStatus status : statuses) {
pathArr[index] = path.getPathAsString(); pathArr[index] = status.getPathAsString();
enableArr[index] = path.isEnabled(); enableArr[index] = status.isEnabled();
readonlyArr[index] = path.isReadOnly(); readonlyArr[index] = status.isReadOnly();
++index; ++index;
} }

View file

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package ghidra.app.plugin.core.script.osgi; package ghidra.app.plugin.core.osgi;
import java.awt.*; import java.awt.*;
import java.io.File; import java.io.File;
@ -27,12 +27,15 @@ import javax.swing.table.TableColumn;
import docking.widgets.filechooser.GhidraFileChooser; import docking.widgets.filechooser.GhidraFileChooser;
import docking.widgets.filechooser.GhidraFileChooserMode; import docking.widgets.filechooser.GhidraFileChooserMode;
import docking.widgets.table.*; import docking.widgets.table.*;
import ghidra.app.script.osgi.BundleHost; import ghidra.app.services.ConsoleService;
import ghidra.framework.plugintool.ComponentProviderAdapter; import ghidra.framework.plugintool.ComponentProviderAdapter;
import ghidra.framework.plugintool.PluginTool; import ghidra.framework.plugintool.PluginTool;
import ghidra.framework.preferences.Preferences; import ghidra.framework.preferences.Preferences;
import ghidra.util.Msg;
import ghidra.util.exception.CancelledException;
import ghidra.util.filechooser.GhidraFileChooserModel; import ghidra.util.filechooser.GhidraFileChooserModel;
import ghidra.util.filechooser.GhidraFileFilter; import ghidra.util.filechooser.GhidraFileFilter;
import ghidra.util.task.*;
import resources.ResourceManager; import resources.ResourceManager;
/** /**
@ -49,14 +52,29 @@ public class BundleStatusProvider extends ComponentProviderAdapter {
private Color selectionColor; private Color selectionColor;
private GhidraFileChooser fileChooser; private GhidraFileChooser fileChooser;
private GhidraFileFilter filter; private GhidraFileFilter filter;
private final BundleHost bundleHost;
public void notifyTableChanged() { public void notifyTableChanged() {
bundleStatusTable.notifyTableChanged(new TableModelEvent(bundleStatusModel)); bundleStatusTable.notifyTableChanged(new TableModelEvent(bundleStatusModel));
} }
public BundleStatusProvider(PluginTool tool, String owner, BundleHost bundleHost) { public BundleStatusProvider(PluginTool tool, String owner) {
super(tool, "Bundle Status Manager", owner); super(tool, "Bundle Status Manager", owner);
this.bundleHost = BundleHost.getInstance();
this.bundleStatusModel = new BundleStatusModel(this, bundleHost); this.bundleStatusModel = new BundleStatusModel(this, bundleHost);
bundleStatusModel.addListener(new BundleStatusListener() {
@Override
public void bundleEnablementChanged(BundleStatus status, boolean enabled) {
if (!enabled && status.isActive()) {
startActivateDeactiveTask(status, false);
}
}
@Override
public void bundleActivationChanged(BundleStatus status, boolean newValue) {
startActivateDeactiveTask(status, newValue);
}
});
this.filter = new GhidraFileFilter() { this.filter = new GhidraFileFilter() {
@Override @Override
@ -66,7 +84,7 @@ public class BundleStatusProvider extends ComponentProviderAdapter {
@Override @Override
public boolean accept(File path, GhidraFileChooserModel model) { public boolean accept(File path, GhidraFileChooserModel model) {
return BundlePath.getType(path) != BundlePath.Type.INVALID; return GhidraBundle.getType(path) != GhidraBundle.Type.INVALID;
} }
}; };
this.fileChooser = null; this.fileChooser = null;
@ -108,7 +126,7 @@ public class BundleStatusProvider extends ComponentProviderAdapter {
buttonPanel.add(removeButton, gbc); buttonPanel.add(removeButton, gbc);
bundleStatusTable = new GTable(bundleStatusModel); bundleStatusTable = new GTable(bundleStatusModel);
bundleStatusTable.setName("BUNDLEPATH_TABLE"); bundleStatusTable.setName("BUNDLESTATUS_TABLE");
bundleStatusTable.setSelectionBackground(selectionColor); bundleStatusTable.setSelectionBackground(selectionColor);
bundleStatusTable.setSelectionForeground(Color.BLACK); bundleStatusTable.setSelectionForeground(Color.BLACK);
bundleStatusTable.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION); bundleStatusTable.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
@ -133,9 +151,9 @@ public class BundleStatusProvider extends ComponentProviderAdapter {
column.setCellRenderer(new GBooleanCellRenderer() { column.setCellRenderer(new GBooleanCellRenderer() {
@Override @Override
public Component getTableCellRendererComponent(GTableCellRenderingData data) { public Component getTableCellRendererComponent(GTableCellRenderingData data) {
BundlePath path = (BundlePath) data.getRowObject(); BundleStatus status = (BundleStatus) data.getRowObject();
Component x = super.getTableCellRendererComponent(data); Component x = super.getTableCellRendererComponent(data);
if (path.getBusy()) { if (status.getBusy()) {
cb.setVisible(false); cb.setVisible(false);
cb.setEnabled(false); cb.setEnabled(false);
setHorizontalAlignment(SwingConstants.CENTER); setHorizontalAlignment(SwingConstants.CENTER);
@ -156,7 +174,7 @@ public class BundleStatusProvider extends ComponentProviderAdapter {
FontMetrics fontmetrics = panel.getFontMetrics(panel.getFont()); FontMetrics fontmetrics = panel.getFontMetrics(panel.getFont());
column.setMaxWidth(10 + column.setMaxWidth(10 +
SwingUtilities.computeStringWidth(fontmetrics, BundlePath.Type.SourceDir.toString())); SwingUtilities.computeStringWidth(fontmetrics, GhidraBundle.Type.SourceDir.toString()));
column = bundleStatusTable.getColumnModel().getColumn(bundleStatusModel.pathColumn.index); column = bundleStatusTable.getColumnModel().getColumn(bundleStatusModel.pathColumn.index);
column.setCellRenderer(new GTableCellRenderer() { column.setCellRenderer(new GTableCellRenderer() {
@ -164,15 +182,15 @@ public class BundleStatusProvider extends ComponentProviderAdapter {
public Component getTableCellRendererComponent(GTableCellRenderingData data) { public Component getTableCellRendererComponent(GTableCellRenderingData data) {
JLabel c = (JLabel) super.getTableCellRendererComponent(data); JLabel c = (JLabel) super.getTableCellRendererComponent(data);
BundlePath path = (BundlePath) data.getValue(); BundleStatus status = (BundleStatus) data.getValue();
if (!path.exists()) { if (!status.pathExists()) {
c.setForeground(Color.RED); c.setForeground(Color.RED);
} }
return c; return c;
} }
}); });
GTableFilterPanel<BundlePath> filterPanel = GTableFilterPanel<BundleStatus> filterPanel =
new GTableFilterPanel<>(bundleStatusTable, bundleStatusModel); new GTableFilterPanel<>(bundleStatusTable, bundleStatusModel);
JScrollPane scrollPane = new JScrollPane(bundleStatusTable); JScrollPane scrollPane = new JScrollPane(bundleStatusTable);
@ -272,6 +290,39 @@ public class BundleStatusProvider extends ComponentProviderAdapter {
bundleStatusTable.dispose(); bundleStatusTable.dispose();
} }
private void startActivateDeactiveTask(BundleStatus status, boolean activate) {
status.setBusy(true);
notifyTableChanged();
ConsoleService console = getTool().getService(ConsoleService.class);
new TaskLauncher(new Task((activate ? "Activating" : "Deactivating ") + " bundle...") {
@Override
public void run(TaskMonitor monitor) throws CancelledException {
try {
GhidraBundle sb = bundleHost.getGhidraBundle(status.getPath());
if (activate) {
sb.build(console.getStdErr());
bundleHost.activateSynchronously(sb.getBundleLoc());
}
else { // deactivate
bundleHost.deactivateSynchronously(sb.getBundleLoc());
}
}
catch (Exception e) {
e.printStackTrace(console.getStdErr());
status.setActive(!activate);
Msg.showError(this, getComponent(), "bundle activation failed", e.getMessage());
}
finally {
status.setBusy(false);
notifyTableChanged();
}
}
}, null, 1000);
}
// XXX workaround for RowObjectSelection.. repair
void selectRow(int rowIndex) { void selectRow(int rowIndex) {
bundleStatusTable.selectRow(rowIndex); bundleStatusTable.selectRow(rowIndex);
} }

View file

@ -0,0 +1,61 @@
package ghidra.app.plugin.core.osgi;
import java.io.File;
import java.io.PrintWriter;
import org.osgi.framework.Bundle;
import generic.jar.ResourceFile;
public interface GhidraBundle {
enum Type {
BndScript, Jar, SourceDir, INVALID
}
/**
* attempt to build loadable bundle, if possible
*
* @param writer console for user messages
* @return true if build was successful
* @throws Exception XXX
*/
boolean build(PrintWriter writer) throws Exception;
String getBundleLoc();
Bundle getBundle() throws GhidraBundleException;
Bundle install() throws GhidraBundleException;
String getSummary();
static GhidraBundle.Type getType(ResourceFile rf) {
if (rf.isDirectory()) {
return GhidraBundle.Type.SourceDir;
}
String n = rf.getName().toLowerCase();
if (n.endsWith(".bnd")) {
return GhidraBundle.Type.BndScript;
}
if (n.endsWith(".jar")) {
return GhidraBundle.Type.Jar;
}
return GhidraBundle.Type.INVALID;
}
static public GhidraBundle.Type getType(File f) {
if (f.isDirectory()) {
return GhidraBundle.Type.SourceDir;
}
String n = f.getName().toLowerCase();
if (n.endsWith(".bnd")) {
return GhidraBundle.Type.BndScript;
}
if (n.endsWith(".jar")) {
return GhidraBundle.Type.Jar;
}
return GhidraBundle.Type.INVALID;
}
}

View file

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package ghidra.app.script.osgi; package ghidra.app.plugin.core.osgi;
import org.osgi.framework.BundleActivator; import org.osgi.framework.BundleActivator;
import org.osgi.framework.BundleContext; import org.osgi.framework.BundleContext;

View file

@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package ghidra.app.script.osgi; package ghidra.app.plugin.core.osgi;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@ -51,9 +51,9 @@ public class GhidraBundleException extends OSGiException {
BundleException be = (BundleException) e; BundleException be = (BundleException) e;
switch (be.getType()) { switch (be.getType()) {
default: default:
case BundleException.UNSPECIFIED:
return "No exception type"; return "No exception type";
case BundleException.UNSPECIFIED:
return "UNSPECIFIED";
/** /**
* The operation was unsupported. This type can be used anywhere a * The operation was unsupported. This type can be used anywhere a
* BundleException can be thrown. * BundleException can be thrown.

View file

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package ghidra.app.script.osgi; package ghidra.app.plugin.core.osgi;
import java.io.*; import java.io.*;
import java.nio.file.Files; import java.nio.file.Files;
@ -22,20 +22,23 @@ import java.util.*;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import org.osgi.framework.Bundle; import org.osgi.framework.Bundle;
import org.osgi.framework.BundleException;
import org.osgi.framework.wiring.BundleRequirement; import org.osgi.framework.wiring.BundleRequirement;
import generic.io.NullPrintWriter;
import generic.jar.ResourceFile; import generic.jar.ResourceFile;
import ghidra.app.plugin.core.osgi.BundleHost.BuildFailure;
import ghidra.app.script.GhidraScriptUtil; import ghidra.app.script.GhidraScriptUtil;
import ghidra.app.script.ScriptInfo; import ghidra.app.script.ScriptInfo;
import ghidra.app.script.osgi.BundleHost.BuildFailure;
/** /**
* The SourceBundleInfo class is a cache of information for bundles built from source directories. * The SourceBundleInfo class is a cache of information for bundles built from source directories.
*/ */
public class SourceBundleInfo { public class GhidraSourceBundle implements GhidraBundle {
private final BundleHost bundleHost; final private BundleHost bundleHost;
final private ResourceFile sourceDir; final private ResourceFile sourceDir;
final String symbolicName;
final private String symbolicName;
final private Path binDir; final private Path binDir;
final private String bundleLoc; final private String bundleLoc;
boolean foundNewManifest; boolean foundNewManifest;
@ -51,9 +54,9 @@ public class SourceBundleInfo {
// cached values parsed form @imports tags on default-package source files // cached values parsed form @imports tags on default-package source files
public SourceBundleInfo(BundleHost bundleHost, ResourceFile sourceDir) { public GhidraSourceBundle(BundleHost bundleHost, ResourceFile sourceDirectory) {
this.bundleHost = bundleHost; this.bundleHost = bundleHost;
this.sourceDir = sourceDir; this.sourceDir = sourceDirectory;
this.symbolicName = BundleHost.getSymbolicNameFromSourceDir(sourceDir); this.symbolicName = BundleHost.getSymbolicNameFromSourceDir(sourceDir);
this.binDir = GhidraScriptUtil.getCompiledBundlesDir().resolve(symbolicName); this.binDir = GhidraScriptUtil.getCompiledBundlesDir().resolve(symbolicName);
@ -61,6 +64,12 @@ public class SourceBundleInfo {
} }
/**
* for testing only!!!
*
* @param sourceFile a ghidra script file
* @return the directory its class is compiled to
*/
static public Path getBindirFromScriptFile(ResourceFile sourceFile) { static public Path getBindirFromScriptFile(ResourceFile sourceFile) {
ResourceFile tmpSourceDir = sourceFile.getParentFile(); ResourceFile tmpSourceDir = sourceFile.getParentFile();
String tmpSymbolicName = BundleHost.getSymbolicNameFromSourceDir(tmpSourceDir); String tmpSymbolicName = BundleHost.getSymbolicNameFromSourceDir(tmpSourceDir);
@ -88,6 +97,7 @@ public class SourceBundleInfo {
return binDir; return binDir;
} }
@Override
public Bundle install() throws GhidraBundleException { public Bundle install() throws GhidraBundleException {
return bundleHost.installFromLoc(getBundleLoc()); return bundleHost.installFromLoc(getBundleLoc());
} }
@ -105,10 +115,9 @@ public class SourceBundleInfo {
/** /**
* update buildReqs based on \@imports tag in java files from the default package * update buildReqs based on \@imports tag in java files from the default package
* * @throws GhidraBundleException on failure to parse the \@imports tag
* @throws OSGiException on failure to parse the \@imports tag
*/ */
private void computeRequirements() throws OSGiException { private void computeRequirements() throws GhidraBundleException {
// parse metadata from all Java source in sourceDir // parse metadata from all Java source in sourceDir
buildReqs.clear(); buildReqs.clear();
req2file.clear(); req2file.clear();
@ -119,7 +128,13 @@ public class SourceBundleInfo {
ScriptInfo si = GhidraScriptUtil.getScriptInfo(rf); ScriptInfo si = GhidraScriptUtil.getScriptInfo(rf);
String imps = si.getImports(); String imps = si.getImports();
if (imps != null && !imps.isEmpty()) { if (imps != null && !imps.isEmpty()) {
List<BundleRequirement> reqs = BundleHost.parseImports(imps); List<BundleRequirement> reqs;
try {
reqs = BundleHost.parseImports(imps);
}
catch (BundleException e) {
throw new GhidraBundleException(getBundleLoc(), "parsing manifest", e);
}
buildReqs.put(rf, reqs); buildReqs.put(rf, reqs);
for (BundleRequirement req : reqs) { for (BundleRequirement req : reqs) {
req2file.computeIfAbsent(req.toString(), x -> new ArrayList<>()).add(rf); req2file.computeIfAbsent(req.toString(), x -> new ArrayList<>()).add(rf);
@ -235,16 +250,75 @@ public class SourceBundleInfo {
} }
} }
@Override
public String getSummary() { public String getSummary() {
return summary; return summary;
} }
public Bundle getBundle() { @Override
public Bundle getBundle() throws GhidraBundleException {
return bundleHost.getBundle(getBundleLoc()); return bundleHost.getBundle(getBundleLoc());
} }
@Override
public String getBundleLoc() { public String getBundleLoc() {
return bundleLoc; return bundleLoc;
} }
@Override
public boolean build(PrintWriter writer) throws Exception {
if (writer == null) {
writer = new NullPrintWriter();
}
boolean needsCompile = false;
updateFromFilesystem(writer);
int failing = getFailingSourcesCount();
int newSourcecount = getNewSourcesCount();
long lastBundleActivation = 0; // XXX record last bundle activation in bundlestatusmodel
if (failing > 0 && (lastBundleActivation > getLastCompileAttempt())) {
needsCompile = true;
}
if (newSourcecount == 0) {
if (failing > 0) {
writer.printf("%s hasn't changed, with %d file%s failing in previous build(s):\n",
getSourceDir().toString(), failing, failing > 1 ? "s" : "");
writer.printf("%s\n", getPreviousBuildErrors());
}
if (newManifestFile()) {
needsCompile = true;
}
}
else {
needsCompile = true;
}
if (needsCompile) {
writer.printf("%d new files, %d skipped, %s\n", newSourcecount, failing,
newManifestFile() ? ", new manifest" : "");
// if there a bundle is currently active, uninstall it
Bundle b = getBundle();
if (b != null) {
bundleHost.deactivateSynchronously(b);
}
// once we've committed to recompile and regenerate generated classes, delete the old stuff
deleteOldBinaries();
BundleCompiler bundleCompiler = new BundleCompiler(bundleHost);
long startTime = System.nanoTime();
bundleCompiler.compileToExplodedBundle(this, writer);
long endTime = System.nanoTime();
writer.printf("%3.2f seconds compile time.\n", (endTime - startTime) / 1e9);
bundleHost.fireSourceBundleCompiled(this);
return true;
}
return false;
}
} }

View file

@ -13,10 +13,10 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package ghidra.app.script.osgi; package ghidra.app.plugin.core.osgi;
import ghidra.util.exception.UsrException; import ghidra.util.exception.UsrException;
public class OSGiException extends UsrException { public class OSGiException extends UsrException {
public OSGiException(String msg, Throwable cause) { public OSGiException(String msg, Throwable cause) {
super(msg, cause); super(msg, cause);

View file

@ -1,10 +1,10 @@
package ghidra.app.script.osgi; package ghidra.app.plugin.core.osgi;
import org.osgi.framework.Bundle; import org.osgi.framework.Bundle;
public interface OSGiListener { public interface OSGiListener {
void sourceBundleCompiled(SourceBundleInfo sbi); void sourceBundleCompiled(GhidraSourceBundle sb);
void bundleActivationChange(Bundle b, boolean newActivation); void bundleActivationChange(Bundle b, boolean newActivation);
} }

View file

@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package ghidra.app.script.osgi; package ghidra.app.plugin.core.osgi;
import java.util.List; import java.util.List;
import java.util.Scanner; import java.util.Scanner;

View file

@ -29,7 +29,6 @@ import javax.swing.tree.TreePath;
import javax.swing.tree.TreeSelectionModel; import javax.swing.tree.TreeSelectionModel;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.osgi.framework.Bundle;
import docking.ActionContext; import docking.ActionContext;
import docking.action.KeyBindingData; import docking.action.KeyBindingData;
@ -40,10 +39,8 @@ import docking.widgets.tree.GTree;
import docking.widgets.tree.GTreeNode; import docking.widgets.tree.GTreeNode;
import docking.widgets.tree.support.BreadthFirstIterator; import docking.widgets.tree.support.BreadthFirstIterator;
import generic.jar.ResourceFile; import generic.jar.ResourceFile;
import ghidra.app.plugin.core.script.osgi.*; import ghidra.app.plugin.core.osgi.*;
import ghidra.app.script.*; import ghidra.app.script.*;
import ghidra.app.script.osgi.BundleHost;
import ghidra.app.script.osgi.SourceBundleInfo;
import ghidra.app.services.ConsoleService; import ghidra.app.services.ConsoleService;
import ghidra.framework.options.SaveState; import ghidra.framework.options.SaveState;
import ghidra.framework.plugintool.ComponentProviderAdapter; import ghidra.framework.plugintool.ComponentProviderAdapter;
@ -99,10 +96,9 @@ public class GhidraScriptComponentProvider extends ComponentProviderAdapter {
} }
}; };
GhidraScriptComponentProvider(GhidraScriptMgrPlugin plugin, BundleHost bundleHost) { GhidraScriptComponentProvider(GhidraScriptMgrPlugin plugin) {
super(plugin.getTool(), "Script Manager", plugin.getName()); super(plugin.getTool(), "Script Manager", plugin.getName());
this.plugin = plugin; this.plugin = plugin;
this.bundleHost = bundleHost;
setHelpLocation(new HelpLocation(plugin.getName(), plugin.getName())); setHelpLocation(new HelpLocation(plugin.getName(), plugin.getName()));
setIcon(ResourceManager.loadImage("images/play.png")); setIcon(ResourceManager.loadImage("images/play.png"));
@ -116,7 +112,6 @@ public class GhidraScriptComponentProvider extends ComponentProviderAdapter {
} }
void dispose() { void dispose() {
editorMap.clear(); editorMap.clear();
scriptCategoryTree.dispose(); scriptCategoryTree.dispose();
scriptTable.dispose(); scriptTable.dispose();
@ -138,7 +133,7 @@ public class GhidraScriptComponentProvider extends ComponentProviderAdapter {
} }
private void performRefresh() { private void performRefresh() {
GhidraScriptUtil.setScriptBundlePaths(bundleStatusProvider.getModel().getPaths()); GhidraScriptUtil.setScriptBundlePaths(bundleStatusProvider.getModel().getEnabledPaths());
GhidraScriptUtil.clearMetadata(); GhidraScriptUtil.clearMetadata();
refresh(); refresh();
} }
@ -341,7 +336,7 @@ public class GhidraScriptComponentProvider extends ComponentProviderAdapter {
} }
public List<ResourceFile> getScriptDirectories() { public List<ResourceFile> getScriptDirectories() {
return bundleStatusProvider.getModel().getPaths().stream().filter( return bundleStatusProvider.getModel().getEnabledPaths().stream().filter(
ResourceFile::isDirectory).collect(Collectors.toList()); ResourceFile::isDirectory).collect(Collectors.toList());
} }
@ -397,7 +392,7 @@ public class GhidraScriptComponentProvider extends ComponentProviderAdapter {
} }
void runScript(String scriptName, TaskListener listener) { void runScript(String scriptName, TaskListener listener) {
List<ResourceFile> dirPaths = bundleStatusProvider.getModel().getPaths(); List<ResourceFile> dirPaths = bundleStatusProvider.getModel().getEnabledPaths();
for (ResourceFile dir : dirPaths) { for (ResourceFile dir : dirPaths) {
ResourceFile scriptSource = new ResourceFile(dir, scriptName); ResourceFile scriptSource = new ResourceFile(dir, scriptName);
if (scriptSource.exists()) { if (scriptSource.exists()) {
@ -457,7 +452,8 @@ public class GhidraScriptComponentProvider extends ComponentProviderAdapter {
console.addErrorMessage("", "Unable to instantiate script: " + scriptName); console.addErrorMessage("", "Unable to instantiate script: " + scriptName);
} }
catch (ClassNotFoundException e) { catch (ClassNotFoundException e) {
console.addErrorMessage("", "Unable to locate script class: " + e.getMessage()); console.addErrorMessage("", "Unable to locate script class: " + scriptName);
e.printStackTrace();
} }
// show the error icon // show the error icon
@ -537,7 +533,7 @@ public class GhidraScriptComponentProvider extends ComponentProviderAdapter {
private void updateAvailableScriptFilesForAllPaths() { private void updateAvailableScriptFilesForAllPaths() {
List<ResourceFile> scriptsToRemove = tableModel.getScripts(); List<ResourceFile> scriptsToRemove = tableModel.getScripts();
List<ResourceFile> scriptAccumulator = new ArrayList<>(); List<ResourceFile> scriptAccumulator = new ArrayList<>();
List<ResourceFile> bundlePaths = bundleStatusProvider.getModel().getPaths(); List<ResourceFile> bundlePaths = bundleStatusProvider.getModel().getEnabledPaths();
for (ResourceFile bundlePath : bundlePaths) { for (ResourceFile bundlePath : bundlePaths) {
if (bundlePath.isDirectory()) { if (bundlePath.isDirectory()) {
updateAvailableScriptFilesForDirectory(scriptsToRemove, scriptAccumulator, updateAvailableScriptFilesForDirectory(scriptsToRemove, scriptAccumulator,
@ -732,56 +728,8 @@ public class GhidraScriptComponentProvider extends ComponentProviderAdapter {
return true; return true;
} }
final private BundleHost bundleHost;
void startActivateDeactiveTask(BundlePath path, boolean activate) {
path.setBusy(true);
bundleStatusProvider.notifyTableChanged();
ConsoleService console = plugin.getTool().getService(ConsoleService.class);
new TaskLauncher(new Task((activate ? "Activating" : "Deactivating ") + " bundle...") {
@Override
public void run(TaskMonitor monitor) throws CancelledException {
try {
if (activate) {
if (path.getType() == BundlePath.Type.SourceDir) {
SourceBundleInfo sbi = bundleHost.getSourceBundleInfo(path.getPath());
bundleHost.compileSourceBundle(sbi, console.getStdErr());
}
String loc = bundleStatusProvider.getModel().getBundleLoc(path);
if (loc != null) {
bundleHost.activateSynchronously(loc);
}
}
else { // deactivate
String loc = bundleStatusProvider.getModel().getBundleLoc(path);
if (loc != null) {
Bundle bundle = bundleHost.getBundle(loc);
if (bundle != null) {
bundleHost.deactivateSynchronously(bundle);
}
}
}
}
catch (Exception e) {
e.printStackTrace(console.getStdErr());
path.setActive(!activate);
Msg.showError(this, GhidraScriptComponentProvider.this.getComponent(),
"bundle activation failed", e.getMessage());
}
finally {
path.setBusy(false);
bundleStatusProvider.notifyTableChanged();
}
}
}, null, 1000);
}
private void build() { private void build() {
bundleStatusProvider = bundleStatusProvider = new BundleStatusProvider(plugin.getTool(), plugin.getName());
new BundleStatusProvider(plugin.getTool(), plugin.getName(), bundleHost);
bundleStatusProvider.getModel().addListener(new BundleStatusListener() { bundleStatusProvider.getModel().addListener(new BundleStatusListener() {
@Override @Override
@ -791,20 +739,11 @@ public class GhidraScriptComponentProvider extends ComponentProviderAdapter {
} }
@Override @Override
public void bundleEnablementChanged(BundlePath path, boolean enabled) { public void bundleEnablementChanged(BundleStatus status, boolean enabled) {
if (!enabled && path.isActive()) { if (status.isDirectory()) {
startActivateDeactiveTask(path, false);
}
if (path.isDirectory()) {
performRefresh(); performRefresh();
} }
} }
@Override
public void bundleActivationChanged(BundlePath path, boolean newValue) {
startActivateDeactiveTask(path, newValue);
}
}); });
scriptRoot = new RootNode(); scriptRoot = new RootNode();
@ -1075,7 +1014,7 @@ public class GhidraScriptComponentProvider extends ComponentProviderAdapter {
bundleStatusProvider.getModel().restoreState(saveState); bundleStatusProvider.getModel().restoreState(saveState);
// pull in the just-loaded paths // pull in the just-loaded paths
List<ResourceFile> paths = bundleStatusProvider.getModel().getPaths(); List<ResourceFile> paths = bundleStatusProvider.getModel().getEnabledPaths();
GhidraScriptUtil.setScriptBundlePaths(paths); GhidraScriptUtil.setScriptBundlePaths(paths);
actionManager.restoreUserDefinedKeybindings(saveState); actionManager.restoreUserDefinedKeybindings(saveState);
actionManager.restoreScriptsThatAreInTool(saveState); actionManager.restoreScriptsThatAreInTool(saveState);
@ -1213,7 +1152,7 @@ public class GhidraScriptComponentProvider extends ComponentProviderAdapter {
public List<ResourceFile> getWritableScriptDirectories() { public List<ResourceFile> getWritableScriptDirectories() {
BundleStatusModel m = bundleStatusProvider.getModel(); BundleStatusModel m = bundleStatusProvider.getModel();
return m.getPaths().stream().filter(ResourceFile::isDirectory).filter( return m.getEnabledPaths().stream().filter(ResourceFile::isDirectory).filter(
m::isWriteable).collect(Collectors.toList()); m::isWriteable).collect(Collectors.toList());
} }

View file

@ -26,7 +26,6 @@ import ghidra.app.plugin.ProgramPlugin;
import ghidra.app.plugin.core.eclipse.EclipseConnection; import ghidra.app.plugin.core.eclipse.EclipseConnection;
import ghidra.app.plugin.core.eclipse.EclipseIntegrationOptionsPlugin; import ghidra.app.plugin.core.eclipse.EclipseIntegrationOptionsPlugin;
import ghidra.app.script.GhidraState; import ghidra.app.script.GhidraState;
import ghidra.app.script.osgi.BundleHost;
import ghidra.app.services.*; import ghidra.app.services.*;
import ghidra.framework.options.SaveState; import ghidra.framework.options.SaveState;
import ghidra.framework.options.ToolOptions; import ghidra.framework.options.ToolOptions;
@ -51,13 +50,10 @@ public class GhidraScriptMgrPlugin extends ProgramPlugin implements GhidraScript
final private GhidraScriptComponentProvider provider; final private GhidraScriptComponentProvider provider;
final private BundleHost bundleHost;
public GhidraScriptMgrPlugin(PluginTool tool) { public GhidraScriptMgrPlugin(PluginTool tool) {
super(tool, true, true, true); super(tool, true, true, true);
bundleHost = BundleHost.getInstance(); provider = new GhidraScriptComponentProvider(this);
provider = new GhidraScriptComponentProvider(this, bundleHost);
} }
@Override @Override

View file

@ -169,7 +169,7 @@ public class GhidraScriptUtil {
* @deprecated accessing class file directly precludes OSGi wiring according to requirements and capabilities * @deprecated accessing class file directly precludes OSGi wiring according to requirements and capabilities
*/ */
@Deprecated @Deprecated
public static List<ResourceFile> getExplodedBundlePaths() { public static List<ResourceFile> getExplodedCompiledSourceBundlePaths() {
try { try {
return Files.list(getOsgiDir()).filter(Files::isDirectory).map( return Files.list(getOsgiDir()).filter(Files::isDirectory).map(
x -> new ResourceFile(x.toFile())).collect(Collectors.toList()); x -> new ResourceFile(x.toFile())).collect(Collectors.toList());

View file

@ -16,21 +16,20 @@
package ghidra.app.script; package ghidra.app.script;
import java.io.*; import java.io.*;
import java.lang.reflect.InvocationTargetException;
import org.osgi.framework.Bundle; import org.osgi.framework.Bundle;
import generic.jar.ResourceFile; import generic.jar.ResourceFile;
import ghidra.app.script.osgi.*; import ghidra.app.plugin.core.osgi.*;
import ghidra.util.Msg; import ghidra.util.Msg;
public class JavaScriptProvider extends GhidraScriptProvider { public class JavaScriptProvider extends GhidraScriptProvider {
static public SourceBundleInfo getBundleInfoForSource(ResourceFile sourceFile) { static public GhidraSourceBundle getBundleForSource(ResourceFile sourceFile) {
ResourceFile sourceDir = getSourceDirectoryContaining(sourceFile); ResourceFile sourceDir = getSourceDirectoryContaining(sourceFile);
if (sourceDir == null) { if (sourceDir == null) {
return null; return null;
} }
return BundleHost.getInstance().getSourceBundleInfo(sourceDir); return (GhidraSourceBundle) BundleHost.getInstance().getGhidraBundle(sourceDir);
} }
@Override @Override
@ -45,16 +44,16 @@ public class JavaScriptProvider extends GhidraScriptProvider {
@Override @Override
public boolean deleteScript(ResourceFile sourceFile) { public boolean deleteScript(ResourceFile sourceFile) {
Bundle b = getBundleInfoForSource(sourceFile).getBundle(); try {
if (b != null) { Bundle b = getBundleForSource(sourceFile).getBundle();
try { if (b != null) {
BundleHost.getInstance().deactivateSynchronously(b); BundleHost.getInstance().deactivateSynchronously(b);
} }
catch (GhidraBundleException | InterruptedException e) { }
e.printStackTrace(); catch (GhidraBundleException | InterruptedException e) {
Msg.error(this, "while stopping script's bundle to delete it", e); e.printStackTrace();
return false; Msg.error(this, "while deactivating bundle for delete", e);
} return false;
} }
return super.deleteScript(sourceFile); return super.deleteScript(sourceFile);
} }
@ -62,7 +61,6 @@ public class JavaScriptProvider extends GhidraScriptProvider {
@Override @Override
public GhidraScript getScriptInstance(ResourceFile sourceFile, PrintWriter writer) public GhidraScript getScriptInstance(ResourceFile sourceFile, PrintWriter writer)
throws ClassNotFoundException, InstantiationException, IllegalAccessException { throws ClassNotFoundException, InstantiationException, IllegalAccessException {
try { try {
Class<?> clazz = loadClass(sourceFile, writer); Class<?> clazz = loadClass(sourceFile, writer);
Object object; Object object;
@ -80,25 +78,21 @@ public class JavaScriptProvider extends GhidraScriptProvider {
return null; // class is not GhidraScript return null; // class is not GhidraScript
} }
catch (OSGiException | IOException | InterruptedException | IllegalArgumentException catch (ClassNotFoundException | InstantiationException | IllegalAccessException e) {
| InvocationTargetException | NoSuchMethodException | SecurityException e) { throw e;
}
catch (Exception e) {
throw new ClassNotFoundException("", e); throw new ClassNotFoundException("", e);
} }
} }
static public Class<?> loadClass(ResourceFile sourceFile, PrintWriter writer) static public Class<?> loadClass(ResourceFile sourceFile, PrintWriter writer) throws Exception {
throws IOException, OSGiException, ClassNotFoundException, InterruptedException {
BundleHost bundleHost = BundleHost.getInstance(); BundleHost bundleHost = BundleHost.getInstance();
SourceBundleInfo bi = getBundleInfoForSource(sourceFile); GhidraSourceBundle bi = getBundleForSource(sourceFile);
bundleHost.compileSourceBundle(bi, writer); bi.build(writer);
Bundle b = bi.install();
// as much source as possible built, install bundle and start it if necessary
Bundle b = bi.getBundle();
if (b == null) {
b = bi.install();
}
bundleHost.activateSynchronously(b); bundleHost.activateSynchronously(b);
String classname = bi.classNameForScript(sourceFile); String classname = bi.classNameForScript(sourceFile);

View file

@ -48,7 +48,7 @@ import generic.jar.ResourceFile;
import generic.test.TestUtils; import generic.test.TestUtils;
import ghidra.app.plugin.core.codebrowser.CodeBrowserPlugin; import ghidra.app.plugin.core.codebrowser.CodeBrowserPlugin;
import ghidra.app.plugin.core.console.ConsoleComponentProvider; import ghidra.app.plugin.core.console.ConsoleComponentProvider;
import ghidra.app.plugin.core.script.osgi.BundleStatusProvider; import ghidra.app.plugin.core.osgi.BundleStatusProvider;
import ghidra.app.script.*; import ghidra.app.script.*;
import ghidra.app.services.ConsoleService; import ghidra.app.services.ConsoleService;
import ghidra.framework.Application; import ghidra.framework.Application;
@ -979,7 +979,7 @@ public abstract class AbstractGhidraScriptMgrPluginTest
// destroy any NewScriptxxx files...and Temp ones too // destroy any NewScriptxxx files...and Temp ones too
BundleStatusProvider bundleStatusProvider = BundleStatusProvider bundleStatusProvider =
(BundleStatusProvider) TestUtils.getInstanceField("bundleStatusProvider", provider); (BundleStatusProvider) TestUtils.getInstanceField("bundleStatusProvider", provider);
List<ResourceFile> paths = bundleStatusProvider.getModel().getPaths(); List<ResourceFile> paths = bundleStatusProvider.getModel().getEnabledPaths();
for (ResourceFile path : paths) { for (ResourceFile path : paths) {
File file = path.getFile(false); File file = path.getFile(false);
File[] listFiles = file.listFiles(); File[] listFiles = file.listFiles();

View file

@ -26,9 +26,9 @@ import org.junit.Test;
import docking.widgets.table.SelectionManager; import docking.widgets.table.SelectionManager;
import generic.jar.ResourceFile; import generic.jar.ResourceFile;
import generic.test.AbstractGenericTest; import generic.test.AbstractGenericTest;
import ghidra.app.plugin.core.osgi.GhidraSourceBundle;
import ghidra.app.script.GhidraScriptUtil; import ghidra.app.script.GhidraScriptUtil;
import ghidra.app.script.JavaScriptProvider; import ghidra.app.script.JavaScriptProvider;
import ghidra.app.script.osgi.SourceBundleInfo;
import ghidra.test.ScriptTaskListener; import ghidra.test.ScriptTaskListener;
import ghidra.util.TaskUtilities; import ghidra.util.TaskUtilities;
import utilities.util.FileUtilities; import utilities.util.FileUtilities;
@ -149,7 +149,7 @@ public class GhidraScriptMgrPlugin2Test extends AbstractGhidraScriptMgrPluginTes
// remove all class files from the user script bin dir // remove all class files from the user script bin dir
File userScriptsBinDir = File userScriptsBinDir =
SourceBundleInfo.getBindirFromScriptFile(new ResourceFile(newScriptFile)).toFile(); GhidraSourceBundle.getBindirFromScriptFile(new ResourceFile(newScriptFile)).toFile();
File[] userScriptBinDirFiles; File[] userScriptBinDirFiles;
if (userScriptsBinDir.exists()) { if (userScriptsBinDir.exists()) {
userScriptBinDirFiles = userScriptsBinDir.listFiles(classFileFilter); userScriptBinDirFiles = userScriptsBinDir.listFiles(classFileFilter);
@ -195,7 +195,7 @@ public class GhidraScriptMgrPlugin2Test extends AbstractGhidraScriptMgrPluginTes
// verify that the generated class file is placed in the default scripting home/bin // verify that the generated class file is placed in the default scripting home/bin
File userScriptsBinDir = File userScriptsBinDir =
SourceBundleInfo.getBindirFromScriptFile(systemScriptFile).toFile(); GhidraSourceBundle.getBindirFromScriptFile(systemScriptFile).toFile();
String className = scriptName.replace(".java", ".class"); String className = scriptName.replace(".java", ".class");
File expectedClassFile = new File(userScriptsBinDir, className); File expectedClassFile = new File(userScriptsBinDir, className);
@ -232,7 +232,7 @@ public class GhidraScriptMgrPlugin2Test extends AbstractGhidraScriptMgrPluginTes
// verify a bin dir was created and that the class file is in it // verify a bin dir was created and that the class file is in it
File binDir = File binDir =
SourceBundleInfo.getBindirFromScriptFile(new ResourceFile(newScriptFile)).toFile(); GhidraSourceBundle.getBindirFromScriptFile(new ResourceFile(newScriptFile)).toFile();
assertTrue("bin output dir not created", binDir.exists()); assertTrue("bin output dir not created", binDir.exists());
File scriptClassFile = new File(binDir, rawScriptName + ".class"); File scriptClassFile = new File(binDir, rawScriptName + ".class");

View file

@ -30,7 +30,7 @@ import docking.action.DockingActionIf;
import docking.widgets.filter.FilterTextField; import docking.widgets.filter.FilterTextField;
import docking.widgets.list.ListPanel; import docking.widgets.list.ListPanel;
import generic.jar.ResourceFile; import generic.jar.ResourceFile;
import ghidra.app.plugin.core.script.osgi.BundleStatusProvider; import ghidra.app.plugin.core.osgi.BundleStatusProvider;
import ghidra.app.script.GhidraScriptUtil; import ghidra.app.script.GhidraScriptUtil;
import ghidra.app.script.JavaScriptProvider; import ghidra.app.script.JavaScriptProvider;
import ghidra.util.StringUtilities; import ghidra.util.StringUtilities;

View file

@ -138,7 +138,7 @@ public class GhidraPythonInterpreter extends InteractiveInterpreter {
systemState.path.append(Py.newString(resourceFile.getFile(false).getAbsolutePath())); systemState.path.append(Py.newString(resourceFile.getFile(false).getAbsolutePath()));
} }
for (ResourceFile resourceFile : GhidraScriptUtil.getExplodedBundlePaths()) { for (ResourceFile resourceFile : GhidraScriptUtil.getExplodedCompiledSourceBundlePaths()) {
systemState.path.append(Py.newString(resourceFile.getFile(false).getAbsolutePath())); systemState.path.append(Py.newString(resourceFile.getFile(false).getAbsolutePath()));
} }

View file

@ -27,8 +27,8 @@ import docking.widgets.tree.GTree;
import docking.widgets.tree.GTreeNode; import docking.widgets.tree.GTreeNode;
import generic.jar.ResourceFile; import generic.jar.ResourceFile;
import ghidra.app.plugin.core.console.ConsoleComponentProvider; import ghidra.app.plugin.core.console.ConsoleComponentProvider;
import ghidra.app.plugin.core.osgi.BundleStatusProvider;
import ghidra.app.plugin.core.script.*; import ghidra.app.plugin.core.script.*;
import ghidra.app.plugin.core.script.osgi.BundleStatusProvider;
import ghidra.app.script.GhidraScriptUtil; import ghidra.app.script.GhidraScriptUtil;
import ghidra.app.services.ConsoleService; import ghidra.app.services.ConsoleService;
import ghidra.util.HelpLocation; import ghidra.util.HelpLocation;