This message was deleted.
# plugin-development
s
This message was deleted.
m
That's basically the only way. Except that it's not reliable, since a plugin can also be applied in an
afterEvaluate
, so potentially, your code executes before. In practice it's unlikely.
b
yeah, agreed it’s unlikely and that should be good enoughf or my case, just feels… unfortuante. but i get why it’s effectively intractable.
m
it would be if there was a configuration phase where it's not allowed to register more plugins
1
f
Inverting the logic should work at least a little more reliable. Basically configure whatever needs to be configured and when the plugin is applied "unconfigure" it.
Copy code
// do thing
project.withPlugin("org.springframework.boot") {
  // undo thing
}
m
unconfiguring is not always trivial though
you typically don't want to unset things that the user might have configured explicitly
f
Indeed.
b
agreed, don’t want to force unset
also, not sure how i would. my goal is to exclude certain dependencies for non-executable modules (i.e. our rest api clients libs really need to stop accidentally publishing logging implementations) - so i don’t know how it’d walk back an exclude. maybe possible, but ugly.
basically i want:
Copy code
if(module isn't executable - i.e. doesn't have spring boot plugin applied) {
  exclude a bunch of logging implementations
}
seems like afterEvaluate is the best option
m
depends
you could have 2 different convention plugins
one for modules with spring boot, the other without
and users would apply one or the other
f
That would also be my recommendation. Create a
library
and
application
plugin. The
library
plugin does what you want, the
application
plugin does not.
m
so you'd be in control
so instead of thinking "is the boot plugin applied?", you'd think in terms of "what is this component type?"
f
Overly smart plugins that work with this kind of detection logic get very complicated very fast. The magic they perform usually also poorly interacts with other plugins and users get even more confused then they already are by Gradle alone.
👍 1
t
agreed with the above conversation, and also https://github.com/gradle/gradle/issues/18848
👍 1
b
yes, i agree that a secondary convention is the way to go. i’m going down that route now
but… i’m not sure how to get around this. in user-library project there is a “bad” declaration like:
Copy code
dependency {
    ...
    implementation "ch.qos.logback:logback-core"
    ...
}
i want to force this to be excluded by applying my plugin which has:
Copy code
dependencies {
    // Exclude any logging implementations from being exposed by library as they can break consumers
    configurations.runtimeOnly {
        ...
        exclude(group = "ch.qos.logback", module = "logback-core")
        ...
    }
}
this seems to work for gradle consumers, but the generated maven pom looks like:
Copy code
<dependency>
      <groupId>ch.qos.logback</groupId>
      <artifactId>logback-core</artifactId>
      <scope>runtime</scope>
      <exclusions>
        <exclusion>
          <artifactId>logback-core</artifactId>
          <groupId>ch.qos.logback</groupId>
        </exclusion>
      </exclusions>
    </dependency>
which… doesn’t actually remove it from a maven consumer.
it’s fine for transitives, but if a consumer directly declares something, i want to have a way to actually not expose it in the generated pom. i assume i need some other gradle facility for that?
i guess maybe i just want to do a REPLACE in all these cases. in my libraries, instead of trying to delete any and all instances of the depednency, i could instead replace them with say
slf4j-api
.
guess not, that doesn’t seem to affect the published pom.
f
What I would do is to actually fail if such a dependency is found. Explaining to the engineer that what they are doing is wrong and that they have to remove the dependency.
Copy code
configurations.runtimeOnly {
    resolutionStrategy.eachDependency {
        if (requested.group == "ch.qos.logback" && requested.name == "logback-core") {
            error("no no")
        }
    }
}
That said, to filter things out from the POM you'd actually want to hook into the
maven-publish
plugin and filter them out there. Coincidently this would solve your library vs application problem as well, because I would assume that nobody packages their applications in form of JARs and uploads them to a Maven repository. 😛
b
that’s not a bad idea, to remove them as transitives and force fail on directs
unforutnately the above resolution strategy approach doesn’t seem to work. it doesn’t get fired when doing even a clean build. but if i use
configurations.all
it fails. but that’s not what i want, i want to be able to have these deps in the test runtime
f
I only used
runtimeOnly
in my example code because it was the one you used in your example code. If you want to capture dependencies introduced by any
main
dependency (
implementation
,
api
, and
runtimeOnly
) then use `runtimeClasspath`:
Copy code
configurations.runtimeClasspath {
    resolutionStrategy.eachDependency {
        if (requested.group == "ch.qos.logback" && requested.name == "logback-core") {
            error("no no")
        }
    }
}
This will always trigger when the dependencies are resolved.
What I noticed while trying it out is that Gradle does not actually fail the build if an exception is thrown from this context. It just states that it could not resolve the dependency. 🤔
v
Did you actually try to build it just called
dependencies
task? The latter does lenient resolving where only failed is printed. If you actually try to build, it should fail if I'm not mistaken.
f
I only executed a sync in IntelliJ. If it fails on build then it might do what Ben is looking for.
b
so i have:
Copy code
dependencies {
    configurations.runtimeClasspath {
        val configName = this.name
        dependencies.configureEach {
            if (isBannedLoggingImpl(this)) {
                throw GradleException(
                    """Logging Implementations must NOT be added to the runtime classpath of shared library modules. 
                            |Please remove dependency declaration on: $configName("${this.group}:${this.name}") 
                        """.trimMargin()
                )
            }
        }
    }
}
and in the consuming project:
Copy code
implementation "ch.qos.logback:logback-core"
    api "ch.qos.logback:logback-core"
    runtimeOnly "ch.qos.logback:logback-core"
all of which i’d expect to fail. but building it doesn’t, it works fine. if i change the plugin to
configurations.all
it does fail.
v
And btw. if you go down the initial approach with
afterEvaluate
and presence check, you might additionally add a
withPlugin
for the absent plugin that throws an exception, so that the build fails if the plugin in question is applied in a later
afterEvaluate
.
b
err, my bad, i changed my approach from what you were talking about originally @Fleshgrinder - because of that issue, it was just reporting it couldn’t find dep
so my final impl that seems to fail for any gradle task i give to the consumer is:
Copy code
configurations.all {
        val configName = this.name

        // We can allow logging impls for any test configurations
        if (!configName.contains("test")) {
            dependencies.configureEach {
                if (isBannedLoggingImpl(this)) {
                    throw GradleException(
                        """GD Error: Logging Implementations must NOT be added to the runtime classpath of shared library modules. 
                            |Please remove dependency declaration on: $configName("${this.group}:${this.name}") 
                        """.trimMargin()
                    )
                }
            }
        }
    }
it’s pretty janky, specifically the skipping based on “test” being in the configuration name (but i do need to make sure logging impl libs can show up at test time)