Skip to content

Dependency renames ignored in virtual workspaces, breaking two-major crate collisions #108

Description

@rsJames-ttrpg

Reindeer computes its public-target set from the first-order dependencies of all workspace members, but resolves the renames that name those targets from the root package only. In a virtual workspace (no root package) renames are therefore ignored entirely. This leaves no native way to resolve a real collision: when two semver-incompatible majors of one crate are both public, reindeer emits two bare alias rules with the same name, which buck2 rejects as a duplicate target.

In a virtual workspace, suppose member A depends on parquet 57 and member B on parquet 58. Both are first-order deps of a workspace member, so reindeer marks both public and emits:

alias(name = "parquet", actual = ":parquet-57")
alias(name = "parquet", actual = ":parquet-58")

This fails with a double-registration error. The versioned library targets (parquet-57, parquet-58) are disambiguated correctly by CollisionInfo; only the bare public aliases collide.

The existing escape hatches don't work here; Cargo dependency rename (parquet57 = { package = "parquet" }) — this is the intended mechanism (it flows through dep_renamed → public_rule_name, and CollisionInfo::new_with_reserved even reserves the renamed alias name). But dep_renamed is built solely from root_pkg.dependencies, with the comment:

▎ // Only the root package's renames matter. We don't attempt to merge different rename choices made by different workspace members.

  1. A virtual manifest has no root package, so the map is empty and the rename is silently dropped. Declaring the rename in a member has no effect.
  2. visibility fixup — only tunes the bare alias; the versioned rust_library is hardcoded to Visibility::Private (non-root, non-split), so there's no way to expose :parquet-57 directly instead.
  3. reindeer.toml — no field to rename/remap/suppress an alias or pick a winner among versions.

The public-set computation iterates every workspace member ("first-order dependencies of any workspace member"), so reindeer will happily make a non-root member's dependency public — but then refuses to let that same member rename it, because rename resolution is root-only. The rename map feeds the naming of targets that are themselves derived from all members; sourcing it from a different (narrower) set than the thing it names is the root of the bug.

In the canonical single-third-party-manifest setup there is a root package, so this never surfaces. But virtual workspaces with member-level third-party deps are a supported configuration everywhere else in the code, and this is the one place that assumes a root package.

Candidate fixes (looking for direction before I send a PR)

  1. Source renames from all workspace members, consistent with the public-set logic. This needs a defined merge policy, since the same dependency can be referenced by multiple members — e.g. a declared rename takes precedence over the bare name, identical renames agree, and two distinct renames for the same target are a hard error rather than an order-dependent silent pick. (Today, with root-only, conflicts are impossible, so this introduces a new — but loud — error class only for the newly-supported case.)
  2. A backward-compatible variant: behave exactly as today when a root package exists, and only consult member renames when there is no root. This keeps the change invisible to every current root-package user and confines the new behavior + error to the virtual case that's broken today, at the cost of two code paths.
  3. An explicit config surface instead of overloading Cargo renames e.g. a per-(package, version) alias-name setting in reindeer.toml or a fixup, decoupling "what's the public alias name" from Cargo's rename mechanism entirely.

I have a working implementation of (1) (with the merge policy above and unit tests) and can adapt it to (2) easily. Before I open a PR, which direction would you prefer — or is there a reason the root-only restriction is intentional that I'm missing?

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