use super::{
compile::{self, CompileEntry},
deps, fingerprint, link, lock, proj_parse,
proj_parse::{Flags, Profile, Project},
toolchain,
ui::{StepStatus, Ui},
};
use miette::{IntoDiagnostic, Result};
use std::{
fs,
path::{Path, PathBuf},
};
pub struct BuildCtx<'a> {
pub project: &'a Project,
pub dir: PathBuf,
pub root: PathBuf,
pub profile: Option<(&'a str, &'a Profile)>,
pub force: bool,
}
impl<'a> BuildCtx<'a> {
pub fn profile_name(&self) -> &str {
self.profile.map_or("default", |(name, _)| name)
}
}
fn built_artifact(
project: &Project,
dir: &Path,
root: &Path,
profile_name: &str,
) -> PathBuf {
let scoped = link::output_scoped(project, dir, root);
if project.is_library() {
link::library_path(project, root, profile_name, scoped)
} else {
link::binary_path(project, root, profile_name, scoped)
}
}
pub fn build_ctx(ctx: &BuildCtx) -> Result<()> {
let ui = Ui::new();
let profile_name = ctx.profile_name();
let project = ctx.project.with_profile(ctx.profile.map(|(_, p)| p));
let lock = lock::LockFile::load(ctx.dir.join("conjure.lock"))?;
let dep_commits: Vec<String> =
lock.entries().values().map(|e| e.commit.clone()).collect();
let mut file_cache = fingerprint::FileCache::load(&ctx.dir);
let fp = fingerprint::fingerprint(
&project,
&ctx.dir,
profile_name,
&dep_commits.iter().map(String::as_str).collect::<Vec<_>>(),
&mut file_cache,
)?;
file_cache.save(&ctx.dir);
let state_file = ctx.dir.join(".conjure/build/state.kdl");
let up_to_date = !ctx.force
&& fs::read_to_string(&state_file).is_ok_and(|s| s == fp)
&& built_artifact(&project, &ctx.dir, &ctx.root, profile_name).exists();
if up_to_date {
ui.println(Some(&StepStatus::Info), "Nothing to do")?;
return Ok(());
}
let dep_libs =
deps::build_deps(&ui, &project, &ctx.dir, &ctx.root, ctx.profile)?;
let (pkg_cflags, pkg_libs) =
deps::resolve_pkg_config(&project, &ctx.dir, &ctx.root)?;
let (objects, entries): (Vec<PathBuf>, Vec<CompileEntry>) =
compile::compile_entries(
&ctx.dir,
&ctx.root,
&project,
profile_name,
pkg_cflags,
)?
.into_iter()
.unzip();
let compile = project
.compile
.as_ref()
.ok_or_else(|| miette::miette!("No compile section in conjure.kdl"))?;
let ld_flags = compile
.ld_flags
.as_ref()
.map(Flags::list)
.unwrap_or_default();
let threads = compile.threads.unwrap_or_else(|| {
std::thread::available_parallelism().map_or(1, |n| n.get())
});
let build_env = toolchain::resolve(compile, &project.language).env();
compile::compile(&ui, &ctx.dir, entries, threads, build_env)?;
let inputs = link::LinkInputs {
objects: &objects,
libs: &dep_libs,
extra_libs: &pkg_libs,
ld_flags: &ld_flags,
compile,
};
link::produce(&ui, &project, &ctx.dir, &ctx.root, profile_name, &inputs)?;
let _ = fs::create_dir_all(state_file.parent().unwrap());
let _ = fs::write(state_file, fp);
Ok(())
}
pub fn build(
project: &Project,
profile: Option<(&str, &Profile)>,
force: bool,
) -> Result<()> {
let cwd = std::env::current_dir().into_diagnostic()?;
let ctx = BuildCtx {
project,
dir: cwd.clone(),
root: cwd.clone(),
profile,
force,
};
build_ctx(&ctx)?;
if let Some(siblings) = &project.siblings {
for sib in siblings.values() {
let sib_dir = cwd.join(&sib.path);
let sib_project =
proj_parse::Project::from_file(sib_dir.join("conjure.kdl"))?;
let sib_ctx = BuildCtx {
project: &sib_project,
dir: sib_dir,
root: cwd.clone(),
profile,
force,
};
build_ctx(&sib_ctx)?;
}
}
Ok(())
}