Persistent task that runs a different task when source file changes

I am currently trying to achieve a daemon-like process that automatically triggers a task (usually compilation, but could be any theoretically) when a file changes.

One prototype I have made involves using the Tooling API embedded in a separate application which is then run in the project folder. I then manually detect any file changes to the directories I am watching then trigger a build. The drawback to this is that I have to distribute a whole application to users in order to do this.

I am trying to see if anyone here has attempted this as a Gradle task/plugin. Maybe even perhaps as a JavaExec?

I suspect that there might be issues trying to run two different tasks on the same build on the same daemon, especially if the “parent” build is still executing, with regards to model caching or task caching. But theretically speaking, would this be possible?

I’m not aware of any comprehensive efforts to implement this. It is something that is on our radar as a feature to add, but it is not scheduled as of this time. Any effort done in this area by anyone would likely lead to at least academic gains that could then feed in to the “out of the box” solution.

Running two build concurrently is “supported”, in so far as that it is designed for. That is not to say there won’t be some unforeseen issues though.

You’ll have to just experiment and address issues as they arise. We are more than willing to work with you on this and would appreciate the effort.

Use the Apache file monitor (http://commons.apache.org/proper/commons-vfs/) and run the file watcher from Gradle… something like this:

//Copy ASD files over

task copyASDFiles (dependsOn: [‘compileAll’]) << {

copyPaths.each { source, dest ->

copy {

println “Copying from $source to $dest”

from source

into dest

}

}

}

copyASDFiles {

copyPaths.each { source, dest ->

inputs.dir source

outputs.dir dest

}

}

task runFileWatcher (dependsOn: ‘copyASDFiles’) << {

def watchPaths = [

“content”,

“src/main/webapp”

]

FileSystemManager fsManager = VFS.getManager()

// We set up a queue so we can call back to this thread to handle file change events. Gradle complains otherwise.

SynchronousQueue queue = new SynchronousQueue()

def fm = new DefaultFileMonitor(new FileListener() {

void fileCreated(FileChangeEvent event) { triggerCopy() }

void fileDeleted(FileChangeEvent event) { triggerCopy() }

void fileChanged(FileChangeEvent event) { triggerCopy() }

private void triggerCopy() { queue.put(‘run-task’) }

})

fm.recursive = true

watchPaths.each { path ->

println(“Watching folder: $path”)

FileObject listenDir = fsManager.resolveFile("$rootDir/$path")

fm.addFile(listenDir)

}

fm.start()

// Handle file change events

while(true) {

queue.take()

def task = tasks.executeTaskHack

task.actions.each {

it.execute(task)

}

}

}

tasks.runFileWatcher.description = ‘Monitors file for changes and copies them in to another project’

/*

This task is a hack to allows to be able to call a task programatically

along with all of its task dependencies

*/

task executeTaskHack(type: GradleBuild) {

tasks = [‘copyASDFiles’]

}

I was lazy. Why reinventing a half-baked version of gradle’s out-of-date and dependency system, when you get that for free? I have a somewhat hacky-but-working version in my TaskWatcher task type.

The plugin configures the task for you. You only have to specify a set of tasks to watch and a polling interval.

watchTasks {
    tasks = [ "foo", "bar" ]
    pollingInterval = 2000
}

The default for the ‘pollingInterval’ is 5s.