Dynamically created tasks are never called

Hello.

I’m trying to migrate an Ant build (which gave me some gray hair already) to Gradle (I was told it’s better). My problem with it is that I managed to run it once, but it won’t do it any more. I’ve tried deleting .gradle directory, modifying the files specified as inputs, installing different versions (1.0, 1.8, 1.9 and 2.0). 1.0 - I was never able to compile anything, same for 2.0 - some internal build errors unrelated to my project, 1.8 and 1.9 behave the same. Below is the relevant script:

task previewFonts {
  inputs.dir "./fonts"
  inputs.file "./templates/Preview.as.template"
  outputs.dir "./ant/previews"
}
  previewFonts << {
  def generatedAs = "./generated/previews"
  FileTree tree = fileTree(dir: "./fonts")
  tree.include "**/regular/*.otf"
  tree.include "**/regular/*.ttf"
  file("./ant/previews").mkdirs()
  file(generatedAs).mkdirs()
    tree.each { f ->
            def d = f.getParentFile()
    d = d.getParentFile()
    def fname = f.getName().replaceFirst("[.][^.]+\$", "")
    def plocation = d.getPath() + "/description.properties"
    Properties props = new Properties();
    props.load(new FileInputStream(plocation))
    def pname = new String(
      props.getProperty("preview.name").getBytes("ISO-8859-1"), "UTF-8");
    def png = "./ant/previews/${pname}.png"
            def converter = task "convert${d.getName()}${fname}" {
      inputs.file f
      inputs.file plocation
      println "convert task declared"
    }
          converter << {
      println "recompiling"
        def label = "label:" + new String(
        props.getProperty("preview.text").getBytes("ISO-8859-1"), "UTF-8");
      def fg = "label:" + props.getProperty("preview.foreground")
      def bg = props.getProperty("preview.background")
      def size = props.getProperty("preview.size")
        new ByteArrayOutputStream().withStream { es ->
        def result = exec {
          errorOutput = es
          executable = "convert"
          args "-background", bg, "-fill", fg, "-pointsize", size,
          "-font", f, label, png
        }
        // TODO: Find out how to get the error message.
        if (result.getExitValue() != 0) {
          def message = new String("Preview not created: ${es.toString()}")
          throw new GradleScriptException(message,
                                          new RuntimeException(message));
        }
      }
    }
            def copier = tasks.create(name: "asTemplate${d.getName()}${fname}", type: Copy) {
      def aname = pascalCase fname
      println "rebuilding template ${generatedAs}/${aname}"
      from "./templates"
      into "${generatedAs}"
      include "Preview.as.template"
              }
    copier.doLast {
      println "doLast ${aname}"
      rename { file -> println("renaming: ${file} to ${aname}"); "${aname}.as" }
      expand(asClass: "${aname}", png: png)
    }
  }
}

Here’s example output I get for running ./gradlew -i previewFonts -C rebuild

Please use CMSClassUnloadingEnabled in place of CMSPermGenSweepingEnabled in the future
Starting Build
Settings evaluated using empty settings script.
Projects loaded. Root project using build file '/home/wvxvw/workspace/fonts/build.gradle'.
Included projects: [root project 'fonts']
Evaluating root project 'fonts' using build file '/home/wvxvw/workspace/fonts/build.gradle'.
Compiling build file '/home/wvxvw/workspace/fonts/build.gradle' using BuildScriptClasspathScriptTransformer.
Compiling build file '/home/wvxvw/workspace/fonts/build.gradle' using BuildScriptTransformer.
All projects evaluated.
Selected primary task 'previewFonts'
Tasks to be executed: [task ':previewFonts']
:previewFonts (Thread[main,5,main]) started.
:previewFonts
Executing task ':previewFonts' (up-to-date check took 0.613 secs) due to:
  No history is available.
convert task declared
rebuilding template ./generated/previews/ChampagneLimousines
convert task declared
... (lots more of the same)
convert task declared
rebuilding template ./generated/previews/UNIVERSALFRUITCAKE
:previewFonts (Thread[main,5,main]) completed. Took 1.038 secs.
  BUILD SUCCESSFUL
  Total time: 4.413 secs

This, obviously, didn’t do anything, i.e. no previews were generated. (4.5 seconds for doing absolutely nothing seems like a farily long time though…) Anyways, how do I handle this situation? Where to look? Any logs with explanations for why the task wasn’t executed? I’ve searched StackOverflow and the documentation for forcing task execution, but all I could find is that “it’s not supported”…

Note, that if I add outputs and force task’s execution with task.executie(), then taks are executed properly, if their dependencies require that.

I struggle to understand what you are expecting your build to do. Can you please elaborate?

First problem is that you are trying to declare some tasks (‘convert…’ and ‘asTemplate…’) while executing other task (‘previewFonts’) which is too late to do it. You are doing it in execution phase while only tasks declared in configuration phase are taken into consideration by Gradle (you might want to read manual section on build lifecycle).

Secondly you declare these new tasks but they are detached from task graph - you didn’t specify any dependencies that would say that these tasks should be executed when ‘previewFonts’ is executed.

What is the goal: I need to convert multiple font files (TTF and OTF) into images, which display their preview. The inputs are the TTF or OTF files and a property file that describes what text should be used to display the preview, the color of the text, its background and the size.

The problem: I want to use Gradle’s ability to recognize changes in files and only compile the previews for the fonts which had changed, property files which changed. Ideally, it would also know how to remove the previews of the fonts that had been removed (but I guess, that’s not possible or is it?).

How I would approach this problem with other build systems, eg. Ant, Make or SCons: I’d create a target / builder / recipe with parameters s.t. multiple invocations of this target / builder / recipe would produce different outputs, based on the parameters. After reading Gradle documentation I’ve received an impression that for some obsucre reason this way is not possible, and instead I have to create multiple tasks, or write a task class of my own, which handles the situation.

Since I’ve posted this, I switched to writing a class that handles the task. Unfortunately, I couldn’t use any of the Gradle’s own functionality - the build caching is worthless in my case as is the graph building, and it seems like it’s not possible to make friends between Gradle and Git so that it could properly understand what change had happened in the project being built. So, I basically ended up with writing a shell script in a pug-ugly Java+Ruby nonsense language… Sorry for saying this. I would still admit though that this is a slight improvement over Ant :slight_smile:

One thing you might do is to start simple by doing all the work for all files in one task, specify inputs and outputs for it which means that it will only run if anything has changed. If on the other hand there is a lot of these files and they change a lot then going with multiple task will definitely be more efficient. Note that Gradle won’t automagically know if something has change, you have to tell it by specifying inputs and outputs: http://www.gradle.org/docs/current/userguide/more_about_tasks.html#sec:up_to_date_checks

Creating target/builder/recipe that takes parameters in Gradle is done by implementing a custom Task class which is simple and not somthing you should avoid: http://www.gradle.org/docs/current/userguide/custom_tasks.html. You will definitely end up with a cleaner solution than what you have pasted in this thread.

Why is “build caching and graph building worthless” for you? What do you mean by build caching? And by graph building?

Why is graph worthless for me:

None of about 70 build targets depends on any other target. So this is a very trivial case of a graph - completely disconnected set of vertices.

Why is caching worthless:

The caching doesn’t account for the possiblity of the file being deleted (it leaves stale binaries in the output directory). Caching is too slow, some times the up-to-date check is about as expensive as the build itslelf, so it doesn’t make sense to do the check (The files that need to be transcoded are quite large - some TTFs containing full Unicode range can be about 50Mb or so.) Caching doesn’t recognize simple name changes, which would be recognized by Git (and would be infered as file was renamed, which may or may not need to trigger a build, but the policy for this decision is non-existent). I ended up writing some Groovy code to parse Git logs and run tasks based on the parse.