First plugin failed to get extension properties

Hello, I’m try write my first plugin to capture the total buildtime and can’t figure out, why my extensions properties are always default / not set. Thanks for your advies.

GradlePlugin:

public class GradlePlugin implements Plugin<Project> {
    
    @Override
    public void apply(final Project project) {
       final GradleExtension extension = project.getExtensions().create("gradleplugin", GradleExtension.class, project.getObjects());
        project.getLogger().lifecycle("isEnabled: " + extension.isEnabled());
        project.getLogger().lifecycle("getFoo: " + extension.getFoo());
        registerBuildService(extension, gradle);
    }

GradleExtension:

public class GradleExtension {
    private final Property<Boolean> enabled;
    private final Property<String> foo;
    
    public GradleExtension(final ObjectFactory objects) {
        enabled = objects.property(Boolean.class).convention(Boolean.TRUE);
        foo = objects.property(String.class);
    }

   // getters

build.gradle.kts

plugins {
    id("...gradleplugin") version "0.0.1-SNAPSHOT"
}

gradleplugin {
    foo("bar")
}

Output:

> Configure project :gradle-plugin
isEnabled: true
getFoo: null
... many other tasks
BUILD SUCCESSFUL in 712ms

Because you register the extension and then immediately query the values and only after your apply method finishes the buildscript has a chance to set the values. Because of that you have the lazy Propertys and wire them to other properties that you then evaluate at execution time after the buildscript has a chance to set them.

Thanks @Vampire: I changed as suggestest the evaluation into the buildservice, but still no success

public class GradlePlugin implements Plugin<Project> {
    
    @Override
    public void apply(final Project project) {
        final GradleExtension extension = project.getExtensions().create("gradleplugin", GradleExtension.class, project.getObjects());
        
        final GradleInternal gradle = (GradleInternal) project.getGradle();
        registerBuildService(extension, gradle);
    }

    private void registerBuildService(final GradleExtension extension, final GradleInternal gradle) {
        final BuildEventListenerRegistryInternal registry = gradle.getServices().get(BuildEventListenerRegistryInternal.class);
        final Provider<BuildService> buildServiceProvider = gradle.getSharedServices().registerIfAbsent("build-service", BuildService.class, service -> {
            service.getParameters().getEnabled().set(extension.getEnabled());
            service.getParameters().getFoo().set(extension.getFoo());
        });
        registry.onOperationCompletion(buildServiceProvider);
    }
}

BuildService

public abstract class BuildService extends AbstractBuildService implements BuildService<BuildService.Params>, BuildOperationListener, AutoCloseable {
    
    public interface Params extends BuildParameters {
        Property<Boolean> getEnabled();
        Property<String> getFoo();
    }
    
    private static final Logger LOGGER = Logging.getLogger(BuildService .class);
    
    @Override
    public void close() throws Exception {
        LOGGER.lifecycle("isEnabled: " + getParameters().getEnabled().get());
        LOGGER.lifecycle("getFoo: " + getParameters().getFoo().getOrElse("unset"));
     }
}

Output:

> Task :gradleplugin:assemble UP-TO-DATE
isEnabled: true
getFoo: unset

BUILD SUCCESSFUL in 346ms

You do various bad things with internal classes and also implement the wrong listener type.
You should reread the Shared Build Services documentation.
Don’t use GradleInternal and BuildEventListenerRegistryInternal but simply inject BuildEventsListenerRegistry.
Don’t implement BuildOperationListener, but OperationCompletionListener.
Do not use registry.onOperationCompletion, but registry.onTaskCompletion.

Now your build service should probably be seen as used until the build finished and your close method properly be called in the end of the build and seeing the configured values.

Thanks @Vampire, I reworked all my bad things :slight_smile: and it works now.
Sadly with less info than from parameters of BuildOperationListener

1 Like

What info do you miss?

Actually I’m just missing the info about when the entire task finished as RunRootBuildWorkBuildOperationType.
Other things I found with DefaultTaskSuccessResult and DefaultTaskSkippedResult

I see.
I guess you just need to take the start time of the first OperationResult and have roughly the same information.