Hi, I have a simple plugin I’ve written. It works...
# plugin-development
c
Hi, I have a simple plugin I’ve written. It works great when used in my automated test and when used in single-project builds. Things get weird when it’s used in a subproject of a multi-project build. My plugin’s
apply
method looks like this:
Copy code
class SslCertGenPlugin implements Plugin<Project> {

    void apply(Project project) {
        Security.addProvider(new BouncyCastleProvider())
        CertificateExtension certificate = project.extensions.create( "certificate", CertificateExtension )
        project.tasks.register("generateCert", X509Certificate, project, certificate )
    }
}
However, in a multi-project build where the plugin could be applied to a subproject this doesn’t work. It assumes the plugin is defined at the root project even when it’s not. So how does a plugin know the difference? When it was applied at the root vs a subproject?
t
So how does a plugin know the difference? When it was applied at the root vs a subproject?
You can check things like
project.rootProject == project
,
project.path == ":"
, or
project.parent == null
c
So would the Project instance passed to the plugin be the root Project instance always or is it the instance of the subproject since both root projects and subprojects both use Project instances?
t
The passed project is the one you apply the plugin on, so can be the root project or a subproject. And if you apply the plugin in several projects (root or subprojects), there'll be one instance of the plugin per project, with its
apply()
called for each of them.
e
Copy code
Security.addProvider(new BouncyCastleProvider())
that part seems like not a good idea, even if you can tell if you're applied to the root project or not
depending on which projects' buildscripts classpaths your plugin is loaded in, there may be multiple different BouncyCastleProvider classes
it would be fine if you could change the
X509Certificate
task to locally use BC instead of relying on it globally
c
I’ll have to look into how you can use a security provider without registering it globally. I’m not sure you can do that.
But I could check to see if it’s already there and just not do it.
e
worker with process isolation would definitely be able to do it "locally". otherwise you can just directly use the bouncycastle classes, e.g.
new org.bouncycastle.jcajce.provider.asymmetric.x509.CertificateFactory()
instead of
java.security.cert.CertificateFactory.getInstance("X.509")
c
Most of what I’m doing directly is with BC classes as Java Security doesn’t support creating certs directly. I commented out the registration and re-ran my test. They passed so I guess I’ll remove that line.
v
Even with being able to determine whether applied to root- or subproject, in 98.7% of the cases it is not a good idea, but a sign that you do something wrong. 😄 So yeah, if you anyway use BC directly and not as SP, there is not much value in registering it as SP.
worker with process isolation would definitely be able to do it "locally".
... no those processes also are reused if possible, unless you somehow trick Gradle to always create a new one
c
I did find some issues that were using things from the root project that were tripping things up. I fixed those, but registering BC does seem important in some environments because they will lack BC then the exceptions start.
e
classpath for the worker would make that process incompatible with others… so I would have thought
v
Well, but still very flaky thing to do, just like using any other static state is a bad idea. As a small example, SpotBugs heavily overuses static state in its implementation. This makes it practically impossible to use it embedded in-process. Some time ago, the SpotBugs Gradle Plugin was not aware of that and called it in-process. By that it happened that build of project A influenced build of project B that was running on the same Gradle daemon as SpotBugs had kept static state that was reused and thus produced wrong results and sometimes warnings and sometimes errors. Now the plugin uses Worker API to parallelize work but then calls SpotBugs out-of-process so that this static state cannot be carried over. Registering a security provider from an arbitrary Gradle plugin is most probably similar dangerous.
e
if it doesn't then actual process isolation will involve javaexec :-/
v
classpath for the worker would make that process incompatible with others… so I would have thought
Why is the classpath different? It can be the same, especially when doing action from the same plugin repeatedly.
Also, are you sure new classpath means new process? Like you can have classloader isolation in-process, you could maybe have classloader isolation within the worker process and thus reuse workers for differen classpaths. I don't remember which way it is.
e
I mean, their plugin could move BC to the worker classpath. if other workers aren't doing that, then it'd be different
v
Comment from `WorkerDaemonStarter`:
// For flat classloaders, we include the work classpath along with the WorkerDaemonServer implementation.
// As a consequence of a flat classloader, the work is able to see the classes of the WorkerDaemonServer
// at runtime.
// This is fine for internal work, but for user-provided work, we serialize the work classpath and load
// it on the worker side.
// We primarily use a flat classloader for Java compilation workers, as using a hierarchical classloader
// caused performance regressions. The Java compiler seems to hammer the classloader, and performance
// is better with a flat classloader. A hierarchical classloader should be preferred when classloader
// performance is not a concern.
especially line 4 and 5
process isolation is not as isolated as one might assume sometimes 🙂
e
ah well then. there's definitely some process fork options that can't be handled without a new process though, does Gradle ignore them?
v
ah well then. there's definitely some process fork options that can't be handled without a new process though, does Gradle ignore them?
Probably not. This is probably handled like with the Gradle daemon args. If those are not equal, the worker is not compatible and is not reused. So you have to check which properties those are and then set one of them to some UUID value, maybe the tmp dir property or something like that. That is what I referred to above as tricking Gradle into not reusing the processes.