mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2025-10-05 10:49:34 +02:00
GP-1816 - OSGi Bundles - minor refactoring of OSGi bundle code during exploration.
This commit is contained in:
parent
d89764d9bb
commit
2d5f53e051
20 changed files with 629 additions and 583 deletions
|
@ -18,16 +18,15 @@ package ghidra.app.plugin.core.osgi;
|
||||||
import generic.jar.ResourceFile;
|
import generic.jar.ResourceFile;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An error produced during {@link GhidraBundle#build()} with a time stamp
|
* An error produced during {@link GhidraBundle#build()} with a timestamp.
|
||||||
*/
|
*/
|
||||||
public class BuildError {
|
public class BuildError {
|
||||||
// the lastModified time of the source causing this error
|
// the lastModified time of the source causing this error
|
||||||
private final long lastModified;
|
private final long lastModified;
|
||||||
|
|
||||||
private final StringBuilder message = new StringBuilder();
|
private final StringBuilder message = new StringBuilder();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Construct an object to record error message produced for {@code sourceFile}
|
* Construct an object to record error message produced for {@code sourceFile}.
|
||||||
* @param sourceFile the file causing this error
|
* @param sourceFile the file causing this error
|
||||||
*/
|
*/
|
||||||
public BuildError(ResourceFile sourceFile) {
|
public BuildError(ResourceFile sourceFile) {
|
||||||
|
@ -35,15 +34,15 @@ public class BuildError {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Append {@code str} to the current error message
|
* Append the given string to the current error message.
|
||||||
*
|
* @param s the string to append
|
||||||
* @param str the string to append
|
|
||||||
*/
|
*/
|
||||||
public void append(String str) {
|
public void append(String s) {
|
||||||
message.append(str);
|
message.append(s);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* The error message.
|
||||||
* @return the error message
|
* @return the error message
|
||||||
*/
|
*/
|
||||||
String getMessage() {
|
String getMessage() {
|
||||||
|
@ -51,6 +50,7 @@ public class BuildError {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* The last modified time of the source for this build error.
|
||||||
* @return the last modified time of the source for this build error
|
* @return the last modified time of the source for this build error
|
||||||
*/
|
*/
|
||||||
public long getLastModified() {
|
public long getLastModified() {
|
||||||
|
|
|
@ -15,7 +15,8 @@
|
||||||
*/
|
*/
|
||||||
package ghidra.app.plugin.core.osgi;
|
package ghidra.app.plugin.core.osgi;
|
||||||
|
|
||||||
import java.io.*;
|
import java.io.IOException;
|
||||||
|
import java.io.PrintWriter;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
@ -46,19 +47,19 @@ import ghidra.util.task.TaskMonitor;
|
||||||
* Hosts the embedded OSGi framework and manages {@link GhidraBundle}s.
|
* Hosts the embedded OSGi framework and manages {@link GhidraBundle}s.
|
||||||
*
|
*
|
||||||
* <br/><br/>
|
* <br/><br/>
|
||||||
* note: {@link GhidraBundle}, its implementations, and this class constitute
|
* Note: {@link GhidraBundle}, its implementations, and this class constitute a bridge between
|
||||||
* a bridge between OSGi's {@link Bundle} and Ghidra.
|
* OSGi's {@link Bundle} and Ghidra.
|
||||||
* <ul>
|
* <ul>
|
||||||
* <li> unqualified, "bundle" will mean {@link GhidraBundle}
|
* <li> unqualified, "bundle" will mean {@link GhidraBundle}
|
||||||
* <li> use of OSGi types, including {@link Bundle} and {@link Framework}, should be package
|
* <li> use of OSGi types, including {@link Bundle} and {@link Framework}, should be package scoped
|
||||||
* scoped (not public)
|
* (not public)
|
||||||
* <li> bundle lifecycle is simplified to "active"(same as OSGi "active" state)
|
* <li> bundle lifecycle is simplified to "active"(same as OSGi "active" state) and "inactive"
|
||||||
* and "inactive" (OSGi "uninstalled" state)
|
* (OSGi "uninstalled" state)
|
||||||
* </ul>
|
* </ul>
|
||||||
*/
|
*/
|
||||||
public class BundleHost {
|
public class BundleHost {
|
||||||
public static final String ACTIVATING_BUNDLE_ERROR_MSG = "activating bundle";
|
public static final String ACTIVATING_BUNDLE_ERROR_MSG = "activating bundle";
|
||||||
protected static final boolean STDERR_DEBUGGING = false;
|
private static final boolean STDERR_DEBUGGING = false;
|
||||||
private static final String SAVE_STATE_TAG_FILE = "BundleHost_FILE";
|
private static final String SAVE_STATE_TAG_FILE = "BundleHost_FILE";
|
||||||
private static final String SAVE_STATE_TAG_ENABLE = "BundleHost_ENABLE";
|
private static final String SAVE_STATE_TAG_ENABLE = "BundleHost_ENABLE";
|
||||||
private static final String SAVE_STATE_TAG_ACTIVE = "BundleHost_ACTIVE";
|
private static final String SAVE_STATE_TAG_ACTIVE = "BundleHost_ACTIVE";
|
||||||
|
@ -66,22 +67,10 @@ public class BundleHost {
|
||||||
|
|
||||||
private final BundleMap bundleMap = new BundleMap();
|
private final BundleMap bundleMap = new BundleMap();
|
||||||
|
|
||||||
BundleContext frameworkBundleContext;
|
private BundleContext frameworkBundleContext;
|
||||||
Framework felixFramework;
|
private Framework felixFramework;
|
||||||
|
|
||||||
List<BundleHostListener> listeners = new CopyOnWriteArrayList<>();
|
private List<BundleHostListener> listeners = new CopyOnWriteArrayList<>();
|
||||||
|
|
||||||
/** constructor */
|
|
||||||
public BundleHost() {
|
|
||||||
//
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* stop the framework.
|
|
||||||
*/
|
|
||||||
public void dispose() {
|
|
||||||
stopFramework();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If a {@link GhidraBundle} hasn't already been added for {@bundleFile}, add it now as a
|
* If a {@link GhidraBundle} hasn't already been added for {@bundleFile}, add it now as a
|
||||||
|
@ -132,8 +121,8 @@ public class BundleHost {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Assuming there is currently a bundle managed with file {@code bundleFile},
|
* Assuming there is currently a bundle managed with file {@code bundleFile}, return its
|
||||||
* return its {@link GhidraBundle}, otherwise show an error dialog and return {@code null}.
|
* {@link GhidraBundle}, otherwise show an error dialog and return {@code null}.
|
||||||
*
|
*
|
||||||
* @param bundleFile the bundleFile of the sought bundle
|
* @param bundleFile the bundleFile of the sought bundle
|
||||||
* @return a {@link GhidraBundle} or {@code null}
|
* @return a {@link GhidraBundle} or {@code null}
|
||||||
|
@ -149,8 +138,8 @@ public class BundleHost {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If there is currently a bundle managed with file {@code bundleFile},
|
* If there is currently a bundle managed with file {@code bundleFile}, return its
|
||||||
* return its {@link GhidraBundle}, otherwise return {@code null}.
|
* {@link GhidraBundle}, otherwise return {@code null}.
|
||||||
*
|
*
|
||||||
* @param bundleFile the bundleFile of the sought bundle
|
* @param bundleFile the bundleFile of the sought bundle
|
||||||
* @return a {@link GhidraBundle} or {@code null}
|
* @return a {@link GhidraBundle} or {@code null}
|
||||||
|
@ -180,7 +169,7 @@ public class BundleHost {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a new GhidraBundle and add to the list of managed bundles
|
* Create a new GhidraBundle and add to the list of managed bundles.
|
||||||
*
|
*
|
||||||
* @param bundleFile the bundle file
|
* @param bundleFile the bundle file
|
||||||
* @param enabled if the new bundle should be enabled
|
* @param enabled if the new bundle should be enabled
|
||||||
|
@ -282,7 +271,7 @@ public class BundleHost {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Bundle installFromLoc(String bundleLocation) throws GhidraBundleException {
|
private Bundle installFromLoc(String bundleLocation) throws GhidraBundleException {
|
||||||
try {
|
try {
|
||||||
return frameworkBundleContext.installBundle(bundleLocation);
|
return frameworkBundleContext.installBundle(bundleLocation);
|
||||||
}
|
}
|
||||||
|
@ -291,17 +280,8 @@ public class BundleHost {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Bundle installAsLoc(String bundleLocation, InputStream contents) throws GhidraBundleException {
|
|
||||||
try {
|
|
||||||
return frameworkBundleContext.installBundle(bundleLocation, contents);
|
|
||||||
}
|
|
||||||
catch (BundleException e) {
|
|
||||||
throw new GhidraBundleException(bundleLocation, "installing as bundle location", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* return all of the currently managed bundles
|
* Return all of the currently managed bundles.
|
||||||
*
|
*
|
||||||
* @return all the bundles
|
* @return all the bundles
|
||||||
*/
|
*/
|
||||||
|
@ -310,7 +290,7 @@ public class BundleHost {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* return the list of currently managed bundle files
|
* Return the list of currently managed bundle files.
|
||||||
*
|
*
|
||||||
* @return all the bundle files
|
* @return all the bundle files
|
||||||
*/
|
*/
|
||||||
|
@ -318,14 +298,6 @@ public class BundleHost {
|
||||||
return bundleMap.getBundleFiles();
|
return bundleMap.getBundleFiles();
|
||||||
}
|
}
|
||||||
|
|
||||||
void dumpLoadedBundles() {
|
|
||||||
System.err.printf("=== Bundles ===\n");
|
|
||||||
for (Bundle bundle : frameworkBundleContext.getBundles()) {
|
|
||||||
System.err.printf("%s: %s: %s: %s\n", bundle.getBundleId(), bundle.getSymbolicName(),
|
|
||||||
bundle.getState(), bundle.getVersion());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Attempt to resolve a list of BundleRequirements with active Bundle capabilities.
|
* Attempt to resolve a list of BundleRequirements with active Bundle capabilities.
|
||||||
*
|
*
|
||||||
|
@ -370,7 +342,7 @@ public class BundleHost {
|
||||||
return tmpRequirements.isEmpty();
|
return tmpRequirements.isEmpty();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected String buildExtraSystemPackages() {
|
private String buildExtraSystemPackages() {
|
||||||
Set<String> packages = new HashSet<>();
|
Set<String> packages = new HashSet<>();
|
||||||
OSGiUtils.getPackagesFromClasspath(packages);
|
OSGiUtils.getPackagesFromClasspath(packages);
|
||||||
return packages.stream().collect(Collectors.joining(","));
|
return packages.stream().collect(Collectors.joining(","));
|
||||||
|
@ -387,11 +359,11 @@ public class BundleHost {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A directory for use by the OSGi framework as a cache
|
* A directory for use by the OSGi framework as a cache.
|
||||||
*
|
*
|
||||||
* @return the directory
|
* @return the directory
|
||||||
*/
|
*/
|
||||||
protected static Path getCacheDir() {
|
private static Path getCacheDir() {
|
||||||
return BundleHost.getOsgiDir().resolve("felixcache");
|
return BundleHost.getOsgiDir().resolve("felixcache");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -401,16 +373,19 @@ public class BundleHost {
|
||||||
return cacheDir.toAbsolutePath().toString();
|
return cacheDir.toAbsolutePath().toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void createAndConfigureFramework() throws IOException {
|
private void createAndConfigureFramework() throws IOException {
|
||||||
Properties config = new Properties();
|
Properties config = new Properties();
|
||||||
|
|
||||||
// allow multiple bundles w/ the same symbolic name -- location can distinguish
|
// allow multiple bundles w/ the same symbolic name -- location can distinguish
|
||||||
config.setProperty(Constants.FRAMEWORK_BSNVERSION, Constants.FRAMEWORK_BSNVERSION_MULTIPLE);
|
config.setProperty(Constants.FRAMEWORK_BSNVERSION, Constants.FRAMEWORK_BSNVERSION_MULTIPLE);
|
||||||
// use the default, inferred from environment
|
|
||||||
// config.setProperty(Constants.FRAMEWORK_SYSTEMCAPABILITIES,"osgi.ee; osgi.ee=\"JavaSE\";version:List=\"...\"");
|
|
||||||
|
|
||||||
// compute and add everything in the class path. extra packages have lower precedence than imports,
|
// use the default, inferred from environment
|
||||||
// so an Import-Package / @importpackage will override the "living off the land" default
|
// config.setProperty(Constants.FRAMEWORK_SYSTEMCAPABILITIES,
|
||||||
|
// "osgi.ee; osgi.ee=\"JavaSE\";version:List=\"...\"");
|
||||||
|
|
||||||
|
// compute and add everything in the class path. extra packages have lower precedence than
|
||||||
|
// imports, so an Import-Package / @importpackage will override the "living off the land"
|
||||||
|
// default
|
||||||
config.setProperty(Constants.FRAMEWORK_SYSTEMPACKAGES_EXTRA, buildExtraSystemPackages());
|
config.setProperty(Constants.FRAMEWORK_SYSTEMPACKAGES_EXTRA, buildExtraSystemPackages());
|
||||||
|
|
||||||
// only clean on first startup, o/w keep our storage around
|
// only clean on first startup, o/w keep our storage around
|
||||||
|
@ -423,23 +398,26 @@ public class BundleHost {
|
||||||
config.put(FelixConstants.LOG_LEVEL_PROP, "1");
|
config.put(FelixConstants.LOG_LEVEL_PROP, "1");
|
||||||
if (STDERR_DEBUGGING) {
|
if (STDERR_DEBUGGING) {
|
||||||
config.put(FelixConstants.LOG_LEVEL_PROP, "999");
|
config.put(FelixConstants.LOG_LEVEL_PROP, "999");
|
||||||
// config.put(FelixConstants.LOG_LOGGER_PROP, new org.apache.felix.framework.Logger() {...});
|
// config.put(FelixConstants.LOG_LOGGER_PROP,
|
||||||
|
// new org.apache.felix.framework.Logger() {...});
|
||||||
}
|
}
|
||||||
|
|
||||||
FrameworkFactory factory = new FrameworkFactory();
|
FrameworkFactory factory = new FrameworkFactory();
|
||||||
felixFramework = factory.newFramework(config);
|
felixFramework = factory.newFramework(config);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void addDebuggingListeners() {
|
private void addDebuggingListeners() {
|
||||||
frameworkBundleContext.addFrameworkListener(new FrameworkListener() {
|
if (!STDERR_DEBUGGING) {
|
||||||
@Override
|
return;
|
||||||
public void frameworkEvent(FrameworkEvent event) {
|
|
||||||
System.err.printf("%s %s\n", event.getBundle(), event);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
frameworkBundleContext.addFrameworkListener(
|
||||||
|
event -> {
|
||||||
|
String msg = String.format("AA: %s %s\n", event.getBundle(), event);
|
||||||
|
Msg.debug(this, msg);
|
||||||
});
|
});
|
||||||
frameworkBundleContext.addServiceListener(new ServiceListener() {
|
|
||||||
@Override
|
frameworkBundleContext.addServiceListener(event -> {
|
||||||
public void serviceChanged(ServiceEvent event) {
|
|
||||||
|
|
||||||
String type = "?";
|
String type = "?";
|
||||||
if (event.getType() == ServiceEvent.REGISTERED) {
|
if (event.getType() == ServiceEvent.REGISTERED) {
|
||||||
|
@ -449,15 +427,14 @@ public class BundleHost {
|
||||||
type = "unregistering";
|
type = "unregistering";
|
||||||
}
|
}
|
||||||
|
|
||||||
System.err.printf("%s %s from %s\n", event.getSource(), type,
|
String msg = String.format("BB: %s %s from %s\n", event.getSource(), type,
|
||||||
event.getServiceReference().getBundle().getLocation());
|
event.getServiceReference().getBundle().getLocation());
|
||||||
|
Msg.debug(this, msg);
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* start the framework
|
* Start the framework.
|
||||||
*
|
*
|
||||||
* @throws OSGiException framework failures
|
* @throws OSGiException framework failures
|
||||||
* @throws IOException filesystem setup
|
* @throws IOException filesystem setup
|
||||||
|
@ -469,48 +446,53 @@ public class BundleHost {
|
||||||
felixFramework.init();
|
felixFramework.init();
|
||||||
}
|
}
|
||||||
catch (BundleException e) {
|
catch (BundleException e) {
|
||||||
throw new OSGiException("initializing felix OSGi framework", e);
|
throw new OSGiException("Exception initializing felix OSGi framework", e);
|
||||||
}
|
}
|
||||||
|
|
||||||
frameworkBundleContext = felixFramework.getBundleContext();
|
frameworkBundleContext = felixFramework.getBundleContext();
|
||||||
|
|
||||||
if (STDERR_DEBUGGING) {
|
|
||||||
addDebuggingListeners();
|
addDebuggingListeners();
|
||||||
}
|
|
||||||
|
|
||||||
frameworkBundleContext
|
Bundle bundle = frameworkBundleContext.getBundle();
|
||||||
.addBundleListener(new MyBundleListener(frameworkBundleContext.getBundle()));
|
frameworkBundleContext.addBundleListener(new MyBundleListener(bundle));
|
||||||
|
|
||||||
try {
|
try {
|
||||||
felixFramework.start();
|
felixFramework.start();
|
||||||
}
|
}
|
||||||
catch (BundleException e) {
|
catch (BundleException e) {
|
||||||
throw new OSGiException("starting felix OSGi framework", e);
|
throw new OSGiException("Exception starting felix OSGi framework", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* stop the OSGi framework synchronously
|
* Stop the OSGi framework.
|
||||||
|
*
|
||||||
|
* <p>This may wait for up to 5 seconds for the framework to fully stop. If that timeout
|
||||||
|
* passes an error will be logged.
|
||||||
*/
|
*/
|
||||||
protected void stopFramework() {
|
public void stopFramework() {
|
||||||
if (felixFramework != null) {
|
if (felixFramework == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
felixFramework.stop();
|
felixFramework.stop();
|
||||||
// any bundles that linger after a few seconds might be the source
|
|
||||||
// of subtle problems, so wait for them to stop and report any problems.
|
// any bundles that linger after a few seconds might be the source of subtle problems,
|
||||||
|
// so wait for them to stop and report any problems.
|
||||||
FrameworkEvent event = felixFramework.waitForStop(5000);
|
FrameworkEvent event = felixFramework.waitForStop(5000);
|
||||||
if (event.getType() == FrameworkEvent.WAIT_TIMEDOUT) {
|
if (event.getType() == FrameworkEvent.WAIT_TIMEDOUT) {
|
||||||
Msg.error(this, "Stopping OSGi framework timed out after 5 seconds.");
|
Msg.error(this, "Stopping OSGi framework timed-out after 5 seconds.");
|
||||||
}
|
}
|
||||||
felixFramework = null;
|
felixFramework = null;
|
||||||
}
|
}
|
||||||
catch (BundleException | InterruptedException e) {
|
catch (BundleException | InterruptedException e) {
|
||||||
Msg.error(this, "Failed to stop OSGi framework.");
|
Msg.error(this, "Failed to stop OSGi framework.", e);
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* Gets the host framework.
|
||||||
* @return the OSGi framework
|
* @return the OSGi framework
|
||||||
*/
|
*/
|
||||||
Framework getHostFramework() {
|
Framework getHostFramework() {
|
||||||
|
@ -565,7 +547,8 @@ public class BundleHost {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Deactivate a bundle. Either an exception is thrown or the bundle will be in "UNINSTALLED" state.
|
* Deactivate a bundle. Either an exception is thrown or the bundle will be in "UNINSTALLED"
|
||||||
|
* state.
|
||||||
*
|
*
|
||||||
* @param bundle the bundle
|
* @param bundle the bundle
|
||||||
* @throws GhidraBundleException if there's a problem activating
|
* @throws GhidraBundleException if there's a problem activating
|
||||||
|
@ -575,7 +558,7 @@ public class BundleHost {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
FrameworkWiring frameworkWiring = felixFramework.adapt(FrameworkWiring.class);
|
FrameworkWiring frameworkWiring = felixFramework.adapt(FrameworkWiring.class);
|
||||||
LinkedList<Bundle> dependentBundles = new LinkedList<Bundle>(
|
LinkedList<Bundle> dependentBundles = new LinkedList<>(
|
||||||
frameworkWiring.getDependencyClosure(Collections.singleton(bundle)));
|
frameworkWiring.getDependencyClosure(Collections.singleton(bundle)));
|
||||||
while (!dependentBundles.isEmpty()) {
|
while (!dependentBundles.isEmpty()) {
|
||||||
Bundle dependentBundle = dependentBundles.pop();
|
Bundle dependentBundle = dependentBundles.pop();
|
||||||
|
@ -596,7 +579,8 @@ public class BundleHost {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Deactivate a bundle. Either an exception is thrown or the bundle will be in "UNINSTALLED" state.
|
* Deactivate a bundle. Either an exception is thrown or the bundle will be in "UNINSTALLED"
|
||||||
|
* state.
|
||||||
*
|
*
|
||||||
* @param bundleLocation the bundle location identifier
|
* @param bundleLocation the bundle location identifier
|
||||||
* @throws InterruptedException if the wait is interrupted
|
* @throws InterruptedException if the wait is interrupted
|
||||||
|
@ -611,8 +595,8 @@ public class BundleHost {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Refreshes the specified bundles. This forces the update (replacement)
|
* Refreshes the specified bundles. This forces the update (replacement) or removal of packages
|
||||||
* or removal of packages exported by the specified bundles.
|
* exported by the specified bundles.
|
||||||
*
|
*
|
||||||
* @param bundles the bundles to refresh
|
* @param bundles the bundles to refresh
|
||||||
* @see FrameworkWiring#refreshBundles
|
* @see FrameworkWiring#refreshBundles
|
||||||
|
@ -620,22 +604,19 @@ public class BundleHost {
|
||||||
protected void refreshBundlesSynchronously(Collection<Bundle> bundles) {
|
protected void refreshBundlesSynchronously(Collection<Bundle> bundles) {
|
||||||
FrameworkWiring frameworkWiring = felixFramework.adapt(FrameworkWiring.class);
|
FrameworkWiring frameworkWiring = felixFramework.adapt(FrameworkWiring.class);
|
||||||
final CountDownLatch latch = new CountDownLatch(1);
|
final CountDownLatch latch = new CountDownLatch(1);
|
||||||
frameworkWiring.refreshBundles(bundles, new FrameworkListener() {
|
frameworkWiring.refreshBundles(bundles, event -> {
|
||||||
@Override
|
|
||||||
public void frameworkEvent(FrameworkEvent event) {
|
|
||||||
if (event.getType() == FrameworkEvent.ERROR) {
|
if (event.getType() == FrameworkEvent.ERROR) {
|
||||||
Bundle bundle = event.getBundle();
|
Bundle bundle = event.getBundle();
|
||||||
Msg.error(BundleHost.this,
|
Msg.error(BundleHost.this,
|
||||||
String.format("OSGi error refreshing bundle: %s", bundle));
|
String.format("OSGi error refreshing bundle: %s", bundle));
|
||||||
}
|
}
|
||||||
latch.countDown();
|
latch.countDown();
|
||||||
}
|
|
||||||
});
|
});
|
||||||
try {
|
try {
|
||||||
latch.await();
|
latch.await();
|
||||||
}
|
}
|
||||||
catch (InterruptedException e) {
|
catch (InterruptedException e) {
|
||||||
e.printStackTrace();
|
Msg.error(this, "Exception waiting for bundles to refresh", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -649,8 +630,11 @@ public class BundleHost {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Activate a set of bundles and any dependencies in topological order. This method doesn't rely on the
|
* Activate a set of bundles and any dependencies in topological order. This method doesn't
|
||||||
* framework, and so will add non-active dependencies.
|
* rely on the framework, and so will add non-active dependencies.
|
||||||
|
*
|
||||||
|
* <p>To load bundles without loading inactive dependencies, call
|
||||||
|
* {@link #activateInStages(Collection, TaskMonitor, PrintWriter)}.
|
||||||
*
|
*
|
||||||
* @param bundles bundles to activate
|
* @param bundles bundles to activate
|
||||||
* @param monitor a task monitor
|
* @param monitor a task monitor
|
||||||
|
@ -671,9 +655,13 @@ public class BundleHost {
|
||||||
activateSynchronously(bundle.getLocationIdentifier());
|
activateSynchronously(bundle.getLocationIdentifier());
|
||||||
}
|
}
|
||||||
catch (GhidraBundleException e) {
|
catch (GhidraBundleException e) {
|
||||||
|
// TODO should we report failing bundles to the console as well so they get logged
|
||||||
|
// in headless mode?
|
||||||
fireBundleException(e);
|
fireBundleException(e);
|
||||||
}
|
}
|
||||||
catch (Exception e) {
|
catch (Exception e) {
|
||||||
|
// write the error to the console or log file
|
||||||
|
console.println("Unexpected error activating bundles: " + bundles);
|
||||||
e.printStackTrace(console);
|
e.printStackTrace(console);
|
||||||
}
|
}
|
||||||
monitor.incrementProgress(1);
|
monitor.incrementProgress(1);
|
||||||
|
@ -682,7 +670,10 @@ public class BundleHost {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Activate a set of bundles in dependency topological order by resolving against currently
|
* Activate a set of bundles in dependency topological order by resolving against currently
|
||||||
* active bundles in stages. No bundles outside those requested will be activated.
|
* active bundles in stages. <b>No bundles outside those requested will be activated.</b>
|
||||||
|
*
|
||||||
|
* <p>To have inactive dependencies loaded, call
|
||||||
|
* {@link #activateAll(Collection, TaskMonitor, PrintWriter)}.
|
||||||
*
|
*
|
||||||
* @param bundles bundles to activate
|
* @param bundles bundles to activate
|
||||||
* @param monitor a task monitor
|
* @param monitor a task monitor
|
||||||
|
@ -702,11 +693,13 @@ public class BundleHost {
|
||||||
requirementMap.put(bundle, requirements);
|
requirementMap.put(bundle, requirements);
|
||||||
}
|
}
|
||||||
catch (GhidraBundleException e) {
|
catch (GhidraBundleException e) {
|
||||||
|
// TODO should we report failing bundles to the console as well so they get logged
|
||||||
|
// in headless mode?
|
||||||
fireBundleException(e);
|
fireBundleException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
List<GhidraBundle> bundlesRemaining = new ArrayList<>(requirementMap.keySet());
|
|
||||||
|
|
||||||
|
List<GhidraBundle> bundlesRemaining = new ArrayList<>(requirementMap.keySet());
|
||||||
monitor.setMaximum(bundlesRemaining.size());
|
monitor.setMaximum(bundlesRemaining.size());
|
||||||
while (!bundlesRemaining.isEmpty() && !monitor.isCancelled()) {
|
while (!bundlesRemaining.isEmpty() && !monitor.isCancelled()) {
|
||||||
List<GhidraBundle> resolvableBundles = bundlesRemaining.stream()
|
List<GhidraBundle> resolvableBundles = bundlesRemaining.stream()
|
||||||
|
@ -731,9 +724,13 @@ public class BundleHost {
|
||||||
activateSynchronously(bundle.getLocationIdentifier());
|
activateSynchronously(bundle.getLocationIdentifier());
|
||||||
}
|
}
|
||||||
catch (GhidraBundleException e) {
|
catch (GhidraBundleException e) {
|
||||||
|
// TODO should we report failing bundles to the console as well so they get logged
|
||||||
|
// in headless mode?
|
||||||
fireBundleException(e);
|
fireBundleException(e);
|
||||||
}
|
}
|
||||||
catch (Exception e) {
|
catch (Exception e) {
|
||||||
|
// write the error to the console or log file
|
||||||
|
console.println("Unexpected error activating bundles: " + bundles);
|
||||||
e.printStackTrace(console);
|
e.printStackTrace(console);
|
||||||
}
|
}
|
||||||
monitor.incrementProgress(1);
|
monitor.incrementProgress(1);
|
||||||
|
@ -813,7 +810,7 @@ public class BundleHost {
|
||||||
*
|
*
|
||||||
* <p>Bundles that had been active are reactivated.
|
* <p>Bundles that had been active are reactivated.
|
||||||
*
|
*
|
||||||
* <p>note: This is done once on startup after system bundles have been added.
|
* <p>Note: This is done once on startup after system bundles have been added.
|
||||||
*
|
*
|
||||||
* @param saveState the state object
|
* @param saveState the state object
|
||||||
* @param tool the tool
|
* @param tool the tool
|
||||||
|
@ -863,7 +860,7 @@ public class BundleHost {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!bundlesToActivate.isEmpty()) {
|
if (!bundlesToActivate.isEmpty()) {
|
||||||
TaskLauncher.launchNonModal("restoring bundle state",
|
TaskLauncher.launchNonModal("Restoring bundle state",
|
||||||
(monitor) -> activateInStages(bundlesToActivate, monitor, new NullPrintWriter()));
|
(monitor) -> activateInStages(bundlesToActivate, monitor, new NullPrintWriter()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -896,35 +893,43 @@ public class BundleHost {
|
||||||
saveState.putBooleans(SAVE_STATE_TAG_SYSTEM, bundleIsSystem);
|
saveState.putBooleans(SAVE_STATE_TAG_SYSTEM, bundleIsSystem);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class Dependency {
|
//=================================================================================================
|
||||||
// exists only to be distinguished by id
|
// Inner Classes
|
||||||
|
//=================================================================================================
|
||||||
|
|
||||||
|
private static class BundleEdge {
|
||||||
|
// edge type for dependency graph
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Utility class to build a dependency graph from bundles where capabilities map to requirements.
|
* Utility class to build a dependency graph from bundles where capabilities map to
|
||||||
|
* requirements.
|
||||||
*/
|
*/
|
||||||
private class BundleDependencyGraph extends DirectedMultigraph<GhidraBundle, Dependency> {
|
private class BundleDependencyGraph extends DirectedMultigraph<GhidraBundle, BundleEdge> {
|
||||||
final Map<GhidraBundle, List<BundleCapability>> capabilityMap = new HashMap<>();
|
|
||||||
final List<GhidraBundle> availableBundles;
|
|
||||||
final TaskMonitor monitor;
|
|
||||||
|
|
||||||
BundleDependencyGraph(Collection<GhidraBundle> startingBundles, TaskMonitor monitor) {
|
private final List<GhidraBundle> availableBundles = new ArrayList<>();
|
||||||
|
private final Map<GhidraBundle, List<BundleCapability>> capabilityMap = new HashMap<>();
|
||||||
|
private final TaskMonitor monitor;
|
||||||
|
|
||||||
|
BundleDependencyGraph(Collection<GhidraBundle> activatingBundles, TaskMonitor monitor) {
|
||||||
super(null, null, false);
|
super(null, null, false);
|
||||||
this.monitor = monitor;
|
this.monitor = monitor;
|
||||||
|
|
||||||
// maintain a list of bundles available for resolution, starting with all of the enabled bundles
|
// maintain a list of bundles available for resolution, starting with all of the
|
||||||
this.availableBundles = new ArrayList<>();
|
// enabled bundles
|
||||||
for (GhidraBundle bundle : getGhidraBundles()) {
|
for (GhidraBundle bundle : getGhidraBundles()) {
|
||||||
if (bundle.isEnabled()) {
|
if (bundle.isEnabled()) {
|
||||||
addToAvailable(bundle);
|
addToAvailable(bundle);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// An edge A->B indicates that the capabilities of A resolve some requirement(s) of B
|
// An edge A->B indicates that the capabilities of A resolve some requirement(s) of B
|
||||||
|
|
||||||
// "front" accumulates bundles and links to bundles already in the graph that they provide capabilities for.
|
// "front" accumulates bundles and links to bundles already in the graph that they
|
||||||
// e.g. if front[A]=[B,...] then A->B, B is already in the graph, and we will add A next iteration.
|
// provide capabilities for. e.g., if front[A]=[B,...] then A->B, B is already in the
|
||||||
|
// graph, and we will add A next iteration.
|
||||||
Map<GhidraBundle, Set<GhidraBundle>> front = new HashMap<>();
|
Map<GhidraBundle, Set<GhidraBundle>> front = new HashMap<>();
|
||||||
for (GhidraBundle bundle : startingBundles) {
|
for (GhidraBundle bundle : activatingBundles) {
|
||||||
front.put(bundle, null);
|
front.put(bundle, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -939,46 +944,45 @@ public class BundleHost {
|
||||||
handleBackEdges(newFront);
|
handleBackEdges(newFront);
|
||||||
front = newFront;
|
front = newFront;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Iterable<GhidraBundle> inTopologicalOrder() {
|
Iterable<GhidraBundle> inTopologicalOrder() {
|
||||||
return () -> new TopologicalOrderIterator<>(this);
|
return () -> new TopologicalOrderIterator<>(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
void handleBackEdges(Map<GhidraBundle, Set<GhidraBundle>> newFront) {
|
private void handleBackEdges(Map<GhidraBundle, Set<GhidraBundle>> newFront) {
|
||||||
Iterator<Entry<GhidraBundle, Set<GhidraBundle>>> newFrontIter =
|
Iterator<Entry<GhidraBundle, Set<GhidraBundle>>> it =
|
||||||
newFront.entrySet().iterator();
|
newFront.entrySet().iterator();
|
||||||
while (newFrontIter.hasNext() && !monitor.isCancelled()) {
|
while (it.hasNext() && !monitor.isCancelled()) {
|
||||||
Entry<GhidraBundle, Set<GhidraBundle>> entry = newFrontIter.next();
|
Entry<GhidraBundle, Set<GhidraBundle>> entry = it.next();
|
||||||
GhidraBundle source = entry.getKey();
|
GhidraBundle source = entry.getKey();
|
||||||
if (containsVertex(source)) {
|
if (containsVertex(source)) {
|
||||||
for (GhidraBundle destination : entry.getValue()) {
|
for (GhidraBundle destination : entry.getValue()) {
|
||||||
if (source != destination) {
|
if (source != destination) {
|
||||||
addEdge(source, destination, new Dependency());
|
addEdge(source, destination, new BundleEdge());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
newFrontIter.remove();
|
it.remove();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void addFront(Map<GhidraBundle, Set<GhidraBundle>> front) {
|
private void addFront(Map<GhidraBundle, Set<GhidraBundle>> front) {
|
||||||
for (Entry<GhidraBundle, Set<GhidraBundle>> e : front.entrySet()) {
|
for (Entry<GhidraBundle, Set<GhidraBundle>> entry : front.entrySet()) {
|
||||||
GhidraBundle source = e.getKey();
|
GhidraBundle source = entry.getKey();
|
||||||
if (addToAvailable(source)) {
|
if (addToAvailable(source)) {
|
||||||
addVertex(source);
|
addVertex(source);
|
||||||
Set<GhidraBundle> destinations = e.getValue();
|
Set<GhidraBundle> dependents = entry.getValue();
|
||||||
if (destinations != null) {
|
if (dependents != null) {
|
||||||
for (GhidraBundle destination : destinations) {
|
for (GhidraBundle destination : dependents) {
|
||||||
addEdge(source, destination, new Dependency());
|
addEdge(source, destination, new BundleEdge());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean addToAvailable(GhidraBundle bundle) {
|
private boolean addToAvailable(GhidraBundle bundle) {
|
||||||
try {
|
try {
|
||||||
capabilityMap.put(bundle, bundle.getAllCapabilities());
|
capabilityMap.put(bundle, bundle.getAllCapabilities());
|
||||||
availableBundles.add(bundle);
|
availableBundles.add(bundle);
|
||||||
|
@ -990,33 +994,38 @@ public class BundleHost {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// populate newFront with edges depBundle -> bundle,
|
// Populate newFront with edges supplierBundle -> dependentBundle, where supplierBundle has
|
||||||
// where depBundle has a capability that resolves a requirement of bundle
|
// a capability that resolves a requirement of dependentBundle. Items added to newFront are
|
||||||
void resolve(GhidraBundle bundle, Map<GhidraBundle, Set<GhidraBundle>> newFront) {
|
// already in the graph. These items will be added if the given bundle to resolve
|
||||||
|
// becomes a dependent on this added supplier.
|
||||||
|
private void resolve(GhidraBundle toResolve,
|
||||||
|
Map<GhidraBundle, Set<GhidraBundle>> newFront) {
|
||||||
List<BundleRequirement> requirements;
|
List<BundleRequirement> requirements;
|
||||||
try {
|
try {
|
||||||
requirements = new ArrayList<>(bundle.getAllRequirements());
|
List<BundleRequirement> bundleRequirements = toResolve.getAllRequirements();
|
||||||
if (requirements.isEmpty()) {
|
if (bundleRequirements.isEmpty()) {
|
||||||
return;
|
return; // no dependencies to resolve
|
||||||
}
|
}
|
||||||
|
requirements = new ArrayList<>(bundleRequirements);
|
||||||
}
|
}
|
||||||
catch (GhidraBundleException e) {
|
catch (GhidraBundleException e) {
|
||||||
fireBundleException(e);
|
fireBundleException(e);
|
||||||
removeVertex(bundle);
|
removeVertex(toResolve);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (GhidraBundle depBundle : availableBundles) {
|
for (GhidraBundle supplierBundle : availableBundles) {
|
||||||
for (BundleCapability capability : capabilityMap.get(depBundle)) {
|
for (BundleCapability capability : capabilityMap.get(supplierBundle)) {
|
||||||
if (monitor.isCancelled()) {
|
if (monitor.isCancelled()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Iterator<BundleRequirement> reqIter = requirements.iterator();
|
Iterator<BundleRequirement> it = requirements.iterator();
|
||||||
while (reqIter.hasNext()) {
|
while (it.hasNext()) {
|
||||||
BundleRequirement req = reqIter.next();
|
BundleRequirement req = it.next();
|
||||||
if (req.matches(capability)) {
|
if (req.matches(capability)) {
|
||||||
newFront.computeIfAbsent(depBundle, b -> new HashSet<>()).add(bundle);
|
newFront.computeIfAbsent(supplierBundle, b -> new HashSet<>())
|
||||||
reqIter.remove();
|
.add(toResolve);
|
||||||
|
it.remove();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (requirements.isEmpty()) {
|
if (requirements.isEmpty()) {
|
||||||
|
@ -1024,13 +1033,15 @@ public class BundleHost {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// if requirements remain, some will be resolved by system
|
|
||||||
// and others will generate helpful errors for the user during activation
|
// If requirements remain, some will be resolved by system and others will generate
|
||||||
|
// helpful errors for the user during activation
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The {@code BundleListener} that notifies {@link BundleHostListener}s of bundle activation changes
|
* The {@code BundleListener} that notifies {@link BundleHostListener}s of bundle activation
|
||||||
|
* changes.
|
||||||
*/
|
*/
|
||||||
private class MyBundleListener implements BundleListener {
|
private class MyBundleListener implements BundleListener {
|
||||||
private final Bundle systemBundle;
|
private final Bundle systemBundle;
|
||||||
|
@ -1050,8 +1061,10 @@ public class BundleHost {
|
||||||
if (STDERR_DEBUGGING) {
|
if (STDERR_DEBUGGING) {
|
||||||
String symbolicName = osgiBundle.getSymbolicName();
|
String symbolicName = osgiBundle.getSymbolicName();
|
||||||
String locationIdentifier = osgiBundle.getLocation();
|
String locationIdentifier = osgiBundle.getLocation();
|
||||||
System.err.printf("%s %s from %s\n", OSGiUtils.getEventTypeString(event),
|
String message =
|
||||||
|
String.format("CC: %s %s from %s\n", OSGiUtils.getEventTypeString(event),
|
||||||
symbolicName, locationIdentifier);
|
symbolicName, locationIdentifier);
|
||||||
|
Msg.debug(this, message);
|
||||||
}
|
}
|
||||||
GhidraBundle bundle;
|
GhidraBundle bundle;
|
||||||
switch (event.getType()) {
|
switch (event.getType()) {
|
||||||
|
|
|
@ -45,7 +45,7 @@ public class BundleMap {
|
||||||
bundlesByLocation.put(bundle.getLocationIdentifier(), bundle);
|
bundlesByLocation.put(bundle.getLocationIdentifier(), bundle);
|
||||||
}
|
}
|
||||||
finally {
|
finally {
|
||||||
lock.writeLock().unlock();
|
writeLock.unlock();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -197,6 +197,7 @@ public class BundleMap {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* Returns the currently mapped bundles.
|
||||||
* @return the currently mapped bundles
|
* @return the currently mapped bundles
|
||||||
*/
|
*/
|
||||||
public Collection<GhidraBundle> getGhidraBundles() {
|
public Collection<GhidraBundle> getGhidraBundles() {
|
||||||
|
@ -210,6 +211,7 @@ public class BundleMap {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* Returns the currently mapped bundle files.
|
||||||
* @return the currently mapped bundle files
|
* @return the currently mapped bundle files
|
||||||
*/
|
*/
|
||||||
public Collection<ResourceFile> getBundleFiles() {
|
public Collection<ResourceFile> getBundleFiles() {
|
||||||
|
|
|
@ -45,7 +45,7 @@ import resources.Icons;
|
||||||
import resources.ResourceManager;
|
import resources.ResourceManager;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* component for managing OSGi bundle status
|
* Component for managing OSGi bundle status
|
||||||
*/
|
*/
|
||||||
public class BundleStatusComponentProvider extends ComponentProviderAdapter {
|
public class BundleStatusComponentProvider extends ComponentProviderAdapter {
|
||||||
|
|
||||||
|
@ -289,7 +289,7 @@ public class BundleStatusComponentProvider extends ComponentProviderAdapter {
|
||||||
files.stream().map(ResourceFile::new).collect(Collectors.toUnmodifiableList());
|
files.stream().map(ResourceFile::new).collect(Collectors.toUnmodifiableList());
|
||||||
Collection<GhidraBundle> bundles = bundleHost.add(resourceFiles, true, false);
|
Collection<GhidraBundle> bundles = bundleHost.add(resourceFiles, true, false);
|
||||||
|
|
||||||
TaskLauncher.launchNonModal("activating new bundles", (monitor) -> {
|
TaskLauncher.launchNonModal("Activating new bundles", (monitor) -> {
|
||||||
bundleHost.activateAll(bundles, monitor,
|
bundleHost.activateAll(bundles, monitor,
|
||||||
getTool().getService(ConsoleService.class).getStdErr());
|
getTool().getService(ConsoleService.class).getStdErr());
|
||||||
});
|
});
|
||||||
|
@ -339,9 +339,6 @@ public class BundleStatusComponentProvider extends ComponentProviderAdapter {
|
||||||
return panel;
|
return panel;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* cleanup this component
|
|
||||||
*/
|
|
||||||
public void dispose() {
|
public void dispose() {
|
||||||
filterPanel.dispose();
|
filterPanel.dispose();
|
||||||
}
|
}
|
||||||
|
@ -351,9 +348,10 @@ public class BundleStatusComponentProvider extends ComponentProviderAdapter {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This is for testing only! during normal execution, statuses are only added through BundleHostListener bundle(s) added events.
|
* This is for testing only! during normal execution, statuses are only added through
|
||||||
|
* BundleHostListener bundle(s) added events.
|
||||||
*
|
*
|
||||||
* <p>each new bundle will be enabled and writable
|
* <p>Each new bundle will be enabled and writable
|
||||||
*
|
*
|
||||||
* @param bundleFiles the files to use
|
* @param bundleFiles the files to use
|
||||||
*/
|
*/
|
||||||
|
@ -363,6 +361,10 @@ public class BundleStatusComponentProvider extends ComponentProviderAdapter {
|
||||||
.collect(Collectors.toList()));
|
.collect(Collectors.toList()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//=================================================================================================
|
||||||
|
// Inner Classes
|
||||||
|
//=================================================================================================
|
||||||
|
|
||||||
private final class RemoveBundlesTask extends Task {
|
private final class RemoveBundlesTask extends Task {
|
||||||
private final DeactivateAndDisableBundlesTask deactivateBundlesTask;
|
private final DeactivateAndDisableBundlesTask deactivateBundlesTask;
|
||||||
private final List<BundleStatus> statuses;
|
private final List<BundleStatus> statuses;
|
||||||
|
@ -378,7 +380,7 @@ public class BundleStatusComponentProvider extends ComponentProviderAdapter {
|
||||||
public void run(TaskMonitor monitor) throws CancelledException {
|
public void run(TaskMonitor monitor) throws CancelledException {
|
||||||
deactivateBundlesTask.run(monitor);
|
deactivateBundlesTask.run(monitor);
|
||||||
monitor.checkCanceled();
|
monitor.checkCanceled();
|
||||||
// partition bundles into system (bundles.get(true)) and non-system (bundles.get(false)).
|
// partition bundles into system (bundles.get(true)) / non-system (bundles.get(false))
|
||||||
Map<Boolean, List<GhidraBundle>> bundles = statuses.stream()
|
Map<Boolean, List<GhidraBundle>> bundles = statuses.stream()
|
||||||
.map(bs -> bundleHost.getExistingGhidraBundle(bs.getFile()))
|
.map(bs -> bundleHost.getExistingGhidraBundle(bs.getFile()))
|
||||||
.collect(Collectors.partitioningBy(GhidraBundle::isSystemBundle));
|
.collect(Collectors.partitioningBy(GhidraBundle::isSystemBundle));
|
||||||
|
@ -483,8 +485,8 @@ public class BundleStatusComponentProvider extends ComponentProviderAdapter {
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Activating/deactivating a single bundle doesn't require resolving dependents,
|
* Activating/deactivating a single bundle doesn't require resolving dependents, so this task
|
||||||
* so this task is slightly different from the others.
|
* is slightly different from the others.
|
||||||
*/
|
*/
|
||||||
private class ActivateDeactivateBundleTask extends Task {
|
private class ActivateDeactivateBundleTask extends Task {
|
||||||
private final BundleStatus status;
|
private final BundleStatus status;
|
||||||
|
|
|
@ -22,7 +22,7 @@ import java.util.concurrent.CopyOnWriteArrayList;
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import javax.swing.*;
|
import javax.swing.JLabel;
|
||||||
import javax.swing.event.TableModelEvent;
|
import javax.swing.event.TableModelEvent;
|
||||||
|
|
||||||
import org.osgi.framework.Bundle;
|
import org.osgi.framework.Bundle;
|
||||||
|
@ -32,8 +32,10 @@ import generic.jar.ResourceFile;
|
||||||
import generic.util.Path;
|
import generic.util.Path;
|
||||||
import ghidra.docking.settings.Settings;
|
import ghidra.docking.settings.Settings;
|
||||||
import ghidra.framework.plugintool.ServiceProvider;
|
import ghidra.framework.plugintool.ServiceProvider;
|
||||||
import ghidra.util.*;
|
import ghidra.util.Swing;
|
||||||
import ghidra.util.table.column.*;
|
import ghidra.util.SystemUtilities;
|
||||||
|
import ghidra.util.table.column.AbstractGColumnRenderer;
|
||||||
|
import ghidra.util.table.column.GColumnRenderer;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Model for {@link BundleStatus} objects.
|
* Model for {@link BundleStatus} objects.
|
||||||
|
@ -73,10 +75,10 @@ public class BundleStatusTableModel
|
||||||
}
|
}
|
||||||
|
|
||||||
private BundleStatus getStatus(GhidraBundle bundle) {
|
private BundleStatus getStatus(GhidraBundle bundle) {
|
||||||
return getStatusFromLoc(bundle.getLocationIdentifier());
|
return getStatusFromLocation(bundle.getLocationIdentifier());
|
||||||
}
|
}
|
||||||
|
|
||||||
private BundleStatus getStatusFromLoc(String bundleLoc) {
|
private BundleStatus getStatusFromLocation(String bundleLoc) {
|
||||||
return bundleLocToStatusMap.get(bundleLoc);
|
return bundleLocToStatusMap.get(bundleLoc);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -254,7 +256,7 @@ public class BundleStatusTableModel
|
||||||
// wrap the assigned comparator to detect if the order changes
|
// wrap the assigned comparator to detect if the order changes
|
||||||
|
|
||||||
AtomicBoolean changed = new AtomicBoolean(false);
|
AtomicBoolean changed = new AtomicBoolean(false);
|
||||||
Comparator<BundleStatus> wrapper = new Comparator<BundleStatus>() {
|
Comparator<BundleStatus> wrapper = new Comparator<>() {
|
||||||
Comparator<BundleStatus> comparator = sortingContext.getComparator();
|
Comparator<BundleStatus> comparator = sortingContext.getComparator();
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -367,7 +369,7 @@ public class BundleStatusTableModel
|
||||||
@Override
|
@Override
|
||||||
public void bundleException(GhidraBundleException exception) {
|
public void bundleException(GhidraBundleException exception) {
|
||||||
Swing.runLater(() -> {
|
Swing.runLater(() -> {
|
||||||
BundleStatus status = getStatusFromLoc(exception.getBundleLocation());
|
BundleStatus status = getStatusFromLocation(exception.getBundleLocation());
|
||||||
if (status != null) {
|
if (status != null) {
|
||||||
status.setSummary(exception.getMessage());
|
status.setSummary(exception.getMessage());
|
||||||
int rowIndex = getRowIndex(status);
|
int rowIndex = getRowIndex(status);
|
||||||
|
|
|
@ -30,8 +30,20 @@ import generic.jar.ResourceFile;
|
||||||
*/
|
*/
|
||||||
public abstract class GhidraBundle {
|
public abstract class GhidraBundle {
|
||||||
|
|
||||||
protected final ResourceFile file;
|
/**
|
||||||
|
* A {@link GhidraBundle} can be
|
||||||
|
* <ul>
|
||||||
|
* <li>a Bndtools .bnd script</li>
|
||||||
|
* <li>an OSGi bundle .jar file</li>
|
||||||
|
* <li>a directory of Java source</li>
|
||||||
|
* </ul>
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
enum Type {
|
||||||
|
BND_SCRIPT, JAR, SOURCE_DIR, INVALID
|
||||||
|
}
|
||||||
|
|
||||||
|
protected final ResourceFile bundleFile; // can be a dir or a jar file
|
||||||
protected final BundleHost bundleHost;
|
protected final BundleHost bundleHost;
|
||||||
protected boolean enabled;
|
protected boolean enabled;
|
||||||
protected boolean systemBundle;
|
protected boolean systemBundle;
|
||||||
|
@ -39,29 +51,29 @@ public abstract class GhidraBundle {
|
||||||
GhidraBundle(BundleHost bundleHost, ResourceFile bundleFile, boolean enabled,
|
GhidraBundle(BundleHost bundleHost, ResourceFile bundleFile, boolean enabled,
|
||||||
boolean systemBundle) {
|
boolean systemBundle) {
|
||||||
this.bundleHost = bundleHost;
|
this.bundleHost = bundleHost;
|
||||||
this.file = bundleFile;
|
this.bundleFile = bundleFile;
|
||||||
this.enabled = enabled;
|
this.enabled = enabled;
|
||||||
this.systemBundle = systemBundle;
|
this.systemBundle = systemBundle;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* clean build artifacts generated during build of this bundle
|
* Clean build artifacts generated during build of this bundle.
|
||||||
*
|
*
|
||||||
* @return true if anything was done
|
* @return true if anything was done
|
||||||
*/
|
*/
|
||||||
abstract boolean clean();
|
abstract boolean clean();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* build OSGi bundle if possible
|
* Build OSGi bundle if needed and if possible.
|
||||||
*
|
*
|
||||||
* @param writer console for build messages to user
|
* @param writer console for build messages to user
|
||||||
* @return true if build happened, false if already built
|
* @return true if build happened, false if already built or could not build
|
||||||
* @throws Exception if the build cannot complete
|
* @throws Exception if the build cannot complete
|
||||||
*/
|
*/
|
||||||
public abstract boolean build(PrintWriter writer) throws Exception;
|
public abstract boolean build(PrintWriter writer) throws Exception;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* same as {@link #build(PrintWriter)} with writer = {@link System#err}.
|
* Same as {@link #build(PrintWriter)} with writer = {@link System#err}.
|
||||||
*
|
*
|
||||||
* @return true if build happened, false if already built
|
* @return true if build happened, false if already built
|
||||||
* @throws Exception if the build cannot complete
|
* @throws Exception if the build cannot complete
|
||||||
|
@ -74,28 +86,43 @@ public abstract class GhidraBundle {
|
||||||
* Return the location identifier of the bundle that this GhidraBundle represents.
|
* Return the location identifier of the bundle that this GhidraBundle represents.
|
||||||
*
|
*
|
||||||
* <p>The location identifier is used by the framework, e.g. it is passed to
|
* <p>The location identifier is used by the framework, e.g. it is passed to
|
||||||
* {@link org.osgi.framework.BundleContext#installBundle} when the bundle is
|
* {@link org.osgi.framework.BundleContext#installBundle} when the bundle is first installed.
|
||||||
* first installed.
|
|
||||||
*
|
*
|
||||||
* <p>Although the bundle location is a URI, outside of interactions with the framework,
|
* <p>Although the bundle location is a URI, outside of interactions with the framework, the
|
||||||
* the bundle location should remain opaque.
|
* bundle location should remain opaque.
|
||||||
*
|
*
|
||||||
* @return location identifier of this bundle
|
* @return location identifier of this bundle
|
||||||
*/
|
*/
|
||||||
public abstract String getLocationIdentifier();
|
public abstract String getLocationIdentifier();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns all bundle requirements.
|
||||||
|
*
|
||||||
|
* @return the requirements
|
||||||
|
* @throws GhidraBundleException if there is an exception parsing / loading bundle requirements
|
||||||
|
*/
|
||||||
public abstract List<BundleRequirement> getAllRequirements() throws GhidraBundleException;
|
public abstract List<BundleRequirement> getAllRequirements() throws GhidraBundleException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns all bundle capabilities.
|
||||||
|
*
|
||||||
|
* @return the capabilities
|
||||||
|
* @throws GhidraBundleException if there is an exception parsing / loading bundle capabilities
|
||||||
|
*/
|
||||||
public abstract List<BundleCapability> getAllCapabilities() throws GhidraBundleException;
|
public abstract List<BundleCapability> getAllCapabilities() throws GhidraBundleException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return the file where this bundle is loaded from
|
* The file where this bundle is loaded from.
|
||||||
|
*
|
||||||
|
* @return the file from where this bundle is loaded
|
||||||
*/
|
*/
|
||||||
public ResourceFile getFile() {
|
public ResourceFile getFile() {
|
||||||
return file;
|
return bundleFile;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* True if this bundle is enabled.
|
||||||
|
*
|
||||||
* @return true if this bundle is enabled
|
* @return true if this bundle is enabled
|
||||||
*/
|
*/
|
||||||
public boolean isEnabled() {
|
public boolean isEnabled() {
|
||||||
|
@ -103,7 +130,7 @@ public abstract class GhidraBundle {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* set the enablement flag for this bundle.
|
* Set the enablement flag for this bundle.
|
||||||
*
|
*
|
||||||
* <p>If a bundle is enabled its contents will be scanned, e.g. for scripts.
|
* <p>If a bundle is enabled its contents will be scanned, e.g. for scripts.
|
||||||
*
|
*
|
||||||
|
@ -123,7 +150,7 @@ public abstract class GhidraBundle {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the type of a GhidraBundle from its file.
|
* Get the type of {@link GhidraBundle} from its file.
|
||||||
*
|
*
|
||||||
* @param file a bundle file
|
* @param file a bundle file
|
||||||
* @return the type
|
* @return the type
|
||||||
|
@ -163,8 +190,8 @@ public abstract class GhidraBundle {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the OSGi bundle represented by this GhidraBundle or null if it isn't in
|
* Get the OSGi bundle represented by this GhidraBundle or null if it isn't in the "installed"
|
||||||
* the "installed" state.
|
* state.
|
||||||
*
|
*
|
||||||
* @return a Bundle or null
|
* @return a Bundle or null
|
||||||
*/
|
*/
|
||||||
|
@ -173,6 +200,8 @@ public abstract class GhidraBundle {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* True if this bundle is active.
|
||||||
|
*
|
||||||
* @return true if this bundle is active
|
* @return true if this bundle is active
|
||||||
*/
|
*/
|
||||||
public boolean isActive() {
|
public boolean isActive() {
|
||||||
|
@ -180,17 +209,8 @@ public abstract class GhidraBundle {
|
||||||
return (bundle != null) && bundle.getState() == Bundle.ACTIVE;
|
return (bundle != null) && bundle.getState() == Bundle.ACTIVE;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
@Override
|
||||||
* A GhidraBundle can be
|
public String toString() {
|
||||||
* <ul>
|
return getOSGiBundle().getSymbolicName();
|
||||||
* <li>a Bndtools .bnd script</li>
|
|
||||||
* <li>an OSGi bundle .jar file</li>
|
|
||||||
* <li>a directory of Java source</li>
|
|
||||||
* </ul>
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
enum Type {
|
|
||||||
BND_SCRIPT, JAR, SOURCE_DIR, INVALID
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -67,7 +67,7 @@ public class GhidraJarBundle extends GhidraBundle {
|
||||||
}
|
}
|
||||||
|
|
||||||
protected ManifestParser createManifestParser() throws GhidraBundleException {
|
protected ManifestParser createManifestParser() throws GhidraBundleException {
|
||||||
try (Jar jar = new Jar(file.getFile(true))) {
|
try (Jar jar = new Jar(bundleFile.getFile(true))) {
|
||||||
Manifest manifest = jar.getManifest();
|
Manifest manifest = jar.getManifest();
|
||||||
if (manifest == null) {
|
if (manifest == null) {
|
||||||
throw new GhidraBundleException(bundleLocation, "jar bundle with no manifest");
|
throw new GhidraBundleException(bundleLocation, "jar bundle with no manifest");
|
||||||
|
|
|
@ -35,8 +35,7 @@ import javax.tools.*;
|
||||||
import javax.tools.JavaFileObject.Kind;
|
import javax.tools.JavaFileObject.Kind;
|
||||||
|
|
||||||
import org.apache.felix.framework.util.manifestparser.ManifestParser;
|
import org.apache.felix.framework.util.manifestparser.ManifestParser;
|
||||||
import org.osgi.framework.Bundle;
|
import org.osgi.framework.*;
|
||||||
import org.osgi.framework.BundleException;
|
|
||||||
import org.osgi.framework.Constants;
|
import org.osgi.framework.Constants;
|
||||||
import org.osgi.framework.wiring.*;
|
import org.osgi.framework.wiring.*;
|
||||||
import org.phidias.compile.BundleJavaManager;
|
import org.phidias.compile.BundleJavaManager;
|
||||||
|
@ -47,17 +46,16 @@ import generic.io.NullPrintWriter;
|
||||||
import generic.jar.ResourceFile;
|
import generic.jar.ResourceFile;
|
||||||
import ghidra.app.script.*;
|
import ghidra.app.script.*;
|
||||||
import ghidra.util.Msg;
|
import ghidra.util.Msg;
|
||||||
|
import util.CollectionUtils;
|
||||||
import utilities.util.FileUtilities;
|
import utilities.util.FileUtilities;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@link GhidraSourceBundle} represents a Java source directory that is compiled on build to an OSGi bundle.
|
* Represents a Java source directory that is compiled on build to an OSGi bundle.
|
||||||
*
|
*
|
||||||
* <p>A manifest and {@link BundleActivator} are generated if not already present.
|
* <p>A manifest and {@link BundleActivator} are generated if not already present.
|
||||||
*/
|
*/
|
||||||
/**
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
public class GhidraSourceBundle extends GhidraBundle {
|
public class GhidraSourceBundle extends GhidraBundle {
|
||||||
|
private static final String INSTRCTION_ACTIVATOR = "org.osgi.framework.BundleActivator";
|
||||||
private static final String GENERATED_ACTIVATOR_CLASSNAME = "GeneratedActivator";
|
private static final String GENERATED_ACTIVATOR_CLASSNAME = "GeneratedActivator";
|
||||||
private static final String GENERATED_VERSION = "1.0";
|
private static final String GENERATED_VERSION = "1.0";
|
||||||
|
|
||||||
|
@ -70,43 +68,53 @@ public class GhidraSourceBundle extends GhidraBundle {
|
||||||
private static final Predicate<String> IS_CLASS_FILE =
|
private static final Predicate<String> IS_CLASS_FILE =
|
||||||
Pattern.compile("(\\$.*)?\\.class", Pattern.CASE_INSENSITIVE).asMatchPredicate();
|
Pattern.compile("(\\$.*)?\\.class", Pattern.CASE_INSENSITIVE).asMatchPredicate();
|
||||||
|
|
||||||
protected interface DiscrepencyCallback {
|
|
||||||
/**
|
/**
|
||||||
* Invoked when there is a discrepancy between {@code sourceFile} and its
|
* Used to report source and class file deviation
|
||||||
* corresponding class file(s), {@code classFiles}
|
*/
|
||||||
|
protected interface DiscrepancyCallback {
|
||||||
|
/**
|
||||||
|
* Invoked when there is a discrepancy between {@code sourceFile} and its corresponding
|
||||||
|
* class file(s), {@code classFiles}
|
||||||
*
|
*
|
||||||
* @param sourceFile the source file or null to indicate the class files have no corresponding source
|
* @param sourceFile the source file or null to indicate the class files have no
|
||||||
|
* corresponding source
|
||||||
* @param classFiles corresponding class file(s)
|
* @param classFiles corresponding class file(s)
|
||||||
* @throws Throwable an exception
|
* @throws Throwable an exception
|
||||||
*/
|
*/
|
||||||
void found(ResourceFile sourceFile, Collection<Path> classFiles) throws Throwable;
|
void found(ResourceFile sourceFile, Collection<Path> classFiles) throws Throwable;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// This is a hash; it is used to create a unique directory name for this bundle
|
||||||
private final String symbolicName;
|
private final String symbolicName;
|
||||||
private final Path binaryDir;
|
private final Path binaryDir;
|
||||||
private final String bundleLoc;
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The bundle location id
|
||||||
|
* @see #getLocationIdentifier()
|
||||||
|
*/
|
||||||
|
private final String bundleLocationId;
|
||||||
|
|
||||||
|
// These 2 lists are updated to track source file to class file changes; newSources are those
|
||||||
|
// that need to be compiled; oldBinaries are those that no longer have source files (such as
|
||||||
|
// when a source file is deleted)
|
||||||
private final List<ResourceFile> newSources = new ArrayList<>();
|
private final List<ResourceFile> newSources = new ArrayList<>();
|
||||||
private final List<Path> oldBinaries = new ArrayList<>();
|
private final List<Path> oldBinaries = new ArrayList<>();
|
||||||
|
|
||||||
private JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
|
private JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
|
||||||
|
|
||||||
//// information indexed by source file
|
// information indexed by source file
|
||||||
private final Map<ResourceFile, BuildError> buildErrors = new HashMap<>();
|
private final Map<ResourceFile, BuildError> buildErrors = new HashMap<>();
|
||||||
private final Map<ResourceFile, List<BundleRequirement>> sourceFileToRequirements =
|
private final Map<ResourceFile, List<BundleRequirement>> sourceFileToRequirements =
|
||||||
new HashMap<>();
|
new HashMap<>();
|
||||||
|
|
||||||
private final Map<String, List<ResourceFile>> requirementToSourceFileMap = new HashMap<>();
|
private final Map<String, List<ResourceFile>> requirementToSourceFileMap = new HashMap<>();
|
||||||
|
private final Set<String> missedRequirements = new HashSet<>();
|
||||||
private final Set<String> importPackageValues = new HashSet<>();
|
private final Set<String> importPackageValues = new HashSet<>();
|
||||||
|
|
||||||
private Set<String> missedRequirements = new HashSet<>();
|
|
||||||
|
|
||||||
private long lastCompileAttempt;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a new GhidraSourceBundle.
|
* Create a new GhidraSourceBundle.
|
||||||
*
|
*
|
||||||
* @param bundleHost the {@link BundleHost} instance this bundle will belong to
|
* @param bundleHost the instance this bundle will belong to
|
||||||
* @param sourceDirectory the source bundle directory
|
* @param sourceDirectory the source bundle directory
|
||||||
* @param enabled true to start enabled
|
* @param enabled true to start enabled
|
||||||
* @param systemBundle true if this is a Ghidra system bundle
|
* @param systemBundle true if this is a Ghidra system bundle
|
||||||
|
@ -115,18 +123,18 @@ public class GhidraSourceBundle extends GhidraBundle {
|
||||||
boolean systemBundle) {
|
boolean systemBundle) {
|
||||||
super(bundleHost, sourceDirectory, enabled, systemBundle);
|
super(bundleHost, sourceDirectory, enabled, systemBundle);
|
||||||
|
|
||||||
this.symbolicName = GhidraSourceBundle.sourceDirHash(getSourceDirectory());
|
this.symbolicName = sourceDirHash(getSourceDirectory());
|
||||||
this.binaryDir = GhidraSourceBundle.getCompiledBundlesDir().resolve(symbolicName);
|
this.binaryDir = getCompiledBundlesDir().resolve(symbolicName);
|
||||||
|
this.bundleLocationId =
|
||||||
this.bundleLoc = "reference:file://" + binaryDir.toAbsolutePath().normalize().toString();
|
"reference:file://" + binaryDir.toAbsolutePath().normalize().toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* (alias of {@link #getFile} for readability)
|
* (alias of {@link #getFile} for readability)
|
||||||
* @return the source directory this bundle represents
|
* @return the source directory this bundle represents
|
||||||
*/
|
*/
|
||||||
protected final ResourceFile getSourceDirectory() {
|
private final ResourceFile getSourceDirectory() {
|
||||||
return file;
|
return bundleFile;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -142,8 +150,8 @@ public class GhidraSourceBundle extends GhidraBundle {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* When a source bundle doesn't have a manifest, Ghidra computes the bundle's
|
* When a source bundle doesn't have a manifest, Ghidra computes the bundle's symbolic name as
|
||||||
* symbolic name as a hash of the source directory path.
|
* a hash of the source directory path.
|
||||||
*
|
*
|
||||||
* <p>This hash is also used as the final path component of the compile destination:
|
* <p>This hash is also used as the final path component of the compile destination:
|
||||||
* <br/> {@code $USERHOME/.ghidra/.ghidra_<ghidra version>/osgi/compiled-bundles/<sourceDirHash> }
|
* <br/> {@code $USERHOME/.ghidra/.ghidra_<ghidra version>/osgi/compiled-bundles/<sourceDirHash> }
|
||||||
|
@ -157,18 +165,6 @@ public class GhidraSourceBundle extends GhidraBundle {
|
||||||
return Integer.toHexString(sourceDir.getAbsolutePath().hashCode());
|
return Integer.toHexString(sourceDir.getAbsolutePath().hashCode());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* for testing only!!!
|
|
||||||
*
|
|
||||||
* @param sourceFile a ghidra script file
|
|
||||||
* @return the directory its class is compiled to
|
|
||||||
*/
|
|
||||||
public static Path getBindirFromScriptFile(ResourceFile sourceFile) {
|
|
||||||
ResourceFile tmpSourceDir = sourceFile.getParentFile();
|
|
||||||
String tmpSymbolicName = GhidraSourceBundle.sourceDirHash(tmpSourceDir);
|
|
||||||
return GhidraSourceBundle.getCompiledBundlesDir().resolve(tmpSymbolicName);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return the class name corresponding to a script in this source bundle.
|
* Return the class name corresponding to a script in this source bundle.
|
||||||
*
|
*
|
||||||
|
@ -188,16 +184,16 @@ public class GhidraSourceBundle extends GhidraBundle {
|
||||||
return relativePath.replace(File.separatorChar, '.');
|
return relativePath.replace(File.separatorChar, '.');
|
||||||
}
|
}
|
||||||
|
|
||||||
void clearBuildErrors(ResourceFile sourceFile) {
|
private void clearBuildErrors(ResourceFile sourceFile) {
|
||||||
buildErrors.remove(sourceFile);
|
buildErrors.remove(sourceFile);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* append build error
|
* Append the given build error.
|
||||||
* @param sourceFile the file w/ errors
|
* @param sourceFile the file w/ errors
|
||||||
* @param err an error string
|
* @param err an error string
|
||||||
*/
|
*/
|
||||||
protected void buildError(ResourceFile sourceFile, String err) {
|
private void buildError(ResourceFile sourceFile, String err) {
|
||||||
BuildError error = buildErrors.computeIfAbsent(sourceFile, BuildError::new);
|
BuildError error = buildErrors.computeIfAbsent(sourceFile, BuildError::new);
|
||||||
error.append(err);
|
error.append(err);
|
||||||
}
|
}
|
||||||
|
@ -206,7 +202,7 @@ public class GhidraSourceBundle extends GhidraBundle {
|
||||||
* Get any errors associated with building the given source file.
|
* Get any errors associated with building the given source file.
|
||||||
*
|
*
|
||||||
* @param sourceFile the source file
|
* @param sourceFile the source file
|
||||||
* @return a {@link BuildError} object
|
* @return the build error or null if no errors
|
||||||
*/
|
*/
|
||||||
public BuildError getErrors(ResourceFile sourceFile) {
|
public BuildError getErrors(ResourceFile sourceFile) {
|
||||||
return buildErrors.get(sourceFile);
|
return buildErrors.get(sourceFile);
|
||||||
|
@ -233,9 +229,10 @@ public class GhidraSourceBundle extends GhidraBundle {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* update buildReqs based on \@importpackage tag in java files in the default(unnamed) package
|
* Update build requirements based on {@code @importpackage} tag in java files in the
|
||||||
|
* default (unnamed) package.
|
||||||
*
|
*
|
||||||
* @throws GhidraBundleException on failure to parse the \@importpackage tag
|
* @throws GhidraBundleException on failure to parse the {@code @importpackage} tag
|
||||||
*/
|
*/
|
||||||
private void updateRequirementsFromMetadata() throws GhidraBundleException {
|
private void updateRequirementsFromMetadata() throws GhidraBundleException {
|
||||||
sourceFileToRequirements.clear();
|
sourceFileToRequirements.clear();
|
||||||
|
@ -243,17 +240,23 @@ public class GhidraSourceBundle extends GhidraBundle {
|
||||||
importPackageValues.clear();
|
importPackageValues.clear();
|
||||||
|
|
||||||
for (ResourceFile rootSourceFile : getSourceDirectory().listFiles()) {
|
for (ResourceFile rootSourceFile : getSourceDirectory().listFiles()) {
|
||||||
if (rootSourceFile.getName().endsWith(".java")) {
|
if (!rootSourceFile.getName().endsWith(".java")) {
|
||||||
// without GhidraScriptComponentProvider.updateAvailableScriptFilesForDirectory,
|
continue;
|
||||||
// or GhidraScriptComponentProvider.newScript this might be the earliest need for
|
}
|
||||||
|
|
||||||
|
// Without GhidraScriptComponentProvider.updateAvailableScriptFilesForDirectory, or
|
||||||
|
// GhidraScriptComponentProvider.newScript this might be the earliest need for
|
||||||
// ScriptInfo, so allow construction.
|
// ScriptInfo, so allow construction.
|
||||||
|
|
||||||
// NB: ScriptInfo will update field values if lastModified has changed since last time they were computed
|
// NB: ScriptInfo will update field values if lastModified has changed since last time
|
||||||
|
// they were computed
|
||||||
String importPackage = parseImportPackageMetadata(rootSourceFile);
|
String importPackage = parseImportPackageMetadata(rootSourceFile);
|
||||||
if (importPackage != null && !importPackage.isEmpty()) {
|
if (importPackage == null || importPackage.isEmpty()) {
|
||||||
importPackageValues.addAll(
|
continue;
|
||||||
ManifestParser.parseDelimitedString(importPackage.strip(), ","));
|
}
|
||||||
|
|
||||||
|
List<String> parts = ManifestParser.parseDelimitedString(importPackage.strip(), ",");
|
||||||
|
importPackageValues.addAll(parts);
|
||||||
List<BundleRequirement> requirements;
|
List<BundleRequirement> requirements;
|
||||||
try {
|
try {
|
||||||
requirements = OSGiUtils.parseImportPackage(importPackage);
|
requirements = OSGiUtils.parseImportPackage(importPackage);
|
||||||
|
@ -270,11 +273,9 @@ public class GhidraSourceBundle extends GhidraBundle {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* assumes that {@link #updateRequirementsFromMetadata()} has been called recently
|
* Assumes that {@link #updateRequirementsFromMetadata()} has been called recently
|
||||||
*
|
*
|
||||||
* @return deduped requirements
|
* @return deduped requirements
|
||||||
*/
|
*/
|
||||||
|
@ -288,11 +289,14 @@ public class GhidraSourceBundle extends GhidraBundle {
|
||||||
return dedupedReqs;
|
return dedupedReqs;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected ManifestParser createSourceManifestParser() {
|
private ManifestParser createSourceManifestParser() {
|
||||||
ResourceFile manifestFile = getSourceManifestFile();
|
ResourceFile manifestFile = getSourceManifestFile();
|
||||||
if (manifestFile.exists()) {
|
if (!manifestFile.exists()) {
|
||||||
try (InputStream manifestInputStream = manifestFile.getInputStream()) {
|
return null;
|
||||||
Manifest manifest = new Manifest(manifestInputStream);
|
}
|
||||||
|
|
||||||
|
try (InputStream is = manifestFile.getInputStream()) {
|
||||||
|
Manifest manifest = new Manifest(is);
|
||||||
Attributes mainAttributes = manifest.getMainAttributes();
|
Attributes mainAttributes = manifest.getMainAttributes();
|
||||||
Map<String, Object> headerMap = mainAttributes.entrySet()
|
Map<String, Object> headerMap = mainAttributes.entrySet()
|
||||||
.stream()
|
.stream()
|
||||||
|
@ -304,14 +308,12 @@ public class GhidraSourceBundle extends GhidraBundle {
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<BundleRequirement> getAllRequirements() throws GhidraBundleException {
|
public List<BundleRequirement> getAllRequirements() throws GhidraBundleException {
|
||||||
ManifestParser manifestParser = createSourceManifestParser();
|
ManifestParser parser = createSourceManifestParser();
|
||||||
if (manifestParser != null) {
|
if (parser != null) {
|
||||||
return manifestParser.getRequirements();
|
return parser.getRequirements();
|
||||||
}
|
}
|
||||||
|
|
||||||
updateRequirementsFromMetadata();
|
updateRequirementsFromMetadata();
|
||||||
|
@ -319,17 +321,17 @@ public class GhidraSourceBundle extends GhidraBundle {
|
||||||
return new ArrayList<>(reqs.values());
|
return new ArrayList<>(reqs.values());
|
||||||
}
|
}
|
||||||
|
|
||||||
protected static void findPackageDirs(List<String> packages, ResourceFile directory) {
|
private static void findPackageDirs(List<String> packages, ResourceFile dir) {
|
||||||
for (ResourceFile file : directory
|
|
||||||
.listFiles(f -> f.isDirectory() || f.getName().endsWith(".java"))) {
|
|
||||||
if (!file.getName().matches("internal|private")) {
|
|
||||||
boolean added = false;
|
boolean added = false;
|
||||||
|
ResourceFile[] files = dir.listFiles(f -> f.isDirectory() || f.getName().endsWith(".java"));
|
||||||
|
for (ResourceFile file : files) {
|
||||||
|
if (!file.getName().matches("internal|private")) {
|
||||||
if (file.isDirectory()) {
|
if (file.isDirectory()) {
|
||||||
findPackageDirs(packages, file);
|
findPackageDirs(packages, file);
|
||||||
}
|
}
|
||||||
else if (!added) {
|
else if (!added) {
|
||||||
added = true;
|
added = true;
|
||||||
packages.add(directory.getAbsolutePath());
|
packages.add(dir.getAbsolutePath());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -363,21 +365,23 @@ public class GhidraSourceBundle extends GhidraBundle {
|
||||||
return OSGiUtils.parseExportPackage(sb.substring(1));
|
return OSGiUtils.parseExportPackage(sb.substring(1));
|
||||||
}
|
}
|
||||||
catch (BundleException e) {
|
catch (BundleException e) {
|
||||||
throw new GhidraBundleException(getLocationIdentifier(), "exports error", e);
|
throw new GhidraBundleException(getLocationIdentifier(), "Exports error", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* look for new sources, metadata, manifest file.
|
* Look for new sources, metadata, manifest file. This will find files that need to be
|
||||||
|
* compiled and files that need to be removed.
|
||||||
*
|
*
|
||||||
* <p>if files had errors last time, haven't changed, and no new requirements are available, remove them.
|
* <p>If files had errors last time, haven't changed, and no new requirements are available,
|
||||||
|
* remove them.
|
||||||
*
|
*
|
||||||
* @param writer for reporting status to user
|
* @param writer for reporting status to user
|
||||||
* @throws IOException while accessing manifest file
|
* @throws IOException while accessing manifest file
|
||||||
* @throws OSGiException while parsing imports
|
* @throws OSGiException while parsing imports
|
||||||
*/
|
*/
|
||||||
void updateFromFilesystem(PrintWriter writer) throws IOException, OSGiException {
|
private void updateFromFilesystem(PrintWriter writer) throws IOException, OSGiException {
|
||||||
// look for new source files
|
|
||||||
newSources.clear();
|
newSources.clear();
|
||||||
oldBinaries.clear();
|
oldBinaries.clear();
|
||||||
|
|
||||||
|
@ -395,13 +399,13 @@ public class GhidraSourceBundle extends GhidraBundle {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// we don't want to rebuild source files that had errors last time and haven't changed,
|
// we don't want to rebuild source files that had errors last time and haven't changed, so
|
||||||
// so remove them from newSources. Also remove old error messages.
|
// remove them from newSources. Also remove old error messages.
|
||||||
Iterator<ResourceFile> newSourceIterator = newSources.iterator();
|
Iterator<ResourceFile> it = newSources.iterator();
|
||||||
while (newSourceIterator.hasNext()) {
|
while (it.hasNext()) {
|
||||||
ResourceFile newSourceFile = newSourceIterator.next();
|
ResourceFile newSourceFile = it.next();
|
||||||
if (stillHasErrors(newSourceFile)) {
|
if (stillHasErrors(newSourceFile)) {
|
||||||
newSourceIterator.remove();
|
it.remove();
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
// any errors are old, so remove them
|
// any errors are old, so remove them
|
||||||
|
@ -421,46 +425,40 @@ public class GhidraSourceBundle extends GhidraBundle {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void deleteOldBinaries() throws IOException {
|
private void deleteOldBinaries() throws IOException {
|
||||||
// dedupe and omit files that don't exist
|
// dedup and omit files that don't exist
|
||||||
oldBinaries.sort(null);
|
oldBinaries.sort(null);
|
||||||
Iterable<Path> paths =
|
|
||||||
() -> oldBinaries.stream().distinct().filter(Files::exists).iterator();
|
|
||||||
|
|
||||||
for (Path path : paths) {
|
Iterator<Path> toDelete =
|
||||||
|
oldBinaries.stream().distinct().filter(Files::exists).sorted().iterator();
|
||||||
|
for (Path path : CollectionUtils.asIterable(toDelete)) {
|
||||||
Files.delete(path);
|
Files.delete(path);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
oldBinaries.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
int getBuildErrorCount() {
|
private int getBuildErrorCount() {
|
||||||
return buildErrors.size();
|
return buildErrors.size();
|
||||||
}
|
}
|
||||||
|
|
||||||
int getNewSourcesCount() {
|
private int getNewSourcesCount() {
|
||||||
return newSources.size();
|
return newSources.size();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* used just after {@link #build} to get the newly compiled source files
|
* Used just after {@link #build} to get the newly compiled source files.
|
||||||
* @return new source files
|
* @return new source files
|
||||||
*/
|
*/
|
||||||
public List<ResourceFile> getNewSources() {
|
public List<ResourceFile> getNewSources() {
|
||||||
return newSources;
|
return Collections.unmodifiableList(newSources);
|
||||||
}
|
|
||||||
|
|
||||||
void compileAttempted() {
|
|
||||||
lastCompileAttempt = System.currentTimeMillis();
|
|
||||||
}
|
|
||||||
|
|
||||||
long getLastCompileAttempt() {
|
|
||||||
return lastCompileAttempt;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getLocationIdentifier() {
|
public String getLocationIdentifier() {
|
||||||
return bundleLoc;
|
return bundleLocationId;
|
||||||
}
|
}
|
||||||
|
|
||||||
ResourceFile getSourceManifestFile() {
|
private ResourceFile getSourceManifestFile() {
|
||||||
return new ResourceFile(getSourceDirectory(), "META-INF" + File.separator + "MANIFEST.MF");
|
return new ResourceFile(getSourceDirectory(), "META-INF" + File.separator + "MANIFEST.MF");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -468,19 +466,20 @@ public class GhidraSourceBundle extends GhidraBundle {
|
||||||
return binaryDir.resolve("META-INF").resolve("MANIFEST.MF");
|
return binaryDir.resolve("META-INF").resolve("MANIFEST.MF");
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean hasSourceManifest() {
|
private boolean hasSourceManifest() {
|
||||||
return getSourceManifestFile().exists();
|
return getSourceManifestFile().exists();
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean hasNewManifest() {
|
private boolean hasNewManifest() {
|
||||||
ResourceFile sourceManifest = getSourceManifestFile();
|
ResourceFile sourceManifest = getSourceManifestFile();
|
||||||
Path binaryManifest = getBinaryManifestPath();
|
Path binaryManifest = getBinaryManifestPath();
|
||||||
|
|
||||||
return sourceManifest.exists() && (Files.notExists(binaryManifest) ||
|
boolean oldOrMissingBinaryManifest = Files.notExists(binaryManifest) ||
|
||||||
sourceManifest.lastModified() > binaryManifest.toFile().lastModified());
|
sourceManifest.lastModified() > binaryManifest.toFile().lastModified();
|
||||||
|
return sourceManifest.exists() && oldOrMissingBinaryManifest;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected static boolean wipeContents(Path path) throws IOException {
|
private static boolean wipeContents(Path path) throws IOException {
|
||||||
if (Files.exists(path)) {
|
if (Files.exists(path)) {
|
||||||
boolean anythingDeleted = false;
|
boolean anythingDeleted = false;
|
||||||
try (Stream<Path> walk = Files.walk(path)) {
|
try (Stream<Path> walk = Files.walk(path)) {
|
||||||
|
@ -498,12 +497,12 @@ public class GhidraSourceBundle extends GhidraBundle {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* if source with a previous requirement error now resolves, add it to newSources.
|
* If source with a previous requirement error now resolves, add it to newSources.
|
||||||
*
|
*
|
||||||
* <p>The reason for the previous build error isn't necessarily a missing requirement,
|
* <p>The reason for the previous build error isn't necessarily a missing requirement, but this
|
||||||
* but this shouldn't be too expensive.
|
* shouldn't be too expensive.
|
||||||
*/
|
*/
|
||||||
private void addSourcesIfResolutionWillPass() {
|
private void addSourcesThatNoLongerHaveMissingRequirements() {
|
||||||
for (ResourceFile sourceFile : buildErrors.keySet()) {
|
for (ResourceFile sourceFile : buildErrors.keySet()) {
|
||||||
List<BundleRequirement> requirements = sourceFileToRequirements.get(sourceFile);
|
List<BundleRequirement> requirements = sourceFileToRequirements.get(sourceFile);
|
||||||
if (requirements != null && !requirements.isEmpty() &&
|
if (requirements != null && !requirements.isEmpty() &&
|
||||||
|
@ -519,10 +518,10 @@ public class GhidraSourceBundle extends GhidraBundle {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* if a file that previously built without errors is now missing some requirements,
|
* If a file that previously built without errors is now missing some requirements, rebuild it
|
||||||
* rebuild it to capture errors (if any).
|
* to capture errors (if any).
|
||||||
*/
|
*/
|
||||||
void addSourcesIfResolutionWillFail() {
|
private void addSourcesThatNowHaveMissingRequirements() {
|
||||||
// if previous successes no longer resolve, (cleanup) and try again
|
// if previous successes no longer resolve, (cleanup) and try again
|
||||||
for (Entry<ResourceFile, List<BundleRequirement>> e : sourceFileToRequirements.entrySet()) {
|
for (Entry<ResourceFile, List<BundleRequirement>> e : sourceFileToRequirements.entrySet()) {
|
||||||
ResourceFile sourceFile = e.getKey();
|
ResourceFile sourceFile = e.getKey();
|
||||||
|
@ -542,39 +541,42 @@ public class GhidraSourceBundle extends GhidraBundle {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean build(PrintWriter writer) throws Exception {
|
public boolean build(PrintWriter writer) throws Exception {
|
||||||
if (writer == null) {
|
|
||||||
writer = new NullPrintWriter();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
writer = NullPrintWriter.dummyIfNull(writer);
|
||||||
boolean needsCompile = false;
|
boolean needsCompile = false;
|
||||||
|
|
||||||
if (hasSourceManifest()) {
|
if (hasSourceManifest()) {
|
||||||
sourceFileToRequirements.clear();
|
sourceFileToRequirements.clear();
|
||||||
requirementToSourceFileMap.clear();
|
requirementToSourceFileMap.clear();
|
||||||
ArrayList<BundleRequirement> reqs = new ArrayList<>(getAllRequirements());
|
List<BundleRequirement> reqs = new ArrayList<>(getAllRequirements());
|
||||||
bundleHost.resolve(reqs);
|
bundleHost.resolve(reqs);
|
||||||
HashSet<String> newMissedRequirements = new HashSet<>();
|
Set<String> newMissedRequirements = new HashSet<>();
|
||||||
for (BundleRequirement req : reqs) {
|
for (BundleRequirement req : reqs) {
|
||||||
newMissedRequirements.add(req.toString());
|
newMissedRequirements.add(req.toString());
|
||||||
}
|
}
|
||||||
if (hasNewManifest() || !newMissedRequirements.equals(missedRequirements)) {
|
if (hasNewManifest() || !newMissedRequirements.equals(missedRequirements)) {
|
||||||
missedRequirements = newMissedRequirements;
|
missedRequirements.clear();
|
||||||
|
missedRequirements.addAll(newMissedRequirements);
|
||||||
wipeBinDir();
|
wipeBinDir();
|
||||||
buildErrors.clear();
|
buildErrors.clear();
|
||||||
}
|
}
|
||||||
updateFromFilesystem(writer);
|
updateFromFilesystem(writer);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
updateFromFilesystem(writer);
|
|
||||||
|
|
||||||
|
// this gets all source files from the file system that we know need to be compiled
|
||||||
|
// based upon missing or outdated class files
|
||||||
|
updateFromFilesystem(writer);
|
||||||
updateRequirementsFromMetadata();
|
updateRequirementsFromMetadata();
|
||||||
addSourcesIfResolutionWillPass();
|
|
||||||
addSourcesIfResolutionWillFail();
|
// these 2 calls handle source files that need to be compiled based upon changes in
|
||||||
|
// added or removed bundles / requirements
|
||||||
|
addSourcesThatNoLongerHaveMissingRequirements();
|
||||||
|
addSourcesThatNowHaveMissingRequirements();
|
||||||
}
|
}
|
||||||
|
|
||||||
int buildErrorsLastTime = getBuildErrorCount();
|
int buildErrorsLastTime = getBuildErrorCount();
|
||||||
int newSourceCount = getNewSourcesCount();
|
int newSourceCount = getNewSourcesCount();
|
||||||
|
|
||||||
if (newSourceCount == 0) {
|
if (newSourceCount == 0) {
|
||||||
if (buildErrorsLastTime > 0) {
|
if (buildErrorsLastTime > 0) {
|
||||||
writer.printf("%s hasn't changed, with %d file%s failing in previous build(s):\n",
|
writer.printf("%s hasn't changed, with %d file%s failing in previous build(s):\n",
|
||||||
|
@ -600,7 +602,7 @@ public class GhidraSourceBundle extends GhidraBundle {
|
||||||
bundleHost.deactivateSynchronously(osgiBundle);
|
bundleHost.deactivateSynchronously(osgiBundle);
|
||||||
}
|
}
|
||||||
|
|
||||||
// once we've committed to recompile and regenerate generated classes, delete the old stuff
|
// once we've committed to recompile and regenerate classes, delete the old stuff
|
||||||
deleteOldBinaries();
|
deleteOldBinaries();
|
||||||
|
|
||||||
String summary = compileToExplodedBundle(writer);
|
String summary = compileToExplodedBundle(writer);
|
||||||
|
@ -635,16 +637,19 @@ public class GhidraSourceBundle extends GhidraBundle {
|
||||||
|
|
||||||
private ResourceFile[] correspondingBinaries(ResourceFile source) {
|
private ResourceFile[] correspondingBinaries(ResourceFile source) {
|
||||||
String parentPath = source.getParentFile().getAbsolutePath();
|
String parentPath = source.getParentFile().getAbsolutePath();
|
||||||
String relPath = parentPath.substring(getSourceDirectory().getAbsolutePath().length());
|
int sourceDirLength = getSourceDirectory().getAbsolutePath().length();
|
||||||
if (relPath.startsWith(File.separator)) {
|
String relativePath = parentPath.substring(sourceDirLength);
|
||||||
relPath = relPath.substring(1);
|
if (relativePath.startsWith(File.separator)) {
|
||||||
|
relativePath = relativePath.substring(1);
|
||||||
}
|
}
|
||||||
String className0 = source.getName();
|
|
||||||
String className = className0.substring(0, className0.length() - 5);// drop ".java"
|
String javaFileName = source.getName();
|
||||||
ResourceFile binarySubdir = new ResourceFile(binaryDir.resolve(relPath).toFile());
|
String className = javaFileName.substring(0, javaFileName.length() - 5); // drop ".java"
|
||||||
|
ResourceFile binarySubdir = new ResourceFile(binaryDir.resolve(relativePath).toFile());
|
||||||
if (!binarySubdir.exists() || !binarySubdir.isDirectory()) {
|
if (!binarySubdir.exists() || !binarySubdir.isDirectory()) {
|
||||||
return new ResourceFile[] {};
|
return new ResourceFile[] {};
|
||||||
}
|
}
|
||||||
|
|
||||||
return binarySubdir.listFiles(f -> {
|
return binarySubdir.listFiles(f -> {
|
||||||
String fileName = f.getName();
|
String fileName = f.getName();
|
||||||
return fileName.startsWith(className) &&
|
return fileName.startsWith(className) &&
|
||||||
|
@ -674,23 +679,23 @@ public class GhidraSourceBundle extends GhidraBundle {
|
||||||
* binary_root/com/blah/Blah$Inner$Innerer.class
|
* binary_root/com/blah/Blah$Inner$Innerer.class
|
||||||
* ...
|
* ...
|
||||||
* </pre>
|
* </pre>
|
||||||
* @param callback callback
|
* @param discrepancy the discrepancy callback
|
||||||
*/
|
*/
|
||||||
protected void visitDiscrepancies(DiscrepencyCallback callback) {
|
protected void visitDiscrepancies(DiscrepancyCallback discrepancy) {
|
||||||
try {
|
try {
|
||||||
Deque<ResourceFile> stack = new ArrayDeque<>();
|
|
||||||
// start in the source directory root
|
|
||||||
stack.add(getSourceDirectory());
|
|
||||||
|
|
||||||
|
Deque<ResourceFile> stack = new ArrayDeque<>();
|
||||||
|
ResourceFile sourceDir = getSourceDirectory();
|
||||||
|
stack.add(sourceDir); // start in the source directory root
|
||||||
while (!stack.isEmpty()) {
|
while (!stack.isEmpty()) {
|
||||||
ResourceFile sourceSubdir = stack.pop();
|
ResourceFile sourceSubdir = stack.pop();
|
||||||
String relPath = sourceSubdir.getAbsolutePath()
|
String relPath = sourceSubdir.getAbsolutePath()
|
||||||
.substring(getSourceDirectory().getAbsolutePath().length());
|
.substring(sourceDir.getAbsolutePath().length());
|
||||||
if (relPath.startsWith(File.separator)) {
|
if (relPath.startsWith(File.separator)) {
|
||||||
relPath = relPath.substring(1);
|
relPath = relPath.substring(1);
|
||||||
}
|
}
|
||||||
Path binarySubdir = binaryDir.resolve(relPath);
|
|
||||||
|
|
||||||
|
Path binarySubdir = binaryDir.resolve(relPath);
|
||||||
ClassMapper mapper = new ClassMapper(binarySubdir);
|
ClassMapper mapper = new ClassMapper(binarySubdir);
|
||||||
|
|
||||||
// for each source file, lookup class files by class name
|
// for each source file, lookup class files by class name
|
||||||
|
@ -701,13 +706,13 @@ public class GhidraSourceBundle extends GhidraBundle {
|
||||||
else {
|
else {
|
||||||
List<Path> classFiles = mapper.findAndRemove(sourceFile);
|
List<Path> classFiles = mapper.findAndRemove(sourceFile);
|
||||||
if (classFiles != null) {
|
if (classFiles != null) {
|
||||||
callback.found(sourceFile, classFiles);
|
discrepancy.found(sourceFile, classFiles);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// any remaining .class files are missing .java files
|
// any remaining .class files are missing .java files
|
||||||
if (mapper.hasExtraClassFiles()) {
|
if (mapper.hasExtraClassFiles()) {
|
||||||
callback.found(null, mapper.extraClassFiles());
|
discrepancy.found(null, mapper.extraClassFiles());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -716,12 +721,15 @@ public class GhidraSourceBundle extends GhidraBundle {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* requirements that resolve internally are never "missing", but will only resolve _after_ build/install */
|
/*
|
||||||
|
* Requirements that resolve internally are never "missing", but resolve _after_ build/install
|
||||||
|
*/
|
||||||
private boolean resolveInternally(List<BundleRequirement> requirements)
|
private boolean resolveInternally(List<BundleRequirement> requirements)
|
||||||
throws GhidraBundleException {
|
throws GhidraBundleException {
|
||||||
if (requirements.isEmpty()) {
|
if (requirements.isEmpty()) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
List<BundleCapability> capabilities = getAllCapabilities();
|
List<BundleCapability> capabilities = getAllCapabilities();
|
||||||
Iterator<BundleRequirement> requirementIterator = requirements.iterator();
|
Iterator<BundleRequirement> requirementIterator = requirements.iterator();
|
||||||
boolean anyMissing = false;
|
boolean anyMissing = false;
|
||||||
|
@ -738,28 +746,38 @@ public class GhidraSourceBundle extends GhidraBundle {
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* when calling the java compiler programmatically, we map import requests to files with
|
* When calling the java compiler programmatically, we map import requests to files with a
|
||||||
* a custom {@link JavaFileManager}. We wrap the system JavaFileManager with one that
|
* custom {@link JavaFileManager}. We wrap the system JavaFileManager with one that handles
|
||||||
* handles ResourceFiles then wrap that with Phidia, which handles imports based on
|
* ResourceFiles then wrap that with phidias, which handles imports based on bundle
|
||||||
* bundle requirements.
|
* requirements.
|
||||||
*/
|
*/
|
||||||
private BundleJavaManager createBundleJavaManager(PrintWriter writer, Summary summary,
|
private BundleJavaManager createBundleJavaManager(PrintWriter writer, Summary summary,
|
||||||
List<String> options) throws IOException, GhidraBundleException {
|
List<String> options) throws IOException, GhidraBundleException {
|
||||||
final ResourceFileJavaFileManager resourceFileJavaManager = new ResourceFileJavaFileManager(
|
|
||||||
Collections.singletonList(getSourceDirectory()), buildErrors.keySet());
|
|
||||||
|
|
||||||
|
ResourceFileJavaFileManager resourceFileJavaManager = new ResourceFileJavaFileManager(
|
||||||
|
Collections.singletonList(getSourceDirectory()), buildErrors.keySet());
|
||||||
BundleJavaManager bundleJavaManager = new MyBundleJavaManager(bundleHost.getHostFramework(),
|
BundleJavaManager bundleJavaManager = new MyBundleJavaManager(bundleHost.getHostFramework(),
|
||||||
resourceFileJavaManager, options);
|
resourceFileJavaManager, 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
|
||||||
// bundle dependencies available to the compiler classpath. Here, we are compiling in an as-yet
|
// bundle dependencies available to the compiler classpath. Here, we are compiling in an
|
||||||
// non-existing bundle, so we forge the wiring based on @importpackage metadata.
|
// as-yet non-existing bundle, so we forge the wiring based on @importpackage metadata.
|
||||||
|
|
||||||
// get wires for currently active bundles to satisfy all requirements
|
// get wires for currently active bundles to satisfy all requirements
|
||||||
List<BundleRequirement> requirements = getAllRequirements();
|
List<BundleRequirement> requirements = getAllRequirements();
|
||||||
List<BundleWiring> bundleWirings = bundleHost.resolve(requirements);
|
List<BundleWiring> bundleWirings = bundleHost.resolve(requirements);
|
||||||
|
|
||||||
if (!resolveInternally(requirements)) {
|
if (!resolveInternally(requirements)) {
|
||||||
|
writeErrorUnresolved(writer, summary, requirements);
|
||||||
|
}
|
||||||
|
|
||||||
|
// send the capabilities to phidias
|
||||||
|
bundleWirings.forEach(bundleJavaManager::addBundleWiring);
|
||||||
|
return bundleJavaManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void writeErrorUnresolved(PrintWriter writer, Summary summary,
|
||||||
|
List<BundleRequirement> requirements) {
|
||||||
|
|
||||||
writer.printf("%d import requirement%s remain%s unresolved:\n", requirements.size(),
|
writer.printf("%d import requirement%s remain%s unresolved:\n", requirements.size(),
|
||||||
requirements.size() > 1 ? "s" : "", requirements.size() > 1 ? "" : "s");
|
requirements.size() > 1 ? "s" : "", requirements.size() > 1 ? "" : "s");
|
||||||
for (BundleRequirement requirement : requirements) {
|
for (BundleRequirement requirement : requirements) {
|
||||||
|
@ -770,6 +788,7 @@ public class GhidraSourceBundle extends GhidraBundle {
|
||||||
requiringFiles.stream()
|
requiringFiles.stream()
|
||||||
.map(generic.util.Path::toPathString)
|
.map(generic.util.Path::toPathString)
|
||||||
.collect(Collectors.joining(",")));
|
.collect(Collectors.joining(",")));
|
||||||
|
|
||||||
for (ResourceFile sourceFile : requiringFiles) {
|
for (ResourceFile sourceFile : requiringFiles) {
|
||||||
buildError(sourceFile,
|
buildError(sourceFile,
|
||||||
generic.util.Path.toPathString(sourceFile) + " : failed import " +
|
generic.util.Path.toPathString(sourceFile) + " : failed import " +
|
||||||
|
@ -782,29 +801,24 @@ public class GhidraSourceBundle extends GhidraBundle {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
summary.printf("%d missing package import%s:%s", requirements.size(),
|
String singularity = requirements.size() > 1 ? "s" : "";
|
||||||
requirements.size() > 1 ? "s" : "",
|
String missing = requirements.stream()
|
||||||
requirements.stream()
|
|
||||||
.flatMap(
|
.flatMap(
|
||||||
r -> OSGiUtils.extractPackageNamesFromFailedResolution(r.toString())
|
r -> OSGiUtils.extractPackageNamesFromFailedResolution(r.toString()).stream())
|
||||||
.stream())
|
|
||||||
.distinct()
|
.distinct()
|
||||||
.collect(Collectors.joining(",")));
|
.collect(Collectors.joining(","));
|
||||||
}
|
summary.printf("%d missing package import%s:%s", requirements.size(), singularity, missing);
|
||||||
// send the capabilities to phidias
|
|
||||||
bundleWirings.forEach(bundleJavaManager::addBundleWiring);
|
|
||||||
return bundleJavaManager;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Try building sourcefiles.. on success return true.
|
* Try building source files. On success return true.
|
||||||
*
|
*
|
||||||
* If build fails, collect errors, remove files that caused
|
* If build fails, collect errors, remove files that caused
|
||||||
* errors from source files, and return false.
|
* errors from source files, and return false.
|
||||||
*/
|
*/
|
||||||
private boolean tryBuild(PrintWriter writer, BundleJavaManager bundleJavaManager,
|
private boolean tryBuild(PrintWriter writer, BundleJavaManager bundleJavaManager,
|
||||||
List<ResourceFileJavaFileObject> sourceFiles, List<String> options) throws IOException {
|
List<ResourceFileJavaFileObject> sourceFiles, List<String> options) throws IOException {
|
||||||
DiagnosticCollector<JavaFileObject> diagnostics = new DiagnosticCollector<JavaFileObject>();
|
DiagnosticCollector<JavaFileObject> diagnostics = new DiagnosticCollector<>();
|
||||||
JavaCompiler.CompilationTask task =
|
JavaCompiler.CompilationTask task =
|
||||||
compiler.getTask(writer, bundleJavaManager, diagnostics, options, null, sourceFiles);
|
compiler.getTask(writer, bundleJavaManager, diagnostics, options, null, sourceFiles);
|
||||||
|
|
||||||
|
@ -812,6 +826,7 @@ public class GhidraSourceBundle extends GhidraBundle {
|
||||||
if (successfulCompilation) {
|
if (successfulCompilation) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
Set<ResourceFileJavaFileObject> filesWithErrors = new HashSet<>();
|
Set<ResourceFileJavaFileObject> filesWithErrors = new HashSet<>();
|
||||||
for (Diagnostic<? extends JavaFileObject> diagnostic : diagnostics.getDiagnostics()) {
|
for (Diagnostic<? extends JavaFileObject> diagnostic : diagnostics.getDiagnostics()) {
|
||||||
String error = diagnostic.toString() + "\n";
|
String error = diagnostic.toString() + "\n";
|
||||||
|
@ -836,9 +851,9 @@ public class GhidraSourceBundle extends GhidraBundle {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* generate a manifest (and an activator)
|
* Generate a manifest (and an activator)
|
||||||
*
|
*
|
||||||
* assumes that {@link #updateRequirementsFromMetadata()} has been called recently
|
* <p>Assumes that {@link #updateRequirementsFromMetadata()} has been called recently
|
||||||
*/
|
*/
|
||||||
private String generateManifest(PrintWriter writer, Summary summary, Path binaryManifest)
|
private String generateManifest(PrintWriter writer, Summary summary, Path binaryManifest)
|
||||||
throws OSGiException, IOException {
|
throws OSGiException, IOException {
|
||||||
|
@ -849,14 +864,14 @@ public class GhidraSourceBundle extends GhidraBundle {
|
||||||
GhidraSourceBundle.sourceDirHash(getSourceDirectory()));
|
GhidraSourceBundle.sourceDirHash(getSourceDirectory()));
|
||||||
analyzer.setProperty("Bundle-Version", GENERATED_VERSION);
|
analyzer.setProperty("Bundle-Version", GENERATED_VERSION);
|
||||||
|
|
||||||
if (!importPackageValues.isEmpty()) {
|
if (importPackageValues.isEmpty()) {
|
||||||
|
analyzer.setProperty("Import-Package", "*");
|
||||||
|
}
|
||||||
|
else {
|
||||||
// constrain analyzed imports according to what's declared in @importpackage tags
|
// constrain analyzed imports according to what's declared in @importpackage tags
|
||||||
analyzer.setProperty("Import-Package",
|
analyzer.setProperty("Import-Package",
|
||||||
importPackageValues.stream().collect(Collectors.joining(",")) + ",*");
|
importPackageValues.stream().collect(Collectors.joining(",")) + ",*");
|
||||||
}
|
}
|
||||||
else {
|
|
||||||
analyzer.setProperty("Import-Package", "*");
|
|
||||||
}
|
|
||||||
|
|
||||||
analyzer.setProperty("Export-Package", "!*.private.*,!*.internal.*,*");
|
analyzer.setProperty("Export-Package", "!*.private.*,!*.internal.*,*");
|
||||||
|
|
||||||
|
@ -866,44 +881,13 @@ public class GhidraSourceBundle extends GhidraBundle {
|
||||||
manifest = analyzer.calcManifest();
|
manifest = analyzer.calcManifest();
|
||||||
}
|
}
|
||||||
catch (Exception e) {
|
catch (Exception e) {
|
||||||
summary.print("bad manifest");
|
summary.print("Bad manifest");
|
||||||
throw new OSGiException("failed to calculate manifest by analyzing code", e);
|
throw new OSGiException("Failed to calculate manifest by analyzing code", e);
|
||||||
}
|
}
|
||||||
|
|
||||||
String activatorClassName = null;
|
if (!addActivatorClass(writer, analyzer, manifest, summary)) {
|
||||||
try {
|
|
||||||
for (Clazz clazz : analyzer.getClassspace().values()) {
|
|
||||||
if (clazz.is(QUERY.IMPLEMENTS,
|
|
||||||
new Instruction("org.osgi.framework.BundleActivator"), analyzer)) {
|
|
||||||
System.err.printf("found BundleActivator class %s\n", clazz);
|
|
||||||
activatorClassName = clazz.toString();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception e) {
|
|
||||||
summary.print("failed bnd analysis");
|
|
||||||
throw new OSGiException("failed to query classes while searching for activator", e);
|
|
||||||
}
|
|
||||||
|
|
||||||
Attributes manifestAttributes = manifest.getMainAttributes();
|
|
||||||
if (activatorClassName == null) {
|
|
||||||
activatorClassName = GENERATED_ACTIVATOR_CLASSNAME;
|
|
||||||
if (!buildDefaultActivator(binaryDir, activatorClassName, writer)) {
|
|
||||||
summary.print("failed to build generated activator");
|
|
||||||
return summary.getValue();
|
return summary.getValue();
|
||||||
}
|
}
|
||||||
// since we add the activator after bndtools built the imports, we should add its imports too
|
|
||||||
String imps = manifestAttributes.getValue(Constants.IMPORT_PACKAGE);
|
|
||||||
if (imps == null) {
|
|
||||||
manifestAttributes.putValue(Constants.IMPORT_PACKAGE,
|
|
||||||
GhidraBundleActivator.class.getPackageName());
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
manifestAttributes.putValue(Constants.IMPORT_PACKAGE,
|
|
||||||
imps + "," + GhidraBundleActivator.class.getPackageName());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
manifestAttributes.putValue(Constants.BUNDLE_ACTIVATOR, activatorClassName);
|
|
||||||
|
|
||||||
// write the manifest
|
// write the manifest
|
||||||
Files.createDirectories(binaryManifest.getParent());
|
Files.createDirectories(binaryManifest.getParent());
|
||||||
|
@ -917,6 +901,50 @@ public class GhidraSourceBundle extends GhidraBundle {
|
||||||
return summary.getValue();
|
return summary.getValue();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean addActivatorClass(PrintWriter writer, Analyzer analyzer, Manifest manifest,
|
||||||
|
Summary summary) throws OSGiException, IOException {
|
||||||
|
|
||||||
|
String activatorClassName = null;
|
||||||
|
try {
|
||||||
|
for (Clazz clazz : analyzer.getClassspace().values()) {
|
||||||
|
if (clazz.is(QUERY.IMPLEMENTS,
|
||||||
|
new Instruction(INSTRCTION_ACTIVATOR), analyzer)) {
|
||||||
|
Msg.trace(this, "Found BundleActivator class " + clazz);
|
||||||
|
activatorClassName = clazz.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception e) {
|
||||||
|
summary.print("Failed bnd analysis");
|
||||||
|
throw new OSGiException("Failed to query classes while searching for activator", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
Attributes manifestAttributes = manifest.getMainAttributes();
|
||||||
|
if (activatorClassName != null) {
|
||||||
|
manifestAttributes.putValue(Constants.BUNDLE_ACTIVATOR, activatorClassName);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
activatorClassName = GENERATED_ACTIVATOR_CLASSNAME;
|
||||||
|
if (!buildDefaultActivator(binaryDir, activatorClassName, writer)) {
|
||||||
|
summary.print("Failed to build generated activator");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// since we add the activator after bndtools built the imports, add its imports too
|
||||||
|
String imports = manifestAttributes.getValue(Constants.IMPORT_PACKAGE);
|
||||||
|
String activatorPackageName = GhidraBundleActivator.class.getPackageName();
|
||||||
|
if (imports == null) {
|
||||||
|
manifestAttributes.putValue(Constants.IMPORT_PACKAGE, activatorPackageName);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
manifestAttributes.putValue(Constants.IMPORT_PACKAGE, imports + "," +
|
||||||
|
activatorPackageName);
|
||||||
|
}
|
||||||
|
manifestAttributes.putValue(Constants.BUNDLE_ACTIVATOR, activatorClassName);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* create and compile a default bundle activator
|
* create and compile a default bundle activator
|
||||||
*
|
*
|
||||||
|
@ -928,8 +956,8 @@ public class GhidraSourceBundle extends GhidraBundle {
|
||||||
*/
|
*/
|
||||||
private boolean buildDefaultActivator(Path bindir, String activatorClassName, Writer writer)
|
private boolean buildDefaultActivator(Path bindir, String activatorClassName, Writer writer)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
Path activatorSourceFileName = bindir.resolve(activatorClassName + ".java");
|
|
||||||
|
|
||||||
|
Path activatorSourceFileName = bindir.resolve(activatorClassName + ".java");
|
||||||
try (PrintWriter activatorWriter = new PrintWriter(
|
try (PrintWriter activatorWriter = new PrintWriter(
|
||||||
Files.newBufferedWriter(activatorSourceFileName, Charset.forName("UTF-8")))) {
|
Files.newBufferedWriter(activatorSourceFileName, Charset.forName("UTF-8")))) {
|
||||||
activatorWriter.println("import " + GhidraBundleActivator.class.getName() + ";");
|
activatorWriter.println("import " + GhidraBundleActivator.class.getName() + ";");
|
||||||
|
@ -959,15 +987,18 @@ public class GhidraSourceBundle extends GhidraBundle {
|
||||||
|
|
||||||
try (StandardJavaFileManager javaFileManager =
|
try (StandardJavaFileManager javaFileManager =
|
||||||
compiler.getStandardFileManager(null, null, null);
|
compiler.getStandardFileManager(null, null, null);
|
||||||
BundleJavaManager bundleJavaManager = new MyBundleJavaManager(
|
BundleJavaManager bundleManager = new MyBundleJavaManager(
|
||||||
bundleHost.getHostFramework(), javaFileManager, options);) {
|
bundleHost.getHostFramework(), javaFileManager, options);) {
|
||||||
Iterable<? extends JavaFileObject> sourceFiles =
|
Iterable<? extends JavaFileObject> sourceFiles =
|
||||||
javaFileManager.getJavaFileObjectsFromPaths(List.of(activatorSourceFileName));
|
javaFileManager.getJavaFileObjectsFromPaths(List.of(activatorSourceFileName));
|
||||||
DiagnosticCollector<JavaFileObject> diagnostics =
|
DiagnosticCollector<JavaFileObject> diagnostics =
|
||||||
new DiagnosticCollector<JavaFileObject>();
|
new DiagnosticCollector<>();
|
||||||
JavaCompiler.CompilationTask task = compiler.getTask(writer, bundleJavaManager,
|
JavaCompiler.CompilationTask task = compiler.getTask(writer, bundleManager,
|
||||||
diagnostics, options, null, sourceFiles);
|
diagnostics, options, null, sourceFiles);
|
||||||
if (!task.call()) {
|
if (task.call()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
for (Diagnostic<? extends JavaFileObject> diagnostic : diagnostics
|
for (Diagnostic<? extends JavaFileObject> diagnostic : diagnostics
|
||||||
.getDiagnostics()) {
|
.getDiagnostics()) {
|
||||||
writer.write(diagnostic.getSource().toString() + ": " +
|
writer.write(diagnostic.getSource().toString() + ": " +
|
||||||
|
@ -975,19 +1006,17 @@ public class GhidraSourceBundle extends GhidraBundle {
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* compile a source directory to an exploded bundle
|
* Compile a source directory to an exploded bundle.
|
||||||
*
|
*
|
||||||
* @param writer for updating the user during compilation
|
* @param writer for updating the user during compilation
|
||||||
|
* @return a summary of the work performed
|
||||||
* @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
|
||||||
*/
|
*/
|
||||||
private String compileToExplodedBundle(PrintWriter writer) throws IOException, OSGiException {
|
private String compileToExplodedBundle(PrintWriter writer) throws IOException, OSGiException {
|
||||||
compileAttempted();
|
|
||||||
|
|
||||||
Files.createDirectories(binaryDir);
|
Files.createDirectories(binaryDir);
|
||||||
|
|
||||||
|
@ -1047,8 +1076,12 @@ public class GhidraSourceBundle extends GhidraBundle {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//=================================================================================================
|
||||||
|
// Inner Classes
|
||||||
|
//=================================================================================================
|
||||||
|
|
||||||
private static class MyBundleJavaManager extends BundleJavaManager {
|
private static class MyBundleJavaManager extends BundleJavaManager {
|
||||||
static URL[] EMPTY_URL_ARRAY = new URL[0];
|
private static URL[] EMPTY_URL_ARRAY = new URL[0];
|
||||||
|
|
||||||
MyBundleJavaManager(Bundle bundle, JavaFileManager javaFileManager, List<String> options)
|
MyBundleJavaManager(Bundle bundle, JavaFileManager javaFileManager, List<String> options)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
|
@ -1056,20 +1089,19 @@ public class GhidraSourceBundle extends GhidraBundle {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* since the JavaCompiler tasks can close the class loader returned by this
|
* Since the JavaCompiler tasks can close the class loader returned by this method, make
|
||||||
* method, make sure we're returning a copy.
|
* sure we're returning a copy.
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public ClassLoader getClassLoader() {
|
public ClassLoader getClassLoader() {
|
||||||
return new URLClassLoader(EMPTY_URL_ARRAY, super.getClassLoader());
|
return new URLClassLoader(EMPTY_URL_ARRAY, super.getClassLoader());
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class Summary {
|
private static class Summary {
|
||||||
static String SEPERATOR = ", ";
|
private static final String SEPERATOR = ", ";
|
||||||
final StringWriter stringWriter = new StringWriter();
|
private final StringWriter stringWriter = new StringWriter();
|
||||||
final PrintWriter printWriter = new PrintWriter(stringWriter, true);
|
private final PrintWriter printWriter = new PrintWriter(stringWriter, true);
|
||||||
|
|
||||||
void printf(String format, Object... args) {
|
void printf(String format, Object... args) {
|
||||||
if (stringWriter.getBuffer().length() > 0) {
|
if (stringWriter.getBuffer().length() > 0) {
|
||||||
|
@ -1096,12 +1128,12 @@ public class GhidraSourceBundle extends GhidraBundle {
|
||||||
* "B" -> [directory/B.class, directory/B$inner.class]
|
* "B" -> [directory/B.class, directory/B$inner.class]
|
||||||
* </pre>
|
* </pre>
|
||||||
*
|
*
|
||||||
* <p>A list of classes are then processed one at a time with {@link ClassMapper#findAndRemove}.
|
* <p>A list of classes are then processed with {@link ClassMapper#findAndRemove}.
|
||||||
*
|
*
|
||||||
* <p>After processing, "extras" are handled with {@link ClassMapper#extraClassFiles}.
|
* <p>After processing, "extras" are handled with {@link ClassMapper#extraClassFiles}.
|
||||||
*/
|
*/
|
||||||
private static class ClassMapper {
|
private static class ClassMapper {
|
||||||
final Map<String, List<Path>> classToClassFilesMap;
|
private final Map<String, List<Path>> classToClassFilesMap;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Map classes in {@code directory} with {@link ClassMapper}.
|
* Map classes in {@code directory} with {@link ClassMapper}.
|
||||||
|
@ -1129,8 +1161,7 @@ public class GhidraSourceBundle extends GhidraBundle {
|
||||||
if (money >= 0) {
|
if (money >= 0) {
|
||||||
return fileName.substring(0, money);
|
return fileName.substring(0, money);
|
||||||
}
|
}
|
||||||
// drop ".class"
|
return fileName.substring(0, fileName.length() - 6); // drop ".class"
|
||||||
return fileName.substring(0, fileName.length() - 6);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
List<Path> findAndRemove(ResourceFile sourceFile) {
|
List<Path> findAndRemove(ResourceFile sourceFile) {
|
||||||
|
@ -1139,7 +1170,7 @@ public class GhidraSourceBundle extends GhidraBundle {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
className = className.substring(0, className.length() - 5);
|
className = className.substring(0, className.length() - 5); // drop ".java"
|
||||||
long lastModifiedSource = sourceFile.lastModified();
|
long lastModifiedSource = sourceFile.lastModified();
|
||||||
List<Path> classFiles = classToClassFilesMap.remove(className);
|
List<Path> classFiles = classToClassFilesMap.remove(className);
|
||||||
if (classFiles == null) {
|
if (classFiles == null) {
|
||||||
|
@ -1158,17 +1189,15 @@ public class GhidraSourceBundle extends GhidraBundle {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean hasExtraClassFiles() {
|
boolean hasExtraClassFiles() {
|
||||||
return !classToClassFilesMap.isEmpty();
|
return !classToClassFilesMap.isEmpty();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Collection<Path> extraClassFiles() {
|
Collection<Path> extraClassFiles() {
|
||||||
return classToClassFilesMap.values()
|
return classToClassFilesMap.values()
|
||||||
.stream()
|
.stream()
|
||||||
.flatMap(l -> l.stream())
|
.flatMap(l -> l.stream())
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -281,5 +281,4 @@ public class OSGiUtils {
|
||||||
Msg.error(OSGiUtils.class, "Error while collecting packages from jar", e);
|
Msg.error(OSGiUtils.class, "Error while collecting packages from jar", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1166,7 +1166,7 @@ public class GhidraScriptComponentProvider extends ComponentProviderAdapter {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class RefreshingBundleHostListener implements BundleHostListener {
|
private class RefreshingBundleHostListener implements BundleHostListener {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void bundleBuilt(GhidraBundle bundle, String summary) {
|
public void bundleBuilt(GhidraBundle bundle, String summary) {
|
||||||
|
@ -1179,8 +1179,8 @@ public class GhidraScriptComponentProvider extends ComponentProviderAdapter {
|
||||||
GhidraSourceBundle sourceBundle = (GhidraSourceBundle) bundle;
|
GhidraSourceBundle sourceBundle = (GhidraSourceBundle) bundle;
|
||||||
ResourceFile sourceDirectory = sourceBundle.getFile();
|
ResourceFile sourceDirectory = sourceBundle.getFile();
|
||||||
if (summary == null) {
|
if (summary == null) {
|
||||||
// a null summary means the build didn't change anything,
|
// a null summary means the build didn't change anything, so use any errors from
|
||||||
// so use any errors from the last build
|
// the last build
|
||||||
for (ResourceFile sourceFile : sourceBundle.getAllErrors().keySet()) {
|
for (ResourceFile sourceFile : sourceBundle.getAllErrors().keySet()) {
|
||||||
if (sourceFile.getParentFile().equals(sourceDirectory)) {
|
if (sourceFile.getParentFile().equals(sourceDirectory)) {
|
||||||
ScriptInfo scriptInfo = infoManager.getScriptInfo(sourceFile);
|
ScriptInfo scriptInfo = infoManager.getScriptInfo(sourceFile);
|
||||||
|
|
|
@ -83,7 +83,7 @@ public class GhidraScriptUtil {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* initialize state of GhidraScriptUtil with user, system paths, and optional extra system paths.
|
* Initialize state of GhidraScriptUtil with user, system, and optional extra system paths.
|
||||||
*
|
*
|
||||||
* @param aBundleHost the host to use
|
* @param aBundleHost the host to use
|
||||||
* @param extraSystemPaths additional system paths for this run, can be null
|
* @param extraSystemPaths additional system paths for this run, can be null
|
||||||
|
@ -106,7 +106,7 @@ public class GhidraScriptUtil {
|
||||||
*/
|
*/
|
||||||
public static void dispose() {
|
public static void dispose() {
|
||||||
if (bundleHost != null) {
|
if (bundleHost != null) {
|
||||||
bundleHost.dispose();
|
bundleHost.stopFramework();
|
||||||
bundleHost = null;
|
bundleHost = null;
|
||||||
}
|
}
|
||||||
providers = null;
|
providers = null;
|
||||||
|
|
|
@ -121,8 +121,8 @@ public class JavaScriptProvider extends GhidraScriptProvider {
|
||||||
throw new ClassNotFoundException(
|
throw new ClassNotFoundException(
|
||||||
"Failed to find source bundle containing script: " + sourceFile.toString());
|
"Failed to find source bundle containing script: " + sourceFile.toString());
|
||||||
}
|
}
|
||||||
bundleHost.activateAll(Collections.singletonList(bundle), TaskMonitor.DUMMY, writer);
|
|
||||||
|
|
||||||
|
bundleHost.activateAll(Collections.singletonList(bundle), TaskMonitor.DUMMY, writer);
|
||||||
String classname = bundle.classNameForScript(sourceFile);
|
String classname = bundle.classNameForScript(sourceFile);
|
||||||
Class<?> clazz = bundle.getOSGiBundle().loadClass(classname); // throws ClassNotFoundException
|
Class<?> clazz = bundle.getOSGiBundle().loadClass(classname); // throws ClassNotFoundException
|
||||||
return clazz;
|
return clazz;
|
||||||
|
|
|
@ -87,7 +87,7 @@ public class BundleHostTest extends AbstractGhidraHeadlessIntegrationTest {
|
||||||
|
|
||||||
@After
|
@After
|
||||||
public void tearDown() {
|
public void tearDown() {
|
||||||
bundleHost.dispose();
|
bundleHost.stopFramework();
|
||||||
capturingBundleHostListener = null;
|
capturingBundleHostListener = null;
|
||||||
bundleHost = null;
|
bundleHost = null;
|
||||||
|
|
||||||
|
|
|
@ -435,11 +435,11 @@ public class BundleStatusManagerTest extends AbstractGhidraScriptMgrPluginTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
void awaitActivation() throws InterruptedException {
|
void awaitActivation() throws InterruptedException {
|
||||||
assertTrue(activationLatch.await(5000, TimeUnit.MILLISECONDS));
|
assertTrue(activationLatch.await(30000, TimeUnit.MILLISECONDS));
|
||||||
}
|
}
|
||||||
|
|
||||||
void awaitDisablement() throws InterruptedException {
|
void awaitDisablement() throws InterruptedException {
|
||||||
assertTrue(disablementLatch.await(5000, TimeUnit.MILLISECONDS));
|
assertTrue(disablementLatch.await(30000, TimeUnit.MILLISECONDS));
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,6 +18,7 @@ package ghidra.app.plugin.core.script;
|
||||||
import static org.junit.Assert.*;
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
import java.io.*;
|
import java.io.*;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
|
||||||
import org.apache.logging.log4j.*;
|
import org.apache.logging.log4j.*;
|
||||||
import org.apache.logging.log4j.core.config.Configurator;
|
import org.apache.logging.log4j.core.config.Configurator;
|
||||||
|
@ -150,7 +151,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 =
|
||||||
GhidraSourceBundle.getBindirFromScriptFile(new ResourceFile(newScriptFile)).toFile();
|
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 +196,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 =
|
||||||
GhidraSourceBundle.getBindirFromScriptFile(systemScriptFile).toFile();
|
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 +233,7 @@ public class GhidraScriptMgrPlugin2Test extends AbstractGhidraScriptMgrPluginTes
|
||||||
waitForScriptCompletion(scriptID, 20000);
|
waitForScriptCompletion(scriptID, 20000);
|
||||||
|
|
||||||
// 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 = GhidraSourceBundle.getBindirFromScriptFile(newScriptFile).toFile();
|
File binDir = getBinDirFromScriptFile(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");
|
||||||
|
@ -493,4 +494,11 @@ public class GhidraScriptMgrPlugin2Test extends AbstractGhidraScriptMgrPluginTes
|
||||||
assertContainsText("The field of the script still has state--the script was not recreated",
|
assertContainsText("The field of the script still has state--the script was not recreated",
|
||||||
"*2*", output);
|
"*2*", output);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Path getBinDirFromScriptFile(ResourceFile sourceFile) {
|
||||||
|
ResourceFile tmpSourceDir = sourceFile.getParentFile();
|
||||||
|
String tmpSymbolicName = GhidraSourceBundle.sourceDirHash(tmpSourceDir);
|
||||||
|
return GhidraSourceBundle.getCompiledBundlesDir().resolve(tmpSymbolicName);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
/* ###
|
/* ###
|
||||||
* IP: GHIDRA
|
* IP: GHIDRA
|
||||||
* REVIEWED: YES
|
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -24,14 +23,12 @@ import java.io.*;
|
||||||
*
|
*
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
@Deprecated(forRemoval = true, since = "10.2") // This is not used
|
||||||
public class JavaCompiler {
|
public class JavaCompiler {
|
||||||
|
|
||||||
private IOThread cmdOut;
|
private IOThread cmdOut;
|
||||||
private IOThread cmdErr;
|
private IOThread cmdErr;
|
||||||
|
|
||||||
/**
|
|
||||||
* Compile a java file.
|
|
||||||
*/
|
|
||||||
public void compile(File javaFile) {
|
public void compile(File javaFile) {
|
||||||
String name = javaFile.getName();
|
String name = javaFile.getName();
|
||||||
String className = name.substring(0, name.indexOf(".")) + ".class";
|
String className = name.substring(0, name.indexOf(".")) + ".class";
|
||||||
|
@ -60,8 +57,8 @@ public class JavaCompiler {
|
||||||
argV[5] = javaFile.getAbsolutePath();
|
argV[5] = javaFile.getAbsolutePath();
|
||||||
try {
|
try {
|
||||||
Process p = Runtime.getRuntime().exec(argV);
|
Process p = Runtime.getRuntime().exec(argV);
|
||||||
for (int i=0; i<argV.length; i++) {
|
for (String element : argV) {
|
||||||
System.out.print(argV[i] + " ");
|
System.out.print(element + " ");
|
||||||
}
|
}
|
||||||
System.out.println();
|
System.out.println();
|
||||||
|
|
||||||
|
@ -74,18 +71,13 @@ public class JavaCompiler {
|
||||||
cmdOut.join();
|
cmdOut.join();
|
||||||
cmdErr.join();
|
cmdErr.join();
|
||||||
|
|
||||||
} catch (Exception e) {
|
}
|
||||||
|
catch (Exception e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Set up threads to read from stdin and stderr and output that
|
|
||||||
* to stdout.
|
|
||||||
* @param stdin
|
|
||||||
* @param stderr
|
|
||||||
*/
|
|
||||||
private void setupIO(InputStream stdin, InputStream stderr) {
|
private void setupIO(InputStream stdin, InputStream stderr) {
|
||||||
cmdOut = new IOThread(stdin); //
|
cmdOut = new IOThread(stdin); //
|
||||||
cmdErr = new IOThread(stderr);
|
cmdErr = new IOThread(stderr);
|
||||||
|
|
|
@ -19,6 +19,10 @@ import java.io.PrintWriter;
|
||||||
|
|
||||||
public class NullPrintWriter extends PrintWriter {
|
public class NullPrintWriter extends PrintWriter {
|
||||||
|
|
||||||
|
public static PrintWriter dummyIfNull(PrintWriter pw) {
|
||||||
|
return pw == null ? new NullPrintWriter() : pw;
|
||||||
|
}
|
||||||
|
|
||||||
public NullPrintWriter() {
|
public NullPrintWriter() {
|
||||||
super(new NullWriter());
|
super(new NullWriter());
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,11 +20,9 @@ import java.net.InetAddress;
|
||||||
import java.net.UnknownHostException;
|
import java.net.UnknownHostException;
|
||||||
import java.text.DateFormat;
|
import java.text.DateFormat;
|
||||||
import java.text.SimpleDateFormat;
|
import java.text.SimpleDateFormat;
|
||||||
import java.util.Date;
|
import java.util.*;
|
||||||
import java.util.Properties;
|
|
||||||
|
|
||||||
import ghidra.util.HTMLUtilities;
|
import ghidra.util.HTMLUtilities;
|
||||||
import ghidra.util.SystemUtilities;
|
|
||||||
|
|
||||||
public class FileLocker {
|
public class FileLocker {
|
||||||
|
|
||||||
|
@ -91,28 +89,16 @@ public class FileLocker {
|
||||||
|
|
||||||
Properties properties = new Properties();
|
Properties properties = new Properties();
|
||||||
|
|
||||||
InputStream is = null;
|
try (InputStream is = new FileInputStream(lockFile)) {
|
||||||
try {
|
|
||||||
is = new FileInputStream(lockFile);
|
|
||||||
properties.load(is);
|
properties.load(is);
|
||||||
return properties;
|
return properties;
|
||||||
}
|
}
|
||||||
catch (FileNotFoundException e) {
|
catch (FileNotFoundException e) {
|
||||||
// should never happen
|
// should not happen
|
||||||
}
|
}
|
||||||
catch (IOException e) {
|
catch (IOException e) {
|
||||||
// ignore
|
// ignore
|
||||||
}
|
}
|
||||||
finally {
|
|
||||||
if (is != null) {
|
|
||||||
try {
|
|
||||||
is.close();
|
|
||||||
}
|
|
||||||
catch (IOException e) {
|
|
||||||
// we tried!
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -178,25 +164,13 @@ public class FileLocker {
|
||||||
|
|
||||||
private boolean storeProperties(Properties properties) {
|
private boolean storeProperties(Properties properties) {
|
||||||
|
|
||||||
OutputStream os = null;
|
try (OutputStream os = new FileOutputStream(lockFile)) {
|
||||||
try {
|
|
||||||
os = new FileOutputStream(lockFile);
|
|
||||||
properties.store(os, "Ghidra Lock File");
|
properties.store(os, "Ghidra Lock File");
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
catch (IOException e) {
|
catch (IOException e) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
finally {
|
|
||||||
if (os != null) {
|
|
||||||
try {
|
|
||||||
os.close();
|
|
||||||
}
|
|
||||||
catch (IOException e) {
|
|
||||||
// don't care; we tried
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isLockOwner() {
|
private boolean isLockOwner() {
|
||||||
|
@ -212,7 +186,7 @@ public class FileLocker {
|
||||||
for (String key : PROPERTY_KEYS) {
|
for (String key : PROPERTY_KEYS) {
|
||||||
String originalProperty = createdLockProperties.getProperty(key);
|
String originalProperty = createdLockProperties.getProperty(key);
|
||||||
String currentProperty = currentLockProperties.getProperty(key);
|
String currentProperty = currentLockProperties.getProperty(key);
|
||||||
if (!SystemUtilities.isEqual(originalProperty, currentProperty)) {
|
if (!Objects.equals(originalProperty, currentProperty)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
/* ###
|
/* ###
|
||||||
* IP: GHIDRA
|
* IP: GHIDRA
|
||||||
* REVIEWED: YES
|
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -16,12 +15,12 @@
|
||||||
*/
|
*/
|
||||||
package ghidra.framework.project;
|
package ghidra.framework.project;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
|
||||||
import generic.util.FileLocker;
|
import generic.util.FileLocker;
|
||||||
import generic.util.LockFactory;
|
import generic.util.LockFactory;
|
||||||
import ghidra.framework.model.ProjectLocator;
|
import ghidra.framework.model.ProjectLocator;
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A simple delegate for creating and using locks in Ghidra.
|
* A simple delegate for creating and using locks in Ghidra.
|
||||||
*/
|
*/
|
||||||
|
@ -29,7 +28,7 @@ class ProjectLock {
|
||||||
|
|
||||||
private final File lockFile;
|
private final File lockFile;
|
||||||
|
|
||||||
FileLocker locker;
|
private FileLocker locker;
|
||||||
|
|
||||||
public ProjectLock(ProjectLocator projectLocator) {
|
public ProjectLock(ProjectLocator projectLocator) {
|
||||||
this.lockFile = projectLocator.getProjectLockFile();
|
this.lockFile = projectLocator.getProjectLockFile();
|
||||||
|
|
|
@ -880,7 +880,8 @@ public final class FileUtilities {
|
||||||
*
|
*
|
||||||
* @param f1 the parent file
|
* @param f1 the parent file
|
||||||
* @param f2 the child file
|
* @param f2 the child file
|
||||||
* @return the portion of the second file that trails the full path of the first file.
|
* @return the portion of the second file that trails the full path of the first file; null as
|
||||||
|
* described above
|
||||||
* @throws IOException if there is an error canonicalizing the path
|
* @throws IOException if there is an error canonicalizing the path
|
||||||
*/
|
*/
|
||||||
public static String relativizePath(File f1, File f2) throws IOException {
|
public static String relativizePath(File f1, File f2) throws IOException {
|
||||||
|
@ -903,17 +904,18 @@ public final class FileUtilities {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return the relative path string of one resource file in another. If
|
* Return the relative path string of one resource file in another. If no path can be
|
||||||
* no path can be constructed or the files are the same, then null is returned.
|
* constructed or the files are the same, then null is returned.
|
||||||
*
|
*
|
||||||
* Note: unlike {@link #relativizePath(File, File)}, this function does not resolve symbolic links.
|
* Note: unlike {@link #relativizePath(File, File)}, this function does not resolve symbolic
|
||||||
|
* links.
|
||||||
*
|
*
|
||||||
* <P>For example, given, in this order, two files with these paths
|
* <P>For example, given, in this order, two files with these paths
|
||||||
* <code>/a/b</code> and <code>/a/b/c</code>, this method will return 'c'.
|
* <code>/a/b</code> and <code>/a/b/c</code>, this method will return 'c'.
|
||||||
*
|
*
|
||||||
* @param f1 the parent resource file
|
* @param f1 the parent resource file
|
||||||
* @param f2 the child resource file
|
* @param f2 the child resource file
|
||||||
* @return the relative path of {@code f2} in {@code f1}
|
* @return the relative path of {@code f2} in {@code f1}; null if f1 is not a parent of f2
|
||||||
*/
|
*/
|
||||||
public static String relativizePath(ResourceFile f1, ResourceFile f2) {
|
public static String relativizePath(ResourceFile f1, ResourceFile f2) {
|
||||||
StringBuilder sb = new StringBuilder(f2.getName());
|
StringBuilder sb = new StringBuilder(f2.getName());
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue