Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).

## Unreleased
### Added
- Add a `dev_dependencies` manifest section for dependencies that are only needed to work on the package itself. They are resolved when the package is the root package and are not propagated to packages depending on it. Entries accept the same fields as `dependencies`, including `target` and `pass_targets`; listing a package in both sections is an error. Older Bender versions ignore the section with warning `W03`, which is the correct behavior for a consumer.

## 0.32.1 - 2026-07-07
### Added
Expand Down
61 changes: 60 additions & 1 deletion book/src/dependencies.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,65 @@ dependencies:
my_local_ip: { path: "../local_ips/my_ip" }
```

## Dev-Dependencies

Packages listed under `dev_dependencies` are only needed to work on the package itself. They are resolved when the package is the **root package** — the one Bender is invoked in — and are **not** propagated to packages that depend on it.

```yaml
dependencies:
common_cells: "1.39.0"

dev_dependencies:
common_verification: { version: "0.2.5", target: test }
```

The two sections take exactly the same entries: Git, path and revision dependencies, `remote` shorthands, `target` filters and `pass_targets` all behave identically. The only difference is propagation. Listing the same package in both sections is an error.

> **Note:** The section is spelled `dev_dependencies`, matching the snake_case used throughout the manifest. Cargo's kebab-case `dev-dependencies` is *not* accepted; it is reported as an unknown field via warning `W03`.

This is the mechanism to reach for when a package's testbench needs verification IP that its consumers should not inherit. A downstream project depending on such a package resolves only its `dependencies`; the `dev_dependencies` are ignored entirely and never appear in the downstream [`Bender.lock`](./lockfile.md).

### Choosing Between `target` and `dev_dependencies`

The two features are orthogonal and solve different problems:

| | Effect |
|---|---|
| `target` on a dependency | Filters the dependency out of source listings when the target is inactive. The dependency is still resolved and locked, for the root package *and* for everyone depending on it. |
| `dev_dependencies` | Removes the dependency from the graph entirely for anyone depending on this package. Within the root package it behaves exactly like a regular dependency. |

They compose: a dev-dependency may carry a `target`, in which case it is resolved for the root package but only contributes sources when that target is active. This is usually what you want, and it also removes the need for a grouping mechanism — [target expressions](./targets.md) already select subsets of your dev-dependencies:

```yaml
dev_dependencies:
my_vip: { version: "0.2", target: test }
my_tb_utils: { path: "../tb_utils", target: tb }
```

```bash
bender script vsim -t tb
```

### Keeping Dev-Only Sources Consistent

Because Bender emits one flat file list for the whole graph, source groups of a dependency are visible to downstream projects even though its dev-dependencies are not. A package whose sources reference a dev-dependency must therefore keep those sources behind a target that downstream projects do not enable, or the generated file list will reference modules that are no longer in the graph.

The [recommended target conventions](./targets.md#recommended-conventions) draw exactly this line:

- Code under `target: test` is *reusable* verification IP that consumers are expected to enable. Anything it needs belongs in `dependencies`.
- Code under `target: tb` is *non-reusable* testbench and testharness code that consumers never enable. Anything it needs belongs in `dev_dependencies`.

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

sources:
- src/core.sv
- target: tb
files:
- tb/tb_core.sv # may use my_tb_utils; consumers never enable `tb`
```

## Remotes

To avoid repeating full Git URLs, you can define `remotes` in your manifest.
Expand Down Expand Up @@ -96,7 +155,7 @@ remotes:

Dependencies can be conditionally included or configured using targets. For details on how to use target expressions or pass targets to dependencies, see the [Targets](./targets.md) documentation.

> **Note:** A `target` on a dependency only filters that dependency out of *source listings and generated scripts*. It does **not** affect dependency resolution: every dependency declared in [`Bender.yml`](./manifest.md) is still resolved and recorded in [`Bender.lock`](./lockfile.md) regardless of which targets are active.
> **Note:** A `target` on a dependency only filters that dependency out of *source listings and generated scripts*. It does **not** affect dependency resolution: every dependency declared in [`Bender.yml`](./manifest.md) is still resolved and recorded in [`Bender.lock`](./lockfile.md) regardless of which targets are active. To keep a dependency out of dependent packages, use [dev-dependencies](#dev-dependencies).

## Git LFS Support

Expand Down
8 changes: 8 additions & 0 deletions book/src/manifest.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,14 @@ dependencies:
common_cells: { git: "https://github.com/pulp-platform/common_cells.git", version: "1.39" }
```

Packages that are only needed to work on this package itself — testbench infrastructure, for example — belong in `dev_dependencies` instead. They are resolved only when this package is the root package, and are never propagated to packages that depend on it. See [Dev-Dependencies](./dependencies.md#dev-dependencies) for details.

```yaml
# Packages needed only when working on this package itself. Optional.
dev_dependencies:
my_tb_utils: { git: "https://github.com/pulp-platform/my_tb_utils.git", version: "0.1" }
```

The sources section lists the HDL source files belonging to this package. It is optional for packages that only provide headers or are otherwise used without their own source files. More details on the format can be found [here](./sources.md).

```yaml
Expand Down
2 changes: 2 additions & 0 deletions book/src/targets.md
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,8 @@ dependencies:
common_verification: { version: "0.2", target: any(test, simulation) }
```
A `target` here only filters the dependency out of source listings; the dependency is still resolved and inherited by packages depending on yours. To keep a dependency out of dependent packages entirely, declare it under [`dev_dependencies`](./dependencies.md#dev-dependencies) instead. The two compose, and target expressions are the intended way to select subsets of your dev-dependencies.

## Built-in Targets

Bender automatically activates certain targets based on the subcommand and output format. These "default targets" ensure that tool-specific workarounds or flow-specific files are included correctly. You can disable this behavior with the `--no-default-target` flag.
Expand Down
10 changes: 6 additions & 4 deletions src/cmd/fusesoc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,10 @@ pub fn run_single(sess: &Session, args: &FusesocArgs) -> Result<()> {
.load_sources(
sources,
Some(name.as_str()),
sess.manifest.dependencies.keys().cloned().collect(),
sess.manifest
.root_dependencies()
.map(|(name, _)| name.clone())
.collect(),
IndexMap::new(),
version_string.clone(),
)
Expand All @@ -90,9 +93,8 @@ pub fn run_single(sess: &Session, args: &FusesocArgs) -> Result<()> {

let fuse_depend_string = sess
.manifest
.dependencies
.keys()
.map(|dep| {
.root_dependencies()
.map(|(dep, _)| {
(
dep.to_string(),
format!(
Expand Down
11 changes: 4 additions & 7 deletions src/cmd/parents.rs
Original file line number Diff line number Diff line change
Expand Up @@ -167,11 +167,11 @@ pub fn get_parent_array(
targets: bool,
) -> Result<IndexMap<String, Vec<String>>> {
let mut map = IndexMap::<String, Vec<String>>::new();
if sess.manifest.dependencies.contains_key(dep) {
if let Some(root_dep) = sess.manifest.root_dependency(dep) {
if targets {
map.insert(
sess.manifest.package.name.clone(),
match sess.manifest.dependencies.get(dep).unwrap() {
match root_dep {
Dependency::Version {
target: targetspec,
pass_targets: tgts,
Expand Down Expand Up @@ -199,11 +199,8 @@ pub fn get_parent_array(
},
);
} else {
let dep_str = format!(
"{}",
DependencyConstraint::from(&sess.manifest.dependencies[dep])
);
let source = DependencySource::from(&sess.manifest.dependencies[dep]);
let dep_str = format!("{}", DependencyConstraint::from(root_dep));
let source = DependencySource::from(root_dep);
let dep_source = format_dep_source(&source, sess.root);
map.insert(
sess.manifest.package.name.clone(),
Expand Down
3 changes: 1 addition & 2 deletions src/cmd/sources.rs
Original file line number Diff line number Diff line change
Expand Up @@ -163,8 +163,7 @@ pub fn get_passed_targets(
if used_packages.contains(&sess.manifest.package.name) {
required_packages.insert(sess.manifest.package.name.clone());
sess.manifest
.dependencies
.iter()
.root_dependencies()
.for_each(|(name, dep)| match dep {
Dependency::Version {
target: filter,
Expand Down
Loading
Loading