diff options
| author | Ulf Lilleengen <[email protected]> | 2025-08-19 13:58:25 +0200 |
|---|---|---|
| committer | Ulf Lilleengen <[email protected]> | 2025-08-25 19:44:51 +0200 |
| commit | d835b5385734d6211e247b13b7f4b3c53c6b6fff (patch) | |
| tree | 4d272bff3a8b1864ee084b21fa4f2e700c0ac17a /release | |
| parent | b00496fd299ca1dbc122476f0477b4032d5727c7 (diff) | |
chore: move tool to separate repo
Diffstat (limited to 'release')
| -rw-r--r-- | release/Cargo.toml | 27 | ||||
| -rw-r--r-- | release/src/build.rs | 50 | ||||
| -rw-r--r-- | release/src/cargo.rs | 220 | ||||
| -rw-r--r-- | release/src/main.rs | 514 | ||||
| -rw-r--r-- | release/src/semver_check.rs | 178 | ||||
| -rw-r--r-- | release/src/types.rs | 60 |
6 files changed, 0 insertions, 1049 deletions
diff --git a/release/Cargo.toml b/release/Cargo.toml deleted file mode 100644 index 9701a76e5..000000000 --- a/release/Cargo.toml +++ /dev/null | |||
| @@ -1,27 +0,0 @@ | |||
| 1 | [package] | ||
| 2 | name = "embassy-release" | ||
| 3 | version = "0.1.0" | ||
| 4 | edition = "2021" | ||
| 5 | |||
| 6 | [dependencies] | ||
| 7 | clap = { version = "4.5.1", features = ["derive"] } | ||
| 8 | walkdir = "2.5.0" | ||
| 9 | toml = "0.9.5" | ||
| 10 | toml_edit = { version = "0.23.1", features = ["serde"] } | ||
| 11 | serde = { version = "1.0.198", features = ["derive"] } | ||
| 12 | regex = "1.10.4" | ||
| 13 | anyhow = "1" | ||
| 14 | petgraph = "0.8.2" | ||
| 15 | semver = "1.0.26" | ||
| 16 | cargo-semver-checks = "0.43.0" | ||
| 17 | log = "0.4" | ||
| 18 | simple_logger = "5.0.0" | ||
| 19 | temp-file = "0.1.9" | ||
| 20 | flate2 = "1.1.1" | ||
| 21 | crates-index = "3.11.0" | ||
| 22 | tar = "0.4" | ||
| 23 | reqwest = { version = "0.12", features = ["blocking"] } | ||
| 24 | cargo-manifest = "0.19.1" | ||
| 25 | |||
| 26 | [package.metadata.embassy] | ||
| 27 | skip = true | ||
diff --git a/release/src/build.rs b/release/src/build.rs deleted file mode 100644 index 7c777d36c..000000000 --- a/release/src/build.rs +++ /dev/null | |||
| @@ -1,50 +0,0 @@ | |||
| 1 | use anyhow::Result; | ||
| 2 | |||
| 3 | use crate::cargo::{CargoArgsBuilder, CargoBatchBuilder}; | ||
| 4 | |||
| 5 | pub(crate) fn build(ctx: &crate::Context, crate_name: Option<&str>) -> Result<()> { | ||
| 6 | let mut batch_builder = CargoBatchBuilder::new(); | ||
| 7 | |||
| 8 | // Process either specific crate or all crates | ||
| 9 | let crates_to_build: Vec<_> = if let Some(name) = crate_name { | ||
| 10 | // Build only the specified crate | ||
| 11 | if let Some(krate) = ctx.crates.get(name) { | ||
| 12 | vec![krate] | ||
| 13 | } else { | ||
| 14 | return Err(anyhow::anyhow!("Crate '{}' not found", name)); | ||
| 15 | } | ||
| 16 | } else { | ||
| 17 | // Build all crates | ||
| 18 | ctx.crates.values().collect() | ||
| 19 | }; | ||
| 20 | |||
| 21 | // Process selected crates and add their build configurations to the batch | ||
| 22 | for krate in crates_to_build { | ||
| 23 | for config in &krate.configs { | ||
| 24 | let mut args_builder = CargoArgsBuilder::new() | ||
| 25 | .subcommand("build") | ||
| 26 | .arg("--release") | ||
| 27 | .arg(format!("--manifest-path={}/Cargo.toml", krate.path.to_string_lossy())); | ||
| 28 | |||
| 29 | if let Some(ref target) = config.target { | ||
| 30 | args_builder = args_builder.target(target); | ||
| 31 | } | ||
| 32 | |||
| 33 | if !config.features.is_empty() { | ||
| 34 | args_builder = args_builder.features(&config.features); | ||
| 35 | } | ||
| 36 | |||
| 37 | if let Some(ref artifact_dir) = config.artifact_dir { | ||
| 38 | args_builder = args_builder.artifact_dir(artifact_dir); | ||
| 39 | } | ||
| 40 | |||
| 41 | batch_builder.add_command(args_builder.build()); | ||
| 42 | } | ||
| 43 | } | ||
| 44 | |||
| 45 | // Execute the cargo batch command | ||
| 46 | let batch_args = batch_builder.build(); | ||
| 47 | crate::cargo::run(&batch_args, &ctx.root)?; | ||
| 48 | |||
| 49 | Ok(()) | ||
| 50 | } | ||
diff --git a/release/src/cargo.rs b/release/src/cargo.rs deleted file mode 100644 index c1ed4118f..000000000 --- a/release/src/cargo.rs +++ /dev/null | |||
| @@ -1,220 +0,0 @@ | |||
| 1 | //! Tools for working with Cargo. | ||
| 2 | |||
| 3 | use std::ffi::OsStr; | ||
| 4 | use std::path::{Path, PathBuf}; | ||
| 5 | use std::process::{Command, Stdio}; | ||
| 6 | |||
| 7 | use anyhow::{bail, Result}; | ||
| 8 | use serde::{Deserialize, Serialize}; | ||
| 9 | |||
| 10 | use crate::windows_safe_path; | ||
| 11 | |||
| 12 | #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] | ||
| 13 | pub struct Artifact { | ||
| 14 | pub executable: PathBuf, | ||
| 15 | } | ||
| 16 | |||
| 17 | /// Execute cargo with the given arguments and from the specified directory. | ||
| 18 | pub fn run(args: &[String], cwd: &Path) -> Result<()> { | ||
| 19 | run_with_env::<[(&str, &str); 0], _, _>(args, cwd, [], false)?; | ||
| 20 | Ok(()) | ||
| 21 | } | ||
| 22 | |||
| 23 | /// Execute cargo with the given arguments and from the specified directory. | ||
| 24 | pub fn run_with_env<I, K, V>(args: &[String], cwd: &Path, envs: I, capture: bool) -> Result<String> | ||
| 25 | where | ||
| 26 | I: IntoIterator<Item = (K, V)> + core::fmt::Debug, | ||
| 27 | K: AsRef<OsStr>, | ||
| 28 | V: AsRef<OsStr>, | ||
| 29 | { | ||
| 30 | if !cwd.is_dir() { | ||
| 31 | bail!("The `cwd` argument MUST be a directory"); | ||
| 32 | } | ||
| 33 | |||
| 34 | // Make sure to not use a UNC as CWD! | ||
| 35 | // That would make `OUT_DIR` a UNC which will trigger things like the one fixed in https://github.com/dtolnay/rustversion/pull/51 | ||
| 36 | // While it's fixed in `rustversion` it's not fixed for other crates we are | ||
| 37 | // using now or in future! | ||
| 38 | let cwd = windows_safe_path(cwd); | ||
| 39 | |||
| 40 | println!( | ||
| 41 | "Running `cargo {}` in {:?} - Environment {:?}", | ||
| 42 | args.join(" "), | ||
| 43 | cwd, | ||
| 44 | envs | ||
| 45 | ); | ||
| 46 | |||
| 47 | let mut command = Command::new(get_cargo()); | ||
| 48 | |||
| 49 | command | ||
| 50 | .args(args) | ||
| 51 | .current_dir(cwd) | ||
| 52 | .envs(envs) | ||
| 53 | .stdout(if capture { Stdio::piped() } else { Stdio::inherit() }) | ||
| 54 | .stderr(if capture { Stdio::piped() } else { Stdio::inherit() }); | ||
| 55 | |||
| 56 | if args.iter().any(|a| a.starts_with('+')) { | ||
| 57 | // Make sure the right cargo runs | ||
| 58 | command.env_remove("CARGO"); | ||
| 59 | } | ||
| 60 | |||
| 61 | let output = command.stdin(Stdio::inherit()).output()?; | ||
| 62 | |||
| 63 | // Make sure that we return an appropriate exit code here, as Github Actions | ||
| 64 | // requires this in order to function correctly: | ||
| 65 | if output.status.success() { | ||
| 66 | Ok(String::from_utf8_lossy(&output.stdout).to_string()) | ||
| 67 | } else { | ||
| 68 | bail!("Failed to execute cargo subcommand `cargo {}`", args.join(" "),) | ||
| 69 | } | ||
| 70 | } | ||
| 71 | |||
| 72 | fn get_cargo() -> String { | ||
| 73 | // On Windows when executed via `cargo run` (e.g. via the xtask alias) the | ||
| 74 | // `cargo` on the search path is NOT the cargo-wrapper but the `cargo` from the | ||
| 75 | // toolchain - that one doesn't understand `+toolchain` | ||
| 76 | #[cfg(target_os = "windows")] | ||
| 77 | let cargo = if let Ok(cargo) = std::env::var("CARGO_HOME") { | ||
| 78 | format!("{cargo}/bin/cargo") | ||
| 79 | } else { | ||
| 80 | String::from("cargo") | ||
| 81 | }; | ||
| 82 | |||
| 83 | #[cfg(not(target_os = "windows"))] | ||
| 84 | let cargo = String::from("cargo"); | ||
| 85 | |||
| 86 | cargo | ||
| 87 | } | ||
| 88 | |||
| 89 | #[derive(Debug, Default)] | ||
| 90 | pub struct CargoArgsBuilder { | ||
| 91 | toolchain: Option<String>, | ||
| 92 | subcommand: String, | ||
| 93 | target: Option<String>, | ||
| 94 | features: Vec<String>, | ||
| 95 | args: Vec<String>, | ||
| 96 | } | ||
| 97 | |||
| 98 | impl CargoArgsBuilder { | ||
| 99 | #[must_use] | ||
| 100 | pub fn new() -> Self { | ||
| 101 | Self { | ||
| 102 | toolchain: None, | ||
| 103 | subcommand: String::new(), | ||
| 104 | target: None, | ||
| 105 | features: vec![], | ||
| 106 | args: vec![], | ||
| 107 | } | ||
| 108 | } | ||
| 109 | |||
| 110 | #[must_use] | ||
| 111 | pub fn toolchain<S>(mut self, toolchain: S) -> Self | ||
| 112 | where | ||
| 113 | S: Into<String>, | ||
| 114 | { | ||
| 115 | self.toolchain = Some(toolchain.into()); | ||
| 116 | self | ||
| 117 | } | ||
| 118 | |||
| 119 | #[must_use] | ||
| 120 | pub fn subcommand<S>(mut self, subcommand: S) -> Self | ||
| 121 | where | ||
| 122 | S: Into<String>, | ||
| 123 | { | ||
| 124 | self.subcommand = subcommand.into(); | ||
| 125 | self | ||
| 126 | } | ||
| 127 | |||
| 128 | #[must_use] | ||
| 129 | pub fn target<S>(mut self, target: S) -> Self | ||
| 130 | where | ||
| 131 | S: Into<String>, | ||
| 132 | { | ||
| 133 | self.target = Some(target.into()); | ||
| 134 | self | ||
| 135 | } | ||
| 136 | |||
| 137 | #[must_use] | ||
| 138 | pub fn features(mut self, features: &[String]) -> Self { | ||
| 139 | self.features = features.to_vec(); | ||
| 140 | self | ||
| 141 | } | ||
| 142 | |||
| 143 | #[must_use] | ||
| 144 | pub fn artifact_dir<S>(mut self, artifact_dir: S) -> Self | ||
| 145 | where | ||
| 146 | S: Into<String>, | ||
| 147 | { | ||
| 148 | self.args.push(format!("--artifact-dir={}", artifact_dir.into())); | ||
| 149 | self | ||
| 150 | } | ||
| 151 | |||
| 152 | #[must_use] | ||
| 153 | pub fn arg<S>(mut self, arg: S) -> Self | ||
| 154 | where | ||
| 155 | S: Into<String>, | ||
| 156 | { | ||
| 157 | self.args.push(arg.into()); | ||
| 158 | self | ||
| 159 | } | ||
| 160 | |||
| 161 | #[must_use] | ||
| 162 | pub fn build(&self) -> Vec<String> { | ||
| 163 | let mut args = vec![]; | ||
| 164 | |||
| 165 | if let Some(ref toolchain) = self.toolchain { | ||
| 166 | args.push(format!("+{toolchain}")); | ||
| 167 | } | ||
| 168 | |||
| 169 | args.push(self.subcommand.clone()); | ||
| 170 | |||
| 171 | if let Some(ref target) = self.target { | ||
| 172 | args.push(format!("--target={target}")); | ||
| 173 | } | ||
| 174 | |||
| 175 | if !self.features.is_empty() { | ||
| 176 | args.push(format!("--features={}", self.features.join(","))); | ||
| 177 | } | ||
| 178 | |||
| 179 | for arg in self.args.iter() { | ||
| 180 | args.push(arg.clone()); | ||
| 181 | } | ||
| 182 | |||
| 183 | args | ||
| 184 | } | ||
| 185 | } | ||
| 186 | |||
| 187 | #[derive(Debug, Default)] | ||
| 188 | pub struct CargoBatchBuilder { | ||
| 189 | commands: Vec<Vec<String>>, | ||
| 190 | } | ||
| 191 | |||
| 192 | impl CargoBatchBuilder { | ||
| 193 | #[must_use] | ||
| 194 | pub fn new() -> Self { | ||
| 195 | Self { commands: vec![] } | ||
| 196 | } | ||
| 197 | |||
| 198 | #[must_use] | ||
| 199 | pub fn command(mut self, args: Vec<String>) -> Self { | ||
| 200 | self.commands.push(args); | ||
| 201 | self | ||
| 202 | } | ||
| 203 | |||
| 204 | pub fn add_command(&mut self, args: Vec<String>) -> &mut Self { | ||
| 205 | self.commands.push(args); | ||
| 206 | self | ||
| 207 | } | ||
| 208 | |||
| 209 | #[must_use] | ||
| 210 | pub fn build(&self) -> Vec<String> { | ||
| 211 | let mut args = vec!["batch".to_string()]; | ||
| 212 | |||
| 213 | for command in &self.commands { | ||
| 214 | args.push("---".to_string()); | ||
| 215 | args.extend(command.clone()); | ||
| 216 | } | ||
| 217 | |||
| 218 | args | ||
| 219 | } | ||
| 220 | } | ||
diff --git a/release/src/main.rs b/release/src/main.rs deleted file mode 100644 index 22c926b86..000000000 --- a/release/src/main.rs +++ /dev/null | |||
| @@ -1,514 +0,0 @@ | |||
| 1 | use std::collections::{BTreeMap, HashMap}; | ||
| 2 | use std::fs; | ||
| 3 | use std::path::{Path, PathBuf}; | ||
| 4 | use std::process::Command as ProcessCommand; | ||
| 5 | |||
| 6 | use anyhow::{anyhow, bail, Result}; | ||
| 7 | use cargo_semver_checks::ReleaseType; | ||
| 8 | use clap::{Parser, Subcommand}; | ||
| 9 | use petgraph::graph::{Graph, NodeIndex}; | ||
| 10 | use petgraph::visit::Bfs; | ||
| 11 | use petgraph::{Directed, Direction}; | ||
| 12 | use simple_logger::SimpleLogger; | ||
| 13 | use toml_edit::{DocumentMut, Item, Value}; | ||
| 14 | use types::*; | ||
| 15 | |||
| 16 | fn check_publish_dependencies(ctx: &Context) -> Result<()> { | ||
| 17 | for krate in ctx.crates.values() { | ||
| 18 | if krate.publish { | ||
| 19 | for dep_name in &krate.dependencies { | ||
| 20 | if let Some(dep_crate) = ctx.crates.get(dep_name) { | ||
| 21 | if !dep_crate.publish { | ||
| 22 | return Err(anyhow!( | ||
| 23 | "Publishable crate '{}' depends on non-publishable crate '{}'. This is not allowed.", | ||
| 24 | krate.name, | ||
| 25 | dep_name | ||
| 26 | )); | ||
| 27 | } | ||
| 28 | } | ||
| 29 | } | ||
| 30 | } | ||
| 31 | } | ||
| 32 | Ok(()) | ||
| 33 | } | ||
| 34 | |||
| 35 | mod build; | ||
| 36 | mod cargo; | ||
| 37 | mod semver_check; | ||
| 38 | mod types; | ||
| 39 | |||
| 40 | /// Tool to traverse and operate on intra-repo Rust crate dependencies | ||
| 41 | #[derive(Parser, Debug)] | ||
| 42 | #[command(author, version, about)] | ||
| 43 | struct Args { | ||
| 44 | /// Command to perform on each crate | ||
| 45 | #[command(subcommand)] | ||
| 46 | command: Command, | ||
| 47 | } | ||
| 48 | |||
| 49 | #[derive(Debug, Subcommand)] | ||
| 50 | enum Command { | ||
| 51 | /// All crates and their direct dependencies | ||
| 52 | List, | ||
| 53 | /// List all dependencies for a crate | ||
| 54 | Dependencies { | ||
| 55 | /// Crate name to print dependencies for. | ||
| 56 | #[arg(value_name = "CRATE")] | ||
| 57 | crate_name: String, | ||
| 58 | }, | ||
| 59 | /// List all dependencies for a crate | ||
| 60 | Dependents { | ||
| 61 | /// Crate name to print dependencies for. | ||
| 62 | #[arg(value_name = "CRATE")] | ||
| 63 | crate_name: String, | ||
| 64 | }, | ||
| 65 | |||
| 66 | /// Build | ||
| 67 | Build { | ||
| 68 | /// Crate to check. If not specified checks all crates. | ||
| 69 | #[arg(value_name = "CRATE")] | ||
| 70 | crate_name: Option<String>, | ||
| 71 | }, | ||
| 72 | /// SemverCheck | ||
| 73 | SemverCheck { | ||
| 74 | /// Specific crate name to check | ||
| 75 | #[arg(value_name = "CRATE")] | ||
| 76 | crate_name: String, | ||
| 77 | }, | ||
| 78 | /// Prepare to release a crate and all dependents that needs updating | ||
| 79 | /// - Semver checks | ||
| 80 | /// - Bump versions and commit | ||
| 81 | /// - Create tag. | ||
| 82 | PrepareRelease { | ||
| 83 | /// Crate to release. Will traverse that crate an it's dependents. If not specified checks all crates. | ||
| 84 | #[arg(value_name = "CRATE")] | ||
| 85 | crate_name: String, | ||
| 86 | }, | ||
| 87 | } | ||
| 88 | |||
| 89 | fn update_version(c: &mut Crate, new_version: &str) -> Result<()> { | ||
| 90 | let path = c.path.join("Cargo.toml"); | ||
| 91 | c.version = new_version.to_string(); | ||
| 92 | let content = fs::read_to_string(&path)?; | ||
| 93 | let mut doc: DocumentMut = content.parse()?; | ||
| 94 | for section in ["package"] { | ||
| 95 | if let Some(Item::Table(dep_table)) = doc.get_mut(section) { | ||
| 96 | dep_table.insert("version", Item::Value(Value::from(new_version))); | ||
| 97 | } | ||
| 98 | } | ||
| 99 | fs::write(&path, doc.to_string())?; | ||
| 100 | Ok(()) | ||
| 101 | } | ||
| 102 | |||
| 103 | fn update_versions(to_update: &Crate, dep: &CrateId, new_version: &str) -> Result<()> { | ||
| 104 | let path = to_update.path.join("Cargo.toml"); | ||
| 105 | let content = fs::read_to_string(&path)?; | ||
| 106 | let mut doc: DocumentMut = content.parse()?; | ||
| 107 | let mut changed = false; | ||
| 108 | for section in ["dependencies", "dev-dependencies", "build-dependencies"] { | ||
| 109 | if let Some(Item::Table(dep_table)) = doc.get_mut(section) { | ||
| 110 | if let Some(item) = dep_table.get_mut(&dep) { | ||
| 111 | match item { | ||
| 112 | // e.g., foo = "0.1.0" | ||
| 113 | Item::Value(Value::String(_)) => { | ||
| 114 | *item = Item::Value(Value::from(new_version)); | ||
| 115 | changed = true; | ||
| 116 | } | ||
| 117 | // e.g., foo = { version = "...", ... } | ||
| 118 | Item::Value(Value::InlineTable(inline)) => { | ||
| 119 | if inline.contains_key("version") { | ||
| 120 | inline["version"] = Value::from(new_version); | ||
| 121 | changed = true; | ||
| 122 | } | ||
| 123 | } | ||
| 124 | _ => {} // Leave unusual formats untouched | ||
| 125 | } | ||
| 126 | } | ||
| 127 | } | ||
| 128 | } | ||
| 129 | |||
| 130 | if changed { | ||
| 131 | fs::write(&path, doc.to_string())?; | ||
| 132 | println!("🔧 Updated {} to {} in {}", dep, new_version, path.display()); | ||
| 133 | } | ||
| 134 | Ok(()) | ||
| 135 | } | ||
| 136 | |||
| 137 | fn list_crates(root: &PathBuf) -> Result<BTreeMap<CrateId, Crate>> { | ||
| 138 | let mut crates = BTreeMap::new(); | ||
| 139 | discover_crates(root, &mut crates)?; | ||
| 140 | Ok(crates) | ||
| 141 | } | ||
| 142 | |||
| 143 | fn discover_crates(dir: &PathBuf, crates: &mut BTreeMap<CrateId, Crate>) -> Result<()> { | ||
| 144 | let d = std::fs::read_dir(dir)?; | ||
| 145 | for c in d { | ||
| 146 | let entry = c?; | ||
| 147 | if entry.file_type()?.is_dir() { | ||
| 148 | let path = dir.join(entry.path()); | ||
| 149 | let cargo_toml = path.join("Cargo.toml"); | ||
| 150 | |||
| 151 | if cargo_toml.exists() { | ||
| 152 | let content = fs::read_to_string(&cargo_toml)?; | ||
| 153 | |||
| 154 | // Try to parse as a crate, skip if it's a workspace | ||
| 155 | let parsed: Result<ParsedCrate, _> = toml::from_str(&content); | ||
| 156 | if let Ok(parsed) = parsed { | ||
| 157 | let id = parsed.package.name; | ||
| 158 | |||
| 159 | let metadata = &parsed.package.metadata.embassy; | ||
| 160 | |||
| 161 | if metadata.skip { | ||
| 162 | continue; | ||
| 163 | } | ||
| 164 | |||
| 165 | let mut dependencies = Vec::new(); | ||
| 166 | for (k, _) in parsed.dependencies { | ||
| 167 | if k.starts_with("embassy-") { | ||
| 168 | dependencies.push(k); | ||
| 169 | } | ||
| 170 | } | ||
| 171 | |||
| 172 | let mut configs = metadata.build.clone(); | ||
| 173 | if configs.is_empty() { | ||
| 174 | configs.push(BuildConfig { | ||
| 175 | features: vec![], | ||
| 176 | target: None, | ||
| 177 | artifact_dir: None, | ||
| 178 | }) | ||
| 179 | } | ||
| 180 | |||
| 181 | crates.insert( | ||
| 182 | id.clone(), | ||
| 183 | Crate { | ||
| 184 | name: id, | ||
| 185 | version: parsed.package.version, | ||
| 186 | path, | ||
| 187 | dependencies, | ||
| 188 | configs, | ||
| 189 | publish: parsed.package.publish, | ||
| 190 | }, | ||
| 191 | ); | ||
| 192 | } | ||
| 193 | } else { | ||
| 194 | // Recursively search subdirectories, but only for examples, tests, and docs | ||
| 195 | let file_name = entry.file_name(); | ||
| 196 | let dir_name = file_name.to_string_lossy(); | ||
| 197 | if dir_name == "examples" || dir_name == "tests" || dir_name == "docs" { | ||
| 198 | discover_crates(&path, crates)?; | ||
| 199 | } | ||
| 200 | } | ||
| 201 | } | ||
| 202 | } | ||
| 203 | Ok(()) | ||
| 204 | } | ||
| 205 | |||
| 206 | fn build_graph(crates: &BTreeMap<CrateId, Crate>) -> (Graph<CrateId, ()>, HashMap<CrateId, NodeIndex>) { | ||
| 207 | let mut graph = Graph::<CrateId, (), Directed>::new(); | ||
| 208 | let mut node_indices: HashMap<CrateId, NodeIndex> = HashMap::new(); | ||
| 209 | |||
| 210 | // Helper to insert or get existing node | ||
| 211 | let get_or_insert_node = |id: CrateId, graph: &mut Graph<CrateId, ()>, map: &mut HashMap<CrateId, NodeIndex>| { | ||
| 212 | if let Some(&idx) = map.get(&id) { | ||
| 213 | idx | ||
| 214 | } else { | ||
| 215 | let idx = graph.add_node(id.clone()); | ||
| 216 | map.insert(id, idx); | ||
| 217 | idx | ||
| 218 | } | ||
| 219 | }; | ||
| 220 | |||
| 221 | for krate in crates.values() { | ||
| 222 | get_or_insert_node(krate.name.clone(), &mut graph, &mut node_indices); | ||
| 223 | } | ||
| 224 | |||
| 225 | for krate in crates.values() { | ||
| 226 | // Insert crate node if not exists | ||
| 227 | let crate_idx = get_or_insert_node(krate.name.clone(), &mut graph, &mut node_indices); | ||
| 228 | |||
| 229 | // Insert dependencies and connect edges | ||
| 230 | for dep in krate.dependencies.iter() { | ||
| 231 | let dep_idx = get_or_insert_node(dep.clone(), &mut graph, &mut node_indices); | ||
| 232 | graph.add_edge(crate_idx, dep_idx, ()); | ||
| 233 | } | ||
| 234 | } | ||
| 235 | |||
| 236 | (graph, node_indices) | ||
| 237 | } | ||
| 238 | |||
| 239 | struct Context { | ||
| 240 | root: PathBuf, | ||
| 241 | crates: BTreeMap<String, Crate>, | ||
| 242 | graph: Graph<String, ()>, | ||
| 243 | indices: HashMap<String, NodeIndex>, | ||
| 244 | } | ||
| 245 | |||
| 246 | fn find_repo_root() -> Result<PathBuf> { | ||
| 247 | let mut path = std::env::current_dir()?.canonicalize()?; | ||
| 248 | |||
| 249 | loop { | ||
| 250 | // Check if this directory contains a .git directory | ||
| 251 | if path.join(".git").exists() { | ||
| 252 | return Ok(path); | ||
| 253 | } | ||
| 254 | |||
| 255 | // Move to parent directory | ||
| 256 | match path.parent() { | ||
| 257 | Some(parent) => path = parent.to_path_buf(), | ||
| 258 | None => break, | ||
| 259 | } | ||
| 260 | } | ||
| 261 | |||
| 262 | Err(anyhow!( | ||
| 263 | "Could not find repository root. Make sure you're running this tool from within the embassy repository." | ||
| 264 | )) | ||
| 265 | } | ||
| 266 | |||
| 267 | fn load_context() -> Result<Context> { | ||
| 268 | let root = find_repo_root()?; | ||
| 269 | let crates = list_crates(&root)?; | ||
| 270 | let (graph, indices) = build_graph(&crates); | ||
| 271 | |||
| 272 | let ctx = Context { | ||
| 273 | root, | ||
| 274 | crates, | ||
| 275 | graph, | ||
| 276 | indices, | ||
| 277 | }; | ||
| 278 | |||
| 279 | // Check for publish dependency conflicts | ||
| 280 | check_publish_dependencies(&ctx)?; | ||
| 281 | |||
| 282 | Ok(ctx) | ||
| 283 | } | ||
| 284 | |||
| 285 | fn main() -> Result<()> { | ||
| 286 | SimpleLogger::new().init().unwrap(); | ||
| 287 | let args = Args::parse(); | ||
| 288 | let mut ctx = load_context()?; | ||
| 289 | |||
| 290 | match args.command { | ||
| 291 | Command::List => { | ||
| 292 | let ordered = petgraph::algo::toposort(&ctx.graph, None).unwrap(); | ||
| 293 | for node in ordered.iter() { | ||
| 294 | let start = ctx.graph.node_weight(*node).unwrap(); | ||
| 295 | let mut bfs = Bfs::new(&ctx.graph, *node); | ||
| 296 | while let Some(node) = bfs.next(&ctx.graph) { | ||
| 297 | let weight = ctx.graph.node_weight(node).unwrap(); | ||
| 298 | let c = ctx.crates.get(weight).unwrap(); | ||
| 299 | if weight == start { | ||
| 300 | println!("+ {}-{}", weight, c.version); | ||
| 301 | } else { | ||
| 302 | println!("|- {}-{}", weight, c.version); | ||
| 303 | } | ||
| 304 | } | ||
| 305 | println!(""); | ||
| 306 | } | ||
| 307 | } | ||
| 308 | Command::Dependencies { crate_name } => { | ||
| 309 | let idx = ctx.indices.get(&crate_name).expect("unable to find crate in tree"); | ||
| 310 | let mut bfs = Bfs::new(&ctx.graph, *idx); | ||
| 311 | while let Some(node) = bfs.next(&ctx.graph) { | ||
| 312 | let weight = ctx.graph.node_weight(node).unwrap(); | ||
| 313 | let crt = ctx.crates.get(weight).unwrap(); | ||
| 314 | if *weight == crate_name { | ||
| 315 | println!("+ {}-{}", weight, crt.version); | ||
| 316 | } else { | ||
| 317 | println!("|- {}-{}", weight, crt.version); | ||
| 318 | } | ||
| 319 | } | ||
| 320 | } | ||
| 321 | Command::Dependents { crate_name } => { | ||
| 322 | let idx = ctx.indices.get(&crate_name).expect("unable to find crate in tree"); | ||
| 323 | let weight = ctx.graph.node_weight(*idx).unwrap(); | ||
| 324 | let crt = ctx.crates.get(weight).unwrap(); | ||
| 325 | println!("+ {}-{}", weight, crt.version); | ||
| 326 | for parent in ctx.graph.neighbors_directed(*idx, Direction::Incoming) { | ||
| 327 | let weight = ctx.graph.node_weight(parent).unwrap(); | ||
| 328 | let crt = ctx.crates.get(weight).unwrap(); | ||
| 329 | println!("|- {}-{}", weight, crt.version); | ||
| 330 | } | ||
| 331 | } | ||
| 332 | Command::Build { crate_name } => { | ||
| 333 | build::build(&ctx, crate_name.as_deref())?; | ||
| 334 | } | ||
| 335 | Command::SemverCheck { crate_name } => { | ||
| 336 | let c = ctx.crates.get(&crate_name).unwrap(); | ||
| 337 | if !c.publish { | ||
| 338 | bail!("Cannot run semver-check on non-publishable crate '{}'", crate_name); | ||
| 339 | } | ||
| 340 | check_semver(&c)?; | ||
| 341 | } | ||
| 342 | Command::PrepareRelease { crate_name } => { | ||
| 343 | let start = ctx.indices.get(&crate_name).expect("unable to find crate in tree"); | ||
| 344 | |||
| 345 | // Check if the target crate is publishable | ||
| 346 | let start_weight = ctx.graph.node_weight(*start).unwrap(); | ||
| 347 | let start_crate = ctx.crates.get(start_weight).unwrap(); | ||
| 348 | if !start_crate.publish { | ||
| 349 | bail!("Cannot prepare release for non-publishable crate '{}'", crate_name); | ||
| 350 | } | ||
| 351 | |||
| 352 | let mut rgraph = ctx.graph.clone(); | ||
| 353 | rgraph.reverse(); | ||
| 354 | |||
| 355 | let mut bfs = Bfs::new(&rgraph, *start); | ||
| 356 | |||
| 357 | while let Some(node) = bfs.next(&rgraph) { | ||
| 358 | let weight = rgraph.node_weight(node).unwrap(); | ||
| 359 | println!("Preparing {}", weight); | ||
| 360 | let mut c = ctx.crates.get_mut(weight).unwrap(); | ||
| 361 | if c.publish { | ||
| 362 | let ver = semver::Version::parse(&c.version)?; | ||
| 363 | let newver = match check_semver(&c)? { | ||
| 364 | ReleaseType::Major | ReleaseType::Minor => semver::Version::new(ver.major, ver.minor + 1, 0), | ||
| 365 | ReleaseType::Patch => semver::Version::new(ver.major, ver.minor, ver.patch + 1), | ||
| 366 | _ => unreachable!(), | ||
| 367 | }; | ||
| 368 | |||
| 369 | println!("Updating {} from {} -> {}", weight, c.version, newver.to_string()); | ||
| 370 | let newver = newver.to_string(); | ||
| 371 | |||
| 372 | update_version(&mut c, &newver)?; | ||
| 373 | let c = ctx.crates.get(weight).unwrap(); | ||
| 374 | |||
| 375 | // Update all nodes further down the tree | ||
| 376 | let mut bfs = Bfs::new(&rgraph, node); | ||
| 377 | while let Some(dep_node) = bfs.next(&rgraph) { | ||
| 378 | let dep_weight = rgraph.node_weight(dep_node).unwrap(); | ||
| 379 | let dep = ctx.crates.get(dep_weight).unwrap(); | ||
| 380 | update_versions(dep, &c.name, &newver)?; | ||
| 381 | } | ||
| 382 | |||
| 383 | // Update changelog | ||
| 384 | update_changelog(&ctx.root, &c)?; | ||
| 385 | } | ||
| 386 | } | ||
| 387 | |||
| 388 | let weight = rgraph.node_weight(*start).unwrap(); | ||
| 389 | let c = ctx.crates.get(weight).unwrap(); | ||
| 390 | publish_release(&ctx.root, &c, false)?; | ||
| 391 | |||
| 392 | println!("# Please inspect changes and run the following commands when happy:"); | ||
| 393 | |||
| 394 | println!("git commit -a -m 'chore: prepare crate releases'"); | ||
| 395 | let mut bfs = Bfs::new(&rgraph, *start); | ||
| 396 | while let Some(node) = bfs.next(&rgraph) { | ||
| 397 | let weight = rgraph.node_weight(node).unwrap(); | ||
| 398 | let c = ctx.crates.get(weight).unwrap(); | ||
| 399 | if c.publish { | ||
| 400 | println!("git tag {}-v{}", weight, c.version); | ||
| 401 | } | ||
| 402 | } | ||
| 403 | |||
| 404 | println!(""); | ||
| 405 | println!("# Run these commands to publish the crate and dependents:"); | ||
| 406 | |||
| 407 | let mut bfs = Bfs::new(&rgraph, *start); | ||
| 408 | while let Some(node) = bfs.next(&rgraph) { | ||
| 409 | let weight = rgraph.node_weight(node).unwrap(); | ||
| 410 | let c = ctx.crates.get(weight).unwrap(); | ||
| 411 | |||
| 412 | let mut args: Vec<String> = vec![ | ||
| 413 | "publish".to_string(), | ||
| 414 | "--manifest-path".to_string(), | ||
| 415 | c.path.join("Cargo.toml").display().to_string(), | ||
| 416 | ]; | ||
| 417 | |||
| 418 | let config = c.configs.first().unwrap(); // TODO | ||
| 419 | if !config.features.is_empty() { | ||
| 420 | args.push("--features".into()); | ||
| 421 | args.push(config.features.join(",")); | ||
| 422 | } | ||
| 423 | |||
| 424 | if let Some(target) = &config.target { | ||
| 425 | args.push("--target".into()); | ||
| 426 | args.push(target.clone()); | ||
| 427 | } | ||
| 428 | |||
| 429 | /* | ||
| 430 | let mut dry_run = args.clone(); | ||
| 431 | dry_run.push("--dry-run".to_string()); | ||
| 432 | |||
| 433 | println!("cargo {}", dry_run.join(" ")); | ||
| 434 | */ | ||
| 435 | if c.publish { | ||
| 436 | println!("cargo {}", args.join(" ")); | ||
| 437 | } | ||
| 438 | } | ||
| 439 | |||
| 440 | println!(""); | ||
| 441 | println!("# Run this command to push changes and tags:"); | ||
| 442 | println!("git push --tags"); | ||
| 443 | } | ||
| 444 | } | ||
| 445 | Ok(()) | ||
| 446 | } | ||
| 447 | |||
| 448 | fn check_semver(c: &Crate) -> Result<ReleaseType> { | ||
| 449 | let min_version = semver_check::minimum_update(c)?; | ||
| 450 | println!("Version should be bumped to {:?}", min_version); | ||
| 451 | Ok(min_version) | ||
| 452 | } | ||
| 453 | |||
| 454 | fn update_changelog(repo: &Path, c: &Crate) -> Result<()> { | ||
| 455 | let args: Vec<String> = vec![ | ||
| 456 | "release".to_string(), | ||
| 457 | "replace".to_string(), | ||
| 458 | "--config".to_string(), | ||
| 459 | repo.join("release").join("release.toml").display().to_string(), | ||
| 460 | "--manifest-path".to_string(), | ||
| 461 | c.path.join("Cargo.toml").display().to_string(), | ||
| 462 | "--execute".to_string(), | ||
| 463 | "--no-confirm".to_string(), | ||
| 464 | ]; | ||
| 465 | |||
| 466 | let status = ProcessCommand::new("cargo").args(&args).output()?; | ||
| 467 | |||
| 468 | println!("{}", core::str::from_utf8(&status.stdout).unwrap()); | ||
| 469 | eprintln!("{}", core::str::from_utf8(&status.stderr).unwrap()); | ||
| 470 | if !status.status.success() { | ||
| 471 | return Err(anyhow!("release replace failed")); | ||
| 472 | } else { | ||
| 473 | Ok(()) | ||
| 474 | } | ||
| 475 | } | ||
| 476 | |||
| 477 | fn publish_release(_repo: &Path, c: &Crate, push: bool) -> Result<()> { | ||
| 478 | let config = c.configs.first().unwrap(); // TODO | ||
| 479 | |||
| 480 | let mut args: Vec<String> = vec![ | ||
| 481 | "publish".to_string(), | ||
| 482 | "--manifest-path".to_string(), | ||
| 483 | c.path.join("Cargo.toml").display().to_string(), | ||
| 484 | ]; | ||
| 485 | |||
| 486 | args.push("--features".into()); | ||
| 487 | args.push(config.features.join(",")); | ||
| 488 | |||
| 489 | if let Some(target) = &config.target { | ||
| 490 | args.push("--target".into()); | ||
| 491 | args.push(target.clone()); | ||
| 492 | } | ||
| 493 | |||
| 494 | if !push { | ||
| 495 | args.push("--dry-run".to_string()); | ||
| 496 | args.push("--allow-dirty".to_string()); | ||
| 497 | args.push("--keep-going".to_string()); | ||
| 498 | } | ||
| 499 | |||
| 500 | let status = ProcessCommand::new("cargo").args(&args).output()?; | ||
| 501 | |||
| 502 | println!("{}", core::str::from_utf8(&status.stdout).unwrap()); | ||
| 503 | eprintln!("{}", core::str::from_utf8(&status.stderr).unwrap()); | ||
| 504 | if !status.status.success() { | ||
| 505 | return Err(anyhow!("publish failed")); | ||
| 506 | } else { | ||
| 507 | Ok(()) | ||
| 508 | } | ||
| 509 | } | ||
| 510 | |||
| 511 | /// Make the path "Windows"-safe | ||
| 512 | pub fn windows_safe_path(path: &Path) -> PathBuf { | ||
| 513 | PathBuf::from(path.to_str().unwrap().to_string().replace("\\\\?\\", "")) | ||
| 514 | } | ||
diff --git a/release/src/semver_check.rs b/release/src/semver_check.rs deleted file mode 100644 index 6255260f3..000000000 --- a/release/src/semver_check.rs +++ /dev/null | |||
| @@ -1,178 +0,0 @@ | |||
| 1 | use std::collections::HashSet; | ||
| 2 | use std::env; | ||
| 3 | use std::path::PathBuf; | ||
| 4 | |||
| 5 | use anyhow::anyhow; | ||
| 6 | use cargo_semver_checks::{Check, GlobalConfig, ReleaseType, Rustdoc}; | ||
| 7 | use flate2::read::GzDecoder; | ||
| 8 | use tar::Archive; | ||
| 9 | |||
| 10 | use crate::cargo::CargoArgsBuilder; | ||
| 11 | use crate::types::{BuildConfig, Crate}; | ||
| 12 | |||
| 13 | /// Return the minimum required bump for the next release. | ||
| 14 | /// Even if nothing changed this will be [ReleaseType::Patch] | ||
| 15 | pub fn minimum_update(krate: &Crate) -> Result<ReleaseType, anyhow::Error> { | ||
| 16 | let config = krate.configs.first().unwrap(); // TODO | ||
| 17 | |||
| 18 | let package_name = krate.name.clone(); | ||
| 19 | let baseline_path = download_baseline(&package_name, &krate.version)?; | ||
| 20 | let mut baseline_krate = krate.clone(); | ||
| 21 | baseline_krate.path = baseline_path; | ||
| 22 | |||
| 23 | // Compare features as it's not covered by semver-checks | ||
| 24 | if compare_features(&baseline_krate, &krate)? { | ||
| 25 | return Ok(ReleaseType::Minor); | ||
| 26 | } | ||
| 27 | let baseline_path = build_doc_json(&baseline_krate, config)?; | ||
| 28 | let current_path = build_doc_json(krate, config)?; | ||
| 29 | |||
| 30 | let baseline = Rustdoc::from_path(&baseline_path); | ||
| 31 | let doc = Rustdoc::from_path(¤t_path); | ||
| 32 | let mut semver_check = Check::new(doc); | ||
| 33 | semver_check.with_default_features(); | ||
| 34 | semver_check.set_baseline(baseline); | ||
| 35 | semver_check.set_packages(vec![package_name]); | ||
| 36 | let extra_current_features = config.features.clone(); | ||
| 37 | let extra_baseline_features = config.features.clone(); | ||
| 38 | semver_check.set_extra_features(extra_current_features, extra_baseline_features); | ||
| 39 | if let Some(target) = &config.target { | ||
| 40 | semver_check.set_build_target(target.clone()); | ||
| 41 | } | ||
| 42 | let mut cfg = GlobalConfig::new(); | ||
| 43 | cfg.set_log_level(Some(log::Level::Info)); | ||
| 44 | |||
| 45 | let result = semver_check.check_release(&mut cfg)?; | ||
| 46 | |||
| 47 | let mut min_required_update = ReleaseType::Patch; | ||
| 48 | for (_, report) in result.crate_reports() { | ||
| 49 | if let Some(required_bump) = report.required_bump() { | ||
| 50 | let required_is_stricter = | ||
| 51 | (min_required_update == ReleaseType::Patch) || (required_bump == ReleaseType::Major); | ||
| 52 | if required_is_stricter { | ||
| 53 | min_required_update = required_bump; | ||
| 54 | } | ||
| 55 | } | ||
| 56 | } | ||
| 57 | |||
| 58 | Ok(min_required_update) | ||
| 59 | } | ||
| 60 | |||
| 61 | fn compare_features(old: &Crate, new: &Crate) -> Result<bool, anyhow::Error> { | ||
| 62 | let mut old = read_features(&old.path)?; | ||
| 63 | let new = read_features(&new.path)?; | ||
| 64 | |||
| 65 | old.retain(|r| !new.contains(r)); | ||
| 66 | log::info!("Features removed in new: {:?}", old); | ||
| 67 | Ok(!old.is_empty()) | ||
| 68 | } | ||
| 69 | |||
| 70 | fn download_baseline(name: &str, version: &str) -> Result<PathBuf, anyhow::Error> { | ||
| 71 | let config = crates_index::IndexConfig { | ||
| 72 | dl: "https://crates.io/api/v1/crates".to_string(), | ||
| 73 | api: Some("https://crates.io".to_string()), | ||
| 74 | }; | ||
| 75 | |||
| 76 | let url = | ||
| 77 | config | ||
| 78 | .download_url(name, version) | ||
| 79 | .ok_or(anyhow!("unable to download baseline for {}-{}", name, version))?; | ||
| 80 | |||
| 81 | let parent_dir = env::var("RELEASER_CACHE").map_err(|_| anyhow!("RELEASER_CACHE not set"))?; | ||
| 82 | |||
| 83 | let extract_path = PathBuf::from(&parent_dir).join(format!("{}-{}", name, version)); | ||
| 84 | |||
| 85 | if extract_path.exists() { | ||
| 86 | return Ok(extract_path); | ||
| 87 | } | ||
| 88 | |||
| 89 | let response = reqwest::blocking::get(url)?; | ||
| 90 | let bytes = response.bytes()?; | ||
| 91 | |||
| 92 | let decoder = GzDecoder::new(&bytes[..]); | ||
| 93 | let mut archive = Archive::new(decoder); | ||
| 94 | archive.unpack(&parent_dir)?; | ||
| 95 | |||
| 96 | Ok(extract_path) | ||
| 97 | } | ||
| 98 | |||
| 99 | fn read_features(crate_path: &PathBuf) -> Result<HashSet<String>, anyhow::Error> { | ||
| 100 | let cargo_toml_path = crate_path.join("Cargo.toml"); | ||
| 101 | |||
| 102 | if !cargo_toml_path.exists() { | ||
| 103 | return Err(anyhow!("Cargo.toml not found at {:?}", cargo_toml_path)); | ||
| 104 | } | ||
| 105 | |||
| 106 | let manifest = cargo_manifest::Manifest::from_path(&cargo_toml_path)?; | ||
| 107 | |||
| 108 | let mut set = HashSet::new(); | ||
| 109 | if let Some(features) = manifest.features { | ||
| 110 | for f in features.keys() { | ||
| 111 | set.insert(f.clone()); | ||
| 112 | } | ||
| 113 | } | ||
| 114 | if let Some(deps) = manifest.dependencies { | ||
| 115 | for (k, v) in deps.iter() { | ||
| 116 | if v.optional() { | ||
| 117 | set.insert(k.clone()); | ||
| 118 | } | ||
| 119 | } | ||
| 120 | } | ||
| 121 | |||
| 122 | Ok(set) | ||
| 123 | } | ||
| 124 | |||
| 125 | fn build_doc_json(krate: &Crate, config: &BuildConfig) -> Result<PathBuf, anyhow::Error> { | ||
| 126 | let target_dir = std::env::var("CARGO_TARGET_DIR"); | ||
| 127 | |||
| 128 | let target_path = if let Ok(target) = target_dir { | ||
| 129 | PathBuf::from(target) | ||
| 130 | } else { | ||
| 131 | PathBuf::from(&krate.path).join("target") | ||
| 132 | }; | ||
| 133 | |||
| 134 | let current_path = target_path; | ||
| 135 | let current_path = if let Some(target) = &config.target { | ||
| 136 | current_path.join(target.clone()) | ||
| 137 | } else { | ||
| 138 | current_path | ||
| 139 | }; | ||
| 140 | let current_path = current_path | ||
| 141 | .join("doc") | ||
| 142 | .join(format!("{}.json", krate.name.to_string().replace("-", "_"))); | ||
| 143 | |||
| 144 | std::fs::remove_file(¤t_path).ok(); | ||
| 145 | let features = config.features.clone(); | ||
| 146 | |||
| 147 | log::info!("Building doc json for {} with features: {:?}", krate.name, features); | ||
| 148 | |||
| 149 | let envs = vec![( | ||
| 150 | "RUSTDOCFLAGS", | ||
| 151 | "--cfg docsrs --cfg not_really_docsrs --cfg semver_checks", | ||
| 152 | )]; | ||
| 153 | |||
| 154 | // always use `specific nightly` toolchain so we don't have to deal with potentially | ||
| 155 | // different versions of the doc-json | ||
| 156 | let cargo_builder = CargoArgsBuilder::default() | ||
| 157 | .toolchain("nightly-2025-06-29") | ||
| 158 | .subcommand("rustdoc") | ||
| 159 | .features(&features); | ||
| 160 | let cargo_builder = if let Some(target) = &config.target { | ||
| 161 | cargo_builder.target(target.clone()) | ||
| 162 | } else { | ||
| 163 | cargo_builder | ||
| 164 | }; | ||
| 165 | |||
| 166 | let cargo_builder = cargo_builder | ||
| 167 | .arg("-Zunstable-options") | ||
| 168 | .arg("-Zhost-config") | ||
| 169 | .arg("-Ztarget-applies-to-host") | ||
| 170 | .arg("--lib") | ||
| 171 | .arg("--output-format=json") | ||
| 172 | .arg("-Zbuild-std=alloc,core") | ||
| 173 | .arg("--config=host.rustflags=[\"--cfg=instability_disable_unstable_docs\"]"); | ||
| 174 | let cargo_args = cargo_builder.build(); | ||
| 175 | log::debug!("{cargo_args:#?}"); | ||
| 176 | crate::cargo::run_with_env(&cargo_args, &krate.path, envs, false)?; | ||
| 177 | Ok(current_path) | ||
| 178 | } | ||
diff --git a/release/src/types.rs b/release/src/types.rs deleted file mode 100644 index be0a883f1..000000000 --- a/release/src/types.rs +++ /dev/null | |||
| @@ -1,60 +0,0 @@ | |||
| 1 | use std::collections::BTreeMap; | ||
| 2 | use std::path::PathBuf; | ||
| 3 | |||
| 4 | use serde::Deserialize; | ||
| 5 | |||
| 6 | #[derive(Debug, Deserialize)] | ||
| 7 | pub struct ParsedCrate { | ||
| 8 | pub package: ParsedPackage, | ||
| 9 | pub dependencies: BTreeMap<String, toml::Value>, | ||
| 10 | } | ||
| 11 | |||
| 12 | #[derive(Debug, Deserialize)] | ||
| 13 | pub struct ParsedPackage { | ||
| 14 | pub name: String, | ||
| 15 | pub version: String, | ||
| 16 | #[serde(default = "default_publish")] | ||
| 17 | pub publish: bool, | ||
| 18 | #[serde(default)] | ||
| 19 | pub metadata: Metadata, | ||
| 20 | } | ||
| 21 | |||
| 22 | fn default_publish() -> bool { | ||
| 23 | true | ||
| 24 | } | ||
| 25 | |||
| 26 | #[derive(Debug, Deserialize, Default)] | ||
| 27 | pub struct Metadata { | ||
| 28 | #[serde(default)] | ||
| 29 | pub embassy: MetadataEmbassy, | ||
| 30 | } | ||
| 31 | |||
| 32 | #[allow(dead_code)] | ||
| 33 | #[derive(Debug, Deserialize, Default)] | ||
| 34 | pub struct MetadataEmbassy { | ||
| 35 | #[serde(default)] | ||
| 36 | pub skip: bool, | ||
| 37 | #[serde(default)] | ||
| 38 | pub build: Vec<BuildConfig>, | ||
| 39 | } | ||
| 40 | |||
| 41 | #[derive(Debug, Clone, Deserialize)] | ||
| 42 | pub struct BuildConfig { | ||
| 43 | #[serde(default)] | ||
| 44 | pub features: Vec<String>, | ||
| 45 | pub target: Option<String>, | ||
| 46 | #[serde(rename = "artifact-dir")] | ||
| 47 | pub artifact_dir: Option<String>, | ||
| 48 | } | ||
| 49 | |||
| 50 | pub type CrateId = String; | ||
| 51 | |||
| 52 | #[derive(Debug, Clone)] | ||
| 53 | pub struct Crate { | ||
| 54 | pub name: String, | ||
| 55 | pub version: String, | ||
| 56 | pub path: PathBuf, | ||
| 57 | pub dependencies: Vec<CrateId>, | ||
| 58 | pub configs: Vec<BuildConfig>, | ||
| 59 | pub publish: bool, | ||
| 60 | } | ||
