Skip to content

A Path to v2.0#482

Open
tomasaschan wants to merge 7 commits into
masterfrom
a-path-to-v2
Open

A Path to v2.0#482
tomasaschan wants to merge 7 commits into
masterfrom
a-path-to-v2

Conversation

@tomasaschan

Copy link
Copy Markdown
Collaborator

There are a lot of things in this library that I'd love to fix - but most of the fixes become a lot more complicated to execute on, because we also need to maintain backwards compatibility. (The response to release v1.0.8 shows that this is clearly important to at least some, loud, part of the community.)

I no longer want to keep adding //nolint comments to allow for keeping code compatible with Go 1.12, released in 2019 with the last minor release published in 2020. I no longer want to fight to keep APIs and behavior consistent across umpteen almost but not quite identical implementations of parsing flag values to slices of various primitive types. I no longer want to keep names like Blacklist in the public API.

I'm opening this PR in very early draft to start carving a path to a new major version of this library, that allows us to fix all of the above without breaking semver promises. Before this is actually released, it is likely also a good idea to move it to an import path that is not tied to a single Github user's account - but we have to start somewhere. As a first step, I just want to have CI in place to start iterating.

@tomasaschan tomasaschan marked this pull request as ready for review June 26, 2026 09:58
tomasaschan and others added 2 commits June 26, 2026 12:35
Run Go's own flag package test suite against pflag v2 (dot-imported as
"flag") to keep the drop-in-replacement promise honest. The suite is the
spec for v2: as the package is currently empty, every failure is an
implementation gap to close (see v2/conformance/README.md for the gap
list / TODO).

The suite is build-tagged "conformance" so it does not affect the normal
`go test ./...` run, and is Go-version aware: a separate copy of upstream
flag_test.go is vendored per minor version, each pinned with a build
constraint (e.g. go1.26 && !go1.27), so the CI matrix tests each
supported Go release against that release's own flag tests and upstream
changes surface as diffs. hack/sync.sh regenerates the copies (also wired
to `go generate ./conformance/...`); a guard test fails loudly if the
running toolchain has no vendored copy.

Adds a non-blocking conformance-v2 CI job (continue-on-error until v2 is
complete).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add the second conformance promise (POSIX/GNU argument syntax) alongside
the vendored stdlib flag suite, plus the machinery to reconcile the two
when they conflict.

- gnuposix_test.go: hand-written suite keyed to the POSIX Utility Syntax
  Guidelines (1-14) and the GNU long-option extensions. No reusable
  Go-native POSIX corpus exists to vendor, so each case cites the rule it
  checks.

- divergences.json: single source of truth cataloguing the intentional
  differences between pflag and the stdlib flag package. POSIX wins, so
  the listed stdlib tests are *expected* to fail; each entry names them
  and the rule that overrides them. Categories: posix-overrides-stdlib,
  pflag-design-differs, pflag-omits-gnu-feature, not-implemented-yet.

- hack/oracle: reads `go test -json` and gates CI on the catalogue rather
  than raw pass/fail: green iff failures match the documented set exactly
  (catches regressions and silently-resolved divergences). The top-level
  "status" field tolerates a build failure while "bootstrapping" so CI is
  green during v2 build-out; "not-implemented-yet" entries are lenient
  (tolerated when failing, retirable when they start passing).

- internal/divergence: catalogue types + parsing, shared by the manifest
  test and the oracle (no build tag, no v2 dependency).

CI conformance-v2 job now pipes the suite through the oracle and drops
continue-on-error; it is green now (bootstrapping) and tightens to
enforcing once v2 compiles.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@brandon1024

Copy link
Copy Markdown

Hey @tomasaschan! As the project starts to move in the direction of a new major version, I would like to become a bit more involved in this transition. I've been a heavy user of pflag internally at Siemens for a while now, and there are aspects of the design of pflag that I would love to see improved in a new major release.

Are you open to collaboration and discussion around v2? Do we have a space where I (and the wider community) could discuss what this transition would look like, whether in the form of a GitHub Project or a matrix/discord community?

@tomasaschan

Copy link
Copy Markdown
Collaborator Author

@brandon1024 Contributions are always welcome!

There isn't much of a collab space for this yet - mostly because I don't have enough permissions to e.g. create Github Projects, and I don't want to build on something that is tied to my own personal accounts anywhere (we already have enough issues around this library being under a user account...).

Why don't we use this PR thread as a first place to discuss, and then we can move on from there when we feel there's enough maturity in the ideas to justify something more elaborate?

@brandon1024

Copy link
Copy Markdown

Yep makes sense, sounds good to me 👍

Everything you described in your first comment resonates with me. Frankly, maintaining compatibility with an old, arbitrary version of the Go toolchain is a bit absurd. I stumbled into this while I was writing tests in #457. And having looked at some of these umpteen flag value types, I feel the pain.. As I'm sure you know, the flag types aren't implemented in a particularly strong way either -- raw interface types everywhere, questionable/redundant string conversions. It was quite discouraging the moment I realized I would need to patch quite a few other flag types after finishing #457.

Anyway, I don't want this thread to become a rant fest. Collecting your points:

  • Identify a roadmap towards a dedicated GH organization for the project (sf Expand my permissions a bit #441).
  • Modernize the Go toolchain requirements. Perhaps we could consider switching to a model where we maintain compatibility with the previous (minor) release, tracking upstream. Just a thought.
  • Improve tests to ensure that there are no regressions in flag behaviour (in particular, edge cases).

Anything else you would add here?

Documentation is something I would add, particularly around how flags are parsed and any edge cases that result. Testable examples, too. And more generally, the language could use some work.

Aside from the points you've mentioned, in terms of ABI or design, did you have any thoughts on the direction for v2? I have some thoughts, but I'd like to hear yours first.

@tomasaschan

tomasaschan commented Jul 2, 2026

Copy link
Copy Markdown
Collaborator Author

I started carving out this space by implementing two things: a test harness that can run the appropriate version of the upstream flag test suite (to keep track of the "drop-in replacement" promise) and a test suite for POSIX conventions (to keep track of the "POSIX compliant" promise), as well as something that tracks how we handle cases where they disagree.

In addition to making sure we actually deliver on those promises, there are a bunch of promises from 1.0 I'd like to break:

  • Keeping Go version constraints constant doesn't seem valuable; I'd like to make the promise for v2.0 that we support stable and oldstable, and roll that forward with the Go project itself. Given Go's own strong backwards compatibility promises, I don't see a lot of reasons why our users would have to use new versions of the library on old Go versions. The semver promise for pflag 2.x would not include not requiring newer Go versions than we did before.

  • I don't want to promise that the flag-parsing behavior of 1.x is preserved. The slice-valued thing is maybe the prime example, but there are also a lot of other corner cases that might have the "wrong" behavior now, or inconsistencies where different types behave differently, that we will never be able to fix if we don't allow changes to behavior that are, by some definition, breaking. 1.x->2.0 is the time to make those changes.

  • I've seen people complain about breaking changes when the layout or contents of generated help text was changed. I don't think that should be in scope for the semver promise.

  • Since we're changing the import path anyway (adding /v2 at the end) we should also consider moving the code to a proper Github org. github.com/pflag is already taken so we might need to come up with an org name (although they don't have anything public there, so maybe we can talk to them about it?).

When it comes to implementation details, I'd like to make use of new Go features that 1.0 couldn't due to the Go 1.12 compatibility promise:

  • We should be able to build almost all functionality on a single generic and pluggable implementation, exposing shims or wrappers to stay flag-compliant where necessary.

  • Usage and API docs are currently scattered and sometimes pretty outdated; starting over with the implementation will enable us to structure this more deliberately.

  • I would like to make sure that whatever tests, codegen etc we use can be as reliant as possible on go as the entrypoint - go generate ./... && go test ./... should be enough (no makefiles etc needed)

Note: all of these thoughts are very off-the-top-of-my-head at this point.

@brandon1024

brandon1024 commented Jul 6, 2026

Copy link
Copy Markdown

and a test suite for POSIX conventions (to keep track of the "POSIX compliant" promise)

We should do this, but I would note that some of the POSIX guidelines cannot be easily tested here. Many of the POSIX guidelines relate to the tool itself (guidelines 1-2, 11-13), and other guidelines are widely disregarded (e.g. allowing interspersed operands and options (guideline 9), long-form options with stuck values (guideline 6), defaults for long-form options (guideline 7)). Not to mention the fact that POSIX makes no distinction between long- and short-form flags, this is a GNU convention.

But I agree, we should have a suite for asserting that pflag behaves properly for well-accepted fundamental POSIX guidelines (e.g. guideline 10, 14).

there are a bunch of promises from 1.0 I'd like to break:

I agree with everything here. We will need to be crystal clear on what we consider part of our compatibility promise for 2.0.0.

a test harness that can run the appropriate version of the upstream flag test suite (to keep track of the "drop-in replacement" promise)

I've been experimenting with an implementation of GNU/POSIX arg parsing that relies on struct embedding of the standard FlagSet, rather than a complete re-implementation of the entire stdlib package. It's basically a flag.FlagSet with a new parser. This was an interesting experiment because it ensures type compatibility with the stdlib, and allows us to use POSIX or stdlib parsing interchangeably.

My plan has been to prepare a Go proposal to include this as part of the standard library, but could be interesting alternative here in the spirit of the drop-in replacement promise.

Food for thought.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants