What is a ValueSource and when would you use providers.of(...) over providers.environmentVariable or providers.exec?
answer
- ValueSource<T, P> implement obtain()
- providers.of(MySource) { parameters... }
- general external read, config-cache safe
- params isolated/snapshotted
- named, testable class vs inline read
basics
~10 sA ValueSource is a reusable, parameterized unit of external reading. You implement obtain() and call providers.of(MySource) { parameters... } to get a lazy, config-cache-safe Provider for any custom external input.
solid answer
~40 sA `ValueSource<T, P : ValueSourceParameters>` is the general-purpose way to wrap *any* external read — an HTTP call, reading a config file with custom parsing, querying the OS — into a lazy, config-cache-compatible `Provider<T>`. You implement `obtain(): T?` and declare typed `parameters`; Gradle instantiates it, and you obtain a provider via `providers.of(MySource::class) { parameters.path.set(...) }`. Prefer it over the built-ins when no specialized factory fits: `environmentVariable`/`systemProperty`/`gradleProperty` cover those specific sources, and `exec` covers processes, but `ValueSource` covers everything else while keeping the same guarantees. Gradle isolates the parameters, may re-run `obtain()` as needed, and treats the result as a tracked configuration-cache input. It also keeps build scripts clean by encapsulating the read in a named, testable class.
code
kotlin · 12 linesabstract class FileLineCount :
ValueSource<Int, FileLineCount.Params> {
interface Params : ValueSourceParameters {
val file: RegularFileProperty
}
override fun obtain(): Int =
parameters.file.get().asFile.readLines().size
}
val lines = providers.of(FileLineCount::class) {
parameters.file.set(layout.projectDirectory.file("data.txt"))
}go deeper
Awareness that custom external reads exist via ValueSource; details not expected.
Know the obtain()/parameters shape and that providers.of yields a lazy provider.
Explain isolation, config-cache tracking, when to pick it over built-ins, and the no-Project rule.
Standardize ValueSource as the org pattern for external inputs and govern cost/idempotence of obtain().
## The gap ValueSource fills ProviderFactory has specialized factories: `environmentVariable`, `systemProperty`, `gradleProperty` for those sources, and `exec` for processes. But many builds need *other* external inputs — read a JSON descriptor, hit an internal service, look up something from the OS. Doing that ad hoc is eager and invisible to the configuration cache. `ValueSource` is the sanctioned, general abstraction. ## Anatomy A `ValueSource` has two type parameters: the value type `T` and a `ValueSourceParameters` type `P` (use `ValueSourceParameters.None` if you need none). You implement: ```kotlin abstract class GitBranchSource : ValueSource<String, ValueSourceParameters.None> { override fun obtain(): String? { val proc = ProcessBuilder("git", "branch", "--show-current").start() return proc.inputStream.bufferedReader().readText().trim() } } ``` Obtain a provider: ```kotlin val branch: Provider<String> = providers.of(GitBranchSource::class) { } ``` For parameters, declare an interface extending `ValueSourceParameters` with `Property`/`RegularFileProperty` getters and set them in the configuration block. ## What Gradle guarantees - **Laziness**: `obtain()` runs only when the provider is queried. - **Isolation**: parameters are isolated (snapshotted) so the source is reproducible. - **Config-cache tracking**: the obtained value is treated as an input; Gradle decides when to re-run `obtain()` rather than trusting a stale cache. - **Reusability/testability**: the logic lives in a named class you can unit test, not inline in a script. ## Choosing the right tool | Need | Use | |------|-----| | Env var | `providers.environmentVariable` | | System property | `providers.systemProperty` | | Gradle property / -P | `providers.gradleProperty` | | Run a process | `providers.exec` | | Anything else external | `ValueSource` + `providers.of` | ## Caution `obtain()` may be invoked at build time and potentially on each build; keep it cheap or have it cache. It must not capture `Project` or other non-serializable build state.
- How do you pass inputs into a ValueSource?Define a parameters interface extending ValueSourceParameters with Property-typed getters and set them in the providers.of { parameters.* = ... } block; Gradle isolates them.
- Can a ValueSource hold a Project reference?No. It must be serializable/isolatable; capturing Project (or other non-isolatable state) breaks the configuration cache. Pass needed data via parameters.
- Does ValueSource cache its result across builds?Gradle may reuse the value via the configuration cache, but obtain() is not a persistent memoization; treat it as potentially run each build and keep it cheap.
saying these in an interview costs you the question
- Putting expensive uncached I/O in obtain() and assuming it runs once
- Capturing Project/non-serializable state inside the ValueSource
- Using a ValueSource where a built-in (environmentVariable/exec) already fits