ResolutionStrategy / DependencySubstitution with detached-Configurations

I am trying to design a DSL that allows the user to specify some dependency substitutions that should apply to all configurations. At the moment I do:

project.getConfigurations().all(
        (configuration) -> config.execute( configuration.getResolutionStrategy() )
);

This works great… until I create detached Configurations. I had thought that the ConfigurationContainer#all call would apply to detached configs as well, but apparently not.

So I would need to keep those substitutions around so I can apply them later when I create the detached Configurations. Which would be fine except that I have no idea how to create a ResolutionStrategy myself to be able to “keep” those substitutions.

Is it kosher to keep Closures or Actions around as part of plugin / task state?

Other ideas?

Thanks

Specifically, no by design. The ConfigurationContainer interface inherits all from DomainObjectCollection, which operates on all objects in the collection. The concept of a detached Configuration is specifically one that is not added to that container. It’s all objects in that instance of the container.

You don’t show what config is here, but if it works, you can use it exactly the same way as you’re doing with all. You’d just call it on every detached configuration when you create it rather than it happening automatically as each Configuration is added to the container. For example,

Configuration detachedConfiguration = project.getConfigurations().detachedConfiguration();
config.execute( detachedConfiguration.getResolutionStrategy() );
// do whatever else with the detached configuration

I’m not sure I understand what you’re thinking about “keeping it around” any more than you’re doing already. It’s a little bit hard to explain without a more concrete full example, but in the lifecycle, I wouldn’t necessarily think you would be keeping references to Closure / Action instances vs. declaring and using them.

config in the example is an Action.

“keep around” == hold references to

These substitutions are defined before I create any of the detached Configurations. So to apply the substitutions to these new detached Configurations I’d have to keep around (hold reference to) the Closure or Action so I can apply it later.

So put more high-level… how would you apply substitutions to detached Configurations as you create them when those substitutions have been previously defined?

How do you get from the DSL you were working on in other posts to the Action that’s shown here? I would normally expect an extension registered by your plugin that would end up holding the domain objects that represent the substitutions configured in the build script. There might be an Action involved in configuring these objects and you might end up executing one to configure it, but I wouldn’t necessarily expect that an Action given to the resolutionStrategy is directly what is created in the DSL.

So here is an example of the DSL fragment:

jakartaTransformation {
    ...

    dependencyResolutions {
        dependencySubstitution {
            substitute module( project.jpa ) with module( project.jakartaJpa )
            substitute module( project.jta ) with module( project.jakartaJta )
        }
    }

    shadow('org.hibernate.orm:hibernate-core:6.0.0.Alpha7') {
        runTests( 'org.hibernate.orm:hibernate-core:6.0.0.Alpha7:tests' ) {
            include 'org/hibernate/orm/test/**'
        }
    }
}

The extension registered under jakartaTransformation is (short-form):

interface TransformerSpec {
    void dependencyResolutions(Closure config);
    void dependencyResolutions(Action<ResolutionStrategy> config);

    void shadow(Object shadowSource, Closure config);
    void shadow(Object shadowSource, Action<ShadowSpec> config);
}

interface ShadowSpec {
	void runTests(Object testsDependencyNotation, Action<ShadowTestSpec> config);
	void runTests(Object testsDependencyNotation, Closure config);
}

The shadow and runTests blocks each create a detached configuration. But as you can see, they are created at a time when I no longer have reference to the substitutions.

Now granted, I could do this (which is effectively the same):

jakartaTransformation {
    ...

    shadow('org.hibernate.orm:hibernate-core:6.0.0.Alpha7') {
        dependencyResolutions {
            dependencySubstitution {
                substitute module( project.jpa ) with module( project.jakartaJpa )
                substitute module( project.jta ) with module( project.jakartaJta )
            }
        }

        runTests( 'org.hibernate.orm:hibernate-core:6.0.0.Alpha7:tests' ) {
            include 'org/hibernate/orm/test/**'

            dependencyResolutions {
                dependencySubstitution {
                    substitute module( project.jpa ) with module( project.jakartaJpa )
                    substitute module( project.jta ) with module( project.jakartaJta )
                }
            }
        }
    }
}

Though hopefully you can see why I’d rather refactor that into commonality.

The only option I could come up with so far was to keep the substitution Closure/Action around to use later:

class TransformerSpecImpl implements TransformerSpec {
    ...

    private final List<Substitutions> substitutions = new ArrayList<>();

    @Override
    public void dependencyResolutions(Closure config) {
        project.getConfigurations().all(
                (configuration) -> ConfigureUtil.configure( config, configuration.getResolutionStrategy() )
        );

        substitutions.add(
                (resolutionStrategy) -> ConfigureUtil.configure( config, resolutionStrategy )
        );
    }

    public void applyDependencyResolutionStrategy(Configuration configuration) {
        for ( Substitutions substitution : substitutions ) {
            substitution.applySubstitutions( configuration.getResolutionStrategy() );
        }
    }

    ...

    @FunctionalInterface
    public interface Substitutions {
        void applySubstitutions(ResolutionStrategy resolutionStrategy);
    }
}

Later, when creating the detached Configurations I can access these substitutions and apply them:

    sourceDependencyConfiguration = getProject().getConfigurations().detachedConfiguration( ... );
    applyDependencyResolutionStrategy( sourceDependencyConfiguration );

It works. Just not sure whether holding on to those Closures/Actions references has negative side effects. Hence my questions