skip to content

How do you define a task in Gradle that copies files from one directory into another, and what are the core DSL elements you use?

level: juniorimportance: must knowfreq 65%

answer

  1. Copy task type
  2. from / into
  3. include/exclude/rename/expand
  4. incremental + UP-TO-DATE
  5. register over create

basics

~10 s

Register a task of type Copy and configure from (source) and into (destination). Gradle copies the matched files into the target directory when the task runs.

solid answer

~30 s

Use the built-in `Copy` task type. You register it with `tasks.register("name", Copy::class)` and configure two essentials: `from(...)` declares one or more source files/dirs, and `into(...)` declares the destination directory. Inside the configuration you can add `include`/`exclude` ANT-style patterns to filter, `rename` to transform file names, and `expand`/`filter` to substitute tokens. `Copy` is incremental and idempotent — re-running it without input changes is UP-TO-DATE. Prefer `tasks.register` over `tasks.create` so the task is configured lazily. Avoid calling `project.copy {}` from the configuration phase for build outputs; use a real `Copy` task so Gradle tracks inputs/outputs and caching.

code

kotlin · 7 lines
kotlin
tasks.register<Copy>("copyConfig") {
    from("src/main/config")
    into(layout.buildDirectory.dir("config"))
    include("**/*.properties")
    exclude("**/*-local.properties")
    rename("(.+)\\.properties", "$1.props")
}

go deeper

for a junior

Name the Copy type and the from/into pair; show a minimal register block.

for a middle

Add include/exclude/rename and explain incrementality (UP-TO-DATE) and register-vs-create.

for a senior

Discuss CopySpec composition, nested specs for fan-out, and when imperative copy {} is acceptable vs a tracked task.

for a principal

Frame copy tasks as cacheable, declared-IO units in a reproducible build; govern conventions across modules.

## The `Copy` task type Gradle ships a built-in task type called `Copy`. A *task type* is a class that already knows how to do a job; you just register an instance and configure it. `Copy` extends `AbstractCopyTask`, which gives it a fluent file-copy DSL via the `CopySpec` interface. ### The two mandatory pieces - **`from(...)`** — the source. Can be a directory, a file, a `FileTree`, a `Configuration`, or even another `CopySpec`. Multiple `from` calls accumulate. - **`into(...)`** — the single destination directory. ```kotlin tasks.register<Copy>("copyDocs") { from("src/docs") into(layout.buildDirectory.dir("docs")) } ``` ### Filtering and transforming Because `Copy` implements `CopySpec`, you get: - `include("**/*.txt")` / `exclude("**/*.tmp")` — ANT glob patterns. - `rename("(.+)\\.txt", "$1.md")` — regex-based renaming. - `expand(mapOf("version" to project.version))` — Groovy `SimpleTemplateEngine` token substitution (`${version}`), useful for filtering text resources. - `filter { line -> ... }` / `eachFile { fcd -> ... }` — line- or file-level hooks; `eachFile` receives a `FileCopyDetails` so you can change `fcd.path`, `fcd.mode`, or call `fcd.exclude()`. ### Why a task, not `project.copy {}` `Copy` is an **incremental** task: Gradle snapshots its declared inputs (`from`) and outputs (`into`). If nothing changed, the task is skipped as `UP-TO-DATE`, and it participates in the build cache. Calling the imperative `project.copy {}` API does the copy immediately and is *not* tracked — fine for a quick one-off inside a task action, wrong for declaring build outputs. ### Laziness Use `tasks.register<Copy>(...)` (configuration-avoidance API) rather than `tasks.create`; the configuration block runs only if the task is actually needed in the task graph.

  • What happens if you call `into` twice in a Copy task?
    The last `into` wins — a Copy task has a single root destination directory. To copy different sources to different subdirectories you nest child `CopySpec`s with `from(...) { into("subdir") }`.
  • Why is `tasks.register` preferred over `tasks.create` here?
    `register` is the configuration-avoidance API: the configuration block runs lazily only if the task ends up in the graph, saving configuration-time work for builds that don't run it.

saying these in an interview costs you the question

  • Saying `into` accepts multiple destinations — it's a single root dir.
  • Using `project.copy {}` to declare build outputs (untracked, breaks incrementality/caching).

context