cargo_subcommand/
manifest.rs

1use serde::Deserialize;
2use std::{
3    collections::HashMap,
4    path::{Path, PathBuf},
5};
6
7use crate::error::{Error, Result};
8use crate::utils;
9
10#[derive(Clone, Debug, Deserialize)]
11pub struct Manifest {
12    pub workspace: Option<Workspace>,
13    pub package: Option<Package>,
14    pub lib: Option<Lib>,
15    #[serde(default, rename = "bin")]
16    pub bins: Vec<Bin>,
17    #[serde(default, rename = "example")]
18    pub examples: Vec<Example>,
19}
20
21impl Manifest {
22    pub fn parse_from_toml(path: &Path) -> Result<Self> {
23        let contents = std::fs::read_to_string(path).map_err(|e| Error::Io(path.to_owned(), e))?;
24        toml::from_str(&contents).map_err(|e| Error::Toml(path.to_owned(), e))
25    }
26
27    /// Returns a mapping from manifest directory to manifest path and loaded manifest
28    pub fn members(&self, workspace_root: &Path) -> Result<HashMap<PathBuf, (PathBuf, Manifest)>> {
29        let workspace = self
30            .workspace
31            .as_ref()
32            .ok_or(Error::ManifestNotAWorkspace)?;
33        let workspace_root = utils::canonicalize(workspace_root)?;
34
35        // Check all member packages inside the workspace
36        let mut all_members = HashMap::new();
37
38        for member in &workspace.members {
39            for manifest_dir in glob::glob(workspace_root.join(member).to_str().unwrap())? {
40                let manifest_dir = manifest_dir?;
41                let manifest_path = manifest_dir.join("Cargo.toml");
42                let manifest = Manifest::parse_from_toml(&manifest_path)?;
43
44                // Workspace members cannot themselves be/contain a new workspace
45                if manifest.workspace.is_some() {
46                    return Err(Error::UnexpectedWorkspace(manifest_path));
47                }
48
49                // And because they cannot contain a [workspace], they may not be a virtual manifest
50                // and must hence contain [package]
51                if manifest.package.is_none() {
52                    return Err(Error::NoPackageInManifest(manifest_path));
53                }
54
55                all_members.insert(manifest_dir, (manifest_path, manifest));
56            }
57        }
58
59        Ok(all_members)
60    }
61
62    /// Returns `self` if it contains `[package]` but not `[workspace]`, (i.e. it cannot be
63    /// a workspace nor a virtual manifest), and describes a package named `name` if not [`None`].
64    pub fn map_nonvirtual_package(
65        self,
66        manifest_path: PathBuf,
67        name: Option<&str>,
68    ) -> Result<(PathBuf, Self)> {
69        if self.workspace.is_some() {
70            return Err(Error::UnexpectedWorkspace(manifest_path));
71        }
72
73        if let Some(package) = &self.package {
74            if let Some(name) = name {
75                if package.name == name {
76                    Ok((manifest_path, self))
77                } else {
78                    Err(Error::PackageNotFound(manifest_path, name.into()))
79                }
80            } else {
81                Ok((manifest_path, self))
82            }
83        } else {
84            Err(Error::NoPackageInManifest(manifest_path))
85        }
86    }
87}
88
89#[derive(Clone, Debug, Deserialize)]
90#[serde(rename_all = "kebab-case")]
91pub struct Workspace {
92    #[serde(default)]
93    pub default_members: Vec<String>,
94    #[serde(default)]
95    pub members: Vec<String>,
96}
97
98const fn default_true() -> bool {
99    true
100}
101
102#[derive(Clone, Debug, Deserialize)]
103pub struct Package {
104    pub name: String,
105
106    // https://doc.rust-lang.org/cargo/reference/cargo-targets.html#target-auto-discovery
107    #[serde(default = "default_true")]
108    pub autobins: bool,
109    #[serde(default = "default_true")]
110    pub autoexamples: bool,
111    // #[serde(default = "default_true")]
112    // pub autotests: bool,
113    // #[serde(default = "default_true")]
114    // pub autobenches: bool,
115}
116
117#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, Deserialize)]
118pub enum CrateType {
119    Bin,
120    Lib,
121    Staticlib,
122    Cdylib,
123}
124
125#[derive(Clone, Debug, Deserialize)]
126pub struct Lib {
127    pub name: Option<String>,
128    pub path: Option<PathBuf>,
129    // pub crate_type: Vec<CrateType>,
130}
131
132#[derive(Clone, Debug, Deserialize)]
133pub struct Bin {
134    pub name: String,
135    pub path: Option<PathBuf>,
136    // pub crate_type: Vec<CrateType>,
137}
138
139#[derive(Clone, Debug, Deserialize)]
140pub struct Example {
141    pub name: String,
142    pub path: Option<PathBuf>,
143    // pub crate_type: Vec<CrateType>,
144}