[IGS] Add a non-interactive way to make a consent decision for IDEA builds

When running builds from IDEA, especially tests, the interactive consent request is barely noticeable. Also, some of the tools windows are non-interactive terminals. Thus, we detect the IDEA builds and fail fast to make it noticeable.
^KTI-1443 Fixed
This commit is contained in:
Alexander.Likhachev
2023-11-15 01:52:31 +01:00
committed by Space Team
parent ab5699f106
commit 2bd77d5cdc
3 changed files with 70 additions and 25 deletions
@@ -0,0 +1,17 @@
/*
* Copyright 2010-2023 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.build
import org.gradle.api.GradleException
class CannotRequestConsentWithinIdeException(consentDetailsLink: String?) : GradleException(
"""
|$USER_CONSENT_REQUEST
|The consent cannot be requested in interactive mode when running from IDE.
|Please either invoke Gradle from the command line or add -P$CONSENT_DECISION_GRADLE_PROPERTY=(true,false) to the run parameters in order to make a decision
|${if (consentDetailsLink != null) USER_CONSENT_DETAILS_LINK_TEMPLATE.formatWithLink(consentDetailsLink) else ""}
""".trimMargin()
)
@@ -25,10 +25,9 @@ internal val USER_CONSENT_REQUEST = """
! ATTENTION REQUIRED ! ! ATTENTION REQUIRED !
Most probably you're a developer from the Kotlin team. We are asking for your consent for automatic configuration of local.properties file Most probably you're a developer from the Kotlin team. We are asking for your consent for automatic configuration of local.properties file
for providing some optimizations and collecting additional debug information. for providing some optimizations and collecting additional debug information.
""".trimIndent() """.trimIndent()
internal val USER_CONSENT_DETAILS_LINK_TEMPLATE = "You can read more details here: $linkPlaceholder" internal const val USER_CONSENT_DETAILS_LINK_TEMPLATE = "You can read more details here: $linkPlaceholder"
internal const val PROMPT_REQUEST = "Do you agree with this? Please answer with 'yes' or 'no': " internal const val PROMPT_REQUEST = "Do you agree with this? Please answer with 'yes' or 'no': "
@@ -53,16 +52,18 @@ internal class ConsentManager(
} }
} }
fun askForConsent(consentDetailsLink: String? = null): Boolean { private fun printConsentRequest(consentDetailsLink: String? = null) {
output.println()
output.println(USER_CONSENT_REQUEST) output.println(USER_CONSENT_REQUEST)
if (consentDetailsLink != null) { if (consentDetailsLink != null) {
output.println(USER_CONSENT_DETAILS_LINK_TEMPLATE.formatWithLink(consentDetailsLink)) output.println(USER_CONSENT_DETAILS_LINK_TEMPLATE.formatWithLink(consentDetailsLink))
} }
repeat(MAX_REQUEST_ATTEMPTS) { output.println()
output.println(PROMPT_REQUEST) }
when (input.readLine()) {
"yes" -> { fun applyConsentDecision(consentGiven: Boolean, consentDetailsLink: String? = null): Boolean {
output.println("You've given the consent") if (consentGiven) {
output.println("You've given the consent for the automatic configuration of local.properties")
modifier.putLine( modifier.putLine(
if (consentDetailsLink != null) { if (consentDetailsLink != null) {
USER_CONSENT_MARKER_WITH_DETAILS_LINK.formatWithLink(consentDetailsLink) USER_CONSENT_MARKER_WITH_DETAILS_LINK.formatWithLink(consentDetailsLink)
@@ -70,13 +71,21 @@ internal class ConsentManager(
USER_CONSENT_MARKER USER_CONSENT_MARKER
} }
) )
return true } else {
} output.println("You've refused to give the consent for the automatic configuration of local.properties")
"no" -> {
output.println("You've refused to give the consent")
modifier.putLine(USER_REFUSAL_MARKER) modifier.putLine(USER_REFUSAL_MARKER)
return false
} }
return consentGiven
}
fun askForConsent(consentDetailsLink: String? = null): Boolean {
printConsentRequest(consentDetailsLink)
repeat(MAX_REQUEST_ATTEMPTS) {
output.println(PROMPT_REQUEST)
return when (input.readLine()) {
"yes" -> applyConsentDecision(true, consentDetailsLink)
"no" -> applyConsentDecision(false, consentDetailsLink)
else -> return@repeat
} }
} }
// we didn't receive an answer, let's ask next time // we didn't receive an answer, let's ask next time
@@ -18,7 +18,12 @@ private const val DOMAIN_NAME = "kotlin-build-properties.labs.jb.gg"
private const val SETUP_JSON_URL = "https://$DOMAIN_NAME/setup.json" private const val SETUP_JSON_URL = "https://$DOMAIN_NAME/setup.json"
private const val PLUGIN_SWITCH_PROPERTY = "kotlin.build.internal.gradle.setup" private const val PLUGIN_SWITCH_PROPERTY = "kotlin.build.internal.gradle.setup"
private const val CONSENT_GRADLE_PROPERTY = "kotlin.build.internal.gradle.setup.consent" private const val GLOBAL_CONSENT_GRADLE_PROPERTY = "kotlin.build.internal.gradle.setup.consent"
private val isInIdea
get() = System.getProperty("idea.active").toBoolean()
internal const val CONSENT_DECISION_GRADLE_PROPERTY = "kotlin.build.internal.gradle.setup.consent.give"
abstract class InternalGradleSetupSettingsPlugin : Plugin<Settings> { abstract class InternalGradleSetupSettingsPlugin : Plugin<Settings> {
private val log = Logging.getLogger(javaClass) private val log = Logging.getLogger(javaClass)
@@ -34,7 +39,8 @@ abstract class InternalGradleSetupSettingsPlugin : Plugin<Settings> {
} }
try { try {
val modifier = LocalPropertiesModifier(target.rootDir.resolve("local.properties")) val modifier = LocalPropertiesModifier(target.rootDir.resolve("local.properties"))
val consentManager = ConsentManager(modifier, target.providers.gradleProperty(CONSENT_GRADLE_PROPERTY).orNull?.toBoolean()) val consentManager =
ConsentManager(modifier, target.providers.gradleProperty(GLOBAL_CONSENT_GRADLE_PROPERTY).orNull?.toBoolean())
val initialDecision = consentManager.getUserDecision() val initialDecision = consentManager.getUserDecision()
if (initialDecision == false) { if (initialDecision == false) {
log.debug("Skipping automatic local.properties configuration as you've opted out") log.debug("Skipping automatic local.properties configuration as you've opted out")
@@ -44,12 +50,25 @@ abstract class InternalGradleSetupSettingsPlugin : Plugin<Settings> {
val setupFile = connection.getInputStream().buffered().use { val setupFile = connection.getInputStream().buffered().use {
parseSetupFile(it) parseSetupFile(it)
} }
if (initialDecision == null && !consentManager.askForConsent(setupFile.consentDetailsLink)) { if (initialDecision == null) {
val nonInteractiveDecision = target.providers.gradleProperty(CONSENT_DECISION_GRADLE_PROPERTY).map { it.toBoolean() }
if (isInIdea && !nonInteractiveDecision.isPresent) {
throw CannotRequestConsentWithinIdeException(setupFile.consentDetailsLink)
}
val consentReceived = if (nonInteractiveDecision.isPresent) {
consentManager.applyConsentDecision(nonInteractiveDecision.get(), setupFile.consentDetailsLink)
} else {
consentManager.askForConsent(setupFile.consentDetailsLink)
}
if (!consentReceived) {
log.debug("Skipping automatic local.properties configuration as the consent wasn't given") log.debug("Skipping automatic local.properties configuration as the consent wasn't given")
return return
} }
}
modifier.applySetup(setupFile) modifier.applySetup(setupFile)
log.info("Automatic local.properties setup has been applied.") log.info("Automatic local.properties setup has been applied.")
} catch (e: CannotRequestConsentWithinIdeException) {
throw e
} catch (e: UnknownHostException) { } catch (e: UnknownHostException) {
log.debug("Cannot connect to the internal properties storage", e) log.debug("Cannot connect to the internal properties storage", e)
} catch (e: SocketTimeoutException) { } catch (e: SocketTimeoutException) {