For project isolation, we are forced to avoid allp...
# plugin-development
j
For project isolation, we are forced to avoid allprojects and subprojects blocks. So if we want our plugin to be applied to all projects, the only way is the user must apply it to every single project? I would like to see strategies about how to handle what multi project plugins are doing but migrated to project isolation.
m
The strategy is to have convention plugins. Each project should apply at least one plugin, which says what it is. This plugin would basically do what your all projects block does.
1️⃣ 1
f
There are still situations where one needs a global thing that child things can access (e.g. https://github.com/nebula-plugins/gradle-resolution-rules). Something that I found to work (and what I'd like to contribute to this project) is a settings plugin. There we have access to
gradle.beforeProject
and within it we can auto-register (especially with
withPlugin
) things on the project without breaking isolation (at least so far, not sure what changes are upcoming). What's missing from settings plugins is the generation of accessors for extensions etc. This makes them tedious to use for global configuration.
v
There are still situations where one needs a global thing that child things can access
Sounds like the need for a shared build service
f
Not really in the case of the Netflix plugin, no. It's interacting with the configurations and dependencies exclusions and alignments and thus needs to hook into them. I think that they found a nice workaround, but it's taxing on users. Since you have to understand that you have to apply the plugin to the root project and that child projects access the root project. Something that a settings plugin can automatically do. That said, it would be even nicer if Gradle would provide APIs for this. Just like we now have them for JaCoCo which is a similar issue. A build service does not help here.
j
@melix and if you need to access to other projects outputs, for example merging some outputs in child build dirs into the root build dir, the only way is copying the jacoco aggregation approach?
I am seeing a pattern of forcing to apply a plugin in the root project, for example in Kover and nexus publish plugins. I have a plugin where I am doing that too and I would like to know how those plugins would migrate to support project isolation, cc @Marc Philipp
c
In my plugins I have two tactics. For “concentration points” I use shared build services. For enforcement of build use I have this in the top level build gradle:
Copy code
allprojects {
  afterEvaluate { Project p ->
    if (p.getPlugins().hasPlugin(BasePlugin)) {
      assert p.getPlugins().hasPlugin(BaseConvention)
    }
  }
}
where the BaseConvention is my convention plugin and BasePlugin is the Gradle one.
The BaseConvention has the following:
Copy code
project.afterEvaluate(p -> p.getPlugins().configureEach(plugin -> {
  Collection<Class<? extends ConventionPlugin>> conventions = KNOWN_CONVENTIONS.get(plugin.getClass());
  if (conventions != null && conventions.stream().noneMatch(convention -> project.getPlugins().hasPlugin(convention))) {
    throw new IllegalStateException("Cannot apply " + plugin.getClass() + " directly. You must use a registered convention plugin: " + conventions);
  }
}));
where
ConventionPlugin
is my own invention:
Copy code
public interface ConventionPlugin<T, P extends Plugin<? extends T>> extends Plugin<T> {
  Class<P> isConventionFor();
}
along with:
Copy code
static {
  for (ConventionPlugin<?, ?> conventionPlugin : ServiceLoader.load(ConventionPlugin.class, BaseConvention.class.getClassLoader())) {
    KNOWN_CONVENTIONS.computeIfAbsent(conventionPlugin.isConventionFor(), k -> new ArrayList<>()).add(conventionPlugin.getClass());
  }
}