[ 
https://issues.apache.org/jira/browse/SOLR-17697?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=18071531#comment-18071531
 ] 

Jan Høydahl edited comment on SOLR-17697 at 5/15/26 2:16 PM:
-------------------------------------------------------------

Here is a coding agent prompt that can be used as a template to implement 
picocli support for a tool. Procedure for adding more tools will be:
 # Pick a tool you want to implement picocli support for
 # Check out feature branch {{{}jira/SOLR-17697-picocli{}}},
 # Make a new branch for your tool
 # Implement the picocli supprt, feel free to use the prompt template below
 # Make a PR with target branch {{{}jira/SOLR-17697-picocli{}}},  
 # Once review is done, it is squash-merged into the feature branch, and at the 
end we do a normal merge to main to preserve each commit

{code}
# Task: Add PicoClI support for <ToolName> and <ToolsName2>

> **Before starting:** Replace every occurrence of `<ToolName>` in this prompt
> with the actual class name (e.g. `ZkRmTool`). If the task asks to implement
> several tools together, adapt to that in below steps.

You are adding picocli support to an existing Solr CLI tool located in
`solr/core/src/java/org/apache/solr/cli/`. Respect `AGENTS.md` at all times.

Solr's CLI already uses commons-cli for command-line parsing. You will add
picocli support in parallel; commons-cli code will be removed in a later stage.

## How the dual-path pattern works

Every tool extends `ToolBase`, which exposes two entry points:

- `runImpl(CommandLine cli)` — the existing commons-cli path. Do not modify its
  signature or behaviour.
- `callTool()` — the picocli path. You must implement this.

The environment variable `SOLR_TOOL_OPTS="-Dsolr.picocli=true"` activates the
picocli path in `SolrCLI.main()`. This same variable is used during validation
(see the Validation section).

## Steps

### 1. Read the target tool in full before making any changes

Open `<ToolName>.java` and read it completely. Note which mixins from step 5 are
relevant to this tool before proceeding.

### 2. Extract business logic

Choose the sub-step that matches the tool's current structure:

**2a. If `runImpl()` does work inline:**
Extract that logic into a private `doXxx(...)` method with explicit, non-CLI
parameters. Both `runImpl()` and `callTool()` then call `doXxx(...)`.

**2b. If `runImpl()` delegates to methods that accept a commons-cli 
`CommandLine`
as an argument:**
Refactor those methods to be independent of commons-cli so they can be called
from both paths.

Do not change business logic behaviour or command output. Use `ZkRmTool` as the
reference implementation for both sub-steps.

### 3. Add `@Command` to the class

Annotate the class with `@picocli.CommandLine.Command`. Required attributes:

```java
@SuppressWarnings("UnnecessarilyFullyQualified")
@picocli.CommandLine.Command(
    name = "...",
    description = "..."
)
```

Do **not** set `synopsisHeading` — it is propagated from the root command.

### 4. Add usage examples and exit code documentation to `@Command`

All annotations below are written in Java annotation syntax.

**Examples** — always add:

```java
footerHeading = "%nExamples:%n",
footer = {
  "  # Short description of what this example does",
  "  bin/solr <command> --option value",
  "",
  "  # Another scenario",
  "  bin/solr <command> --option value --flag"
}
```

Use `bin/solr <command>` as the invocation prefix. Use realistic but generic
argument values (e.g. `myCollection`, `myconfig`, `localhost:9983`,
`http://localhost:8983`). Include 2–3 concise examples. Make sure the examples
chosen are the most common/useful and are valid.

**Exit codes** — add only for commands users are likely to call from scripts
where exit code drives control flow (e.g. create, delete, status, stop, zk
subcommands). Omit for informational or always-succeeding commands (e.g.
version, start):

```java
exitCodeListHeading = "%nExit Codes:%n",
exitCodeList = {
  "0:Operation completed successfully.",
  "1:Operation failed; check output for details."
}
```

See `StatusTool` and `DeleteTool` for concrete examples of both annotations in 
use.

### 5. Declare picocli fields

Use mixins wherever possible — prefer reuse over declaring new `@Option` fields.
Cross-reference your notes from step 1 to select only the mixins this tool 
needs:

| Mixin | Options provided | When to use |
|---|---|---|
| `ZkConnectionOptions` | `--zk-host`, `--solr-url`, `--credentials` | All ZK 
tools |
| `RecursiveOption` | `--recursive` | cp, ls, rm |
| `ConfigSetOptions` | `--conf-name`, `--conf-dir` | upconfig, downconfig |
| `HelpMixin` | `--help` | When help option is useful to document | 
| `CredentialsOptions` | `--credentials`, `-u` | For options needing basic auth 
credentials |
| `ConnectionOptions` | `--solr-url`, `-s`, `--zk-host`, `-z` | Tools needing a 
solr url or zookeeper host |
| `ConfigSetOptions` | `--conf-name`, `--conf-dir` | Tools working with config 
sets | 

`ToolBase` already provides `--verbose`; do not redeclare it.

For any remaining options, use `@Option` / `@Parameters`, copying description
wording from the existing commons-cli `Option` definition. See `ZkLsTool` or
`ZkMkrootTool` for simple examples, `ZkCpTool` for a more complex one.

### 6. Implement `callTool()`

Resolve the connection, call `doXxx()` (or the equivalent method from step 2),
return `0` on success, throw an exception on any failure. `ToolBase.call()` 
handles 
printing and the exit code. See `ZkRmTool` for the complete pattern.

Do not perform any I/O (ZK or Solr connection setup, file reads, etc.) during
argument parsing — no I/O in `@Option` defaults or field initialisers.
All I/O belongs in `callTool()`.

### 7. Register the tool

Choose the correct registration point based on the tool type:

- **ZK subcommand** — verify `<ToolName>` is already listed in `ZkTool.java`.
  If it is missing, add it there.
- **Top-level tool** — register `<ToolName>` in the subcommands list in
  `SolrCLI.java`.
- **New subcommand** - If asked to implement a new sub-command, follow the 
pattern
  laid out in ZkTool to provide a new sub command hierarchy.

## Validation

### Unit tests

```bash
./gradlew :solr:core:test --tests "org.apache.solr.cli.*<ToolName>*"
```

### Integration tests (if a BATS test exists in `solr/packaging/test/`)

Run the test twice — once on each code path. The env var here is the same one
described in the dual-path overview above:

```bash
# commons-cli path
./gradlew integrationTests --tests=test_<name>.bats

# picocli path
SOLR_TOOL_OPTS="-Dsolr.picocli=true" ./gradlew integrationTests 
--tests=test_<name>.bats
```

On picocli-only failures, prefer adjusting picocli output to match existing
assertions rather than weakening assertions. Only adjust assertions that check
formatting (not functional output), and only where both code paths must be
accommodated.

### Style and static checks

```bash
./gradlew tidy
./gradlew check -x test
```
{code}
 


was (Author: janhoy):
Here is a coding agent prompt that can be used as a template to implement 
picocli support for a tool. Procedure for adding more tools will be:
 # Pick a tool you want to implement picocli support for
 # Check out feature branch {{{}jira/SOLR-17697-picocli{}}},
 # Make a new branch for your tool
 # Implement the picocli supprt, feel free to use the prompt template below
 # Make a PR with target branch {{{}jira/SOLR-17697-picocli{}}},  
 # Once review is done, it is squash-merged into the feature branch, and at the 
end we do a normal merge to main to preserve each commit

{code:java}
Task: Add PicoCli support for XxxxxxTool and YyyyyyTool (replace)

You are adding picocli support to an existing Solr CLI tool in 
solr/core/src/java/org/apache/solr/cli/. Respect AGENTS.md at all times.

Solr's CLI already use commons-cli for cmdline parsing. You will add picocli
support in parallel, and the project will remove commons-cli code at a later 
stage.

## How the dual-path pattern works 
- Every tool extends ToolBase, which has two entry points: 
- runImpl(CommandLine cli) — existing commons-cli path, do not touch 
- callTool() — picocli path, you must implement this 
- SOLR_PICOCLI=true env var activates the picocli path in SolrCLI.main() 

## Steps 

1. Read the target tool in full before making any changes. 

2. Extract business logic: 
- if runImpl() does work inline, extract business logic into a private 
doXxx(...) 
  method with explicit parameters
- if runImpl() delegates to methods taking commons-cli CommandLine as method 
argument, 
  refactor such method to be independent of commons-cli and callable from both 
paths.
- Both runImpl() and callTool() can now call the generic method(s).
Do not change the business logic behaviour or command output. See ZkRmTool as a 
reference. 

3. Add @picocli.CommandLine.Command to the class (name, 
mixinStandardHelpOptions=true, 
description). Do NOT set synopsisHeading — it is propagated from the root 
command. 

4. Declare picocli fields using mixins wherever possible — prefer reuse over 
new @Option: 
- ZkConnectionOptions — --zk-host, --solr-url, --credentials (all ZK tools use 
this)
- RecursiveOption — --recursive (used by cp, ls, rm) 
- ConfigSetOptions — --conf-name, --conf-dir (used by upconfig, downconfig)
- ToolBase already provides --verbose; do not redeclare it 
- For remaining options use @Option / @Parameters, copying description wording 
from 
the existing commons-cli Option. See ZkLsTool or ZkMkrootTool for simple 
examples, 
ZkCpTool for a more complex one. 

5. Implement callTool(): resolve connection, call doXxx() or similar, return 0 
on success, throw on 
error (ToolBase.call() handles printing and exit code). See ZkRmTool for the 
pattern. 

6. Register top-level tools in SolrCLI.java subcommands list. ZK subcommands 
are already
listed in ZkTool.java — verify the tool is present there. 

7. Do NOT resolve ZK/Solr connections during argument parsing (no I/O in 
@Option defaults 
or initialisers. All I/O belongs in callTool().

## Validation 

- Run existing Java tests for the tool: 
./gradlew :solr:core:test --tests "org.apache.solr.cli.*<ToolName>*"

- If a BATS test exists in solr/packaging/test/, run it twice: 
./gradlew integrationTests --tests=test_<name>.bats
SOLR_PICOCLI=true ./gradlew integrationTests --tests=test_<name>.bats 
On picocli-only failures, prefer adjusting picocli output to match existing 
assertions
over weakening assertions. Only adjust assertions that check formatting rather 
than 
functional output, and only if they must accept both code paths. 

- ./gradlew tidy 
- ./gradlew check -x test 
{code}

> Use picocli instead of commons-cli
> ----------------------------------
>
>                 Key: SOLR-17697
>                 URL: https://issues.apache.org/jira/browse/SOLR-17697
>             Project: Solr
>          Issue Type: Improvement
>          Components: cli
>            Reporter: Jan Høydahl
>            Assignee: Jan Høydahl
>            Priority: Major
>              Labels: pull-request-available
>          Time Spent: 8.5h
>  Remaining Estimate: 0h
>
> Apache commons-cli has served us well for years, but our CLI has out-grown 
> its capabilities, with multiple sub commands and a plethora of options and 
> arguments. We have much custom code to work around limitations.
> By embracing [Picocli|https://picocli.info/], an annotation based cli 
> framework, it will be easier to maintain the cli and add further tools. We 
> propose to target 10.1 and do work gradually in a feature branch.
> The feature branch is {{{}jira/SOLR-17697-picocli{}}}, you can view it in a 
> [draft PR not intended for merge|https://github.com/apache/solr/pull/3254]. 
> That PR has a description on how to contribute, and a checklist of tools not 
> yet converted. Once all tools are converted, all tests are green and all docs 
> updated, we can merge this work.
> There is an env var {{SOLR_PICOCLI=true}} that we currently use to swtich 
> from old commons-cli and new picocli. Once the feature branch is merged we 
> can choose to make SOLR_PICOCLI default to true but retain the old 
> commons-cli parser code for a few releases, so there will be a workaround for 
> potential bugs. 



--
This message was sent by Atlassian Jira
(v8.20.10#820010)

---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to