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] 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
663/// Whether all build dependencies of the given rockspec have been installed
664/// into the build tree, so that the package can start building.
665///
666/// A build dependency is considered ready when it has been freshly installed
667/// into the build tree, or when it was already present in the build lockfile
668/// and satisfies the dependency constraint.
669fn 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}