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
id="ghidra.ghidradev"
label="GhidraDev"
version="3.0.1.qualifier"
version="3.0.2.qualifier"
provider-name="Ghidra">
<description>

View file

@ -53,6 +53,16 @@ change with future releases.</p>
</ul>
<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>
<ul>
<li>
@ -157,7 +167,7 @@ that specify other projects on their build paths.</p>
<h2><a name="OptionalRequirements"></a>Optional Requirements</h2>
<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>
Gradle - required version(s) specified by linked Ghidra release
@ -413,6 +423,15 @@ installation directory.</p>
</li>
</ul>
</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>
<p>(<a href="#top">Back to Top</a>)</p>

View file

@ -3,7 +3,7 @@ Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: GhidraDev
Bundle-SymbolicName: ghidra.ghidradev;singleton:=true
Bundle-Version: 3.0.1.qualifier
Bundle-Version: 3.0.2.qualifier
Bundle-Activator: ghidradev.Activator
Require-Bundle: org.eclipse.ant.core;bundle-version="3.6.200",
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)
IFolder ghidraFolder =
javaProject.getProject().getFolder(GhidraProjectUtils.GHIDRA_FOLDER_NAME);
GhidraApplicationLayout oldGhidraLayout = ghidraFolder.exists()
? new GhidraApplicationLayout(ghidraFolder.getLocation().toFile())
: null;
IPath oldGhidraPath = null;
GhidraApplicationLayout oldGhidraLayout = 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
// 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.
if (entry.getEntryKind() == IClasspathEntry.CPE_CONTAINER &&
entry.getPath().toString().startsWith(JavaRuntime.JRE_CONTAINER)) {
if (oldGhidraLayout == null) {
if (oldGhidraPath == null) {
vmEntryCandidate = entry;
}
}
@ -394,19 +402,17 @@ public class GhidraProjectUtils {
entryFolder = ResourcesPlugin.getWorkspace().getRoot().getFolder(entry.getPath());
}
if (entryFolder != null && entryFolder.isLinked() &&
inGhidraInstallation(oldGhidraLayout, entryFolder.getLocation())) {
String oldGhidraInstallPath =
oldGhidraLayout.getApplicationInstallationDir().getAbsolutePath();
inGhidraInstallation(oldGhidraPath, entryFolder.getLocation())) {
String origPath = entryFolder.getLocation().toString();
String newPath = ghidraInstallDir.getAbsolutePath() +
origPath.substring(oldGhidraInstallPath.length());
origPath.substring(oldGhidraPath.toString().length());
entryFolder.createLink(new Path(newPath), IResource.REPLACE, monitor);
classpathEntriesToKeep.add(JavaCore.newSourceEntry(entryFolder.getFullPath()));
}
// 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
// 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())) {
classpathEntriesToKeep.add(entry);
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
* directory.
* Checks to see if the given path is contained within the given Ghidra installation path.
*
* @param ghidraLayout A Ghidra layout that contains the installation directory to check.
* @param ghidraInstallPath A Ghidra installation path.
* @param path The path to check.
* @return True if the given path is contained within the given Ghidra layout's installation
* directory.
* @return True if the given path is contained within the given Ghidra installation directory
* path.
*/
private static boolean inGhidraInstallation(GhidraApplicationLayout ghidraLayout, IPath path) {
return ghidraLayout != null &&
new Path(ghidraLayout.getApplicationInstallationDir().getAbsolutePath())
.isPrefixOf(path);
private static boolean inGhidraInstallation(IPath ghidraInstallPath, IPath path) {
return ghidraInstallPath != null && ghidraInstallPath.isPrefixOf(path);
}
/**

View file

@ -29,8 +29,7 @@ import javax.naming.OperationNotSupportedException;
import org.eclipse.core.runtime.*;
import org.eclipse.jdt.core.IClasspathEntry;
import org.eclipse.jdt.core.IJavaProject;
import org.osgi.framework.Bundle;
import org.osgi.framework.FrameworkUtil;
import org.osgi.framework.*;
import ghidradev.Activator;
@ -40,6 +39,7 @@ import ghidradev.Activator;
public class PyDevUtils {
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.
@ -47,12 +47,15 @@ public class PyDevUtils {
* @return True if a supported version of PyDev is installed; otherwise, false.
*/
public static boolean isSupportedPyDevInstalled() {
Version min = Version.valueOf(MIN_SUPPORTED_VERSION);
Version max = Version.valueOf(MAX_SUPPORTED_VERSION);
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
// operation.
getJython27InterpreterNames();
return true;
return version.compareTo(min) >= 0 && version.compareTo(max) <= 0;
}
}
catch (OperationNotSupportedException | NoClassDefFoundError e) {

View file

@ -22,8 +22,7 @@ import java.util.stream.Collectors;
import org.eclipse.core.runtime.*;
import org.eclipse.jdt.core.IClasspathEntry;
import org.eclipse.jdt.core.IJavaProject;
import org.osgi.framework.Bundle;
import org.osgi.framework.FrameworkUtil;
import org.osgi.framework.*;
import org.python.pydev.ast.interpreter_managers.InterpreterInfo;
import org.python.pydev.ast.interpreter_managers.InterpreterManagersAPI;
import org.python.pydev.core.*;
@ -44,19 +43,22 @@ import ghidradev.EclipseMessageUtils;
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.
*/
public static boolean isPyDevInstalled() throws NoClassDefFoundError {
for (Bundle bundle : FrameworkUtil.getBundle(
PyDevUtilsInternal.class).getBundleContext().getBundles()) {
public static Version getPyDevVersion() throws NoClassDefFoundError {
for (Bundle bundle : FrameworkUtil.getBundle(PyDevUtilsInternal.class)
.getBundleContext()
.getBundles()) {
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.setToolTipText("Enables Python support using the PyDev " +
"Eclipse plugin. Requires PyDev version " + PyDevUtils.MIN_SUPPORTED_VERSION +
" or later.");
" - " + PyDevUtils.MAX_SUPPORTED_VERSION);
enablePythonCheckboxButton.setSelection(PyDevUtils.isSupportedPyDevInstalled());
enablePythonCheckboxButton.addSelectionListener(new SelectionListener() {
@Override
@ -166,7 +166,7 @@ public class EnablePythonWizardPage extends WizardPage {
if (pyDevEnabled) {
if (!pyDevInstalled) {
message = "PyDev version " + PyDevUtils.MIN_SUPPORTED_VERSION +
" or later is not installed.";
" - " + PyDevUtils.MAX_SUPPORTED_VERSION + " is not installed.";
}
else {
try {