Tar within Tar not giving expected results

Never mind WHY I want to do this, I am just trying to emulate a legacy system.

I wish to use Gradle (v2.10) to package a tar file within a tar file.

Here is my build.gradle script:

task tar1(type: Tar) {
    extension 'tar'
    archiveName 'tar1.tar'
    dirMode 0755
    fileMode 0644
    into ('/') {
        from ('src/files')
    }
}

task tar2(type: Tar, dependsOn: 'tar1') {

    extension 'tar'
    archiveName 'tar2.tar'
    dirMode 0755
    fileMode 0644
    into ('/') {
        from (project.distsDir) {
            include{'tar1.tar'}
        }
    }
}

My build command is gradle clean tar2

For some reason I cannot fathom the Tar task is adding a 0 length tar2.tar file to tar2.tar:

~/example$ tar -tvf build/distributions/tar2.tar
-rw-r--r-- 0/0               0 2016-02-03 11:40 tar2.tar
-rw-r--r-- 0/0           10240 2016-02-03 11:40 tar1.tar

I only want tar1.tar to exist in tar2.tar.

What am I doing incorrectly or is this a bug in the Gradle Tar task?

This works.

task tar1(type: Tar) {
    extension 'tar'
    archiveName 'tar1.tar'
    dirMode 0755
    fileMode 0644
    from ('src/files')
}

task tar2(type: Tar, dependsOn: 'tar1') {

    extension 'tar'
    archiveName 'tar2.tar'
    dirMode 0755
    fileMode 0644
    from (project.distsDir) {
        include 'tar1.tar' 
    }
}

There are many syntaxes that work, with subtle differences that aren’t explained very well. A bit like perl in that way. Oh well, it’s doing what I want.

Hopefully I can explain some of the behaviour you’re seeing. I think the first example (the one that doesn’t work) ultimately fails because of this line:

include { "tar1.tar" }

That’s because the curly braces represent a closure, which is an argument of the include() method. You can find that variant documented in the DSL guide. The closure represents a function that returns a boolean value to indicate whether a matching file should be included in the tar file or not.

I’ve very rarely seen it used in build files, but you could your example to work with:

include { file -> file.name == "tar1.tar" }

Most people just use the include(String includePattern, ...) syntax, which is what you ended up going with in your working example.

Note that for archive tasks, such as Zip and Tar, there is an implicit into() representing the archive file itself. You only need to add explicit into() calls if you want to copy files into a fixed sub-directory of the archive.

Finally, your build is quite fragile because you specify the location of the tar1.tar file twice, which means if you decide to change its name, you have to remember to update it in two locations. Off the top of my head, you should be able to do this:

task tar2(type: Tar, dependsOn: 'tar1') {
    archiveName 'tar2.tar'
    dirMode 0755
    fileMode 0644
    from (tar1)
}

Note how the from() references a task. I haven’t tried it, but it should work because the Tar task has a known output file: the created tar file. Gradle works this out and makes it the input of the from(). Now if you change the location or name of the first tar file, the second task will continue to work without any modifications.

Hope that helps.

Thanks for the explanation!

As you can see I’d already figured out what I needed to do without precisely knowing why!

Also, someone on Stack Overflow responded with another suggestion for removing the dependencies:

task tar2(type: Tar) {
    archiveName 'tar2.tar'
    dirMode 0755
    fileMode 0644
    from tar1.outputs
}

If you’re interested in the different types of object you can use with from(), check out the Tar entry in the DSL reference. It links to the docs for the project.files() method, which lists all the supported types.

As you can see, you can pass both a task instance and a TaskOutputs (the type of tar1.outputs), among others.