How do I undo meta class changes after executing GroovyShell? - groovy

For example, if I execute a Groovy script, which modifies the String meta class, adding a method foo()
GroovyShell shell1 = new GroovyShell();
shell1.evaluate("String.metaClass.foo = {-> delegate.toUpperCase()}");
when I create a new shell after that and execute it, the changes are still there
GroovyShell shell2 = new GroovyShell();
Object result = shell2.evaluate("'a'.foo()");
Is there a way to undo all meta class changes after executing the GroovyShell? I tried
shell1.getClassLoader().clearCache();
and
shell1.resetLoadedClasses();
but that did not make a change.

You can use
GroovySystem.metaClassRegistry.removeMetaClass(String.class);
to revert all changes made to the String meta class.
Alternatively you could only change the meta class of a certain String instance, thus not all instances of String would be affected.

You can use MetaClassRegistryCleaner too.
Before doing some metaclass changes, you can do
MetaClassRegistryCleaner registryCleaner = MetaClassRegistryCleaner.createAndRegister()
GroovySystem.metaClassRegistry.addMetaClassRegistryChangeEventListener(registryCleaner)
And when you want to reset the metaclass changes to the state they were earlier.
You can do
registryCleaner.clean()
GroovySystem.metaClassRegistry.removeMetaClassRegistryChangeEventListener(registryCleaner)
This way you can reset all the metaclass changes made during the duration.

I realise that this is a somewhat older question, but it's the first result on Google when I was searching for exactly the same issue.
The solution I chose was to put groovy into a new classloader (by using plexus-classworlds), so when the script is finished, the classloader is disposed (and so any changes to the metaclass are also disposed).

Related

How to declare a constructor or extends a class of a groovy script?

I am working on a shared library for Jenkins, and I want to access some utilities methods between some classes, but not all of them, thus I have established some statements:
I would like to avoid using static methods, since it does not access pipeline steps directly, and passing the pipeline instance every call would be a pain;
I would like to avoid a singleton as well, or prefixing every method call with the util class' instance;
Since it is not supposed to be shared between all classes I would like to avoid putting every method as a file on vars/ special directory, but I would like a similar behavior;
Despite extending the class would be a anti-pattern, it would be acceptable, though I would like to avoid the verbose Java syntax for declaring the class the same name as the file, once it is implicit in groovy;
This question does solve my problem partially, although there are issues with serialization, I noted that when I use checkpoint and some build is resumed from some stage, the instance loses all extra methods.
This other question would have helped me fix the serialization issue, however the author seems the have solved the root cause of his problem using a way that is not the original question titled for.
Is there a way to extends a implicit script class in groovy without using the class NameOfFile extends SomeOtherClass { put every thing inside this block } syntax? And without working with inner-class?
Or else, is there a way to declare a constructor using the script groovy syntax analogue as the previous question?
Or even, is there a way to change the serialization behavior to install the extra methods again after unserializing?
Appendix
The script syntax works more-or-less like this:
Consider the content of file src/cicd/pipeline/SomePipeline.groovy:
package cicd.pipeline
// there is no need to wrap everything inside class SomePipeline,
// since it is implicit
def method() {
// instance method, here I can access pipeline steps freely
}
def static otherMethod() {
// static method, here it is unable to access pipeline steps
// without a instance
}
#groovy.transform.Field
def field
def call() {
// if the class is used as method it will run
this.method()
SomePipeline.otherMethod() // or simply otherMethod() should work
this.field = 'foo'
println "this instance ${this.getClass().canonicalName} should be cicd.pipeline.SomePipeline"
}
// any code other than methods or variables with #Field
// annotation will be inside a implicit run method that is
// triggered likewise main method but isn't a static one
def localVar = 'foo'
println "It will not execute on constructor since it is on run: $localVar"
println "Method: ${org.codehaus.groovy.runtime.StackTraceUtils.sanitize(new Throwable()).stackTrace[0].methodName}"
println "this instance ${this.getClass().canonicalName} should be cicd.pipeline.SomePipeline"
If I was going to use the Java verbose syntax I would have to wrap almost everything inside a class SomePipeline which is implicit in groovy, this is the script syntax I want to keep.
I realised that this.getClass().superclass.canonicalName when outside Jenkins pipeline is groovy.lang.Script and when inside pipeline is org.jenkinsci.plugins.workflow.cps.CpsScript and based on this resource I was able to elaborate the following solution:
abstract class CustomScript extends org.jenkinsci.plugins.workflow.cps.CpsScript {
public CustomScript() {
// do something here, it will always execute regardless
// serialization, and before everything
}
}
#groovy.transform.BaseScript CustomScript baseScript
That is it, worked as expected! Of course you can elaborate this solution better in order to reduce repeating and avoid inner-classes, but I will leave it for your imagination.

Eclipse Groovy Plugin: A transform used a generics containing ClassNode Entry <List, Iterable> for the method ... directly

What should one do with this?
protected List<BasePropCont> getChildren( boolean updCacheOn = false,
Entry< List, Iterable > _subIterEntry = null )
{
return null
// orig code removed since it seems not to matter
}
show the following as an error:
Groovy:A transform used a generics containing ClassNode
Entry <List, Iterable>
for the method
protected java.util.List getChildren(boolean updCacheOn, Entry _subIterEntry) { ... }
directly.
You are not supposed to do this. Please create a new ClassNode referring to the old
ClassNode and use the new ClassNode instead of the old one.
Otherwise the compiler will create wrong descriptors and a potential
NullPointerException in TypeResolver in the OpenJDK.
If this is not your own doing, please report this bug to the writer of the transform.
hah ... the solution was to watch the auto-handled imports with the editors Save Action. Somehow the Plugin removed some imports (maybe in a state where there were compile errors present while coding) and among them was
import java.util.Map.Entry
:-/
Manually adding it solves it.
Had similiar problems with other "disappearing" imports already.
(The actual cause for this strange error may be some (of many found) Entry classes from the default Groovy imports or within the scope)

Jenkins Workflow Error When Accessing Variable Inside a Closure

I'm using some groovy inside a Jenkins Workflow script that includes a closure.
def newMarkup = new StreamingMarkupBuilder().bind {
mkp.yield(xml)
}.toString()
As I understand it mkp should be a variable made available inside closures when using StreamMarkupBuilder, however when I try and run this I get the error,
groovy.lang.MissingPropertyException: No such property: mkp for class: WorkflowScript
So my question is why doesn't Jenkins recognise that mkp is a property of the StreamMarkupBuilder class and not the workflow script?
Sounds like a bug in groovy-cps. Try encapsulating your logic inside a method marked with the #NonCPS annotation. If it starts working, then groovy-cps is to blame, and you can file a bug in the Jenkins JIRA in the workflow-plugin component with steps to reproduce, although I suspect even with the MissingPropertyException fixed the code would still not run due to JENKINS-26481.

Groovy - Add property or method dynamically to metaClass of this

I want to dynamically add a field and methods to the metaClass of my current object. I tried
this.metaClass.testProp = "test"
to add a field called testProp. However, I get
groovy.lang.MissingPropertyException: No such property: testProp for class: groovy.lang.MetaClassImpl
When I do the same on class level, e.g. adding testProp to the class and not directly to the object
Process.metaClass.testProp = "test"
it works, but my object does NOT inherit the field. Any ideas or pointers in the right direction would be greatly appreciated.
Short answer:
Process.metaClass.testProp = "test"
this.metaClass = null
assert this.testProp == "test"
Long answer:
I assume, there are 3 things that make you a problem here. The first one is that there is a global registry for meta classes. The second is that Groovy allowed per instance meta classes (and this is the default for Groovy classes). And the third one is that the default meta class does not allow runtime meta programming.
So what happens if you do runtime meta programming is that the default needs to replaced by ExpandoMetaClass (EMC), to allow for that. But since there is a per instance meta class logic, this replacement may not affect all instances. The meta class of the instance is taken from the global registry the first time it is used. Process.metaClass.testProp = "test" changes the global saved one. Any instance that already has a meta class, will not know of the change and thus not know the property. this.metaClass.testProp = "test" will change only the meta class of the current instance, other instances may not know of it. You can use this.metaclass = null to reset it. And it will again request the meta class from the global registry.If you did the per instance change, your change is away. If you did the global change, your change is visible now.
All this counts if you work with the default meta class in Groovy (MetaClassImpl). If you do a change like a in your code, the new meta class will be ExpandoMetaClass (EMC). This meta class allows for changes, so further changes will not cause a replacement of the meta class. To ensure all instance take an ExpandoMetaClass from the beginning you normally have some kind of setup code like this: ExpandoMetaClass.enableGlobally()
So to solve your question I could simply do this
Process.metaClass.testProp = "test"
this.metaClass = null
assert this.testProp == "test"
If you used ExpandoMetaClass.enableGlobally() in some setup code before, you can leave the reset code for the meta class out (or if the global meta class is the same as the one for "this" and if it is EMC). Grails for example uses EMC as default

GroovyClassLoader Hierarchy and CompilerConfiguration

Problem:
Create GroovyClassLoader GCLA with explicitly set CompilerConfiguration
Create another GroovyClassLoader GCLB with different CompilerConfiguration that sets the BaseScriptClass and uses GCLA as parent
-> a script loaded in GCLB will not have the BaseScriptClass set correctly (but uses the potentially set basescript of GCLA)
Long Story:
My application uses four Groovy scripts to allow customization of certain actions. Each script provides a small DSL, where the DLSs are different for each script. When the action is excecuted in the application, the corresponding script will be called. Additionally some common groovy scripts can be loaded at application startup for storing of common data or definition of helper functions.
The scripts are heavily typechecked at the start of the application.
My first approach was to have one GroovyClassLoader which first loads the common groovy scripts and then does a loadClass for each of the scripts. The DSL is created by simply adding corresponding elements to the binding of each script befor the script is run. The GroovyClassLoader uses a CompilerConfiguration with TypeCheck-Extensions to typecheck the scripts.
This works quite well but has two severe limitations:
the typechecking has to be performed based on the name of the script (because the DSLs for each script is different)
the typechecking script gets quite complicated because it has to handle all four DSLs
To get rid of the named limitations I tried to use a distinct GroovyClassLoader (with my actual ClassLoader as parent) for each of the script (i. e. one per DSL) and set a corresponding ScriptBaseClass which provides the functionality for the DSL. This works very well: the typechecking code is reduced drastically, I can have distinct typechecking scripts for each DSL and I don't have to mess with the name of the script.
But I don't get this to work with the common groovy scripts. As soon as I try to use the GroovyClassLoader of the common scripts as parent for the GroovyClassLoader of the DSL scripts, the DSL no longer gets the correct BaseScriptClass although it's explictly set in the CompilerConfiguration. It seems that the child GroovyClassLoader uses the BaseScriptClass of the parent GroovyClassLoader.
Any ideas of how to get this working?
UPDATE
In my original post I did not mention that I'm using scala. Actually I tried the following code snippet:
val rootConfig = {
val cf = new CompilerConfiguration(CompilerConfiguration.DEFAULT)
cf.setSourceEncoding("UTF-8")
cf.addCompilationCustomizers(new ASTTransformationCustomizer(classOf[CompileStatic]))
cf
}
val childConfig = {
val cfg = new CompilerConfiguration(CompilerConfiguration.DEFAULT)
cfg.setSourceEncoding("UTF-8")
cfg.setScriptBaseClass("NonExisting")
cfg.addCompilationCustomizers(new ASTTransformationCustomizer(classOf[CompileStatic]))
cfg
}
val rootGCL = new GroovyClassLoader(getClass.getClassLoader, rootConfig, false)
val childGCL = new GroovyClassLoader(rootGCL, childConfig, false)
This does not work and the child ClassLoader seems to ignore the CompilerConfiguration completely: it does not complain about the non-exiting BaseScriptClass and if I switch off the static-compilation in the rootConfig it will not even perform the static checks.
If I move the ClassLoader creation to a Java file everything works as expected
GroovyClassLoader has several constructor. For example one for ClassLoader and one for GroovyClassLoader. The GroovyClassLoader version will take the configuration from it. But if you do for example new GroovyClassLoader((ClassLoader) parent) or go with giving the configuration yourself. And for that it is even enough to give null as configuration. The constructor logic will take CompilerConfiguration.DEFAULT then to configure the GroovyClassLoader: new GroovyClassLoader(parent,null) and if you want to be very sure no strange paths are added to the GroovyClassLoader, use the variant with a boolean new GroovyClassLoader(parent,null,false)

Resources