Gradle: How to copy a list of absolute paths into a single directory

Viewed 983

In Gradle, I'm trying to copy a list of absolute paths into a single directory, but I want to keep the last component of the absolute path as a subfolder of the destination directory.

Currently I have:

def copyTask = tasks.create(name: "copyFolders", type: Copy, overwrite: true) {
    from ["/sources/a-1/first", "/sources/a-3/b-1/c-1/second"]
    into "/destination"
}

but this results in the files contained in /sources/foo/bar and /sources/one/two/three/path being copied into /destination. Instead, I want /destination to contain both bar and path folders (with their contents inside them).


It should be able to handle this case correctly:

sources
├── a-1
│   └── fist
│       └── lorem.txt
|       └── subfolder
│           └── lorem2.txt
├── a-2
│   └── fist-b
│       └── lorem.txt
|       └── subfolder
│           └── lorem2.txt
└── a-3
    └── b-1
        └── c-1
            └── second
                └── ipsum.log

->

destination
├── first
│   └── lorem.txt
│   └── subfolder
│       └── lorem2.txt
└── path
    └── ipsum.log

Based on @szymon answer I got:

def copyTask = tasks.create(name: "copyToDestDir", type: Copy, overwrite: true) {
    includeEmptyDirs = false
    def fromDir = "/sources"
    def sourcePaths = ["/sources/a-1/first", "/sources/a-3/b-1/c-1/second"]
    from fileTree(fromDir).dir.listFiles()
    eachFile { copySpec ->
        def shouldBeCopied = false
        sourcePaths.find {
            if (copySpec.getFile().getCanonicalPath().startsWith(it)) {
                shouldBeCopied = true
                return true // break iteration
            }
        }
        if (shouldBeCopied) {
            String[] parts = file.path.split('/')
            copySpec.path = parts.size() > 1 ? "${parts[parts.size() - 2]}/${parts[parts.size() - 1]}" : parts[0]
        } else {
            copySpec.exclude()
        }
    }
    into destDir
}

but this wrongly results in:

destination
├── first
│   └── lorem.txt
└── subfolder
│    └── lorem2.txt
└── path
    └── ipsum.log

How to achieve this?

2 Answers

Try following Gradle task:

task copyFiles(type: Copy, overwrite: true) {
    includeEmptyDirs = false
    from fileTree("sources").dir.listFiles()
    eachFile { file ->
        String[] parts = file.path.split('/')
        file.path = parts.size() > 1 ? "${parts[parts.size() - 2]}/${parts[parts.size() - 1]}" : parts[0]
    }
    into "destDir"
}

The whole part happens in eachFile {} closure where we specify a target file.path - I used this imperative approach, but it can be replaced with fancy regex that keeps last folder and file name and replaces all other parts with empty string (I tried to come up with such regex but I failed).

How it works

My input directory looks like this:

sources
├── foo
│   └── bar
│       └── lorem.txt
└── one
    └── two
        └── three
            └── path
                └── ipsum.log

I run a task:

#: ./gradlew copyFiles                                                  
:copyFiles UP-TO-DATE

BUILD SUCCESSFUL

And the destDir looks like this:

destDir
├── bar
│   └── lorem.txt
└── path
    └── ipsum.log

Tested with Gradle 2.14.1, 3.5-rc-2 and 4.2.1

I've finally solved this doing this. Note that the top level into directive is needed or it won't work, and the nested into directives are relative to the top-level one.

def copyTask = tasks.create(name: "copyToDestDir", type: Copy, overwrite: true) {
    includeEmptyDirs = false
    def fromDir = "/sources"
    def sourcePaths = ["/sources/a-1/first", "/sources/a-3/b-1/c-1/second"]
    def destDir = "/destination"
    sourcePaths.each { sourcePath ->
        from(sourcePath) {
            into sourcePath.substring(sourcePath.lastIndexOf('/') + 1)
        }
    }
    into destDir
    dependsOn cleanUpDestDir
}

Thanks to @SzymonStepniak for the inspiration.

Related