From 2021744aab62f78f2627dcbdfee7c8a4b9564321 Mon Sep 17 00:00:00 2001 From: Slawomir Jaranowski Date: Thu, 2 Apr 2026 01:00:14 +0200 Subject: [PATCH 1/3] Migrate from JAnsi to JLine, introduce MessageBuilderFactory What changed: - Replaced JAnsi with JLine for terminal output handling. - Added new MessageBuilder and MessageBuilderFactory for JLine integration as replacement for maven-shared-utils Why: - JAnsi is going to EOL --- apache-maven/pom.xml | 10 +- apache-maven/src/bin/mvn | 2 +- apache-maven/src/bin/mvn.cmd | 2 +- apache-maven/src/lib/jansi-native/README.txt | 8 - apache-maven/src/lib/jline-native/README.txt | 8 + .../appended-resources/META-INF/LICENSE.vm | 2 + .../licenses/BSD-3-Clause.txt | 28 + apache-maven/src/main/assembly/component.xml | 8 +- maven-core/pom.xml | 4 - .../LifecycleExecutionException.java | 21 +- .../DefaultMojoExecutionConfigurator.java | 11 +- .../internal/LifecycleDependencyResolver.java | 6 +- .../lifecycle/internal/MojoExecutor.java | 14 +- .../apache/maven/message/MessageBuilder.java | 277 +++++ .../maven/message/MessageBuilderFactory.java | 51 + .../internal/DefaultMessageBuilder.java | 78 ++ .../DefaultMessageBuilderFactory.java | 52 + .../internal/DeprecatedPluginValidator.java | 11 +- maven-embedder/pom.xml | 9 +- .../apache/maven/cli/CLIReportingUtils.java | 4 +- .../java/org/apache/maven/cli/MavenCli.java | 33 +- .../maven/cli/event/ExecutionEventLogger.java | 23 +- .../AbstractMavenTransferListener.java | 3 +- .../org/apache/maven/cli/MavenCliTest.java | 2 +- .../cli/event/ExecutionEventLoggerTest.java | 2 +- maven-jline/pom.xml | 60 ++ .../org/apache/maven/jline/FastTerminal.java | 291 ++++++ .../jline/JLineMessageBuilderFactory.java | 156 +++ .../org/apache/maven/jline/MessageUtils.java | 127 +++ .../main/java/org/fusesource/jansi/Ansi.java | 957 ++++++++++++++++++ maven-jline/src/site/site.xml | 35 + maven-slf4j-provider/pom.xml | 4 +- .../org/slf4j/impl/MavenSimpleLogger.java | 26 +- pom.xml | 52 +- 34 files changed, 2271 insertions(+), 106 deletions(-) delete mode 100644 apache-maven/src/lib/jansi-native/README.txt create mode 100644 apache-maven/src/lib/jline-native/README.txt create mode 100644 apache-maven/src/main/appended-resources/licenses/BSD-3-Clause.txt create mode 100644 maven-core/src/main/java/org/apache/maven/message/MessageBuilder.java create mode 100644 maven-core/src/main/java/org/apache/maven/message/MessageBuilderFactory.java create mode 100644 maven-core/src/main/java/org/apache/maven/message/internal/DefaultMessageBuilder.java create mode 100644 maven-core/src/main/java/org/apache/maven/message/internal/DefaultMessageBuilderFactory.java create mode 100644 maven-jline/pom.xml create mode 100644 maven-jline/src/main/java/org/apache/maven/jline/FastTerminal.java create mode 100644 maven-jline/src/main/java/org/apache/maven/jline/JLineMessageBuilderFactory.java create mode 100644 maven-jline/src/main/java/org/apache/maven/jline/MessageUtils.java create mode 100644 maven-jline/src/main/java/org/fusesource/jansi/Ansi.java create mode 100644 maven-jline/src/site/site.xml 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..fdc6a97e5fd9 --- /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. \ No newline at end of file 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..45e2edc829d4 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,10 @@ */ package org.apache.maven.lifecycle; +import org.apache.maven.message.MessageBuilder; +import org.apache.maven.message.MessageBuilderFactory; 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 +56,24 @@ public LifecycleExecutionException(String message, MojoExecution execution, Mave this.project = project; } - public LifecycleExecutionException(MojoExecution execution, MavenProject project, Throwable cause) { - this(createMessage(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..589acac8ce98 --- /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) +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..1ea94d98320d 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 @@ -569,14 +569,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 +1015,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 +1029,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 +1037,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 +1097,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..e30327d597a8 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,7 +27,7 @@ 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; 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..4d71a448c942 --- /dev/null +++ b/maven-jline/src/main/java/org/apache/maven/jline/MessageUtils.java @@ -0,0 +1,127 @@ +/* + * 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); + 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 + + From ba233624363b20c864c5b6ccc66c5d499bf48854 Mon Sep 17 00:00:00 2001 From: Slawomir Jaranowski Date: Fri, 3 Apr 2026 21:20:11 +0200 Subject: [PATCH 2/3] Fix after review --- .../main/appended-resources/licenses/BSD-3-Clause.txt | 2 +- .../maven/lifecycle/LifecycleExecutionException.java | 10 ++++++++++ .../message/internal/DefaultMessageBuilderFactory.java | 2 +- 3 files changed, 12 insertions(+), 2 deletions(-) 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 index fdc6a97e5fd9..4b0469c95f9f 100644 --- a/apache-maven/src/main/appended-resources/licenses/BSD-3-Clause.txt +++ b/apache-maven/src/main/appended-resources/licenses/BSD-3-Clause.txt @@ -25,4 +25,4 @@ 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. \ No newline at end of file +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 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 45e2edc829d4..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 @@ -20,6 +20,7 @@ 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; @@ -56,6 +57,15 @@ 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(new DefaultMessageBuilderFactory(), execution, project, cause), execution, project, cause); + } + public LifecycleExecutionException( MessageBuilderFactory messageBuilderFactory, MojoExecution execution, 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 index 589acac8ce98..a3e1daf9241e 100644 --- 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 @@ -28,7 +28,7 @@ @Named @Singleton @Priority(-1) -class DefaultMessageBuilderFactory implements MessageBuilderFactory { +public class DefaultMessageBuilderFactory implements MessageBuilderFactory { @Override public boolean isColorEnabled() { From 4a92c2810739cb337e82dcbb765896f41a2ec383 Mon Sep 17 00:00:00 2001 From: Slawomir Jaranowski Date: Tue, 7 Apr 2026 22:30:28 +0200 Subject: [PATCH 3/3] Fix color mode detecting --- .../java/org/apache/maven/cli/MavenCli.java | 6 + .../org/apache/maven/cli/MavenCliTest.java | 121 +++++++++++------- .../org/apache/maven/jline/MessageUtils.java | 7 +- 3 files changed, 87 insertions(+), 47 deletions(-) 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 1ea94d98320d..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 @@ -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 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 e30327d597a8..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 @@ -33,6 +33,7 @@ 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-jline/src/main/java/org/apache/maven/jline/MessageUtils.java b/maven-jline/src/main/java/org/apache/maven/jline/MessageUtils.java index 4d71a448c942..94b3f10baa12 100644 --- a/maven-jline/src/main/java/org/apache/maven/jline/MessageUtils.java +++ b/maven-jline/src/main/java/org/apache/maven/jline/MessageUtils.java @@ -45,8 +45,11 @@ public static void systemInstall() { public static void systemInstall(Consumer builderConsumer, Consumer terminalConsumer) { MessageUtils.terminal = new FastTerminal( () -> { - TerminalBuilder builder = - TerminalBuilder.builder().name("Maven").dumb(true); + TerminalBuilder builder = TerminalBuilder.builder() + .name("Maven") + .dumb(true) + .systemOutput(TerminalBuilder.SystemOutput.SysOut) + .color(true); if (builderConsumer != null) { builderConsumer.accept(builder); }