Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,10 @@
public abstract class AbstractCompiler implements Compiler {
private final Logger log = LoggerFactory.getLogger(getClass());

private final org.codehaus.plexus.logging.Logger plexusLogger;
// created on first use, through a factory method so that verifying this class does not load the Plexus
// logging classes: the Plexus logging classes are optional, a compiler created without a container
// (see DefaultCompilerManager.fromServiceLoader) must not need them
private volatile org.codehaus.plexus.logging.Logger plexusLogger;

protected static final String EOL = System.lineSeparator();

Expand Down Expand Up @@ -72,8 +75,6 @@ protected AbstractCompiler(
this.outputFileEnding = outputFileEnding;

this.outputFile = outputFile;

this.plexusLogger = new PlexusLoggerWrapper(log);
}

/**
Expand All @@ -90,7 +91,11 @@ protected Logger getLog() {
*/
@Deprecated
protected org.codehaus.plexus.logging.Logger getLogger() {
return plexusLogger;
org.codehaus.plexus.logging.Logger result = plexusLogger;
if (result == null) {
plexusLogger = result = PlexusLoggerWrapper.wrap(log);
}
return result;
}

// ----------------------------------------------------------------------
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ class PlexusLoggerWrapper implements org.codehaus.plexus.logging.Logger {

private final Logger log;

static org.codehaus.plexus.logging.Logger wrap(Logger log) {
return new PlexusLoggerWrapper(log);
}

PlexusLoggerWrapper(Logger log) {
this.log = log;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,16 @@
import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Provider;
import javax.inject.Singleton;

import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Objects;
import java.util.ServiceConfigurationError;
import java.util.ServiceLoader;

import org.codehaus.plexus.compiler.AbstractCompiler;
import org.codehaus.plexus.compiler.Compiler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
Expand All @@ -45,11 +52,115 @@ public class DefaultCompilerManager implements CompilerManager {
+ "might indicate, that the compiler needs a more recent Java runtime. "
+ "IllegalArgumentException in ClassReader.<init> might mean, that you need to upgrade Maven.";

private static final int MAX_LOOKUP_FAILURES = 100;

@Inject
private Map<String, Provider<Compiler>> compilers;

private final Logger log = LoggerFactory.getLogger(getClass());

/**
* Creates a manager populated through {@link ServiceLoader} instead of a dependency injection container.
* Intended for callers that cannot use Sisu/Plexus, for example Maven 4 API plugins that bind the result
* with a plugin-local {@code @Provides} method.
* <p>
* Every {@link Compiler} registered in {@code META-INF/services/org.codehaus.plexus.compiler.Compiler} of the
* given class loader is registered under its {@code @Named} value, the key Sisu uses (equal to
* {@link AbstractCompiler#getCompilerId()}), or under the compiler id if it has no {@code @Named}. Instance
* scope follows the Sisu-based manager: a compiler class annotated {@code @Singleton} (javac, eclipse) is
* shared, any other one gets a new instance on every {@link #getCompiler(String)} call, so it needs a public
* no-argument constructor.
* If two providers share an id, the first one found wins.
* <p>
* A provider that cannot be loaded or instantiated, for example because an optional dependency such as
* ecj, AspectJ or Error Prone is missing from the class loader, is skipped with a warning and the other
* providers are still registered. Asking for the skipped id throws {@link NoSuchCompilerException}.
*
* @param classLoader the class loader to look providers up in
* @return a new manager holding every compiler that could be loaded
* @since 2.18.0
*/
public static DefaultCompilerManager fromServiceLoader(ClassLoader classLoader) {
Objects.requireNonNull(classLoader, "classLoader");
DefaultCompilerManager manager = new DefaultCompilerManager();
Map<String, Provider<Compiler>> providers = new HashMap<>();
Iterator<Compiler> iterator =
ServiceLoader.load(Compiler.class, classLoader).iterator();
int failures = 0;
while (failures < MAX_LOOKUP_FAILURES) {
Compiler compiler;
try {
if (!iterator.hasNext()) {
break;
}
compiler = iterator.next();
} catch (ServiceConfigurationError e) {
// the iterator has already moved past the offending entry, so carry on with the next one;
// the cap only guards against an iterator that would fail forever
failures++;
manager.log.warn("Skipping a compiler that cannot be loaded: {}", e.getMessage(), e);
continue;
}
String id = idOf(compiler);
if (id == null) {
manager.log.warn("Skipping compiler {}: it has neither @Named nor a compiler id", compiler.getClass());
continue;
}
Class<? extends Compiler> type = compiler.getClass();
providers.putIfAbsent(
id,
type.isAnnotationPresent(Singleton.class)
? new SingletonProvider(compiler)
: new NewInstanceProvider(type));
}
manager.compilers = providers;
return manager;
}

/**
* The key Sisu registers the compiler under: its {@code @Named} value, which equals
* {@link AbstractCompiler#getCompilerId()} for every compiler in this project.
*/
private static String idOf(Compiler compiler) {
Named named = compiler.getClass().getAnnotation(Named.class);
if (named != null && !named.value().isEmpty()) {
return named.value();
}
return compiler instanceof AbstractCompiler ? ((AbstractCompiler) compiler).getCompilerId() : null;
}

/** Hands out the one instance, like a Sisu binding of a {@code @Singleton} class. */
private static final class SingletonProvider implements Provider<Compiler> {
private final Compiler instance;

SingletonProvider(Compiler instance) {
this.instance = instance;
}

@Override
public Compiler get() {
return instance;
}
}

/** Creates a fresh compiler per call, like an unscoped Sisu binding. */
private static final class NewInstanceProvider implements Provider<Compiler> {
private final Class<? extends Compiler> type;

NewInstanceProvider(Class<? extends Compiler> type) {
this.type = type;
}

@Override
public Compiler get() {
try {
return type.getConstructor().newInstance();
} catch (ReflectiveOperationException e) {
throw new IllegalStateException("Cannot instantiate " + type.getName(), e);
}
}
}

// ----------------------------------------------------------------------
// CompilerManager Implementation
// ----------------------------------------------------------------------
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
package org.codehaus.plexus.compiler.manager;

/**
* The MIT License
*
* Copyright (c) 2005, The Codehaus
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
* of the Software, and to permit persons to whom the Software is furnished to do
* so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
import java.net.URL;
import java.net.URLClassLoader;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;

import org.codehaus.plexus.compiler.AbstractCompiler;
import org.codehaus.plexus.compiler.Compiler;
import org.codehaus.plexus.compiler.CompilerConfiguration;
import org.codehaus.plexus.compiler.CompilerException;
import org.codehaus.plexus.compiler.CompilerOutputStyle;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;

import static org.junit.jupiter.api.Assertions.assertInstanceOf;
import static org.junit.jupiter.api.Assertions.assertNotSame;
import static org.junit.jupiter.api.Assertions.assertSame;
import static org.junit.jupiter.api.Assertions.assertThrows;

/**
* Builds a {@link DefaultCompilerManager} with no Sisu or Plexus container.
*/
class ServiceLoaderCompilerManagerTest {

@javax.inject.Named("per-call")
public static class PerCallCompiler extends AbstractCompiler {
public PerCallCompiler() {
super(CompilerOutputStyle.ONE_OUTPUT_FILE_PER_INPUT_FILE, ".x", ".y", null);
}

@Override
public String getCompilerId() {
return "per-call";
}

@Override
public String[] createCommandLine(CompilerConfiguration config) throws CompilerException {
return new String[0];
}
}

@javax.inject.Named("shared")
@javax.inject.Singleton
public static class SharedCompiler extends PerCallCompiler {
@Override
public String getCompilerId() {
return "shared";
}
}

private static URLClassLoader loaderWith(Path dir, String... lines) throws Exception {
Path services = dir.resolve("META-INF/services/" + Compiler.class.getName());
Files.createDirectories(services.getParent());
Files.write(services, String.join("\n", lines).getBytes(StandardCharsets.UTF_8));
return new URLClassLoader(
new URL[] {dir.toUri().toURL()}, ServiceLoaderCompilerManagerTest.class.getClassLoader());
}

@Test
void compilersAreFoundById(@TempDir Path dir) throws Exception {
try (URLClassLoader loader = loaderWith(dir, PerCallCompiler.class.getName(), SharedCompiler.class.getName())) {
CompilerManager manager = DefaultCompilerManager.fromServiceLoader(loader);

assertInstanceOf(PerCallCompiler.class, manager.getCompiler("per-call"));
assertInstanceOf(SharedCompiler.class, manager.getCompiler("shared"));
assertThrows(NoSuchCompilerException.class, () -> manager.getCompiler("foo"));
}
}

@Test
void instanceScopeFollowsSisu(@TempDir Path dir) throws Exception {
try (URLClassLoader loader = loaderWith(dir, PerCallCompiler.class.getName(), SharedCompiler.class.getName())) {
CompilerManager manager = DefaultCompilerManager.fromServiceLoader(loader);

assertNotSame(manager.getCompiler("per-call"), manager.getCompiler("per-call"));
assertSame(manager.getCompiler("shared"), manager.getCompiler("shared"));
}
}

@Test
void providerThatCannotBeLoadedIsSkipped(@TempDir Path dir) throws Exception {
try (URLClassLoader loader = loaderWith(
dir,
"org.example.MissingCompiler",
PerCallCompiler.class.getName(),
"org.example.OtherMissingCompiler",
SharedCompiler.class.getName())) {
CompilerManager manager = DefaultCompilerManager.fromServiceLoader(loader);

assertInstanceOf(PerCallCompiler.class, manager.getCompiler("per-call"));
assertInstanceOf(SharedCompiler.class, manager.getCompiler("shared"));
assertThrows(NoSuchCompilerException.class, () -> manager.getCompiler("missing"));
}
}
}
61 changes: 61 additions & 0 deletions plexus-compiler-service-loader-test/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
<?xml version="1.0" encoding="UTF-8"?>
<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/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<parent>
<groupId>org.codehaus.plexus</groupId>
<artifactId>plexus-compiler</artifactId>
<version>2.17.2-SNAPSHOT</version>
</parent>

<artifactId>plexus-compiler-service-loader-test</artifactId>

<name>Plexus Compiler ServiceLoader Tests</name>
<description>Tests that need every compiler implementation on the classpath: the ServiceLoader entries must find
them all and must not drift from the Sisu index.</description>

<properties>
<javaVersion>21</javaVersion>
<maven.deploy.skip>true</maven.deploy.skip>
</properties>

<dependencies>
<dependency>
<groupId>org.codehaus.plexus</groupId>
<artifactId>plexus-compiler-api</artifactId>
</dependency>
<dependency>
<groupId>org.codehaus.plexus</groupId>
<artifactId>plexus-compiler-manager</artifactId>
</dependency>
<dependency>
<groupId>org.codehaus.plexus</groupId>
<artifactId>plexus-compiler-aspectj</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.codehaus.plexus</groupId>
<artifactId>plexus-compiler-csharp</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.codehaus.plexus</groupId>
<artifactId>plexus-compiler-eclipse</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.codehaus.plexus</groupId>
<artifactId>plexus-compiler-javac</artifactId>
</dependency>
<dependency>
<groupId>org.codehaus.plexus</groupId>
<artifactId>plexus-compiler-javac-errorprone</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>
Loading
Loading