Executing a TaskAction (of type IncrementalTaskInputs) for all subprojects in a Multi-project Build

I have a simple class that prints to screen if the content of a project’s ‘src’ directory has been modified since the previous execution:

task checkIfProjectIsOutOfDate(type: IsOutOfDate) {
    inputDir = file("${project.projectDir}/src")
    outputDir = file("C:/empty-directory")
}

class IsOutOfDate extends DefaultTask {
    @InputDirectory
    def File inputDir

    @OutputDirectory
    def File outputDir

    @TaskAction
    void execute(IncrementalTaskInputs inputs) {
        def outOfDate = false

        inputs.outOfDate { change ->
            println "out of date: ${change.file.name}"
        }

        inputs.removed { change ->
            println "removed: ${change.file.name}"
        }
    }
}

This works as expected. I now need to modify this code to check each sub-project of a multi-project build.

RootProject
|— Project A
|— Project B
|— Project C

If I update my ‘listOutOfDateProjects’ to use ‘subprojects’:

task listOutOfDateProjects(type: IsOutOfDate) {

    subprojects {
	inputDir = file("${project.projectDir}/src")
	outputDir = file("C:/emptyDirectory")
	println "Configuring input & output directories for ${project.name}"
    }
}

Now only the last of my subprojects (Project C) is evaluated, as I’m assuming ‘listOutOfDateProjects’ is configured for each subproject before ‘IsOutOfDate::execute’ is called.

My questions are:

a) How can I update the ‘listOutOfDateProjects’ task so each sub-project is evaluated for out of date inputs?

b) If I update the ‘IsOutOfDate::execute’ TaskAction to return a Boolean (true for out-of-date projects), how can I obtain this return value in my ‘listOutOfDateProjects’ task for each subproject?

Have you tried just creating the task for each project?

subprojects {
    task checkIfProjectIsOutOfDate(type: IsOutOfDate) {
        inputDir = file("${project.projectDir}/src")
        outputDir = file("C:/empty-directory")
    }
}

Thanks Sterling.

Placing the ‘subprojects’ block outside of the declaration of my task fixed the issue I was having executing this logic from a build.gradle file.

However after moving this logic to a custom plugin the problem of only the last subproject (Project C) being evaluate for out of date inputs is occurring once again.:

MyPlugin.groovy:

Class MyPlugin implements Plugin<Project> {

    void apply(Project project) {
        
        Task isOutOfDate = project.task('isOutOfDate', type: IsOutOfDate)

        project.subprojects { currentProject ->

            project.tasks.getByName('isOutOfDate') {
                inputDir = new File("${currentProject.projectDir}/src")
                outputDir = new File("C:/empty-directory")
            }
        }
    }
}

IsOutOfDate.groovy:

class IsOutOfDate extends DefaultTask {

    @InputDirectory
    def File inputDir

    @OutputDirectory
    def File outputDir

    @TaskAction
    void execute(IncrementalTaskInputs inputs) {

        inputs.outOfDate { change ->
            println "out of date ${change.file.name}"
        }

        inputs.removed { change ->
            println "removed: ${change.file.name}"
        }
    }
}

I’ve attempted creating another Plugin task to check all subprojects like so, but to no success - still only the last subproject get evaluated:

Task checkAllSubProjects = project.task('checkAllSubProjects')

project.subprojects { currentProject ->

    Task isSubProjectOutOfDate = project.tasks.getByName('isOutOfDate') {
        inputDir = new File("${currentProject.projectDir}/src")
        outputDir = new File("C:/empty-directory")
    }

checkAllSubProjects.dependsOn isSubProjectOutOfDate
}

I see two options to put that logic into a plugin.

Option 1: Have a plugin that is applied on your root project only and adds a task for each subproject

Class MyPlugin implements Plugin<Project> {

    void apply(Project project) {
        project.subprojects { currentProject ->
            Task isOutOfDate = currentProject.task('isOutOfDate', type: IsOutOfDate) {
                inputDir = currentProject.file("src")
                outputDir = new File("C:/empty-directory")
            }
        }
    }
}

Option 2: Apply a plugin for each subproject and let the plugin create one task for the project it is applied to

Class MyPlugin implements Plugin<Project> {

    void apply(Project project) {
            Task isOutOfDate = project.task('isOutOfDate', type: IsOutOfDate) {
                inputDir = project.file("src")
                outputDir = new File("C:/empty-directory")
            }
        }
    }
}

cheers,
René