diff --git a/maven-embedder/pom.xml b/maven-embedder/pom.xml index 957d2d79a4..4f7bd0c165 100644 --- a/maven-embedder/pom.xml +++ b/maven-embedder/pom.xml @@ -146,6 +146,11 @@ under the License. commons-cli commons-cli + + org.junit.jupiter + junit-jupiter-params + test + org.mockito mockito-core diff --git a/maven-embedder/src/main/java/org/apache/maven/cli/CLIManager.java b/maven-embedder/src/main/java/org/apache/maven/cli/CLIManager.java index 41cb71a829..e643f1a67e 100644 --- a/maven-embedder/src/main/java/org/apache/maven/cli/CLIManager.java +++ b/maven-embedder/src/main/java/org/apache/maven/cli/CLIManager.java @@ -38,6 +38,10 @@ public class CLIManager { public static final char BATCH_MODE = 'B'; + public static final String NON_INTERACTIVE = "non-interactive"; + + public static final String FORCE_INTERACTIVE = "force-interactive"; + public static final char SET_USER_PROPERTY = 'D'; /** @@ -173,7 +177,16 @@ public class CLIManager { .build()); options.addOption(Option.builder(Character.toString(BATCH_MODE)) .longOpt("batch-mode") - .desc("Run in non-interactive (batch) mode (disables output color)") + .desc("Run in non-interactive mode. Alias for --non-interactive (kept for backwards compatability)") + .build()); + options.addOption(Option.builder() + .longOpt(NON_INTERACTIVE) + .desc("Run in non-interactive mode. Alias for --batch-mode") + .build()); + options.addOption(Option.builder() + .longOpt(FORCE_INTERACTIVE) + .desc( + "Run in interactive mode. Overrides, if applicable, the CI environment variable and --non-interactive/--batch-mode options") .build()); options.addOption(Option.builder(SUPPRESS_SNAPSHOT_UPDATES) .longOpt("no-snapshot-updates") 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 b3f29d57cd..d5700c310e 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 @@ -119,7 +119,10 @@ import org.sonatype.plexus.components.sec.dispatcher.SecUtil; import org.sonatype.plexus.components.sec.dispatcher.model.SettingsSecurity; import static java.util.Comparator.comparing; +import static org.apache.maven.cli.CLIManager.BATCH_MODE; import static org.apache.maven.cli.CLIManager.COLOR; +import static org.apache.maven.cli.CLIManager.FORCE_INTERACTIVE; +import static org.apache.maven.cli.CLIManager.NON_INTERACTIVE; import static org.apache.maven.cli.ResolveFile.resolveFile; import static org.apache.maven.shared.utils.logging.MessageUtils.buffer; @@ -486,10 +489,10 @@ public class MavenCli { */ void logging(CliRequest cliRequest) { // LOG LEVEL - cliRequest.verbose = cliRequest.commandLine.hasOption(CLIManager.VERBOSE) - || cliRequest.commandLine.hasOption(CLIManager.DEBUG); - cliRequest.quiet = !cliRequest.verbose && cliRequest.commandLine.hasOption(CLIManager.QUIET); - cliRequest.showErrors = cliRequest.verbose || cliRequest.commandLine.hasOption(CLIManager.ERRORS); + CommandLine commandLine = cliRequest.commandLine; + cliRequest.verbose = commandLine.hasOption(CLIManager.VERBOSE) || commandLine.hasOption(CLIManager.DEBUG); + cliRequest.quiet = !cliRequest.verbose && commandLine.hasOption(CLIManager.QUIET); + cliRequest.showErrors = cliRequest.verbose || commandLine.hasOption(CLIManager.ERRORS); slf4jLoggerFactory = LoggerFactory.getILoggerFactory(); Slf4jConfiguration slf4jConfiguration = Slf4jConfigurationFactory.getConfiguration(slf4jLoggerFactory); @@ -506,7 +509,7 @@ public class MavenCli { // LOG COLOR String styleColor = cliRequest.getUserProperties().getProperty(STYLE_COLOR_PROPERTY, "auto"); - styleColor = cliRequest.commandLine.getOptionValue(COLOR, styleColor); + styleColor = commandLine.getOptionValue(COLOR, styleColor); if ("always".equals(styleColor) || "yes".equals(styleColor) || "force".equals(styleColor)) { MessageUtils.setColorEnabled(true); } else if ("never".equals(styleColor) || "no".equals(styleColor) || "none".equals(styleColor)) { @@ -514,14 +517,17 @@ public class MavenCli { } else if (!"auto".equals(styleColor) && !"tty".equals(styleColor) && !"if-tty".equals(styleColor)) { throw new IllegalArgumentException( "Invalid color configuration value '" + styleColor + "'. Supported are 'auto', 'always', 'never'."); - } else if (cliRequest.commandLine.hasOption(CLIManager.BATCH_MODE) - || cliRequest.commandLine.hasOption(CLIManager.LOG_FILE)) { - MessageUtils.setColorEnabled(false); + } else { + boolean isBatchMode = !commandLine.hasOption(FORCE_INTERACTIVE) + && (commandLine.hasOption(BATCH_MODE) || commandLine.hasOption(NON_INTERACTIVE)); + if (isBatchMode || commandLine.hasOption(CLIManager.LOG_FILE)) { + MessageUtils.setColorEnabled(false); + } } // LOG STREAMS - if (cliRequest.commandLine.hasOption(CLIManager.LOG_FILE)) { - File logFile = new File(cliRequest.commandLine.getOptionValue(CLIManager.LOG_FILE)); + if (commandLine.hasOption(CLIManager.LOG_FILE)) { + File logFile = new File(commandLine.getOptionValue(CLIManager.LOG_FILE)); logFile = resolveFile(logFile, cliRequest.workingDirectory); // redirect stdout and stderr to file @@ -541,8 +547,8 @@ public class MavenCli { plexusLoggerManager = new Slf4jLoggerManager(); slf4jLogger = slf4jLoggerFactory.getLogger(this.getClass().getName()); - if (cliRequest.commandLine.hasOption(CLIManager.FAIL_ON_SEVERITY)) { - String logLevelThreshold = cliRequest.commandLine.getOptionValue(CLIManager.FAIL_ON_SEVERITY); + if (commandLine.hasOption(CLIManager.FAIL_ON_SEVERITY)) { + String logLevelThreshold = commandLine.getOptionValue(CLIManager.FAIL_ON_SEVERITY); if (slf4jLoggerFactory instanceof MavenSlf4jWrapperFactory) { LogLevelRecorder logLevelRecorder = new LogLevelRecorder(logLevelThreshold); @@ -557,7 +563,7 @@ public class MavenCli { } } - if (cliRequest.commandLine.hasOption(CLIManager.DEBUG)) { + if (commandLine.hasOption(CLIManager.DEBUG)) { slf4jLogger.warn("The option '--debug' is deprecated and may be repurposed as Java debug" + " in a future version. Use -X/--verbose instead."); } @@ -1242,7 +1248,7 @@ public class MavenCli { request.setShowErrors(cliRequest.showErrors); // default: false File baseDirectory = new File(workingDirectory, "").getAbsoluteFile(); - disableOnPresentOption(commandLine, CLIManager.BATCH_MODE, request::setInteractiveMode); + disableInteractiveModeIfNeeded(cliRequest, request); enableOnPresentOption(commandLine, CLIManager.SUPPRESS_SNAPSHOT_UPDATES, request::setNoSnapshotUpdates); request.setGoals(commandLine.getArgList()); request.setReactorFailureBehavior(determineReactorFailureBehaviour(commandLine)); @@ -1304,6 +1310,27 @@ public class MavenCli { return request; } + private void disableInteractiveModeIfNeeded(final CliRequest cliRequest, final MavenExecutionRequest request) { + CommandLine commandLine = cliRequest.getCommandLine(); + if (commandLine.hasOption(FORCE_INTERACTIVE)) { + return; + } + + boolean runningOnCI = isRunningOnCI(cliRequest.getSystemProperties()); + if (runningOnCI) { + slf4jLogger.info("Making this build non-interactive, because the environment variable CI equals \"true\"." + + " Disable this detection by removing that variable or adding --force-interactive."); + request.setInteractiveMode(false); + } else if (commandLine.hasOption(BATCH_MODE) || commandLine.hasOption(NON_INTERACTIVE)) { + request.setInteractiveMode(false); + } + } + + private static boolean isRunningOnCI(Properties systemProperties) { + String ciEnv = systemProperties.getProperty("env.CI"); + return ciEnv != null && !"false".equals(ciEnv); + } + private String determineLocalRepositoryPath(final MavenExecutionRequest request) { String userDefinedLocalRepo = request.getUserProperties().getProperty(MavenCli.LOCAL_REPO_PROPERTY); if (userDefinedLocalRepo != null) { @@ -1424,7 +1451,10 @@ public class MavenCli { final boolean verbose, final CommandLine commandLine, final MavenExecutionRequest request) { - if (quiet || commandLine.hasOption(CLIManager.NO_TRANSFER_PROGRESS)) { + boolean runningOnCI = isRunningOnCI(request.getSystemProperties()); + boolean quietCI = runningOnCI && !commandLine.hasOption(FORCE_INTERACTIVE); + + if (quiet || commandLine.hasOption(CLIManager.NO_TRANSFER_PROGRESS) || quietCI) { return new QuietMavenTransferListener(); } else if (request.isInteractiveMode() && !commandLine.hasOption(CLIManager.LOG_FILE)) { // 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 e9e90e7278..7ba238dbc1 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 @@ -26,6 +26,7 @@ import java.nio.file.Path; import java.nio.file.Paths; import java.util.Collections; import java.util.List; +import java.util.stream.Stream; import org.apache.commons.cli.CommandLine; import org.apache.commons.cli.CommandLineParser; @@ -34,6 +35,9 @@ import org.apache.commons.cli.Option; import org.apache.commons.cli.Options; import org.apache.commons.cli.ParseException; import org.apache.maven.Maven; +import org.apache.maven.cli.transfer.ConsoleMavenTransferListener; +import org.apache.maven.cli.transfer.QuietMavenTransferListener; +import org.apache.maven.cli.transfer.Slf4jMavenTransferListener; import org.apache.maven.eventspy.internal.EventSpyDispatcher; import org.apache.maven.execution.MavenExecutionRequest; import org.apache.maven.execution.ProfileActivation; @@ -45,9 +49,13 @@ import org.apache.maven.toolchain.building.ToolchainsBuildingRequest; import org.apache.maven.toolchain.building.ToolchainsBuildingResult; import org.codehaus.plexus.DefaultPlexusContainer; import org.codehaus.plexus.PlexusContainer; +import org.eclipse.aether.transfer.TransferListener; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; import org.mockito.InOrder; import static java.util.Arrays.asList; @@ -333,6 +341,20 @@ class MavenCliTest { cli.logging(request); assertFalse(MessageUtils.isColorEnabled()); + MessageUtils.setColorEnabled(true); + request = new CliRequest(new String[] {"--non-interactive"}, null); + cli.cli(request); + cli.properties(request); + cli.logging(request); + assertFalse(MessageUtils.isColorEnabled()); + + MessageUtils.setColorEnabled(true); + request = new CliRequest(new String[] {"--force-interactive", "--non-interactive"}, null); + 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"; @@ -585,6 +607,68 @@ class MavenCliTest { assertThat(request.getCommandLine().getArgs(), equalTo(new String[] {"prefix:3.0.0:bar", "validate"})); } + @ParameterizedTest + @MethodSource("activateBatchModeArguments") + public void activateBatchMode(boolean ciEnv, String[] cliArgs, boolean isBatchMode) throws Exception { + CliRequest request = new CliRequest(cliArgs, null); + if (ciEnv) request.getSystemProperties().put("env.CI", "true"); + cli.cli(request); + + boolean batchMode = !cli.populateRequest(request).isInteractiveMode(); + + assertThat(batchMode, is(isBatchMode)); + } + + public static Stream activateBatchModeArguments() { + return Stream.of( + Arguments.of(false, new String[] {}, false), + Arguments.of(true, new String[] {}, true), + Arguments.of(true, new String[] {"--force-interactive"}, false), + Arguments.of(true, new String[] {"--force-interactive", "--non-interactive"}, false), + Arguments.of(true, new String[] {"--force-interactive", "--batch-mode"}, false), + Arguments.of(true, new String[] {"--force-interactive", "--non-interactive", "--batch-mode"}, false), + Arguments.of(false, new String[] {"--non-interactive"}, true), + Arguments.of(false, new String[] {"--batch-mode"}, true), + Arguments.of(false, new String[] {"--non-interactive", "--batch-mode"}, true)); + } + + @ParameterizedTest + @MethodSource("calculateTransferListenerArguments") + public void calculateTransferListener(boolean ciEnv, String[] cliArgs, Class expectedSubClass) + throws Exception { + CliRequest request = new CliRequest(cliArgs, null); + if (ciEnv) request.getSystemProperties().put("env.CI", "true"); + cli.cli(request); + cli.logging(request); + + TransferListener transferListener = cli.populateRequest(request).getTransferListener(); + + assertThat(transferListener.getClass(), is(expectedSubClass)); + } + + public static Stream calculateTransferListenerArguments() { + return Stream.of( + Arguments.of(false, new String[] {}, ConsoleMavenTransferListener.class), + Arguments.of(true, new String[] {}, QuietMavenTransferListener.class), + Arguments.of(false, new String[] {"-ntp"}, QuietMavenTransferListener.class), + Arguments.of(false, new String[] {"--quiet"}, QuietMavenTransferListener.class), + Arguments.of(true, new String[] {"--force-interactive"}, ConsoleMavenTransferListener.class), + Arguments.of( + true, + new String[] {"--force-interactive", "--non-interactive"}, + ConsoleMavenTransferListener.class), + Arguments.of( + true, new String[] {"--force-interactive", "--batch-mode"}, ConsoleMavenTransferListener.class), + Arguments.of( + true, + new String[] {"--force-interactive", "--non-interactive", "--batch-mode"}, + ConsoleMavenTransferListener.class), + Arguments.of(false, new String[] {"--non-interactive"}, Slf4jMavenTransferListener.class), + Arguments.of(false, new String[] {"--batch-mode"}, Slf4jMavenTransferListener.class), + Arguments.of( + false, new String[] {"--non-interactive", "--batch-mode"}, Slf4jMavenTransferListener.class)); + } + private MavenProject createMavenProject(String groupId, String artifactId) { MavenProject project = new MavenProject(); project.setGroupId(groupId);