I am trying to load a projects class during the execution of a maven mojo.
Unfortunately this operation fails, since the class loader is missing a referenced class.
Looking around I found already the approaches Maven mojo plugin to load class from hosting project and Maven plugin can't load class
I combined the two approaches, ending up with the following code:
private ClassLoader getClassLoader(final MavenProject project) {
try {
final List<URL> classPathUrls = new ArrayList<>();
// adding the projects dependency jars
final Set<Artifact> artifacts = getProject().getDependencyArtifacts();
for (final Artifact artifact : artifacts) {
classPathUrls.add(artifact.getFile().toURI().toURL());
}
// adding the projects classes itself
final List<String> classpathElements = project.getCompileClasspathElements();
classpathElements.add(project.getBuild().getOutputDirectory());
classpathElements.add(project.getBuild().getTestOutputDirectory());
for (final String classpathElement : classpathElements) {
classPathUrls.add(new File(classpathElement).toURI().toURL());
}
// creating a class loader
final URL urls[] = new URL[classPathUrls.size()];
for (int i = 0; i < classPathUrls.size(); ++i) {
urls[i] = classPathUrls.get(i);
}
return new URLClassLoader(urls, this.getClass().getClassLoader());
} catch (final Exception e) {
getLog().debug("Couldn't get the classloader.");
return this.getClass().getClassLoader();
}
}
The class which fails to load is an implementation of the interface "org.bson.codecs.Codec", which is contained in "org.mongodb:bson", which is implicit imported via the dependency:
<dependency>
<groupId>org.mongodb</groupId>
<artifactId>mongodb-driver-sync</artifactId>
<version>4.1.1</version>
<scope>provided</scope>
</dependency>
This dependency has a dependency to another jar (scope: provided), containing the mentioned interface, which can be seen in the maven dependency tree:
$> mvn dependency:tree
[INFO] net.my.project:my-project:jar:1.0-SNAPSHOT
[INFO] +- org.mongodb:mongodb-driver-sync:jar:4.1.1:provided
[INFO] | +- org.mongodb:bson:jar:4.1.1:provided
[INFO] | \- org.mongodb:mongodb-driver-core:jar:4.1.1:provided
When I look to the class path elements of the created class loader, I see that mongodb-driver-sync.jar is included, but since it declares the "org.mongodb:bson" dependency with scope provided it the interface is still not in class path.
So, the final question is: How can I load a class which references a class from an "indirect" dependency?
I've noticed, that
project.getArtifacts()
was empty, even though the javadoc says it is supposed to contain all dependencies (lazily populated).
So, with additional research I found Custom Maven Plugin development - getArtifacts is empty though dependencies are included which suggests to adjust the #Mojo annotation:
#Mojo(name = "mojoName", requiresDependencyResolution = ResolutionScope.COMPILE)
after adjusting the annotation, it is even enough to use the "project.getCompileClasspathElements();", it is not necessary anymore to iterate through the artifacts at all.
Related
I am trying to implement a simple modular application with a self written plugin loader. For that I created a gradle multiproject build. The main project is going to be the started application. In the subproject spi is a module containing the Plugin interface, while in the subproject simple-plugin I wanted to create a plugin to test the plugin loading.
The Plugin interface is very simple:
package io.github.zeroneca.orgascreen.spi;
public interface Plugin {
String getId();
}
module-info.java:
module io.github.zeroneca.orgascreen.spi {
exports io.github.zeroneca.orgascreen.spi;
}
SimplePlugin.java:
package io.github.zeroneca.orgascreen.plugins.simple;
import io.github.zeroneca.orgascreen.spi.Plugin;
public class SimplePlugin implements Plugin {
private final String id = "SimplePlugin";
#Override
public String getId() { return id; }
}
module-info.java
module io.github.zeroneca.orgascreen.plugins.simple {
requires io.github.zeroneca.orgascreen.spi;
provides io.github.zeroneca.orgascreen.spi.Plugin with io.github.zeroneca.orgascreen.plugins.simple.SimplePlugin;
}
Now in the class PluginLoader I'm loading all jars from a directory and want to add all Plugins to a List:
package io.github.zeroneca.orgascreen;
// several imports
public class PluginLoader {
private static final List<Plugin> PLUGINS = new ArrayList<>();
public static void load() {
URL[] pluginURLs = getPluginURLs(); // returns all URLs of jar files in a directory
URLClassLoader urlClassLoader = URLClassLoader.newInstance(pluginURLs);
ServiceLoader<Plugin> serviceLoader = ServiceLoader.load(Plugin.class, urlClassLoader);
for (Plugin plugin : serviceLoader) {
PLUGINS.add(plugin);
}
}
// some more methods hidden here
}
module-info.java
module io.github.zeroneca.orgascreen {
requires io.github.zeroneca.orgascreen.spi;
uses io.github.zeroneca.orgascreen.spi.Plugin;
}
Now after building with gradle and copying the resulting jar simple-plugin-0.1.jar into the directory, I can see, that the URL of the file is in pluginJars but the List PLUGINS remains empty. I'm not sure what I'm doing wrong, online searches didn't help, I cannot see a difference to working solution, but the use of gradle.
gradle 6.5.1 is in use and modularity.inferModulePath is set to true.
Help is greatly appreciated!
Note: when adding META-INF/services/io.github.zeroneca.orgascreen.spi.Plugin to the jar of SimplePlugin it works, so something must be wrong with the module declarations. But I just can't see what.
I'm trying to get a list of dependencies of some maven artifacts using org.apache.maven.project.MavenProject.
My code is like this.
public List<Dependencies> loadProject() {
Model mavenModel = new Model();
mavenModel.setModelVersion("4.0.0");
mavenModel.setGroupId("org");
mavenModel.setArtifactId("wso2");
mavenModel.setVersion("1.0.0");
addDependency(mavenModel, "com.google.inject", "guice", "4.2.2");
addDependency(mavenModel, "ch.qos.logback", "logback-classic", "1.2.3");
MavenProject mavenProject = new MavenProject(mavenModel);
//******* Need to resolve dependencies of `mavenProject` and *******
//******* get the list of dependencies of this project. *******
return dependencies;
}
private static void addDependency(Model mavenModel, String groupId, String artifactId, String version) {
Dependency dependency = new Dependency();
dependency.setGroupId(groupId);
dependency.setArtifactId(artifactId);
dependency.setVersion(version);
mavenModel.addDependency(dependency);
}
Basically I'm trying to get the dependency tree results which returns by mvn dependency:tree command as a list by programmatically.
Example
For the artifacts:
com.google.inject:guide:4.2.2
ch.qos.logback:logback-classic:1.2.3
Dependency list:
List = [
com.google.inject:guice:jar:4.2.2:compile,
javax.inject:javax.inject:jar:1:compile,
aopalliance:aopalliance:jar:1.0:compile,
com.google.guava:guava:jar:25.1-android:compile,
com.google.code.findbugs:jsr305:jar:3.0.2:compile,
org.checkerframework:checker-compat-qual:jar:2.0.0:compile,
com.google.errorprone:error_prone_annotations:jar:2.1.3:compile,
com.google.j2objc:j2objc-annotations:jar:1.1:compile,
org.codehaus.mojo:animal-sniffer-annotations:jar:1.14:compile,
ch.qos.logback:logback-classic:jar:1.2.3:compile,
ch.qos.logback:logback-core:jar:1.2.3:compile,
org.slf4j:slf4j-api:jar:1.7.25:compile
]
You can use the method public Set<Artifact> getArtifacts() of your MavenProject class which returns a set of artifacts representing all dependencies that the project has, including transitive ones.
NB: Contents are lazily populated, so depending on what phases have run dependencies in some scopes won't be included. eg. if only compile phase has run, dependencies with scope test won't be included.
All the information are coming from the documentation that you can find here.
I have an aggregator plugin in Maven with an injected DependencyGraphBuilder. My goal is to resolve the DependencyNode of the current project with all child nodes initialized as well.
#Mojo(name = "mojo", defaultPhase = LifecyclePhase.SITE,
requiresDependencyResolution = ResolutionScope.COMPILE, aggregator = true)
public class MyMojo extends AbstractMojo {
#Component(hint = "default")
private DependencyGraphBuilder dependencyGraphBuilder;
#Parameter(defaultValue = "${reactorProjects}", readonly = true, required = true)
private List<MavenProject> reactorProjects;
}
Currently, if i try to resolve to root node, i am only able to get the dependencies of the current project. I assume that Maven did not resolve that dependencies (only for the current project).
dependencyGraphBuilder.buildDependencyGraph(project, null, reactorProjects);
Summing up:
How can i resolve the child nodes to build to full dependency tree
using DependencyGraphBuilder in a Maven reactor project or is this not possible for aggregator projects?
Additional Info:
I have looked at similar questions, but all of those did not assume an aggregator project.
Since you are trying to implement mvn dependency:tree why not to copy the solution from the Maven Dependency Plugin. Take a look at TreeMojo which also uses DependencyGraphBuilder with DependencyNodeVisitor.
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" )
What I'm trying to do is to write some Maven plugin which scans application classes looking for implementation of a particular interface (it might be classes with some annotation as well) and basis on the result generating some code. I've successfully implemented plugin running in the generate-sources phase and writing source code to the generated-sources directory.
The problem is with scanning classpath for the particular interface implementations/classes with some annotation.
I am using the Reflections library to scan classes in the following way:
private Set<Class< ? extends MyInterface >> scan(final String packageName) {
final Reflections reflections = new Reflections(packageName);
return reflections.getSubTypesOf(MyInterface.class);
}
Unfortunately, this method returns empty set. When I print my classpath in the class extending org.apache.maven.plugin.AbstractMojo (the same within which I'm using Reflections) I get the following result:
/home/pd5108/apache-maven-2.2.1/boot/classworlds-1.1.jar
The classes I want to find using Reflections exists in dependend JARs as well as in the module within which plugin is configured. Looking at the classpath printed out it seems that at this point (generate-sources phase) dependencies defined in maven all not available on classpath yet - probably they are added in the next phases. Is that true? Is there any other approach I can use?
Here is the way how classpath is printed out:
URL[] urls = ((URLClassLoader)sysClassLoader).getURLs();
for(int i=0; i< urls.length; i++) {
System.out.println(urls[i].getFile());
}
Required MOJO class fields:
/**
* The project currently being built.
*
* #parameter expression="${project}"
* #readonly
* #required
*/
private MavenProject project;
/** #parameter expression="${localRepository}" */
protected ArtifactRepository m_localRepository;
/**#parameter default-value="${localRepository}" */
private org.apache.maven.artifact.repository.ArtifactRepository
localRepository;
/** #parameter default-value="${project.remoteArtifactRepositories}" */
private java.util.List remoteRepositories;
/** #component */
private org.apache.maven.artifact.factory.ArtifactFactory artifactFactory;
/** #component */
private org.apache.maven.artifact.resolver.ArtifactResolver resolver;
Resolution of all dependencies JARs:
final List<Dependency> dependencies = project.getDependencies();
for (Dependency d : dependencies) {
final Artifact artifact =
artifactFactory.createArtifactWithClassifier(d.getGroupId(),
d.getArtifactId(), d.getVersion(), d.getType(),
d.getClassifier());
try {
resolver.resolve(artifact, remoteRepositories,
localRepository );
} catch (ArtifactResolutionException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (ArtifactNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
File artifactFile = artifact.getFile();
System.out.println(artifactFile.getAbsolutePath());
}
And now we need to scan these JARs using reflection API looking for the appropriate classes.
At this point I think that there's no other way, since I work in generate-sources phase and artifact values for the next phases are not computed yet.
There are artifact dependencies defined in <dependencies> section and plugin dependencies defined under <plugin><dependencies>.
Plugin dependencies are added to the classpath while I am not sure about the artifact dependencies. Did you try to add your plugin dependencies under the <plugin><dependencies>?
Might be quicker/safer/easier to just have the plugin config list the classes you want to generate code based on. Then you just add it to the pom and you're done. No reflection needed, and it'd certainly speed up the running of the plugin.