Task has no actions. Always UP-TO-DATE. WTH?

This is with gradle 4.9.

I seem to be missing something fundamental here. I have a plugin that provides a task that never runs because gradle continually deems it “UP-TO-DATE”. The exact same code in a script task runs just fine. Can someone enlighten me as to what basic knowledge I seem to be missing which would explain why my class-based task doesn’t work while it’s script counterpart runs?

Assumptions
I have other tasks in the plugin providing my class-based task, so the plugin is loaded correctly and the erroneous task is at the very least defined properly.

Works Just Fine:

task details {
    doLast {
        def appProps = new Properties()
        file("$projectDir/src/main/resources/application.properties").withInputStream { appProps.load(it) }
        def portNumber = appProps.getProperty("server.port")
        logger.lifecycle("servicePort: {}",portNumber)
    }
}

Causes Tears

class ServiceDetailsTask extends DefaultTask {
    @TaskAction
    def getServiceDetails() {
        doLast {
            def appProps = new Properties()
            file("$projectDir/src/main/resources/application.properties").withInputStream { appProps.load(it) }
            def portNumber = appProps.getProperty("server.port")
            logger.lifecycle("servicePort: {}", portNumber)
        }
    }
}

Here’s the output from trying to run the class-based version of the task:

ServiceInfoPlugin loaded.
xspLog: Applied XspServiceToolsPlugin
All projects evaluated.
Selected primary task 'serviceDetails' from project :
Tasks to be executed: [task ':serviceDetails']
:serviceDetails (Thread[Task worker for ':',5,main]) started.

> Task :serviceDetails UP-TO-DATE
Skipping task ':serviceDetails' as it has no actions.
:serviceDetails (Thread[Task worker for ':',5,main]) completed. Took 0.006 secs.

There doesn’t seem to be enough details here to reproduce your exact issue. However, one definite issue is the use of doLast { ... } in the class version. Using doLast { ... } creates a task action in the script task. @TaskAction on the method accomplishes this in the class version. I would expect an error claiming that you can’t add a task action after the task has started executing with what you’re showing though rather than the messaging that the task has no actions.

I didn’t get that error you mentioned. I just got the “UP-TO-DATE” message. Here’s all the information I know to give. If there’s anything else you or anyone needs to help me with this, just ask. I would really appreciate any inkling of a direction as to what stupid thing I’m missing to make this work.

I removed the “doLast” from the task class, but it didn’t make a difference.

EDIT
After re-reading a lot of documentation, I found something interesting. While the documentation never comes out and says it, every example has a task class being executed by extending it with a script task. There doesn’t seem to be any mention or example of executing a class-based task directly. That has to be possible though, doesn’t it? It would be stupid if you have to write a script task just to run a class-based task.

Plugin Class

class XspServiceToolsPlugin implements Plugin<Project> {
    void apply(Project project) {
        project.task("serviceDetails")
    }
}

Changed the task class per what you mentioned:

class ServiceDetailsTask extends DefaultTask {

    @TaskAction
    def getServiceDetails() {
        def appProps = new Properties()
        file("$projectDir/src/main/resources/application.properties").withInputStream { appProps.load(it) }
        def portNumber = appProps.getProperty("server.port")
        logger.lifecycle("servicePort: {}", portNumber)
    }
}

Import into the main project

buildscript {
    dependencies {
        classpath("com.comcast.xsp.gradle:xsp-gradle-service-tools:[1.0.0-b1,1.0.0)")
    }
}
apply plugin: 'xsp.gradle.servicetools'

Still got no joy

C:\dev\_projects\xsp-reference-service2>gradlew serviceDetails -i
Initialized native services in: C:\dev\.gradle\native
The client will now receive all logging from the daemon (pid: 6908). The daemon log file: C:\dev\.gradle\daemon\4.9\daemon-6908.out.log
Starting 23rd build in daemon [uptime: 1 hrs 40 mins 44.536 secs, performance: 100%, no major garbage collections]
Using 4 worker leases.
Starting Build
Settings evaluated using settings file 'C:\dev\_projects\xsp-reference-service2\settings.gradle'.
Projects loaded. Root project using build file 'C:\dev\_projects\xsp-reference-service2\build.gradle'.
Included projects: [root project 'xsp-reference-service']

> Configure project :
Evaluating root project 'xsp-reference-service' using build file 'C:\dev\_projects\xsp-reference-service2\build.gradle'.
All projects evaluated.
Selected primary task 'serviceDetails' from project :
Tasks to be executed: [task ':serviceDetails']
:serviceDetails (Thread[Task worker for ':',5,main]) started.

> Task :serviceDetails UP-TO-DATE
Skipping task ':serviceDetails' as it has no actions.
:serviceDetails (Thread[Task worker for ':',5,main]) completed. Took 0.012 secs.

Deprecated Gradle features were used in this build, making it incompatible with Gradle 5.0.
Use '--warning-mode all' to show the individual deprecation warnings.
See https://docs.gradle.org/4.9/userguide/command_line_interface.html#sec:command_line_warnings

BUILD SUCCESSFUL in 3s

Your plugin class was the missing information here.

void apply(Project project) {
    project.task("serviceDetails")
}

This creates a DefaultTask called serviceDetails, but you want a ServiceDetailsTask:

void apply(Project project) {
    project.tasks.create("serviceDetails", ServiceDetailsTask)
}