1use anyhow::Context;
7use cargo_metadata::Package;
8use serde::Serialize;
9
10use std::collections::BTreeMap;
11
12use crate::get_workspace_packages;
13
14#[derive(Default, Clone, Copy, clap::ValueEnum)]
15pub(crate) enum Format {
16 #[default]
17 Json,
18 Starlark,
19}
20
21pub fn version_command(format: Format) -> Result<(), anyhow::Error> {
22 #[derive(Serialize)]
23 struct Output {
24 rust_versions: BTreeMap<String, String>,
25 rust_dep_versions: BTreeMap<String, String>,
26 }
27
28 let mut output = Output {
29 rust_versions: BTreeMap::new(),
30 rust_dep_versions: BTreeMap::new(),
31 };
32
33 let packages = get_workspace_packages()?;
35
36 for package in &packages {
38 output
39 .rust_versions
40 .insert(package.name.to_string(), package.version.to_string());
41 }
42
43 output.rust_dep_versions.extend(
45 output
46 .rust_versions
47 .iter()
48 .map(|(k, v)| (k.clone(), v.clone())),
49 );
50
51 for package in &packages {
53 extract_package_dependencies(package, &mut output.rust_dep_versions);
54 }
55
56 for crate_name in output.rust_versions.keys() {
58 output.rust_dep_versions.remove(crate_name);
59 }
60
61 match format {
62 Format::Json => {
63 println!(
64 "{}",
65 serde_json::to_string_pretty(&output).context("failed to serialize")?
66 );
67 }
68 Format::Starlark => {
69 println!("RUST_VERSIONS = {{");
70 for (k, v) in output.rust_versions {
71 println!(" \"{k}\": \"{v}\",");
72 }
73 println!("}} # RUST_VERSIONS\n");
74
75 println!("RUST_DEP_VERSIONS = {{");
76 for (k, v) in output.rust_dep_versions {
77 println!(" \"{k}\": \"{v}\",");
78 }
79 println!("}} # RUST_DEP_VERSIONS");
80 }
81 };
82
83 Ok(())
84}
85
86fn extract_package_dependencies(package: &Package, rust_deps: &mut BTreeMap<String, String>) {
87 for dep in &package.dependencies {
88 let req_str = dep.req.to_string();
90 if dep.path.is_some() || req_str == "*" {
91 continue;
92 }
93
94 log::debug!(
95 "[{}] {}: version {}",
96 package.manifest_path,
97 dep.name,
98 req_str
99 );
100
101 if let Some(existing_version) = rust_deps.get(&dep.name) {
103 if existing_version != &req_str {
104 log::warn!(
105 "[{}] crate \"{}\" {} mismatches existing {}",
106 package.manifest_path,
107 dep.name,
108 req_str,
109 existing_version
110 );
111 }
112 } else {
113 rust_deps.insert(dep.name.clone(), req_str);
114 }
115 }
116}