Explain how archive file naming and the archiveFile output are exposed as lazy Provider/Property types, and why wiring archiveFile downstream is better than hardcoding a path.
answer
- Property = settable Provider, lazy
- archiveBaseName/version/classifier/extension -> archiveFileName
- archiveFile: Provider<RegularFile> (read-only)
- flatMap { it.archiveFile } carries task dependency
- no manual dependsOn needed; config-cache friendly
basics
~10 sArchive name parts (archiveBaseName, version, etc.) are Property objects and the resulting file is archiveFile, a Provider<RegularFile>. Wiring that provider into other tasks keeps the dependency lazy and lets Gradle track it correctly.
solid answer
~40 sOn `AbstractArchiveTask`, naming is built from lazy `Property` values: `archiveBaseName`, `archiveAppendix`, `archiveVersion`, `archiveClassifier`, `archiveExtension`, with `archiveFileName` derived from them (or overridden). `destinationDirectory` is a `DirectoryProperty`, and the resolved output is `archiveFile`, a read-only `Provider<RegularFile>`. Because these are providers, you can pass `zipTask.flatMap { it.archiveFile }` into another task's input *without forcing the value or the task to be configured eagerly*. Gradle then infers a task dependency automatically (the provider carries its producing task), so you don't need an explicit `dependsOn`, and the value is computed only when needed. Hardcoding `build/dist/app.zip` defeats this: it breaks if the version or destination changes, requires a manual `dependsOn`, and bypasses Gradle's input/output wiring that drives up-to-date checks and the configuration cache.
code
kotlin · 11 linesval packageDist = tasks.register<Zip>("packageDist") {
from("src/main/dist")
archiveBaseName.set("myapp")
archiveVersion.set(project.version.toString())
}
tasks.register<Copy>("stage") {
// lazy provider -> implicit dependsOn packageDist
from(packageDist.flatMap { it.archiveFile })
into(layout.buildDirectory.dir("staging"))
}go deeper
Know that the archive's name is built from base name and version and the result is archiveFile.
Explain the naming properties and that archiveFile is a Provider<RegularFile> you can pass to from(...).
Articulate why provider wiring gives implicit task dependencies, laziness, and correct up-to-date/config-cache behavior versus hardcoding a path.
Set conventions for plugins that expose archive outputs as providers, ensuring consumers across modules wire lazily and the build stays configuration-cache compatible at scale.
## The lazy configuration model Gradle's modern API is built on **providers**: `Provider<T>` is a lazily-computed value, and `Property<T>` is a `Provider` you can also set. The point is to defer reading a value until it's actually needed, so plugins can be wired together before everything is configured. Archive tasks are a textbook example. ## Naming properties `AbstractArchiveTask` exposes the name as composable lazy pieces: - `archiveBaseName: Property<String>` - `archiveAppendix: Property<String>` - `archiveVersion: Property<String>` - `archiveClassifier: Property<String>` - `archiveExtension: Property<String>` - `archiveFileName: Property<String>` — by default derived as `baseName-appendix-version-classifier.extension`, but you may set it directly to override. - `destinationDirectory: DirectoryProperty` Because they're properties, you set them with `.set(...)` or `.convention(...)`, and they can themselves be wired from other providers (e.g. `archiveVersion.set(provider { project.version.toString() })`). ## The output: archiveFile The single most important output is: ``` val archiveFile: Provider<RegularFile> ``` It resolves to `destinationDirectory / archiveFileName`. It is **read-only** — you shape it via the naming/destination properties, not by setting `archiveFile` itself. ## Why wire it instead of hardcoding Consider feeding a built zip into a publish/copy/exec step: ```kotlin val packageDist = tasks.register<Zip>("packageDist") { from("src/main/dist") archiveBaseName.set("myapp") archiveVersion.set(project.version.toString()) } tasks.register<Copy>("stage") { from(packageDist.flatMap { it.archiveFile }) // lazy + carries task dependency into(layout.buildDirectory.dir("staging")) } ``` Benefits of the provider wiring: 1. **Implicit task dependency** — the provider knows which task produces the file, so `stage` automatically `dependsOn` `packageDist`. No manual wiring to forget. 2. **Laziness / configuration avoidance** — `packageDist` isn't configured until needed; works with the configuration-avoidance API (`register`). 3. **Correctness under change** — if the version, base name, or destination changes, the consumer follows automatically. 4. **Up-to-date & configuration cache** — Gradle tracks the real input/output relationship, so caching and incremental builds behave correctly. Hardcoding `"build/distributions/myapp-1.0.zip"` loses every one of these: stale on version bumps, needs an explicit `dependsOn`, and is invisible to Gradle's wiring.
- Why don't you need an explicit dependsOn when consuming archiveFile via flatMap?A Provider produced by a task carries that task as its producer. When you wire it into another task's input, Gradle infers the dependency automatically, so the consumer runs after the archive task without a manual dependsOn.
- What's the difference between .set() and .convention() on these properties?.set() assigns the value definitively. .convention() supplies a default that applies only if no explicit value is set, letting downstream config override it — useful in plugins.
- Why is reading archiveFile.get() at configuration time discouraged?Calling get() forces eager resolution, defeating laziness and potentially breaking configuration avoidance/cache. Pass the provider itself and let Gradle resolve it at execution time.
saying these in an interview costs you the question
- Hardcoding the archive path string instead of consuming archiveFile.
- Calling .get() on providers at configuration time and forcing eager evaluation.
- Adding a manual dependsOn that duplicates the dependency a provider already carries.