diff --git a/api/maven-api-cli/src/main/java/org/apache/maven/api/cli/InvokerRequest.java b/api/maven-api-cli/src/main/java/org/apache/maven/api/cli/InvokerRequest.java index 6572d026c537..3df660c13083 100644 --- a/api/maven-api-cli/src/main/java/org/apache/maven/api/cli/InvokerRequest.java +++ b/api/maven-api-cli/src/main/java/org/apache/maven/api/cli/InvokerRequest.java @@ -54,6 +54,15 @@ public interface InvokerRequest { */ boolean parsingFailed(); + /** + * Returns {@code true} if this call happens in "embedded" mode. + * + * @see ParserRequest#embedded() + */ + default boolean embedded() { + return parserRequest().embedded(); + } + /** * Returns the current working directory for the Maven execution. * This is typically the directory from which Maven was invoked. @@ -138,7 +147,9 @@ default Lookup lookup() { * @return an {@link Optional} containing the input stream, or empty if not applicable */ @Nonnull - Optional in(); + default Optional stdIn() { + return Optional.ofNullable(parserRequest().stdIn()); + } /** * Returns the output stream for the Maven execution, if running in embedded mode. @@ -146,7 +157,9 @@ default Lookup lookup() { * @return an {@link Optional} containing the output stream, or empty if not applicable */ @Nonnull - Optional out(); + default Optional stdOut() { + return Optional.ofNullable(parserRequest().stdOut()); + } /** * Returns the error stream for the Maven execution, if running in embedded mode. @@ -154,7 +167,9 @@ default Lookup lookup() { * @return an {@link Optional} containing the error stream, or empty if not applicable */ @Nonnull - Optional err(); + default Optional stdErr() { + return Optional.ofNullable(parserRequest().stdErr()); + } /** * Returns a list of core extensions, if configured in the .mvn/extensions.xml file. diff --git a/api/maven-api-cli/src/main/java/org/apache/maven/api/cli/ParserRequest.java b/api/maven-api-cli/src/main/java/org/apache/maven/api/cli/ParserRequest.java index 7c7c28bdf30a..5a5913b76608 100644 --- a/api/maven-api-cli/src/main/java/org/apache/maven/api/cli/ParserRequest.java +++ b/api/maven-api-cli/src/main/java/org/apache/maven/api/cli/ParserRequest.java @@ -125,30 +125,37 @@ public interface ParserRequest { /** * Returns the input stream to be used for the Maven execution. - * If not set, System.in will be used by default. + * If not set, {@link System#in} will be used by default. * * @return the input stream, or null if not set */ @Nullable - InputStream in(); + InputStream stdIn(); /** * Returns the output stream to be used for the Maven execution. - * If not set, System.out will be used by default. + * If not set, {@link System#out} will be used by default. * * @return the output stream, or null if not set */ @Nullable - OutputStream out(); + OutputStream stdOut(); /** * Returns the error stream to be used for the Maven execution. - * If not set, System.err will be used by default. + * If not set, {@link System#err} will be used by default. * * @return the error stream, or null if not set */ @Nullable - OutputStream err(); + OutputStream stdErr(); + + /** + * Returns {@code true} if this call happens in "embedded" mode, for example by another application that + * embeds Maven. When running in "embedded" mode, Maven will not try to grab system terminal and will use + * provided {@link #stdIn()} or {@link InputStream#nullInputStream()} as standard in stream. + */ + boolean embedded(); /** * Creates a new Builder instance for constructing a Maven ParserRequest. @@ -251,9 +258,10 @@ class Builder { private Path cwd; private Path mavenHome; private Path userHome; - private InputStream in; - private OutputStream out; - private OutputStream err; + private InputStream stdIn; + private OutputStream stdOut; + private OutputStream stdErr; + private boolean embedded = false; private Builder( String command, String commandName, List args, MessageBuilderFactory messageBuilderFactory) { @@ -284,18 +292,23 @@ public Builder userHome(Path userHome) { return this; } - public Builder in(InputStream in) { - this.in = in; + public Builder stdIn(InputStream stdIn) { + this.stdIn = stdIn; return this; } - public Builder out(OutputStream out) { - this.out = out; + public Builder stdOut(OutputStream stdOut) { + this.stdOut = stdOut; return this; } - public Builder err(OutputStream err) { - this.err = err; + public Builder stdErr(OutputStream stdErr) { + this.stdErr = stdErr; + return this; + } + + public Builder embedded(boolean embedded) { + this.embedded = embedded; return this; } @@ -310,9 +323,10 @@ public ParserRequest build() { cwd, mavenHome, userHome, - in, - out, - err); + stdIn, + stdOut, + stdErr, + embedded); } @SuppressWarnings("ParameterNumber") @@ -326,9 +340,10 @@ private static class ParserRequestImpl implements ParserRequest { private final Path cwd; private final Path mavenHome; private final Path userHome; - private final InputStream in; - private final OutputStream out; - private final OutputStream err; + private final InputStream stdIn; + private final OutputStream stdOut; + private final OutputStream stdErr; + private final boolean embedded; private ParserRequestImpl( String command, @@ -340,9 +355,10 @@ private ParserRequestImpl( Path cwd, Path mavenHome, Path userHome, - InputStream in, - OutputStream out, - OutputStream err) { + InputStream stdIn, + OutputStream stdOut, + OutputStream stdErr, + boolean embedded) { this.command = requireNonNull(command, "command"); this.commandName = requireNonNull(commandName, "commandName"); this.args = List.copyOf(requireNonNull(args, "args")); @@ -352,9 +368,10 @@ private ParserRequestImpl( this.cwd = cwd; this.mavenHome = mavenHome; this.userHome = userHome; - this.in = in; - this.out = out; - this.err = err; + this.stdIn = stdIn; + this.stdOut = stdOut; + this.stdErr = stdErr; + this.embedded = embedded; } @Override @@ -403,18 +420,23 @@ public Path userHome() { } @Override - public InputStream in() { - return in; + public InputStream stdIn() { + return stdIn; + } + + @Override + public OutputStream stdOut() { + return stdOut; } @Override - public OutputStream out() { - return out; + public OutputStream stdErr() { + return stdErr; } @Override - public OutputStream err() { - return err; + public boolean embedded() { + return embedded; } } diff --git a/api/maven-api-core/src/test/java/org/apache/maven/api/services/SourcesTest.java b/api/maven-api-core/src/test/java/org/apache/maven/api/services/SourcesTest.java index fe5b73ae1fe3..be768cbd8430 100644 --- a/api/maven-api-core/src/test/java/org/apache/maven/api/services/SourcesTest.java +++ b/api/maven-api-core/src/test/java/org/apache/maven/api/services/SourcesTest.java @@ -19,6 +19,7 @@ package org.apache.maven.api.services; import java.io.IOException; +import java.io.InputStream; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; @@ -121,9 +122,10 @@ void testStreamReading() throws IOException { Files.writeString(testFile, content); Source source = Sources.fromPath(testFile); - String readContent = new String(source.openStream().readAllBytes()); - - assertEquals(content, readContent); + try (InputStream inputStream = source.openStream()) { + String readContent = new String(inputStream.readAllBytes()); + assertEquals(content, readContent); + } } @Test diff --git a/compat/maven-compat/src/test/java/org/apache/maven/project/inheritance/AbstractProjectInheritanceTestCase.java b/compat/maven-compat/src/test/java/org/apache/maven/project/inheritance/AbstractProjectInheritanceTestCase.java index af9c2804abc7..7ee78716f1db 100644 --- a/compat/maven-compat/src/test/java/org/apache/maven/project/inheritance/AbstractProjectInheritanceTestCase.java +++ b/compat/maven-compat/src/test/java/org/apache/maven/project/inheritance/AbstractProjectInheritanceTestCase.java @@ -49,7 +49,7 @@ protected File projectFile(String groupId, String artifactId) { // ---------------------------------------------------------------------- protected File getLocalRepositoryPath() { - return getTestFile("src/test/resources/inheritance-repo/" + getTestSeries()); + return getTestFile("target/test-classes/inheritance-repo/" + getTestSeries()); } @Override diff --git a/impl/maven-cli/pom.xml b/impl/maven-cli/pom.xml index 337aa7a5072f..f43c3b754f62 100644 --- a/impl/maven-cli/pom.xml +++ b/impl/maven-cli/pom.xml @@ -114,10 +114,31 @@ under the License. maven-resolver-transport-jdk test + + org.jline + jline-native + test + + + org.apache.maven.plugins + maven-dependency-plugin + + + unpack-jline-native + + unpack-dependencies + + + jline-native + org/jline/nativ/** + + + + org.codehaus.modello modello-maven-plugin diff --git a/impl/maven-cli/src/main/java/org/apache/maven/cling/ClingSupport.java b/impl/maven-cli/src/main/java/org/apache/maven/cling/ClingSupport.java index f6b48abc54b5..fdb957d5bb8a 100644 --- a/impl/maven-cli/src/main/java/org/apache/maven/cling/ClingSupport.java +++ b/impl/maven-cli/src/main/java/org/apache/maven/cling/ClingSupport.java @@ -19,11 +19,17 @@ package org.apache.maven.cling; import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import org.apache.maven.api.annotations.Nullable; import org.apache.maven.api.cli.Invoker; import org.apache.maven.api.cli.InvokerException; -import org.apache.maven.api.cli.InvokerRequest; +import org.apache.maven.api.cli.Parser; +import org.apache.maven.api.cli.ParserRequest; +import org.apache.maven.api.services.MessageBuilderFactory; import org.apache.maven.cling.invoker.logging.SystemLogger; +import org.apache.maven.jline.JLineMessageBuilderFactory; import org.codehaus.plexus.classworlds.ClassWorld; import static java.util.Objects.requireNonNull; @@ -59,14 +65,26 @@ private ClingSupport(ClassWorld classWorld, boolean classWorldManaged) { /** * The main entry point. */ - public int run(String[] args) throws IOException { + public int run( + String[] args, + @Nullable InputStream stdIn, + @Nullable OutputStream stdOut, + @Nullable OutputStream stdErr, + boolean embedded) + throws IOException { try (Invoker invoker = createInvoker()) { - return invoker.invoke(parseArguments(args)); + return invoker.invoke(createParser() + .parseInvocation(createParserRequestBuilder(args) + .stdIn(stdIn) + .stdOut(stdOut) + .stdErr(stdErr) + .embedded(embedded) + .build())); } catch (InvokerException.ExitException e) { return e.getExitCode(); } catch (Exception e) { // last resort; as ideally we should get ExitException only - new SystemLogger().error(e.getMessage(), e); + new SystemLogger(stdErr).error(e.getMessage(), e); return 1; } finally { if (classWorldManaged) { @@ -75,7 +93,13 @@ public int run(String[] args) throws IOException { } } + protected MessageBuilderFactory createMessageBuilderFactory() { + return new JLineMessageBuilderFactory(); + } + protected abstract Invoker createInvoker(); - protected abstract InvokerRequest parseArguments(String[] args); + protected abstract Parser createParser(); + + protected abstract ParserRequest.Builder createParserRequestBuilder(String[] args); } diff --git a/impl/maven-cli/src/main/java/org/apache/maven/cling/MavenCling.java b/impl/maven-cli/src/main/java/org/apache/maven/cling/MavenCling.java index 8e6855176af7..c07c853f393a 100644 --- a/impl/maven-cli/src/main/java/org/apache/maven/cling/MavenCling.java +++ b/impl/maven-cli/src/main/java/org/apache/maven/cling/MavenCling.java @@ -19,14 +19,16 @@ package org.apache.maven.cling; import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import org.apache.maven.api.annotations.Nullable; import org.apache.maven.api.cli.Invoker; -import org.apache.maven.api.cli.InvokerRequest; +import org.apache.maven.api.cli.Parser; import org.apache.maven.api.cli.ParserRequest; import org.apache.maven.cling.invoker.ProtoLookup; import org.apache.maven.cling.invoker.mvn.MavenInvoker; import org.apache.maven.cling.invoker.mvn.MavenParser; -import org.apache.maven.jline.JLineMessageBuilderFactory; import org.codehaus.plexus.classworlds.ClassWorld; /** @@ -38,7 +40,7 @@ public class MavenCling extends ClingSupport { * circumstances. */ public static void main(String[] args) throws IOException { - int exitCode = new MavenCling().run(args); + int exitCode = new MavenCling().run(args, null, null, null, false); System.exit(exitCode); } @@ -46,7 +48,20 @@ public static void main(String[] args) throws IOException { * ClassWorld Launcher "enhanced" entry point: returning exitCode and accepts Class World. */ public static int main(String[] args, ClassWorld world) throws IOException { - return new MavenCling(world).run(args); + return new MavenCling(world).run(args, null, null, null, false); + } + + /** + * ClassWorld Launcher "embedded" entry point: returning exitCode and accepts Class World and streams. + */ + public static int main( + String[] args, + ClassWorld world, + @Nullable InputStream stdIn, + @Nullable OutputStream stdOut, + @Nullable OutputStream stdErr) + throws IOException { + return new MavenCling(world).run(args, stdIn, stdOut, stdErr, true); } public MavenCling() { @@ -64,9 +79,12 @@ protected Invoker createInvoker() { } @Override - protected InvokerRequest parseArguments(String[] args) { - return new MavenParser() - .parseInvocation(ParserRequest.mvn(args, new JLineMessageBuilderFactory()) - .build()); + protected Parser createParser() { + return new MavenParser(); + } + + @Override + protected ParserRequest.Builder createParserRequestBuilder(String[] args) { + return ParserRequest.mvn(args, createMessageBuilderFactory()); } } diff --git a/impl/maven-cli/src/main/java/org/apache/maven/cling/MavenEncCling.java b/impl/maven-cli/src/main/java/org/apache/maven/cling/MavenEncCling.java index 620668f70760..66a00d43294a 100644 --- a/impl/maven-cli/src/main/java/org/apache/maven/cling/MavenEncCling.java +++ b/impl/maven-cli/src/main/java/org/apache/maven/cling/MavenEncCling.java @@ -19,14 +19,16 @@ package org.apache.maven.cling; import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import org.apache.maven.api.annotations.Nullable; import org.apache.maven.api.cli.Invoker; -import org.apache.maven.api.cli.InvokerRequest; +import org.apache.maven.api.cli.Parser; import org.apache.maven.api.cli.ParserRequest; import org.apache.maven.cling.invoker.ProtoLookup; import org.apache.maven.cling.invoker.mvnenc.EncryptInvoker; import org.apache.maven.cling.invoker.mvnenc.EncryptParser; -import org.apache.maven.jline.JLineMessageBuilderFactory; import org.codehaus.plexus.classworlds.ClassWorld; /** @@ -38,7 +40,7 @@ public class MavenEncCling extends ClingSupport { * circumstances. */ public static void main(String[] args) throws IOException { - int exitCode = new MavenEncCling().run(args); + int exitCode = new MavenEncCling().run(args, null, null, null, false); System.exit(exitCode); } @@ -46,7 +48,20 @@ public static void main(String[] args) throws IOException { * ClassWorld Launcher "enhanced" entry point: returning exitCode and accepts Class World. */ public static int main(String[] args, ClassWorld world) throws IOException { - return new MavenEncCling(world).run(args); + return new MavenEncCling().run(args, null, null, null, false); + } + + /** + * ClassWorld Launcher "embedded" entry point: returning exitCode and accepts Class World and streams. + */ + public static int main( + String[] args, + ClassWorld world, + @Nullable InputStream stdIn, + @Nullable OutputStream stdOut, + @Nullable OutputStream stdErr) + throws IOException { + return new MavenEncCling(world).run(args, stdIn, stdOut, stdErr, true); } public MavenEncCling() { @@ -64,9 +79,12 @@ protected Invoker createInvoker() { } @Override - protected InvokerRequest parseArguments(String[] args) { - return new EncryptParser() - .parseInvocation(ParserRequest.mvnenc(args, new JLineMessageBuilderFactory()) - .build()); + protected Parser createParser() { + return new EncryptParser(); + } + + @Override + protected ParserRequest.Builder createParserRequestBuilder(String[] args) { + return ParserRequest.mvnenc(args, createMessageBuilderFactory()); } } diff --git a/impl/maven-cli/src/main/java/org/apache/maven/cling/MavenShellCling.java b/impl/maven-cli/src/main/java/org/apache/maven/cling/MavenShellCling.java index 50ff43ee9505..403d91d86123 100644 --- a/impl/maven-cli/src/main/java/org/apache/maven/cling/MavenShellCling.java +++ b/impl/maven-cli/src/main/java/org/apache/maven/cling/MavenShellCling.java @@ -19,14 +19,16 @@ package org.apache.maven.cling; import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import org.apache.maven.api.annotations.Nullable; import org.apache.maven.api.cli.Invoker; -import org.apache.maven.api.cli.InvokerRequest; +import org.apache.maven.api.cli.Parser; import org.apache.maven.api.cli.ParserRequest; import org.apache.maven.cling.invoker.ProtoLookup; import org.apache.maven.cling.invoker.mvnsh.ShellInvoker; import org.apache.maven.cling.invoker.mvnsh.ShellParser; -import org.apache.maven.jline.JLineMessageBuilderFactory; import org.codehaus.plexus.classworlds.ClassWorld; /** @@ -38,7 +40,7 @@ public class MavenShellCling extends ClingSupport { * circumstances. */ public static void main(String[] args) throws IOException { - int exitCode = new MavenShellCling().run(args); + int exitCode = new MavenShellCling().run(args, null, null, null, false); System.exit(exitCode); } @@ -46,7 +48,20 @@ public static void main(String[] args) throws IOException { * ClassWorld Launcher "enhanced" entry point: returning exitCode and accepts Class World. */ public static int main(String[] args, ClassWorld world) throws IOException { - return new MavenShellCling(world).run(args); + return new MavenShellCling().run(args, null, null, null, false); + } + + /** + * ClassWorld Launcher "embedded" entry point: returning exitCode and accepts Class World and streams. + */ + public static int main( + String[] args, + ClassWorld world, + @Nullable InputStream stdIn, + @Nullable OutputStream stdOut, + @Nullable OutputStream stdErr) + throws IOException { + return new MavenShellCling(world).run(args, stdIn, stdOut, stdErr, true); } public MavenShellCling() { @@ -64,9 +79,12 @@ protected Invoker createInvoker() { } @Override - protected InvokerRequest parseArguments(String[] args) { - return new ShellParser() - .parseInvocation(ParserRequest.mvnsh(args, new JLineMessageBuilderFactory()) - .build()); + protected Parser createParser() { + return new ShellParser(); + } + + @Override + protected ParserRequest.Builder createParserRequestBuilder(String[] args) { + return ParserRequest.mvnsh(args, createMessageBuilderFactory()); } } diff --git a/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/BaseInvokerRequest.java b/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/BaseInvokerRequest.java index f2d8d519aed1..9cb49cb27d52 100644 --- a/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/BaseInvokerRequest.java +++ b/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/BaseInvokerRequest.java @@ -18,8 +18,6 @@ */ package org.apache.maven.cling.invoker; -import java.io.InputStream; -import java.io.OutputStream; import java.nio.file.Path; import java.util.List; import java.util.Map; @@ -44,9 +42,6 @@ public abstract class BaseInvokerRequest implements InvokerRequest { private final Path topDirectory; private final Path rootDirectory; private final List coreExtensions; - private final InputStream in; - private final OutputStream out; - private final OutputStream err; @SuppressWarnings("ParameterNumber") public BaseInvokerRequest( @@ -59,9 +54,6 @@ public BaseInvokerRequest( @Nonnull Map systemProperties, @Nonnull Path topDirectory, @Nullable Path rootDirectory, - @Nullable InputStream in, - @Nullable OutputStream out, - @Nullable OutputStream err, @Nullable List coreExtensions) { this.parserRequest = requireNonNull(parserRequest); this.parsingFailed = parsingFailed; @@ -74,10 +66,6 @@ public BaseInvokerRequest( this.topDirectory = requireNonNull(topDirectory); this.rootDirectory = rootDirectory; this.coreExtensions = coreExtensions; - - this.in = in; - this.out = out; - this.err = err; } @Override @@ -125,21 +113,6 @@ public Optional rootDirectory() { return Optional.ofNullable(rootDirectory); } - @Override - public Optional in() { - return Optional.ofNullable(in); - } - - @Override - public Optional out() { - return Optional.ofNullable(out); - } - - @Override - public Optional err() { - return Optional.ofNullable(err); - } - @Override public Optional> coreExtensions() { return Optional.ofNullable(coreExtensions); diff --git a/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/LookupInvoker.java b/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/LookupInvoker.java index 26a311f96a1e..0afbb1fbdd75 100644 --- a/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/LookupInvoker.java +++ b/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/LookupInvoker.java @@ -20,6 +20,8 @@ import java.io.FileNotFoundException; import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; import java.io.PrintWriter; import java.nio.file.Files; import java.nio.file.Path; @@ -136,10 +138,6 @@ public final int invoke(InvokerRequest invokerRequest) { } catch (Exception e) { // other exceptions (including InvokerException but sans Exit, see above): we need to inform user throw handleException(context, e); - } finally { - if (context.terminal != null) { - context.terminal.writer().flush(); - } } } finally { Thread.currentThread().setContextClassLoader(oldCL); @@ -167,19 +165,19 @@ protected int doInvoke(C context) throws Exception { } protected InvokerException.ExitException handleException(C context, Exception e) { - Logger logger = context.logger; - if (logger instanceof AccumulatingLogger) { - logger = new SystemLogger(); - } printErrors( context, context.invokerRequest.options().showErrors().orElse(false), List.of(new Logger.Entry(Logger.Level.ERROR, e.getMessage(), e.getCause())), - logger); + context.logger); return new InvokerException.ExitException(2); } protected void printErrors(C context, boolean showStackTrace, List entries, Logger logger) { + // if accumulating logger passed, this is "early failure", swap logger for stdErr and use that to emit log + if (logger instanceof AccumulatingLogger) { + logger = new SystemLogger(context.invokerRequest.stdErr().orElse(null)); + } // this is important message; many Maven IT assert for presence of this message logger.error("Error executing " + context.invokerRequest.parserRequest().commandName() + "."); for (Logger.Entry entry : entries) { @@ -209,7 +207,7 @@ protected void validate(C context) throws Exception { .args() .contains(CommonsCliOptions.CLIManager.SHOW_ERRORS_CLI_ARG), entries, - new SystemLogger()); + context.logger); // we skip handleException above as we did output throw new InvokerException.ExitException(1); } @@ -308,14 +306,16 @@ protected void createTerminal(C context) { if (context.terminal == null) { MessageUtils.systemInstall( builder -> { - builder.streams( - context.invokerRequest.in().orElse(null), - context.invokerRequest.out().orElse(null)); - builder.systemOutput(TerminalBuilder.SystemOutput.ForcedSysOut); - // The exec builder suffers from https://github.com/jline/jline3/issues/1098 - // We could re-enable it when fixed to provide support for non-standard architectures, - // for which JLine does not provide any native library. - builder.exec(false); + if (context.invokerRequest.embedded()) { + InputStream in = context.invokerRequest.stdIn().orElse(InputStream.nullInputStream()); + OutputStream out = context.invokerRequest.stdOut().orElse(OutputStream.nullOutputStream()); + builder.streams(in, out); + builder.provider("exec"); + context.coloredOutput = context.coloredOutput != null ? context.coloredOutput : false; + context.closeables.add(out::flush); + } else { + builder.systemOutput(TerminalBuilder.SystemOutput.ForcedSysOut); + } if (context.coloredOutput != null) { builder.color(context.coloredOutput); } @@ -323,24 +323,25 @@ protected void createTerminal(C context) { terminal -> doConfigureWithTerminal(context, terminal)); context.terminal = MessageUtils.getTerminal(); - // JLine is quite slow to start due to the native library unpacking and loading - // so boot it asynchronously context.closeables.add(MessageUtils::systemUninstall); MessageUtils.registerShutdownHook(); // safety belt - if (context.coloredOutput != null) { - MessageUtils.setColorEnabled(context.coloredOutput); - } } else { - if (context.coloredOutput != null) { - MessageUtils.setColorEnabled(context.coloredOutput); - } + doConfigureWithTerminal(context, context.terminal); } } protected void doConfigureWithTerminal(C context, Terminal terminal) { context.terminal = terminal; Options options = context.invokerRequest.options(); - if (options.rawStreams().isEmpty() || !options.rawStreams().get()) { + // tricky thing: align what JLine3 detected and Maven thinks: + // if embedded, we default to context.coloredOutput=false unless overridden (see above) + // if not embedded, JLine3 may detect redirection and will create dumb terminal. + // To align Maven with outcomes, we set here color enabled based on these premises. + // Note: Maven3 suffers from similar thing: if you do `mvn3 foo > log.txt`, the output will + // not be not colored (good), but Maven will print out "Message scheme: color". + MessageUtils.setColorEnabled( + context.coloredOutput != null ? context.coloredOutput : !Terminal.TYPE_DUMB.equals(terminal.getType())); + if (!options.rawStreams().orElse(false)) { MavenSimpleLogger stdout = (MavenSimpleLogger) context.loggerFactory.getLogger("stdout"); MavenSimpleLogger stderr = (MavenSimpleLogger) context.loggerFactory.getLogger("stderr"); stdout.setLogLevel(LocationAwareLogger.INFO_INT); diff --git a/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/logging/SystemLogger.java b/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/logging/SystemLogger.java index 5332748af156..6b3752c62823 100644 --- a/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/logging/SystemLogger.java +++ b/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/logging/SystemLogger.java @@ -36,8 +36,8 @@ public class SystemLogger implements Logger { private final PrintWriter out; private final Level threshold; - public SystemLogger() { - this(null, null); + public SystemLogger(@Nullable OutputStream out) { + this(out, null); } public SystemLogger(@Nullable OutputStream out, @Nullable Level threshold) { diff --git a/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvn/MavenInvokerRequest.java b/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvn/MavenInvokerRequest.java index 2133918f9a19..efd5e9ececb8 100644 --- a/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvn/MavenInvokerRequest.java +++ b/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvn/MavenInvokerRequest.java @@ -18,8 +18,6 @@ */ package org.apache.maven.cling.invoker.mvn; -import java.io.InputStream; -import java.io.OutputStream; import java.nio.file.Path; import java.util.List; import java.util.Map; @@ -49,9 +47,6 @@ public MavenInvokerRequest( Map systemProperties, Path topDirectory, Path rootDirectory, - InputStream in, - OutputStream out, - OutputStream err, List coreExtensions, MavenOptions options) { super( @@ -64,9 +59,6 @@ public MavenInvokerRequest( systemProperties, topDirectory, rootDirectory, - in, - out, - err, coreExtensions); this.options = requireNonNull(options); } diff --git a/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvn/MavenParser.java b/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvn/MavenParser.java index 168b2bd7cfe5..7de8e0d51889 100644 --- a/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvn/MavenParser.java +++ b/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvn/MavenParser.java @@ -99,9 +99,6 @@ protected MavenInvokerRequest getInvokerRequest(LocalContext context) { context.systemProperties, context.topDirectory, context.rootDirectory, - context.parserRequest.in(), - context.parserRequest.out(), - context.parserRequest.err(), context.extensions, (MavenOptions) context.options); } diff --git a/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvnenc/EncryptInvokerRequest.java b/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvnenc/EncryptInvokerRequest.java index fd7e1e6ccd65..2b2900b5987e 100644 --- a/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvnenc/EncryptInvokerRequest.java +++ b/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvnenc/EncryptInvokerRequest.java @@ -18,8 +18,6 @@ */ package org.apache.maven.cling.invoker.mvnenc; -import java.io.InputStream; -import java.io.OutputStream; import java.nio.file.Path; import java.util.List; import java.util.Map; @@ -46,9 +44,6 @@ public EncryptInvokerRequest( Map systemProperties, Path topDirectory, Path rootDirectory, - InputStream in, - OutputStream out, - OutputStream err, List coreExtensions, EncryptOptions options) { super( @@ -61,9 +56,6 @@ public EncryptInvokerRequest( systemProperties, topDirectory, rootDirectory, - in, - out, - err, coreExtensions); this.options = requireNonNull(options); } diff --git a/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvnenc/EncryptParser.java b/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvnenc/EncryptParser.java index a9c7f8f42f73..9a1da7ddbdc7 100644 --- a/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvnenc/EncryptParser.java +++ b/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvnenc/EncryptParser.java @@ -49,9 +49,6 @@ protected EncryptInvokerRequest getInvokerRequest(LocalContext context) { context.systemProperties, context.topDirectory, context.rootDirectory, - context.parserRequest.in(), - context.parserRequest.out(), - context.parserRequest.err(), context.extensions, (EncryptOptions) context.options); } diff --git a/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvnsh/ShellInvokerRequest.java b/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvnsh/ShellInvokerRequest.java index c15eb836a053..9268b9ed30fd 100644 --- a/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvnsh/ShellInvokerRequest.java +++ b/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvnsh/ShellInvokerRequest.java @@ -18,8 +18,6 @@ */ package org.apache.maven.cling.invoker.mvnsh; -import java.io.InputStream; -import java.io.OutputStream; import java.nio.file.Path; import java.util.List; import java.util.Map; @@ -46,9 +44,6 @@ public ShellInvokerRequest( Map systemProperties, Path topDirectory, Path rootDirectory, - InputStream in, - OutputStream out, - OutputStream err, List coreExtensions, ShellOptions options) { super( @@ -61,9 +56,6 @@ public ShellInvokerRequest( systemProperties, topDirectory, rootDirectory, - in, - out, - err, coreExtensions); this.options = requireNonNull(options); } diff --git a/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvnsh/ShellParser.java b/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvnsh/ShellParser.java index aa400573c081..5c91146f1fcf 100644 --- a/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvnsh/ShellParser.java +++ b/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvnsh/ShellParser.java @@ -48,9 +48,6 @@ protected ShellInvokerRequest getInvokerRequest(LocalContext context) { context.systemProperties, context.topDirectory, context.rootDirectory, - context.parserRequest.in(), - context.parserRequest.out(), - context.parserRequest.err(), context.extensions, (ShellOptions) context.options); } diff --git a/impl/maven-cli/src/test/java/org/apache/maven/cling/invoker/mvn/MavenInvokerTest.java b/impl/maven-cli/src/test/java/org/apache/maven/cling/invoker/mvn/MavenInvokerTest.java index b72c42b3616f..7e33362d827c 100644 --- a/impl/maven-cli/src/test/java/org/apache/maven/cling/invoker/mvn/MavenInvokerTest.java +++ b/impl/maven-cli/src/test/java/org/apache/maven/cling/invoker/mvn/MavenInvokerTest.java @@ -21,7 +21,6 @@ import java.nio.file.FileSystem; import java.nio.file.Files; import java.nio.file.Path; -import java.util.Arrays; import java.util.List; import java.util.Map; @@ -45,6 +44,7 @@ /** * Local UT. */ +@Disabled @Order(200) public class MavenInvokerTest extends MavenInvokerTestSupport { @Override @@ -64,7 +64,7 @@ void defaultFs( @TempDir(cleanup = CleanupMode.ON_SUCCESS) Path cwd, @TempDir(cleanup = CleanupMode.ON_SUCCESS) Path userHome) throws Exception { - invoke(cwd, userHome, Arrays.asList("clean", "verify")); + invoke(cwd, userHome, List.of("verify"), List.of()); } @Test @@ -93,7 +93,7 @@ void conflictingExtensions( Path userExtensions = userConf.resolve("extensions.xml"); Files.writeString(userExtensions, extensionsXml); - assertThrows(InvokerException.class, () -> invoke(cwd, userHome, Arrays.asList("clean", "verify"))); + assertThrows(InvokerException.class, () -> invoke(cwd, userHome, List.of("validate"), List.of())); } @Test @@ -106,6 +106,7 @@ void conflictingSettings( + false oss-development @@ -151,9 +152,14 @@ void conflictingSettings( Path userExtensions = userConf.resolve("settings.xml"); Files.writeString(userExtensions, settingsXml); - Map logs = invoke(cwd, userHome, List.of("verify"), List.of("--force-interactive")); + // we just execute a Mojo for downloading it only and to assert from which URL it came + Map logs = invoke( + cwd, + userHome, + List.of("eu.maveniverse.maven.plugins:toolbox:0.6.1:help"), + List.of("--force-interactive")); - String log = logs.get("verify"); + String log = logs.get("eu.maveniverse.maven.plugins:toolbox:0.6.1:help"); assertTrue(log.contains("https://repo1.maven.org/maven2"), log); assertFalse(log.contains("https://repo.maven.apache.org/maven2"), log); } @@ -162,7 +168,7 @@ void conflictingSettings( @Test void jimFs() throws Exception { try (FileSystem fs = Jimfs.newFileSystem(Configuration.unix())) { - invoke(fs.getPath("/cwd"), fs.getPath("/home"), Arrays.asList("clean", "verify")); + invoke(fs.getPath("/cwd"), fs.getPath("/home"), List.of("verify"), List.of()); } } } diff --git a/impl/maven-cli/src/test/java/org/apache/maven/cling/invoker/mvn/MavenInvokerTestSupport.java b/impl/maven-cli/src/test/java/org/apache/maven/cling/invoker/mvn/MavenInvokerTestSupport.java index 73bdc841efdd..7f2db7c3ee9c 100644 --- a/impl/maven-cli/src/test/java/org/apache/maven/cling/invoker/mvn/MavenInvokerTestSupport.java +++ b/impl/maven-cli/src/test/java/org/apache/maven/cling/invoker/mvn/MavenInvokerTestSupport.java @@ -18,9 +18,11 @@ */ package org.apache.maven.cling.invoker.mvn; +import java.io.ByteArrayOutputStream; import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; import java.util.HashMap; import java.util.List; @@ -34,6 +36,12 @@ import static org.junit.jupiter.api.Assertions.assertEquals; public abstract class MavenInvokerTestSupport { + static { + System.setProperty( + "library.jline.path", + Path.of("target/dependency/org/jline/nativ").toAbsolutePath().toString()); + } + public static final String POM_STRING = """ @@ -80,10 +88,6 @@ public static void main(String... args) { } """; - protected void invoke(Path cwd, Path userHome, Collection goals) throws Exception { - invoke(cwd, userHome, goals, List.of()); - } - protected Map invoke(Path cwd, Path userHome, Collection goals, Collection args) throws Exception { Files.createDirectories(cwd.resolve(".mvn")); @@ -97,17 +101,34 @@ protected Map invoke(Path cwd, Path userHome, Collection Parser parser = createParser(); try (Invoker invoker = createInvoker()) { for (String goal : goals) { - Path logFile = cwd.resolve(goal + "-build.log").toAbsolutePath(); + ByteArrayOutputStream stdout = new ByteArrayOutputStream(); + ByteArrayOutputStream stderr = new ByteArrayOutputStream(); List mvnArgs = new ArrayList<>(args); - mvnArgs.addAll(List.of("-l", logFile.toString(), goal)); + mvnArgs.add(goal); int exitCode = invoker.invoke( parser.parseInvocation(ParserRequest.mvn(mvnArgs, new JLineMessageBuilderFactory()) .cwd(cwd) .userHome(userHome) + .stdOut(stdout) + .stdErr(stderr) + .embedded(true) .build())); - String log = Files.readString(logFile); - logs.put(goal, log); - assertEquals(0, exitCode, log); + + // dump things out + System.out.println("==================================================="); + System.out.println("args: " + Arrays.toString(mvnArgs.toArray())); + System.out.println("==================================================="); + System.out.println("stdout: " + stdout); + System.out.println("==================================================="); + + System.err.println("==================================================="); + System.err.println("args: " + Arrays.toString(mvnArgs.toArray())); + System.err.println("==================================================="); + System.err.println("stderr: " + stderr); + System.err.println("==================================================="); + + logs.put(goal, stdout.toString()); + assertEquals(0, exitCode, "OUT:" + stdout + "\nERR:" + stderr); } } return logs; diff --git a/impl/maven-cli/src/test/java/org/apache/maven/cling/invoker/mvn/resident/ResidentMavenInvokerTest.java b/impl/maven-cli/src/test/java/org/apache/maven/cling/invoker/mvn/resident/ResidentMavenInvokerTest.java index a9d017c96cec..150bcafe09f4 100644 --- a/impl/maven-cli/src/test/java/org/apache/maven/cling/invoker/mvn/resident/ResidentMavenInvokerTest.java +++ b/impl/maven-cli/src/test/java/org/apache/maven/cling/invoker/mvn/resident/ResidentMavenInvokerTest.java @@ -20,7 +20,7 @@ import java.nio.file.FileSystem; import java.nio.file.Path; -import java.util.Arrays; +import java.util.List; import com.google.common.jimfs.Configuration; import com.google.common.jimfs.Jimfs; @@ -39,6 +39,7 @@ /** * Resident UT. */ +@Disabled @Order(100) public class ResidentMavenInvokerTest extends MavenInvokerTestSupport { @@ -59,14 +60,14 @@ void defaultFs( @TempDir(cleanup = CleanupMode.ON_SUCCESS) Path cwd, @TempDir(cleanup = CleanupMode.ON_SUCCESS) Path userHome) throws Exception { - invoke(cwd, userHome, Arrays.asList("clean", "verify")); + invoke(cwd, userHome, List.of("verify"), List.of()); } @Disabled("Until we move off fully from File") @Test void jimFs() throws Exception { try (FileSystem fs = Jimfs.newFileSystem(Configuration.unix())) { - invoke(fs.getPath("/cwd"), fs.getPath("/home"), Arrays.asList("clean", "verify")); + invoke(fs.getPath("/cwd"), fs.getPath("/home"), List.of("verify"), List.of()); } } } diff --git a/impl/maven-executor/src/main/java/org/apache/maven/api/cli/ExecutorRequest.java b/impl/maven-executor/src/main/java/org/apache/maven/api/cli/ExecutorRequest.java index 494cb06e1bd1..70e76da74aad 100644 --- a/impl/maven-executor/src/main/java/org/apache/maven/api/cli/ExecutorRequest.java +++ b/impl/maven-executor/src/main/java/org/apache/maven/api/cli/ExecutorRequest.java @@ -19,6 +19,7 @@ package org.apache.maven.api.cli; import java.io.IOException; +import java.io.InputStream; import java.io.OutputStream; import java.nio.file.Path; import java.nio.file.Paths; @@ -117,6 +118,14 @@ public interface ExecutorRequest { @Nonnull Optional> jvmArguments(); + /** + * Optional provider for STD in of the Maven. If given, this provider will be piped into std input of + * Maven. + * + * @return an Optional containing the stdin provider, or empty if not specified. + */ + Optional stdIn(); + /** * Optional consumer for STD out of the Maven. If given, this consumer will get all output from the std out of * Maven. Note: whether consumer gets to consume anything depends on invocation arguments passed in @@ -124,7 +133,7 @@ public interface ExecutorRequest { * * @return an Optional containing the stdout consumer, or empty if not specified. */ - Optional stdoutConsumer(); + Optional stdOut(); /** * Optional consumer for STD err of the Maven. If given, this consumer will get all output from the std err of @@ -133,7 +142,7 @@ public interface ExecutorRequest { * * @return an Optional containing the stderr consumer, or empty if not specified. */ - Optional stderrConsumer(); + Optional stdErr(); /** * Returns {@link Builder} created from this instance. @@ -149,8 +158,9 @@ default Builder toBuilder() { jvmSystemProperties().orElse(null), environmentVariables().orElse(null), jvmArguments().orElse(null), - stdoutConsumer().orElse(null), - stderrConsumer().orElse(null)); + stdIn().orElse(null), + stdOut().orElse(null), + stdErr().orElse(null)); } /** @@ -171,6 +181,7 @@ static Builder mavenBuilder(@Nullable Path installationDirectory) { null, null, null, + null, null); } @@ -183,8 +194,9 @@ class Builder { private Map jvmSystemProperties; private Map environmentVariables; private List jvmArguments; - private OutputStream stdoutConsumer; - private OutputStream stderrConsumer; + private InputStream stdIn; + private OutputStream stdOut; + private OutputStream stdErr; private Builder() {} @@ -198,8 +210,9 @@ private Builder( Map jvmSystemProperties, Map environmentVariables, List jvmArguments, - OutputStream stdoutConsumer, - OutputStream stderrConsumer) { + InputStream stdIn, + OutputStream stdOut, + OutputStream stdErr) { this.command = command; this.arguments = arguments; this.cwd = cwd; @@ -208,8 +221,9 @@ private Builder( this.jvmSystemProperties = jvmSystemProperties; this.environmentVariables = environmentVariables; this.jvmArguments = jvmArguments; - this.stdoutConsumer = stdoutConsumer; - this.stderrConsumer = stderrConsumer; + this.stdIn = stdIn; + this.stdOut = stdOut; + this.stdErr = stdErr; } @Nonnull @@ -302,14 +316,20 @@ public Builder jvmArgument(String jvmArgument) { } @Nonnull - public Builder stdoutConsumer(OutputStream stdoutConsumer) { - this.stdoutConsumer = stdoutConsumer; + public Builder stdIn(InputStream stdIn) { + this.stdIn = stdIn; + return this; + } + + @Nonnull + public Builder stdOut(OutputStream stdOut) { + this.stdOut = stdOut; return this; } @Nonnull - public Builder stderrConsumer(OutputStream stderrConsumer) { - this.stderrConsumer = stderrConsumer; + public Builder stdErr(OutputStream stdErr) { + this.stdErr = stdErr; return this; } @@ -324,8 +344,9 @@ public ExecutorRequest build() { jvmSystemProperties, environmentVariables, jvmArguments, - stdoutConsumer, - stderrConsumer); + stdIn, + stdOut, + stdErr); } private static class Impl implements ExecutorRequest { @@ -337,8 +358,9 @@ private static class Impl implements ExecutorRequest { private final Map jvmSystemProperties; private final Map environmentVariables; private final List jvmArguments; - private final OutputStream stdoutConsumer; - private final OutputStream stderrConsumer; + private final InputStream stdIn; + private final OutputStream stdOut; + private final OutputStream stdErr; @SuppressWarnings("ParameterNumber") private Impl( @@ -350,8 +372,9 @@ private Impl( Map jvmSystemProperties, Map environmentVariables, List jvmArguments, - OutputStream stdoutConsumer, - OutputStream stderrConsumer) { + InputStream stdIn, + OutputStream stdOut, + OutputStream stdErr) { this.command = requireNonNull(command); this.arguments = arguments == null ? List.of() : List.copyOf(arguments); this.cwd = getCanonicalPath(requireNonNull(cwd)); @@ -360,8 +383,9 @@ private Impl( this.jvmSystemProperties = jvmSystemProperties != null ? Map.copyOf(jvmSystemProperties) : null; this.environmentVariables = environmentVariables != null ? Map.copyOf(environmentVariables) : null; this.jvmArguments = jvmArguments != null ? List.copyOf(jvmArguments) : null; - this.stdoutConsumer = stdoutConsumer; - this.stderrConsumer = stderrConsumer; + this.stdIn = stdIn; + this.stdOut = stdOut; + this.stdErr = stdErr; } @Override @@ -405,18 +429,23 @@ public Optional> jvmArguments() { } @Override - public Optional stdoutConsumer() { - return Optional.ofNullable(stdoutConsumer); + public Optional stdIn() { + return Optional.ofNullable(stdIn); + } + + @Override + public Optional stdOut() { + return Optional.ofNullable(stdOut); } @Override - public Optional stderrConsumer() { - return Optional.ofNullable(stderrConsumer); + public Optional stdErr() { + return Optional.ofNullable(stdErr); } @Override public String toString() { - return "Impl{" + "command='" + return getClass().getSimpleName() + "{" + "command='" + command + '\'' + ", arguments=" + arguments + ", cwd=" + cwd + ", installationDirectory=" @@ -424,9 +453,10 @@ public String toString() { + userHomeDirectory + ", jvmSystemProperties=" + jvmSystemProperties + ", environmentVariables=" + environmentVariables + ", jvmArguments=" - + jvmArguments + ", stdoutConsumer=" - + stdoutConsumer + ", stderrConsumer=" - + stderrConsumer + '}'; + + jvmArguments + ", stdinProvider=" + + stdIn + ", stdoutConsumer=" + + stdOut + ", stderrConsumer=" + + stdErr + '}'; } } } diff --git a/impl/maven-executor/src/main/java/org/apache/maven/cling/executor/embedded/EmbeddedMavenExecutor.java b/impl/maven-executor/src/main/java/org/apache/maven/cling/executor/embedded/EmbeddedMavenExecutor.java index 76928b124717..1ed38e4fa239 100644 --- a/impl/maven-executor/src/main/java/org/apache/maven/cling/executor/embedded/EmbeddedMavenExecutor.java +++ b/impl/maven-executor/src/main/java/org/apache/maven/cling/executor/embedded/EmbeddedMavenExecutor.java @@ -56,8 +56,13 @@ * long as instance of this class is not closed. Subsequent execution requests over same installation home are cached. */ public class EmbeddedMavenExecutor implements Executor { - protected static final Map MAIN_CLASSES = - Map.of("mvn", "org.apache.maven.cling.MavenCling", "mvnenc", "org.apache.maven.cling.MavenEncCling"); + protected static final Map MAIN_CLASSES = Map.of( + "mvn", + "org.apache.maven.cling.MavenCling", + "mvnenc", + "org.apache.maven.cling.MavenEncCling", + "mvnsh", + "org.apache.maven.cling.MavenShellCling"); protected static final class Context { private final URLClassLoader bootClassLoader; @@ -144,11 +149,11 @@ public int execute(ExecutorRequest executorRequest) throws ExecutorException { Thread.currentThread().setContextClassLoader(context.tccl); try { - if (executorRequest.stdoutConsumer().isPresent()) { - System.setOut(new PrintStream(executorRequest.stdoutConsumer().get(), true)); + if (executorRequest.stdOut().isPresent()) { + System.setOut(new PrintStream(executorRequest.stdOut().get(), true)); } - if (executorRequest.stderrConsumer().isPresent()) { - System.setErr(new PrintStream(executorRequest.stderrConsumer().get(), true)); + if (executorRequest.stdErr().isPresent()) { + System.setErr(new PrintStream(executorRequest.stdErr().get(), true)); } return context.exec.apply(executorRequest); } catch (Exception e) { @@ -235,7 +240,6 @@ protected Context doCreate(Path mavenHome, ExecutorRequest executorRequest) { } Properties properties = prepareProperties(executorRequest); - System.setProperties(properties); URLClassLoader bootClassLoader = createMavenBootClassLoader(boot, Collections.emptyList()); Thread.currentThread().setContextClassLoader(bootClassLoader); @@ -294,16 +298,14 @@ protected Context doCreate(Path mavenHome, ExecutorRequest executorRequest) { System.setProperties(prepareProperties(r)); try { try { - if (r.stdoutConsumer().isPresent() - || r.stderrConsumer().isPresent()) { + if (r.stdOut().isPresent() || r.stdErr().isPresent()) { ansiConsoleInstalled.set(null, 1); } ArrayList args = new ArrayList<>(mavenArgs); args.addAll(r.arguments()); return (int) mainMethod.invoke(null, args.toArray(new String[0]), classWorld); } finally { - if (r.stdoutConsumer().isPresent() - || r.stderrConsumer().isPresent()) { + if (r.stdOut().isPresent() || r.stdErr().isPresent()) { ansiConsoleInstalled.set(null, 0); } } @@ -336,12 +338,17 @@ protected Properties prepareProperties(ExecutorRequest request) { properties.setProperty("maven.home", mavenHome.toString()); properties.setProperty( "maven.multiModuleProjectDirectory", request.cwd().toString()); - String mainClass = requireNonNull(MAIN_CLASSES.get(request.command()), "mainClass"); - properties.setProperty("maven.mainClass", mainClass); + + // Maven 3.x + properties.setProperty( + "library.jansi.path", mavenHome.resolve("lib/jansi-native").toString()); + + // Maven 4.x properties.setProperty( "library.jline.path", mavenHome.resolve("lib/jline-native").toString()); // TODO: is this needed? properties.setProperty("org.jline.terminal.provider", "dumb"); + properties.setProperty("maven.mainClass", requireNonNull(MAIN_CLASSES.get(request.command()), "mainClass")); if (request.jvmSystemProperties().isPresent()) { properties.putAll(request.jvmSystemProperties().get()); diff --git a/impl/maven-executor/src/main/java/org/apache/maven/cling/executor/forked/ForkedMavenExecutor.java b/impl/maven-executor/src/main/java/org/apache/maven/cling/executor/forked/ForkedMavenExecutor.java index 74e7983c531f..095f07211cfd 100644 --- a/impl/maven-executor/src/main/java/org/apache/maven/cling/executor/forked/ForkedMavenExecutor.java +++ b/impl/maven-executor/src/main/java/org/apache/maven/cling/executor/forked/ForkedMavenExecutor.java @@ -20,16 +20,18 @@ import java.io.ByteArrayOutputStream; import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; import java.io.UncheckedIOException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.ArrayList; +import java.util.Arrays; import java.util.HashMap; import java.util.List; -import java.util.function.Consumer; +import java.util.concurrent.CountDownLatch; -import org.apache.maven.api.annotations.Nullable; import org.apache.maven.api.cli.Executor; import org.apache.maven.api.cli.ExecutorException; import org.apache.maven.api.cli.ExecutorRequest; @@ -42,12 +44,22 @@ * but provides the best isolation. */ public class ForkedMavenExecutor implements Executor { + protected final boolean useMavenArgsEnv; + + public ForkedMavenExecutor() { + this(true); + } + + public ForkedMavenExecutor(boolean useMavenArgsEnv) { + this.useMavenArgsEnv = useMavenArgsEnv; + } + @Override public int execute(ExecutorRequest executorRequest) throws ExecutorException { requireNonNull(executorRequest); validate(executorRequest); - return doExecute(executorRequest, wrapStdouterrConsumer(executorRequest)); + return doExecute(executorRequest); } @Override @@ -61,7 +73,7 @@ public String mavenVersion(ExecutorRequest executorRequest) throws ExecutorExcep int exitCode = execute(executorRequest.toBuilder() .cwd(cwd) .arguments(List.of("--version", "--quiet")) - .stdoutConsumer(stdout) + .stdOut(stdout) .build()); if (exitCode == 0) { if (stdout.size() > 0) { @@ -85,31 +97,7 @@ public String mavenVersion(ExecutorRequest executorRequest) throws ExecutorExcep protected void validate(ExecutorRequest executorRequest) throws ExecutorException {} - @Nullable - protected Consumer wrapStdouterrConsumer(ExecutorRequest executorRequest) { - if (executorRequest.stdoutConsumer().isEmpty() - && executorRequest.stderrConsumer().isEmpty()) { - return null; - } else { - return p -> { - try { - if (executorRequest.stdoutConsumer().isPresent()) { - p.getInputStream() - .transferTo(executorRequest.stdoutConsumer().get()); - } - if (executorRequest.stderrConsumer().isPresent()) { - p.getErrorStream() - .transferTo(executorRequest.stderrConsumer().get()); - } - } catch (IOException e) { - throw new UncheckedIOException(e); - } - }; - } - } - - protected int doExecute(ExecutorRequest executorRequest, Consumer processConsumer) - throws ExecutorException { + protected int doExecute(ExecutorRequest executorRequest) throws ExecutorException { ArrayList cmdAndArguments = new ArrayList<>(); cmdAndArguments.add(executorRequest .installationDirectory() @@ -117,6 +105,13 @@ protected int doExecute(ExecutorRequest executorRequest, Consumer proce .resolve(IS_WINDOWS ? executorRequest.command() + ".cmd" : executorRequest.command()) .toString()); + String mavenArgsEnv = System.getenv("MAVEN_ARGS"); + if (useMavenArgsEnv && mavenArgsEnv != null && !mavenArgsEnv.isEmpty()) { + Arrays.stream(mavenArgsEnv.split(" ")) + .filter(s -> !s.trim().isEmpty()) + .forEach(cmdAndArguments::add); + } + cmdAndArguments.addAll(executorRequest.arguments()); ArrayList jvmArgs = new ArrayList<>(); @@ -144,6 +139,7 @@ protected int doExecute(ExecutorRequest executorRequest, Consumer proce mavenOpts += String.join(" ", jvmArgs); env.put("MAVEN_OPTS", mavenOpts); } + env.remove("MAVEN_ARGS"); // we already used it if configured to do so try { ProcessBuilder pb = new ProcessBuilder() @@ -154,9 +150,7 @@ protected int doExecute(ExecutorRequest executorRequest, Consumer proce } Process process = pb.start(); - if (processConsumer != null) { - processConsumer.accept(process); - } + pump(process, executorRequest).await(); return process.waitFor(); } catch (IOException e) { throw new ExecutorException("IO problem while executing command: " + cmdAndArguments, e); @@ -164,4 +158,49 @@ protected int doExecute(ExecutorRequest executorRequest, Consumer proce throw new ExecutorException("Interrupted while executing command: " + cmdAndArguments, e); } } + + protected CountDownLatch pump(Process p, ExecutorRequest executorRequest) { + CountDownLatch latch = new CountDownLatch(3); + String suffix = "-pump-" + p.pid(); + Thread stdoutPump = new Thread(() -> { + try { + OutputStream stdout = executorRequest.stdOut().orElse(OutputStream.nullOutputStream()); + p.getInputStream().transferTo(stdout); + stdout.flush(); + } catch (IOException e) { + throw new UncheckedIOException(e); + } finally { + latch.countDown(); + } + }); + stdoutPump.setName("stdout" + suffix); + stdoutPump.start(); + Thread stderrPump = new Thread(() -> { + try { + OutputStream stderr = executorRequest.stdErr().orElse(OutputStream.nullOutputStream()); + p.getErrorStream().transferTo(stderr); + stderr.flush(); + } catch (IOException e) { + throw new UncheckedIOException(e); + } finally { + latch.countDown(); + } + }); + stderrPump.setName("stderr" + suffix); + stderrPump.start(); + Thread stdinPump = new Thread(() -> { + try { + OutputStream in = p.getOutputStream(); + executorRequest.stdIn().orElse(InputStream.nullInputStream()).transferTo(in); + in.flush(); + } catch (IOException e) { + throw new UncheckedIOException(e); + } finally { + latch.countDown(); + } + }); + stdinPump.setName("stdin" + suffix); + stdinPump.start(); + return latch; + } } diff --git a/impl/maven-executor/src/main/java/org/apache/maven/cling/executor/internal/ToolboxTool.java b/impl/maven-executor/src/main/java/org/apache/maven/cling/executor/internal/ToolboxTool.java index 2f4ef540a325..af9ff88b5619 100644 --- a/impl/maven-executor/src/main/java/org/apache/maven/cling/executor/internal/ToolboxTool.java +++ b/impl/maven-executor/src/main/java/org/apache/maven/cling/executor/internal/ToolboxTool.java @@ -54,8 +54,8 @@ public Map dump(ExecutorRequest.Builder executorRequest) throws ByteArrayOutputStream stderr = new ByteArrayOutputStream(); ExecutorRequest.Builder builder = mojo(executorRequest, "gav-dump") .argument("-DasProperties") - .stdoutConsumer(stdout) - .stderrConsumer(stderr); + .stdOut(stdout) + .stdErr(stderr); doExecute(builder); try { Properties properties = new Properties(); @@ -76,8 +76,8 @@ public String localRepository(ExecutorRequest.Builder executorRequest) throws Ex ByteArrayOutputStream stdout = new ByteArrayOutputStream(); ByteArrayOutputStream stderr = new ByteArrayOutputStream(); ExecutorRequest.Builder builder = mojo(executorRequest, "gav-local-repository-path") - .stdoutConsumer(stdout) - .stderrConsumer(stderr); + .stdOut(stdout) + .stdErr(stderr); doExecute(builder); return shaveStdout(stdout); } @@ -89,8 +89,8 @@ public String artifactPath(ExecutorRequest.Builder executorRequest, String gav, ByteArrayOutputStream stderr = new ByteArrayOutputStream(); ExecutorRequest.Builder builder = mojo(executorRequest, "gav-artifact-path") .argument("-Dgav=" + gav) - .stdoutConsumer(stdout) - .stderrConsumer(stderr); + .stdOut(stdout) + .stdErr(stderr); if (repositoryId != null) { builder.argument("-Drepository=" + repositoryId + "::unimportant"); } @@ -105,8 +105,8 @@ public String metadataPath(ExecutorRequest.Builder executorRequest, String gav, ByteArrayOutputStream stderr = new ByteArrayOutputStream(); ExecutorRequest.Builder builder = mojo(executorRequest, "gav-metadata-path") .argument("-Dgav=" + gav) - .stdoutConsumer(stdout) - .stderrConsumer(stderr); + .stdOut(stdout) + .stdErr(stderr); if (repositoryId != null) { builder.argument("-Drepository=" + repositoryId + "::unimportant"); } @@ -126,8 +126,8 @@ private void doExecute(ExecutorRequest.Builder builder) { int ec = helper.execute(request); if (ec != 0) { throw new ExecutorException("Unexpected exit code=" + ec + "; stdout=" - + request.stdoutConsumer().orElse(null) + "; stderr=" - + request.stderrConsumer().orElse(null)); + + request.stdOut().orElse(null) + "; stderr=" + + request.stdErr().orElse(null)); } } diff --git a/impl/maven-executor/src/test/java/org/apache/maven/cling/executor/MavenExecutorTestSupport.java b/impl/maven-executor/src/test/java/org/apache/maven/cling/executor/MavenExecutorTestSupport.java index 42c6171a210b..94e58deb7f5a 100644 --- a/impl/maven-executor/src/test/java/org/apache/maven/cling/executor/MavenExecutorTestSupport.java +++ b/impl/maven-executor/src/test/java/org/apache/maven/cling/executor/MavenExecutorTestSupport.java @@ -25,16 +25,19 @@ import java.util.Collection; import java.util.List; +import org.apache.maven.api.annotations.Nullable; import org.apache.maven.api.cli.Executor; import org.apache.maven.api.cli.ExecutorRequest; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.Timeout; import org.junit.jupiter.api.io.CleanupMode; import org.junit.jupiter.api.io.TempDir; import static org.junit.jupiter.api.Assertions.assertEquals; public abstract class MavenExecutorTestSupport { + @Timeout(15) @Test void mvnenc( @TempDir(cleanup = CleanupMode.ON_SUCCESS) Path cwd, @@ -55,6 +58,7 @@ void mvnenc( } @Disabled("JUnit on Windows fails to clean up as mvn3 seems does not close log file properly") + @Timeout(15) @Test void dump3( @TempDir(cleanup = CleanupMode.ON_SUCCESS) Path cwd, @@ -73,6 +77,7 @@ void dump3( System.out.println(Files.readString(cwd.resolve(logfile))); } + @Timeout(15) @Test void dump4( @TempDir(cleanup = CleanupMode.ON_SUCCESS) Path cwd, @@ -91,6 +96,7 @@ void dump4( System.out.println(Files.readString(cwd.resolve(logfile))); } + @Timeout(15) @Test void defaultFs(@TempDir(cleanup = CleanupMode.ON_SUCCESS) Path tempDir) throws Exception { layDownFiles(tempDir); @@ -99,12 +105,14 @@ void defaultFs(@TempDir(cleanup = CleanupMode.ON_SUCCESS) Path tempDir) throws E tempDir.resolve(logfile), List.of(mvn4ExecutorRequestBuilder() .cwd(tempDir) + .argument("-V") .argument("verify") .argument("-l") .argument(logfile) .build())); } + @Timeout(15) @Test void version() throws Exception { assertEquals( @@ -113,6 +121,7 @@ void version() throws Exception { } @Disabled("JUnit on Windows fails to clean up as mvn3 seems does not close log file properly") + @Timeout(15) @Test void defaultFs3x(@TempDir(cleanup = CleanupMode.ON_SUCCESS) Path tempDir) throws Exception { layDownFiles(tempDir); @@ -121,12 +130,14 @@ void defaultFs3x(@TempDir(cleanup = CleanupMode.ON_SUCCESS) Path tempDir) throws tempDir.resolve(logfile), List.of(mvn3ExecutorRequestBuilder() .cwd(tempDir) + .argument("-V") .argument("verify") .argument("-l") .argument(logfile) .build())); } + @Timeout(15) @Test void version3x() throws Exception { assertEquals( @@ -180,12 +191,12 @@ public static void main(String... args) { } """; - protected void execute(Path logFile, Collection requests) throws Exception { + protected void execute(@Nullable Path logFile, Collection requests) throws Exception { try (Executor invoker = createExecutor()) { for (ExecutorRequest request : requests) { int exitCode = invoker.execute(request); if (exitCode != 0) { - throw new FailedExecution(request, exitCode, Files.readString(logFile)); + throw new FailedExecution(request, exitCode, logFile == null ? "" : Files.readString(logFile)); } } } diff --git a/impl/maven-executor/src/test/java/org/apache/maven/cling/executor/impl/HelperImplTest.java b/impl/maven-executor/src/test/java/org/apache/maven/cling/executor/impl/HelperImplTest.java index 2fef7f16b469..44c438071e95 100644 --- a/impl/maven-executor/src/test/java/org/apache/maven/cling/executor/impl/HelperImplTest.java +++ b/impl/maven-executor/src/test/java/org/apache/maven/cling/executor/impl/HelperImplTest.java @@ -29,6 +29,7 @@ import org.apache.maven.cling.executor.forked.ForkedMavenExecutor; import org.apache.maven.cling.executor.internal.HelperImpl; import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Timeout; import org.junit.jupiter.api.io.TempDir; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.EnumSource; @@ -45,6 +46,7 @@ public class HelperImplTest { @TempDir private static Path userHome; + @Timeout(15) @ParameterizedTest @EnumSource(ExecutorHelper.Mode.class) void dump3(ExecutorHelper.Mode mode) throws Exception { @@ -58,6 +60,7 @@ void dump3(ExecutorHelper.Mode mode) throws Exception { assertEquals(System.getProperty("maven3version"), dump.get("maven.version")); } + @Timeout(15) @ParameterizedTest @EnumSource(ExecutorHelper.Mode.class) void dump4(ExecutorHelper.Mode mode) throws Exception { @@ -71,6 +74,7 @@ void dump4(ExecutorHelper.Mode mode) throws Exception { assertEquals(System.getProperty("maven4version"), dump.get("maven.version")); } + @Timeout(15) @ParameterizedTest @EnumSource(ExecutorHelper.Mode.class) void version3(ExecutorHelper.Mode mode) { @@ -83,6 +87,7 @@ void version3(ExecutorHelper.Mode mode) { assertEquals(System.getProperty("maven3version"), helper.mavenVersion()); } + @Timeout(15) @ParameterizedTest @EnumSource(ExecutorHelper.Mode.class) void version4(ExecutorHelper.Mode mode) { @@ -95,6 +100,7 @@ void version4(ExecutorHelper.Mode mode) { assertEquals(System.getProperty("maven4version"), helper.mavenVersion()); } + @Timeout(15) @ParameterizedTest @EnumSource(ExecutorHelper.Mode.class) void localRepository3(ExecutorHelper.Mode mode) { @@ -109,6 +115,7 @@ void localRepository3(ExecutorHelper.Mode mode) { assertTrue(Files.isDirectory(local)); } + @Timeout(15) @ParameterizedTest @EnumSource(ExecutorHelper.Mode.class) @Disabled("disable temporarily so that we can get the debug statement") @@ -124,6 +131,7 @@ void localRepository4(ExecutorHelper.Mode mode) { assertTrue(Files.isDirectory(local)); } + @Timeout(15) @ParameterizedTest @EnumSource(ExecutorHelper.Mode.class) void artifactPath3(ExecutorHelper.Mode mode) { @@ -141,6 +149,7 @@ void artifactPath3(ExecutorHelper.Mode mode) { "path=" + path); } + @Timeout(15) @ParameterizedTest @EnumSource(ExecutorHelper.Mode.class) void artifactPath4(ExecutorHelper.Mode mode) { @@ -158,6 +167,7 @@ void artifactPath4(ExecutorHelper.Mode mode) { "path=" + path); } + @Timeout(15) @ParameterizedTest @EnumSource(ExecutorHelper.Mode.class) void metadataPath3(ExecutorHelper.Mode mode) { @@ -172,6 +182,7 @@ void metadataPath3(ExecutorHelper.Mode mode) { assertTrue(path.endsWith("aopalliance" + File.separator + "maven-metadata-someremote.xml"), "path=" + path); } + @Timeout(15) @ParameterizedTest @EnumSource(ExecutorHelper.Mode.class) void metadataPath4(ExecutorHelper.Mode mode) { diff --git a/its/core-it-support/maven-it-helper/src/main/java/org/apache/maven/it/Verifier.java b/its/core-it-support/maven-it-helper/src/main/java/org/apache/maven/it/Verifier.java index b51180030ed6..7ec9acdaaba6 100644 --- a/its/core-it-support/maven-it-helper/src/main/java/org/apache/maven/it/Verifier.java +++ b/its/core-it-support/maven-it-helper/src/main/java/org/apache/maven/it/Verifier.java @@ -227,8 +227,7 @@ public void execute() throws VerificationException { } ByteArrayOutputStream stdout = new ByteArrayOutputStream(); ByteArrayOutputStream stderr = new ByteArrayOutputStream(); - ExecutorRequest request = - builder.stdoutConsumer(stdout).stderrConsumer(stderr).build(); + ExecutorRequest request = builder.stdOut(stdout).stdErr(stderr).build(); int ret = executorHelper.execute(mode, request); if (ret > 0) { String dump; diff --git a/pom.xml b/pom.xml index 79728b9c13ed..993db6af31b8 100644 --- a/pom.xml +++ b/pom.xml @@ -472,6 +472,11 @@ under the License. jline-terminal-jni ${jlineVersion} + + org.jline + jline-native + ${jlineVersion} + org.jline jansi-core @@ -839,10 +844,43 @@ under the License. + + org.apache.maven.plugins + maven-dependency-plugin + + + prepare-for-mockito-agent + + properties + + + + + + mockito + + + org.mockito:mockito-core:jar + + + + + + + org.apache.maven.plugins + maven-surefire-plugin + + -Xmx256m -javaagent:${org.mockito:mockito-core:jar} + + + + + + graph