I want to write custom Lombok Annotation handlers. I know http://notatube.blogspot.de/2010/12/project-lombok-creating-custom.html. But the current lombok jar file does not contain many .class files, but files named .SCL.lombok instead.
I found, the .SCL.lombok files are the .class files, the build script of Lombok does rename them while generating the jar file, and the ShadowClassLoader is capable of loading these classes -- and the acronym SCL seems to come from this. It seems the reason for this is just to "Avoid contaminating the namespace of any project using an SCL-based jar. Autocompleters in IDEs will NOT suggest anything other than actual public API."
I was only able to compile my custom handler by
unpacking the contents of the lombok.jar
renaming the .SCL.lombok files to .class
adding the resulting directory to the compile classpath
In addition, to be able to use my custom handler, I needed to create a new fat jar containing both the lombok classes and my custom handler. The custom lombok class loader essentially prevents adding custom handlers in other multiple jars.
Is this the only way to extend Lombok? Or am I missing something?
I am using the following buildscript
apply plugin: 'java'
repositories {
jcenter()
}
configurations {
lombok
compileOnly
}
def unpackedAndRenamedLombokDir = file("$buildDir/lombok")
task unpackAndRenameLombok {
inputs.files configurations.lombok
outputs.dir unpackedAndRenamedLombokDir
doFirst {
mkdir unpackedAndRenamedLombokDir
delete unpackedAndRenamedLombokDir.listFiles()
}
doLast {
copy {
from zipTree(configurations.lombok.singleFile)
into unpackedAndRenamedLombokDir
rename "(.*)[.]SCL[.]lombok", '$1.class'
}
}
}
sourceSets {
main {
compileClasspath += configurations.compileOnly
output.dir(unpackedAndRenamedLombokDir, builtBy: unpackAndRenameLombok)
}
}
tasks.compileJava {
dependsOn unpackAndRenameLombok
}
dependencies {
compile files("${System.properties['java.home']}/../lib/tools.jar")
compile "org.eclipse.jdt:org.eclipse.jdt.core:3.10.0"
compile 'javax.inject:javax.inject:1'
lombok 'org.projectlombok:lombok:1.16.6'
compileOnly files(unpackedAndRenamedLombokDir)
}
In the meantime Reinier Zwitserloot created a new git-branch sclExpansionUpdate, that contains an updated version of the ShadowClassLoader:
ShadowClassLoader is now friendlier to trying to extend lombok.
Your (separate) jar/dir should have a file named
META-INF/ShadowClassLoader. This file should contain the string
'lombok'. If you have that, any classes in that jar/dir will be loaded
in the same space as lombok classes. You can also rename the class
files to .SCL.lombok to avoid other loaders from finding them.
I guess this did not yet make it into the main branch because it certainly has not been tested that much - I just tried it out for myself and it contains a little bug that prevents loading the required META-INF/services from extensions. To fix it you should replace two method calls to partOfShadow with inOwnBase:
[... line 443]
Enumeration<URL> sec = super.getResources(name);
while (sec.hasMoreElements()) {
URL item = sec.nextElement();
if (!inOwnBase(item, name)) vector.add(item); // <<-- HERE
}
if (altName != null) {
Enumeration<URL> tern = super.getResources(altName);
while (tern.hasMoreElements()) {
URL item = tern.nextElement();
if (!inOwnBase(item, altName)) vector.add(item); // <<-- AND HERE
}
}
I tested it with the above fix and it seems to work fine (not tested much though).
On a side note: with this new extension mechanism, it is now finally also possible to have the extensions annotation handlers and annotations in a different namespace than "lombok" - nice!
Using the input from this question and from the other answer (by Balder), we managed to put together a custom Lombok annotation handler: Symbok. Feel free to use that as a sample for writing your own.
BTW, instead of writing a custom Lombok handler, you could also implement a javac plugin instead -- it might be simpler.
Related
I created a base image for a Java application using Jib which I want to extend using Jib.
(The Java application provides extensibility by loading additional Jars from the classpath)
In the extending gradle project, I did this:
jib {
....
container {
entrypoint = 'INHERIT'
}
...
}
It allowed me to reuse the entrypoint and args attributes added in the base image but I also want to extend/reuse the base classpath file.
As Jib creates /app/jib-classpath-file in the extending gradle project, the base layer /app/jib-classpath-file is not visible ( I would assume).
To workaround the issue, I added this in extending container configuration block.
extraClasspath = ['/app/libs/*']
Is there an idiomatic way of achieving this in Jib? One option I was thinking is to specify unique classpath files in base and extending projects and
use them like this in the Java command line:
java -cp #/app/jib-BASE-classpath-file #/app/jib-EXTENDED-classpath-file, but I am not finding the option of specifying the classpath file.
What is the recommended way? Thanks
Not a total solution but this shows how to remove the jvm args layer from the child image build altogether.
//build.gradle.kts
buildscript {
dependencies {
classpath("com.google.cloud.tools:jib-layer-filter-extension-gradle:0.1.0")
}
}
...
jib {
...
pluginExtensions {
pluginExtension {
implementation = "com.google.cloud.tools.jib.gradle.extension.layerfilter.JibLayerFilterExtension"
configuration (Action<com.google.cloud.tools.jib.gradle.extension.layerfilter.Configuration> {
filters { filter { glob ="**/jib-*-file" } }
})
}
}
}
This filters out files added in any given layer of a given jib build. If all the files from a layer are filtered out as in this case then the layer is never added to the image.
Sadly this does not let you see the contents of the java-classpath-file in the base image, which you would need to be able to extend it.
References:
https://github.com/GoogleContainerTools/jib/tree/master/jib-gradle-plugin#jib-extensions
https://github.com/GoogleContainerTools/jib-extensions/tree/master/first-party/jib-layer-filter-extension-gradle
https://github.com/GoogleContainerTools/jib-extensions
I know how to ignore classes defined in their own .java files, but not aware of how to ignore inner classes.
For example, I have class A with nested class B:
class A {
...
static class B {
...
}
}
jacocoTestReport keeps checking the coverage when I want to ignore them in jacoco.gradle file with this syntax(learned from this post: How to ignore inner/nested classes with JaCoCo?): (setFrom part is for later versions of Gradle, where classDirectories = files() is deprecated)
apply plugin: "jacoco"
jacoco {
toolVersion = "0.8.3"
}
jacocoTestReport {
afterEvaluate {
classDirectories.setFrom(files(classDirectories.files.collect {
fileTree(dir: it,
exclude: [
"com/example/xxx/*",
"com/example/xxx/A\$.*B*"
])
}))
}
}
($ must be escaped, while in the post there is no need because he uses Maven when I use Gradle)
So, how can I ignore this inner class?
At last I found the answer with several trial-and-failure. Seems that the naming pattern follows compiled Java classes naming convention, as mentioned in the other post, and will not require the . between the outer class and the inner class. So, it should be like A$B. And, there may be some .class interfering(my guess), so I added A$B*(for other normal classes, last * is not needed).
So it becomes:
"com/example/xxx/A\$B*"
I hope there be some documentation about this pattern of exclusion. There is not, yet.
I'm trying to weave Java and Kotlin class files with AspectJ the following way:
android.applicationVariants.all { variant ->
JavaCompile javaCompile = variant.javaCompiler
javaCompile.doLast {
String[] args = ["-showWeaveInfo",
"-1.8",
"-inpath", javaCompile.destinationDir.toString(),
"-aspectpath", javaCompile.classpath.asPath,
"-d", javaCompile.destinationDir.toString(),
"-classpath", javaCompile.classpath.asPath,
"-bootclasspath", project.android.bootClasspath.join(
File.pathSeparator)]
I've made sure that the Kotlin paths are included in the appropriate paths, but no Kotlin classes are processed.
How do you make sure that Kotlin class files get processed this way?
the task is called KotlinCompile, because the Gradle Kotlin DSL differs (here's the examples).
not certain if your script could be directly migrated, but it might be variant.kotlinCompiler.
alternatively, it is possible to hook into those tasks alike:
tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).all {
doLast {
}
}
that .kt is not being processed is because a Java compiler does not recognize them. would assume, that you might require KAPT, in order to have those AspectJ annotations processed.
I am using rackerlabs/gradle-jaxb-plugin to generate classes from schema.
I am using a super interface com.test.xsd.Element. Using xjb I am configuring the interface.
This interface is in my current project src/main/java location.
Jaxb classes generating successfully and Implementing the interface.
But compilation fails with:
com.test.xsd" package does not exist.import com.test.xsd.Element.Element;
how we can add this interface to class path.
Code:
subprojects { project ->
apply plugin: 'org.openrepose.gradle.plugins.jaxb'
dependencies {
jaxb 'com.sun.xml.bind:jaxb-xjc:2.2.7'
jaxb 'com.sun.xml.bind:jaxb-impl:2.2.7'
jaxb 'javax.xml.bind:jaxb-api:2.2.7'
}
def generatedDir = "${project.buildDir}/generated-sources/xjc"
jaxb {
xjc {
args['-npa']
extension=true
destinationDir = "${generatedDir}"
generateEpisodeFiles=false
}
}
sourceSets {
main {
java {
srcDirs += "${generatedDir}"
}
}
}
}
Finlly done with ant task with gradle.
That is very flexible, I think. we can generate classes from multiple xsds and multiple binding files from different location.
org.openrepose.gradle.plugins.jaxb bit difficult to manage
In a custom plugin (or task) I would like to read all compiled classes (preferrably those that have changed from last compilation) with a classloader so that I'll be able to use reflection on them.
Is that possible?
1) It would be great to have a cook right after a Java class was compiled so that I could read it, but I found no way to do this.
2) I'm thinking of something like this ...
compileJava.doLast {
ClassLoader parent = getClass().getClassLoader();
GroovyClassLoader loader = new GroovyClassLoader(parent);
// retrieve all class files
// for each class file, loader.parseClass(classFile)
}
In a gradle script getClass().getClassloader() will get you the classloader of the gradle script. This will NOT contain the compiled classes or compile/runtime jars. I think you want to do something similar to:
Collection<URL> urls = sourceSets.main.runtimeClasspath.files.collect { it.toURI().toURL() }
Classloader parent = new URLClassLoader(urls.toArray());
If you want to only act on the classes that have changed you are best to do do that in an incremental task