apple / apple/swift-argument-parser

GenerateDoccReference plugin silently misroutes output across products when --output-directory is passed manually

Open
#938 1 comment 0 reactions 0 assignees View on GitHub
Dominant language
Swift
Stars
3.8k
Forks
411
Avg merge
7d 14h
Merged PRs (30d)
15

Description

In a package with more than one executable product eligible for `generate-docc-reference` (single-target, depends on `ArgumentParser`), passing a manual `--output-directory` to `swift package generate-docc-reference` does not produce the "runtime failure" the plugin's own `--help` text promises. Instead it silently wins over the plugin's own per-target `--output-directory` for *every* eligible product, so all of them get written into the one directory passed on the command line, overwriting/misrouting output for every product other than the one intended. The command still exits 0 and prints the plugin's own (unused) per-target default paths, so the misrouting is silent.

**ArgumentParser version:** `1.8.2` (commit `6a52f3251125d74daf04fcbd5e6f08a75d074382`, also reproduced on `1.6.1`)
**Swift version:** `swift-driver version: 1.148.6 Apple Swift version 6.3.2 (swiftlang-6.3.2.1.108 clang-2100.1.1.101) Target: arm64-apple-macosx26.0`

### Checklist
- [ ] If possible, I've reproduced the issue using the `main` branch of this package — not run directly, but `Plugins/GenerateCommon/GeneratePlugin.swift` and `Plugins/GenerateDoccReference/GenerateDoccReference.swift` are byte-identical between the `1.8.2` tag and `main` at `2f77f2fccb6e84fecff338c37b199e33e7dfd119`, so the same bug is present there
- [x] I've searched for [existing GitHub issues](https://github.com/apple/swift-argument-parser/issues)

### Steps to Reproduce

1. Create a package with two executable targets, both depending on `ArgumentParser`, each with a single underlying target (e.g. `ToolA` and `ToolB`).
2. Run:
```
swift package --allow-writing-to-package-directory \
generate-docc-reference --style docc \
--output-directory Sources/ToolA/Documentation/ToolA.docc
```
3. Observe the output:
```
Generating docc reference for ToolA...
Generated docc reference in '.../Sources/ToolA/ToolA.docc'
Generating docc reference for ToolB...
Generated docc reference in '.../Sources/ToolB/ToolB.docc'
```
4. Check disk: `Sources/ToolA/ToolA.docc` and `Sources/ToolB/ToolB.docc` are empty (the plugin's `createOutputDirectory()` still runs against its own computed default), and both `tool-a.md` and `tool-b.md` end up written into `Sources/ToolA/Documentation/ToolA.docc` — the single path passed on the command line — instead of ToolB's reference going anywhere near `ToolB`.

This traces back to `Plugins/GenerateDoccReference/GenerateDoccReference.swift`, where the loop over `buildResult.builtArtifacts` computes a fresh `--output-directory` per artifact, then appends `extractor.remainingArguments` (any args the user passed through, including a manual `--output-directory`) *after* that computed flag, for every artifact in the loop:

```swift
var generationToolArguments = [
builtArtifact.path.string,
"--output-directory",
outputDirectory.string,
]
generationToolArguments.append(contentsOf: extractor.remainingArguments)
```

The underlying `generate-docc-reference-tool` (itself an ArgumentParser command) takes the last occurrence of a repeated `--output-directory` rather than erroring, so the user-supplied path silently overrides the plugin's per-target default on every iteration.

### Expected behavior

Either:
- The underlying tool should actually reject a duplicate/manually supplied `--output-directory` with an error, matching what the plugin's own help text promises, or
- The plugin should filter `--tool`/`--output-directory` out of `extractor.remainingArguments` before forwarding, so passing them manually is a no-op (or a clear error) rather than a silent footgun in multi-executable packages.

### Actual behavior

The command exits 0 and prints `Generated docc reference in ''` for every eligible product, but the manually-supplied `--output-directory` silently wins for all of them — every product's reference is written into that single directory instead of its own, and the printed default paths are left as empty directories.

Contributor guide

Open the contributing guide

Research direction

Start with Plugins/GenerateDoccReference/GenerateDoccReference.swift and the corresponding Plugins/GenerateCommon/GeneratePlugin.swift, then reproduce the two-executable package scenario with the documented swift package generate-docc-reference command. Trace how extractor.remainingArguments are appended for each built artifact; done means a manually supplied --output-directory no longer silently misroutes or overwrites references, and the behavior matches the chosen error or no-op expectation.

Written by the indexing model from the issue text.

Assessment

Tech stack
swift
Domain
cli, tooling
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Quiet
Clarity
Clearly specified
Newbie friendliness
72/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.