diff --git a/repo/gradle-settings-conventions/internal-gradle-setup/src/main/kotlin/ConsentManager.kt b/repo/gradle-settings-conventions/internal-gradle-setup/src/main/kotlin/ConsentManager.kt index 55ac172f7bc..d8bec40356c 100644 --- a/repo/gradle-settings-conventions/internal-gradle-setup/src/main/kotlin/ConsentManager.kt +++ b/repo/gradle-settings-conventions/internal-gradle-setup/src/main/kotlin/ConsentManager.kt @@ -11,8 +11,12 @@ import java.io.PrintStream private val isInIdeaSync get() = System.getProperty("idea.sync.active").toBoolean() +private const val linkPlaceholder = "%LINK%" + internal const val USER_CONSENT_MARKER = "# This line indicates that you have chosen to enable automatic configuration of properties." +internal const val USER_CONSENT_MARKER_WITH_DETAILS_LINK = "$USER_CONSENT_MARKER Details: $linkPlaceholder" + internal const val USER_REFUSAL_MARKER = "# This line indicates that you have chosen to disable automatic configuration of properties. If you want to enable it, remove this line." @@ -24,8 +28,12 @@ internal val USER_CONSENT_REQUEST = """ """.trimIndent() +internal 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 fun String.formatWithLink(link: String) = replace(linkPlaceholder, link) + internal class ConsentManager( private val modifier: LocalPropertiesModifier, private val input: BufferedReader = System.`in`.bufferedReader(), @@ -38,14 +46,23 @@ internal class ConsentManager( else -> null } - fun askForConsent(): Boolean { + fun askForConsent(consentDetailsLink: String? = null): Boolean { output.println(USER_CONSENT_REQUEST) + if (consentDetailsLink != null) { + output.println(USER_CONSENT_DETAILS_LINK_TEMPLATE.formatWithLink(consentDetailsLink)) + } while (true) { output.println(PROMPT_REQUEST) when (input.readLine()) { "yes" -> { output.println("You've given the consent") - modifier.putLine(USER_CONSENT_MARKER) + modifier.putLine( + if (consentDetailsLink != null) { + USER_CONSENT_MARKER_WITH_DETAILS_LINK.formatWithLink(consentDetailsLink) + } else { + USER_CONSENT_MARKER + } + ) return true } "no" -> { diff --git a/repo/gradle-settings-conventions/internal-gradle-setup/src/main/kotlin/InternalGradleSetupSettingsPlugin.kt b/repo/gradle-settings-conventions/internal-gradle-setup/src/main/kotlin/InternalGradleSetupSettingsPlugin.kt index 66e1c561376..8e3cbdd1287 100644 --- a/repo/gradle-settings-conventions/internal-gradle-setup/src/main/kotlin/InternalGradleSetupSettingsPlugin.kt +++ b/repo/gradle-settings-conventions/internal-gradle-setup/src/main/kotlin/InternalGradleSetupSettingsPlugin.kt @@ -23,7 +23,7 @@ abstract class InternalGradleSetupSettingsPlugin : Plugin { private val log = Logging.getLogger(javaClass) override fun apply(target: Settings) { - // `kotlin-build-gradle-plugin` is not used here intentionally, as it caches properties, we don't want to cache them before modificaation + // `kotlin-build-gradle-plugin` is not used here intentionally, as it caches properties, we don't want to cache them before modification val shouldApplyPlugin = target.providers.gradleProperty(PLUGIN_SWITCH_PROPERTY).orElse("false").map(String::toBoolean) if (!shouldApplyPlugin.get()) return // the plugin is disabled, do nothing at all val isTeamCityBuild = (target as? ExtensionAware)?.extra?.has("teamcity") == true || System.getenv("TEAMCITY_VERSION") != null @@ -43,7 +43,7 @@ abstract class InternalGradleSetupSettingsPlugin : Plugin { val setupFile = connection.getInputStream().buffered().use { parseSetupFile(it) } - if (initialDecision == null && !consentManager.askForConsent()) { + if (initialDecision == null && !consentManager.askForConsent(setupFile.consentDetailsLink)) { log.debug("Skipping automatic local.properties configuration as the consent wasn't given") return } diff --git a/repo/gradle-settings-conventions/internal-gradle-setup/src/main/kotlin/SetupFile.kt b/repo/gradle-settings-conventions/internal-gradle-setup/src/main/kotlin/SetupFile.kt index d9587f5384e..e26a04ba63a 100644 --- a/repo/gradle-settings-conventions/internal-gradle-setup/src/main/kotlin/SetupFile.kt +++ b/repo/gradle-settings-conventions/internal-gradle-setup/src/main/kotlin/SetupFile.kt @@ -5,9 +5,11 @@ package org.jetbrains.kotlin.build +import kotlinx.serialization.ExperimentalSerializationApi import kotlinx.serialization.Serializable import kotlinx.serialization.decodeFromString import kotlinx.serialization.json.Json +import kotlinx.serialization.json.JsonNamingStrategy import java.io.BufferedReader import java.io.InputStream import java.io.InputStreamReader @@ -16,9 +18,14 @@ import kotlin.streams.asSequence @Serializable internal data class SetupFile( val properties: Map, + val consentDetailsLink: String? = null, ) -private val json = Json { ignoreUnknownKeys = true } +@OptIn(ExperimentalSerializationApi::class) +private val json = Json { + ignoreUnknownKeys = true + namingStrategy = JsonNamingStrategy.SnakeCase +} // can't use decodeFromStream: https://github.com/Kotlin/kotlinx.serialization/issues/2218 internal fun parseSetupFile(inputStream: InputStream): SetupFile = diff --git a/repo/gradle-settings-conventions/internal-gradle-setup/src/test/kotlin/ConsentManagerTest.kt b/repo/gradle-settings-conventions/internal-gradle-setup/src/test/kotlin/ConsentManagerTest.kt index ddd5c449266..a88aecb3d74 100644 --- a/repo/gradle-settings-conventions/internal-gradle-setup/src/test/kotlin/ConsentManagerTest.kt +++ b/repo/gradle-settings-conventions/internal-gradle-setup/src/test/kotlin/ConsentManagerTest.kt @@ -52,24 +52,42 @@ class ConsentManagerTest { @Test fun testConsentRequestAgree() = testUserDecision("yes", true, USER_CONSENT_MARKER) + @Test + fun testConsentRequestAgreeWithDetailsLink() = testUserDecision( + "yes", + true, + USER_CONSENT_MARKER_WITH_DETAILS_LINK.formatWithLink("https://example.org"), + consentDetailsLink = "https://example.org" + ) + @Test fun testConsentRequestRefusal() = testUserDecision("no", false, USER_REFUSAL_MARKER) @Test fun testConsentRequestAnswerAfterBadInput() = testUserDecision("\nasdasd\nno", false, USER_REFUSAL_MARKER, 3) - private fun testUserDecision(prompt: String, expectedDecision: Boolean, expectedLine: String, promptCount: Int = 1) { + private fun testUserDecision( + prompt: String, + expectedDecision: Boolean, + expectedLine: String, + promptCount: Int = 1, + consentDetailsLink: String? = null, + ) { StringInputStream(prompt).bufferedReader().use { input -> val outputStream = ByteArrayOutputStream(255) PrintStream(outputStream).use { printStream -> val consentManager = ConsentManager(modifier, input, printStream) - val userDecision = consentManager.askForConsent() + val userDecision = consentManager.askForConsent(consentDetailsLink) assertEquals(expectedDecision, userDecision) val content = Files.readAllLines(localPropertiesFile) assertTrue(content.contains(expectedLine)) val output = String(outputStream.toByteArray()) assertContainsExactTimes(output, USER_CONSENT_REQUEST, 1) assertContainsExactTimes(output, PROMPT_REQUEST, promptCount) + + if (consentDetailsLink != null) { + assertContainsExactTimes(output, USER_CONSENT_DETAILS_LINK_TEMPLATE.formatWithLink(consentDetailsLink), 1) + } } } } diff --git a/repo/gradle-settings-conventions/internal-gradle-setup/src/test/kotlin/SetupFileParseTest.kt b/repo/gradle-settings-conventions/internal-gradle-setup/src/test/kotlin/SetupFileParseTest.kt index 4c8fb984f0e..d4e1b6ab805 100644 --- a/repo/gradle-settings-conventions/internal-gradle-setup/src/test/kotlin/SetupFileParseTest.kt +++ b/repo/gradle-settings-conventions/internal-gradle-setup/src/test/kotlin/SetupFileParseTest.kt @@ -29,6 +29,7 @@ class SetupFileParseTest { parseSetupFile(it) } assertSampleSetupFileIsParsedCorrectly(setupFile) + assertNull(setupFile.consentDetailsLink) } @Test @@ -37,5 +38,15 @@ class SetupFileParseTest { parseSetupFile(it) } assertSampleSetupFileIsParsedCorrectly(setupFile) + assertNull(setupFile.consentDetailsLink) + } + + @Test + fun testParsingWithConsentDetailsLink() { + val setupFile = openPropertiesJsonStream("properties-with-consent-details").use { + parseSetupFile(it) + } + assertSampleSetupFileIsParsedCorrectly(setupFile) + assertEquals(setupFile.consentDetailsLink, "https://example.org") } } \ No newline at end of file diff --git a/repo/gradle-settings-conventions/internal-gradle-setup/src/test/resources/properties-with-consent-details.json b/repo/gradle-settings-conventions/internal-gradle-setup/src/test/resources/properties-with-consent-details.json new file mode 100644 index 00000000000..d9ecae321be --- /dev/null +++ b/repo/gradle-settings-conventions/internal-gradle-setup/src/test/resources/properties-with-consent-details.json @@ -0,0 +1,8 @@ +{ + "properties": { + "newProperty1": "someValue", + "newProperty2": "someOtherValue", + "alreadySetProperty": "newValue" + }, + "consent_details_link": "https://example.org" +} \ No newline at end of file