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();