lux_lib/operations/
install_project.rs1use bon::Builder;
2use miette::Diagnostic;
3use thiserror::Error;
4
5use crate::{
6 build::{BuildBehaviour, BuildError},
7 config::Config,
8 drivers::install_packages::{InstallPackages, InstallPackagesError},
9 hash::HasIntegrity,
10 lockfile::LockedPackage,
11 operations::PackageInstallSpec,
12 package::{PackageName, PackageReq},
13 package_db::{PackageDB, PackageDBError},
14 pipeline::discover::FoundPackage,
15 project::{IntoLocalRockspecError, Project, ProjectError},
16 rockspec::Rockspec,
17 tree::{self, InstallTree, TreeError},
18 workspace::{Workspace, WorkspaceError},
19};
20
21#[derive(Debug, Error, Diagnostic)]
22pub enum InstallProjectError {
23 #[error(transparent)]
24 #[diagnostic(transparent)]
25 Project(#[from] ProjectError),
26 #[error(transparent)]
27 #[diagnostic(transparent)]
28 LocalRockspec(#[from] IntoLocalRockspecError),
29 #[error(transparent)]
30 #[diagnostic(transparent)]
31 PackageDB(#[from] PackageDBError),
32 #[error(transparent)]
33 #[diagnostic(transparent)]
34 Tree(#[from] TreeError),
35 #[error(transparent)]
36 #[diagnostic(transparent)]
37 Install(#[from] Box<InstallPackagesError>),
38 #[error(transparent)]
39 #[diagnostic(transparent)]
40 Build(#[from] Box<BuildError>),
41 #[error("package '{0}' was not installed")]
42 PackageNotInstalled(PackageName),
43 #[error("failed to hash the project's sources")]
44 Hash(#[source] std::io::Error),
45 #[error(transparent)]
46 #[diagnostic(transparent)]
47 Workspace(#[from] WorkspaceError),
48}
49
50impl From<InstallPackagesError> for InstallProjectError {
51 fn from(source: InstallPackagesError) -> Self {
52 Self::Install(Box::new(source))
53 }
54}
55
56impl From<BuildError> for InstallProjectError {
57 fn from(source: BuildError) -> Self {
58 Self::Build(Box::new(source))
59 }
60}
61
62#[derive(Builder)]
66#[builder(start_fn = new, finish_fn(name = _build, vis = ""))]
67pub struct InstallProject<'a, T>
68where
69 T: InstallTree,
70{
71 project: &'a Project,
72
73 config: &'a Config,
74
75 tree: &'a T,
76
77 #[builder(default = BuildBehaviour::Force)]
78 behaviour: BuildBehaviour,
79
80 workspace: Option<&'a Workspace>,
82}
83
84impl<
85 T: InstallTree + Sync + Send,
86 State: install_project_builder::State + install_project_builder::IsComplete,
87 > InstallProjectBuilder<'_, T, State>
88{
89 pub async fn build(self) -> Result<LockedPackage, InstallProjectError> {
92 let args = self._build();
93 let config = args.config;
94 let project = args.project;
95 let tree = args.tree;
96
97 let rockspec = project.local_remote_rockspec()?;
98 let name = rockspec.package().clone();
99
100 let behaviour = if matches!(args.behaviour, BuildBehaviour::Ignore) {
101 match tree.lockfile()?.entrypoint(&name) {
102 Some(existing) => {
103 let source_hash = project
104 .root()
105 .hash()
106 .await
107 .map_err(InstallProjectError::Hash)?;
108 if existing.version() == rockspec.version()
109 && existing.hashes().source == source_hash
110 {
111 return Ok(existing.clone());
112 }
113 BuildBehaviour::Force
114 }
115 None => args.behaviour,
116 }
117 } else {
118 args.behaviour
119 };
120
121 let root = FoundPackage::from_project_root(rockspec, project.root().to_path_buf());
122 let package_db = match args.workspace {
123 Some(workspace) => PackageDB::open(config, workspace).await?,
124 None => PackageDB::from_config(config).await?,
125 }
126 .with_local(vec![root]);
127
128 let install_spec =
129 PackageInstallSpec::new(PackageReq::from(name.clone()), tree::EntryType::Entrypoint)
130 .build_behaviour(behaviour)
131 .build();
132
133 InstallPackages::new(config, tree)
134 .package_db(package_db)
135 .package(install_spec)
136 .install()
137 .await?
138 .0
139 .into_iter()
140 .find(|package| package.name() == &name)
141 .ok_or(InstallProjectError::PackageNotInstalled(name))
142 }
143}