You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@maven.apache.org by gn...@apache.org on 2026/10/15 07:16:21 UTC
(maven) branch maven-4.0.x updated: [Backport 4.0.x] fix: print usage when no goals and no POM are specified, improve -f error messages (#13510)
This is an automated email from the ASF dual-hosted git repository.
gnodet pushed a commit to branch maven-4.0.x
in repository https://gitbox.apache.org/repos/asf/maven.git
The following commit(s) were added to refs/heads/maven-4.0.x by this push:
new f534244574 [Backport 4.0.x] fix: print usage when no goals and no POM are specified, improve -f error messages (#13510)
f534244574 is described below
commit f534244574971946dc0f24c325cf2985ac7a1d37
Author: Guillaume Nodet <gn...@gmail.com>
AuthorDate: Thu Oct 15 09:16:16 2026 +0200
[Backport 4.0.x] fix: print usage when no goals and no POM are specified, improve -f error messages (#13510)
* fix: print usage when no goals and no POM are specified, improve -f error messages
- Print usage (same output as `mvn -h`) and exit 0 when `mvn` is invoked with no arguments and no POM is found; avoids confusing BUILD FAILURE for new users
- Clean up -f/--file missing-file error: log the self-explanatory IAE message directly instead of wrapping it in an opaque 'Error determining top directory' prefix
- Fix double error message when -f points to a non-existent POM file
- Fix shell scripts (mvn, mvn.cmd) to not exit when -f argument does not exist; add [ERROR] prefix to Windows cmd error messages
- Catch IllegalArgumentException in ProjectModelResolver and wrap as ModelBuilderException to avoid unexpected exception propagation
- Style: collapse duplicate catch blocks into multi-catch
(cherry picked from commit f4ddd1829d4aaac468db9e643796614da9a1b55b)
* fix: add missing assertTrue import in ProjectModelResolverTest
---
apache-maven/src/assembly/maven/bin/mvn | 11 ++----
apache-maven/src/assembly/maven/bin/mvn.cmd | 4 +-
.../org/apache/maven/cling/invoker/BaseParser.java | 10 +++++
.../maven/cling/invoker/mvn/MavenInvoker.java | 10 +++++
.../apache/maven/project/ProjectModelResolver.java | 4 +-
.../maven/project/ProjectModelResolverTest.java | 43 +++++++++++++++++++---
6 files changed, 65 insertions(+), 17 deletions(-)
diff --git a/apache-maven/src/assembly/maven/bin/mvn b/apache-maven/src/assembly/maven/bin/mvn
index da61fa8a59..15e899f678 100755
--- a/apache-maven/src/assembly/maven/bin/mvn
+++ b/apache-maven/src/assembly/maven/bin/mvn
@@ -179,14 +179,11 @@ find_file_argument_basedir() {
elif [ -f "${arg}" ]; then
basedir=`dirname "${arg}"`
basedir=`cd "$basedir" && pwd -P`
- if [ ! -d "$basedir" ]; then
- echo "Directory $basedir extracted from the -f/--file command-line argument ${arg} does not exist" >&2
- exit 1
- fi
- else
- echo "POM file ${arg} specified with the -f/--file command line argument does not exist" >&2
- exit 1
fi
+ # If the argument does not exist (e.g. a Cygwin/MSYS path that will be
+ # converted later, or a genuinely missing file), fall back to the current
+ # directory. The Java layer validates the path and produces the proper
+ # error message when the file is truly absent.
break
fi
if [ "$arg" = "-f" -o "$arg" = "--file" ]; then
diff --git a/apache-maven/src/assembly/maven/bin/mvn.cmd b/apache-maven/src/assembly/maven/bin/mvn.cmd
index 9841a3a9d1..67a147ca8d 100644
--- a/apache-maven/src/assembly/maven/bin/mvn.cmd
+++ b/apache-maven/src/assembly/maven/bin/mvn.cmd
@@ -131,7 +131,7 @@ if "%FILE_ARG%" == "" (
goto findBaseDir
)
if not exist "%FILE_ARG%" (
- echo POM file "%FILE_ARG%" specified the -f/--file command-line argument does not exist >&2
+ echo [ERROR] POM file "%FILE_ARG%" specified with the -f/--file command-line argument does not exist >&2
goto error
)
if exist "%FILE_ARG%\*" (
@@ -140,7 +140,7 @@ if exist "%FILE_ARG%\*" (
call :get_directory_from_file "%FILE_ARG%"
)
if not exist "%POM_DIR%" (
- echo Directory "%POM_DIR%" extracted from the -f/--file command-line argument "%FILE_ARG%" does not exist >&2
+ echo [ERROR] Directory "%POM_DIR%" extracted from the -f/--file command-line argument "%FILE_ARG%" does not exist >&2
goto error
)
set "WDIR=%POM_DIR%"
diff --git a/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/BaseParser.java b/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/BaseParser.java
index 5f79b1dc1d..57f8fc315c 100644
--- a/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/BaseParser.java
+++ b/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/BaseParser.java
@@ -137,6 +137,12 @@ public InvokerRequest parseInvocation(ParserRequest parserRequest) {
// top/root
try {
context.topDirectory = getTopDirectory(context);
+ } catch (IllegalArgumentException e) {
+ // User-facing error (e.g. -f points to a non-existent file): report the message directly,
+ // without wrapping it in an internal "Error determining top directory" prefix.
+ context.parsingFailed = true;
+ context.topDirectory = context.cwd;
+ parserRequest.logger().error(e.getMessage());
} catch (Exception e) {
context.parsingFailed = true;
context.topDirectory = context.cwd;
@@ -144,6 +150,10 @@ public InvokerRequest parseInvocation(ParserRequest parserRequest) {
}
try {
context.rootDirectory = getRootDirectory(context);
+ } catch (IllegalArgumentException e) {
+ context.parsingFailed = true;
+ context.rootDirectory = context.cwd;
+ parserRequest.logger().error(e.getMessage());
} catch (Exception e) {
context.parsingFailed = true;
context.rootDirectory = context.cwd;
diff --git a/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvn/MavenInvoker.java b/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvn/MavenInvoker.java
index e6372ccfd8..03357c5f09 100644
--- a/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvn/MavenInvoker.java
+++ b/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvn/MavenInvoker.java
@@ -36,6 +36,7 @@
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;
@@ -132,6 +133,15 @@ protected void postCommands(MavenContext context) throws Exception {
} else if (context.options().strictChecksums().orElse(false)) {
logger.info("Enabling strict checksum verification on all artifact downloads.");
}
+
+ // If no goals/phases were specified and there is no POM in the current directory,
+ // print usage (like `mvn -h`) and exit successfully instead of failing with
+ // "No goals have been specified for this build."
+ // When a POM is present the default goal (if any) may apply, so let Maven proceed normally.
+ if (context.options().goals().orElse(List.of()).isEmpty() && determinePom(context, context.lookup) == null) {
+ context.options().displayHelp(context.invokerRequest.parserRequest(), determineWriter(context));
+ throw new InvokerException.ExitException(0);
+ }
}
protected void toolchains(MavenContext context, MavenExecutionRequest request) throws Exception {
diff --git a/impl/maven-core/src/main/java/org/apache/maven/project/ProjectModelResolver.java b/impl/maven-core/src/main/java/org/apache/maven/project/ProjectModelResolver.java
index 7b03a2d87b..f6b389d409 100644
--- a/impl/maven-core/src/main/java/org/apache/maven/project/ProjectModelResolver.java
+++ b/impl/maven-core/src/main/java/org/apache/maven/project/ProjectModelResolver.java
@@ -217,7 +217,7 @@ public ModelSource resolveModel(final Parent parent) throws UnresolvableModelExc
parent.setVersion(versionRangeResult.getHighestVersion().toString());
return resolveModel(parent.getGroupId(), parent.getArtifactId(), parent.getVersion());
- } catch (final VersionRangeResolutionException e) {
+ } catch (final VersionRangeResolutionException | IllegalArgumentException e) {
throw new UnresolvableModelException(
e.getMessage(), parent.getGroupId(), parent.getArtifactId(), parent.getVersion(), e);
}
@@ -260,7 +260,7 @@ public ModelSource resolveModel(final Dependency dependency) throws Unresolvable
dependency.setVersion(versionRangeResult.getHighestVersion().toString());
return resolveModel(dependency.getGroupId(), dependency.getArtifactId(), dependency.getVersion());
- } catch (VersionRangeResolutionException e) {
+ } catch (VersionRangeResolutionException | IllegalArgumentException e) {
throw new UnresolvableModelException(
e.getMessage(), dependency.getGroupId(), dependency.getArtifactId(), dependency.getVersion(), e);
}
diff --git a/impl/maven-core/src/test/java/org/apache/maven/project/ProjectModelResolverTest.java b/impl/maven-core/src/test/java/org/apache/maven/project/ProjectModelResolverTest.java
index 522fc58e03..19e7ebad16 100644
--- a/impl/maven-core/src/test/java/org/apache/maven/project/ProjectModelResolverTest.java
+++ b/impl/maven-core/src/test/java/org/apache/maven/project/ProjectModelResolverTest.java
@@ -40,6 +40,7 @@
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
/**
* Test cases for the project {@code ModelResolver} implementation.
@@ -56,7 +57,7 @@ class ProjectModelResolverTest extends AbstractMavenProjectTestCase {
}
@Test
- void testResolveParentThrowsUnresolvableModelExceptionWhenNotFound() throws Exception {
+ void testResolveParentThrowsUnresolvableModelExceptionWhenNotFound() {
final Parent parent = new Parent();
parent.setGroupId("org.apache");
parent.setArtifactId("apache");
@@ -71,7 +72,7 @@ void testResolveParentThrowsUnresolvableModelExceptionWhenNotFound() throws Exce
}
@Test
- void testResolveParentThrowsUnresolvableModelExceptionWhenNoMatchingVersionFound() throws Exception {
+ void testResolveParentThrowsUnresolvableModelExceptionWhenNoMatchingVersionFound() {
final Parent parent = new Parent();
parent.setGroupId("org.apache");
parent.setArtifactId("apache");
@@ -85,7 +86,7 @@ void testResolveParentThrowsUnresolvableModelExceptionWhenNoMatchingVersionFound
}
@Test
- void testResolveParentThrowsUnresolvableModelExceptionWhenUsingRangesWithoutUpperBound() throws Exception {
+ void testResolveParentThrowsUnresolvableModelExceptionWhenUsingRangesWithoutUpperBound() {
final Parent parent = new Parent();
parent.setGroupId("org.apache");
parent.setArtifactId("apache");
@@ -98,6 +99,21 @@ void testResolveParentThrowsUnresolvableModelExceptionWhenUsingRangesWithoutUppe
assertEquals("The requested parent version range '[1,)' does not specify an upper bound", e.getMessage());
}
+ @Test
+ void testResolveParentThrowsUnresolvableModelExceptionWhenUsingWrongRangeFormat() {
+ final Parent parent = new Parent();
+ parent.setGroupId("org.apache");
+ parent.setArtifactId("apache");
+ parent.setVersion("[1:2)");
+
+ UnresolvableModelException e = assertThrows(
+ UnresolvableModelException.class,
+ () -> newModelResolver().resolveModel(parent),
+ "Expected 'UnresolvableModelException' not thrown.");
+ assertNotNull(e.getMessage());
+ assertTrue(e.getMessage().contains("[1:2)"));
+ }
+
@Test
void testResolveParentSuccessfullyResolvesExistingParentWithoutRange() throws Exception {
final Parent parent = new Parent();
@@ -121,7 +137,7 @@ void testResolveParentSuccessfullyResolvesExistingParentUsingHighestVersion() th
}
@Test
- void testResolveDependencyThrowsUnresolvableModelExceptionWhenNotFound() throws Exception {
+ void testResolveDependencyThrowsUnresolvableModelExceptionWhenNotFound() {
final Dependency dependency = new Dependency();
dependency.setGroupId("org.apache");
dependency.setArtifactId("apache");
@@ -136,7 +152,7 @@ void testResolveDependencyThrowsUnresolvableModelExceptionWhenNotFound() throws
}
@Test
- void testResolveDependencyThrowsUnresolvableModelExceptionWhenNoMatchingVersionFound() throws Exception {
+ void testResolveDependencyThrowsUnresolvableModelExceptionWhenNoMatchingVersionFound() {
final Dependency dependency = new Dependency();
dependency.setGroupId("org.apache");
dependency.setArtifactId("apache");
@@ -150,7 +166,7 @@ void testResolveDependencyThrowsUnresolvableModelExceptionWhenNoMatchingVersionF
}
@Test
- void testResolveDependencyThrowsUnresolvableModelExceptionWhenUsingRangesWithoutUpperBound() throws Exception {
+ void testResolveDependencyThrowsUnresolvableModelExceptionWhenUsingRangesWithoutUpperBound() {
final Dependency dependency = new Dependency();
dependency.setGroupId("org.apache");
dependency.setArtifactId("apache");
@@ -163,6 +179,21 @@ void testResolveDependencyThrowsUnresolvableModelExceptionWhenUsingRangesWithout
assertEquals("The requested dependency version range '[1,)' does not specify an upper bound", e.getMessage());
}
+ @Test
+ void testResolveDependencyThrowsUnresolvableModelExceptionWhenUsingWrongRangeFormat() {
+ final Dependency dependency = new Dependency();
+ dependency.setGroupId("org.apache");
+ dependency.setArtifactId("apache");
+ dependency.setVersion("[2:3)");
+
+ UnresolvableModelException e = assertThrows(
+ UnresolvableModelException.class,
+ () -> newModelResolver().resolveModel(dependency),
+ "Expected 'UnresolvableModelException' not thrown.");
+ assertNotNull(e.getMessage());
+ assertTrue(e.getMessage().contains("[2:3)"));
+ }
+
@Test
void testResolveDependencySuccessfullyResolvesExistingDependencyWithoutRange() throws Exception {
final Dependency dependency = new Dependency();