[Build, IGS] Implement LocalPropertiesModifier

#KTI-1223 In Progress
This commit is contained in:
Alexander.Likhachev
2023-05-01 13:42:58 +02:00
committed by Space Team
parent e30b72fa8f
commit 6804d0a0af
2 changed files with 340 additions and 0 deletions
@@ -0,0 +1,84 @@
/*
* 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 java.io.File
import java.io.StringReader
import java.util.Properties
private const val SYNCED_PROPERTIES_START_LINE = "# Automatically configured by the `internal-gradle-setup` plugin"
internal val SYNCED_PROPERTIES_START_LINES = """
$SYNCED_PROPERTIES_START_LINE
# Please do not edit these properties manually, the changes will be lost
# If you want to override some values, put them before this section and remove from this section
""".trimIndent()
internal const val SYNCED_PROPERTIES_END_LINE = "# the end of automatically configured properties"
internal class LocalPropertiesModifier(private val localProperties: File) {
private val manuallyConfiguredPropertiesContent by lazy {
if (!localProperties.exists()) return@lazy ""
var insideAutomaticallyConfiguredSection = false
// filter out the automatically configured lines
localProperties.readLines().filter { line ->
if (line == SYNCED_PROPERTIES_START_LINE) {
insideAutomaticallyConfiguredSection = true
}
val shouldIncludeThisLine = !insideAutomaticallyConfiguredSection
if (line == SYNCED_PROPERTIES_END_LINE) {
insideAutomaticallyConfiguredSection = false
}
shouldIncludeThisLine
}.joinToString("\n")
}
fun applySetup(setupFile: SetupFile) {
localProperties.parentFile.apply {
if (!exists()) {
mkdirs()
}
}
if (localProperties.exists() && !localProperties.isFile) {
error("$localProperties is not a file!")
}
val manuallyConfiguredProperties = Properties().apply {
StringReader(manuallyConfiguredPropertiesContent).use {
load(it)
}
}
val propertiesToSetup = setupFile.properties.mapValues { PropertyValue(it.value, manuallyConfiguredProperties.containsKey(it.key)) }
localProperties.writeText(
"""
|${manuallyConfiguredPropertiesContent.addSuffix("\n")}
|$SYNCED_PROPERTIES_START_LINES
|${propertiesToSetup.asPropertiesLines}
|$SYNCED_PROPERTIES_END_LINE
|
""".trimMargin()
)
}
}
private fun String.addSuffix(suffix: String): String {
if (this.endsWith(suffix)) return this
return "$this$suffix"
}
internal data class PropertyValue(
val value: String,
val isOverridden: Boolean = false,
)
internal val Map<String, PropertyValue>.asPropertiesLines: String
get() = map { (key, value) ->
when (value.isOverridden) {
true -> """
#$key=${value.value} the property is overridden
""".trimIndent()
false -> "$key=${value.value}"
}
}.joinToString("\n")
@@ -0,0 +1,256 @@
/*
* 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.junit.jupiter.api.DisplayName
import org.junit.jupiter.api.io.TempDir
import java.io.FileInputStream
import java.nio.file.Files
import java.nio.file.Path
import java.util.*
import kotlin.test.*
class LocalPropertiesModifierTest {
@TempDir
lateinit var workingDir: Path
private val localPropertiesFile by lazy {
workingDir.resolve("local.properties")
}
private val modifier by lazy {
LocalPropertiesModifier(localPropertiesFile.toFile())
}
private val setupFile = SetupFile(
mapOf(
"newProperty1" to "someValue",
"newProperty2" to "someOtherValue",
"alreadySetProperty" to "newValue",
)
)
@Test
@DisplayName("sync is able to create local.properties file")
fun testSyncingWithAbsentFile() {
assertTrue(Files.notExists(localPropertiesFile))
modifier.applySetup(setupFile)
assertTrue(Files.exists(localPropertiesFile))
localPropertiesFile.propertiesFileContentAssertions { fileContents, properties ->
assertContainsMarkersOnce(fileContents)
assertEquals(setupFile.properties.size, properties.size)
for ((key, value) in setupFile.properties) {
assertEquals(value, properties[key])
}
}
}
@Test
@DisplayName("sync populates empty local.properties file")
fun testSyncingIntoEmptyFile() {
Files.createFile(localPropertiesFile)
modifier.applySetup(setupFile)
assertTrue(Files.exists(localPropertiesFile))
localPropertiesFile.propertiesFileContentAssertions { fileContents, properties ->
assertContainsMarkersOnce(fileContents)
assertEquals(setupFile.properties.size, properties.size)
for ((key, value) in setupFile.properties) {
assertEquals(value, properties[key])
}
}
}
/**
* Checks that a file like
* ```
* a=1
* b=2
* c=3
* ```
* is being transformed into
* ```
* a=1
* b=2
* c=3
* #header
* d=4
* f=5
* #footer
* ```
*/
@Test
@DisplayName("sync shouldn't remove any existing properties not managed by the sync")
fun testSyncingIntoNonEmptyFile() {
val initialContent = mapOf(
"oldProperty1" to PropertyValue("oldValue1"),
"oldProperty2" to PropertyValue("oldValue2"),
)
fillInitialLocalPropertiesFile(initialContent)
modifier.applySetup(setupFile)
localPropertiesFile.propertiesFileContentAssertions { fileContents, properties ->
assertContainsMarkersOnce(fileContents)
val expectedProperties = setupFile.properties + initialContent.mapValues { it.value.value }
assertEquals(expectedProperties.size, properties.size)
for ((key, value) in expectedProperties) {
assertEquals(value, properties[key])
}
}
}
/**
* Checks that a file like
* ```
* a=1
* b=2
* f=3
* ```
* is being transformed into
* ```
* a=1
* b=2
* c=3
* #header
* d=4
* #footer
* ```
*/
@Test
@DisplayName("sync shouldn't override properties if they already manually set")
fun testSyncingDoesNotOverrideValues() {
val initialContent = mapOf(
"oldProperty1" to PropertyValue("oldValue1"),
"oldProperty2" to PropertyValue("oldValue2"),
"alreadySetProperty" to PropertyValue("oldValue3"),
)
fillInitialLocalPropertiesFile(initialContent)
modifier.applySetup(setupFile)
localPropertiesFile.propertiesFileContentAssertions { fileContents, properties ->
assertContainsMarkersOnce(fileContents)
val expectedProperties = setupFile.properties + initialContent.mapValues { it.value.value }
assertEquals(expectedProperties.size, properties.size)
for ((key, value) in expectedProperties) {
assertEquals(value, properties[key])
}
}
}
/**
* Checks that a file like
* ```
* a=1
* b=2
* c=3
* #header
* d=4
* #footer
* e=5
* ```
* is being transformed into
* ```
* a=1
* b=2
* c=3
* e=5
* #header
* d=10
* #footer
* ```
*/
@Test
@DisplayName("sync should override automatically set properties")
fun testSyncingOverrideAutomaticallySetValues() {
val initialContent = mapOf(
"oldProperty1" to PropertyValue("oldValue1"),
"oldProperty2" to PropertyValue("oldValue2"),
"alreadySetProperty" to PropertyValue("oldValue3"),
)
fillInitialLocalPropertiesFile(initialContent)
modifier.applySetup(setupFile)
val newProperties = mapOf(
"newManualProperty" to PropertyValue("5"),
"otherAlreadySetProperty" to PropertyValue("5"),
)
fillInitialLocalPropertiesFile(newProperties)
val anotherSetupFile = SetupFile(
mapOf(
"newProperty2" to "other", // a new value
"newProperty3" to "someOtherValue", // a new record
"otherAlreadySetProperty" to "someOtherValue",
)
)
modifier.applySetup(anotherSetupFile)
localPropertiesFile.propertiesFileContentAssertions { fileContents, properties ->
assertContainsMarkersOnce(fileContents)
val expectedProperties =
anotherSetupFile.properties + initialContent.mapValues { it.value.value } + newProperties.mapValues { it.value.value }
assertEquals(expectedProperties.size, properties.size)
for ((key, value) in expectedProperties) {
assertEquals(value, properties[key])
}
}
}
private fun assertContainsMarkersOnce(content: String) {
assertContainsOnce(content, SYNCED_PROPERTIES_START_LINES)
assertContainsOnce(content, SYNCED_PROPERTIES_END_LINE)
}
private fun assertContainsOnce(content: String, substring: String) {
var currentOffset = 0
var count = 0
var nextIndex = content.indexOf(substring, currentOffset)
while (nextIndex != -1 && count < 2) {
count++
currentOffset = nextIndex + substring.length
nextIndex = content.indexOf(substring, currentOffset)
}
assertEquals(1, count)
}
private fun fillInitialLocalPropertiesFile(content: Map<String, PropertyValue>) {
val localPropertiesFile = localPropertiesFile.toFile()
localPropertiesFile.appendText(
"""
|${content.asPropertiesLines}
""".trimMargin()
)
}
private fun Path.propertiesFileContentAssertions(assertions: (String, Properties) -> Unit) {
val fileContent = Files.readAllLines(this).joinToString("\n")
try {
val localProperties = Properties().apply {
FileInputStream(localPropertiesFile.toFile()).use {
load(it)
}
}
assertions(fileContent, localProperties)
} catch (e: Throwable) {
println(
"""
|Some assertions on the properties file have failed.
|File contents:
|======
|$fileContent
|======
""".trimMargin()
)
throw e
}
}
}