skip to content

Explain how `rename`, `expand`, `filter`, and `eachFile` work inside a Copy task's CopySpec, and when you'd use each.

level: middleimportance: should knowfreq 40%

answer

  1. rename = name only (regex/closure)
  2. expand = ${token} via SimpleTemplateEngine
  3. filter = line/ReplaceTokens content
  4. eachFile = FileCopyDetails per file
  5. text-only: scope with nested spec

basics

~10 s

rename 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 s

All 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 lines
kotlin
tasks.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

for a junior

Recognize rename and that expand substitutes tokens; basic usage.

for a middle

Differentiate all four, give a use for each, and know the text-only scoping rule.

for a senior

Discuss ordering, FileCopyDetails permissions/exclude, ReplaceTokens vs expand, escaping in expand.

for a principal

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.

context