xtask/
versions.rs

1// Copyright (c) Meta Platforms, Inc. and affiliates.
2//
3// This software may be used and distributed according to the terms of the
4// GNU General Public License version 2.
5
6use 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    // Get workspace packages
34    let packages = get_workspace_packages()?;
35
36    // Extract package versions from workspace packages
37    for package in &packages {
38        output
39            .rust_versions
40            .insert(package.name.to_string(), package.version.to_string());
41    }
42
43    // Include tree crates as deps initially
44    output.rust_dep_versions.extend(
45        output
46            .rust_versions
47            .iter()
48            .map(|(k, v)| (k.clone(), v.clone())),
49    );
50
51    // Extract dependencies from all packages
52    for package in &packages {
53        extract_package_dependencies(package, &mut output.rust_dep_versions);
54    }
55
56    // Remove tree crates from deps for output
57    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        // Skip path dependencies and wildcard versions
89        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        // Check for version mismatches
102        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}