cli_xtask/config/
dist.rs

1use cargo_metadata::{
2    camino::{Utf8Path, Utf8PathBuf},
3    Metadata, Package,
4};
5use eyre::eyre;
6
7use super::{DistPackageConfig, DistPackageConfigBuilder};
8use crate::Result;
9
10/// Configures and constructs [`DistConfig`].
11///
12/// # Examples
13///
14/// ```rust
15/// # fn main() -> cli_xtask::Result<()> {
16/// use cli_xtask::{config::DistConfigBuilder, workspace};
17///
18/// let workspace = workspace::current();
19/// let config = DistConfigBuilder::new("app", workspace).build()?;
20/// # Ok(())
21/// # }
22/// ```
23#[derive(Debug)]
24pub struct DistConfigBuilder<'a> {
25    name: String,
26    metadata: &'a Metadata,
27    dist_target_directory: Utf8PathBuf,
28    dist_base_working_directory: Utf8PathBuf,
29    packages: Vec<DistPackageConfig<'a>>,
30    #[cfg(feature = "subcommand-dist-build-bin")]
31    cargo_build_options: Vec<String>,
32}
33
34impl<'a> DistConfigBuilder<'a> {
35    /// Creates a new `DistConfigBuilder` from the given name.
36    ///
37    /// Created `DistConfig` will be associated with current cargo workspace.
38    ///
39    /// # Examples
40    ///
41    /// ```rust
42    /// # fn main() -> cli_xtask::Result<()> {
43    /// use cli_xtask::{config::DistConfigBuilder, workspace};
44    ///
45    /// let workspace = workspace::current();
46    /// let config = DistConfigBuilder::new("app-v1.0", workspace).build()?;
47    /// # Ok(())
48    /// # }
49    /// ```
50    pub fn new(name: impl Into<String>, workspace: &'a Metadata) -> Self {
51        let name = name.into();
52        let dist_target_directory = workspace.target_directory.join("dist");
53        let dist_base_working_directory = workspace.target_directory.join("xtask/dist").join(&name);
54
55        Self {
56            name,
57            metadata: workspace,
58            dist_target_directory,
59            dist_base_working_directory,
60            packages: vec![],
61            #[cfg(feature = "subcommand-dist-build-bin")]
62            cargo_build_options: vec![],
63        }
64    }
65
66    /// Creates a new `DistConfigBuilder` from the default packages of the given workspace.
67    ///
68    /// Created `DistConfig` will be associated with current cargo workspace.
69    ///
70    /// # Examples
71    ///
72    /// ```rust
73    /// # fn main() -> cli_xtask::Result<()> {
74    /// use cli_xtask::{config::{DistConfigBuilder, DistPackageConfigBuilder}, workspace};
75    ///
76    /// let workspace = workspace::current();
77    ///
78    /// let (dist_config, pkg_configs) = DistConfigBuilder::from_default_packages("app-v1.0", workspace);
79    /// let pkg_configs = pkg_configs.into_iter().map(DistPackageConfigBuilder::build).collect::<Result<Vec<_>, _>>()?;
80    /// let dist_config = dist_config.packages(pkg_configs).build()?;
81    /// # Ok(())
82    /// # }
83    /// ```
84    pub fn from_default_packages(
85        name: impl Into<String>,
86        workspace: &'a Metadata,
87    ) -> (Self, Vec<DistPackageConfigBuilder<'a>>) {
88        Self::from_packages(name, workspace, &workspace.workspace_default_packages())
89    }
90
91    /// Creates a new `DistConfigBuilder` from the root package of given
92    /// workspace.
93    ///
94    /// The name of the created `DistConfig` will be generated from the name and
95    /// version of the root package.
96    ///
97    /// # Errors
98    ///
99    /// Returns an error if the root package is not found.
100    ///
101    /// # Examples
102    ///
103    /// ```rust
104    /// # fn main() -> cli_xtask::Result<()> {
105    /// use cli_xtask::{config::DistConfigBuilder, workspace};
106    ///
107    /// let workspace = workspace::current();
108    ///
109    /// let (dist_config, pkg_config) = DistConfigBuilder::from_root_package(workspace)?;
110    /// let dist_config = dist_config.package(pkg_config.build()?).build()?;
111    ///
112    /// let root_package = workspace.root_package().unwrap();
113    /// assert_eq!(
114    ///     dist_config.name(),
115    ///     format!("{}-v{}", root_package.name, root_package.version)
116    /// );
117    /// # Ok(())
118    /// # }
119    /// ```
120    pub fn from_root_package(
121        workspace: &'a Metadata,
122    ) -> Result<(Self, DistPackageConfigBuilder<'a>)> {
123        let package = workspace
124            .root_package()
125            .ok_or_else(|| eyre!("no root package found"))?;
126        Ok(Self::from_package(workspace, package))
127    }
128
129    /// Creates a new `DistConfigBuilder` from a package with the given name in
130    /// the the given workspace.
131    ///
132    /// The name of the created `DistConfig` will be generated from the name and
133    /// version of the given package.
134    ///
135    /// # Errors
136    ///
137    /// Returns an error if the package with the specified name is not found.
138    ///
139    /// # Examples
140    ///
141    /// ```rust
142    /// # fn main() -> cli_xtask::Result<()> {
143    /// use cli_xtask::{config::DistConfigBuilder, workspace};
144    ///
145    /// let workspace = workspace::current();
146    /// let package = workspace.workspace_packages()[0];
147    ///
148    /// let (dist_config, pkg_config) = DistConfigBuilder::from_package_name(workspace, &package.name)?;
149    /// let dist_config = dist_config.package(pkg_config.build()?).build()?;
150    ///
151    /// assert_eq!(
152    ///     dist_config.name(),
153    ///     format!("{}-v{}", package.name, package.version)
154    /// );
155    /// # Ok(())
156    /// # }
157    /// ```
158    pub fn from_package_name(
159        workspace: &'a Metadata,
160        name: &str,
161    ) -> Result<(Self, DistPackageConfigBuilder<'a>)> {
162        let workspace_packages = workspace.workspace_packages();
163        let package = workspace_packages
164            .iter()
165            .find(|package| package.name == name)
166            .ok_or_else(|| eyre!("no package found"))?;
167        Ok(Self::from_package(workspace, package))
168    }
169
170    fn from_package(
171        workspace: &'a Metadata,
172        package: &'a Package,
173    ) -> (Self, DistPackageConfigBuilder<'a>) {
174        let name = format!("{}-v{}", package.name, package.version);
175
176        let dist = Self::new(name, workspace);
177        let package_builder = DistPackageConfigBuilder::new(package);
178
179        (dist, package_builder)
180    }
181
182    fn from_packages(
183        name: impl Into<String>,
184        workspace: &'a Metadata,
185        packages: &[&'a Package],
186    ) -> (Self, Vec<DistPackageConfigBuilder<'a>>) {
187        let name = name.into();
188
189        let dist = Self::new(name, workspace);
190        let package_builders = packages
191            .iter()
192            .copied()
193            .map(DistPackageConfigBuilder::new)
194            .collect();
195        (dist, package_builders)
196    }
197
198    /// Creates a new [`DistPackageConfigBuilder`] from the given package name.
199    ///
200    /// # Errors
201    ///
202    /// Returns an error if the package with the specified name is not found.
203    ///
204    /// # Examples
205    ///
206    /// ```rust
207    /// # fn main() -> cli_xtask::Result<()> {
208    /// use cli_xtask::{config::DistConfigBuilder, workspace};
209    ///
210    /// let workspace = workspace::current();
211    /// let package = workspace.workspace_packages()[0];
212    ///
213    /// let dist_config = DistConfigBuilder::new("app-dist", workspace);
214    /// let pkg_config = dist_config.package_by_name(&package.name)?.build()?;
215    /// let dist_config = dist_config.package(pkg_config).build()?;
216    /// # Ok(())
217    /// # }
218    /// ```
219    pub fn package_by_name(&self, name: &str) -> Result<DistPackageConfigBuilder<'a>> {
220        let package = self
221            .metadata
222            .workspace_packages()
223            .into_iter()
224            .find(|package| package.name == name)
225            .ok_or_else(|| eyre!("no package found"))?;
226        let package_builder = DistPackageConfigBuilder::new(package);
227        Ok(package_builder)
228    }
229
230    /// Adds the given package to the `DistConfig`.
231    ///
232    /// # Examples
233    ///
234    /// ```rust
235    /// # fn main() -> cli_xtask::Result<()> {
236    /// use cli_xtask::{config::DistConfigBuilder, workspace};
237    ///
238    /// let workspace = workspace::current();
239    /// let package = workspace.workspace_packages()[0];
240    ///
241    /// let dist_config = DistConfigBuilder::new("app-dist", workspace);
242    /// let pkg_config = dist_config.package_by_name(&package.name)?.build()?;
243    /// let dist_config = dist_config.package(pkg_config).build()?;
244    /// # Ok(())
245    /// # }
246    pub fn package(mut self, package: DistPackageConfig<'a>) -> Self {
247        self.packages.push(package);
248        self
249    }
250
251    /// Adds the given packages to the `DistConfig`.
252    ///
253    /// # Examples
254    ///
255    /// ```rust
256    /// # fn main() -> cli_xtask::Result<()> {
257    /// use cli_xtask::{config::{DistConfigBuilder, DistPackageConfig}, workspace, Result};
258    ///
259    /// let workspace = workspace::current();
260    /// let packages = workspace.workspace_packages();
261    ///
262    /// let dist_config = DistConfigBuilder::new("app-dist", workspace);
263    /// let pkg_configs = packages.iter().map(|package| -> Result<DistPackageConfig> {
264    ///     let pkg_config = dist_config.package_by_name(&package.name)?.build()?;
265    ///     Ok(pkg_config)
266    /// }).collect::<Result<Vec<_>>>()?;
267    /// let dist_config = dist_config.packages(pkg_configs).build()?;
268    /// # Ok(())
269    /// # }
270    pub fn packages(mut self, packages: impl IntoIterator<Item = DistPackageConfig<'a>>) -> Self {
271        self.packages.extend(packages);
272        self
273    }
274
275    /// Adds the given cargo build options to the `DistConfig`.
276    ///
277    /// # Examples
278    ///
279    /// ```rust
280    /// # fn main() -> cli_xtask::Result<()> {
281    /// use cli_xtask::{config::{DistConfigBuilder, DistPackageConfig}, workspace, Result};
282    ///
283    /// let workspace = workspace::current();
284    /// let packages = workspace.workspace_packages();
285    ///
286    /// let dist_config = DistConfigBuilder::new("app-dist", workspace);
287    /// dist_config.cargo_build_options(["--features", "feature-a"]).build()?;
288    /// # Ok(())
289    /// # }
290    #[cfg(feature = "subcommand-dist-build-bin")]
291    #[cfg_attr(docsrs, doc(cfg(feature = "subcommand-dist-build-bin")))]
292    pub fn cargo_build_options(
293        mut self,
294        options: impl IntoIterator<Item = impl Into<String>>,
295    ) -> Self {
296        self.cargo_build_options
297            .extend(options.into_iter().map(Into::into));
298        self
299    }
300
301    /// Builds a [`DistConfig`] from the current configuration.
302    ///
303    /// # Errors
304    ///
305    /// Returns an error if the [`DistConfig`] cannot be built.
306    pub fn build(self) -> Result<DistConfig<'a>> {
307        Ok(DistConfig {
308            name: self.name,
309            metadata: self.metadata,
310            dist_target_directory: self.dist_target_directory,
311            dist_base_working_directory: self.dist_base_working_directory,
312            packages: self.packages,
313            #[cfg(feature = "subcommand-dist-build-bin")]
314            cargo_build_options: self.cargo_build_options,
315        })
316    }
317}
318
319/// Configuration for the distribution.
320///
321/// This struct is build from [`DistConfigBuilder`].
322///
323/// # Examples
324///
325/// ```rust
326/// # fn main() -> cli_xtask::Result<()> {
327/// use cli_xtask::{config::DistConfigBuilder, workspace};
328///
329/// let workspace = workspace::current();
330/// let config = DistConfigBuilder::new("app", workspace).build()?;
331/// # Ok(())
332/// # }
333/// ```
334#[derive(Debug)]
335pub struct DistConfig<'a> {
336    name: String,
337    metadata: &'a Metadata,
338    dist_target_directory: Utf8PathBuf,
339    dist_base_working_directory: Utf8PathBuf,
340    packages: Vec<DistPackageConfig<'a>>,
341    #[cfg(feature = "subcommand-dist-build-bin")]
342    cargo_build_options: Vec<String>,
343}
344
345impl<'a> DistConfig<'a> {
346    /// Returns the name of the distribution.
347    ///
348    /// By default, the name is formed as `<package-name>-v<package-version>`.
349    pub fn name(&self) -> &str {
350        &self.name
351    }
352
353    /// Returns the cargo workspace [`Metadata`].
354    pub fn metadata(&self) -> &'a Metadata {
355        self.metadata
356    }
357
358    /// Returns the target directory that will be used to store the distribution
359    /// archive.
360    pub fn dist_target_directory(&self) -> &Utf8Path {
361        &self.dist_target_directory
362    }
363
364    /// Returns the base working directory where the distribution artifacts will
365    /// be copied at.
366    pub fn dist_base_working_directory(&self) -> &Utf8Path {
367        &self.dist_base_working_directory
368    }
369
370    /// Returns the working directory where the distribution artifacts will be
371    /// copied at.
372    pub fn dist_working_directory(&self, target_triple: Option<&str>) -> Utf8PathBuf {
373        let target_triple = target_triple.unwrap_or("noarch");
374        self.dist_base_working_directory.join(target_triple)
375    }
376
377    /// Returns the configurations of the packages that will be distributed.
378    pub fn packages(&self) -> &[DistPackageConfig] {
379        &self.packages
380    }
381
382    /// Returns the cargo build options that will be used to build the
383    #[cfg(feature = "subcommand-dist-build-bin")]
384    #[cfg_attr(docsrs, doc(cfg(feature = "subcommand-dist-build-bin")))]
385    pub fn cargo_build_options(&self) -> &[String] {
386        &self.cargo_build_options
387    }
388}