use std::path::PathBuf;
use std::sync::Arc;
use anyhow::{Context, Result};
use async_std::fs;
use futures::channel::mpsc::Sender;
use futures::stream::StreamExt;
use crate::common::{remove_dir_all, BUILDING, ERROR, SUCCESS};
use crate::config::{RtcBuild, STAGE_DIR};
use crate::pipelines::HtmlPipeline;
pub struct BuildSystem {
cfg: Arc<RtcBuild>,
html_pipeline: Arc<HtmlPipeline>,
}
impl BuildSystem {
pub async fn new(cfg: Arc<RtcBuild>, ignore_chan: Option<Sender<PathBuf>>) -> Result<Self> {
let html_pipeline = Arc::new(HtmlPipeline::new(cfg.clone(), ignore_chan)?);
Ok(Self { cfg, html_pipeline })
}
#[tracing::instrument(level = "trace", skip(self))]
pub async fn build(&mut self) -> Result<()> {
tracing::info!("{} starting build", BUILDING);
let res = self.do_build().await;
match res {
Ok(_) => {
tracing::info!("{} success", SUCCESS);
Ok(())
}
Err(err) => {
tracing::error!("{} error\n{:?}", ERROR, err);
Err(err)
}
}
}
async fn do_build(&mut self) -> Result<()> {
fs::create_dir_all(self.cfg.final_dist.as_path())
.await
.with_context(|| "error creating build environment directory: dist")?;
self.prepare_staging_dist().await.context("error preparing build environment")?;
self.html_pipeline.clone().spawn().await.context("error from HTML pipeline")?;
self.finalize_dist().await.context("error applying built distribution")?;
Ok(())
}
async fn prepare_staging_dist(&self) -> Result<()> {
let staging_dist = self.cfg.staging_dist.as_path();
remove_dir_all(staging_dist.into()).await.context("error cleaning staging dist dir")?;
fs::create_dir_all(staging_dist)
.await
.with_context(|| "error creating build environment directory: staging dist dir")?;
Ok(())
}
#[tracing::instrument(level = "trace", skip(self))]
async fn finalize_dist(&self) -> Result<()> {
let staging_dist = self.cfg.staging_dist.clone();
tracing::info!("applying new distribution");
self.clean_final().await?;
self.move_stage_to_final().await?;
fs::remove_dir(staging_dist).await.context("error deleting staging dist dir")?;
Ok(())
}
async fn move_stage_to_final(&self) -> Result<()> {
let final_dist = self.cfg.final_dist.clone();
let staging_dist = self.cfg.staging_dist.clone();
let mut entries = fs::read_dir(&staging_dist).await.context("error reading staging dist dir")?;
while let Some(entry) = entries.next().await {
let entry = entry.context("error reading contents of staging dist dir")?;
let target_path = final_dist.join(entry.file_name());
fs::rename(entry.path(), &target_path)
.await
.with_context(|| format!("error moving {:?} to {:?}", &entry.path(), &target_path))?;
}
Ok(())
}
async fn clean_final(&self) -> Result<()> {
let final_dist = self.cfg.final_dist.clone();
let mut entries = fs::read_dir(&final_dist).await.context("error reading final dist dir")?;
while let Some(entry) = entries.next().await {
let entry = entry.context("error reading contents of final dist dir")?;
if entry.file_name() == STAGE_DIR {
continue;
}
let file_type = entry.file_type().await.context("error reading metadata of file in final dist dir")?;
if file_type.is_dir() {
remove_dir_all(entry.path().into()).await.context("error cleaning final dist")?;
} else if file_type.is_symlink() || file_type.is_file() {
fs::remove_file(entry.path()).await.context("error cleaning final dist")?;
}
}
Ok(())
}
}