mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2025-10-04 02:09:44 +02:00
GP-5138: GhidraDev/PyDev/PyGhidra integration
This commit is contained in:
parent
7c4d91f568
commit
31ee251a5c
35 changed files with 1300 additions and 315 deletions
|
@ -898,6 +898,7 @@ src/main/resources/images/pencil16.png||GHIDRA||||END|
|
|||
src/main/resources/images/pin.png||GHIDRA||||END|
|
||||
src/main/resources/images/play_again.png||GHIDRA||||END|
|
||||
src/main/resources/images/preferences-system.png||Tango Icons - Public Domain|||tango|END|
|
||||
src/main/resources/images/python.png||GHIDRA||||END|
|
||||
src/main/resources/images/question_zero.png||GHIDRA||||END|
|
||||
src/main/resources/images/red-cross.png||GHIDRA||||END|
|
||||
src/main/resources/images/redQuestionMark.png||GHIDRA||||END|
|
||||
|
|
BIN
Ghidra/Features/Base/src/main/resources/images/python.png
Normal file
BIN
Ghidra/Features/Base/src/main/resources/images/python.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.3 KiB |
Binary file not shown.
Before Width: | Height: | Size: 72 KiB |
|
@ -160,11 +160,14 @@ class PyGhidraLauncher:
|
|||
install_dir = install_dir or os.getenv("GHIDRA_INSTALL_DIR")
|
||||
self._install_dir = self._validate_install_dir(install_dir)
|
||||
|
||||
java_home_override = os.getenv("JAVA_HOME_OVERRIDE")
|
||||
if java_home_override:
|
||||
self._java_home = java_home_override
|
||||
|
||||
# check if we are in the ghidra source tree
|
||||
support = Path(install_dir) / "support"
|
||||
if not support.exists():
|
||||
self._dev_mode = True
|
||||
self._java_home = os.getenv("JAVA_HOME_OVERRIDE")
|
||||
|
||||
self._plugins: List[Tuple[Path, ExtensionDetails]] = []
|
||||
self.verbose = verbose
|
||||
|
@ -469,7 +472,7 @@ class PyGhidraLauncher:
|
|||
self._pre_launch_init()
|
||||
self._launch()
|
||||
except Exception as e:
|
||||
self._report_fatal_error("An error occured launching Ghidra", str(e), e)
|
||||
self._report_fatal_error("An error occurred launching Ghidra", str(e), e)
|
||||
|
||||
def get_install_path(self, plugin_name: str) -> Path:
|
||||
"""
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
<feature
|
||||
id="ghidra.ghidradev"
|
||||
label="GhidraDev"
|
||||
version="4.0.1.qualifier"
|
||||
version="5.0.0.qualifier"
|
||||
provider-name="Ghidra">
|
||||
|
||||
<description>
|
||||
|
|
|
@ -167,7 +167,9 @@
|
|||
<setEntry value="com.google.guava.failureaccess@default:default"/>
|
||||
<setEntry value="com.google.guava@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.jna.platform@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*2.0.1@default:default"/>
|
||||
<setEntry value="jakarta.servlet-api@default:default"/>
|
||||
<setEntry value="javax.xml@default:default"/>
|
||||
<setEntry value="jaxen@default:default"/>
|
||||
<setEntry value="org.apache.aries.spifly.dynamic.bundle@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.serializer@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.buildship.compat@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.ui@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.manipulation@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.ui@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.server@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.http@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.promise@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.ast*6.3.1.201802272029@default:default"/>
|
||||
<setEntry value="org.python.pydev.core*6.3.1.201802272029@default:default"/>
|
||||
<setEntry value="org.python.pydev.jython*6.3.1.201802272029@default:default"/>
|
||||
<setEntry value="org.python.pydev.parser*6.3.1.201802272029@default:default"/>
|
||||
<setEntry value="org.python.pydev.shared_core*6.3.1.201802272029@default:default"/>
|
||||
<setEntry value="org.python.pydev.shared_interactive_console*6.3.1.201802272029@default:default"/>
|
||||
<setEntry value="org.python.pydev.shared_ui*6.3.1.201802272029@default:default"/>
|
||||
<setEntry value="org.python.pydev*9.3.0.202203051235@default:default"/>
|
||||
<setEntry value="org.python.pydev.ast*9.3.0.202203051235@default:default"/>
|
||||
<setEntry value="org.python.pydev.core*9.3.0.202203051235@default:default"/>
|
||||
<setEntry value="org.python.pydev.customizations*9.3.0.202203051235@default:default"/>
|
||||
<setEntry value="org.python.pydev.debug*9.3.0.202203051235@default:default"/>
|
||||
<setEntry value="org.python.pydev.django*9.3.0.202203051235@default:default"/>
|
||||
<setEntry value="org.python.pydev.help*9.3.0.202203051235@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.pb@default:default"/>
|
||||
<setEntry value="org.tukaani.xz@default:default"/>
|
||||
|
@ -430,7 +439,7 @@
|
|||
<setAttribute key="selected_workspace_bundles">
|
||||
<setEntry value="ghidra.ghidradev@default:default"/>
|
||||
</setAttribute>
|
||||
<booleanAttribute key="show_selected_only" value="true"/>
|
||||
<booleanAttribute key="show_selected_only" value="false"/>
|
||||
<booleanAttribute key="tracing" value="false"/>
|
||||
<booleanAttribute key="useCustomFeatures" value="false"/>
|
||||
<booleanAttribute key="useDefaultConfig" value="true"/>
|
||||
|
|
|
@ -3,7 +3,7 @@ Manifest-Version: 1.0
|
|||
Bundle-ManifestVersion: 2
|
||||
Bundle-Name: GhidraDev
|
||||
Bundle-SymbolicName: ghidra.ghidradev;singleton:=true
|
||||
Bundle-Version: 4.0.1.qualifier
|
||||
Bundle-Version: 5.0.0.qualifier
|
||||
Bundle-Activator: ghidradev.Activator
|
||||
Require-Bundle: org.eclipse.ant.core;bundle-version="3.7.200",
|
||||
org.eclipse.buildship.core;bundle-version="3.1.8",
|
||||
|
@ -21,10 +21,11 @@ Require-Bundle: org.eclipse.ant.core;bundle-version="3.7.200",
|
|||
org.eclipse.ltk.core.refactoring;bundle-version="3.14.200",
|
||||
org.eclipse.ui;bundle-version="3.205.0",
|
||||
org.eclipse.ui.ide;bundle-version="3.22.0",
|
||||
com.python.pydev.debug;bundle-version="6.3.1";resolution:=optional,
|
||||
org.python.pydev;bundle-version="[6.3.1,10.0.0)";resolution:=optional,
|
||||
org.python.pydev.core;bundle-version="[6.3.1,10.0.0)";resolution:=optional,
|
||||
org.python.pydev.ast;bundle-version="[6.3.1,10.0.0)";resolution:=optional,
|
||||
com.python.pydev.debug;bundle-version="9.3.0";resolution:=optional,
|
||||
org.python.pydev;bundle-version="9.3.0";resolution:=optional,
|
||||
org.python.pydev.core;bundle-version="9.3.0";resolution:=optional,
|
||||
org.python.pydev.ast;bundle-version="9.3.0";resolution:=optional,
|
||||
org.python.pydev.debug;bundle-version="9.3.0";resolution:=optional,
|
||||
org.eclipse.cdt.core;bundle-version="5.9.1";resolution:=optional,
|
||||
org.eclipse.cdt.ui;bundle-version="5.9.0";resolution:=optional
|
||||
Bundle-RequiredExecutionEnvironment: JavaSE-21
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
# GhidraDev Eclipse Plugin
|
||||
GhidraDev provides support for developing and debugging Ghidra scripts and modules in Eclipse.
|
||||
|
||||
The information provided in this document is effective as of GhidraDev 4.0.0 and is subject to
|
||||
The information provided in this document is effective as of GhidraDev 5.0.0 and is subject to
|
||||
change with future releases.
|
||||
|
||||
## Table of Contents
|
||||
|
@ -31,6 +31,9 @@ change with future releases.
|
|||
12. [Building](#building)
|
||||
|
||||
## Change History
|
||||
__5.0.0:__
|
||||
* Added support for PyGhidra.
|
||||
|
||||
__4.0.1:__
|
||||
* New Ghidra module projects now contain a default `README.md` file.
|
||||
* Fixed a bug that prevented an imported module source project from being discovered by Ghidra when
|
||||
|
@ -132,7 +135,7 @@ __1.0.1:__
|
|||
* Ghidra 11.2 or later
|
||||
|
||||
## Optional Requirements
|
||||
* PyDev 6.3.1 - 9.3.0 ([more info](#pydev-support))
|
||||
* PyDev 9.3.0 or later ([more info](#pydev-support))
|
||||
* Gradle - required version(s) specified by linked Ghidra release
|
||||
([more info](#export-ghidra-module-extension))
|
||||
|
||||
|
@ -268,7 +271,10 @@ Ghidra from Eclipse independent of a project is not supported.
|
|||
|
||||
## PyDev Support
|
||||
GhidraDev is able to integrate with PyDev to conveniently configure Python support into Ghidra
|
||||
script and module projects.
|
||||
script and module projects. GhidraDev supports both Jython and PyGhidra Python implementations.
|
||||
|
||||
__NOTE:__ PyDev discontinued Jython 2 support in version 10.0.0. If you want to use GhidraDev with
|
||||
Jython, you must use __PyDev 9.3.0__. The latest vesions of PyDev support PyGhidra.
|
||||
|
||||
### Installing PyDev
|
||||
From Eclipse:
|
||||
|
@ -293,13 +299,19 @@ GhidraDev can add Python support to a Ghidra project when:
|
|||
* Creating a new Ghidra script project
|
||||
* Linking a Ghidra installation to an existing Java project
|
||||
|
||||
In order for GhidraDev to add in Python support, PyDev must have a Jython interpreter configured.
|
||||
GhidraDev will present a list of detected Jython interpreters that it found in PyDev's preferences.
|
||||
If no Jython interpreters were found, one can be added from GhidraDev by clicking the `+` icon.
|
||||
When the `+` icon is clicked, GhidraDev will attempt to find the Jython interpreter bundled with the
|
||||
selected Ghidra installation and automatically configure PyDev to use it. If for some reason
|
||||
GhidraDev was unable to find a Jython interpreter in the Ghidra installation, one will have to be
|
||||
added manually in the PyDev preferences.
|
||||
In order for GhidraDev to add in Python support, PyDev must have a PyGhidra or Jython interpreter
|
||||
configured. GhidraDev will present a list of detected PyGhidra/Jython interpreters that it found in
|
||||
PyDev's preferences. If no interpreters were found, one can be added from GhidraDev by clicking
|
||||
the `+` icons.
|
||||
|
||||
When the Jython `+` icon is clicked, GhidraDev will attempt to find the Jython interpreter bundled
|
||||
with the selected Ghidra installation and automatically configure PyDev to use it. If for some
|
||||
reason GhidraDev was unable to find a Jython interpreter in the Ghidra installation, one will have
|
||||
to be added manually in the PyDev preferences.
|
||||
|
||||
When the PyGhidra `+` icon is clicked, GhidraDev will attempt to find the PyGhidra interpreter
|
||||
that was last used to launch PyGhidra. If it cannot find it, you will have to launch PyGhidra
|
||||
and try again.
|
||||
|
||||
## Upgrading
|
||||
GhidraDev is upgraded differently depending on how it was installed. If GhidraDev was
|
||||
|
@ -347,9 +359,6 @@ installation directory.
|
|||
to your Ghidra module project, which automatically happens when the project is created.
|
||||
Simply [relink](#link-ghidra) your Ghidra installation to the project, and your project will
|
||||
pick up any newly discovered Ghidra extensions.
|
||||
* __Why doesn't GhidraDev support PyDev 10.0 or later?__
|
||||
* PyDev dropped support for Python 2 in their 10.0 release. Ghidra currently does not support
|
||||
Python 3.
|
||||
|
||||
## Additional Resources
|
||||
For more information on the GhidraDev plugin and developing for Ghidra in an Eclipse environment,
|
||||
|
@ -358,14 +367,14 @@ at `<GhidraInstallDir>/docs/GhidraClass/Intermediate/Scripting.html`.
|
|||
|
||||
## Building
|
||||
GhidraDev is currently built from Eclipse and distributed with Ghidra manually. Ideally we will use
|
||||
Gradle one day, but we aren't there yet. We do rely on `gradle prepDev` to generate the Eclipse
|
||||
project and build GhidraDev's dependencies though.
|
||||
Gradle one day, but we aren't there yet. We do rely on Gradle to generate the Eclipse project and
|
||||
build GhidraDev's dependencies though.
|
||||
|
||||
__NOTE:__ Only "Eclipse for RCP and RAP Developers" has the ability to do the below instructions.
|
||||
The following instructions assume that you are using this version of Eclipse.
|
||||
|
||||
#### Importing GhidraDev Eclipse projects (they are deactivated by default):
|
||||
1. Run `gradle eclipse -PeclipsePDE`
|
||||
1. Run `gradle prepGhidraDev eclipse -PeclipsePDE`
|
||||
2. From Eclipse, `File -> Import -> General -> Existing Projects into Workspace`
|
||||
3. From the ghidra repo, import `Eclipse GhidraDevFeature` and `Eclipse GhidraDevPlugin`
|
||||
|
||||
|
|
|
@ -81,8 +81,8 @@ task pyDevUnpack(type:Copy) {
|
|||
!pyDevDestDir.exists()
|
||||
}
|
||||
|
||||
File depsFile = file("${DEPS_DIR}/GhidraDev/PyDev 6.3.1.zip")
|
||||
File binRepoFile = file("${BIN_REPO}/GhidraBuild/EclipsePlugins/GhidraDev/buildDependencies/PyDev 6.3.1.zip")
|
||||
File depsFile = file("${DEPS_DIR}/GhidraDev/PyDev 9.3.0.zip")
|
||||
File binRepoFile = file("${BIN_REPO}/GhidraBuild/EclipsePlugins/GhidraDev/buildDependencies/PyDev 9.3.0.zip")
|
||||
|
||||
// First check if the file is in the dependencies repo. If not, check in the bin repo.
|
||||
def pyDevZipTree = depsFile.exists() ? zipTree(depsFile) : zipTree(binRepoFile)
|
||||
|
@ -115,6 +115,13 @@ task cdtUnpack(type:Copy) {
|
|||
destinationDir cdtDestDir
|
||||
}
|
||||
|
||||
task prepGhidraDev {
|
||||
dependsOn("utilityJar")
|
||||
dependsOn("launchSupportJar")
|
||||
dependsOn("pyDevUnpack")
|
||||
dependsOn("cdtUnpack")
|
||||
}
|
||||
|
||||
// We do not currently build GhidraDev plugin at Ghidra build time so we must
|
||||
// copy the prebuilt zip file from the BIN_REPO
|
||||
rootProject.assembleDistribution {
|
||||
|
@ -129,9 +136,3 @@ rootProject.assembleMarkdownToHtml {
|
|||
into "Extensions/Eclipse/GhidraDev/"
|
||||
}
|
||||
}
|
||||
|
||||
// PrepDev dependencies
|
||||
rootProject.prepDev.dependsOn utilityJar
|
||||
rootProject.prepDev.dependsOn launchSupportJar
|
||||
rootProject.prepDev.dependsOn pyDevUnpack
|
||||
rootProject.prepDev.dependsOn cdtUnpack
|
||||
|
|
Binary file not shown.
After Width: | Height: | Size: 1.3 KiB |
|
@ -378,6 +378,11 @@
|
|||
id="GhidraHeadlessLaunchConfigurationType"
|
||||
name="Ghidra Headless">
|
||||
</launchConfigurationType>
|
||||
<launchConfigurationType
|
||||
delegate="ghidradev.ghidraprojectcreator.launchers.PyGhidraLaunchDelegate"
|
||||
id="PyGhidraGuiLaunchConfigurationType"
|
||||
name="PyGhidra">
|
||||
</launchConfigurationType>
|
||||
</extension>
|
||||
<extension
|
||||
point="org.eclipse.debug.ui.launchConfigurationTypeImages">
|
||||
|
@ -391,6 +396,11 @@
|
|||
icon="icons/GhidraIcon16_bw.png"
|
||||
id="GhidraHeadlessLaunchConfigurationTypeImage">
|
||||
</launchConfigurationTypeImage>
|
||||
<launchConfigurationTypeImage
|
||||
configTypeID="PyGhidraGuiLaunchConfigurationType"
|
||||
icon="icons/python.png"
|
||||
id="PyGhidraGuiLaunchConfigurationTypeImage">
|
||||
</launchConfigurationTypeImage>
|
||||
</extension>
|
||||
<extension
|
||||
point="org.eclipse.debug.core.launchDelegates">
|
||||
|
@ -408,6 +418,13 @@
|
|||
name="Ghidra Headless"
|
||||
type="GhidraHeadlessLaunchConfigurationType">
|
||||
</launchDelegate>
|
||||
<launchDelegate
|
||||
delegate="ghidradev.ghidraprojectcreator.launchers.PyGhidraLaunchDelegate"
|
||||
id="PyGhidraGuiLaunchDelegate"
|
||||
modes="run, debug"
|
||||
name="PyGhidra GUI"
|
||||
type="PyGhidraGuiLaunchConfigurationType">
|
||||
</launchDelegate>
|
||||
</extension>
|
||||
<extension
|
||||
point="org.eclipse.debug.ui.launchConfigurationTabGroups">
|
||||
|
@ -423,6 +440,12 @@
|
|||
id="GhidraHeadlessLaunchConfigurationTabGroup"
|
||||
type="GhidraHeadlessLaunchConfigurationType">
|
||||
</launchConfigurationTabGroup>
|
||||
<launchConfigurationTabGroup
|
||||
class="org.python.pydev.debug.ui.PythonTabGroup"
|
||||
description="Run and debug PyGhidra modules and scripts"
|
||||
id="PyGhidraGuiLaunchConfigurationTabGroup"
|
||||
type="PyGhidraGuiLaunchConfigurationType">
|
||||
</launchConfigurationTabGroup>
|
||||
</extension>
|
||||
<extension
|
||||
point="org.eclipse.debug.ui.launchShortcuts">
|
||||
|
@ -474,6 +497,30 @@
|
|||
</enablement>
|
||||
</contextualLaunch>
|
||||
</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
|
||||
point="org.eclipse.core.expressions.propertyTesters">
|
||||
|
@ -505,6 +552,13 @@
|
|||
properties="isGhidraModuleProject"
|
||||
type="java.lang.Object">
|
||||
</propertyTester>
|
||||
<propertyTester
|
||||
class="ghidradev.ghidraprojectcreator.testers.PyGhidraProjectPropertyTester"
|
||||
id="PyGhidraProjectPropertyTester"
|
||||
namespace="ghidradev.ghidraprojectcreator.testers"
|
||||
properties="isPyGhidraProject"
|
||||
type="java.lang.Object">
|
||||
</propertyTester>
|
||||
</extension>
|
||||
|
||||
</plugin>
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -33,7 +33,7 @@ import org.eclipse.swt.widgets.Display;
|
|||
import org.eclipse.ui.IPerspectiveDescriptor;
|
||||
import org.eclipse.ui.PlatformUI;
|
||||
|
||||
import ghidra.launch.JavaConfig;
|
||||
import ghidra.launch.AppConfig;
|
||||
import ghidradev.EclipseMessageUtils;
|
||||
import ghidradev.ghidraprojectcreator.utils.*;
|
||||
|
||||
|
@ -62,10 +62,10 @@ public class GhidraLaunchDelegate extends JavaLaunchDelegate {
|
|||
}
|
||||
IFolder ghidraFolder =
|
||||
javaProject.getProject().getFolder(GhidraProjectUtils.GHIDRA_FOLDER_NAME);
|
||||
JavaConfig javaConfig;
|
||||
AppConfig appConfig;
|
||||
String ghidraInstallPath = ghidraFolder.getLocation().toOSString();
|
||||
try {
|
||||
javaConfig = new JavaConfig(new File(ghidraInstallPath));
|
||||
appConfig = new AppConfig(new File(ghidraInstallPath));
|
||||
}
|
||||
catch (ParseException | IOException e) {
|
||||
EclipseMessageUtils.showErrorDialog(
|
||||
|
@ -98,7 +98,7 @@ public class GhidraLaunchDelegate extends JavaLaunchDelegate {
|
|||
}
|
||||
|
||||
// Set VM arguments
|
||||
String vmArgs = javaConfig.getLaunchProperties().getVmArgs();
|
||||
String vmArgs = appConfig.getLaunchProperties().getVmArgs();
|
||||
vmArgs += " " + configuration.getAttribute(GhidraLaunchUtils.ATTR_VM_ARGUMENTS, "").trim();
|
||||
vmArgs += " -Dghidra.external.modules=\"%s%s%s\"".formatted(
|
||||
javaProject.getProject().getLocation(), File.pathSeparator,
|
||||
|
@ -171,7 +171,7 @@ public class GhidraLaunchDelegate extends JavaLaunchDelegate {
|
|||
}
|
||||
|
||||
// Start PyDev debugger
|
||||
if (PyDevUtils.isSupportedPyDevInstalled()) {
|
||||
if (PyDevUtils.isSupportedJythonPyDevInstalled()) {
|
||||
try {
|
||||
PyDevUtils.startPyDevRemoteDebugger();
|
||||
}
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
}
|
|
@ -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.");
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -49,6 +49,12 @@ public class GhidraLaunchUtils {
|
|||
*/
|
||||
public static final String HEADLESS_LAUNCH = "GhidraHeadlessLaunchConfigurationType";
|
||||
|
||||
/**
|
||||
* Launch configuration ID for a PyGhidra GUI launch. Must match corresponding value in
|
||||
* plugin.xml.
|
||||
*/
|
||||
public static final String PYGHIDRA_GUI_LAUNCH = "PyGhidraGuiLaunchConfigurationType";
|
||||
|
||||
/**
|
||||
* Program arguments that will get passed to the launched Ghidra. These will be appended
|
||||
* to the required program arguments that are required to launch Ghidra, which are hidden
|
||||
|
|
|
@ -30,6 +30,7 @@ import org.eclipse.ltk.core.refactoring.*;
|
|||
|
||||
import ghidra.GhidraApplicationLayout;
|
||||
import ghidra.util.exception.CancelledException;
|
||||
import ghidradev.ghidraprojectcreator.utils.PyDevUtils.ProjectPythonInterpreter;
|
||||
import utilities.util.FileUtilities;
|
||||
|
||||
/**
|
||||
|
@ -89,7 +90,7 @@ public class GhidraModuleUtils {
|
|||
*/
|
||||
public static IJavaProject createGhidraModuleProject(String projectName, File projectDir,
|
||||
boolean createRunConfig, String runConfigMemory, GhidraApplicationLayout ghidraLayout,
|
||||
String jythonInterpreterName, IProgressMonitor monitor)
|
||||
ProjectPythonInterpreter jythonInterpreterName, IProgressMonitor monitor)
|
||||
throws IOException, ParseException, CoreException {
|
||||
|
||||
// Create empty Ghidra project
|
||||
|
@ -227,8 +228,7 @@ public class GhidraModuleUtils {
|
|||
* @param createRunConfig Whether or not to create a new run configuration for the project.
|
||||
* @param runConfigMemory The run configuration's desired memory. Could be null.
|
||||
* @param ghidraLayout The Ghidra layout to link the project to.
|
||||
* @param jythonInterpreterName The name of the Jython interpreter to use for Python support.
|
||||
* Could be null if Python support is not wanted.
|
||||
* @param pythonInterpreter The Python interpreter to use.
|
||||
* @param monitor The progress monitor to use during project creation.
|
||||
* @return The imported project.
|
||||
* @throws IOException If there was a file-related problem with creating the project.
|
||||
|
@ -237,13 +237,13 @@ public class GhidraModuleUtils {
|
|||
*/
|
||||
public static IJavaProject importGhidraModuleSource(String projectName, File moduleSourceDir,
|
||||
boolean createRunConfig, String runConfigMemory, GhidraApplicationLayout ghidraLayout,
|
||||
String jythonInterpreterName, IProgressMonitor monitor)
|
||||
ProjectPythonInterpreter pythonInterpreter, IProgressMonitor monitor)
|
||||
throws IOException, ParseException, CoreException {
|
||||
|
||||
// Create empty Ghidra project
|
||||
IJavaProject javaProject =
|
||||
GhidraProjectUtils.createEmptyGhidraProject(projectName, moduleSourceDir,
|
||||
createRunConfig, runConfigMemory, ghidraLayout, jythonInterpreterName, monitor);
|
||||
createRunConfig, runConfigMemory, ghidraLayout, pythonInterpreter, monitor);
|
||||
IProject project = javaProject.getProject();
|
||||
|
||||
// Set default output location
|
||||
|
|
|
@ -36,9 +36,10 @@ import org.eclipse.ui.part.FileEditorInput;
|
|||
import generic.jar.ResourceFile;
|
||||
import ghidra.GhidraApplicationLayout;
|
||||
import ghidra.framework.GModule;
|
||||
import ghidra.launch.JavaConfig;
|
||||
import ghidra.launch.AppConfig;
|
||||
import ghidradev.Activator;
|
||||
import ghidradev.EclipseMessageUtils;
|
||||
import ghidradev.ghidraprojectcreator.utils.PyDevUtils.ProjectPythonInterpreter;
|
||||
import utility.module.ModuleUtilities;
|
||||
|
||||
/**
|
||||
|
@ -262,8 +263,7 @@ public class GhidraProjectUtils {
|
|||
* @param createRunConfig Whether or not to create a new run configuration for the project.
|
||||
* @param runConfigMemory The run configuration's desired memory. Could be null.
|
||||
* @param ghidraLayout The Ghidra layout to link the project to.
|
||||
* @param jythonInterpreterName The name of the Jython interpreter to use for Python support.
|
||||
* Could be null if Python support is not wanted.
|
||||
* @param pythonInterpreter The Python interpreter to use.
|
||||
* @param monitor The progress monitor to use during project creation.
|
||||
* @return The created project.
|
||||
* @throws IOException If there was a file-related problem with creating the project.
|
||||
|
@ -272,12 +272,12 @@ public class GhidraProjectUtils {
|
|||
*/
|
||||
public static IJavaProject createEmptyGhidraProject(String projectName, File projectDir,
|
||||
boolean createRunConfig, String runConfigMemory, GhidraApplicationLayout ghidraLayout,
|
||||
String jythonInterpreterName, IProgressMonitor monitor)
|
||||
ProjectPythonInterpreter pythonInterpreter, IProgressMonitor monitor)
|
||||
throws IOException, ParseException, CoreException {
|
||||
|
||||
// Get Ghidra's Java configuration
|
||||
JavaConfig javaConfig =
|
||||
new JavaConfig(ghidraLayout.getApplicationInstallationDir().getFile(false));
|
||||
AppConfig appConfig =
|
||||
new AppConfig(ghidraLayout.getApplicationInstallationDir().getFile(false));
|
||||
|
||||
// Make new Java project
|
||||
IWorkspace workspace = ResourcesPlugin.getWorkspace();
|
||||
|
@ -299,7 +299,7 @@ public class GhidraProjectUtils {
|
|||
javaProject.setRawClasspath(new IClasspathEntry[0], monitor);
|
||||
|
||||
// Configure Java compiler for the project
|
||||
configureJavaCompiler(javaProject, javaConfig);
|
||||
configureJavaCompiler(javaProject, appConfig);
|
||||
|
||||
// Setup default bin folder
|
||||
IFolder binFolder = project.getFolder("bin/default");
|
||||
|
@ -310,7 +310,7 @@ public class GhidraProjectUtils {
|
|||
monitor);
|
||||
|
||||
// Link in Ghidra to the project
|
||||
linkGhidraToProject(javaProject, ghidraLayout, javaConfig, jythonInterpreterName, monitor);
|
||||
linkGhidraToProject(javaProject, ghidraLayout, appConfig, pythonInterpreter, monitor);
|
||||
|
||||
// Create run configuration (if necessary)
|
||||
if (createRunConfig) {
|
||||
|
@ -338,23 +338,22 @@ public class GhidraProjectUtils {
|
|||
*
|
||||
* @param javaProject The Java project to link.
|
||||
* @param ghidraLayout The Ghidra layout to link the project to.
|
||||
* @param javaConfig Ghidra's Java configuration.
|
||||
* @param jythonInterpreterName The name of the Jython interpreter to use for Python support.
|
||||
* Could be null if Python support is not wanted.
|
||||
* @param appConfig Ghidra's application configuration.
|
||||
* @param pythonInterpreter The Python interpreter to use.
|
||||
* @param monitor The progress monitor used during link.
|
||||
* @throws IOException If there was a file-related problem with linking in Ghidra.
|
||||
* @throws CoreException If there was an Eclipse-related problem with linking in Ghidra.
|
||||
*/
|
||||
public static void linkGhidraToProject(IJavaProject javaProject,
|
||||
GhidraApplicationLayout ghidraLayout, JavaConfig javaConfig,
|
||||
String jythonInterpreterName, IProgressMonitor monitor)
|
||||
GhidraApplicationLayout ghidraLayout, AppConfig appConfig,
|
||||
ProjectPythonInterpreter pythonInterpreter, IProgressMonitor monitor)
|
||||
throws CoreException, IOException {
|
||||
|
||||
// Gets the Ghidra installation directory to link to from the Ghidra layout
|
||||
File ghidraInstallDir = ghidraLayout.getApplicationInstallationDir().getFile(false);
|
||||
|
||||
// Get the Java VM used to launch the Ghidra to link to
|
||||
IVMInstall vm = getGhidraVm(javaConfig);
|
||||
IVMInstall vm = getGhidraVm(appConfig);
|
||||
IPath vmPath =
|
||||
new Path(JavaRuntime.JRE_CONTAINER).append(vm.getVMInstallType().getId()).append(
|
||||
vm.getName());
|
||||
|
@ -457,17 +456,15 @@ public class GhidraProjectUtils {
|
|||
GhidraModuleUtils.writeAntProperties(javaProject.getProject(), ghidraLayout);
|
||||
|
||||
// Setup Python for the project
|
||||
if (PyDevUtils.isSupportedPyDevInstalled()) {
|
||||
try {
|
||||
PyDevUtils.setupPythonForProject(javaProject, libraryClasspathEntries,
|
||||
jythonInterpreterName, monitor);
|
||||
pythonInterpreter, monitor);
|
||||
}
|
||||
catch (OperationNotSupportedException e) {
|
||||
EclipseMessageUtils.showErrorDialog("PyDev error",
|
||||
"Failed to setup Python for the project. PyDev version is not supported.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks to see if the given path is contained within the given Ghidra installation path.
|
||||
|
@ -559,14 +556,14 @@ public class GhidraProjectUtils {
|
|||
/**
|
||||
* Gets the required VM used to build and run the Ghidra defined by the given layout.
|
||||
*
|
||||
* @param javaConfig Ghidra's Java configuration.
|
||||
* @param appConfig Ghidra's application configuration.
|
||||
* @return The required VM used to build and run the Ghidra defined by the given layout.
|
||||
* @throws IOException If there was a file-related problem with getting the VM.
|
||||
* @throws CoreException If there was an Eclipse-related problem with creating the project.
|
||||
*/
|
||||
private static IVMInstall getGhidraVm(JavaConfig javaConfig) throws IOException, CoreException {
|
||||
private static IVMInstall getGhidraVm(AppConfig appConfig) throws IOException, CoreException {
|
||||
|
||||
File requiredJavaHomeDir = javaConfig.getSavedJavaHome(); // safe to assume it's valid
|
||||
File requiredJavaHomeDir = appConfig.getSavedJavaHome(); // safe to assume it's valid
|
||||
|
||||
// First look for a matching VM in Eclipse's existing list.
|
||||
// NOTE: Mac has its own VM type, so be sure to check it for VM matches too.
|
||||
|
@ -617,19 +614,19 @@ public class GhidraProjectUtils {
|
|||
* Configures the default Java compiler behavior for the given java project.
|
||||
*
|
||||
* @param jp The Java project to configure.
|
||||
* @param javaConfig Ghidra's Java configuration.
|
||||
* @param appConfig Ghidra's application configuration.
|
||||
*/
|
||||
private static void configureJavaCompiler(IJavaProject jp, JavaConfig javaConfig) {
|
||||
private static void configureJavaCompiler(IJavaProject jp, AppConfig appConfig) {
|
||||
|
||||
final String WARNING = JavaCore.WARNING;
|
||||
final String IGNORE = JavaCore.IGNORE;
|
||||
final String ERROR = JavaCore.ERROR;
|
||||
|
||||
// Compliance
|
||||
jp.setOption(JavaCore.COMPILER_SOURCE, javaConfig.getCompilerComplianceLevel());
|
||||
jp.setOption(JavaCore.COMPILER_COMPLIANCE, javaConfig.getCompilerComplianceLevel());
|
||||
jp.setOption(JavaCore.COMPILER_SOURCE, appConfig.getCompilerComplianceLevel());
|
||||
jp.setOption(JavaCore.COMPILER_COMPLIANCE, appConfig.getCompilerComplianceLevel());
|
||||
jp.setOption(JavaCore.COMPILER_CODEGEN_TARGET_PLATFORM,
|
||||
javaConfig.getCompilerComplianceLevel());
|
||||
appConfig.getCompilerComplianceLevel());
|
||||
|
||||
// Code style
|
||||
jp.setOption(JavaCore.COMPILER_PB_STATIC_ACCESS_RECEIVER, WARNING);
|
||||
|
|
|
@ -26,6 +26,7 @@ import org.eclipse.jdt.core.*;
|
|||
import ghidra.GhidraApplicationLayout;
|
||||
import ghidra.framework.GModule;
|
||||
import ghidradev.Activator;
|
||||
import ghidradev.ghidraprojectcreator.utils.PyDevUtils.ProjectPythonInterpreter;
|
||||
|
||||
/**
|
||||
* Utility methods for working with Ghidra scripts in Eclipse.
|
||||
|
@ -45,8 +46,7 @@ public class GhidraScriptUtils {
|
|||
* @param linkUserScripts Whether or not to link in the user scripts directory.
|
||||
* @param linkSystemScripts Whether or not to link in the system scripts directories.
|
||||
* @param ghidraLayout The Ghidra layout to link the project to.
|
||||
* @param jythonInterpreterName The name of the Jython interpreter to use for Python support.
|
||||
* Could be null if Python support is not wanted.
|
||||
* @param pythonInterpreter The Python interpreter to use.
|
||||
* @param monitor The progress monitor to use during project creation.
|
||||
* @return The created project.
|
||||
* @throws IOException If there was a file-related problem with creating the project.
|
||||
|
@ -56,15 +56,14 @@ public class GhidraScriptUtils {
|
|||
public static IJavaProject createGhidraScriptProject(String projectName, File projectDir,
|
||||
boolean createRunConfig, String runConfigMemory, boolean linkUserScripts,
|
||||
boolean linkSystemScripts, GhidraApplicationLayout ghidraLayout,
|
||||
String jythonInterpreterName, IProgressMonitor monitor)
|
||||
ProjectPythonInterpreter pythonInterpreter, IProgressMonitor monitor)
|
||||
throws IOException, ParseException, CoreException {
|
||||
|
||||
List<IClasspathEntry> classpathEntries = new ArrayList<>();
|
||||
|
||||
// Create empty Ghidra project
|
||||
IJavaProject javaProject = GhidraProjectUtils.createEmptyGhidraProject(projectName,
|
||||
projectDir, createRunConfig, runConfigMemory, ghidraLayout, jythonInterpreterName,
|
||||
monitor);
|
||||
projectDir, createRunConfig, runConfigMemory, ghidraLayout, pythonInterpreter, monitor);
|
||||
|
||||
// Link each module's ghidra_scripts directory to the project
|
||||
if (linkSystemScripts) {
|
||||
|
|
|
@ -26,6 +26,7 @@ import java.util.stream.Stream;
|
|||
|
||||
import javax.naming.OperationNotSupportedException;
|
||||
|
||||
import org.eclipse.core.resources.IProject;
|
||||
import org.eclipse.core.runtime.*;
|
||||
import org.eclipse.jdt.core.IClasspathEntry;
|
||||
import org.eclipse.jdt.core.IJavaProject;
|
||||
|
@ -38,23 +39,58 @@ import ghidradev.Activator;
|
|||
*/
|
||||
public class PyDevUtils {
|
||||
|
||||
public final static String MIN_SUPPORTED_VERSION = "6.3.1";
|
||||
public final static String MAX_SUPPORTED_VERSION = "9.3.0";
|
||||
public final static String MIN_SUPPORTED_VERSION = "9.3.0";
|
||||
public final static String MAX_JYTHON_SUPPORTED_VERSION = "9.3.0";
|
||||
|
||||
/**
|
||||
* Checks to see if a supported version of PyDev is installed.
|
||||
*
|
||||
* @return True if a supported version of PyDev is installed; otherwise, false.
|
||||
* The various types of supported Python interpreters
|
||||
*/
|
||||
public static boolean isSupportedPyDevInstalled() {
|
||||
public static enum ProjectPythonInterpreterType {
|
||||
NONE,
|
||||
PYGHIDRA,
|
||||
JYTHON
|
||||
}
|
||||
|
||||
/**
|
||||
* The projects Python interpreter to use
|
||||
*
|
||||
* @param name The name of the interpreter
|
||||
* @param type The {@link ProjectPythonInterpreterType type} of the interpreter
|
||||
*/
|
||||
public static record ProjectPythonInterpreter(String name, ProjectPythonInterpreterType type) {}
|
||||
|
||||
/**
|
||||
* {@return true if a supported version of PyDev is installed for use with PyGhidra; otherwise,
|
||||
* false}
|
||||
*/
|
||||
public static boolean isSupportedPyGhidraPyDevInstalled() {
|
||||
Version min = Version.valueOf(MIN_SUPPORTED_VERSION);
|
||||
Version max = Version.valueOf(MAX_SUPPORTED_VERSION);
|
||||
try {
|
||||
Version version = PyDevUtilsInternal.getPyDevVersion();
|
||||
if (version != null) {
|
||||
return version.compareTo(min) >= 0;
|
||||
}
|
||||
}
|
||||
catch (NoClassDefFoundError e) {
|
||||
// Fall through to return false
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@return true if a supported version of PyDev is installed for use with Jython; otherwise,
|
||||
* false}
|
||||
*/
|
||||
public static boolean isSupportedJythonPyDevInstalled() {
|
||||
Version min = Version.valueOf(MIN_SUPPORTED_VERSION);
|
||||
Version max = Version.valueOf(MAX_JYTHON_SUPPORTED_VERSION);
|
||||
try {
|
||||
Version version = PyDevUtilsInternal.getPyDevVersion();
|
||||
if (version != null) {
|
||||
// Make sure the installed version of PyDev is new enough to support the following
|
||||
// operation.
|
||||
getJython27InterpreterNames();
|
||||
getJythonInterpreterNames();
|
||||
return version.compareTo(min) >= 0 && version.compareTo(max) <= 0;
|
||||
}
|
||||
}
|
||||
|
@ -66,15 +102,53 @@ public class PyDevUtils {
|
|||
}
|
||||
|
||||
/**
|
||||
* Gets a list of discovered Jython 2.7 interpreter names.
|
||||
*
|
||||
* @return a list of discovered Jython 2.7 interpreter names.
|
||||
* Gets a list of discovered PyGhidra interpreter names.
|
||||
* @param requiredFileMatch if not {@code null}, only interpreter names that correspond to the
|
||||
* given interpreter file will be returned.
|
||||
* @return a list of discovered PyGhidra interpreter names.
|
||||
* @throws OperationNotSupportedException if PyDev is not installed or it does not support this
|
||||
* operation.
|
||||
*/
|
||||
public static List<String> getJython27InterpreterNames() throws OperationNotSupportedException {
|
||||
public static List<String> getPyGhidraInterpreterNames(File requiredFileMatch)
|
||||
throws OperationNotSupportedException {
|
||||
try {
|
||||
return PyDevUtilsInternal.getJython27InterpreterNames();
|
||||
return PyDevUtilsInternal.getPyGhidraInterpreterNames(requiredFileMatch);
|
||||
}
|
||||
catch (NoClassDefFoundError | NoSuchMethodError e) {
|
||||
throw new OperationNotSupportedException(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a list of discovered Jython interpreter names.
|
||||
*
|
||||
* @return a list of discovered Jython interpreter names.
|
||||
* @throws OperationNotSupportedException if PyDev is not installed or it does not support this
|
||||
* operation.
|
||||
*/
|
||||
public static List<String> getJythonInterpreterNames() throws OperationNotSupportedException {
|
||||
try {
|
||||
return PyDevUtilsInternal.getJythonInterpreterNames();
|
||||
}
|
||||
catch (NoClassDefFoundError | NoSuchMethodError e) {
|
||||
throw new OperationNotSupportedException(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the given PyGhidra interpreter to PyDev.
|
||||
*
|
||||
* @param interpreterName The name of the interpreter to add.
|
||||
* @param interpreterFile The interpreter file to add.
|
||||
* @param pypredefDir The pypredef directory to use (could be null if not supported)
|
||||
* @throws OperationNotSupportedException if PyDev is not installed or it does not support this
|
||||
* operation.
|
||||
*/
|
||||
public static void addPyGhidraInterpreter(String interpreterName, File interpreterFile,
|
||||
File pypredefDir) throws OperationNotSupportedException {
|
||||
try {
|
||||
PyDevUtilsInternal.addPyGhidraInterpreter(interpreterName, interpreterFile,
|
||||
pypredefDir);
|
||||
}
|
||||
catch (NoClassDefFoundError | NoSuchMethodError e) {
|
||||
throw new OperationNotSupportedException(e.getMessage());
|
||||
|
@ -107,8 +181,7 @@ public class PyDevUtils {
|
|||
*
|
||||
* @param javaProject The Java project to enable Python for.
|
||||
* @param classpathEntries The classpath entries to add to the Python path.
|
||||
* @param jythonInterpreterName The name of the Jython interpreter to use for Python support.
|
||||
* If this is null, Python support will be removed from the project.
|
||||
* @param pythonInterpreter The Python interpreter to use.
|
||||
* @param monitor The progress monitor used during link.
|
||||
* @throws CoreException if there was an Eclipse-related problem with enabling Python for the
|
||||
* project.
|
||||
|
@ -116,11 +189,11 @@ public class PyDevUtils {
|
|||
* operation.
|
||||
*/
|
||||
public static void setupPythonForProject(IJavaProject javaProject,
|
||||
List<IClasspathEntry> classpathEntries, String jythonInterpreterName,
|
||||
List<IClasspathEntry> classpathEntries, ProjectPythonInterpreter pythonInterpreter,
|
||||
IProgressMonitor monitor) throws CoreException, OperationNotSupportedException {
|
||||
try {
|
||||
PyDevUtilsInternal.setupPythonForProject(javaProject, classpathEntries,
|
||||
jythonInterpreterName, monitor);
|
||||
pythonInterpreter, monitor);
|
||||
}
|
||||
catch (NoClassDefFoundError | NoSuchMethodError e) {
|
||||
throw new OperationNotSupportedException(e.getMessage());
|
||||
|
@ -151,6 +224,15 @@ public class PyDevUtils {
|
|||
return "org.python.pydev.ui.pythonpathconf.interpreterPreferencesPageJython";
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the PyDev Python preference page ID.
|
||||
*
|
||||
* @return the PyDev Python preference page ID.
|
||||
*/
|
||||
public static String getPythonPreferencePageId() {
|
||||
return "org.python.pydev.ui.pythonpathconf.interpreterPreferencesPagePython";
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets The PyDev source directory.
|
||||
*
|
||||
|
@ -184,4 +266,138 @@ public class PyDevUtils {
|
|||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks to see if the given project is a Python project.
|
||||
*
|
||||
* @param project The project to check.
|
||||
* @return True if the given project is a Python project; otherwise, false.
|
||||
* @throws OperationNotSupportedException if PyDev is not installed or it does not support this
|
||||
* operation.
|
||||
*/
|
||||
public static boolean isPythonProject(IProject project) throws OperationNotSupportedException {
|
||||
try {
|
||||
return PyDevUtilsInternal.isPythonProject(project);
|
||||
}
|
||||
catch (NoClassDefFoundError | NoSuchMethodError e) {
|
||||
throw new OperationNotSupportedException(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks to see if the given project is a PyGhidra project.
|
||||
*
|
||||
* @param project The project to check.
|
||||
* @return True if the given project is a PyGhidra project; otherwise, false.
|
||||
* @throws OperationNotSupportedException if PyDev is not installed or it does not support this
|
||||
* operation.
|
||||
*/
|
||||
public static boolean isPyGhidraProject(IProject project)
|
||||
throws OperationNotSupportedException {
|
||||
try {
|
||||
return PyDevUtilsInternal.isPyGhidraProject(project);
|
||||
}
|
||||
catch (NoClassDefFoundError | NoSuchMethodError e) {
|
||||
throw new OperationNotSupportedException(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the interpreter name of the given Python project.
|
||||
*
|
||||
* @param project The project to get the interpreter name from.
|
||||
* @return The interpreter name of the given Python project, or null it it's not a Python
|
||||
* project or doesn't have an interpreter.
|
||||
* @throws OperationNotSupportedException if PyDev is not installed or it does not support this
|
||||
* operation.
|
||||
*/
|
||||
public static String getInterpreterName(IProject project)
|
||||
throws OperationNotSupportedException {
|
||||
try {
|
||||
return PyDevUtilsInternal.getInterpreterName(project);
|
||||
}
|
||||
catch (NoClassDefFoundError | NoSuchMethodError e) {
|
||||
throw new OperationNotSupportedException(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the PyDev "project" attribute.
|
||||
*
|
||||
* @return The PyDev "project" attribute.
|
||||
* @throws OperationNotSupportedException if PyDev is not installed or it does not support this
|
||||
* operation.
|
||||
*/
|
||||
public static String getAttrProject() throws OperationNotSupportedException {
|
||||
try {
|
||||
return PyDevUtilsInternal.getAttrProject();
|
||||
}
|
||||
catch (NoClassDefFoundError e) {
|
||||
throw new OperationNotSupportedException(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the PyDev "location" attribute.
|
||||
*
|
||||
* @return The PyDev "location" attribute.
|
||||
* @throws OperationNotSupportedException if PyDev is not installed or it does not support this
|
||||
* operation.
|
||||
*/
|
||||
public static String getAttrLocation() throws OperationNotSupportedException {
|
||||
try {
|
||||
return PyDevUtilsInternal.getAttrLocation();
|
||||
}
|
||||
catch (NoClassDefFoundError e) {
|
||||
throw new OperationNotSupportedException(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the PyDev "program arguments" attribute.
|
||||
*
|
||||
* @return The PyDev "program arguments" attribute.
|
||||
* @throws OperationNotSupportedException if PyDev is not installed or it does not support this
|
||||
* operation.
|
||||
*/
|
||||
public static String getAttrProgramArguments() throws OperationNotSupportedException {
|
||||
try {
|
||||
return PyDevUtilsInternal.getAttrProgramArguments();
|
||||
}
|
||||
catch (NoClassDefFoundError e) {
|
||||
throw new OperationNotSupportedException(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the PyDev "interpreter" attribute.
|
||||
*
|
||||
* @return The PyDev "interpreter" attribute.
|
||||
* @throws OperationNotSupportedException if PyDev is not installed or it does not support this
|
||||
* operation.
|
||||
*/
|
||||
public static String getAttrInterpreter() throws OperationNotSupportedException {
|
||||
try {
|
||||
return PyDevUtilsInternal.getAttrInterpreter();
|
||||
}
|
||||
catch (NoClassDefFoundError e) {
|
||||
throw new OperationNotSupportedException(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the PyDev "interpreter default" attribute.
|
||||
*
|
||||
* @return The PyDev "interpreter default" attribute.
|
||||
* @throws OperationNotSupportedException if PyDev is not installed or it does not support this
|
||||
* operation.
|
||||
*/
|
||||
public static String getAttrInterpreterDefault() throws OperationNotSupportedException {
|
||||
try {
|
||||
return PyDevUtilsInternal.getAttrInterpreterDefault();
|
||||
}
|
||||
catch (NoClassDefFoundError e) {
|
||||
throw new OperationNotSupportedException(e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,6 +19,7 @@ import java.io.File;
|
|||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.eclipse.core.resources.IProject;
|
||||
import org.eclipse.core.runtime.*;
|
||||
import org.eclipse.jdt.core.IClasspathEntry;
|
||||
import org.eclipse.jdt.core.IJavaProject;
|
||||
|
@ -26,11 +27,13 @@ import org.osgi.framework.*;
|
|||
import org.python.pydev.ast.interpreter_managers.InterpreterInfo;
|
||||
import org.python.pydev.ast.interpreter_managers.InterpreterManagersAPI;
|
||||
import org.python.pydev.core.*;
|
||||
import org.python.pydev.debug.core.Constants;
|
||||
import org.python.pydev.plugin.nature.PythonNature;
|
||||
|
||||
import com.python.pydev.debug.remote.client_api.PydevRemoteDebuggerServer;
|
||||
|
||||
import ghidradev.EclipseMessageUtils;
|
||||
import ghidradev.ghidraprojectcreator.utils.PyDevUtils.ProjectPythonInterpreter;
|
||||
|
||||
/**
|
||||
* Utility methods for interacting with PyDev.
|
||||
|
@ -62,20 +65,50 @@ class PyDevUtilsInternal {
|
|||
}
|
||||
|
||||
/**
|
||||
* Gets a list of discovered Jython 2.7 interpreter names.
|
||||
* Gets a list of discovered PyGhidra interpreter names.
|
||||
*
|
||||
* @return a list of discovered Jython 2.7 interpreter names.
|
||||
* @param requiredFileMatch if not {@code null}, only interpreter names that correspond to the
|
||||
* given interpreter file will be returned.
|
||||
* @return a list of discovered PyGhidra interpreter names.
|
||||
* @throws NoClassDefFoundError if PyDev is not installed or it does not support this operation.
|
||||
* @throws NoSuchMethodError if PyDev is not installed or it does not support this operation.
|
||||
*/
|
||||
public static List<String> getJython27InterpreterNames()
|
||||
public static List<String> getPyGhidraInterpreterNames(File requiredFileMatch)
|
||||
throws NoClassDefFoundError, NoSuchMethodError {
|
||||
|
||||
List<String> interpreters = new ArrayList<>();
|
||||
IInterpreterManager iMan = InterpreterManagersAPI.getPythonInterpreterManager(true);
|
||||
|
||||
for (IInterpreterInfo info : iMan.getInterpreterInfos()) {
|
||||
ISystemModulesManager modulesManager = info.getModulesManager();
|
||||
if (info.getInterpreterType() == IPythonNature.INTERPRETER_TYPE_PYTHON &&
|
||||
!modulesManager.getAllModulesStartingWith("pyghidra.__main__").isEmpty()) {
|
||||
if (requiredFileMatch == null ||
|
||||
requiredFileMatch.getAbsolutePath().equals(info.getExecutableOrJar())) {
|
||||
interpreters.add(info.getName());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return interpreters;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a list of discovered Jython interpreter names.
|
||||
*
|
||||
* @return a list of discovered Jython interpreter names.
|
||||
* @throws NoClassDefFoundError if PyDev is not installed or it does not support this operation.
|
||||
* @throws NoSuchMethodError if PyDev is not installed or it does not support this operation.
|
||||
*/
|
||||
public static List<String> getJythonInterpreterNames()
|
||||
throws NoClassDefFoundError, NoSuchMethodError {
|
||||
|
||||
List<String> interpreters = new ArrayList<>();
|
||||
IInterpreterManager iMan = InterpreterManagersAPI.getJythonInterpreterManager(true);
|
||||
|
||||
for (IInterpreterInfo info : iMan.getInterpreterInfos()) {
|
||||
if (info.getInterpreterType() == IPythonNature.INTERPRETER_TYPE_JYTHON && info.getVersion().equals("2.7")) {
|
||||
if (info.getInterpreterType() == IPythonNature.INTERPRETER_TYPE_JYTHON &&
|
||||
info.getVersion().equals("2.7")) {
|
||||
interpreters.add(info.getName());
|
||||
}
|
||||
}
|
||||
|
@ -83,6 +116,38 @@ class PyDevUtilsInternal {
|
|||
return interpreters;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the given PyGhidra interpreter to PyDev.
|
||||
*
|
||||
* @param interpreterName The name of the interpreter to add.
|
||||
* @param interpreterFile The interpreter to add.
|
||||
* @param pypredefDir The pypredef directory to use (could be null if not supported)
|
||||
* @throws NoClassDefFoundError if PyDev is not installed or it does not support this operation.
|
||||
* @throws NoSuchMethodError if PyDev is not installed or it does not support this operation.
|
||||
*/
|
||||
public static void addPyGhidraInterpreter(String interpreterName, File interpreterFile,
|
||||
File pypredefDir) throws NoClassDefFoundError, NoSuchMethodError {
|
||||
IProgressMonitor monitor = new NullProgressMonitor();
|
||||
IInterpreterManager iMan = InterpreterManagersAPI.getPythonInterpreterManager(true);
|
||||
IInterpreterInfo[] interpreterInfos = iMan.getInterpreterInfos();
|
||||
for (IInterpreterInfo iInfo : interpreterInfos) {
|
||||
if (iInfo.getName().equals(interpreterName) &&
|
||||
iInfo.getExecutableOrJar().equals(interpreterFile.getAbsolutePath())) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
IInterpreterInfo iInfo =
|
||||
iMan.createInterpreterInfo(interpreterFile.getAbsolutePath(), monitor, false);
|
||||
iInfo.setName(interpreterName);
|
||||
if (iInfo instanceof InterpreterInfo ii && pypredefDir != null) {
|
||||
ii.addPredefinedCompletionsPath(pypredefDir.getAbsolutePath());
|
||||
}
|
||||
IInterpreterInfo[] newInterpreterInfos =
|
||||
Arrays.copyOf(interpreterInfos, interpreterInfos.length + 1);
|
||||
newInterpreterInfos[interpreterInfos.length] = iInfo;
|
||||
iMan.setInfos(newInterpreterInfos, null, monitor);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the given Jython interpreter to PyDev.
|
||||
*
|
||||
|
@ -119,26 +184,38 @@ class PyDevUtilsInternal {
|
|||
*
|
||||
* @param javaProject The Java project to setup Python for.
|
||||
* @param classpathEntries The classpath entries to add to the Python path.
|
||||
* @param jythonInterpreterName The name of the Jython interpreter to use for Python support.
|
||||
* If this is null, Python support will be removed from the project.
|
||||
* @param pythonInterpreter The Python interpreter to use.
|
||||
* @param monitor The progress monitor used during link.
|
||||
* @throws CoreException If there was an Eclipse-related problem with enabling Python for the project.
|
||||
* @throws NoClassDefFoundError if PyDev is not installed or it does not support this operation.
|
||||
* @throws NoSuchMethodError if PyDev is not installed or it does not support this operation.
|
||||
*/
|
||||
public static void setupPythonForProject(IJavaProject javaProject,
|
||||
List<IClasspathEntry> classpathEntries, String jythonInterpreterName,
|
||||
List<IClasspathEntry> classpathEntries, ProjectPythonInterpreter pythonInterpreter,
|
||||
IProgressMonitor monitor)
|
||||
throws CoreException, NoClassDefFoundError, NoSuchMethodError {
|
||||
|
||||
PythonNature.removeNature(javaProject.getProject(), monitor);
|
||||
|
||||
if (jythonInterpreterName != null) {
|
||||
String libs = classpathEntries.stream().map(e -> e.getPath().toOSString()).collect(
|
||||
Collectors.joining("|"));
|
||||
PythonNature.addNature(javaProject.getProject(), monitor,
|
||||
IPythonNature.JYTHON_VERSION_2_7, null, libs, jythonInterpreterName, null);
|
||||
String version;
|
||||
String libs;
|
||||
switch (pythonInterpreter.type()) {
|
||||
case PYGHIDRA:
|
||||
version = IPythonNature.PYTHON_VERSION_INTERPRETER;
|
||||
libs = null;
|
||||
break;
|
||||
case JYTHON:
|
||||
version = IPythonNature.JYTHON_VERSION_INTERPRETER;
|
||||
libs = classpathEntries.stream()
|
||||
.map(e -> e.getPath().toOSString())
|
||||
.collect(Collectors.joining("|"));
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
PythonNature.addNature(javaProject.getProject(), monitor, version, null, libs,
|
||||
pythonInterpreter.name(), null);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -151,6 +228,109 @@ class PyDevUtilsInternal {
|
|||
PydevRemoteDebuggerServer.startServer();
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks to see if the given project is a Python project.
|
||||
*
|
||||
* @param project The project to check.
|
||||
* @return True if the given project is a Python project; otherwise, false.
|
||||
* @throws NoClassDefFoundError if PyDev is not installed or it does not support this operation.
|
||||
*/
|
||||
public static boolean isPythonProject(IProject project) throws NoClassDefFoundError {
|
||||
try {
|
||||
return project.hasNature(PythonNature.PYTHON_NATURE_ID);
|
||||
}
|
||||
catch (CoreException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks to see if the given project is a PyGhidra project.
|
||||
*
|
||||
* @param project The project to check.
|
||||
* @return True if the given project is a PyGhidra project; otherwise, false.
|
||||
* @throws NoClassDefFoundError if PyDev is not installed or it does not support this operation.
|
||||
*/
|
||||
public static boolean isPyGhidraProject(IProject project) throws NoClassDefFoundError {
|
||||
return isPythonProject(project) && GhidraProjectUtils.isGhidraProject(project);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the interpreter name of the given Python project.
|
||||
*
|
||||
* @param project The project to get the interpreter name from.
|
||||
* @return The interpreter name of the given Python project, or null it it's not a Python
|
||||
* project or doesn't have an interpreter.
|
||||
* @throws NoClassDefFoundError if PyDev is not installed or it does not support this operation.
|
||||
* @throws NoSuchMethodError if PyDev is not installed or it does not support this operation.
|
||||
*/
|
||||
public static String getInterpreterName(IProject project)
|
||||
throws NoClassDefFoundError, NoSuchMethodError {
|
||||
PythonNature nature = PythonNature.getPythonNature(project);
|
||||
if (nature != null) {
|
||||
try {
|
||||
IInterpreterInfo info = nature.getProjectInterpreter();
|
||||
if (info != null) {
|
||||
return info.getName();
|
||||
}
|
||||
}
|
||||
catch (PythonNatureWithoutProjectException | MisconfigurationException e) {
|
||||
// Fall through
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the PyDev "project" attribute.
|
||||
*
|
||||
* @return The PyDev "project" attribute.
|
||||
* @throws NoClassDefFoundError if PyDev is not installed or it does not support this operation.
|
||||
*/
|
||||
public static String getAttrProject() throws NoClassDefFoundError {
|
||||
return Constants.ATTR_PROJECT;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the PyDev "location" attribute.
|
||||
*
|
||||
* @return The PyDev "location" attribute.
|
||||
* @throws NoClassDefFoundError if PyDev is not installed or it does not support this operation.
|
||||
*/
|
||||
public static String getAttrLocation() throws NoClassDefFoundError {
|
||||
return Constants.ATTR_LOCATION;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the PyDev "program arguments" attribute.
|
||||
*
|
||||
* @return The PyDev "program arguments" attribute.
|
||||
* @throws NoClassDefFoundError if PyDev is not installed or it does not support this operation.
|
||||
*/
|
||||
public static String getAttrProgramArguments() throws NoClassDefFoundError {
|
||||
return Constants.ATTR_PROGRAM_ARGUMENTS;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the PyDev "interpreter" attribute.
|
||||
*
|
||||
* @return The PyDev "interpreter" attribute.
|
||||
* @throws NoClassDefFoundError if PyDev is not installed or it does not support this operation.
|
||||
*/
|
||||
public static String getAttrInterpreter() throws NoClassDefFoundError {
|
||||
return Constants.ATTR_INTERPRETER;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the PyDev "interpreter default" attribute.
|
||||
*
|
||||
* @return The PyDev "interpreter default" attribute.
|
||||
* @throws NoClassDefFoundError if PyDev is not installed or it does not support this operation.
|
||||
*/
|
||||
public static String getAttrInterpreterDefault() throws NoClassDefFoundError {
|
||||
return Constants.ATTR_INTERPRETER_DEFAULT;
|
||||
}
|
||||
|
||||
private PyDevUtilsInternal() throws NoClassDefFoundError {
|
||||
// Prevent instantiation
|
||||
}
|
||||
|
|
|
@ -36,6 +36,7 @@ import ghidra.GhidraApplicationLayout;
|
|||
import ghidradev.EclipseMessageUtils;
|
||||
import ghidradev.ghidraprojectcreator.utils.GhidraModuleUtils;
|
||||
import ghidradev.ghidraprojectcreator.utils.GhidraModuleUtils.ModuleTemplateType;
|
||||
import ghidradev.ghidraprojectcreator.utils.PyDevUtils.ProjectPythonInterpreter;
|
||||
import ghidradev.ghidraprojectcreator.wizards.pages.*;
|
||||
import utilities.util.FileUtilities;
|
||||
|
||||
|
@ -87,12 +88,12 @@ public class CreateGhidraModuleProjectWizard extends Wizard implements INewWizar
|
|||
boolean createRunConfig = projectPage.shouldCreateRunConfig();
|
||||
String runConfigMemory = projectPage.getRunConfigMemory();
|
||||
File projectDir = projectPage.getProjectDir();
|
||||
String jythonInterpreterName = pythonPage.getJythonInterpreterName();
|
||||
ProjectPythonInterpreter pythonInterpreter = pythonPage.getProjectPythonInterpreter();
|
||||
Set<ModuleTemplateType> moduleTemplateTypes = projectConfigPage.getModuleTemplateTypes();
|
||||
try {
|
||||
getContainer().run(true, false,
|
||||
monitor -> create(ghidraInstallDir, projectName, projectDir, createRunConfig,
|
||||
runConfigMemory, moduleTemplateTypes, jythonInterpreterName, monitor));
|
||||
runConfigMemory, moduleTemplateTypes, pythonInterpreter, monitor));
|
||||
}
|
||||
catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
|
@ -115,14 +116,13 @@ public class CreateGhidraModuleProjectWizard extends Wizard implements INewWizar
|
|||
* @param createRunConfig Whether or not to create a new run configuration for the project.
|
||||
* @param runConfigMemory The run configuration's desired memory. Could be null.
|
||||
* @param moduleTemplateTypes The desired module template types.
|
||||
* @param jythonInterpreterName The name of the Jython interpreter to use for Python support.
|
||||
* Could be null if Python support is not wanted.
|
||||
* @param pythonInterpreter The Python interpreter to use.
|
||||
* @param monitor The monitor to use during project creation.
|
||||
* @throws InvocationTargetException if an error occurred during project creation.
|
||||
*/
|
||||
private void create(File ghidraInstallDir, String projectName, File projectDir,
|
||||
boolean createRunConfig, String runConfigMemory,
|
||||
Set<ModuleTemplateType> moduleTemplateTypes, String jythonInterpreterName,
|
||||
Set<ModuleTemplateType> moduleTemplateTypes, ProjectPythonInterpreter pythonInterpreter,
|
||||
IProgressMonitor monitor) throws InvocationTargetException {
|
||||
try {
|
||||
info("Creating " + projectName + " at " + projectDir);
|
||||
|
@ -133,7 +133,7 @@ public class CreateGhidraModuleProjectWizard extends Wizard implements INewWizar
|
|||
|
||||
IJavaProject javaProject =
|
||||
GhidraModuleUtils.createGhidraModuleProject(projectName, projectDir,
|
||||
createRunConfig, runConfigMemory, ghidraLayout, jythonInterpreterName, monitor);
|
||||
createRunConfig, runConfigMemory, ghidraLayout, pythonInterpreter, monitor);
|
||||
monitor.worked(1);
|
||||
|
||||
IFile sourceFile = GhidraModuleUtils.configureModuleSource(javaProject,
|
||||
|
|
|
@ -32,6 +32,7 @@ import org.eclipse.ui.IWorkbench;
|
|||
import ghidra.GhidraApplicationLayout;
|
||||
import ghidradev.EclipseMessageUtils;
|
||||
import ghidradev.ghidraprojectcreator.utils.GhidraScriptUtils;
|
||||
import ghidradev.ghidraprojectcreator.utils.PyDevUtils.ProjectPythonInterpreter;
|
||||
import ghidradev.ghidraprojectcreator.wizards.pages.*;
|
||||
import utilities.util.FileUtilities;
|
||||
|
||||
|
@ -81,11 +82,11 @@ public class CreateGhidraScriptProjectWizard extends Wizard implements INewWizar
|
|||
String runConfigMemory = projectPage.getRunConfigMemory();
|
||||
boolean linkUserScripts = projectConfigPage.shouldLinkUsersScripts();
|
||||
boolean linkSystemScripts = projectConfigPage.shouldLinkSystemScripts();
|
||||
String jythonInterpreterName = pythonPage.getJythonInterpreterName();
|
||||
ProjectPythonInterpreter pythonInterpreter = pythonPage.getProjectPythonInterpreter();
|
||||
try {
|
||||
getContainer().run(true, false,
|
||||
monitor -> create(ghidraInstallDir, projectName, projectDir, createRunConfig,
|
||||
runConfigMemory, linkUserScripts, linkSystemScripts, jythonInterpreterName,
|
||||
runConfigMemory, linkUserScripts, linkSystemScripts, pythonInterpreter,
|
||||
monitor));
|
||||
}
|
||||
catch (InterruptedException e) {
|
||||
|
@ -110,15 +111,14 @@ public class CreateGhidraScriptProjectWizard extends Wizard implements INewWizar
|
|||
* @param runConfigMemory The run configuration's desired memory. Could be null.
|
||||
* @param linkUserScripts Whether or not to link in the user scripts directory.
|
||||
* @param linkSystemScripts Whether or not to link in the system scripts directories.
|
||||
* @param jythonInterpreterName The name of the Jython interpreter to use for Python support.
|
||||
* Could be null if Python support is not wanted.
|
||||
* @param pythonInterpreter The Python interpreter to use.
|
||||
* @param monitor The monitor to use during project creation.
|
||||
* @throws InvocationTargetException if an error occurred during project creation.
|
||||
*/
|
||||
private void create(File ghidraInstallDir, String projectName, File projectDir,
|
||||
boolean createRunConfig, String runConfigMemory, boolean linkUserScripts,
|
||||
boolean linkSystemScripts, String jythonInterpreterName, IProgressMonitor monitor)
|
||||
throws InvocationTargetException {
|
||||
boolean linkSystemScripts, ProjectPythonInterpreter pythonInterpreter,
|
||||
IProgressMonitor monitor) throws InvocationTargetException {
|
||||
try {
|
||||
info("Creating " + projectName + " at " + projectDir);
|
||||
monitor.beginTask("Creating " + projectName, 2);
|
||||
|
@ -128,7 +128,7 @@ public class CreateGhidraScriptProjectWizard extends Wizard implements INewWizar
|
|||
|
||||
GhidraScriptUtils.createGhidraScriptProject(projectName, projectDir, createRunConfig,
|
||||
runConfigMemory, linkUserScripts, linkSystemScripts, ghidraLayout,
|
||||
jythonInterpreterName, monitor);
|
||||
pythonInterpreter, monitor);
|
||||
monitor.worked(1);
|
||||
|
||||
info("Finished creating " + projectName);
|
||||
|
|
|
@ -40,7 +40,7 @@ import org.eclipse.ui.INewWizard;
|
|||
import org.eclipse.ui.IWorkbench;
|
||||
|
||||
import ghidra.GhidraApplicationLayout;
|
||||
import ghidra.launch.JavaConfig;
|
||||
import ghidra.launch.AppConfig;
|
||||
import ghidradev.EclipseMessageUtils;
|
||||
import ghidradev.ghidraprojectcreator.utils.GhidraProjectUtils;
|
||||
import ghidradev.ghidraprojectcreator.wizards.pages.ChooseGhidraModuleProjectWizardPage;
|
||||
|
@ -123,7 +123,7 @@ public class ExportGhidraModuleWizard extends Wizard implements INewWizard {
|
|||
// TODO: It's more correct to get this from the project's classpath, since Ghidra's
|
||||
// saved Java home can change from launch to launch.
|
||||
GhidraApplicationLayout ghidraLayout = new GhidraApplicationLayout(new File(ghidraInstallDirPath));
|
||||
File javaHomeDir = new JavaConfig(
|
||||
File javaHomeDir = new AppConfig(
|
||||
ghidraLayout.getApplicationInstallationDir().getFile(false)).getSavedJavaHome();
|
||||
if(javaHomeDir == null) {
|
||||
throw new IOException("Failed to get the Java home associated with the project. " +
|
||||
|
|
|
@ -32,6 +32,7 @@ import org.eclipse.ui.IWorkbench;
|
|||
import ghidra.GhidraApplicationLayout;
|
||||
import ghidradev.EclipseMessageUtils;
|
||||
import ghidradev.ghidraprojectcreator.utils.GhidraModuleUtils;
|
||||
import ghidradev.ghidraprojectcreator.utils.PyDevUtils.ProjectPythonInterpreter;
|
||||
import ghidradev.ghidraprojectcreator.wizards.pages.*;
|
||||
import utilities.util.FileUtilities;
|
||||
|
||||
|
@ -76,11 +77,11 @@ public class ImportGhidraModuleSourceWizard extends Wizard implements IImportWiz
|
|||
String projectName = projectPage.getProjectName();
|
||||
boolean createRunConfig = projectPage.shouldCreateRunConfig();
|
||||
String runConfigMemory = projectPage.getRunConfigMemory();
|
||||
String jythonInterpreterName = pythonPage.getJythonInterpreterName();
|
||||
ProjectPythonInterpreter pythonInterpreter = pythonPage.getProjectPythonInterpreter();
|
||||
try {
|
||||
getContainer().run(true, false,
|
||||
monitor -> importModuleSource(ghidraInstallDir, projectName, moduleSourceDir,
|
||||
createRunConfig, runConfigMemory, jythonInterpreterName, monitor));
|
||||
createRunConfig, runConfigMemory, pythonInterpreter, monitor));
|
||||
}
|
||||
catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
|
@ -102,14 +103,14 @@ public class ImportGhidraModuleSourceWizard extends Wizard implements IImportWiz
|
|||
* @param moduleSourceDir The module source directory to import.
|
||||
* @param createRunConfig Whether or not to create a new run configuration for the project.
|
||||
* @param runConfigMemory The run configuration's desired memory. Could be null.
|
||||
* @param jythonInterpreterName The name of the Jython interpreter to use for Python support.
|
||||
* Could be null if Python support is not wanted.
|
||||
* @param pythonInterpreter The Python interpreter to use.
|
||||
* @param monitor The monitor to use during project creation.
|
||||
* @throws InvocationTargetException if an error occurred during project creation.
|
||||
*/
|
||||
private void importModuleSource(File ghidraInstallDir, String projectName, File moduleSourceDir,
|
||||
boolean createRunConfig, String runConfigMemory, String jythonInterpreterName,
|
||||
IProgressMonitor monitor) throws InvocationTargetException {
|
||||
boolean createRunConfig, String runConfigMemory,
|
||||
ProjectPythonInterpreter pythonInterpreter, IProgressMonitor monitor)
|
||||
throws InvocationTargetException {
|
||||
try {
|
||||
info("Importing " + projectName + " at " + moduleSourceDir);
|
||||
monitor.beginTask("Importing " + projectName, 2);
|
||||
|
@ -118,7 +119,7 @@ public class ImportGhidraModuleSourceWizard extends Wizard implements IImportWiz
|
|||
monitor.worked(1);
|
||||
|
||||
GhidraModuleUtils.importGhidraModuleSource(projectName, moduleSourceDir,
|
||||
createRunConfig, runConfigMemory, ghidraLayout, jythonInterpreterName, monitor);
|
||||
createRunConfig, runConfigMemory, ghidraLayout, pythonInterpreter, monitor);
|
||||
monitor.worked(1);
|
||||
|
||||
info("Finished importing " + projectName);
|
||||
|
|
|
@ -31,8 +31,9 @@ import org.eclipse.jface.viewers.ISelection;
|
|||
import org.eclipse.jface.wizard.Wizard;
|
||||
|
||||
import ghidra.GhidraApplicationLayout;
|
||||
import ghidra.launch.JavaConfig;
|
||||
import ghidra.launch.AppConfig;
|
||||
import ghidradev.ghidraprojectcreator.utils.GhidraProjectUtils;
|
||||
import ghidradev.ghidraprojectcreator.utils.PyDevUtils.ProjectPythonInterpreter;
|
||||
import ghidradev.ghidraprojectcreator.wizards.pages.*;
|
||||
|
||||
/**
|
||||
|
@ -63,10 +64,10 @@ public class LinkGhidraWizard extends Wizard {
|
|||
public boolean performFinish() {
|
||||
File ghidraInstallDir = ghidraInstallationPage.getGhidraInstallDir();
|
||||
IJavaProject javaProject = projectPage.getJavaProject();
|
||||
String jythonInterpreterName = pythonPage.getJythonInterpreterName();
|
||||
ProjectPythonInterpreter pythonInterpreter = pythonPage.getProjectPythonInterpreter();
|
||||
try {
|
||||
getContainer().run(true, false,
|
||||
monitor -> link(ghidraInstallDir, javaProject, jythonInterpreterName, monitor));
|
||||
monitor -> link(ghidraInstallDir, javaProject, pythonInterpreter, monitor));
|
||||
}
|
||||
catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
|
@ -85,23 +86,23 @@ public class LinkGhidraWizard extends Wizard {
|
|||
*
|
||||
* @param ghidraInstallDir The Ghidra installation directory to use.
|
||||
* @param javaProject The Java project to link.
|
||||
* @param jythonInterpreterName The name of the Jython interpreter to use for Python support.
|
||||
* Could be null if Python support is not wanted.
|
||||
* @param pythonInterpreter The Python interpreter to use.
|
||||
* @param monitor The monitor to use during project link.
|
||||
* @throws InvocationTargetException if an error occurred during link.
|
||||
*/
|
||||
private void link(File ghidraInstallDir, IJavaProject javaProject, String jythonInterpreterName,
|
||||
IProgressMonitor monitor) throws InvocationTargetException {
|
||||
private void link(File ghidraInstallDir, IJavaProject javaProject,
|
||||
ProjectPythonInterpreter pythonInterpreter, IProgressMonitor monitor)
|
||||
throws InvocationTargetException {
|
||||
IProject project = javaProject.getProject();
|
||||
try {
|
||||
info("Linking " + project.getName());
|
||||
monitor.beginTask("Linking " + project.getName(), 2);
|
||||
|
||||
GhidraApplicationLayout ghidraLayout = new GhidraApplicationLayout(ghidraInstallDir);
|
||||
JavaConfig javaConfig =
|
||||
new JavaConfig(ghidraLayout.getApplicationInstallationDir().getFile(false));
|
||||
GhidraProjectUtils.linkGhidraToProject(javaProject, ghidraLayout, javaConfig,
|
||||
jythonInterpreterName, monitor);
|
||||
AppConfig appConfig =
|
||||
new AppConfig(ghidraLayout.getApplicationInstallationDir().getFile(false));
|
||||
GhidraProjectUtils.linkGhidraToProject(javaProject, ghidraLayout, appConfig,
|
||||
pythonInterpreter, monitor);
|
||||
monitor.worked(1);
|
||||
|
||||
project.refreshLocal(IResource.DEPTH_INFINITE, monitor);
|
||||
|
|
|
@ -27,7 +27,7 @@ import org.eclipse.swt.layout.GridLayout;
|
|||
import org.eclipse.swt.widgets.*;
|
||||
import org.eclipse.ui.dialogs.PreferencesUtil;
|
||||
|
||||
import ghidra.launch.JavaConfig;
|
||||
import ghidra.launch.AppConfig;
|
||||
import ghidra.launch.JavaFinder.JavaFilter;
|
||||
import ghidradev.ghidraprojectcreator.preferences.GhidraProjectCreatorPreferencePage;
|
||||
import ghidradev.ghidraprojectcreator.preferences.GhidraProjectCreatorPreferences;
|
||||
|
@ -108,8 +108,8 @@ public class ChooseGhidraInstallationWizardPage extends WizardPage {
|
|||
File ghidraInstallDir = new File(ghidraInstallDirCombo.getText());
|
||||
GhidraProjectCreatorPreferencePage.validateGhidraInstallation(ghidraInstallDir);
|
||||
try {
|
||||
JavaConfig javaConfig = new JavaConfig(ghidraInstallDir);
|
||||
if (!javaConfig.isSupportedJavaHomeDir(javaConfig.getSavedJavaHome(),
|
||||
AppConfig appConfig = new AppConfig(ghidraInstallDir);
|
||||
if (!appConfig.isSupportedJavaHomeDir(appConfig.getSavedJavaHome(),
|
||||
JavaFilter.JDK_ONLY)) {
|
||||
message = "A supported JDK is not associated with this Ghidra " +
|
||||
"installation. Please run this Ghidra and try again.";
|
||||
|
|
|
@ -15,8 +15,7 @@
|
|||
*/
|
||||
package ghidradev.ghidraprojectcreator.wizards.pages;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.*;
|
||||
import java.nio.file.Files;
|
||||
import java.util.List;
|
||||
|
||||
|
@ -27,13 +26,15 @@ import org.eclipse.jface.wizard.WizardPage;
|
|||
import org.eclipse.swt.SWT;
|
||||
import org.eclipse.swt.events.SelectionEvent;
|
||||
import org.eclipse.swt.events.SelectionListener;
|
||||
import org.eclipse.swt.layout.GridData;
|
||||
import org.eclipse.swt.layout.GridLayout;
|
||||
import org.eclipse.swt.layout.*;
|
||||
import org.eclipse.swt.widgets.*;
|
||||
import org.eclipse.ui.dialogs.PreferencesUtil;
|
||||
|
||||
import ghidra.launch.AppConfig;
|
||||
import ghidradev.EclipseMessageUtils;
|
||||
import ghidradev.ghidraprojectcreator.utils.PyDevUtils;
|
||||
import ghidradev.ghidraprojectcreator.utils.PyDevUtils.ProjectPythonInterpreter;
|
||||
import ghidradev.ghidraprojectcreator.utils.PyDevUtils.ProjectPythonInterpreterType;
|
||||
|
||||
/**
|
||||
* A wizard page that lets the user enable python for their project.
|
||||
|
@ -41,7 +42,11 @@ import ghidradev.ghidraprojectcreator.utils.PyDevUtils;
|
|||
public class EnablePythonWizardPage extends WizardPage {
|
||||
|
||||
private ChooseGhidraInstallationWizardPage ghidraInstallationPage;
|
||||
private Button enablePythonCheckboxButton;
|
||||
private Button pyghidraButton;
|
||||
private Button jythonButton;
|
||||
private Button noneButton;
|
||||
private Combo pyghidraCombo;
|
||||
private Button addPyGhidraButton;
|
||||
private Combo jythonCombo;
|
||||
private Button addJythonButton;
|
||||
|
||||
|
@ -61,46 +66,107 @@ public class EnablePythonWizardPage extends WizardPage {
|
|||
public void createControl(Composite parent) {
|
||||
|
||||
Composite container = new Composite(parent, SWT.NULL);
|
||||
container.setLayout(new GridLayout(3, false));
|
||||
container.setLayout(new GridLayout(1, false));
|
||||
|
||||
// Enable Python checkbox.
|
||||
enablePythonCheckboxButton = new Button(container, SWT.CHECK);
|
||||
enablePythonCheckboxButton.setText("Enable Python");
|
||||
enablePythonCheckboxButton.setToolTipText("Enables Python support using the PyDev " +
|
||||
"Eclipse plugin. Requires PyDev version " + PyDevUtils.MIN_SUPPORTED_VERSION +
|
||||
" - " + PyDevUtils.MAX_SUPPORTED_VERSION);
|
||||
enablePythonCheckboxButton.setSelection(PyDevUtils.isSupportedPyDevInstalled());
|
||||
enablePythonCheckboxButton.addSelectionListener(new SelectionListener() {
|
||||
// Project type selection
|
||||
SelectionListener projectTypeSelectionListener = new SelectionListener() {
|
||||
@Override
|
||||
public void widgetSelected(SelectionEvent evt) {
|
||||
validate();
|
||||
validate(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void widgetDefaultSelected(SelectionEvent evt) {
|
||||
validate();
|
||||
validate(null);
|
||||
}
|
||||
};
|
||||
Group projectTypeGroup = new Group(container, SWT.SHADOW_ETCHED_OUT);
|
||||
projectTypeGroup.setLayout(new RowLayout(SWT.HORIZONTAL));
|
||||
projectTypeGroup.setText("Project Type");
|
||||
pyghidraButton = new Button(projectTypeGroup, SWT.RADIO);
|
||||
pyghidraButton.setSelection(PyDevUtils.isSupportedPyGhidraPyDevInstalled());
|
||||
pyghidraButton.setText("PyGhidra");
|
||||
pyghidraButton.setToolTipText("Enables PyGhidra support using the PyDev " +
|
||||
"Eclipse plugin. Requires PyDev version " + PyDevUtils.MIN_SUPPORTED_VERSION +
|
||||
" or later.");
|
||||
pyghidraButton.addSelectionListener(projectTypeSelectionListener);
|
||||
jythonButton = new Button(projectTypeGroup, SWT.RADIO);
|
||||
jythonButton.setSelection(false);
|
||||
jythonButton.setText("Jython");
|
||||
jythonButton.setToolTipText("Enables Jython support using the PyDev " +
|
||||
"Eclipse plugin. Requires PyDev version " + PyDevUtils.MIN_SUPPORTED_VERSION +
|
||||
" - " + PyDevUtils.MAX_JYTHON_SUPPORTED_VERSION);
|
||||
jythonButton.addSelectionListener(projectTypeSelectionListener);
|
||||
noneButton = new Button(projectTypeGroup, SWT.RADIO);
|
||||
noneButton.setSelection(!PyDevUtils.isSupportedPyGhidraPyDevInstalled());
|
||||
noneButton.setText("None");
|
||||
noneButton.setToolTipText("Disables Python support for the project.");
|
||||
noneButton.addSelectionListener(projectTypeSelectionListener);
|
||||
|
||||
Composite interpreterContainer = new Composite(container, SWT.NULL);
|
||||
interpreterContainer.setLayout(new GridLayout(3, false));
|
||||
interpreterContainer.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
|
||||
|
||||
// PyGhidra interpreter combo box
|
||||
Label pyGhidraLabel = new Label(interpreterContainer, SWT.NULL);
|
||||
pyGhidraLabel.setText("PyGhidra interpreter:");
|
||||
pyghidraCombo = new Combo(interpreterContainer, SWT.DROP_DOWN | SWT.READ_ONLY);
|
||||
pyghidraCombo.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
|
||||
pyghidraCombo.setToolTipText("The wizard requires a Python interpreter to be " +
|
||||
"selected. Click the + button to add or manage Python interpreters.");
|
||||
File savedPyGhidraInterpreter = populatePyGhidraCombo(null);
|
||||
pyghidraCombo.addModifyListener(evt -> validate(null));
|
||||
|
||||
// PyGhidra interpreter add button
|
||||
addPyGhidraButton = new Button(interpreterContainer, SWT.BUTTON1);
|
||||
addPyGhidraButton.setText("+");
|
||||
addPyGhidraButton.setToolTipText("Adds/manages PyGhidra interpreters.");
|
||||
addPyGhidraButton.addListener(SWT.Selection, evt -> {
|
||||
try {
|
||||
File ghidraDir = ghidraInstallationPage.getGhidraInstallDir();
|
||||
File pyghidraInterpreter = findPyGhidraInterpreter();
|
||||
File pypredefDir = new File(ghidraDir, "docs/ghidra_stubs/pypredef");
|
||||
if (!pypredefDir.isDirectory()) {
|
||||
pypredefDir = null;
|
||||
}
|
||||
if (EclipseMessageUtils.showQuestionDialog("Python Found",
|
||||
"PyGhidra was previously launched with: \"" + pyghidraInterpreter +
|
||||
"\". Would you like to use it as your interpreter?")) {
|
||||
PyDevUtils.addPyGhidraInterpreter("pyghidra_" + ghidraDir.getName(),
|
||||
pyghidraInterpreter, pypredefDir);
|
||||
populatePyGhidraCombo(pyghidraInterpreter);
|
||||
validate(pyghidraInterpreter);
|
||||
return;
|
||||
}
|
||||
}
|
||||
catch (Exception e) {
|
||||
// Fall through to show PyDev's Python preference page
|
||||
}
|
||||
|
||||
PreferenceDialog dialog = PreferencesUtil.createPreferenceDialogOn(null,
|
||||
PyDevUtils.getPythonPreferencePageId(), null, null);
|
||||
dialog.open();
|
||||
populatePyGhidraCombo(savedPyGhidraInterpreter);
|
||||
validate(null);
|
||||
});
|
||||
new Label(container, SWT.NONE).setText(""); // filler
|
||||
new Label(container, SWT.NONE).setText(""); // filler
|
||||
|
||||
// Jython interpreter combo box
|
||||
Label jythonLabel = new Label(container, SWT.NULL);
|
||||
Label jythonLabel = new Label(interpreterContainer, SWT.NULL);
|
||||
jythonLabel.setText("Jython interpreter:");
|
||||
jythonCombo = new Combo(container, SWT.DROP_DOWN | SWT.READ_ONLY);
|
||||
jythonCombo = new Combo(interpreterContainer, SWT.DROP_DOWN | SWT.READ_ONLY);
|
||||
jythonCombo.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
|
||||
jythonCombo.setToolTipText("The wizard requires a Jython interpreter to be " +
|
||||
"selected. Click the + button to add or manage Jython interpreters.");
|
||||
populateJythonCombo();
|
||||
jythonCombo.addModifyListener(evt -> validate());
|
||||
jythonCombo.addModifyListener(evt -> validate(null));
|
||||
|
||||
// Jython interpreter add button
|
||||
addJythonButton = new Button(container, SWT.BUTTON1);
|
||||
addJythonButton = new Button(interpreterContainer, SWT.BUTTON1);
|
||||
addJythonButton.setText("+");
|
||||
addJythonButton.setToolTipText("Adds/manages Jython interpreters.");
|
||||
addJythonButton.addListener(SWT.Selection, evt -> {
|
||||
try {
|
||||
if (PyDevUtils.getJython27InterpreterNames().isEmpty()) {
|
||||
if (PyDevUtils.getJythonInterpreterNames().isEmpty()) {
|
||||
File ghidraDir = ghidraInstallationPage.getGhidraInstallDir();
|
||||
File jythonFile = findJythonInterpreter(ghidraDir);
|
||||
File jythonLib = findJythonLibrary(ghidraDir);
|
||||
|
@ -111,7 +177,7 @@ public class EnablePythonWizardPage extends WizardPage {
|
|||
PyDevUtils.addJythonInterpreter("jython_" + ghidraDir.getName(),
|
||||
jythonFile, jythonLib);
|
||||
populateJythonCombo();
|
||||
validate();
|
||||
validate(null);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
@ -124,80 +190,142 @@ public class EnablePythonWizardPage extends WizardPage {
|
|||
PyDevUtils.getJythonPreferencePageId(), null, null);
|
||||
dialog.open();
|
||||
populateJythonCombo();
|
||||
validate();
|
||||
validate(null);
|
||||
});
|
||||
|
||||
validate();
|
||||
validate(savedPyGhidraInterpreter);
|
||||
setControl(container);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether or not Python should be enabled.
|
||||
*
|
||||
* @return True if python should be enabled; otherwise, false.
|
||||
*/
|
||||
public boolean shouldEnablePython() {
|
||||
return enablePythonCheckboxButton.getSelection();
|
||||
@Override
|
||||
public void setVisible(boolean visible) {
|
||||
if (visible) {
|
||||
validate(populatePyGhidraCombo(null));
|
||||
}
|
||||
super.setVisible(visible);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the name of the Jython interpreter to use.
|
||||
*
|
||||
* @return The name of the Jython interpreter to use. Could be null of Python isn't
|
||||
* enabled.
|
||||
* {@return the project Python interpreter to use}
|
||||
*/
|
||||
public String getJythonInterpreterName() {
|
||||
if (enablePythonCheckboxButton.getSelection()) {
|
||||
return jythonCombo.getText();
|
||||
public ProjectPythonInterpreter getProjectPythonInterpreter() {
|
||||
if (pyghidraButton.getSelection()) {
|
||||
return new ProjectPythonInterpreter(pyghidraCombo.getText(),
|
||||
ProjectPythonInterpreterType.PYGHIDRA);
|
||||
}
|
||||
return null;
|
||||
if (jythonButton.getSelection()) {
|
||||
return new ProjectPythonInterpreter(jythonCombo.getText(),
|
||||
ProjectPythonInterpreterType.JYTHON);
|
||||
}
|
||||
return new ProjectPythonInterpreter(null, ProjectPythonInterpreterType.NONE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates the fields on the page and updates the page's status.
|
||||
* Should be called every time a field on the page changes.
|
||||
*
|
||||
* @param pyghidraInterpreter The Python interpreter used to launch PyGhidra (could be null if
|
||||
* unknown).
|
||||
*/
|
||||
private void validate() {
|
||||
private void validate(File pyghidraInterpreter) {
|
||||
String message = null;
|
||||
boolean pyDevInstalled = PyDevUtils.isSupportedPyDevInstalled();
|
||||
boolean pyDevEnabled = enablePythonCheckboxButton.getSelection();
|
||||
boolean comboEnabled = pyDevInstalled && pyDevEnabled;
|
||||
boolean pyghidraSupported = PyDevUtils.isSupportedPyGhidraPyDevInstalled();
|
||||
boolean jythonSupported = PyDevUtils.isSupportedJythonPyDevInstalled();
|
||||
|
||||
if (pyDevEnabled) {
|
||||
if (!pyDevInstalled) {
|
||||
if (pyghidraButton.getSelection()) {
|
||||
if (!pyghidraSupported) {
|
||||
message = "PyDev version " + PyDevUtils.MIN_SUPPORTED_VERSION +
|
||||
" - " + PyDevUtils.MAX_SUPPORTED_VERSION + " is not installed.";
|
||||
" or later is not installed.";
|
||||
}
|
||||
else {
|
||||
try {
|
||||
List<String> interpreters = PyDevUtils.getJython27InterpreterNames();
|
||||
if (pyghidraInterpreter == null) {
|
||||
pyghidraInterpreter = findPyGhidraInterpreter();
|
||||
}
|
||||
if (pyghidraInterpreter == null) {
|
||||
message =
|
||||
"Please first launch PyGhidra to associate the Ghidra installation with a supported version of Python.";
|
||||
pyghidraSupported = false;
|
||||
}
|
||||
else {
|
||||
List<String> interpreters =
|
||||
PyDevUtils.getPyGhidraInterpreterNames(pyghidraInterpreter);
|
||||
if (interpreters.isEmpty()) {
|
||||
message = "No Jython interpreters found. Click the + button to add one.";
|
||||
message ="No PyGhidra interpreters found. Click the + button or set project type to \"None\".";
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (OperationNotSupportedException e) {
|
||||
message = "PyDev version is not supported.";
|
||||
comboEnabled = false;
|
||||
message = "PyDev version is not supported for Jython.";
|
||||
pyghidraSupported = false;
|
||||
}
|
||||
catch (Exception e) {
|
||||
message =
|
||||
"Failed to lookup Python interpreter associated with the Ghidra installation.";
|
||||
pyghidraSupported = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (jythonButton.getSelection()) {
|
||||
if (!jythonSupported) {
|
||||
message = "PyDev version " + PyDevUtils.MIN_SUPPORTED_VERSION +
|
||||
" - " + PyDevUtils.MAX_JYTHON_SUPPORTED_VERSION + " is not installed.";
|
||||
}
|
||||
else {
|
||||
try {
|
||||
List<String> interpreters = PyDevUtils.getJythonInterpreterNames();
|
||||
if (interpreters.isEmpty()) {
|
||||
message =
|
||||
"No Jython interpreters found. Click the + button or set project type to \"None\".";
|
||||
}
|
||||
}
|
||||
catch (OperationNotSupportedException e) {
|
||||
message = "PyDev version is not supported for Jython.";
|
||||
jythonSupported = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
jythonCombo.setEnabled(comboEnabled);
|
||||
addJythonButton.setEnabled(comboEnabled);
|
||||
pyghidraCombo.setEnabled(pyghidraButton.getSelection() && pyghidraSupported);
|
||||
addPyGhidraButton.setEnabled(pyghidraButton.getSelection() && pyghidraSupported);
|
||||
jythonCombo.setEnabled(jythonButton.getSelection() && jythonSupported);
|
||||
addJythonButton.setEnabled(jythonButton.getSelection() && jythonSupported);
|
||||
|
||||
setErrorMessage(message);
|
||||
setPageComplete(message == null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Populates the Jython combo box with discovered Jython names.
|
||||
* Populates the PyGhidra combo box with discovered PyGhidra interpreter names.
|
||||
*
|
||||
* @param pyghidraInterpreter The Python interpreter used to launch PyGhidra (could be null if
|
||||
* unknown).
|
||||
* @return The Python interpreter used to launch PyGhidra (could be null if unknown).
|
||||
*/
|
||||
private File populatePyGhidraCombo(File pyghidraInterpreter) {
|
||||
pyghidraCombo.removeAll();
|
||||
try {
|
||||
if (pyghidraInterpreter == null) {
|
||||
pyghidraInterpreter = findPyGhidraInterpreter();
|
||||
}
|
||||
PyDevUtils.getPyGhidraInterpreterNames(pyghidraInterpreter).forEach(pyghidraCombo::add);
|
||||
}
|
||||
catch (Exception e) {
|
||||
// Nothing to do. Combo should and will be empty.
|
||||
}
|
||||
if (pyghidraCombo.getItemCount() > 0) {
|
||||
pyghidraCombo.select(0);
|
||||
}
|
||||
return pyghidraInterpreter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Populates the Jython combo box with discovered Jython interpreter names.
|
||||
*/
|
||||
private void populateJythonCombo() {
|
||||
jythonCombo.removeAll();
|
||||
try {
|
||||
for (String jythonName : PyDevUtils.getJython27InterpreterNames()) {
|
||||
jythonCombo.add(jythonName);
|
||||
}
|
||||
PyDevUtils.getJythonInterpreterNames().forEach(jythonCombo::add);
|
||||
}
|
||||
catch (OperationNotSupportedException e) {
|
||||
// Nothing to do. Combo should and will be empty.
|
||||
|
@ -207,6 +335,32 @@ public class EnablePythonWizardPage extends WizardPage {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Find's the Python interpreter file that was used to launch PyGhidra.
|
||||
*
|
||||
* @return The Python interpreter file that was used to launch PyGhidra, or null if one could
|
||||
* not be found.
|
||||
* @throws Exception if there was a problem finding the Python interpreter.
|
||||
*/
|
||||
private File findPyGhidraInterpreter() throws Exception {
|
||||
File ghidraDir = ghidraInstallationPage.getGhidraInstallDir();
|
||||
AppConfig appConfig = new AppConfig(ghidraDir);
|
||||
List<String> cmd = appConfig.getSavedPythonCommand();
|
||||
if (cmd == null) {
|
||||
return null;
|
||||
}
|
||||
cmd.add("-c");
|
||||
cmd.add("import sys; print(sys.executable)");
|
||||
Process p = new ProcessBuilder(cmd).redirectErrorStream(true).start();
|
||||
BufferedReader reader =
|
||||
new BufferedReader(new InputStreamReader(p.getInputStream()));
|
||||
String pythonExecutable = reader.readLine();
|
||||
if (p.waitFor() == 0) {
|
||||
return new File(pythonExecutable);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find's a Jython interpreter file in the given Ghidra installation directory.
|
||||
*
|
||||
|
|
|
@ -13,6 +13,7 @@ GhidraDevPlugin/icons/GhidraIcon16_bw.png||GHIDRA||||END|
|
|||
GhidraDevPlugin/icons/brick_add.png||FAMFAMFAM Icons - CC 2.5|||famfamfam silk icon set|END|
|
||||
GhidraDevPlugin/icons/brick_go.png||FAMFAMFAM Icons - CC 2.5|||famfamfam silk icon set|END|
|
||||
GhidraDevPlugin/icons/folder_link.png||FAMFAMFAM Icons - CC 2.5|||famfamfam silk icon set|END|
|
||||
GhidraDevPlugin/icons/python.png||GHIDRA||||END|
|
||||
GhidraDevPlugin/icons/script_add.png||FAMFAMFAM Icons - CC 2.5|||famfamfam silk icon set|END|
|
||||
GhidraDevPlugin/icons/script_code_red.png||FAMFAMFAM Icons - CC 2.5|||famfamfam silk icon set|END|
|
||||
GhidraDevPlugin/plugin.xml||GHIDRA||||END|
|
||||
|
|
|
@ -78,20 +78,20 @@ public class LaunchSupport {
|
|||
try {
|
||||
|
||||
File installDir = new File(installDirPath).getCanonicalFile(); // change relative path to absolute
|
||||
JavaConfig javaConfig = new JavaConfig(installDir);
|
||||
AppConfig appConfig = new AppConfig(installDir);
|
||||
JavaFinder javaFinder = JavaFinder.create();
|
||||
|
||||
// Pass control to a mode-specific handler
|
||||
switch (mode.toLowerCase()) {
|
||||
case "-java_home":
|
||||
exitCode = handleJavaHome(javaConfig, javaFinder, JavaFilter.ANY, ask, save);
|
||||
exitCode = handleJavaHome(appConfig, javaFinder, JavaFilter.ANY, ask, save);
|
||||
break;
|
||||
case "-jdk_home":
|
||||
exitCode =
|
||||
handleJavaHome(javaConfig, javaFinder, JavaFilter.JDK_ONLY, ask, save);
|
||||
handleJavaHome(appConfig, javaFinder, JavaFilter.JDK_ONLY, ask, save);
|
||||
break;
|
||||
case "-vmargs":
|
||||
exitCode = handleVmArgs(javaConfig);
|
||||
exitCode = handleVmArgs(appConfig);
|
||||
break;
|
||||
default:
|
||||
System.err.println("LaunchSupport received illegal argument: " + mode);
|
||||
|
@ -109,7 +109,7 @@ public class LaunchSupport {
|
|||
* Handles figuring out a Java home directory to use for the launch. If it is successfully
|
||||
* determined, an exit code that indicates success is returned.
|
||||
*
|
||||
* @param javaConfig The Java configuration that defines what we support.
|
||||
* @param appConfig The appConfig configuration that defines what we support.
|
||||
* @param javaFinder The Java finder.
|
||||
* @param javaFilter A filter used to restrict what kind of Java installations we search for.
|
||||
* @param ask True to interact with the user to they can specify a Java home directory.
|
||||
|
@ -120,12 +120,12 @@ public class LaunchSupport {
|
|||
* successfully determined.
|
||||
* @throws IOException if there was a disk-related problem.
|
||||
*/
|
||||
private static int handleJavaHome(JavaConfig javaConfig, JavaFinder javaFinder,
|
||||
private static int handleJavaHome(AppConfig appConfig, JavaFinder javaFinder,
|
||||
JavaFilter javaFilter, boolean ask, boolean save) throws IOException {
|
||||
if (ask) {
|
||||
return askJavaHome(javaConfig, javaFinder, javaFilter);
|
||||
return askJavaHome(appConfig, javaFinder, javaFilter);
|
||||
}
|
||||
return findJavaHome(javaConfig, javaFinder, javaFilter, save);
|
||||
return findJavaHome(appConfig, javaFinder, javaFilter, save);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -133,7 +133,7 @@ public class LaunchSupport {
|
|||
* found, its path is printed to STDOUT and an exit code that indicates success is
|
||||
* returned. Otherwise, nothing is printed to STDOUT and an error exit code is returned.
|
||||
*
|
||||
* @param javaConfig The Java configuration that defines what we support.
|
||||
* @param appConfig The application configuration that defines what we support.
|
||||
* @param javaFinder The Java finder.
|
||||
* @param javaFilter A filter used to restrict what kind of Java installations we search for.
|
||||
* @param save True if the determined Java home directory should get saved to a file.
|
||||
|
@ -141,19 +141,19 @@ public class LaunchSupport {
|
|||
* successfully determined.
|
||||
* @throws IOException if there was a problem saving the java home to disk.
|
||||
*/
|
||||
private static int findJavaHome(JavaConfig javaConfig, JavaFinder javaFinder,
|
||||
private static int findJavaHome(AppConfig appConfig, JavaFinder javaFinder,
|
||||
JavaFilter javaFilter, boolean save) throws IOException {
|
||||
|
||||
File javaHomeDir;
|
||||
LaunchProperties launchProperties = javaConfig.getLaunchProperties();
|
||||
LaunchProperties launchProperties = appConfig.getLaunchProperties();
|
||||
|
||||
// PRIORITY 1: JAVA_HOME_OVERRIDE property
|
||||
// If a valid java home override is specified in the launch properties, use that.
|
||||
// Someone presumably wants to force that specific version.
|
||||
javaHomeDir = launchProperties.getJavaHomeOverride();
|
||||
if (javaConfig.isSupportedJavaHomeDir(javaHomeDir, javaFilter)) {
|
||||
if (appConfig.isSupportedJavaHomeDir(javaHomeDir, javaFilter)) {
|
||||
if (save) {
|
||||
javaConfig.saveJavaHome(javaHomeDir);
|
||||
appConfig.saveJavaHome(javaHomeDir);
|
||||
}
|
||||
System.out.println(javaHomeDir);
|
||||
return EXIT_SUCCESS;
|
||||
|
@ -162,10 +162,10 @@ public class LaunchSupport {
|
|||
// PRIORITY 2: Java on PATH
|
||||
// This program (LaunchSupport) was started with the Java on the PATH. Try to use this one
|
||||
// next because it is most likely the one that is being upgraded on the user's system.
|
||||
javaHomeDir = javaFinder.findSupportedJavaHomeFromCurrentJavaHome(javaConfig, javaFilter);
|
||||
javaHomeDir = javaFinder.findSupportedJavaHomeFromCurrentJavaHome(appConfig, javaFilter);
|
||||
if (javaHomeDir != null) {
|
||||
if (save) {
|
||||
javaConfig.saveJavaHome(javaHomeDir);
|
||||
appConfig.saveJavaHome(javaHomeDir);
|
||||
}
|
||||
System.out.println(javaHomeDir);
|
||||
return EXIT_SUCCESS;
|
||||
|
@ -173,19 +173,19 @@ public class LaunchSupport {
|
|||
|
||||
// PRIORITY 3: Last used Java
|
||||
// Check to see if a prior launch resulted in that Java being saved. If so, try to use that.
|
||||
javaHomeDir = javaConfig.getSavedJavaHome();
|
||||
if (javaConfig.isSupportedJavaHomeDir(javaHomeDir, javaFilter)) {
|
||||
javaHomeDir = appConfig.getSavedJavaHome();
|
||||
if (appConfig.isSupportedJavaHomeDir(javaHomeDir, javaFilter)) {
|
||||
System.out.println(javaHomeDir);
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
// PRIORITY 4: Find all supported Java installations, and use the newest.
|
||||
List<File> javaHomeDirs =
|
||||
javaFinder.findSupportedJavaHomeFromInstallations(javaConfig, javaFilter);
|
||||
javaFinder.findSupportedJavaHomeFromInstallations(appConfig, javaFilter);
|
||||
if (!javaHomeDirs.isEmpty()) {
|
||||
javaHomeDir = javaHomeDirs.iterator().next();
|
||||
if (save) {
|
||||
javaConfig.saveJavaHome(javaHomeDir);
|
||||
appConfig.saveJavaHome(javaHomeDir);
|
||||
}
|
||||
System.out.println(javaHomeDir);
|
||||
return EXIT_SUCCESS;
|
||||
|
@ -199,7 +199,7 @@ public class LaunchSupport {
|
|||
* If a valid Java home directory was successfully determined, it is saved to the user's
|
||||
* Java home save file, and an exit code that indicates success is returned.
|
||||
*
|
||||
* @param javaConfig The Java configuration that defines what we support.
|
||||
* @param appConfig The application configuration that defines what we support.
|
||||
* @param javaFinder The Java finder.
|
||||
* @param javaFilter A filter used to restrict what kind of Java installations we search for.
|
||||
* * @return A suggested exit code based on whether or not a valid Java home directory was
|
||||
|
@ -207,13 +207,13 @@ public class LaunchSupport {
|
|||
* @throws IOException if there was a problem interacting with the user, or saving the java
|
||||
* home location to disk.
|
||||
*/
|
||||
private static int askJavaHome(JavaConfig javaConfig, JavaFinder javaFinder,
|
||||
private static int askJavaHome(AppConfig appConfig, JavaFinder javaFinder,
|
||||
JavaFilter javaFilter) throws IOException {
|
||||
|
||||
String javaName = javaFilter.equals(JavaFilter.JDK_ONLY) ? "JDK" : "Java";
|
||||
String javaRange;
|
||||
int min = javaConfig.getMinSupportedJava();
|
||||
int max = javaConfig.getMaxSupportedJava();
|
||||
int min = appConfig.getMinSupportedJava();
|
||||
int max = appConfig.getMaxSupportedJava();
|
||||
if (min == max) {
|
||||
javaRange = min + "";
|
||||
}
|
||||
|
@ -226,7 +226,7 @@ public class LaunchSupport {
|
|||
|
||||
System.out.println("******************************************************************");
|
||||
System.out.println(
|
||||
javaName + " " + javaRange + " (" + javaConfig.getSupportedArchitecture() +
|
||||
javaName + " " + javaRange + " (" + appConfig.getSupportedArchitecture() +
|
||||
"-bit) could not be found and must be manually chosen!");
|
||||
System.out.println("******************************************************************");
|
||||
|
||||
|
@ -254,13 +254,13 @@ public class LaunchSupport {
|
|||
continue;
|
||||
}
|
||||
try {
|
||||
JavaVersion javaVersion = javaConfig.getJavaVersion(javaHomeDir, javaFilter);
|
||||
if (javaConfig.isJavaVersionSupported(javaVersion)) {
|
||||
JavaVersion javaVersion = appConfig.getJavaVersion(javaHomeDir, javaFilter);
|
||||
if (appConfig.isJavaVersionSupported(javaVersion)) {
|
||||
break;
|
||||
}
|
||||
System.out.println(
|
||||
"Java version " + javaVersion + " is outside of supported range: [" +
|
||||
javaRange + " " + javaConfig.getSupportedArchitecture() + "-bit]");
|
||||
javaRange + " " + appConfig.getSupportedArchitecture() + "-bit]");
|
||||
}
|
||||
catch (FileNotFoundException e) {
|
||||
System.out.println(
|
||||
|
@ -271,7 +271,7 @@ public class LaunchSupport {
|
|||
}
|
||||
}
|
||||
|
||||
File javaHomeSaveFile = javaConfig.saveJavaHome(javaHomeDir);
|
||||
File javaHomeSaveFile = appConfig.saveJavaHome(javaHomeDir);
|
||||
System.out.println("Saved changes to " + javaHomeSaveFile);
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
@ -281,18 +281,18 @@ public class LaunchSupport {
|
|||
* to STDOUT as a new-line delimited string that can be parsed and added to the command line,
|
||||
* and an exit code that indicates success is returned.
|
||||
|
||||
* @param javaConfig The Java configuration that defines what we support.
|
||||
* @param appConfig The appConfig configuration that defines what we support.
|
||||
* @return A suggested exit code based on whether or not the VM arguments were successfully
|
||||
* gotten.
|
||||
*/
|
||||
private static int handleVmArgs(JavaConfig javaConfig) {
|
||||
if (javaConfig.getLaunchProperties() == null) {
|
||||
private static int handleVmArgs(AppConfig appConfig) {
|
||||
if (appConfig.getLaunchProperties() == null) {
|
||||
System.out.println("Launch properties file was not specified!");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
// Force newline style to make cross-platform parsing consistent
|
||||
javaConfig.getLaunchProperties().getVmArgList().forEach(e -> System.out.print(e + "\r\n"));
|
||||
appConfig.getLaunchProperties().getVmArgList().forEach(e -> System.out.print(e + "\r\n"));
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,21 +17,23 @@ package ghidra.launch;
|
|||
|
||||
import java.io.*;
|
||||
import java.text.ParseException;
|
||||
import java.util.Properties;
|
||||
import java.util.*;
|
||||
|
||||
import ghidra.launch.JavaFinder.JavaFilter;
|
||||
|
||||
/**
|
||||
* Class to determine and represent a required Java configuration, including minimum and maximum
|
||||
* supported versions, compiler compliance level, etc.
|
||||
* Class to determine and represent a required application configuration, including minimum and
|
||||
* maximum supported Java versions, compiler compliance level, etc.
|
||||
*/
|
||||
public class JavaConfig {
|
||||
public class AppConfig {
|
||||
|
||||
private static final String LAUNCH_PROPERTIES_NAME = "launch.properties";
|
||||
private static final String JAVA_HOME_SAVE_NAME = "java_home.save";
|
||||
private static final String PYTHON_COMMAND_SAVE_NAME = "python_command.save";
|
||||
|
||||
private LaunchProperties launchProperties;
|
||||
private File javaHomeSaveFile;
|
||||
private File pythonCommandSaveFile;
|
||||
|
||||
private String applicationName; // example: Ghidra
|
||||
private String applicationVersion; // example: 9.0.1
|
||||
|
@ -42,43 +44,43 @@ public class JavaConfig {
|
|||
private String compilerComplianceLevel;
|
||||
|
||||
/**
|
||||
* Creates a new Java configuration for the given installation.
|
||||
* Creates a new application configuration for the given installation.
|
||||
*
|
||||
* @param installDir The installation directory.
|
||||
* @throws FileNotFoundException if a required file was not found.
|
||||
* @throws IOException if there was a problem reading a required file.
|
||||
* @throws ParseException if there was a problem parsing a required file.
|
||||
*/
|
||||
public JavaConfig(File installDir) throws FileNotFoundException, IOException, ParseException {
|
||||
public AppConfig(File installDir) throws FileNotFoundException, IOException, ParseException {
|
||||
initApplicationProperties(installDir);
|
||||
initLaunchProperties(installDir);
|
||||
initJavaHomeSaveFile(installDir);
|
||||
javaHomeSaveFile = getSaveFile(installDir, JAVA_HOME_SAVE_NAME);
|
||||
pythonCommandSaveFile = getSaveFile(installDir, PYTHON_COMMAND_SAVE_NAME);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the launch properties associated with this Java configuration. Certain aspects of the
|
||||
* Java configuration are stored in the launch properties.
|
||||
* Gets the launch properties associated with this application configuration.
|
||||
*
|
||||
* @return The launch properties associated with this Java configuration. Could be null if
|
||||
* this Java configuration does not use launch properties.
|
||||
* @return The launch properties associated with this application configuration. Could be null
|
||||
* if this application configuration does not use launch properties.
|
||||
*/
|
||||
public LaunchProperties getLaunchProperties() {
|
||||
return launchProperties;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the Java configuration's minimum supported major Java version.
|
||||
* Gets the application configuration's minimum supported major Java version.
|
||||
*
|
||||
* @return The Java configuration's minimum supported major Java version.
|
||||
* @return The application configuration's minimum supported major Java version.
|
||||
*/
|
||||
public int getMinSupportedJava() {
|
||||
return minSupportedJava;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the Java configuration's maximum supported major Java version.
|
||||
* Gets the application configuration's maximum supported major Java version.
|
||||
*
|
||||
* @return The Java configuration's maximum supported major Java version. If there is no
|
||||
* @return The application configuration's maximum supported major Java version. If there is no
|
||||
* restriction, the value will be 0.
|
||||
*/
|
||||
public int getMaxSupportedJava() {
|
||||
|
@ -86,20 +88,20 @@ public class JavaConfig {
|
|||
}
|
||||
|
||||
/**
|
||||
* Gets the Java configuration's supported Java architecture. All supported Java
|
||||
* Gets the application configuration's supported Java architecture. All supported Java
|
||||
* configurations must have an architecture of <code>64</code>.
|
||||
*
|
||||
* @return The Java configuration's supported Java architecture (64).
|
||||
* @return The application configuration's supported Java architecture (64).
|
||||
*/
|
||||
public int getSupportedArchitecture() {
|
||||
return 64;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the Java configuration's compiler compliance level that was used to build the
|
||||
* associated installation.
|
||||
* Gets the application configuration's Java compiler compliance level that was used to build
|
||||
* the associated installation.
|
||||
*
|
||||
* @return The Java configuration's compiler compliance level.
|
||||
* @return The application configuration's compiler compliance level.
|
||||
*/
|
||||
public String getCompilerComplianceLevel() {
|
||||
return compilerComplianceLevel;
|
||||
|
@ -115,10 +117,13 @@ public class JavaConfig {
|
|||
public File getSavedJavaHome() throws IOException {
|
||||
try (BufferedReader reader = new BufferedReader(new FileReader(javaHomeSaveFile))) {
|
||||
String line = reader.readLine().trim();
|
||||
if (line != null && !line.isEmpty()) {
|
||||
if (line != null) {
|
||||
line = line.trim();
|
||||
if (!line.isEmpty()) {
|
||||
return new File(line);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (FileNotFoundException e) {
|
||||
// Fall through to return null
|
||||
}
|
||||
|
@ -148,12 +153,12 @@ public class JavaConfig {
|
|||
}
|
||||
|
||||
/**
|
||||
* Tests to see if the given directory is a supported Java home directory for this Java
|
||||
* Tests to see if the given directory is a supported Java home directory for this application
|
||||
* configuration.
|
||||
*
|
||||
* @param dir The directory to test.
|
||||
* @param javaFilter A filter used to restrict what kind of Java installations we support.
|
||||
* @return True if the given directory is a supported Java home directory for this Java
|
||||
* @return True if the given directory is a supported Java home directory for this application
|
||||
* configuration.
|
||||
*/
|
||||
public boolean isSupportedJavaHomeDir(File dir, JavaFilter javaFilter) {
|
||||
|
@ -166,10 +171,10 @@ public class JavaConfig {
|
|||
}
|
||||
|
||||
/**
|
||||
* Tests to see if the given Java version is supported by this Java launch configuration.
|
||||
* Tests to see if the given Java version is supported by this application configuration.
|
||||
*
|
||||
* @param javaVersion The java version to check.
|
||||
* @return True if the given Java version is supported by this Java launch configuration.
|
||||
* @return True if the given Java version is supported by this application configuration.
|
||||
*/
|
||||
public boolean isJavaVersionSupported(JavaVersion javaVersion) {
|
||||
if (javaVersion.getArchitecture() != getSupportedArchitecture()) {
|
||||
|
@ -233,6 +238,30 @@ public class JavaConfig {
|
|||
return runAndGetJavaVersion(javaExecutable);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the Python command from the user's Python command save file.
|
||||
*
|
||||
* @return The Python command from the user's Python command save file, or null if the file
|
||||
* does not exist or is empty.
|
||||
* @throws IOException if there was a problem reading the Python command save file.
|
||||
*/
|
||||
public List<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".
|
||||
*
|
||||
|
@ -354,13 +383,13 @@ public class JavaConfig {
|
|||
}
|
||||
|
||||
/**
|
||||
* Initializes the Java home save file.
|
||||
* Gets the given "save file".
|
||||
*
|
||||
* @param installDir The Ghidra installation directory. This is the directory that has the
|
||||
* "Ghidra" subdirectory in it.
|
||||
* @throws FileNotFoundException if the user's home directory was not found.
|
||||
*/
|
||||
private void initJavaHomeSaveFile(File installDir) throws FileNotFoundException {
|
||||
private File getSaveFile(File installDir, String saveFileName) throws FileNotFoundException {
|
||||
boolean isDev = new File(installDir, "build.gradle").isFile();
|
||||
String appName = applicationName.replaceAll("\\s", "").toLowerCase();
|
||||
|
||||
|
@ -385,16 +414,14 @@ public class JavaConfig {
|
|||
// Handle legacy application layout
|
||||
if (applicationLayoutVersion.equals("1")) {
|
||||
userSettingsDir = new File(userHomeDir, "." + appName + "/." + userSettingsDirName);
|
||||
javaHomeSaveFile = new File(userSettingsDir, JAVA_HOME_SAVE_NAME);
|
||||
return;
|
||||
return new File(userSettingsDir, saveFileName);
|
||||
}
|
||||
|
||||
// Look for XDG environment variable
|
||||
String xdgConfigHomeDirStr = System.getenv("XDG_CONFIG_HOME");
|
||||
if (xdgConfigHomeDirStr != null && !xdgConfigHomeDirStr.isEmpty()) {
|
||||
userSettingsDir = new File(xdgConfigHomeDirStr, appName + "/" + userSettingsDirName);
|
||||
javaHomeSaveFile = new File(userSettingsDir, JAVA_HOME_SAVE_NAME);
|
||||
return;
|
||||
return new File(userSettingsDir, saveFileName);
|
||||
}
|
||||
|
||||
// Look in current user settings directory
|
||||
|
@ -420,7 +447,7 @@ public class JavaConfig {
|
|||
"Failed to find the user settings directory: Unsupported operating system.");
|
||||
}
|
||||
|
||||
javaHomeSaveFile = new File(userSettingsDir, JAVA_HOME_SAVE_NAME);
|
||||
return new File(userSettingsDir, saveFileName);
|
||||
}
|
||||
|
||||
/**
|
|
@ -79,11 +79,11 @@ public abstract class JavaFinder {
|
|||
* Returns a list of supported Java home directories from discovered Java installations.
|
||||
* The list is sorted from newest Java version to oldest.
|
||||
*
|
||||
* @param javaConfig The Java configuration that defines what we support.
|
||||
* @param appConfig The appConfig configuration that defines what we support.
|
||||
* @param javaFilter A filter used to restrict what kind of Java installations we search for.
|
||||
* @return A sorted list of supported Java home directories from discovered Java installations.
|
||||
*/
|
||||
public List<File> findSupportedJavaHomeFromInstallations(JavaConfig javaConfig,
|
||||
public List<File> findSupportedJavaHomeFromInstallations(AppConfig appConfig,
|
||||
JavaFilter javaFilter) {
|
||||
Set<File> potentialJavaHomeSet = new TreeSet<>();
|
||||
for (File javaRootInstallDir : getJavaRootInstallDirs()) {
|
||||
|
@ -107,8 +107,8 @@ public abstract class JavaFinder {
|
|||
for (File potentialJavaHomeDir : potentialJavaHomeSet) {
|
||||
try {
|
||||
JavaVersion javaVersion =
|
||||
javaConfig.getJavaVersion(potentialJavaHomeDir, javaFilter);
|
||||
if (javaConfig.isJavaVersionSupported(javaVersion)) {
|
||||
appConfig.getJavaVersion(potentialJavaHomeDir, javaFilter);
|
||||
if (appConfig.isJavaVersionSupported(javaVersion)) {
|
||||
javaHomeToVersionMap.put(potentialJavaHomeDir, javaVersion);
|
||||
}
|
||||
}
|
||||
|
@ -130,12 +130,12 @@ public abstract class JavaFinder {
|
|||
* Returns the Java home directory corresponding to the current "java.home" system
|
||||
* property (if it supported).
|
||||
*
|
||||
* @param javaConfig The Java configuration that defines what we support.
|
||||
* @param appConfig The appConfig configuration that defines what we support.
|
||||
* @param javaFilter A filter used to restrict what kind of Java installations we search for.
|
||||
* @return The Java home directory corresponding to the current "java.home" system property.
|
||||
* Could be null if the current "java.home" is not supported.
|
||||
*/
|
||||
public File findSupportedJavaHomeFromCurrentJavaHome(JavaConfig javaConfig,
|
||||
public File findSupportedJavaHomeFromCurrentJavaHome(AppConfig appConfig,
|
||||
JavaFilter javaFilter) {
|
||||
Set<File> potentialJavaHomeSet = new HashSet<>();
|
||||
String javaHomeProperty = System.getProperty("java.home");
|
||||
|
@ -149,8 +149,8 @@ public abstract class JavaFinder {
|
|||
}
|
||||
for (File potentialJavaHomeDir : potentialJavaHomeSet) {
|
||||
try {
|
||||
if (javaConfig.isJavaVersionSupported(
|
||||
javaConfig.getJavaVersion(potentialJavaHomeDir, javaFilter))) {
|
||||
if (appConfig.isJavaVersionSupported(
|
||||
appConfig.getJavaVersion(potentialJavaHomeDir, javaFilter))) {
|
||||
return potentialJavaHomeDir;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -432,6 +432,9 @@ task assembleDistribution (type: Copy) {
|
|||
from ("${ROOT_PROJECT_DIR}/build/typestubs/src") {
|
||||
into 'docs/ghidra_stubs/typestubs'
|
||||
}
|
||||
from ("${ROOT_PROJECT_DIR}/build/typestubs/pypredef") {
|
||||
into 'docs/ghidra_stubs/pypredef'
|
||||
}
|
||||
from (createGhidraStubsWheel) {
|
||||
into 'docs/ghidra_stubs'
|
||||
}
|
||||
|
|
|
@ -95,9 +95,9 @@ ext.deps = [
|
|||
destination: file("${DEPS_DIR}/BSim")
|
||||
],
|
||||
[
|
||||
name: "PyDev 6.3.1.zip",
|
||||
url: "https://sourceforge.net/projects/pydev/files/pydev/PyDev%206.3.1/PyDev%206.3.1.zip",
|
||||
sha256: "4d81fe9d8afe7665b8ea20844d3f5107f446742927c59973eade4f29809b0699",
|
||||
name: "PyDev 9.3.0.zip",
|
||||
url: "https://sourceforge.net/projects/pydev/files/pydev/PyDev%209.3.0/PyDev%209.3.0.zip",
|
||||
sha256: "45398edf2adb56078a80bc88a919941578f0c0b363efbdd011bfd158a99b112e",
|
||||
destination: file("${DEPS_DIR}/GhidraDev")
|
||||
],
|
||||
[
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue