GP-5138: GhidraDev/PyDev/PyGhidra integration

This commit is contained in:
Ryan Kurtz 2024-12-11 12:43:39 -05:00
parent 7c4d91f568
commit 31ee251a5c
35 changed files with 1300 additions and 315 deletions

View file

@ -0,0 +1,107 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidradev.ghidraprojectcreator.launchers;
import javax.naming.OperationNotSupportedException;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.*;
import org.eclipse.debug.core.*;
import org.eclipse.debug.ui.ILaunchShortcut;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.ui.IEditorInput;
import org.eclipse.ui.IEditorPart;
import ghidradev.Activator;
import ghidradev.EclipseMessageUtils;
import ghidradev.ghidraprojectcreator.testers.GhidraProjectPropertyTester;
import ghidradev.ghidraprojectcreator.utils.*;
/**
* PyGhidra launch shortcut actions. These shortcuts appear when you right click on a
* PyGhidra project or file and select "Run As" or "Debug As".
* <p>
* The {@link GhidraProjectPropertyTester} is used to determine whether or not the shortcuts appear.
*/
public abstract class AbstractPyGhidraLaunchShortcut implements ILaunchShortcut {
private String launchConfigTypeId;
private String launchConfigNameSuffix;
/**
* Creates a new PyGhidra launch shortcut associated with the given launch configuration type ID.
*
* @param launchConfigTypeId The launch configuration type ID of this PyGhidra launch shortcut.
* @param launchConfigNameSuffix A string to append to the name of the launch configuration.
*/
protected AbstractPyGhidraLaunchShortcut(String launchConfigTypeId,
String launchConfigNameSuffix) {
this.launchConfigTypeId = launchConfigTypeId;
this.launchConfigNameSuffix = launchConfigNameSuffix;
}
@Override
public void launch(ISelection selection, String mode) {
IProject project = GhidraProjectUtils.getSelectedProject(selection);
if (project != null) {
launch(project, mode);
}
}
@Override
public void launch(IEditorPart editor, String mode) {
IEditorInput input = editor.getEditorInput();
IResource resource = input.getAdapter(IResource.class);
if (resource != null) {
launch(resource.getProject(), mode);
}
}
/**
* Launches the given Python nature in the given mode with a PyGhidra launcher.
*
* @param project The project to launch.
* @param mode The mode to launch in (run/debug).
*/
private void launch(IProject project, String mode) {
ILaunchManager launchManager = DebugPlugin.getDefault().getLaunchManager();
ILaunchConfigurationType launchType =
launchManager.getLaunchConfigurationType(launchConfigTypeId);
String launchConfigName = project.getName() + launchConfigNameSuffix;
try {
ILaunchConfiguration lc = GhidraLaunchUtils.getLaunchConfig(launchConfigName);
ILaunchConfigurationWorkingCopy wc = null;
if (lc == null) {
wc = launchType.newInstance(null, launchConfigName);
wc.setAttribute(PyDevUtils.getAttrProject(), project.getName());
}
else if (lc.getType().equals(launchType)) {
wc = lc.getWorkingCopy();
}
else {
throw new CoreException(new Status(IStatus.ERROR, Activator.PLUGIN_ID,
IStatus.ERROR, "Failed to launch. Run configuration with name \"" +
launchConfigName + "\" already exists.",
null));
}
wc.doSave().launch(mode, null);
}
catch (CoreException | OperationNotSupportedException e) {
EclipseMessageUtils.showErrorDialog(e.getMessage());
}
}
}

View file

@ -33,7 +33,7 @@ import org.eclipse.swt.widgets.Display;
import org.eclipse.ui.IPerspectiveDescriptor;
import org.eclipse.ui.PlatformUI;
import ghidra.launch.JavaConfig;
import ghidra.launch.AppConfig;
import ghidradev.EclipseMessageUtils;
import ghidradev.ghidraprojectcreator.utils.*;
@ -62,10 +62,10 @@ public class GhidraLaunchDelegate extends JavaLaunchDelegate {
}
IFolder ghidraFolder =
javaProject.getProject().getFolder(GhidraProjectUtils.GHIDRA_FOLDER_NAME);
JavaConfig javaConfig;
AppConfig appConfig;
String ghidraInstallPath = ghidraFolder.getLocation().toOSString();
try {
javaConfig = new JavaConfig(new File(ghidraInstallPath));
appConfig = new AppConfig(new File(ghidraInstallPath));
}
catch (ParseException | IOException e) {
EclipseMessageUtils.showErrorDialog(
@ -98,7 +98,7 @@ public class GhidraLaunchDelegate extends JavaLaunchDelegate {
}
// Set VM arguments
String vmArgs = javaConfig.getLaunchProperties().getVmArgs();
String vmArgs = appConfig.getLaunchProperties().getVmArgs();
vmArgs += " " + configuration.getAttribute(GhidraLaunchUtils.ATTR_VM_ARGUMENTS, "").trim();
vmArgs += " -Dghidra.external.modules=\"%s%s%s\"".formatted(
javaProject.getProject().getLocation(), File.pathSeparator,
@ -171,7 +171,7 @@ public class GhidraLaunchDelegate extends JavaLaunchDelegate {
}
// Start PyDev debugger
if (PyDevUtils.isSupportedPyDevInstalled()) {
if (PyDevUtils.isSupportedJythonPyDevInstalled()) {
try {
PyDevUtils.startPyDevRemoteDebugger();
}

View file

@ -0,0 +1,33 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidradev.ghidraprojectcreator.launchers;
import ghidradev.ghidraprojectcreator.utils.GhidraLaunchUtils;
/**
* The PyGhidra GUI launch shortcut actions.
*
* @see AbstractGhidraLaunchShortcut
*/
public class PyGhidraGuiLaunchShortcut extends AbstractPyGhidraLaunchShortcut {
/**
* Creates a new PyGhidra GUI launch shortcut.
*/
public PyGhidraGuiLaunchShortcut() {
super(GhidraLaunchUtils.PYGHIDRA_GUI_LAUNCH, " GUI");
}
}

View file

@ -0,0 +1,142 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidradev.ghidraprojectcreator.launchers;
import java.io.File;
import java.io.IOException;
import java.text.ParseException;
import java.util.HashMap;
import java.util.Map;
import javax.naming.OperationNotSupportedException;
import org.eclipse.core.resources.IFolder;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.debug.core.*;
import org.eclipse.debug.ui.IDebugUIConstants;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.swt.widgets.Display;
import org.eclipse.ui.IPerspectiveDescriptor;
import org.eclipse.ui.PlatformUI;
import org.python.pydev.debug.ui.launching.RegularLaunchConfigurationDelegate;
import ghidra.launch.AppConfig;
import ghidradev.EclipseMessageUtils;
import ghidradev.ghidraprojectcreator.utils.GhidraProjectUtils;
import ghidradev.ghidraprojectcreator.utils.PyDevUtils;
/**
* The PyGhidra Launch delegate handles the final launch of PyGhidra.
* We can do any extra custom launch behavior here.
*/
public class PyGhidraLaunchDelegate extends RegularLaunchConfigurationDelegate {
@Override
public void launch(ILaunchConfiguration configuration, String mode, ILaunch launch,
IProgressMonitor monitor) throws CoreException {
try {
ILaunchConfigurationWorkingCopy wc = configuration.getWorkingCopy();
// Get project
String projectName = wc.getAttribute(PyDevUtils.getAttrProject(), "");
IJavaProject javaProject = GhidraProjectUtils.getGhidraProject(projectName);
if (javaProject == null) {
EclipseMessageUtils.showErrorDialog("Failed to launch project \"" + projectName +
"\".\nDoes not appear to be a Ghidra project.");
return;
}
IProject project = javaProject.getProject();
// Get needed application.properties values
String javaComplianceLevel = null;
String ghidraVmErrorMsg = "";
try {
IFolder ghidraFolder = project.getFolder(GhidraProjectUtils.GHIDRA_FOLDER_NAME);
String ghidraInstallPath = ghidraFolder.getLocation().toOSString();
AppConfig appConfig = new AppConfig(new File(ghidraInstallPath));
javaComplianceLevel = appConfig.getCompilerComplianceLevel();
}
catch (ParseException | IOException e) {
ghidraVmErrorMsg = e.getMessage();
}
if (javaComplianceLevel == null) {
EclipseMessageUtils
.showErrorDialog("Failed to get JVM compliance level from project \"" +
projectName + "\".\n" + ghidraVmErrorMsg);
return;
}
// Set program location
wc.setAttribute(PyDevUtils.getAttrLocation(),
"${workspace_loc:%s/Ghidra/Ghidra/Features/PyGhidra/pypkg/src/pyghidra}"
.formatted(project.getName()));
// Set program arguments
wc.setAttribute(PyDevUtils.getAttrProgramArguments(), "-v -g");
// Set Python interpreter
String interpreterName = PyDevUtils.getInterpreterName(project);
wc.setAttribute(PyDevUtils.getAttrInterpreter(), interpreterName);
wc.setAttribute(PyDevUtils.getAttrInterpreterDefault(), interpreterName);
// Set environment variables
Map<String, String> env = new HashMap<>();
//env.put("GHIDRA_INSTALL_DIR", "${project_loc:/%s/Ghidra}".formatted(project.getName()));
env.put("GHIDRA_INSTALL_DIR",
"${resource_loc:/%s/Ghidra}".formatted(project.getName()));
env.put("JAVA_HOME_OVERRIDE", "${ee_home:JavaSE-%s}".formatted(javaComplianceLevel));
if (mode.equals("debug")) {
env.put("PYGHIDRA_DEBUG", "1");
handleDebugMode();
}
wc.setAttribute(ILaunchManager.ATTR_ENVIRONMENT_VARIABLES, env);
super.launch(wc.doSave(), mode, launch, monitor);
}
catch (OperationNotSupportedException e) {
EclipseMessageUtils.showErrorDialog("PyDev error",
"Failed to launch. PyDev version is not supported.");
}
}
/**
* Handles extra things that should happen when we are launching in debug mode.
*/
private static void handleDebugMode() {
Display.getDefault().asyncExec(() -> {
// Switch to debug perspective
if (PlatformUI.getWorkbench() != null) {
IPerspectiveDescriptor descriptor =
PlatformUI.getWorkbench().getPerspectiveRegistry().findPerspectiveWithId(
IDebugUIConstants.ID_DEBUG_PERSPECTIVE);
EclipseMessageUtils.getWorkbenchPage().setPerspective(descriptor);
}
// Start PyDev debugger
try {
PyDevUtils.startPyDevRemoteDebugger();
}
catch (OperationNotSupportedException e) {
EclipseMessageUtils.error(
"Failed to start the PyDev remote debugger. PyDev version is not supported.");
}
});
}
}

View file

@ -0,0 +1,40 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidradev.ghidraprojectcreator.testers;
import javax.naming.OperationNotSupportedException;
import org.eclipse.core.expressions.PropertyTester;
import ghidradev.ghidraprojectcreator.utils.GhidraProjectUtils;
import ghidradev.ghidraprojectcreator.utils.PyDevUtils;
/**
* A {@link PropertyTester} used to determine if a given Eclipse resource is part
* of a PyGhidra project.
*/
public class PyGhidraProjectPropertyTester extends PropertyTester {
@Override
public boolean test(Object receiver, String property, Object[] args, Object expectedValue) {
try {
return PyDevUtils.isPyGhidraProject(GhidraProjectUtils.getEnclosingProject(receiver));
}
catch (OperationNotSupportedException e) {
return false;
}
}
}

View file

@ -49,6 +49,12 @@ public class GhidraLaunchUtils {
*/
public static final String HEADLESS_LAUNCH = "GhidraHeadlessLaunchConfigurationType";
/**
* Launch configuration ID for a PyGhidra GUI launch. Must match corresponding value in
* plugin.xml.
*/
public static final String PYGHIDRA_GUI_LAUNCH = "PyGhidraGuiLaunchConfigurationType";
/**
* Program arguments that will get passed to the launched Ghidra. These will be appended
* to the required program arguments that are required to launch Ghidra, which are hidden

View file

@ -30,6 +30,7 @@ import org.eclipse.ltk.core.refactoring.*;
import ghidra.GhidraApplicationLayout;
import ghidra.util.exception.CancelledException;
import ghidradev.ghidraprojectcreator.utils.PyDevUtils.ProjectPythonInterpreter;
import utilities.util.FileUtilities;
/**
@ -89,7 +90,7 @@ public class GhidraModuleUtils {
*/
public static IJavaProject createGhidraModuleProject(String projectName, File projectDir,
boolean createRunConfig, String runConfigMemory, GhidraApplicationLayout ghidraLayout,
String jythonInterpreterName, IProgressMonitor monitor)
ProjectPythonInterpreter jythonInterpreterName, IProgressMonitor monitor)
throws IOException, ParseException, CoreException {
// Create empty Ghidra project
@ -227,8 +228,7 @@ public class GhidraModuleUtils {
* @param createRunConfig Whether or not to create a new run configuration for the project.
* @param runConfigMemory The run configuration's desired memory. Could be null.
* @param ghidraLayout The Ghidra layout to link the project to.
* @param jythonInterpreterName The name of the Jython interpreter to use for Python support.
* Could be null if Python support is not wanted.
* @param pythonInterpreter The Python interpreter to use.
* @param monitor The progress monitor to use during project creation.
* @return The imported project.
* @throws IOException If there was a file-related problem with creating the project.
@ -237,13 +237,13 @@ public class GhidraModuleUtils {
*/
public static IJavaProject importGhidraModuleSource(String projectName, File moduleSourceDir,
boolean createRunConfig, String runConfigMemory, GhidraApplicationLayout ghidraLayout,
String jythonInterpreterName, IProgressMonitor monitor)
ProjectPythonInterpreter pythonInterpreter, IProgressMonitor monitor)
throws IOException, ParseException, CoreException {
// Create empty Ghidra project
IJavaProject javaProject =
GhidraProjectUtils.createEmptyGhidraProject(projectName, moduleSourceDir,
createRunConfig, runConfigMemory, ghidraLayout, jythonInterpreterName, monitor);
createRunConfig, runConfigMemory, ghidraLayout, pythonInterpreter, monitor);
IProject project = javaProject.getProject();
// Set default output location

View file

@ -36,9 +36,10 @@ import org.eclipse.ui.part.FileEditorInput;
import generic.jar.ResourceFile;
import ghidra.GhidraApplicationLayout;
import ghidra.framework.GModule;
import ghidra.launch.JavaConfig;
import ghidra.launch.AppConfig;
import ghidradev.Activator;
import ghidradev.EclipseMessageUtils;
import ghidradev.ghidraprojectcreator.utils.PyDevUtils.ProjectPythonInterpreter;
import utility.module.ModuleUtilities;
/**
@ -262,8 +263,7 @@ public class GhidraProjectUtils {
* @param createRunConfig Whether or not to create a new run configuration for the project.
* @param runConfigMemory The run configuration's desired memory. Could be null.
* @param ghidraLayout The Ghidra layout to link the project to.
* @param jythonInterpreterName The name of the Jython interpreter to use for Python support.
* Could be null if Python support is not wanted.
* @param pythonInterpreter The Python interpreter to use.
* @param monitor The progress monitor to use during project creation.
* @return The created project.
* @throws IOException If there was a file-related problem with creating the project.
@ -272,12 +272,12 @@ public class GhidraProjectUtils {
*/
public static IJavaProject createEmptyGhidraProject(String projectName, File projectDir,
boolean createRunConfig, String runConfigMemory, GhidraApplicationLayout ghidraLayout,
String jythonInterpreterName, IProgressMonitor monitor)
ProjectPythonInterpreter pythonInterpreter, IProgressMonitor monitor)
throws IOException, ParseException, CoreException {
// Get Ghidra's Java configuration
JavaConfig javaConfig =
new JavaConfig(ghidraLayout.getApplicationInstallationDir().getFile(false));
AppConfig appConfig =
new AppConfig(ghidraLayout.getApplicationInstallationDir().getFile(false));
// Make new Java project
IWorkspace workspace = ResourcesPlugin.getWorkspace();
@ -299,7 +299,7 @@ public class GhidraProjectUtils {
javaProject.setRawClasspath(new IClasspathEntry[0], monitor);
// Configure Java compiler for the project
configureJavaCompiler(javaProject, javaConfig);
configureJavaCompiler(javaProject, appConfig);
// Setup default bin folder
IFolder binFolder = project.getFolder("bin/default");
@ -310,7 +310,7 @@ public class GhidraProjectUtils {
monitor);
// Link in Ghidra to the project
linkGhidraToProject(javaProject, ghidraLayout, javaConfig, jythonInterpreterName, monitor);
linkGhidraToProject(javaProject, ghidraLayout, appConfig, pythonInterpreter, monitor);
// Create run configuration (if necessary)
if (createRunConfig) {
@ -338,23 +338,22 @@ public class GhidraProjectUtils {
*
* @param javaProject The Java project to link.
* @param ghidraLayout The Ghidra layout to link the project to.
* @param javaConfig Ghidra's Java configuration.
* @param jythonInterpreterName The name of the Jython interpreter to use for Python support.
* Could be null if Python support is not wanted.
* @param appConfig Ghidra's application configuration.
* @param pythonInterpreter The Python interpreter to use.
* @param monitor The progress monitor used during link.
* @throws IOException If there was a file-related problem with linking in Ghidra.
* @throws CoreException If there was an Eclipse-related problem with linking in Ghidra.
*/
public static void linkGhidraToProject(IJavaProject javaProject,
GhidraApplicationLayout ghidraLayout, JavaConfig javaConfig,
String jythonInterpreterName, IProgressMonitor monitor)
GhidraApplicationLayout ghidraLayout, AppConfig appConfig,
ProjectPythonInterpreter pythonInterpreter, IProgressMonitor monitor)
throws CoreException, IOException {
// Gets the Ghidra installation directory to link to from the Ghidra layout
File ghidraInstallDir = ghidraLayout.getApplicationInstallationDir().getFile(false);
// Get the Java VM used to launch the Ghidra to link to
IVMInstall vm = getGhidraVm(javaConfig);
IVMInstall vm = getGhidraVm(appConfig);
IPath vmPath =
new Path(JavaRuntime.JRE_CONTAINER).append(vm.getVMInstallType().getId()).append(
vm.getName());
@ -457,15 +456,13 @@ public class GhidraProjectUtils {
GhidraModuleUtils.writeAntProperties(javaProject.getProject(), ghidraLayout);
// Setup Python for the project
if (PyDevUtils.isSupportedPyDevInstalled()) {
try {
PyDevUtils.setupPythonForProject(javaProject, libraryClasspathEntries,
jythonInterpreterName, monitor);
}
catch (OperationNotSupportedException e) {
EclipseMessageUtils.showErrorDialog("PyDev error",
"Failed to setup Python for the project. PyDev version is not supported.");
}
try {
PyDevUtils.setupPythonForProject(javaProject, libraryClasspathEntries,
pythonInterpreter, monitor);
}
catch (OperationNotSupportedException e) {
EclipseMessageUtils.showErrorDialog("PyDev error",
"Failed to setup Python for the project. PyDev version is not supported.");
}
}
@ -559,14 +556,14 @@ public class GhidraProjectUtils {
/**
* Gets the required VM used to build and run the Ghidra defined by the given layout.
*
* @param javaConfig Ghidra's Java configuration.
* @param appConfig Ghidra's application configuration.
* @return The required VM used to build and run the Ghidra defined by the given layout.
* @throws IOException If there was a file-related problem with getting the VM.
* @throws CoreException If there was an Eclipse-related problem with creating the project.
*/
private static IVMInstall getGhidraVm(JavaConfig javaConfig) throws IOException, CoreException {
private static IVMInstall getGhidraVm(AppConfig appConfig) throws IOException, CoreException {
File requiredJavaHomeDir = javaConfig.getSavedJavaHome(); // safe to assume it's valid
File requiredJavaHomeDir = appConfig.getSavedJavaHome(); // safe to assume it's valid
// First look for a matching VM in Eclipse's existing list.
// NOTE: Mac has its own VM type, so be sure to check it for VM matches too.
@ -617,19 +614,19 @@ public class GhidraProjectUtils {
* Configures the default Java compiler behavior for the given java project.
*
* @param jp The Java project to configure.
* @param javaConfig Ghidra's Java configuration.
* @param appConfig Ghidra's application configuration.
*/
private static void configureJavaCompiler(IJavaProject jp, JavaConfig javaConfig) {
private static void configureJavaCompiler(IJavaProject jp, AppConfig appConfig) {
final String WARNING = JavaCore.WARNING;
final String IGNORE = JavaCore.IGNORE;
final String ERROR = JavaCore.ERROR;
// Compliance
jp.setOption(JavaCore.COMPILER_SOURCE, javaConfig.getCompilerComplianceLevel());
jp.setOption(JavaCore.COMPILER_COMPLIANCE, javaConfig.getCompilerComplianceLevel());
jp.setOption(JavaCore.COMPILER_SOURCE, appConfig.getCompilerComplianceLevel());
jp.setOption(JavaCore.COMPILER_COMPLIANCE, appConfig.getCompilerComplianceLevel());
jp.setOption(JavaCore.COMPILER_CODEGEN_TARGET_PLATFORM,
javaConfig.getCompilerComplianceLevel());
appConfig.getCompilerComplianceLevel());
// Code style
jp.setOption(JavaCore.COMPILER_PB_STATIC_ACCESS_RECEIVER, WARNING);

View file

@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -26,6 +26,7 @@ import org.eclipse.jdt.core.*;
import ghidra.GhidraApplicationLayout;
import ghidra.framework.GModule;
import ghidradev.Activator;
import ghidradev.ghidraprojectcreator.utils.PyDevUtils.ProjectPythonInterpreter;
/**
* Utility methods for working with Ghidra scripts in Eclipse.
@ -45,8 +46,7 @@ public class GhidraScriptUtils {
* @param linkUserScripts Whether or not to link in the user scripts directory.
* @param linkSystemScripts Whether or not to link in the system scripts directories.
* @param ghidraLayout The Ghidra layout to link the project to.
* @param jythonInterpreterName The name of the Jython interpreter to use for Python support.
* Could be null if Python support is not wanted.
* @param pythonInterpreter The Python interpreter to use.
* @param monitor The progress monitor to use during project creation.
* @return The created project.
* @throws IOException If there was a file-related problem with creating the project.
@ -56,15 +56,14 @@ public class GhidraScriptUtils {
public static IJavaProject createGhidraScriptProject(String projectName, File projectDir,
boolean createRunConfig, String runConfigMemory, boolean linkUserScripts,
boolean linkSystemScripts, GhidraApplicationLayout ghidraLayout,
String jythonInterpreterName, IProgressMonitor monitor)
ProjectPythonInterpreter pythonInterpreter, IProgressMonitor monitor)
throws IOException, ParseException, CoreException {
List<IClasspathEntry> classpathEntries = new ArrayList<>();
// Create empty Ghidra project
IJavaProject javaProject = GhidraProjectUtils.createEmptyGhidraProject(projectName,
projectDir, createRunConfig, runConfigMemory, ghidraLayout, jythonInterpreterName,
monitor);
projectDir, createRunConfig, runConfigMemory, ghidraLayout, pythonInterpreter, monitor);
// Link each module's ghidra_scripts directory to the project
if (linkSystemScripts) {

View file

@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -26,6 +26,7 @@ import java.util.stream.Stream;
import javax.naming.OperationNotSupportedException;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.runtime.*;
import org.eclipse.jdt.core.IClasspathEntry;
import org.eclipse.jdt.core.IJavaProject;
@ -38,23 +39,58 @@ 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";
public final static String MIN_SUPPORTED_VERSION = "9.3.0";
public final static String MAX_JYTHON_SUPPORTED_VERSION = "9.3.0";
/**
* Checks to see if a supported version of PyDev is installed.
*
* @return True if a supported version of PyDev is installed; otherwise, false.
* The various types of supported Python interpreters
*/
public static boolean isSupportedPyDevInstalled() {
public static enum ProjectPythonInterpreterType {
NONE,
PYGHIDRA,
JYTHON
}
/**
* The projects Python interpreter to use
*
* @param name The name of the interpreter
* @param type The {@link ProjectPythonInterpreterType type} of the interpreter
*/
public static record ProjectPythonInterpreter(String name, ProjectPythonInterpreterType type) {}
/**
* {@return true if a supported version of PyDev is installed for use with PyGhidra; otherwise,
* false}
*/
public static boolean isSupportedPyGhidraPyDevInstalled() {
Version min = Version.valueOf(MIN_SUPPORTED_VERSION);
Version max = Version.valueOf(MAX_SUPPORTED_VERSION);
try {
Version version = PyDevUtilsInternal.getPyDevVersion();
if (version != null) {
return version.compareTo(min) >= 0;
}
}
catch (NoClassDefFoundError e) {
// Fall through to return false
}
return false;
}
/**
* {@return true if a supported version of PyDev is installed for use with Jython; otherwise,
* false}
*/
public static boolean isSupportedJythonPyDevInstalled() {
Version min = Version.valueOf(MIN_SUPPORTED_VERSION);
Version max = Version.valueOf(MAX_JYTHON_SUPPORTED_VERSION);
try {
Version version = PyDevUtilsInternal.getPyDevVersion();
if (version != null) {
// Make sure the installed version of PyDev is new enough to support the following
// operation.
getJython27InterpreterNames();
getJythonInterpreterNames();
return version.compareTo(min) >= 0 && version.compareTo(max) <= 0;
}
}
@ -66,15 +102,53 @@ public class PyDevUtils {
}
/**
* Gets a list of discovered Jython 2.7 interpreter names.
*
* @return a list of discovered Jython 2.7 interpreter names.
* Gets a list of discovered PyGhidra interpreter names.
* @param requiredFileMatch if not {@code null}, only interpreter names that correspond to the
* given interpreter file will be returned.
* @return a list of discovered PyGhidra interpreter names.
* @throws OperationNotSupportedException if PyDev is not installed or it does not support this
* operation.
*/
public static List<String> getJython27InterpreterNames() throws OperationNotSupportedException {
public static List<String> getPyGhidraInterpreterNames(File requiredFileMatch)
throws OperationNotSupportedException {
try {
return PyDevUtilsInternal.getJython27InterpreterNames();
return PyDevUtilsInternal.getPyGhidraInterpreterNames(requiredFileMatch);
}
catch (NoClassDefFoundError | NoSuchMethodError e) {
throw new OperationNotSupportedException(e.getMessage());
}
}
/**
* Gets a list of discovered Jython interpreter names.
*
* @return a list of discovered Jython interpreter names.
* @throws OperationNotSupportedException if PyDev is not installed or it does not support this
* operation.
*/
public static List<String> getJythonInterpreterNames() throws OperationNotSupportedException {
try {
return PyDevUtilsInternal.getJythonInterpreterNames();
}
catch (NoClassDefFoundError | NoSuchMethodError e) {
throw new OperationNotSupportedException(e.getMessage());
}
}
/**
* Adds the given PyGhidra interpreter to PyDev.
*
* @param interpreterName The name of the interpreter to add.
* @param interpreterFile The interpreter file to add.
* @param pypredefDir The pypredef directory to use (could be null if not supported)
* @throws OperationNotSupportedException if PyDev is not installed or it does not support this
* operation.
*/
public static void addPyGhidraInterpreter(String interpreterName, File interpreterFile,
File pypredefDir) throws OperationNotSupportedException {
try {
PyDevUtilsInternal.addPyGhidraInterpreter(interpreterName, interpreterFile,
pypredefDir);
}
catch (NoClassDefFoundError | NoSuchMethodError e) {
throw new OperationNotSupportedException(e.getMessage());
@ -107,8 +181,7 @@ public class PyDevUtils {
*
* @param javaProject The Java project to enable Python for.
* @param classpathEntries The classpath entries to add to the Python path.
* @param jythonInterpreterName The name of the Jython interpreter to use for Python support.
* If this is null, Python support will be removed from the project.
* @param pythonInterpreter The Python interpreter to use.
* @param monitor The progress monitor used during link.
* @throws CoreException if there was an Eclipse-related problem with enabling Python for the
* project.
@ -116,11 +189,11 @@ public class PyDevUtils {
* operation.
*/
public static void setupPythonForProject(IJavaProject javaProject,
List<IClasspathEntry> classpathEntries, String jythonInterpreterName,
List<IClasspathEntry> classpathEntries, ProjectPythonInterpreter pythonInterpreter,
IProgressMonitor monitor) throws CoreException, OperationNotSupportedException {
try {
PyDevUtilsInternal.setupPythonForProject(javaProject, classpathEntries,
jythonInterpreterName, monitor);
pythonInterpreter, monitor);
}
catch (NoClassDefFoundError | NoSuchMethodError e) {
throw new OperationNotSupportedException(e.getMessage());
@ -151,6 +224,15 @@ public class PyDevUtils {
return "org.python.pydev.ui.pythonpathconf.interpreterPreferencesPageJython";
}
/**
* Gets the PyDev Python preference page ID.
*
* @return the PyDev Python preference page ID.
*/
public static String getPythonPreferencePageId() {
return "org.python.pydev.ui.pythonpathconf.interpreterPreferencesPagePython";
}
/**
* Gets The PyDev source directory.
*
@ -184,4 +266,138 @@ public class PyDevUtils {
return null;
}
/**
* Checks to see if the given project is a Python project.
*
* @param project The project to check.
* @return True if the given project is a Python project; otherwise, false.
* @throws OperationNotSupportedException if PyDev is not installed or it does not support this
* operation.
*/
public static boolean isPythonProject(IProject project) throws OperationNotSupportedException {
try {
return PyDevUtilsInternal.isPythonProject(project);
}
catch (NoClassDefFoundError | NoSuchMethodError e) {
throw new OperationNotSupportedException(e.getMessage());
}
}
/**
* Checks to see if the given project is a PyGhidra project.
*
* @param project The project to check.
* @return True if the given project is a PyGhidra project; otherwise, false.
* @throws OperationNotSupportedException if PyDev is not installed or it does not support this
* operation.
*/
public static boolean isPyGhidraProject(IProject project)
throws OperationNotSupportedException {
try {
return PyDevUtilsInternal.isPyGhidraProject(project);
}
catch (NoClassDefFoundError | NoSuchMethodError e) {
throw new OperationNotSupportedException(e.getMessage());
}
}
/**
* Gets the interpreter name of the given Python project.
*
* @param project The project to get the interpreter name from.
* @return The interpreter name of the given Python project, or null it it's not a Python
* project or doesn't have an interpreter.
* @throws OperationNotSupportedException if PyDev is not installed or it does not support this
* operation.
*/
public static String getInterpreterName(IProject project)
throws OperationNotSupportedException {
try {
return PyDevUtilsInternal.getInterpreterName(project);
}
catch (NoClassDefFoundError | NoSuchMethodError e) {
throw new OperationNotSupportedException(e.getMessage());
}
}
/**
* Gets the PyDev "project" attribute.
*
* @return The PyDev "project" attribute.
* @throws OperationNotSupportedException if PyDev is not installed or it does not support this
* operation.
*/
public static String getAttrProject() throws OperationNotSupportedException {
try {
return PyDevUtilsInternal.getAttrProject();
}
catch (NoClassDefFoundError e) {
throw new OperationNotSupportedException(e.getMessage());
}
}
/**
* Gets the PyDev "location" attribute.
*
* @return The PyDev "location" attribute.
* @throws OperationNotSupportedException if PyDev is not installed or it does not support this
* operation.
*/
public static String getAttrLocation() throws OperationNotSupportedException {
try {
return PyDevUtilsInternal.getAttrLocation();
}
catch (NoClassDefFoundError e) {
throw new OperationNotSupportedException(e.getMessage());
}
}
/**
* Gets the PyDev "program arguments" attribute.
*
* @return The PyDev "program arguments" attribute.
* @throws OperationNotSupportedException if PyDev is not installed or it does not support this
* operation.
*/
public static String getAttrProgramArguments() throws OperationNotSupportedException {
try {
return PyDevUtilsInternal.getAttrProgramArguments();
}
catch (NoClassDefFoundError e) {
throw new OperationNotSupportedException(e.getMessage());
}
}
/**
* Gets the PyDev "interpreter" attribute.
*
* @return The PyDev "interpreter" attribute.
* @throws OperationNotSupportedException if PyDev is not installed or it does not support this
* operation.
*/
public static String getAttrInterpreter() throws OperationNotSupportedException {
try {
return PyDevUtilsInternal.getAttrInterpreter();
}
catch (NoClassDefFoundError e) {
throw new OperationNotSupportedException(e.getMessage());
}
}
/**
* Gets the PyDev "interpreter default" attribute.
*
* @return The PyDev "interpreter default" attribute.
* @throws OperationNotSupportedException if PyDev is not installed or it does not support this
* operation.
*/
public static String getAttrInterpreterDefault() throws OperationNotSupportedException {
try {
return PyDevUtilsInternal.getAttrInterpreterDefault();
}
catch (NoClassDefFoundError e) {
throw new OperationNotSupportedException(e.getMessage());
}
}
}

View file

@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -19,6 +19,7 @@ import java.io.File;
import java.util.*;
import java.util.stream.Collectors;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.runtime.*;
import org.eclipse.jdt.core.IClasspathEntry;
import org.eclipse.jdt.core.IJavaProject;
@ -26,11 +27,13 @@ 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.*;
import org.python.pydev.debug.core.Constants;
import org.python.pydev.plugin.nature.PythonNature;
import com.python.pydev.debug.remote.client_api.PydevRemoteDebuggerServer;
import ghidradev.EclipseMessageUtils;
import ghidradev.ghidraprojectcreator.utils.PyDevUtils.ProjectPythonInterpreter;
/**
* Utility methods for interacting with PyDev.
@ -62,20 +65,50 @@ class PyDevUtilsInternal {
}
/**
* Gets a list of discovered Jython 2.7 interpreter names.
* Gets a list of discovered PyGhidra interpreter names.
*
* @return a list of discovered Jython 2.7 interpreter names.
* @param requiredFileMatch if not {@code null}, only interpreter names that correspond to the
* given interpreter file will be returned.
* @return a list of discovered PyGhidra interpreter names.
* @throws NoClassDefFoundError if PyDev is not installed or it does not support this operation.
* @throws NoSuchMethodError if PyDev is not installed or it does not support this operation.
*/
public static List<String> getJython27InterpreterNames()
public static List<String> getPyGhidraInterpreterNames(File requiredFileMatch)
throws NoClassDefFoundError, NoSuchMethodError {
List<String> interpreters = new ArrayList<>();
IInterpreterManager iMan = InterpreterManagersAPI.getPythonInterpreterManager(true);
for (IInterpreterInfo info : iMan.getInterpreterInfos()) {
ISystemModulesManager modulesManager = info.getModulesManager();
if (info.getInterpreterType() == IPythonNature.INTERPRETER_TYPE_PYTHON &&
!modulesManager.getAllModulesStartingWith("pyghidra.__main__").isEmpty()) {
if (requiredFileMatch == null ||
requiredFileMatch.getAbsolutePath().equals(info.getExecutableOrJar())) {
interpreters.add(info.getName());
}
}
}
return interpreters;
}
/**
* Gets a list of discovered Jython interpreter names.
*
* @return a list of discovered Jython interpreter names.
* @throws NoClassDefFoundError if PyDev is not installed or it does not support this operation.
* @throws NoSuchMethodError if PyDev is not installed or it does not support this operation.
*/
public static List<String> getJythonInterpreterNames()
throws NoClassDefFoundError, NoSuchMethodError {
List<String> interpreters = new ArrayList<>();
IInterpreterManager iMan = InterpreterManagersAPI.getJythonInterpreterManager(true);
for (IInterpreterInfo info : iMan.getInterpreterInfos()) {
if (info.getInterpreterType() == IPythonNature.INTERPRETER_TYPE_JYTHON && info.getVersion().equals("2.7")) {
if (info.getInterpreterType() == IPythonNature.INTERPRETER_TYPE_JYTHON &&
info.getVersion().equals("2.7")) {
interpreters.add(info.getName());
}
}
@ -83,6 +116,38 @@ class PyDevUtilsInternal {
return interpreters;
}
/**
* Adds the given PyGhidra interpreter to PyDev.
*
* @param interpreterName The name of the interpreter to add.
* @param interpreterFile The interpreter to add.
* @param pypredefDir The pypredef directory to use (could be null if not supported)
* @throws NoClassDefFoundError if PyDev is not installed or it does not support this operation.
* @throws NoSuchMethodError if PyDev is not installed or it does not support this operation.
*/
public static void addPyGhidraInterpreter(String interpreterName, File interpreterFile,
File pypredefDir) throws NoClassDefFoundError, NoSuchMethodError {
IProgressMonitor monitor = new NullProgressMonitor();
IInterpreterManager iMan = InterpreterManagersAPI.getPythonInterpreterManager(true);
IInterpreterInfo[] interpreterInfos = iMan.getInterpreterInfos();
for (IInterpreterInfo iInfo : interpreterInfos) {
if (iInfo.getName().equals(interpreterName) &&
iInfo.getExecutableOrJar().equals(interpreterFile.getAbsolutePath())) {
return;
}
}
IInterpreterInfo iInfo =
iMan.createInterpreterInfo(interpreterFile.getAbsolutePath(), monitor, false);
iInfo.setName(interpreterName);
if (iInfo instanceof InterpreterInfo ii && pypredefDir != null) {
ii.addPredefinedCompletionsPath(pypredefDir.getAbsolutePath());
}
IInterpreterInfo[] newInterpreterInfos =
Arrays.copyOf(interpreterInfos, interpreterInfos.length + 1);
newInterpreterInfos[interpreterInfos.length] = iInfo;
iMan.setInfos(newInterpreterInfos, null, monitor);
}
/**
* Adds the given Jython interpreter to PyDev.
*
@ -119,26 +184,38 @@ class PyDevUtilsInternal {
*
* @param javaProject The Java project to setup Python for.
* @param classpathEntries The classpath entries to add to the Python path.
* @param jythonInterpreterName The name of the Jython interpreter to use for Python support.
* If this is null, Python support will be removed from the project.
* @param pythonInterpreter The Python interpreter to use.
* @param monitor The progress monitor used during link.
* @throws CoreException If there was an Eclipse-related problem with enabling Python for the project.
* @throws NoClassDefFoundError if PyDev is not installed or it does not support this operation.
* @throws NoSuchMethodError if PyDev is not installed or it does not support this operation.
*/
public static void setupPythonForProject(IJavaProject javaProject,
List<IClasspathEntry> classpathEntries, String jythonInterpreterName,
List<IClasspathEntry> classpathEntries, ProjectPythonInterpreter pythonInterpreter,
IProgressMonitor monitor)
throws CoreException, NoClassDefFoundError, NoSuchMethodError {
PythonNature.removeNature(javaProject.getProject(), monitor);
if (jythonInterpreterName != null) {
String libs = classpathEntries.stream().map(e -> e.getPath().toOSString()).collect(
Collectors.joining("|"));
PythonNature.addNature(javaProject.getProject(), monitor,
IPythonNature.JYTHON_VERSION_2_7, null, libs, jythonInterpreterName, null);
String version;
String libs;
switch (pythonInterpreter.type()) {
case PYGHIDRA:
version = IPythonNature.PYTHON_VERSION_INTERPRETER;
libs = null;
break;
case JYTHON:
version = IPythonNature.JYTHON_VERSION_INTERPRETER;
libs = classpathEntries.stream()
.map(e -> e.getPath().toOSString())
.collect(Collectors.joining("|"));
break;
default:
return;
}
PythonNature.addNature(javaProject.getProject(), monitor, version, null, libs,
pythonInterpreter.name(), null);
}
/**
@ -151,6 +228,109 @@ class PyDevUtilsInternal {
PydevRemoteDebuggerServer.startServer();
}
/**
* Checks to see if the given project is a Python project.
*
* @param project The project to check.
* @return True if the given project is a Python project; otherwise, false.
* @throws NoClassDefFoundError if PyDev is not installed or it does not support this operation.
*/
public static boolean isPythonProject(IProject project) throws NoClassDefFoundError {
try {
return project.hasNature(PythonNature.PYTHON_NATURE_ID);
}
catch (CoreException e) {
return false;
}
}
/**
* Checks to see if the given project is a PyGhidra project.
*
* @param project The project to check.
* @return True if the given project is a PyGhidra project; otherwise, false.
* @throws NoClassDefFoundError if PyDev is not installed or it does not support this operation.
*/
public static boolean isPyGhidraProject(IProject project) throws NoClassDefFoundError {
return isPythonProject(project) && GhidraProjectUtils.isGhidraProject(project);
}
/**
* Gets the interpreter name of the given Python project.
*
* @param project The project to get the interpreter name from.
* @return The interpreter name of the given Python project, or null it it's not a Python
* project or doesn't have an interpreter.
* @throws NoClassDefFoundError if PyDev is not installed or it does not support this operation.
* @throws NoSuchMethodError if PyDev is not installed or it does not support this operation.
*/
public static String getInterpreterName(IProject project)
throws NoClassDefFoundError, NoSuchMethodError {
PythonNature nature = PythonNature.getPythonNature(project);
if (nature != null) {
try {
IInterpreterInfo info = nature.getProjectInterpreter();
if (info != null) {
return info.getName();
}
}
catch (PythonNatureWithoutProjectException | MisconfigurationException e) {
// Fall through
}
}
return null;
}
/**
* Gets the PyDev "project" attribute.
*
* @return The PyDev "project" attribute.
* @throws NoClassDefFoundError if PyDev is not installed or it does not support this operation.
*/
public static String getAttrProject() throws NoClassDefFoundError {
return Constants.ATTR_PROJECT;
}
/**
* Gets the PyDev "location" attribute.
*
* @return The PyDev "location" attribute.
* @throws NoClassDefFoundError if PyDev is not installed or it does not support this operation.
*/
public static String getAttrLocation() throws NoClassDefFoundError {
return Constants.ATTR_LOCATION;
}
/**
* Gets the PyDev "program arguments" attribute.
*
* @return The PyDev "program arguments" attribute.
* @throws NoClassDefFoundError if PyDev is not installed or it does not support this operation.
*/
public static String getAttrProgramArguments() throws NoClassDefFoundError {
return Constants.ATTR_PROGRAM_ARGUMENTS;
}
/**
* Gets the PyDev "interpreter" attribute.
*
* @return The PyDev "interpreter" attribute.
* @throws NoClassDefFoundError if PyDev is not installed or it does not support this operation.
*/
public static String getAttrInterpreter() throws NoClassDefFoundError {
return Constants.ATTR_INTERPRETER;
}
/**
* Gets the PyDev "interpreter default" attribute.
*
* @return The PyDev "interpreter default" attribute.
* @throws NoClassDefFoundError if PyDev is not installed or it does not support this operation.
*/
public static String getAttrInterpreterDefault() throws NoClassDefFoundError {
return Constants.ATTR_INTERPRETER_DEFAULT;
}
private PyDevUtilsInternal() throws NoClassDefFoundError {
// Prevent instantiation
}

View file

@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -36,6 +36,7 @@ import ghidra.GhidraApplicationLayout;
import ghidradev.EclipseMessageUtils;
import ghidradev.ghidraprojectcreator.utils.GhidraModuleUtils;
import ghidradev.ghidraprojectcreator.utils.GhidraModuleUtils.ModuleTemplateType;
import ghidradev.ghidraprojectcreator.utils.PyDevUtils.ProjectPythonInterpreter;
import ghidradev.ghidraprojectcreator.wizards.pages.*;
import utilities.util.FileUtilities;
@ -87,12 +88,12 @@ public class CreateGhidraModuleProjectWizard extends Wizard implements INewWizar
boolean createRunConfig = projectPage.shouldCreateRunConfig();
String runConfigMemory = projectPage.getRunConfigMemory();
File projectDir = projectPage.getProjectDir();
String jythonInterpreterName = pythonPage.getJythonInterpreterName();
ProjectPythonInterpreter pythonInterpreter = pythonPage.getProjectPythonInterpreter();
Set<ModuleTemplateType> moduleTemplateTypes = projectConfigPage.getModuleTemplateTypes();
try {
getContainer().run(true, false,
monitor -> create(ghidraInstallDir, projectName, projectDir, createRunConfig,
runConfigMemory, moduleTemplateTypes, jythonInterpreterName, monitor));
runConfigMemory, moduleTemplateTypes, pythonInterpreter, monitor));
}
catch (InterruptedException e) {
Thread.currentThread().interrupt();
@ -115,14 +116,13 @@ public class CreateGhidraModuleProjectWizard extends Wizard implements INewWizar
* @param createRunConfig Whether or not to create a new run configuration for the project.
* @param runConfigMemory The run configuration's desired memory. Could be null.
* @param moduleTemplateTypes The desired module template types.
* @param jythonInterpreterName The name of the Jython interpreter to use for Python support.
* Could be null if Python support is not wanted.
* @param pythonInterpreter The Python interpreter to use.
* @param monitor The monitor to use during project creation.
* @throws InvocationTargetException if an error occurred during project creation.
*/
private void create(File ghidraInstallDir, String projectName, File projectDir,
boolean createRunConfig, String runConfigMemory,
Set<ModuleTemplateType> moduleTemplateTypes, String jythonInterpreterName,
Set<ModuleTemplateType> moduleTemplateTypes, ProjectPythonInterpreter pythonInterpreter,
IProgressMonitor monitor) throws InvocationTargetException {
try {
info("Creating " + projectName + " at " + projectDir);
@ -133,7 +133,7 @@ public class CreateGhidraModuleProjectWizard extends Wizard implements INewWizar
IJavaProject javaProject =
GhidraModuleUtils.createGhidraModuleProject(projectName, projectDir,
createRunConfig, runConfigMemory, ghidraLayout, jythonInterpreterName, monitor);
createRunConfig, runConfigMemory, ghidraLayout, pythonInterpreter, monitor);
monitor.worked(1);
IFile sourceFile = GhidraModuleUtils.configureModuleSource(javaProject,

View file

@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -32,6 +32,7 @@ import org.eclipse.ui.IWorkbench;
import ghidra.GhidraApplicationLayout;
import ghidradev.EclipseMessageUtils;
import ghidradev.ghidraprojectcreator.utils.GhidraScriptUtils;
import ghidradev.ghidraprojectcreator.utils.PyDevUtils.ProjectPythonInterpreter;
import ghidradev.ghidraprojectcreator.wizards.pages.*;
import utilities.util.FileUtilities;
@ -81,11 +82,11 @@ public class CreateGhidraScriptProjectWizard extends Wizard implements INewWizar
String runConfigMemory = projectPage.getRunConfigMemory();
boolean linkUserScripts = projectConfigPage.shouldLinkUsersScripts();
boolean linkSystemScripts = projectConfigPage.shouldLinkSystemScripts();
String jythonInterpreterName = pythonPage.getJythonInterpreterName();
ProjectPythonInterpreter pythonInterpreter = pythonPage.getProjectPythonInterpreter();
try {
getContainer().run(true, false,
monitor -> create(ghidraInstallDir, projectName, projectDir, createRunConfig,
runConfigMemory, linkUserScripts, linkSystemScripts, jythonInterpreterName,
runConfigMemory, linkUserScripts, linkSystemScripts, pythonInterpreter,
monitor));
}
catch (InterruptedException e) {
@ -110,15 +111,14 @@ public class CreateGhidraScriptProjectWizard extends Wizard implements INewWizar
* @param runConfigMemory The run configuration's desired memory. Could be null.
* @param linkUserScripts Whether or not to link in the user scripts directory.
* @param linkSystemScripts Whether or not to link in the system scripts directories.
* @param jythonInterpreterName The name of the Jython interpreter to use for Python support.
* Could be null if Python support is not wanted.
* @param pythonInterpreter The Python interpreter to use.
* @param monitor The monitor to use during project creation.
* @throws InvocationTargetException if an error occurred during project creation.
*/
private void create(File ghidraInstallDir, String projectName, File projectDir,
boolean createRunConfig, String runConfigMemory, boolean linkUserScripts,
boolean linkSystemScripts, String jythonInterpreterName, IProgressMonitor monitor)
throws InvocationTargetException {
boolean linkSystemScripts, ProjectPythonInterpreter pythonInterpreter,
IProgressMonitor monitor) throws InvocationTargetException {
try {
info("Creating " + projectName + " at " + projectDir);
monitor.beginTask("Creating " + projectName, 2);
@ -128,7 +128,7 @@ public class CreateGhidraScriptProjectWizard extends Wizard implements INewWizar
GhidraScriptUtils.createGhidraScriptProject(projectName, projectDir, createRunConfig,
runConfigMemory, linkUserScripts, linkSystemScripts, ghidraLayout,
jythonInterpreterName, monitor);
pythonInterpreter, monitor);
monitor.worked(1);
info("Finished creating " + projectName);

View file

@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -40,7 +40,7 @@ import org.eclipse.ui.INewWizard;
import org.eclipse.ui.IWorkbench;
import ghidra.GhidraApplicationLayout;
import ghidra.launch.JavaConfig;
import ghidra.launch.AppConfig;
import ghidradev.EclipseMessageUtils;
import ghidradev.ghidraprojectcreator.utils.GhidraProjectUtils;
import ghidradev.ghidraprojectcreator.wizards.pages.ChooseGhidraModuleProjectWizardPage;
@ -123,7 +123,7 @@ public class ExportGhidraModuleWizard extends Wizard implements INewWizard {
// TODO: It's more correct to get this from the project's classpath, since Ghidra's
// saved Java home can change from launch to launch.
GhidraApplicationLayout ghidraLayout = new GhidraApplicationLayout(new File(ghidraInstallDirPath));
File javaHomeDir = new JavaConfig(
File javaHomeDir = new AppConfig(
ghidraLayout.getApplicationInstallationDir().getFile(false)).getSavedJavaHome();
if(javaHomeDir == null) {
throw new IOException("Failed to get the Java home associated with the project. " +

View file

@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -32,6 +32,7 @@ import org.eclipse.ui.IWorkbench;
import ghidra.GhidraApplicationLayout;
import ghidradev.EclipseMessageUtils;
import ghidradev.ghidraprojectcreator.utils.GhidraModuleUtils;
import ghidradev.ghidraprojectcreator.utils.PyDevUtils.ProjectPythonInterpreter;
import ghidradev.ghidraprojectcreator.wizards.pages.*;
import utilities.util.FileUtilities;
@ -76,11 +77,11 @@ public class ImportGhidraModuleSourceWizard extends Wizard implements IImportWiz
String projectName = projectPage.getProjectName();
boolean createRunConfig = projectPage.shouldCreateRunConfig();
String runConfigMemory = projectPage.getRunConfigMemory();
String jythonInterpreterName = pythonPage.getJythonInterpreterName();
ProjectPythonInterpreter pythonInterpreter = pythonPage.getProjectPythonInterpreter();
try {
getContainer().run(true, false,
monitor -> importModuleSource(ghidraInstallDir, projectName, moduleSourceDir,
createRunConfig, runConfigMemory, jythonInterpreterName, monitor));
createRunConfig, runConfigMemory, pythonInterpreter, monitor));
}
catch (InterruptedException e) {
Thread.currentThread().interrupt();
@ -102,14 +103,14 @@ public class ImportGhidraModuleSourceWizard extends Wizard implements IImportWiz
* @param moduleSourceDir The module source directory to import.
* @param createRunConfig Whether or not to create a new run configuration for the project.
* @param runConfigMemory The run configuration's desired memory. Could be null.
* @param jythonInterpreterName The name of the Jython interpreter to use for Python support.
* Could be null if Python support is not wanted.
* @param pythonInterpreter The Python interpreter to use.
* @param monitor The monitor to use during project creation.
* @throws InvocationTargetException if an error occurred during project creation.
*/
private void importModuleSource(File ghidraInstallDir, String projectName, File moduleSourceDir,
boolean createRunConfig, String runConfigMemory, String jythonInterpreterName,
IProgressMonitor monitor) throws InvocationTargetException {
boolean createRunConfig, String runConfigMemory,
ProjectPythonInterpreter pythonInterpreter, IProgressMonitor monitor)
throws InvocationTargetException {
try {
info("Importing " + projectName + " at " + moduleSourceDir);
monitor.beginTask("Importing " + projectName, 2);
@ -118,7 +119,7 @@ public class ImportGhidraModuleSourceWizard extends Wizard implements IImportWiz
monitor.worked(1);
GhidraModuleUtils.importGhidraModuleSource(projectName, moduleSourceDir,
createRunConfig, runConfigMemory, ghidraLayout, jythonInterpreterName, monitor);
createRunConfig, runConfigMemory, ghidraLayout, pythonInterpreter, monitor);
monitor.worked(1);
info("Finished importing " + projectName);

View file

@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -31,8 +31,9 @@ import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.wizard.Wizard;
import ghidra.GhidraApplicationLayout;
import ghidra.launch.JavaConfig;
import ghidra.launch.AppConfig;
import ghidradev.ghidraprojectcreator.utils.GhidraProjectUtils;
import ghidradev.ghidraprojectcreator.utils.PyDevUtils.ProjectPythonInterpreter;
import ghidradev.ghidraprojectcreator.wizards.pages.*;
/**
@ -63,10 +64,10 @@ public class LinkGhidraWizard extends Wizard {
public boolean performFinish() {
File ghidraInstallDir = ghidraInstallationPage.getGhidraInstallDir();
IJavaProject javaProject = projectPage.getJavaProject();
String jythonInterpreterName = pythonPage.getJythonInterpreterName();
ProjectPythonInterpreter pythonInterpreter = pythonPage.getProjectPythonInterpreter();
try {
getContainer().run(true, false,
monitor -> link(ghidraInstallDir, javaProject, jythonInterpreterName, monitor));
monitor -> link(ghidraInstallDir, javaProject, pythonInterpreter, monitor));
}
catch (InterruptedException e) {
Thread.currentThread().interrupt();
@ -85,23 +86,23 @@ public class LinkGhidraWizard extends Wizard {
*
* @param ghidraInstallDir The Ghidra installation directory to use.
* @param javaProject The Java project to link.
* @param jythonInterpreterName The name of the Jython interpreter to use for Python support.
* Could be null if Python support is not wanted.
* @param pythonInterpreter The Python interpreter to use.
* @param monitor The monitor to use during project link.
* @throws InvocationTargetException if an error occurred during link.
*/
private void link(File ghidraInstallDir, IJavaProject javaProject, String jythonInterpreterName,
IProgressMonitor monitor) throws InvocationTargetException {
private void link(File ghidraInstallDir, IJavaProject javaProject,
ProjectPythonInterpreter pythonInterpreter, IProgressMonitor monitor)
throws InvocationTargetException {
IProject project = javaProject.getProject();
try {
info("Linking " + project.getName());
monitor.beginTask("Linking " + project.getName(), 2);
GhidraApplicationLayout ghidraLayout = new GhidraApplicationLayout(ghidraInstallDir);
JavaConfig javaConfig =
new JavaConfig(ghidraLayout.getApplicationInstallationDir().getFile(false));
GhidraProjectUtils.linkGhidraToProject(javaProject, ghidraLayout, javaConfig,
jythonInterpreterName, monitor);
AppConfig appConfig =
new AppConfig(ghidraLayout.getApplicationInstallationDir().getFile(false));
GhidraProjectUtils.linkGhidraToProject(javaProject, ghidraLayout, appConfig,
pythonInterpreter, monitor);
monitor.worked(1);
project.refreshLocal(IResource.DEPTH_INFINITE, monitor);

View file

@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -27,7 +27,7 @@ import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.*;
import org.eclipse.ui.dialogs.PreferencesUtil;
import ghidra.launch.JavaConfig;
import ghidra.launch.AppConfig;
import ghidra.launch.JavaFinder.JavaFilter;
import ghidradev.ghidraprojectcreator.preferences.GhidraProjectCreatorPreferencePage;
import ghidradev.ghidraprojectcreator.preferences.GhidraProjectCreatorPreferences;
@ -108,8 +108,8 @@ public class ChooseGhidraInstallationWizardPage extends WizardPage {
File ghidraInstallDir = new File(ghidraInstallDirCombo.getText());
GhidraProjectCreatorPreferencePage.validateGhidraInstallation(ghidraInstallDir);
try {
JavaConfig javaConfig = new JavaConfig(ghidraInstallDir);
if (!javaConfig.isSupportedJavaHomeDir(javaConfig.getSavedJavaHome(),
AppConfig appConfig = new AppConfig(ghidraInstallDir);
if (!appConfig.isSupportedJavaHomeDir(appConfig.getSavedJavaHome(),
JavaFilter.JDK_ONLY)) {
message = "A supported JDK is not associated with this Ghidra " +
"installation. Please run this Ghidra and try again.";

View file

@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -15,8 +15,7 @@
*/
package ghidradev.ghidraprojectcreator.wizards.pages;
import java.io.File;
import java.io.IOException;
import java.io.*;
import java.nio.file.Files;
import java.util.List;
@ -27,13 +26,15 @@ import org.eclipse.jface.wizard.WizardPage;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.layout.*;
import org.eclipse.swt.widgets.*;
import org.eclipse.ui.dialogs.PreferencesUtil;
import ghidra.launch.AppConfig;
import ghidradev.EclipseMessageUtils;
import ghidradev.ghidraprojectcreator.utils.PyDevUtils;
import ghidradev.ghidraprojectcreator.utils.PyDevUtils.ProjectPythonInterpreter;
import ghidradev.ghidraprojectcreator.utils.PyDevUtils.ProjectPythonInterpreterType;
/**
* A wizard page that lets the user enable python for their project.
@ -41,7 +42,11 @@ import ghidradev.ghidraprojectcreator.utils.PyDevUtils;
public class EnablePythonWizardPage extends WizardPage {
private ChooseGhidraInstallationWizardPage ghidraInstallationPage;
private Button enablePythonCheckboxButton;
private Button pyghidraButton;
private Button jythonButton;
private Button noneButton;
private Combo pyghidraCombo;
private Button addPyGhidraButton;
private Combo jythonCombo;
private Button addJythonButton;
@ -61,46 +66,107 @@ public class EnablePythonWizardPage extends WizardPage {
public void createControl(Composite parent) {
Composite container = new Composite(parent, SWT.NULL);
container.setLayout(new GridLayout(3, false));
container.setLayout(new GridLayout(1, false));
// Enable Python checkbox.
enablePythonCheckboxButton = new Button(container, SWT.CHECK);
enablePythonCheckboxButton.setText("Enable Python");
enablePythonCheckboxButton.setToolTipText("Enables Python support using the PyDev " +
"Eclipse plugin. Requires PyDev version " + PyDevUtils.MIN_SUPPORTED_VERSION +
" - " + PyDevUtils.MAX_SUPPORTED_VERSION);
enablePythonCheckboxButton.setSelection(PyDevUtils.isSupportedPyDevInstalled());
enablePythonCheckboxButton.addSelectionListener(new SelectionListener() {
// Project type selection
SelectionListener projectTypeSelectionListener = new SelectionListener() {
@Override
public void widgetSelected(SelectionEvent evt) {
validate();
validate(null);
}
@Override
public void widgetDefaultSelected(SelectionEvent evt) {
validate();
validate(null);
}
};
Group projectTypeGroup = new Group(container, SWT.SHADOW_ETCHED_OUT);
projectTypeGroup.setLayout(new RowLayout(SWT.HORIZONTAL));
projectTypeGroup.setText("Project Type");
pyghidraButton = new Button(projectTypeGroup, SWT.RADIO);
pyghidraButton.setSelection(PyDevUtils.isSupportedPyGhidraPyDevInstalled());
pyghidraButton.setText("PyGhidra");
pyghidraButton.setToolTipText("Enables PyGhidra support using the PyDev " +
"Eclipse plugin. Requires PyDev version " + PyDevUtils.MIN_SUPPORTED_VERSION +
" or later.");
pyghidraButton.addSelectionListener(projectTypeSelectionListener);
jythonButton = new Button(projectTypeGroup, SWT.RADIO);
jythonButton.setSelection(false);
jythonButton.setText("Jython");
jythonButton.setToolTipText("Enables Jython support using the PyDev " +
"Eclipse plugin. Requires PyDev version " + PyDevUtils.MIN_SUPPORTED_VERSION +
" - " + PyDevUtils.MAX_JYTHON_SUPPORTED_VERSION);
jythonButton.addSelectionListener(projectTypeSelectionListener);
noneButton = new Button(projectTypeGroup, SWT.RADIO);
noneButton.setSelection(!PyDevUtils.isSupportedPyGhidraPyDevInstalled());
noneButton.setText("None");
noneButton.setToolTipText("Disables Python support for the project.");
noneButton.addSelectionListener(projectTypeSelectionListener);
Composite interpreterContainer = new Composite(container, SWT.NULL);
interpreterContainer.setLayout(new GridLayout(3, false));
interpreterContainer.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
// PyGhidra interpreter combo box
Label pyGhidraLabel = new Label(interpreterContainer, SWT.NULL);
pyGhidraLabel.setText("PyGhidra interpreter:");
pyghidraCombo = new Combo(interpreterContainer, SWT.DROP_DOWN | SWT.READ_ONLY);
pyghidraCombo.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
pyghidraCombo.setToolTipText("The wizard requires a Python interpreter to be " +
"selected. Click the + button to add or manage Python interpreters.");
File savedPyGhidraInterpreter = populatePyGhidraCombo(null);
pyghidraCombo.addModifyListener(evt -> validate(null));
// PyGhidra interpreter add button
addPyGhidraButton = new Button(interpreterContainer, SWT.BUTTON1);
addPyGhidraButton.setText("+");
addPyGhidraButton.setToolTipText("Adds/manages PyGhidra interpreters.");
addPyGhidraButton.addListener(SWT.Selection, evt -> {
try {
File ghidraDir = ghidraInstallationPage.getGhidraInstallDir();
File pyghidraInterpreter = findPyGhidraInterpreter();
File pypredefDir = new File(ghidraDir, "docs/ghidra_stubs/pypredef");
if (!pypredefDir.isDirectory()) {
pypredefDir = null;
}
if (EclipseMessageUtils.showQuestionDialog("Python Found",
"PyGhidra was previously launched with: \"" + pyghidraInterpreter +
"\". Would you like to use it as your interpreter?")) {
PyDevUtils.addPyGhidraInterpreter("pyghidra_" + ghidraDir.getName(),
pyghidraInterpreter, pypredefDir);
populatePyGhidraCombo(pyghidraInterpreter);
validate(pyghidraInterpreter);
return;
}
}
catch (Exception e) {
// Fall through to show PyDev's Python preference page
}
PreferenceDialog dialog = PreferencesUtil.createPreferenceDialogOn(null,
PyDevUtils.getPythonPreferencePageId(), null, null);
dialog.open();
populatePyGhidraCombo(savedPyGhidraInterpreter);
validate(null);
});
new Label(container, SWT.NONE).setText(""); // filler
new Label(container, SWT.NONE).setText(""); // filler
// Jython interpreter combo box
Label jythonLabel = new Label(container, SWT.NULL);
Label jythonLabel = new Label(interpreterContainer, SWT.NULL);
jythonLabel.setText("Jython interpreter:");
jythonCombo = new Combo(container, SWT.DROP_DOWN | SWT.READ_ONLY);
jythonCombo = new Combo(interpreterContainer, SWT.DROP_DOWN | SWT.READ_ONLY);
jythonCombo.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
jythonCombo.setToolTipText("The wizard requires a Jython interpreter to be " +
"selected. Click the + button to add or manage Jython interpreters.");
populateJythonCombo();
jythonCombo.addModifyListener(evt -> validate());
jythonCombo.addModifyListener(evt -> validate(null));
// Jython interpreter add button
addJythonButton = new Button(container, SWT.BUTTON1);
addJythonButton = new Button(interpreterContainer, SWT.BUTTON1);
addJythonButton.setText("+");
addJythonButton.setToolTipText("Adds/manages Jython interpreters.");
addJythonButton.addListener(SWT.Selection, evt -> {
try {
if (PyDevUtils.getJython27InterpreterNames().isEmpty()) {
if (PyDevUtils.getJythonInterpreterNames().isEmpty()) {
File ghidraDir = ghidraInstallationPage.getGhidraInstallDir();
File jythonFile = findJythonInterpreter(ghidraDir);
File jythonLib = findJythonLibrary(ghidraDir);
@ -111,7 +177,7 @@ public class EnablePythonWizardPage extends WizardPage {
PyDevUtils.addJythonInterpreter("jython_" + ghidraDir.getName(),
jythonFile, jythonLib);
populateJythonCombo();
validate();
validate(null);
return;
}
}
@ -124,80 +190,142 @@ public class EnablePythonWizardPage extends WizardPage {
PyDevUtils.getJythonPreferencePageId(), null, null);
dialog.open();
populateJythonCombo();
validate();
validate(null);
});
validate();
validate(savedPyGhidraInterpreter);
setControl(container);
}
/**
* Checks whether or not Python should be enabled.
*
* @return True if python should be enabled; otherwise, false.
*/
public boolean shouldEnablePython() {
return enablePythonCheckboxButton.getSelection();
@Override
public void setVisible(boolean visible) {
if (visible) {
validate(populatePyGhidraCombo(null));
}
super.setVisible(visible);
}
/**
* Gets the name of the Jython interpreter to use.
*
* @return The name of the Jython interpreter to use. Could be null of Python isn't
* enabled.
* {@return the project Python interpreter to use}
*/
public String getJythonInterpreterName() {
if (enablePythonCheckboxButton.getSelection()) {
return jythonCombo.getText();
public ProjectPythonInterpreter getProjectPythonInterpreter() {
if (pyghidraButton.getSelection()) {
return new ProjectPythonInterpreter(pyghidraCombo.getText(),
ProjectPythonInterpreterType.PYGHIDRA);
}
return null;
if (jythonButton.getSelection()) {
return new ProjectPythonInterpreter(jythonCombo.getText(),
ProjectPythonInterpreterType.JYTHON);
}
return new ProjectPythonInterpreter(null, ProjectPythonInterpreterType.NONE);
}
/**
* Validates the fields on the page and updates the page's status.
* Should be called every time a field on the page changes.
*
* @param pyghidraInterpreter The Python interpreter used to launch PyGhidra (could be null if
* unknown).
*/
private void validate() {
private void validate(File pyghidraInterpreter) {
String message = null;
boolean pyDevInstalled = PyDevUtils.isSupportedPyDevInstalled();
boolean pyDevEnabled = enablePythonCheckboxButton.getSelection();
boolean comboEnabled = pyDevInstalled && pyDevEnabled;
boolean pyghidraSupported = PyDevUtils.isSupportedPyGhidraPyDevInstalled();
boolean jythonSupported = PyDevUtils.isSupportedJythonPyDevInstalled();
if (pyDevEnabled) {
if (!pyDevInstalled) {
if (pyghidraButton.getSelection()) {
if (!pyghidraSupported) {
message = "PyDev version " + PyDevUtils.MIN_SUPPORTED_VERSION +
" - " + PyDevUtils.MAX_SUPPORTED_VERSION + " is not installed.";
" or later is not installed.";
}
else {
try {
List<String> interpreters = PyDevUtils.getJython27InterpreterNames();
if (interpreters.isEmpty()) {
message = "No Jython interpreters found. Click the + button to add one.";
if (pyghidraInterpreter == null) {
pyghidraInterpreter = findPyGhidraInterpreter();
}
if (pyghidraInterpreter == null) {
message =
"Please first launch PyGhidra to associate the Ghidra installation with a supported version of Python.";
pyghidraSupported = false;
}
else {
List<String> interpreters =
PyDevUtils.getPyGhidraInterpreterNames(pyghidraInterpreter);
if (interpreters.isEmpty()) {
message ="No PyGhidra interpreters found. Click the + button or set project type to \"None\".";
}
}
}
catch (OperationNotSupportedException e) {
message = "PyDev version is not supported.";
comboEnabled = false;
message = "PyDev version is not supported for Jython.";
pyghidraSupported = false;
}
catch (Exception e) {
message =
"Failed to lookup Python interpreter associated with the Ghidra installation.";
pyghidraSupported = false;
}
}
}
else if (jythonButton.getSelection()) {
if (!jythonSupported) {
message = "PyDev version " + PyDevUtils.MIN_SUPPORTED_VERSION +
" - " + PyDevUtils.MAX_JYTHON_SUPPORTED_VERSION + " is not installed.";
}
else {
try {
List<String> interpreters = PyDevUtils.getJythonInterpreterNames();
if (interpreters.isEmpty()) {
message =
"No Jython interpreters found. Click the + button or set project type to \"None\".";
}
}
catch (OperationNotSupportedException e) {
message = "PyDev version is not supported for Jython.";
jythonSupported = false;
}
}
}
jythonCombo.setEnabled(comboEnabled);
addJythonButton.setEnabled(comboEnabled);
pyghidraCombo.setEnabled(pyghidraButton.getSelection() && pyghidraSupported);
addPyGhidraButton.setEnabled(pyghidraButton.getSelection() && pyghidraSupported);
jythonCombo.setEnabled(jythonButton.getSelection() && jythonSupported);
addJythonButton.setEnabled(jythonButton.getSelection() && jythonSupported);
setErrorMessage(message);
setPageComplete(message == null);
}
/**
* Populates the Jython combo box with discovered Jython names.
* Populates the PyGhidra combo box with discovered PyGhidra interpreter names.
*
* @param pyghidraInterpreter The Python interpreter used to launch PyGhidra (could be null if
* unknown).
* @return The Python interpreter used to launch PyGhidra (could be null if unknown).
*/
private File populatePyGhidraCombo(File pyghidraInterpreter) {
pyghidraCombo.removeAll();
try {
if (pyghidraInterpreter == null) {
pyghidraInterpreter = findPyGhidraInterpreter();
}
PyDevUtils.getPyGhidraInterpreterNames(pyghidraInterpreter).forEach(pyghidraCombo::add);
}
catch (Exception e) {
// Nothing to do. Combo should and will be empty.
}
if (pyghidraCombo.getItemCount() > 0) {
pyghidraCombo.select(0);
}
return pyghidraInterpreter;
}
/**
* Populates the Jython combo box with discovered Jython interpreter names.
*/
private void populateJythonCombo() {
jythonCombo.removeAll();
try {
for (String jythonName : PyDevUtils.getJython27InterpreterNames()) {
jythonCombo.add(jythonName);
}
PyDevUtils.getJythonInterpreterNames().forEach(jythonCombo::add);
}
catch (OperationNotSupportedException e) {
// Nothing to do. Combo should and will be empty.
@ -207,6 +335,32 @@ public class EnablePythonWizardPage extends WizardPage {
}
}
/**
* Find's the Python interpreter file that was used to launch PyGhidra.
*
* @return The Python interpreter file that was used to launch PyGhidra, or null if one could
* not be found.
* @throws Exception if there was a problem finding the Python interpreter.
*/
private File findPyGhidraInterpreter() throws Exception {
File ghidraDir = ghidraInstallationPage.getGhidraInstallDir();
AppConfig appConfig = new AppConfig(ghidraDir);
List<String> cmd = appConfig.getSavedPythonCommand();
if (cmd == null) {
return null;
}
cmd.add("-c");
cmd.add("import sys; print(sys.executable)");
Process p = new ProcessBuilder(cmd).redirectErrorStream(true).start();
BufferedReader reader =
new BufferedReader(new InputStreamReader(p.getInputStream()));
String pythonExecutable = reader.readLine();
if (p.waitFor() == 0) {
return new File(pythonExecutable);
}
return null;
}
/**
* Find's a Jython interpreter file in the given Ghidra installation directory.
*