1use std::{
2 collections::{HashMap, HashSet},
3 io,
4 sync::Arc,
5};
6
7use crate::{
8 build::{Build, BuildBehaviour, BuildError, RemotePackageSourceSpec, SrcRockSource},
9 config::Config,
10 lockfile::{
11 FlushLockfileError, LocalPackage, LocalPackageId, LockConstraint, Lockfile, OptState,
12 PinnedState, ReadOnly, ReadWrite,
13 },
14 lua_installation::{LuaInstallation, LuaInstallationError},
15 lua_rockspec::BuildBackendSpec,
16 lua_version::LuaVersionUnset,
17 luarocks::{
18 install_binary_rock::{BinaryRockInstall, InstallBinaryRockError},
19 luarocks_installation::{LuaRocksError, LuaRocksInstallError, LuaRocksInstallation},
20 },
21 operations::resolve::{
22 build_dependencies_to_install, PackageInstallData, Resolve, ResolveDependenciesError,
23 },
24 package::{PackageName, PackageNameList, PackageReq},
25 remote_package_db::{RemotePackageDB, RemotePackageDBError},
26 rockspec::Rockspec,
27 tree::{self, InstallTree, Tree, TreeError},
28 workspace::{Workspace, WorkspaceTreeError},
29};
30
31pub use crate::operations::install::spec::PackageInstallSpec;
32
33use super::{DownloadedRockspec, RemoteRockDownload};
34use bon::Builder;
35use bytes::Bytes;
36use futures::stream::FuturesUnordered;
37use futures::StreamExt;
38use itertools::Itertools;
39use miette::Diagnostic;
40use thiserror::Error;
41use tokio::sync::mpsc::{UnboundedReceiver, UnboundedSender};
42use tokio::task::{JoinError, JoinHandle};
43
44use tracing::Instrument;
45pub mod spec;
46
47#[derive(Builder)]
51#[builder(start_fn = new, finish_fn(name = _build, vis = ""))]
52pub struct Install<'a, T>
53where
54 T: InstallTree + Clone + Send + Sync,
55{
56 #[builder(start_fn)]
57 config: &'a Config,
58 #[builder(field)]
59 packages: Vec<PackageInstallSpec>,
60 #[builder(setters(name = "_tree", vis = ""))]
61 tree: T,
62 package_db: Option<RemotePackageDB>,
63}
64
65impl<'a, State> InstallBuilder<'a, Tree, State>
66where
67 State: install_builder::State,
68{
69 pub fn workspace(
70 self,
71 workspace: &'a Workspace,
72 ) -> Result<InstallBuilder<'a, Tree, install_builder::SetTree<State>>, WorkspaceTreeError>
73 where
74 State::Tree: install_builder::IsUnset,
75 {
76 let config = self.config;
77 Ok(self._tree(workspace.tree(config)?))
78 }
79}
80
81impl<'a, T, State> InstallBuilder<'a, T, State>
82where
83 State: install_builder::State,
84 T: InstallTree + Clone + Send + Sync,
85{
86 pub fn tree(self, tree: T) -> InstallBuilder<'a, T, install_builder::SetTree<State>>
87 where
88 State::Tree: install_builder::IsUnset,
89 {
90 self._tree(tree)
91 }
92
93 pub fn packages(self, packages: Vec<PackageInstallSpec>) -> Self {
94 Self { packages, ..self }
95 }
96
97 pub fn package(self, package: PackageInstallSpec) -> Self {
98 Self {
99 packages: self
100 .packages
101 .into_iter()
102 .chain(std::iter::once(package))
103 .collect(),
104 ..self
105 }
106 }
107}
108
109impl<State, T> InstallBuilder<'_, T, State>
110where
111 State: install_builder::State + install_builder::IsComplete,
112 T: InstallTree + Clone + Send + Sync + 'static,
113{
114 pub async fn install(self) -> Result<Vec<LocalPackage>, InstallError> {
116 let install_built = self._build();
117 if install_built.packages.is_empty() {
118 return Ok(Vec::default());
119 }
120 let count = install_built.packages.len();
121 let span = if count > 1 {
122 tracing::info_span!("Installing", count)
123 } else {
124 let install_spec = &install_built.packages[0];
125 tracing::info_span!("Installing", package = install_spec.package.to_string())
126 };
127 install_impl(install_built).instrument(span).await
128 }
129}
130
131type InstallWorkerOutput = Result<(LocalPackageId, (LocalPackage, tree::EntryType)), InstallError>;
132
133#[derive(Error, Debug, Diagnostic)]
134pub enum InstallError {
135 #[error("unable to resolve dependencies")]
136 #[diagnostic(forward(0))]
137 ResolveDependencies(#[from] Box<ResolveDependenciesError>),
138 #[error(transparent)]
139 #[diagnostic(transparent)]
140 LuaVersionUnset(#[from] LuaVersionUnset),
141 #[error(transparent)]
142 #[diagnostic(transparent)]
143 LuaInstallation(#[from] LuaInstallationError),
144 #[error(transparent)]
145 #[diagnostic(transparent)]
146 FlushLockfile(#[from] FlushLockfileError),
147 #[error(transparent)]
148 #[diagnostic(transparent)]
149 Tree(#[from] TreeError),
150 #[error(transparent)]
151 #[diagnostic(transparent)]
152 WorkspaceTree(#[from] WorkspaceTreeError),
153 #[error("error instantiating LuaRocks compatibility layer")]
154 #[diagnostic(forward(0))]
155 LuaRocks(#[from] LuaRocksError),
156 #[error("error installing LuaRocks compatibility layer")]
157 #[diagnostic(forward(0))]
158 LuaRocksInstall(#[from] Box<LuaRocksInstallError>),
159 #[error("failed to build {0}")]
160 Build(PackageName, #[source] Box<BuildError>),
161 #[error("failed to install build depencency {0}")]
162 BuildDependency(PackageName, #[source] Box<BuildError>),
163 #[error("error initialising remote package DB")]
164 #[diagnostic(forward(0))]
165 RemotePackageDB(#[from] RemotePackageDBError),
166 #[error("failed to install pre-built rock {0}")]
167 InstallBinaryRock(PackageName, #[source] Box<InstallBinaryRockError>),
168 #[error("cannot install duplicate entrypoints:\n{0}")]
169 DuplicateEntrypoints(PackageNameList),
170 #[error("cannot install conflicting entrypoints:\n{0}")]
171 #[diagnostic(help(
172 "only a single entrypoint per package is allowed.\nretry with `--force` to overwrite the existing entrypoint"
173 ))]
174 ConflictingEntrypoints(String),
175 #[error("install worker panicked")]
176 #[diagnostic(help(
177 r#"this is a bug in Lux, please report it, ideally with `RUST_BACKTRACE=1`.
178retrying with fewer parallel jobs (`--max-jobs`) may avoid the panic in the meantime"#
179 ))]
180 Join(#[from] tokio::task::JoinError),
181}
182
183impl From<ResolveDependenciesError> for InstallError {
184 fn from(source: ResolveDependenciesError) -> Self {
185 Self::ResolveDependencies(Box::new(source))
186 }
187}
188
189impl From<LuaRocksInstallError> for InstallError {
190 fn from(source: LuaRocksInstallError) -> Self {
191 Self::LuaRocksInstall(Box::new(source))
192 }
193}
194
195async fn install_impl<T>(install: Install<'_, T>) -> Result<Vec<LocalPackage>, InstallError>
196where
197 T: InstallTree + Clone + Send + Sync + 'static,
198{
199 let package_db = match install.package_db {
200 Some(db) => db,
201 None => RemotePackageDB::from_config(install.config).await?,
202 };
203
204 let duplicate_entrypoints = install
205 .packages
206 .iter()
207 .filter(|pkg| pkg.entry_type == tree::EntryType::Entrypoint)
208 .map(|pkg| pkg.package.name())
209 .duplicates()
210 .cloned()
211 .collect_vec();
212
213 if !duplicate_entrypoints.is_empty() {
214 return Err(InstallError::DuplicateEntrypoints(PackageNameList::new(
215 duplicate_entrypoints,
216 )));
217 }
218
219 let packages = install.packages;
220 let package_db = Arc::new(package_db);
221 let config = install.config;
222 let tree = &install.tree;
223
224 let (dep_tx, mut dep_rx) = tokio::sync::mpsc::unbounded_channel();
225 let (build_dep_tx, build_dep_rx) = tokio::sync::mpsc::unbounded_channel();
226 let (build_dep_install_done_tx, mut build_dep_install_done_rx) =
227 tokio::sync::mpsc::unbounded_channel::<(LocalPackage, PackageInstallData)>();
228
229 let lockfile = tree.lockfile()?;
230 let build_lockfile = tree.build_tree(config)?.lockfile()?;
231
232 let entrypoint_specs = packages
233 .iter()
234 .filter(|spec| spec.entry_type == tree::EntryType::Entrypoint)
235 .collect_vec();
236
237 let conflicting_entrypoints: HashMap<PackageName, LocalPackage> = entrypoint_specs
241 .iter()
242 .filter_map(|spec| lockfile.entrypoint(spec.package.name()).cloned())
243 .map(|package| (package.name().clone(), package))
244 .collect();
245
246 let unforced_conflicts = entrypoint_specs
247 .iter()
248 .filter(|spec| spec.build_behaviour != BuildBehaviour::Force)
249 .filter_map(|spec| conflicting_entrypoints.get(spec.package.name()))
250 .map(|existing| format!("{}@{}", existing.name(), existing.version()))
251 .collect_vec();
252
253 if !unforced_conflicts.is_empty() {
254 return Err(InstallError::ConflictingEntrypoints(
255 unforced_conflicts.join("\n"),
256 ));
257 }
258
259 let conflicting_entrypoints = conflicting_entrypoints.into_values().collect_vec();
263 for package in &conflicting_entrypoints {
264 tree.cleanup(package, tree::EntryType::Entrypoint)?;
265 }
266
267 let lua = Arc::new(LuaInstallation::new_from_config(config).await?);
268
269 let mut resolve_worker = spawn_resolve_worker(
270 config,
271 packages,
272 package_db,
273 lockfile.clone(),
274 build_lockfile.clone(),
275 dep_tx,
276 build_dep_tx,
277 );
278 let mut build_deps_worker = spawn_build_deps_worker(
279 config,
280 tree,
281 lua.clone(),
282 build_dep_rx,
283 build_dep_install_done_tx,
284 );
285
286 let mut all_packages: HashMap<LocalPackageId, PackageInstallData> = HashMap::new();
287 let mut scheduled_packages: HashSet<LocalPackageId> = HashSet::new();
288 let mut installed_packages: HashMap<LocalPackageId, (LocalPackage, tree::EntryType)> =
289 HashMap::new();
290 let mut installed_build_deps: HashMap<LocalPackageId, (LocalPackage, PackageInstallData)> =
291 HashMap::new();
292 let mut ongoing_installs: FuturesUnordered<
293 tracing::instrument::Instrumented<tokio::task::JoinHandle<InstallWorkerOutput>>,
294 > = FuturesUnordered::new();
295 let mut resolve_done = false;
296 let mut build_deps_done = false;
297 let mut dep_rx_drained = false;
298 let mut build_dep_rx_drained = false;
299 let mut install_loop_result: Result<(), InstallError> = Ok(());
300 let max_jobs = config.max_jobs();
301
302 'install: loop {
303 if resolve_done && build_deps_done && dep_rx_drained && build_dep_rx_drained {
304 break;
305 }
306 tokio::select! {
307 resolve_result = &mut resolve_worker, if !resolve_done => {
308 if let Err(err) = worker_result(resolve_result) {
309 install_loop_result = Err(*err);
310 break 'install;
311 }
312 resolve_done = true;
313 }
314 build_deps_result = &mut build_deps_worker, if !build_deps_done => {
315 if let Err(err) = worker_result(build_deps_result) {
316 install_loop_result = Err(*err);
317 break 'install;
318 }
319 build_deps_done = true;
320 }
321 build_dep = build_dep_install_done_rx.recv(), if !build_dep_rx_drained => {
322 if let Some((build_dep, install_data)) = build_dep {
323 installed_build_deps.insert(build_dep.spec.id(), (build_dep, install_data));
324 } else {
325 build_dep_rx_drained = true;
326 }
327 }
328 dep = dep_rx.recv(), if !dep_rx_drained => {
329 if let Some(dep) = dep {
330 all_packages.insert(dep.spec.id(), dep);
331 } else {
332 dep_rx_drained = true;
333 }
334 }
335 }
336
337 for (package_id, package_install_data) in ready_to_install(
338 &all_packages,
339 &scheduled_packages,
340 &build_lockfile,
341 &installed_build_deps
342 .values()
343 .map(|(pkg, _)| pkg.name().clone())
344 .collect(),
345 ) {
346 if max_jobs > 0 && ongoing_installs.len() >= max_jobs {
347 if let Err(err) =
348 wait_for_next_install(&mut ongoing_installs, &mut installed_packages).await
349 {
350 install_loop_result = Err(err);
351 break 'install;
352 }
353 }
354 scheduled_packages.insert(package_id);
355 ongoing_installs.push(spawn_install_worker(
356 package_install_data,
357 &lua,
358 tree,
359 config,
360 ));
361 }
362 }
363
364 match install_loop_result {
365 Ok(_) => {
366 while wait_for_next_install(&mut ongoing_installs, &mut installed_packages).await? {}
367
368 lockfile.map_then_flush(|lockfile| {
369 for package in &conflicting_entrypoints {
370 lockfile.remove_by_id(&package.id());
371 }
372 for (package_id, (package, is_entrypoint)) in installed_packages.iter().unique() {
373 lockfile.add_dependencies(
374 package_id,
375 package,
376 *is_entrypoint,
377 &all_packages,
378 &installed_packages,
379 )?;
380 lockfile.add_build_dependencies(
381 package_id,
382 package,
383 &all_packages,
384 &installed_build_deps,
385 )?;
386 }
387 Ok::<_, io::Error>(())
388 })?;
389
390 Ok(installed_packages
391 .into_values()
392 .map(|(pkg, _)| pkg)
393 .collect_vec())
394 }
395 Err(err) => {
396 resolve_worker.into_inner().abort();
397 build_deps_worker.into_inner().abort();
398 for install in ongoing_installs {
399 install.into_inner().abort();
400 }
401 Err(err)
402 }
403 }
404}
405
406fn spawn_resolve_worker(
407 config: &Config,
408 packages: Vec<PackageInstallSpec>,
409 package_db: Arc<RemotePackageDB>,
410 lockfile: Lockfile<ReadOnly>,
411 build_lockfile: Lockfile<ReadOnly>,
412 dep_tx: UnboundedSender<PackageInstallData>,
413 build_dep_tx: UnboundedSender<PackageInstallData>,
414) -> tracing::instrument::Instrumented<JoinHandle<Result<(), InstallError>>> {
415 tokio::spawn({
416 let config = config.clone();
417 let lockfile = Arc::new(lockfile);
418 let build_lockfile = Arc::new(build_lockfile);
419 async move {
420 Resolve::new()
421 .dependencies_tx(dep_tx)
422 .build_dependencies_tx(build_dep_tx)
423 .packages(packages)
424 .package_db(package_db)
425 .lockfile(lockfile)
426 .build_lockfile(build_lockfile)
427 .config(&config)
428 .get_all_dependencies()
429 .await?;
430 Ok::<(), InstallError>(())
431 }
432 })
433 .instrument(tracing::trace_span!("resolve_worker"))
434}
435
436fn spawn_build_deps_worker<T>(
437 config: &Config,
438 tree: &T,
439 lua: Arc<LuaInstallation>,
440 mut build_dep_rx: UnboundedReceiver<PackageInstallData>,
441 build_dep_install_done_tx: UnboundedSender<(LocalPackage, PackageInstallData)>,
442) -> tracing::instrument::Instrumented<JoinHandle<Result<(), InstallError>>>
443where
444 T: InstallTree + Clone + Send + Sync + 'static,
445{
446 tokio::spawn({
447 let config = config.clone();
448 let tree = tree.clone();
449 let lua = lua.clone();
450 async move {
451 while let Some(build_dep_spec) = build_dep_rx.recv().await {
452 let rockspec = build_dep_spec.downloaded_rock.rockspec();
453 let package = rockspec.package().clone();
454 let span = tracing::info_span!(
455 "Installing build dependency",
456 package = package.to_string(),
457 version = rockspec.version().to_string()
458 );
459 let pkg = async {
460 let build_tree = tree.build_tree(&config)?;
461 let mut build_lockfile = build_tree.lockfile()?.write_guard();
462 let pkg = Build::new()
463 .rockspec(rockspec)
464 .lua(&lua)
465 .tree(&build_tree)
466 .entry_type(tree::EntryType::Entrypoint)
467 .config(&config)
468 .constraint(build_dep_spec.spec.constraint())
469 .behaviour(build_dep_spec.build_behaviour)
470 .build()
471 .await
472 .map_err(|err| InstallError::BuildDependency(package, Box::new(err)))?;
473 build_lockfile.add_entrypoint(&pkg);
474 Ok::<_, InstallError>(pkg)
475 }
476 .instrument(span)
477 .await?;
478 let _ = build_dep_install_done_tx.send((pkg, build_dep_spec));
479 }
480 Ok::<(), InstallError>(())
481 }
482 })
483 .instrument(tracing::trace_span!("build_deps_worker"))
484}
485
486fn ready_to_install(
487 all_packages: &HashMap<LocalPackageId, PackageInstallData>,
488 scheduled: &HashSet<LocalPackageId>,
489 build_lockfile: &Lockfile<ReadOnly>,
490 installed_build_deps: &HashSet<PackageName>,
491) -> Vec<(LocalPackageId, PackageInstallData)> {
492 all_packages
493 .iter()
494 .filter(|(id, data)| {
495 !scheduled.contains(*id)
496 && match &data.downloaded_rock {
497 RemoteRockDownload::BinaryRock { .. } => true,
498 _ => build_dependencies_ready(
499 data.downloaded_rock.rockspec(),
500 data.build_behaviour,
501 build_lockfile,
502 installed_build_deps,
503 ),
504 }
505 })
506 .map(|(id, data)| (id.clone(), data.clone()))
507 .collect()
508}
509
510fn spawn_install_worker<T>(
511 data: PackageInstallData,
512 lua: &Arc<LuaInstallation>,
513 tree: &T,
514 config: &Config,
515) -> tracing::instrument::Instrumented<JoinHandle<InstallWorkerOutput>>
516where
517 T: InstallTree + Clone + Send + Sync + 'static,
518{
519 let config = config.clone();
520 let tree = tree.clone();
521 let lua = lua.clone();
522 let entry_type = data.entry_type;
523 tokio::spawn(async move {
524 let pkg = install_package(data, &lua, &tree, &config).await?;
525 Ok::<_, InstallError>((pkg.id(), (pkg, entry_type)))
526 })
527 .instrument(tracing::trace_span!("install_worker"))
528}
529
530#[tracing::instrument(level = "trace", skip_all)]
531async fn install_package<T>(
532 data: PackageInstallData,
533 lua: &Arc<LuaInstallation>,
534 tree: &T,
535 config: &Config,
536) -> Result<LocalPackage, InstallError>
537where
538 T: InstallTree + Sync,
539{
540 match data.downloaded_rock {
541 RemoteRockDownload::RockspecOnly { rockspec_download } => {
542 install_rockspec(
543 rockspec_download,
544 None,
545 data.spec.constraint(),
546 data.build_behaviour,
547 data.pin,
548 data.opt,
549 data.entry_type,
550 lua,
551 tree,
552 config,
553 )
554 .await
555 }
556 RemoteRockDownload::BinaryRock {
557 rockspec_download,
558 packed_rock,
559 } => {
560 install_binary_rock(
561 rockspec_download,
562 packed_rock,
563 data.spec.constraint(),
564 data.build_behaviour,
565 data.pin,
566 data.opt,
567 data.entry_type,
568 config,
569 tree,
570 )
571 .await
572 }
573 RemoteRockDownload::SrcRock {
574 rockspec_download,
575 src_rock,
576 source_url,
577 } => {
578 let src_rock_source = SrcRockSource {
579 bytes: src_rock,
580 source_url,
581 };
582 install_rockspec(
583 rockspec_download,
584 Some(src_rock_source),
585 data.spec.constraint(),
586 data.build_behaviour,
587 data.pin,
588 data.opt,
589 data.entry_type,
590 lua,
591 tree,
592 config,
593 )
594 .await
595 }
596 }
597}
598
599fn worker_result(
600 result: Result<Result<(), InstallError>, JoinError>,
601) -> Result<(), Box<InstallError>> {
602 match result {
603 Ok(Ok(())) => Ok(()),
604 Ok(Err(err)) => Err(err.into()),
605 Err(join) => Err(InstallError::from(join).into()),
606 }
607}
608
609async fn wait_for_next_install(
610 ongoing_installs: &mut FuturesUnordered<
611 tracing::instrument::Instrumented<tokio::task::JoinHandle<InstallWorkerOutput>>,
612 >,
613 installed_packages: &mut HashMap<LocalPackageId, (LocalPackage, tree::EntryType)>,
614) -> Result<bool, InstallError> {
615 if let Some(result) = ongoing_installs.next().await {
616 match result {
617 Ok(Ok((id, installed))) => {
618 installed_packages.insert(id, installed);
619 Ok(true)
620 }
621 Ok(Err(err)) => Err(err),
622 Err(join) => Err(InstallError::from(join)),
623 }
624 } else {
625 Ok(false)
626 }
627}
628trait LockfileExt {
629 fn add_dependencies(
630 self,
631 id: &LocalPackageId,
632 pkg: &LocalPackage,
633 entry_type: tree::EntryType,
634 all_packages: &HashMap<LocalPackageId, PackageInstallData>,
635 installed_packages: &HashMap<LocalPackageId, (LocalPackage, tree::EntryType)>,
636 ) -> io::Result<()>;
637
638 fn add_build_dependencies(
639 self,
640 id: &LocalPackageId,
641 pkg: &LocalPackage,
642 all_packages: &HashMap<LocalPackageId, PackageInstallData>,
643 build_dependencies: &HashMap<LocalPackageId, (LocalPackage, PackageInstallData)>,
644 ) -> io::Result<()>;
645}
646
647impl LockfileExt for &mut Lockfile<ReadWrite> {
648 fn add_dependencies(
649 self,
650 id: &LocalPackageId,
651 pkg: &LocalPackage,
652 entry_type: tree::EntryType,
653 all_packages: &HashMap<LocalPackageId, PackageInstallData>,
654 installed_packages: &HashMap<LocalPackageId, (LocalPackage, tree::EntryType)>,
655 ) -> io::Result<()> {
656 if entry_type == tree::EntryType::Entrypoint {
657 self.add_entrypoint(pkg);
658 }
659
660 for dependency_id in all_packages
661 .get(id)
662 .map(|pkg| pkg.spec.dependencies())
663 .unwrap_or_default()
664 .into_iter()
665 {
666 self.add_dependency(
667 pkg,
668 installed_packages
669 .get(dependency_id)
670 .map(|(pkg, _)| pkg)
671 .ok_or(io::Error::other(
672 r#"
673error writing dependencies to the lockfile.
674A required dependency was not installed correctly.
675This is likely because an install thread panicked and was interrupted unexpectedly.
676
677[THIS IS A BUG!]
678"#,
679 ))?,
680 );
681 }
682 Ok(())
683 }
684
685 fn add_build_dependencies(
686 self,
687 id: &LocalPackageId,
688 pkg: &LocalPackage,
689 all_packages: &HashMap<LocalPackageId, PackageInstallData>,
690 build_dependencies: &HashMap<LocalPackageId, (LocalPackage, PackageInstallData)>,
691 ) -> io::Result<()> {
692 for dependency_id in all_packages
693 .get(id)
694 .map(|pkg| pkg.spec.build_dependencies())
695 .unwrap_or_default()
696 .into_iter()
697 {
698 self.add_build_dependency(
699 pkg,
700 build_dependencies
701 .get(dependency_id)
702 .map(|(pkg, _)| pkg)
703 .ok_or(io::Error::other(
704 r#"
705error writing build dependencies to the lockfile.
706A required build dependency was not installed correctly.
707This is likely because an install thread panicked and was interrupted unexpectedly.
708
709[THIS IS A BUG!]
710"#,
711 ))?,
712 );
713 }
714 Ok(())
715 }
716}
717
718fn build_dependencies_ready(
725 rockspec: &impl Rockspec,
726 behaviour: BuildBehaviour,
727 build_lockfile: &Lockfile<ReadOnly>,
728 installed: &HashSet<PackageName>,
729) -> bool {
730 let build_deps = rockspec.build_dependencies().current_platform();
731 build_dependencies_to_install(rockspec).iter().all(|name| {
732 installed.contains(name)
733 || (behaviour != BuildBehaviour::Force
734 && build_lockfile
735 .has_rock(
736 &build_deps
737 .iter()
738 .find(|dep| dep.name() == name)
739 .map(|dep| dep.package_req().clone())
740 .unwrap_or_else(|| PackageReq::from(name.clone())),
741 None,
742 )
743 .is_some())
744 })
745}
746
747#[allow(clippy::too_many_arguments)]
748#[tracing::instrument(
749 name = "Installing",
750 level = "info",
751 skip_all,
752 fields(
753 package = rockspec_download.rockspec.package().to_string(),
754 version = rockspec_download.rockspec.version().to_string(),
755 ),
756)]
757async fn install_rockspec<T>(
758 rockspec_download: DownloadedRockspec,
759 src_rock_source: Option<SrcRockSource>,
760 constraint: LockConstraint,
761 behaviour: BuildBehaviour,
762 pin: PinnedState,
763 opt: OptState,
764 entry_type: tree::EntryType,
765 lua: &LuaInstallation,
766 tree: &T,
767 config: &Config,
768) -> Result<LocalPackage, InstallError>
769where
770 T: InstallTree + Sync,
771{
772 let package = rockspec_download.rockspec.package().clone();
773 let rockspec = rockspec_download.rockspec;
774 let source = rockspec_download.source;
775
776 if let Some(BuildBackendSpec::LuaRock(_)) = &rockspec.build().current_platform().build_backend {
777 let luarocks_tree = tree.build_tree(config)?;
778 let luarocks = LuaRocksInstallation::new(config, luarocks_tree)?;
779 luarocks.ensure_installed(lua).await?;
780 }
781
782 let source_spec = match src_rock_source {
783 Some(src_rock_source) => RemotePackageSourceSpec::SrcRock(src_rock_source),
784 None => RemotePackageSourceSpec::RockSpec(rockspec_download.source_url),
785 };
786
787 let pkg = Build::new()
788 .rockspec(&rockspec)
789 .lua(lua)
790 .tree(tree)
791 .entry_type(entry_type)
792 .config(config)
793 .pin(pin)
794 .opt(opt)
795 .constraint(constraint)
796 .behaviour(behaviour)
797 .source(source)
798 .source_spec(source_spec)
799 .build()
800 .await
801 .map_err(|err| InstallError::Build(package, Box::new(err)))?;
802 Ok(pkg)
803}
804
805#[allow(clippy::too_many_arguments)]
806#[tracing::instrument(
807 name = "Installing (pre-built)",
808 level = "info",
809 skip_all,
810 fields(
811 package = rockspec_download.rockspec.package().to_string(),
812 version = rockspec_download.rockspec.version().to_string(),
813 ),
814)]
815async fn install_binary_rock(
816 rockspec_download: DownloadedRockspec,
817 packed_rock: Bytes,
818 constraint: LockConstraint,
819 behaviour: BuildBehaviour,
820 pin: PinnedState,
821 opt: OptState,
822 entry_type: tree::EntryType,
823 config: &Config,
824 tree: &impl InstallTree,
825) -> Result<LocalPackage, InstallError> {
826 let rockspec = rockspec_download.rockspec;
827 let package = rockspec.package().clone();
828 let pkg = BinaryRockInstall::new(
829 &rockspec,
830 rockspec_download.source,
831 packed_rock,
832 entry_type,
833 config,
834 tree,
835 )
836 .pin(pin)
837 .opt(opt)
838 .constraint(constraint)
839 .behaviour(behaviour)
840 .install()
841 .await
842 .map_err(|err| InstallError::InstallBinaryRock(package, Box::new(err)))?;
843 Ok(pkg)
844}