How do I stop gradle for Android from deleting my intermediate files before later tasks can consume them?


(Laurence Gonsalves) #1

I posted this question on Stack Overflow as well, but got no response there. I hope it’s ok to cross-post here.

In my Android Studio/gradle build I’m trying to automatically generate my app icon. The task that generates the icon image (a PNG) is being run at the correct time, but the image is being deleted before the task that consumes it runs.

This is a simplified version of what I’ve added to my app/build.gradle which illustrates the problem:

task createIcon(type:Exec) {
    def outdir =
        new File("$buildDir/intermediates/res/merged/debug/drawable-ldpi/")
    outputs.dir(outdir)
    if (!outdir.exists()) {
        outdir.mkdirs()
    }
    def svg = 'src/images/ic_launcher.svg'
    inputs.source(new File(svg))
    def png = new File(outdir, "ic_launcher.png")
    outputs.file(png)
    commandLine "$workingDir/pngToSvg.sh", png, svg
}

tasks.whenTaskAdded{ t ->
      if(t.name.equals("generateDebugAssets")){
          t.dependsOn createIcon
      }
}

If I run ./gradlew clean and then ./gradlew assembleDebug, I can see that the output file of my createIcon task, “app/build/intermediates/res/merged/debug/drawable-ldpi/ic_launcher.png” is created and then very soon afterwards deleted. I get the following output from gradlew:

:app:preBuild UP-TO-DATE
:app:preDebugBuild UP-TO-DATE
...
Bitmap saved as: /home/laurence/src/foo/app/build/intermediates/res/merged/debug/drawable-ldpi/ic_launcher.png
:app:generateDebugAssets
:app:mergeDebugAssets
:app:generateDebugResValues UP-TO-DATE
:app:generateDebugResources
:app:mergeDebugResources
:app:processDebugManifest
:app:processDebugResources
/home/laurence/src/foo/app/build/intermediates/manifests/full/debug/AndroidManifest.xml:13:23-44 : AAPT: No resource found that matches the given name (at 'icon' with value '@drawable/ic_launcher').

:app:processDebugResources FAILED

FAILURE: Build failed with an exception.

* What went wrong:
Execution failed for task ':app:processDebugResources'.
> com.android.ide.common.process.ProcessException: org.gradle.process.internal.ExecException: Process 'command '/home/laurence/.local/android-sdks/build-tools/23.0.2/aapt'' finished with non-zero exit value 1

* Try:
Run with --stacktrace option to get the stack trace. Run with --info or --debug option to get more log output.

BUILD FAILED

You can see that the task :app:processDebugResources fails because aapt claims it can’t find @drawable/ic_launcher (the value of android:icon in my AndroidManifest.xml) which should correspond to the the place where I’m generating the PNG.

Note that I’ve confirmed that the PNG is being generated not just from the “Bitmap saved as” line in the console output, but also by watching the destination directory during the build. The file winks into existence and then vanishes a moment later.

Even weirder than that, if I run ./gradlew assembleDebug a second time the build succeeds!

How do I prevent my generated PNG from being deleted and ensuring that processDebugResources can see it without having to run my build twice?


(Stefan Oehme) #2

Looks like you are directly writing to the output location of another task. If that other task has a “sync”-like behavior (deleting stuff that it doesn’t know about), then that would explain the behavior your are seeing.

It would be better to generate your picture to a dedicated folder and add that folder to the list of resource folders.


(Laurence Gonsalves) #3

Thanks for your reply. Yes! That looks like it was indeed the problem. I actually managed to figure this out last night just before going to bed, and was going to post the solution today.

There were two smaller bugs, and one big bug.

One small bug was pointed out by Lance Java on Stack Overflow: the directory creation was being done in the configuration phase, rather than the execution phase. The other small bug was that this should have connected to generateDebugResources, not generateDebugAssets. Fixing these smaller bugs didn’t solve the problem, however.

The big bug was, like you said, that the files were being written to a sub-tree that was also being written to by another task. I was doing this because I didn’t know how else to get the files picked up for inclusion in the assembly, but this was not the correct approach as the other task(s) that wrote to that sub-tree would sometimes stomp on the files (depending on what order the tasks executed), causing them to “vanish”.

To fix all of these problems, the first step is to choose a new output directory for our task that won’t interfere with other tasks:

def fromSvgDir = new File(buildDir, "generated/fromSvg/")

We then add this to the srcDirs for res in the existing android block near the top of the build.gradle file:

sourceSets {
    main {
        res {
            srcDirs += fromSvgDir
        }
    }
}

Finally, have our task use that directory, and also fix those two other bugs:

task createIcon(type:Exec) {
    def outdir =
        new File(fromSvgDir, "drawable-ldpi/")
    outputs.dir(outdir)
    doFirst {
        outdir.mkdirs()
    }
    def svg = 'src/images/ic_launcher.svg'
    inputs.source(new File(svg))
    def png = new File(outdir, "ic_launcher.png")
    outputs.file(png)
    commandLine "$workingDir/pngToSvg.sh", png, svg
}

tasks.whenTaskAdded{ t ->
      if(t.name.equals("generateDebugResources")){
          t.dependsOn createIcon
      }
}

This works reliably, and also has the advantage that the generated PNGs will go through the PNG cruncher just like the non-generated PNGs.