cargo_publish_ordered/
workspace.rs1use 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 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}