Skip to content

feat: add dev_dependencies manifest section - #341

Draft
fischeti wants to merge 3 commits into
masterfrom
dev-deps
Draft

feat: add dev_dependencies manifest section#341
fischeti wants to merge 3 commits into
masterfrom
dev-deps

Conversation

@fischeti

@fischeti fischeti commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

What

Adds a dev_dependencies section to Bender.yml for dependencies that are only needed to work on a package itself. They are resolved when the package is the root package and are not propagated to packages that depend on it — Cargo's [dev-dependencies] semantics.

dependencies:
  common_cells: "1.39.0"

dev_dependencies:
  my_tb_utils: { path: "../tb_utils", target: tb }

Entries accept exactly the same fields as dependencies — Git/path/revision sources, remote shorthands, target, pass_targets. Listing a package in both sections is an error. The section is spelled dev_dependencies only: every key in the manifest format is snake_case, so Cargo's kebab-case dev-dependencies is deliberately not aliased and falls through to the W03 unknown-field warning, which names the offending key.

Why

Today the only knob is target on a dependency, and as the book already notes, it only filters that dependency out of source listings and generated scripts — it does not affect resolution. Every declared dependency is still resolved, locked, and inherited by everyone downstream. There is currently no way to say "this one is mine, don't inherit it".

Design note: one section, not named groups

Deliberately a single section rather than Python-style named dependency groups. Bender already has a full target-expression language with CLI selection (-t), package scoping, and negation — a second grouping mechanism would overlap with it and raise questions like "what if group test and target: test disagree?". Target expressions give grouping for free:

dev_dependencies:
  my_vip:      { version: "0.2", target: test }
  my_tb_utils: { path: "../tb_utils", target: tb }
bender script vsim -t tb

Implementation

  • config.rs — new dev_dependencies field on Manifest/PartialManifest. Dependency validation is factored into a shared closure so both sections behave identically. Manifest::root_dependencies() / root_dependency() mark every site where dev-deps legitimately enter the picture, keeping the root-only rule greppable rather than implicit.
  • resolver.rs — the root manifest's dev-deps are registered alongside its regular deps and their version constraints chained into mark(). Transitive manifests are untouched, so a dependency's dev-deps are never read.
  • Consumerssess.rs (lockfile validation, topological roots, root package source group), cmd/sources.rs (pass_targets), cmd/parents.rs, cmd/fusesoc.rs.

Drive-by fix

bender fusesoc and bender fusesoc --single disagreed on the root package's depend list: the full run derives it from the shared source tree, --single reads the manifest directly. Aligned so both emit the same list.

Reviewer notes

The source-leakage contract. Bender emits one flat file list for the whole graph, so a dependency's source groups stay visible downstream even though its dev-deps are not. A package whose sources reference a dev-dependency must keep those sources behind a target consumers don't enable, or the generated file list references modules no longer in the graph.

This maps cleanly onto the convention added in #327:

  • target: testreusable VIP that consumers enable → its deps belong in dependencies
  • target: tbnon-reusable testbench code consumers never enable → its deps belong in dev_dependencies

This is documented under Keeping Dev-Only Sources Consistent, but it is currently a documented contract, not enforced. Making it structural would mean dropping tb-gated sources for non-root packages — a separate, more invasive change, deliberately left out of scope here.

Behavioral consequence. As in Cargo, simulating a sub-IP's testbench from a top-level repo no longer pulls that sub-IP's dev-deps, since it isn't the root. That may warrant a --with-dev-deps <pkg> escape hatch later; happy to add it if reviewers think the PULP workflow needs it.

Backward compatibility. Older Bender versions route the unknown dev_dependencies key to the extra-field path, emit W03, and ignore it — which is exactly the correct behavior for a consumer, so this degrades gracefully.

Testing

Split by nature rather than by feature:

  • Manifest parsing/validation — section separation, rejection of the kebab-case spelling, field parity with dependencies, and the duplicate-section error are pure PartialManifest::validate behavior, so they are unit tests in src/config.rs, alongside the existing ones in progress.rs and diagnostic.rs. No subprocess, no fixture.
  • Graph behaviortests/resolution.rs runs the binary against a multi-package fixture built under tests/tmp/, using only path dependencies so it needs no network. Covers root-only resolution, lockfile contents, target composition, bender parents, and a dev-dep that is also a transitive dep of a regular dep (unifies to one lockfile entry, still ranks correctly in the file list). Named for what it covers rather than for this feature, so later resolution tests have a home.

Note that tests/cli_regression deliberately does not carry this: it diffs the current binary against one built from master and asserts stdout is byte-identical, so a new manifest section fails there by construction (golden emits W03 and ignores the field; new resolves it). Its fixture stays untouched, which also means it keeps proving this PR changes nothing for manifests without dev_dependencies.

All local tests pass — 20 lib, 5 resolution, 8 pickle, 18 script. cargo fmt --check clean; clippy warning count unchanged at 16, all pre-existing.

🤖 Generated with Claude Code

Dependencies that are only needed to work on a package itself, such as
testbench infrastructure, currently have no way to be kept out of
dependent projects. The `target` field on a dependency only filters it
out of source listings; it is still resolved and inherited by everyone
depending on the package.

Add a `dev_dependencies` section (alias `dev-dependencies`) whose
entries are resolved only when the package is the root package, and are
never propagated to packages depending on it. Entries accept exactly the
same fields as `dependencies`, so `remote` shorthands, `target` and
`pass_targets` all keep working. Listing a package in both sections is
an error.

Deliberately a single section rather than named groups: target
expressions already provide the selection axis, and a second grouping
mechanism would overlap with them.

`Manifest::root_dependencies` and `root_dependency` mark the sites where
dev-dependencies enter the picture, so the root-only rule stays visible
in the code. This also fixes `bender fusesoc --single` disagreeing with
`bender fusesoc` about the root package's `depend` list, since the
former reads the manifest directly while the latter derives it from the
shared source tree.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@fischeti fischeti changed the title Add dev_dependencies manifest section feat: dev_dependencies manifest section Aug 3, 2026
@fischeti fischeti changed the title feat: dev_dependencies manifest section feat: add dev_dependencies manifest section Aug 3, 2026
fischeti and others added 2 commits August 3, 2026 10:39
…on.rs`

The manifest-level cases (section separation, the `dev-dependencies`
alias, field parity with `dependencies`, and the duplicate-section
error) are pure `PartialManifest::validate` behavior and do not need a
subprocess. Move them to `#[cfg(test)] mod tests` in `config.rs`,
alongside the existing unit tests in `progress.rs` and `diagnostic.rs`.

What remains genuinely needs an end-to-end run against a multi-package
graph, so keep it as an integration test but name the file for what it
covers rather than for one feature, so later resolution and source-tree
tests have a home.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Every key in the manifest format is snake_case, and the alias was the
only serde alias or rename in `config.rs`, so it would have been the
sole kebab-case spelling accepted anywhere in a `Bender.yml`. The
aliases bender does carry are all CLI backward-compatibility shims;
there is nothing to stay compatible with for a brand-new section.

The kebab-case spelling now falls through to the unknown-field path,
which names the offending key:

    warning[W03]: Ignoring unknown field dev-dependencies in package X.
     ╰─› help: Check for typos in dev-dependencies or remove it [...]

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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