Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Auto-Require Maven Plugin - avaje-inject-maven-plugin #274

Merged
merged 13 commits into from
Feb 6, 2023
61 changes: 61 additions & 0 deletions auto-requires-plugin/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>io.avaje</groupId>
<artifactId>auto-require-plugin</artifactId>
<packaging>maven-plugin</packaging>
<version>1.0-SNAPSHOT</version>
<name>avaje-autorequire-plugin Maven Mojo</name>
<url>http://maven.apache.org</url>
<properties>
<maven.compiler.target>11</maven.compiler.target>
<maven.compiler.source>11</maven.compiler.source>
</properties>
<dependencies>
<dependency>
<groupId>org.apache.maven</groupId>
<artifactId>maven-core</artifactId>
<version>3.8.7</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.apache.maven.plugin-tools</groupId>
<artifactId>maven-plugin-annotations</artifactId>
<version>3.7.1</version>
<scope>provided</scope>
</dependency>

<dependency>
<groupId>io.avaje</groupId>
<artifactId>avaje-inject</artifactId>
<version>8.12-RC3</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>com.spotify.fmt</groupId>
<artifactId>fmt-maven-plugin</artifactId>
<version>2.19</version>
<configuration>
<skipSortingImports>true</skipSortingImports>
</configuration>
<executions>
<execution>
<goals>
<goal>format</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-plugin-plugin</artifactId>
<version>3.7.1</version>
<configuration>
<goalPrefix>load-spi</goalPrefix>
</configuration>
</plugin>
</plugins>
</build>
</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
package io.avaje.requires;

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.ServiceLoader;
import java.util.Set;

import org.apache.maven.artifact.Artifact;
import org.apache.maven.artifact.resolver.filter.ScopeArtifactFilter;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugins.annotations.LifecyclePhase;
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.Parameter;
import org.apache.maven.plugins.annotations.ResolutionScope;
import org.apache.maven.project.MavenProject;

import io.avaje.inject.spi.Module;
import io.avaje.inject.spi.Plugin;

@Mojo(
name = "load-spi",
defaultPhase = LifecyclePhase.PROCESS_SOURCES,
requiresDependencyResolution = ResolutionScope.COMPILE)
public class AutoRequiresMojo extends AbstractMojo {

@Parameter(defaultValue = "${project}", readonly = true, required = true)
private MavenProject project;

@Override
public void execute() throws MojoExecutionException {

final var listUrl = getCompileDependencies();

final var directory = new File(project.getBuild().getDirectory());
if (!directory.exists()) {
directory.mkdirs();
}

try (var newClassLoader = createClassLoader(listUrl);
var moduleWriter = createFileWriter("avaje-module-provides.txt");
var pluginWriter = createFileWriter("avaje-plugin-provides.txt")) {

writeProvidedPlugins(newClassLoader, pluginWriter);
writeProvidedModules(newClassLoader, moduleWriter);

} catch (final IOException e) {
throw new MojoExecutionException("Failed to write spi classes", e);
}
}

private List<URL> getCompileDependencies() throws MojoExecutionException {

final List<URL> listUrl = new ArrayList<>();
project.setArtifactFilter(new ScopeArtifactFilter("compile"));
final var deps = project.getArtifacts();

for (final Artifact artifact : deps) {
try {
URL url;
url = artifact.getFile().toURI().toURL();
listUrl.add(url);
} catch (final MalformedURLException e) {
throw new MojoExecutionException("Failed to get compile dependencies", e);
}
}
return listUrl;
}

private URLClassLoader createClassLoader(List<URL> listUrl) {
return new URLClassLoader(
listUrl.toArray(new URL[listUrl.size()]), Thread.currentThread().getContextClassLoader());
}

private FileWriter createFileWriter(String string) throws IOException {
return new FileWriter(new File(project.getBuild().getDirectory(), string));
}

private void writeProvidedPlugins(URLClassLoader newClassLoader, FileWriter pluginWriter)
throws IOException {
for (final var plugin : ServiceLoader.load(Plugin.class, newClassLoader)) {
for (final Class<?> providedType : plugin.provides()) {

pluginWriter.write(providedType.getCanonicalName());
pluginWriter.write("\n");
}
}
}

private void writeProvidedModules(URLClassLoader newClassLoader, FileWriter moduleWriter)
throws IOException {
final Set<String> providedTypes = new HashSet<>();

for (final Module module : ServiceLoader.load(Module.class, newClassLoader)) {

for (final Class<?> provide : module.provides()) {
providedTypes.add(provide.getCanonicalName());
}
for (final Class<?> provide : module.autoProvides()) {
providedTypes.add(provide.getCanonicalName());
}
for (final Class<?> provide : module.autoProvidesAspects()) {
providedTypes.add(wrapAspect(provide.getCanonicalName()));
}
}

for (final String providedType : providedTypes) {
moduleWriter.write(providedType);
moduleWriter.write("\n");
}
}

static String wrapAspect(String aspect) {
return "io.avaje.inject.aop.AspectProvider<" + aspect + ">";
}
}
31 changes: 30 additions & 1 deletion blackbox-test-inject/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -73,5 +73,34 @@
</dependency>

</dependencies>

<build>
<plugins>
<plugin>
<groupId>io.avaje</groupId>
<artifactId>auto-require-plugin</artifactId>
<version>1.0-SNAPSHOT</version>
<executions>
<execution>
<phase>process-sources</phase>
<goals>
<goal>load-spi</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<annotationProcessorPaths>
<path>
<groupId>io.avaje</groupId>
<artifactId>avaje-inject-generator</artifactId>
<version>${project.version}</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
</plugins>
</build>
</project>
Original file line number Diff line number Diff line change
@@ -1,42 +1,46 @@
package io.avaje.inject.generator;

import io.avaje.inject.spi.Module;

import java.util.*;

import io.avaje.inject.spi.Module;

/**
* The types provided by other modules in the classpath at compile time.
* <p>
* When we depend on these types they add to the module autoRequires() classes.
*
* <p>When we depend on these types they add to the module autoRequires() classes.
*/
final class ExternalProvider {

private final Set<String> providedTypes = new HashSet<>();

void init() {
ServiceLoader<Module> load = ServiceLoader.load(Module.class, ExternalProvider.class.getClassLoader());
void init(Set<String> moduleFileProvided) {

providedTypes.addAll(moduleFileProvided);
ServiceLoader<Module> load =
ServiceLoader.load(Module.class, ExternalProvider.class.getClassLoader());
Iterator<Module> iterator = load.iterator();

while (iterator.hasNext()) {
try {
Module module = iterator.next();
for (final Class<?> provide : module.provides()) {
providedTypes.add(provide.getCanonicalName());
}
for (Class<?> provide : module.autoProvides()) {
for (final Class<?> provide : module.autoProvides()) {
providedTypes.add(provide.getCanonicalName());
}
for (final Class<?> provide : module.autoProvidesAspects()) {
providedTypes.add(Util.wrapAspect(provide.getCanonicalName()));
}
} catch (ServiceConfigurationError expected) {
} catch (final ServiceConfigurationError expected) {
// ignore expected error reading the module that we are also writing
}
}
}

/**
* Return true if this type is provided by another module in the classpath.
* We will add it to autoRequires().
* Return true if this type is provided by another module in the classpath. We will add it to
* autoRequires().
*/
boolean provides(String type) {
return providedTypes.contains(type);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,13 @@ final class ProcessingContext {
private final Set<String> uniqueModuleNames = new HashSet<>();
private final ExternalProvider externalProvide = new ExternalProvider();

ProcessingContext(ProcessingEnvironment processingEnv) {
ProcessingContext(ProcessingEnvironment processingEnv, Set<String> moduleFileProvided) {
this.processingEnv = processingEnv;
this.messager = processingEnv.getMessager();
this.filer = processingEnv.getFiler();
this.elementUtils = processingEnv.getElementUtils();
this.typeUtils = processingEnv.getTypeUtils();
externalProvide.init();
externalProvide.init(moduleFileProvided);
}

/**
Expand Down
Loading