diff --git a/maven-core/src/main/java/org/apache/maven/internal/MessageHelper.java b/maven-core/src/main/java/org/apache/maven/internal/MessageHelper.java new file mode 100644 index 000000000000..3448084ce637 --- /dev/null +++ b/maven-core/src/main/java/org/apache/maven/internal/MessageHelper.java @@ -0,0 +1,130 @@ +package org.apache.maven.internal; + +/* + * 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. + */ + +import java.util.ArrayList; +import java.util.List; + +/** + * Helper class to format warning messages to the console + */ +public class MessageHelper +{ + + public static final int DEFAULT_TERMINAL_WIDTH = 80; + + public static final int DEFAULT_MAX_SIZE = 65; + + public static final String LINE_LENGTH_PROPERTY = "maven.console.lineLength"; + + private static final char BOX_CHAR = '*'; + + public static int getConsoleLineLength() + { + try + { + int length; + String prop = System.getProperty( LINE_LENGTH_PROPERTY ); + if ( prop != null ) + { + length = Integer.parseInt( prop ); + } + else + { + Class ansiConsoleClass = Class.forName( "org.fusesource.jansi.AnsiConsole" ); + Object out = ansiConsoleClass.getMethod( "out" ).invoke( null ); + length = (Integer) out.getClass().getMethod( "getTerminalWidth" ).invoke( out ); + } + return Math.max( length, DEFAULT_TERMINAL_WIDTH ); + } + catch ( Throwable t ) + { + // ignore exceptions + return DEFAULT_TERMINAL_WIDTH; + } + } + + public static String separatorLine() + { + return separatorLine( DEFAULT_MAX_SIZE ); + } + + public static String separatorLine( int length ) + { + StringBuilder sb = new StringBuilder( length ); + repeat( sb, '*', length ); + return sb.toString(); + } + + public static List messageBox( String... lines ) + { + return messageBox( DEFAULT_MAX_SIZE, lines ); + } + + public static List messageBox( int size, String... lines ) + { + int rem = size - 4; + List result = new ArrayList<>(); + StringBuilder sb = new StringBuilder( size ); + // first line + sb.setLength( 0 ); + repeat( sb, BOX_CHAR, size ); + result.add( sb.toString() ); + // lines + for ( String line : lines ) + { + sb.setLength( 0 ); + String[] words = line.split( " " ); + for ( String word : words ) + { + if ( sb.length() >= rem - word.length() - ( sb.length() > 0 ? 1 : 0 ) ) + { + repeat( sb, ' ', rem - sb.length() ); + result.add( BOX_CHAR + " " + sb + " " + BOX_CHAR ); + sb.setLength( 0 ); + } + if ( sb.length() > 0 ) + { + sb.append( ' ' ); + } + sb.append( word ); + } + + while ( sb.length() < rem ) + { + sb.append( ' ' ); + } + result.add( BOX_CHAR + " " + sb + " " + BOX_CHAR ); + } + // last line + sb.setLength( 0 ); + repeat( sb, BOX_CHAR, size ); + result.add( sb.toString() ); + return result; + } + + private static void repeat( StringBuilder sb, char c, int nb ) + { + for ( int i = 0; i < nb; i++ ) + { + sb.append( c ); + } + } +} 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 2980e885851b..974ff83fd1c4 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 @@ -24,6 +24,7 @@ import org.apache.maven.artifact.resolver.filter.CumulativeScopeArtifactFilter; import org.apache.maven.execution.ExecutionEvent; import org.apache.maven.execution.MavenSession; +import org.apache.maven.internal.MessageHelper; import org.apache.maven.lifecycle.LifecycleExecutionException; import org.apache.maven.lifecycle.MissingProjectException; import org.apache.maven.plugin.BuildPluginManager; @@ -44,6 +45,8 @@ import org.codehaus.plexus.component.repository.exception.ComponentLookupException; import org.codehaus.plexus.util.StringUtils; import org.eclipse.aether.SessionData; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.util.ArrayList; import java.util.Arrays; @@ -75,6 +78,8 @@ public class MojoExecutor { + private static final Logger LOGGER = LoggerFactory.getLogger( MojoExecutor.class ); + @Requirement private BuildPluginManager pluginManager; @@ -254,7 +259,19 @@ private static class ProjectLock implements AutoCloseable boolean aggregator = mojoDescriptor.isAggregator(); acquiredAggregatorLock = aggregator ? aggregatorLock.writeLock() : aggregatorLock.readLock(); acquiredProjectLock = getProjectLock( session ); - acquiredAggregatorLock.lock(); + if ( !acquiredAggregatorLock.tryLock() ) + { + int size = Math.max( MessageHelper.getConsoleLineLength() - "[WARNING] ".length() - 1, + MessageHelper.DEFAULT_MAX_SIZE ); + for ( String s : MessageHelper.messageBox( size, + "An aggregator Mojo is already executing in parallel build, but aggregator " + + "Mojos require exclusive access to reactor to prevent race conditions. This " + + "mojo execution will be blocked until the aggregator work is done." ) ) + { + LOGGER.warn( s ); + } + acquiredAggregatorLock.lock(); + } acquiredProjectLock.lock(); } else diff --git a/maven-core/src/main/java/org/apache/maven/lifecycle/internal/builder/BuilderCommon.java b/maven-core/src/main/java/org/apache/maven/lifecycle/internal/builder/BuilderCommon.java index 25ab6a46a6e2..973d17cd7060 100644 --- a/maven-core/src/main/java/org/apache/maven/lifecycle/internal/builder/BuilderCommon.java +++ b/maven-core/src/main/java/org/apache/maven/lifecycle/internal/builder/BuilderCommon.java @@ -24,6 +24,7 @@ import org.apache.maven.execution.ExecutionEvent; import org.apache.maven.execution.MavenExecutionRequest; import org.apache.maven.execution.MavenSession; +import org.apache.maven.internal.MessageHelper; import org.apache.maven.lifecycle.LifecycleExecutionException; import org.apache.maven.lifecycle.LifecycleNotFoundException; import org.apache.maven.lifecycle.LifecyclePhaseNotFoundException; @@ -103,22 +104,24 @@ public MavenExecutionPlan resolveBuildPlan( MavenSession session, MavenProject p final Set unsafePlugins = executionPlan.getNonThreadSafePlugins(); if ( !unsafePlugins.isEmpty() ) { - logger.warn( "*****************************************************************" ); - logger.warn( "* Your build is requesting parallel execution, but project *" ); - logger.warn( "* contains the following plugin(s) that have goals not marked *" ); - logger.warn( "* as @threadSafe to support parallel building. *" ); - logger.warn( "* While this /may/ work fine, please look for plugin updates *" ); - logger.warn( "* and/or request plugins be made thread-safe. *" ); - logger.warn( "* If reporting an issue, report it against the plugin in *" ); - logger.warn( "* question, not against maven-core *" ); - logger.warn( "*****************************************************************" ); + int size = Math.max( MessageHelper.getConsoleLineLength() - "[WARNING] ".length() - 1, + MessageHelper.DEFAULT_MAX_SIZE ); + for ( String s : MessageHelper.messageBox( size, + "Your build is requesting parallel execution, but project contains the following " + + "plugin(s) that have goals not marked as @threadSafe to support parallel building.", + "While this /may/ work fine, please look for plugin updates and/or " + + "request plugins be made thread-safe.", + "If reporting an issue, report it against the plugin in question, not against maven-core." ) ) + { + logger.warn( s ); + } if ( logger.isDebugEnabled() ) { final Set unsafeGoals = executionPlan.getNonThreadSafeMojos(); logger.warn( "The following goals are not marked @threadSafe in " + project.getName() + ":" ); for ( MojoDescriptor unsafeGoal : unsafeGoals ) { - logger.warn( unsafeGoal.getId() ); + logger.warn( " - " + unsafeGoal.getId() ); } } else @@ -126,11 +129,11 @@ public MavenExecutionPlan resolveBuildPlan( MavenSession session, MavenProject p logger.warn( "The following plugins are not marked @threadSafe in " + project.getName() + ":" ); for ( Plugin unsafePlugin : unsafePlugins ) { - logger.warn( unsafePlugin.getId() ); + logger.warn( " - " + unsafePlugin.getId() ); } logger.warn( "Enable debug to see more precisely which goals are not marked @threadSafe." ); } - logger.warn( "*****************************************************************" ); + logger.warn( MessageHelper.separatorLine( size ) ); } } diff --git a/maven-core/src/test/java/org/apache/maven/internal/MessageHelperTest.java b/maven-core/src/test/java/org/apache/maven/internal/MessageHelperTest.java new file mode 100644 index 000000000000..26432f1ef9d7 --- /dev/null +++ b/maven-core/src/test/java/org/apache/maven/internal/MessageHelperTest.java @@ -0,0 +1,71 @@ +package org.apache.maven.internal; + +/* + * 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. + */ + +import java.util.ArrayList; +import java.util.List; + +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + +public class MessageHelperTest +{ + + @Test + public void testBuilderCommon() + { + List msgs = new ArrayList<>(); + msgs.add( "*****************************************************************" ); + msgs.add( "* Your build is requesting parallel execution, but project *" ); + msgs.add( "* contains the following plugin(s) that have goals not marked *" ); + msgs.add( "* as @threadSafe to support parallel building. *" ); + msgs.add( "* While this /may/ work fine, please look for plugin updates *" ); + msgs.add( "* and/or request plugins be made thread-safe. *" ); + msgs.add( "* If reporting an issue, report it against the plugin in *" ); + msgs.add( "* question, not against maven-core *" ); + msgs.add( "*****************************************************************" ); + + assertEquals( msgs, MessageHelper.messageBox( + "Your build is requesting parallel execution, but project contains the following " + + "plugin(s) that have goals not marked as @threadSafe to support parallel building.", + "While this /may/ work fine, please look for plugin updates and/or " + + "request plugins be made thread-safe.", + "If reporting an issue, report it against the plugin in question, not against maven-core" + ) ); + } + + @Test + public void testMojoExecutor() + { + List msgs = new ArrayList<>(); + msgs.add( "*****************************************************************" ); + msgs.add( "* An aggregator Mojo is already executing in parallel build, *" ); + msgs.add( "* but aggregator Mojos require exclusive access to reactor to *" ); + msgs.add( "* prevent race conditions. This mojo execution will be blocked *" ); + msgs.add( "* until the aggregator work is done. *" ); + msgs.add( "*****************************************************************" ); + + assertEquals( msgs, MessageHelper.messageBox( + "An aggregator Mojo is already executing in parallel build, but aggregator " + + "Mojos require exclusive access to reactor to prevent race conditions. This " + + "mojo execution will be blocked until the aggregator work is done." ) ); + } +} 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 17da65548a79..9738ecad9008 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 @@ -33,6 +33,7 @@ import org.apache.maven.execution.ExecutionEvent; import org.apache.maven.execution.MavenExecutionResult; import org.apache.maven.execution.MavenSession; +import org.apache.maven.internal.MessageHelper; import org.apache.maven.plugin.MojoExecution; import org.apache.maven.plugin.descriptor.MojoDescriptor; import org.apache.maven.project.MavenProject; @@ -60,11 +61,10 @@ public class ExecutionEventLogger public ExecutionEventLogger() { - logger = LoggerFactory.getLogger( ExecutionEventLogger.class ); + this( LoggerFactory.getLogger( ExecutionEventLogger.class ) ); } - // TODO should we deprecate? - public ExecutionEventLogger( Logger logger ) + ExecutionEventLogger( Logger logger ) { this.logger = Objects.requireNonNull( logger, "logger cannot be null" ); } @@ -83,7 +83,7 @@ private static String chars( char c, int count ) private void infoLine( char c ) { - infoMain( chars( c, LINE_LENGTH ) ); + infoMain( chars( c, MessageHelper.getConsoleLineLength() ) ); } private void infoMain( String msg ) @@ -112,9 +112,10 @@ public void sessionStarted( ExecutionEvent event ) logger.info( "" ); final List projects = event.getSession().getProjects(); + final int lineLength = MessageHelper.getConsoleLineLength(); for ( MavenProject project : projects ) { - int len = LINE_LENGTH - project.getName().length() - project.getPackaging().length() - 2; + int len = lineLength - project.getName().length() - project.getPackaging().length() - 2; logger.info( "{}{}[{}]", project.getName(), chars( ' ', ( len > 0 ) ? len : 1 ), project.getPackaging() ); } @@ -182,6 +183,8 @@ private void logReactorSummary( MavenSession session ) List projects = session.getProjects(); + final int lineLength = MessageHelper.getConsoleLineLength() - 8; + int maxProjectNameLength = lineLength - ( LINE_LENGTH - MAX_PROJECT_NAME_LENGTH ); for ( MavenProject project : projects ) { StringBuilder buffer = new StringBuilder( 128 ); @@ -195,9 +198,9 @@ private void logReactorSummary( MavenSession session ) buffer.append( ' ' ); } - if ( buffer.length() <= MAX_PROJECT_NAME_LENGTH ) + if ( buffer.length() <= maxProjectNameLength ) { - while ( buffer.length() < MAX_PROJECT_NAME_LENGTH ) + while ( buffer.length() < maxProjectNameLength ) { buffer.append( '.' ); } @@ -303,11 +306,12 @@ public void projectStarted( ExecutionEvent event ) final String postHeader = " >--"; final int headerLen = preHeader.length() + projectKey.length() + postHeader.length(); + final int lineLength = MessageHelper.getConsoleLineLength() - 8; - String prefix = chars( '-', Math.max( 0, ( LINE_LENGTH - headerLen ) / 2 ) ) + preHeader; + String prefix = chars( '-', Math.max( 0, ( lineLength - headerLen ) / 2 ) ) + preHeader; String suffix = postHeader - + chars( '-', Math.max( 0, LINE_LENGTH - headerLen - prefix.length() + preHeader.length() ) ); + + chars( '-', Math.max( 0, lineLength - headerLen - prefix.length() + preHeader.length() ) ); logger.info( buffer().strong( prefix ).project( projectKey ).strong( suffix ).toString() ); @@ -328,14 +332,14 @@ public void projectStarted( ExecutionEvent event ) } String progress = " [" + number + '/' + totalProjects + ']'; - int pad = LINE_LENGTH - building.length() - progress.length(); + int pad = lineLength - building.length() - progress.length(); infoMain( building + ( ( pad > 0 ) ? chars( ' ', pad ) : "" ) + progress ); } // ----------[ packaging ]---------- - prefix = chars( '-', Math.max( 0, ( LINE_LENGTH - project.getPackaging().length() - 4 ) / 2 ) ); - suffix = chars( '-', Math.max( 0, LINE_LENGTH - project.getPackaging().length() - 4 - prefix.length() ) ); + prefix = chars( '-', Math.max( 0, ( lineLength - project.getPackaging().length() - 4 ) / 2 ) ); + suffix = chars( '-', Math.max( 0, lineLength - project.getPackaging().length() - 4 - prefix.length() ) ); infoMain( prefix + "[ " + project.getPackaging() + " ]" + suffix ); } } @@ -360,12 +364,17 @@ public void mojoStarted( ExecutionEvent event ) { logger.info( "" ); - MessageBuilder buffer = buffer().strong( "--- " ); - append( buffer, event.getMojoExecution() ); - append( buffer, event.getProject() ); - buffer.strong( " ---" ); + int len = 2; + MessageBuilder buffer = buffer().a( " " ); + len += append( buffer, event.getMojoExecution() ); + len += append( buffer, event.getProject() ); + buffer.a( " " ); - logger.info( buffer.toString() ); + int rem = Math.max( MessageHelper.getConsoleLineLength() - len, 6 ); + + logger.info( buffer().strong( chars( '-', rem / 2 ) ).toString() + + buffer.toString() + + buffer().strong( chars( '-', rem - rem / 2 ) ).toString() ); } } @@ -419,13 +428,16 @@ public void forkSucceeded( ExecutionEvent event ) } } - private void append( MessageBuilder buffer, MojoExecution me ) + private int append( MessageBuilder buffer, MojoExecution me ) { + int len = me.getArtifactId().length() + me.getVersion().length() + me.getGoal().length() + 2; buffer.mojo( me.getArtifactId() + ':' + me.getVersion() + ':' + me.getGoal() ); if ( me.getExecutionId() != null ) { + len += 3 + me.getExecutionId().length(); buffer.a( ' ' ).strong( '(' + me.getExecutionId() + ')' ); } + return len; } private void appendForkInfo( MessageBuilder buffer, MojoDescriptor md ) @@ -451,9 +463,10 @@ private void appendForkInfo( MessageBuilder buffer, MojoDescriptor md ) buffer.strong( buff.toString() ); } - private void append( MessageBuilder buffer, MavenProject project ) + private int append( MessageBuilder buffer, MavenProject project ) { buffer.a( " @ " ).project( project.getArtifactId() ); + return 3 + project.getArtifactId().length(); } @Override 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 ca4c8a47cb9a..ac0e0fa3c94e 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 @@ -24,6 +24,7 @@ import static org.mockito.Mockito.when; import org.apache.maven.execution.ExecutionEvent; +import org.apache.maven.internal.MessageHelper; import org.apache.maven.project.MavenProject; import org.apache.maven.shared.utils.logging.MessageUtils; import org.junit.AfterClass; @@ -40,12 +41,14 @@ public class ExecutionEventLoggerTest public static void setUp() { MessageUtils.setColorEnabled( false ); + System.setProperty( MessageHelper.LINE_LENGTH_PROPERTY, "80" ); } @AfterClass public static void tearDown() { MessageUtils.setColorEnabled( true ); + System.clearProperty( MessageHelper.LINE_LENGTH_PROPERTY ); } @Test