use crate::app::build::add_metadata::add_metadata_to_selected_components;
use crate::app::build::componentize::componentize;
use crate::app::build::gen_rpc::gen_rpc;
use crate::app::build::link::link;
use crate::app::build::task_result_marker::{TaskResultMarker, TaskResultMarkerHashSource};
use crate::app::context::ApplicationContext;
use crate::fs;
use crate::log::{log_warn_action, LogColorize};
use crate::model::app::AppBuildStep;
use anyhow::{anyhow, Context};
use chrono::{DateTime, Utc};
use std::cmp::Ordering;
use std::fmt::Debug;
use std::path::{Path, PathBuf};
use std::time::SystemTime;
use tracing::debug;
use walkdir::WalkDir;
pub mod add_metadata;
pub mod clean;
pub mod command;
pub mod componentize;
pub mod gen_rpc;
pub mod link;
pub mod task_result_marker;
pub async fn build_app(ctx: &mut ApplicationContext) -> anyhow::Result<()> {
if ctx.config.should_run_step(AppBuildStep::GenRpc) {
gen_rpc(ctx).await?;
}
if ctx.config.should_run_step(AppBuildStep::Componentize) {
componentize(ctx).await?;
}
if ctx.config.should_run_step(AppBuildStep::Link) {
link(ctx).await?;
}
if ctx.config.should_run_step(AppBuildStep::AddMetadata) {
add_metadata_to_selected_components(ctx).await?;
}
Ok(())
}
fn env_var_flag(name: &str) -> bool {
std::env::var(name)
.ok()
.map(|flag| {
let flag = flag.to_lowercase();
flag.starts_with("t") || flag == "1"
})
.unwrap_or_default()
}
fn delete_path_logged(context: &str, path: &Path) -> anyhow::Result<()> {
if path.exists() {
log_warn_action(
"Deleting",
format!("{} {}", context, path.log_color_highlight()),
);
fs::remove(path).with_context(|| {
anyhow!(
"Failed to delete {}, path: {}",
context.log_color_highlight(),
path.log_color_highlight()
)
})?;
}
Ok(())
}
fn is_up_to_date<S, T, SP, TP, FS, FT>(skip_check: bool, sources: FS, targets: FT) -> bool
where
S: Debug + IntoIterator<Item = SP>,
T: Debug + IntoIterator<Item = TP>,
SP: AsRef<Path>,
TP: AsRef<Path>,
FS: FnOnce() -> S,
FT: FnOnce() -> T,
{
if skip_check {
debug!("skipping up-to-date check");
return false;
}
fn max_modified(path: &Path) -> Option<SystemTime> {
let mut max_modified: Option<SystemTime> = None;
let mut update_max_modified = |modified: SystemTime| {
if max_modified.is_none_or(|max_mod| max_mod.cmp(&modified) == Ordering::Less) {
max_modified = Some(modified)
}
};
if let Ok(metadata) = fs::metadata(path) {
if metadata.is_dir() {
WalkDir::new(path)
.into_iter()
.filter_map(|entry| entry.ok().and_then(|entry| entry.metadata().ok()))
.filter(|metadata| !metadata.is_dir())
.filter_map(|metadata| metadata.modified().ok())
.for_each(update_max_modified)
} else if let Ok(modified) = metadata.modified() {
update_max_modified(modified)
}
}
debug!(
path = %path.display(),
max_modified = max_modified.map(|d| DateTime::<Utc>::from(d).to_string()),
"max modified"
);
max_modified
}
fn max_modified_short_circuit_on_missing<I: IntoIterator<Item = TP>, TP: AsRef<Path>>(
paths: I,
) -> Option<SystemTime> {
paths
.into_iter()
.map(|path| max_modified(path.as_ref()).ok_or(()))
.collect::<Result<Vec<_>, _>>()
.and_then(|mod_times| mod_times.into_iter().max().ok_or(()))
.ok()
}
let targets = targets();
debug!(targets=?targets, "collected targets");
let max_target_modified = max_modified_short_circuit_on_missing(targets);
let max_target_modified = match max_target_modified {
Some(modified) => modified,
None => {
debug!("missing targets, not up-to-date");
return false;
}
};
let sources = sources();
debug!(source=?sources, "collected sources");
let max_source_modified = max_modified_short_circuit_on_missing(sources);
match max_source_modified {
Some(max_source_modified) => {
let up_to_date = max_source_modified.cmp(&max_target_modified) == Ordering::Less;
debug!(up_to_date, "up to date result based on timestamps");
up_to_date
}
None => {
debug!("missing sources, not up-to-date");
false
}
}
}
pub struct TaskUpToDateCheck<S, T, SP, TP, FS, FT>
where
S: Debug + IntoIterator<Item = SP>,
T: Debug + IntoIterator<Item = TP>,
SP: AsRef<Path>,
TP: AsRef<Path>,
FS: FnOnce() -> S,
FT: FnOnce() -> T,
{
marker_dir: PathBuf,
skip_check: bool,
task_result_marker: Option<TaskResultMarker>,
sources: FS,
targets: FT,
}
impl<S, T, SP, TP, FS, FT> TaskUpToDateCheck<S, T, SP, TP, FS, FT>
where
S: Debug + IntoIterator<Item = SP>,
T: Debug + IntoIterator<Item = TP>,
SP: AsRef<Path>,
TP: AsRef<Path>,
FS: FnOnce() -> S,
FT: FnOnce() -> T,
{
pub fn with_task_result_marker<HS: TaskResultMarkerHashSource>(
mut self,
source: HS,
) -> anyhow::Result<Self> {
self.task_result_marker = Some(TaskResultMarker::new(&self.marker_dir, source)?);
Ok(self)
}
pub fn with_sources<NS, NSP, NFS>(
self,
sources: NFS,
) -> TaskUpToDateCheck<NS, T, NSP, TP, NFS, FT>
where
NS: Debug + IntoIterator<Item = NSP>,
NSP: AsRef<Path>,
NFS: FnOnce() -> NS,
{
TaskUpToDateCheck {
marker_dir: self.marker_dir,
skip_check: self.skip_check,
task_result_marker: self.task_result_marker,
sources,
targets: self.targets,
}
}
pub fn with_targets<NT, NTP, NFT>(
self,
targets: NFT,
) -> TaskUpToDateCheck<S, NT, SP, NTP, FS, NFT>
where
NT: Debug + IntoIterator<Item = NTP>,
NTP: AsRef<Path>,
NFT: FnOnce() -> NT,
{
TaskUpToDateCheck {
marker_dir: self.marker_dir,
skip_check: self.skip_check,
task_result_marker: self.task_result_marker,
sources: self.sources,
targets,
}
}
pub fn run_or_skip<Run: FnOnce() -> anyhow::Result<()>, Skip: FnOnce()>(
self,
run: Run,
skip: Skip,
) -> anyhow::Result<()> {
if is_up_to_date(self.skip_check, self.sources, self.targets) {
skip();
Ok(())
} else {
match self.task_result_marker {
Some(marker) => marker.result(run()),
None => run(),
}
}
}
pub async fn run_async_or_skip<Run: AsyncFnOnce() -> anyhow::Result<()>, Skip: FnOnce()>(
self,
run: Run,
skip: Skip,
) -> anyhow::Result<()> {
if is_up_to_date(self.skip_check, self.sources, self.targets) {
skip();
Ok(())
} else {
match self.task_result_marker {
Some(marker) => marker.result(run().await),
None => run().await,
}
}
}
}
type EmptyIter = std::iter::Empty<PathBuf>;
type EmptyFn = fn() -> EmptyIter;
type EmptyTaskUpToDateCheck =
TaskUpToDateCheck<EmptyIter, EmptyIter, PathBuf, PathBuf, EmptyFn, EmptyFn>;
impl EmptyTaskUpToDateCheck {
pub fn empty(ctx: &ApplicationContext) -> Self {
Self {
marker_dir: ctx.application.task_result_marker_dir(),
skip_check: ctx.config.skip_up_to_date_checks,
task_result_marker: None,
sources: std::iter::empty::<PathBuf>,
targets: std::iter::empty::<PathBuf>,
}
}
}
pub fn new_task_up_to_date_check(ctx: &ApplicationContext) -> EmptyTaskUpToDateCheck {
EmptyTaskUpToDateCheck::empty(ctx)
}