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}