[Build, IGS] Implement LocalPropertiesModifier
#KTI-1223 In Progress
This commit is contained in:
committed by
Space Team
parent
e30b72fa8f
commit
6804d0a0af
+84
@@ -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")
|
||||
+256
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user