# Working With Files

version 9.7.0-20260709013317+0000

File operations are fundamental to nearly every Gradle build. They involve handling source files, managing file dependencies, and generating reports. Gradle provides a robust API that simplifies these operations, enabling developers to perform necessary file tasks easily.

## Hardcoded paths and laziness

It is best practice to avoid hardcoded paths in build scripts.

In addition to avoiding hardcoded paths, Gradle encourages laziness in its build scripts. This means that tasks and operations should be deferred until they are actually needed rather than executed eagerly.

Many examples in this chapter use hard-coded paths as string literals. This makes them easy to understand, but it is not good practice. The problem is that paths often change, and the more places you need to change them, the more likely you will miss one and break the build.

Where possible, you should use tasks, task properties, and project properties — in that order of preference — to configure file paths.

For example, if you create a task that packages the compiled classes of a Java application, you should use an implementation similar to this:

build.gradle.kts

    val archivesDirPath = layout.buildDirectory.dir("archives")

    tasks.register<Zip>("packageClasses") {
        archiveAppendix = "classes"
        destinationDirectory = archivesDirPath

        from(tasks.compileJava)
    }

build.gradle

    def archivesDirPath = layout.buildDirectory.dir('archives')

    tasks.register('packageClasses', Zip) {
        archiveAppendix = "classes"
        destinationDirectory = archivesDirPath

        from compileJava
    }

The `compileJava` task is the source of the files to package, and the project property `archivesDirPath` stores the location of the archives, as we are likely to use it elsewhere in the build.

Using a task directly as an argument like this relies on it having defined outputs, so it won’t always be possible. This example could be further improved by relying on the Java plugin’s convention for `destinationDirectory` rather than overriding it, but it does demonstrate the use of project properties.

## Locating files

To perform some action on a file, you need to know where it is, and that’s the information provided by file paths. Gradle builds on the standard Java `File` class, which represents the location of a single file and provides APIs for dealing with collections of paths.

### Using `ProjectLayout`

The `ProjectLayout` class is used to access various directories and files within a project. It provides methods to retrieve paths to the project directory, build directory, settings file, and other important locations within the project’s file structure. This class is particularly useful when you need to work with files in a build script or plugin in different project paths:

build.gradle.kts

    val archivesDirPath = layout.buildDirectory.dir("archives")

build.gradle

    def archivesDirPath = layout.buildDirectory.dir('archives')

You can learn more about the `ProjectLayout` class in Services.

### Using `Project.file()`

Gradle provides the Project.file(java.lang.Object) method for specifying the location of a single file or directory.

Relative paths are resolved relative to the project directory, while absolute paths remain unchanged.

| Never use `new File(relative path)` unless passed to `file()` or `files()` or `from()` or other methods defined in terms of `file()` or `files()`. Otherwise, this creates a path relative to the current working directory (CWD). Gradle can make no guarantees about the location of the CWD, which means builds that rely on it may break at any time.

Here are some examples of using the `file()` method with different types of arguments:

build.gradle.kts

    // Using a relative path
    var configFile = file("src/config.xml")

    // Using an absolute path
    configFile = file(configFile.absolutePath)

    // Using a File object with a relative path
    configFile = file(File("src/config.xml"))

    // Using a java.nio.file.Path object with a relative path
    configFile = file(Paths.get("src", "config.xml"))

    // Using an absolute java.nio.file.Path object
    configFile = file(Paths.get(System.getProperty("user.home")).resolve("global-config.xml"))

build.gradle

    // Using a relative path
    File configFile = file('src/config.xml')

    // Using an absolute path
    configFile = file(configFile.absolutePath)

    // Using a File object with a relative path
    configFile = file(new File('src/config.xml'))

    // Using a java.nio.file.Path object with a relative path
    configFile = file(Paths.get('src', 'config.xml'))

    // Using an absolute java.nio.file.Path object
    configFile = file(Paths.get(System.getProperty('user.home')).resolve('global-config.xml'))

As you can see, you can pass strings, `File` instances and `Path` instances to the `file()` method, all of which result in an absolute `File` object.

In the case of multi-project builds, the `file()` method will always turn relative paths into paths relative to the current project directory, which may be a child project.

### Using `ProjectLayout.settingsDirectory()`

To use a path relative to the settings directory, access the `Project.layout`, retrieve the `settingsDirectory` from it, and construct an absolute path.

For example:

build.gradle.kts

    val configFile = layout.settingsDirectory.file("shared/config.xml").asFile

build.gradle

    File configFile = layout.settingsDirectory.file("shared/config.xml").asFile

Let’s say you’re working on a multi-project build in the directory: `dev/projects/AcmeHealth`. The build script above is at: `AcmeHealth/subprojects/AcmePatientRecordLib/build.gradle`. The absolute file path will resolve to: `dev/projects/AcmeHealth/shared/config.xml`:

    dev
    ├── projects
    │   ├── AcmeHealth
    │   │   ├── subprojects
    │   │   │   ├── AcmePatientRecordLib
    │   │   │   │   └── build.gradle
    │   │   │   └── ...
    │   │   ├── shared
    │   │   │   └── config.xml
    │   │   └── ...
    │   └── ...
    └── settings.gradle

Note that `Project` also provides Project.getRootProject() for multi-project builds which, in the example, would resolve to: `dev/projects/AcmeHealth/subprojects/AcmePatientRecordLib`.

### Using `FileCollection`

A file collection is simply a set of file paths represented by the FileCollection interface.

The set of paths can be any file path. The file paths don’t have to be related in any way, so they don’t have to be in the same directory or have a shared parent directory.

The recommended way to specify a collection of files is to use the ProjectLayout.files(java.lang.Object...) method, which returns a `FileCollection` instance. This flexible method allows you to pass multiple strings, `File` instances, collections of strings, collections of `File`s, and more. You can also pass in tasks as arguments if they have defined outputs.

| `files()` properly handles relative paths and `File(relative path)` instances, resolving them relative to the project directory.

As with the Project.file(java.lang.Object) method covered in the previous section, all relative paths are evaluated relative to the current project directory. The following example demonstrates some of the variety of argument types you can use — strings, `File` instances, lists, or `Paths`:

build.gradle.kts

    val collection: FileCollection = layout.files(
        "src/file1.txt",
        File("src/file2.txt"),
        listOf("src/file3.csv", "src/file4.csv"),
        Paths.get("src", "file5.txt")
    )

build.gradle

    FileCollection collection = layout.files('src/file1.txt',
                                      new File('src/file2.txt'),
                                      ['src/file3.csv', 'src/file4.csv'],
                                      Paths.get('src', 'file5.txt'))

File collections have important attributes in Gradle. They can be:

- created lazily

- iterated over

- filtered

- combined

Lazy creation of a file collection is useful when evaluating the files that make up a collection when a build runs. In the following example, we query the file system to find out what files exist in a particular directory and then make those into a file collection:

build.gradle.kts

    tasks.register("list") {
        val projectDirectory = layout.projectDirectory
        doLast {
            var srcDir: File? = null

            val collection = projectDirectory.files({
                srcDir?.listFiles()
            })

            srcDir = projectDirectory.file("src").asFile
            println("Contents of ${srcDir.name}")
            collection.map { it.relativeTo(projectDirectory.asFile) }.sorted().forEach { println(it) }

            srcDir = projectDirectory.file("src2").asFile
            println("Contents of ${srcDir.name}")
            collection.map { it.relativeTo(projectDirectory.asFile) }.sorted().forEach { println(it) }
        }
    }

build.gradle

    tasks.register('list') {
        Directory projectDirectory = layout.projectDirectory
        doLast {
            File srcDir

            // Create a file collection using a closure
            collection = projectDirectory.files { srcDir.listFiles() }

            srcDir = projectDirectory.file('src').asFile
            println "Contents of $srcDir.name"
            collection.collect { projectDirectory.asFile.relativePath(it) }.sort().each { println it }

            srcDir = projectDirectory.file('src2').asFile
            println "Contents of $srcDir.name"
            collection.collect { projectDirectory.asFile.relativePath(it) }.sort().each { println it }
        }
    }

    $ ./gradlew -q list

    Contents of src
    src/dir1
    src/file1.txt
    Contents of src2
    src2/dir1
    src2/dir2

The key to lazy creation is passing a closure (in Groovy) or a `Provider` (in Kotlin) to the `files()` method. Your closure or provider must return a value of a type accepted by `files()`, such as `List<File>`, `String`, or `FileCollection`.

Iterating over a file collection can be done through the `each()` method (in Groovy) or `forEach` method (in Kotlin) on the collection or using the collection in a `for` loop. In both approaches, the file collection is treated as a set of `File` instances, i.e., your iteration variable will be of type `File`.

The following example demonstrates such iteration. It also demonstrates how you can convert file collections to other types using the `as` operator (or supported properties):

build.gradle.kts

    // Iterate over the files in the collection
    collection.forEach { file: File ->
        println(file.name)
    }

    // Convert the collection to various types
    val set: Set<File> = collection.files
    val list: List<File> = collection.toList()
    val path: String = collection.asPath
    val file: File = collection.singleFile

    // Add and subtract collections
    val union = collection + projectLayout.files("src/file2.txt")
    val difference = collection - projectLayout.files("src/file2.txt")

build.gradle

    // Iterate over the files in the collection
    collection.each { File file ->
        println file.name
    }

    // Convert the collection to various types
    Set set = collection.files
    Set set2 = collection as Set
    List list = collection as List
    String path = collection.asPath
    File file = collection.singleFile

    // Add and subtract collections
    def union = collection + projectLayout.files('src/file2.txt')
    def difference = collection - projectLayout.files('src/file2.txt')

You can also see at the end of the example how to combine file collections using the `+` and `-` operators to merge and subtract them. An important feature of the resulting file collections is that they are live. In other words, when you combine file collections this way, the result always reflects what’s currently in the source file collections, even if they change during the build.

For example, imagine `collection` in the above example gains an extra file or two after `union` is created. As long as you use `union` after those files are added to `collection`, `union` will also contain those additional files. The same goes for the `different` file collection.

Live collections are also important when it comes to filtering. Suppose you want to use a subset of a file collection. In that case, you can take advantage of the FileCollection.filter(org.gradle.api.specs.Spec) method to determine which files to "keep". In the following example, we create a new collection that consists of only the files that end with `.txt` in the source collection:

build.gradle.kts

    val textFiles: FileCollection = collection.filter { f: File ->
        f.name.endsWith(".txt")
    }

build.gradle

    FileCollection textFiles = collection.filter { File f ->
        f.name.endsWith(".txt")
    }

    $ ./gradlew -q filterTextFiles

    src/file1.txt
    src/file2.txt
    src/file5.txt

If `collection` changes at any time, either by adding or removing files from itself, then `textFiles` will immediately reflect the change because it is also a live collection. Note that the closure you pass to `filter()` takes a `File` as an argument and should return a boolean.

### Understanding implicit conversion to file collections

Many objects in Gradle have properties which accept a set of input files. For example, the JavaCompile task has a `source` property that defines the source files to compile. You can set the value of this property using any of the types supported by the files() method, as mentioned in the API docs. This means you can, for example, set the property to a `File`, `String`, collection, `FileCollection` or even a closure or `Provider`.

This is a feature of specific tasks! That means implicit conversion will not happen for just any task that has a `FileCollection` or `FileTree` property. If you want to know whether implicit conversion happens in a particular situation, you will need to read the relevant documentation, such as the corresponding task’s API docs. Alternatively, you can remove all doubt by explicitly using ProjectLayout.files(java.lang.Object...) in your build.

Here are some examples of the different types of arguments that the `source` property can take:

build.gradle.kts

    tasks.register<JavaCompile>("compile") {
        // Use a File object to specify the source directory
        source = fileTree(file("src/main/java"))

        // Use a String path to specify the source directory
        source = fileTree("src/main/java")

        // Use a collection to specify multiple source directories
        source = fileTree(listOf("src/main/java", "../shared/java"))

        // Use a FileCollection (or FileTree in this case) to specify the source files
        source = fileTree("src/main/java").matching { include("org/gradle/api/**") }

        // Using a closure to specify the source files.
        setSource({
            // Use the contents of each zip file in the src dir
            file("src").listFiles().filter { it.name.endsWith(".zip") }.map { zipTree(it) }
        })
    }

build.gradle

    tasks.register('compile', JavaCompile) {

        // Use a File object to specify the source directory
        source = file('src/main/java')

        // Use a String path to specify the source directory
        source = 'src/main/java'

        // Use a collection to specify multiple source directories
        source = ['src/main/java', '../shared/java']

        // Use a FileCollection (or FileTree in this case) to specify the source files
        source = fileTree(dir: 'src/main/java').matching { include 'org/gradle/api/**' }

        // Using a closure to specify the source files.
        source = {
            // Use the contents of each zip file in the src dir
            file('src').listFiles().findAll {it.name.endsWith('.zip')}.collect { zipTree(it) }
        }
    }

One other thing to note is that properties like `source` have corresponding methods in core Gradle tasks. Those methods follow the convention of appending to collections of values rather than replacing them. Again, this method accepts any of the types supported by the files() method, as shown here:

build.gradle.kts

    tasks.named<JavaCompile>("compile") {
        // Add some source directories use String paths
        source("src/main/java", "src/main/groovy")

        // Add a source directory using a File object
        source(file("../shared/java"))

        // Add some source directories using a closure
        setSource({ file("src/test/").listFiles() })
    }

build.gradle

    compile {
        // Add some source directories use String paths
        source 'src/main/java', 'src/main/groovy'

        // Add a source directory using a File object
        source file('../shared/java')

        // Add some source directories using a closure
        source { file('src/test/').listFiles() }
    }

As this is a common convention, we recommend that you follow it in your own custom tasks. Specifically, if you plan to add a method to configure a collection-based property, make sure the method appends rather than replaces values.

### Using `FileTree`

A file tree is a file collection that retains the directory structure of the files it contains and has the type FileTree. This means all the paths in a file tree must have a shared parent directory. The following diagram highlights the distinction between file trees and file collections in the typical case of copying files:

| Although `FileTree` extends `FileCollection` (an is-a relationship), their behaviors differ. In other words, you can use a file tree wherever a file collection is required, but remember that a file collection is a flat list/set of files, while a file tree is a file and directory hierarchy. To convert a file tree to a flat collection, use the FileTree.getFiles() property.

The simplest way to create a file tree is to pass a file or directory path to the Project.fileTree(java.lang.Object) method. This will create a tree of all the files and directories in that base directory (but not the base directory itself). The following example demonstrates how to use this method and how to filter the files and directories using Ant-style patterns:

build.gradle.kts

    // Create a file tree with a base directory
    var tree: ConfigurableFileTree = fileTree("src/main")

    // Add include and exclude patterns to the tree
    tree.include("**/*.java")
    tree.exclude("**/Abstract*")

    // Create a tree using closure
    tree = fileTree("src") {
        include("**/*.java")
    }

    // Create a tree using a map
    tree = fileTree("dir" to "src", "include" to "**/*.java")
    tree = fileTree("dir" to "src", "includes" to listOf("**/*.java", "**/*.xml"))
    tree = fileTree("dir" to "src", "include" to "**/*.java", "exclude" to "**/*test*/**")

build.gradle

    // Create a file tree with a base directory
    ConfigurableFileTree tree = fileTree(dir: 'src/main')

    // Add include and exclude patterns to the tree
    tree.include '**/*.java'
    tree.exclude '**/Abstract*'

    // Create a tree using closure
    tree = fileTree('src') {
        include '**/*.java'
    }

    // Create a tree using a map
    tree = fileTree(dir: 'src', include: '**/*.java')
    tree = fileTree(dir: 'src', includes: ['**/*.java', '**/*.xml'])
    tree = fileTree(dir: 'src', include: '**/*.java', exclude: '**/*test*/**')

You can see more examples of supported patterns in the API docs for PatternFilterable.

By default, `fileTree()` returns a `FileTree` instance that applies some default exclude patterns for convenience — the same defaults as Ant. For the complete default exclude list, see the Ant manual.

If those default excludes prove problematic, you can work around the issue by changing the default excludes in the settings script:

settings.gradle.kts

    import org.apache.tools.ant.DirectoryScanner

    DirectoryScanner.removeDefaultExclude("**/.git")
    DirectoryScanner.removeDefaultExclude("**/.git/**")

settings.gradle

    import org.apache.tools.ant.DirectoryScanner

    DirectoryScanner.removeDefaultExclude('**/.git')
    DirectoryScanner.removeDefaultExclude('**/.git/**')

| Gradle does not support changing default excludes during the execution phase.

You can do many of the same things with file trees that you can with file collections:

- iterate over them (depth first)

- filter them (using FileTree.matching(org.gradle.api.Action) and Ant-style patterns)

- merge them

You can also traverse file trees using the FileTree.visit(org.gradle.api.Action) method. All of these techniques are demonstrated in the following example:

build.gradle.kts

    // Iterate over the contents of a tree
    tree.forEach{ file: File ->
        println(file)
    }

    // Filter a tree
    val filtered: FileTree = tree.matching {
        include("org/gradle/api/**")
    }

    // Add trees together
    val sum: FileTree = tree + fileTree("src/test")

    // Visit the elements of the tree
    tree.visit {
        println("${this.relativePath} => ${this.file}")
    }

build.gradle

    // Iterate over the contents of a tree
    tree.each {File file ->
        println file
    }

    // Filter a tree
    FileTree filtered = tree.matching {
        include 'org/gradle/api/**'
    }

    // Add trees together
    FileTree sum = tree + fileTree(dir: 'src/test')

    // Visit the elements of the tree
    tree.visit {element ->
        println "$element.relativePath => $element.file"
    }

## Copying files

Copying files in Gradle primarily uses `CopySpec`, a mechanism that makes it easy to manage resources such as source code, configuration files, and other assets in your project build process.

### Understanding `CopySpec`

`CopySpec` is a copy specification that allows you to define what files to copy, where to copy them from, and where to copy them. It provides a flexible and expressive way to specify complex file copying operations, including filtering files based on patterns, renaming files, and including/excluding files based on various criteria.

`CopySpec` instances are used in the `Copy` task to specify the files and directories to be copied.

`CopySpec` has two important attributes:

1. It is independent of tasks, allowing you to share copy specs within a build.

2. It is hierarchical, providing fine-grained control within the overall copy specification.

#### 1. Sharing copy specs

Consider a build with several tasks that copy a project’s static website resources or add them to an archive. One task might copy the resources to a folder for a local HTTP server, and another might package them into a distribution. You could manually specify the file locations and appropriate inclusions each time they are needed, but human error is more likely to creep in, resulting in inconsistencies between tasks.

One solution is the Project.copySpec(org.gradle.api.Action) method. This allows you to create a copy spec outside a task, which can then be attached to an appropriate task using the CopySpec.with(org.gradle.api.file.CopySpec…​) method. The following example demonstrates how this is done:

build.gradle.kts

    val webAssetsSpec: CopySpec = copySpec {
        from("src/main/webapp")
        include("**/*.html", "**/*.png", "**/*.jpg")
        rename("(.+)-staging(.+)", "$1$2")
    }

    tasks.register<Copy>("copyAssets") {
        into(layout.buildDirectory.dir("inPlaceApp"))
        with(webAssetsSpec)
    }

    tasks.register<Zip>("distApp") {
        archiveFileName = "my-app-dist.zip"
        destinationDirectory = layout.buildDirectory.dir("dists")

        from(appClasses)
        with(webAssetsSpec)
    }

build.gradle

    CopySpec webAssetsSpec = copySpec {
        from 'src/main/webapp'
        include '**/*.html', '**/*.png', '**/*.jpg'
        rename '(.+)-staging(.+)', '$1$2'
    }

    tasks.register('copyAssets', Copy) {
        into layout.buildDirectory.dir("inPlaceApp")
        with webAssetsSpec
    }

    tasks.register('distApp', Zip) {
        archiveFileName = 'my-app-dist.zip'
        destinationDirectory = layout.buildDirectory.dir('dists')

        from appClasses
        with webAssetsSpec
    }

Both the `copyAssets` and `distApp` tasks will process the static resources under `src/main/webapp`, as specified by `webAssetsSpec`.

| The configuration defined by `webAssetsSpec` will not apply to the app classes included by the `distApp` task. That’s because `from appClasses` is its own child specification independent of `with webAssetsSpec`. This can be confusing, so it’s probably best to treat `with()` as an extra `from()` specification in the task. Hence, it doesn’t make sense to define a standalone copy spec without at least one `from()` defined.

Suppose you encounter a scenario in which you want to apply the same copy configuration to different sets of files. In that case, you can share the configuration block directly without using `copySpec()`. Here’s an example that has two independent tasks that happen to want to process image files only:

build.gradle.kts

    val webAssetPatterns = Action<CopySpec> {
        include("**/*.html", "**/*.png", "**/*.jpg")
    }

    tasks.register<Copy>("copyAppAssets") {
        into(layout.buildDirectory.dir("inPlaceApp"))
        from("src/main/webapp", webAssetPatterns)
    }

    tasks.register<Zip>("archiveDistAssets") {
        archiveFileName = "distribution-assets.zip"
        destinationDirectory = layout.buildDirectory.dir("dists")

        from("distResources", webAssetPatterns)
    }

build.gradle

    def webAssetPatterns = {
        include '**/*.html', '**/*.png', '**/*.jpg'
    }

    tasks.register('copyAppAssets', Copy) {
        into layout.buildDirectory.dir("inPlaceApp")
        from 'src/main/webapp', webAssetPatterns
    }

    tasks.register('archiveDistAssets', Zip) {
        archiveFileName = 'distribution-assets.zip'
        destinationDirectory = layout.buildDirectory.dir('dists')

        from 'distResources', webAssetPatterns
    }

In this case, we assign the copy configuration to its own variable and apply it to whatever `from()` specification we want. This doesn’t just work for inclusions but also exclusions, file renaming, and file content filtering.

#### 2. Using child specifications

If you only use a single copy spec, the file filtering and renaming will apply to all files copied. Sometimes, this is what you want, but not always. Consider the following example that copies files into a directory structure that a Java Servlet container can use to deliver a website:

This is not a straightforward copy as the `WEB-INF` directory and its subdirectories don’t exist within the project, so they must be created during the copy. In addition, we only want HTML and image files going directly into the root folder — `build/explodedWar` — and only JavaScript files going into the `js` directory. We need separate filter patterns for those two sets of files.

The solution is to use child specifications, which can be applied to both `from()` and `into()` declarations. The following task definition does the necessary work:

build.gradle.kts

    tasks.register<Copy>("nestedSpecs") {
        into(layout.buildDirectory.dir("explodedWar"))
        exclude("**/*staging*")
        from("src/dist") {
            include("**/*.html", "**/*.png", "**/*.jpg")
        }
        from(sourceSets.main.get().output) {
            into("WEB-INF/classes")
        }
        into("WEB-INF/lib") {
            from(configurations.runtimeClasspath)
        }
    }

build.gradle

    tasks.register('nestedSpecs', Copy) {
        into layout.buildDirectory.dir("explodedWar")
        exclude '**/*staging*'
        from('src/dist') {
            include '**/*.html', '**/*.png', '**/*.jpg'
        }
        from(sourceSets.main.output) {
            into 'WEB-INF/classes'
        }
        into('WEB-INF/lib') {
            from configurations.runtimeClasspath
        }
    }

Notice how the `src/dist` configuration has a nested inclusion specification; it is the child copy spec. You can, of course, add content filtering and renaming here as required. A child copy spec is still a copy spec.

The above example also demonstrates how you can copy files into a subdirectory of the destination either by using a child `into()` on a `from()` or a child `from()` on an `into()`. Both approaches are acceptable, but you should create and follow a convention to ensure consistency across your build files.

| Don’t get your `into()` specifications mixed up. For a normal copy, one to the filesystem rather than an archive, there should always be one "root" `into()` that specifies the overall destination directory of the copy. Any other `into()` should have a child spec attached, and its path will be relative to the root `into()`.

One final thing to be aware of is that a child copy spec inherits its destination path, include patterns, exclude patterns, copy actions, name mappings, and filters from its parent. So, be careful where you place your configuration.

### Using the `Sync` task

The Sync task, which extends the `Copy` task, copies the source files into the destination directory and then removes any files from the destination directory which it did not copy. It synchronizes the contents of a directory with its source.

This can be useful for doing things such as installing your application, creating an exploded copy of your archives, or maintaining a copy of the project’s dependencies.

Here is an example that maintains a copy of the project’s runtime dependencies in the `build/libs` directory:

build.gradle.kts

    tasks.register<Sync>("libs") {
        from(configurations["runtime"])
        into(layout.buildDirectory.dir("libs"))
    }

build.gradle

    tasks.register('libs', Sync) {
        from configurations.runtime
        into layout.buildDirectory.dir('libs')
    }

You can also perform the same function in your own tasks with the Project.sync(org.gradle.api.Action) method.

### Using the `Copy` task

You can copy a file by creating an instance of Gradle’s builtin Copy task and configuring it with the location of the file and where you want to put it.

This example mimics copying a generated report into a directory that will be packed into an archive, such as a ZIP or TAR:

build.gradle.kts

    tasks.register<Copy>("copyReport") {
        from(layout.buildDirectory.file("reports/my-report.pdf"))
        into(layout.buildDirectory.dir("toArchive"))
    }

build.gradle

    tasks.register('copyReport', Copy) {
        from layout.buildDirectory.file("reports/my-report.pdf")
        into layout.buildDirectory.dir("toArchive")
    }

The file and directory paths are then used to specify what file to copy using Copy.from(java.lang.Object…​) and which directory to copy it to using Copy.into(java.lang.Object).

Although hard-coded paths make for simple examples, they make the build brittle. Using a reliable, single source of truth, such as a task or shared project property, is better. In the following modified example, we use a report task defined elsewhere that has the report’s location stored in its `outputFile` property:

build.gradle.kts

    tasks.register<Copy>("copyReport2") {
        from(myReportTask.flatMap { it.outputFile })
        into(archiveReportsTask.flatMap { it.dirToArchive })
    }

build.gradle

    tasks.register('copyReport2', Copy) {
        from myReportTask.outputFile
        into archiveReportsTask.dirToArchive
    }

We have also assumed that the reports will be archived by `archiveReportsTask`, which provides us with the directory that will be archived and hence where we want to put the copies of the reports.

### Copying multiple files

You can extend the previous examples to multiple files very easily by providing multiple arguments to `from()`:

build.gradle.kts

    tasks.register<Copy>("copyReportsForArchiving") {
        from(layout.buildDirectory.file("reports/my-report.pdf"), layout.projectDirectory.file("src/docs/manual.pdf"))
        into(layout.buildDirectory.dir("toArchive"))
    }

build.gradle

    tasks.register('copyReportsForArchiving', Copy) {
        from layout.buildDirectory.file("reports/my-report.pdf"), layout.projectDirectory.file("src/docs/manual.pdf")
        into layout.buildDirectory.dir("toArchive")
    }

Two files are now copied into the archive directory.

You can also use multiple `from()` statements to do the same thing, as shown in the first example of the section File copying in depth.

But what if you want to copy all the PDFs in a directory without specifying each one? To do this, attach inclusion and/or exclusion patterns to the copy specification. Here, we use a string pattern to include PDFs only:

build.gradle.kts

    tasks.register<Copy>("copyPdfReportsForArchiving") {
        from(layout.buildDirectory.dir("reports"))
        include("*.pdf")
        into(layout.buildDirectory.dir("toArchive"))
    }

build.gradle

    tasks.register('copyPdfReportsForArchiving', Copy) {
        from layout.buildDirectory.dir("reports")
        include "*.pdf"
        into layout.buildDirectory.dir("toArchive")
    }

One thing to note, as demonstrated in the following diagram, is that only the PDFs that reside directly in the `reports` directory are copied:

You can include files in subdirectories by using an Ant-style glob pattern (`**/*`), as done in this updated example:

build.gradle.kts

    tasks.register<Copy>("copyAllPdfReportsForArchiving") {
        from(layout.buildDirectory.dir("reports"))
        include("**/*.pdf")
        into(layout.buildDirectory.dir("toArchive"))
    }

build.gradle

    tasks.register('copyAllPdfReportsForArchiving', Copy) {
        from layout.buildDirectory.dir("reports")
        include "**/*.pdf"
        into layout.buildDirectory.dir("toArchive")
    }

This task has the following effect:

Remember that a deep filter like this has the side effect of copying the directory structure below `reports` and the files. If you want to copy the files without the directory structure, you must use an explicit `fileTree(dir) { includes }.files` expression.

### Copying directory hierarchies

You may need to copy files as well as the directory structure in which they reside. This is the default behavior when you specify a directory as the `from()` argument, as demonstrated by the following example that copies everything in the `reports` directory, including all its subdirectories, to the destination:

build.gradle.kts

    tasks.register<Copy>("copyReportsDirForArchiving") {
        from(layout.buildDirectory.dir("reports"))
        into(layout.buildDirectory.dir("toArchive"))
    }

build.gradle

    tasks.register('copyReportsDirForArchiving', Copy) {
        from layout.buildDirectory.dir("reports")
        into layout.buildDirectory.dir("toArchive")
    }

The key aspect that users need help with is controlling how much of the directory structure goes to the destination. In the above example, do you get a `toArchive/reports` directory, or does everything in `reports` go straight into `toArchive`? The answer is the latter. If a directory is part of the `from()` path, then it won’t appear in the destination.

So how do you ensure that `reports` itself is copied across, but not any other directory in `${layout.buildDirectory}`? The answer is to add it as an include pattern:

build.gradle.kts

    tasks.register<Copy>("copyReportsDirForArchiving2") {
        from(layout.buildDirectory) {
            include("reports/**")
        }
        into(layout.buildDirectory.dir("toArchive"))
    }

build.gradle

    tasks.register('copyReportsDirForArchiving2', Copy) {
        from(layout.buildDirectory) {
            include "reports/**"
        }
        into layout.buildDirectory.dir("toArchive")
    }

You’ll get the same behavior as before except with one extra directory level in the destination, i.e., `toArchive/reports`.

One thing to note is how the `include()` directive applies only to the `from()`, whereas the directive in the previous section applied to the whole task. These different levels of granularity in the copy specification allow you to handle most requirements that you will come across easily.

### Understanding file copying

The basic process of copying files in Gradle is a simple one:

- Define a task of type Copy

- Specify which files (and potentially directories) to copy

- Specify a destination for the copied files

But this apparent simplicity hides a rich API that allows fine-grained control of which files are copied, where they go, and what happens to them as they are copied — renaming of the files and token substitution of file content are both possibilities, for example.

Let’s start with the last two items on the list, which involve `CopySpec`. The CopySpec interface, which the `Copy` task implements, offers:

- A CopySpec.from(java.lang.Object…​) method to define what to copy

- An CopySpec.into(java.lang.Object) method to define the destination

`CopySpec` has several additional methods that allow you to control the copying process, but these two are the only required ones. `into()` is straightforward, requiring a directory path as its argument in any form supported by the Project.file(java.lang.Object) method. The `from()` configuration is far more flexible.

Not only does `from()` accept multiple arguments, it also allows several different types of argument. For example, some of the most common types are:

- A `String` — treated as a file path or, if it starts with "file://", a file URI

- A `File` — used as a file path

- A `FileCollection` or `FileTree` — all files in the collection are included in the copy

- A task — the files or directories that form a task’s defined outputs are included

In fact, `from()` accepts all the same arguments as Project.files(java.lang.Object…​) so see that method for a more detailed list of acceptable types.

Something else to consider is what type of thing a file path refers to:

- A file — the file is copied as is

- A directory — this is effectively treated as a file tree: everything in it, including subdirectories, is copied. However, the directory itself is not included in the copy.

- A non-existent file — the path is ignored

Here is an example that uses multiple `from()` specifications, each with a different argument type. You will probably also notice that `into()` is configured lazily using a closure (in Groovy) or a Provider (in Kotlin) — a technique that also works with `from()`:

build.gradle.kts

    tasks.register<Copy>("anotherCopyTask") {
        // Copy everything under src/main/webapp
        from("src/main/webapp")
        // Copy a single file
        from("src/staging/index.html")
        // Copy the output of a task
        from(copyTask)
        // Copy the output of a task using Task outputs explicitly.
        from(tasks["copyTaskWithPatterns"].outputs)
        // Copy the contents of a Zip file
        from(zipTree("src/main/assets.zip"))
        // Determine the destination directory later
        into({ getDestDir() })
    }

build.gradle

    tasks.register('anotherCopyTask', Copy) {
        // Copy everything under src/main/webapp
        from 'src/main/webapp'
        // Copy a single file
        from 'src/staging/index.html'
        // Copy the output of a task
        from copyTask
        // Copy the output of a task using Task outputs explicitly.
        from copyTaskWithPatterns.outputs
        // Copy the contents of a Zip file
        from zipTree('src/main/assets.zip')
        // Determine the destination directory later
        into { getDestDir() }
    }

Note that the lazy configuration of `into()` is different from a child specification, even though the syntax is similar. Keep an eye on the number of arguments to distinguish between them.

### Copying files in your own tasks

| Using the `Project.copy` method at execution time, as described here, is not compatible with the configuration cache. A possible solution is to implement the task as a proper class and use FileSystemOperations.copy method instead, as described in the configuration cache chapter.

Occasionally, you want to copy files or directories as part of a task. For example, a custom archiving task based on an unsupported archive format might want to copy files to a temporary directory before they are archived. You still want to take advantage of Gradle’s copy API without introducing an extra `Copy` task.

The solution is to use the Project.copy(org.gradle.api.Action) method. Configuring it with a copy spec works like the `Copy` task. Here’s a trivial example:

build.gradle.kts

    tasks.register("copyMethod") {
        doLast {
            copy {
                from("src/main/webapp")
                into(layout.buildDirectory.dir("explodedWar"))
                include("**/*.html")
                include("**/*.jsp")
            }
        }
    }

build.gradle

    tasks.register('copyMethod') {
        doLast {
            copy {
                from 'src/main/webapp'
                into layout.buildDirectory.dir('explodedWar')
                include '**/*.html'
                include '**/*.jsp'
            }
        }
    }

The above example demonstrates the basic syntax and also highlights two major limitations of using the `copy()` method:

1. The `copy()` method is not incremental. The example’s `copyMethod` task will always execute because it has no information about what files make up the task’s inputs. You have to define the task inputs and outputs manually.

2. Using a task as a copy source, i.e., as an argument to `from()`, won’t create an automatic task dependency between your task and that copy source. As such, if you use the `copy()` method as part of a task action, you must explicitly declare all inputs and outputs to get the correct behavior.

The following example shows how to work around these limitations using the dynamic API for task inputs and outputs:

build.gradle.kts

    tasks.register("copyMethodWithExplicitDependencies") {
        // up-to-date check for inputs, plus add copyTask as dependency
        inputs.files(copyTask)
            .withPropertyName("inputs")
            .withPathSensitivity(PathSensitivity.RELATIVE)
        outputs.dir("some-dir") // up-to-date check for outputs
            .withPropertyName("outputDir")
        doLast {
            copy {
                // Copy the output of copyTask
                from(copyTask)
                into("some-dir")
            }
        }
    }

build.gradle

    tasks.register('copyMethodWithExplicitDependencies') {
        // up-to-date check for inputs, plus add copyTask as dependency
        inputs.files(copyTask)
            .withPropertyName("inputs")
            .withPathSensitivity(PathSensitivity.RELATIVE)
        outputs.dir('some-dir') // up-to-date check for outputs
            .withPropertyName("outputDir")
        doLast {
            copy {
                // Copy the output of copyTask
                from copyTask
                into 'some-dir'
            }
        }
    }

These limitations make it preferable to use the `Copy` task wherever possible because of its built-in support for incremental building and task dependency inference. That is why the `copy()` method is intended for use by custom tasks that need to copy files as part of their function. Custom tasks that use the `copy()` method should declare the necessary inputs and outputs relevant to the copy action.

## Renaming files

Renaming files in Gradle can be done using the `CopySpec` API, which provides methods for renaming files as they are copied.

### Using `Copy.rename()`

If the files used and generated by your builds sometimes don’t have names that suit, you can rename those files as you copy them. Gradle allows you to do this as part of a copy specification using the `rename()` configuration.

The following example removes the "-staging" marker from the names of any files that have it:

build.gradle.kts

    tasks.register<Copy>("copyFromStaging") {
        from("src/main/webapp")
        into(layout.buildDirectory.dir("explodedWar"))

        rename("(.+)-staging(.+)", "$1$2")
    }

build.gradle

    tasks.register('copyFromStaging', Copy) {
        from "src/main/webapp"
        into layout.buildDirectory.dir('explodedWar')

        rename '(.+)-staging(.+)', '$1$2'
    }

As in the above example, you can use regular expressions for this or closures that use more complex logic to determine the target filename. For example, the following task truncates filenames:

build.gradle.kts

    tasks.register<Copy>("copyWithTruncate") {
        from(layout.buildDirectory.dir("reports"))
        rename { filename: String ->
            if (filename.length > 10) {
                filename.slice(0..7) + "~" + filename.length
            }
            else filename
        }
        into(layout.buildDirectory.dir("toArchive"))
    }

build.gradle

    tasks.register('copyWithTruncate', Copy) {
        from layout.buildDirectory.dir("reports")
        rename { String filename ->
            if (filename.size() > 10) {
                return filename[0..7] + "~" + filename.size()
            }
            else return filename
        }
        into layout.buildDirectory.dir("toArchive")
    }

As with filtering, you can also rename a subset of files by configuring it as part of a child specification on a `from()`.

### Using `Copyspec.rename{}`

The example of how to rename files on copy gives you most of the information you need to perform this operation. It demonstrates the two options for renaming:

1. Using a regular expression

2. Using a closure

Regular expressions are a flexible approach to renaming, particularly as Gradle supports regex groups that allow you to remove and replace parts of the source filename. The following example shows how you can remove the string "-staging" from any filename that contains it using a simple regular expression:

build.gradle.kts

    tasks.register<Copy>("rename") {
        from("src/main/webapp")
        into(layout.buildDirectory.dir("explodedWar"))
        // Use a regular expression to map the file name
        rename("(.+)-staging(.+)", "$1$2")
        rename("(.+)-staging(.+)".toRegex().pattern, "$1$2")
        // Use a closure to convert all file names to upper case
        rename { fileName: String ->
            fileName.uppercase()
        }
    }

build.gradle

    tasks.register('rename', Copy) {
        from 'src/main/webapp'
        into layout.buildDirectory.dir('explodedWar')
        // Use a regular expression to map the file name
        rename '(.+)-staging(.+)', '$1$2'
        rename(/(.+)-staging(.+)/, '$1$2')
        // Use a closure to convert all file names to upper case
        rename { String fileName ->
            fileName.toUpperCase()
        }
    }

You can use any regular expression supported by the Java `Pattern` class and the substitution string. The second argument of `rename()` works on the same principles as the `Matcher.appendReplacement()` method.

Regular expressions in Groovy build scripts

There are two common issues people come across when using regular expressions in this context:

1. If you use a slashy string (those delimited by '/') for the first argument, you must include the parentheses for `rename()` as shown in the above example.

2. It’s safest to use single quotes for the second argument, otherwise you need to escape the '$' in group substitutions, i.e. `"\$1\$2"`.

The first is a minor inconvenience, but slashy strings have the advantage that you don’t have to escape backslash ('\') characters in the regular expression. The second issue stems from Groovy’s support for embedded expressions using `${ }` syntax in double-quoted and slashy strings.

The closure syntax for `rename()` is straightforward and can be used for any requirements that simple regular expressions can’t handle. You’re given a file’s name, and you return a new name for that file or `null` if you don’t want to change the name. Be aware that the closure will be executed for every file copied, so try to avoid expensive operations where possible.

## Filtering files

Filtering files in Gradle involves selectively including or excluding files based on certain criteria.

### Using `CopySpec.include()` and `CopySpec.exclude()`

You can apply filtering in any copy specification through the CopySpec.include(java.lang.String…​) and CopySpec.exclude(java.lang.String…​) methods.

These methods are typically used with Ant-style include or exclude patterns, as described in PatternFilterable.

You can also perform more complex logic by using a closure that takes a FileTreeElement and returns `true` if the file should be included or `false` otherwise. The following example demonstrates both forms, ensuring that only `.html` and `.jsp` files are copied, except for those `.html` files with the word "DRAFT" in their content:

build.gradle.kts

    tasks.register<Copy>("copyTaskWithPatterns") {
        from("src/main/webapp")
        into(layout.buildDirectory.dir("explodedWar"))
        include("**/*.html")
        include("**/*.jsp")
        exclude { details: FileTreeElement ->
            details.file.name.endsWith(".html") &&
                details.file.readText().contains("DRAFT")
        }
    }

build.gradle

    tasks.register('copyTaskWithPatterns', Copy) {
        from 'src/main/webapp'
        into layout.buildDirectory.dir('explodedWar')
        include '**/*.html'
        include '**/*.jsp'
        exclude { FileTreeElement details ->
            details.file.name.endsWith('.html') &&
                details.file.text.contains('DRAFT')
        }
    }

A question you may ask yourself at this point is what happens when inclusion and exclusion patterns overlap? Which pattern wins? Here are the basic rules:

- If there are no explicit inclusions or exclusions, everything is included

- If at least one inclusion is specified, only files and directories matching the patterns are included

- Any exclusion pattern overrides any inclusions, so if a file or directory matches at least one exclusion pattern, it won’t be included, regardless of the inclusion patterns

Bear these rules in mind when creating combined inclusion and exclusion specifications so that you end up with the exact behavior you want.

Note that the inclusions and exclusions in the above example will apply to all `from()` configurations. If you want to apply filtering to a subset of the copied files, you’ll need to use child specifications.

## Filtering file content

Filtering file content in Gradle involves replacing placeholders or tokens in files with dynamic values.

### Using `CopySpec.filter()`

Transforming the content of files while they are being copied involves basic templating that uses token substitution, removal of lines of text, or even more complex filtering using a full-blown template engine.

The following example demonstrates several forms of filtering, including token substitution using the CopySpec.expand(java.util.Map) method and another using CopySpec.filter(java.lang.Class) with an Ant filter:

build.gradle.kts

    import org.apache.tools.ant.filters.FixCrLfFilter
    import org.apache.tools.ant.filters.ReplaceTokens
    tasks.register<Copy>("filter") {
        from("src/main/webapp")
        into(layout.buildDirectory.dir("explodedWar"))
        // Substitute property tokens in files
        expand("copyright" to "2009", "version" to "2.3.1")
        // Use some of the filters provided by Ant
        filter(FixCrLfFilter::class)
        filter(ReplaceTokens::class, "tokens" to mapOf("copyright" to "2009", "version" to "2.3.1"))
        // Use a closure to filter each line
        filter { line: String ->
            "[$line]"
        }
        // Use a closure to remove lines
        filter { line: String ->
            if (line.startsWith('-')) null else line
        }
        filteringCharset = "UTF-8"
    }

build.gradle

    import org.apache.tools.ant.filters.FixCrLfFilter
    import org.apache.tools.ant.filters.ReplaceTokens

    tasks.register('filter', Copy) {
        from 'src/main/webapp'
        into layout.buildDirectory.dir('explodedWar')
        // Substitute property tokens in files
        expand(copyright: '2009', version: '2.3.1')
        // Use some of the filters provided by Ant
        filter(FixCrLfFilter)
        filter(ReplaceTokens, tokens: [copyright: '2009', version: '2.3.1'])
        // Use a closure to filter each line
        filter { String line ->
            "[$line]"
        }
        // Use a closure to remove lines
        filter { String line ->
            line.startsWith('-') ? null : line
        }
        filteringCharset = 'UTF-8'
    }

The `filter()` method has two variants, which behave differently:

- one takes a `FilterReader` and is designed to work with Ant filters, such as `ReplaceTokens`

- one takes a closure or Transformer that defines the transformation for each line of the source file

Note that both variants assume the source files are text-based. When you use the `ReplaceTokens` class with `filter()`, you create a template engine that replaces tokens of the form `@tokenName@` (the Ant-style token) with values you define.

### Using `CopySpec.expand()`

The `expand()` method treats the source files as Groovy templates, which evaluates and expands expressions of the form `${expression}.`

You can pass in property names and values that are then expanded in the source files. `expand()` allows for more than basic token substitution as the embedded expressions are full-blown Groovy expressions.

| Specifying the character set when reading and writing the file is good practice. Otherwise, the transformations won’t work properly for non-ASCII text. You configure the character set with the CopySpec.setFilteringCharset(String) property. If it’s not specified, the JVM default character set is used, which will likely differ from the one you want.

## Setting file permissions

Setting file permissions in Gradle involves specifying the permissions for files or directories created or modified during the build process.

### Using `CopySpec.filePermissions{}`

For any `CopySpec` involved in copying files, may it be the `Copy` task itself, or any child specifications, you can explicitly set the permissions the destination files will have via the CopySpec.filePermissions {} configurations block.

### Using `CopySpec.dirPermissions{}`

You can do the same for directories too, independently of files, via the CopySpec.dirPermissions {} configurations block.

| Not setting permissions explicitly will preserve the permissions of the original files or directories. The exception are archive tasks, see Reproducible archives for details.

Example 1. Setting file permissions for files and directories
build.gradle.kts

    tasks.register<Copy>("permissions") {
        from("src/main/webapp")
        into(layout.buildDirectory.dir("explodedWar"))
        filePermissions {
            user {
                read = true
                execute = true
            }
            other.execute = false
        }
        dirPermissions {
            unix("r-xr-x---")
        }
    }

build.gradle

    tasks.register('permissions', Copy) {
        from 'src/main/webapp'
        into layout.buildDirectory.dir('explodedWar')
        filePermissions {
            user {
                read = true
                execute = true
            }
            other.execute = false
        }
        dirPermissions {
            unix('r-xr-x---')
        }
    }

For a detailed description of file permissions, see FilePermissions and UserClassFilePermissions. For details on the convenience method used in the samples, see ConfigurableFilePermissions.unix(String).

Using empty configuration blocks for file or directory permissions still sets them explicitly, just to fixed default values. Everything inside one of these configuration blocks is relative to the default values. Default permissions differ for files and directories:

- file: read & write for owner, read for group, read for other (`0644`, `rw-r—​r--`)

- directory: read, write & execute for owner, read & execute for group, read & execute for other (`0755`, `rwxr-xr-x`)

It’s also possible to set permissions for a specific file. See the following example:

Example 2. Setting file permissions for a specific file
build.gradle.kts

    tasks.register<Copy>("specificPermissions") {
        from("src/main/webapp")
        into(layout.buildDirectory.dir("explodedWarWithScript"))
        eachFile {
            if (name == "script.sh") {
                permissions {
                    user {
                        execute = true
                    }
                }
            }
        }
    }

build.gradle

    tasks.register("specificPermissions", Copy) {
        from 'src/main/webapp'
        into layout.buildDirectory.dir('explodedWarWithScript')
        eachFile {
            if (name == "script.sh") {
                permissions {
                    user {
                        execute = true
                    }
                }
            }
        }
    }

| Setting file permissions explicitly for a file doesn’t support `UP-TO-DATE` checks, so a task using this setting will always run.

## Moving files and directories

Moving files and directories in Gradle is a straightforward process that can be accomplished using several APIs. When implementing file-moving logic in your build scripts, it’s important to consider file paths, conflicts, and task dependencies.

### Using `File.renameTo()`

`File.renameTo()` is a method in Java (and by extension, in Gradle’s Groovy DSL) used to rename or move a file or directory. When you call `renameTo()` on a `File` object, you provide another `File` object representing the new name or location. If the operation is successful, `renameTo()` returns `true`; otherwise, it returns `false`.

It’s important to note that `renameTo()` has some limitations and platform-specific behavior.

In this example, the `moveFile` task uses the `Copy` task type to specify the source and destination directories. Inside the `doLast` closure, it uses `File.renameTo()` to move the file from the source directory to the destination directory:

build.gradle.kts

    tasks.register("moveFile") {
        doLast {
            val sourceFile = file("source.txt")
            val destFile = file("destination/new_name.txt")

            if (sourceFile.renameTo(destFile)) {
                println("File moved successfully.")
            }
        }
    }

build.gradle

    task moveFile {
        doLast {
            def sourceFile = file('source.txt')
            def destFile = file('destination/new_name.txt')

            if (sourceFile.renameTo(destFile)) {
                println "File moved successfully."
            }
        }
    }

### Using the `Copy` task

In this example, the `moveFile` task copies the file `source.txt` to the destination directory and renames it to `new_name.txt` in the process. This achieves a similar effect to moving a file.

build.gradle.kts

    tasks.register<Copy>("moveFileCopy") {
        from("source.txt")
        into("destination")
        rename { "new_name.txt" }
    }

build.gradle

    task moveFileCopy(type: Copy) {
        from 'source.txt'
        into 'destination'
        rename { fileName ->
            'new_name.txt'
        }
    }

## Deleting files and directories

Deleting files and directories in Gradle involves removing them from the file system.

### Using the `Delete` task

You can easily delete files and directories using the Delete task. You must specify which files and directories to delete in a way supported by the Project.files(java.lang.Object…​) method.

For example, the following task deletes the entire contents of a build’s output directory:

build.gradle.kts

    tasks.register<Delete>("myClean") {
        delete(buildDir)
    }

build.gradle

    tasks.register('myClean', Delete) {
        delete buildDir
    }

If you want more control over which files are deleted, you can’t use inclusions and exclusions the same way you use them for copying files. Instead, you use the built-in filtering mechanisms of `FileCollection` and `FileTree`. The following example does just that to clear out temporary files from a source directory:

build.gradle.kts

    tasks.register<Delete>("cleanTempFiles") {
        delete(fileTree("src").matching {
            include("**/*.tmp")
        })
    }

build.gradle

    tasks.register('cleanTempFiles', Delete) {
        delete fileTree("src").matching {
            include "**/*.tmp"
        }
    }

### Using `Project.delete()`

The Project.delete(org.gradle.api.Action) method can delete files and directories.

This method takes one or more arguments representing the files or directories to be deleted.

For example, the following task deletes the entire contents of a build’s output directory:

build.gradle.kts

    tasks.register<Delete>("myClean") {
        delete(buildDir)
    }

build.gradle

    tasks.register('myClean', Delete) {
        delete buildDir
    }

If you want more control over which files are deleted, you can’t use inclusions and exclusions the same way you use them for copying files. Instead, you use the built-in filtering mechanisms of `FileCollection` and `FileTree`. The following example does just that to clear out temporary files from a source directory:

build.gradle.kts

    tasks.register<Delete>("cleanTempFiles") {
        delete(fileTree("src").matching {
            include("**/*.tmp")
        })
    }

build.gradle

    tasks.register('cleanTempFiles', Delete) {
        delete fileTree("src").matching {
            include "**/*.tmp"
        }
    }

## Creating archives

From the perspective of Gradle, packing files into an archive is effectively a copy in which the destination is the archive file rather than a directory on the file system. Creating archives looks a lot like copying, with all the same features.

### Using the `Zip`, `Tar`, or `Jar` task

The simplest case involves archiving the entire contents of a directory, which this example demonstrates by creating a ZIP of the `toArchive` directory:

build.gradle.kts

    tasks.register<Zip>("packageDistribution") {
        archiveFileName = "my-distribution.zip"
        destinationDirectory = layout.buildDirectory.dir("dist")

        from(layout.buildDirectory.dir("toArchive"))
    }

build.gradle

    tasks.register('packageDistribution', Zip) {
        archiveFileName = "my-distribution.zip"
        destinationDirectory = layout.buildDirectory.dir('dist')

        from layout.buildDirectory.dir("toArchive")
    }

Notice how we specify the destination and name of the archive instead of an `into()`: both are required. You often won’t see them explicitly set because most projects apply the Base Plugin. It provides some conventional values for those properties.

The following example demonstrates this; you can learn more about the conventions in the archive naming section.

Each type of archive has its own task type, the most common ones being Zip, Tar and Jar. They all share most of the configuration options of `Copy`, including filtering and renaming.

One of the most common scenarios involves copying files into specified archive subdirectories. For example, let’s say you want to package all PDFs into a `docs` directory in the archive’s root. This `docs` directory doesn’t exist in the source location, so you must create it as part of the archive. You do this by adding an `into()` declaration for just the PDFs:

build.gradle.kts

    plugins {
        base
    }

    version = "1.0.0"

    tasks.register<Zip>("packageDistribution") {
        from(layout.buildDirectory.dir("toArchive")) {
            exclude("**/*.pdf")
        }

        from(layout.buildDirectory.dir("toArchive")) {
            include("**/*.pdf")
            into("docs")
        }
    }

build.gradle

    plugins {
        id 'base'
    }

    version = "1.0.0"

    tasks.register('packageDistribution', Zip) {
        from(layout.buildDirectory.dir("toArchive")) {
            exclude "**/*.pdf"
        }

        from(layout.buildDirectory.dir("toArchive")) {
            include "**/*.pdf"
            into "docs"
        }
    }

As you can see, you can have multiple `from()` declarations in a copy specification, each with its own configuration. See Using child copy specifications for more information on this feature.

### Understanding archive creation

Archives are essentially self-contained file systems, and Gradle treats them as such. This is why working with archives is similar to working with files and directories.

Out of the box, Gradle supports the creation of ZIP and TAR archives and, by extension, Java’s JAR, WAR, and EAR formats—Java’s archive formats are all ZIPs. Each of these formats has a corresponding task type to create them: Zip, Tar, Jar, War, and Ear. These all work the same way and are based on copy specifications, just like the `Copy` task.

Creating an archive file is essentially a file copy in which the destination is implicit, i.e., the archive file itself. Here is a basic example that specifies the path and name of the target archive file:

build.gradle.kts

    tasks.register<Zip>("packageDistribution") {
        archiveFileName = "my-distribution.zip"
        destinationDirectory = layout.buildDirectory.dir("dist")

        from(layout.buildDirectory.dir("toArchive"))
    }

build.gradle

    tasks.register('packageDistribution', Zip) {
        archiveFileName = "my-distribution.zip"
        destinationDirectory = layout.buildDirectory.dir('dist')

        from layout.buildDirectory.dir("toArchive")
    }

The full power of copy specifications is available to you when creating archives, which means you can do content filtering, file renaming, or anything else covered in the previous section. A common requirement is copying files into subdirectories of the archive that don’t exist in the source folders, something that can be achieved with `into()` child specifications.

Gradle allows you to create as many archive tasks as you want, but it’s worth considering that many convention-based plugins provide their own. For example, the Java plugin adds a `jar` task for packaging a project’s compiled classes and resources in a JAR. Many of these plugins provide sensible conventions for the names of archives and the copy specifications used. We recommend you use these tasks wherever you can rather than overriding them with your own.

### Naming archives

Gradle has several conventions around the naming of archives and where they are created based on the plugins your project uses. The main convention is provided by the Base Plugin, which defaults to creating archives in the `layout.buildDirectory.dir("distributions")` directory and typically uses archive names of the form [projectName]-[version].[type].

The following example comes from a project named `archive-naming`, hence the `myZip` task creates an archive named `archive-naming-1.0.zip`:

build.gradle.kts

    plugins {
        base
    }

    version = "1.0"

    tasks.register<Zip>("myZip") {
        from("somedir")
        val projectDir = layout.projectDirectory.asFile
        doLast {
            println(archiveFileName.get())
            println(destinationDirectory.get().asFile.relativeTo(projectDir))
            println(archiveFile.get().asFile.relativeTo(projectDir))
        }
    }

build.gradle

    plugins {
        id 'base'
    }

    version = 1.0

    tasks.register('myZip', Zip) {
        from 'somedir'
        File projectDir = layout.projectDirectory.asFile
        doLast {
            println archiveFileName.get()
            println projectDir.relativePath(destinationDirectory.get().asFile)
            println projectDir.relativePath(archiveFile.get().asFile)
        }
    }

    $ ./gradlew -q myZip

    archive-naming-1.0.zip
    build/distributions
    build/distributions/archive-naming-1.0.zip

Note that the archive name does not derive from the task’s name that creates it.

If you want to change the name and location of a generated archive file, you can provide values for the corresponding task’s `archiveFileName` and `destinationDirectory` properties. These override any conventions that would otherwise apply.

Alternatively, you can make use of the default archive name pattern provided by AbstractArchiveTask.getArchiveFileName(): [archiveBaseName]-[archiveAppendix]-[archiveVersion]-[archiveClassifier].[archiveExtension]. You can set each of these properties on the task separately. Note that the Base Plugin uses the convention of the project name for archiveBaseName, project version for archiveVersion, and the archive type for archiveExtension. It does not provide values for the other properties.

This example — from the same project as the one above — configures just the `archiveBaseName` property, overriding the default value of the project name:

build.gradle.kts

    tasks.register<Zip>("myCustomZip") {
        archiveBaseName = "customName"
        from("somedir")

        doLast {
            println(archiveFileName.get())
        }
    }

build.gradle

    tasks.register('myCustomZip', Zip) {
        archiveBaseName = 'customName'
        from 'somedir'

        doLast {
            println archiveFileName.get()
        }
    }

    $ ./gradlew -q myCustomZip

    customName-1.0.zip

You can also override the default `archiveBaseName` value for all the archive tasks in your build by configuring the `archivesName` property in the `base` block, as demonstrated by the following example. The block is defined by the `base` plugin and backed by the `BasePluginExtension` class.

build.gradle.kts

    plugins {
        base
    }

    version = "1.0"

    base {
        archivesName = "gradle"
        distsDirectory = layout.buildDirectory.dir("custom-dist")
        libsDirectory = layout.buildDirectory.dir("custom-libs")
    }

    val myZip = tasks.register<Zip>("myZip") {
        from("somedir")
    }

    val myOtherZip = tasks.register<Zip>("myOtherZip") {
        archiveAppendix = "wrapper"
        archiveClassifier = "src"
        from("somedir")
    }

    tasks.register("echoNames") {
        val projectNameString = project.name
        val archiveFileName = myZip.flatMap { it.archiveFileName }
        val myOtherArchiveFileName = myOtherZip.flatMap { it.archiveFileName }
        doLast {
            println("Project name: $projectNameString")
            println(archiveFileName.get())
            println(myOtherArchiveFileName.get())
        }
    }

build.gradle

    plugins {
        id 'base'
    }

    version = 1.0
    base {
        archivesName = "gradle"
        distsDirectory = layout.buildDirectory.dir('custom-dist')
        libsDirectory = layout.buildDirectory.dir('custom-libs')
    }

    def myZip = tasks.register('myZip', Zip) {
        from 'somedir'
    }

    def myOtherZip = tasks.register('myOtherZip', Zip) {
        archiveAppendix = 'wrapper'
        archiveClassifier = 'src'
        from 'somedir'
    }

    tasks.register('echoNames') {
        def projectNameString = project.name
        def archiveFileName = myZip.flatMap { it.archiveFileName }
        def myOtherArchiveFileName = myOtherZip.flatMap { it.archiveFileName }
        doLast {
            println "Project name: $projectNameString"
            println archiveFileName.get()
            println myOtherArchiveFileName.get()
        }
    }

    $ ./gradlew -q echoNames

    Project name: archives-changed-base-name
    gradle-1.0.zip
    gradle-wrapper-1.0-src.zip

You can find all the possible archive task properties in the API documentation for AbstractArchiveTask. Still, we have also summarized the main ones here:

`archiveFileName` — `Property<String>`, default: `archiveBaseName-archiveAppendix-archiveVersion-archiveClassifier.archiveExtension`

  The complete file name of the generated archive. If any of the properties in the default value are empty, their '-' separator is dropped.

`archiveFile` — `Provider<RegularFile>`, read-only, default: `destinationDirectory/archiveFileName`

  The absolute file path of the generated archive.

`destinationDirectory` — `DirectoryProperty`, default: depends on archive type

  The target directory in which to put the generated archive. By default, JARs and WARs go into `layout.buildDirectory.dir("libs")`. ZIPs and TARs go into `layout.buildDirectory.dir("distributions")`.

`archiveBaseName` — `Property<String>`, default: `project.name`

  The base name portion of the archive file name, typically a project name or some other descriptive name for what it contains.

`archiveAppendix` — `Property<String>`, default: `null`

  The appendix portion of the archive file name that comes immediately after the base name. It is typically used to distinguish between different forms of content, such as code and docs, or a minimal distribution versus a full or complete one.

`archiveVersion` — `Property<String>`, default: `project.version`

  The version portion of the archive file name, typically in the form of a normal project or product version.

`archiveClassifier` — `Property<String>`, default: `null`

  The classifier portion of the archive file name. Often used to distinguish between archives that target different platforms.

`archiveExtension` — `Property<String>`, default: depends on archive type and compression type

  The filename extension for the archive. By default, this is set based on the archive task type and the compression type (if you’re creating a TAR). Will be one of: `zip`, `jar`, `war`, `tar`, `tgz` or `tbz2`. You can of course set this to a custom extension if you wish.

### Sharing content between multiple archives

As described in the `CopySpec` section above, you can use the Project.copySpec(org.gradle.api.Action) method to share content between archives.

### Using archives as file trees

An archive is a directory and file hierarchy packed into a single file. In other words, it’s a special case of a file tree, and that’s exactly how Gradle treats archives.

Instead of using the `fileTree()` method, which only works on normal file systems, you use the Project.zipTree(java.lang.Object) and Project.tarTree(java.lang.Object) methods to wrap archive files of the corresponding type (note that JAR, WAR and EAR files are ZIPs). Both methods return `FileTree` instances that you can then use in the same way as normal file trees. For example, you can extract some or all of the files of an archive by copying its contents to some directory on the file system. Or you can merge one archive into another.

Here are some simple examples of creating archive-based file trees:

build.gradle.kts

    // Create a ZIP file tree using path
    val zip: FileTree = zipTree("someFile.zip")

    // Create a TAR file tree using path
    val tar: FileTree = tarTree("someFile.tar")

    // tar tree attempts to guess the compression based on the file extension
    // however if you must specify the compression explicitly you can:
    val someTar: FileTree = tarTree(resources.gzip("someTar.ext"))

build.gradle

    // Create a ZIP file tree using path
    FileTree zip = zipTree('someFile.zip')

    // Create a TAR file tree using path
    FileTree tar = tarTree('someFile.tar')

    //tar tree attempts to guess the compression based on the file extension
    //however if you must specify the compression explicitly you can:
    FileTree someTar = tarTree(resources.gzip('someTar.ext'))

You can see a practical example of extracting an archive file in the unpacking archives section below.

## Reproducible archives

| Starting with Gradle 9, archives are reproducible by default.

It’s desirable to recreate archives exactly the same, byte for byte, on different machines. Building an artifact from source code should produce the same result no matter when and where it is built. This is necessary for projects like reproducible-builds.org.

Reproducing the same byte-for-byte archive generally poses some challenges since the order of the files in an archive can be influenced by the underlying file system. Each time a ZIP, TAR, JAR, WAR or EAR is built from source, the order of the files inside the archive may change. Files on disk can also have different timestamps or permissions depending on the environment.

Starting with Gradle 9.0.0, all AbstractArchiveTask (e.g. `Jar`, `Zip`) tasks produce reproducible archives by default:

- File order in the archive is deterministic

- Files have fixed timestamps (exact values depend on the archive type)

- Directories have fixed permissions set to `0755`

- Files have fixed permissions set to `0644`

You can further customize the directory permissions, file permissions, or file timestamps while keeping the archives reproducible.

### Preserve file system-defined aspects

If desired, you can revert individual aspects of reproducible archives.

build.gradle.kts

    tasks.withType<AbstractArchiveTask>().configureEach {
        // Use file timestamps from the file system
        isPreserveFileTimestamps = true   (1)
        // Make file order based on the file system
        isReproducibleFileOrder = false   (2)
        // Use permissions from the file system
        useFileSystemPermissions()        (3)
    }

build.gradle

    tasks.withType(AbstractArchiveTask).configureEach {
        // Use file timestamps from the file system
        preserveFileTimestamps = true  (1)
        // Make file order based on the file system
        reproducibleFileOrder = false  (2)
        // Use permissions from the file system
        useFileSystemPermissions()     (3)
    }

1 | Set `isPreserveFileTimestamps` to `true` to use timestamp values from the file system.
2 | Set `isReproducibleFileOrder` to `false` to preserve the file order based on the file system.
3 | Call `useFileSystemPermissions()` to preserve the permissions from the file system.

In case the file system is already the source of truth for the permissions, e.g., via the version control tools, you can preserve the permissions across all archives tasks by configuring a property:

gradle.properties

    org.gradle.archives.use-file-system-permissions=true

## Permissions for files inside archives

Setting file permissions in Gradle archives is done in the same way as when copying file, see Setting file permissions for details. The main difference is that archive tasks by default set the permissions of files and directories in the archive to fixed values, which are `0644` for files and `0755` for directories for reproducibility.

We recognize two main cases, where users may want to change the permissions of files and directories in archives:

1. Case where the file system is already the source of truth for the permissions. For such configuration see Preserve file system-defined aspects section.

2. Case where the permissions of files and directories in the archive should be set to specific values, for example to set the executable bit on a script file. For such configuration see Setting file permissions section.

## Timestamp for files inside archives Incubating

The `reproducibleFileTimestamp` property allows you to set a custom timestamp for every entry in the archive. Note that the `isPreserveFileTimestamps` property must be `false` (which is the default value) to use a custom timestamp, otherwise the build fails.

The timestamp is specified in milliseconds since the Unix epoch. It can be used to complement reproducibility by using a meaningful, verifiable timestamp rather than the default.

For example, the `SOURCE_DATE_EPOCH` environment variable can be set to the Unix timestamp from the most recent git commit using `git log -1 --format=%ct`, and then used in the Gradle build:

build.gradle.kts

    // import java.time.Instant

    tasks.withType<AbstractArchiveTask>().configureEach {
        reproducibleFileTimestamp = providers.environmentVariable("SOURCE_DATE_EPOCH").map {
            Instant.ofEpochSecond(it.toLong()).toEpochMilli()
        }
    }

build.gradle

    tasks.withType(AbstractArchiveTask).configureEach {
        reproducibleFileTimestamp = providers.environmentVariable('SOURCE_DATE_EPOCH').map {
            java.time.Instant.ofEpochSecond(it.toLong()).toEpochMilli()
        }
    }

| Timestamp resolution and range vary by archive format. The exact value stored in the archive file may have lower resolution than the `reproducibleFileTimestamp` property. Each format also has a minimum supported timestamp: 1980-02-01 00:00 for ZIP archives and 1970-01-02T00:00:00Z for TAR archives. Smaller values are raised to the minimum. ZIP archives also have a maximum supported timestamp of 2097-11-01T00:00:00Z; the build fails for larger values.

## Unpacking archives

Archives are effectively self-contained file systems, so unpacking them is a case of copying the files from that file system onto the local file system — or even into another archive. Gradle enables this by providing some wrapper functions that make archives available as hierarchical collections of files (file trees).

### Using `Project.zipTree` and `Project.tarTree`

The two functions of interest are Project.zipTree(java.lang.Object) and Project.tarTree(java.lang.Object), which produce a FileTree from a corresponding archive file.

That file tree can then be used in a `from()` specification, like so:

build.gradle.kts

    tasks.register<Copy>("unpackFiles") {
        from(zipTree("src/resources/thirdPartyResources.zip"))
        into(layout.buildDirectory.dir("resources"))
    }

build.gradle

    tasks.register('unpackFiles', Copy) {
        from zipTree("src/resources/thirdPartyResources.zip")
        into layout.buildDirectory.dir("resources")
    }

As with a normal copy, you can control which files are unpacked via filters and even rename files as they are unpacked.

More advanced processing can be handled by the eachFile() method. For example, you might need to extract different subtrees of the archive into different paths within the destination directory. The following sample uses the method to extract the files within the archive’s `libs` directory into the root destination directory, rather than into a `libs` subdirectory:

build.gradle.kts

    tasks.register<Copy>("unpackLibsDirectory") {
        from(zipTree("src/resources/thirdPartyResources.zip")) {
            include("libs/**")  (1)
            eachFile {
                relativePath = RelativePath(true, *relativePath.segments.drop(1).toTypedArray())  (2)
            }
            includeEmptyDirs = false  (3)
        }
        into(layout.buildDirectory.dir("resources"))
    }

build.gradle

    tasks.register('unpackLibsDirectory', Copy) {
        from(zipTree("src/resources/thirdPartyResources.zip")) {
            include "libs/**"  (1)
            eachFile { fcd ->
                fcd.relativePath = new RelativePath(true, fcd.relativePath.segments.drop(1))  (2)
            }
            includeEmptyDirs = false  (3)
        }
        into layout.buildDirectory.dir("resources")
    }

1 | Extracts only the subset of files that reside in the `libs` directory
2 | Remaps the path of the extracting files into the destination directory by dropping the `libs` segment from the file path
3 | Ignores the empty directories resulting from the remapping, see Caution note below

| You can not change the destination path of empty directories with this technique. You can learn more in this issue.

If you’re a Java developer wondering why there is no `jarTree()` method, that’s because `zipTree()` works perfectly well for JARs, WARs, and EARs.

## Creating "uber" or "fat" JARs

In Java, applications and their dependencies were typically packaged as separate JARs within a single distribution archive. That still happens, but another approach that is now common is placing the classes and resources of the dependencies directly into the application JAR, creating what is known as an Uber or fat JAR.

Creating "uber" or "fat" JARs in Gradle involves packaging all dependencies into a single JAR file, making it easier to distribute and run the application.

### Using the Shadow Plugin

Gradle does not have full built-in support for creating uber JARs, but you can use third-party plugins like the Shadow plugin (`com.gradleup.shadow`) to achieve this. This plugin packages your project classes and dependencies into a single JAR file.

### Using `Project.zipTree()` and the `Jar` task

To copy the contents of other JAR files into the application JAR, use the Project.zipTree(java.lang.Object) method and the Jar task. This is demonstrated by the `uberJar` task in the following example:

build.gradle.kts

    plugins {
        java
    }

    version = "1.0.0"

    repositories {
        mavenCentral()
    }

    dependencies {
        implementation("commons-io:commons-io:2.6")
    }

    tasks.register<Jar>("uberJar") {
        archiveClassifier = "uber"

        from(sourceSets.main.get().output)

        dependsOn(configurations.runtimeClasspath)
        from({
            configurations.runtimeClasspath.get().filter { it.name.endsWith("jar") }.map { zipTree(it) }
        })
    }

build.gradle

    plugins {
        id 'java'
    }

    version = '1.0.0'

    repositories {
        mavenCentral()
    }

    dependencies {
        implementation 'commons-io:commons-io:2.6'
    }

    tasks.register('uberJar', Jar) {
        archiveClassifier = 'uber'

        from sourceSets.main.output

        dependsOn configurations.runtimeClasspath
        from {
            configurations.runtimeClasspath.findAll { it.name.endsWith('jar') }.collect { zipTree(it) }
        }
    }

In this case, we’re taking the runtime dependencies of the project — `configurations.runtimeClasspath.files` — and wrapping each of the JAR files with the `zipTree()` method. The result is a collection of ZIP file trees, the contents of which are copied into the uber JAR alongside the application classes.

## Creating directories

Many tasks need to create directories to store the files they generate, which is why Gradle automatically manages this aspect of tasks when they explicitly define file and directory outputs. All core Gradle tasks ensure that any output directories they need are created, if necessary, using this mechanism.

### Using `File.mkdirs` and `Files.createDirectories`

In cases where you need to create a directory manually, you can use the standard `Files.createDirectories` or `File.mkdirs` methods from within your build scripts or custom task implementations.

Here is a simple example that creates a single `images` directory in the project folder:

build.gradle.kts

    tasks.register("ensureDirectory") {
        // Store target directory into a variable to avoid project reference in the configuration cache
        val directory = file("images")

        doLast {
            Files.createDirectories(directory.toPath())
        }
    }

build.gradle

    tasks.register('ensureDirectory') {
        // Store target directory into a variable to avoid project reference in the configuration cache
        def directory = file("images")

        doLast {
            Files.createDirectories(directory.toPath())
        }
    }

As described in the Apache Ant manual, the `mkdir` task will automatically create all necessary directories in the given path. It will do nothing if the directory already exists.

### Using `Project.mkdir`

You can create directories in Gradle using the `mkdir` method, which is available in the `Project` object. This method takes a `File` object or a `String` representing the path of the directory to be created:

build.gradle.kts

    tasks.register("createDirs") {
        doLast {
            mkdir("src/main/resources")
            mkdir(file("build/generated"))

            // Create multiple dirs
            mkdir(files("src/main/resources", "src/test/resources"))

            // Check dir existence
            val dir = file("src/main/resources")
            if (!dir.exists()) {
                mkdir(dir)
            }
        }
    }

build.gradle

    tasks.register('createDirs') {
        doLast {
            mkdir 'src/main/resources'
            mkdir file('build/generated')

            // Create multiple dirs
            mkdir files(['src/main/resources', 'src/test/resources'])

            // Check dir existence
            def dir = file('src/main/resources')
            if (!dir.exists()) {
                mkdir dir
            }
        }
    }

## Installing executables

When you are building a standalone executable, you may want to install this file on your system, so it ends up in your path.

### Using the `Copy` task

You can use a `Copy` task to install the executable into shared directories like `/usr/local/bin`. The installation directory probably contains many other executables, some of which may even be unreadable by Gradle. To support the unreadable files in the `Copy` task’s destination directory and to avoid time consuming up-to-date checks, you can use Task.doNotTrackState():

build.gradle.kts

    tasks.register<Copy>("installExecutable") {
        from("build/my-binary")
        into("/usr/local/bin")
        doNotTrackState("Installation directory contains unrelated files")
    }

build.gradle

    tasks.register("installExecutable", Copy) {
        from "build/my-binary"
        into "/usr/local/bin"
        doNotTrackState("Installation directory contains unrelated files")
    }

## Deploying single files into application servers

Deploying a single file to an application server typically refers to the process of transferring a packaged application artifact, such as a WAR file, to the application server’s deployment directory.

### Using the `Copy` task

When working with application servers, you can use a `Copy` task to deploy the application archive (e.g. a WAR file). Since you are deploying a single file, the destination directory of the `Copy` is the whole deployment directory. The deployment directory sometimes does contain unreadable files like named pipes, so Gradle may have problems doing up-to-date checks. In order to support this use-case, you can use Task.doNotTrackState():

build.gradle.kts

    plugins {
        war
    }

    tasks.register<Copy>("deployToTomcat") {
        from(tasks.war)
        into(layout.projectDirectory.dir("tomcat/webapps"))
        doNotTrackState("Deployment directory contains unreadable files")
    }

build.gradle

    plugins {
        id 'war'
    }

    tasks.register("deployToTomcat", Copy) {
        from war
        into layout.projectDirectory.dir('tomcat/webapps')
        doNotTrackState("Deployment directory contains unreadable files")
    }