diff --git a/compiler/util-io/src/org/jetbrains/kotlin/konan/properties/Properties.kt b/compiler/util-io/src/org/jetbrains/kotlin/konan/properties/Properties.kt index c4924226a69..2b4a2ba94dd 100644 --- a/compiler/util-io/src/org/jetbrains/kotlin/konan/properties/Properties.kt +++ b/compiler/util-io/src/org/jetbrains/kotlin/konan/properties/Properties.kt @@ -107,13 +107,33 @@ fun Properties.resolvablePropertyString( */ private fun String.resolveValue(properties: Properties, visitedProperties: MutableSet = mutableSetOf()): List = when { - startsWith("$") -> { - val property = this.substringAfter('$') + contains("$") -> { + val prefix = this.substringBefore('$', missingDelimiterValue = "") + val withoutSigil = this.substringAfter('$') + val property = withoutSigil.substringBefore('/') + val relative = withoutSigil.substringAfter('/', missingDelimiterValue = "") // Keep track of visited properties to avoid running in circles. if (!visitedProperties.add(property)) { error("Circular dependency: ${visitedProperties.joinToString()}") } - properties.resolvablePropertyList(property, visitedProperties = visitedProperties) + val substitutionResult = properties.resolvablePropertyList(property, visitedProperties = visitedProperties) + when { + substitutionResult.size > 1 -> when { + relative.isNotEmpty() -> + error("Cannot append `/$relative` to multiple values: ${substitutionResult.joinToString()}") + prefix.isNotEmpty() -> + error("Cannot add prefix `$prefix` to multiple values: ${substitutionResult.joinToString()}") + else -> substitutionResult + } + else -> substitutionResult.map { + // Avoid repeated '/' at the end. + if (relative.isNotEmpty()) { + "$prefix${it.dropLastWhile { it == '/' }}/$relative" + } else { + "$prefix$it" + } + } + } } else -> listOf(this) } diff --git a/compiler/util-io/test/org/jetbrains/kotlin/konan/properties/ResolvablePropertiesTests.kt b/compiler/util-io/test/org/jetbrains/kotlin/konan/properties/ResolvablePropertiesTests.kt index bad1a5725a4..d3e0602f17f 100644 --- a/compiler/util-io/test/org/jetbrains/kotlin/konan/properties/ResolvablePropertiesTests.kt +++ b/compiler/util-io/test/org/jetbrains/kotlin/konan/properties/ResolvablePropertiesTests.kt @@ -65,6 +65,61 @@ class ResolvablePropertiesTests { props.resolvablePropertyString("k1") } + @Test + fun `trivial relative path`() { + val props = propertiesOf( + "k1" to "v1", + "k2" to "\$k1/sysroot" + ) + assertEquals("v1/sysroot", props.resolvablePropertyString("k2")) + } + + @Test + fun `two-fold relative path`() { + val props = propertiesOf( + "k1" to "v1", + "k2" to "\$k1/toolchain", + "k3" to "\$k2/sysroot" + ) + assertEquals("v1/toolchain/sysroot", props.resolvablePropertyString("k3")) + } + + @Test + fun `absolute path`() { + val props = propertiesOf( + "k1" to "/", + "k2" to "\$k1/bin" + ) + assertEquals("/bin", props.resolvablePropertyString("k2")) + } + + @Test(expected = java.lang.IllegalStateException::class) + fun `incorrect relative path`() { + val props = propertiesOf( + "k1" to "v1 v2", + "k2" to "\$k1/sysroot" + ) + props.resolvablePropertyString("k2") + } + + @Test + fun `simple prefix`() { + val props = propertiesOf( + "k1" to "v1", + "k2" to "--key=\$k1" + ) + assertEquals("--key=v1", props.resolvablePropertyString("k2")) + } + + @Test + fun `with prefix and relative`() { + val props = propertiesOf( + "absoluteTargetSysRoot" to "/", + "include" to "-I\$absoluteTargetSysRoot/usr/include/c++/4.9.4" + ) + assertEquals("-I/usr/include/c++/4.9.4", props.resolvablePropertyString("include")) + } + companion object { private fun propertiesOf(vararg pairs: Pair): Properties = Properties().also {