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