uv_configuration/
install_options.rs

1use std::collections::BTreeSet;
2
3use tracing::debug;
4
5use uv_normalize::PackageName;
6
7/// Minimal view of a package used to apply install filters.
8#[derive(Debug, Clone, Copy)]
9pub struct InstallTarget<'a> {
10    /// The package name.
11    pub name: &'a PackageName,
12    /// Whether the package refers to a local source (path, directory, editable, etc.).
13    pub is_local: bool,
14}
15
16#[derive(Debug, Clone, Default)]
17pub struct InstallOptions {
18    /// Omit the project itself from the resolution.
19    pub no_install_project: bool,
20    /// Include only the project itself in the resolution.
21    pub only_install_project: bool,
22    /// Omit all workspace members (including the project itself) from the resolution.
23    pub no_install_workspace: bool,
24    /// Include only workspace members (including the project itself) in the resolution.
25    pub only_install_workspace: bool,
26    /// Omit all local packages from the resolution.
27    pub no_install_local: bool,
28    /// Include only local packages in the resolution.
29    pub only_install_local: bool,
30    /// Omit the specified packages from the resolution.
31    pub no_install_package: Vec<PackageName>,
32    /// Include only the specified packages in the resolution.
33    pub only_install_package: Vec<PackageName>,
34}
35
36impl InstallOptions {
37    #[allow(clippy::fn_params_excessive_bools)]
38    pub fn new(
39        no_install_project: bool,
40        only_install_project: bool,
41        no_install_workspace: bool,
42        only_install_workspace: bool,
43        no_install_local: bool,
44        only_install_local: bool,
45        no_install_package: Vec<PackageName>,
46        only_install_package: Vec<PackageName>,
47    ) -> Self {
48        Self {
49            no_install_project,
50            only_install_project,
51            no_install_workspace,
52            only_install_workspace,
53            no_install_local,
54            only_install_local,
55            no_install_package,
56            only_install_package,
57        }
58    }
59
60    /// Returns `true` if a package passes the install filters.
61    pub fn include_package(
62        &self,
63        target: InstallTarget<'_>,
64        project_name: Option<&PackageName>,
65        members: &BTreeSet<PackageName>,
66    ) -> bool {
67        let package_name = target.name;
68
69        // If `--only-install-package` is set, only include specified packages.
70        if !self.only_install_package.is_empty() {
71            if self.only_install_package.contains(package_name) {
72                return true;
73            }
74            debug!("Omitting `{package_name}` from resolution due to `--only-install-package`");
75            return false;
76        }
77
78        // If `--only-install-local` is set, only include local packages.
79        if self.only_install_local {
80            if target.is_local {
81                return true;
82            }
83            debug!("Omitting `{package_name}` from resolution due to `--only-install-local`");
84            return false;
85        }
86
87        // If `--only-install-workspace` is set, only include the project and workspace members.
88        if self.only_install_workspace {
89            // Check if it's the project itself
90            if let Some(project_name) = project_name {
91                if package_name == project_name {
92                    return true;
93                }
94            }
95
96            // Check if it's a workspace member
97            if members.contains(package_name) {
98                return true;
99            }
100
101            // Otherwise, exclude it
102            debug!("Omitting `{package_name}` from resolution due to `--only-install-workspace`");
103            return false;
104        }
105
106        // If `--only-install-project` is set, only include the project itself.
107        if self.only_install_project {
108            if let Some(project_name) = project_name {
109                if package_name == project_name {
110                    return true;
111                }
112            }
113            debug!("Omitting `{package_name}` from resolution due to `--only-install-project`");
114            return false;
115        }
116
117        // If `--no-install-project` is set, remove the project itself.
118        if self.no_install_project {
119            if let Some(project_name) = project_name {
120                if package_name == project_name {
121                    debug!(
122                        "Omitting `{package_name}` from resolution due to `--no-install-project`"
123                    );
124                    return false;
125                }
126            }
127        }
128
129        // If `--no-install-workspace` is set, remove the project and any workspace members.
130        if self.no_install_workspace {
131            // In some cases, the project root might be omitted from the list of workspace members
132            // encoded in the lockfile. (But we already checked this above if `--no-install-project`
133            // is set.)
134            if !self.no_install_project {
135                if let Some(project_name) = project_name {
136                    if package_name == project_name {
137                        debug!(
138                            "Omitting `{package_name}` from resolution due to `--no-install-workspace`"
139                        );
140                        return false;
141                    }
142                }
143            }
144
145            if members.contains(package_name) {
146                debug!("Omitting `{package_name}` from resolution due to `--no-install-workspace`");
147                return false;
148            }
149        }
150
151        // If `--no-install-local` is set, remove local packages.
152        if self.no_install_local {
153            if target.is_local {
154                debug!("Omitting `{package_name}` from resolution due to `--no-install-local`");
155                return false;
156            }
157        }
158
159        // If `--no-install-package` is provided, remove the requested packages.
160        if self.no_install_package.contains(package_name) {
161            debug!("Omitting `{package_name}` from resolution due to `--no-install-package`");
162            return false;
163        }
164
165        true
166    }
167}