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] 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] LuaRocksInstallError),
159 #[error("failed to build {0}")]
160 Build(PackageName, #[source] BuildError),
161 #[error("failed to install build depencency {0}")]
162 BuildDependency(PackageName, #[source] 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] InstallBinaryRockError),
168 #[error("cannot install duplicate entrypoints:\n{0}")]
169 DuplicateEntrypoints(PackageNameList),
170 #[error("install worker panicked")]
171 #[diagnostic(help(
172 r#"this is a bug in Lux, please report it, ideally with `RUST_BACKTRACE=1`.
173retrying with fewer parallel jobs (`--max-jobs`) may avoid the panic in the meantime"#
174 ))]
175 Join(#[from] tokio::task::JoinError),
176}
177
178async fn install_impl<T>(install: Install<'_, T>) -> Result<Vec<LocalPackage>, InstallError>
179where
180 T: InstallTree + Clone + Send + Sync + 'static,
181{
182 let package_db = match install.package_db {
183 Some(db) => db,
184 None => RemotePackageDB::from_config(install.config).await?,
185 };
186
187 let duplicate_entrypoints = install
188 .packages
189 .iter()
190 .filter(|pkg| pkg.entry_type == tree::EntryType::Entrypoint)
191 .map(|pkg| pkg.package.name())
192 .duplicates()
193 .cloned()
194 .collect_vec();
195
196 if !duplicate_entrypoints.is_empty() {
197 return Err(InstallError::DuplicateEntrypoints(PackageNameList::new(
198 duplicate_entrypoints,
199 )));
200 }
201
202 let packages = install.packages;
203 let package_db = Arc::new(package_db);
204 let config = install.config;
205 let tree = &install.tree;
206
207 let (dep_tx, mut dep_rx) = tokio::sync::mpsc::unbounded_channel();
208 let (build_dep_tx, build_dep_rx) = tokio::sync::mpsc::unbounded_channel();
209 let (build_dep_install_done_tx, mut build_dep_install_done_rx) =
210 tokio::sync::mpsc::unbounded_channel::<(LocalPackage, PackageInstallData)>();
211
212 let lockfile = tree.lockfile()?;
213 let build_lockfile = tree.build_tree(config)?.lockfile()?;
214
215 let lua = Arc::new(LuaInstallation::new_from_config(config).await?);
216
217 let mut resolve_worker = spawn_resolve_worker(
218 config,
219 packages,
220 package_db,
221 lockfile.clone(),
222 build_lockfile.clone(),
223 dep_tx,
224 build_dep_tx,
225 );
226 let mut build_deps_worker = spawn_build_deps_worker(
227 config,
228 tree,
229 lua.clone(),
230 build_dep_rx,
231 build_dep_install_done_tx,
232 );
233
234 let mut all_packages: HashMap<LocalPackageId, PackageInstallData> = HashMap::new();
235 let mut scheduled_packages: HashSet<LocalPackageId> = HashSet::new();
236 let mut installed_packages: HashMap<LocalPackageId, (LocalPackage, tree::EntryType)> =
237 HashMap::new();
238 let mut installed_build_deps: HashMap<LocalPackageId, (LocalPackage, PackageInstallData)> =
239 HashMap::new();
240 let mut ongoing_installs: FuturesUnordered<
241 tracing::instrument::Instrumented<tokio::task::JoinHandle<InstallWorkerOutput>>,
242 > = FuturesUnordered::new();
243 let mut resolve_done = false;
244 let mut build_deps_done = false;
245 let mut dep_rx_drained = false;
246 let mut build_dep_rx_drained = false;
247 let mut install_loop_result: Result<(), InstallError> = Ok(());
248 let max_jobs = config.max_jobs();
249
250 'install: loop {
251 if resolve_done && build_deps_done && dep_rx_drained && build_dep_rx_drained {
252 break;
253 }
254 tokio::select! {
255 resolve_result = &mut resolve_worker, if !resolve_done => {
256 if let Err(err) = worker_result(resolve_result) {
257 install_loop_result = Err(*err);
258 break 'install;
259 }
260 resolve_done = true;
261 }
262 build_deps_result = &mut build_deps_worker, if !build_deps_done => {
263 if let Err(err) = worker_result(build_deps_result) {
264 install_loop_result = Err(*err);
265 break 'install;
266 }
267 build_deps_done = true;
268 }
269 build_dep = build_dep_install_done_rx.recv(), if !build_dep_rx_drained => {
270 if let Some((build_dep, install_data)) = build_dep {
271 installed_build_deps.insert(build_dep.spec.id(), (build_dep, install_data));
272 } else {
273 build_dep_rx_drained = true;
274 }
275 }
276 dep = dep_rx.recv(), if !dep_rx_drained => {
277 if let Some(dep) = dep {
278 all_packages.insert(dep.spec.id(), dep);
279 } else {
280 dep_rx_drained = true;
281 }
282 }
283 }
284
285 for (package_id, package_install_data) in ready_to_install(
286 &all_packages,
287 &scheduled_packages,
288 &build_lockfile,
289 &installed_build_deps
290 .values()
291 .map(|(pkg, _)| pkg.name().clone())
292 .collect(),
293 ) {
294 if max_jobs > 0 && ongoing_installs.len() >= max_jobs {
295 if let Err(err) =
296 wait_for_next_install(&mut ongoing_installs, &mut installed_packages).await
297 {
298 install_loop_result = Err(err);
299 break 'install;
300 }
301 }
302 scheduled_packages.insert(package_id);
303 ongoing_installs.push(spawn_install_worker(
304 package_install_data,
305 &lua,
306 tree,
307 config,
308 ));
309 }
310 }
311
312 match install_loop_result {
313 Ok(_) => {
314 while wait_for_next_install(&mut ongoing_installs, &mut installed_packages).await? {}
315
316 lockfile.map_then_flush(|lockfile| {
317 for (package_id, (package, is_entrypoint)) in installed_packages.iter().unique() {
318 lockfile.add_dependencies(
319 package_id,
320 package,
321 *is_entrypoint,
322 &all_packages,
323 &installed_packages,
324 )?;
325 lockfile.add_build_dependencies(
326 package_id,
327 package,
328 &all_packages,
329 &installed_build_deps,
330 )?;
331 }
332 Ok::<_, io::Error>(())
333 })?;
334
335 Ok(installed_packages
336 .into_values()
337 .map(|(pkg, _)| pkg)
338 .collect_vec())
339 }
340 Err(err) => {
341 resolve_worker.into_inner().abort();
342 build_deps_worker.into_inner().abort();
343 for install in ongoing_installs {
344 install.into_inner().abort();
345 }
346 Err(err)
347 }
348 }
349}
350
351fn spawn_resolve_worker(
352 config: &Config,
353 packages: Vec<PackageInstallSpec>,
354 package_db: Arc<RemotePackageDB>,
355 lockfile: Lockfile<ReadOnly>,
356 build_lockfile: Lockfile<ReadOnly>,
357 dep_tx: UnboundedSender<PackageInstallData>,
358 build_dep_tx: UnboundedSender<PackageInstallData>,
359) -> tracing::instrument::Instrumented<JoinHandle<Result<(), InstallError>>> {
360 tokio::spawn({
361 let config = config.clone();
362 let lockfile = Arc::new(lockfile);
363 let build_lockfile = Arc::new(build_lockfile);
364 async move {
365 Resolve::new()
366 .dependencies_tx(dep_tx)
367 .build_dependencies_tx(build_dep_tx)
368 .packages(packages)
369 .package_db(package_db)
370 .lockfile(lockfile)
371 .build_lockfile(build_lockfile)
372 .config(&config)
373 .get_all_dependencies()
374 .await?;
375 Ok::<(), InstallError>(())
376 }
377 })
378 .instrument(tracing::trace_span!("resolve_worker"))
379}
380
381fn spawn_build_deps_worker<T>(
382 config: &Config,
383 tree: &T,
384 lua: Arc<LuaInstallation>,
385 mut build_dep_rx: UnboundedReceiver<PackageInstallData>,
386 build_dep_install_done_tx: UnboundedSender<(LocalPackage, PackageInstallData)>,
387) -> tracing::instrument::Instrumented<JoinHandle<Result<(), InstallError>>>
388where
389 T: InstallTree + Clone + Send + Sync + 'static,
390{
391 tokio::spawn({
392 let config = config.clone();
393 let tree = tree.clone();
394 let lua = lua.clone();
395 async move {
396 while let Some(build_dep_spec) = build_dep_rx.recv().await {
397 let rockspec = build_dep_spec.downloaded_rock.rockspec();
398 let package = rockspec.package().clone();
399 let span = tracing::info_span!(
400 "Installing build dependency",
401 package = package.to_string(),
402 version = rockspec.version().to_string()
403 );
404 let pkg = async {
405 let build_tree = tree.build_tree(&config)?;
406 let mut build_lockfile = build_tree.lockfile()?.write_guard();
407 let pkg = Build::new()
408 .rockspec(rockspec)
409 .lua(&lua)
410 .tree(&build_tree)
411 .entry_type(tree::EntryType::Entrypoint)
412 .config(&config)
413 .constraint(build_dep_spec.spec.constraint())
414 .behaviour(build_dep_spec.build_behaviour)
415 .build()
416 .await
417 .map_err(|err| InstallError::BuildDependency(package, err))?;
418 build_lockfile.add_entrypoint(&pkg);
419 Ok::<_, InstallError>(pkg)
420 }
421 .instrument(span)
422 .await?;
423 let _ = build_dep_install_done_tx.send((pkg, build_dep_spec));
424 }
425 Ok::<(), InstallError>(())
426 }
427 })
428 .instrument(tracing::trace_span!("build_deps_worker"))
429}
430
431fn ready_to_install(
432 all_packages: &HashMap<LocalPackageId, PackageInstallData>,
433 scheduled: &HashSet<LocalPackageId>,
434 build_lockfile: &Lockfile<ReadOnly>,
435 installed_build_deps: &HashSet<PackageName>,
436) -> Vec<(LocalPackageId, PackageInstallData)> {
437 all_packages
438 .iter()
439 .filter(|(id, data)| {
440 !scheduled.contains(*id)
441 && match &data.downloaded_rock {
442 RemoteRockDownload::BinaryRock { .. } => true,
443 _ => build_dependencies_ready(
444 data.downloaded_rock.rockspec(),
445 data.build_behaviour,
446 build_lockfile,
447 installed_build_deps,
448 ),
449 }
450 })
451 .map(|(id, data)| (id.clone(), data.clone()))
452 .collect()
453}
454
455fn spawn_install_worker<T>(
456 data: PackageInstallData,
457 lua: &Arc<LuaInstallation>,
458 tree: &T,
459 config: &Config,
460) -> tracing::instrument::Instrumented<JoinHandle<InstallWorkerOutput>>
461where
462 T: InstallTree + Clone + Send + Sync + 'static,
463{
464 let config = config.clone();
465 let tree = tree.clone();
466 let lua = lua.clone();
467 let entry_type = data.entry_type;
468 tokio::spawn(async move {
469 let pkg = install_package(data, &lua, &tree, &config).await?;
470 Ok::<_, InstallError>((pkg.id(), (pkg, entry_type)))
471 })
472 .instrument(tracing::trace_span!("install_worker"))
473}
474
475#[tracing::instrument(level = "trace", skip_all)]
476async fn install_package<T>(
477 data: PackageInstallData,
478 lua: &Arc<LuaInstallation>,
479 tree: &T,
480 config: &Config,
481) -> Result<LocalPackage, InstallError>
482where
483 T: InstallTree + Sync,
484{
485 match data.downloaded_rock {
486 RemoteRockDownload::RockspecOnly { rockspec_download } => {
487 install_rockspec(
488 rockspec_download,
489 None,
490 data.spec.constraint(),
491 data.build_behaviour,
492 data.pin,
493 data.opt,
494 data.entry_type,
495 lua,
496 tree,
497 config,
498 )
499 .await
500 }
501 RemoteRockDownload::BinaryRock {
502 rockspec_download,
503 packed_rock,
504 } => {
505 install_binary_rock(
506 rockspec_download,
507 packed_rock,
508 data.spec.constraint(),
509 data.build_behaviour,
510 data.pin,
511 data.opt,
512 data.entry_type,
513 config,
514 tree,
515 )
516 .await
517 }
518 RemoteRockDownload::SrcRock {
519 rockspec_download,
520 src_rock,
521 source_url,
522 } => {
523 let src_rock_source = SrcRockSource {
524 bytes: src_rock,
525 source_url,
526 };
527 install_rockspec(
528 rockspec_download,
529 Some(src_rock_source),
530 data.spec.constraint(),
531 data.build_behaviour,
532 data.pin,
533 data.opt,
534 data.entry_type,
535 lua,
536 tree,
537 config,
538 )
539 .await
540 }
541 }
542}
543
544fn worker_result(
545 result: Result<Result<(), InstallError>, JoinError>,
546) -> Result<(), Box<InstallError>> {
547 match result {
548 Ok(Ok(())) => Ok(()),
549 Ok(Err(err)) => Err(err.into()),
550 Err(join) => Err(InstallError::from(join).into()),
551 }
552}
553
554async fn wait_for_next_install(
555 ongoing_installs: &mut FuturesUnordered<
556 tracing::instrument::Instrumented<tokio::task::JoinHandle<InstallWorkerOutput>>,
557 >,
558 installed_packages: &mut HashMap<LocalPackageId, (LocalPackage, tree::EntryType)>,
559) -> Result<bool, InstallError> {
560 if let Some(result) = ongoing_installs.next().await {
561 match result {
562 Ok(Ok((id, installed))) => {
563 installed_packages.insert(id, installed);
564 Ok(true)
565 }
566 Ok(Err(err)) => Err(err),
567 Err(join) => Err(InstallError::from(join)),
568 }
569 } else {
570 Ok(false)
571 }
572}
573trait LockfileExt {
574 fn add_dependencies(
575 self,
576 id: &LocalPackageId,
577 pkg: &LocalPackage,
578 entry_type: tree::EntryType,
579 all_packages: &HashMap<LocalPackageId, PackageInstallData>,
580 installed_packages: &HashMap<LocalPackageId, (LocalPackage, tree::EntryType)>,
581 ) -> io::Result<()>;
582
583 fn add_build_dependencies(
584 self,
585 id: &LocalPackageId,
586 pkg: &LocalPackage,
587 all_packages: &HashMap<LocalPackageId, PackageInstallData>,
588 build_dependencies: &HashMap<LocalPackageId, (LocalPackage, PackageInstallData)>,
589 ) -> io::Result<()>;
590}
591
592impl LockfileExt for &mut Lockfile<ReadWrite> {
593 fn add_dependencies(
594 self,
595 id: &LocalPackageId,
596 pkg: &LocalPackage,
597 entry_type: tree::EntryType,
598 all_packages: &HashMap<LocalPackageId, PackageInstallData>,
599 installed_packages: &HashMap<LocalPackageId, (LocalPackage, tree::EntryType)>,
600 ) -> io::Result<()> {
601 if entry_type == tree::EntryType::Entrypoint {
602 self.add_entrypoint(pkg);
603 }
604
605 for dependency_id in all_packages
606 .get(id)
607 .map(|pkg| pkg.spec.dependencies())
608 .unwrap_or_default()
609 .into_iter()
610 {
611 self.add_dependency(
612 pkg,
613 installed_packages
614 .get(dependency_id)
615 .map(|(pkg, _)| pkg)
616 .ok_or(io::Error::other(
617 r#"
618error writing dependencies to the lockfile.
619A required dependency was not installed correctly.
620This is likely because an install thread panicked and was interrupted unexpectedly.
621
622[THIS IS A BUG!]
623"#,
624 ))?,
625 );
626 }
627 Ok(())
628 }
629
630 fn add_build_dependencies(
631 self,
632 id: &LocalPackageId,
633 pkg: &LocalPackage,
634 all_packages: &HashMap<LocalPackageId, PackageInstallData>,
635 build_dependencies: &HashMap<LocalPackageId, (LocalPackage, PackageInstallData)>,
636 ) -> io::Result<()> {
637 for dependency_id in all_packages
638 .get(id)
639 .map(|pkg| pkg.spec.build_dependencies())
640 .unwrap_or_default()
641 .into_iter()
642 {
643 self.add_build_dependency(
644 pkg,
645 build_dependencies
646 .get(dependency_id)
647 .map(|(pkg, _)| pkg)
648 .ok_or(io::Error::other(
649 r#"
650error writing build dependencies to the lockfile.
651A required build dependency was not installed correctly.
652This is likely because an install thread panicked and was interrupted unexpectedly.
653
654[THIS IS A BUG!]
655"#,
656 ))?,
657 );
658 }
659 Ok(())
660 }
661}
662
663fn build_dependencies_ready(
670 rockspec: &impl Rockspec,
671 behaviour: BuildBehaviour,
672 build_lockfile: &Lockfile<ReadOnly>,
673 installed: &HashSet<PackageName>,
674) -> bool {
675 let build_deps = rockspec.build_dependencies().current_platform();
676 build_dependencies_to_install(rockspec).iter().all(|name| {
677 installed.contains(name)
678 || (behaviour != BuildBehaviour::Force
679 && build_lockfile
680 .has_rock(
681 &build_deps
682 .iter()
683 .find(|dep| dep.name() == name)
684 .map(|dep| dep.package_req().clone())
685 .unwrap_or_else(|| PackageReq::from(name.clone())),
686 None,
687 )
688 .is_some())
689 })
690}
691
692#[allow(clippy::too_many_arguments)]
693#[tracing::instrument(
694 name = "Installing",
695 level = "info",
696 skip_all,
697 fields(
698 package = rockspec_download.rockspec.package().to_string(),
699 version = rockspec_download.rockspec.version().to_string(),
700 ),
701)]
702async fn install_rockspec<T>(
703 rockspec_download: DownloadedRockspec,
704 src_rock_source: Option<SrcRockSource>,
705 constraint: LockConstraint,
706 behaviour: BuildBehaviour,
707 pin: PinnedState,
708 opt: OptState,
709 entry_type: tree::EntryType,
710 lua: &LuaInstallation,
711 tree: &T,
712 config: &Config,
713) -> Result<LocalPackage, InstallError>
714where
715 T: InstallTree + Sync,
716{
717 let package = rockspec_download.rockspec.package().clone();
718 let rockspec = rockspec_download.rockspec;
719 let source = rockspec_download.source;
720
721 if let Some(BuildBackendSpec::LuaRock(_)) = &rockspec.build().current_platform().build_backend {
722 let luarocks_tree = tree.build_tree(config)?;
723 let luarocks = LuaRocksInstallation::new(config, luarocks_tree)?;
724 luarocks.ensure_installed(lua).await?;
725 }
726
727 let source_spec = match src_rock_source {
728 Some(src_rock_source) => RemotePackageSourceSpec::SrcRock(src_rock_source),
729 None => RemotePackageSourceSpec::RockSpec(rockspec_download.source_url),
730 };
731
732 let pkg = Build::new()
733 .rockspec(&rockspec)
734 .lua(lua)
735 .tree(tree)
736 .entry_type(entry_type)
737 .config(config)
738 .pin(pin)
739 .opt(opt)
740 .constraint(constraint)
741 .behaviour(behaviour)
742 .source(source)
743 .source_spec(source_spec)
744 .build()
745 .await
746 .map_err(|err| InstallError::Build(package, err))?;
747 Ok(pkg)
748}
749
750#[allow(clippy::too_many_arguments)]
751#[tracing::instrument(
752 name = "Installing (pre-built)",
753 level = "info",
754 skip_all,
755 fields(
756 package = rockspec_download.rockspec.package().to_string(),
757 version = rockspec_download.rockspec.version().to_string(),
758 ),
759)]
760async fn install_binary_rock(
761 rockspec_download: DownloadedRockspec,
762 packed_rock: Bytes,
763 constraint: LockConstraint,
764 behaviour: BuildBehaviour,
765 pin: PinnedState,
766 opt: OptState,
767 entry_type: tree::EntryType,
768 config: &Config,
769 tree: &impl InstallTree,
770) -> Result<LocalPackage, InstallError> {
771 let rockspec = rockspec_download.rockspec;
772 let package = rockspec.package().clone();
773 let pkg = BinaryRockInstall::new(
774 &rockspec,
775 rockspec_download.source,
776 packed_rock,
777 entry_type,
778 config,
779 tree,
780 )
781 .pin(pin)
782 .opt(opt)
783 .constraint(constraint)
784 .behaviour(behaviour)
785 .install()
786 .await
787 .map_err(|err| InstallError::InstallBinaryRock(package, err))?;
788 Ok(pkg)
789}