diff --git a/Ghidra/Features/Base/certification.manifest b/Ghidra/Features/Base/certification.manifest index 16ac75be9d..a9fd2ec2ca 100644 --- a/Ghidra/Features/Base/certification.manifest +++ b/Ghidra/Features/Base/certification.manifest @@ -898,6 +898,7 @@ src/main/resources/images/pencil16.png||GHIDRA||||END| src/main/resources/images/pin.png||GHIDRA||||END| src/main/resources/images/play_again.png||GHIDRA||||END| src/main/resources/images/preferences-system.png||Tango Icons - Public Domain|||tango|END| +src/main/resources/images/python.png||GHIDRA||||END| src/main/resources/images/question_zero.png||GHIDRA||||END| src/main/resources/images/red-cross.png||GHIDRA||||END| src/main/resources/images/redQuestionMark.png||GHIDRA||||END| diff --git a/Ghidra/Features/Base/src/main/resources/images/python.png b/Ghidra/Features/Base/src/main/resources/images/python.png new file mode 100644 index 0000000000..d89d2e7d1c Binary files /dev/null and b/Ghidra/Features/Base/src/main/resources/images/python.png differ diff --git a/Ghidra/Features/Jython/src/main/resources/images/python.png b/Ghidra/Features/Jython/src/main/resources/images/python.png deleted file mode 100644 index 193c227431..0000000000 Binary files a/Ghidra/Features/Jython/src/main/resources/images/python.png and /dev/null differ diff --git a/Ghidra/Features/PyGhidra/src/main/py/src/pyghidra/launcher.py b/Ghidra/Features/PyGhidra/src/main/py/src/pyghidra/launcher.py index dcb80189df..e16c5ca4a7 100644 --- a/Ghidra/Features/PyGhidra/src/main/py/src/pyghidra/launcher.py +++ b/Ghidra/Features/PyGhidra/src/main/py/src/pyghidra/launcher.py @@ -160,11 +160,14 @@ class PyGhidraLauncher: install_dir = install_dir or os.getenv("GHIDRA_INSTALL_DIR") self._install_dir = self._validate_install_dir(install_dir) + java_home_override = os.getenv("JAVA_HOME_OVERRIDE") + if java_home_override: + self._java_home = java_home_override + # check if we are in the ghidra source tree support = Path(install_dir) / "support" if not support.exists(): self._dev_mode = True - self._java_home = os.getenv("JAVA_HOME_OVERRIDE") self._plugins: List[Tuple[Path, ExtensionDetails]] = [] self.verbose = verbose @@ -469,7 +472,7 @@ class PyGhidraLauncher: self._pre_launch_init() self._launch() except Exception as e: - self._report_fatal_error("An error occured launching Ghidra", str(e), e) + self._report_fatal_error("An error occurred launching Ghidra", str(e), e) def get_install_path(self, plugin_name: str) -> Path: """ diff --git a/GhidraBuild/EclipsePlugins/GhidraDev/GhidraDevFeature/feature.xml b/GhidraBuild/EclipsePlugins/GhidraDev/GhidraDevFeature/feature.xml index f4a0c661f6..d77986fab7 100644 --- a/GhidraBuild/EclipsePlugins/GhidraDev/GhidraDevFeature/feature.xml +++ b/GhidraBuild/EclipsePlugins/GhidraDev/GhidraDevFeature/feature.xml @@ -2,7 +2,7 @@ diff --git a/GhidraBuild/EclipsePlugins/GhidraDev/GhidraDevPlugin/.launch/GhidraDev.launch b/GhidraBuild/EclipsePlugins/GhidraDev/GhidraDevPlugin/.launch/GhidraDev.launch index b78e35f9df..655bd9c454 100644 --- a/GhidraBuild/EclipsePlugins/GhidraDev/GhidraDevPlugin/.launch/GhidraDev.launch +++ b/GhidraBuild/EclipsePlugins/GhidraDev/GhidraDevPlugin/.launch/GhidraDev.launch @@ -167,7 +167,9 @@ - + + + @@ -176,7 +178,6 @@ - @@ -202,6 +203,8 @@ + + @@ -296,7 +299,7 @@ - + @@ -308,10 +311,11 @@ + + - @@ -414,14 +418,19 @@ - - - - - - - - + + + + + + + + + + + + + @@ -430,7 +439,7 @@ - + diff --git a/GhidraBuild/EclipsePlugins/GhidraDev/GhidraDevPlugin/META-INF/MANIFEST.MF b/GhidraBuild/EclipsePlugins/GhidraDev/GhidraDevPlugin/META-INF/MANIFEST.MF index e6c54306be..54947f8f3c 100644 --- a/GhidraBuild/EclipsePlugins/GhidraDev/GhidraDevPlugin/META-INF/MANIFEST.MF +++ b/GhidraBuild/EclipsePlugins/GhidraDev/GhidraDevPlugin/META-INF/MANIFEST.MF @@ -3,7 +3,7 @@ Manifest-Version: 1.0 Bundle-ManifestVersion: 2 Bundle-Name: GhidraDev Bundle-SymbolicName: ghidra.ghidradev;singleton:=true -Bundle-Version: 4.0.1.qualifier +Bundle-Version: 5.0.0.qualifier Bundle-Activator: ghidradev.Activator Require-Bundle: org.eclipse.ant.core;bundle-version="3.7.200", org.eclipse.buildship.core;bundle-version="3.1.8", @@ -21,10 +21,11 @@ Require-Bundle: org.eclipse.ant.core;bundle-version="3.7.200", org.eclipse.ltk.core.refactoring;bundle-version="3.14.200", org.eclipse.ui;bundle-version="3.205.0", org.eclipse.ui.ide;bundle-version="3.22.0", - com.python.pydev.debug;bundle-version="6.3.1";resolution:=optional, - org.python.pydev;bundle-version="[6.3.1,10.0.0)";resolution:=optional, - org.python.pydev.core;bundle-version="[6.3.1,10.0.0)";resolution:=optional, - org.python.pydev.ast;bundle-version="[6.3.1,10.0.0)";resolution:=optional, + com.python.pydev.debug;bundle-version="9.3.0";resolution:=optional, + org.python.pydev;bundle-version="9.3.0";resolution:=optional, + org.python.pydev.core;bundle-version="9.3.0";resolution:=optional, + org.python.pydev.ast;bundle-version="9.3.0";resolution:=optional, + org.python.pydev.debug;bundle-version="9.3.0";resolution:=optional, org.eclipse.cdt.core;bundle-version="5.9.1";resolution:=optional, org.eclipse.cdt.ui;bundle-version="5.9.0";resolution:=optional Bundle-RequiredExecutionEnvironment: JavaSE-21 diff --git a/GhidraBuild/EclipsePlugins/GhidraDev/GhidraDevPlugin/README.md b/GhidraBuild/EclipsePlugins/GhidraDev/GhidraDevPlugin/README.md index cb16d73996..4fa5b8a0d4 100644 --- a/GhidraBuild/EclipsePlugins/GhidraDev/GhidraDevPlugin/README.md +++ b/GhidraBuild/EclipsePlugins/GhidraDev/GhidraDevPlugin/README.md @@ -1,7 +1,7 @@ # GhidraDev Eclipse Plugin GhidraDev provides support for developing and debugging Ghidra scripts and modules in Eclipse. -The information provided in this document is effective as of GhidraDev 4.0.0 and is subject to +The information provided in this document is effective as of GhidraDev 5.0.0 and is subject to change with future releases. ## Table of Contents @@ -31,6 +31,9 @@ change with future releases. 12. [Building](#building) ## Change History +__5.0.0:__ +* Added support for PyGhidra. + __4.0.1:__ * New Ghidra module projects now contain a default `README.md` file. * Fixed a bug that prevented an imported module source project from being discovered by Ghidra when @@ -132,7 +135,7 @@ __1.0.1:__ * Ghidra 11.2 or later ## Optional Requirements -* PyDev 6.3.1 - 9.3.0 ([more info](#pydev-support)) +* PyDev 9.3.0 or later ([more info](#pydev-support)) * Gradle - required version(s) specified by linked Ghidra release ([more info](#export-ghidra-module-extension)) @@ -268,7 +271,10 @@ Ghidra from Eclipse independent of a project is not supported. ## PyDev Support GhidraDev is able to integrate with PyDev to conveniently configure Python support into Ghidra -script and module projects. +script and module projects. GhidraDev supports both Jython and PyGhidra Python implementations. + +__NOTE:__ PyDev discontinued Jython 2 support in version 10.0.0. If you want to use GhidraDev with +Jython, you must use __PyDev 9.3.0__. The latest vesions of PyDev support PyGhidra. ### Installing PyDev From Eclipse: @@ -293,13 +299,19 @@ GhidraDev can add Python support to a Ghidra project when: * Creating a new Ghidra script project * Linking a Ghidra installation to an existing Java project -In order for GhidraDev to add in Python support, PyDev must have a Jython interpreter configured. -GhidraDev will present a list of detected Jython interpreters that it found in PyDev's preferences. -If no Jython interpreters were found, one can be added from GhidraDev by clicking the `+` icon. -When the `+` icon is clicked, GhidraDev will attempt to find the Jython interpreter bundled with the -selected Ghidra installation and automatically configure PyDev to use it. If for some reason -GhidraDev was unable to find a Jython interpreter in the Ghidra installation, one will have to be -added manually in the PyDev preferences. +In order for GhidraDev to add in Python support, PyDev must have a PyGhidra or Jython interpreter +configured. GhidraDev will present a list of detected PyGhidra/Jython interpreters that it found in +PyDev's preferences. If no interpreters were found, one can be added from GhidraDev by clicking +the `+` icons. + +When the Jython `+` icon is clicked, GhidraDev will attempt to find the Jython interpreter bundled +with the selected Ghidra installation and automatically configure PyDev to use it. If for some +reason GhidraDev was unable to find a Jython interpreter in the Ghidra installation, one will have +to be added manually in the PyDev preferences. + +When the PyGhidra `+` icon is clicked, GhidraDev will attempt to find the PyGhidra interpreter +that was last used to launch PyGhidra. If it cannot find it, you will have to launch PyGhidra +and try again. ## Upgrading GhidraDev is upgraded differently depending on how it was installed. If GhidraDev was @@ -347,9 +359,6 @@ installation directory. to your Ghidra module project, which automatically happens when the project is created. Simply [relink](#link-ghidra) your Ghidra installation to the project, and your project will pick up any newly discovered Ghidra extensions. -* __Why doesn't GhidraDev support PyDev 10.0 or later?__ - * PyDev dropped support for Python 2 in their 10.0 release. Ghidra currently does not support - Python 3. ## Additional Resources For more information on the GhidraDev plugin and developing for Ghidra in an Eclipse environment, @@ -358,14 +367,14 @@ at `/docs/GhidraClass/Intermediate/Scripting.html`. ## Building GhidraDev is currently built from Eclipse and distributed with Ghidra manually. Ideally we will use -Gradle one day, but we aren't there yet. We do rely on `gradle prepDev` to generate the Eclipse -project and build GhidraDev's dependencies though. +Gradle one day, but we aren't there yet. We do rely on Gradle to generate the Eclipse project and +build GhidraDev's dependencies though. __NOTE:__ Only "Eclipse for RCP and RAP Developers" has the ability to do the below instructions. The following instructions assume that you are using this version of Eclipse. #### Importing GhidraDev Eclipse projects (they are deactivated by default): -1. Run `gradle eclipse -PeclipsePDE` +1. Run `gradle prepGhidraDev eclipse -PeclipsePDE` 2. From Eclipse, `File -> Import -> General -> Existing Projects into Workspace` 3. From the ghidra repo, import `Eclipse GhidraDevFeature` and `Eclipse GhidraDevPlugin` diff --git a/GhidraBuild/EclipsePlugins/GhidraDev/GhidraDevPlugin/build.gradle b/GhidraBuild/EclipsePlugins/GhidraDev/GhidraDevPlugin/build.gradle index e04ec6715b..9b4e22453e 100644 --- a/GhidraBuild/EclipsePlugins/GhidraDev/GhidraDevPlugin/build.gradle +++ b/GhidraBuild/EclipsePlugins/GhidraDev/GhidraDevPlugin/build.gradle @@ -81,8 +81,8 @@ task pyDevUnpack(type:Copy) { !pyDevDestDir.exists() } - File depsFile = file("${DEPS_DIR}/GhidraDev/PyDev 6.3.1.zip") - File binRepoFile = file("${BIN_REPO}/GhidraBuild/EclipsePlugins/GhidraDev/buildDependencies/PyDev 6.3.1.zip") + File depsFile = file("${DEPS_DIR}/GhidraDev/PyDev 9.3.0.zip") + File binRepoFile = file("${BIN_REPO}/GhidraBuild/EclipsePlugins/GhidraDev/buildDependencies/PyDev 9.3.0.zip") // First check if the file is in the dependencies repo. If not, check in the bin repo. def pyDevZipTree = depsFile.exists() ? zipTree(depsFile) : zipTree(binRepoFile) @@ -115,6 +115,13 @@ task cdtUnpack(type:Copy) { destinationDir cdtDestDir } +task prepGhidraDev { + dependsOn("utilityJar") + dependsOn("launchSupportJar") + dependsOn("pyDevUnpack") + dependsOn("cdtUnpack") +} + // We do not currently build GhidraDev plugin at Ghidra build time so we must // copy the prebuilt zip file from the BIN_REPO rootProject.assembleDistribution { @@ -129,9 +136,3 @@ rootProject.assembleMarkdownToHtml { into "Extensions/Eclipse/GhidraDev/" } } - -// PrepDev dependencies -rootProject.prepDev.dependsOn utilityJar -rootProject.prepDev.dependsOn launchSupportJar -rootProject.prepDev.dependsOn pyDevUnpack -rootProject.prepDev.dependsOn cdtUnpack diff --git a/GhidraBuild/EclipsePlugins/GhidraDev/GhidraDevPlugin/icons/python.png b/GhidraBuild/EclipsePlugins/GhidraDev/GhidraDevPlugin/icons/python.png new file mode 100644 index 0000000000..d89d2e7d1c Binary files /dev/null and b/GhidraBuild/EclipsePlugins/GhidraDev/GhidraDevPlugin/icons/python.png differ diff --git a/GhidraBuild/EclipsePlugins/GhidraDev/GhidraDevPlugin/plugin.xml b/GhidraBuild/EclipsePlugins/GhidraDev/GhidraDevPlugin/plugin.xml index d377ab0ca7..834baa1ee3 100644 --- a/GhidraBuild/EclipsePlugins/GhidraDev/GhidraDevPlugin/plugin.xml +++ b/GhidraBuild/EclipsePlugins/GhidraDev/GhidraDevPlugin/plugin.xml @@ -378,6 +378,11 @@ id="GhidraHeadlessLaunchConfigurationType" name="Ghidra Headless"> + + @@ -391,6 +396,11 @@ icon="icons/GhidraIcon16_bw.png" id="GhidraHeadlessLaunchConfigurationTypeImage"> + + @@ -408,6 +418,13 @@ name="Ghidra Headless" type="GhidraHeadlessLaunchConfigurationType"> + + @@ -423,6 +440,12 @@ id="GhidraHeadlessLaunchConfigurationTabGroup" type="GhidraHeadlessLaunchConfigurationType"> + + @@ -474,6 +497,30 @@ + + + + + + + + + + + + + + @@ -505,6 +552,13 @@ properties="isGhidraModuleProject" type="java.lang.Object"> + + diff --git a/GhidraBuild/EclipsePlugins/GhidraDev/GhidraDevPlugin/src/main/java/ghidradev/ghidraprojectcreator/launchers/AbstractPyGhidraLaunchShortcut.java b/GhidraBuild/EclipsePlugins/GhidraDev/GhidraDevPlugin/src/main/java/ghidradev/ghidraprojectcreator/launchers/AbstractPyGhidraLaunchShortcut.java new file mode 100644 index 0000000000..c24cb497c5 --- /dev/null +++ b/GhidraBuild/EclipsePlugins/GhidraDev/GhidraDevPlugin/src/main/java/ghidradev/ghidraprojectcreator/launchers/AbstractPyGhidraLaunchShortcut.java @@ -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". + *

+ * 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()); + } + } +} diff --git a/GhidraBuild/EclipsePlugins/GhidraDev/GhidraDevPlugin/src/main/java/ghidradev/ghidraprojectcreator/launchers/GhidraLaunchDelegate.java b/GhidraBuild/EclipsePlugins/GhidraDev/GhidraDevPlugin/src/main/java/ghidradev/ghidraprojectcreator/launchers/GhidraLaunchDelegate.java index 12beee4473..80d8d5c0b7 100644 --- a/GhidraBuild/EclipsePlugins/GhidraDev/GhidraDevPlugin/src/main/java/ghidradev/ghidraprojectcreator/launchers/GhidraLaunchDelegate.java +++ b/GhidraBuild/EclipsePlugins/GhidraDev/GhidraDevPlugin/src/main/java/ghidradev/ghidraprojectcreator/launchers/GhidraLaunchDelegate.java @@ -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(); } diff --git a/GhidraBuild/EclipsePlugins/GhidraDev/GhidraDevPlugin/src/main/java/ghidradev/ghidraprojectcreator/launchers/PyGhidraGuiLaunchShortcut.java b/GhidraBuild/EclipsePlugins/GhidraDev/GhidraDevPlugin/src/main/java/ghidradev/ghidraprojectcreator/launchers/PyGhidraGuiLaunchShortcut.java new file mode 100644 index 0000000000..ea081e950e --- /dev/null +++ b/GhidraBuild/EclipsePlugins/GhidraDev/GhidraDevPlugin/src/main/java/ghidradev/ghidraprojectcreator/launchers/PyGhidraGuiLaunchShortcut.java @@ -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"); + } +} diff --git a/GhidraBuild/EclipsePlugins/GhidraDev/GhidraDevPlugin/src/main/java/ghidradev/ghidraprojectcreator/launchers/PyGhidraLaunchDelegate.java b/GhidraBuild/EclipsePlugins/GhidraDev/GhidraDevPlugin/src/main/java/ghidradev/ghidraprojectcreator/launchers/PyGhidraLaunchDelegate.java new file mode 100644 index 0000000000..0d790422cd --- /dev/null +++ b/GhidraBuild/EclipsePlugins/GhidraDev/GhidraDevPlugin/src/main/java/ghidradev/ghidraprojectcreator/launchers/PyGhidraLaunchDelegate.java @@ -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 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."); + } + }); + } +} diff --git a/GhidraBuild/EclipsePlugins/GhidraDev/GhidraDevPlugin/src/main/java/ghidradev/ghidraprojectcreator/testers/PyGhidraProjectPropertyTester.java b/GhidraBuild/EclipsePlugins/GhidraDev/GhidraDevPlugin/src/main/java/ghidradev/ghidraprojectcreator/testers/PyGhidraProjectPropertyTester.java new file mode 100644 index 0000000000..55c18da027 --- /dev/null +++ b/GhidraBuild/EclipsePlugins/GhidraDev/GhidraDevPlugin/src/main/java/ghidradev/ghidraprojectcreator/testers/PyGhidraProjectPropertyTester.java @@ -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; + } + } +} diff --git a/GhidraBuild/EclipsePlugins/GhidraDev/GhidraDevPlugin/src/main/java/ghidradev/ghidraprojectcreator/utils/GhidraLaunchUtils.java b/GhidraBuild/EclipsePlugins/GhidraDev/GhidraDevPlugin/src/main/java/ghidradev/ghidraprojectcreator/utils/GhidraLaunchUtils.java index 73791e259e..bd88074cd0 100644 --- a/GhidraBuild/EclipsePlugins/GhidraDev/GhidraDevPlugin/src/main/java/ghidradev/ghidraprojectcreator/utils/GhidraLaunchUtils.java +++ b/GhidraBuild/EclipsePlugins/GhidraDev/GhidraDevPlugin/src/main/java/ghidradev/ghidraprojectcreator/utils/GhidraLaunchUtils.java @@ -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 diff --git a/GhidraBuild/EclipsePlugins/GhidraDev/GhidraDevPlugin/src/main/java/ghidradev/ghidraprojectcreator/utils/GhidraModuleUtils.java b/GhidraBuild/EclipsePlugins/GhidraDev/GhidraDevPlugin/src/main/java/ghidradev/ghidraprojectcreator/utils/GhidraModuleUtils.java index 97993967d8..8394bb62ac 100644 --- a/GhidraBuild/EclipsePlugins/GhidraDev/GhidraDevPlugin/src/main/java/ghidradev/ghidraprojectcreator/utils/GhidraModuleUtils.java +++ b/GhidraBuild/EclipsePlugins/GhidraDev/GhidraDevPlugin/src/main/java/ghidradev/ghidraprojectcreator/utils/GhidraModuleUtils.java @@ -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 diff --git a/GhidraBuild/EclipsePlugins/GhidraDev/GhidraDevPlugin/src/main/java/ghidradev/ghidraprojectcreator/utils/GhidraProjectUtils.java b/GhidraBuild/EclipsePlugins/GhidraDev/GhidraDevPlugin/src/main/java/ghidradev/ghidraprojectcreator/utils/GhidraProjectUtils.java index 059140616f..8899e3f6a7 100644 --- a/GhidraBuild/EclipsePlugins/GhidraDev/GhidraDevPlugin/src/main/java/ghidradev/ghidraprojectcreator/utils/GhidraProjectUtils.java +++ b/GhidraBuild/EclipsePlugins/GhidraDev/GhidraDevPlugin/src/main/java/ghidradev/ghidraprojectcreator/utils/GhidraProjectUtils.java @@ -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); diff --git a/GhidraBuild/EclipsePlugins/GhidraDev/GhidraDevPlugin/src/main/java/ghidradev/ghidraprojectcreator/utils/GhidraScriptUtils.java b/GhidraBuild/EclipsePlugins/GhidraDev/GhidraDevPlugin/src/main/java/ghidradev/ghidraprojectcreator/utils/GhidraScriptUtils.java index ed083d01d2..6189d24c54 100644 --- a/GhidraBuild/EclipsePlugins/GhidraDev/GhidraDevPlugin/src/main/java/ghidradev/ghidraprojectcreator/utils/GhidraScriptUtils.java +++ b/GhidraBuild/EclipsePlugins/GhidraDev/GhidraDevPlugin/src/main/java/ghidradev/ghidraprojectcreator/utils/GhidraScriptUtils.java @@ -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 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) { diff --git a/GhidraBuild/EclipsePlugins/GhidraDev/GhidraDevPlugin/src/main/java/ghidradev/ghidraprojectcreator/utils/PyDevUtils.java b/GhidraBuild/EclipsePlugins/GhidraDev/GhidraDevPlugin/src/main/java/ghidradev/ghidraprojectcreator/utils/PyDevUtils.java index b452f5311a..59d4e81209 100644 --- a/GhidraBuild/EclipsePlugins/GhidraDev/GhidraDevPlugin/src/main/java/ghidradev/ghidraprojectcreator/utils/PyDevUtils.java +++ b/GhidraBuild/EclipsePlugins/GhidraDev/GhidraDevPlugin/src/main/java/ghidradev/ghidraprojectcreator/utils/PyDevUtils.java @@ -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 getJython27InterpreterNames() throws OperationNotSupportedException { + public static List 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 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 classpathEntries, String jythonInterpreterName, + List 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()); + } + } } diff --git a/GhidraBuild/EclipsePlugins/GhidraDev/GhidraDevPlugin/src/main/java/ghidradev/ghidraprojectcreator/utils/PyDevUtilsInternal.java b/GhidraBuild/EclipsePlugins/GhidraDev/GhidraDevPlugin/src/main/java/ghidradev/ghidraprojectcreator/utils/PyDevUtilsInternal.java index 17a9f6f375..2c061f878e 100644 --- a/GhidraBuild/EclipsePlugins/GhidraDev/GhidraDevPlugin/src/main/java/ghidradev/ghidraprojectcreator/utils/PyDevUtilsInternal.java +++ b/GhidraBuild/EclipsePlugins/GhidraDev/GhidraDevPlugin/src/main/java/ghidradev/ghidraprojectcreator/utils/PyDevUtilsInternal.java @@ -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 getJython27InterpreterNames() + public static List getPyGhidraInterpreterNames(File requiredFileMatch) + throws NoClassDefFoundError, NoSuchMethodError { + + List 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 getJythonInterpreterNames() throws NoClassDefFoundError, NoSuchMethodError { List 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 classpathEntries, String jythonInterpreterName, + List 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 } diff --git a/GhidraBuild/EclipsePlugins/GhidraDev/GhidraDevPlugin/src/main/java/ghidradev/ghidraprojectcreator/wizards/CreateGhidraModuleProjectWizard.java b/GhidraBuild/EclipsePlugins/GhidraDev/GhidraDevPlugin/src/main/java/ghidradev/ghidraprojectcreator/wizards/CreateGhidraModuleProjectWizard.java index d1df4dde44..0fe96922f9 100644 --- a/GhidraBuild/EclipsePlugins/GhidraDev/GhidraDevPlugin/src/main/java/ghidradev/ghidraprojectcreator/wizards/CreateGhidraModuleProjectWizard.java +++ b/GhidraBuild/EclipsePlugins/GhidraDev/GhidraDevPlugin/src/main/java/ghidradev/ghidraprojectcreator/wizards/CreateGhidraModuleProjectWizard.java @@ -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 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 moduleTemplateTypes, String jythonInterpreterName, + Set 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, diff --git a/GhidraBuild/EclipsePlugins/GhidraDev/GhidraDevPlugin/src/main/java/ghidradev/ghidraprojectcreator/wizards/CreateGhidraScriptProjectWizard.java b/GhidraBuild/EclipsePlugins/GhidraDev/GhidraDevPlugin/src/main/java/ghidradev/ghidraprojectcreator/wizards/CreateGhidraScriptProjectWizard.java index b78887d1b3..0b28ffa1d5 100644 --- a/GhidraBuild/EclipsePlugins/GhidraDev/GhidraDevPlugin/src/main/java/ghidradev/ghidraprojectcreator/wizards/CreateGhidraScriptProjectWizard.java +++ b/GhidraBuild/EclipsePlugins/GhidraDev/GhidraDevPlugin/src/main/java/ghidradev/ghidraprojectcreator/wizards/CreateGhidraScriptProjectWizard.java @@ -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); diff --git a/GhidraBuild/EclipsePlugins/GhidraDev/GhidraDevPlugin/src/main/java/ghidradev/ghidraprojectcreator/wizards/ExportGhidraModuleWizard.java b/GhidraBuild/EclipsePlugins/GhidraDev/GhidraDevPlugin/src/main/java/ghidradev/ghidraprojectcreator/wizards/ExportGhidraModuleWizard.java index cda82df764..1a54032ea0 100644 --- a/GhidraBuild/EclipsePlugins/GhidraDev/GhidraDevPlugin/src/main/java/ghidradev/ghidraprojectcreator/wizards/ExportGhidraModuleWizard.java +++ b/GhidraBuild/EclipsePlugins/GhidraDev/GhidraDevPlugin/src/main/java/ghidradev/ghidraprojectcreator/wizards/ExportGhidraModuleWizard.java @@ -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. " + diff --git a/GhidraBuild/EclipsePlugins/GhidraDev/GhidraDevPlugin/src/main/java/ghidradev/ghidraprojectcreator/wizards/ImportGhidraModuleSourceWizard.java b/GhidraBuild/EclipsePlugins/GhidraDev/GhidraDevPlugin/src/main/java/ghidradev/ghidraprojectcreator/wizards/ImportGhidraModuleSourceWizard.java index 1655e08047..6548fb9861 100644 --- a/GhidraBuild/EclipsePlugins/GhidraDev/GhidraDevPlugin/src/main/java/ghidradev/ghidraprojectcreator/wizards/ImportGhidraModuleSourceWizard.java +++ b/GhidraBuild/EclipsePlugins/GhidraDev/GhidraDevPlugin/src/main/java/ghidradev/ghidraprojectcreator/wizards/ImportGhidraModuleSourceWizard.java @@ -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); diff --git a/GhidraBuild/EclipsePlugins/GhidraDev/GhidraDevPlugin/src/main/java/ghidradev/ghidraprojectcreator/wizards/LinkGhidraWizard.java b/GhidraBuild/EclipsePlugins/GhidraDev/GhidraDevPlugin/src/main/java/ghidradev/ghidraprojectcreator/wizards/LinkGhidraWizard.java index c20ff5df65..06353fb030 100644 --- a/GhidraBuild/EclipsePlugins/GhidraDev/GhidraDevPlugin/src/main/java/ghidradev/ghidraprojectcreator/wizards/LinkGhidraWizard.java +++ b/GhidraBuild/EclipsePlugins/GhidraDev/GhidraDevPlugin/src/main/java/ghidradev/ghidraprojectcreator/wizards/LinkGhidraWizard.java @@ -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); diff --git a/GhidraBuild/EclipsePlugins/GhidraDev/GhidraDevPlugin/src/main/java/ghidradev/ghidraprojectcreator/wizards/pages/ChooseGhidraInstallationWizardPage.java b/GhidraBuild/EclipsePlugins/GhidraDev/GhidraDevPlugin/src/main/java/ghidradev/ghidraprojectcreator/wizards/pages/ChooseGhidraInstallationWizardPage.java index 0b9c86d0ab..faaa5d31df 100644 --- a/GhidraBuild/EclipsePlugins/GhidraDev/GhidraDevPlugin/src/main/java/ghidradev/ghidraprojectcreator/wizards/pages/ChooseGhidraInstallationWizardPage.java +++ b/GhidraBuild/EclipsePlugins/GhidraDev/GhidraDevPlugin/src/main/java/ghidradev/ghidraprojectcreator/wizards/pages/ChooseGhidraInstallationWizardPage.java @@ -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."; diff --git a/GhidraBuild/EclipsePlugins/GhidraDev/GhidraDevPlugin/src/main/java/ghidradev/ghidraprojectcreator/wizards/pages/EnablePythonWizardPage.java b/GhidraBuild/EclipsePlugins/GhidraDev/GhidraDevPlugin/src/main/java/ghidradev/ghidraprojectcreator/wizards/pages/EnablePythonWizardPage.java index 1dcf6b71f8..0fbd682131 100644 --- a/GhidraBuild/EclipsePlugins/GhidraDev/GhidraDevPlugin/src/main/java/ghidradev/ghidraprojectcreator/wizards/pages/EnablePythonWizardPage.java +++ b/GhidraBuild/EclipsePlugins/GhidraDev/GhidraDevPlugin/src/main/java/ghidradev/ghidraprojectcreator/wizards/pages/EnablePythonWizardPage.java @@ -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 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 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 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 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. * diff --git a/GhidraBuild/EclipsePlugins/GhidraDev/certification.manifest b/GhidraBuild/EclipsePlugins/GhidraDev/certification.manifest index 7ef12ea347..d7d8abed85 100644 --- a/GhidraBuild/EclipsePlugins/GhidraDev/certification.manifest +++ b/GhidraBuild/EclipsePlugins/GhidraDev/certification.manifest @@ -13,6 +13,7 @@ GhidraDevPlugin/icons/GhidraIcon16_bw.png||GHIDRA||||END| GhidraDevPlugin/icons/brick_add.png||FAMFAMFAM Icons - CC 2.5|||famfamfam silk icon set|END| GhidraDevPlugin/icons/brick_go.png||FAMFAMFAM Icons - CC 2.5|||famfamfam silk icon set|END| GhidraDevPlugin/icons/folder_link.png||FAMFAMFAM Icons - CC 2.5|||famfamfam silk icon set|END| +GhidraDevPlugin/icons/python.png||GHIDRA||||END| GhidraDevPlugin/icons/script_add.png||FAMFAMFAM Icons - CC 2.5|||famfamfam silk icon set|END| GhidraDevPlugin/icons/script_code_red.png||FAMFAMFAM Icons - CC 2.5|||famfamfam silk icon set|END| GhidraDevPlugin/plugin.xml||GHIDRA||||END| diff --git a/GhidraBuild/LaunchSupport/src/main/java/LaunchSupport.java b/GhidraBuild/LaunchSupport/src/main/java/LaunchSupport.java index a3df9fff0a..342daa08e7 100644 --- a/GhidraBuild/LaunchSupport/src/main/java/LaunchSupport.java +++ b/GhidraBuild/LaunchSupport/src/main/java/LaunchSupport.java @@ -78,20 +78,20 @@ public class LaunchSupport { try { File installDir = new File(installDirPath).getCanonicalFile(); // change relative path to absolute - JavaConfig javaConfig = new JavaConfig(installDir); + AppConfig appConfig = new AppConfig(installDir); JavaFinder javaFinder = JavaFinder.create(); // Pass control to a mode-specific handler switch (mode.toLowerCase()) { case "-java_home": - exitCode = handleJavaHome(javaConfig, javaFinder, JavaFilter.ANY, ask, save); + exitCode = handleJavaHome(appConfig, javaFinder, JavaFilter.ANY, ask, save); break; case "-jdk_home": exitCode = - handleJavaHome(javaConfig, javaFinder, JavaFilter.JDK_ONLY, ask, save); + handleJavaHome(appConfig, javaFinder, JavaFilter.JDK_ONLY, ask, save); break; case "-vmargs": - exitCode = handleVmArgs(javaConfig); + exitCode = handleVmArgs(appConfig); break; default: System.err.println("LaunchSupport received illegal argument: " + mode); @@ -109,7 +109,7 @@ public class LaunchSupport { * Handles figuring out a Java home directory to use for the launch. If it is successfully * determined, an exit code that indicates success is returned. * - * @param javaConfig The Java configuration that defines what we support. + * @param appConfig The appConfig configuration that defines what we support. * @param javaFinder The Java finder. * @param javaFilter A filter used to restrict what kind of Java installations we search for. * @param ask True to interact with the user to they can specify a Java home directory. @@ -120,12 +120,12 @@ public class LaunchSupport { * successfully determined. * @throws IOException if there was a disk-related problem. */ - private static int handleJavaHome(JavaConfig javaConfig, JavaFinder javaFinder, + private static int handleJavaHome(AppConfig appConfig, JavaFinder javaFinder, JavaFilter javaFilter, boolean ask, boolean save) throws IOException { if (ask) { - return askJavaHome(javaConfig, javaFinder, javaFilter); + return askJavaHome(appConfig, javaFinder, javaFilter); } - return findJavaHome(javaConfig, javaFinder, javaFilter, save); + return findJavaHome(appConfig, javaFinder, javaFilter, save); } /** @@ -133,7 +133,7 @@ public class LaunchSupport { * found, its path is printed to STDOUT and an exit code that indicates success is * returned. Otherwise, nothing is printed to STDOUT and an error exit code is returned. * - * @param javaConfig The Java configuration that defines what we support. + * @param appConfig The application configuration that defines what we support. * @param javaFinder The Java finder. * @param javaFilter A filter used to restrict what kind of Java installations we search for. * @param save True if the determined Java home directory should get saved to a file. @@ -141,19 +141,19 @@ public class LaunchSupport { * successfully determined. * @throws IOException if there was a problem saving the java home to disk. */ - private static int findJavaHome(JavaConfig javaConfig, JavaFinder javaFinder, + private static int findJavaHome(AppConfig appConfig, JavaFinder javaFinder, JavaFilter javaFilter, boolean save) throws IOException { File javaHomeDir; - LaunchProperties launchProperties = javaConfig.getLaunchProperties(); + LaunchProperties launchProperties = appConfig.getLaunchProperties(); // PRIORITY 1: JAVA_HOME_OVERRIDE property // If a valid java home override is specified in the launch properties, use that. // Someone presumably wants to force that specific version. javaHomeDir = launchProperties.getJavaHomeOverride(); - if (javaConfig.isSupportedJavaHomeDir(javaHomeDir, javaFilter)) { + if (appConfig.isSupportedJavaHomeDir(javaHomeDir, javaFilter)) { if (save) { - javaConfig.saveJavaHome(javaHomeDir); + appConfig.saveJavaHome(javaHomeDir); } System.out.println(javaHomeDir); return EXIT_SUCCESS; @@ -162,10 +162,10 @@ public class LaunchSupport { // PRIORITY 2: Java on PATH // This program (LaunchSupport) was started with the Java on the PATH. Try to use this one // next because it is most likely the one that is being upgraded on the user's system. - javaHomeDir = javaFinder.findSupportedJavaHomeFromCurrentJavaHome(javaConfig, javaFilter); + javaHomeDir = javaFinder.findSupportedJavaHomeFromCurrentJavaHome(appConfig, javaFilter); if (javaHomeDir != null) { if (save) { - javaConfig.saveJavaHome(javaHomeDir); + appConfig.saveJavaHome(javaHomeDir); } System.out.println(javaHomeDir); return EXIT_SUCCESS; @@ -173,19 +173,19 @@ public class LaunchSupport { // PRIORITY 3: Last used Java // Check to see if a prior launch resulted in that Java being saved. If so, try to use that. - javaHomeDir = javaConfig.getSavedJavaHome(); - if (javaConfig.isSupportedJavaHomeDir(javaHomeDir, javaFilter)) { + javaHomeDir = appConfig.getSavedJavaHome(); + if (appConfig.isSupportedJavaHomeDir(javaHomeDir, javaFilter)) { System.out.println(javaHomeDir); return EXIT_SUCCESS; } // PRIORITY 4: Find all supported Java installations, and use the newest. List javaHomeDirs = - javaFinder.findSupportedJavaHomeFromInstallations(javaConfig, javaFilter); + javaFinder.findSupportedJavaHomeFromInstallations(appConfig, javaFilter); if (!javaHomeDirs.isEmpty()) { javaHomeDir = javaHomeDirs.iterator().next(); if (save) { - javaConfig.saveJavaHome(javaHomeDir); + appConfig.saveJavaHome(javaHomeDir); } System.out.println(javaHomeDir); return EXIT_SUCCESS; @@ -199,7 +199,7 @@ public class LaunchSupport { * If a valid Java home directory was successfully determined, it is saved to the user's * Java home save file, and an exit code that indicates success is returned. * - * @param javaConfig The Java configuration that defines what we support. + * @param appConfig The application configuration that defines what we support. * @param javaFinder The Java finder. * @param javaFilter A filter used to restrict what kind of Java installations we search for. * * @return A suggested exit code based on whether or not a valid Java home directory was @@ -207,13 +207,13 @@ public class LaunchSupport { * @throws IOException if there was a problem interacting with the user, or saving the java * home location to disk. */ - private static int askJavaHome(JavaConfig javaConfig, JavaFinder javaFinder, + private static int askJavaHome(AppConfig appConfig, JavaFinder javaFinder, JavaFilter javaFilter) throws IOException { String javaName = javaFilter.equals(JavaFilter.JDK_ONLY) ? "JDK" : "Java"; String javaRange; - int min = javaConfig.getMinSupportedJava(); - int max = javaConfig.getMaxSupportedJava(); + int min = appConfig.getMinSupportedJava(); + int max = appConfig.getMaxSupportedJava(); if (min == max) { javaRange = min + ""; } @@ -226,7 +226,7 @@ public class LaunchSupport { System.out.println("******************************************************************"); System.out.println( - javaName + " " + javaRange + " (" + javaConfig.getSupportedArchitecture() + + javaName + " " + javaRange + " (" + appConfig.getSupportedArchitecture() + "-bit) could not be found and must be manually chosen!"); System.out.println("******************************************************************"); @@ -254,13 +254,13 @@ public class LaunchSupport { continue; } try { - JavaVersion javaVersion = javaConfig.getJavaVersion(javaHomeDir, javaFilter); - if (javaConfig.isJavaVersionSupported(javaVersion)) { + JavaVersion javaVersion = appConfig.getJavaVersion(javaHomeDir, javaFilter); + if (appConfig.isJavaVersionSupported(javaVersion)) { break; } System.out.println( "Java version " + javaVersion + " is outside of supported range: [" + - javaRange + " " + javaConfig.getSupportedArchitecture() + "-bit]"); + javaRange + " " + appConfig.getSupportedArchitecture() + "-bit]"); } catch (FileNotFoundException e) { System.out.println( @@ -271,7 +271,7 @@ public class LaunchSupport { } } - File javaHomeSaveFile = javaConfig.saveJavaHome(javaHomeDir); + File javaHomeSaveFile = appConfig.saveJavaHome(javaHomeDir); System.out.println("Saved changes to " + javaHomeSaveFile); return EXIT_SUCCESS; } @@ -281,18 +281,18 @@ public class LaunchSupport { * to STDOUT as a new-line delimited string that can be parsed and added to the command line, * and an exit code that indicates success is returned. - * @param javaConfig The Java configuration that defines what we support. + * @param appConfig The appConfig configuration that defines what we support. * @return A suggested exit code based on whether or not the VM arguments were successfully * gotten. */ - private static int handleVmArgs(JavaConfig javaConfig) { - if (javaConfig.getLaunchProperties() == null) { + private static int handleVmArgs(AppConfig appConfig) { + if (appConfig.getLaunchProperties() == null) { System.out.println("Launch properties file was not specified!"); return EXIT_FAILURE; } // Force newline style to make cross-platform parsing consistent - javaConfig.getLaunchProperties().getVmArgList().forEach(e -> System.out.print(e + "\r\n")); + appConfig.getLaunchProperties().getVmArgList().forEach(e -> System.out.print(e + "\r\n")); return EXIT_SUCCESS; } } diff --git a/GhidraBuild/LaunchSupport/src/main/java/ghidra/launch/JavaConfig.java b/GhidraBuild/LaunchSupport/src/main/java/ghidra/launch/AppConfig.java similarity index 83% rename from GhidraBuild/LaunchSupport/src/main/java/ghidra/launch/JavaConfig.java rename to GhidraBuild/LaunchSupport/src/main/java/ghidra/launch/AppConfig.java index d83c6ceb9c..62b42efc8a 100644 --- a/GhidraBuild/LaunchSupport/src/main/java/ghidra/launch/JavaConfig.java +++ b/GhidraBuild/LaunchSupport/src/main/java/ghidra/launch/AppConfig.java @@ -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. @@ -17,21 +17,23 @@ package ghidra.launch; import java.io.*; import java.text.ParseException; -import java.util.Properties; +import java.util.*; import ghidra.launch.JavaFinder.JavaFilter; /** - * Class to determine and represent a required Java configuration, including minimum and maximum - * supported versions, compiler compliance level, etc. + * Class to determine and represent a required application configuration, including minimum and + * maximum supported Java versions, compiler compliance level, etc. */ -public class JavaConfig { +public class AppConfig { private static final String LAUNCH_PROPERTIES_NAME = "launch.properties"; private static final String JAVA_HOME_SAVE_NAME = "java_home.save"; + private static final String PYTHON_COMMAND_SAVE_NAME = "python_command.save"; private LaunchProperties launchProperties; private File javaHomeSaveFile; + private File pythonCommandSaveFile; private String applicationName; // example: Ghidra private String applicationVersion; // example: 9.0.1 @@ -42,43 +44,43 @@ public class JavaConfig { private String compilerComplianceLevel; /** - * Creates a new Java configuration for the given installation. + * Creates a new application configuration for the given installation. * * @param installDir The installation directory. * @throws FileNotFoundException if a required file was not found. * @throws IOException if there was a problem reading a required file. * @throws ParseException if there was a problem parsing a required file. */ - public JavaConfig(File installDir) throws FileNotFoundException, IOException, ParseException { + public AppConfig(File installDir) throws FileNotFoundException, IOException, ParseException { initApplicationProperties(installDir); initLaunchProperties(installDir); - initJavaHomeSaveFile(installDir); + javaHomeSaveFile = getSaveFile(installDir, JAVA_HOME_SAVE_NAME); + pythonCommandSaveFile = getSaveFile(installDir, PYTHON_COMMAND_SAVE_NAME); } /** - * Gets the launch properties associated with this Java configuration. Certain aspects of the - * Java configuration are stored in the launch properties. + * Gets the launch properties associated with this application configuration. * - * @return The launch properties associated with this Java configuration. Could be null if - * this Java configuration does not use launch properties. + * @return The launch properties associated with this application configuration. Could be null + * if this application configuration does not use launch properties. */ public LaunchProperties getLaunchProperties() { return launchProperties; } /** - * Gets the Java configuration's minimum supported major Java version. + * Gets the application configuration's minimum supported major Java version. * - * @return The Java configuration's minimum supported major Java version. + * @return The application configuration's minimum supported major Java version. */ public int getMinSupportedJava() { return minSupportedJava; } /** - * Gets the Java configuration's maximum supported major Java version. + * Gets the application configuration's maximum supported major Java version. * - * @return The Java configuration's maximum supported major Java version. If there is no + * @return The application configuration's maximum supported major Java version. If there is no * restriction, the value will be 0. */ public int getMaxSupportedJava() { @@ -86,20 +88,20 @@ public class JavaConfig { } /** - * Gets the Java configuration's supported Java architecture. All supported Java + * Gets the application configuration's supported Java architecture. All supported Java * configurations must have an architecture of 64. * - * @return The Java configuration's supported Java architecture (64). + * @return The application configuration's supported Java architecture (64). */ public int getSupportedArchitecture() { return 64; } /** - * Gets the Java configuration's compiler compliance level that was used to build the - * associated installation. + * Gets the application configuration's Java compiler compliance level that was used to build + * the associated installation. * - * @return The Java configuration's compiler compliance level. + * @return The application configuration's compiler compliance level. */ public String getCompilerComplianceLevel() { return compilerComplianceLevel; @@ -115,8 +117,11 @@ public class JavaConfig { public File getSavedJavaHome() throws IOException { try (BufferedReader reader = new BufferedReader(new FileReader(javaHomeSaveFile))) { String line = reader.readLine().trim(); - if (line != null && !line.isEmpty()) { - return new File(line); + if (line != null) { + line = line.trim(); + if (!line.isEmpty()) { + return new File(line); + } } } catch (FileNotFoundException e) { @@ -148,12 +153,12 @@ public class JavaConfig { } /** - * Tests to see if the given directory is a supported Java home directory for this Java + * Tests to see if the given directory is a supported Java home directory for this application * configuration. * * @param dir The directory to test. * @param javaFilter A filter used to restrict what kind of Java installations we support. - * @return True if the given directory is a supported Java home directory for this Java + * @return True if the given directory is a supported Java home directory for this application * configuration. */ public boolean isSupportedJavaHomeDir(File dir, JavaFilter javaFilter) { @@ -166,10 +171,10 @@ public class JavaConfig { } /** - * Tests to see if the given Java version is supported by this Java launch configuration. + * Tests to see if the given Java version is supported by this application configuration. * * @param javaVersion The java version to check. - * @return True if the given Java version is supported by this Java launch configuration. + * @return True if the given Java version is supported by this application configuration. */ public boolean isJavaVersionSupported(JavaVersion javaVersion) { if (javaVersion.getArchitecture() != getSupportedArchitecture()) { @@ -233,6 +238,30 @@ public class JavaConfig { return runAndGetJavaVersion(javaExecutable); } + /** + * Gets the Python command from the user's Python command save file. + * + * @return The Python command from the user's Python command save file, or null if the file + * does not exist or is empty. + * @throws IOException if there was a problem reading the Python command save file. + */ + public List getSavedPythonCommand() throws IOException { + List command = new ArrayList<>(); + try (BufferedReader reader = new BufferedReader(new FileReader(pythonCommandSaveFile))) { + String line = null; + while ((line = reader.readLine()) != null) { + line = line.trim(); + if (!line.isEmpty()) { + command.add(line); + } + } + return command; + } + catch (FileNotFoundException e) { + return null; + } + } + /** * Gets the version of the given Java executable from the output of running "java -version". * @@ -354,13 +383,13 @@ public class JavaConfig { } /** - * Initializes the Java home save file. + * Gets the given "save file". * * @param installDir The Ghidra installation directory. This is the directory that has the * "Ghidra" subdirectory in it. * @throws FileNotFoundException if the user's home directory was not found. */ - private void initJavaHomeSaveFile(File installDir) throws FileNotFoundException { + private File getSaveFile(File installDir, String saveFileName) throws FileNotFoundException { boolean isDev = new File(installDir, "build.gradle").isFile(); String appName = applicationName.replaceAll("\\s", "").toLowerCase(); @@ -385,16 +414,14 @@ public class JavaConfig { // Handle legacy application layout if (applicationLayoutVersion.equals("1")) { userSettingsDir = new File(userHomeDir, "." + appName + "/." + userSettingsDirName); - javaHomeSaveFile = new File(userSettingsDir, JAVA_HOME_SAVE_NAME); - return; + return new File(userSettingsDir, saveFileName); } // Look for XDG environment variable String xdgConfigHomeDirStr = System.getenv("XDG_CONFIG_HOME"); if (xdgConfigHomeDirStr != null && !xdgConfigHomeDirStr.isEmpty()) { userSettingsDir = new File(xdgConfigHomeDirStr, appName + "/" + userSettingsDirName); - javaHomeSaveFile = new File(userSettingsDir, JAVA_HOME_SAVE_NAME); - return; + return new File(userSettingsDir, saveFileName); } // Look in current user settings directory @@ -420,7 +447,7 @@ public class JavaConfig { "Failed to find the user settings directory: Unsupported operating system."); } - javaHomeSaveFile = new File(userSettingsDir, JAVA_HOME_SAVE_NAME); + return new File(userSettingsDir, saveFileName); } /** diff --git a/GhidraBuild/LaunchSupport/src/main/java/ghidra/launch/JavaFinder.java b/GhidraBuild/LaunchSupport/src/main/java/ghidra/launch/JavaFinder.java index b9a2a83feb..400f26b6fd 100644 --- a/GhidraBuild/LaunchSupport/src/main/java/ghidra/launch/JavaFinder.java +++ b/GhidraBuild/LaunchSupport/src/main/java/ghidra/launch/JavaFinder.java @@ -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. @@ -79,11 +79,11 @@ public abstract class JavaFinder { * Returns a list of supported Java home directories from discovered Java installations. * The list is sorted from newest Java version to oldest. * - * @param javaConfig The Java configuration that defines what we support. + * @param appConfig The appConfig configuration that defines what we support. * @param javaFilter A filter used to restrict what kind of Java installations we search for. * @return A sorted list of supported Java home directories from discovered Java installations. */ - public List findSupportedJavaHomeFromInstallations(JavaConfig javaConfig, + public List findSupportedJavaHomeFromInstallations(AppConfig appConfig, JavaFilter javaFilter) { Set potentialJavaHomeSet = new TreeSet<>(); for (File javaRootInstallDir : getJavaRootInstallDirs()) { @@ -107,8 +107,8 @@ public abstract class JavaFinder { for (File potentialJavaHomeDir : potentialJavaHomeSet) { try { JavaVersion javaVersion = - javaConfig.getJavaVersion(potentialJavaHomeDir, javaFilter); - if (javaConfig.isJavaVersionSupported(javaVersion)) { + appConfig.getJavaVersion(potentialJavaHomeDir, javaFilter); + if (appConfig.isJavaVersionSupported(javaVersion)) { javaHomeToVersionMap.put(potentialJavaHomeDir, javaVersion); } } @@ -130,12 +130,12 @@ public abstract class JavaFinder { * Returns the Java home directory corresponding to the current "java.home" system * property (if it supported). * - * @param javaConfig The Java configuration that defines what we support. + * @param appConfig The appConfig configuration that defines what we support. * @param javaFilter A filter used to restrict what kind of Java installations we search for. * @return The Java home directory corresponding to the current "java.home" system property. * Could be null if the current "java.home" is not supported. */ - public File findSupportedJavaHomeFromCurrentJavaHome(JavaConfig javaConfig, + public File findSupportedJavaHomeFromCurrentJavaHome(AppConfig appConfig, JavaFilter javaFilter) { Set potentialJavaHomeSet = new HashSet<>(); String javaHomeProperty = System.getProperty("java.home"); @@ -149,8 +149,8 @@ public abstract class JavaFinder { } for (File potentialJavaHomeDir : potentialJavaHomeSet) { try { - if (javaConfig.isJavaVersionSupported( - javaConfig.getJavaVersion(potentialJavaHomeDir, javaFilter))) { + if (appConfig.isJavaVersionSupported( + appConfig.getJavaVersion(potentialJavaHomeDir, javaFilter))) { return potentialJavaHomeDir; } } diff --git a/gradle/root/distribution.gradle b/gradle/root/distribution.gradle index 06f35ed853..7ddf0673f7 100644 --- a/gradle/root/distribution.gradle +++ b/gradle/root/distribution.gradle @@ -432,6 +432,9 @@ task assembleDistribution (type: Copy) { from ("${ROOT_PROJECT_DIR}/build/typestubs/src") { into 'docs/ghidra_stubs/typestubs' } + from ("${ROOT_PROJECT_DIR}/build/typestubs/pypredef") { + into 'docs/ghidra_stubs/pypredef' + } from (createGhidraStubsWheel) { into 'docs/ghidra_stubs' } diff --git a/gradle/support/fetchDependencies.gradle b/gradle/support/fetchDependencies.gradle index 147a13a21d..8d292cf55a 100644 --- a/gradle/support/fetchDependencies.gradle +++ b/gradle/support/fetchDependencies.gradle @@ -95,9 +95,9 @@ ext.deps = [ destination: file("${DEPS_DIR}/BSim") ], [ - name: "PyDev 6.3.1.zip", - url: "https://sourceforge.net/projects/pydev/files/pydev/PyDev%206.3.1/PyDev%206.3.1.zip", - sha256: "4d81fe9d8afe7665b8ea20844d3f5107f446742927c59973eade4f29809b0699", + name: "PyDev 9.3.0.zip", + url: "https://sourceforge.net/projects/pydev/files/pydev/PyDev%209.3.0/PyDev%209.3.0.zip", + sha256: "45398edf2adb56078a80bc88a919941578f0c0b363efbdd011bfd158a99b112e", destination: file("${DEPS_DIR}/GhidraDev") ], [