Skip to content

Take bin/fdp back from graphviz, and reach the CLI as python -m fdp - #18

Merged
sammuli merged 2 commits into
mainfrom
feat/fdp-cli-collision
Sep 3, 2026
Merged

Take bin/fdp back from graphviz, and reach the CLI as python -m fdp#18
sammuli merged 2 commits into
mainfrom
feat/fdp-cli-collision

Conversation

@sammuli

@sammuli sammuli commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

The problem

graphviz ships bin/fdp — a symlink to dot that dispatches on argv[0] — and so do we. Anything that pulls graphviz into an FDP environment lands a second owner on $PREFIX/bin/fdp, and the CMF provenance stack does exactly that: cmflib -> dvc -> pydot -> graphviz.

Measured on pixi, in a clean env with fdp and cmflib:

bin/fdp -> dot
$ fdp -V
fdp - graphviz version 14.1.2

The FDP CLI isn't shadowed, it's gonefdp run python ... becomes a graph layout tool. This is what has blocked putting cmflib and toksearch_cmf into the fdp-core metapackage.

It also rules out the obvious mitigation: a deprecation warning is unprintable here, because our entry point is the file that got replaced. The users who need the warning are exactly the ones who can never see it.

The fix

Declare graphviz as our own run dependency. The installer's topological link order puts a dependency before its dependent, so graphviz links first, fdp links last, and we take the file.

Whether that's a fix or insurance depends on the installer, and both are worth having:

  • pixi/rattler — graphviz genuinely wins without this; the CLI is broken today. With it we win from scratch (3/3 runs, deterministic), on an incremental pixi add cmflib, and across a real graphviz 13.1.2 → 14.1.2 upgrade. That last case means rattler respects existing file ownership, not merely link order. This is the fdp-install path, so it's where most users are.
  • micromamba 2.9.0 — already linked fdp last without this dependency (graphviz at step 1193, fdp at 1266), so it already worked. But nothing enforced that order; it fell out of the solve. This makes it guaranteed instead of lucky.

Nothing is lost on the graphviz side: dot -Kfdp is the documented equivalent of its fdp binary.

Why a test, not just a comment

This relies on installer behaviour that isn't a documented guarantee, so it ships with a guard rather than a comment alone. Since graphviz is now a run dependency, the recipe's own test environment reproduces the collision for free — I confirmed graphviz 14.1.2 is actually present there, so the test isn't vacuous. It fails loudly and names the cause if graphviz ever wins again; if that happens, the answer is to rename the CLI.

The guard also asserts python -m fdp works — the one entry point no other package can take, and the fallback if this ever regresses.

python -m fdp

Second commit adds fdp/__main__.py plus tests, so the CLI is reachable as python -m fdp. Unclobberable by construction, and useful independently of any of the above.

Verification

Check Result
fdp built with the fix guard passes; graphviz present in the test env
fdp-core built against released fdp 0.5.1 fails: bin/fdp resolves to: $PREFIX/bin/dotERROR: bin/fdp is graphviz's binary
fdp-core built against this branch passes both outputs: OK: bin/fdp is the FDP CLI alongside cmflib

The negative control does double duty: it proves the guard detects the real failure, and confirms that shipping cmflib in the metapackage with today's released fdp would break the CLI.

Follow-on (not in this PR)

fdp-core has a matching guard and a members.yaml note recording the coupling: its fdp pin must move to whatever release carries this change before cmflib is blessed, or the metapackage ships a broken fdp. So this wants a release once merged.

🤖 Generated with Claude Code

https://claude.ai/code/session_01Kpkx3jFwQxuBRqoVcw6f27

sammuli and others added 2 commits September 2, 2026 20:41
The `fdp` console script is not always reachable. Graphviz installs its
force-directed layout engine at the same path -- bin/fdp, one of the eight
engines it ships -- and conda has no conflict detection: conda-meta records
graphviz as the owner and graphviz wins. Any environment containing cmflib
pulls graphviz transitively (cmflib -> dvc -> pydot -> graphviz), so `fdp run`
there silently invokes a graph layout tool.

`python -m fdp` resolves through the installed package rather than PATH, so it
works regardless. Additive: the console script is untouched.

Sets sys.argv[0] so usage and error messages say "python -m fdp" rather than
"__main__.py", which is what argparse would otherwise derive.

A rename of the console script is planned for a major release; see
docs/2026-09-02-fdp-cli-rename.md in the workspace. This is useful during that
transition and afterwards.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Kpkx3jFwQxuBRqoVcw6f27
graphviz ships `bin/fdp` -- a symlink to `dot` that dispatches on argv[0] --
and so do we. Anything pulling graphviz into an FDP environment lands a
second owner on `$PREFIX/bin/fdp`, and the CMF provenance stack does exactly
that: cmflib -> dvc -> pydot -> graphviz. Measured on pixi, on a clean env
with `fdp` and `cmflib`:

    bin/fdp -> dot
    $ fdp -V
    fdp - graphviz version 14.1.2

The FDP CLI is not shadowed, it is *gone*: `fdp run python ...` becomes a
graph layout tool. There is nothing to warn the user with, because our entry
point is the file that got replaced -- which is why a deprecation notice
could never have worked here. This is what has blocked putting cmflib and
toksearch_cmf into the fdp-core metapackage.

Declaring graphviz as our own run dependency fixes it: the installer's
topological link order puts a dependency before its dependent, so graphviz
links first, `fdp` links last, and we take the file.

Whether that is a fix or an insurance policy depends on the installer, and
both are worth having:

  pixi/rattler -- graphviz genuinely wins without this; the CLI is broken
    today. With it we win from scratch (3/3 runs, deterministic), on an
    incremental `pixi add cmflib`, and across a real graphviz 13.1.2 ->
    14.1.2 upgrade. That last case means rattler respects existing file
    ownership rather than merely link order. This is the path `fdp-install`
    uses, so it is where most users live.

  micromamba 2.9.0 -- already linked `fdp` last without this dependency
    (graphviz at step 1193, fdp at 1266) and so already worked. Nothing
    enforced that order though; it fell out of the solve. This makes it
    guaranteed instead of lucky.

Nothing is lost on the graphviz side: `dot -Kfdp` is the documented
equivalent of its `fdp` binary.

This relies on installer behaviour that is not a documented guarantee, so it
ships with a recipe test rather than a comment alone. graphviz is now a run
dependency, so the test environment reproduces the collision for free; the
test fails loudly, naming the cause, if graphviz ever wins again. If it does,
the answer is to rename the CLI -- but until then no rename, no deprecation,
and no user-visible change.

Also asserts `python -m fdp` keeps working: it is the one entry point no
other package can take, and the fallback if this ever regresses.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Kpkx3jFwQxuBRqoVcw6f27
@sammuli
sammuli merged commit 4790681 into main Sep 3, 2026
1 check passed
@sammuli
sammuli deleted the feat/fdp-cli-collision branch September 3, 2026 03:57
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.

1 participant