Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 12 additions & 5 deletions .github/workflows/test_extension.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,13 @@ jobs:
with:
version: "11.2.1"


- name: Linting with klint
run: gradle ktlintCheck

- name: Run unit tests
run: gradle test

- name: Build Extension
run: gradle buildExtension

Expand All @@ -33,8 +40,8 @@ jobs:
path: ./dist/*zip
retention-days: 1

- name: Install Extension
run: unzip ./dist/*zip -d $GHIDRA_INSTALL_DIR/Ghidra/Extensions

- name: Run Tests
run: echo "Execute your tests here!"
# - name: Install Extension
# run: unzip ./dist/*zip -d $GHIDRA_INSTALL_DIR/Ghidra/Extensions
#
# - name: Run Full Integration Tests
# run: echo "Execute your tests here!"
31 changes: 20 additions & 11 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,9 @@


plugins {
id 'org.jetbrains.kotlin.jvm' version "1.6.0"
id 'org.jetbrains.kotlin.jvm' version "1.9.23"
id 'idea'
id "org.jlleitschuh.gradle.ktlint" version "12.1.2"
}

repositories {
Expand All @@ -34,25 +35,26 @@ else {
}
//----------------------END "DO NOT MODIFY" SECTION-------------------------------

// Set the JVM target to 11, as described in https://stackoverflow.com/a/44297713/13220684
// Ghidra requires 11 and the buildExtension.gradle sets this for Java
// IntelliJ will complain about the discrepancy between the Java and the Kotlin target otherwise
tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).configureEach {
kotlinOptions {
jvmTarget = "17"
}
kotlin {
jvmToolchain(21)
}

dependencies {
// If you are using the Ghidra Jupyter Plugin for Kotlin, add the following line to declare it as a dependency
// This allows using the extension methods and makes sure that the required Kotlin libraries are present and not
// conflicting
// api fileTree(dir: ghidraInstallDir + '/Ghidra/Extensions/GhidraJupyterKotlin/', include: "**/*.jar")


// Unit testing dependencies
testImplementation 'org.jetbrains.kotlin:kotlin-test'


}

// Make it explicit that the compilation depends on some libraries in the `lib` folder,
// otherwise gradle will issue warnings
compileKotlin.dependsOn(copyDependencies)
test {
useJUnitPlatform()
}

// Add the ghidra_scripts directory as an additional source set, so IntelliJ IDEA treats the scripts there as part of
// the module
Expand All @@ -65,3 +67,10 @@ sourceSets {
}
}
}

idea {
module {
downloadSources = true
downloadJavadoc = true
}
}
6 changes: 3 additions & 3 deletions ghidra_scripts/KotlinExtensionExampleScript.kt
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// SCRIPT DESCRIPTION
//@category Examples
//@toolbar world.png
// @category Examples
// @toolbar world.png

import ghidra.app.script.GhidraScript

Expand All @@ -10,4 +10,4 @@ class KotlinExtensionExampleScript : GhidraScript() {
override fun run() {
TODO("Script code goes here")
}
}
}
7 changes: 3 additions & 4 deletions src/main/kotlin/ghidra/examples/HelloWorldService.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
Expand All @@ -15,10 +15,9 @@
*/
package ghidra.examples

import ghidra.examples.KitchenSinkPlugin
import ghidra.framework.plugintool.ServiceInfo

@ServiceInfo(defaultProvider = [KitchenSinkPlugin::class])
interface HelloWorldService {
fun sayHello()
}
}
51 changes: 26 additions & 25 deletions src/main/kotlin/ghidra/examples/KitchenSinkPlugin.kt
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
Expand Down Expand Up @@ -44,7 +44,6 @@ import javax.swing.KeyStroke
* Class description goes here
*
*/
//@formatter:off
@PluginInfo(
status = PluginStatus.RELEASED,
packageName = ExamplesPluginPackage.NAME,
Expand All @@ -53,8 +52,8 @@ import javax.swing.KeyStroke
description = "Sample plugin to demonstrate services and action enablement, hello world. (Kotlin)",
servicesProvided = [HelloWorldService::class],
servicesRequired = [ProgramManager::class],
eventsProduced = [ProgramLocationPluginEvent::class]
) //@formatter:on
eventsProduced = [ProgramLocationPluginEvent::class],
)
class KitchenSinkPlugin(tool: PluginTool?) : ProgramPlugin(tool, false, false) {
private var helloProgramAction: DockingAction? = null
private var program: Program? = null
Expand All @@ -78,19 +77,21 @@ class KitchenSinkPlugin(tool: PluginTool?) : ProgramPlugin(tool, false, false) {
override fun sayHello() {
announce("Hello")
}
})
},
)
}

private fun setupActions() {
var action: DockingAction = object : DockingAction("Hello World", name) {
override fun actionPerformed(context: ActionContext) {
Msg.info(this, "Hello World:: action")
announce("Hello World")
var action: DockingAction =
object : DockingAction("Hello World", name) {
override fun actionPerformed(context: ActionContext) {
Msg.info(this, "Hello World:: action")
announce("Hello World")
}
}
}
val helloGroup = "Hello"

with (action) {
with(action) {
isEnabled = true
val prevImage = ResourceManager.loadImage(PREV_IMAGE)
menuBarData = MenuData(arrayOf("Misc", "Hello World"), prevImage, helloGroup)
Expand All @@ -102,13 +103,14 @@ class KitchenSinkPlugin(tool: PluginTool?) : ProgramPlugin(tool, false, false) {
tool.addAction(action)
}

action = object : DockingAction("Hello Program", name) {
override fun actionPerformed(context: ActionContext) {
Msg.info(this, "Hello Program:: action")
sayHelloProgram()
action =
object : DockingAction("Hello Program", name) {
override fun actionPerformed(context: ActionContext) {
Msg.info(this, "Hello Program:: action")
sayHelloProgram()
}
}
}
with (action) {
with(action) {
isEnabled = true
val nextImage = ResourceManager.loadImage(NEXT_IMAGE)
menuBarData = MenuData(arrayOf("Misc", "Hello Program"), nextImage, helloGroup)
Expand Down Expand Up @@ -136,18 +138,19 @@ class KitchenSinkPlugin(tool: PluginTool?) : ProgramPlugin(tool, false, false) {
}

protected fun sayHelloProgram() {
if (program != null){
if (program != null) {
// Kotlin infers that program could have been nulled again, so we have to assert it is not null with !!
// at the risk of maybe throwing a null pointer exception
announce("Hello ${program!!.name}")
}

}

protected fun announce(message: String?) {
JOptionPane.showMessageDialog(
null, message, "Hello World",
JOptionPane.INFORMATION_MESSAGE
null,
message,
"Hello World",
JOptionPane.INFORMATION_MESSAGE,
)
}

Expand All @@ -170,6 +173,4 @@ class KitchenSinkPlugin(tool: PluginTool?) : ProgramPlugin(tool, false, false) {
private const val NEXT_IMAGE = "images/right.png"
private const val PREV_IMAGE = "images/left.png"
}


}
}
10 changes: 10 additions & 0 deletions src/main/kotlin/ghidra/examples/Sample.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package ghidra.examples

class Sample() {
fun sum(
a: Int,
b: Int,
): Int {
return a + b
}
}
20 changes: 20 additions & 0 deletions src/test/kotlin/SampleTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import ghidra.examples.Sample
import resources.ResourceManager
import kotlin.test.Test
import kotlin.test.assertEquals

class SampleTest {
private val testSample: Sample = Sample()

@Test
fun testSum() {
val expected = 42
assertEquals(expected, testSample.sum(40, 2))
}

@Test
fun testResourceManager() {
val testJson = ResourceManager.getResource("testfile.txt")
assertEquals(testJson.readText(), "test")
}
}
1 change: 1 addition & 0 deletions src/test/resources/testfile.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
test
Loading