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