Task with a complex object as input

I want to build a task that has a complex object as input.
That object is also used in the extension.

I wonder if the task input is defined correctly:

    @Input
    abstract Property<Server> getServer()

What looks a bit weird for me is the way of getting the url inside task action, with 2 get() calls:

    @TaskAction
    def run() {
        println "MyTask.run - ${server.get().url.get()}"
    }

Gradle version is 6.9.1, java is 8, language is Groovy.

Below is the whole (simplified) script:

apply plugin: MyPlugin

abstract class Server {
    abstract Property<String> getUrl()
}

abstract class MyExtension {
    @Nested
    abstract Server getServer()

    // for DSL
    def server(Action action) {
        action.execute(server)
    }
}

abstract class MyTask extends DefaultTask {
    @Input
    abstract Property<Server> getServer()

   @TaskAction
    def run() {
        println "MyTask.run - ${server.get().url.get()}"
    }
}

class MyPlugin implements Plugin<Project> {

    void apply(Project project) {
        MyExtension myExtension = project.extensions.create("myx", MyExtension)

        project.tasks.register("myTask", MyTask) { task ->
            task.server.set(myExtension.server)
        }
    }
}


// test config
myx {
    server {
        url = 'url from config'
    }
}

Actually it is untypical to set the complex object directly.
Due to several reasons.
Currently your @Input is not used at all anyway.
If you run your task a second time without change using --info, you will see

Task ‘:myTask’ is not up-to-date because:
Task has not declared any outputs despite executing actions.

If you add an actual output or use outputs.upToDateWhen { true } and try to run the task, you will see

Cannot fingerprint input property ‘server’: value ‘extension ‘myx’ property ‘server’’ cannot be serialized.

This is the cause, because @Input annotation is only valid on serializable classes, because the serialized state is then used as part of the fingerprint calculation.

Unless you actually want to enable a user to set the whole server object, the usual way is to instead wire the single properties, especially if the complex object is only meant to enable hierarchical DSL syntax.
This also allows to use additional instances of the task without the need to instantiate a Server object, but by simply setting the single properties.

If you really want to use Server as @Input you have to make it serializable.

So this would be the more typical setup:

apply plugin: MyPlugin

abstract class Server {
    abstract Property<String> getUrl()
}

abstract class MyExtension {
    @Nested
    abstract Server getServer()

    // for DSL
    def server(Action action) {
        action.execute(server)
    }
}

abstract class MyTask extends DefaultTask {
    @Input
    abstract Property<String> getServerUrl()

    @TaskAction
    def run() {
        println "MyTask.run - ${serverUrl.get()}"
    }
}

class MyPlugin implements Plugin<Project> {

    void apply(Project project) {
        MyExtension myExtension = project.extensions.create("myx", MyExtension)

        project.tasks.register("myTask", MyTask) { task ->
            task.serverUrl.set(myExtension.server.url)
        }
    }
}


// test config
myx {
    server {
        url = 'url from config'
    }
}

Thank you for the clarification!