picocli
picocli copied to clipboard
Return a nonzero exit code when help/usage is displayed
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.
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 { //...
You may need to do this in the @Command
annotation for the subcommands also...
I already have the exitCodeOnUsageHelp parameter in the annotation. It's not working.
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 {
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.
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);
}
}
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);
}
}
(By the way, picocli does not throw any checked exceptions, so I believe you can omit the try/catch from the main method.)
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.
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.
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?
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.
I wonder if I should have put quotes around 666 for the annotation...
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! 😅
@elyograg were you able to reproduce the 666 vs 154 issue you mentioned? Could you provide a program to reproduce it?
@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.
Yes, this is several months later, but… It's probably because exit code is always 8 bits? because 666 % 256 = 154
Good point! Java can return any int value as exit code, but the OS or shell may truncate it to a single byte…