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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) a
## Unreleased
### Added
- 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).
- Add a per-dependency `git_submodules` list to the manifest (`Bender.yml`) that lets a package restrict which of its submodules are cloned (with optional per-entry `recursive` and `shallow` flags, both defaulting to `true`); when absent, all submodules are cloned recursively as before.

## 0.32.0 - 2026-06-05
### Breaking Changes
Expand Down
20 changes: 20 additions & 0 deletions book/src/dependencies.md
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,26 @@ Only disable submodules when none of your dependencies reference sources that li

When submodules are disabled, Bender emits a warning for each dependency that carries submodules, listing the unchecked-out submodule paths and the `git submodule update --init --recursive` command to fetch them back into its checkout.

### Selecting submodules per dependency

A package maintainer who knows which submodules are actually needed can restrict cloning to those submodules by adding a `git_submodules` list to the dependency's own `Bender.yml`:

```yaml
git_submodules:
- sw/deps/printf # string form: clone this submodule
- submodule: sw/deps/cva6-sdk # map form
recursive: false # skip the submodule's own submodules (default: true)
shallow: false # fetch full history (default: true)
# pd/deps/ihp-130-pdk -> omitted, so it is not cloned
```

- **No `git_submodules` field** (the default): all submodules are cloned recursively.
- **`git_submodules` present**: only the listed submodules are cloned; an empty list (`git_submodules: []`) clones none.
- **`recursive`** (default `true`): also update the submodule's own nested submodules. Set it to `false` to fetch only the top-level submodule.
- **`shallow`** (default `true`): fetch the submodule with `--depth 1`. Set it to `false` to clone the full submodule history.

The global `git_submodules: false` / `--git-submodules false` switch always wins: when submodule cloning is disabled globally, the per-dependency list is ignored and no submodules are cloned.

## Version Resolution and the Lockfile

When you run `bender update`, Bender performs the following:
Expand Down
72 changes: 72 additions & 0 deletions src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,12 @@ pub struct Manifest {
pub workspace: Workspace,
/// Vendorized dependencies
pub vendor_package: Vec<VendorPackage>,
/// Per-dependency submodule selection.
///
/// `None` means clone all submodules recursively (the default). `Some`
/// restricts cloning to the listed submodules only (an empty list clones
/// none).
pub git_submodules: Option<Vec<Submodule>>,
}

impl PrefixPaths for Manifest {
Expand Down Expand Up @@ -455,6 +461,8 @@ pub struct PartialManifest {
pub workspace: Option<PartialWorkspace>,
/// External Import dependencies
pub vendor_package: Option<Vec<PartialVendorPackage>>,
/// Per-dependency submodule selection.
pub git_submodules: Option<Vec<StringOrStruct<PartialSubmodule>>>,
/// Unknown extra fields
#[serde(flatten)]
extra: HashMap<String, Value>,
Expand Down Expand Up @@ -604,6 +612,11 @@ impl Validate for PartialManifest {
.wrap_err("Unable to parse vendor_package.")?,
None => Vec::new(),
};
let git_submodules = self
.git_submodules
.map(|subs| subs.validate(vctx))
.transpose()
.wrap_err(format!("In git_submodules of package `{}`:", pkg.name))?;
if !vctx.pre_output {
self.extra.iter().for_each(|(k, _)| {
Warnings::IgnoreUnknownField {
Expand Down Expand Up @@ -643,6 +656,7 @@ impl Validate for PartialManifest {
frozen,
workspace,
vendor_package,
git_submodules,
})
}
}
Expand Down Expand Up @@ -895,6 +909,64 @@ impl Validate for PartialIncludeDir {
}
}

/// A submodule of a dependency that should be cloned.
#[derive(Clone, Debug, Serialize)]
pub struct Submodule {
/// The path of the submodule within the dependency repository.
pub path: String,
/// Whether to also update the submodule's own submodules (`--recursive`).
pub recursive: bool,
/// Whether to fetch the submodule shallowly (`--depth 1`).
pub shallow: bool,
}

/// A partial submodule selection entry.
#[derive(Serialize, Deserialize, Debug)]
pub struct PartialSubmodule {
/// The path of the submodule within the dependency repository.
pub submodule: String,
/// Whether to also update the submodule's own submodules (default: true).
pub recursive: Option<bool>,
/// Whether to fetch the submodule shallowly (default: true).
pub shallow: Option<bool>,
/// Unknown extra fields
#[serde(flatten)]
extra: HashMap<String, Value>,
}

impl FromStr for PartialSubmodule {
type Err = Void;
fn from_str(s: &str) -> std::result::Result<Self, Void> {
Ok(PartialSubmodule {
submodule: s.into(),
recursive: None,
shallow: None,
extra: HashMap::new(),
})
}
}

impl Validate for PartialSubmodule {
type Output = Submodule;
type Error = Error;
fn validate(self, vctx: &ValidationContext) -> Result<Submodule> {
if !vctx.pre_output {
self.extra.iter().for_each(|(k, _)| {
Warnings::IgnoreUnknownField {
field: k.clone(),
pkg: vctx.package_name.to_string(),
}
.emit();
});
}
Ok(Submodule {
path: self.submodule,
recursive: self.recursive.unwrap_or(true),
shallow: self.shallow.unwrap_or(true),
})
}
}

/// A partial filtered preprocessor define
#[derive(Serialize, Deserialize, Debug, Default)]
pub struct PartialDefine {
Expand Down
83 changes: 65 additions & 18 deletions src/sess.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1221,24 +1221,71 @@ impl<'io, 'sess: 'io, 'ctx: 'sess> SessionIo<'sess, 'ctx> {
}
if path.join(".gitmodules").exists() {
if self.sess.config.git_submodules {
let pb = Some(ProgressHandler::new(
self.sess.multiprogress.clone(),
GitProgressOps::Submodule,
name,
));
local_git
.clone()
.spawn_with(
move |c| {
c.arg("submodule")
.arg("update")
.arg("--init")
.arg("--recursive")
.arg("--progress")
},
pb,
)
.await?;
// Re-read the dependency's own manifest (now checked out on
// disk) to see whether it restricts which of its submodules
// should be cloned. Source/include-dir existence is ignored
// here (via `validate_ignore_sources`) so that excluding a
// submodule that holds such paths does not defeat the
// selection; a missing or unparseable manifest falls back to
// cloning all submodules recursively.
let selection = std::fs::File::open(path.join("Bender.yml"))
.ok()
.and_then(|file| {
serde_yaml_ng::from_reader::<_, PartialManifest>(file).ok()
})
.and_then(|partial| partial.validate_ignore_sources().ok())
.and_then(|m| m.git_submodules);

match selection {
// No `git_submodules` field: clone all submodules recursively.
None => {
let pb = Some(ProgressHandler::new(
self.sess.multiprogress.clone(),
GitProgressOps::Submodule,
name,
));
local_git
.clone()
.spawn_with(
move |c| {
c.arg("submodule")
.arg("update")
.arg("--init")
.arg("--recursive")
.arg("--progress")
},
pb,
)
.await?;
}
// `git_submodules` present: clone only the listed
// submodules (an empty list clones none).
Some(subs) => {
for sub in subs {
let pb = Some(ProgressHandler::new(
self.sess.multiprogress.clone(),
GitProgressOps::Submodule,
name,
));
local_git
.clone()
.spawn_with(
move |c| {
c.arg("submodule").arg("update").arg("--init");
if sub.recursive {
c.arg("--recursive");
}
if sub.shallow {
c.arg("--depth").arg("1");
}
c.arg("--progress").arg("--").arg(&sub.path)
},
pb,
)
.await?;
}
}
}
} else {
// Submodules were disabled via the `--git-submodules` flag,
// so they are left unchecked out. Warn the user, listing the
Expand Down