[MNG-8551] Improve early error reporting (#2076)

CLIng changes:
* parser does not throw but signals if "parsing failed" to invoker via new flag. Invoker should "early bail out" in these cases, as it is possible that even args were not parsed.
* logging is now fixed, in a way, that we depend on runtime configured Slf4j backend being in use. At "early stages" logger is just accumulating log entries, and logs are emitted to STDERR (in case of "early failure") or to proper Slf4j backend, once configured.
* Parser now validates options, all those that can down the road cause error.
* dropped ParserException and other unused bits.

In short: "early" messages go to stderr, while once logger is set up, to logger backend.

---

https://issues.apache.org/jira/browse/MNG-8551
This commit is contained in:
Tamas Cservenak 2025-01-31 21:56:58 +01:00 committed by GitHub
parent 4cc254ef37
commit b2b0bbeda6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
35 changed files with 516 additions and 323 deletions

View File

@ -41,7 +41,7 @@ public interface Invoker extends AutoCloseable {
*
* @param invokerRequest the request containing all necessary information for the invocation
* @return an integer representing the exit code of the invocation (0 typically indicates success)
* @throws InvokerException if an error occurs during the invocation process
* @throws InvokerException if an error occurs during the invocation process.
*/
int invoke(@Nonnull InvokerRequest invokerRequest) throws InvokerException;

View File

@ -47,6 +47,13 @@ public interface InvokerRequest {
@Nonnull
ParserRequest parserRequest();
/**
* Flag representing parser processing result: if there were some fatal errors during
* {@link Parser#parseInvocation(ParserRequest)} this method will return {@code true} and invoker should
* handle this request as "early failure".
*/
boolean parsingFailed();
/**
* Returns the current working directory for the Maven execution.
* This is typically the directory from which Maven was invoked.
@ -74,23 +81,6 @@ public interface InvokerRequest {
@Nonnull
Path userHomeDirectory();
/**
* Returns the list of extra JVM arguments to be passed to the forked process.
* These arguments allow for customization of the JVM environment in which tool will run.
* This property is used ONLY by executors and invokers that spawn a new JVM.
*
* @return an Optional containing the list of extra JVM arguments, or empty if not specified
*/
@Nonnull
Optional<List<String>> jvmArguments();
/**
* Shorthand for {@link Logger} to use.
*/
default Logger logger() {
return parserRequest().logger();
}
/**
* Shorthand for {@link MessageBuilderFactory}.
*/

View File

@ -18,12 +18,17 @@
*/
package org.apache.maven.api.cli;
import java.util.List;
import org.apache.maven.api.annotations.Experimental;
import org.apache.maven.api.annotations.Nonnull;
import org.apache.maven.api.annotations.Nullable;
/**
* Defines a simple logging interface for Maven CLI operations.
* Defines a simple logging interface for Maven CLI operations. These operations happen "early", when there may
* be no logging set up even. Implementations may be "accumulating", in which case {@link #drain()} method should
* be used.
* <p>
* This interface provides methods for logging messages at different severity levels
* and supports logging with or without associated exceptions.
*
@ -136,4 +141,21 @@ default void error(@Nonnull String message) {
default void error(@Nonnull String message, @Nullable Throwable error) {
log(Level.ERROR, message, error);
}
/**
* Logger entries returned by {@link #drain()} method.
* @param level The logging level, never {@code null}.
* @param message The logging message, never {@code null}.
* @param error The error, if applicable.
*/
record Entry(@Nonnull Level level, @Nonnull String message, @Nullable Throwable error) {}
/**
* If this is an accumulating log, it will "drain" this instance. It returns the accumulated log entries, and
* also "resets" this instance to empty (initial) state.
*/
@Nonnull
default List<Entry> drain() {
return List.of();
}
}

View File

@ -18,8 +18,6 @@
*/
package org.apache.maven.api.cli;
import java.io.IOException;
import org.apache.maven.api.annotations.Experimental;
import org.apache.maven.api.annotations.Nonnull;
@ -35,10 +33,9 @@ public interface Parser {
* This method does interpret tool arguments.
*
* @param parserRequest the request containing all necessary information for parsing
* @return the parsed invoker request
* @throws ParserException if there's an error during parsing of the request
* @throws IOException if there's an I/O error during the parsing process
* @return the parsed invoker request. Caller must start by checking {@link InvokerRequest#parsingFailed()} as
* if there are parser errors, this request may not be fully processed and should immediately be failed.
*/
@Nonnull
InvokerRequest parseInvocation(@Nonnull ParserRequest parserRequest) throws ParserException, IOException;
InvokerRequest parseInvocation(@Nonnull ParserRequest parserRequest);
}

View File

@ -1,52 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.maven.api.cli;
import org.apache.maven.api.annotations.Experimental;
import org.apache.maven.api.services.MavenException;
/**
* Represents an exception that occurs during the parsing of Maven command-line arguments.
* This exception is typically thrown when there are user errors in the command-line input,
* such as invalid arguments or references to missing files. When this exception is thrown,
* it indicates that the Maven execution should be stopped and the user should correct the issue.
*
* @since 4.0.0
*/
@Experimental
public class ParserException extends MavenException {
/**
* Constructs a new ParserException with the specified detail message.
*
* @param message the detail message explaining the cause of the exception
*/
public ParserException(String message) {
super(message);
}
/**
* Constructs a new ParserException with the specified detail message and cause.
*
* @param message the detail message explaining the cause of the exception
* @param cause the underlying cause of the exception
*/
public ParserException(String message, Throwable cause) {
super(message, cause);
}
}

View File

@ -30,6 +30,7 @@
import org.apache.maven.api.annotations.Immutable;
import org.apache.maven.api.annotations.Nonnull;
import org.apache.maven.api.annotations.Nullable;
import org.apache.maven.api.cli.logging.AccumulatingLogger;
import org.apache.maven.api.services.Lookup;
import org.apache.maven.api.services.LookupException;
import org.apache.maven.api.services.MessageBuilderFactory;
@ -153,84 +154,72 @@ public interface ParserRequest {
* Creates a new Builder instance for constructing a Maven ParserRequest.
*
* @param args the command-line arguments
* @param logger the logger to be used during parsing
* @param messageBuilderFactory the factory for creating message builders
* @return a new Builder instance
*/
@Nonnull
static Builder mvn(
@Nonnull String[] args, @Nonnull Logger logger, @Nonnull MessageBuilderFactory messageBuilderFactory) {
return mvn(Arrays.asList(args), logger, messageBuilderFactory);
static Builder mvn(@Nonnull String[] args, @Nonnull MessageBuilderFactory messageBuilderFactory) {
return mvn(Arrays.asList(args), messageBuilderFactory);
}
/**
* Creates a new Builder instance for constructing a Maven ParserRequest.
*
* @param args the command-line arguments
* @param logger the logger to be used during parsing
* @param messageBuilderFactory the factory for creating message builders
* @return a new Builder instance
*/
@Nonnull
static Builder mvn(
@Nonnull List<String> args, @Nonnull Logger logger, @Nonnull MessageBuilderFactory messageBuilderFactory) {
return builder(Tools.MVN_CMD, Tools.MVN_NAME, args, logger, messageBuilderFactory);
static Builder mvn(@Nonnull List<String> args, @Nonnull MessageBuilderFactory messageBuilderFactory) {
return builder(Tools.MVN_CMD, Tools.MVN_NAME, args, messageBuilderFactory);
}
/**
* Creates a new Builder instance for constructing a Maven Encrypting Tool ParserRequest.
*
* @param args the command-line arguments
* @param logger the logger to be used during parsing
* @param messageBuilderFactory the factory for creating message builders
* @return a new Builder instance
*/
@Nonnull
static Builder mvnenc(
@Nonnull String[] args, @Nonnull Logger logger, @Nonnull MessageBuilderFactory messageBuilderFactory) {
return mvnenc(Arrays.asList(args), logger, messageBuilderFactory);
static Builder mvnenc(@Nonnull String[] args, @Nonnull MessageBuilderFactory messageBuilderFactory) {
return mvnenc(Arrays.asList(args), messageBuilderFactory);
}
/**
* Creates a new Builder instance for constructing a Maven Encrypting Tool ParserRequest.
*
* @param args the command-line arguments
* @param logger the logger to be used during parsing
* @param messageBuilderFactory the factory for creating message builders
* @return a new Builder instance
*/
@Nonnull
static Builder mvnenc(
@Nonnull List<String> args, @Nonnull Logger logger, @Nonnull MessageBuilderFactory messageBuilderFactory) {
return builder(Tools.MVNENC_CMD, Tools.MVNENC_NAME, args, logger, messageBuilderFactory);
static Builder mvnenc(@Nonnull List<String> args, @Nonnull MessageBuilderFactory messageBuilderFactory) {
return builder(Tools.MVNENC_CMD, Tools.MVNENC_NAME, args, messageBuilderFactory);
}
/**
* Creates a new Builder instance for constructing a Maven Shell Tool ParserRequest.
*
* @param args the command-line arguments
* @param logger the logger to be used during parsing
* @param messageBuilderFactory the factory for creating message builders
* @return a new Builder instance
*/
@Nonnull
static Builder mvnsh(
@Nonnull String[] args, @Nonnull Logger logger, @Nonnull MessageBuilderFactory messageBuilderFactory) {
return mvnsh(Arrays.asList(args), logger, messageBuilderFactory);
static Builder mvnsh(@Nonnull String[] args, @Nonnull MessageBuilderFactory messageBuilderFactory) {
return mvnsh(Arrays.asList(args), messageBuilderFactory);
}
/**
* Creates a new Builder instance for constructing a Maven Shell Tool ParserRequest.
*
* @param args the command-line arguments
* @param logger the logger to be used during parsing
* @param messageBuilderFactory the factory for creating message builders
* @return a new Builder instance
*/
@Nonnull
static Builder mvnsh(
@Nonnull List<String> args, @Nonnull Logger logger, @Nonnull MessageBuilderFactory messageBuilderFactory) {
return builder(Tools.MVNSHELL_CMD, Tools.MVNSHELL_NAME, args, logger, messageBuilderFactory);
static Builder mvnsh(@Nonnull List<String> args, @Nonnull MessageBuilderFactory messageBuilderFactory) {
return builder(Tools.MVNSHELL_CMD, Tools.MVNSHELL_NAME, args, messageBuilderFactory);
}
/**
@ -239,7 +228,6 @@ static Builder mvnsh(
* @param command the Maven command to be executed
* @param commandName the Maven command Name to be executed
* @param args the command-line arguments
* @param logger the logger to be used during parsing
* @param messageBuilderFactory the factory for creating message builders
* @return a new Builder instance
*/
@ -248,17 +236,17 @@ static Builder builder(
@Nonnull String command,
@Nonnull String commandName,
@Nonnull List<String> args,
@Nonnull Logger logger,
@Nonnull MessageBuilderFactory messageBuilderFactory) {
return new Builder(command, commandName, args, logger, messageBuilderFactory);
return new Builder(command, commandName, args, messageBuilderFactory);
}
class Builder {
private final String command;
private final String commandName;
private final List<String> args;
private final Logger logger;
private final MessageBuilderFactory messageBuilderFactory;
private final Logger logger;
private Lookup lookup = EMPTY_LOOKUP;
private Path cwd;
private Path mavenHome;
@ -268,16 +256,12 @@ class Builder {
private OutputStream err;
private Builder(
String command,
String commandName,
List<String> args,
Logger logger,
MessageBuilderFactory messageBuilderFactory) {
String command, String commandName, List<String> args, MessageBuilderFactory messageBuilderFactory) {
this.command = requireNonNull(command, "command");
this.commandName = requireNonNull(commandName, "commandName");
this.args = requireNonNull(args, "args");
this.logger = requireNonNull(logger, "logger");
this.messageBuilderFactory = requireNonNull(messageBuilderFactory, "messageBuilderFactory");
this.logger = new AccumulatingLogger();
}
public Builder lookup(@Nonnull Lookup lookup) {
@ -319,7 +303,7 @@ public ParserRequest build() {
return new ParserRequestImpl(
command,
commandName,
args,
List.copyOf(args),
logger,
messageBuilderFactory,
lookup,

View File

@ -0,0 +1,47 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.maven.api.cli.logging;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.atomic.AtomicReference;
import org.apache.maven.api.cli.Logger;
import static java.util.Objects.requireNonNull;
/**
* Early CLI {@link Logger} that simply accumulates log entries until some point a real logger can emit them. This
* logger is created at start, and it exists while no logging is available yet.
*/
public class AccumulatingLogger implements Logger {
private final AtomicReference<List<Entry>> entries = new AtomicReference<>(new CopyOnWriteArrayList<>());
@Override
public void log(Level level, String message, Throwable error) {
requireNonNull(level, "level");
requireNonNull(message, "message");
entries.get().add(new Entry(level, message, error));
}
@Override
public List<Entry> drain() {
return entries.getAndSet(new CopyOnWriteArrayList<>());
}
}

View File

@ -23,7 +23,7 @@
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.ParserException;
import org.apache.maven.cling.invoker.logging.SystemLogger;
import org.codehaus.plexus.classworlds.ClassWorld;
import static java.util.Objects.requireNonNull;
@ -62,10 +62,11 @@ private ClingSupport(ClassWorld classWorld, boolean classWorldManaged) {
public int run(String[] args) throws IOException {
try (Invoker invoker = createInvoker()) {
return invoker.invoke(parseArguments(args));
} catch (ParserException e) {
System.err.println(e.getMessage());
return 1;
} catch (InvokerException e) {
} 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);
return 1;
} finally {
if (classWorldManaged) {
@ -76,5 +77,5 @@ public int run(String[] args) throws IOException {
protected abstract Invoker createInvoker();
protected abstract InvokerRequest parseArguments(String[] args) throws ParserException, IOException;
protected abstract InvokerRequest parseArguments(String[] args);
}

View File

@ -22,9 +22,7 @@
import org.apache.maven.api.cli.Invoker;
import org.apache.maven.api.cli.InvokerRequest;
import org.apache.maven.api.cli.ParserException;
import org.apache.maven.api.cli.ParserRequest;
import org.apache.maven.cling.invoker.ProtoLogger;
import org.apache.maven.cling.invoker.ProtoLookup;
import org.apache.maven.cling.invoker.mvn.MavenInvoker;
import org.apache.maven.cling.invoker.mvn.MavenParser;
@ -66,9 +64,9 @@ protected Invoker createInvoker() {
}
@Override
protected InvokerRequest parseArguments(String[] args) throws ParserException, IOException {
protected InvokerRequest parseArguments(String[] args) {
return new MavenParser()
.parseInvocation(ParserRequest.mvn(args, new ProtoLogger(), new JLineMessageBuilderFactory())
.parseInvocation(ParserRequest.mvn(args, new JLineMessageBuilderFactory())
.build());
}
}

View File

@ -22,9 +22,7 @@
import org.apache.maven.api.cli.Invoker;
import org.apache.maven.api.cli.InvokerRequest;
import org.apache.maven.api.cli.ParserException;
import org.apache.maven.api.cli.ParserRequest;
import org.apache.maven.cling.invoker.ProtoLogger;
import org.apache.maven.cling.invoker.ProtoLookup;
import org.apache.maven.cling.invoker.mvnenc.EncryptInvoker;
import org.apache.maven.cling.invoker.mvnenc.EncryptParser;
@ -66,9 +64,9 @@ protected Invoker createInvoker() {
}
@Override
protected InvokerRequest parseArguments(String[] args) throws ParserException, IOException {
protected InvokerRequest parseArguments(String[] args) {
return new EncryptParser()
.parseInvocation(ParserRequest.mvnenc(args, new ProtoLogger(), new JLineMessageBuilderFactory())
.parseInvocation(ParserRequest.mvnenc(args, new JLineMessageBuilderFactory())
.build());
}
}

View File

@ -22,9 +22,7 @@
import org.apache.maven.api.cli.Invoker;
import org.apache.maven.api.cli.InvokerRequest;
import org.apache.maven.api.cli.ParserException;
import org.apache.maven.api.cli.ParserRequest;
import org.apache.maven.cling.invoker.ProtoLogger;
import org.apache.maven.cling.invoker.ProtoLookup;
import org.apache.maven.cling.invoker.mvnsh.ShellInvoker;
import org.apache.maven.cling.invoker.mvnsh.ShellParser;
@ -66,9 +64,9 @@ protected Invoker createInvoker() {
}
@Override
protected InvokerRequest parseArguments(String[] args) throws ParserException, IOException {
protected InvokerRequest parseArguments(String[] args) {
return new ShellParser()
.parseInvocation(ParserRequest.mvnsh(args, new ProtoLogger(), new JLineMessageBuilderFactory())
.parseInvocation(ParserRequest.mvnsh(args, new JLineMessageBuilderFactory())
.build());
}
}

View File

@ -35,10 +35,10 @@
public abstract class BaseInvokerRequest implements InvokerRequest {
private final ParserRequest parserRequest;
private final boolean parsingFailed;
private final Path cwd;
private final Path installationDirectory;
private final Path userHomeDirectory;
private final List<String> jvmArguments;
private final Map<String, String> userProperties;
private final Map<String, String> systemProperties;
private final Path topDirectory;
@ -51,6 +51,7 @@ public abstract class BaseInvokerRequest implements InvokerRequest {
@SuppressWarnings("ParameterNumber")
public BaseInvokerRequest(
@Nonnull ParserRequest parserRequest,
boolean parsingFailed,
@Nonnull Path cwd,
@Nonnull Path installationDirectory,
@Nonnull Path userHomeDirectory,
@ -61,13 +62,12 @@ public BaseInvokerRequest(
@Nullable InputStream in,
@Nullable OutputStream out,
@Nullable OutputStream err,
@Nullable List<CoreExtension> coreExtensions,
@Nullable List<String> jvmArguments) {
@Nullable List<CoreExtension> coreExtensions) {
this.parserRequest = requireNonNull(parserRequest);
this.parsingFailed = parsingFailed;
this.cwd = requireNonNull(cwd);
this.installationDirectory = requireNonNull(installationDirectory);
this.userHomeDirectory = requireNonNull(userHomeDirectory);
this.jvmArguments = jvmArguments;
this.userProperties = requireNonNull(userProperties);
this.systemProperties = requireNonNull(systemProperties);
@ -85,6 +85,11 @@ public ParserRequest parserRequest() {
return parserRequest;
}
@Override
public boolean parsingFailed() {
return parsingFailed;
}
@Override
public Path cwd() {
return cwd;
@ -100,11 +105,6 @@ public Path userHomeDirectory() {
return userHomeDirectory;
}
@Override
public Optional<List<String>> jvmArguments() {
return Optional.ofNullable(jvmArguments);
}
@Override
public Map<String, String> userProperties() {
return userProperties;

View File

@ -22,7 +22,6 @@
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
@ -30,18 +29,17 @@
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.Properties;
import java.util.function.UnaryOperator;
import java.util.stream.Collectors;
import org.apache.maven.api.Constants;
import org.apache.maven.api.annotations.Nullable;
import org.apache.maven.api.cli.InvokerRequest;
import org.apache.maven.api.cli.Options;
import org.apache.maven.api.cli.Parser;
import org.apache.maven.api.cli.ParserException;
import org.apache.maven.api.cli.ParserRequest;
import org.apache.maven.api.cli.extensions.CoreExtension;
import org.apache.maven.api.services.Interpolator;
@ -70,6 +68,7 @@ public LocalContext(ParserRequest parserRequest) {
this.systemPropertiesOverrides = new HashMap<>();
}
public boolean parsingFailed = false;
public Path cwd;
public Path installationDirectory;
public Path userHomeDirectory;
@ -94,47 +93,159 @@ public Map<String, String> extraInterpolationSource() {
}
@Override
public InvokerRequest parseInvocation(ParserRequest parserRequest) throws ParserException, IOException {
public InvokerRequest parseInvocation(ParserRequest parserRequest) {
requireNonNull(parserRequest);
LocalContext context = new LocalContext(parserRequest);
// the basics
context.cwd = requireNonNull(getCwd(context));
context.installationDirectory = requireNonNull(getInstallationDirectory(context));
context.userHomeDirectory = requireNonNull(getUserHomeDirectory(context));
try {
context.cwd = getCwd(context);
} catch (Exception e) {
context.parsingFailed = true;
context.cwd = getCanonicalPath(Paths.get("."));
parserRequest.logger().error("Error determining working directory", e);
}
try {
context.installationDirectory = getInstallationDirectory(context);
} catch (Exception e) {
context.parsingFailed = true;
context.installationDirectory = context.cwd;
parserRequest.logger().error("Error determining installation directory", e);
}
try {
context.userHomeDirectory = getUserHomeDirectory(context);
} catch (Exception e) {
context.parsingFailed = true;
context.userHomeDirectory = context.cwd;
parserRequest.logger().error("Error determining user home directory", e);
}
// top/root
context.topDirectory = requireNonNull(getTopDirectory(context));
context.rootDirectory = getRootDirectory(context);
try {
context.topDirectory = getTopDirectory(context);
} catch (Exception e) {
context.parsingFailed = true;
context.topDirectory = context.cwd;
parserRequest.logger().error("Error determining top directory", e);
}
try {
context.rootDirectory = getRootDirectory(context);
} catch (Exception e) {
context.parsingFailed = true;
context.rootDirectory = context.cwd;
parserRequest.logger().error("Error determining root directory", e);
}
// options
List<Options> parsedOptions = parseCliOptions(context);
// warn about deprecated options
PrintWriter printWriter = new PrintWriter(parserRequest.out() != null ? parserRequest.out() : System.out, true);
parsedOptions.forEach(o -> o.warnAboutDeprecatedOptions(parserRequest, printWriter::println));
List<Options> parsedOptions;
try {
parsedOptions = parseCliOptions(context);
} catch (Exception e) {
context.parsingFailed = true;
parsedOptions = List.of(emptyOptions());
parserRequest.logger().error("Error parsing program arguments", e);
}
// assemble options if needed
context.options = assembleOptions(parsedOptions);
try {
context.options = assembleOptions(parsedOptions);
} catch (Exception e) {
context.parsingFailed = true;
context.options = emptyOptions();
parserRequest.logger().error("Error assembling program arguments", e);
}
// system and user properties
context.systemProperties = populateSystemProperties(context);
context.userProperties = populateUserProperties(context);
try {
context.systemProperties = populateSystemProperties(context);
} catch (Exception e) {
context.parsingFailed = true;
context.systemProperties = new HashMap<>();
parserRequest.logger().error("Error populating system properties", e);
}
try {
context.userProperties = populateUserProperties(context);
} catch (Exception e) {
context.parsingFailed = true;
context.userProperties = new HashMap<>();
parserRequest.logger().error("Error populating user properties", e);
}
// options: interpolate
context.options = context.options.interpolate(Interpolator.chain(
context.extraInterpolationSource()::get, context.userProperties::get, context.systemProperties::get));
// core extensions
context.extensions = readCoreExtensionsDescriptor(context);
try {
context.extensions = readCoreExtensionsDescriptor(context);
} catch (Exception e) {
context.parsingFailed = true;
parserRequest.logger().error("Error reading core extensions descriptor", e);
}
// only if not failed so far; otherwise we may have no options to validate
if (!context.parsingFailed) {
validate(context);
}
return getInvokerRequest(context);
}
protected void validate(LocalContext context) {
Options options = context.options;
options.failOnSeverity().ifPresent(severity -> {
String c = severity.toLowerCase(Locale.ENGLISH);
if (!Arrays.asList("warn", "warning", "error").contains(c)) {
context.parsingFailed = true;
context.parserRequest
.logger()
.error("Invalid fail on severity threshold '" + c
+ "'. Supported values are 'WARN', 'WARNING' and 'ERROR'.");
}
});
options.altUserSettings()
.ifPresent(userSettings ->
failIfFileNotExists(context, userSettings, "The specified user settings file does not exist"));
options.altProjectSettings()
.ifPresent(projectSettings -> failIfFileNotExists(
context, projectSettings, "The specified project settings file does not exist"));
options.altInstallationSettings()
.ifPresent(installationSettings -> failIfFileNotExists(
context, installationSettings, "The specified installation settings file does not exist"));
options.altUserToolchains()
.ifPresent(userToolchains -> failIfFileNotExists(
context, userToolchains, "The specified user toolchains file does not exist"));
options.altInstallationToolchains()
.ifPresent(installationToolchains -> failIfFileNotExists(
context, installationToolchains, "The specified installation toolchains file does not exist"));
options.color().ifPresent(color -> {
String c = color.toLowerCase(Locale.ENGLISH);
if (!Arrays.asList("always", "yes", "force", "never", "no", "none", "auto", "tty", "if-tty")
.contains(c)) {
context.parsingFailed = true;
context.parserRequest
.logger()
.error("Invalid color configuration value '" + c
+ "'. Supported values are 'auto', 'always', 'never'.");
}
});
}
protected void failIfFileNotExists(LocalContext context, String fileName, String message) {
Path path = context.cwd.resolve(fileName);
if (!Files.isRegularFile(path)) {
context.parsingFailed = true;
context.parserRequest.logger().error(message + ": " + path);
}
}
protected abstract Options emptyOptions();
protected abstract InvokerRequest getInvokerRequest(LocalContext context);
protected Path getCwd(LocalContext context) throws ParserException {
protected Path getCwd(LocalContext context) {
if (context.parserRequest.cwd() != null) {
Path result = getCanonicalPath(context.parserRequest.cwd());
context.systemPropertiesOverrides.put("user.dir", result.toString());
@ -146,7 +257,7 @@ protected Path getCwd(LocalContext context) throws ParserException {
}
}
protected Path getInstallationDirectory(LocalContext context) throws ParserException {
protected Path getInstallationDirectory(LocalContext context) {
if (context.parserRequest.mavenHome() != null) {
Path result = getCanonicalPath(context.parserRequest.mavenHome());
context.systemPropertiesOverrides.put(Constants.MAVEN_HOME, result.toString());
@ -154,7 +265,8 @@ protected Path getInstallationDirectory(LocalContext context) throws ParserExcep
} else {
String mavenHome = System.getProperty(Constants.MAVEN_HOME);
if (mavenHome == null) {
throw new ParserException("local mode requires " + Constants.MAVEN_HOME + " Java System Property set");
throw new IllegalStateException(
"local mode requires " + Constants.MAVEN_HOME + " Java System Property set");
}
Path result = getCanonicalPath(Paths.get(mavenHome));
mayOverrideDirectorySystemProperty(context, Constants.MAVEN_HOME, result);
@ -162,7 +274,7 @@ protected Path getInstallationDirectory(LocalContext context) throws ParserExcep
}
}
protected Path getUserHomeDirectory(LocalContext context) throws ParserException {
protected Path getUserHomeDirectory(LocalContext context) {
if (context.parserRequest.userHome() != null) {
Path result = getCanonicalPath(context.parserRequest.userHome());
context.systemPropertiesOverrides.put("user.home", result.toString());
@ -185,7 +297,7 @@ protected void mayOverrideDirectorySystemProperty(LocalContext context, String j
}
}
protected Path getTopDirectory(LocalContext context) throws ParserException {
protected Path getTopDirectory(LocalContext context) {
// We need to locate the top level project which may be pointed at using
// the -f/--file option.
Path topDirectory = requireNonNull(context.cwd);
@ -199,11 +311,11 @@ protected Path getTopDirectory(LocalContext context) throws ParserException {
} else if (Files.isRegularFile(path)) {
topDirectory = path.getParent();
if (!Files.isDirectory(topDirectory)) {
throw new ParserException("Directory " + topDirectory
throw new IllegalArgumentException("Directory " + topDirectory
+ " extracted from the -f/--file command-line argument " + arg + " does not exist");
}
} else {
throw new ParserException(
throw new IllegalArgumentException(
"POM file " + arg + " specified with the -f/--file command line argument does not exist");
}
break;
@ -216,11 +328,11 @@ protected Path getTopDirectory(LocalContext context) throws ParserException {
}
@Nullable
protected Path getRootDirectory(LocalContext context) throws ParserException {
protected Path getRootDirectory(LocalContext context) {
return Utils.findRoot(context.topDirectory);
}
protected Map<String, String> populateSystemProperties(LocalContext context) throws ParserException {
protected Map<String, String> populateSystemProperties(LocalContext context) {
Properties systemProperties = new Properties();
// ----------------------------------------------------------------------
@ -248,7 +360,7 @@ protected Map<String, String> populateSystemProperties(LocalContext context) thr
return result;
}
protected Map<String, String> populateUserProperties(LocalContext context) throws ParserException, IOException {
protected Map<String, String> populateUserProperties(LocalContext context) {
Properties userProperties = new Properties();
// ----------------------------------------------------------------------
@ -281,7 +393,11 @@ protected Map<String, String> populateUserProperties(LocalContext context) throw
mavenConf = context.installationDirectory.resolve("");
}
Path propertiesFile = mavenConf.resolve("maven.properties");
MavenPropertiesLoader.loadProperties(userProperties, propertiesFile, callback, false);
try {
MavenPropertiesLoader.loadProperties(userProperties, propertiesFile, callback, false);
} catch (IOException e) {
throw new IllegalStateException("Error loading properties from " + propertiesFile, e);
}
// CLI specified properties are most dominant
userProperties.putAll(userSpecifiedProperties);
@ -289,12 +405,11 @@ protected Map<String, String> populateUserProperties(LocalContext context) throw
return toMap(userProperties);
}
protected abstract List<Options> parseCliOptions(LocalContext context) throws ParserException, IOException;
protected abstract List<Options> parseCliOptions(LocalContext context);
protected abstract Options assembleOptions(List<Options> parsedOptions);
protected List<CoreExtension> readCoreExtensionsDescriptor(LocalContext context)
throws ParserException, IOException {
protected List<CoreExtension> readCoreExtensionsDescriptor(LocalContext context) {
String installationExtensionsFile = context.userProperties.get(Constants.MAVEN_INSTALLATION_EXTENSIONS);
ArrayList<CoreExtension> installationExtensions = new ArrayList<>(readCoreExtensionsDescriptorFromFile(
context.installationDirectory.resolve(installationExtensionsFile)));
@ -318,7 +433,7 @@ protected List<CoreExtension> readCoreExtensionsDescriptor(LocalContext context)
coreExtensions.addAll(mergeExtensions(projectExtensions, projectExtensionsFile, gas, conflicts));
if (!conflicts.isEmpty()) {
throw new ParserException("Extension conflicts: " + String.join("; ", conflicts));
throw new IllegalStateException("Extension conflicts: " + String.join("; ", conflicts));
}
return coreExtensions;
@ -337,8 +452,7 @@ private List<CoreExtension> mergeExtensions(
return extensions;
}
protected List<CoreExtension> readCoreExtensionsDescriptorFromFile(Path extensionsFile)
throws ParserException, IOException {
protected List<CoreExtension> readCoreExtensionsDescriptorFromFile(Path extensionsFile) {
try {
if (extensionsFile != null && Files.exists(extensionsFile)) {
try (InputStream is = Files.newInputStream(extensionsFile)) {
@ -346,25 +460,8 @@ protected List<CoreExtension> readCoreExtensionsDescriptorFromFile(Path extensio
}
}
return List.of();
} catch (XMLStreamException e) {
throw new ParserException("Failed to parse extensions file: " + extensionsFile, e);
} catch (XMLStreamException | IOException e) {
throw new IllegalArgumentException("Failed to parse extensions file: " + extensionsFile, e);
}
}
protected List<String> getJvmArguments(Path rootDirectory) throws ParserException {
if (rootDirectory != null) {
Path jvmConfig = rootDirectory.resolve(".mvn/jvm.config");
if (Files.exists(jvmConfig)) {
try {
return Files.readAllLines(jvmConfig).stream()
.filter(l -> !l.isBlank() && !l.startsWith("#"))
.flatMap(l -> Arrays.stream(l.split(" ")))
.collect(Collectors.toList());
} catch (IOException e) {
throw new ParserException("Failed to read JVM configuration file: " + jvmConfig, e);
}
}
}
return null;
}
}

View File

@ -263,6 +263,7 @@ protected static class CLIManager {
public static final String VERBOSE = "X";
public static final String SHOW_ERRORS = "e";
public static final String FAIL_ON_SEVERITY = "fos";
public static final String NON_INTERACTIVE = "non-interactive";
public static final String BATCH_MODE = "B";
@ -278,6 +279,9 @@ protected static class CLIManager {
public static final String OFFLINE = "o";
public static final String HELP = "h";
// Not an Option: used only for early detection, when CLI args may not be even parsed
public static final String SHOW_ERRORS_CLI_ARG = "-" + SHOW_ERRORS;
// parameters handled by script
public static final String DEBUG = "debug";
public static final String ENC = "enc";

View File

@ -21,7 +21,6 @@
import java.util.Optional;
import org.apache.maven.api.annotations.Nonnull;
import org.apache.maven.api.cli.InvokerException;
import org.apache.maven.api.services.Lookup;
/**
@ -49,5 +48,5 @@ public interface ContainerCapsule extends AutoCloseable {
* Performs a clean shutdown of backing container.
*/
@Override
void close() throws InvokerException;
void close() throws Exception;
}

View File

@ -19,7 +19,6 @@
package org.apache.maven.cling.invoker;
import org.apache.maven.api.annotations.Nonnull;
import org.apache.maven.api.cli.InvokerException;
/**
* Container capsule factory.
@ -31,5 +30,5 @@ public interface ContainerCapsuleFactory<C extends LookupContext> {
* Creates container capsule.
*/
@Nonnull
ContainerCapsule createContainerCapsule(LookupInvoker<C> invoker, C context) throws InvokerException;
ContainerCapsule createContainerCapsule(LookupInvoker<C> invoker, C context) throws Exception;
}

View File

@ -79,12 +79,13 @@ public LookupContext(InvokerRequest invokerRequest, boolean containerCapsuleMana
.build();
}
public Logger logger;
// this one "evolves" as process progresses (instance is immutable but instances are replaced)
public ProtoSession protoSession;
// here we track which user properties we pushed to Java System Properties (internal only)
public Set<String> pushedUserProperties;
public Logger logger;
public ILoggerFactory loggerFactory;
public Slf4jConfiguration slf4jConfiguration;
public Slf4jConfiguration.Level loggerLevel;
@ -132,13 +133,13 @@ public void close() throws InvokerException {
}
}
public final void closeContainer() {
public final void closeContainer() throws Exception {
if (containerCapsuleManaged) {
doCloseContainer();
}
}
public void doCloseContainer() {
public void doCloseContainer() throws Exception {
if (containerCapsule != null) {
try {
containerCapsule.close();

View File

@ -43,6 +43,7 @@
import org.apache.maven.api.cli.InvokerRequest;
import org.apache.maven.api.cli.Logger;
import org.apache.maven.api.cli.Options;
import org.apache.maven.api.cli.logging.AccumulatingLogger;
import org.apache.maven.api.services.BuilderProblem;
import org.apache.maven.api.services.Interpolator;
import org.apache.maven.api.services.Lookup;
@ -64,6 +65,8 @@
import org.apache.maven.artifact.repository.MavenArtifactRepository;
import org.apache.maven.artifact.repository.layout.DefaultRepositoryLayout;
import org.apache.maven.bridge.MavenRepositorySystem;
import org.apache.maven.cling.invoker.logging.Slf4jLogger;
import org.apache.maven.cling.invoker.logging.SystemLogger;
import org.apache.maven.cling.invoker.spi.PropertyContributorsHolder;
import org.apache.maven.cling.logging.Slf4jConfiguration;
import org.apache.maven.cling.logging.Slf4jConfigurationFactory;
@ -108,7 +111,7 @@ public LookupInvoker(Lookup protoLookup, @Nullable Consumer<LookupContext> conte
}
@Override
public int invoke(InvokerRequest invokerRequest) throws InvokerException {
public final int invoke(InvokerRequest invokerRequest) {
requireNonNull(invokerRequest);
Properties oldProps = new Properties();
@ -128,9 +131,15 @@ public int invoke(InvokerRequest invokerRequest) throws InvokerException {
}
return doInvoke(context);
} catch (InvokerException.ExitException e) {
return e.getExitCode();
// contract of ExitException is that nothing needed by us
throw e;
} 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);
@ -139,46 +148,77 @@ public int invoke(InvokerRequest invokerRequest) throws InvokerException {
}
protected int doInvoke(C context) throws Exception {
try {
pushCoreProperties(context);
pushUserProperties(context);
configureLogging(context);
createTerminal(context);
activateLogging(context);
helpOrVersionAndMayExit(context);
preCommands(context);
container(context);
postContainer(context);
pushUserProperties(context); // after PropertyContributor SPI
lookup(context);
init(context);
postCommands(context);
settings(context);
return execute(context);
} finally {
if (context.terminal != null) {
context.terminal.writer().flush();
validate(context);
pushCoreProperties(context);
pushUserProperties(context);
configureLogging(context);
createTerminal(context);
activateLogging(context);
helpOrVersionAndMayExit(context);
preCommands(context);
container(context);
postContainer(context);
pushUserProperties(context); // after PropertyContributor SPI
lookup(context);
init(context);
postCommands(context);
settings(context);
return execute(context);
}
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);
return new InvokerException.ExitException(2);
}
protected void printErrors(C context, boolean showStackTrace, List<Logger.Entry> entries, Logger logger) {
// 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) {
if (showStackTrace) {
logger.log(entry.level(), entry.message(), entry.error());
} else {
logger.error(entry.message());
for (Throwable cause = entry.error();
cause != null && cause != cause.getCause();
cause = cause.getCause()) {
logger.log(entry.level(), "Caused by: " + cause.getMessage());
}
}
}
}
protected InvokerException handleException(C context, Exception e) throws InvokerException {
boolean showStackTrace = context.invokerRequest.options().showErrors().orElse(false);
if (showStackTrace) {
context.logger.error(
"Error executing " + context.invokerRequest.parserRequest().commandName() + ".", e);
} else {
context.logger.error(
"Error executing " + context.invokerRequest.parserRequest().commandName() + ".");
context.logger.error(e.getMessage());
for (Throwable cause = e.getCause(); cause != null && cause != cause.getCause(); cause = cause.getCause()) {
context.logger.error("Caused by: " + cause.getMessage());
}
}
return new InvokerException(e.getMessage(), e);
}
protected abstract C createContext(InvokerRequest invokerRequest);
protected abstract C createContext(InvokerRequest invokerRequest) throws InvokerException;
protected void validate(C context) throws Exception {
if (context.invokerRequest.parsingFailed()) {
// in case of parser errors: report errors and bail out; invokerRequest contents may be incomplete
List<Logger.Entry> entries = context.logger.drain();
printErrors(
context,
context.invokerRequest
.parserRequest()
.args()
.contains(CommonsCliOptions.CLIManager.SHOW_ERRORS_CLI_ARG),
entries,
new SystemLogger());
// we skip handleException above as we did output
throw new InvokerException.ExitException(1);
}
// warn about deprecated options
context.invokerRequest
.options()
.warnAboutDeprecatedOptions(context.invokerRequest.parserRequest(), context.logger::warn);
}
protected void pushCoreProperties(C context) throws Exception {
System.setProperty(
@ -216,7 +256,8 @@ protected void configureLogging(C context) throws Exception {
String styleColor = mavenOptions
.color()
.orElse(userProperties.getOrDefault(
Constants.MAVEN_STYLE_COLOR_PROPERTY, userProperties.getOrDefault("style.color", "auto")));
Constants.MAVEN_STYLE_COLOR_PROPERTY, userProperties.getOrDefault("style.color", "auto")))
.toLowerCase(Locale.ENGLISH);
if ("always".equals(styleColor) || "yes".equals(styleColor) || "force".equals(styleColor)) {
context.coloredOutput = true;
} else if ("never".equals(styleColor) || "no".equals(styleColor) || "none".equals(styleColor)) {
@ -344,11 +385,6 @@ protected void activateLogging(C context) throws Exception {
Options mavenOptions = invokerRequest.options();
context.slf4jConfiguration.activate();
org.slf4j.Logger l = context.loggerFactory.getLogger(this.getClass().getName());
context.logger = (level, message, error) -> l.atLevel(org.slf4j.event.Level.valueOf(level.name()))
.setCause(error)
.log(message);
if (mavenOptions.failOnSeverity().isPresent()) {
String logLevelThreshold = mavenOptions.failOnSeverity().get();
if (context.loggerFactory instanceof LogLevelRecorder recorder) {
@ -369,6 +405,12 @@ protected void activateLogging(C context) throws Exception {
+ "The --fail-on-severity flag will not take effect.");
}
}
// at this point logging is set up, reply so far accumulated logs, if any and swap logger with real one
Logger logger =
new Slf4jLogger(context.loggerFactory.getLogger(getClass().getName()));
context.logger.drain().forEach(e -> logger.log(e.level(), e.message(), e.error()));
context.logger = logger;
}
protected void helpOrVersionAndMayExit(C context) throws Exception {

View File

@ -46,10 +46,6 @@ public PlexusContainerCapsule(
@Override
public void updateLogging(LookupContext context) {
plexusContainer.getLoggerManager().setThresholds(toPlexusLoggingLevel(context.loggerLevel));
org.slf4j.Logger l = context.loggerFactory.getLogger(this.getClass().getName());
context.logger = (level, message, error) -> l.atLevel(org.slf4j.event.Level.valueOf(level.name()))
.setCause(error)
.log(message);
}
@Override

View File

@ -31,7 +31,6 @@
import com.google.inject.Module;
import org.apache.maven.api.Constants;
import org.apache.maven.api.ProtoSession;
import org.apache.maven.api.cli.InvokerException;
import org.apache.maven.api.cli.InvokerRequest;
import org.apache.maven.api.cli.Logger;
import org.apache.maven.api.cli.extensions.CoreExtension;
@ -70,13 +69,9 @@
*/
public class PlexusContainerCapsuleFactory<C extends LookupContext> implements ContainerCapsuleFactory<C> {
@Override
public ContainerCapsule createContainerCapsule(LookupInvoker<C> invoker, C context) throws InvokerException {
try {
return new PlexusContainerCapsule(
context, Thread.currentThread().getContextClassLoader(), container(invoker, context));
} catch (Exception e) {
throw new InvokerException("Failed to create Plexus DI Container", e);
}
public ContainerCapsule createContainerCapsule(LookupInvoker<C> invoker, C context) throws Exception {
return new PlexusContainerCapsule(
context, Thread.currentThread().getContextClassLoader(), container(invoker, context));
}
protected DefaultPlexusContainer container(LookupInvoker<C> invoker, C context) throws Exception {

View File

@ -0,0 +1,47 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.maven.cling.invoker.logging;
import org.apache.maven.api.cli.Logger;
import static java.util.Objects.requireNonNull;
/**
* Proto {@link Logger} that just passes to functioning {@link org.slf4j.Logger} instance.
*/
public class Slf4jLogger implements Logger {
private final org.slf4j.Logger logger;
public Slf4jLogger(org.slf4j.Logger logger) {
this.logger = requireNonNull(logger, "logger");
}
@Override
public void log(Level level, String message, Throwable error) {
requireNonNull(level, "level");
requireNonNull(message, "message");
switch (level) {
case ERROR -> logger.error(message, error);
case WARN -> logger.warn(message, error);
case INFO -> logger.info(message, error);
case DEBUG -> logger.debug(message, error);
default -> logger.error("UNKNOWN LEVEL {}: {}", level, message, error);
}
}
}

View File

@ -16,31 +16,33 @@
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.maven.cling.invoker;
package org.apache.maven.cling.invoker.logging;
import java.io.OutputStream;
import java.io.PrintStream;
import java.io.PrintWriter;
import java.util.Objects;
import org.apache.maven.api.annotations.Nullable;
import org.apache.maven.api.cli.Logger;
import static java.util.Objects.requireNonNull;
/**
* Proto {@link Logger}. Uses provided {@link PrintStream}s or {@link System} ones as fallback.
* Supports only two levels: ERROR and WARNING, that is emitted to STDERR and STDOUT.
* System {@link Logger}. Uses provided {@link PrintStream}s or {@link System#err} ones as fallback.
* This logger is used in case of "early failures" (when no logging may be set up yet).
*/
public class ProtoLogger implements Logger {
public class SystemLogger implements Logger {
private final PrintWriter out;
private final PrintWriter err;
private final Level threshold;
public ProtoLogger() {
public SystemLogger() {
this(null, null);
}
public ProtoLogger(@Nullable OutputStream out, @Nullable OutputStream err) {
this.out = new PrintWriter(toPsOrDef(out, System.out), true);
this.err = new PrintWriter(toPsOrDef(err, System.err), true);
public SystemLogger(@Nullable OutputStream out, @Nullable Level threshold) {
this.out = new PrintWriter(toPsOrDef(out, System.err), true);
this.threshold = Objects.requireNonNullElse(threshold, Level.INFO);
}
private PrintStream toPsOrDef(OutputStream outputStream, PrintStream def) {
@ -50,20 +52,17 @@ private PrintStream toPsOrDef(OutputStream outputStream, PrintStream def) {
if (outputStream instanceof PrintStream ps) {
return ps;
}
return new PrintStream(outputStream);
return new PrintStream(outputStream, true);
}
//
// These are the only methods we need in our primordial logger
//
@Override
public void log(Level level, String message, Throwable error) {
PrintWriter pw = level == Level.ERROR ? err : level == Level.WARN ? out : null;
if (pw != null) {
pw.println(level.name() + " " + message);
requireNonNull(level, "level");
requireNonNull(message, "message");
if (level.ordinal() >= threshold.ordinal()) {
out.println("[" + level.name() + "] " + message);
if (error != null) {
error.printStackTrace(pw);
error.printStackTrace(out);
}
}
}

View File

@ -35,7 +35,7 @@ public MavenContext(InvokerRequest invokerRequest, boolean containerCapsuleManag
public Maven maven;
@Override
public void doCloseContainer() {
public void doCloseContainer() throws Exception {
try {
super.doCloseContainer();
} finally {

View File

@ -34,7 +34,6 @@
import org.apache.maven.api.Constants;
import org.apache.maven.api.MonotonicClock;
import org.apache.maven.api.annotations.Nullable;
import org.apache.maven.api.cli.InvokerException;
import org.apache.maven.api.cli.InvokerRequest;
import org.apache.maven.api.cli.Logger;
import org.apache.maven.api.cli.mvn.MavenOptions;
@ -86,7 +85,7 @@ public MavenInvoker(Lookup protoLookup, @Nullable Consumer<LookupContext> contex
}
@Override
protected MavenContext createContext(InvokerRequest invokerRequest) throws InvokerException {
protected MavenContext createContext(InvokerRequest invokerRequest) {
return new MavenContext(invokerRequest);
}

View File

@ -41,6 +41,7 @@ public class MavenInvokerRequest extends BaseInvokerRequest {
@SuppressWarnings("ParameterNumber")
public MavenInvokerRequest(
ParserRequest parserRequest,
boolean parseFailed,
Path cwd,
Path installationDirectory,
Path userHomeDirectory,
@ -52,10 +53,10 @@ public MavenInvokerRequest(
OutputStream out,
OutputStream err,
List<CoreExtension> coreExtensions,
List<String> jvmArguments,
MavenOptions options) {
super(
parserRequest,
parseFailed,
cwd,
installationDirectory,
userHomeDirectory,
@ -66,8 +67,7 @@ public MavenInvokerRequest(
in,
out,
err,
coreExtensions,
jvmArguments);
coreExtensions);
this.options = requireNonNull(options);
}

View File

@ -28,14 +28,13 @@
import org.apache.commons.cli.ParseException;
import org.apache.maven.api.cli.Options;
import org.apache.maven.api.cli.ParserException;
import org.apache.maven.api.cli.mvn.MavenOptions;
import org.apache.maven.cling.invoker.BaseParser;
public class MavenParser extends BaseParser {
@Override
protected List<Options> parseCliOptions(LocalContext context) throws ParserException, IOException {
protected List<Options> parseCliOptions(LocalContext context) {
ArrayList<Options> result = new ArrayList<>();
// CLI args
result.add(parseMavenCliOptions(context.parserRequest.args()));
@ -47,29 +46,44 @@ protected List<Options> parseCliOptions(LocalContext context) throws ParserExcep
return result;
}
protected MavenOptions parseMavenCliOptions(List<String> args) throws ParserException {
return parseArgs(Options.SOURCE_CLI, args);
protected MavenOptions parseMavenCliOptions(List<String> args) {
try {
return parseArgs(Options.SOURCE_CLI, args);
} catch (ParseException e) {
throw new IllegalArgumentException("Failed to parse CLI arguments: " + e.getMessage(), e.getCause());
}
}
protected MavenOptions parseMavenConfigOptions(Path configFile) throws ParserException, IOException {
protected MavenOptions parseMavenConfigOptions(Path configFile) {
try (Stream<String> lines = Files.lines(configFile, Charset.defaultCharset())) {
List<String> args =
lines.filter(arg -> !arg.isEmpty() && !arg.startsWith("#")).toList();
MavenOptions options = parseArgs("maven.config", args);
if (options.goals().isPresent()) {
// This file can only contain options, not args (goals or phases)
throw new ParserException("Unrecognized maven.config file entries: "
throw new IllegalArgumentException("Unrecognized entries in maven.config (" + configFile + ") file: "
+ options.goals().get());
}
return options;
} catch (ParseException e) {
throw new IllegalArgumentException(
"Failed to parse arguments from maven.config file (" + configFile + "): " + e.getMessage(),
e.getCause());
} catch (IOException e) {
throw new IllegalStateException("Error reading config file: " + configFile, e);
}
}
protected MavenOptions parseArgs(String source, List<String> args) throws ParserException {
protected MavenOptions parseArgs(String source, List<String> args) throws ParseException {
return CommonsCliMavenOptions.parse(source, args.toArray(new String[0]));
}
@Override
protected MavenOptions emptyOptions() {
try {
return CommonsCliMavenOptions.parse(source, args.toArray(new String[0]));
return CommonsCliMavenOptions.parse(Options.SOURCE_CLI, new String[0]);
} catch (ParseException e) {
throw new ParserException("Failed to parse source " + source + ": " + e.getMessage(), e.getCause());
throw new IllegalArgumentException(e);
}
}
@ -77,6 +91,7 @@ protected MavenOptions parseArgs(String source, List<String> args) throws Parser
protected MavenInvokerRequest getInvokerRequest(LocalContext context) {
return new MavenInvokerRequest(
context.parserRequest,
context.parsingFailed,
context.cwd,
context.installationDirectory,
context.userHomeDirectory,
@ -88,7 +103,6 @@ protected MavenInvokerRequest getInvokerRequest(LocalContext context) {
context.parserRequest.out(),
context.parserRequest.err(),
context.extensions,
getJvmArguments(context.rootDirectory),
(MavenOptions) context.options);
}

View File

@ -45,11 +45,11 @@ public ResidentMavenInvoker(Lookup protoLookup) {
@Override
public void close() throws InvokerException {
ArrayList<InvokerException> exceptions = new ArrayList<>();
ArrayList<Exception> exceptions = new ArrayList<>();
for (MavenContext context : residentContext.values()) {
try {
context.doCloseContainer();
} catch (InvokerException e) {
} catch (Exception e) {
exceptions.add(e);
}
}

View File

@ -38,6 +38,7 @@ public class EncryptInvokerRequest extends BaseInvokerRequest {
@SuppressWarnings("ParameterNumber")
public EncryptInvokerRequest(
ParserRequest parserRequest,
boolean parsingFailed,
Path cwd,
Path installationDirectory,
Path userHomeDirectory,
@ -49,10 +50,10 @@ public EncryptInvokerRequest(
OutputStream out,
OutputStream err,
List<CoreExtension> coreExtensions,
List<String> jvmArguments,
EncryptOptions options) {
super(
parserRequest,
parsingFailed,
cwd,
installationDirectory,
userHomeDirectory,
@ -63,8 +64,7 @@ public EncryptInvokerRequest(
in,
out,
err,
coreExtensions,
jvmArguments);
coreExtensions);
this.options = requireNonNull(options);
}

View File

@ -23,15 +23,25 @@
import org.apache.commons.cli.ParseException;
import org.apache.maven.api.cli.Options;
import org.apache.maven.api.cli.ParserException;
import org.apache.maven.api.cli.mvnenc.EncryptOptions;
import org.apache.maven.cling.invoker.BaseParser;
public class EncryptParser extends BaseParser {
@Override
protected EncryptOptions emptyOptions() {
try {
return CommonsCliEncryptOptions.parse(new String[0]);
} catch (ParseException e) {
throw new IllegalArgumentException(e);
}
}
@Override
protected EncryptInvokerRequest getInvokerRequest(LocalContext context) {
return new EncryptInvokerRequest(
context.parserRequest,
context.parsingFailed,
context.cwd,
context.installationDirectory,
context.userHomeDirectory,
@ -43,20 +53,19 @@ protected EncryptInvokerRequest getInvokerRequest(LocalContext context) {
context.parserRequest.out(),
context.parserRequest.err(),
context.extensions,
getJvmArguments(context.rootDirectory),
(EncryptOptions) context.options);
}
@Override
protected List<Options> parseCliOptions(LocalContext context) throws ParserException {
protected List<Options> parseCliOptions(LocalContext context) {
return Collections.singletonList(parseEncryptCliOptions(context.parserRequest.args()));
}
protected CommonsCliEncryptOptions parseEncryptCliOptions(List<String> args) throws ParserException {
protected CommonsCliEncryptOptions parseEncryptCliOptions(List<String> args) {
try {
return CommonsCliEncryptOptions.parse(args.toArray(new String[0]));
} catch (ParseException e) {
throw new ParserException("Failed to parse command line options: " + e.getMessage(), e);
throw new IllegalArgumentException("Failed to parse command line options: " + e.getMessage(), e);
}
}

View File

@ -38,6 +38,7 @@ public class ShellInvokerRequest extends BaseInvokerRequest {
@SuppressWarnings("ParameterNumber")
public ShellInvokerRequest(
ParserRequest parserRequest,
boolean parsingFailed,
Path cwd,
Path installationDirectory,
Path userHomeDirectory,
@ -49,10 +50,10 @@ public ShellInvokerRequest(
OutputStream out,
OutputStream err,
List<CoreExtension> coreExtensions,
List<String> jvmArguments,
ShellOptions options) {
super(
parserRequest,
parsingFailed,
cwd,
installationDirectory,
userHomeDirectory,
@ -63,8 +64,7 @@ public ShellInvokerRequest(
in,
out,
err,
coreExtensions,
jvmArguments);
coreExtensions);
this.options = requireNonNull(options);
}

View File

@ -23,15 +23,24 @@
import org.apache.commons.cli.ParseException;
import org.apache.maven.api.cli.Options;
import org.apache.maven.api.cli.ParserException;
import org.apache.maven.api.cli.mvnsh.ShellOptions;
import org.apache.maven.cling.invoker.BaseParser;
public class ShellParser extends BaseParser {
@Override
protected ShellOptions emptyOptions() {
try {
return CommonsCliShellOptions.parse(new String[0]);
} catch (ParseException e) {
throw new IllegalArgumentException(e);
}
}
@Override
protected ShellInvokerRequest getInvokerRequest(LocalContext context) {
return new ShellInvokerRequest(
context.parserRequest,
context.parsingFailed,
context.cwd,
context.installationDirectory,
context.userHomeDirectory,
@ -43,20 +52,19 @@ protected ShellInvokerRequest getInvokerRequest(LocalContext context) {
context.parserRequest.out(),
context.parserRequest.err(),
context.extensions,
getJvmArguments(context.rootDirectory),
(ShellOptions) context.options);
}
@Override
protected List<Options> parseCliOptions(LocalContext context) throws ParserException {
protected List<Options> parseCliOptions(LocalContext context) {
return Collections.singletonList(parseShellCliOptions(context.parserRequest.args()));
}
protected CommonsCliShellOptions parseShellCliOptions(List<String> args) throws ParserException {
protected CommonsCliShellOptions parseShellCliOptions(List<String> args) {
try {
return CommonsCliShellOptions.parse(args.toArray(new String[0]));
} catch (ParseException e) {
throw new ParserException("Failed to parse command line options: " + e.getMessage(), e);
throw new IllegalArgumentException("Failed to parse command line options: " + e.getMessage(), e);
}
}

View File

@ -143,11 +143,9 @@ private List<Completers.OptDesc> commandOptions(String command) {
private void mvn(CommandInput input) {
try {
shellMavenInvoker.invoke(mavenParser.parseInvocation(ParserRequest.mvn(
input.args(),
shellContext.invokerRequest.logger(),
shellContext.invokerRequest.messageBuilderFactory())
.build()));
shellMavenInvoker.invoke(mavenParser.parseInvocation(
ParserRequest.mvn(input.args(), shellContext.invokerRequest.messageBuilderFactory())
.build()));
} catch (Exception e) {
saveException(e);
}
@ -164,11 +162,9 @@ private List<Completer> mvnCompleter(String name) {
private void mvnenc(CommandInput input) {
try {
shellEncryptInvoker.invoke(encryptParser.parseInvocation(ParserRequest.mvnenc(
input.args(),
shellContext.invokerRequest.logger(),
shellContext.invokerRequest.messageBuilderFactory())
.build()));
shellEncryptInvoker.invoke(encryptParser.parseInvocation(
ParserRequest.mvnenc(input.args(), shellContext.invokerRequest.messageBuilderFactory())
.build()));
} catch (Exception e) {
saveException(e);
}

View File

@ -28,8 +28,8 @@
import com.google.common.jimfs.Configuration;
import com.google.common.jimfs.Jimfs;
import org.apache.maven.api.cli.Invoker;
import org.apache.maven.api.cli.InvokerException;
import org.apache.maven.api.cli.Parser;
import org.apache.maven.api.cli.ParserException;
import org.apache.maven.cling.invoker.ProtoLookup;
import org.codehaus.plexus.classworlds.ClassWorld;
import org.junit.jupiter.api.Disabled;
@ -93,7 +93,7 @@ void conflictingExtensions(
Path userExtensions = userConf.resolve("extensions.xml");
Files.writeString(userExtensions, extensionsXml);
assertThrows(ParserException.class, () -> invoke(cwd, userHome, Arrays.asList("clean", "verify")));
assertThrows(InvokerException.class, () -> invoke(cwd, userHome, Arrays.asList("clean", "verify")));
}
@Test

View File

@ -30,7 +30,6 @@
import org.apache.maven.api.cli.Invoker;
import org.apache.maven.api.cli.Parser;
import org.apache.maven.api.cli.ParserRequest;
import org.apache.maven.cling.invoker.ProtoLogger;
import org.apache.maven.jline.JLineMessageBuilderFactory;
import org.junit.jupiter.api.Assumptions;
@ -110,8 +109,8 @@ protected Map<String, String> invoke(Path cwd, Path userHome, Collection<String>
Path logFile = cwd.resolve(goal + "-build.log").toAbsolutePath();
List<String> mvnArgs = new ArrayList<>(args);
mvnArgs.addAll(List.of("-l", logFile.toString(), goal));
int exitCode = invoker.invoke(parser.parseInvocation(
ParserRequest.mvn(mvnArgs, new ProtoLogger(), new JLineMessageBuilderFactory())
int exitCode = invoker.invoke(
parser.parseInvocation(ParserRequest.mvn(mvnArgs, new JLineMessageBuilderFactory())
.cwd(cwd)
.userHome(userHome)
.build()));

View File

@ -62,6 +62,12 @@ under the License.
<classifier>bin</classifier>
<type>zip</type>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>*</groupId>
<artifactId>*</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>