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