Ok, so here it goes. For a build script:
apply plugin: 'java'
ext { whatever = 17 }
import org.apache.tools.ant.filters.ReplaceTokens
processResources {
filter ReplaceTokens, tokens: project.properties }
}
and a file src/main/resources/test.resource with the contents:
Name is: @name@. Whatever is: @whatever@.
the following happens:
$ ./gradlew clean processResources --stacktrace
:clean
:processResources FAILED
FAILURE: Build failed with an exception.
* What went wrong:
Execution failed for task ':processResources'.
> Could not copy file '/home/wujek/IdeaWorkspace/filter-test/src/main/resources/test.resource' to '/home/wujek/IdeaWorkspace/filter-test/build/resources/main/test.resource'.
* Try:
Run with --info or --debug option to get more log output.
* Exception is:
org.gradle.api.tasks.TaskExecutionException: Execution failed for task ':processResources'.
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.changedetection.state.CacheLockReleasingTaskExecuter$1.run(CacheLockReleasingTaskExecuter.java:35)
at org.gradle.internal.Factories$1.create(Factories.java:22)
at org.gradle.cache.internal.DefaultCacheAccess.longRunningOperation(DefaultCacheAccess.java:179)
at org.gradle.cache.internal.DefaultCacheAccess.longRunningOperation(DefaultCacheAccess.java:232)
at org.gradle.cache.internal.DefaultPersistentDirectoryStore.longRunningOperation(DefaultPersistentDirectoryStore.java:142)
at org.gradle.api.internal.changedetection.state.DefaultTaskArtifactStateCacheAccess.longRunningOperation(DefaultTaskArtifactStateCacheAccess.java:83)
at org.gradle.api.internal.changedetection.state.CacheLockReleasingTaskExecuter.execute(CacheLockReleasingTaskExecuter.java:33)
at org.gradle.api.internal.tasks.execution.SkipUpToDateTaskExecuter.execute(SkipUpToDateTaskExecuter.java:58)
at org.gradle.api.internal.tasks.execution.ContextualisingTaskExecuter.execute(ContextualisingTaskExecuter.java:34)
at org.gradle.api.internal.changedetection.state.CacheLockAcquiringTaskExecuter$1.run(CacheLockAcquiringTaskExecuter.java:39)
at org.gradle.internal.Factories$1.create(Factories.java:22)
at org.gradle.cache.internal.DefaultCacheAccess.useCache(DefaultCacheAccess.java:124)
at org.gradle.cache.internal.DefaultCacheAccess.useCache(DefaultCacheAccess.java:112)
at org.gradle.cache.internal.DefaultPersistentDirectoryStore.useCache(DefaultPersistentDirectoryStore.java:134)
at org.gradle.api.internal.changedetection.state.DefaultTaskArtifactStateCacheAccess.useCache(DefaultTaskArtifactStateCacheAccess.java:79)
at org.gradle.api.internal.changedetection.state.CacheLockAcquiringTaskExecuter.execute(CacheLockAcquiringTaskExecuter.java:37)
at org.gradle.api.internal.tasks.execution.ValidatingTaskExecuter.execute(ValidatingTaskExecuter.java:57)
at org.gradle.api.internal.tasks.execution.SkipEmptySourceFilesTaskExecuter.execute(SkipEmptySourceFilesTaskExecuter.java:41)
at org.gradle.api.internal.tasks.execution.SkipTaskWithNoActionsExecuter.execute(SkipTaskWithNoActionsExecuter.java:51)
at org.gradle.api.internal.tasks.execution.SkipOnlyIfTaskExecuter.execute(SkipOnlyIfTaskExecuter.java:52)
at org.gradle.api.internal.tasks.execution.ExecuteAtMostOnceTaskExecuter.execute(ExecuteAtMostOnceTaskExecuter.java:42)
at org.gradle.api.internal.AbstractTask.executeWithoutThrowingTaskFailure(AbstractTask.java:282)
at org.gradle.execution.taskgraph.DefaultTaskPlanExecutor.executeTask(DefaultTaskPlanExecutor.java:48)
at org.gradle.execution.taskgraph.DefaultTaskPlanExecutor.processTask(DefaultTaskPlanExecutor.java:34)
at org.gradle.execution.taskgraph.DefaultTaskPlanExecutor.process(DefaultTaskPlanExecutor.java:27)
at org.gradle.execution.taskgraph.DefaultTaskGraphExecuter.execute(DefaultTaskGraphExecuter.java:89)
at org.gradle.execution.SelectedTaskExecutionAction.execute(SelectedTaskExecutionAction.java:29)
at org.gradle.execution.DefaultBuildExecuter.execute(DefaultBuildExecuter.java:61)
at org.gradle.execution.DefaultBuildExecuter.access$200(DefaultBuildExecuter.java:23)
at org.gradle.execution.DefaultBuildExecuter$2.proceed(DefaultBuildExecuter.java:67)
at org.gradle.api.internal.changedetection.state.TaskCacheLockHandlingBuildExecuter$1.run(TaskCacheLockHandlingBuildExecuter.java:31)
at org.gradle.internal.Factories$1.create(Factories.java:22)
at org.gradle.cache.internal.DefaultCacheAccess.useCache(DefaultCacheAccess.java:124)
at org.gradle.cache.internal.DefaultCacheAccess.useCache(DefaultCacheAccess.java:112)
at org.gradle.cache.internal.DefaultPersistentDirectoryStore.useCache(DefaultPersistentDirectoryStore.java:134)
at org.gradle.api.internal.changedetection.state.DefaultTaskArtifactStateCacheAccess.useCache(DefaultTaskArtifactStateCacheAccess.java:79)
at org.gradle.api.internal.changedetection.state.TaskCacheLockHandlingBuildExecuter.execute(TaskCacheLockHandlingBuildExecuter.java:29)
at org.gradle.execution.DefaultBuildExecuter.execute(DefaultBuildExecuter.java:61)
at org.gradle.execution.DefaultBuildExecuter.access$200(DefaultBuildExecuter.java:23)
at org.gradle.execution.DefaultBuildExecuter$2.proceed(DefaultBuildExecuter.java:67)
at org.gradle.execution.DryRunBuildExecutionAction.execute(DryRunBuildExecutionAction.java:32)
at org.gradle.execution.DefaultBuildExecuter.execute(DefaultBuildExecuter.java:61)
at org.gradle.execution.DefaultBuildExecuter.execute(DefaultBuildExecuter.java:54)
at org.gradle.initialization.DefaultGradleLauncher.doBuildStages(DefaultGradleLauncher.java:166)
at org.gradle.initialization.DefaultGradleLauncher.doBuild(DefaultGradleLauncher.java:113)
at org.gradle.initialization.DefaultGradleLauncher.run(DefaultGradleLauncher.java:81)
at org.gradle.launcher.exec.InProcessBuildActionExecuter$DefaultBuildController.run(InProcessBuildActionExecuter.java:64)
at org.gradle.launcher.cli.ExecuteBuildAction.run(ExecuteBuildAction.java:33)
at org.gradle.launcher.cli.ExecuteBuildAction.run(ExecuteBuildAction.java:24)
at org.gradle.launcher.exec.InProcessBuildActionExecuter.execute(InProcessBuildActionExecuter.java:35)
at org.gradle.launcher.exec.InProcessBuildActionExecuter.execute(InProcessBuildActionExecuter.java:26)
at org.gradle.launcher.cli.RunBuildAction.run(RunBuildAction.java:50)
at org.gradle.api.internal.Actions$RunnableActionAdapter.execute(Actions.java:171)
at org.gradle.launcher.cli.CommandLineActionFactory$ParseAndBuildAction.execute(CommandLineActionFactory.java:201)
at org.gradle.launcher.cli.CommandLineActionFactory$ParseAndBuildAction.execute(CommandLineActionFactory.java:174)
at org.gradle.launcher.cli.CommandLineActionFactory$WithLogging.execute(CommandLineActionFactory.java:170)
at org.gradle.launcher.cli.CommandLineActionFactory$WithLogging.execute(CommandLineActionFactory.java:139)
at org.gradle.launcher.cli.ExceptionReportingAction.execute(ExceptionReportingAction.java:33)
at org.gradle.launcher.cli.ExceptionReportingAction.execute(ExceptionReportingAction.java:22)
at org.gradle.launcher.Main.doAction(Main.java:48)
at org.gradle.launcher.bootstrap.EntryPoint.run(EntryPoint.java:45)
at org.gradle.launcher.Main.main(Main.java:39)
at org.gradle.launcher.bootstrap.ProcessBootstrap.runNoExit(ProcessBootstrap.java:50)
at org.gradle.launcher.bootstrap.ProcessBootstrap.run(ProcessBootstrap.java:32)
at org.gradle.launcher.GradleMain.main(GradleMain.java:26)
at org.gradle.wrapper.BootstrapMainStarter.start(BootstrapMainStarter.java:33)
at org.gradle.wrapper.WrapperExecutor.execute(WrapperExecutor.java:130)
at org.gradle.wrapper.GradleWrapperMain.main(GradleWrapperMain.java:48)
Caused by: org.gradle.api.GradleException: Could not copy file '/home/wujek/IdeaWorkspace/filter-test/src/main/resources/test.resource' to '/home/wujek/IdeaWorkspace/filter-test/build/resources/main/test.resource'.
at org.gradle.api.internal.file.AbstractFileTreeElement.copyTo(AbstractFileTreeElement.java:70)
at org.gradle.api.internal.file.copy.MappingCopySpecVisitor$FileVisitDetailsImpl.copyTo(MappingCopySpecVisitor.java:127)
at org.gradle.api.internal.file.copy.FileCopySpecVisitor.copyFile(FileCopySpecVisitor.java:56)
at org.gradle.api.internal.file.copy.FileCopySpecVisitor.visitFileOrDir(FileCopySpecVisitor.java:52)
at org.gradle.api.internal.file.copy.FileCopySpecVisitor.visitFile(FileCopySpecVisitor.java:39)
at org.gradle.api.internal.file.copy.NormalizingCopySpecVisitor.visitFile(NormalizingCopySpecVisitor.java:70)
at org.gradle.api.internal.file.copy.MappingCopySpecVisitor.visitFile(MappingCopySpecVisitor.java:57)
at org.gradle.api.internal.file.collections.DirectoryFileTree.walkDir(DirectoryFileTree.java:156)
at org.gradle.api.internal.file.collections.DirectoryFileTree.visitFrom(DirectoryFileTree.java:124)
at org.gradle.api.internal.file.collections.DirectoryFileTree.visit(DirectoryFileTree.java:114)
at org.gradle.api.internal.file.collections.FileTreeAdapter.visit(FileTreeAdapter.java:96)
at org.gradle.api.internal.file.CompositeFileTree.visit(CompositeFileTree.java:54)
at org.gradle.api.internal.file.copy.CopyActionImpl.execute(CopyActionImpl.java:64)
at org.gradle.api.tasks.AbstractCopyTask.copy(AbstractCopyTask.java:42)
at org.gradle.language.jvm.tasks.ProcessResources.copy(ProcessResources.java:30)
at org.gradle.api.internal.BeanDynamicObject$MetaClassAdapter.invokeMethod(BeanDynamicObject.java:216)
at org.gradle.api.internal.BeanDynamicObject.invokeMethod(BeanDynamicObject.java:122)
at org.gradle.api.internal.CompositeDynamicObject.invokeMethod(CompositeDynamicObject.java:147)
at org.gradle.language.jvm.tasks.ProcessResources_Decorated.invokeMethod(Unknown Source)
at org.gradle.util.ReflectionUtil.invoke(ReflectionUtil.groovy:23)
at org.gradle.api.internal.project.taskfactory.AnnotationProcessingTaskFactory$StandardTaskAction.doExecute(AnnotationProcessingTaskFactory.java:217)
at org.gradle.api.internal.project.taskfactory.AnnotationProcessingTaskFactory$StandardTaskAction.execute(AnnotationProcessingTaskFactory.java:210)
at org.gradle.api.internal.project.taskfactory.AnnotationProcessingTaskFactory$StandardTaskAction.execute(AnnotationProcessingTaskFactory.java:199)
at org.gradle.api.internal.AbstractTask$TaskActionWrapper.execute(AbstractTask.java:526)
at org.gradle.api.internal.AbstractTask$TaskActionWrapper.execute(AbstractTask.java:509)
at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.executeAction(ExecuteActionsTaskExecuter.java:80)
at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.executeActions(ExecuteActionsTaskExecuter.java:61)
... 70 more
Caused by: org.gradle.api.InvalidUserDataException: Error - Invalid filter specification for org.apache.tools.ant.filters.ReplaceTokens
at org.gradle.api.internal.file.copy.FilterChain$1.transform(FilterChain.java:69)
at org.gradle.api.internal.file.copy.FilterChain$1.transform(FilterChain.java:58)
at org.gradle.api.internal.ChainingTransformer.transform(ChainingTransformer.java:37)
at org.gradle.api.internal.file.copy.FilterChain.transform(FilterChain.java:39)
at org.gradle.api.internal.file.copy.FilterChain.transform(FilterChain.java:46)
at org.gradle.api.internal.file.copy.MappingCopySpecVisitor$FileVisitDetailsImpl.open(MappingCopySpecVisitor.java:111)
at org.gradle.api.internal.file.AbstractFileTreeElement.copyTo(AbstractFileTreeElement.java:47)
at org.gradle.api.internal.file.copy.MappingCopySpecVisitor$FileVisitDetailsImpl.copyTo(MappingCopySpecVisitor.java:119)
at org.gradle.api.internal.file.AbstractFileTreeElement.copyFile(AbstractFileTreeElement.java:88)
at org.gradle.api.internal.file.AbstractFileTreeElement.copyTo(AbstractFileTreeElement.java:65)
... 96 more
Caused by: org.codehaus.groovy.runtime.typehandling.GroovyCastException: Cannot cast object '{resources=org.gradle.api.internal.resources.DefaultResourceHandler@7b5d82b7, distsDirName=distributions, fileResolver=org.gradle.api.internal.file.BaseDirFileResolver@3830fba7, buildFile=/home/wujek/IdeaWorkspace/filter-test/build.gradle, test=task ':test', javadoc=task ':javadoc', dependencyCacheDir=/home/wujek/IdeaWorkspace/filter-test/build/dependency-cache, extensions=org.gradle.api.internal.plugins.DefaultConvention@17d9adc3, rootDir=/home/wujek/IdeaWorkspace/filter-test, sources=[[source set 'main:java', source set 'main:resources'], [source set 'test:java', source set 'test:resources']], ext=org.gradle.api.internal.plugins.DefaultExtraPropertiesExtension@541ee970, processTestResources=task ':processTestResources', compileJava=task ':compileJava', defaultArtifacts=org.gradle.api.internal.plugins.DefaultArtifactPublicationSet_Decorated@396fe335, projectDir=/home/wujek/IdeaWorkspace/filter-test, description=null, jar=task ':jar', implicitTasks=[task ':dependencies', task ':dependencyInsight', task ':help', task ':projects', task ':properties', task ':tasks'], projectRegistry=org.gradle.api.internal.project.DefaultProjectRegistry@60adca08, ant=org.gradle.api.internal.project.DefaultAntBuilder@31ab04ce, compileTestJava=task ':compileTestJava', defaultTasks=[], buildDir=/home/wujek/IdeaWorkspace/filter-test/build, services=ProjectInternalServiceRegistry, status=integration, allprojects=[root project 'filter-test'], libsDirName=libs, archivesBaseName=filter-test, childProjects={}, distsDir=/home/wujek/IdeaWorkspace/filter-test/build/distributions, convention=org.gradle.api.internal.plugins.DefaultConvention@17d9adc3, parent=null, class=class org.gradle.api.internal.project.DefaultProject_Decorated, buildscript=org.gradle.api.internal.initialization.DefaultScriptHandler@52c889db, projectEvaluationBroadcaster=ProjectEvaluationListener broadcast, logging=org.gradle.logging.internal.DefaultLoggingManager@6bd2b352, properties=(this Map), docsDirName=docs, metaInf=[], depth=0, standardOutputCapture=org.gradle.logging.internal.DefaultLoggingManager@6bd2b352, project=root project 'filter-test', testResultsDir=/home/wujek/IdeaWorkspace/filter-test/build/test-results, subprojects=[], buildTasks=[build], targetCompatibility=1.7, sourceCompatibility=1.7, libsDir=/home/wujek/IdeaWorkspace/filter-test/build/libs, dependencyCacheDirName=dependency-cache, buildScriptSource=org.gradle.groovy.scripts.UriScriptSource@55fb03cb, gradle=build 'filter-test', reporting=org.gradle.api.reporting.ReportingExtension_Decorated@52b63086, components=[org.gradle.api.internal.java.JavaLibrary@372bd7d6], projectEvaluator=org.gradle.configuration.LifecycleProjectEvaluator@1f673ee6, sourceSets=[source set 'main', source set 'test'], buildDependents=task ':buildDependents', repositories=[], plugins=[org.gradle.api.plugins.BasePlugin@9b46a1a, org.gradle.api.plugins.ReportingBasePlugin@2ed5242b, org.gradle.language.base.plugins.LanguageBasePlugin@72f4cfd4, org.gradle.language.jvm.plugins.JvmLanguagePlugin@345077f2, org.gradle.api.plugins.JavaLanguagePlugin@698ceb26, org.gradle.api.plugins.JavaBasePlugin@7d07a550, org.gradle.api.plugins.JavaPlugin@6e4985b7, org.gradle.api.plugins.HelpTasksPlugin@26749cf7, org.gradle.buildsetup.plugins.BuildSetupPlugin@33cbcffe], dependsOnProjects=[], testReportDirName=tests, apiDocTitle=filter-test API, state=project state 'EXECUTED', manifest=org.gradle.api.java.archives.internal.DefaultManifest@33527e25, reportsDir=/home/wujek/IdeaWorkspace/filter-test/build/reports, reportsDirName=reports, build=task ':build', artifacts=org.gradle.api.internal.artifacts.dsl.DefaultArtifactHandler@6cd7c975, version=unspecified, metaClass=org.codehaus.groovy.runtime.HandleMetaClass@4aeb5826[groovy.lang.MetaClassImpl@4aeb5826[class org.gradle.api.internal.project.DefaultProject_Decorated]], testClasses=task ':testClasses', clean=task ':clean', dependencies=org.gradle.api.internal.artifacts.dsl.dependencies.DefaultDependencyHandler@20e84d37, name=filter-test, path=:, logger=org.gradle.api.logging.Logging$LoggerImpl@732566c1, parentIdentifier=null, processResources=task ':processResources', testReportDir=/home/wujek/IdeaWorkspace/filter-test/build/reports/tests, classes=task ':classes', buildNeeded=task ':buildNeeded', module=org.gradle.api.internal.artifacts.ProjectBackedModule@1c7d43f6, check=task ':check', binaries=[[binary 'main', binary 'test']], whatever=17, docsDir=/home/wujek/IdeaWorkspace/filter-test/build/docs, tasks=[task ':assemble', task ':build', task ':buildDependents', task ':buildNeeded', task ':check', task ':classes', task ':clean', task ':compileJava', task ':compileTestJava', task ':jar', task ':javadoc', task ':processResources', task ':processTestResources', task ':setupBuild', task ':test', task ':testClasses'], rootProject=root project 'filter-test', rebuildTasks=[clean, build], inheritedScope=org.gradle.api.internal.ExtensibleDynamicObject$InheritedDynamicObject@63f8da16, scriptPluginFactory=org.gradle.configuration.DefaultScriptPluginFactory@66b60da0, assemble=task ':assemble', asDynamicObject=org.gradle.api.internal.ExtensibleDynamicObject@3a38b80e, testResultsDirName=test-results, antBuilderFactory=org.gradle.api.internal.project.DefaultAntBuilderFactory@61bbbcfe, runtimeClasspath=file collection, configurations=[configuration ':archives', configuration ':compile', configuration ':default', configuration ':runtime', configuration ':testCompile', configuration ':testRuntime'], group=}' with class 'java.util.HashMap' to class 'java.util.Hashtable' due to: java.lang.NullPointerException
at org.gradle.api.internal.BeanDynamicObject$MetaClassAdapter.setProperty(BeanDynamicObject.java:178)
at org.gradle.api.internal.BeanDynamicObject.setProperty(BeanDynamicObject.java:107)
at org.gradle.util.ConfigureUtil.configureByMap(ConfigureUtil.java:43)
at org.gradle.api.internal.file.copy.FilterChain$1.transform(FilterChain.java:65)
... 105 more
As you can see, there is no more stack trace than I showed in the initial post.
Now, here is what happens:
- Project.properties is a HashMap and it contains keys that are mapped to null values - for example, ‘description’ if not set, ‘parent’ or ‘parentIdentifier’ (probably always for non-child builds) 2. ReplaceTokens.setTokens needs a Hashtable, so deep down (initiated by Gradle’s ConfigureUtil.configureByMap:43) Groovy transforms the HashMap to it by calling the Hashtable constructor with the HashMap as argument - DefaultTypeTransformation.castToType:331 3. the Hashtable constructor does nothing else than iterate over the map and fill itself, but it throws NPE when the value is null (Hashtable.put:542 in Oracle Java 7 u 21 64 bit)
So, to make this work the build script has to be changed to (or similar):
filter ReplaceTokens, tokens: project.properties.collectEntries { k, v -> [k, v ?: ''] }
results in (just the final bit, the root cause):
Caused by: java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
at org.apache.tools.ant.filters.ReplaceTokens.read(ReplaceTokens.java:165)
at org.apache.tools.ant.filters.BaseFilterReader.read(BaseFilterReader.java:83)
at org.apache.tools.ant.util.ReaderInputStream.read(ReaderInputStream.java:117)
at org.apache.commons.io.IOUtils.copyLarge(IOUtils.java:1025)
at org.gradle.api.internal.file.AbstractFileTreeElement.copyTo(AbstractFileTreeElement.java:49)
at org.gradle.api.internal.file.copy.MappingCopySpecVisitor$FileVisitDetailsImpl.copyTo(MappingCopySpecVisitor.java:119)
at org.gradle.api.internal.file.AbstractFileTreeElement.copyFile(AbstractFileTreeElement.java:88)
at org.gradle.api.internal.file.AbstractFileTreeElement.copyTo(AbstractFileTreeElement.java:65)
... 96 more
with the line in question ReplaceTokens.read:165 being:
final String replaceWith = (String) hash.get(key.toString());
Which makes me raise the question - do people really do filtering this way? This seems really limited and results in strange errors. It’s actually hard to blame any of the parties, it just the integration of these parts is unfortunate: Ant says in the ReplaceTokens.setTokens documentation explicitly that it accepts only Hashtable of String -> String, so the cast (instead of, for instance, invoking toString) is justified. Groovy can’t really know that the constructor it calls will fails because it doesn’t know that it is calling Hashtable() and it doesn’t like nulls - unless it creates another specific hardcoded case. Does Gradle really have to put have null values in the properties? (I’m not arguing that is shouldn’t, I just don’t know, just asking…)
I wonder if anybody reaches this line.
wujek