[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