diff --git a/apache-maven/pom.xml b/apache-maven/pom.xml index 2fee6c9747ff..5d04cddad841 100644 --- a/apache-maven/pom.xml +++ b/apache-maven/pom.xml @@ -93,8 +93,8 @@ under the License. maven-slf4j-provider - org.fusesource.jansi - jansi + org.jline + jline-terminal-ffm org.junit.jupiter @@ -139,13 +139,13 @@ under the License. maven-dependency-plugin - unpack-jansi-native + unpack-jline-native unpack-dependencies - jansi - org/fusesource/jansi/internal/native/Windows/** + jline-native + org/jline/nativ/** diff --git a/apache-maven/src/bin/mvn b/apache-maven/src/bin/mvn index 47dc7d7f4ed0..02c7ddd75228 100755 --- a/apache-maven/src/bin/mvn +++ b/apache-maven/src/bin/mvn @@ -211,6 +211,6 @@ exec "$JAVACMD" \ -classpath "${CLASSWORLDS_JAR}" \ "-Dclassworlds.conf=${MAVEN_HOME}/bin/m2.conf" \ "-Dmaven.home=${MAVEN_HOME}" \ - "-Dlibrary.jansi.path=${MAVEN_HOME}/lib/jansi-native" \ + "-Dlibrary.jline.path=${MAVEN_HOME}/lib/jline-native" \ "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ ${CLASSWORLDS_LAUNCHER} ${MAVEN_ARGS} "$@" diff --git a/apache-maven/src/bin/mvn.cmd b/apache-maven/src/bin/mvn.cmd index affbe4318216..bf6b152f2f4d 100644 --- a/apache-maven/src/bin/mvn.cmd +++ b/apache-maven/src/bin/mvn.cmd @@ -190,7 +190,7 @@ set "INTERNAL_MAVEN_OPTS=--enable-native-access=ALL-UNNAMED %INTERNAL_MAVEN_OPTS -classpath %CLASSWORLDS_JAR% ^ "-Dclassworlds.conf=%MAVEN_HOME%\bin\m2.conf" ^ "-Dmaven.home=%MAVEN_HOME%" ^ - "-Dlibrary.jansi.path=%MAVEN_HOME%\lib\jansi-native" ^ + "-Dlibrary.jline.path=%MAVEN_HOME%\lib\jline-native" ^ "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" ^ %CLASSWORLDS_LAUNCHER% %MAVEN_ARGS% %MAVEN_CMD_LINE_ARGS% if ERRORLEVEL 1 goto error diff --git a/apache-maven/src/lib/jansi-native/README.txt b/apache-maven/src/lib/jansi-native/README.txt deleted file mode 100644 index 26a957e1b564..000000000000 --- a/apache-maven/src/lib/jansi-native/README.txt +++ /dev/null @@ -1,8 +0,0 @@ -This directory contains Jansi native libraries extracted from Jansi JAR. - -You can add your own build for platforms not natively supported by Jansi. -See here [1] on how to compile for your platform and and here [2] how libraries -follow Jansi's directory and filename conventions. - -[1] https://github.com/fusesource/jansi/tree/master/src/main/native -[2] https://github.com/fusesource/jansi/blob/321a8ff71c731e10f4ea05c607860180276b2215/src/main/java/org/fusesource/jansi/internal/OSInfo.java diff --git a/apache-maven/src/lib/jline-native/README.txt b/apache-maven/src/lib/jline-native/README.txt new file mode 100644 index 000000000000..ed74b45f7e13 --- /dev/null +++ b/apache-maven/src/lib/jline-native/README.txt @@ -0,0 +1,8 @@ +This directory contains JLine native libraries extracted from JLine JAR. + +You can add your own build for platforms not natively supported by JLine. +See here [1] on how to compile for your platform and here [2] how libraries +follow JLine's directory and filename conventions. + +[1] https://github.com/jline/jline3/tree/master/native +[2] https://github.com/jline/jline3/blob/master/native/src/main/java/org/jline/nativ/OSInfo.java diff --git a/apache-maven/src/main/appended-resources/META-INF/LICENSE.vm b/apache-maven/src/main/appended-resources/META-INF/LICENSE.vm index b87dd6ab7b5d..e2b25312a6e5 100644 --- a/apache-maven/src/main/appended-resources/META-INF/LICENSE.vm +++ b/apache-maven/src/main/appended-resources/META-INF/LICENSE.vm @@ -41,6 +41,8 @@ subject to the terms and conditions of the following licenses: #* *##set ( $spdx = 'EPL-2.0' ) #* *##elseif ( $license.url.contains( "www.apache.org/licenses/LICENSE-2.0" ) ) #* *##set ( $spdx = 'Apache-2.0' ) +#* *##elseif ( $license.name == "BSD-3-Clause" || $license.url.contains( "opensource.org/licenses/BSD-3-Clause" ) ) +#* *##set ( $spdx = 'BSD-3-Clause' ) #* *##else #* *### unrecognized license will require analysis to know obligations #* *##set ( $spdx = 'unrecognized' ) diff --git a/apache-maven/src/main/appended-resources/licenses/BSD-3-Clause.txt b/apache-maven/src/main/appended-resources/licenses/BSD-3-Clause.txt new file mode 100644 index 000000000000..4b0469c95f9f --- /dev/null +++ b/apache-maven/src/main/appended-resources/licenses/BSD-3-Clause.txt @@ -0,0 +1,28 @@ +Copyright + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + +1. Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +“AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED +TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/apache-maven/src/main/assembly/component.xml b/apache-maven/src/main/assembly/component.xml index 3413c4f0405a..f63ce88a9766 100644 --- a/apache-maven/src/main/assembly/component.xml +++ b/apache-maven/src/main/assembly/component.xml @@ -63,10 +63,12 @@ under the License. - target/dependency/org/fusesource/jansi/internal/native - lib/jansi-native + target/dependency/org/jline/nativ + lib/jline-native - ** + **/*.so + **/*.jnilib + **/*.dll diff --git a/maven-core/pom.xml b/maven-core/pom.xml index 5f02119eccd8..6867da4a0347 100644 --- a/maven-core/pom.xml +++ b/maven-core/pom.xml @@ -86,10 +86,6 @@ under the License. org.apache.maven.resolver maven-resolver-util - - org.apache.maven.shared - maven-shared-utils - org.eclipse.sisu org.eclipse.sisu.plexus diff --git a/maven-core/src/main/java/org/apache/maven/lifecycle/LifecycleExecutionException.java b/maven-core/src/main/java/org/apache/maven/lifecycle/LifecycleExecutionException.java index c7b8c085e6e9..8636b78f03a3 100644 --- a/maven-core/src/main/java/org/apache/maven/lifecycle/LifecycleExecutionException.java +++ b/maven-core/src/main/java/org/apache/maven/lifecycle/LifecycleExecutionException.java @@ -18,11 +18,11 @@ */ package org.apache.maven.lifecycle; +import org.apache.maven.message.MessageBuilder; +import org.apache.maven.message.MessageBuilderFactory; +import org.apache.maven.message.internal.DefaultMessageBuilderFactory; import org.apache.maven.plugin.MojoExecution; import org.apache.maven.project.MavenProject; -import org.apache.maven.shared.utils.logging.MessageBuilder; - -import static org.apache.maven.shared.utils.logging.MessageUtils.buffer; /** * @author Jason van Zyl @@ -57,16 +57,33 @@ public LifecycleExecutionException(String message, MojoExecution execution, Mave this.project = project; } + /** + * @deprecated Use {@link #LifecycleExecutionException(MessageBuilderFactory, MojoExecution, MavenProject, Throwable)} + * instead to allow for better message formatting. + */ + @Deprecated public LifecycleExecutionException(MojoExecution execution, MavenProject project, Throwable cause) { - this(createMessage(execution, project, cause), execution, project, cause); + this(createMessage(new DefaultMessageBuilderFactory(), execution, project, cause), execution, project, cause); + } + + public LifecycleExecutionException( + MessageBuilderFactory messageBuilderFactory, + MojoExecution execution, + MavenProject project, + Throwable cause) { + this(createMessage(messageBuilderFactory, execution, project, cause), execution, project, cause); } public MavenProject getProject() { return project; } - private static String createMessage(MojoExecution execution, MavenProject project, Throwable cause) { - MessageBuilder buffer = buffer(256); + private static String createMessage( + MessageBuilderFactory messageBuilderFactory, + MojoExecution execution, + MavenProject project, + Throwable cause) { + MessageBuilder buffer = messageBuilderFactory.builder(256); buffer.a("Failed to execute goal"); diff --git a/maven-core/src/main/java/org/apache/maven/lifecycle/internal/DefaultMojoExecutionConfigurator.java b/maven-core/src/main/java/org/apache/maven/lifecycle/internal/DefaultMojoExecutionConfigurator.java index 87f4f88bc938..ca5937f70633 100644 --- a/maven-core/src/main/java/org/apache/maven/lifecycle/internal/DefaultMojoExecutionConfigurator.java +++ b/maven-core/src/main/java/org/apache/maven/lifecycle/internal/DefaultMojoExecutionConfigurator.java @@ -26,6 +26,8 @@ import java.util.stream.Stream; import org.apache.maven.lifecycle.MojoExecutionConfigurator; +import org.apache.maven.message.MessageBuilder; +import org.apache.maven.message.MessageBuilderFactory; import org.apache.maven.model.Plugin; import org.apache.maven.model.PluginExecution; import org.apache.maven.plugin.MojoExecution; @@ -33,9 +35,8 @@ import org.apache.maven.plugin.descriptor.Parameter; import org.apache.maven.plugin.descriptor.PluginDescriptor; import org.apache.maven.project.MavenProject; -import org.apache.maven.shared.utils.logging.MessageBuilder; -import org.apache.maven.shared.utils.logging.MessageUtils; import org.codehaus.plexus.component.annotations.Component; +import org.codehaus.plexus.component.annotations.Requirement; import org.codehaus.plexus.util.StringUtils; import org.codehaus.plexus.util.xml.Xpp3Dom; import org.slf4j.Logger; @@ -50,6 +51,9 @@ public class DefaultMojoExecutionConfigurator implements MojoExecutionConfigurator { private final Logger logger = LoggerFactory.getLogger(getClass()); + @Requirement + private MessageBuilderFactory messageBuilderFactory; + @Override public void configure(MavenProject project, MojoExecution mojoExecution, boolean allowPluginLevelConfig) { String g = mojoExecution.getPlugin().getGroupId(); @@ -142,7 +146,8 @@ private void checkUnknownMojoConfigurationParameters(MojoExecution mojoExecution unknownParameters.stream() .filter(parameterName -> isNotReportPluginsForMavenSite(parameterName, mojoExecution)) .forEach(name -> { - MessageBuilder messageBuilder = MessageUtils.buffer() + MessageBuilder messageBuilder = messageBuilderFactory + .builder() .warning("Parameter '") .warning(name) .warning("' is unknown for plugin '") diff --git a/maven-core/src/main/java/org/apache/maven/lifecycle/internal/LifecycleDependencyResolver.java b/maven-core/src/main/java/org/apache/maven/lifecycle/internal/LifecycleDependencyResolver.java index e1251c2d2890..6abb43f9796b 100644 --- a/maven-core/src/main/java/org/apache/maven/lifecycle/internal/LifecycleDependencyResolver.java +++ b/maven-core/src/main/java/org/apache/maven/lifecycle/internal/LifecycleDependencyResolver.java @@ -38,6 +38,7 @@ import org.apache.maven.eventspy.internal.EventSpyDispatcher; import org.apache.maven.execution.MavenSession; import org.apache.maven.lifecycle.LifecycleExecutionException; +import org.apache.maven.message.MessageBuilderFactory; import org.apache.maven.project.DefaultDependencyResolutionRequest; import org.apache.maven.project.DependencyResolutionException; import org.apache.maven.project.DependencyResolutionResult; @@ -80,6 +81,9 @@ public class LifecycleDependencyResolver { @Inject private ProjectArtifactsCache projectArtifactsCache; + @Inject + private MessageBuilderFactory messageBuilderFactory; + public LifecycleDependencyResolver() {} public LifecycleDependencyResolver(ProjectDependenciesResolver projectDependenciesResolver, Logger logger) { @@ -243,7 +247,7 @@ private Set getDependencies( logger.warn("Try running the build up to the lifecycle phase \"package\""); } else { - throw new LifecycleExecutionException(null, project, e); + throw new LifecycleExecutionException(messageBuilderFactory, null, project, e); } } diff --git a/maven-core/src/main/java/org/apache/maven/lifecycle/internal/MojoExecutor.java b/maven-core/src/main/java/org/apache/maven/lifecycle/internal/MojoExecutor.java index cf40525c79c1..4061820ac652 100644 --- a/maven-core/src/main/java/org/apache/maven/lifecycle/internal/MojoExecutor.java +++ b/maven-core/src/main/java/org/apache/maven/lifecycle/internal/MojoExecutor.java @@ -39,6 +39,7 @@ import org.apache.maven.internal.MultilineMessageHelper; import org.apache.maven.lifecycle.LifecycleExecutionException; import org.apache.maven.lifecycle.MissingProjectException; +import org.apache.maven.message.MessageBuilderFactory; import org.apache.maven.plugin.BuildPluginManager; import org.apache.maven.plugin.MavenPluginManager; import org.apache.maven.plugin.MojoExecution; @@ -88,6 +89,9 @@ public class MojoExecutor { @Requirement private ExecutionEventCatapult eventCatapult; + @Requirement + private MessageBuilderFactory messageBuilderFactory; + private final OwnerReentrantReadWriteLock aggregatorLock = new OwnerReentrantReadWriteLock(); @Requirement @@ -186,7 +190,7 @@ private void execute( try { mavenPluginManager.checkPrerequisites(mojoDescriptor.getPluginDescriptor()); } catch (PluginIncompatibleException e) { - throw new LifecycleExecutionException(mojoExecution, session.getCurrentProject(), e); + throw new LifecycleExecutionException(messageBuilderFactory, mojoExecution, session.getCurrentProject(), e); } if (mojoDescriptor.isProjectRequired() && !session.getRequest().isProjectPresent()) { @@ -194,14 +198,15 @@ private void execute( "Goal requires a project to execute" + " but there is no POM in this directory (" + session.getExecutionRootDirectory() + ")." + " Please verify you invoked Maven from the correct directory."); - throw new LifecycleExecutionException(mojoExecution, null, cause); + throw new LifecycleExecutionException(messageBuilderFactory, mojoExecution, null, cause); } if (mojoDescriptor.isOnlineRequired() && session.isOffline()) { if (MojoExecution.Source.CLI.equals(mojoExecution.getSource())) { Throwable cause = new IllegalStateException( "Goal requires online mode for execution" + " but Maven is currently offline."); - throw new LifecycleExecutionException(mojoExecution, session.getCurrentProject(), cause); + throw new LifecycleExecutionException( + messageBuilderFactory, mojoExecution, session.getCurrentProject(), cause); } else { eventCatapult.fire(ExecutionEvent.Type.MojoSkipped, session, mojoExecution); @@ -330,7 +335,8 @@ private void doExecute2(MavenSession session, MojoExecution mojoExecution) throw | PluginManagerException | PluginConfigurationException | MojoExecutionException e) { - throw new LifecycleExecutionException(mojoExecution, session.getCurrentProject(), e); + throw new LifecycleExecutionException( + messageBuilderFactory, mojoExecution, session.getCurrentProject(), e); } eventCatapult.fire(ExecutionEvent.Type.MojoSucceeded, session, mojoExecution); diff --git a/maven-core/src/main/java/org/apache/maven/message/MessageBuilder.java b/maven-core/src/main/java/org/apache/maven/message/MessageBuilder.java new file mode 100644 index 000000000000..ee834aa374e0 --- /dev/null +++ b/maven-core/src/main/java/org/apache/maven/message/MessageBuilder.java @@ -0,0 +1,277 @@ +/* + * 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.message; + +/** + * Message builder that supports configurable styling. + * + * @since 3.10.0 + * @see MessageBuilderFactory + */ +public interface MessageBuilder extends Appendable { + + /** + * Append message content in trace style. + * By default, bold magenta + * + * @param message the message to append + * @return the current builder + */ + default MessageBuilder trace(Object message) { + return style("." + Constants.MAVEN_STYLE_TRACE_NAME + ":-" + Constants.MAVEN_STYLE_TRACE_DEFAULT, message); + } + + /** + * Append message content in debug style. + * By default, bold cyan + * + * @param message the message to append + * @return the current builder + */ + default MessageBuilder debug(Object message) { + return style("." + Constants.MAVEN_STYLE_DEBUG_NAME + ":-" + Constants.MAVEN_STYLE_DEBUG_DEFAULT, message); + } + + /** + * Append message content in info style. + * By default, bold blue + * + * @param message the message to append + * @return the current builder + */ + default MessageBuilder info(Object message) { + return style("." + Constants.MAVEN_STYLE_INFO_NAME + ":-" + Constants.MAVEN_STYLE_INFO_DEFAULT, message); + } + + /** + * Append message content in warning style. + * By default, bold yellow + * + * @param message the message to append + * @return the current builder + */ + default MessageBuilder warning(Object message) { + return style("." + Constants.MAVEN_STYLE_WARNING_NAME + ":-" + Constants.MAVEN_STYLE_WARNING_DEFAULT, message); + } + + /** + * Append message content in error style. + * By default, bold red + * + * @param message the message to append + * @return the current builder + */ + default MessageBuilder error(Object message) { + return style("." + Constants.MAVEN_STYLE_ERROR_NAME + ":-" + Constants.MAVEN_STYLE_ERROR_DEFAULT, message); + } + + /** + * Append message content in success style. + * By default, bold green + * + * @param message the message to append + * @return the current builder + */ + default MessageBuilder success(Object message) { + return style("." + Constants.MAVEN_STYLE_SUCCESS_NAME + ":-" + Constants.MAVEN_STYLE_SUCCESS_DEFAULT, message); + } + + /** + * Append message content in failure style. + * By default, bold red + * + * @param message the message to append + * @return the current builder + */ + default MessageBuilder failure(Object message) { + return style("." + Constants.MAVEN_STYLE_FAILURE_NAME + ":-" + Constants.MAVEN_STYLE_FAILURE_DEFAULT, message); + } + + /** + * Append message content in strong style. + * By default, bold + * + * @param message the message to append + * @return the current builder + */ + default MessageBuilder strong(Object message) { + return style("." + Constants.MAVEN_STYLE_STRONG_NAME + ":-" + Constants.MAVEN_STYLE_STRONG_DEFAULT, message); + } + + /** + * Append message content in mojo style. + * By default, green + * + * @param message the message to append + * @return the current builder + */ + default MessageBuilder mojo(Object message) { + return style("." + Constants.MAVEN_STYLE_MOJO_NAME + ":-" + Constants.MAVEN_STYLE_MOJO_DEFAULT, message); + } + + /** + * Append message content in project style. + * By default, cyan + * + * @param message the message to append + * @return the current builder + */ + default MessageBuilder project(Object message) { + return style("." + Constants.MAVEN_STYLE_PROJECT_NAME + ":-" + Constants.MAVEN_STYLE_PROJECT_DEFAULT, message); + } + + default MessageBuilder style(String style, Object message) { + return style(style).a(message).resetStyle(); + } + + MessageBuilder style(String style); + + MessageBuilder resetStyle(); + + // + // message building methods modelled after Ansi methods + // + + @Override + MessageBuilder append(CharSequence cs); + + @Override + MessageBuilder append(CharSequence cs, int start, int end); + + @Override + MessageBuilder append(char c); + + /** + * Append content to the message buffer. + * + * @param value the content to append + * @param offset the index of the first {@code char} to append + * @param len the number of {@code char}s to append + * @return the current builder + */ + default MessageBuilder a(char[] value, int offset, int len) { + return append(String.valueOf(value, offset, len)); + } + + /** + * Append content to the message buffer. + * + * @param value the content to append + * @return the current builder + */ + default MessageBuilder a(char[] value) { + return append(String.valueOf(value)); + } + + /** + * Append content to the message buffer. + * + * @param value the content to append + * @param start the starting index of the subsequence to be appended + * @param end the end index of the subsequence to be appended + * @return the current builder + */ + default MessageBuilder a(CharSequence value, int start, int end) { + return append(value, start, end); + } + + /** + * Append content to the message buffer. + * + * @param value the content to append + * @return the current builder + */ + default MessageBuilder a(CharSequence value) { + return append(value); + } + + /** + * Append content to the message buffer. + * + * @param value the content to append + * @return the current builder + */ + default MessageBuilder a(Object value) { + return append(String.valueOf(value)); + } + + /** + * Append newline to the message buffer. + * + * @return the current builder + */ + default MessageBuilder newline() { + return append(System.lineSeparator()); + } + + /** + * Append formatted content to the buffer. + * @see String#format(String, Object...) + * + * @param pattern a format string + * @param args arguments referenced by the format specifiers in the format string + * @return the current builder + */ + default MessageBuilder format(String pattern, Object... args) { + return append(String.format(pattern, args)); + } + + /** + * Set the buffer length. + * + * @param length the new length + * @return the current builder + */ + MessageBuilder setLength(int length); + + /** + * Return the built message. + * + * @return the message + */ + String build(); + + class Constants { + // Style Names + public static final String MAVEN_STYLE_TRANSFER_NAME = "transfer"; + public static final String MAVEN_STYLE_TRACE_NAME = "trace"; + public static final String MAVEN_STYLE_DEBUG_NAME = "debug"; + public static final String MAVEN_STYLE_INFO_NAME = "info"; + public static final String MAVEN_STYLE_WARNING_NAME = "warning"; + public static final String MAVEN_STYLE_ERROR_NAME = "error"; + public static final String MAVEN_STYLE_SUCCESS_NAME = "success"; + public static final String MAVEN_STYLE_FAILURE_NAME = "failure"; + public static final String MAVEN_STYLE_STRONG_NAME = "strong"; + public static final String MAVEN_STYLE_MOJO_NAME = "mojo"; + public static final String MAVEN_STYLE_PROJECT_NAME = "project"; + + // Default Values + public static final String MAVEN_STYLE_TRANSFER_DEFAULT = "f:bright-black"; + public static final String MAVEN_STYLE_TRACE_DEFAULT = "bold,f:magenta"; + public static final String MAVEN_STYLE_DEBUG_DEFAULT = "bold,f:cyan"; + public static final String MAVEN_STYLE_INFO_DEFAULT = "bold,f:blue"; + public static final String MAVEN_STYLE_WARNING_DEFAULT = "bold,f:yellow"; + public static final String MAVEN_STYLE_ERROR_DEFAULT = "bold,f:red"; + public static final String MAVEN_STYLE_SUCCESS_DEFAULT = "bold,f:green"; + public static final String MAVEN_STYLE_FAILURE_DEFAULT = "bold,f:red"; + public static final String MAVEN_STYLE_STRONG_DEFAULT = "bold"; + public static final String MAVEN_STYLE_MOJO_DEFAULT = "f:green"; + public static final String MAVEN_STYLE_PROJECT_DEFAULT = "f:cyan"; + } +} diff --git a/maven-core/src/main/java/org/apache/maven/message/MessageBuilderFactory.java b/maven-core/src/main/java/org/apache/maven/message/MessageBuilderFactory.java new file mode 100644 index 000000000000..642ca7d773de --- /dev/null +++ b/maven-core/src/main/java/org/apache/maven/message/MessageBuilderFactory.java @@ -0,0 +1,51 @@ +/* + * 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.message; + +/** + * A factory for {@link MessageBuilder}. + * + * @since 3.10.0 + */ +public interface MessageBuilderFactory { + /** + * Checks if the underlying output does support styling or not. + * @return whether color styling is supported or not + */ + boolean isColorEnabled(); + + /** + * Returns the terminal width or -1 if not supported. + * @return the terminal width + */ + int getTerminalWidth(); + + /** + * Creates a new message builder. + * @return a new message builder + */ + MessageBuilder builder(); + + /** + * Creates a new message builder of the specified size. + * @param size the initial size of the message builder buffer + * @return a new message builder + */ + MessageBuilder builder(int size); +} diff --git a/maven-core/src/main/java/org/apache/maven/message/internal/DefaultMessageBuilder.java b/maven-core/src/main/java/org/apache/maven/message/internal/DefaultMessageBuilder.java new file mode 100644 index 000000000000..474244f738dd --- /dev/null +++ b/maven-core/src/main/java/org/apache/maven/message/internal/DefaultMessageBuilder.java @@ -0,0 +1,78 @@ +/* + * 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.message.internal; + +import org.apache.maven.message.MessageBuilder; + +class DefaultMessageBuilder implements MessageBuilder { + + private final StringBuilder buffer; + + DefaultMessageBuilder() { + this(new StringBuilder()); + } + + DefaultMessageBuilder(StringBuilder buffer) { + this.buffer = buffer; + } + + @Override + public MessageBuilder style(String style) { + return this; + } + + @Override + public MessageBuilder resetStyle() { + return this; + } + + @Override + public MessageBuilder append(CharSequence cs) { + buffer.append(cs); + return this; + } + + @Override + public MessageBuilder append(CharSequence cs, int start, int end) { + buffer.append(cs, start, end); + return this; + } + + @Override + public MessageBuilder append(char c) { + buffer.append(c); + return this; + } + + @Override + public MessageBuilder setLength(int length) { + buffer.setLength(length); + return this; + } + + @Override + public String build() { + return buffer.toString(); + } + + @Override + public String toString() { + return build(); + } +} diff --git a/maven-core/src/main/java/org/apache/maven/message/internal/DefaultMessageBuilderFactory.java b/maven-core/src/main/java/org/apache/maven/message/internal/DefaultMessageBuilderFactory.java new file mode 100644 index 000000000000..a3e1daf9241e --- /dev/null +++ b/maven-core/src/main/java/org/apache/maven/message/internal/DefaultMessageBuilderFactory.java @@ -0,0 +1,52 @@ +/* + * 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.message.internal; + +import javax.inject.Named; +import javax.inject.Singleton; + +import org.apache.maven.message.MessageBuilder; +import org.apache.maven.message.MessageBuilderFactory; +import org.eclipse.sisu.Priority; + +@Named +@Singleton +@Priority(-1) +public class DefaultMessageBuilderFactory implements MessageBuilderFactory { + + @Override + public boolean isColorEnabled() { + return false; + } + + @Override + public int getTerminalWidth() { + return -1; + } + + @Override + public MessageBuilder builder() { + return new DefaultMessageBuilder(); + } + + @Override + public MessageBuilder builder(int size) { + return new DefaultMessageBuilder(new StringBuilder(size)); + } +} diff --git a/maven-core/src/main/java/org/apache/maven/plugin/internal/DeprecatedPluginValidator.java b/maven-core/src/main/java/org/apache/maven/plugin/internal/DeprecatedPluginValidator.java index 5544a74f34cc..b876969d9a36 100644 --- a/maven-core/src/main/java/org/apache/maven/plugin/internal/DeprecatedPluginValidator.java +++ b/maven-core/src/main/java/org/apache/maven/plugin/internal/DeprecatedPluginValidator.java @@ -23,10 +23,10 @@ import javax.inject.Singleton; import org.apache.maven.execution.MavenSession; +import org.apache.maven.message.MessageBuilderFactory; import org.apache.maven.plugin.PluginValidationManager; import org.apache.maven.plugin.descriptor.MojoDescriptor; import org.apache.maven.plugin.descriptor.Parameter; -import org.apache.maven.shared.utils.logging.MessageUtils; import org.codehaus.plexus.component.configurator.expression.ExpressionEvaluator; import org.codehaus.plexus.configuration.PlexusConfiguration; @@ -39,9 +39,13 @@ @Named class DeprecatedPluginValidator extends AbstractMavenPluginDescriptorSourcedParametersValidator { + private final MessageBuilderFactory messageBuilderFactory; + @Inject - DeprecatedPluginValidator(PluginValidationManager pluginValidationManager) { + DeprecatedPluginValidator( + PluginValidationManager pluginValidationManager, MessageBuilderFactory messageBuilderFactory) { super(pluginValidationManager); + this.messageBuilderFactory = messageBuilderFactory; } @Override @@ -94,7 +98,8 @@ private void checkParameter( } private String logDeprecatedMojo(MojoDescriptor mojoDescriptor) { - return MessageUtils.buffer() + return messageBuilderFactory + .builder() .warning("Goal '") .warning(mojoDescriptor.getGoal()) .warning("' is deprecated: ") diff --git a/maven-embedder/pom.xml b/maven-embedder/pom.xml index 9817dc1043c9..f8b548f9eb7a 100644 --- a/maven-embedder/pom.xml +++ b/maven-embedder/pom.xml @@ -69,8 +69,8 @@ under the License. maven-resolver-util - org.apache.maven.shared - maven-shared-utils + org.apache.maven + maven-jline com.google.inject @@ -160,11 +160,6 @@ under the License. mockito-core test - - org.fusesource.jansi - jansi - test - diff --git a/maven-embedder/src/main/java/org/apache/maven/cli/CLIReportingUtils.java b/maven-embedder/src/main/java/org/apache/maven/cli/CLIReportingUtils.java index e220ce783f4b..7bc661fa7402 100644 --- a/maven-embedder/src/main/java/org/apache/maven/cli/CLIReportingUtils.java +++ b/maven-embedder/src/main/java/org/apache/maven/cli/CLIReportingUtils.java @@ -29,7 +29,7 @@ import org.codehaus.plexus.util.StringUtils; import org.slf4j.Logger; -import static org.apache.maven.shared.utils.logging.MessageUtils.buffer; +import static org.apache.maven.jline.MessageUtils.builder; /** * Utility class used to report errors, statistics, application version info, etc. @@ -53,7 +53,7 @@ public static String showVersion() { final String ls = System.lineSeparator(); Properties properties = getBuildProperties(); StringBuilder version = new StringBuilder(256); - version.append(buffer().strong(createMavenVersionString(properties))).append(ls); + version.append(builder().strong(createMavenVersionString(properties))).append(ls); version.append(reduce(properties.getProperty("distributionShortName") + " home: " + System.getProperty("maven.home", ""))) .append(ls); diff --git a/maven-embedder/src/main/java/org/apache/maven/cli/MavenCli.java b/maven-embedder/src/main/java/org/apache/maven/cli/MavenCli.java index 5c62edfa179e..985362b680d5 100644 --- a/maven-embedder/src/main/java/org/apache/maven/cli/MavenCli.java +++ b/maven-embedder/src/main/java/org/apache/maven/cli/MavenCli.java @@ -84,14 +84,14 @@ import org.apache.maven.execution.scope.internal.MojoExecutionScopeModule; import org.apache.maven.extension.internal.CoreExports; import org.apache.maven.extension.internal.CoreExtensionEntry; +import org.apache.maven.jline.MessageUtils; import org.apache.maven.lifecycle.LifecycleExecutionException; +import org.apache.maven.message.MessageBuilder; import org.apache.maven.model.building.ModelProcessor; import org.apache.maven.project.MavenProject; import org.apache.maven.properties.internal.EnvironmentUtils; import org.apache.maven.properties.internal.SystemProperties; import org.apache.maven.session.scope.internal.SessionScopeModule; -import org.apache.maven.shared.utils.logging.MessageBuilder; -import org.apache.maven.shared.utils.logging.MessageUtils; import org.apache.maven.toolchain.building.DefaultToolchainsBuildingRequest; import org.apache.maven.toolchain.building.ToolchainsBuilder; import org.apache.maven.toolchain.building.ToolchainsBuildingResult; @@ -123,7 +123,7 @@ import static org.apache.maven.cli.CLIManager.COLOR; import static org.apache.maven.cli.ResolveFile.resolveFile; -import static org.apache.maven.shared.utils.logging.MessageUtils.buffer; +import static org.apache.maven.jline.MessageUtils.builder; // TODO push all common bits back to plexus cli and prepare for transition to Guice. We don't need 50 ways to make CLIs @@ -525,6 +525,12 @@ void logging(CliRequest cliRequest) { } else if (cliRequest.commandLine.hasOption(CLIManager.BATCH_MODE) || cliRequest.commandLine.hasOption(CLIManager.LOG_FILE)) { MessageUtils.setColorEnabled(false); + } else { + // if the terminal is detected as dumb terminal, disable colors + if (MessageUtils.getTerminal() != null + && MessageUtils.getTerminal().getType().startsWith("dumb")) { + MessageUtils.setColorEnabled(false); + } } // LOG STREAMS @@ -569,14 +575,15 @@ private void commands(CliRequest cliRequest) { if (slf4jLogger.isDebugEnabled()) { slf4jLogger.debug("Message scheme: {}", (MessageUtils.isColorEnabled() ? "color" : "plain")); + slf4jLogger.debug("Message terminal: {}", MessageUtils.getTerminal()); if (MessageUtils.isColorEnabled()) { - MessageBuilder buff = MessageUtils.buffer(); + MessageBuilder buff = builder(); buff.a("Message styles: "); - buff.a(MessageUtils.level().debug("debug")).a(' '); - buff.a(MessageUtils.level().info("info")).a(' '); - buff.a(MessageUtils.level().warning("warning")).a(' '); - buff.a(MessageUtils.level().error("error")).a(' '); - + buff.a(builder().trace("trace")).a(' '); + buff.a(builder().debug("debug")).a(' '); + buff.a(builder().info("info")).a(' '); + buff.a(builder().warning("warning")).a(' '); + buff.a(builder().error("error")).a(' '); buff.success("success").a(' '); buff.failure("failure").a(' '); buff.strong("strong").a(' '); @@ -1014,11 +1021,12 @@ private int execute(CliRequest cliRequest) throws MavenExecutionRequestPopulatio if (!cliRequest.showErrors) { slf4jLogger.error( "To see the full stack trace of the errors, re-run Maven with the {} switch.", - buffer().strong("-e")); + builder().strong("-e")); } if (!slf4jLogger.isDebugEnabled()) { slf4jLogger.error( - "Re-run Maven using the {} switch to enable full debug logging.", buffer().strong("-X")); + "Re-run Maven using the {} switch to enable full debug logging.", + builder().strong("-X")); } if (!references.isEmpty()) { @@ -1027,7 +1035,7 @@ private int execute(CliRequest cliRequest) throws MavenExecutionRequestPopulatio + ", please read the following articles:"); for (Map.Entry entry : references.entrySet()) { - slf4jLogger.error("{} {}", buffer().strong(entry.getValue()), entry.getKey()); + slf4jLogger.error("{} {}", builder().strong(entry.getValue()), entry.getKey()); } } @@ -1035,7 +1043,8 @@ private int execute(CliRequest cliRequest) throws MavenExecutionRequestPopulatio && !project.equals(result.getTopologicallySortedProjects().get(0))) { slf4jLogger.error(""); slf4jLogger.error("After correcting the problems, you can resume the build with the command"); - slf4jLogger.error(buffer().a(" ") + slf4jLogger.error(builder() + .a(" ") .strong("mvn -rf " + getResumeFrom(result.getTopologicallySortedProjects(), project)) .toString()); } @@ -1094,9 +1103,9 @@ private void logSummary( if (StringUtils.isNotEmpty(referenceKey)) { if (msg.indexOf('\n') < 0) { - msg += " -> " + buffer().strong(referenceKey); + msg += " -> " + builder().strong(referenceKey); } else { - msg += "\n-> " + buffer().strong(referenceKey); + msg += "\n-> " + builder().strong(referenceKey); } } diff --git a/maven-embedder/src/main/java/org/apache/maven/cli/event/ExecutionEventLogger.java b/maven-embedder/src/main/java/org/apache/maven/cli/event/ExecutionEventLogger.java index f7e2b48633e3..b6f8af98a552 100644 --- a/maven-embedder/src/main/java/org/apache/maven/cli/event/ExecutionEventLogger.java +++ b/maven-embedder/src/main/java/org/apache/maven/cli/event/ExecutionEventLogger.java @@ -30,17 +30,18 @@ import org.apache.maven.execution.ExecutionEvent; import org.apache.maven.execution.MavenExecutionResult; import org.apache.maven.execution.MavenSession; +import org.apache.maven.jline.MessageUtils; +import org.apache.maven.message.MessageBuilder; import org.apache.maven.plugin.MojoExecution; import org.apache.maven.plugin.descriptor.MojoDescriptor; import org.apache.maven.project.MavenProject; -import org.apache.maven.shared.utils.logging.MessageBuilder; import org.codehaus.plexus.util.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import static org.apache.maven.cli.CLIReportingUtils.formatDuration; import static org.apache.maven.cli.CLIReportingUtils.formatTimestamp; -import static org.apache.maven.shared.utils.logging.MessageUtils.buffer; +import static org.apache.maven.jline.MessageUtils.builder; /** * Logs execution events to logger, eventually user-supplied. @@ -81,7 +82,7 @@ private void infoLine(char c) { } private void infoMain(String msg) { - logger.info(buffer().strong(msg).toString()); + logger.info(builder().strong(msg).toString()); } @Override @@ -185,9 +186,9 @@ private void logReactorSummary(MavenSession session) { BuildSummary buildSummary = result.getBuildSummary(project); if (buildSummary == null) { - buffer.append(buffer().warning("SKIPPED")); + buffer.append(builder().warning("SKIPPED")); } else if (buildSummary instanceof BuildSuccess) { - buffer.append(buffer().success("SUCCESS")); + buffer.append(builder().success("SUCCESS")); buffer.append(" ["); String buildTimeDuration = formatDuration(buildSummary.getTime()); int padSize = MAX_PADDED_BUILD_TIME_DURATION_LENGTH - buildTimeDuration.length(); @@ -197,7 +198,7 @@ private void logReactorSummary(MavenSession session) { buffer.append(buildTimeDuration); buffer.append(']'); } else if (buildSummary instanceof BuildFailure) { - buffer.append(buffer().failure("FAILURE")); + buffer.append(builder().failure("FAILURE")); buffer.append(" ["); String buildTimeDuration = formatDuration(buildSummary.getTime()); int padSize = MAX_PADDED_BUILD_TIME_DURATION_LENGTH - buildTimeDuration.length(); @@ -214,7 +215,7 @@ private void logReactorSummary(MavenSession session) { private void logResult(MavenSession session) { infoLine('-'); - MessageBuilder buffer = buffer(); + MessageBuilder buffer = MessageUtils.builder(); if (session.getResult().hasExceptions()) { buffer.failure("BUILD FAILURE"); @@ -272,7 +273,7 @@ public void projectStarted(ExecutionEvent event) { + chars('-', Math.max(0, LINE_LENGTH - headerLen - prefix.length() + preHeader.length())); logger.info( - buffer().strong(prefix).project(projectKey).strong(suffix).toString()); + builder().strong(prefix).project(projectKey).strong(suffix).toString()); // Building Project Name Version [i/n] String building = "Building " + event.getProject().getName() + " " @@ -330,7 +331,7 @@ public void mojoStarted(ExecutionEvent event) { if (logger.isInfoEnabled()) { logger.info(""); - MessageBuilder buffer = buffer().strong("--- "); + MessageBuilder buffer = builder().strong("--- "); append(buffer, event.getMojoExecution()); append(buffer, event.getProject()); buffer.strong(" ---"); @@ -350,7 +351,7 @@ public void forkStarted(ExecutionEvent event) { if (logger.isInfoEnabled()) { logger.info(""); - MessageBuilder buffer = buffer().strong(">>> "); + MessageBuilder buffer = builder().strong(">>> "); append(buffer, event.getMojoExecution()); buffer.strong(" > "); appendForkInfo(buffer, event.getMojoExecution().getMojoDescriptor()); @@ -372,7 +373,7 @@ public void forkSucceeded(ExecutionEvent event) { if (logger.isInfoEnabled()) { logger.info(""); - MessageBuilder buffer = buffer().strong("<<< "); + MessageBuilder buffer = builder().strong("<<< "); append(buffer, event.getMojoExecution()); buffer.strong(" < "); appendForkInfo(buffer, event.getMojoExecution().getMojoDescriptor()); diff --git a/maven-embedder/src/main/java/org/apache/maven/cli/transfer/AbstractMavenTransferListener.java b/maven-embedder/src/main/java/org/apache/maven/cli/transfer/AbstractMavenTransferListener.java index 3f5f2cb1380f..a283adff0524 100644 --- a/maven-embedder/src/main/java/org/apache/maven/cli/transfer/AbstractMavenTransferListener.java +++ b/maven-embedder/src/main/java/org/apache/maven/cli/transfer/AbstractMavenTransferListener.java @@ -23,7 +23,7 @@ import java.text.DecimalFormatSymbols; import java.util.Locale; -import org.apache.maven.shared.utils.logging.MessageUtils; +import org.apache.maven.jline.MessageUtils; import org.eclipse.aether.transfer.AbstractTransferListener; import org.eclipse.aether.transfer.TransferCancelledException; import org.eclipse.aether.transfer.TransferEvent; @@ -192,6 +192,7 @@ protected AbstractMavenTransferListener(PrintStream out) { @Override public void transferInitiated(TransferEvent event) { + // TODO use MessageBuilder String darkOn = MessageUtils.isColorEnabled() ? ANSI_DARK_SET : ""; String darkOff = MessageUtils.isColorEnabled() ? ANSI_DARK_RESET : ""; diff --git a/maven-embedder/src/test/java/org/apache/maven/cli/MavenCliTest.java b/maven-embedder/src/test/java/org/apache/maven/cli/MavenCliTest.java index 5c5e7e72e24e..1a7563917aac 100644 --- a/maven-embedder/src/test/java/org/apache/maven/cli/MavenCliTest.java +++ b/maven-embedder/src/test/java/org/apache/maven/cli/MavenCliTest.java @@ -27,12 +27,13 @@ import org.apache.commons.cli.ParseException; import org.apache.maven.Maven; import org.apache.maven.eventspy.internal.EventSpyDispatcher; -import org.apache.maven.shared.utils.logging.MessageUtils; +import org.apache.maven.jline.MessageUtils; import org.apache.maven.toolchain.building.ToolchainsBuildingRequest; import org.apache.maven.toolchain.building.ToolchainsBuildingResult; import org.codehaus.plexus.PlexusContainer; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.mockito.InOrder; @@ -42,7 +43,6 @@ import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.fail; -import static org.junit.jupiter.api.Assumptions.assumeTrue; import static org.mockito.Mockito.any; import static org.mockito.Mockito.inOrder; import static org.mockito.Mockito.mock; @@ -258,58 +258,86 @@ public void testMVNConfigurationFunkyArguments() throws Exception { assertEquals("-Dpom.xml", request.getCommandLine().getOptionValue(CLIManager.ALTERNATE_POM_FILE)); } - @Test - public void testStyleColors() throws Exception { - assumeTrue(MessageUtils.isColorEnabled(), "ANSI not supported"); - CliRequest request; + @Nested + class StyleColors { - MessageUtils.setColorEnabled(true); - request = new CliRequest(new String[] {"-B"}, null); - cli.cli(request); - cli.properties(request); - cli.logging(request); - assertFalse(MessageUtils.isColorEnabled()); + @BeforeEach + public void setUp() { + MessageUtils.systemInstall(); + MessageUtils.getTerminalWidth(); // wait for fast terminal initialize + } - MessageUtils.setColorEnabled(true); - request = new CliRequest(new String[] {"-l", "target/temp/mvn.log"}, null); - request.workingDirectory = "target/temp"; - cli.cli(request); - cli.properties(request); - cli.logging(request); - assertFalse(MessageUtils.isColorEnabled()); + @AfterEach + public void tearDown() { + MessageUtils.systemUninstall(); + } - MessageUtils.setColorEnabled(false); - request = new CliRequest(new String[] {"-Dstyle.color=always"}, null); - cli.cli(request); - cli.properties(request); - cli.logging(request); - assertTrue(MessageUtils.isColorEnabled()); + @Test + public void testStyleColors() throws Exception { + CliRequest request; - MessageUtils.setColorEnabled(true); - request = new CliRequest(new String[] {"-Dstyle.color=never"}, null); - cli.cli(request); - cli.properties(request); - cli.logging(request); - assertFalse(MessageUtils.isColorEnabled()); + MessageUtils.setColorEnabled(true); + request = new CliRequest(new String[] {"-B"}, null); + cli.cli(request); + cli.properties(request); + cli.logging(request); + assertFalse(MessageUtils.isColorEnabled()); - MessageUtils.setColorEnabled(false); - request = new CliRequest(new String[] {"-Dstyle.color=always", "-B", "-l", "target/temp/mvn.log"}, null); - request.workingDirectory = "target/temp"; - cli.cli(request); - cli.properties(request); - cli.logging(request); - assertTrue(MessageUtils.isColorEnabled()); + MessageUtils.setColorEnabled(true); + request = new CliRequest(new String[] {"-l", "target/temp/mvn.log"}, null); + request.workingDirectory = "target/temp"; + cli.cli(request); + cli.properties(request); + cli.logging(request); + assertFalse(MessageUtils.isColorEnabled()); + + MessageUtils.setColorEnabled(false); + request = new CliRequest(new String[] {"-Dstyle.color=always"}, null); + cli.cli(request); + cli.properties(request); + cli.logging(request); + assertTrue(MessageUtils.isColorEnabled()); + + MessageUtils.setColorEnabled(true); + request = new CliRequest(new String[] {"-Dstyle.color=never"}, null); + cli.cli(request); + cli.properties(request); + cli.logging(request); + assertFalse(MessageUtils.isColorEnabled()); - try { MessageUtils.setColorEnabled(false); - request = new CliRequest(new String[] {"-Dstyle.color=maybe", "-B", "-l", "target/temp/mvn.log"}, null); + request = new CliRequest(new String[] {"-Dstyle.color=always", "-B", "-l", "target/temp/mvn.log"}, null); request.workingDirectory = "target/temp"; cli.cli(request); cli.properties(request); cli.logging(request); - fail("maybe is not a valid option"); - } catch (IllegalArgumentException e) { - // noop + assertTrue(MessageUtils.isColorEnabled()); + + try { + MessageUtils.setColorEnabled(false); + request = new CliRequest(new String[] {"-Dstyle.color=maybe", "-B", "-l", "target/temp/mvn.log"}, null); + request.workingDirectory = "target/temp"; + cli.cli(request); + cli.properties(request); + cli.logging(request); + fail("maybe is not a valid option"); + } catch (IllegalArgumentException e) { + // noop + } + } + + @Test + void testDumpTerminal() throws Exception { + // test provide own sys out which is not a tty + // similar will be with redirected output + // check that colors are disabled + + MessageUtils.setColorEnabled(true); + CliRequest request = new CliRequest(new String[] {}, null); + cli.cli(request); + cli.properties(request); + cli.logging(request); + assertFalse(MessageUtils.isColorEnabled()); } } @@ -357,17 +385,20 @@ public void testVersionStringWithoutAnsi() throws Exception { PrintStream oldOut = System.out; System.setOut(new PrintStream(systemOut)); + System.setProperty(MavenCli.MULTIMODULE_PROJECT_DIRECTORY, "."); + // when try { - cli.cli(cliRequest); - } catch (MavenCli.ExitException exitException) { - // expected + assertEquals(0, cli.doMain(cliRequest)); } finally { // restore sysout System.setOut(oldOut); + System.clearProperty(MavenCli.MULTIMODULE_PROJECT_DIRECTORY); } String versionOut = new String(systemOut.toByteArray(), StandardCharsets.UTF_8); + assertTrue(versionOut.contains("Apache Maven")); + // then assertEquals(MessageUtils.stripAnsiCodes(versionOut), versionOut); } diff --git a/maven-embedder/src/test/java/org/apache/maven/cli/event/ExecutionEventLoggerTest.java b/maven-embedder/src/test/java/org/apache/maven/cli/event/ExecutionEventLoggerTest.java index b68d62b5e374..e48362a90dad 100644 --- a/maven-embedder/src/test/java/org/apache/maven/cli/event/ExecutionEventLoggerTest.java +++ b/maven-embedder/src/test/java/org/apache/maven/cli/event/ExecutionEventLoggerTest.java @@ -23,8 +23,8 @@ import org.apache.commons.io.FilenameUtils; import org.apache.maven.execution.ExecutionEvent; import org.apache.maven.execution.MavenSession; +import org.apache.maven.jline.MessageUtils; import org.apache.maven.project.MavenProject; -import org.apache.maven.shared.utils.logging.MessageUtils; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; diff --git a/maven-jline/pom.xml b/maven-jline/pom.xml new file mode 100644 index 000000000000..e7db3548d685 --- /dev/null +++ b/maven-jline/pom.xml @@ -0,0 +1,60 @@ + + + + 4.0.0 + + + org.apache.maven + maven + 3.10.0-SNAPSHOT + + + maven-jline + Maven 3 JLine integration + Provides the JLine integration in Maven + + + + org.jline + jansi-core + + + org.jline + jline-terminal + + + org.jline + jline-terminal-jni + + + org.jline + jline-terminal-ffm + true + + + javax.inject + javax.inject + + + org.apache.maven + maven-core + + + diff --git a/maven-jline/src/main/java/org/apache/maven/jline/FastTerminal.java b/maven-jline/src/main/java/org/apache/maven/jline/FastTerminal.java new file mode 100644 index 000000000000..1c4d9b124c7b --- /dev/null +++ b/maven-jline/src/main/java/org/apache/maven/jline/FastTerminal.java @@ -0,0 +1,291 @@ +/* + * 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.jline; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.PrintWriter; +import java.nio.charset.Charset; +import java.util.concurrent.Callable; +import java.util.concurrent.CompletableFuture; +import java.util.function.Consumer; +import java.util.function.IntConsumer; +import java.util.function.IntSupplier; + +import org.jline.terminal.Attributes; +import org.jline.terminal.Cursor; +import org.jline.terminal.MouseEvent; +import org.jline.terminal.Size; +import org.jline.terminal.Terminal; +import org.jline.terminal.spi.SystemStream; +import org.jline.terminal.spi.TerminalExt; +import org.jline.terminal.spi.TerminalProvider; +import org.jline.utils.ColorPalette; +import org.jline.utils.InfoCmp; +import org.jline.utils.NonBlockingReader; + +public class FastTerminal implements TerminalExt { + + private final CompletableFuture terminal; + + public FastTerminal(Callable builder, Consumer consumer) { + this.terminal = new CompletableFuture<>(); + new Thread( + () -> { + try { + Terminal term = builder.call(); + consumer.accept(term); + terminal.complete(term); + } catch (Exception e) { + terminal.completeExceptionally(e); + } + }, + "fast-terminal-thread") + .start(); + } + + public TerminalExt getTerminal() { + try { + return (TerminalExt) terminal.get(); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + @Override + public String getName() { + return getTerminal().getName(); + } + + @Override + public SignalHandler handle(Signal signal, SignalHandler signalHandler) { + return getTerminal().handle(signal, signalHandler); + } + + @Override + public void raise(Signal signal) { + getTerminal().raise(signal); + } + + @Override + public NonBlockingReader reader() { + return getTerminal().reader(); + } + + @Override + public PrintWriter writer() { + return getTerminal().writer(); + } + + @Override + public Charset encoding() { + return getTerminal().encoding(); + } + + @Override + public InputStream input() { + return getTerminal().input(); + } + + @Override + public OutputStream output() { + return getTerminal().output(); + } + + @Override + public boolean canPauseResume() { + return getTerminal().canPauseResume(); + } + + @Override + public void pause() { + getTerminal().pause(); + } + + @Override + public void pause(boolean b) throws InterruptedException { + getTerminal().pause(b); + } + + @Override + public void resume() { + getTerminal().resume(); + } + + @Override + public boolean paused() { + return getTerminal().paused(); + } + + @Override + public Attributes enterRawMode() { + return getTerminal().enterRawMode(); + } + + @Override + public boolean echo() { + return getTerminal().echo(); + } + + @Override + public boolean echo(boolean b) { + return getTerminal().echo(b); + } + + @Override + public Attributes getAttributes() { + return getTerminal().getAttributes(); + } + + @Override + public void setAttributes(Attributes attributes) { + getTerminal().setAttributes(attributes); + } + + @Override + public Size getSize() { + return getTerminal().getSize(); + } + + @Override + public void setSize(Size size) { + getTerminal().setSize(size); + } + + @Override + public int getWidth() { + return getTerminal().getWidth(); + } + + @Override + public int getHeight() { + return getTerminal().getHeight(); + } + + @Override + public Size getBufferSize() { + return getTerminal().getBufferSize(); + } + + @Override + public void flush() { + getTerminal().flush(); + } + + @Override + public String getType() { + return getTerminal().getType(); + } + + @Override + public boolean puts(InfoCmp.Capability capability, Object... objects) { + return getTerminal().puts(capability, objects); + } + + @Override + public boolean getBooleanCapability(InfoCmp.Capability capability) { + return getTerminal().getBooleanCapability(capability); + } + + @Override + public Integer getNumericCapability(InfoCmp.Capability capability) { + return getTerminal().getNumericCapability(capability); + } + + @Override + public String getStringCapability(InfoCmp.Capability capability) { + return getTerminal().getStringCapability(capability); + } + + @Override + public Cursor getCursorPosition(IntConsumer intConsumer) { + return getTerminal().getCursorPosition(intConsumer); + } + + @Override + public boolean hasMouseSupport() { + return getTerminal().hasMouseSupport(); + } + + @Override + public MouseTracking getCurrentMouseTracking() { + return getTerminal().getCurrentMouseTracking(); + } + + @Override + public boolean trackMouse(MouseTracking mouseTracking) { + return getTerminal().trackMouse(mouseTracking); + } + + @Override + public MouseEvent readMouseEvent() { + return getTerminal().readMouseEvent(); + } + + @Override + public MouseEvent readMouseEvent(IntSupplier intSupplier) { + return getTerminal().readMouseEvent(intSupplier); + } + + @Override + public MouseEvent readMouseEvent(String prefix) { + return getTerminal().readMouseEvent(prefix); + } + + @Override + public MouseEvent readMouseEvent(IntSupplier reader, String prefix) { + return getTerminal().readMouseEvent(reader, prefix); + } + + @Override + public boolean hasFocusSupport() { + return getTerminal().hasFocusSupport(); + } + + @Override + public boolean trackFocus(boolean b) { + return getTerminal().trackFocus(b); + } + + @Override + public ColorPalette getPalette() { + return getTerminal().getPalette(); + } + + @Override + public void close() throws IOException { + getTerminal().close(); + } + + @Override + public TerminalProvider getProvider() { + return getTerminal().getProvider(); + } + + @Override + public SystemStream getSystemStream() { + return getTerminal().getSystemStream(); + } + + @Override + public String toString() { + return getTerminal().toString(); + } +} diff --git a/maven-jline/src/main/java/org/apache/maven/jline/JLineMessageBuilderFactory.java b/maven-jline/src/main/java/org/apache/maven/jline/JLineMessageBuilderFactory.java new file mode 100644 index 000000000000..ff17bd59da0d --- /dev/null +++ b/maven-jline/src/main/java/org/apache/maven/jline/JLineMessageBuilderFactory.java @@ -0,0 +1,156 @@ +/* + * 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.jline; + +import javax.inject.Named; +import javax.inject.Singleton; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import org.apache.maven.message.MessageBuilder; +import org.apache.maven.message.MessageBuilderFactory; +import org.eclipse.sisu.Priority; +import org.jline.utils.AttributedStringBuilder; +import org.jline.utils.AttributedStyle; +import org.jline.utils.StyleResolver; + +@Named +@Singleton +@Priority(10) +public class JLineMessageBuilderFactory implements MessageBuilderFactory { + private final StyleResolver resolver; + + public JLineMessageBuilderFactory() { + this.resolver = new MavenStyleResolver(); + } + + @Override + public boolean isColorEnabled() { + return MessageUtils.isColorEnabled(); + } + + @Override + public int getTerminalWidth() { + return MessageUtils.getTerminalWidth(); + } + + @Override + public MessageBuilder builder() { + return builder(64); + } + + @Override + public MessageBuilder builder(int size) { + return new JlineMessageBuilder(resolver, size); + } + + private static class JlineMessageBuilder implements MessageBuilder { + private final StyleResolver styleResolver; + private final AttributedStringBuilder builder; + + private JlineMessageBuilder(StyleResolver styleResolver, int size) { + this.styleResolver = styleResolver; + this.builder = new AttributedStringBuilder(size); + } + + @Override + public MessageBuilder style(String style) { + if (MessageUtils.isColorEnabled()) { + builder.style(styleResolver.resolve(style)); + } + return this; + } + + @Override + public MessageBuilder resetStyle() { + builder.style(AttributedStyle.DEFAULT); + return this; + } + + @Override + public MessageBuilder append(CharSequence cs) { + builder.append(cs); + return this; + } + + @Override + public MessageBuilder append(CharSequence cs, int start, int end) { + builder.append(cs, start, end); + return this; + } + + @Override + public MessageBuilder append(char c) { + builder.append(c); + return this; + } + + @Override + public MessageBuilder setLength(int length) { + builder.setLength(length); + return this; + } + + @Override + public String build() { + return builder.toAnsi(MessageUtils.terminal); + } + + @Override + public String toString() { + return build(); + } + } + + private static class MavenStyleResolver extends StyleResolver { + private final Map styles = new ConcurrentHashMap<>(); + + private MavenStyleResolver() { + super(key -> { + String v = System.getProperty("maven.style." + key); + if (v == null) { + v = System.getProperty("style." + key); + } + return v; + }); + } + + @Override + public AttributedStyle resolve(String spec) { + return styles.computeIfAbsent(spec, this::doResolve); + } + + @Override + public AttributedStyle resolve(String spec, String defaultSpec) { + return resolve(defaultSpec != null ? spec + ":-" + defaultSpec : spec); + } + + private AttributedStyle doResolve(String spec) { + String def = null; + int i = spec.indexOf(":-"); + if (i != -1) { + String[] parts = spec.split(":-"); + spec = parts[0].trim(); + def = parts[1].trim(); + } + return super.resolve(spec, def); + } + } +} diff --git a/maven-jline/src/main/java/org/apache/maven/jline/MessageUtils.java b/maven-jline/src/main/java/org/apache/maven/jline/MessageUtils.java new file mode 100644 index 000000000000..94b3f10baa12 --- /dev/null +++ b/maven-jline/src/main/java/org/apache/maven/jline/MessageUtils.java @@ -0,0 +1,130 @@ +/* + * 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.jline; + +import java.util.function.Consumer; + +import org.apache.maven.message.MessageBuilder; +import org.apache.maven.message.MessageBuilderFactory; +import org.jline.jansi.AnsiConsole; +import org.jline.terminal.Terminal; +import org.jline.terminal.TerminalBuilder; + +public class MessageUtils { + + static Terminal terminal; + static MessageBuilderFactory messageBuilderFactory = new JLineMessageBuilderFactory(); + static boolean colorEnabled = true; + static Thread shutdownHook; + static final Object STARTUP_SHUTDOWN_MONITOR = new Object(); + + public static void systemInstall(Terminal terminal) { + MessageUtils.terminal = terminal; + } + + public static void systemInstall() { + systemInstall(null, null); + } + + public static void systemInstall(Consumer builderConsumer, Consumer terminalConsumer) { + MessageUtils.terminal = new FastTerminal( + () -> { + TerminalBuilder builder = TerminalBuilder.builder() + .name("Maven") + .dumb(true) + .systemOutput(TerminalBuilder.SystemOutput.SysOut) + .color(true); + if (builderConsumer != null) { + builderConsumer.accept(builder); + } + return builder.build(); + }, + terminal -> { + AnsiConsole.setTerminal(terminal); + AnsiConsole.systemInstall(); + if (terminalConsumer != null) { + terminalConsumer.accept(terminal); + } + }); + } + + public static void registerShutdownHook() { + if (shutdownHook == null) { + shutdownHook = new Thread(() -> { + synchronized (MessageUtils.STARTUP_SHUTDOWN_MONITOR) { + MessageUtils.doSystemUninstall(); + } + }); + Runtime.getRuntime().addShutdownHook(shutdownHook); + } + } + + public static void systemUninstall() { + doSystemUninstall(); + if (shutdownHook != null) { + try { + Runtime.getRuntime().removeShutdownHook(shutdownHook); + } catch (IllegalStateException var3) { + // ignore + } + } + } + + private static void doSystemUninstall() { + try { + if (terminal instanceof FastTerminal) { + // wait for the asynchronous systemInstall call before we uninstall to ensure a consistent state + ((FastTerminal) terminal).getTerminal(); + } + AnsiConsole.systemUninstall(); + } finally { + terminal = null; + } + } + + public static void setColorEnabled(boolean enabled) { + colorEnabled = enabled; + } + + public static boolean isColorEnabled() { + return colorEnabled && terminal != null; + } + + public static int getTerminalWidth() { + return terminal != null ? terminal.getWidth() : -1; + } + + public static MessageBuilder builder() { + return messageBuilderFactory.builder(); + } + + public static Terminal getTerminal() { + return terminal; + } + + /** + * Remove any ANSI code from a message (colors or other escape sequences). + * + * @param msg message eventually containing ANSI codes + * @return the message with ANSI codes removed + */ + public static String stripAnsiCodes(String msg) { + return msg.replaceAll("\u001B\\[[;\\d]*[ -/]*[@-~]", ""); + } +} diff --git a/maven-jline/src/main/java/org/fusesource/jansi/Ansi.java b/maven-jline/src/main/java/org/fusesource/jansi/Ansi.java new file mode 100644 index 000000000000..f861da0098b0 --- /dev/null +++ b/maven-jline/src/main/java/org/fusesource/jansi/Ansi.java @@ -0,0 +1,957 @@ +/* + * 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.fusesource.jansi; + +import java.util.ArrayList; + +/** + * Provides a fluent API for generating + * ANSI escape sequences. + * + * This class comes from Jansi and is provided for backward compatibility + * with maven-shared-utils, while Maven has migrated to JLine (into which Jansi has been merged + * since JLine 3.25.0). + */ +@Deprecated +@SuppressWarnings("unused") +public class Ansi implements Appendable { + + private static final char FIRST_ESC_CHAR = 27; + private static final char SECOND_ESC_CHAR = '['; + + /** + * ANSI 8 colors for fluent API + */ + public enum Color { + BLACK(0, "BLACK"), + RED(1, "RED"), + GREEN(2, "GREEN"), + YELLOW(3, "YELLOW"), + BLUE(4, "BLUE"), + MAGENTA(5, "MAGENTA"), + CYAN(6, "CYAN"), + WHITE(7, "WHITE"), + DEFAULT(9, "DEFAULT"); + + private final int value; + private final String name; + + Color(int index, String name) { + this.value = index; + this.name = name; + } + + @Override + public String toString() { + return name; + } + + public int value() { + return value; + } + + public int fg() { + return value + 30; + } + + public int bg() { + return value + 40; + } + + public int fgBright() { + return value + 90; + } + + public int bgBright() { + return value + 100; + } + } + + /** + * Display attributes, also known as + * SGR + * (Select Graphic Rendition) parameters. + */ + public enum Attribute { + RESET(0, "RESET"), + INTENSITY_BOLD(1, "INTENSITY_BOLD"), + INTENSITY_FAINT(2, "INTENSITY_FAINT"), + ITALIC(3, "ITALIC_ON"), + UNDERLINE(4, "UNDERLINE_ON"), + BLINK_SLOW(5, "BLINK_SLOW"), + BLINK_FAST(6, "BLINK_FAST"), + NEGATIVE_ON(7, "NEGATIVE_ON"), + CONCEAL_ON(8, "CONCEAL_ON"), + STRIKETHROUGH_ON(9, "STRIKETHROUGH_ON"), + UNDERLINE_DOUBLE(21, "UNDERLINE_DOUBLE"), + INTENSITY_BOLD_OFF(22, "INTENSITY_BOLD_OFF"), + ITALIC_OFF(23, "ITALIC_OFF"), + UNDERLINE_OFF(24, "UNDERLINE_OFF"), + BLINK_OFF(25, "BLINK_OFF"), + NEGATIVE_OFF(27, "NEGATIVE_OFF"), + CONCEAL_OFF(28, "CONCEAL_OFF"), + STRIKETHROUGH_OFF(29, "STRIKETHROUGH_OFF"); + + private final int value; + private final String name; + + Attribute(int index, String name) { + this.value = index; + this.name = name; + } + + @Override + public String toString() { + return name; + } + + public int value() { + return value; + } + } + + /** + * ED (Erase in Display) / EL (Erase in Line) parameter (see + * CSI sequence J and K) + * @see Ansi#eraseScreen(Erase) + * @see Ansi#eraseLine(Erase) + */ + public enum Erase { + FORWARD(0, "FORWARD"), + BACKWARD(1, "BACKWARD"), + ALL(2, "ALL"); + + private final int value; + private final String name; + + Erase(int index, String name) { + this.value = index; + this.name = name; + } + + @Override + public String toString() { + return name; + } + + public int value() { + return value; + } + } + + @FunctionalInterface + public interface Consumer { + void apply(Ansi ansi); + } + + public static boolean isEnabled() { + return org.apache.maven.jline.MessageUtils.isColorEnabled() && org.jline.jansi.Ansi.isEnabled(); + } + + public static void setEnabled(final boolean flag) { + org.jline.jansi.Ansi.setEnabled(flag); + } + + public static Ansi ansi() { + if (isEnabled()) { + return new Ansi(); + } else { + return new NoAnsi(); + } + } + + public static Ansi ansi(StringBuilder builder) { + if (isEnabled()) { + return new Ansi(builder); + } else { + return new NoAnsi(builder); + } + } + + public static Ansi ansi(int size) { + if (isEnabled()) { + return new Ansi(size); + } else { + return new NoAnsi(size); + } + } + + private static class NoAnsi extends Ansi { + NoAnsi() { + super(); + } + + NoAnsi(int size) { + super(size); + } + + NoAnsi(StringBuilder builder) { + super(builder); + } + + @Override + public Ansi fg(Color color) { + return this; + } + + @Override + public Ansi bg(Color color) { + return this; + } + + @Override + public Ansi fgBright(Color color) { + return this; + } + + @Override + public Ansi bgBright(Color color) { + return this; + } + + @Override + public Ansi fg(int color) { + return this; + } + + @Override + public Ansi fgRgb(int r, int g, int b) { + return this; + } + + @Override + public Ansi bg(int color) { + return this; + } + + @Override + public Ansi bgRgb(int r, int g, int b) { + return this; + } + + @Override + public Ansi a(Attribute attribute) { + return this; + } + + @Override + public Ansi cursor(int row, int column) { + return this; + } + + @Override + public Ansi cursorToColumn(int x) { + return this; + } + + @Override + public Ansi cursorUp(int y) { + return this; + } + + @Override + public Ansi cursorRight(int x) { + return this; + } + + @Override + public Ansi cursorDown(int y) { + return this; + } + + @Override + public Ansi cursorLeft(int x) { + return this; + } + + @Override + public Ansi cursorDownLine() { + return this; + } + + @Override + public Ansi cursorDownLine(final int n) { + return this; + } + + @Override + public Ansi cursorUpLine() { + return this; + } + + @Override + public Ansi cursorUpLine(final int n) { + return this; + } + + @Override + public Ansi eraseScreen() { + return this; + } + + @Override + public Ansi eraseScreen(Erase kind) { + return this; + } + + @Override + public Ansi eraseLine() { + return this; + } + + @Override + public Ansi eraseLine(Erase kind) { + return this; + } + + @Override + public Ansi scrollUp(int rows) { + return this; + } + + @Override + public Ansi scrollDown(int rows) { + return this; + } + + @Override + public Ansi saveCursorPosition() { + return this; + } + + @Override + @Deprecated + public Ansi restorCursorPosition() { + return this; + } + + @Override + public Ansi restoreCursorPosition() { + return this; + } + + @Override + public Ansi reset() { + return this; + } + } + + private final StringBuilder builder; + private final ArrayList attributeOptions = new ArrayList<>(5); + + public Ansi() { + this(new StringBuilder(80)); + } + + public Ansi(Ansi parent) { + this(new StringBuilder(parent.builder)); + attributeOptions.addAll(parent.attributeOptions); + } + + public Ansi(int size) { + this(new StringBuilder(size)); + } + + public Ansi(StringBuilder builder) { + this.builder = builder; + } + + public Ansi fg(Color color) { + attributeOptions.add(color.fg()); + return this; + } + + public Ansi fg(int color) { + attributeOptions.add(38); + attributeOptions.add(5); + attributeOptions.add(color & 0xff); + return this; + } + + public Ansi fgRgb(int color) { + return fgRgb(color >> 16, color >> 8, color); + } + + public Ansi fgRgb(int r, int g, int b) { + attributeOptions.add(38); + attributeOptions.add(2); + attributeOptions.add(r & 0xff); + attributeOptions.add(g & 0xff); + attributeOptions.add(b & 0xff); + return this; + } + + public Ansi fgBlack() { + return this.fg(Color.BLACK); + } + + public Ansi fgBlue() { + return this.fg(Color.BLUE); + } + + public Ansi fgCyan() { + return this.fg(Color.CYAN); + } + + public Ansi fgDefault() { + return this.fg(Color.DEFAULT); + } + + public Ansi fgGreen() { + return this.fg(Color.GREEN); + } + + public Ansi fgMagenta() { + return this.fg(Color.MAGENTA); + } + + public Ansi fgRed() { + return this.fg(Color.RED); + } + + public Ansi fgYellow() { + return this.fg(Color.YELLOW); + } + + public Ansi bg(Color color) { + attributeOptions.add(color.bg()); + return this; + } + + public Ansi bg(int color) { + attributeOptions.add(48); + attributeOptions.add(5); + attributeOptions.add(color & 0xff); + return this; + } + + public Ansi bgRgb(int color) { + return bgRgb(color >> 16, color >> 8, color); + } + + public Ansi bgRgb(int r, int g, int b) { + attributeOptions.add(48); + attributeOptions.add(2); + attributeOptions.add(r & 0xff); + attributeOptions.add(g & 0xff); + attributeOptions.add(b & 0xff); + return this; + } + + public Ansi bgCyan() { + return this.bg(Color.CYAN); + } + + public Ansi bgDefault() { + return this.bg(Color.DEFAULT); + } + + public Ansi bgGreen() { + return this.bg(Color.GREEN); + } + + public Ansi bgMagenta() { + return this.bg(Color.MAGENTA); + } + + public Ansi bgRed() { + return this.bg(Color.RED); + } + + public Ansi bgYellow() { + return this.bg(Color.YELLOW); + } + + public Ansi fgBright(Color color) { + attributeOptions.add(color.fgBright()); + return this; + } + + public Ansi fgBrightBlack() { + return this.fgBright(Color.BLACK); + } + + public Ansi fgBrightBlue() { + return this.fgBright(Color.BLUE); + } + + public Ansi fgBrightCyan() { + return this.fgBright(Color.CYAN); + } + + public Ansi fgBrightDefault() { + return this.fgBright(Color.DEFAULT); + } + + public Ansi fgBrightGreen() { + return this.fgBright(Color.GREEN); + } + + public Ansi fgBrightMagenta() { + return this.fgBright(Color.MAGENTA); + } + + public Ansi fgBrightRed() { + return this.fgBright(Color.RED); + } + + public Ansi fgBrightYellow() { + return this.fgBright(Color.YELLOW); + } + + public Ansi bgBright(Color color) { + attributeOptions.add(color.bgBright()); + return this; + } + + public Ansi bgBrightCyan() { + return this.bgBright(Color.CYAN); + } + + public Ansi bgBrightDefault() { + return this.bgBright(Color.DEFAULT); + } + + public Ansi bgBrightGreen() { + return this.bgBright(Color.GREEN); + } + + public Ansi bgBrightMagenta() { + return this.bgBright(Color.MAGENTA); + } + + public Ansi bgBrightRed() { + return this.bgBright(Color.RED); + } + + public Ansi bgBrightYellow() { + return this.bgBright(Color.YELLOW); + } + + public Ansi a(Attribute attribute) { + attributeOptions.add(attribute.value()); + return this; + } + + /** + * Moves the cursor to row n, column m. The values are 1-based. + * Any values less than 1 are mapped to 1. + * + * @param row row (1-based) from top + * @param column column (1 based) from left + * @return this Ansi instance + */ + public Ansi cursor(final int row, final int column) { + return appendEscapeSequence('H', Math.max(1, row), Math.max(1, column)); + } + + /** + * Moves the cursor to column n. The parameter n is 1-based. + * If n is less than 1 it is moved to the first column. + * + * @param x the index (1-based) of the column to move to + * @return this Ansi instance + */ + public Ansi cursorToColumn(final int x) { + return appendEscapeSequence('G', Math.max(1, x)); + } + + /** + * Moves the cursor up. If the parameter y is negative it moves the cursor down. + * + * @param y the number of lines to move up + * @return this Ansi instance + */ + public Ansi cursorUp(final int y) { + return y > 0 ? appendEscapeSequence('A', y) : y < 0 ? cursorDown(-y) : this; + } + + /** + * Moves the cursor down. If the parameter y is negative it moves the cursor up. + * + * @param y the number of lines to move down + * @return this Ansi instance + */ + public Ansi cursorDown(final int y) { + return y > 0 ? appendEscapeSequence('B', y) : y < 0 ? cursorUp(-y) : this; + } + + /** + * Moves the cursor right. If the parameter x is negative it moves the cursor left. + * + * @param x the number of characters to move right + * @return this Ansi instance + */ + public Ansi cursorRight(final int x) { + return x > 0 ? appendEscapeSequence('C', x) : x < 0 ? cursorLeft(-x) : this; + } + + /** + * Moves the cursor left. If the parameter x is negative it moves the cursor right. + * + * @param x the number of characters to move left + * @return this Ansi instance + */ + public Ansi cursorLeft(final int x) { + return x > 0 ? appendEscapeSequence('D', x) : x < 0 ? cursorRight(-x) : this; + } + + /** + * Moves the cursor relative to the current position. The cursor is moved right if x is + * positive, left if negative and down if y is positive and up if negative. + * + * @param x the number of characters to move horizontally + * @param y the number of lines to move vertically + * @return this Ansi instance + * @since 2.2 + */ + public Ansi cursorMove(final int x, final int y) { + return cursorRight(x).cursorDown(y); + } + + /** + * Moves the cursor to the beginning of the line below. + * + * @return this Ansi instance + */ + public Ansi cursorDownLine() { + return appendEscapeSequence('E'); + } + + /** + * Moves the cursor to the beginning of the n-th line below. If the parameter n is negative it + * moves the cursor to the beginning of the n-th line above. + * + * @param n the number of lines to move the cursor + * @return this Ansi instance + */ + public Ansi cursorDownLine(final int n) { + return n < 0 ? cursorUpLine(-n) : appendEscapeSequence('E', n); + } + + /** + * Moves the cursor to the beginning of the line above. + * + * @return this Ansi instance + */ + public Ansi cursorUpLine() { + return appendEscapeSequence('F'); + } + + /** + * Moves the cursor to the beginning of the n-th line above. If the parameter n is negative it + * moves the cursor to the beginning of the n-th line below. + * + * @param n the number of lines to move the cursor + * @return this Ansi instance + */ + public Ansi cursorUpLine(final int n) { + return n < 0 ? cursorDownLine(-n) : appendEscapeSequence('F', n); + } + + public Ansi eraseScreen() { + return appendEscapeSequence('J', Erase.ALL.value()); + } + + public Ansi eraseScreen(final Erase kind) { + return appendEscapeSequence('J', kind.value()); + } + + public Ansi eraseLine() { + return appendEscapeSequence('K'); + } + + public Ansi eraseLine(final Erase kind) { + return appendEscapeSequence('K', kind.value()); + } + + public Ansi scrollUp(final int rows) { + if (rows == Integer.MIN_VALUE) { + return scrollDown(Integer.MAX_VALUE); + } + return rows > 0 ? appendEscapeSequence('S', rows) : rows < 0 ? scrollDown(-rows) : this; + } + + public Ansi scrollDown(final int rows) { + if (rows == Integer.MIN_VALUE) { + return scrollUp(Integer.MAX_VALUE); + } + return rows > 0 ? appendEscapeSequence('T', rows) : rows < 0 ? scrollUp(-rows) : this; + } + + @Deprecated + public Ansi restorCursorPosition() { + return restoreCursorPosition(); + } + + public Ansi saveCursorPosition() { + saveCursorPositionSCO(); + return saveCursorPositionDEC(); + } + + // SCO command + public Ansi saveCursorPositionSCO() { + return appendEscapeSequence('s'); + } + + // DEC command + public Ansi saveCursorPositionDEC() { + builder.append(FIRST_ESC_CHAR); + builder.append('7'); + return this; + } + + public Ansi restoreCursorPosition() { + restoreCursorPositionSCO(); + return restoreCursorPositionDEC(); + } + + // SCO command + public Ansi restoreCursorPositionSCO() { + return appendEscapeSequence('u'); + } + + // DEC command + public Ansi restoreCursorPositionDEC() { + builder.append(FIRST_ESC_CHAR); + builder.append('8'); + return this; + } + + public Ansi reset() { + return a(Attribute.RESET); + } + + public Ansi bold() { + return a(Attribute.INTENSITY_BOLD); + } + + public Ansi boldOff() { + return a(Attribute.INTENSITY_BOLD_OFF); + } + + public Ansi a(String value) { + flushAttributes(); + builder.append(value); + return this; + } + + public Ansi a(boolean value) { + flushAttributes(); + builder.append(value); + return this; + } + + public Ansi a(char value) { + flushAttributes(); + builder.append(value); + return this; + } + + public Ansi a(char[] value, int offset, int len) { + flushAttributes(); + builder.append(value, offset, len); + return this; + } + + public Ansi a(char[] value) { + flushAttributes(); + builder.append(value); + return this; + } + + public Ansi a(CharSequence value, int start, int end) { + flushAttributes(); + builder.append(value, start, end); + return this; + } + + public Ansi a(CharSequence value) { + flushAttributes(); + builder.append(value); + return this; + } + + public Ansi a(double value) { + flushAttributes(); + builder.append(value); + return this; + } + + public Ansi a(float value) { + flushAttributes(); + builder.append(value); + return this; + } + + public Ansi a(int value) { + flushAttributes(); + builder.append(value); + return this; + } + + public Ansi a(long value) { + flushAttributes(); + builder.append(value); + return this; + } + + public Ansi a(Object value) { + flushAttributes(); + builder.append(value); + return this; + } + + public Ansi a(StringBuffer value) { + flushAttributes(); + builder.append(value); + return this; + } + + public Ansi newline() { + flushAttributes(); + builder.append(System.lineSeparator()); + return this; + } + + public Ansi format(String pattern, Object... args) { + flushAttributes(); + builder.append(String.format(pattern, args)); + return this; + } + + /** + * Applies another function to this Ansi instance. + * + * @param fun the function to apply + * @return this Ansi instance + * @since 2.2 + */ + public Ansi apply(Consumer fun) { + fun.apply(this); + return this; + } + + /** + * Uses the {@link org.jline.jansi.AnsiRenderer} + * to generate the ANSI escape sequences for the supplied text. + * + * @param text text + * @return this + * @since 2.2 + */ + public Ansi render(final String text) { + a(new org.jline.jansi.Ansi().render(text).toString()); + return this; + } + + /** + * String formats and renders the supplied arguments. Uses the {@link org.jline.jansi.AnsiRenderer} + * to generate the ANSI escape sequences. + * + * @param text format + * @param args arguments + * @return this + * @since 2.2 + */ + public Ansi render(final String text, Object... args) { + a(String.format(new org.jline.jansi.Ansi().render(text).toString(), args)); + return this; + } + + @Override + public String toString() { + flushAttributes(); + return builder.toString(); + } + + /////////////////////////////////////////////////////////////////// + // Private Helper Methods + /////////////////////////////////////////////////////////////////// + + private Ansi appendEscapeSequence(char command) { + flushAttributes(); + builder.append(FIRST_ESC_CHAR); + builder.append(SECOND_ESC_CHAR); + builder.append(command); + return this; + } + + private Ansi appendEscapeSequence(char command, int option) { + flushAttributes(); + builder.append(FIRST_ESC_CHAR); + builder.append(SECOND_ESC_CHAR); + builder.append(option); + builder.append(command); + return this; + } + + private Ansi appendEscapeSequence(char command, Object... options) { + flushAttributes(); + return doAppendEscapeSequence(command, options); + } + + private void flushAttributes() { + if (attributeOptions.isEmpty()) { + return; + } + if (attributeOptions.size() == 1 && attributeOptions.get(0) == 0) { + builder.append(FIRST_ESC_CHAR); + builder.append(SECOND_ESC_CHAR); + builder.append('m'); + } else { + doAppendEscapeSequence('m', attributeOptions.toArray()); + } + attributeOptions.clear(); + } + + private Ansi doAppendEscapeSequence(char command, Object... options) { + builder.append(FIRST_ESC_CHAR); + builder.append(SECOND_ESC_CHAR); + int size = options.length; + for (int i = 0; i < size; i++) { + if (i != 0) { + builder.append(';'); + } + if (options[i] != null) { + builder.append(options[i]); + } + } + builder.append(command); + return this; + } + + @Override + public Ansi append(CharSequence csq) { + builder.append(csq); + return this; + } + + @Override + public Ansi append(CharSequence csq, int start, int end) { + builder.append(csq, start, end); + return this; + } + + @Override + public Ansi append(char c) { + builder.append(c); + return this; + } +} diff --git a/maven-jline/src/site/site.xml b/maven-jline/src/site/site.xml new file mode 100644 index 000000000000..4ee3b709cfc4 --- /dev/null +++ b/maven-jline/src/site/site.xml @@ -0,0 +1,35 @@ + + + + + + + ${project.scm.url} + + + + + + + + + + diff --git a/maven-slf4j-provider/pom.xml b/maven-slf4j-provider/pom.xml index 12efb164c347..bee59b4c035d 100644 --- a/maven-slf4j-provider/pom.xml +++ b/maven-slf4j-provider/pom.xml @@ -38,8 +38,8 @@ under the License. slf4j-api - org.apache.maven.shared - maven-shared-utils + org.apache.maven + maven-jline org.junit.jupiter diff --git a/maven-slf4j-provider/src/main/java/org/slf4j/impl/MavenSimpleLogger.java b/maven-slf4j-provider/src/main/java/org/slf4j/impl/MavenSimpleLogger.java index 0d9b9c390e50..0ef4d2f9af55 100644 --- a/maven-slf4j-provider/src/main/java/org/slf4j/impl/MavenSimpleLogger.java +++ b/maven-slf4j-provider/src/main/java/org/slf4j/impl/MavenSimpleLogger.java @@ -20,8 +20,7 @@ import java.io.PrintStream; -import static org.apache.maven.shared.utils.logging.MessageUtils.buffer; -import static org.apache.maven.shared.utils.logging.MessageUtils.level; +import static org.apache.maven.jline.MessageUtils.builder; /** * Logger for Maven, that support colorization of levels and stacktraces. @@ -29,6 +28,7 @@ * @since 3.5.0 */ public class MavenSimpleLogger extends SimpleLogger { + MavenSimpleLogger(String name) { super(name); } @@ -37,16 +37,16 @@ public class MavenSimpleLogger extends SimpleLogger { protected String renderLevel(int level) { switch (level) { case LOG_LEVEL_TRACE: - return level().debug("TRACE").toString(); + return builder().trace("TRACE").toString(); case LOG_LEVEL_DEBUG: - return level().debug("DEBUG").toString(); + return builder().debug("DEBUG").toString(); case LOG_LEVEL_INFO: - return level().info("INFO").toString(); + return builder().info("INFO").toString(); case LOG_LEVEL_WARN: - return level().warning("WARNING").toString(); + return builder().warning("WARNING").toString(); case LOG_LEVEL_ERROR: default: - return level().error("ERROR").toString(); + return builder().error("ERROR").toString(); } } @@ -55,10 +55,10 @@ protected void writeThrowable(Throwable t, PrintStream stream) { if (t == null) { return; } - stream.print(buffer().failure(t.getClass().getName())); + stream.print(builder().failure(t.getClass().getName())); if (t.getMessage() != null) { stream.print(": "); - stream.print(buffer().failure(t.getMessage())); + stream.print(builder().failure(t.getMessage())); } stream.println(); @@ -69,9 +69,9 @@ private void printStackTrace(Throwable t, PrintStream stream, String prefix) { for (StackTraceElement e : t.getStackTrace()) { stream.print(prefix); stream.print(" "); - stream.print(buffer().strong("at")); + stream.print(builder().strong("at")); stream.print(" " + e.getClassName() + "." + e.getMethodName()); - stream.print(buffer().a(" (").strong(getLocation(e)).a(")")); + stream.print(builder().a(" (").strong(getLocation(e)).a(")")); stream.println(); } for (Throwable se : t.getSuppressed()) { @@ -84,10 +84,10 @@ private void printStackTrace(Throwable t, PrintStream stream, String prefix) { } private void writeThrowable(Throwable t, PrintStream stream, String caption, String prefix) { - stream.print(buffer().a(prefix).strong(caption).a(": ").a(t.getClass().getName())); + stream.print(builder().a(prefix).strong(caption).a(": ").a(t.getClass().getName())); if (t.getMessage() != null) { stream.print(": "); - stream.print(buffer().failure(t.getMessage())); + stream.print(builder().failure(t.getMessage())); } stream.println(); diff --git a/pom.xml b/pom.xml index 1b93d1168470..e0b72c0dda98 100644 --- a/pom.xml +++ b/pom.xml @@ -97,6 +97,7 @@ under the License. maven-repository-metadata maven-slf4j-provider maven-embedder + maven-jline maven-compat apache-maven @@ -156,6 +157,7 @@ under the License. None **/package-info.java 2026-03-24T21:15:50Z + 3.30.9 @@ -229,6 +231,11 @@ under the License. maven-slf4j-provider ${project.version} + + org.apache.maven + maven-jline + ${project.version} + @@ -339,21 +346,24 @@ under the License. ${plexusInterpolationVersion} - org.apache.maven.shared - maven-shared-utils - 3.4.2 - - - - commons-io - commons-io - - + org.jline + jansi-core + ${jlineVersion} + + + org.jline + jline-terminal + ${jlineVersion} + + + org.jline + jline-terminal-jni + ${jlineVersion} - org.fusesource.jansi - jansi - 2.4.3 + org.jline + jline-terminal-ffm + ${jlineVersion} org.slf4j @@ -686,6 +696,22 @@ under the License. true + + enforce-bytecode-version + + enforce + + + + + + org.jline:jline-terminal-ffm + + + + true + +