use bon::Builder;
use miette::Diagnostic;
use thiserror::Error;
use crate::{
build::{BuildBehaviour, BuildError},
config::Config,
drivers::install_packages::{InstallPackages, InstallPackagesError},
hash::HasIntegrity,
lockfile::LockedPackage,
operations::PackageInstallSpec,
package::{PackageName, PackageReq},
package_db::{PackageDB, PackageDBError},
pipeline::discover::FoundPackage,
project::{IntoLocalRockspecError, Project, ProjectError},
rockspec::Rockspec,
tree::{self, InstallTree, TreeError},
workspace::{Workspace, WorkspaceError},
};
#[derive(Debug, Error, Diagnostic)]
pub enum InstallProjectError {
#[error(transparent)]
#[diagnostic(transparent)]
Project(#[from] ProjectError),
#[error(transparent)]
#[diagnostic(transparent)]
LocalRockspec(#[from] IntoLocalRockspecError),
#[error(transparent)]
#[diagnostic(transparent)]
PackageDB(#[from] PackageDBError),
#[error(transparent)]
#[diagnostic(transparent)]
Tree(#[from] TreeError),
#[error(transparent)]
#[diagnostic(transparent)]
Install(#[from] Box<InstallPackagesError>),
#[error(transparent)]
#[diagnostic(transparent)]
Build(#[from] Box<BuildError>),
#[error("package '{0}' was not installed")]
PackageNotInstalled(PackageName),
#[error("failed to hash the project's sources")]
Hash(#[source] std::io::Error),
#[error(transparent)]
#[diagnostic(transparent)]
Workspace(#[from] WorkspaceError),
}
impl From<InstallPackagesError> for InstallProjectError {
fn from(source: InstallPackagesError) -> Self {
Self::Install(Box::new(source))
}
}
impl From<BuildError> for InstallProjectError {
fn from(source: BuildError) -> Self {
Self::Build(Box::new(source))
}
}
#[derive(Builder)]
#[builder(start_fn = new, finish_fn(name = _build, vis = ""))]
pub struct InstallProject<'a, T>
where
T: InstallTree,
{
project: &'a Project,
config: &'a Config,
tree: &'a T,
#[builder(default = BuildBehaviour::Force)]
behaviour: BuildBehaviour,
workspace: Option<&'a Workspace>,
}
impl<
T: InstallTree + Sync + Send,
State: install_project_builder::State + install_project_builder::IsComplete,
> InstallProjectBuilder<'_, T, State>
{
pub async fn build(self) -> Result<LockedPackage, InstallProjectError> {
let args = self._build();
let config = args.config;
let project = args.project;
let tree = args.tree;
let rockspec = project.local_remote_rockspec()?;
let name = rockspec.package().clone();
let behaviour = if matches!(args.behaviour, BuildBehaviour::Ignore) {
match tree.lockfile()?.entrypoint(&name) {
Some(existing) => {
let source_hash = project
.root()
.hash()
.await
.map_err(InstallProjectError::Hash)?;
if existing.version() == rockspec.version()
&& existing.hashes().source == source_hash
{
return Ok(existing.clone());
}
BuildBehaviour::Force
}
None => args.behaviour,
}
} else {
args.behaviour
};
let root = FoundPackage::from_project_root(rockspec, project.root().to_path_buf());
let package_db = match args.workspace {
Some(workspace) => PackageDB::open(config, workspace).await?,
None => PackageDB::from_config(config).await?,
}
.with_local(vec![root]);
let install_spec =
PackageInstallSpec::new(PackageReq::from(name.clone()), tree::EntryType::Entrypoint)
.build_behaviour(behaviour)
.build();
InstallPackages::new(config, tree)
.package_db(package_db)
.package(install_spec)
.install()
.await?
.0
.into_iter()
.find(|package| package.name() == &name)
.ok_or(InstallProjectError::PackageNotInstalled(name))
}
}