[GRADLE-2112] Altering copy tasks during execution Created: 21/Feb/12  Updated: 10/Feb/17  Resolved: 10/Feb/17

Status: Resolved
Project: Gradle
Affects Version/s: None
Fix Version/s: None

Type: Improvement
Reporter: Gradle Forums Assignee: Unassigned
Resolution: Won't Fix Votes: 1


I've run into scenarios several times in which I want to alter the way files are mapped by the CopyTask based on the structure of the rest of the destination file tree. This is a tricky problem to solve given the nature of CopyTasks. I thought I'd share the latest scenario, and the solution I'm using to workaround it.

I have a plugin which adds a VERSION file to any archive task. I recently wanted to modify the plugin such that if the resulting archive has only a single root folder and no root files, put the VERSION file under that one folder with everything else. Otherwise, just put the VERSION file in the root of the archive. For example, if the archive looks like this:


...then add VERSION to the root, like so:


But if the tar is packaged like many distributions, with a single folder at the root of the archive:


...then I want the VERSION file under that top level folder:


I somehow needed my plugin to add a file to all archive tasks, but put the file in a destination dependent on what the rest of the archive ends up looking like. I haven't figured out an obvious way to do this in gradle.

The workaround I came up with uses eachFile() to visit the destination files, but uses a little bit of a hack to ensure that my VERSION file is the last file visited, giving me the opportunity to move it based on what I've observed of the rest of the files. Here's the code. Note that "versionFile" is another task I wrote which builds the VERSION file.

project.afterEvaluate {
// Make sure all archives contain a VERSION file
tasks.withType(AbstractArchiveTask) { task ->
// this is a hack. This whole thing only works if the VERSION file
// visited last by the eachFile() call, and that will only happen
// if its CopySpec is added as the last child of the root CopySpec
// if I don't add the closure argument (the {}), the VERSION file is
// added as a source path to the root CopySpec, to a Set of sources.
// So there is no guarantee of the order in which those sources will
// be visited. If I add the closure argument, then the VERSION file
// is added as a full child CopySpec, to a list of children, where
// order is guaranteed, and I know this will get visited last.
task.from(versionFile.versionFile.path) {}
// jars are a special case, usually have a single root folder, but are
// never unzipped, so I still want the VERSION file in the root
// of the archive
if(task.extension != "jar") {
def rootFileCount = 0;
def rootDirs = [] as Set
// visit all the files going into the archive, and take note
// of the first directory on their path
// if there end up being no files in the root of the archive,
// and only one folder in the root of the archive, then when
// I finally visit the VERSION file, move it into that one lone
// root folder
eachFile { FileCopyDetails details ->
if (details.name != versionFile.versionFile.name)
switch (details.relativePath.segments.length) {
case 1:
// Need to handle an edge case of an archive
// containing only a single file at its root
if (!rootFileCount && rootDirs.size() == 1)
details.relativePath = details.relativePath.prepend(rootDirs.asList()[0])

Comment by Gradle Forums [ 21/Feb/12 ]

I spoke too soon. While this does work in most cases, it doesn't work if an CopySpecs have been added to the task using the with() method. These CopySpecs are ignored by eachFile(), so you'll never see the files coming from them. Noticed my code wasn't working with the zips created by the Application plugin.

It seems that with(CopySpec) wraps the arg in a WrapperCopySpec. This differs from a CopySpecImpl is that getAllCopyActions() doesn't return actions on the parent specs of the 'root' spec. So any eachFile() copy actions you append to a Copy task don't get inherited by any WrapperCopySpec instances. Not sure if this is intentional.

Given that, I think I just can't do this.

Comment by Gradle Forums [ 21/Feb/12 ]

> it doesn't work if an CopySpecs have been added to the task using the with() method. These CopySpecs are ignored by eachFile()

Verified. This looks like a bug to me.

Comment by Terence Kent [ 25/Dec/13 ]

I just recently ran into this bug using Gradle 1.8. It would be really be nice to see this fixed in an upcoming release.

It wasn't terrible to work around the bug, but it took an awful long time to diagnose the problem and track down the existing bug report. For what it's worth, this bug gets my vote.

Comment by Benjamin Muschko [ 15/Nov/16 ]

As announced on the Gradle blog we are planning to completely migrate issues from JIRA to GitHub.

We intend to prioritize issues that are actionable and impactful while working more closely with the community. Many of our JIRA issues are inactionable or irrelevant. We would like to request your help to ensure we can appropriately prioritize JIRA issues you’ve contributed to.

Please confirm that you still advocate for your JIRA issue before December 10th, 2016 by:

  • Checking that your issues contain requisite context, impact, behaviors, and examples as described in our published guidelines.
  • Leave a comment on the JIRA issue or open a new GitHub issue confirming that the above is complete.

We look forward to collaborating with you more closely on GitHub. Thank you for your contribution to Gradle!

Comment by Benjamin Muschko [ 10/Feb/17 ]

Thanks again for reporting this issue. We haven't heard back from you after our inquiry from November 15th. We are closing this issue now. Please create an issue on GitHub if you still feel passionate about getting it resolved.

Generated at Wed Jun 30 12:13:07 CDT 2021 using Jira 8.4.2#804003-sha1:d21414fc212e3af190e92c2d2ac41299b89402cf.