Command Mode with Picocli

Picocli是一种用于创建丰富的命令行应用程序的开源工具。

Picocli is an open source tool for creating rich command line applications.

Quarkus 提供对使用 Picocli 的支持。本指南包含 `picocli`扩展用法的示例。

Quarkus provides support for using Picocli. This guide contains examples of picocli extension usage.

如果你不熟悉 Quarkus 命令模式,请考虑首先阅读Command Mode reference guide

If you are not familiar with the Quarkus Command Mode, consider reading the Command Mode reference guide first.

Extension

配置好 Quarkus 项目后,可以通过在项目基础目录中运行以下命令将 `picocli`扩展添加到项目中。

Once you have your Quarkus project configured you can add the picocli extension to your project by running the following command in your project base directory.

Unresolved directive in picocli.adoc - include::{includes}/devtools/extension-add.adoc[]

这会将以下内容添加到构建文件中:

This will add the following to your build file:

pom.xml
<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-picocli</artifactId>
</dependency>
build.gradle
implementation("io.quarkus:quarkus-picocli")

Simple command line application

仅一个 `Command`的简单 PicocliApplication 可按如下方式创建:

Simple PicocliApplication with only one Command can be created as follows:

package com.acme.picocli;

import picocli.CommandLine;

import jakarta.enterprise.context.Dependent;
import jakarta.inject.Inject;

@CommandLine.Command (1)
public class HelloCommand implements Runnable {

    @CommandLine.Option(names = {"-n", "--name"}, description = "Who will we greet?", defaultValue = "World")
    String name;

    private final GreetingService greetingService;

    public HelloCommand(GreetingService greetingService) { (2)
        this.greetingService = greetingService;
    }

    @Override
    public void run() {
        greetingService.sayHello(name);
    }
}

@Dependent
class GreetingService {
    void sayHello(String name) {
        System.out.println("Hello " + name + "!");
    }
}
1 If there is only one class annotated with picocli.CommandLine.Command it will be used as entry point to Picocli CommandLine.
2 All classes annotated with picocli.CommandLine.Command are registered as CDI beans.

具有 @CommandLine.Command`的 bean 不应使用代理范围(例如不要使用 `@ApplicationScope),因为 Picocli 无法在此类 bean 中设置字段值。此扩展将使用 `@CommandLine.Command`范围将类与 `@Depended`注解一起注册。如果你需要使用代理范围,则注解 setter 而不是字段,例如:

Beans with @CommandLine.Command should not use proxied scopes (e.g. do not use @ApplicationScope) because Picocli will not be able to set field values in such beans. This extension will register classes with @CommandLine.Command annotation using @Depended scope. If you need to use proxied scope, then annotate setter and not field, for example:

@CommandLine.Command
@ApplicationScoped
public class EntryCommand {
    private String name;

    @CommandLine.Option(names = "-n")
    public void setName(String name) {
        this.name = name;
    }

}

Command line application with multiple Commands

当多个类存在 picocli.CommandLine.Command 注释时,其中一个还需要使用 io.quarkus.picocli.runtime.annotations.TopCommand 做注释,可以使用 quarkus.picocli.top-command 属性进行覆盖。

When multiple classes have the picocli.CommandLine.Command annotation, then one of them needs to be also annotated with io.quarkus.picocli.runtime.annotations.TopCommand. This can be overwritten with the quarkus.picocli.top-command property.

package com.acme.picocli;

import io.quarkus.picocli.runtime.annotations.TopCommand;
import picocli.CommandLine;

@TopCommand
@CommandLine.Command(mixinStandardHelpOptions = true, subcommands = {HelloCommand.class, GoodByeCommand.class})
public class EntryCommand {
}

@CommandLine.Command(name = "hello", description = "Greet World!")
class HelloCommand implements Runnable {

    @Override
    public void run() {
        System.out.println("Hello World!");
    }
}

@CommandLine.Command(name = "goodbye", description = "Say goodbye to World!")
class GoodByeCommand implements Runnable {

    @Override
    public void run() {
        System.out.println("Goodbye World!");
    }
}

Customizing Picocli CommandLine instance

您可以通过生成自己的 bean 实例来自定义 picocli 扩展使用的 CommandLine 类:

You can customize CommandLine classes used by the picocli extension by producing your own bean instance:

package com.acme.picocli;

import io.quarkus.picocli.runtime.PicocliCommandLineFactory;
import io.quarkus.picocli.runtime.annotations.TopCommand;
import picocli.CommandLine;

import jakarta.enterprise.context.ApplicationScoped;
import jakarta.enterprise.inject.Produces;

@TopCommand
@CommandLine.Command
public class EntryCommand implements Runnable {
    @CommandLine.Spec
    CommandLine.Model.CommandSpec spec;

    @Override
    public void run() {
        System.out.println("My name is: " + spec.name());
    }
}

@ApplicationScoped
class CustomConfiguration {

    @Produces
    CommandLine customCommandLine(PicocliCommandLineFactory factory) { (1)
        return factory.create().setCommandName("CustomizedName");
    }
}
1 PicocliCommandLineFactory will create an instance of CommandLine with TopCommand and CommandLine.IFactory injected.

Different entry command for each profile

可以使用 @IfBuildProfile 为每个配置文件创建不同的入口命令:

It is possible to create different entry command for each profile, using @IfBuildProfile:

@ApplicationScoped
public class Config {

    @Produces
    @TopCommand
    @IfBuildProfile("dev")
    public Object devCommand() {
        return DevCommand.class; (1)
    }

    @Produces
    @TopCommand
    @IfBuildProfile("prod")
    public Object prodCommand() {
        return new ProdCommand("Configured by me!");
    }

}
1 You can return instance of java.lang.Class here. In such case CommandLine will try to instantiate this class using CommandLine.IFactory.

Configure CDI Beans with parsed arguments

可以使用 Event<CommandLine.ParseResult> 或仅使用 CommandLine.ParseResult 根据 Picocli 解析的参数来配置 CDI bean。这个事件将在由这个扩展创建的 QuarkusApplication 类中生成。如果您提供自己的 @QuarkusMain 则不会触发此事件。CommandLine.ParseResult 是从默认的 CommandLine bean 创建的。

You can use Event<CommandLine.ParseResult> or just CommandLine.ParseResult to configure CDI beans based on arguments parsed by Picocli. This event will be generated in QuarkusApplication class created by this extension. If you are providing your own @QuarkusMain this event will not be raised. CommandLine.ParseResult is created from default CommandLine bean.

@CommandLine.Command
public class EntryCommand implements Runnable {

    @CommandLine.Option(names = "-c", description = "JDBC connection string")
    String connectionString;

    @Inject
    DataSource dataSource;

    @Override
    public void run() {
        try (Connection c = dataSource.getConnection()) {
            // Do something
        } catch (SQLException throwables) {
            // Handle error
        }
    }
}

@ApplicationScoped
class DatasourceConfiguration {

    @Produces
    @ApplicationScoped (1)
    DataSource dataSource(CommandLine.ParseResult parseResult) {
        PGSimpleDataSource ds = new PGSimpleDataSource();
        ds.setURL(parseResult.matchedOption("c").getValue().toString());
        return ds;
    }
}
1 @ApplicationScoped used for lazy initialization

Providing own QuarkusMain

您还可以提供自己的应用程序入口点,带有 QuarkusMain 注释(如 Command Mode reference guide 所述)。

You can also provide your own application entry point annotated with QuarkusMain (as described in Command Mode reference guide).

package com.acme.picocli;

import io.quarkus.runtime.QuarkusApplication;
import io.quarkus.runtime.annotations.QuarkusMain;
import picocli.CommandLine;

import jakarta.inject.Inject;

@QuarkusMain
@CommandLine.Command(name = "demo", mixinStandardHelpOptions = true)
public class ExampleApp implements Runnable, QuarkusApplication {
    @Inject
    CommandLine.IFactory factory; (1)

    @Override
    public void run() {
        // business logic
    }

    @Override
    public int run(String... args) throws Exception {
        return new CommandLine(this, factory).execute(args);
    }
}
1 Quarkus-compatible CommandLine.IFactory bean created by picocli extension.

Native mode support

此扩展使用 Quarkus 标准构建步骤机制来支持 GraalVM Native 映像。在特殊情况下,未来 picocli 版本中的不兼容更改会导致任何问题时,可以使用以下配置作为临时解决办法,以回退到来自 picocli 项目的注释处理器:

This extension uses the Quarkus standard build steps mechanism to support GraalVM Native images. In the exceptional case that incompatible changes in a future picocli release cause any issue, the following configuration can be used to fall back to the annotation processor from the picocli project as a temporary workaround:

<dependency>
  <groupId>info.picocli</groupId>
  <artifactId>picocli-codegen</artifactId>
</dependency>

对于 Gradle,您需要在 build.gradle 文件的 dependencies 部分中添加以下内容:

For Gradle, you need to add the following in dependencies section of the build.gradle file:

annotationProcessor enforcedPlatform("${quarkusPlatformGroupId}:${quarkusPlatformArtifactId}:${quarkusPlatformVersion}")
annotationProcessor 'info.picocli:picocli-codegen'

Development Mode

在开发模式下,即运行 mvn quarkus:dev 时,每次按下 Space bar 键时都会执行应用程序并重新启动。您还可以通过 quarkus.args 系统属性将参数传递给您的命令行应用程序,例如 mvn quarkus:dev -Dquarkus.args='--help'mvn quarkus:dev -Dquarkus.args='-c -w --val 1'

In the development mode, i.e. when running mvn quarkus:dev, the application is executed and restarted every time the Space bar key is pressed. You can also pass arguments to your command line app via the quarkus.args system property, e.g. mvn quarkus:dev -Dquarkus.args='--help' and mvn quarkus:dev -Dquarkus.args='-c -w --val 1'.

Kubernetes support

拥有命令行应用程序后,您还可以通过添加 kubernetes 扩展来生成在 Kubernetes 中安装和使用此应用程序所需的资源。要在您的项目基本目录下安装 kubernetes 扩展,请运行以下命令。

Once you have your command line application, you can also generate the resources necessary to install and use this application in Kubernetes by adding the kubernetes extension. To install the kubernetes extension, run the following command in your project base directory.

Unresolved directive in picocli.adoc - include::{includes}/devtools/extension-add.adoc[]

这会将以下内容添加到您的 pom.xml

This will add the following to your pom.xml:

<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-kubernetes</artifactId>
</dependency>

然后,使用以下命令构建应用程序:

And, next, build the application with:

Unresolved directive in picocli.adoc - include::{includes}/devtools/build.adoc[]

Kubernetes 扩展将检测到 Picocli 扩展的存在,并因此在 target/kubernetes/ 目录中生成 Job 资源而不是 Deployment 资源。

The Kubernetes extension will detect the presence of the Picocli extension and hence generate a Job resource instead of a Deployment resource in the target/kubernetes/ directory.

如果您不想生成 Job 资源,可以使用 quarkus.kubernetes.deployment-kind 属性指定您想要生成的资源。例如,如果您想要生成 Deployment 资源,请使用 quarkus.kubernetes.deployment-kind=Deployment

If you don’t want to generate a Job resource, you can specify the resource you want to generate using the property quarkus.kubernetes.deployment-kind. For example, if you want to generate a Deployment resource, use quarkus.kubernetes.deployment-kind=Deployment.

此外,您可以通过 quarkus.kubernetes.arguments 属性提供 Kubernetes Job 将使用的参数。例如,在添加 quarkus.kubernetes.arguments=A,B 属性并构建项目后,将生成以下 Job 资源:

Moreover, you can provide the arguments that will be used by the Kubernetes job via the property quarkus.kubernetes.arguments. For example, after adding the property quarkus.kubernetes.arguments=A,B and building your project, the following Job resource will be generated:

apiVersion: batch/v1
kind: Job
metadata:
  labels:
    app.kubernetes.io/name: app
    app.kubernetes.io/version: 0.1-SNAPSHOT
  name: app
spec:
  completionMode: NonIndexed
  suspend: false
  template:
    metadata:
      labels:
        app.kubernetes.io/name: app
        app.kubernetes.io/version: 0.1-SNAPSHOT
    spec:
      containers:
        - args:
            - A
            - B
          env:
            - name: KUBERNETES_NAMESPACE
              valueFrom:
                fieldRef:
                  fieldPath: metadata.namespace
          image: docker.io/user/app:0.1-SNAPSHOT
          imagePullPolicy: Always
          name: app
          ports:
            - containerPort: 8080
              name: http
              protocol: TCP
      restartPolicy: OnFailure
      terminationGracePeriodSeconds: 10

最后,Kubernetes 任务将在每次安装在 Kubernetes 中时启动。更多关于如何在 document 中运行 Kubernetes 任务的信息,请点击此处。

Finally, the Kubernetes job will be launched every time it is installed in Kubernetes. You can know more about how to run Kubernetes jobs in this document.

Configuration Reference

Unresolved directive in picocli.adoc - include::{generated-dir}/config/quarkus-picocli.adoc[]