Skip to main content

cargo_publish_ordered/
workspace.rs

1use crate::CARGO_TOML_DEFAULT;
2use crate::error::Error;
3use cargo_metadata::{Metadata, MetadataCommand, PackageId};
4use petgraph::algo::toposort;
5use petgraph::graph::DiGraph;
6use std::collections::HashMap;
7use std::fs;
8use std::path::Path;
9
10pub struct Workspace {
11    pub metadata: Metadata,
12    pub publish_order: Vec<PackageId>,
13}
14
15impl Workspace {
16    /// Load workspace metadata and support caching.
17    pub fn load(manifest_path: Option<&str>) -> Result<Metadata, Error> {
18        let manifest_path = manifest_path.unwrap_or(CARGO_TOML_DEFAULT);
19        let cache_path = Path::new(".cargo_publish_ordered_cache.json");
20
21        let manifest_mtime = fs::metadata(manifest_path).and_then(|m| m.modified()).ok();
22        let cache_mtime = fs::metadata(cache_path).and_then(|m| m.modified()).ok();
23
24        if let (Some(manifest_mtime), Some(cache_mtime)) = (manifest_mtime, cache_mtime) {
25            if cache_mtime > manifest_mtime {
26                if let Ok(cached) = fs::read_to_string(cache_path) {
27                    if let Ok(metadata) = serde_json::from_str(&cached) {
28                        return Ok(metadata);
29                    }
30                }
31            }
32        }
33
34        let mut cmd = MetadataCommand::new();
35        cmd.manifest_path(manifest_path);
36        let metadata = cmd.exec()?;
37
38        if let Ok(json) = serde_json::to_string(&metadata) {
39            let _ = fs::write(cache_path, json);
40        }
41
42        Ok(metadata)
43    }
44
45    pub fn new(manifest_path: Option<&str>, exclude: &[String]) -> Result<Self, Error> {
46        let metadata = Self::load(manifest_path)?;
47
48        let mut graph = DiGraph::<PackageId, ()>::new();
49        let mut package_indices = HashMap::new();
50
51        for package in metadata.workspace_packages() {
52            let idx = graph.add_node(package.id.clone());
53            package_indices.insert(package.id.clone(), idx);
54        }
55
56        for package in metadata.workspace_packages() {
57            let from_idx = package_indices[&package.id];
58            for dep in &package.dependencies {
59                if let Some(to_pkg) = metadata.packages.iter().find(|p| {
60                    p.name.to_string() == dep.name && metadata.workspace_members.contains(&p.id)
61                }) {
62                    if let Some(&to_idx) = package_indices.get(&to_pkg.id) {
63                        graph.add_edge(from_idx, to_idx, ());
64                    }
65                }
66            }
67        }
68
69        let publish_order = toposort(&graph, None)
70            .map_err(|_| Error::CyclicDependency)?
71            .into_iter()
72            .map(|idx| graph[idx].clone())
73            .filter(|pkg_id| !exclude.contains(&metadata[pkg_id].name))
74            .collect();
75
76        Ok(Workspace {
77            metadata,
78            publish_order,
79        })
80    }
81
82    pub fn packages_to_publish(&self) -> Vec<&cargo_metadata::Package> {
83        self.publish_order
84            .iter()
85            .map(|pkg_id| &self.metadata[pkg_id])
86            .collect()
87    }
88}