use crate::env;
use crate::step_job::StepJob;
use crate::tera;
use std::path::PathBuf;
use std::sync::Arc;
use super::types::Step;
impl Step {
pub(crate) fn estimate_files_string_size(&self, files: &[PathBuf]) -> usize {
files
.iter()
.map(|f| {
let path_str = f.to_str().unwrap_or("");
path_str.len() * 2 + 2 + 1
})
.sum()
}
fn render_run_command_size(
&self,
original_job: &StepJob,
files: &[PathBuf],
base_tctx: &tera::Context,
) -> Option<usize> {
let run_cmd = if original_job.check_first {
self.check_first_cmd()
} else {
self.run_cmd(original_job.run_type)
}?;
let run = run_cmd.to_string();
if run.trim().is_empty() {
return None;
}
let run = if let Some(prefix) = &self.prefix {
format!("{prefix} {run}")
} else {
run
};
let mut temp = StepJob::new(
Arc::clone(&original_job.step),
files.to_vec(),
original_job.run_type,
);
temp.check_first = original_job.check_first;
if let Some(wi) = original_job.workspace_indicator() {
temp = temp.with_workspace_indicator(wi.clone());
}
let tctx = temp.tctx(base_tctx);
tera::render(&run, &tctx).ok().map(|s| s.len())
}
pub(crate) fn auto_batch_jobs(
&self,
jobs: Vec<StepJob>,
base_tctx: &tera::Context,
) -> Vec<StepJob> {
if self.stdin.is_some() {
return jobs;
}
let safe_limit = *env::ARG_MAX / 2;
let mut batched_jobs = Vec::with_capacity(jobs.len());
for job in jobs {
if job.skip_reason.is_some() || job.files.len() <= 1 {
batched_jobs.push(job);
continue;
}
let full_size = self
.render_run_command_size(&job, &job.files, base_tctx)
.unwrap_or_else(|| self.estimate_files_string_size(&job.files));
if full_size <= safe_limit {
batched_jobs.push(job);
continue;
}
debug!(
"{}: auto-batching {} files (rendered size: {} bytes, limit: {} bytes)",
self.name,
job.files.len(),
full_size,
safe_limit
);
let mut low = 1;
let mut high = job.files.len();
while low < high {
let mid = (low + high).div_ceil(2);
let test_size = self
.render_run_command_size(&job, &job.files[..mid], base_tctx)
.unwrap_or_else(|| self.estimate_files_string_size(&job.files[..mid]));
if test_size <= safe_limit {
low = mid;
} else {
high = mid - 1;
}
}
let batch_size = low.max(1);
debug!(
"{}: using batch size of {} files per batch",
self.name, batch_size
);
for chunk in job.files.chunks(batch_size) {
let mut new_job = StepJob::new(Arc::clone(&job.step), chunk.to_vec(), job.run_type);
new_job.check_first = job.check_first;
new_job.skip_reason = job.skip_reason.clone();
if let Some(wi) = job.workspace_indicator() {
new_job = new_job.with_workspace_indicator(wi.clone());
}
batched_jobs.push(new_job);
}
}
batched_jobs
}
}
impl Step {
pub(crate) fn has_filters(&self) -> bool {
self.glob.is_some()
|| self.dir.is_some()
|| self
.exclude
.as_ref()
.is_some_and(|pattern| !pattern.is_empty())
|| self.types.is_some()
}
}