Explain how `rename`, `expand`, `filter`, and `eachFile` work inside a Copy task's CopySpec, and when you'd use each.
answer
- rename = name only (regex/closure)
- expand = ${token} via SimpleTemplateEngine
- filter = line/ReplaceTokens content
- eachFile = FileCopyDetails per file
- text-only: scope with nested spec
basics
~10 srename changes file names (regex/closure), expand substitutes ${token} placeholders in text, filter transforms content line-by-line, and eachFile gives a per-file hook to tweak path/permissions or exclude files.
solid answer
~40 sAll four are methods on `CopySpec`, available on any `Copy`/`Sync` task. **`rename`** transforms the *name* of copied files: `rename("(.+)-dev\\.conf", "$1.conf")` or a closure `rename { it.removeSuffix(".sample") }`. **`expand(map)`** runs each text file through Groovy's `SimpleTemplateEngine`, replacing `${key}` (and `<% %>` scriptlets) with values — handy for stamping version/build metadata into resources. **`filter`** transforms content stream-wise: either a line closure `filter { it.replace("@v@", version) }` or an ANT filter reader like `ReplaceTokens`. **`eachFile { fcd -> ... }`** runs per file with a `FileCopyDetails`, letting you mutate `fcd.path` (relocate), `fcd.mode`/`permissions` (Unix perms), or call `fcd.exclude()` to drop it. Order matters: `eachFile` and renames apply during the copy. A key gotcha: `expand`/`filter` only make sense for text files — running them over binaries corrupts them, so scope them with a nested spec.
code
kotlin · 8 linestasks.register<Copy>("processConf") {
into(layout.buildDirectory.dir("conf"))
from("src/main/conf") {
rename("(.+)\\.template", "$1")
expand("version" to project.version, "env" to "prod")
eachFile { if (name.startsWith("local")) exclude() }
}
}go deeper
Recognize rename and that expand substitutes tokens; basic usage.
Differentiate all four, give a use for each, and know the text-only scoping rule.
Discuss ordering, FileCopyDetails permissions/exclude, ReplaceTokens vs expand, escaping in expand.
Standardize resource-stamping patterns and prevent binary-corruption pitfalls in shared specs.
## `CopySpec` — the shared copy grammar `Copy` and `Sync` both implement `CopySpec`, the interface that defines the copy DSL. Four transformation hooks matter most: ### `rename` — change the destination file name Two forms: ```kotlin // regex + replacement (backreferences with $1) rename("(.+)\\.sample", "$1") // closure: receives the source name, returns new name (or null to keep) rename { name -> name.replace("-template", "") } ``` It affects the *name* only, not the directory. ### `expand` — template substitution `expand(mapOf("version" to project.version, "date" to today))` pushes each text file through Groovy's `SimpleTemplateEngine`. Inside files, `${version}` is replaced; `<% %>` scriptlets even allow logic. Great for generating config files. **Only run it on text.** Note that because `${}` is the template syntax, literal dollar-brace sequences in your source must be escaped. ### `filter` — content transformation Two flavors: ```kotlin // line-by-line closure filter { line -> line.replace("DEBUG", "INFO") } // ANT FilterReader import org.apache.tools.ant.filters.ReplaceTokens filter(ReplaceTokens::class, "tokens" to mapOf("v" to project.version.toString())) ``` With `ReplaceTokens`, `@v@` in the file becomes the value. ### `eachFile` — per-file programmatic control ```kotlin eachFile { // 'this' is a FileCopyDetails if (name.endsWith(".secret")) exclude() // drop it path = path.replaceFirst("^conf/", "") // relocate permissions { unix("rw-r--r--") } // set perms (Gradle 8.3+) } ``` `FileCopyDetails` exposes `name`, `path`, `relativePath`, `mode`/`permissions`, and `exclude()`. It runs for every file that survives include/exclude filtering. ### Scoping to avoid corrupting binaries Because `expand`/`filter` mutate bytes, never apply them blanket-wide over a tree containing images or jars. Use nested specs: ```kotlin tasks.register<Copy>("prepare") { into(layout.buildDirectory.dir("app")) from("src/conf") { expand("version" to project.version) } // text only from("src/assets") // binaries untouched } ``` ### Ordering Within a spec, include/exclude decide membership first; then `eachFile`, `rename`, `expand`/`filter` apply to the surviving files during the copy stream.
- Why shouldn't you call `expand` on a whole tree that includes images?`expand` rewrites file content through a text template engine; running it on binary files corrupts them. Scope it to a nested `from(...) { expand(...) }` over text only.
- What object does the `eachFile` closure receive and what can you do with it?A `FileCopyDetails` — you can read `name`/`path`/`relativePath`, change `path` to relocate, set `permissions`/`mode`, or call `exclude()` to drop the file.
- How does `rename` differ from `eachFile` for changing a file's location?`rename` changes only the file name; to change the directory/path you mutate `fcd.path` inside `eachFile` (or nest a child spec with its own `into`).
saying these in an interview costs you the question
- Using `expand`/`filter` over binary files and corrupting them.
- Thinking `rename` can move files to a different directory — it only changes the name.