How to access a dependency of a dependency in Java/Gradle? - java

I'm trying to access a library that is implemented in a library that I implement. I own both these libraries. I.e.:
Lib1 has a certain class I want to access in the consumer app, say Car.java:
public class Car {
public Int wheelsCount;
}
Lib2 has an api which returns the class Car from lib1:
gradle.build:
...
api 'com.me.lib1:1.0' //From maven central (not currently live)
import com.lib1.Car
interface MyApi {
public Car getCar();
}
Consumer's gradle.build: implementation 'com.me.lib2:1.0'
MyApi api = getMyApi()
api.getCar() // Error
Cannot access class 'com.lib1.Car'. Check your module classpath for missing or conflicting dependencies
Question: Is there a way to do this using gradle?

I am not sure what you’re asking. Here is an example which I think should roughly do what you were talking about?
We have a gradle project with 3 subprojects, car, garage and app. car and garage provide libraries, whereas app is our application. Aside from these directories, the root directory contains only a settings.gradle with this content:
rootProject.name = 'example'
include 'car', 'garage', 'app'
Moving on, we have the car project which provides an interface. It’s build.gradle looks like this:
plugins {
id 'java-library'
}
Other than that, it only contains our interface at src/main/java/car/Car.java:
package car;
public interface Car {
void drive();
}
garage is a consumer for the car project, and provides a way to get new Cars. So we need to declare an api dependency on :car like this:
plugins {
id 'java-library'
}
dependencies {
api project(':car')
}
The Garage class itself looks like this (under src/main/java/garage/Garage.java:
package garage;
import car.Car;
public class Garage {
public Car getCar() {
return () -> { System.out.println("Hello World!"); };
}
}
Finally, we have our application which just needs an implementation dependency on garage to use it and a JavaExec task to run it:
plugins {
id 'java'
}
dependencies {
implementation project(':garage')
}
task run(type: JavaExec) {
main = 'app.Main'
classpath = sourceSets.main.runtimeClasspath
}
The actual implementation again goes to src/main/java/app/Main.java:
package app;
import garage.Garage;
public class Main {
public static void main(String[] args) {
Garage g = new Garage();
g.getCar().drive();
}
}
Now we can run the whole thing with gradle :app:run, which should give us output along these lines:
> Task :app:run
Hello World!
BUILD SUCCESSFUL in 506ms
6 actionable tasks: 1 executed, 5 up-to-date
<-------------> 0% WAITING
> IDLE
So as you can see, there’s nothing special you need to do beyond the things you’ve already stated. This is assuming you’re using classpaths. If you’re using the Java 9+ module system there are some additional steps, but this would be beyond the scope of this question I think.

Related

"requires transitive" for modules not working on Eclipse?

I'm starting to study modules, and I would like to try the "requires transitive". So I createad a module for the interface:
module interfacesModule {
exports com.my.interfaces;
}
with 1 interface:
package com.my.interfaces;
public interface MyInterface {
void go();
}
a module for implementation:
module javaModuleA {
requires interfacesModule;
exports com.my.moduleA;
}
class:
package com.my.moduleA;
import com.my.interfaces.MyInterface;
public class ClassA implements MyInterface {
public void go() {
System.out.println("in class A");
}
}
and the main module:
module mainModule {
requires transitive javaModuleA;
requires interfacesModule; // this line I want to comment, to use from requires transitive
}
class:
package com.my;
import com.my.interfaces.MyInterface;
import com.my.moduleA.ClassA;
public class Main {
public static void main(String[] args) {
System.out.println("main java module");
MyInterface a = new ClassA();
a.go();
}
}
This works fine!
Now if i comment the line "requires interfacesModule;" on my main module, should't it still work, as I have the "requires transitive" for javaModuleA?
When I comment it I get "The type com.my.interfaces.MyInterface is not accessible".
A requires transitive javaModuleA; declaration within mainModule implies that a hypothetical module having a requires mainModule declaration would automatically get a dependency as if having a declaration requires javaModuleA; too.
In other words, what you are trying to do, to get an implicit requires interfacesModule; for mainModule, needs a change in javaModuleA
module javaModuleA {
requires transitive interfacesModule;
exports com.my.moduleA;
}
Then, mainModule having a requires javaModuleA; or requires transitive javaModuleA; can use exported packages of interfacesModule without the need for a requires interfacesModule;.
The rationale is that you might have a javaModuleA containing both, the interface and the implementation, as a starting point and decide to refactor the module that has grown too big into javaModuleA and interfacesModule, without the need to adapt the dependent modules. Then, having requires transitive interfacesModule; in javaModuleA allows the modules depending on javaModuleA to continue to work, as if it still was the big module.

Getting the resource path of the java project from a custom gradle plugin

Trying to create a custom gradle plugin in java, how do i get the resources path from inside the task class?
public class MyCustomPlugin implements Plugin<Project> {
#Override
public void apply(Project project) {
project.getTasks().register("doStuff", CustomTask.class);
}
}
public class CustomTask extends DefaultTask {
// How do I get java project resources dir from here?
#Inject
public CustomTask(ProjectLayout projectLayout) {
directoryProperty = projectLayout.getBuildDirectory();
}
#TaskAction
public void execute() {
...
}
}
I would recommend to not get the directory inside the task, because the plugin that provides it might not be applied. Instead I would do it from within your plugin that registers the task, this way you can also ensure that the necessary plugin is actually applied. Gradle will display an error if the task is used without a value being assigned to the input that explains that nothing was assigned.
With the kotlin-dsl:
#CacheableTask
abstract class CustomTask : DefaultTask() {
#get:InputFiles
abstract val resources: FileCollection
//...
}
I cannot answer if #InputFiles is the right annotation for your use case, because I don't know what you want to do with the resource. Refer to the Gradle documentation for more information on the available annotations, and what they do.
plugins {
java
}
tasks.register<CustomTask>("customTask") {
resources.set(sourceSets.main.map { it.resources })
}
Notice the map {} which ensures that our task has a dependency on the processResources task, this is done automatically for us because we stick to the provider API of Gradle for everything.
Note that the resources are by default in one directory, but they don't have to be. This is why the resources are defined as SourceDirectorySet and not as Provider<Directory>. The same is true for anything that originates from the SourceSetContainer. It is easier to explain with Java source code: imagine you have Java and Kotlin, then you will have src/main/java and src/main/kotlin, hence, 2 directories. The former will have a **/*.java include filter, whereas the latter has a **/*.kt includes filter. If we just want to get all sources then we use sourceSets.main.map { it.java.sourceDirectories }, and if we want to get one of both it gets complicated. 😝
First, you'd have to ensure this is a Java project: either applying the "java" plugin from your plugin (project.getPluginManager().apply("java")), or only registering the task when the "java" plugin has been applied by the user (project.getPluginManager().withPlugin("java", ignored -> { project.getTasks().register(…); });).
You could then get the resources from the main source set:
SourceSetContainer sourceSets = project.getExtensions().getByType(SourceSetContainer.class);
// Use named() instead of get() if you prefer/need to use providers
SourceSet mainSourceSet = sourceSets.get(SourceSet.MAIN_SOURCE_SET_NAME);
SourceDirectorySet resources = mainSourceSet.getResources();
BTW, the best practice is to have tasks only declare their inputs and outputs (e.g. I need a set of directories, or files, as inputs, and my outputs will be one single file, or in one single directory) and have the actual wiring with default values be done by the plugin.
You could have the plugin unconditionally register the task, then conditionally when the "java" plugin is applied configure its inputs to the project resources; or conditionally register the task or unconditionally apply the "java" plugin, as I showed above.
You can access the sources through the project.sourceSets.
#Inject
public CustomTask(Project project) {
directoryProperty = project.projectLayout.getBuildDirectory();
sourceSet = project.sourceSets.main
}
See also the reference documentation here: https://docs.gradle.org/current/userguide/java_plugin.html#sec:java_project_layout

Load classpath of project gradle plugin is applied to

I am writing a custom gradle plugin, which will generate some code for me, based on the code it finds in the project it is applied to.
For this I need to find all classes that extend a specific class.
The problem is that the class, that is extended, is not loaded in the classpath, since it is a dependency of the other project.
Currently I got this for my custom Task
public class GenerateCodeTask extends DefaultTask {
#TaskAction
public void generateCode() throws MalformedURLException, ClassNotFoundException {
File buildDir = new File(getProject().getBuildDir(), "classes/main");
File root = new File(getProject().getProjectDir(), "src/main/generated");
URLClassLoader classLoader = new URLClassLoader(new URL[]{buildDir.toURL()});
Class itemClass = classLoader.loadClass("net.minecraft.item.Item");
Reflections reflections = new Reflections(classLoader);
Set<Class<?>> items = reflections.getSubTypesOf(itemClass);
}
}
And this for the plugin
public class EasymodsPlugin implements Plugin<Project> {
#Override
public void apply(Project p) {
Task t = p.getTasks().create("generateCode", GenerateCodeTask.class);
t.dependsOn(p.getTasks().getByPath("compileJava"));
}
}
This is the error I am getting
java.lang.ClassNotFoundException: net.minecraft.item.Item
I know that the problem is that the library containing the class is not loaded, and that causes the exception.
What I want is to be able to load all dependencies of my project into the classloader, so I can use reflections to find all "items" in the project (which I need to generate code)
Greetings Failender
I think you almost got it.
You need the compileClasspath property. I pass it as an input parameter to my task, and build the Class Loader from it:
In plugin:
Set<File> ccp = project.getConfigurations().getByName("compileClasspath").getFiles();
task.classpath = ccp;
In task:
#InputFiles
Iterable<File> classpath;
What this property is adding is:
the path to the classes on projects it depends on that are generated under project_id/build dir. ( So no need to build the path manually )
All library deps for your project. That i think you were missing.
So, in the case your class is of type 1:
It is better if you have different subprojects: the one that has the original classes and the other with the generated ones.
So the second depends on the first:
dependencies {
compile project(':my_project_with_classes_to_extend')
}
In the case is the second one, you can just add the library as a dep to your project and it will find the class.
And rewire the tasks so you are sure the fist project is built before calling your task (just on root level of your 2nd project build.gradle):
I think that´s the part that wasn't really working for you, apparently, compileJava and build are not exactly the same. Or at least, compileJava wasnt working for me either.
myGeneratorTask.dependsOn( ":my_project_with_classes_to_extend:build" )
compileJava.dependsOn( "myGeneratorTask" )

NoSuchMethodError when using realm.io in a library

I transferred the model classes of my Android app inlcuding realm.io functions into a library following the advices described in the realm.io Java docs section Sharing schemas.
I'm facing a NoSuchMethodError when my app (indirectly) calls realm.io methods which are in the the library.
java.lang.NoSuchMethodError: No static method copyOrUpdate(Lio/realm/Realm;Lmy/package/structure/MyModelClass;ZLjava/util/Map;)Lmy/package/structure/MyModelClass; in class Lio/realm/MyModelClassRealmProxy; or its super classes (declaration of 'io.realm.MyModelClassRealmProxy' appears in /data/user/0/my.name.space/files/.jrebel/load-dexes/bundle3/classes.zip)
at io.realm.MyModuleMediator.copyOrUpdate(MyModuleMediator.java:98)
at io.realm.Realm.copyOrUpdate(Realm.java:1164)
at io.realm.Realm.copyToRealmOrUpdate(Realm.java:757)
Here is how my library looks like:
build.gradle (project)
buildscript {
repositories {
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:2.1.2'
classpath 'io.realm:realm-gradle-plugin:1.0.1'
}
}
build.gradle (library module)
apply plugin: 'com.android.library'
apply plugin: 'realm-android'
...
buildTypes {
release {
minifyEnabled false
signingConfig signingConfigs.release
proguardFile getDefaultProguardFile('proguard-rules.pro')
}
}
...
proguard-rules.pro
I used this snippet as a template
-keep class io.realm.annotations.RealmModule
-keep #io.realm.annotations.RealmModule class *
-keep class io.realm.internal.Keep
-keep #io.realm.internal.Keep class * { *; }
-keep my.package.MyModelClass
-dontwarn javax.**
-dontwarn io.realm.**
MyModule.java
package my.package;
import io.realm.annotations.RealmModule;
#RealmModule(library = true, allClasses = true)
public class MyModule { }
MyRealm.java
package my.package;
import ...
public class MyRealm {
private final RealmConfiguration realmConfig;
public MyRealm(Context context) {
realmConfig = new RealmConfiguration.Builder(context)
.name("name.of.my.config")
.modules(new MyModule())
.build();
}
public Realm getRealm() {
return Realm.getInstance(realmConfig);
}
}
MyModelClass.class
package my.package;
import ...
public class MyModelClass extends RealmObject {
public void save(Context context) {
Realm realm = new MyRealm(context).getRealm();
realm.executeTransaction(new Realm.Transaction() {
#Override
public void execute(Realm bgRealm) {
bgRealm.copyToRealmOrUpdate(MyModelClass.this);
}
});
realm.close();
}
}
In my actual app I call something like this which is causing the Exception:
MyActivity.java
// ...
MyModelClass c = new MyModelClass();
c.save(context);
The code above was working well when everything was in the app project.
Am I missing something general?
Is there something more I need to consider regarding the proguard settings of the lib?
May JRebel cause this kind of problem?
I think you might not be using it correctly, I will suggest that you see a full working example like the one they reference are the end of that Sharing Schemas link. The link is to this repository full working example of realm lib and app. I would suggest that you take a look at the Zoo class from the library, the methods are the only thing exposed to the application as you can see in the app's activity here (line 148 and downwards).
I think this was just a confusion on your part, because from what I understood you are calling your library class and using it like it was a Realm instance for that activity context which is not the case. Hope this can steer you in the right path!
Basically you simply create an instance based on you configuration so you don't need to pass a context there.
This happens with JRebel, I would suggest you try with the latest version of it if the issue still persists try not using it.
I received the same error message as the original post.
java.lang.NoSuchMethodError: No static method copyOrUpdate(Lio/realm/Realm;Lmy/package/structure/MyModelClass;ZLjava/util/Map;)Lmy/package/structure/MyModelClass; in class Lio/realm/MyModelClassRealmProxy; or its super classes (declaration of 'io.realm.MyModelClassRealmProxy' appears in /data/user/0/my.name.space/files/.jrebel/load-dexes/bundle3/classes.zip)
at io.realm.MyModuleMediator.copyOrUpdate(MyModuleMediator.java:98)
at io.realm.Realm.copyOrUpdate(Realm.java:1164)
at io.realm.Realm.copyToRealmOrUpdate(Realm.java:757)
But my problem was a different one. I had one Realm module in a library and an additional Realm module in my app. Unfortunately both modules contain an entity with the same class name (but in different packages).
The code generation for realm creates only only one io.realm.<classname>RealmProxy class. Because of that I got the above mentioned exception.
The solution is quite simply: Just rename of the entities.

AspectJ annotated based pointcuts not being invoked

I am attempting to create an android library that checks if internet is available before executing a method that has a custom annotation I have defined. I'm using AspectJ to accomplish this.
My annotation is as follows :
#Target({METHOD}) #Retention(RUNTIME)
public #interface InternetRequired {
}
Now for my aspect:
#Aspect
public class CilantroAspect
{
private static final String POINTCUT_METHOD = "execution(#com.cilantro.service.InternetRequired * *(..))";
private static final String POINTCUT_METHOD2 ="#annotation(com.cilantro.service.InternetRequired)";
;
#Pointcut(POINTCUT_METHOD2)
public void internetAnnotatedMethod() {
}
#Around("internetAnnotatedMethod()")
public void checkInternetConnectivity(ProceedingJoinPoint joinPoint) throws Throwable {
Log.v("Aspect","advice being triggered");
if (Cilantro.isConnected()) {
joinPoint.proceed();
} else {
Cilantro.Toast("Internet not available");
}
}
}
Snippet of my activity with the annotated method.
....
Cilantro.init(this);
test();
}
#InternetRequired
public void test()
{
Toast.makeText(this,"Test method",Toast.LENGTH_LONG).show();
}
When I run my android app the around advice is not being triggered. I tried using POINTCUT_METHOD and POINTCUT_METHOD2. Still no luck.
My android app is configured to use Aspect J so I know that's not the problem because if I make errors within the pointcut definitions it's detected..but just to be sure let me share.
Main build script
buildscript {
repositories {
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:1.2.3'
classpath 'com.uphyca.gradle:gradle-android-aspectj-plugin:0.9.+'
}
....
module containing Aspects
apply plugin: 'com.android.library'
apply plugin: 'android-aspectj'
....
The around advice wasn't being triggered because I was using annotations related to the Aspect from the app level while the aspect was contained in the library. For the aspects to be weaved into the app module at compile time I had to simply publish the library to maven (local for testing and maven central for distribution) and then include the library as a project dependency in a gradle plugin which contains the AspectJ weaving tasks.The plugin is then applied to the app's module.
Here's a snippet of my plugin that's written in groovy. I add my library that contains my Aspects and then run the weaving tasks on the app's module.
project.dependencies {
compile 'com.github.jd-alexander:flender-runtime:1.0'
// TODO this should come transitively
compile 'org.aspectj:aspectjrt:1.8.5'
}
variants.all { variant ->
variant.dex.doFirst {
String[] args = [
"-showWeaveInfo",
"-1.5",
"-inpath", javaCompile.destinationDir.toString(),
"-aspectpath", javaCompile.classpath.asPath,
"-d", javaCompile.destinationDir.toString(),
"-classpath", javaCompile.classpath.asPath,
"-bootclasspath", project.android.bootClasspath.join(File.pathSeparator)
]
log.debug "ajc args: " + Arrays.toString(args)
MessageHandler handler = new MessageHandler(true);
new Main().run(args, handler);
For a complete example of how this is done you can check my library out on github https://github.com/jd-alexander/flender

Categories