Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Return a nonzero exit code when help/usage is displayed #2024

Open
elyograg opened this issue May 14, 2023 · 18 comments
Open

Return a nonzero exit code when help/usage is displayed #2024

elyograg opened this issue May 14, 2023 · 18 comments

Comments

@elyograg
Copy link

The picoclii verson is 4.7.3, running with OpenJDK 17 on Ubuntu Server 22.04. The gradle project builds for compatibility with Java 11.

I can't figure out how to have my program return a nonzero exit code if it displays the usage help. I need my script to do something different based on the exit code. Changng the exit code is easy enough in my own program logic, but I can't figure out how to have picocli do it automatically.

This is the main method I have concocted with help from the documentation, but if I run it without any of the required parameters, its exit code is still zero:

  public static final void main(final String[] args) {
    try {
      final CommandLine cmd = new CommandLine(new MailCheckMain());
      cmd.setHelpFactory(StaticStuff.createLeftAlignedUsageHelp());
      cmd.getCommandSpec().exitCodeOnUsageHelp(1).exitCodeOnVersionHelp(1);
      cmd.execute(args);
    } catch (final Exception e) {
      throw new RuntimeException("Error starting program", e);
    }
  }

This particular class does implement Runnable, so my logic can be found in the run() method. I must be missing something.

@remkop
Copy link
Owner

remkop commented May 14, 2023

I think this can be accomplished in the annotations.
See: https://picocli.info/#_exception_exit_codes
And https://picocli.info/apidocs-all/info.picocli/picocli/CommandLine.Command.html#exitCodeOnUsageHelp()

@Command(exitCodeOnUsageHelp = 42) 
class Cmd { //...

@remkop
Copy link
Owner

remkop commented May 14, 2023

You may need to do this in the @Command annotation for the subcommands also...

@elyograg
Copy link
Author

I already have the exitCodeOnUsageHelp parameter in the annotation. It's not working.

@elyograg
Copy link
Author

This is the class signature:

@Command(name = "mail_status_check", sortOptions = false, scope = ScopeType.INHERIT, description = "End to End mail checker", exitCodeOnUsageHelp = 1)
public class MailCheckMain implements Runnable {

@elyograg
Copy link
Author

If you have any interest, I can share a repo URL for it. It's not on github. I can't find a way to send a private message on github, or I would have already sent it.

@remkop
Copy link
Owner

remkop commented May 15, 2023

Let's try to get a minimal example that reproduces the issue.
This test passes for me. Does it pass for you?

import org.junit.jupiter.api.Test;
import picocli.CommandLine;

import static org.junit.jupiter.api.Assertions.assertEquals;

public class ExitCodeTest {
    @Test
    public void testStandardExitCode() {
        @CommandLine.Command(mixinStandardHelpOptions = true)
        class App implements Runnable {
            public void run() { }
        }

        CommandLine cmd = new CommandLine(new App());
        int exitCode = cmd.execute("-h");
        assertEquals(CommandLine.ExitCode.OK, exitCode);
        int exitCodeVersion = cmd.execute("-V");
        assertEquals(CommandLine.ExitCode.OK, exitCodeVersion);
    }

    @Test
    public void testCustomExitCode() {
        @CommandLine.Command(mixinStandardHelpOptions = true, exitCodeOnUsageHelp = 33, exitCodeOnVersionHelp = 44)
        class App implements Runnable {
            public void run() { }
        }

        CommandLine cmd = new CommandLine(new App());
        int exitCode = cmd.execute("-h");
        assertEquals(33, exitCode);
        int exitCodeVersion = cmd.execute("-V");
        assertEquals(44, exitCodeVersion);
    }
}

@remkop
Copy link
Owner

remkop commented May 15, 2023

Okay, looking at your original code, I can see the answer:
the code needs to call System.exit from the main method with the result of CommandLine::execute.

I believe this will work:

  public static final void main(final String[] args) {
    try {
      final CommandLine cmd = new CommandLine(new MailCheckMain());
      cmd.setHelpFactory(StaticStuff.createLeftAlignedUsageHelp());
      cmd.getCommandSpec().exitCodeOnUsageHelp(1).exitCodeOnVersionHelp(1);
      int exitCode = cmd.execute(args);
      System.exit(exitCode);
    } catch (final Exception e) {
      throw new RuntimeException("Error starting program", e);
    }
  }

@remkop
Copy link
Owner

remkop commented May 15, 2023

(By the way, picocli does not throw any checked exceptions, so I believe you can omit the try/catch from the main method.)

@elyograg
Copy link
Author

Yep, the test worked. I added the System.exit() and now it all works. And I removed the try/catch with no compile errors. It's basically a reflex adding that, because it's very often required.

@elyograg
Copy link
Author

Or maybe it's not completely correct. Running with no options kicks back an exit code of 2, but I set both of the exit codes to 666, so I could be absolutely sure that the exit code would be very unique for usage printing. It's nonzero now which makes my script work better, but not completely right.

@elyograg elyograg reopened this May 15, 2023
@remkop
Copy link
Owner

remkop commented May 15, 2023

The built-in exit codes are as follows:

By default, the execute method returns CommandLine.ExitCode.OK (0) on success, CommandLine.ExitCode.SOFTWARE (1) when an exception occurred in the Runnable, Callable or command method, and CommandLine.ExitCode.USAGE (2) for invalid input. (These are common values according to this StackOverflow answer). This can be customized with the @Command annotation.

Running with no options kicks back an exit code of 2 ... It's nonzero now which makes my script work better, but not completely right.

As I recall your command requires a subcommand to be specified, so an exit code of 2 (incorrect usage) seems correct:
When a required option or required subcommand is missing, the execute method will return exit code 2. Is your expectation different?

@elyograg
Copy link
Author

As I recall your command requires a subcommand to be specified, so an exit code of 2 (incorrect usage) seems correct:
When a required option or required subcommand is missing, the execute method will return exit code 2. Is your expectation different?

This is a different project than the one that requires a subcommand. For this one, only the one Command class is defined. Picocli is my new hammer, and I am finding a lot of nails to use it on. :) It's a GREAT tool.

I had set both the annotation parameter exitCodeOnUsageHelp = 666 and cmd.getCommandSpec().exitCodeOnUsageHelp(666); so I was expecting to see an exit code of 666. I remember that with those setting, the exit code was 154 ... no idea at all where that came from.

The value of 2 is something I can use, and I have done so, but I was hoping for the very distinctive value.

@elyograg
Copy link
Author

I wonder if I should have put quotes around 666 for the annotation...

@remkop
Copy link
Owner

remkop commented May 16, 2023

Can you provide a test (maybe like what I did above) to demonstrate the 666 vs 154 issue you mention?

Quotes in the annotations won’t compile (int value only).

And glad to hear you are enjoying using picocli! 😅

@remkop
Copy link
Owner

remkop commented May 25, 2023

@elyograg were you able to reproduce the 666 vs 154 issue you mentioned? Could you provide a program to reproduce it?

@elyograg
Copy link
Author

@elyograg were you able to reproduce the 666 vs 154 issue you mentioned? Could you provide a program to reproduce it?

I will need to make a minimal project and experiment.

@ambre-m
Copy link

ambre-m commented Apr 2, 2024

Yes, this is several months later, but… It's probably because exit code is always 8 bits? because 666 % 256 = 154

@remkop
Copy link
Owner

remkop commented Apr 2, 2024

Good point! Java can return any int value as exit code, but the OS or shell may truncate it to a single byte…

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants