Custom Task with inputs from multi directories

I have the below custom task. The issue is that I am not getting a UP-TO-DATE when I run the task multi times. I am using the filetree to select a subset of files from 2 seperate directories. I would like this to be the input for the task. I tried a couple different solutions but cannot seem to figure it out. I would appreciate any help

        def watchTree = fileTree(dir: "some/dir/", includes: ['**/app.json', '**/index.html'])
		watchTree = watchTree + fileTree(dir: 'another/dir/')
    task myTask(type: MyTask) {
		inputFolder = watchTree
		outputDir = file("src/main/webapp/mas/$app/")

		baseSenchaPath = "$basePath"
		appName = "$app"
class MyTask extends DefaultTask  {
    final WorkerExecutor workerExecutor

	String appName
	String baseSenchaPath

 	FileCollection inputFolder;
    //private List<FileTree> inputDirList = new ArrayList<>()

    File outputDir

    // The WorkerExecutor will be injected by Gradle at runtime
    public SenchaCommandTask(WorkerExecutor workerExecutor) {
        this.workerExecutor = workerExecutor
	//public void addDirectory(FileTree inputDir) {
	//	inputDirList.add(inputDir)

    void myCommand(IncrementalTaskInputs inputs) {
    	println "Processing app $appName"
		inputs.outOfDate {
			println it.file

	    workerExecutor.submit(MyRunnable.class) { WorkerConfiguration config ->
	        // Use the minimum level of isolation
	        config.isolationMode = IsolationMode.NONE

	        config.params appName
	        config.params baseSenchaPath

I should also mention that the output dir is created by a external process. What is weird is that this worked when I use a task of type exec

I would try using SourceTask instead of DefaultTask because it already has the properties for handling multiple source directories.

Your task would turn into something like:

task myTask(type: MyTask) {
    source fileTree(dir: "some/dir/", includes: ['**/app.json', '**/index.html'])
    source fileTree(dir: 'another/dir/')
    outputDir = file("src/main/webapp/mas/$app/")

    baseSenchaPath = "$basePath"
    appName = "$app"

You should also make sure anything that affects the output is marked as an input. It’s a little strange to have output go into a non-buildDir directory.

Thanks I will give that a try thanks. I also could not get this to work when I had only one source directory.

I was thinking that this does not work because my MyRunnable is actually calling external process that generates the outputs.

I think the root of the issue is with the task not knowing the contents of my output dir. So my task delegates its work via the worker api to MyWorker. In MyWorker I spawn a process that generates files to the output dir. I am thinking the the task does not wait until the spawned process is complete to do its checking on the outputdir. Just a thought

Are you calling/creating your process with the ProcessBuilder? If so, you need to make sure to call waitFor().

I am using the ProcessBuilder. I am also using waitfor() .

I did try changing the output dir to a dir that has nothening to do with the task. When I do this I get a uptodate as expected.

I just hooked up a remote debugger. Looks like gradle is deleting the outputdir. I am assuming it is doing this because gradle this the outputdir is out of date.

This is what I am working with now. Converted my task to java. Everything is working fine except the uptodate check. The runable is what is creating the files that go to the output dir. I also noticed that gradle is deleting the outputdir. I am assuming this is happening because it thinks it is out of date.

public class MyTask extends DefaultTask {
final WorkerExecutor workerExecutor;

private String appName;
private String baseSenchaPath;

// The WorkerExecutor will be injected by Gradle at runtime
public MyTask (WorkerExecutor workerExecutor) {
    this.workerExecutor = workerExecutor;

void runSenchaCommand(/*IncrementalTaskInputs inputs*/) {

	//NOSONAR - Sonar thinks this is slf4j
	getLogger().info("Processing app " + appName);
    workerExecutor.submit(MyRunnable.class, config -> {
		config.setDisplayName("Worker for " + appName);	
		config.setParams(appName, baseSenchaPath);

public FileCollection getInputFolder() {
	File buildDir = getProject().getProjectDir();
	ConfigurableFileTree cft = getProject().fileTree(buildDir + "/sencha6/" + appName);
    cft.include("**/app.json", "**/index.html");

	return cft;

public File getOutputDir() {
	String buildDirPath = getProject().getProjectDir().getAbsolutePath();
	return new File(buildDirPath + "/src/main/webapp/mas/" + appName); 

public String getAppName() {
	return appName;

public void setAppName(String appName) {
	this.appName = appName;

public String getBaseSenchaPath() {
	return baseSenchaPath;

public void setBaseSenchaPath(String baseSenchaPath) {
	this.baseSenchaPath = baseSenchaPath;


public class MyRunnable implements Runnable {
private final String appName;
private final String basePath;

public MyRunnable(String appName, String basePath) {
    this.appName = appName;
    this.basePath = basePath;

public void run()  {
	String[] params = {"sencha", "app", "build", "production"};
	String sechaCommandOutput = "";
    ProcessBuilder processBuilder = new ProcessBuilder(params); File(baseSenchaPath + "/" + appName));
    Process process;
    Integer returnCode = -1;
    try {
    	process = processBuilder.start();
    	sechaCommandOutput = output(process.getInputStream());
    	returnCode = process.waitFor();
    } catch(Exception e) {
    	System.out.println("Error while running the process " + e.getMessage());

	if (returnCode != 0) {
		System.out.println("Sencha Command Return Code = " + returnCode);
		System.out.println("Sencha Command Error = " + sechaCommandOutput);

		throw new GradleException("Sencha Command failed with return code = " + returnCode + " while processing applicaton $appName ----- Error Message: \n " + sechaCommandOutput);
	} else {
		System.out.println("Sencha Command completed processing " + appName + " successfully");

* Get the output
private String output(InputStream inputStream) throws IOException {
	StringBuilder sb = new StringBuilder();
	try (BufferedReader br = new BufferedReader(new InputStreamReader(inputStream));){
		String line = null;
		while ((line = br.readLine()) != null) {
			sb.append(line + System.getProperty("line.separator"));
	return sb.toString();


Thanks to the gradle teams help I found the issue. I was cleaning the output dir by mistake. This is because my clean task was running in the configuration phase instead of the task phase.