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
6 changes: 5 additions & 1 deletion src/main/kotlin/com/squareup/cash/hermit/Hermit.kt
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,10 @@ object Hermit {
return bin?.findChild(exe)?.exists() ?: false
}

fun findPackage(type: PackageType): HermitPackage? {
return this.properties?.packages?.find { it.type == type }
}

private fun clear() {
this.properties = null
this.isHermitProject = false
Expand Down Expand Up @@ -236,4 +240,4 @@ object Hermit {
return ImmutableMap.copyOf(env)
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package com.squareup.cash.hermit.gradle

import com.intellij.execution.target.value.TargetValue
import com.intellij.openapi.project.Project
import org.gradle.util.GradleVersion
import org.jetbrains.plugins.gradle.service.GradleInstallationManager
import org.jetbrains.plugins.gradle.service.execution.BuildLayoutParameters
import org.jetbrains.plugins.gradle.service.execution.gradleUserHomeDir
import org.jetbrains.plugins.gradle.settings.GradleSettings
import java.nio.file.Files
import java.nio.file.Path
import java.util.regex.Pattern

private val GRADLE_JAR_PATTERN = Pattern.compile(System.getProperty("gradle.pattern.core.jar", "gradle-(core-)?(\\d.*)\\.jar"))

/**
* [BuildLayoutParameters] that points to a hermit-managed Gradle installation.
* The [gradleUserHomePath] resolution matches IntelliJ's [LocalBuildLayoutParameters]:
* project Gradle settings first, then GRADLE_USER_HOME env/property, then ~/.gradle.
*/
class HermitBuildLayoutParameters(
private val hermitGradleHome: Path,
project: Project
) : BuildLayoutParameters {

override val gradleHome: TargetValue<Path> = TargetValue.fixed(hermitGradleHome)

override val gradleVersion: GradleVersion? by lazy {
detectGradleVersion(hermitGradleHome)
}

override val gradleUserHomePath: TargetValue<Path> by lazy {
val serviceDir = GradleSettings.getInstance(project).serviceDirectoryPath
val userHome = if (serviceDir != null) {
Path.of(serviceDir)
} else {
gradleUserHomeDir().toPath()
}
TargetValue.fixed(userHome)
}
}

/**
* Detects the Gradle version from a Gradle home directory by inspecting jar filenames in `lib/`.
* Inlined from [GradleInstallationManager] to avoid binary compatibility issues with the Companion
* object across older IntelliJ versions.
*/
private fun detectGradleVersion(gradleHome: Path): GradleVersion? {
val libs = gradleHome.resolve("lib")
if (!Files.isDirectory(libs)) return null

val versionString = try {
Files.list(libs).use { children ->
children
.map { GRADLE_JAR_PATTERN.matcher(it.fileName.toString()) }
.filter { it.matches() }
.map { it.group(2) }
.findFirst()
.orElse(null)
}
} catch (_: Exception) {
null
} ?: return null

return try {
GradleVersion.version(versionString)
} catch (_: IllegalArgumentException) {
// GradleVersion.version(gradleVersion) might throw exception for custom Gradle versions
// https://youtrack.jetbrains.com/issue/IDEA-216892
null
}
}
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
@file:Suppress("UnstableApiUsage")

package com.squareup.cash.hermit.gradle

import com.intellij.openapi.diagnostic.Logger
import com.intellij.openapi.externalSystem.model.task.ExternalSystemTask
import com.intellij.openapi.externalSystem.model.task.ExternalSystemTaskNotificationListener
import com.intellij.openapi.project.Project
import com.squareup.cash.hermit.Hermit
import org.jetbrains.annotations.ApiStatus
import com.squareup.cash.hermit.PackageType
import org.jetbrains.plugins.gradle.service.execution.BuildLayoutParameters
import org.jetbrains.plugins.gradle.service.execution.GradleExecutionAware
import java.nio.file.Path

class HermitGradleExecutionAware: GradleExecutionAware {
private val log: Logger = Logger.getInstance(this.javaClass)
Expand Down Expand Up @@ -42,13 +45,32 @@ class HermitGradleExecutionAware: GradleExecutionAware {
}
}

override fun getBuildLayoutParameters(project: Project, projectPath: String): BuildLayoutParameters? = null
override fun getBuildLayoutParameters(project: Project, projectPath: Path): BuildLayoutParameters? {
val gradleHome = hermitGradleHome(project) ?: return null
log.debug("using hermit Gradle home: $gradleHome")
return HermitBuildLayoutParameters(gradleHome, project)
}

override fun getDefaultBuildLayoutParameters(project: Project): BuildLayoutParameters? {
val gradleHome = hermitGradleHome(project) ?: return null
log.debug("using hermit Gradle home for default build layout: $gradleHome")
return HermitBuildLayoutParameters(gradleHome, project)
}

override fun getDefaultBuildLayoutParameters(project: Project): BuildLayoutParameters? = null
override fun isGradleInstallationHomeDir(project: Project, homePath: Path): Boolean {
val gradleHome = hermitGradleHome(project) ?: return false
return homePath == gradleHome
}

override fun isGradleInstallationHomeDir(project: Project, homePath: String): Boolean = false
private fun hermitGradleHome(project: Project): Path? {
val state = Hermit(project)
if (!state.hasHermit() || state.hermitStatus() == Hermit.HermitStatus.Disabled) return null
val gradlePkg = state.findPackage(PackageType.Gradle) ?: return null
val path = Path.of(gradlePkg.path)
return if (path.toFile().isDirectory) path else null
}

companion object {
const val TIMEOUT_MS = 120000
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,20 @@ import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.diagnostic.Logger
import com.intellij.openapi.project.Project
import com.intellij.openapi.projectRoots.ProjectJdkTable
import com.intellij.openapi.projectRoots.impl.ProjectJdkImpl
import com.intellij.openapi.util.Disposer
import com.squareup.cash.hermit.*
import java.io.File

class HermitJdkUpdater : HermitPropertyHandler {
private val log: Logger = Logger.getInstance(this.javaClass)

override fun handle(hermitPackage: HermitPackage, project: Project) {
if (hermitPackage.type == PackageType.JDK) {
if (!File(hermitPackage.path).isDirectory) {
log.warn("JDK path does not exist: ${hermitPackage.path}, skipping SDK configuration")
UI.showError(project, "Hermit JDK path does not exist: ${hermitPackage.path}. " +
"Try running <code>hermit install</code> from the terminal.")
return
}
val projectSdk = project.projectSdk()
if (hermitPackage.sdkName() != projectSdk?.name) {
log.debug("setting project (" + project.name + ") SDK to " + hermitPackage.logString())
Expand Down
19 changes: 8 additions & 11 deletions src/test/kotlin/com/squareup/cash/hermit/FakeHermit.kt
Original file line number Diff line number Diff line change
Expand Up @@ -35,14 +35,11 @@ object BrokenHermit : AbstractHermit() {

data class FakeHermit(val packages: List<TestPackage>) : AbstractHermit() {
override fun writeTo(path: Path) {
val packageList = packages
.map { "echo \"${it.name}\"" }
.joinToString("\n")
val envList = packages
.flatMap { it.env.entries }
.map { entry -> "echo \"${entry.key}=${entry.value}\""}
.joinToString("\n")
val infoList = JsonArray(packages
val packageList = packages.joinToString("\n") { "echo \"${it.name}\"" }
val envList = packages
.flatMap { it.env.entries }
.joinToString("\n") { entry -> "echo \"${entry.key}=${entry.value}\"" }
val infoList = JsonArray(packages
.map { p -> JsonObject(mapOf(
"Reference" to JsonObject(mapOf(
"Name" to JsonPrimitive(p.name),
Expand All @@ -54,13 +51,13 @@ data class FakeHermit(val packages: List<TestPackage>) : AbstractHermit() {
)) }).toString()

val listBlock = if (packageList.isNotEmpty()) """
if [ ${'$'}1 == "list" ]; then
if [ $1 == "list" ]; then
$packageList
fi
""".trimIndent() else ""

val envBlock = if (envList.isNotEmpty()) """
if [ ${'$'}1 == "env" ]; then
if [ $1 == "env" ]; then
$envList
fi
""".trimIndent()
Expand All @@ -85,4 +82,4 @@ fun String.runCommand(workingDir: Path) {

builder.environment()["HERMIT_ENV"] = workingDir.toString()
builder.start().waitFor(60, TimeUnit.SECONDS)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
package com.squareup.cash.hermit.gradle

import com.intellij.execution.target.value.TargetValue
import com.squareup.cash.hermit.FakeHermit
import com.squareup.cash.hermit.Hermit
import com.squareup.cash.hermit.HermitProjectTestCase
import com.squareup.cash.hermit.PackageType
import com.squareup.cash.hermit.TestPackage
import junit.framework.TestCase
import org.junit.Test
import java.nio.file.Files
import java.nio.file.Path

class HermitGradleExecutionAwareTest : HermitProjectTestCase() {
private val aware = HermitGradleExecutionAware()

private fun gradleRoot(): Path {
val dir = projectDirOrFile.parent.resolve("fake-gradle-home")
Files.createDirectories(dir)
return dir
}

private fun <T> localValue(targetValue: TargetValue<T>): T? {
return targetValue.localValue.blockingGet(0)
}

@Test fun `test getBuildLayoutParameters returns null when hermit has no gradle package`() {
withHermit(FakeHermit(listOf(TestPackage("openjdk", "21", "", "/nonexistent/jdk/path", emptyMap()))))
Hermit(project).enable()
waitAppThreads()

val params = aware.getBuildLayoutParameters(project, projectDirOrFile.parent)
assertNull(params)
}

@Test fun `test getBuildLayoutParameters returns null when hermit is not enabled`() {
val params = aware.getBuildLayoutParameters(project, projectDirOrFile.parent)
assertNull(params)
}

@Test
fun `test getBuildLayoutParameters returns hermit gradle home when gradle package exists`() {
val root = gradleRoot()
withHermit(FakeHermit(listOf(TestPackage("gradle", "9.3.1", "", root.toString(), emptyMap()))))
Hermit(project).enable()
waitAppThreads()

val params = aware.getBuildLayoutParameters(project, projectDirOrFile.parent)!!
assertEquals(root, localValue(params.gradleHome!!))
}

@Test fun `test getDefaultBuildLayoutParameters returns null when no gradle package`() {
withHermit(FakeHermit(emptyList()))
Hermit(project).enable()
waitAppThreads()

val params = aware.getDefaultBuildLayoutParameters(project)
assertNull(params)
}

@Test
fun `test getDefaultBuildLayoutParameters returns hermit gradle home when gradle package exists`() {
val root = gradleRoot()
withHermit(FakeHermit(listOf(TestPackage("gradle", "9.3.1", "", root.toString(), emptyMap()))))
Hermit(project).enable()
waitAppThreads()

val params = aware.getDefaultBuildLayoutParameters(project)!!
assertEquals(root, localValue(params.gradleHome!!))
}

@Test fun `test isGradleInstallationHomeDir returns true for hermit gradle home`() {
val root = gradleRoot()
withHermit(FakeHermit(listOf(TestPackage("gradle", "9.3.1", "", root.toString(), emptyMap()))))
Hermit(project).enable()
waitAppThreads()

assertTrue(aware.isGradleInstallationHomeDir(project, root))
}

@Test fun `test isGradleInstallationHomeDir returns false for non-hermit path`() {
val root = gradleRoot()
withHermit(FakeHermit(listOf(TestPackage("gradle", "9.3.1", "", root.toString(), emptyMap()))))
Hermit(project).enable()
waitAppThreads()

assertFalse(aware.isGradleInstallationHomeDir(project, Path.of("/some/other/path")))
}

@Test fun `test isGradleInstallationHomeDir returns false when hermit has no gradle`() {
withHermit(FakeHermit(emptyList()))
Hermit(project).enable()
waitAppThreads()

assertFalse(aware.isGradleInstallationHomeDir(project, Path.of("/some/path")))
}

@Test fun `test gradlePackage returns the gradle package after enable`() {
val root = gradleRoot()
withHermit(FakeHermit(listOf(TestPackage("gradle", "9.3.1", "", root.toString(), emptyMap()))))
Hermit(project).enable()
waitAppThreads()

val pkg = Hermit(project).findPackage(PackageType.Gradle)!!
TestCase.assertEquals("gradle", pkg.name)
TestCase.assertEquals("9.3.1", pkg.version)
TestCase.assertEquals(root.toString(), pkg.path)
}

@Test fun `test gradlePackage returns null when no gradle package`() {
withHermit(FakeHermit(listOf(TestPackage("openjdk", "21", "", "/nonexistent/jdk/path", emptyMap()))))
Hermit(project).enable()
waitAppThreads()

assertNull(Hermit(project).findPackage(PackageType.Gradle))
}

@Test fun `test build layout parameters provides gradle user home`() {
val root = gradleRoot()
withHermit(FakeHermit(listOf(TestPackage("gradle", "9.3.1", "", root.toString(), emptyMap()))))
Hermit(project).enable()
waitAppThreads()

val params = aware.getBuildLayoutParameters(project, projectDirOrFile.parent)!!
val userHome = localValue(params.gradleUserHomePath)
assertNotNull(userHome)
}

@Test fun `test getBuildLayoutParameters returns null when gradle path does not exist`() {
val nonExistent = "/tmp/hermit-test-nonexistent-gradle-home-${System.nanoTime()}"
withHermit(FakeHermit(listOf(TestPackage("gradle", "9.3.1", "", nonExistent, emptyMap()))))
Hermit(project).enable()
waitAppThreads()

val params = aware.getBuildLayoutParameters(project, projectDirOrFile.parent)
assertNull(params)
}
}
Loading