apple / apple/swift-argument-parser
GenerateDoccReference plugin silently misroutes output across products when --output-directory is passed manually
- 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
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