GP-5138: GhidraDev/PyDev/PyGhidra integration

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

View file

@ -898,6 +898,7 @@ src/main/resources/images/pencil16.png||GHIDRA||||END|
src/main/resources/images/pin.png||GHIDRA||||END| src/main/resources/images/pin.png||GHIDRA||||END|
src/main/resources/images/play_again.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/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/question_zero.png||GHIDRA||||END|
src/main/resources/images/red-cross.png||GHIDRA||||END| src/main/resources/images/red-cross.png||GHIDRA||||END|
src/main/resources/images/redQuestionMark.png||GHIDRA||||END| src/main/resources/images/redQuestionMark.png||GHIDRA||||END|

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 72 KiB

View file

@ -160,11 +160,14 @@ class PyGhidraLauncher:
install_dir = install_dir or os.getenv("GHIDRA_INSTALL_DIR") install_dir = install_dir or os.getenv("GHIDRA_INSTALL_DIR")
self._install_dir = self._validate_install_dir(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 # check if we are in the ghidra source tree
support = Path(install_dir) / "support" support = Path(install_dir) / "support"
if not support.exists(): if not support.exists():
self._dev_mode = True self._dev_mode = True
self._java_home = os.getenv("JAVA_HOME_OVERRIDE")
self._plugins: List[Tuple[Path, ExtensionDetails]] = [] self._plugins: List[Tuple[Path, ExtensionDetails]] = []
self.verbose = verbose self.verbose = verbose
@ -469,7 +472,7 @@ class PyGhidraLauncher:
self._pre_launch_init() self._pre_launch_init()
self._launch() self._launch()
except Exception as e: 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: def get_install_path(self, plugin_name: str) -> Path:
""" """

View file

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

View file

@ -167,7 +167,9 @@
<setEntry value="com.google.guava.failureaccess@default:default"/> <setEntry value="com.google.guava.failureaccess@default:default"/>
<setEntry value="com.google.guava@default:default"/> <setEntry value="com.google.guava@default:default"/>
<setEntry value="com.ibm.icu@default:default"/> <setEntry value="com.ibm.icu@default:default"/>
<setEntry value="com.python.pydev.analysis*6.3.1.201802272029@default:default"/> <setEntry value="com.python.pydev.analysis*9.3.0.202203051235@default:default"/>
<setEntry value="com.python.pydev.debug*9.3.0.202203051235@default:default"/>
<setEntry value="com.python.pydev.refactoring*9.3.0.202203051235@default:default"/>
<setEntry value="com.sun.el.javax.el@default:default"/> <setEntry value="com.sun.el.javax.el@default:default"/>
<setEntry value="com.sun.jna.platform@default:default"/> <setEntry value="com.sun.jna.platform@default:default"/>
<setEntry value="com.sun.jna@default:default"/> <setEntry value="com.sun.jna@default:default"/>
@ -176,7 +178,6 @@
<setEntry value="jakarta.inject.jakarta.inject-api*1.0.5@default:default"/> <setEntry value="jakarta.inject.jakarta.inject-api*1.0.5@default:default"/>
<setEntry value="jakarta.inject.jakarta.inject-api*2.0.1@default:default"/> <setEntry value="jakarta.inject.jakarta.inject-api*2.0.1@default:default"/>
<setEntry value="jakarta.servlet-api@default:default"/> <setEntry value="jakarta.servlet-api@default:default"/>
<setEntry value="javax.xml@default:default"/>
<setEntry value="jaxen@default:default"/> <setEntry value="jaxen@default:default"/>
<setEntry value="org.apache.aries.spifly.dynamic.bundle@default:default"/> <setEntry value="org.apache.aries.spifly.dynamic.bundle@default:default"/>
<setEntry value="org.apache.batik.constants@default:default"/> <setEntry value="org.apache.batik.constants@default:default"/>
@ -202,6 +203,8 @@
<setEntry value="org.apache.xml.resolver@default:default"/> <setEntry value="org.apache.xml.resolver@default:default"/>
<setEntry value="org.apache.xml.serializer@default:default"/> <setEntry value="org.apache.xml.serializer@default:default"/>
<setEntry value="org.apache.xmlgraphics@default:default"/> <setEntry value="org.apache.xmlgraphics@default:default"/>
<setEntry value="org.commonmark-gfm-tables@default:default"/>
<setEntry value="org.commonmark@default:default"/>
<setEntry value="org.eclipse.ant.core@default:default"/> <setEntry value="org.eclipse.ant.core@default:default"/>
<setEntry value="org.eclipse.buildship.compat@default:default"/> <setEntry value="org.eclipse.buildship.compat@default:default"/>
<setEntry value="org.eclipse.buildship.core@default:default"/> <setEntry value="org.eclipse.buildship.core@default:default"/>
@ -296,7 +299,7 @@
<setEntry value="org.eclipse.help.base@default:default"/> <setEntry value="org.eclipse.help.base@default:default"/>
<setEntry value="org.eclipse.help.ui@default:default"/> <setEntry value="org.eclipse.help.ui@default:default"/>
<setEntry value="org.eclipse.help@default:default"/> <setEntry value="org.eclipse.help@default:default"/>
<setEntry value="org.eclipse.jdt.annotation*2.3.0.v20240111-2306@default:default"/> <setEntry value="org.eclipse.jdt.annotation@default:default"/>
<setEntry value="org.eclipse.jdt.core.compiler.batch@default:default"/> <setEntry value="org.eclipse.jdt.core.compiler.batch@default:default"/>
<setEntry value="org.eclipse.jdt.core.manipulation@default:default"/> <setEntry value="org.eclipse.jdt.core.manipulation@default:default"/>
<setEntry value="org.eclipse.jdt.core@default:default"/> <setEntry value="org.eclipse.jdt.core@default:default"/>
@ -308,10 +311,11 @@
<setEntry value="org.eclipse.jdt.launching@default:default"/> <setEntry value="org.eclipse.jdt.launching@default:default"/>
<setEntry value="org.eclipse.jdt.ui@default:default"/> <setEntry value="org.eclipse.jdt.ui@default:default"/>
<setEntry value="org.eclipse.jem.util@default:default"/> <setEntry value="org.eclipse.jem.util@default:default"/>
<setEntry value="org.eclipse.jetty.ee10.servlet@default:default"/>
<setEntry value="org.eclipse.jetty.ee10.webapp@default:default"/>
<setEntry value="org.eclipse.jetty.ee8.security@default:default"/> <setEntry value="org.eclipse.jetty.ee8.security@default:default"/>
<setEntry value="org.eclipse.jetty.ee8.server@default:default"/> <setEntry value="org.eclipse.jetty.ee8.server@default:default"/>
<setEntry value="org.eclipse.jetty.ee8.servlet@default:default"/> <setEntry value="org.eclipse.jetty.ee8.servlet@default:default"/>
<setEntry value="org.eclipse.jetty.ee8.webapp@default:default"/>
<setEntry value="org.eclipse.jetty.ee@default:default"/> <setEntry value="org.eclipse.jetty.ee@default:default"/>
<setEntry value="org.eclipse.jetty.http@default:default"/> <setEntry value="org.eclipse.jetty.http@default:default"/>
<setEntry value="org.eclipse.jetty.io@default:default"/> <setEntry value="org.eclipse.jetty.io@default:default"/>
@ -414,14 +418,19 @@
<setEntry value="org.osgi.util.position@default:default"/> <setEntry value="org.osgi.util.position@default:default"/>
<setEntry value="org.osgi.util.promise@default:default"/> <setEntry value="org.osgi.util.promise@default:default"/>
<setEntry value="org.osgi.util.xml@default:default"/> <setEntry value="org.osgi.util.xml@default:default"/>
<setEntry value="org.python.pydev*6.3.1.201802272029@default:default"/> <setEntry value="org.python.pydev*9.3.0.202203051235@default:default"/>
<setEntry value="org.python.pydev.ast*6.3.1.201802272029@default:default"/> <setEntry value="org.python.pydev.ast*9.3.0.202203051235@default:default"/>
<setEntry value="org.python.pydev.core*6.3.1.201802272029@default:default"/> <setEntry value="org.python.pydev.core*9.3.0.202203051235@default:default"/>
<setEntry value="org.python.pydev.jython*6.3.1.201802272029@default:default"/> <setEntry value="org.python.pydev.customizations*9.3.0.202203051235@default:default"/>
<setEntry value="org.python.pydev.parser*6.3.1.201802272029@default:default"/> <setEntry value="org.python.pydev.debug*9.3.0.202203051235@default:default"/>
<setEntry value="org.python.pydev.shared_core*6.3.1.201802272029@default:default"/> <setEntry value="org.python.pydev.django*9.3.0.202203051235@default:default"/>
<setEntry value="org.python.pydev.shared_interactive_console*6.3.1.201802272029@default:default"/> <setEntry value="org.python.pydev.help*9.3.0.202203051235@default:default"/>
<setEntry value="org.python.pydev.shared_ui*6.3.1.201802272029@default:default"/> <setEntry value="org.python.pydev.jython*9.3.0.202203051235@default:default"/>
<setEntry value="org.python.pydev.parser*9.3.0.202203051235@default:default"/>
<setEntry value="org.python.pydev.refactoring*9.3.0.202203051235@default:default"/>
<setEntry value="org.python.pydev.shared_core*9.3.0.202203051235@default:default"/>
<setEntry value="org.python.pydev.shared_interactive_console*9.3.0.202203051235@default:default"/>
<setEntry value="org.python.pydev.shared_ui*9.3.0.202203051235@default:default"/>
<setEntry value="org.sat4j.core@default:default"/> <setEntry value="org.sat4j.core@default:default"/>
<setEntry value="org.sat4j.pb@default:default"/> <setEntry value="org.sat4j.pb@default:default"/>
<setEntry value="org.tukaani.xz@default:default"/> <setEntry value="org.tukaani.xz@default:default"/>
@ -430,7 +439,7 @@
<setAttribute key="selected_workspace_bundles"> <setAttribute key="selected_workspace_bundles">
<setEntry value="ghidra.ghidradev@default:default"/> <setEntry value="ghidra.ghidradev@default:default"/>
</setAttribute> </setAttribute>
<booleanAttribute key="show_selected_only" value="true"/> <booleanAttribute key="show_selected_only" value="false"/>
<booleanAttribute key="tracing" value="false"/> <booleanAttribute key="tracing" value="false"/>
<booleanAttribute key="useCustomFeatures" value="false"/> <booleanAttribute key="useCustomFeatures" value="false"/>
<booleanAttribute key="useDefaultConfig" value="true"/> <booleanAttribute key="useDefaultConfig" value="true"/>

View file

@ -3,7 +3,7 @@ Manifest-Version: 1.0
Bundle-ManifestVersion: 2 Bundle-ManifestVersion: 2
Bundle-Name: GhidraDev Bundle-Name: GhidraDev
Bundle-SymbolicName: ghidra.ghidradev;singleton:=true Bundle-SymbolicName: ghidra.ghidradev;singleton:=true
Bundle-Version: 4.0.1.qualifier Bundle-Version: 5.0.0.qualifier
Bundle-Activator: ghidradev.Activator Bundle-Activator: ghidradev.Activator
Require-Bundle: org.eclipse.ant.core;bundle-version="3.7.200", Require-Bundle: org.eclipse.ant.core;bundle-version="3.7.200",
org.eclipse.buildship.core;bundle-version="3.1.8", 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.ltk.core.refactoring;bundle-version="3.14.200",
org.eclipse.ui;bundle-version="3.205.0", org.eclipse.ui;bundle-version="3.205.0",
org.eclipse.ui.ide;bundle-version="3.22.0", org.eclipse.ui.ide;bundle-version="3.22.0",
com.python.pydev.debug;bundle-version="6.3.1";resolution:=optional, com.python.pydev.debug;bundle-version="9.3.0";resolution:=optional,
org.python.pydev;bundle-version="[6.3.1,10.0.0)";resolution:=optional, org.python.pydev;bundle-version="9.3.0";resolution:=optional,
org.python.pydev.core;bundle-version="[6.3.1,10.0.0)";resolution:=optional, org.python.pydev.core;bundle-version="9.3.0";resolution:=optional,
org.python.pydev.ast;bundle-version="[6.3.1,10.0.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.core;bundle-version="5.9.1";resolution:=optional,
org.eclipse.cdt.ui;bundle-version="5.9.0";resolution:=optional org.eclipse.cdt.ui;bundle-version="5.9.0";resolution:=optional
Bundle-RequiredExecutionEnvironment: JavaSE-21 Bundle-RequiredExecutionEnvironment: JavaSE-21

View file

@ -1,7 +1,7 @@
# GhidraDev Eclipse Plugin # GhidraDev Eclipse Plugin
GhidraDev provides support for developing and debugging Ghidra scripts and modules in Eclipse. 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. change with future releases.
## Table of Contents ## Table of Contents
@ -31,6 +31,9 @@ change with future releases.
12. [Building](#building) 12. [Building](#building)
## Change History ## Change History
__5.0.0:__
* Added support for PyGhidra.
__4.0.1:__ __4.0.1:__
* New Ghidra module projects now contain a default `README.md` file. * 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 * 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 * Ghidra 11.2 or later
## Optional Requirements ## 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 * Gradle - required version(s) specified by linked Ghidra release
([more info](#export-ghidra-module-extension)) ([more info](#export-ghidra-module-extension))
@ -268,7 +271,10 @@ Ghidra from Eclipse independent of a project is not supported.
## PyDev Support ## PyDev Support
GhidraDev is able to integrate with PyDev to conveniently configure Python support into Ghidra 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 ### Installing PyDev
From Eclipse: From Eclipse:
@ -293,13 +299,19 @@ GhidraDev can add Python support to a Ghidra project when:
* Creating a new Ghidra script project * Creating a new Ghidra script project
* Linking a Ghidra installation to an existing Java 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. In order for GhidraDev to add in Python support, PyDev must have a PyGhidra or Jython interpreter
GhidraDev will present a list of detected Jython interpreters that it found in PyDev's preferences. configured. GhidraDev will present a list of detected PyGhidra/Jython interpreters that it found in
If no Jython interpreters were found, one can be added from GhidraDev by clicking the `+` icon. PyDev's preferences. If no interpreters were found, one can be added from GhidraDev by clicking
When the `+` icon is clicked, GhidraDev will attempt to find the Jython interpreter bundled with the the `+` icons.
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 When the Jython `+` icon is clicked, GhidraDev will attempt to find the Jython interpreter bundled
added manually in the PyDev preferences. 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 ## Upgrading
GhidraDev is upgraded differently depending on how it was installed. If GhidraDev was 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. 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 Simply [relink](#link-ghidra) your Ghidra installation to the project, and your project will
pick up any newly discovered Ghidra extensions. 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 ## Additional Resources
For more information on the GhidraDev plugin and developing for Ghidra in an Eclipse environment, For more information on the GhidraDev plugin and developing for Ghidra in an Eclipse environment,
@ -358,14 +367,14 @@ at `<GhidraInstallDir>/docs/GhidraClass/Intermediate/Scripting.html`.
## Building ## Building
GhidraDev is currently built from Eclipse and distributed with Ghidra manually. Ideally we will use 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 Gradle one day, but we aren't there yet. We do rely on Gradle to generate the Eclipse project and
project and build GhidraDev's dependencies though. build GhidraDev's dependencies though.
__NOTE:__ Only "Eclipse for RCP and RAP Developers" has the ability to do the below instructions. __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. The following instructions assume that you are using this version of Eclipse.
#### Importing GhidraDev Eclipse projects (they are deactivated by default): #### 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` 2. From Eclipse, `File -> Import -> General -> Existing Projects into Workspace`
3. From the ghidra repo, import `Eclipse GhidraDevFeature` and `Eclipse GhidraDevPlugin` 3. From the ghidra repo, import `Eclipse GhidraDevFeature` and `Eclipse GhidraDevPlugin`

View file

@ -81,8 +81,8 @@ task pyDevUnpack(type:Copy) {
!pyDevDestDir.exists() !pyDevDestDir.exists()
} }
File depsFile = file("${DEPS_DIR}/GhidraDev/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 6.3.1.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. // 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) def pyDevZipTree = depsFile.exists() ? zipTree(depsFile) : zipTree(binRepoFile)
@ -115,6 +115,13 @@ task cdtUnpack(type:Copy) {
destinationDir cdtDestDir 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 // We do not currently build GhidraDev plugin at Ghidra build time so we must
// copy the prebuilt zip file from the BIN_REPO // copy the prebuilt zip file from the BIN_REPO
rootProject.assembleDistribution { rootProject.assembleDistribution {
@ -129,9 +136,3 @@ rootProject.assembleMarkdownToHtml {
into "Extensions/Eclipse/GhidraDev/" into "Extensions/Eclipse/GhidraDev/"
} }
} }
// PrepDev dependencies
rootProject.prepDev.dependsOn utilityJar
rootProject.prepDev.dependsOn launchSupportJar
rootProject.prepDev.dependsOn pyDevUnpack
rootProject.prepDev.dependsOn cdtUnpack

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

View file

@ -378,6 +378,11 @@
id="GhidraHeadlessLaunchConfigurationType" id="GhidraHeadlessLaunchConfigurationType"
name="Ghidra Headless"> name="Ghidra Headless">
</launchConfigurationType> </launchConfigurationType>
<launchConfigurationType
delegate="ghidradev.ghidraprojectcreator.launchers.PyGhidraLaunchDelegate"
id="PyGhidraGuiLaunchConfigurationType"
name="PyGhidra">
</launchConfigurationType>
</extension> </extension>
<extension <extension
point="org.eclipse.debug.ui.launchConfigurationTypeImages"> point="org.eclipse.debug.ui.launchConfigurationTypeImages">
@ -391,6 +396,11 @@
icon="icons/GhidraIcon16_bw.png" icon="icons/GhidraIcon16_bw.png"
id="GhidraHeadlessLaunchConfigurationTypeImage"> id="GhidraHeadlessLaunchConfigurationTypeImage">
</launchConfigurationTypeImage> </launchConfigurationTypeImage>
<launchConfigurationTypeImage
configTypeID="PyGhidraGuiLaunchConfigurationType"
icon="icons/python.png"
id="PyGhidraGuiLaunchConfigurationTypeImage">
</launchConfigurationTypeImage>
</extension> </extension>
<extension <extension
point="org.eclipse.debug.core.launchDelegates"> point="org.eclipse.debug.core.launchDelegates">
@ -408,6 +418,13 @@
name="Ghidra Headless" name="Ghidra Headless"
type="GhidraHeadlessLaunchConfigurationType"> type="GhidraHeadlessLaunchConfigurationType">
</launchDelegate> </launchDelegate>
<launchDelegate
delegate="ghidradev.ghidraprojectcreator.launchers.PyGhidraLaunchDelegate"
id="PyGhidraGuiLaunchDelegate"
modes="run, debug"
name="PyGhidra GUI"
type="PyGhidraGuiLaunchConfigurationType">
</launchDelegate>
</extension> </extension>
<extension <extension
point="org.eclipse.debug.ui.launchConfigurationTabGroups"> point="org.eclipse.debug.ui.launchConfigurationTabGroups">
@ -423,6 +440,12 @@
id="GhidraHeadlessLaunchConfigurationTabGroup" id="GhidraHeadlessLaunchConfigurationTabGroup"
type="GhidraHeadlessLaunchConfigurationType"> type="GhidraHeadlessLaunchConfigurationType">
</launchConfigurationTabGroup> </launchConfigurationTabGroup>
<launchConfigurationTabGroup
class="org.python.pydev.debug.ui.PythonTabGroup"
description="Run and debug PyGhidra modules and scripts"
id="PyGhidraGuiLaunchConfigurationTabGroup"
type="PyGhidraGuiLaunchConfigurationType">
</launchConfigurationTabGroup>
</extension> </extension>
<extension <extension
point="org.eclipse.debug.ui.launchShortcuts"> point="org.eclipse.debug.ui.launchShortcuts">
@ -474,6 +497,30 @@
</enablement> </enablement>
</contextualLaunch> </contextualLaunch>
</shortcut> </shortcut>
<shortcut
class="ghidradev.ghidraprojectcreator.launchers.PyGhidraGuiLaunchShortcut"
icon="icons/python.png"
id="PyGhidraGuiLaunchShortcut"
label="PyGhidra"
modes="run, debug">
<contextualLaunch>
<enablement>
<with
variable="selection">
<count
value="1">
</count>
<iterate
ifEmpty="false">
<test
property="ghidradev.ghidraprojectcreator.testers.isPyGhidraProject"
value="true">
</test>
</iterate>
</with>
</enablement>
</contextualLaunch>
</shortcut>
</extension> </extension>
<extension <extension
point="org.eclipse.core.expressions.propertyTesters"> point="org.eclipse.core.expressions.propertyTesters">
@ -505,6 +552,13 @@
properties="isGhidraModuleProject" properties="isGhidraModuleProject"
type="java.lang.Object"> type="java.lang.Object">
</propertyTester> </propertyTester>
<propertyTester
class="ghidradev.ghidraprojectcreator.testers.PyGhidraProjectPropertyTester"
id="PyGhidraProjectPropertyTester"
namespace="ghidradev.ghidraprojectcreator.testers"
properties="isPyGhidraProject"
type="java.lang.Object">
</propertyTester>
</extension> </extension>
</plugin> </plugin>

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -49,6 +49,12 @@ public class GhidraLaunchUtils {
*/ */
public static final String HEADLESS_LAUNCH = "GhidraHeadlessLaunchConfigurationType"; 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 * 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 * to the required program arguments that are required to launch Ghidra, which are hidden

View file

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

View file

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

View file

@ -26,6 +26,7 @@ import org.eclipse.jdt.core.*;
import ghidra.GhidraApplicationLayout; import ghidra.GhidraApplicationLayout;
import ghidra.framework.GModule; import ghidra.framework.GModule;
import ghidradev.Activator; import ghidradev.Activator;
import ghidradev.ghidraprojectcreator.utils.PyDevUtils.ProjectPythonInterpreter;
/** /**
* Utility methods for working with Ghidra scripts in Eclipse. * 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 linkUserScripts Whether or not to link in the user scripts directory.
* @param linkSystemScripts Whether or not to link in the system scripts directories. * @param linkSystemScripts Whether or not to link in the system scripts directories.
* @param ghidraLayout The Ghidra layout to link the project to. * @param ghidraLayout The Ghidra layout to link the project to.
* @param jythonInterpreterName The name of the Jython interpreter to use for Python support. * @param pythonInterpreter The Python interpreter to use.
* Could be null if Python support is not wanted.
* @param monitor The progress monitor to use during project creation. * @param monitor The progress monitor to use during project creation.
* @return The created project. * @return The created project.
* @throws IOException If there was a file-related problem with creating the 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, public static IJavaProject createGhidraScriptProject(String projectName, File projectDir,
boolean createRunConfig, String runConfigMemory, boolean linkUserScripts, boolean createRunConfig, String runConfigMemory, boolean linkUserScripts,
boolean linkSystemScripts, GhidraApplicationLayout ghidraLayout, boolean linkSystemScripts, GhidraApplicationLayout ghidraLayout,
String jythonInterpreterName, IProgressMonitor monitor) ProjectPythonInterpreter pythonInterpreter, IProgressMonitor monitor)
throws IOException, ParseException, CoreException { throws IOException, ParseException, CoreException {
List<IClasspathEntry> classpathEntries = new ArrayList<>(); List<IClasspathEntry> classpathEntries = new ArrayList<>();
// Create empty Ghidra project // Create empty Ghidra project
IJavaProject javaProject = GhidraProjectUtils.createEmptyGhidraProject(projectName, IJavaProject javaProject = GhidraProjectUtils.createEmptyGhidraProject(projectName,
projectDir, createRunConfig, runConfigMemory, ghidraLayout, jythonInterpreterName, projectDir, createRunConfig, runConfigMemory, ghidraLayout, pythonInterpreter, monitor);
monitor);
// Link each module's ghidra_scripts directory to the project // Link each module's ghidra_scripts directory to the project
if (linkSystemScripts) { if (linkSystemScripts) {

View file

@ -26,6 +26,7 @@ import java.util.stream.Stream;
import javax.naming.OperationNotSupportedException; import javax.naming.OperationNotSupportedException;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.runtime.*; import org.eclipse.core.runtime.*;
import org.eclipse.jdt.core.IClasspathEntry; import org.eclipse.jdt.core.IClasspathEntry;
import org.eclipse.jdt.core.IJavaProject; import org.eclipse.jdt.core.IJavaProject;
@ -38,23 +39,58 @@ import ghidradev.Activator;
*/ */
public class PyDevUtils { public class PyDevUtils {
public final static String MIN_SUPPORTED_VERSION = "6.3.1"; public final static String MIN_SUPPORTED_VERSION = "9.3.0";
public final static String MAX_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. * The various types of supported Python interpreters
*
* @return True if a supported version of PyDev is installed; otherwise, false.
*/ */
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 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 { try {
Version version = PyDevUtilsInternal.getPyDevVersion(); Version version = PyDevUtilsInternal.getPyDevVersion();
if (version != null) { if (version != null) {
// Make sure the installed version of PyDev is new enough to support the following // Make sure the installed version of PyDev is new enough to support the following
// operation. // operation.
getJython27InterpreterNames(); getJythonInterpreterNames();
return version.compareTo(min) >= 0 && version.compareTo(max) <= 0; 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. * Gets a list of discovered PyGhidra interpreter names.
* * @param requiredFileMatch if not {@code null}, only interpreter names that correspond to the
* @return a list of discovered Jython 2.7 interpreter names. * 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 * @throws OperationNotSupportedException if PyDev is not installed or it does not support this
* operation. * operation.
*/ */
public static List<String> getJython27InterpreterNames() throws OperationNotSupportedException { public static List<String> getPyGhidraInterpreterNames(File requiredFileMatch)
throws OperationNotSupportedException {
try { try {
return PyDevUtilsInternal.getJython27InterpreterNames(); return PyDevUtilsInternal.getPyGhidraInterpreterNames(requiredFileMatch);
}
catch (NoClassDefFoundError | NoSuchMethodError e) {
throw new OperationNotSupportedException(e.getMessage());
}
}
/**
* Gets a list of discovered Jython interpreter names.
*
* @return a list of discovered Jython interpreter names.
* @throws OperationNotSupportedException if PyDev is not installed or it does not support this
* operation.
*/
public static List<String> getJythonInterpreterNames() throws OperationNotSupportedException {
try {
return PyDevUtilsInternal.getJythonInterpreterNames();
}
catch (NoClassDefFoundError | NoSuchMethodError e) {
throw new OperationNotSupportedException(e.getMessage());
}
}
/**
* Adds the given PyGhidra interpreter to PyDev.
*
* @param interpreterName The name of the interpreter to add.
* @param interpreterFile The interpreter file to add.
* @param pypredefDir The pypredef directory to use (could be null if not supported)
* @throws OperationNotSupportedException if PyDev is not installed or it does not support this
* operation.
*/
public static void addPyGhidraInterpreter(String interpreterName, File interpreterFile,
File pypredefDir) throws OperationNotSupportedException {
try {
PyDevUtilsInternal.addPyGhidraInterpreter(interpreterName, interpreterFile,
pypredefDir);
} }
catch (NoClassDefFoundError | NoSuchMethodError e) { catch (NoClassDefFoundError | NoSuchMethodError e) {
throw new OperationNotSupportedException(e.getMessage()); throw new OperationNotSupportedException(e.getMessage());
@ -107,8 +181,7 @@ public class PyDevUtils {
* *
* @param javaProject The Java project to enable Python for. * @param javaProject The Java project to enable Python for.
* @param classpathEntries The classpath entries to add to the Python path. * @param classpathEntries The classpath entries to add to the Python path.
* @param jythonInterpreterName The name of the Jython interpreter to use for Python support. * @param pythonInterpreter The Python interpreter to use.
* If this is null, Python support will be removed from the project.
* @param monitor The progress monitor used during link. * @param monitor The progress monitor used during link.
* @throws CoreException if there was an Eclipse-related problem with enabling Python for the * @throws CoreException if there was an Eclipse-related problem with enabling Python for the
* project. * project.
@ -116,11 +189,11 @@ public class PyDevUtils {
* operation. * operation.
*/ */
public static void setupPythonForProject(IJavaProject javaProject, public static void setupPythonForProject(IJavaProject javaProject,
List<IClasspathEntry> classpathEntries, String jythonInterpreterName, List<IClasspathEntry> classpathEntries, ProjectPythonInterpreter pythonInterpreter,
IProgressMonitor monitor) throws CoreException, OperationNotSupportedException { IProgressMonitor monitor) throws CoreException, OperationNotSupportedException {
try { try {
PyDevUtilsInternal.setupPythonForProject(javaProject, classpathEntries, PyDevUtilsInternal.setupPythonForProject(javaProject, classpathEntries,
jythonInterpreterName, monitor); pythonInterpreter, monitor);
} }
catch (NoClassDefFoundError | NoSuchMethodError e) { catch (NoClassDefFoundError | NoSuchMethodError e) {
throw new OperationNotSupportedException(e.getMessage()); throw new OperationNotSupportedException(e.getMessage());
@ -151,6 +224,15 @@ public class PyDevUtils {
return "org.python.pydev.ui.pythonpathconf.interpreterPreferencesPageJython"; 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. * Gets The PyDev source directory.
* *
@ -184,4 +266,138 @@ public class PyDevUtils {
return null; return null;
} }
/**
* Checks to see if the given project is a Python project.
*
* @param project The project to check.
* @return True if the given project is a Python project; otherwise, false.
* @throws OperationNotSupportedException if PyDev is not installed or it does not support this
* operation.
*/
public static boolean isPythonProject(IProject project) throws OperationNotSupportedException {
try {
return PyDevUtilsInternal.isPythonProject(project);
}
catch (NoClassDefFoundError | NoSuchMethodError e) {
throw new OperationNotSupportedException(e.getMessage());
}
}
/**
* Checks to see if the given project is a PyGhidra project.
*
* @param project The project to check.
* @return True if the given project is a PyGhidra project; otherwise, false.
* @throws OperationNotSupportedException if PyDev is not installed or it does not support this
* operation.
*/
public static boolean isPyGhidraProject(IProject project)
throws OperationNotSupportedException {
try {
return PyDevUtilsInternal.isPyGhidraProject(project);
}
catch (NoClassDefFoundError | NoSuchMethodError e) {
throw new OperationNotSupportedException(e.getMessage());
}
}
/**
* Gets the interpreter name of the given Python project.
*
* @param project The project to get the interpreter name from.
* @return The interpreter name of the given Python project, or null it it's not a Python
* project or doesn't have an interpreter.
* @throws OperationNotSupportedException if PyDev is not installed or it does not support this
* operation.
*/
public static String getInterpreterName(IProject project)
throws OperationNotSupportedException {
try {
return PyDevUtilsInternal.getInterpreterName(project);
}
catch (NoClassDefFoundError | NoSuchMethodError e) {
throw new OperationNotSupportedException(e.getMessage());
}
}
/**
* Gets the PyDev "project" attribute.
*
* @return The PyDev "project" attribute.
* @throws OperationNotSupportedException if PyDev is not installed or it does not support this
* operation.
*/
public static String getAttrProject() throws OperationNotSupportedException {
try {
return PyDevUtilsInternal.getAttrProject();
}
catch (NoClassDefFoundError e) {
throw new OperationNotSupportedException(e.getMessage());
}
}
/**
* Gets the PyDev "location" attribute.
*
* @return The PyDev "location" attribute.
* @throws OperationNotSupportedException if PyDev is not installed or it does not support this
* operation.
*/
public static String getAttrLocation() throws OperationNotSupportedException {
try {
return PyDevUtilsInternal.getAttrLocation();
}
catch (NoClassDefFoundError e) {
throw new OperationNotSupportedException(e.getMessage());
}
}
/**
* Gets the PyDev "program arguments" attribute.
*
* @return The PyDev "program arguments" attribute.
* @throws OperationNotSupportedException if PyDev is not installed or it does not support this
* operation.
*/
public static String getAttrProgramArguments() throws OperationNotSupportedException {
try {
return PyDevUtilsInternal.getAttrProgramArguments();
}
catch (NoClassDefFoundError e) {
throw new OperationNotSupportedException(e.getMessage());
}
}
/**
* Gets the PyDev "interpreter" attribute.
*
* @return The PyDev "interpreter" attribute.
* @throws OperationNotSupportedException if PyDev is not installed or it does not support this
* operation.
*/
public static String getAttrInterpreter() throws OperationNotSupportedException {
try {
return PyDevUtilsInternal.getAttrInterpreter();
}
catch (NoClassDefFoundError e) {
throw new OperationNotSupportedException(e.getMessage());
}
}
/**
* Gets the PyDev "interpreter default" attribute.
*
* @return The PyDev "interpreter default" attribute.
* @throws OperationNotSupportedException if PyDev is not installed or it does not support this
* operation.
*/
public static String getAttrInterpreterDefault() throws OperationNotSupportedException {
try {
return PyDevUtilsInternal.getAttrInterpreterDefault();
}
catch (NoClassDefFoundError e) {
throw new OperationNotSupportedException(e.getMessage());
}
}
} }

View file

@ -19,6 +19,7 @@ import java.io.File;
import java.util.*; import java.util.*;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.runtime.*; import org.eclipse.core.runtime.*;
import org.eclipse.jdt.core.IClasspathEntry; import org.eclipse.jdt.core.IClasspathEntry;
import org.eclipse.jdt.core.IJavaProject; import org.eclipse.jdt.core.IJavaProject;
@ -26,11 +27,13 @@ import org.osgi.framework.*;
import org.python.pydev.ast.interpreter_managers.InterpreterInfo; import org.python.pydev.ast.interpreter_managers.InterpreterInfo;
import org.python.pydev.ast.interpreter_managers.InterpreterManagersAPI; import org.python.pydev.ast.interpreter_managers.InterpreterManagersAPI;
import org.python.pydev.core.*; import org.python.pydev.core.*;
import org.python.pydev.debug.core.Constants;
import org.python.pydev.plugin.nature.PythonNature; import org.python.pydev.plugin.nature.PythonNature;
import com.python.pydev.debug.remote.client_api.PydevRemoteDebuggerServer; import com.python.pydev.debug.remote.client_api.PydevRemoteDebuggerServer;
import ghidradev.EclipseMessageUtils; import ghidradev.EclipseMessageUtils;
import ghidradev.ghidraprojectcreator.utils.PyDevUtils.ProjectPythonInterpreter;
/** /**
* Utility methods for interacting with PyDev. * 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 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. * @throws NoSuchMethodError if PyDev is not installed or it does not support this operation.
*/ */
public static List<String> getJython27InterpreterNames() public static List<String> getPyGhidraInterpreterNames(File requiredFileMatch)
throws NoClassDefFoundError, NoSuchMethodError {
List<String> interpreters = new ArrayList<>();
IInterpreterManager iMan = InterpreterManagersAPI.getPythonInterpreterManager(true);
for (IInterpreterInfo info : iMan.getInterpreterInfos()) {
ISystemModulesManager modulesManager = info.getModulesManager();
if (info.getInterpreterType() == IPythonNature.INTERPRETER_TYPE_PYTHON &&
!modulesManager.getAllModulesStartingWith("pyghidra.__main__").isEmpty()) {
if (requiredFileMatch == null ||
requiredFileMatch.getAbsolutePath().equals(info.getExecutableOrJar())) {
interpreters.add(info.getName());
}
}
}
return interpreters;
}
/**
* Gets a list of discovered Jython interpreter names.
*
* @return a list of discovered Jython interpreter names.
* @throws NoClassDefFoundError if PyDev is not installed or it does not support this operation.
* @throws NoSuchMethodError if PyDev is not installed or it does not support this operation.
*/
public static List<String> getJythonInterpreterNames()
throws NoClassDefFoundError, NoSuchMethodError { throws NoClassDefFoundError, NoSuchMethodError {
List<String> interpreters = new ArrayList<>(); List<String> interpreters = new ArrayList<>();
IInterpreterManager iMan = InterpreterManagersAPI.getJythonInterpreterManager(true); IInterpreterManager iMan = InterpreterManagersAPI.getJythonInterpreterManager(true);
for (IInterpreterInfo info : iMan.getInterpreterInfos()) { 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()); interpreters.add(info.getName());
} }
} }
@ -83,6 +116,38 @@ class PyDevUtilsInternal {
return interpreters; 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. * Adds the given Jython interpreter to PyDev.
* *
@ -119,26 +184,38 @@ class PyDevUtilsInternal {
* *
* @param javaProject The Java project to setup Python for. * @param javaProject The Java project to setup Python for.
* @param classpathEntries The classpath entries to add to the Python path. * @param classpathEntries The classpath entries to add to the Python path.
* @param jythonInterpreterName The name of the Jython interpreter to use for Python support. * @param pythonInterpreter The Python interpreter to use.
* If this is null, Python support will be removed from the project.
* @param monitor The progress monitor used during link. * @param monitor The progress monitor used during link.
* @throws CoreException If there was an Eclipse-related problem with enabling Python for the project. * @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 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. * @throws NoSuchMethodError if PyDev is not installed or it does not support this operation.
*/ */
public static void setupPythonForProject(IJavaProject javaProject, public static void setupPythonForProject(IJavaProject javaProject,
List<IClasspathEntry> classpathEntries, String jythonInterpreterName, List<IClasspathEntry> classpathEntries, ProjectPythonInterpreter pythonInterpreter,
IProgressMonitor monitor) IProgressMonitor monitor)
throws CoreException, NoClassDefFoundError, NoSuchMethodError { throws CoreException, NoClassDefFoundError, NoSuchMethodError {
PythonNature.removeNature(javaProject.getProject(), monitor); PythonNature.removeNature(javaProject.getProject(), monitor);
if (jythonInterpreterName != null) { String version;
String libs = classpathEntries.stream().map(e -> e.getPath().toOSString()).collect( String libs;
Collectors.joining("|")); switch (pythonInterpreter.type()) {
PythonNature.addNature(javaProject.getProject(), monitor, case PYGHIDRA:
IPythonNature.JYTHON_VERSION_2_7, null, libs, jythonInterpreterName, null); 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(); 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 { private PyDevUtilsInternal() throws NoClassDefFoundError {
// Prevent instantiation // Prevent instantiation
} }

View file

@ -36,6 +36,7 @@ import ghidra.GhidraApplicationLayout;
import ghidradev.EclipseMessageUtils; import ghidradev.EclipseMessageUtils;
import ghidradev.ghidraprojectcreator.utils.GhidraModuleUtils; import ghidradev.ghidraprojectcreator.utils.GhidraModuleUtils;
import ghidradev.ghidraprojectcreator.utils.GhidraModuleUtils.ModuleTemplateType; import ghidradev.ghidraprojectcreator.utils.GhidraModuleUtils.ModuleTemplateType;
import ghidradev.ghidraprojectcreator.utils.PyDevUtils.ProjectPythonInterpreter;
import ghidradev.ghidraprojectcreator.wizards.pages.*; import ghidradev.ghidraprojectcreator.wizards.pages.*;
import utilities.util.FileUtilities; import utilities.util.FileUtilities;
@ -87,12 +88,12 @@ public class CreateGhidraModuleProjectWizard extends Wizard implements INewWizar
boolean createRunConfig = projectPage.shouldCreateRunConfig(); boolean createRunConfig = projectPage.shouldCreateRunConfig();
String runConfigMemory = projectPage.getRunConfigMemory(); String runConfigMemory = projectPage.getRunConfigMemory();
File projectDir = projectPage.getProjectDir(); File projectDir = projectPage.getProjectDir();
String jythonInterpreterName = pythonPage.getJythonInterpreterName(); ProjectPythonInterpreter pythonInterpreter = pythonPage.getProjectPythonInterpreter();
Set<ModuleTemplateType> moduleTemplateTypes = projectConfigPage.getModuleTemplateTypes(); Set<ModuleTemplateType> moduleTemplateTypes = projectConfigPage.getModuleTemplateTypes();
try { try {
getContainer().run(true, false, getContainer().run(true, false,
monitor -> create(ghidraInstallDir, projectName, projectDir, createRunConfig, monitor -> create(ghidraInstallDir, projectName, projectDir, createRunConfig,
runConfigMemory, moduleTemplateTypes, jythonInterpreterName, monitor)); runConfigMemory, moduleTemplateTypes, pythonInterpreter, monitor));
} }
catch (InterruptedException e) { catch (InterruptedException e) {
Thread.currentThread().interrupt(); 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 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 runConfigMemory The run configuration's desired memory. Could be null.
* @param moduleTemplateTypes The desired module template types. * @param moduleTemplateTypes The desired module template types.
* @param jythonInterpreterName The name of the Jython interpreter to use for Python support. * @param pythonInterpreter The Python interpreter to use.
* Could be null if Python support is not wanted.
* @param monitor The monitor to use during project creation. * @param monitor The monitor to use during project creation.
* @throws InvocationTargetException if an error occurred during project creation. * @throws InvocationTargetException if an error occurred during project creation.
*/ */
private void create(File ghidraInstallDir, String projectName, File projectDir, private void create(File ghidraInstallDir, String projectName, File projectDir,
boolean createRunConfig, String runConfigMemory, boolean createRunConfig, String runConfigMemory,
Set<ModuleTemplateType> moduleTemplateTypes, String jythonInterpreterName, Set<ModuleTemplateType> moduleTemplateTypes, ProjectPythonInterpreter pythonInterpreter,
IProgressMonitor monitor) throws InvocationTargetException { IProgressMonitor monitor) throws InvocationTargetException {
try { try {
info("Creating " + projectName + " at " + projectDir); info("Creating " + projectName + " at " + projectDir);
@ -133,7 +133,7 @@ public class CreateGhidraModuleProjectWizard extends Wizard implements INewWizar
IJavaProject javaProject = IJavaProject javaProject =
GhidraModuleUtils.createGhidraModuleProject(projectName, projectDir, GhidraModuleUtils.createGhidraModuleProject(projectName, projectDir,
createRunConfig, runConfigMemory, ghidraLayout, jythonInterpreterName, monitor); createRunConfig, runConfigMemory, ghidraLayout, pythonInterpreter, monitor);
monitor.worked(1); monitor.worked(1);
IFile sourceFile = GhidraModuleUtils.configureModuleSource(javaProject, IFile sourceFile = GhidraModuleUtils.configureModuleSource(javaProject,

View file

@ -32,6 +32,7 @@ import org.eclipse.ui.IWorkbench;
import ghidra.GhidraApplicationLayout; import ghidra.GhidraApplicationLayout;
import ghidradev.EclipseMessageUtils; import ghidradev.EclipseMessageUtils;
import ghidradev.ghidraprojectcreator.utils.GhidraScriptUtils; import ghidradev.ghidraprojectcreator.utils.GhidraScriptUtils;
import ghidradev.ghidraprojectcreator.utils.PyDevUtils.ProjectPythonInterpreter;
import ghidradev.ghidraprojectcreator.wizards.pages.*; import ghidradev.ghidraprojectcreator.wizards.pages.*;
import utilities.util.FileUtilities; import utilities.util.FileUtilities;
@ -81,11 +82,11 @@ public class CreateGhidraScriptProjectWizard extends Wizard implements INewWizar
String runConfigMemory = projectPage.getRunConfigMemory(); String runConfigMemory = projectPage.getRunConfigMemory();
boolean linkUserScripts = projectConfigPage.shouldLinkUsersScripts(); boolean linkUserScripts = projectConfigPage.shouldLinkUsersScripts();
boolean linkSystemScripts = projectConfigPage.shouldLinkSystemScripts(); boolean linkSystemScripts = projectConfigPage.shouldLinkSystemScripts();
String jythonInterpreterName = pythonPage.getJythonInterpreterName(); ProjectPythonInterpreter pythonInterpreter = pythonPage.getProjectPythonInterpreter();
try { try {
getContainer().run(true, false, getContainer().run(true, false,
monitor -> create(ghidraInstallDir, projectName, projectDir, createRunConfig, monitor -> create(ghidraInstallDir, projectName, projectDir, createRunConfig,
runConfigMemory, linkUserScripts, linkSystemScripts, jythonInterpreterName, runConfigMemory, linkUserScripts, linkSystemScripts, pythonInterpreter,
monitor)); monitor));
} }
catch (InterruptedException e) { 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 runConfigMemory The run configuration's desired memory. Could be null.
* @param linkUserScripts Whether or not to link in the user scripts directory. * @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 linkSystemScripts Whether or not to link in the system scripts directories.
* @param jythonInterpreterName The name of the Jython interpreter to use for Python support. * @param pythonInterpreter The Python interpreter to use.
* Could be null if Python support is not wanted.
* @param monitor The monitor to use during project creation. * @param monitor The monitor to use during project creation.
* @throws InvocationTargetException if an error occurred during project creation. * @throws InvocationTargetException if an error occurred during project creation.
*/ */
private void create(File ghidraInstallDir, String projectName, File projectDir, private void create(File ghidraInstallDir, String projectName, File projectDir,
boolean createRunConfig, String runConfigMemory, boolean linkUserScripts, boolean createRunConfig, String runConfigMemory, boolean linkUserScripts,
boolean linkSystemScripts, String jythonInterpreterName, IProgressMonitor monitor) boolean linkSystemScripts, ProjectPythonInterpreter pythonInterpreter,
throws InvocationTargetException { IProgressMonitor monitor) throws InvocationTargetException {
try { try {
info("Creating " + projectName + " at " + projectDir); info("Creating " + projectName + " at " + projectDir);
monitor.beginTask("Creating " + projectName, 2); monitor.beginTask("Creating " + projectName, 2);
@ -128,7 +128,7 @@ public class CreateGhidraScriptProjectWizard extends Wizard implements INewWizar
GhidraScriptUtils.createGhidraScriptProject(projectName, projectDir, createRunConfig, GhidraScriptUtils.createGhidraScriptProject(projectName, projectDir, createRunConfig,
runConfigMemory, linkUserScripts, linkSystemScripts, ghidraLayout, runConfigMemory, linkUserScripts, linkSystemScripts, ghidraLayout,
jythonInterpreterName, monitor); pythonInterpreter, monitor);
monitor.worked(1); monitor.worked(1);
info("Finished creating " + projectName); info("Finished creating " + projectName);

View file

@ -40,7 +40,7 @@ import org.eclipse.ui.INewWizard;
import org.eclipse.ui.IWorkbench; import org.eclipse.ui.IWorkbench;
import ghidra.GhidraApplicationLayout; import ghidra.GhidraApplicationLayout;
import ghidra.launch.JavaConfig; import ghidra.launch.AppConfig;
import ghidradev.EclipseMessageUtils; import ghidradev.EclipseMessageUtils;
import ghidradev.ghidraprojectcreator.utils.GhidraProjectUtils; import ghidradev.ghidraprojectcreator.utils.GhidraProjectUtils;
import ghidradev.ghidraprojectcreator.wizards.pages.ChooseGhidraModuleProjectWizardPage; 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 // 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. // saved Java home can change from launch to launch.
GhidraApplicationLayout ghidraLayout = new GhidraApplicationLayout(new File(ghidraInstallDirPath)); GhidraApplicationLayout ghidraLayout = new GhidraApplicationLayout(new File(ghidraInstallDirPath));
File javaHomeDir = new JavaConfig( File javaHomeDir = new AppConfig(
ghidraLayout.getApplicationInstallationDir().getFile(false)).getSavedJavaHome(); ghidraLayout.getApplicationInstallationDir().getFile(false)).getSavedJavaHome();
if(javaHomeDir == null) { if(javaHomeDir == null) {
throw new IOException("Failed to get the Java home associated with the project. " + throw new IOException("Failed to get the Java home associated with the project. " +

View file

@ -32,6 +32,7 @@ import org.eclipse.ui.IWorkbench;
import ghidra.GhidraApplicationLayout; import ghidra.GhidraApplicationLayout;
import ghidradev.EclipseMessageUtils; import ghidradev.EclipseMessageUtils;
import ghidradev.ghidraprojectcreator.utils.GhidraModuleUtils; import ghidradev.ghidraprojectcreator.utils.GhidraModuleUtils;
import ghidradev.ghidraprojectcreator.utils.PyDevUtils.ProjectPythonInterpreter;
import ghidradev.ghidraprojectcreator.wizards.pages.*; import ghidradev.ghidraprojectcreator.wizards.pages.*;
import utilities.util.FileUtilities; import utilities.util.FileUtilities;
@ -76,11 +77,11 @@ public class ImportGhidraModuleSourceWizard extends Wizard implements IImportWiz
String projectName = projectPage.getProjectName(); String projectName = projectPage.getProjectName();
boolean createRunConfig = projectPage.shouldCreateRunConfig(); boolean createRunConfig = projectPage.shouldCreateRunConfig();
String runConfigMemory = projectPage.getRunConfigMemory(); String runConfigMemory = projectPage.getRunConfigMemory();
String jythonInterpreterName = pythonPage.getJythonInterpreterName(); ProjectPythonInterpreter pythonInterpreter = pythonPage.getProjectPythonInterpreter();
try { try {
getContainer().run(true, false, getContainer().run(true, false,
monitor -> importModuleSource(ghidraInstallDir, projectName, moduleSourceDir, monitor -> importModuleSource(ghidraInstallDir, projectName, moduleSourceDir,
createRunConfig, runConfigMemory, jythonInterpreterName, monitor)); createRunConfig, runConfigMemory, pythonInterpreter, monitor));
} }
catch (InterruptedException e) { catch (InterruptedException e) {
Thread.currentThread().interrupt(); Thread.currentThread().interrupt();
@ -102,14 +103,14 @@ public class ImportGhidraModuleSourceWizard extends Wizard implements IImportWiz
* @param moduleSourceDir The module source directory to import. * @param moduleSourceDir The module source directory to import.
* @param createRunConfig Whether or not to create a new run configuration for the project. * @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 runConfigMemory The run configuration's desired memory. Could be null.
* @param jythonInterpreterName The name of the Jython interpreter to use for Python support. * @param pythonInterpreter The Python interpreter to use.
* Could be null if Python support is not wanted.
* @param monitor The monitor to use during project creation. * @param monitor The monitor to use during project creation.
* @throws InvocationTargetException if an error occurred during project creation. * @throws InvocationTargetException if an error occurred during project creation.
*/ */
private void importModuleSource(File ghidraInstallDir, String projectName, File moduleSourceDir, private void importModuleSource(File ghidraInstallDir, String projectName, File moduleSourceDir,
boolean createRunConfig, String runConfigMemory, String jythonInterpreterName, boolean createRunConfig, String runConfigMemory,
IProgressMonitor monitor) throws InvocationTargetException { ProjectPythonInterpreter pythonInterpreter, IProgressMonitor monitor)
throws InvocationTargetException {
try { try {
info("Importing " + projectName + " at " + moduleSourceDir); info("Importing " + projectName + " at " + moduleSourceDir);
monitor.beginTask("Importing " + projectName, 2); monitor.beginTask("Importing " + projectName, 2);
@ -118,7 +119,7 @@ public class ImportGhidraModuleSourceWizard extends Wizard implements IImportWiz
monitor.worked(1); monitor.worked(1);
GhidraModuleUtils.importGhidraModuleSource(projectName, moduleSourceDir, GhidraModuleUtils.importGhidraModuleSource(projectName, moduleSourceDir,
createRunConfig, runConfigMemory, ghidraLayout, jythonInterpreterName, monitor); createRunConfig, runConfigMemory, ghidraLayout, pythonInterpreter, monitor);
monitor.worked(1); monitor.worked(1);
info("Finished importing " + projectName); info("Finished importing " + projectName);

View file

@ -31,8 +31,9 @@ import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.wizard.Wizard; import org.eclipse.jface.wizard.Wizard;
import ghidra.GhidraApplicationLayout; import ghidra.GhidraApplicationLayout;
import ghidra.launch.JavaConfig; import ghidra.launch.AppConfig;
import ghidradev.ghidraprojectcreator.utils.GhidraProjectUtils; import ghidradev.ghidraprojectcreator.utils.GhidraProjectUtils;
import ghidradev.ghidraprojectcreator.utils.PyDevUtils.ProjectPythonInterpreter;
import ghidradev.ghidraprojectcreator.wizards.pages.*; import ghidradev.ghidraprojectcreator.wizards.pages.*;
/** /**
@ -63,10 +64,10 @@ public class LinkGhidraWizard extends Wizard {
public boolean performFinish() { public boolean performFinish() {
File ghidraInstallDir = ghidraInstallationPage.getGhidraInstallDir(); File ghidraInstallDir = ghidraInstallationPage.getGhidraInstallDir();
IJavaProject javaProject = projectPage.getJavaProject(); IJavaProject javaProject = projectPage.getJavaProject();
String jythonInterpreterName = pythonPage.getJythonInterpreterName(); ProjectPythonInterpreter pythonInterpreter = pythonPage.getProjectPythonInterpreter();
try { try {
getContainer().run(true, false, getContainer().run(true, false,
monitor -> link(ghidraInstallDir, javaProject, jythonInterpreterName, monitor)); monitor -> link(ghidraInstallDir, javaProject, pythonInterpreter, monitor));
} }
catch (InterruptedException e) { catch (InterruptedException e) {
Thread.currentThread().interrupt(); Thread.currentThread().interrupt();
@ -85,23 +86,23 @@ public class LinkGhidraWizard extends Wizard {
* *
* @param ghidraInstallDir The Ghidra installation directory to use. * @param ghidraInstallDir The Ghidra installation directory to use.
* @param javaProject The Java project to link. * @param javaProject The Java project to link.
* @param jythonInterpreterName The name of the Jython interpreter to use for Python support. * @param pythonInterpreter The Python interpreter to use.
* Could be null if Python support is not wanted.
* @param monitor The monitor to use during project link. * @param monitor The monitor to use during project link.
* @throws InvocationTargetException if an error occurred during link. * @throws InvocationTargetException if an error occurred during link.
*/ */
private void link(File ghidraInstallDir, IJavaProject javaProject, String jythonInterpreterName, private void link(File ghidraInstallDir, IJavaProject javaProject,
IProgressMonitor monitor) throws InvocationTargetException { ProjectPythonInterpreter pythonInterpreter, IProgressMonitor monitor)
throws InvocationTargetException {
IProject project = javaProject.getProject(); IProject project = javaProject.getProject();
try { try {
info("Linking " + project.getName()); info("Linking " + project.getName());
monitor.beginTask("Linking " + project.getName(), 2); monitor.beginTask("Linking " + project.getName(), 2);
GhidraApplicationLayout ghidraLayout = new GhidraApplicationLayout(ghidraInstallDir); GhidraApplicationLayout ghidraLayout = new GhidraApplicationLayout(ghidraInstallDir);
JavaConfig javaConfig = AppConfig appConfig =
new JavaConfig(ghidraLayout.getApplicationInstallationDir().getFile(false)); new AppConfig(ghidraLayout.getApplicationInstallationDir().getFile(false));
GhidraProjectUtils.linkGhidraToProject(javaProject, ghidraLayout, javaConfig, GhidraProjectUtils.linkGhidraToProject(javaProject, ghidraLayout, appConfig,
jythonInterpreterName, monitor); pythonInterpreter, monitor);
monitor.worked(1); monitor.worked(1);
project.refreshLocal(IResource.DEPTH_INFINITE, monitor); project.refreshLocal(IResource.DEPTH_INFINITE, monitor);

View file

@ -27,7 +27,7 @@ import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.*; import org.eclipse.swt.widgets.*;
import org.eclipse.ui.dialogs.PreferencesUtil; import org.eclipse.ui.dialogs.PreferencesUtil;
import ghidra.launch.JavaConfig; import ghidra.launch.AppConfig;
import ghidra.launch.JavaFinder.JavaFilter; import ghidra.launch.JavaFinder.JavaFilter;
import ghidradev.ghidraprojectcreator.preferences.GhidraProjectCreatorPreferencePage; import ghidradev.ghidraprojectcreator.preferences.GhidraProjectCreatorPreferencePage;
import ghidradev.ghidraprojectcreator.preferences.GhidraProjectCreatorPreferences; import ghidradev.ghidraprojectcreator.preferences.GhidraProjectCreatorPreferences;
@ -108,8 +108,8 @@ public class ChooseGhidraInstallationWizardPage extends WizardPage {
File ghidraInstallDir = new File(ghidraInstallDirCombo.getText()); File ghidraInstallDir = new File(ghidraInstallDirCombo.getText());
GhidraProjectCreatorPreferencePage.validateGhidraInstallation(ghidraInstallDir); GhidraProjectCreatorPreferencePage.validateGhidraInstallation(ghidraInstallDir);
try { try {
JavaConfig javaConfig = new JavaConfig(ghidraInstallDir); AppConfig appConfig = new AppConfig(ghidraInstallDir);
if (!javaConfig.isSupportedJavaHomeDir(javaConfig.getSavedJavaHome(), if (!appConfig.isSupportedJavaHomeDir(appConfig.getSavedJavaHome(),
JavaFilter.JDK_ONLY)) { JavaFilter.JDK_ONLY)) {
message = "A supported JDK is not associated with this Ghidra " + message = "A supported JDK is not associated with this Ghidra " +
"installation. Please run this Ghidra and try again."; "installation. Please run this Ghidra and try again.";

View file

@ -15,8 +15,7 @@
*/ */
package ghidradev.ghidraprojectcreator.wizards.pages; package ghidradev.ghidraprojectcreator.wizards.pages;
import java.io.File; import java.io.*;
import java.io.IOException;
import java.nio.file.Files; import java.nio.file.Files;
import java.util.List; import java.util.List;
@ -27,13 +26,15 @@ import org.eclipse.jface.wizard.WizardPage;
import org.eclipse.swt.SWT; import org.eclipse.swt.SWT;
import org.eclipse.swt.events.SelectionEvent; import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.SelectionListener; import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.layout.GridData; import org.eclipse.swt.layout.*;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.*; import org.eclipse.swt.widgets.*;
import org.eclipse.ui.dialogs.PreferencesUtil; import org.eclipse.ui.dialogs.PreferencesUtil;
import ghidra.launch.AppConfig;
import ghidradev.EclipseMessageUtils; import ghidradev.EclipseMessageUtils;
import ghidradev.ghidraprojectcreator.utils.PyDevUtils; 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. * 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 { public class EnablePythonWizardPage extends WizardPage {
private ChooseGhidraInstallationWizardPage ghidraInstallationPage; 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 Combo jythonCombo;
private Button addJythonButton; private Button addJythonButton;
@ -61,46 +66,107 @@ public class EnablePythonWizardPage extends WizardPage {
public void createControl(Composite parent) { public void createControl(Composite parent) {
Composite container = new Composite(parent, SWT.NULL); Composite container = new Composite(parent, SWT.NULL);
container.setLayout(new GridLayout(3, false)); container.setLayout(new GridLayout(1, false));
// Enable Python checkbox. // Project type selection
enablePythonCheckboxButton = new Button(container, SWT.CHECK); SelectionListener projectTypeSelectionListener = new SelectionListener() {
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() {
@Override @Override
public void widgetSelected(SelectionEvent evt) { public void widgetSelected(SelectionEvent evt) {
validate(); validate(null);
} }
@Override @Override
public void widgetDefaultSelected(SelectionEvent evt) { 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 // Jython interpreter combo box
Label jythonLabel = new Label(container, SWT.NULL); Label jythonLabel = new Label(interpreterContainer, SWT.NULL);
jythonLabel.setText("Jython interpreter:"); 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.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
jythonCombo.setToolTipText("The wizard requires a Jython interpreter to be " + jythonCombo.setToolTipText("The wizard requires a Jython interpreter to be " +
"selected. Click the + button to add or manage Jython interpreters."); "selected. Click the + button to add or manage Jython interpreters.");
populateJythonCombo(); populateJythonCombo();
jythonCombo.addModifyListener(evt -> validate()); jythonCombo.addModifyListener(evt -> validate(null));
// Jython interpreter add button // Jython interpreter add button
addJythonButton = new Button(container, SWT.BUTTON1); addJythonButton = new Button(interpreterContainer, SWT.BUTTON1);
addJythonButton.setText("+"); addJythonButton.setText("+");
addJythonButton.setToolTipText("Adds/manages Jython interpreters."); addJythonButton.setToolTipText("Adds/manages Jython interpreters.");
addJythonButton.addListener(SWT.Selection, evt -> { addJythonButton.addListener(SWT.Selection, evt -> {
try { try {
if (PyDevUtils.getJython27InterpreterNames().isEmpty()) { if (PyDevUtils.getJythonInterpreterNames().isEmpty()) {
File ghidraDir = ghidraInstallationPage.getGhidraInstallDir(); File ghidraDir = ghidraInstallationPage.getGhidraInstallDir();
File jythonFile = findJythonInterpreter(ghidraDir); File jythonFile = findJythonInterpreter(ghidraDir);
File jythonLib = findJythonLibrary(ghidraDir); File jythonLib = findJythonLibrary(ghidraDir);
@ -111,7 +177,7 @@ public class EnablePythonWizardPage extends WizardPage {
PyDevUtils.addJythonInterpreter("jython_" + ghidraDir.getName(), PyDevUtils.addJythonInterpreter("jython_" + ghidraDir.getName(),
jythonFile, jythonLib); jythonFile, jythonLib);
populateJythonCombo(); populateJythonCombo();
validate(); validate(null);
return; return;
} }
} }
@ -124,80 +190,142 @@ public class EnablePythonWizardPage extends WizardPage {
PyDevUtils.getJythonPreferencePageId(), null, null); PyDevUtils.getJythonPreferencePageId(), null, null);
dialog.open(); dialog.open();
populateJythonCombo(); populateJythonCombo();
validate(); validate(null);
}); });
validate(); validate(savedPyGhidraInterpreter);
setControl(container); setControl(container);
} }
/** @Override
* Checks whether or not Python should be enabled. public void setVisible(boolean visible) {
* if (visible) {
* @return True if python should be enabled; otherwise, false. validate(populatePyGhidraCombo(null));
*/ }
public boolean shouldEnablePython() { super.setVisible(visible);
return enablePythonCheckboxButton.getSelection();
} }
/** /**
* Gets the name of the Jython interpreter to use. * {@return the project Python interpreter to use}
*
* @return The name of the Jython interpreter to use. Could be null of Python isn't
* enabled.
*/ */
public String getJythonInterpreterName() { public ProjectPythonInterpreter getProjectPythonInterpreter() {
if (enablePythonCheckboxButton.getSelection()) { if (pyghidraButton.getSelection()) {
return jythonCombo.getText(); 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. * Validates the fields on the page and updates the page's status.
* Should be called every time a field on the page changes. * 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; String message = null;
boolean pyDevInstalled = PyDevUtils.isSupportedPyDevInstalled(); boolean pyghidraSupported = PyDevUtils.isSupportedPyGhidraPyDevInstalled();
boolean pyDevEnabled = enablePythonCheckboxButton.getSelection(); boolean jythonSupported = PyDevUtils.isSupportedJythonPyDevInstalled();
boolean comboEnabled = pyDevInstalled && pyDevEnabled;
if (pyDevEnabled) { if (pyghidraButton.getSelection()) {
if (!pyDevInstalled) { if (!pyghidraSupported) {
message = "PyDev version " + PyDevUtils.MIN_SUPPORTED_VERSION + message = "PyDev version " + PyDevUtils.MIN_SUPPORTED_VERSION +
" - " + PyDevUtils.MAX_SUPPORTED_VERSION + " is not installed."; " or later is not installed.";
} }
else { else {
try { try {
List<String> interpreters = PyDevUtils.getJython27InterpreterNames(); if (pyghidraInterpreter == null) {
if (interpreters.isEmpty()) { pyghidraInterpreter = findPyGhidraInterpreter();
message = "No Jython interpreters found. Click the + button to add one."; }
if (pyghidraInterpreter == null) {
message =
"Please first launch PyGhidra to associate the Ghidra installation with a supported version of Python.";
pyghidraSupported = false;
}
else {
List<String> interpreters =
PyDevUtils.getPyGhidraInterpreterNames(pyghidraInterpreter);
if (interpreters.isEmpty()) {
message ="No PyGhidra interpreters found. Click the + button or set project type to \"None\".";
}
} }
} }
catch (OperationNotSupportedException e) { catch (OperationNotSupportedException e) {
message = "PyDev version is not supported."; message = "PyDev version is not supported for Jython.";
comboEnabled = false; pyghidraSupported = false;
}
catch (Exception e) {
message =
"Failed to lookup Python interpreter associated with the Ghidra installation.";
pyghidraSupported = false;
}
}
}
else if (jythonButton.getSelection()) {
if (!jythonSupported) {
message = "PyDev version " + PyDevUtils.MIN_SUPPORTED_VERSION +
" - " + PyDevUtils.MAX_JYTHON_SUPPORTED_VERSION + " is not installed.";
}
else {
try {
List<String> interpreters = PyDevUtils.getJythonInterpreterNames();
if (interpreters.isEmpty()) {
message =
"No Jython interpreters found. Click the + button or set project type to \"None\".";
}
}
catch (OperationNotSupportedException e) {
message = "PyDev version is not supported for Jython.";
jythonSupported = false;
} }
} }
} }
jythonCombo.setEnabled(comboEnabled); pyghidraCombo.setEnabled(pyghidraButton.getSelection() && pyghidraSupported);
addJythonButton.setEnabled(comboEnabled); addPyGhidraButton.setEnabled(pyghidraButton.getSelection() && pyghidraSupported);
jythonCombo.setEnabled(jythonButton.getSelection() && jythonSupported);
addJythonButton.setEnabled(jythonButton.getSelection() && jythonSupported);
setErrorMessage(message); setErrorMessage(message);
setPageComplete(message == null); 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() { private void populateJythonCombo() {
jythonCombo.removeAll(); jythonCombo.removeAll();
try { try {
for (String jythonName : PyDevUtils.getJython27InterpreterNames()) { PyDevUtils.getJythonInterpreterNames().forEach(jythonCombo::add);
jythonCombo.add(jythonName);
}
} }
catch (OperationNotSupportedException e) { catch (OperationNotSupportedException e) {
// Nothing to do. Combo should and will be empty. // Nothing to do. Combo should and will be empty.
@ -207,6 +335,32 @@ public class EnablePythonWizardPage extends WizardPage {
} }
} }
/**
* Find's the Python interpreter file that was used to launch PyGhidra.
*
* @return The Python interpreter file that was used to launch PyGhidra, or null if one could
* not be found.
* @throws Exception if there was a problem finding the Python interpreter.
*/
private File findPyGhidraInterpreter() throws Exception {
File ghidraDir = ghidraInstallationPage.getGhidraInstallDir();
AppConfig appConfig = new AppConfig(ghidraDir);
List<String> cmd = appConfig.getSavedPythonCommand();
if (cmd == null) {
return null;
}
cmd.add("-c");
cmd.add("import sys; print(sys.executable)");
Process p = new ProcessBuilder(cmd).redirectErrorStream(true).start();
BufferedReader reader =
new BufferedReader(new InputStreamReader(p.getInputStream()));
String pythonExecutable = reader.readLine();
if (p.waitFor() == 0) {
return new File(pythonExecutable);
}
return null;
}
/** /**
* Find's a Jython interpreter file in the given Ghidra installation directory. * Find's a Jython interpreter file in the given Ghidra installation directory.
* *

View file

@ -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_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/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/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_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/icons/script_code_red.png||FAMFAMFAM Icons - CC 2.5|||famfamfam silk icon set|END|
GhidraDevPlugin/plugin.xml||GHIDRA||||END| GhidraDevPlugin/plugin.xml||GHIDRA||||END|

View file

@ -78,20 +78,20 @@ public class LaunchSupport {
try { try {
File installDir = new File(installDirPath).getCanonicalFile(); // change relative path to absolute 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(); JavaFinder javaFinder = JavaFinder.create();
// Pass control to a mode-specific handler // Pass control to a mode-specific handler
switch (mode.toLowerCase()) { switch (mode.toLowerCase()) {
case "-java_home": case "-java_home":
exitCode = handleJavaHome(javaConfig, javaFinder, JavaFilter.ANY, ask, save); exitCode = handleJavaHome(appConfig, javaFinder, JavaFilter.ANY, ask, save);
break; break;
case "-jdk_home": case "-jdk_home":
exitCode = exitCode =
handleJavaHome(javaConfig, javaFinder, JavaFilter.JDK_ONLY, ask, save); handleJavaHome(appConfig, javaFinder, JavaFilter.JDK_ONLY, ask, save);
break; break;
case "-vmargs": case "-vmargs":
exitCode = handleVmArgs(javaConfig); exitCode = handleVmArgs(appConfig);
break; break;
default: default:
System.err.println("LaunchSupport received illegal argument: " + mode); 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 * 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. * 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 javaFinder The Java finder.
* @param javaFilter A filter used to restrict what kind of Java installations we search for. * @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. * @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. * successfully determined.
* @throws IOException if there was a disk-related problem. * @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 { JavaFilter javaFilter, boolean ask, boolean save) throws IOException {
if (ask) { 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 * 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. * 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 javaFinder The Java finder.
* @param javaFilter A filter used to restrict what kind of Java installations we search for. * @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. * @param save True if the determined Java home directory should get saved to a file.
@ -141,19 +141,19 @@ public class LaunchSupport {
* successfully determined. * successfully determined.
* @throws IOException if there was a problem saving the java home to disk. * @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 { JavaFilter javaFilter, boolean save) throws IOException {
File javaHomeDir; File javaHomeDir;
LaunchProperties launchProperties = javaConfig.getLaunchProperties(); LaunchProperties launchProperties = appConfig.getLaunchProperties();
// PRIORITY 1: JAVA_HOME_OVERRIDE property // PRIORITY 1: JAVA_HOME_OVERRIDE property
// If a valid java home override is specified in the launch properties, use that. // If a valid java home override is specified in the launch properties, use that.
// Someone presumably wants to force that specific version. // Someone presumably wants to force that specific version.
javaHomeDir = launchProperties.getJavaHomeOverride(); javaHomeDir = launchProperties.getJavaHomeOverride();
if (javaConfig.isSupportedJavaHomeDir(javaHomeDir, javaFilter)) { if (appConfig.isSupportedJavaHomeDir(javaHomeDir, javaFilter)) {
if (save) { if (save) {
javaConfig.saveJavaHome(javaHomeDir); appConfig.saveJavaHome(javaHomeDir);
} }
System.out.println(javaHomeDir); System.out.println(javaHomeDir);
return EXIT_SUCCESS; return EXIT_SUCCESS;
@ -162,10 +162,10 @@ public class LaunchSupport {
// PRIORITY 2: Java on PATH // PRIORITY 2: Java on PATH
// This program (LaunchSupport) was started with the Java on the PATH. Try to use this one // 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. // 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 (javaHomeDir != null) {
if (save) { if (save) {
javaConfig.saveJavaHome(javaHomeDir); appConfig.saveJavaHome(javaHomeDir);
} }
System.out.println(javaHomeDir); System.out.println(javaHomeDir);
return EXIT_SUCCESS; return EXIT_SUCCESS;
@ -173,19 +173,19 @@ public class LaunchSupport {
// PRIORITY 3: Last used Java // PRIORITY 3: Last used Java
// Check to see if a prior launch resulted in that Java being saved. If so, try to use that. // Check to see if a prior launch resulted in that Java being saved. If so, try to use that.
javaHomeDir = javaConfig.getSavedJavaHome(); javaHomeDir = appConfig.getSavedJavaHome();
if (javaConfig.isSupportedJavaHomeDir(javaHomeDir, javaFilter)) { if (appConfig.isSupportedJavaHomeDir(javaHomeDir, javaFilter)) {
System.out.println(javaHomeDir); System.out.println(javaHomeDir);
return EXIT_SUCCESS; return EXIT_SUCCESS;
} }
// PRIORITY 4: Find all supported Java installations, and use the newest. // PRIORITY 4: Find all supported Java installations, and use the newest.
List<File> javaHomeDirs = List<File> javaHomeDirs =
javaFinder.findSupportedJavaHomeFromInstallations(javaConfig, javaFilter); javaFinder.findSupportedJavaHomeFromInstallations(appConfig, javaFilter);
if (!javaHomeDirs.isEmpty()) { if (!javaHomeDirs.isEmpty()) {
javaHomeDir = javaHomeDirs.iterator().next(); javaHomeDir = javaHomeDirs.iterator().next();
if (save) { if (save) {
javaConfig.saveJavaHome(javaHomeDir); appConfig.saveJavaHome(javaHomeDir);
} }
System.out.println(javaHomeDir); System.out.println(javaHomeDir);
return EXIT_SUCCESS; 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 * 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. * 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 javaFinder The Java finder.
* @param javaFilter A filter used to restrict what kind of Java installations we search for. * @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 * * @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 * @throws IOException if there was a problem interacting with the user, or saving the java
* home location to disk. * home location to disk.
*/ */
private static int askJavaHome(JavaConfig javaConfig, JavaFinder javaFinder, private static int askJavaHome(AppConfig appConfig, JavaFinder javaFinder,
JavaFilter javaFilter) throws IOException { JavaFilter javaFilter) throws IOException {
String javaName = javaFilter.equals(JavaFilter.JDK_ONLY) ? "JDK" : "Java"; String javaName = javaFilter.equals(JavaFilter.JDK_ONLY) ? "JDK" : "Java";
String javaRange; String javaRange;
int min = javaConfig.getMinSupportedJava(); int min = appConfig.getMinSupportedJava();
int max = javaConfig.getMaxSupportedJava(); int max = appConfig.getMaxSupportedJava();
if (min == max) { if (min == max) {
javaRange = min + ""; javaRange = min + "";
} }
@ -226,7 +226,7 @@ public class LaunchSupport {
System.out.println("******************************************************************"); System.out.println("******************************************************************");
System.out.println( System.out.println(
javaName + " " + javaRange + " (" + javaConfig.getSupportedArchitecture() + javaName + " " + javaRange + " (" + appConfig.getSupportedArchitecture() +
"-bit) could not be found and must be manually chosen!"); "-bit) could not be found and must be manually chosen!");
System.out.println("******************************************************************"); System.out.println("******************************************************************");
@ -254,13 +254,13 @@ public class LaunchSupport {
continue; continue;
} }
try { try {
JavaVersion javaVersion = javaConfig.getJavaVersion(javaHomeDir, javaFilter); JavaVersion javaVersion = appConfig.getJavaVersion(javaHomeDir, javaFilter);
if (javaConfig.isJavaVersionSupported(javaVersion)) { if (appConfig.isJavaVersionSupported(javaVersion)) {
break; break;
} }
System.out.println( System.out.println(
"Java version " + javaVersion + " is outside of supported range: [" + "Java version " + javaVersion + " is outside of supported range: [" +
javaRange + " " + javaConfig.getSupportedArchitecture() + "-bit]"); javaRange + " " + appConfig.getSupportedArchitecture() + "-bit]");
} }
catch (FileNotFoundException e) { catch (FileNotFoundException e) {
System.out.println( 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); System.out.println("Saved changes to " + javaHomeSaveFile);
return EXIT_SUCCESS; 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, * 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. * 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 * @return A suggested exit code based on whether or not the VM arguments were successfully
* gotten. * gotten.
*/ */
private static int handleVmArgs(JavaConfig javaConfig) { private static int handleVmArgs(AppConfig appConfig) {
if (javaConfig.getLaunchProperties() == null) { if (appConfig.getLaunchProperties() == null) {
System.out.println("Launch properties file was not specified!"); System.out.println("Launch properties file was not specified!");
return EXIT_FAILURE; return EXIT_FAILURE;
} }
// Force newline style to make cross-platform parsing consistent // 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; return EXIT_SUCCESS;
} }
} }

View file

@ -17,21 +17,23 @@ package ghidra.launch;
import java.io.*; import java.io.*;
import java.text.ParseException; import java.text.ParseException;
import java.util.Properties; import java.util.*;
import ghidra.launch.JavaFinder.JavaFilter; import ghidra.launch.JavaFinder.JavaFilter;
/** /**
* Class to determine and represent a required Java configuration, including minimum and maximum * Class to determine and represent a required application configuration, including minimum and
* supported versions, compiler compliance level, etc. * 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 LAUNCH_PROPERTIES_NAME = "launch.properties";
private static final String JAVA_HOME_SAVE_NAME = "java_home.save"; 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 LaunchProperties launchProperties;
private File javaHomeSaveFile; private File javaHomeSaveFile;
private File pythonCommandSaveFile;
private String applicationName; // example: Ghidra private String applicationName; // example: Ghidra
private String applicationVersion; // example: 9.0.1 private String applicationVersion; // example: 9.0.1
@ -42,43 +44,43 @@ public class JavaConfig {
private String compilerComplianceLevel; 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. * @param installDir The installation directory.
* @throws FileNotFoundException if a required file was not found. * @throws FileNotFoundException if a required file was not found.
* @throws IOException if there was a problem reading a required file. * @throws IOException if there was a problem reading a required file.
* @throws ParseException if there was a problem parsing 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); initApplicationProperties(installDir);
initLaunchProperties(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 * Gets the launch properties associated with this application configuration.
* Java configuration are stored in the launch properties.
* *
* @return The launch properties associated with this Java configuration. Could be null if * @return The launch properties associated with this application configuration. Could be null
* this Java configuration does not use launch properties. * if this application configuration does not use launch properties.
*/ */
public LaunchProperties getLaunchProperties() { public LaunchProperties getLaunchProperties() {
return launchProperties; 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() { public int getMinSupportedJava() {
return minSupportedJava; 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. * restriction, the value will be 0.
*/ */
public int getMaxSupportedJava() { 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 <code>64</code>. * configurations must have an architecture of <code>64</code>.
* *
* @return The Java configuration's supported Java architecture (64). * @return The application configuration's supported Java architecture (64).
*/ */
public int getSupportedArchitecture() { public int getSupportedArchitecture() {
return 64; return 64;
} }
/** /**
* Gets the Java configuration's compiler compliance level that was used to build the * Gets the application configuration's Java compiler compliance level that was used to build
* associated installation. * the associated installation.
* *
* @return The Java configuration's compiler compliance level. * @return The application configuration's compiler compliance level.
*/ */
public String getCompilerComplianceLevel() { public String getCompilerComplianceLevel() {
return compilerComplianceLevel; return compilerComplianceLevel;
@ -115,8 +117,11 @@ public class JavaConfig {
public File getSavedJavaHome() throws IOException { public File getSavedJavaHome() throws IOException {
try (BufferedReader reader = new BufferedReader(new FileReader(javaHomeSaveFile))) { try (BufferedReader reader = new BufferedReader(new FileReader(javaHomeSaveFile))) {
String line = reader.readLine().trim(); String line = reader.readLine().trim();
if (line != null && !line.isEmpty()) { if (line != null) {
return new File(line); line = line.trim();
if (!line.isEmpty()) {
return new File(line);
}
} }
} }
catch (FileNotFoundException e) { 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. * configuration.
* *
* @param dir The directory to test. * @param dir The directory to test.
* @param javaFilter A filter used to restrict what kind of Java installations we support. * @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. * configuration.
*/ */
public boolean isSupportedJavaHomeDir(File dir, JavaFilter javaFilter) { 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. * @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) { public boolean isJavaVersionSupported(JavaVersion javaVersion) {
if (javaVersion.getArchitecture() != getSupportedArchitecture()) { if (javaVersion.getArchitecture() != getSupportedArchitecture()) {
@ -233,6 +238,30 @@ public class JavaConfig {
return runAndGetJavaVersion(javaExecutable); 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<String> getSavedPythonCommand() throws IOException {
List<String> 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". * 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 * @param installDir The Ghidra installation directory. This is the directory that has the
* "Ghidra" subdirectory in it. * "Ghidra" subdirectory in it.
* @throws FileNotFoundException if the user's home directory was not found. * @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(); boolean isDev = new File(installDir, "build.gradle").isFile();
String appName = applicationName.replaceAll("\\s", "").toLowerCase(); String appName = applicationName.replaceAll("\\s", "").toLowerCase();
@ -385,16 +414,14 @@ public class JavaConfig {
// Handle legacy application layout // Handle legacy application layout
if (applicationLayoutVersion.equals("1")) { if (applicationLayoutVersion.equals("1")) {
userSettingsDir = new File(userHomeDir, "." + appName + "/." + userSettingsDirName); userSettingsDir = new File(userHomeDir, "." + appName + "/." + userSettingsDirName);
javaHomeSaveFile = new File(userSettingsDir, JAVA_HOME_SAVE_NAME); return new File(userSettingsDir, saveFileName);
return;
} }
// Look for XDG environment variable // Look for XDG environment variable
String xdgConfigHomeDirStr = System.getenv("XDG_CONFIG_HOME"); String xdgConfigHomeDirStr = System.getenv("XDG_CONFIG_HOME");
if (xdgConfigHomeDirStr != null && !xdgConfigHomeDirStr.isEmpty()) { if (xdgConfigHomeDirStr != null && !xdgConfigHomeDirStr.isEmpty()) {
userSettingsDir = new File(xdgConfigHomeDirStr, appName + "/" + userSettingsDirName); userSettingsDir = new File(xdgConfigHomeDirStr, appName + "/" + userSettingsDirName);
javaHomeSaveFile = new File(userSettingsDir, JAVA_HOME_SAVE_NAME); return new File(userSettingsDir, saveFileName);
return;
} }
// Look in current user settings directory // Look in current user settings directory
@ -420,7 +447,7 @@ public class JavaConfig {
"Failed to find the user settings directory: Unsupported operating system."); "Failed to find the user settings directory: Unsupported operating system.");
} }
javaHomeSaveFile = new File(userSettingsDir, JAVA_HOME_SAVE_NAME); return new File(userSettingsDir, saveFileName);
} }
/** /**

View file

@ -79,11 +79,11 @@ public abstract class JavaFinder {
* Returns a list of supported Java home directories from discovered Java installations. * Returns a list of supported Java home directories from discovered Java installations.
* The list is sorted from newest Java version to oldest. * 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. * @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. * @return A sorted list of supported Java home directories from discovered Java installations.
*/ */
public List<File> findSupportedJavaHomeFromInstallations(JavaConfig javaConfig, public List<File> findSupportedJavaHomeFromInstallations(AppConfig appConfig,
JavaFilter javaFilter) { JavaFilter javaFilter) {
Set<File> potentialJavaHomeSet = new TreeSet<>(); Set<File> potentialJavaHomeSet = new TreeSet<>();
for (File javaRootInstallDir : getJavaRootInstallDirs()) { for (File javaRootInstallDir : getJavaRootInstallDirs()) {
@ -107,8 +107,8 @@ public abstract class JavaFinder {
for (File potentialJavaHomeDir : potentialJavaHomeSet) { for (File potentialJavaHomeDir : potentialJavaHomeSet) {
try { try {
JavaVersion javaVersion = JavaVersion javaVersion =
javaConfig.getJavaVersion(potentialJavaHomeDir, javaFilter); appConfig.getJavaVersion(potentialJavaHomeDir, javaFilter);
if (javaConfig.isJavaVersionSupported(javaVersion)) { if (appConfig.isJavaVersionSupported(javaVersion)) {
javaHomeToVersionMap.put(potentialJavaHomeDir, 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 * Returns the Java home directory corresponding to the current "java.home" system
* property (if it supported). * 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. * @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. * @return The Java home directory corresponding to the current "java.home" system property.
* Could be null if the current "java.home" is not supported. * Could be null if the current "java.home" is not supported.
*/ */
public File findSupportedJavaHomeFromCurrentJavaHome(JavaConfig javaConfig, public File findSupportedJavaHomeFromCurrentJavaHome(AppConfig appConfig,
JavaFilter javaFilter) { JavaFilter javaFilter) {
Set<File> potentialJavaHomeSet = new HashSet<>(); Set<File> potentialJavaHomeSet = new HashSet<>();
String javaHomeProperty = System.getProperty("java.home"); String javaHomeProperty = System.getProperty("java.home");
@ -149,8 +149,8 @@ public abstract class JavaFinder {
} }
for (File potentialJavaHomeDir : potentialJavaHomeSet) { for (File potentialJavaHomeDir : potentialJavaHomeSet) {
try { try {
if (javaConfig.isJavaVersionSupported( if (appConfig.isJavaVersionSupported(
javaConfig.getJavaVersion(potentialJavaHomeDir, javaFilter))) { appConfig.getJavaVersion(potentialJavaHomeDir, javaFilter))) {
return potentialJavaHomeDir; return potentialJavaHomeDir;
} }
} }

View file

@ -432,6 +432,9 @@ task assembleDistribution (type: Copy) {
from ("${ROOT_PROJECT_DIR}/build/typestubs/src") { from ("${ROOT_PROJECT_DIR}/build/typestubs/src") {
into 'docs/ghidra_stubs/typestubs' into 'docs/ghidra_stubs/typestubs'
} }
from ("${ROOT_PROJECT_DIR}/build/typestubs/pypredef") {
into 'docs/ghidra_stubs/pypredef'
}
from (createGhidraStubsWheel) { from (createGhidraStubsWheel) {
into 'docs/ghidra_stubs' into 'docs/ghidra_stubs'
} }

View file

@ -95,9 +95,9 @@ ext.deps = [
destination: file("${DEPS_DIR}/BSim") destination: file("${DEPS_DIR}/BSim")
], ],
[ [
name: "PyDev 6.3.1.zip", name: "PyDev 9.3.0.zip",
url: "https://sourceforge.net/projects/pydev/files/pydev/PyDev%206.3.1/PyDev%206.3.1.zip", url: "https://sourceforge.net/projects/pydev/files/pydev/PyDev%209.3.0/PyDev%209.3.0.zip",
sha256: "4d81fe9d8afe7665b8ea20844d3f5107f446742927c59973eade4f29809b0699", sha256: "45398edf2adb56078a80bc88a919941578f0c0b363efbdd011bfd158a99b112e",
destination: file("${DEPS_DIR}/GhidraDev") destination: file("${DEPS_DIR}/GhidraDev")
], ],
[ [