|
| 1 | +// Copyright (C) Parity Technologies (UK) Ltd. |
| 2 | +// SPDX-License-Identifier: Apache-2.0 |
| 3 | + |
| 4 | +// Licensed under the Apache License, Version 2.0 (the "License"); |
| 5 | +// you may not use this file except in compliance with the License. |
| 6 | +// You may obtain a copy of the License at |
| 7 | +// |
| 8 | +// http://www.apache.org/licenses/LICENSE-2.0 |
| 9 | +// |
| 10 | +// Unless required by applicable law or agreed to in writing, software |
| 11 | +// distributed under the License is distributed on an "AS IS" BASIS, |
| 12 | +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 13 | +// See the License for the specific language governing permissions and |
| 14 | +// limitations under the License. |
| 15 | + |
| 16 | +//! Polkadot SDK Version Manager Library |
| 17 | +//! |
| 18 | +//! This library provides functionality to manage and update Polkadot SDK dependencies |
| 19 | +//! in Cargo.toml files. |
| 20 | +
|
| 21 | +mod tests; |
| 22 | +pub mod versions; |
| 23 | + |
| 24 | +use std::{ |
| 25 | + collections::BTreeMap, |
| 26 | + fs, |
| 27 | + path::{Path, PathBuf}, |
| 28 | +}; |
| 29 | +use toml_edit::DocumentMut; |
| 30 | + |
| 31 | +pub use versions::{ |
| 32 | + get_orml_crates_and_version, get_polkadot_sdk_versions, get_release_branches_versions, |
| 33 | + get_version_mapping_with_fallback, include_orml_crates_in_version_mapping, Repository, |
| 34 | +}; |
| 35 | + |
| 36 | +pub const DEFAULT_GIT_SERVER: &str = "https://raw.githubusercontent.com"; |
| 37 | + |
| 38 | +/// Validates that the provided path points to a valid Cargo.toml file. |
| 39 | +/// |
| 40 | +/// If the path is a directory, it will append "Cargo.toml" to it. |
| 41 | +/// Returns an error if the resulting path does not exist. |
| 42 | +/// |
| 43 | +/// # Arguments |
| 44 | +/// |
| 45 | +/// * `path` - A PathBuf that should point to either a Cargo.toml file or a directory containing one |
| 46 | +/// |
| 47 | +/// # Errors |
| 48 | +/// |
| 49 | +/// Returns an error if the Cargo.toml file cannot be found at the specified path. |
| 50 | +pub fn validate_workspace_path(mut path: PathBuf) -> Result<PathBuf, Box<dyn std::error::Error>> { |
| 51 | + if path.is_dir() { |
| 52 | + path = path.join("Cargo.toml"); |
| 53 | + } |
| 54 | + |
| 55 | + if !path.exists() { |
| 56 | + return Err(format!( |
| 57 | + "Could not find workspace root Cargo.toml file at {}", |
| 58 | + path.display() |
| 59 | + ) |
| 60 | + .into()); |
| 61 | + } |
| 62 | + |
| 63 | + Ok(path) |
| 64 | +} |
| 65 | + |
| 66 | +/// Updates dependencies in a Cargo.toml file based on the provided version mappings. |
| 67 | +/// |
| 68 | +/// # Arguments |
| 69 | +/// |
| 70 | +/// * `cargo_toml_path` - Path to the Cargo.toml file to update |
| 71 | +/// * `crates_versions` - A map of crate names to their versions |
| 72 | +/// * `overwrite` - If true, will overwrite local path dependencies |
| 73 | +/// * `only_check` - If true, only checks if dependencies match without updating |
| 74 | +/// |
| 75 | +/// # Errors |
| 76 | +/// |
| 77 | +/// Returns an error if: |
| 78 | +/// - The file cannot be read or written |
| 79 | +/// - The TOML content is invalid |
| 80 | +/// - `only_check` is true and dependencies are not up to date |
| 81 | +pub fn update_dependencies( |
| 82 | + cargo_toml_path: &Path, |
| 83 | + crates_versions: &BTreeMap<String, String>, |
| 84 | + overwrite: bool, |
| 85 | + only_check: bool, |
| 86 | +) -> Result<(), Box<dyn std::error::Error>> { |
| 87 | + let cargo_toml = |
| 88 | + update_dependencies_impl(cargo_toml_path, crates_versions, overwrite, only_check)?; |
| 89 | + |
| 90 | + match cargo_toml { |
| 91 | + Some(new_content) => { |
| 92 | + fs::write(cargo_toml_path, new_content)?; |
| 93 | + println!("Updated dependencies in {}", cargo_toml_path.display()); |
| 94 | + } |
| 95 | + None => { |
| 96 | + println!( |
| 97 | + "Dependencies in {} are already up to date", |
| 98 | + cargo_toml_path.display() |
| 99 | + ); |
| 100 | + } |
| 101 | + } |
| 102 | + |
| 103 | + Ok(()) |
| 104 | +} |
| 105 | + |
| 106 | +/// Internal implementation of dependency update logic. |
| 107 | +/// |
| 108 | +/// Returns `Some(String)` with the new content if changes were made, |
| 109 | +/// or `None` if no changes were needed. |
| 110 | +fn update_dependencies_impl( |
| 111 | + cargo_toml_path: &Path, |
| 112 | + crates_versions: &BTreeMap<String, String>, |
| 113 | + overwrite: bool, |
| 114 | + only_check: bool, |
| 115 | +) -> Result<Option<String>, Box<dyn std::error::Error>> { |
| 116 | + let cargo_toml_content = fs::read_to_string(cargo_toml_path)?; |
| 117 | + let mut cargo_toml: DocumentMut = cargo_toml_content.parse()?; |
| 118 | + // Check if cargo workspace is defined |
| 119 | + let deps = match cargo_toml.as_table_mut().get_mut("workspace") { |
| 120 | + Some(toml_edit::Item::Table(table)) => table, |
| 121 | + _ => cargo_toml.as_table_mut(), |
| 122 | + }; |
| 123 | + |
| 124 | + for table in ["dependencies", "dev-dependencies", "build-dependencies"].iter() { |
| 125 | + if let Some(toml_edit::Item::Table(dep_table)) = deps.get_mut(table) { |
| 126 | + update_table_dependencies(dep_table, crates_versions, overwrite); |
| 127 | + } |
| 128 | + } |
| 129 | + |
| 130 | + let new_content = cargo_toml.to_string(); |
| 131 | + if new_content != cargo_toml_content { |
| 132 | + if only_check { |
| 133 | + Err("Dependencies are not up to date".into()) |
| 134 | + } else { |
| 135 | + Ok(Some(new_content)) |
| 136 | + } |
| 137 | + } else { |
| 138 | + Ok(None) |
| 139 | + } |
| 140 | +} |
| 141 | + |
| 142 | +/// Updates dependencies within a specific TOML table. |
| 143 | +/// |
| 144 | +/// This function modifies the dependency table in-place, updating versions |
| 145 | +/// and removing git/path-related fields as appropriate. |
| 146 | +/// |
| 147 | +/// # Arguments |
| 148 | +/// |
| 149 | +/// * `dep_table` - The TOML table containing dependencies |
| 150 | +/// * `crates_versions` - A map of crate names to their versions |
| 151 | +/// * `overwrite` - If true, will overwrite local path dependencies |
| 152 | +pub fn update_table_dependencies( |
| 153 | + dep_table: &mut toml_edit::Table, |
| 154 | + crates_versions: &BTreeMap<String, String>, |
| 155 | + overwrite: bool, |
| 156 | +) { |
| 157 | + for (dep_key, dep_value) in dep_table.iter_mut() { |
| 158 | + let dep_key_str = dep_key.get(); |
| 159 | + |
| 160 | + // account for dep renaming: |
| 161 | + let lookup_key = if let Some(table) = dep_value.as_table_like() { |
| 162 | + table |
| 163 | + .get("package") |
| 164 | + .and_then(|p| p.as_str()) |
| 165 | + .unwrap_or(dep_key_str) |
| 166 | + } else { |
| 167 | + dep_key_str |
| 168 | + }; |
| 169 | + |
| 170 | + let Some(crate_version) = crates_versions.get(lookup_key) else { |
| 171 | + log::debug!("Could not find version for {}", lookup_key); |
| 172 | + continue; |
| 173 | + }; |
| 174 | + |
| 175 | + if let Some(table) = dep_value.as_table_like_mut() { |
| 176 | + if !overwrite && table.get("path").is_some() { |
| 177 | + continue; |
| 178 | + } |
| 179 | + |
| 180 | + table.remove("rev"); |
| 181 | + table.remove("branch"); |
| 182 | + table.remove("tag"); |
| 183 | + table.remove("path"); |
| 184 | + table.remove("git"); |
| 185 | + |
| 186 | + let mut new_table = toml_edit::InlineTable::default(); |
| 187 | + |
| 188 | + // Directly create a `toml_edit::Value` for the version |
| 189 | + new_table.get_or_insert( |
| 190 | + "version", |
| 191 | + toml_edit::value(crate_version.clone()).as_value().unwrap(), |
| 192 | + ); |
| 193 | + |
| 194 | + for (key, value) in table.iter() { |
| 195 | + // Ensure we're inserting `Value`s, not `Item`s |
| 196 | + if key != "version" && value.is_value() { |
| 197 | + new_table.get_or_insert(key, value.as_value().unwrap().clone()); |
| 198 | + } |
| 199 | + } |
| 200 | + new_table.fmt(); |
| 201 | + |
| 202 | + // Replace the original table-like item with the new inline table |
| 203 | + *dep_value = toml_edit::Item::Value(toml_edit::Value::InlineTable(new_table)); |
| 204 | + } else if dep_value.is_str() { |
| 205 | + *dep_value = toml_edit::value(crate_version.clone()); |
| 206 | + } else { |
| 207 | + log::error!("Unexpected dependency value type for {}", dep_key_str); |
| 208 | + continue; |
| 209 | + } |
| 210 | + |
| 211 | + log::debug!("Setting {} to {}", dep_key_str, crate_version); |
| 212 | + } |
| 213 | +} |
0 commit comments