use crate::graph::Product;
use crate::object_store::{ExplainAction, ObjectStore};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ProductAction {
Skip,
Restore,
Build,
}
pub trait BuildPolicy: Sync + Send {
fn classify(
&self,
ctx: &crate::build_context::BuildContext,
product: &Product,
object_store: &ObjectStore,
input_checksum: &str,
dep_changed: bool,
force: bool,
) -> ProductAction;
fn explain(
&self,
ctx: &crate::build_context::BuildContext,
product: &Product,
object_store: &ObjectStore,
input_checksum: &str,
force: bool,
) -> ExplainAction;
}
pub struct IncrementalPolicy;
impl BuildPolicy for IncrementalPolicy {
fn classify(
&self,
ctx: &crate::build_context::BuildContext,
product: &Product,
object_store: &ObjectStore,
input_checksum: &str,
dep_changed: bool,
force: bool,
) -> ProductAction {
let desc_key = product.descriptor_key(input_checksum);
let needs_rebuild = object_store.needs_rebuild_descriptor(ctx, &desc_key, &product.outputs);
if !force && !dep_changed && !needs_rebuild {
ProductAction::Skip
} else if !force && !dep_changed && object_store.can_restore_descriptor(ctx, &desc_key) {
ProductAction::Restore
} else {
ProductAction::Build
}
}
fn explain(
&self,
ctx: &crate::build_context::BuildContext,
product: &Product,
object_store: &ObjectStore,
input_checksum: &str,
force: bool,
) -> ExplainAction {
let desc_key = product.descriptor_key(input_checksum);
object_store.explain_descriptor(ctx, &desc_key, &product.outputs, force)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::build_context::BuildContext;
use crate::graph::BuildGraph;
use std::fs;
#[test]
fn classify_decision_table() {
let tmp = tempfile::TempDir::new().unwrap();
let store = ObjectStore::new_in(tmp.path());
let policy = IncrementalPolicy;
let ctx = BuildContext::new();
ctx.set_mtime_check(false);
let chk = "cafe0123";
let mut g = BuildGraph::new();
let out = tmp.path().join("out.txt");
let gen_id = g
.add_product(
vec![tmp.path().join("in.txt")],
vec![out.clone()],
"gen",
None,
)
.unwrap();
let chk_id = g
.add_product(vec![tmp.path().join("in2.txt")], vec![], "check", None)
.unwrap();
let generator = g.get_product(gen_id).unwrap();
assert_eq!(
policy.classify(&ctx, generator, &store, chk, false, false),
ProductAction::Build
);
let checker = g.get_product(chk_id).unwrap();
store
.store_marker(&ctx, &checker.descriptor_key(chk))
.unwrap();
assert_eq!(
policy.classify(&ctx, checker, &store, chk, false, false),
ProductAction::Skip
);
assert_eq!(
policy.classify(&ctx, checker, &store, chk, true, false),
ProductAction::Build,
"dep_changed must invalidate a matching marker"
);
assert_eq!(
policy.classify(&ctx, checker, &store, chk, false, true),
ProductAction::Build,
"force must beat a matching marker"
);
fs::write(&out, b"built output").unwrap();
store
.store_blob_descriptor(&ctx, &generator.descriptor_key(chk), &out)
.unwrap();
assert_eq!(
policy.classify(&ctx, generator, &store, chk, false, false),
ProductAction::Skip
);
fs::remove_file(&out).unwrap();
assert_eq!(
policy.classify(&ctx, generator, &store, chk, false, false),
ProductAction::Restore
);
assert_eq!(
policy.classify(&ctx, generator, &store, chk, true, false),
ProductAction::Build,
"dep_changed must beat a restorable cache"
);
assert_eq!(
policy.classify(&ctx, generator, &store, chk, false, true),
ProductAction::Build,
"force must beat a restorable cache"
);
assert_eq!(
policy.classify(&ctx, generator, &store, "other993", false, false),
ProductAction::Build
);
}
#[test]
fn classify_corrupted_output_restores() {
let tmp = tempfile::TempDir::new().unwrap();
let store = ObjectStore::new_in(tmp.path());
let policy = IncrementalPolicy;
let ctx = BuildContext::new();
ctx.set_mtime_check(false);
let chk = "beef4567";
let mut g = BuildGraph::new();
let out = tmp.path().join("out.bin");
let id = g
.add_product(
vec![tmp.path().join("in.bin")],
vec![out.clone()],
"gen",
None,
)
.unwrap();
let p = g.get_product(id).unwrap();
fs::write(&out, b"good").unwrap();
store
.store_blob_descriptor(&ctx, &p.descriptor_key(chk), &out)
.unwrap();
fs::write(&out, b"corrupted").unwrap();
assert_eq!(
policy.classify(&ctx, p, &store, chk, false, false),
ProductAction::Restore
);
}
}