wip 2
This commit is contained in:
+178
-1
@@ -1 +1,178 @@
|
||||
pub struct Builder;
|
||||
use std::{
|
||||
fs,
|
||||
path::{Path, PathBuf},
|
||||
process::Command,
|
||||
};
|
||||
|
||||
use anyhow::{Context, bail};
|
||||
use sha2::{Digest, Sha256};
|
||||
|
||||
use crate::{
|
||||
config::Config,
|
||||
graph::{TaskPlan, TaskPlanner},
|
||||
log,
|
||||
recipe::RecipeSet,
|
||||
};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Builder {
|
||||
root: PathBuf,
|
||||
config: Config,
|
||||
container_ready: bool,
|
||||
}
|
||||
|
||||
impl Builder {
|
||||
pub fn new(root: PathBuf, config: Config) -> Self {
|
||||
Self {
|
||||
root,
|
||||
config,
|
||||
container_ready: false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn build(
|
||||
&mut self,
|
||||
recipes: &RecipeSet,
|
||||
requested: &[String],
|
||||
rebuild: bool,
|
||||
dry_run: bool,
|
||||
) -> anyhow::Result<()> {
|
||||
let plan = TaskPlanner::new(&self.root, &self.config.arch, recipes)
|
||||
.build_plan(requested, rebuild)?;
|
||||
self.print_plan(&plan);
|
||||
if !dry_run {
|
||||
bail!("task execution is not implemented yet");
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn fetch(
|
||||
&mut self,
|
||||
recipes: &RecipeSet,
|
||||
requested: &[String],
|
||||
dry_run: bool,
|
||||
) -> anyhow::Result<()> {
|
||||
let plan =
|
||||
TaskPlanner::new(&self.root, &self.config.arch, recipes).fetch_plan(requested)?;
|
||||
self.print_plan(&plan);
|
||||
if !dry_run {
|
||||
bail!("task execution is not implemented yet");
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn ensure_container_ready(&mut self) -> anyhow::Result<()> {
|
||||
if !self.container_ready {
|
||||
self.ensure_container_image(&self.abs_config_path(&self.config.container_dockerfile))?;
|
||||
self.container_ready = true;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn ensure_container_image(&self, dockerfile: &Path) -> anyhow::Result<()> {
|
||||
if !dockerfile.exists() {
|
||||
bail!(
|
||||
"configured container Dockerfile does not exist: {}",
|
||||
dockerfile.display()
|
||||
);
|
||||
}
|
||||
|
||||
let hash = self.container_build_hash(dockerfile)?;
|
||||
let stamp = self.root.join("build/container-image.hash");
|
||||
if fs::read_to_string(&stamp).ok().as_deref() == Some(hash.as_str())
|
||||
&& self.container_image_exists()?
|
||||
{
|
||||
log::skip(
|
||||
"image",
|
||||
&format!("using cached {}", self.config.container_image),
|
||||
);
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
log::step(
|
||||
"image",
|
||||
&format!(
|
||||
"building {} from {}",
|
||||
self.config.container_image,
|
||||
dockerfile.display()
|
||||
),
|
||||
);
|
||||
let runtime = self.config.container_runtime.as_str();
|
||||
let status = Command::new(runtime)
|
||||
.arg("build")
|
||||
.arg("-f")
|
||||
.arg(dockerfile)
|
||||
.arg("-t")
|
||||
.arg(&self.config.container_image)
|
||||
.arg(&self.root)
|
||||
.status()
|
||||
.with_context(|| {
|
||||
format!(
|
||||
"failed to build container image `{}` from {}",
|
||||
self.config.container_image,
|
||||
dockerfile.display()
|
||||
)
|
||||
})?;
|
||||
if !status.success() {
|
||||
bail!(
|
||||
"container image build failed for `{}` with {status}",
|
||||
self.config.container_image
|
||||
);
|
||||
}
|
||||
|
||||
fs::create_dir_all(stamp.parent().unwrap())?;
|
||||
fs::write(stamp, hash)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn container_image_exists(&self) -> anyhow::Result<bool> {
|
||||
let runtime = self.config.container_runtime.as_str();
|
||||
let status = Command::new(runtime)
|
||||
.arg("image")
|
||||
.arg("exists")
|
||||
.arg(&self.config.container_image)
|
||||
.status()
|
||||
.with_context(|| {
|
||||
format!(
|
||||
"failed to inspect container image `{}`",
|
||||
self.config.container_image
|
||||
)
|
||||
})?;
|
||||
Ok(status.success())
|
||||
}
|
||||
|
||||
fn container_build_hash(&self, dockerfile: &Path) -> anyhow::Result<String> {
|
||||
let mut hasher = Sha256::new();
|
||||
hasher.update(fs::read(dockerfile)?);
|
||||
hasher.update(self.config.container_image.as_bytes());
|
||||
Ok(hex::encode(hasher.finalize()))
|
||||
}
|
||||
|
||||
fn print_plan(&self, plan: &TaskPlan) {
|
||||
if plan.is_empty() {
|
||||
log::skip("plan", "nothing to do");
|
||||
return;
|
||||
}
|
||||
|
||||
log::step(
|
||||
"plan",
|
||||
&format!(
|
||||
"{} active task(s), {} edge(s)",
|
||||
plan.order().len(),
|
||||
plan.dependency_count()
|
||||
),
|
||||
);
|
||||
for task in plan.order() {
|
||||
println!("{task}");
|
||||
}
|
||||
}
|
||||
|
||||
fn abs_config_path(&self, path: &Path) -> PathBuf {
|
||||
if path.is_absolute() {
|
||||
path.to_path_buf()
|
||||
} else {
|
||||
self.root.join(path)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user