lux_lib/operations/
pin.rs1use std::io;
2
3use crate::{
4 fs,
5 lockfile::{FlushLockfileError, LocalPackageId, 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(LocalPackageId),
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("Lux does not allow pinning dependencies, as doing so could break the version requirement in a future update."))]
42 NotAnEntrypoint { rock: PackageSpec },
43 #[error(transparent)]
44 #[diagnostic(transparent)]
45 Fs(#[from] fs::FsError),
46}
47
48pub fn set_pinned_state(
49 package_id: &LocalPackageId,
50 tree: &Tree,
51 pin: PinnedState,
52) -> Result<(), PinError> {
53 let lockfile = tree.lockfile()?;
54 let mut package = lockfile
55 .get(package_id)
56 .ok_or_else(|| PinError::PackageNotFound(package_id.clone()))?
57 .clone();
58
59 if !lockfile.is_entrypoint(&package.id()) {
60 return Err(PinError::NotAnEntrypoint {
61 rock: package.to_package(),
62 });
63 }
64
65 if pin == package.pinned() {
66 return Err(PinError::PinStateUnchanged {
67 pin_state: package.pinned(),
68 rock: package.to_package(),
69 });
70 }
71
72 let old_package = package.clone();
73 let package_root = tree.root_for(&package);
74 let items = fs::sync::read_dir(&package_root)?
75 .filter_map(Result::ok)
76 .map(|dir| dir.path())
77 .collect_vec();
78
79 package.spec.pinned = pin;
80
81 if lockfile.get(&package.id()).is_some() {
82 return Err(PinError::PinStateConflict {
83 pin_state: package.pinned(),
84 rock: package.to_package(),
85 });
86 }
87
88 let new_root = tree.root_for(&package);
89
90 fs::sync::create_dir_all(&new_root)?;
91
92 fs_extra::move_items(&items, new_root, &CopyOptions::new())?;
93
94 lockfile.map_then_flush(|lockfile| {
95 lockfile.remove(&old_package);
96 lockfile.add_entrypoint(&package);
97
98 Ok::<_, io::Error>(())
99 })?;
100
101 Ok(())
102}