Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add more granularity to JAVA_HOME checks #104

Merged
merged 8 commits into from
Aug 27, 2020
Merged
Show file tree
Hide file tree
Changes from 4 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
43 changes: 35 additions & 8 deletions doctor-plugin/src/main/java/com/osacky/doctor/DoctorExtension.kt
Original file line number Diff line number Diff line change
@@ -1,21 +1,19 @@
package com.osacky.doctor

import org.gradle.api.Action
import org.gradle.api.model.ObjectFactory
import org.gradle.kotlin.dsl.newInstance
import org.gradle.kotlin.dsl.property
import javax.inject.Inject

open class DoctorExtension(objects: ObjectFactory) {

internal val javaHomeHandler = objects.newInstance<JavaHomeHandler>()

/**
* Throw an exception when multiple Gradle Daemons are running.
*/
val disallowMultipleDaemons = objects.property<Boolean>().convention(false)
/**
* Ensure that we are using JAVA_HOME to build with this Gradle.
*/
val ensureJavaHomeMatches = objects.property<Boolean>().convention(true)
/**
* Ensure we have JAVA_HOME set.
*/
val ensureJavaHomeIsSet = objects.property<Boolean>().convention(true)
/**
* Show a message if the download speed is less than this many megabytes / sec.
*/
Expand All @@ -42,4 +40,33 @@ open class DoctorExtension(objects: ObjectFactory) {
* Do not allow building all apps simultaneously. This is likely not what the user intended.
*/
val allowBuildingAllAndroidAppsSimultaneously = objects.property<Boolean>().convention(false)

/**
* Configures `JAVA_HOME`-specific behavior.
*/
fun javaHome(action: Action<JavaHomeHandler>) {
action.execute(javaHomeHandler)
}
}

abstract class JavaHomeHandler @Inject constructor(objects: ObjectFactory) {
/**
* Ensure that we are using `JAVA_HOME` to build with this Gradle.
*/
val ensureJavaHomeMatches = objects.property<Boolean>().convention(true)
/**
* Ensure we have `JAVA_HOME` set.
*/
val ensureJavaHomeIsSet = objects.property<Boolean>().convention(true)

/**
* Fail on any `JAVA_HOME` issues.
*/
val failOnError = objects.property<Boolean>().convention(true)

/**
* Extra message text, if any, to show with the Gradle Doctor message. This is useful if you have a wiki page or
* other instructions that you want to link for developers on your team if they encounter an issue.
*/
val extraMessage = objects.property<String>().convention(null)
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ class DoctorPlugin : Plugin<Project> {
val intervalMeasurer = IntervalMeasurer()
val pillBoxPrinter = PillBoxPrinter(target.logger)
val daemonChecker = BuildDaemonChecker(extension, DaemonCheck(), pillBoxPrinter)
val javaHomeCheck = JavaHomeCheck(extension, pillBoxPrinter)
val javaHomeCheck = JavaHomeCheck(extension, pillBoxPrinter, target.logger)
val garbagePrinter = GarbagePrinter(clock, DirtyBeanCollector(), extension)
val operations = BuildOperations(target.gradle)
val javaAnnotationTime = JavaAnnotationTime(operations, extension, target.buildscript.configurations)
Expand Down
76 changes: 50 additions & 26 deletions doctor-plugin/src/main/java/com/osacky/doctor/JavaHomeCheck.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,44 +3,68 @@ package com.osacky.doctor
import com.osacky.doctor.internal.Finish
import com.osacky.doctor.internal.PillBoxPrinter
import org.gradle.api.GradleException
import org.gradle.api.logging.Logger
import org.gradle.internal.jvm.Jvm
import java.io.File
import java.util.Collections

class JavaHomeCheck(
private val extension: DoctorExtension,
private val pillBoxPrinter: PillBoxPrinter
private val pillBoxPrinter: PillBoxPrinter,
private val logger: Logger
) : BuildStartFinishListener {

private val recordedErrors = Collections.synchronizedSet(LinkedHashSet<String>())

override fun onStart() {
if (extension.ensureJavaHomeIsSet.get() && environmentJavaHome == null) {
throw GradleException(
pillBoxPrinter.createPill(
"""
JAVA_HOME is not set.
Please set JAVA_HOME so that switching between Android Studio and the terminal does not trigger a full rebuild.
To set JAVA_HOME: (using bash)
echo "export JAVA_HOME=${'$'}(/usr/libexec/java_home)" >> ~/.bash_profile
or `~/.zshrc` if using zsh.
""".trimIndent()
)
)
val extraMessage = extension.javaHomeHandler.extraMessage.get()
val failOnError = extension.javaHomeHandler.failOnError.get()

if (extension.javaHomeHandler.ensureJavaHomeIsSet.get() && environmentJavaHome == null) {
val message = buildString {
appendln("JAVA_HOME is not set.")
appendln("Please set JAVA_HOME so that switching between Android Studio and the terminal does not trigger a full rebuild.")
appendln("To set JAVA_HOME: (using bash)")
appendln("echo \"export JAVA_HOME=${'$'}(/usr/libexec/java_home)\" >> ~/.bash_profile")
appendln("or `~/.zshrc` if using zsh.")
extraMessage?.let {
appendln()
appendln(extraMessage)
}
}
val pill = pillBoxPrinter.createPill(message)
if (failOnError) {
throw GradleException(pill)
} else {
recordedErrors.add(pill)
}
}
if (extension.ensureJavaHomeMatches.get() && !isGradleUsingJavaHome()) {
throw GradleException(
pillBoxPrinter.createPill(
"""
|Gradle is not using JAVA_HOME.
|JAVA_HOME is ${environmentJavaHome.toFile().toPath().toAbsolutePath()}
|Gradle is using ${gradleJavaHome.toPath().toAbsolutePath()}
|This can slow down your build significantly when switching from Android Studio to the terminal.
|To fix: Project Structure -> JDK Location.
|Set this to your JAVA_HOME.
""".trimMargin()
)
)
if (extension.javaHomeHandler.ensureJavaHomeMatches.get() && !isGradleUsingJavaHome()) {
val message = buildString {
appendln("Gradle is not using JAVA_HOME.")
appendln("JAVA_HOME is ${environmentJavaHome.toFile().toPath().toAbsolutePath()}")
appendln("Gradle is using ${gradleJavaHome.toPath().toAbsolutePath()}")
appendln("This can slow down your build significantly when switching from Android Studio to the terminal.")
appendln("To fix: Project Structure -> JDK Location.")
appendln("Set this to your JAVA_HOME.")
extraMessage?.let {
appendln()
appendln(extraMessage)
}
}
val pill = pillBoxPrinter.createPill(message)
if (failOnError) {
throw GradleException(pill)
} else {
recordedErrors.add(pill)
}
}
}

override fun onFinish(): Finish {
if (recordedErrors.isNotEmpty()) {
logger.error(recordedErrors.joinToString("\n\n"))
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have my own silly API design here that we'll need to follow in order to get a consistent printout of all the error messages together. A build can generate multiple warnings or errors so they are grouped using this API.

To get a consistent printout and so that all the errors are printed together at the end of the build we'll need to do something like this:

Suggested change
logger.error(recordedErrors.joinToString("\n\n"))
return Finish.Message(recordedErrors.joinToString("\n\n"))

That being said, you might need to add a new class to Finish where you can pass a list of messages.

Here's an example from DownloadSpeedMeasurer: https://github.com/runningcode/gradle-doctor/blob/master/doctor-plugin/src/main/java/com/osacky/doctor/DownloadSpeedMeasurer.kt#L50

}
return Finish.None
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,10 @@ class ConfigurationCacheTest {
| id "com.osacky.doctor"
|}
|doctor {
| disallowMultipleDaemons = false
| ensureJavaHomeMatches = false
| javaHome {
| disallowMultipleDaemons = false
| ensureJavaHomeMatches = false
| }
|}
""".trimMargin("|")
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -113,8 +113,10 @@ class PluginIntegrationTest constructor(private val version: String) {
| id "com.osacky.doctor"
|}
|doctor {
| disallowMultipleDaemons = false
| ensureJavaHomeMatches = true
| javaHome {
| disallowMultipleDaemons = false
| ensureJavaHomeMatches = true
| }
|}
""".trimMargin("|")
)
Expand All @@ -134,6 +136,70 @@ class PluginIntegrationTest constructor(private val version: String) {
)
}

// This is failing, perhaps because it is actually trying to use "foo" as JAVA_HOME.
@Test @Ignore
fun testJavaHomeNotSetWithConsoleError() {
assumeSupportedVersion()

writeBuildGradle(
"""
|plugins {
| id "com.osacky.doctor"
|}
|doctor {
| javaHome {
| disallowMultipleDaemons = false
| ensureJavaHomeMatches = true
| failOnError = false
| }
|}
""".trimMargin("|")
)
testProjectRoot.newFile("settings.gradle")

val result = createRunner()
.withEnvironment(mapOf("JAVA_HOME" to "foo"))
.withArguments("tasks")
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

does adding forwardOutput() give any clues as to why this is failing in the logs?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have no idea, I was just copying the previous test (which is also ignored)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Was leaving these as a toe-hold

.buildAndFail()
// Still prints the error
assertThat(result.output).contains(
"""
|> =============================== Gradle Doctor Prescriptions ============================================
| | Gradle is not using JAVA_HOME. |
| | JAVA_HOME is foo |
| """
.trimMargin("|")
)
}

// This is failing, perhaps because it is actually trying to use "foo" as JAVA_HOME.
@Test @Ignore
fun testJavaHomeNotSetWithCustomMessage() {
assumeSupportedVersion()

writeBuildGradle(
"""
|plugins {
| id "com.osacky.doctor"
|}
|doctor {
| javaHome {
| disallowMultipleDaemons = false
| ensureJavaHomeMatches = true
| extraMessage = "Check for more details here!"
| }
|}
""".trimMargin("|")
)
testProjectRoot.newFile("settings.gradle")

val result = createRunner()
.withEnvironment(mapOf("JAVA_HOME" to "foo"))
.withArguments("tasks")
.buildAndFail()
assertThat(result.output).contains("Check for more details here!")
}

@Test
fun testFailAssembleMultipleProjects() {
assumeSupportedVersion()
Expand Down
12 changes: 8 additions & 4 deletions doctor-plugin/src/test/java/com/osacky/doctor/TestDaggerTime.kt
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,10 @@ class TestDaggerTime {
}
doctor {
disallowMultipleDaemons = false
daggerThreshold = 100
ensureJavaHomeMatches = !System.getenv().containsKey("CI")
javaHome {
daggerThreshold = 100
ensureJavaHomeMatches = !System.getenv().containsKey("CI")
}
}
""".trimIndent()
)
Expand Down Expand Up @@ -75,8 +77,10 @@ class TestDaggerTime {
apply plugin: 'com.soundcloud.delect'
doctor {
disallowMultipleDaemons = false
daggerThreshold = 100
ensureJavaHomeMatches = false
javaHome {
daggerThreshold = 100
ensureJavaHomeMatches = false
}
}
""".trimIndent()
)
Expand Down