Kotlin classes not available on variant.javaCompiler.doLast - java

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.

Related

How to define gradle tasks dependency in a task in custom gradle plugin

I'm writing a custom gradle plugin in which I want to have a bunch of common for several of my projects tasks and a sort of a 'main' task to control which of these tasks to turn on.
Regular tasks in the plugin are e.g.:
CopyDockerResourcesTask
CopyContainerFilesTask
PerformAnalysisTask
and the 'main' task is:
BaseProjectTask
so then in the project in build.gradle I'd like to be able to do this:
BaseProjectTask {
copyDockerResources = true
copyContainerFiles = true
performAnalysis = true
}
I want the default behaviour of the plugin to be to not to do anything, only add certain tasks if they are turned on in BaseProjectTask.
I wanted to achieve this with adding task dependency in #TaskAction method of BaseProjectTask:
class BaseProjectTask extends DefaultTask {
private final BaseProjectExtension extension
private final Project project
#Optional
#Input
Boolean copyContainerFiles = false
...
#Inject
BaseProjectTask(Project project, BaseProjectExtension extension) {
this.project = project
this.extension = extension
}
#TaskAction
def execute() {
if (copyContainerFiles) {
project.tasks.assemble.dependsOn(project.tasks.copyContainerFiles)
}
...
}
}
Creating task dependency, this line:
project.tasks.assemble.dependsOn(project.tasks.copyContainerFiles)
doesn't work.
Edit:
My current findings are that defining task dependency in #TaskAction is too late as this is execution phase. I could do it in the constructor (this way it works) but its too early as property copyContainerFiles isn't set yet.
Does anyone know a way of adding code in the task class that would be fired in the configuration phase? I think this is what I'm missing.
You need to configure task dependencies during the build configuration phase, as you surmised.
It's not possible to do it in the #TaskAction method. It's fundamental to the way Gradle works that it needs to know how tasks depend on each other before it starts executing the build. That allows Gradle to do some useful things, such as only executing the tasks that are not up to date, or working out what tasks will execute without actually executing them.
In general, tasks should not be aware of one another1.
When you are trying to do this in a plugin using values in a project extension, you must wait until after the project has evaluated so that the build script code executes first. You can do this with project.afterEvaluate()2.
So you can do the following (using Kotlin DSL3):
project.afterEvaluate {
tasks.register("baseTask") {
if (baseProjectExtension.copyDockerResources)
dependsOn(tasks.getByName("copyDockerResources"))
if (baseProjectExtension.copyContainerFiles)
dependsOn(tasks.getByName("copyContainerFiles"))
if (baseProjectExtension.performAnalysis)
dependsOn(tasks.getByName("performAnalysis"))
}
}
1See How to declare dependencies of a Gradle custom task?
2See https://docs.gradle.org/current/userguide/build_lifecycle.html#sec:project_evaluation
3What I am familiar with. Hopefully not too much trouble to convert to Groovy.

How can I run SpotBugs using Java for static code analysis?

I was working on injecting of groovy scripts dynamically in Java. So before executing those scripts, I want to get sure of that they do not have potential bugs using SpotBugs (static code analyzer).
Here is the Psuedo-Code:
Here it should return the infinite loop bug.
String script = "class Hello { static void main(String []args) { def i = 0; while ( i <= 0) { i = i - 1; } } } ";
List<Bugs> bugs = SpotBugs.getBugs(script);
if (bugs == null) {
execute(script);
}
So how to do the SpotBugs.getBugs(script) using java, the input script will not be hard-coded as in above example, but will be dynamically fetched.
Easiest API
The easiest way is to write the compiled code to class files (in a temp directory if needed). By having compiled class as file, you will be able to use the FindBugs class which provide an API to configure the scope and rules without playing with internal classes that are subject to changes.
Groovy dynamic (default) vs static compiling
However, the main obstacle you'll face is that groovy bytecode is too obfuscated for SpotBugs. For the call to function abc(), you will not see an invoke to method abc in the bytecode. It will be a reference to a global functions map that is created at runtime. Groovy has a mode to compile to a less dynamic format. This mode does not allow functions to be created at runtime. You can check the configuration to instruct the compiler for the static mode in this test repo: https://github.com/find-sec-bugs/find-sec-bugs-demos/tree/master/groovy-simple. This is, however, a Gradle compilation not a programmatic API that received a String as code.
seems like SpotBugs should run using maven, which means it will package and include only the groovy scripts that are valid.
hence, you will not need to check before execution.

Groovy global storing of closures from different scripts

Is it to possible to store closures from different groovy scripts?
Let's say I have some kind of class that should store the closures:
package com.test
class ClosureContainer {
static closures = [:]
static def AddClosure(String name, Closure cl) {
println "Adding closure named ${name}"
closures[name] = cl
}
}
And then I would like to have groovy scripts that would add closures to it:
import com.test.ClosureContainer as container
container.AddClosure("Interesting stuff", {
println "I'm doing some interesting stuff"
})
And later, I should be able to call it like:
def closureCode = ClosureContainer.closures["Interesting stuff"]
closureCode()
What is the best approach to do it in Groovy? I'm not sure how to handle invoking of the scripts because classes are generated from these scripts.
I'm able to create instances of the scripts during runtime, but I'm not able to retrieve list of the classes/scripts without hardcoding it.
UPDATE:
Let's suppose I have testScript.groovy in package com.test.scripts that adds a few closures to the container. I tried to let the gradle generate classes from the scripts and create instance in the code like this:
def className = Class.forName("com.test.scripts.testScript")
def instance = className.newInstance()
instance.run()
And I'm hardcoding the testScript name. But there will be a lot of scripts and I should be able to retrieve it dynamically.
The recommended way to run groovy script is to use GroovyScriptEngine:
String[] path = new String[] { "." };
GroovyScriptEngine engine = new GroovyScriptEngine(path);
engine.run("yoursriptname.groovy", new Binding())
NB: yoursriptname should be the path of your script relative to path.
If you want to pass bindings (arguments and then get the results) you have to use Binding.

Gradle Custom Plugins: Read Compiled Java Class

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

Writing custom Lombok Annotation handlers

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.

Categories

Resources