This is an automated email from the ASF dual-hosted git repository. rmannibucau pushed a commit to branch MNG-7827 in repository https://gitbox.apache.org/repos/asf/maven.git
commit 74be0f4143e22e098cd03013e731498418ac8542 Author: Romain Manni-Bucau <rmannibu...@gmail.com> AuthorDate: Mon Jun 26 19:35:00 2023 +0200 [MNG-7827] Ensure maven 4 Log API is the primary and documented API for mojos --- maven-plugin-api/pom.xml | 14 +++ .../java/org/apache/maven/plugin/AbstractMojo.java | 48 +++++++- .../maven/plugin/logging/SystemStreamLog.java | 56 ++++++++-- .../org/apache/maven/internal/impl/DefaultLog.java | 118 ++++++++++++++++++++ .../org/apache/maven/plugin/AbstractMojoTest.java | 123 +++++++++++++++++++++ 5 files changed, 348 insertions(+), 11 deletions(-) diff --git a/maven-plugin-api/pom.xml b/maven-plugin-api/pom.xml index 274a62ef6..db3000e36 100644 --- a/maven-plugin-api/pom.xml +++ b/maven-plugin-api/pom.xml @@ -52,6 +52,20 @@ under the License. <groupId>org.codehaus.plexus</groupId> <artifactId>plexus-classworlds</artifactId> </dependency> + + <dependency> + <groupId>${project.groupId}</groupId> + <artifactId>maven-api-core</artifactId> + <version>${project.version}</version> + <scope>provided</scope> + </dependency> + + <dependency> + <groupId>org.slf4j</groupId> + <artifactId>slf4j-jdk14</artifactId> + <version>${slf4jVersion}</version> + <scope>test</scope> + </dependency> </dependencies> <build> diff --git a/maven-plugin-api/src/main/java/org/apache/maven/plugin/AbstractMojo.java b/maven-plugin-api/src/main/java/org/apache/maven/plugin/AbstractMojo.java index 5e76c40d9..dba25a40f 100644 --- a/maven-plugin-api/src/main/java/org/apache/maven/plugin/AbstractMojo.java +++ b/maven-plugin-api/src/main/java/org/apache/maven/plugin/AbstractMojo.java @@ -18,11 +18,18 @@ */ package org.apache.maven.plugin; +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; import java.util.Map; +import java.util.function.Supplier; import org.apache.maven.plugin.logging.Log; import org.apache.maven.plugin.logging.SystemStreamLog; +import static java.util.Optional.ofNullable; + /** * Abstract class to provide most of the infrastructure required to implement a <code>Mojo</code> except for * the execute method.<br> @@ -147,7 +154,7 @@ public abstract class AbstractMojo implements Mojo, ContextEnabled { private Map pluginContext; /** - * @deprecated Use SLF4J directly + * @deprecated Use an injected {@link Log} instead in the mojo or component. */ @Deprecated @Override @@ -167,13 +174,48 @@ public abstract class AbstractMojo implements Mojo, ContextEnabled { * method directly whenever you need the logger, it is fast enough and needs no caching. * * @see org.apache.maven.plugin.Mojo#getLog() - * @deprecated Use SLF4J directly + * @deprecated Use an injected {@link org.apache.maven.api.plugin.Log} instead in the mojo or component. */ @Deprecated @Override public Log getLog() { if (log == null) { - log = new SystemStreamLog(); + // unlikely for a standard plugin, idea is to try to fallback on maven-core impl else use stdout/stderr + try { + // ensure we have slf4j + final ClassLoader loader = ofNullable(getClass().getClassLoader()) + .orElseGet(() -> Thread.currentThread().getContextClassLoader()); + final Class<?> lf = loader.loadClass("org.slf4j.LoggerFactory"); + final Method getLogger = lf.getDeclaredMethod("getLogger", Class.class); + if (!getLogger.isAccessible()) { + getLogger.setAccessible(true); + } + + // ensure we have maven-core - else we don't care to align on it + final Constructor<?> delegatingLogConstructor = loader.loadClass( + "org.apache.maven.internal.impl.DefaultLog") + .getDeclaredConstructor(getLogger.getReturnType()); + if (!delegatingLogConstructor.isAccessible()) { + delegatingLogConstructor.setAccessible(true); + } + + // load the slf4j logger and its log impl + create a facade to comply the deprecated API + final Object logger = getLogger.invoke(null, getClass()); + final Object delegate = delegatingLogConstructor.newInstance(logger); + log = (Log) Proxy.newProxyInstance( // Supplier is mainly an "unwrap" impl for advanced cases + loader, new Class<?>[] {Log.class, Supplier.class}, (proxy, method, args) -> { + if (method.getDeclaringClass() == Supplier.class) { + return delegate; + } + try { + return method.invoke(delegate, args); + } catch (InvocationTargetException ite) { + throw ite.getTargetException(); + } + }); + } catch (Error | Exception e) { + log = new SystemStreamLog(); + } } return log; diff --git a/maven-plugin-api/src/main/java/org/apache/maven/plugin/logging/SystemStreamLog.java b/maven-plugin-api/src/main/java/org/apache/maven/plugin/logging/SystemStreamLog.java index 07ff6e690..eda0f9c93 100644 --- a/maven-plugin-api/src/main/java/org/apache/maven/plugin/logging/SystemStreamLog.java +++ b/maven-plugin-api/src/main/java/org/apache/maven/plugin/logging/SystemStreamLog.java @@ -20,16 +20,17 @@ package org.apache.maven.plugin.logging; import java.io.PrintWriter; import java.io.StringWriter; +import java.util.function.Supplier; /** * Logger with "standard" output and error output stream. * * @author jdcasey * - * @deprecated Use SLF4J directly + * @deprecated Use directly {@link org.apache.maven.api.plugin.Log} instead. */ @Deprecated -public class SystemStreamLog implements Log { +public class SystemStreamLog implements Log, org.apache.maven.api.plugin.Log { /** * @see org.apache.maven.plugin.logging.Log#debug(java.lang.CharSequence) */ @@ -51,6 +52,16 @@ public class SystemStreamLog implements Log { print("debug", error); } + @Override + public void debug(final Supplier<String> content) { + debug(content.get()); + } + + @Override + public void debug(final Supplier<String> content, final Throwable error) { + debug(content.get(), error); + } + /** * @see org.apache.maven.plugin.logging.Log#info(java.lang.CharSequence) */ @@ -72,6 +83,16 @@ public class SystemStreamLog implements Log { print("info", error); } + @Override + public void info(final Supplier<String> content) { + info(content.get()); + } + + @Override + public void info(final Supplier<String> content, final Throwable error) { + info(content.get(), error); + } + /** * @see org.apache.maven.plugin.logging.Log#warn(java.lang.CharSequence) */ @@ -93,6 +114,16 @@ public class SystemStreamLog implements Log { print("warn", error); } + @Override + public void warn(final Supplier<String> content) { + warn(content.get()); + } + + @Override + public void warn(final Supplier<String> content, final Throwable error) { + warn(content.get(), error); + } + /** * @see org.apache.maven.plugin.logging.Log#error(java.lang.CharSequence) */ @@ -109,8 +140,7 @@ public class SystemStreamLog implements Log { error.printStackTrace(pWriter); - System.err.println( - "[error] " + content.toString() + System.lineSeparator() + System.lineSeparator() + sWriter.toString()); + System.err.println("[error] " + content.toString() + System.lineSeparator() + System.lineSeparator() + sWriter); } /** @@ -122,7 +152,17 @@ public class SystemStreamLog implements Log { error.printStackTrace(pWriter); - System.err.println("[error] " + sWriter.toString()); + System.err.println("[error] " + sWriter); + } + + @Override + public void error(final Supplier<String> content) { + error(content.get()); + } + + @Override + public void error(final Supplier<String> content, final Throwable error) { + error(content.get(), error); } /** @@ -164,7 +204,7 @@ public class SystemStreamLog implements Log { error.printStackTrace(pWriter); - System.out.println("[" + prefix + "] " + sWriter.toString()); + System.out.println("[" + prefix + "] " + sWriter); } private void print(String prefix, CharSequence content, Throwable error) { @@ -173,7 +213,7 @@ public class SystemStreamLog implements Log { error.printStackTrace(pWriter); - System.out.println("[" + prefix + "] " + content.toString() + System.lineSeparator() + System.lineSeparator() - + sWriter.toString()); + System.out.println( + "[" + prefix + "] " + content.toString() + System.lineSeparator() + System.lineSeparator() + sWriter); } } diff --git a/maven-plugin-api/src/test/java/org/apache/maven/internal/impl/DefaultLog.java b/maven-plugin-api/src/test/java/org/apache/maven/internal/impl/DefaultLog.java new file mode 100644 index 000000000..34f5b6143 --- /dev/null +++ b/maven-plugin-api/src/test/java/org/apache/maven/internal/impl/DefaultLog.java @@ -0,0 +1,118 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.maven.internal.impl; + +import java.util.function.Supplier; + +import org.apache.maven.api.plugin.Log; +import org.slf4j.Logger; + +// to not depend on maven-core we copy it to pass tests +public class DefaultLog implements Log { + private final Logger logger; + + public DefaultLog(final Logger logger) { + this.logger = logger; + } + + // @VisibleForTests + public Logger getLogger() { + return logger; + } + + @Override + public boolean isDebugEnabled() { + return false; + } + + @Override + public void debug(final CharSequence content) {} + + @Override + public void debug(final CharSequence content, final Throwable error) {} + + @Override + public void debug(final Throwable error) {} + + @Override + public void debug(final Supplier<String> content) {} + + @Override + public void debug(final Supplier<String> content, final Throwable error) {} + + @Override + public boolean isInfoEnabled() { + return false; + } + + @Override + public void info(final CharSequence content) {} + + @Override + public void info(final CharSequence content, final Throwable error) {} + + @Override + public void info(final Throwable error) {} + + @Override + public void info(final Supplier<String> content) {} + + @Override + public void info(final Supplier<String> content, final Throwable error) {} + + @Override + public boolean isWarnEnabled() { + return false; + } + + @Override + public void warn(final CharSequence content) {} + + @Override + public void warn(final CharSequence content, final Throwable error) {} + + @Override + public void warn(final Throwable error) {} + + @Override + public void warn(final Supplier<String> content) {} + + @Override + public void warn(final Supplier<String> content, final Throwable error) {} + + @Override + public boolean isErrorEnabled() { + return false; + } + + @Override + public void error(final CharSequence content) {} + + @Override + public void error(final CharSequence content, final Throwable error) {} + + @Override + public void error(final Throwable error) {} + + @Override + public void error(final Supplier<String> content) {} + + @Override + public void error(final Supplier<String> content, final Throwable error) {} +} diff --git a/maven-plugin-api/src/test/java/org/apache/maven/plugin/AbstractMojoTest.java b/maven-plugin-api/src/test/java/org/apache/maven/plugin/AbstractMojoTest.java new file mode 100644 index 000000000..9f7f3f137 --- /dev/null +++ b/maven-plugin-api/src/test/java/org/apache/maven/plugin/AbstractMojoTest.java @@ -0,0 +1,123 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.maven.plugin; + +import java.io.IOException; +import java.lang.reflect.Proxy; +import java.net.URL; +import java.util.Enumeration; +import java.util.List; +import java.util.function.Supplier; + +import org.apache.maven.internal.impl.DefaultLog; +import org.apache.maven.plugin.logging.Log; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.function.Executable; + +import static java.util.Collections.emptyEnumeration; +import static java.util.Collections.emptyList; +import static java.util.Collections.singletonList; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class AbstractMojoTest { + @Test + void getDefaultLogSystem() throws Throwable { + ignoring( // ignore slf4j to get default behavior + singletonList("org.slf4j."), + singletonList("org/slf4j/impl/StaticLoggerBinder.class"), + this::assertSystem); + } + + @Test + void getDefaultLogSystemOnMissingMavenCore() throws Throwable { + ignoring(singletonList("org.apache.maven.internal.impl."), emptyList(), this::assertSystem); + } + + @Test + void getDefaultLogSlf4j() { + final Log log = captureLog(); + assertTrue(Proxy.isProxyClass(log.getClass())); + final org.apache.maven.api.plugin.Log delegate = ((Supplier<org.apache.maven.api.plugin.Log>) log).get(); + assertEquals( + CaptureLogMojo.class.getName(), + assertInstanceOf(DefaultLog.class, delegate).getLogger().getName()); + } + + private void assertSystem() { + final Log log = captureLog(); + assertFalse(Proxy.isProxyClass(log.getClass())); + assertEquals( + "org.apache.maven.plugin.logging.SystemStreamLog", + log.getClass().getName()); + } + + private Log captureLog() { + final CaptureLogMojo mojo = new CaptureLogMojo(); + mojo.execute(); + assertNotNull(mojo.ref); + return mojo.ref; + } + + private void ignoring(final List<String> packageNames, final List<String> resources, final Executable test) + throws Throwable { + if (packageNames.isEmpty() && resources.isEmpty()) { + test.execute(); + return; + } + + final Thread thread = Thread.currentThread(); + final ClassLoader parent = thread.getContextClassLoader(); + final ClassLoader custom = new ClassLoader(parent) { + @Override + protected Class<?> loadClass(final String name, final boolean resolve) throws ClassNotFoundException { + if (name != null && packageNames.stream().anyMatch(name::startsWith)) { + throw new ClassNotFoundException(name); + } + return super.loadClass(name, resolve); + } + + @Override + public Enumeration<URL> getResources(final String name) throws IOException { + if (name != null && resources.stream().anyMatch(name::startsWith)) { + return emptyEnumeration(); + } + return super.getResources(name); + } + }; + try { + thread.setContextClassLoader(custom); + + } finally { + thread.setContextClassLoader(parent); + } + } + + private static class CaptureLogMojo extends AbstractMojo { + private Log ref; + + @Override + public void execute() { + ref = getLog(); + } + } +}