Skip to content
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) a

## Unreleased
### Added
- Add version namespaces to Git version dependencies, allowing versioned forks to be tagged and resolved under their own prefix (e.g. `companyX-v1.2.0`). The prefix may be given via the `version_prefix` field or embedded in the version string (`version: "companyX-v1.2.0"`). Defaults to `v` for full backwards compatibility; namespaces are strict and never mix, and the resolved prefix is recorded in `Bender.lock`.
- `bender audit` now aligns its version-bump suggestions to the namespace a dependency is currently resolved under, falling back to the default `v` namespace when the current checkout is not a version.
- Add `git_submodules` config field and `--git-submodules <true|false>` flag (env `BENDER_GIT_SUBMODULES`) to control cloning of dependency submodules; defaults to `true`, the flag overrides the configured value in either direction (https://github.com/pulp-platform/bender/pull/314).

### Fixed
Expand Down
28 changes: 27 additions & 1 deletion book/src/dependencies.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,33 @@ dependencies:
axi: { version: ">=0.23.0, <0.26.0" }
```

> **Note:** Bender only recognizes Git tags that follow the `vX.Y.Z` format (e.g., `v1.2.1`).
> **Note:** By default, Bender only recognizes Git tags that follow the `vX.Y.Z` format (e.g., `v1.2.1`). See [Version Namespaces](#version-namespaces) if you need a different prefix.

#### Version Namespaces

The leading `v` in a version tag is simply the default *prefix*. If you maintain your own versioned fork of an open-source IP — for example to ship internally patched releases — you can tag those releases under your own namespace and depend on them with `version_prefix`:

```yaml
dependencies:
# Resolves only tags of the form `companyX-v<semver>`, e.g. `companyX-v1.2.0`.
common_cells: { git: "...", version: "1.21.0", version_prefix: "companyX-v" }
```

You can equivalently embed the prefix directly in the version string:

```yaml
dependencies:
common_cells: { git: "...", version: "companyX-v1.21.0" }
```

The embedded form requires the version requirement to begin with a number (e.g. `companyX-v1.21.0`, `companyX-v1.*`). For operator-based ranges such as `>=1.21.0`, use the `version_prefix` field alongside a plain `version`. If both a field and an embedded prefix are given, they must agree.

The prefix is the entire literal string preceding the semantic version, so you are free to choose any convention (`companyX-v`, `acme-`, …). A dependency without a prefix keeps the default `v`, so existing manifests and lockfiles are unaffected. Prefixes only apply to Git version dependencies.

Namespaces are **strict and never mix**:

- A dependency resolves *only* tags carrying its own prefix. There is no fallback to the default `v` namespace (or any other).
- If the same dependency is required with two different prefixes anywhere in the dependency tree, resolution fails rather than guessing. Resolve this by adding an `overrides` entry that pins the dependency to a single namespace.

#### Revision-based
Use this for specific commits, branches, or tags that don't follow SemVer.
Expand Down
1 change: 1 addition & 0 deletions book/src/lockfile.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ packages:

- **revision:** The full 40-character Git commit hash.
- **version:** The SemVer version that was resolved.
- **version_prefix:** The version [namespace](./dependencies.md#version-namespaces) the version was resolved under. Omitted for the default `v` prefix.
- **source:** Where to download the package from.
- **dependencies:** A list of other packages that this specific package depends on, ensuring the entire tree is captured.

Expand Down
32 changes: 28 additions & 4 deletions src/cmd/audit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,16 @@ pub struct AuditArgs {
pub ignore_url_conflict: bool,
}

/// Parse the version requirement from a parent constraint string.
///
/// These strings come from `DependencyConstraint`'s `Display`. A namespaced
/// constraint renders as `"<req> (prefix `<p>`)"`; since the namespace is
/// already fixed by resolution, only the requirement is relevant here, so we
/// drop any trailing prefix annotation before parsing.
fn parent_version_req(s: &str) -> std::result::Result<VersionReq, semver::Error> {
VersionReq::parse(s.split(" (prefix ").next().unwrap_or(s).trim())
}

/// Execute the `audit` subcommand.
pub fn run(sess: &Session, args: &AuditArgs) -> Result<()> {
let rt = Runtime::new().into_diagnostic()?;
Expand Down Expand Up @@ -85,10 +95,24 @@ pub fn run(sess: &Session, args: &AuditArgs) -> Result<()> {
.unwrap_or_default();
let current_revision = sess.dependency(*pkg).revision.clone();
let current_revision_unwrapped = current_revision.as_deref().unwrap_or_default();
// Align update suggestions with the namespace the dependency is
// currently resolved under. When the current checkout is not a version
// (e.g. a path or revision), fall back to the default `v` namespace.
let current_prefix = if current_version.is_some() {
sess.dependency(*pkg)
.version_prefix
.as_deref()
.unwrap_or(crate::config::DEFAULT_VERSION_PREFIX)
} else {
crate::config::DEFAULT_VERSION_PREFIX
};
let available_versions = match dep_versions.get(pkg).unwrap() {
DependencyVersions::Git(versions) => {
versions.versions.iter().map(|(v, _)| v.clone()).collect()
}
DependencyVersions::Git(versions) => versions
.versions
.iter()
.filter(|tv| tv.prefix == current_prefix)
.map(|tv| tv.version.clone())
.collect(),
_ => vec![],
};
let highest_version = available_versions.iter().max();
Expand All @@ -102,7 +126,7 @@ pub fn run(sess: &Session, args: &AuditArgs) -> Result<()> {
.map(|v| (v[0].clone(), v[1].clone()))
.unwrap_or_else(|| ("".to_string(), "".to_string()));
for parent in parent_array.values() {
match VersionReq::parse(&parent[0]) {
match parent_version_req(&parent[0]) {
Ok(parent_version) => {
compatible_versions.retain(|v| parent_version.matches(v));
version_req_exists = true;
Expand Down
9 changes: 8 additions & 1 deletion src/cmd/packages.rs
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,14 @@ pub fn run(sess: &Session, args: &PackagesArgs) -> Result<()> {
"{}:\t{}\tat {}\t{}\n",
pkg_source.name,
match pkg_source.version {
Some(ref v) => format!("v{}", v),
Some(ref v) => format!(
"{}{}",
pkg_source
.version_prefix
.as_deref()
.unwrap_or(crate::config::DEFAULT_VERSION_PREFIX),
v
),
None => "".to_string(),
},
pkg_source.source,
Expand Down
Loading
Loading