Copy action where source is determined by subprojects

I have a multi-project build and in the root project I want to write a Zip-type task which will gather files from sub-projects (the same set of files from each subproject) and then put them into a zip file within the root project. The only way I’ve been able to figure out how to do this is to make my task Zip task depend on another task which gathers the files from the sub-projects into a directory on the root project, and then zip up that directory, like this:

task copyArtifactsForDistributionZip(description: 'Copies the artifacts that will be included in the distribution zip file') {
 ext {
  tmpDir = file("${projectDir}/tmp")
  artifactTempDir = file("${tmpDir}/dist")
 }
    outputs.dir artifactTempDir
   doLast {
  subprojects.each { project ->
   def projectDir = project.file("${artifactTempDir}/${project.name}")
     copy {
    from project.buildDir
    into projectDir
    include 'docs/**'
    include 'libs/**'
    include 'reports/**'
    include 'test-results/**'
   }
  }
 }
}
  task buildDistributionZip(type: Zip) {
 dependsOn 'copyArtifactsForDistributionZip'
  from copyArtifactsForDistributionZip.outputs.files
}

What I would really like to do is to encapsulate all of this within the Zip task itself, without having to make it dependent on the other task (and therefore avoid having to physically copy the files from the child projects into the root project. Is there an easy way to do this? Is there a way I can do something like (this is pseudo-code - I know it won’t work but just want to show the logic)

task buildDistributionZip(type: Zip) {
   from(subprojects.each.collect)
}

You don’t have to copy things. I’m not sure how exactly the output should look like, but this should give you a start:

task distributionZip(type: Zip) {

subprojects.each { subproject ->

dependsOn subproject.build

into(subproject.name) {

from subproject.buildDir

include ‘docs/**’

include ‘libs/**’

include ‘reports/**’

include ‘test-results/**’

}

}

}

Edit: Depending on the buildDirs instead of the build task.

task distributionZip(type: Zip) {

subprojects.each { subproject ->

inputs.dir subproject.buildDir

into(subproject.name) {

from subproject.buildDir

include ‘docs/**’

include ‘libs/**’

include ‘reports/**’

include ‘test-results/**’

}

}

}

This is now really untested, though.

The task as you wrote it does not even compile - I get this error:

C:\Projects1\master>gradle distributionZip
  FAILURE: Build failed with an exception.
  * Where:
Script 'C:\Projects1\master\project-build.gradle' line: 27
  * What went wrong:
A problem occurred evaluating script.
> Could not find property 'build' on project ':Content'.
  * Try:
Run with --stacktrace option to get the stack trace. Run with --info or --debug option to get more log output.
  BUILD FAILED
  Total time: 4.181 secs

If I change it around a bit and remove the dependsOn

task distributionZip(type: Zip) {
 subprojects.each { subproject ->
   into(subproject.name) {
     from subproject.buildDir
     include 'docs/**'
     include 'libs/**'
     include 'reports/**'
     include 'test-results/**'
   }
 }
}

then the task does not do anything - I get an UP-TO-DATE status when running the build, even though I have never run the task before.

Heh. The task as I write it worked perfectly fine for me on gradle 1.0. Maybe you can depend the buildDirs? cf edit of my response

It won’t work if the subproject doesn’t apply the Java plugin as it won’t have a ‘build’ task.

It’s saying up to date because the source files don’t exist most likely.

You’ll need to configure your task dependencies so that the files you are trying to copy actually exist.

They do exist though.

I guess what I’m ultimately trying to accomplish is to have the root project of a build have a task which can generate a zip file of things that the subprojects tell it. So in the archive.zip at the root project there should be a subdirectory for each sub-project. Inside each subdirectory there should be “stuff” that each sub-project puts there. The root project should not know or care about what each subproject puts there - the root project could call a task or something that generates a CopySpec, which would in turn allow the root project to collect things from the subprojects and place it into the archive in the root project.

If you run with ‘-i’ Gradle will tell you why it considers a task to be up to date. That should help here.

I’d invert the way you’re thinking about this and have the subprojects inject instead of pulling from the subprojects.

root project:

task distributionZip(type: Zip) {}

Subproject(s):

rootProject.distributionZips {
    dependsOn // whatever is needed to create stuff
    into (project.name) {
      // configure to heart's content
    }

I don’t see how what you have injects from the subprojects down into the root project. If I do this in the root project:

allprojects {
 task buildDistributionZip(type: Zip) { }
}
  buildDistributionZip {
archiveName = 'artifacts.zip'
destinationDir = project.projectDir
}

And then in my subprojects I have this (all subprojects have the same thing at the moment just to prove the concept, but each subproject could configure differently):

distributionZips {
 includeEmptyDirs = false
 into(project.name) {
  from(buildDir) {
   include 'docs/**'
   include 'libs/**'
   include 'reports/**'
   include 'test-results/**'
  }
 }
}

My question is: How do I link the buildDistributionZip in the root project to the outputs of the tasks in the subprojects? Right now if I execute “gradle buildDistributionZip” from the root project directory it executes the task from the root project before going into the subprojects.

I made a mistake in my original post. Check what’s there now.

Ahhh so that’s how you do it. The subproject actually injects stuff directly into the root project’s task. The subprojects themselves don’t have a task.

The only issue now is in the structure inside the zip file. When I have this block inside build.gradle for a subproject

rootProject.distributionZips {
    dependsOn // whatever is needed to create stuff
    into (project.name) {
      // configure to heart's content
    }

the attribute “project.name” gets resolved to the project name of the root project - not the project name of the sub project. Inside the zip file at the root I want folders for each of the sub projects, so inside the zip it would look somewhat like this:

archive.zip
|
---SubprojectA
-------Some files/folders gathered from SubprojectA
---SubprojectB
-------Some files/folders gathered from SubprojectB

this.project.name’ should give you what you want.

Ahhh beautiful! I figured it had to do something with scoping…Thanks!

It’s a curly one for sure.

Each task object has a ‘getProject()’ method which returns the project the task is attached to. In this case it was shadowing the project that backed the script. The ‘this’ pointer lets you resolve to then containing script class, which implicitly delegates to the backing project, which has a ‘getProject()’ method that just returns “self”. The joys of dynamic languages :slight_smile:

Just 1 more thing to keep in the back of my mind! Thanks a bunch - I really appreciate it.