Skip to content

[RFE] Add graph check command for pre-rebuild conflict detection #998

@stmcgovern

Description

@stmcgovern

Problem

When a set of requirements has incompatible version constraints, fromager
discovers this only after the full bootstrap completes — during
write_constraints_file(). On large dependency trees this means hours of
build work before the run fails with "no single version meets all
requirements."

The typical workflow is iterative: bootstrap fails with conflicts, the user
edits constraints, re-runs bootstrap. Each iteration costs hours. There is
no cheap way to check the previous build's graph for structural issues or
classify which version conflicts are resolvable before committing to another
full run.

What exists today

  • graph explain-duplicates reports which packages have multiple
    versions and which parents require them. It shows the specifier detail
    but doesn't classify conflicts or provide exit codes for scripting.

  • write_constraints_file resolves conflicts by cascade: process
    packages alphabetically, skip edges from already-resolved parent
    versions, pick the highest feasible version. This runs post-build
    and discovers conflicts only after all wheels are built.

Neither checks graph structure (dangling edges, cycles) or answers the
pre-rebuild question: which conflicts can be resolved by pinning, and
which will cause write_constraints_file to fail?

Proposal

Add a graph check command that checks a previous build's graph.json
in seconds. It performs three checks:

Check What it catches
Well-formed Dangling edges — targets referenced but not in graph (would crash from_dict)
Acyclic Self-loops and cycles in raw JSON (absorbed by from_dict, invisible after construction)
Version-unique Multiple versions of the same package — extra builds, potential conflicts

When version-unique fails, the command classifies each multi-version
package using the same specifier.filter logic as show_explain_duplicates
and write_constraints_file:

  • Collapsible: one version satisfies all consumer specifiers. The extra
    version was an unnecessary build. Reports the feasible pin and binding
    parent.
  • Required: no single version satisfies all consumers.
    write_constraints_file will fail for this package.

The classification is conservative relative to write_constraints_file:
it checks specifiers from ALL parent versions, while the resolver cascades
(skips edges from already-resolved parents). So:

  • Collapsible here guarantees write_constraints_file succeeds
  • Required here may overcount — the resolver's cascade may resolve some

This is the right trade-off for a pre-rebuild gate: false positives for
"required" are safe (the maintainer investigates), false negatives for
"collapsible" would be dangerous (silent build failure).

Output modes

  • Human-readable (default): three-check summary, build efficiency, conflict
    detail with binding parents and specifiers
  • --json: machine-readable for CI integration
  • --constraints: collapsible pins in constraints.txt format

Exit codes

  • 0: all checks pass
  • 1: any check fails (dangling edges, cycles, or version conflicts)

Background

This work comes from fromager-analyze,
an external analysis tool that checks graph.json for structural properties
and version conflicts. The tool was validated against real e2e graphs:

Graph Nodes Well-formed Acyclic Version-unique Conflicts
build-parallel 16 PASS PASS PASS 0
conflict-graph 301 PASS PASS FAIL 24 (20 collapsible, 4 required)
old-graph 219 FAIL (4 dangling) FAIL (self-loops) FAIL 12

The accompanying PR brings the core validation into fromager proper, where it has access
to DependencyGraph and packaging directly rather than reimplementing
them.

Scope

The PR will add:

  • _check_well_formed() — dangling edge detection on raw graph dict
  • _check_acyclicity() — cycle and self-loop detection on raw graph dict
  • _classify_conflicts() — collapsible/required classification via
    DependencyGraph
  • _print_check() — human-readable, JSON, and constraints output
  • check CLI command under fromager graph
  • 25 tests covering all checks, output modes, and edge cases

Follow on RFE/PR will add the maintainer skill to use this tool.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions