GP-4062_4063: Handling Ghidra installation moving, and capping supported

PyDev version at 9.3.0 (Python 2 support was dropped in 10.0)
This commit is contained in:
Ryan Kurtz 2023-11-22 11:50:40 -05:00
parent 280d5ce8d1
commit f20e649273
7 changed files with 63 additions and 36 deletions

View file

@ -2,7 +2,7 @@
<feature <feature
id="ghidra.ghidradev" id="ghidra.ghidradev"
label="GhidraDev" label="GhidraDev"
version="3.0.1.qualifier" version="3.0.2.qualifier"
provider-name="Ghidra"> provider-name="Ghidra">
<description> <description>

View file

@ -53,6 +53,16 @@ change with future releases.</p>
</ul> </ul>
<h2><a name="ChangeHistory"></a>Change History</h2> <h2><a name="ChangeHistory"></a>Change History</h2>
<p><u><b>3.0.2</b>:</u>
<ul>
<li>
GhidraDev no longer throws an IOException when performing a "Link Ghidra" action on a Ghidra
project whose original Ghidra installation moved.
</li>
<li>
GhidraDev now prevents unsupported versions of PyDev from being used.
</li>
</ul>
<p><u><b>3.0.1</b>:</u> <p><u><b>3.0.1</b>:</u>
<ul> <ul>
<li> <li>
@ -157,7 +167,7 @@ that specify other projects on their build paths.</p>
<h2><a name="OptionalRequirements"></a>Optional Requirements</h2> <h2><a name="OptionalRequirements"></a>Optional Requirements</h2>
<ul> <ul>
<li>PyDev 6.3.1 or later (<a href="#PyDevSupport">more info</a>)</li> <li>PyDev 6.3.1 - 9.3.0 (<a href="#PyDevSupport">more info</a>)</li>
<li>CDT 8.6.0 or later</li> <li>CDT 8.6.0 or later</li>
<li> <li>
Gradle - required version(s) specified by linked Ghidra release Gradle - required version(s) specified by linked Ghidra release
@ -413,6 +423,15 @@ installation directory.</p>
</li> </li>
</ul> </ul>
</li> </li>
<li>
<b><i>Why doesn't GhidraDev support PyDev 10.0 or later?</i></b>
<ul>
<li>
<p>PyDev dropped support for Python 2 in their 10.0 release. Ghidra currently does not
support Python 3.</p>
</li>
</ul>
</li>
</ul> </ul>
<p>(<a href="#top">Back to Top</a>)</p> <p>(<a href="#top">Back to Top</a>)</p>

View file

@ -3,7 +3,7 @@ Manifest-Version: 1.0
Bundle-ManifestVersion: 2 Bundle-ManifestVersion: 2
Bundle-Name: GhidraDev Bundle-Name: GhidraDev
Bundle-SymbolicName: ghidra.ghidradev;singleton:=true Bundle-SymbolicName: ghidra.ghidradev;singleton:=true
Bundle-Version: 3.0.1.qualifier Bundle-Version: 3.0.2.qualifier
Bundle-Activator: ghidradev.Activator Bundle-Activator: ghidradev.Activator
Require-Bundle: org.eclipse.ant.core;bundle-version="3.6.200", Require-Bundle: org.eclipse.ant.core;bundle-version="3.6.200",
org.eclipse.buildship.core;bundle-version="3.1.5", org.eclipse.buildship.core;bundle-version="3.1.5",

View file

@ -357,9 +357,17 @@ public class GhidraProjectUtils {
// Get the project's existing linked Ghidra installation folder and path (it may not exist) // Get the project's existing linked Ghidra installation folder and path (it may not exist)
IFolder ghidraFolder = IFolder ghidraFolder =
javaProject.getProject().getFolder(GhidraProjectUtils.GHIDRA_FOLDER_NAME); javaProject.getProject().getFolder(GhidraProjectUtils.GHIDRA_FOLDER_NAME);
GhidraApplicationLayout oldGhidraLayout = ghidraFolder.exists() IPath oldGhidraPath = null;
? new GhidraApplicationLayout(ghidraFolder.getLocation().toFile()) GhidraApplicationLayout oldGhidraLayout = null;
: null; if (ghidraFolder.exists() ) {
oldGhidraPath = ghidraFolder.getLocation();
if (oldGhidraPath != null) {
File oldGhidraDir = oldGhidraPath.toFile();
if (oldGhidraDir.exists()) {
oldGhidraLayout = new GhidraApplicationLayout(oldGhidraDir);
}
}
}
// Loop through the project's existing classpath to decide what to keep (things that aren't // Loop through the project's existing classpath to decide what to keep (things that aren't
// related to Ghidra), and things to not keep (things that will be added fresh from the new // related to Ghidra), and things to not keep (things that will be added fresh from the new
@ -372,7 +380,7 @@ public class GhidraProjectUtils {
// We'll decide whether or not to keep it later. // We'll decide whether or not to keep it later.
if (entry.getEntryKind() == IClasspathEntry.CPE_CONTAINER && if (entry.getEntryKind() == IClasspathEntry.CPE_CONTAINER &&
entry.getPath().toString().startsWith(JavaRuntime.JRE_CONTAINER)) { entry.getPath().toString().startsWith(JavaRuntime.JRE_CONTAINER)) {
if (oldGhidraLayout == null) { if (oldGhidraPath == null) {
vmEntryCandidate = entry; vmEntryCandidate = entry;
} }
} }
@ -394,19 +402,17 @@ public class GhidraProjectUtils {
entryFolder = ResourcesPlugin.getWorkspace().getRoot().getFolder(entry.getPath()); entryFolder = ResourcesPlugin.getWorkspace().getRoot().getFolder(entry.getPath());
} }
if (entryFolder != null && entryFolder.isLinked() && if (entryFolder != null && entryFolder.isLinked() &&
inGhidraInstallation(oldGhidraLayout, entryFolder.getLocation())) { inGhidraInstallation(oldGhidraPath, entryFolder.getLocation())) {
String oldGhidraInstallPath =
oldGhidraLayout.getApplicationInstallationDir().getAbsolutePath();
String origPath = entryFolder.getLocation().toString(); String origPath = entryFolder.getLocation().toString();
String newPath = ghidraInstallDir.getAbsolutePath() + String newPath = ghidraInstallDir.getAbsolutePath() +
origPath.substring(oldGhidraInstallPath.length()); origPath.substring(oldGhidraPath.toString().length());
entryFolder.createLink(new Path(newPath), IResource.REPLACE, monitor); entryFolder.createLink(new Path(newPath), IResource.REPLACE, monitor);
classpathEntriesToKeep.add(JavaCore.newSourceEntry(entryFolder.getFullPath())); classpathEntriesToKeep.add(JavaCore.newSourceEntry(entryFolder.getFullPath()));
} }
// If it's anything else that doesn't live in the old Ghidra installation, keep it. // If it's anything else that doesn't live in the old Ghidra installation, keep it.
// Note that installed Ghidra extensions can live in the user settings directory // Note that installed Ghidra extensions can live in the user settings directory
// which is outside the installation directory. We don't want to keep these. // which is outside the installation directory. We don't want to keep these.
else if (!inGhidraInstallation(oldGhidraLayout, entry.getPath()) && else if (!inGhidraInstallation(oldGhidraPath, entry.getPath()) &&
!isGhidraExtension(oldGhidraLayout, entry.getPath())) { !isGhidraExtension(oldGhidraLayout, entry.getPath())) {
classpathEntriesToKeep.add(entry); classpathEntriesToKeep.add(entry);
ghidraLayout.getExtensionInstallationDirs(); ghidraLayout.getExtensionInstallationDirs();
@ -459,18 +465,15 @@ public class GhidraProjectUtils {
} }
/** /**
* Checks to see if the given path is contained within the given Ghidra layout's installation * Checks to see if the given path is contained within the given Ghidra installation path.
* directory.
* *
* @param ghidraLayout A Ghidra layout that contains the installation directory to check. * @param ghidraInstallPath A Ghidra installation path.
* @param path The path to check. * @param path The path to check.
* @return True if the given path is contained within the given Ghidra layout's installation * @return True if the given path is contained within the given Ghidra installation directory
* directory. * path.
*/ */
private static boolean inGhidraInstallation(GhidraApplicationLayout ghidraLayout, IPath path) { private static boolean inGhidraInstallation(IPath ghidraInstallPath, IPath path) {
return ghidraLayout != null && return ghidraInstallPath != null && ghidraInstallPath.isPrefixOf(path);
new Path(ghidraLayout.getApplicationInstallationDir().getAbsolutePath())
.isPrefixOf(path);
} }
/** /**

View file

@ -29,8 +29,7 @@ import javax.naming.OperationNotSupportedException;
import org.eclipse.core.runtime.*; import org.eclipse.core.runtime.*;
import org.eclipse.jdt.core.IClasspathEntry; import org.eclipse.jdt.core.IClasspathEntry;
import org.eclipse.jdt.core.IJavaProject; import org.eclipse.jdt.core.IJavaProject;
import org.osgi.framework.Bundle; import org.osgi.framework.*;
import org.osgi.framework.FrameworkUtil;
import ghidradev.Activator; import ghidradev.Activator;
@ -40,6 +39,7 @@ import ghidradev.Activator;
public class PyDevUtils { public class PyDevUtils {
public final static String MIN_SUPPORTED_VERSION = "6.3.1"; public final static String MIN_SUPPORTED_VERSION = "6.3.1";
public final static String MAX_SUPPORTED_VERSION = "9.3.0";
/** /**
* Checks to see if a supported version of PyDev is installed. * Checks to see if a supported version of PyDev is installed.
@ -47,12 +47,15 @@ public class PyDevUtils {
* @return True if a supported version of PyDev is installed; otherwise, false. * @return True if a supported version of PyDev is installed; otherwise, false.
*/ */
public static boolean isSupportedPyDevInstalled() { public static boolean isSupportedPyDevInstalled() {
Version min = Version.valueOf(MIN_SUPPORTED_VERSION);
Version max = Version.valueOf(MAX_SUPPORTED_VERSION);
try { try {
if (PyDevUtilsInternal.isPyDevInstalled()) { Version version = PyDevUtilsInternal.getPyDevVersion();
if (version != null) {
// Make sure the installed version of PyDev is new enough to support the following // Make sure the installed version of PyDev is new enough to support the following
// operation. // operation.
getJython27InterpreterNames(); getJython27InterpreterNames();
return true; return version.compareTo(min) >= 0 && version.compareTo(max) <= 0;
} }
} }
catch (OperationNotSupportedException | NoClassDefFoundError e) { catch (OperationNotSupportedException | NoClassDefFoundError e) {

View file

@ -22,8 +22,7 @@ import java.util.stream.Collectors;
import org.eclipse.core.runtime.*; import org.eclipse.core.runtime.*;
import org.eclipse.jdt.core.IClasspathEntry; import org.eclipse.jdt.core.IClasspathEntry;
import org.eclipse.jdt.core.IJavaProject; import org.eclipse.jdt.core.IJavaProject;
import org.osgi.framework.Bundle; import org.osgi.framework.*;
import org.osgi.framework.FrameworkUtil;
import org.python.pydev.ast.interpreter_managers.InterpreterInfo; import org.python.pydev.ast.interpreter_managers.InterpreterInfo;
import org.python.pydev.ast.interpreter_managers.InterpreterManagersAPI; import org.python.pydev.ast.interpreter_managers.InterpreterManagersAPI;
import org.python.pydev.core.*; import org.python.pydev.core.*;
@ -44,19 +43,22 @@ import ghidradev.EclipseMessageUtils;
class PyDevUtilsInternal { class PyDevUtilsInternal {
/** /**
* Checks to see if PyDev is installed. * Get the version of PyDev that is installed
* *
* @return True if PyDev is installed; otherwise, false. * @return The {@link Version} of the installed PyDev, or null if PyDev is not installed.
* @throws NoClassDefFoundError if PyDev is not installed. * @throws NoClassDefFoundError if PyDev is not installed.
*/ */
public static boolean isPyDevInstalled() throws NoClassDefFoundError { public static Version getPyDevVersion() throws NoClassDefFoundError {
for (Bundle bundle : FrameworkUtil.getBundle( for (Bundle bundle : FrameworkUtil.getBundle(PyDevUtilsInternal.class)
PyDevUtilsInternal.class).getBundleContext().getBundles()) { .getBundleContext()
.getBundles()) {
if (bundle.getSymbolicName().contains("pydev")) { if (bundle.getSymbolicName().contains("pydev")) {
return true; // remove qualifier to make version comparisons more straightforward
Version version = bundle.getVersion();
return new Version(version.getMajor(), version.getMinor(), version.getMicro());
} }
} }
return false; return null;
} }
/** /**

View file

@ -68,7 +68,7 @@ public class EnablePythonWizardPage extends WizardPage {
enablePythonCheckboxButton.setText("Enable Python"); enablePythonCheckboxButton.setText("Enable Python");
enablePythonCheckboxButton.setToolTipText("Enables Python support using the PyDev " + enablePythonCheckboxButton.setToolTipText("Enables Python support using the PyDev " +
"Eclipse plugin. Requires PyDev version " + PyDevUtils.MIN_SUPPORTED_VERSION + "Eclipse plugin. Requires PyDev version " + PyDevUtils.MIN_SUPPORTED_VERSION +
" or later."); " - " + PyDevUtils.MAX_SUPPORTED_VERSION);
enablePythonCheckboxButton.setSelection(PyDevUtils.isSupportedPyDevInstalled()); enablePythonCheckboxButton.setSelection(PyDevUtils.isSupportedPyDevInstalled());
enablePythonCheckboxButton.addSelectionListener(new SelectionListener() { enablePythonCheckboxButton.addSelectionListener(new SelectionListener() {
@Override @Override
@ -166,7 +166,7 @@ public class EnablePythonWizardPage extends WizardPage {
if (pyDevEnabled) { if (pyDevEnabled) {
if (!pyDevInstalled) { if (!pyDevInstalled) {
message = "PyDev version " + PyDevUtils.MIN_SUPPORTED_VERSION + message = "PyDev version " + PyDevUtils.MIN_SUPPORTED_VERSION +
" or later is not installed."; " - " + PyDevUtils.MAX_SUPPORTED_VERSION + " is not installed.";
} }
else { else {
try { try {