lux_lib/operations/
pin.rs1use std::io;
2
3use crate::{
4 fs,
5 lockfile::{FlushLockfileError, LockedPackageId, PinnedState},
6 package::PackageSpec,
7 tree::{InstallTree, Tree, TreeError},
8};
9use fs_extra::dir::CopyOptions;
10use itertools::Itertools;
11use miette::Diagnostic;
12use thiserror::Error;
13
14#[derive(Error, Debug, Diagnostic)]
17pub enum PinError {
18 #[error("package with ID '{0}' not found in the lockfile")]
19 #[diagnostic(help("this is probably a bug"))]
20 PackageNotFound(LockedPackageId),
21 #[error("rock {rock} is already {}pinned!", if *.pin_state == PinnedState::Unpinned { "un" } else { "" })]
22 PinStateUnchanged {
23 pin_state: PinnedState,
24 rock: PackageSpec,
25 },
26 #[error("cannot change the pin state of '{rock}', since a second version of '{rock}' is already installed with 'pin: {}'", .pin_state.as_bool())]
27 PinStateConflict {
28 pin_state: PinnedState,
29 rock: PackageSpec,
30 },
31 #[error(transparent)]
32 #[diagnostic(transparent)]
33 FlushLockfile(#[from] FlushLockfileError),
34 #[error(transparent)]
35 #[diagnostic(transparent)]
36 Tree(#[from] TreeError),
37 #[error("failed to move the old package")]
38 #[diagnostic(help("make sure Lux has write access to the install directory"))]
39 MoveItemsFailure(#[from] fs_extra::error::Error),
40 #[error("cannot change pin state of {rock}, because it is not an entrypoint")]
41 #[diagnostic(help(
42 "Lux does not allow pinning dependencies, as doing so could break the version requirement in a future update."
43 ))]
44 NotAnEntrypoint { rock: PackageSpec },
45 #[error(transparent)]
46 #[diagnostic(transparent)]
47 Fs(#[from] fs::FsError),
48}
49
50pub fn set_pinned_state(
51 package_id: &LockedPackageId,
52 tree: &Tree,
53 pin: PinnedState,
54) -> Result<(), PinError> {
55 let lockfile = tree.lockfile()?;
56 let package = lockfile
57 .get(package_id)
58 .ok_or_else(|| PinError::PackageNotFound(package_id.clone()))?
59 .clone();
60
61 if !lockfile.is_entrypoint(&package.id()) {
62 return Err(PinError::NotAnEntrypoint {
63 rock: package.to_package(),
64 });
65 }
66
67 if pin == package.pinned() {
68 return Err(PinError::PinStateUnchanged {
69 pin_state: package.pinned(),
70 rock: package.to_package(),
71 });
72 }
73
74 let old_package = package.clone();
75 let layout = tree.layout_for(&package.spec);
76 let items = fs::sync::read_dir(&layout.root)?
77 .filter_map(Result::ok)
78 .map(|dir| dir.path())
79 .collect_vec();
80
81 let package = package.repin(pin);
82
83 if lockfile.get(&package.id()).is_some() {
84 return Err(PinError::PinStateConflict {
85 pin_state: package.pinned(),
86 rock: package.to_package(),
87 });
88 }
89
90 fs::sync::create_dir_all(&layout.root)?;
91
92 fs_extra::move_items(&items, layout.root, &CopyOptions::new())?;
93
94 lockfile.map_then_flush(|lockfile| {
95 let binaries = lockfile.binaries(&old_package.id()).cloned();
96 lockfile.remove(&old_package);
97 lockfile.add_entrypoint(&package);
98 if let Some(binaries) = binaries {
99 lockfile.set_binaries(&package, binaries);
100 }
101
102 Ok::<_, io::Error>(())
103 })?;
104
105 Ok(())
106}