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
13 changes: 13 additions & 0 deletions .run/devReload.run.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="devReload" type="GradleRunConfiguration" factoryName="Gradle">
<ExternalSystemSettings>
<option name="executionTaskName" value=":plugin:devReload" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="externalSystemIdString" value="GRADLE" />
<option name="scriptParameters" value="" />
<option name="useModuleSettings" value="true" />
<option name="vmOptions" value="" />
</ExternalSystemSettings>
<method v="2" />
</configuration>
</component>
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ Edit-run loop:
1. Run `./gradlew installPlugin` to build the plugin and install it directly to Toolbox.
2. Restart the Toolbox to apply the installed plugin.

Alternatively, run `./gradlew :plugin:devReload` to build, install, and restart the Toolbox in a single step.
Add `-PskipToolboxRestart=true` to skip the restart, or `-PtoolboxExecutable=/path/to/jetbrains-toolbox` to point at a custom Toolbox executable.

To check the logs:
1. Go to the `About` page.
2. Click `Show log files`.
Expand Down
6 changes: 6 additions & 0 deletions build-logic/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,12 @@ gradlePlugin {
displayName = "Install Toolbox Plugin"
description = "Installs the plugin into the local Toolbox directory"
}
create("toolboxDev") {
id = "com.redhat.devtools.toolbox.dev"
implementationClass = "com.redhat.devtools.toolbox.buildlogic.DevToolboxPlugin"
displayName = "Dev Reload for Toolbox Plugin"
description = "Registers quit, launch, and dev-reload tasks to restart the local JetBrains Toolbox"
}
create("toolboxPublish") {
id = "com.redhat.devtools.toolbox.publish"
implementationClass = "com.redhat.devtools.toolbox.buildlogic.PublishToolboxPlugin"
Expand Down
112 changes: 112 additions & 0 deletions build-logic/src/main/kotlin/toolbox/buildlogic/DevToolboxPlugin.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
/*
* Copyright (c) 2026 Red Hat, Inc.
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Red Hat, Inc. - initial API and implementation
*/
package com.redhat.devtools.toolbox.buildlogic

import java.nio.file.Path
import org.gradle.api.DefaultTask
import org.gradle.api.GradleException
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.api.provider.Property
import org.gradle.api.tasks.Internal
import org.gradle.api.tasks.TaskAction
import org.gradle.work.DisableCachingByDefault

class DevToolboxPlugin : Plugin<Project> {

override fun apply(target: Project) {
val skipRestart = readFlag(target, "skipToolboxRestart")

val installTask = target.tasks.named("installPlugin")

val quitTask = target.tasks.register("quitToolbox", QuitToolboxTask::class.java) {
group = "toolbox"
description = "Requests the running JetBrains Toolbox application to quit and waits for it to exit"
restartDelayMs.set(readDelay(target))
skip.set(skipRestart)
onlyIf { !skip.get() }
mustRunAfter("installPlugin")
}

val launchTask = target.tasks.register("launchToolbox", LaunchToolboxTask::class.java) {
group = "toolbox"
description = "Launches the JetBrains Toolbox application"
skip.set(skipRestart)
if (!skipRestart) {
executable.set(ToolboxApp.resolveExecutable(target).toString())
}
onlyIf { !skip.get() }
mustRunAfter(quitTask)
}

target.tasks.register("devReload", DevReloadTask::class.java) {
group = "toolbox"
description = "Builds and installs the plugin, restarts JetBrains Toolbox, and applies the change"
dependsOn(installTask, quitTask, launchTask)
}
}

private fun readFlag(target: Project, name: String): Boolean =
(target.findProperty(name) as? String)?.toBoolean() == true

private fun readDelay(target: Project): Long =
(target.findProperty("toolboxRestartDelayMs") as? String)?.toLongOrNull() ?: 2000L

@DisableCachingByDefault(because = "Controls the running Toolbox application")
abstract class QuitToolboxTask : DefaultTask() {
@get:Internal
abstract val restartDelayMs: Property<Long>
@get:Internal
abstract val skip: Property<Boolean>

@TaskAction
fun quit() {
println("Requesting JetBrains Toolbox to quit...")
ToolboxApp.quit()
println("Waiting ${restartDelayMs.get()}ms for JetBrains Toolbox to exit...")
val exited = ToolboxApp.waitForExit(restartDelayMs.get())
if (exited) {
println("JetBrains Toolbox quit.")
} else {
throw GradleException(
"JetBrains Toolbox did not exit within ${restartDelayMs.get()}ms of the quit request. " +
"The quit request did not finish; launch was not performed. " +
"Increase toolboxRestartDelayMs or quit Toolbox manually and re-run."
)
}
}
}

@DisableCachingByDefault(because = "Launches the Toolbox application")
abstract class LaunchToolboxTask : DefaultTask() {
@get:Internal
abstract val skip: Property<Boolean>
@get:Internal
abstract val executable: Property<String>

@TaskAction
fun launch() {
val executable = this.executable.get()
println("Launching JetBrains Toolbox: $executable")
ToolboxApp.launch(Path.of(executable))
println("JetBrains Toolbox launched.")
}
}

@DisableCachingByDefault(because = "Orchestrates a Toolbox restart")
abstract class DevReloadTask : DefaultTask() {
@TaskAction
fun devReload() {
println("Dev reload complete: plugin installed and JetBrains Toolbox restarted.")
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,13 @@ import org.gradle.api.tasks.Input
import org.gradle.api.tasks.InputFile
import org.gradle.api.tasks.TaskAction
import org.gradle.work.DisableCachingByDefault
import java.nio.file.Path
import java.util.*
import kotlin.io.path.div

class InstallToolboxPlugin : Plugin<Project> {
override fun apply(target: Project) {
val installTask = target.tasks.register("installPlugin", InstallTask::class.java) {
group = "toolbox"
description = "Builds and installs the plugin into the local JetBrains Toolbox plugins directory"
extensionId.set(target.group.toString())
extensionJsonFile.set(target.layout.buildDirectory.file("generated/extension.json"))
pluginRuntimeDependencies.from(target.configurations.named("runtimeClasspath"))
Expand All @@ -51,22 +51,7 @@ class InstallToolboxPlugin : Plugin<Project> {
fun install() {
println("Installing Toolbox plugin...")
project.sync {
val userHome = System.getProperty("user.home").let { Path.of(it) }
val os = System.getProperty("os.name").lowercase(Locale.getDefault())
val toolboxCachesDir = when {
os.contains("win") -> System.getenv("LOCALAPPDATA")?.let { Path.of(it) } ?: (userHome / "AppData" / "Local")
os.contains("linux") -> System.getenv("XDG_CACHE_HOME")?.let { Path.of(it) } ?: (userHome / ".cache")
os.contains("mac") -> userHome / "Library" / "Caches"
else -> error("Unknown os: $os")
} / "JetBrains" / "Toolbox"

val pluginsDir = when {
os.contains("win") -> toolboxCachesDir / "cache"
os.contains("linux") || os.contains("mac") -> toolboxCachesDir
else -> error("Unknown os: $os")
} / "plugins"

val targetDir = pluginsDir / extensionId.get()
val targetDir = ToolboxApp.pluginsDir() / extensionId.get()

// Copy jar task output and the generated JSON
from(project.tasks.getByName("jar"))
Expand Down
Loading
Loading