Skip to content

Instantly share code, notes, and snippets.

@aSemy
Last active January 30, 2025 15:14
Show Gist options
  • Save aSemy/c6b4acdd0667e559c34d3d495ce1a923 to your computer and use it in GitHub Desktop.
Save aSemy/c6b4acdd0667e559c34d3d495ce1a923 to your computer and use it in GitHub Desktop.
Gradle environment variable container
import org.gradle.api.Named
import org.gradle.api.NamedDomainObjectContainer
import org.gradle.api.SupportsKotlinAssignmentOverloading
import org.gradle.api.model.ObjectFactory
import org.gradle.api.plugins.ExtensionAware
import org.gradle.api.provider.Property
import org.gradle.api.provider.Provider
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.Internal
import org.gradle.api.tasks.Nested
import org.gradle.api.tasks.Optional
import javax.inject.Inject
/**
* Key-value store of environment variables.
*/
// workaround for Gradle's confusing behaviour with MapProperty
// https://github.com/gradle/gradle/issues/12222
// https://github.com/gradle/gradle/issues/13364
// https://github.com/gradle/gradle/issues/32160
// https://github.com/gradle/gradle/issues/32152
// https://github.com/gradle/gradle/issues/32149
// https://github.com/gradle/gradle/issues/18352
// https://github.com/gradle/gradle/issues/17090
// https://github.com/gradle/gradle/issues/12222
internal abstract class EnvVarContainer @Inject constructor(
private val objects: ObjectFactory,
) : ExtensionAware {
/**
* Container for all environment variable key-values.
*
* The visibility must be 'protected' not 'private', otherwise Gradle can't see the nested values.
*/
@get:Nested
protected val content: NamedDomainObjectContainer<Entry> =
objects.domainObjectContainer(Entry::class.java, { name ->
val entry = Entry(
key = name,
value = objects.property(String::class.java)
)
[email protected](name, entry)
entry
})
fun set(key: String, value: String) {
content.maybeCreate(key).value.set(value)
}
fun set(key: String, value: Provider<String>) {
content.maybeCreate(key).value.set(value)
}
fun convention(key: String, value: String) {
content.maybeCreate(key).value.convention(value)
}
fun convention(key: String, value: Provider<String>) {
content.maybeCreate(key).value.convention(value)
}
/**
* Add all values from [other] into this container.
*
* The values from [other] will be [set][Property.set],
* overriding any [convention][Property.convention] values.
*/
fun setAll(other: EnvVarContainer) {
other.content.all { entry ->
set(entry.key, entry.value)
}
}
/**
* Add all values from [other] into this container.
*
* The values from [other] will be [conventions][Property.convention],
* meaning any [set][Property.set] values will take precedence.
*/
fun addConventions(other: EnvVarContainer) {
other.content.all { entry ->
convention(entry.key, entry.value)
}
}
/**
* Compute and return all values.
* This should only be done during Gradle's execution phase, when all values can be safely computed.
*/
fun compute(): Map<String, String?> =
content.associate { it.key to it.value.orNull }
/**
* A single key-value entry in [EnvVarContainer].
*/
@SupportsKotlinAssignmentOverloading
class Entry internal constructor(
@get:Input
val key: String,
@get:Input
@get:Optional
val value: Property<String>,
) : Named {
@Suppress("unused") // IJ doesn't correctly detect assignment overloading usages
fun assign(value: String): Unit = this.value.set(value)
@Suppress("unused") // IJ doesn't correctly detect assignment overloading usages
fun assign(value: Provider<String>): Unit = this.value.set(value)
@Internal
override fun getName(): String = key
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment