Skip to main content

lux_lib/operations/install/
mod.rs

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