Picocli extension for Quarkus Command Mode

109 views
Skip to first unread message

Remko Popma

unread,
May 6, 2020, 8:20:45 PM5/6/20
to Quarkus Development mailing list
Hi all,

I've started working on a Quarkus extension to facilitate the use of picocli with the Command Mode introduced in Quarkus 1.4.

The objectives of this extension are:
  • enable dependency injection in picocli subcommands and other classes specified via annotations
  • provide documentation for using picocli with Quarkus Command Mode
picocli integration with CDI containers
The canonical programming model for picocli is to annotate a command class with @Command(subcommands = Another.class).
Picocli will instantiate the Another class when it matches that subcommand in the user input.
This instantiation is customizable via the IFactory interface.
The default factory simply calls the constructor, so no components are injected into this subcommand. Sadness.
The extension should provide a factory implementation that uses ARC or CDI, with documentation on how to use it (and in general how to use picocli with Command Mode Quarkus).

Timeline
I hope to be able to get this included in Quarkus 1.5 since my understanding is that Quarkus 1.6 will drop support for Java 8.

Status
The fork has the basic scaffolding for an extension (deployment, runtime, integration tests and docs).
The basic factory is done, tests and documentation are early stage and are still work in progress.
Feedback welcome!

Questions
Scope
Did I miss anything? If anyone has questions or feedback on the above, that would be great.

How to proceed
Should I create a GitHub issue? (Unsure what the community customs are for discussing on this list vs creating GitHub issues.)

Remko


Stuart Douglas

unread,
May 6, 2020, 8:53:24 PM5/6/20
to Remko Popma, Quarkus Development mailing list
Will this allow us to use the annotation based parameter parsing for non command based systems?

There was an example in another thread, where a user wanted to be able to specify kafka topics on the command line. I suggested that we would
likely want some way of binding command line parameters to Quarkus configuration values. Could we extend Picoli to support this?

Stuart

--
You received this message because you are subscribed to the Google Groups "Quarkus Development mailing list" group.
To unsubscribe from this group and stop receiving emails from it, send an email to quarkus-dev...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/quarkus-dev/83fd5231-3126-4fa5-81ca-1e3fc9be24e1%40googlegroups.com.

Remko Popma

unread,
May 6, 2020, 9:25:09 PM5/6/20
to Quarkus Development mailing list
Hi Stuart,

I believe you are referring to this thread: https://github.com/quarkusio/quarkus/issues/9058
Yes, this should be possible.

The only question I have is when the `@ConfigBinding` annotation would be evaluated:

  1. Quarkus first does some initialization, then invokes the QuarkusApplication::run method, which starts picocli command line parsing.
  2. The canonical way to use picocli is to construct a CommandLine instance with your command and call execute on it with the command line args. Picocli will then populate `@Option` and `@Parameters`-annotated fields and invoke the `run` or `call` method of your command. (There is an alternative parseArgs method that can be used instead of execute that only populates the annotated fields and then returns.)
  3. Once the `run` or `call` method is invoked (or parseArgs returns), the `@Option` and `@Parameters`-annotated fields have been initialized, so from this point on, that field can be used as a source of configuration values.

Does this match your expectations?

For simple cases (without subcommands), the extension may not even be necessary.
To expand on Max Andersen's example, the code below should already work with Quarkus 1.4+:

//import io.quarkus.picocli.QuarkusPicocliFactory;
import io.quarkus.runtime.Quarkus;
import io.quarkus.runtime.QuarkusApplication;
import io.quarkus.runtime.annotations.QuarkusMain;
import org.eclipse.microprofile.config.inject.ConfigProperty;
import org.eclipse.microprofile.context.ManagedExecutor;
import picocli.CommandLine;
import picocli.CommandLine.Command;
import picocli.CommandLine.Mixin;
import picocli.CommandLine.Option;
import picocli.CommandLine.Parameters;

import javax.enterprise.context.ApplicationScoped;
import javax.inject.Inject;
import java.util.List;
import java.util.concurrent.Callable;

@QuarkusMain
@ApplicationScoped
@Command(name = "configdemo", mixinStandardHelpOptions = true)
public class ConfigurationExample implements Callable<Integer>, QuarkusApplication {
//@Inject
//QuarkusPicocliFactory factory; // requires the picocli extension, but not needed here

@Option(names = {"-t", "--topic"}, description = "Specifies a Kafka Topic to connect to")
@ConfigBinding("quarkus.kafka-streams.topics")
List<String> topics;

@Override
public Integer call() throws Exception {
System.out.printf("Now do something with topics %s...%n", topics);
return 0;
}

@Override
public int run(String... args) throws Exception {
//int exitCode = new CommandLine(this, factory).execute(args); // requires the picocli extension (but not needed here)
int exitCode = new CommandLine(this).execute(args);
System.out.printf("%s exiting with %d...%n", getClass().getName(), exitCode);
return exitCode;
}

public static void main(String[] args) {
System.setProperty("quarkus.banner.enabled", "false");
Quarkus.run(ConfigurationExample.class, args);
}
}

To unsubscribe from this group and stop receiving emails from it, send an email to quark...@googlegroups.com.

Remko Popma

unread,
May 6, 2020, 9:34:57 PM5/6/20
to Quarkus Development mailing list
Thinking about this some more, we can also set a custom execution strategy to do further initialization based on this `@ConfigBinding` annotation before invoking the command's run or call method.

Stuart Douglas

unread,
May 7, 2020, 8:14:39 PM5/7/20
to Remko Popma, Quarkus Development mailing list
On Thu, 7 May 2020 at 11:25, Remko Popma <remko...@gmail.com> wrote:
Hi Stuart,

I believe you are referring to this thread: https://github.com/quarkusio/quarkus/issues/9058
Yes, this should be possible.

The only question I have is when the `@ConfigBinding` annotation would be evaluated:

  1. Quarkus first does some initialization, then invokes the QuarkusApplication::run method, which starts picocli command line parsing.
  2. The canonical way to use picocli is to construct a CommandLine instance with your command and call execute on it with the command line args. Picocli will then populate `@Option` and `@Parameters`-annotated fields and invoke the `run` or `call` method of your command. (There is an alternative parseArgs method that can be used instead of execute that only populates the annotated fields and then returns.)
  3. Once the `run` or `call` method is invoked (or parseArgs returns), the `@Option` and `@Parameters`-annotated fields have been initialized, so from this point on, that field can be used as a source of configuration values.

Does this match your expectations?

Mostly, I think we will want to run parseArgs early in the lifecycle, to make sure that everything is initialized and available. We may have applications that are not really picocli apps (as they don't have a Command, they are just running a web server), but it still wants to parse arguments, print usage info etc. 

It sounds like this still should be possible just by manually calling parseArgs ?

Stuart
 
To unsubscribe from this group and stop receiving emails from it, send an email to quarkus-dev...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/quarkus-dev/24a274c0-2ef2-43d4-aa3b-b684c42b2d23%40googlegroups.com.

Remko Popma

unread,
May 7, 2020, 9:50:48 PM5/7/20
to Quarkus Development mailing list


On Friday, May 8, 2020 at 9:14:39 AM UTC+9, Stuart Douglas wrote:


On Thu, 7 May 2020 at 11:25, Remko Popma <remko...@gmail.com> wrote:
Hi Stuart,

I believe you are referring to this thread: https://github.com/quarkusio/quarkus/issues/9058
Yes, this should be possible.

The only question I have is when the `@ConfigBinding` annotation would be evaluated:

  1. Quarkus first does some initialization, then invokes the QuarkusApplication::run method, which starts picocli command line parsing.
  2. The canonical way to use picocli is to construct a CommandLine instance with your command and call execute on it with the command line args. Picocli will then populate `@Option` and `@Parameters`-annotated fields and invoke the `run` or `call` method of your command. (There is an alternative parseArgs method that can be used instead of execute that only populates the annotated fields and then returns.)
  3. Once the `run` or `call` method is invoked (or parseArgs returns), the `@Option` and `@Parameters`-annotated fields have been initialized, so from this point on, that field can be used as a source of configuration values.

Does this match your expectations?

Mostly, I think we will want to run parseArgs early in the lifecycle, to make sure that everything is initialized and available. We may have applications that are not really picocli apps (as they don't have a Command, they are just running a web server), but it still wants to parse arguments, print usage info etc. 

It sounds like this still should be possible just by manually calling parseArgs ?

Yes, calling parseArgs is one way of doing it. Another way would be to use the execute method, and install a custom execution strategy.

The parseArgs method:
  • parseArgs is a low-level method that only parses user input and populates annotated fields.
  • If the user specified invalid input, parseArgs will throw a ParameterException.
  • The parseArgs method does not show the usage help message or version information automatically; we need to check and do this in the code that calls parseArgs.
  • See the picocli user manual section DIY execution for a recipe of what code that uses parseArgs directly looks like.

By contrast, the execute method:
  • the execute method first invokes parseArgs
  • then checks if the user requested help or version info, and if so prints the requested info to the standard output stream and returns an exit code
  • catches any ParameterException and prints a user-friendly message to the standard error stream
  • next, calls the execution strategy. The default strategy expects the annotated class to implement either Runnable or Callable, and invokes its run or call method. Custom execution strategies may do something else or nothing at all.
  • finally, the execute method returns an exit code (which the caller is free to ignore)

In the use case described in https://github.com/quarkusio/quarkus/issues/6497 and https://github.com/quarkusio/quarkus/issues/9058 I imagine we don't want to run any user-defined code, only populate the fields, so perhaps a do-nothing execution strategy is the best solution.
Can we expect all `@Option` and `@ConfigBinding`-annotated fields to be in a single class, or do we need to support them being spread out over multiple classes?
Happy to discuss further. 

Note that strictly speaking these use cases do not require the picocli extension that is the topic of this thread; we can progress the configuration use case separate from the picocli extension.
 
 

Stuart
 

Stuart Douglas

unread,
May 7, 2020, 10:17:03 PM5/7/20
to Remko Popma, Quarkus Development mailing list
I am not sure, I think for the non-command use case a single class makes sense.
 

Note that strictly speaking these use cases do not require the picocli extension that is the topic of this thread; we can progress the configuration use case separate from the picocli extension.

Yea, this is a bit different to the extension use case, but I think we want something that is integrated rather than two different ways to do things.

Stuart
 
To unsubscribe from this group and stop receiving emails from it, send an email to quarkus-dev...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/quarkus-dev/4be3b3cc-858f-498b-9577-d9f25842f2b5%40googlegroups.com.

Michał Górniewski

unread,
May 11, 2020, 7:18:07 AM5/11/20
to Quarkus Development mailing list
Could we just use combination of @ConfigProperty and @CommandLine.Option and generate ConfigSource for all fields which have both of those annotations?
In build phase we would programmatically construct CommandLine instance to check if command spec if valid (e.g. no duplicate paramaters). And then, at runtime we would use same CommandLine to parseArgs and return ConfigSource.
Help would be handled with RUNTIME_INIT.

Remko Popma

unread,
May 18, 2020, 7:13:46 AM5/18/20
to Quarkus Development mailing list
Update:

Note that Michał Górniewski's PR https://github.com/quarkusio/quarkus/pull/9180 has been merged, so the upcoming Quarkus 1.5 release will include a picocli extension (and a much better one than I could have done 👍).

Michał did a fantastic job not just in the design, implementation and the requirements of this extension, but also in integrating the feedback from various reviewers.
Really great work! Many thanks, Michał!

Georgios Andrianakis

unread,
May 18, 2020, 7:21:12 AM5/18/20
to remko...@gmail.com, Quarkus Development mailing list
Excellent work and super quick turnaround in incorporating feedback over the weekend :)

Thanks a ton folks!

To unsubscribe from this group and stop receiving emails from it, send an email to quarkus-dev...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/quarkus-dev/ce58b90c-4aec-4d52-94fa-a6e739e31733%40googlegroups.com.

Daniel Platz

unread,
Jun 27, 2020, 3:55:18 PM6/27/20
to Quarkus Development mailing list
Does something like this already exist in the picocli extension? or is it planned?
After working with picocli and quarkus now for some time I notice that i currently have to decide between a command-parameter/option and a configproperty; but i would prefer to have some mix sometimes. E.g. for testing and different profiles i would like to have the option to set some parameters in the application.properties. So, it defines a default. Only if a parameter is set when running the cli app, it should be overwritten.
Does this make sense? Or is it maybe already possible today in some way?

Remko Popma

unread,
Jun 27, 2020, 6:40:13 PM6/27/20
to Quarkus Development mailing list


On Sunday, June 28, 2020 at 4:55:18 AM UTC+9, Daniel Platz wrote:
Does something like this already exist in the picocli extension? or is it planned?
After working with picocli and quarkus now for some time I notice that i currently have to decide between a command-parameter/option and a configproperty; but i would prefer to have some mix sometimes. E.g. for testing and different profiles i would like to have the option to set some parameters in the application.properties. So, it defines a default. Only if a parameter is set when running the cli app, it should be overwritten.
Does this make sense? Or is it maybe already possible today in some way?

Hi Daniel,

This may already be possible: picocli has a default provider mechanism where applications can plug in their own source of default values.

Picocli offers a concrete implementation of this mechanism, PropertiesDefaultProvider,
which tries to find a properties file named .${COMMAND-NAME}.properties in the user home directory 
(where ${COMMAND-NAME} is the name of the command, so ~/.git.properties if your command is named "git").

One idea is to try to leverage this default implementation, and point it to the `application.properties` file.
The location of the properties file can be controlled with system property "picocli.defaults.${COMMAND-NAME}.path"
("picocli.defaults.git.path" if your command is named "git"),
in which case the value of the property must be the path to the file containing the default values.

Another idea is to create a custom default provider implementation that reads default values from the `application.properties` file,
potentially taking the source code of `PropertiesDefaultProvider` as a starting point.

Remko.
Reply all
Reply to author
Forward
0 new messages