use std::collections::{HashMap, HashSet};
use anyhow::{Context, Result, bail};
use rayon::prelude::*;
use bob::Interrupted;
use bob::build::{self, Build};
use bob::config::{Config, Pkgsrc};
use bob::db::Database;
use bob::sandbox::SandboxScope;
pub fn check_up_to_date(config: &Config, pkgsrc: &Pkgsrc, db: &Database) -> Result<usize> {
let pkgsrc_env = match db.load_pkgsrc_env() {
Ok(env) => env,
Err(_) => {
tracing::warn!("PkgsrcEnv not cached, skipping up-to-date check");
return Ok(0);
}
};
let packages_dir = pkgsrc_env.packages.join("All");
let mut up_to_date_count = 0usize;
db.clear_build_reasons()?;
bob::print_status("Calculating package build status");
let start = std::time::Instant::now();
let pool = rayon::ThreadPoolBuilder::new()
.num_threads(config.scan_threads())
.thread_name(|i| format!("up-to-date-{i}"))
.build()
.context("Failed to build thread pool for up-to-date check")?;
let rows = db.get_buildable_rows()?;
let order: Vec<i64> = rows.iter().map(|&(id, ..)| id).collect();
let packages: HashMap<i64, (String, String)> = rows
.into_iter()
.map(|(id, pkgname, pkg_location)| (id, (pkgname, pkg_location)))
.collect();
let mut forward_deps: HashMap<i64, Vec<i64>> =
packages.keys().map(|&id| (id, Vec::new())).collect();
let mut reverse_deps: HashMap<i64, Vec<i64>> = HashMap::new();
for (pkg, dep) in db.get_buildable_depends()? {
forward_deps.entry(pkg).or_default().push(dep);
reverse_deps.entry(dep).or_default().push(pkg);
}
let mut remaining: HashSet<i64> = packages.keys().copied().collect();
let mut needs_rebuild: HashSet<i64> = HashSet::new();
let mut propagated_from: HashMap<i64, i64> = HashMap::new();
let mut checked_results: Vec<(i64, anyhow::Result<Option<bob::BuildReason>>)> = Vec::new();
{
let tx = db.transaction()?;
for (&id, (pkgname, _)) in &packages {
let pkgfile = packages_dir.join(format!("{}.tgz", pkgname));
if !pkgfile.exists() {
needs_rebuild.insert(id);
db.store_build_reason(id, &bob::BuildReason::PackageNotFound.to_string())?;
}
}
tx.commit()?;
}
while !remaining.is_empty() {
let ready: Vec<i64> = order
.iter()
.filter(|id| {
remaining.contains(id)
&& forward_deps[*id].iter().all(|dep| !remaining.contains(dep))
})
.copied()
.collect();
if ready.is_empty() {
break;
}
let to_check: Vec<i64> = ready
.iter()
.filter(|id| !needs_rebuild.contains(id))
.copied()
.collect();
let wave_results: Vec<_> = pool.install(|| {
to_check
.par_iter()
.map(|&id| {
let depends: Vec<&str> = forward_deps[&id]
.iter()
.map(|dep| packages[dep].0.as_str())
.collect();
let result = bob::pkg_up_to_date(
&packages[&id].0,
&depends,
&packages_dir,
&pkgsrc.basedir,
);
(id, result)
})
.collect()
});
for (id, result) in wave_results {
if matches!(&result, Ok(Some(_)) | Err(_)) {
needs_rebuild.insert(id);
let mut worklist = vec![id];
while let Some(dep) = worklist.pop() {
if let Some(dependents) = reverse_deps.get(&dep) {
for &dependent in dependents {
if needs_rebuild.insert(dependent) {
propagated_from.insert(dependent, dep);
worklist.push(dependent);
}
}
}
}
}
checked_results.push((id, result));
}
for id in ready {
remaining.remove(&id);
}
}
let build_id = db.build_id()?;
let mut history_inputs = Vec::new();
{
let tx = db.transaction()?;
for (id, result) in checked_results {
let (pkgname, pkg_location) = &packages[&id];
match result {
Ok(None) => {
if db.is_successful(id)? {
continue;
}
let build_result = bob::BuildResult {
pkgname: pkgsrc::PkgName::new(pkgname),
pkgpath: pkgsrc::PkgPath::new(pkg_location).ok(),
state: bob::PackageState::UpToDate,
log_dir: None,
build_stats: bob::PkgBuildStats::default(),
};
db.store_build_result(id, &build_result)?;
if let Some(mut input) = build_result.history_input() {
input.build_id = Some(build_id.clone());
history_inputs.push(input);
}
up_to_date_count += 1;
}
Ok(Some(reason)) => {
db.store_build_reason(id, &reason.to_string())?;
}
Err(e) => {
tracing::debug!(
pkgname,
error = format!("{e:#}"),
"Error checking up-to-date status"
);
db.store_build_reason(id, &format!("check failed: {}", e))?;
}
}
}
for (id, dep) in propagated_from {
let reason = bob::BuildReason::DependencyRefresh(packages[&dep].0.clone());
db.store_build_reason(id, &reason.to_string())?;
}
tx.commit()?;
}
db.record_history_batch(&history_inputs)
.context("Failed to record up-to-date history")?;
bob::print_elapsed("Calculating package build status", start.elapsed());
db.record_up_to_date_count(&build_id, up_to_date_count)?;
Ok(up_to_date_count)
}
pub fn run_build_with(
config: &Config,
pkgsrc: &Pkgsrc,
db: &Database,
state: &bob::RunState,
scope: SandboxScope,
) -> Result<build::BuildSummary> {
let buildable = db.get_buildable_packages()?;
if buildable.is_empty() {
bail!("No packages to build");
}
let pkgsrc_env = db
.load_pkgsrc_env()
.context("PkgsrcEnv not cached - try 'bob clean' first")?;
let mut build = Build::new(config, pkgsrc, pkgsrc_env, scope, buildable);
build.load_cached_from_db(db)?;
tracing::debug!("Calling build.start()");
let build_start_time = std::time::Instant::now();
let mut summary = build.start(state, db)?;
let build_elapsed = build_start_time.elapsed();
tracing::debug!(
elapsed_ms = build_elapsed.as_millis(),
"build.start() returned"
);
db.add_build_duration(build_elapsed)?;
if state.interrupted() {
return Err(Interrupted.into());
}
let skipped_results = db.get_scan_outcomes()?;
let scanfail_results: Vec<(pkgsrc::PkgPath, String)> = db
.get_scan_failures()?
.into_iter()
.filter_map(|(p, e)| pkgsrc::PkgPath::new(&p).ok().map(|pp| (pp, e)))
.collect();
let build_id = db.build_id().ok();
if let Some(bid) = &build_id
&& let Some(rev) = db.load_vcs_info().ok().and_then(|v| v.revision_full)
&& let Err(e) = db.store_build_revision(bid, &rev)
{
tracing::warn!(error = format!("{e:#}"), "Failed to save build revision");
}
let history_inputs: Vec<_> = skipped_results
.iter()
.filter_map(|result| {
result.history_input().map(|mut input| {
input.build_id = build_id.clone();
input
})
})
.collect();
if let Err(e) = db.record_history_batch(&history_inputs) {
tracing::warn!(error = format!("{e:#}"), "Failed to save skipped history");
}
summary.results.extend(skipped_results);
summary.scanfail.extend(scanfail_results);
Ok(summary)
}