Untar does not preserve executable bit in Gradle 2.4

This is a regression from Gradle 2.3.

On Mac OS X, when I have Gradle fetch http://nodejs.org/dist/v0.12.3/node-v0.12.3-darwin-x64.tar.gz and extract it using tartree (see https://github.com/srs/gradle-node-plugin/blob/master/src/main/groovy/com/moowork/gradle/node/task/SetupTask.groovy for details) bin/node does not have the executable bit set and Gradle throws a permission denied error when I try to call it (see https://github.com/srs/gradle-node-plugin/blob/master/src/main/groovy/com/moowork/gradle/node/exec/ExecRunner.groovy for how it is called).

This is a regression from Gradle 2.3 where the extraction works as expected. I have checked the tar file manually and verified that the executable bit should be set for that file.

Are you sure that the executable bit is not set for bin/node when using Gradle 2.4? I cannot reproduce it with a script like this:

repositories {
    ivy {
        url = "http://nodejs.org/dist"
        layout 'pattern', {
            artifact 'v[revision]/[artifact](-v[revision]-[classifier]).[ext]'
        }
    }
}

configurations {
    node
}

dependencies {
    node "org.nodejs:node:0.12.3:darwin-x64@tar.gz"
}

task untar(type: Sync) {
    from tarTree(configurations.node.singleFile)
    into new File(buildDir, "node")
}

Nor using your plugin like this:

plugins {
    id "com.moowork.node" version "0.10"
}

node {
    download = true
    version = "0.12.3"
}

And executing the build like this:

Erdi’s MacBook Pro:node erdi$ ./gradlew -version

------------------------------------------------------------
Gradle 2.4
------------------------------------------------------------

Build time:   2015-05-05 08:09:24 UTC
Build number: none
Revision:     5c9c3bc20ca1c281ac7972643f1e2d190f2c943c

Groovy:       2.3.10
Ant:          Apache Ant(TM) version 1.9.4 compiled on April 29 2014
JVM:          1.7.0_67 (Oracle Corporation 24.65-b04)
OS:           Mac OS X 10.10.3 x86_64

Erdi’s MacBook Pro:node erdi$ ./gradlew nodeSetup
:nodeSetup

BUILD SUCCESSFUL

Total time: 5.189 secs
Erdi’s MacBook Pro:node erdi$ ls -la ~/.gradle/nodejs/node-v0.12.3-darwin-x64/bin/node 
-rwxr-xr-x  1 erdi  staff  18565212 20 May 13:51 /Users/erdi/.gradle/nodejs/node-v0.12.3-darwin-x64/bin/node

It would appear that it is only happening as part of the integration tests in a project that relies on the node plugin :confused:.

The source is at https://github.com/palantir/gradle-bowerdeps-plugin. If I update to Gradle 2.4 and run ./gradlew integTest, that appears to be where the executable bit isn’t set correctly.

com.palantir.bowerdeps.BowerDepsPluginIntegSpec > grunt dependencies added FAILED
    org.gradle.api.GradleException at BowerDepsPluginIntegSpec.groovy:106
        Caused by: org.gradle.internal.exceptions.LocationAwareException
            Caused by: org.gradle.api.tasks.TaskExecutionException
                Caused by: org.gradle.process.internal.ExecException
                    Caused by: net.rubygrapefruit.platform.NativeException
                        Caused by: java.io.IOException
                            Caused by: java.io.IOException

Detailed stacktrace:

    <failure message="org.gradle.api.GradleException: Build aborted because of an internal error." type="org.gradle.api.GradleException">org.gradle.api.GradleException: Build aborted because of an internal error.
	at nebula.test.functional.internal.DefaultExecutionResult.rethrowFailure(DefaultExecutionResult.groovy:95)
	at nebula.test.IntegrationSpec.runTasksSuccessfully(IntegrationSpec.groovy:234)
	at com.palantir.bowerdeps.BowerDepsPluginIntegSpec.grunt dependencies added(BowerDepsPluginIntegSpec.groovy:106)
Caused by: org.gradle.internal.exceptions.LocationAwareException: Execution failed for task ':installGrunt'.
	at org.gradle.initialization.DefaultExceptionAnalyser.transform(DefaultExceptionAnalyser.java:77)
	at org.gradle.initialization.MultipleBuildFailuresExceptionAnalyser.transform(MultipleBuildFailuresExceptionAnalyser.java:47)
	at org.gradle.initialization.StackTraceSanitizingExceptionAnalyser.transform(StackTraceSanitizingExceptionAnalyser.java:30)
	at org.gradle.initialization.DefaultGradleLauncher.doBuild(DefaultGradleLauncher.java:108)
	at org.gradle.initialization.DefaultGradleLauncher.run(DefaultGradleLauncher.java:86)
	at org.gradle.launcher.exec.InProcessBuildActionExecuter$DefaultBuildController.run(InProcessBuildActionExecuter.java:90)
	at org.gradle.tooling.internal.provider.runner.BuildModelActionRunner.run(BuildModelActionRunner.java:54)
	at org.gradle.launcher.exec.ChainingBuildActionRunner.run(ChainingBuildActionRunner.java:35)
	at org.gradle.launcher.exec.InProcessBuildActionExecuter.execute(InProcessBuildActionExecuter.java:41)
	at org.gradle.launcher.exec.InProcessBuildActionExecuter.execute(InProcessBuildActionExecuter.java:28)
	at org.gradle.tooling.internal.provider.DaemonBuildActionExecuter.execute(DaemonBuildActionExecuter.java:44)
	at org.gradle.tooling.internal.provider.DaemonBuildActionExecuter.execute(DaemonBuildActionExecuter.java:31)
	at org.gradle.tooling.internal.provider.LoggingBridgingBuildActionExecuter.execute(LoggingBridgingBuildActionExecuter.java:62)
	at org.gradle.tooling.internal.provider.LoggingBridgingBuildActionExecuter.execute(LoggingBridgingBuildActionExecuter.java:34)
	at org.gradle.tooling.internal.provider.ProviderConnection.run(ProviderConnection.java:113)
	at org.gradle.tooling.internal.provider.ProviderConnection.run(ProviderConnection.java:98)
	at org.gradle.tooling.internal.provider.DefaultConnection.getModel(DefaultConnection.java:164)
	at org.gradle.tooling.internal.consumer.connection.CancellableModelBuilderBackedModelProducer.produceModel(CancellableModelBuilderBackedModelProducer.java:58)
	at org.gradle.tooling.internal.consumer.connection.AbstractConsumerConnection.run(AbstractConsumerConnection.java:56)
	at org.gradle.tooling.internal.consumer.DefaultBuildLauncher$1.run(DefaultBuildLauncher.java:82)
	at org.gradle.tooling.internal.consumer.DefaultBuildLauncher$1.run(DefaultBuildLauncher.java:76)
	at org.gradle.tooling.internal.consumer.connection.LazyConsumerActionExecutor.run(LazyConsumerActionExecutor.java:83)
	at org.gradle.tooling.internal.consumer.connection.ProgressLoggingConsumerActionExecutor.run(ProgressLoggingConsumerActionExecutor.java:58)
	at org.gradle.tooling.internal.consumer.async.DefaultAsyncConsumerActionExecutor$1$1.run(DefaultAsyncConsumerActionExecutor.java:55)
	at org.gradle.internal.concurrent.ExecutorPolicy$CatchAndRecordFailures.onExecute(ExecutorPolicy.java:54)
	at org.gradle.internal.concurrent.StoppableExecutorImpl$1.run(StoppableExecutorImpl.java:40)
Caused by: org.gradle.api.tasks.TaskExecutionException: Execution failed for task ':installGrunt'.
	at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.executeActions(ExecuteActionsTaskExecuter.java:69)
	at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.execute(ExecuteActionsTaskExecuter.java:46)
	at org.gradle.api.internal.tasks.execution.PostExecutionAnalysisTaskExecuter.execute(PostExecutionAnalysisTaskExecuter.java:35)
	at org.gradle.api.internal.tasks.execution.SkipUpToDateTaskExecuter.execute(SkipUpToDateTaskExecuter.java:64)
	at org.gradle.api.internal.tasks.execution.ValidatingTaskExecuter.execute(ValidatingTaskExecuter.java:58)
	at org.gradle.api.internal.tasks.execution.SkipEmptySourceFilesTaskExecuter.execute(SkipEmptySourceFilesTaskExecuter.java:42)
	at org.gradle.api.internal.tasks.execution.SkipTaskWithNoActionsExecuter.execute(SkipTaskWithNoActionsExecuter.java:52)
	at org.gradle.api.internal.tasks.execution.SkipOnlyIfTaskExecuter.execute(SkipOnlyIfTaskExecuter.java:53)
	at org.gradle.api.internal.tasks.execution.ExecuteAtMostOnceTaskExecuter.execute(ExecuteAtMostOnceTaskExecuter.java:43)
	at org.gradle.api.internal.AbstractTask.executeWithoutThrowingTaskFailure(AbstractTask.java:310)
	at org.gradle.execution.taskgraph.AbstractTaskPlanExecutor$TaskExecutorWorker.executeTask(AbstractTaskPlanExecutor.java:79)
	at org.gradle.execution.taskgraph.AbstractTaskPlanExecutor$TaskExecutorWorker.processTask(AbstractTaskPlanExecutor.java:63)
	at org.gradle.execution.taskgraph.AbstractTaskPlanExecutor$TaskExecutorWorker.run(AbstractTaskPlanExecutor.java:51)
	at org.gradle.execution.taskgraph.DefaultTaskPlanExecutor.process(DefaultTaskPlanExecutor.java:23)
	at org.gradle.execution.taskgraph.DefaultTaskGraphExecuter.execute(DefaultTaskGraphExecuter.java:88)
	at org.gradle.execution.SelectedTaskExecutionAction.execute(SelectedTaskExecutionAction.java:37)
	at org.gradle.execution.DefaultBuildExecuter.execute(DefaultBuildExecuter.java:62)
	at org.gradle.execution.DefaultBuildExecuter.access$200(DefaultBuildExecuter.java:23)
	at org.gradle.execution.DefaultBuildExecuter$2.proceed(DefaultBuildExecuter.java:68)
	at org.gradle.execution.DryRunBuildExecutionAction.execute(DryRunBuildExecutionAction.java:32)
	at org.gradle.execution.DefaultBuildExecuter.execute(DefaultBuildExecuter.java:62)
	at org.gradle.execution.DefaultBuildExecuter.execute(DefaultBuildExecuter.java:55)
	at org.gradle.initialization.DefaultGradleLauncher.doBuildStages(DefaultGradleLauncher.java:149)
	at org.gradle.initialization.DefaultGradleLauncher.doBuild(DefaultGradleLauncher.java:106)
	... 22 more
Caused by: org.gradle.process.internal.ExecException: A problem occurred starting process 'command '/Users/nmalaguti/git/gradle-bowerdeps-plugin/build/test/com.palantir.bowerdeps.BowerDepsPluginIntegSpec/grunt-dependencies-added_1/build/node/node-v0.12.3-darwin-x64/bin/node''
	at org.gradle.process.internal.DefaultExecHandle.setEndStateInfo(DefaultExecHandle.java:196)
	at org.gradle.process.internal.DefaultExecHandle.failed(DefaultExecHandle.java:325)
	at org.gradle.process.internal.ExecHandleRunner.run(ExecHandleRunner.java:83)
	... 2 more
Caused by: net.rubygrapefruit.platform.NativeException: Could not start '/Users/nmalaguti/git/gradle-bowerdeps-plugin/build/test/com.palantir.bowerdeps.BowerDepsPluginIntegSpec/grunt-dependencies-added_1/build/node/node-v0.12.3-darwin-x64/bin/node'
	at net.rubygrapefruit.platform.internal.DefaultProcessLauncher.start(DefaultProcessLauncher.java:27)
	at org.gradle.process.internal.ExecHandleRunner.run(ExecHandleRunner.java:65)
	... 2 more
Caused by: java.io.IOException: Cannot run program &quot;/Users/nmalaguti/git/gradle-bowerdeps-plugin/build/test/com.palantir.bowerdeps.BowerDepsPluginIntegSpec/grunt-dependencies-added_1/build/node/node-v0.12.3-darwin-x64/bin/node&quot; (in directory &quot;/Users/nmalaguti/git/gradle-bowerdeps-plugin/build/test/com.palantir.bowerdeps.BowerDepsPluginIntegSpec/grunt-dependencies-added_1&quot;): error=13, Permission denied
	at net.rubygrapefruit.platform.internal.DefaultProcessLauncher.start(DefaultProcessLauncher.java:25)
	... 3 more
Caused by: java.io.IOException: error=13, Permission denied
	... 4 more
</failure>

Looking at the permissions on the files:

$ ls -l build/test/com.palantir.bowerdeps.BowerDepsPluginIntegSpec/grunt-dependencies-added_1/build/node/node-v0.12.3-darwin-x64/bin/
total 36264
-rw-r--r--  1 nmalaguti  1082149631  18565212 May 26 13:06 node
-rw-r--r--  1 nmalaguti  1082149631         0 May 26 13:06 npm

I’m not sure why this is different between Gradle 2.3 and 2.4. It doesn’t appear to happen when using the node plugin normally.

Might be related:

(Could not reproduce due to running into “FreeBSD is not Linux” issue).

One issue you will certainly run into trying to unpack node using gradle (which btw. uses Apache Ant Tar task code to do the job) you may have a problem with lack of symbolic links in Java. At least npm in the archive is a symbolic link, therefore after unpacking them it will be a plain file withe a size zero.

Related Apache Ant problem reports:

https://bz.apache.org/bugzilla/show_bug.cgi?id=19252 does not untar symbolic links properly

https://bz.apache.org/bugzilla/show_bug.cgi?id=15244 tar task should be able to store symbolic links as links

https://bz.apache.org/bugzilla/show_bug.cgi?id=14320 copy fileset followsymlinks=“false” does not copy symlinks at all

Nick, I checked out the master branch of your plugin, updated the wrapper to 2.4 and ran the integration tests yet was unable to reproduce this issue. The only other difference I can see is you are using a lightly older version of Java 1.7 (I’m on 1.7.0_75).

I just want to point out that symbolic link support is actually a restriction imposed by the facts that Gradle still has to be JDK6 compatible. If Gradle’s lowest JDK support was 1.7 and Path was used alongside File, then symbolic links could be supported.

There is a number of issues with gradle node plugins (they didn’t take into account my node_modules directory somewhere below the plugin path nor the global installation of grunt or gulp, for example), but this:

allowed be to use a pre-installed instance of io.js to run the integration test successfully.

Thanks to @nmalaguti for troubleshooting this!