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 @@ -[package] -name = "embassy-release" -version = "0.1.0" -edition = "2021" - -[dependencies] -clap = { version = "4.5.1", features = ["derive"] } -walkdir = "2.5.0" -toml = "0.9.5" -toml_edit = { version = "0.23.1", features = ["serde"] } -serde = { version = "1.0.198", features = ["derive"] } -regex = "1.10.4" -anyhow = "1" -petgraph = "0.8.2" -semver = "1.0.26" -cargo-semver-checks = "0.43.0" -log = "0.4" -simple_logger = "5.0.0" -temp-file = "0.1.9" -flate2 = "1.1.1" -crates-index = "3.11.0" -tar = "0.4" -reqwest = { version = "0.12", features = ["blocking"] } -cargo-manifest = "0.19.1" - -[package.metadata.embassy] -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 @@ -use anyhow::Result; - -use crate::cargo::{CargoArgsBuilder, CargoBatchBuilder}; - -pub(crate) fn build(ctx: &crate::Context, crate_name: Option<&str>) -> Result<()> { - let mut batch_builder = CargoBatchBuilder::new(); - - // Process either specific crate or all crates - let crates_to_build: Vec<_> = if let Some(name) = crate_name { - // Build only the specified crate - if let Some(krate) = ctx.crates.get(name) { - vec![krate] - } else { - return Err(anyhow::anyhow!("Crate '{}' not found", name)); - } - } else { - // Build all crates - ctx.crates.values().collect() - }; - - // Process selected crates and add their build configurations to the batch - for krate in crates_to_build { - for config in &krate.configs { - let mut args_builder = CargoArgsBuilder::new() - .subcommand("build") - .arg("--release") - .arg(format!("--manifest-path={}/Cargo.toml", krate.path.to_string_lossy())); - - if let Some(ref target) = config.target { - args_builder = args_builder.target(target); - } - - if !config.features.is_empty() { - args_builder = args_builder.features(&config.features); - } - - if let Some(ref artifact_dir) = config.artifact_dir { - args_builder = args_builder.artifact_dir(artifact_dir); - } - - batch_builder.add_command(args_builder.build()); - } - } - - // Execute the cargo batch command - let batch_args = batch_builder.build(); - crate::cargo::run(&batch_args, &ctx.root)?; - - Ok(()) -} 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 @@ -//! Tools for working with Cargo. - -use std::ffi::OsStr; -use std::path::{Path, PathBuf}; -use std::process::{Command, Stdio}; - -use anyhow::{bail, Result}; -use serde::{Deserialize, Serialize}; - -use crate::windows_safe_path; - -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -pub struct Artifact { - pub executable: PathBuf, -} - -/// Execute cargo with the given arguments and from the specified directory. -pub fn run(args: &[String], cwd: &Path) -> Result<()> { - run_with_env::<[(&str, &str); 0], _, _>(args, cwd, [], false)?; - Ok(()) -} - -/// Execute cargo with the given arguments and from the specified directory. -pub fn run_with_env(args: &[String], cwd: &Path, envs: I, capture: bool) -> Result -where - I: IntoIterator + core::fmt::Debug, - K: AsRef, - V: AsRef, -{ - if !cwd.is_dir() { - bail!("The `cwd` argument MUST be a directory"); - } - - // Make sure to not use a UNC as CWD! - // That would make `OUT_DIR` a UNC which will trigger things like the one fixed in https://github.com/dtolnay/rustversion/pull/51 - // While it's fixed in `rustversion` it's not fixed for other crates we are - // using now or in future! - let cwd = windows_safe_path(cwd); - - println!( - "Running `cargo {}` in {:?} - Environment {:?}", - args.join(" "), - cwd, - envs - ); - - let mut command = Command::new(get_cargo()); - - command - .args(args) - .current_dir(cwd) - .envs(envs) - .stdout(if capture { Stdio::piped() } else { Stdio::inherit() }) - .stderr(if capture { Stdio::piped() } else { Stdio::inherit() }); - - if args.iter().any(|a| a.starts_with('+')) { - // Make sure the right cargo runs - command.env_remove("CARGO"); - } - - let output = command.stdin(Stdio::inherit()).output()?; - - // Make sure that we return an appropriate exit code here, as Github Actions - // requires this in order to function correctly: - if output.status.success() { - Ok(String::from_utf8_lossy(&output.stdout).to_string()) - } else { - bail!("Failed to execute cargo subcommand `cargo {}`", args.join(" "),) - } -} - -fn get_cargo() -> String { - // On Windows when executed via `cargo run` (e.g. via the xtask alias) the - // `cargo` on the search path is NOT the cargo-wrapper but the `cargo` from the - // toolchain - that one doesn't understand `+toolchain` - #[cfg(target_os = "windows")] - let cargo = if let Ok(cargo) = std::env::var("CARGO_HOME") { - format!("{cargo}/bin/cargo") - } else { - String::from("cargo") - }; - - #[cfg(not(target_os = "windows"))] - let cargo = String::from("cargo"); - - cargo -} - -#[derive(Debug, Default)] -pub struct CargoArgsBuilder { - toolchain: Option, - subcommand: String, - target: Option, - features: Vec, - args: Vec, -} - -impl CargoArgsBuilder { - #[must_use] - pub fn new() -> Self { - Self { - toolchain: None, - subcommand: String::new(), - target: None, - features: vec![], - args: vec![], - } - } - - #[must_use] - pub fn toolchain(mut self, toolchain: S) -> Self - where - S: Into, - { - self.toolchain = Some(toolchain.into()); - self - } - - #[must_use] - pub fn subcommand(mut self, subcommand: S) -> Self - where - S: Into, - { - self.subcommand = subcommand.into(); - self - } - - #[must_use] - pub fn target(mut self, target: S) -> Self - where - S: Into, - { - self.target = Some(target.into()); - self - } - - #[must_use] - pub fn features(mut self, features: &[String]) -> Self { - self.features = features.to_vec(); - self - } - - #[must_use] - pub fn artifact_dir(mut self, artifact_dir: S) -> Self - where - S: Into, - { - self.args.push(format!("--artifact-dir={}", artifact_dir.into())); - self - } - - #[must_use] - pub fn arg(mut self, arg: S) -> Self - where - S: Into, - { - self.args.push(arg.into()); - self - } - - #[must_use] - pub fn build(&self) -> Vec { - let mut args = vec![]; - - if let Some(ref toolchain) = self.toolchain { - args.push(format!("+{toolchain}")); - } - - args.push(self.subcommand.clone()); - - if let Some(ref target) = self.target { - args.push(format!("--target={target}")); - } - - if !self.features.is_empty() { - args.push(format!("--features={}", self.features.join(","))); - } - - for arg in self.args.iter() { - args.push(arg.clone()); - } - - args - } -} - -#[derive(Debug, Default)] -pub struct CargoBatchBuilder { - commands: Vec>, -} - -impl CargoBatchBuilder { - #[must_use] - pub fn new() -> Self { - Self { commands: vec![] } - } - - #[must_use] - pub fn command(mut self, args: Vec) -> Self { - self.commands.push(args); - self - } - - pub fn add_command(&mut self, args: Vec) -> &mut Self { - self.commands.push(args); - self - } - - #[must_use] - pub fn build(&self) -> Vec { - let mut args = vec!["batch".to_string()]; - - for command in &self.commands { - args.push("---".to_string()); - args.extend(command.clone()); - } - - args - } -} 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 @@ -use std::collections::{BTreeMap, HashMap}; -use std::fs; -use std::path::{Path, PathBuf}; -use std::process::Command as ProcessCommand; - -use anyhow::{anyhow, bail, Result}; -use cargo_semver_checks::ReleaseType; -use clap::{Parser, Subcommand}; -use petgraph::graph::{Graph, NodeIndex}; -use petgraph::visit::Bfs; -use petgraph::{Directed, Direction}; -use simple_logger::SimpleLogger; -use toml_edit::{DocumentMut, Item, Value}; -use types::*; - -fn check_publish_dependencies(ctx: &Context) -> Result<()> { - for krate in ctx.crates.values() { - if krate.publish { - for dep_name in &krate.dependencies { - if let Some(dep_crate) = ctx.crates.get(dep_name) { - if !dep_crate.publish { - return Err(anyhow!( - "Publishable crate '{}' depends on non-publishable crate '{}'. This is not allowed.", - krate.name, - dep_name - )); - } - } - } - } - } - Ok(()) -} - -mod build; -mod cargo; -mod semver_check; -mod types; - -/// Tool to traverse and operate on intra-repo Rust crate dependencies -#[derive(Parser, Debug)] -#[command(author, version, about)] -struct Args { - /// Command to perform on each crate - #[command(subcommand)] - command: Command, -} - -#[derive(Debug, Subcommand)] -enum Command { - /// All crates and their direct dependencies - List, - /// List all dependencies for a crate - Dependencies { - /// Crate name to print dependencies for. - #[arg(value_name = "CRATE")] - crate_name: String, - }, - /// List all dependencies for a crate - Dependents { - /// Crate name to print dependencies for. - #[arg(value_name = "CRATE")] - crate_name: String, - }, - - /// Build - Build { - /// Crate to check. If not specified checks all crates. - #[arg(value_name = "CRATE")] - crate_name: Option, - }, - /// SemverCheck - SemverCheck { - /// Specific crate name to check - #[arg(value_name = "CRATE")] - crate_name: String, - }, - /// Prepare to release a crate and all dependents that needs updating - /// - Semver checks - /// - Bump versions and commit - /// - Create tag. - PrepareRelease { - /// Crate to release. Will traverse that crate an it's dependents. If not specified checks all crates. - #[arg(value_name = "CRATE")] - crate_name: String, - }, -} - -fn update_version(c: &mut Crate, new_version: &str) -> Result<()> { - let path = c.path.join("Cargo.toml"); - c.version = new_version.to_string(); - let content = fs::read_to_string(&path)?; - let mut doc: DocumentMut = content.parse()?; - for section in ["package"] { - if let Some(Item::Table(dep_table)) = doc.get_mut(section) { - dep_table.insert("version", Item::Value(Value::from(new_version))); - } - } - fs::write(&path, doc.to_string())?; - Ok(()) -} - -fn update_versions(to_update: &Crate, dep: &CrateId, new_version: &str) -> Result<()> { - let path = to_update.path.join("Cargo.toml"); - let content = fs::read_to_string(&path)?; - let mut doc: DocumentMut = content.parse()?; - let mut changed = false; - for section in ["dependencies", "dev-dependencies", "build-dependencies"] { - if let Some(Item::Table(dep_table)) = doc.get_mut(section) { - if let Some(item) = dep_table.get_mut(&dep) { - match item { - // e.g., foo = "0.1.0" - Item::Value(Value::String(_)) => { - *item = Item::Value(Value::from(new_version)); - changed = true; - } - // e.g., foo = { version = "...", ... } - Item::Value(Value::InlineTable(inline)) => { - if inline.contains_key("version") { - inline["version"] = Value::from(new_version); - changed = true; - } - } - _ => {} // Leave unusual formats untouched - } - } - } - } - - if changed { - fs::write(&path, doc.to_string())?; - println!("🔧 Updated {} to {} in {}", dep, new_version, path.display()); - } - Ok(()) -} - -fn list_crates(root: &PathBuf) -> Result> { - let mut crates = BTreeMap::new(); - discover_crates(root, &mut crates)?; - Ok(crates) -} - -fn discover_crates(dir: &PathBuf, crates: &mut BTreeMap) -> Result<()> { - let d = std::fs::read_dir(dir)?; - for c in d { - let entry = c?; - if entry.file_type()?.is_dir() { - let path = dir.join(entry.path()); - let cargo_toml = path.join("Cargo.toml"); - - if cargo_toml.exists() { - let content = fs::read_to_string(&cargo_toml)?; - - // Try to parse as a crate, skip if it's a workspace - let parsed: Result = toml::from_str(&content); - if let Ok(parsed) = parsed { - let id = parsed.package.name; - - let metadata = &parsed.package.metadata.embassy; - - if metadata.skip { - continue; - } - - let mut dependencies = Vec::new(); - for (k, _) in parsed.dependencies { - if k.starts_with("embassy-") { - dependencies.push(k); - } - } - - let mut configs = metadata.build.clone(); - if configs.is_empty() { - configs.push(BuildConfig { - features: vec![], - target: None, - artifact_dir: None, - }) - } - - crates.insert( - id.clone(), - Crate { - name: id, - version: parsed.package.version, - path, - dependencies, - configs, - publish: parsed.package.publish, - }, - ); - } - } else { - // Recursively search subdirectories, but only for examples, tests, and docs - let file_name = entry.file_name(); - let dir_name = file_name.to_string_lossy(); - if dir_name == "examples" || dir_name == "tests" || dir_name == "docs" { - discover_crates(&path, crates)?; - } - } - } - } - Ok(()) -} - -fn build_graph(crates: &BTreeMap) -> (Graph, HashMap) { - let mut graph = Graph::::new(); - let mut node_indices: HashMap = HashMap::new(); - - // Helper to insert or get existing node - let get_or_insert_node = |id: CrateId, graph: &mut Graph, map: &mut HashMap| { - if let Some(&idx) = map.get(&id) { - idx - } else { - let idx = graph.add_node(id.clone()); - map.insert(id, idx); - idx - } - }; - - for krate in crates.values() { - get_or_insert_node(krate.name.clone(), &mut graph, &mut node_indices); - } - - for krate in crates.values() { - // Insert crate node if not exists - let crate_idx = get_or_insert_node(krate.name.clone(), &mut graph, &mut node_indices); - - // Insert dependencies and connect edges - for dep in krate.dependencies.iter() { - let dep_idx = get_or_insert_node(dep.clone(), &mut graph, &mut node_indices); - graph.add_edge(crate_idx, dep_idx, ()); - } - } - - (graph, node_indices) -} - -struct Context { - root: PathBuf, - crates: BTreeMap, - graph: Graph, - indices: HashMap, -} - -fn find_repo_root() -> Result { - let mut path = std::env::current_dir()?.canonicalize()?; - - loop { - // Check if this directory contains a .git directory - if path.join(".git").exists() { - return Ok(path); - } - - // Move to parent directory - match path.parent() { - Some(parent) => path = parent.to_path_buf(), - None => break, - } - } - - Err(anyhow!( - "Could not find repository root. Make sure you're running this tool from within the embassy repository." - )) -} - -fn load_context() -> Result { - let root = find_repo_root()?; - let crates = list_crates(&root)?; - let (graph, indices) = build_graph(&crates); - - let ctx = Context { - root, - crates, - graph, - indices, - }; - - // Check for publish dependency conflicts - check_publish_dependencies(&ctx)?; - - Ok(ctx) -} - -fn main() -> Result<()> { - SimpleLogger::new().init().unwrap(); - let args = Args::parse(); - let mut ctx = load_context()?; - - match args.command { - Command::List => { - let ordered = petgraph::algo::toposort(&ctx.graph, None).unwrap(); - for node in ordered.iter() { - let start = ctx.graph.node_weight(*node).unwrap(); - let mut bfs = Bfs::new(&ctx.graph, *node); - while let Some(node) = bfs.next(&ctx.graph) { - let weight = ctx.graph.node_weight(node).unwrap(); - let c = ctx.crates.get(weight).unwrap(); - if weight == start { - println!("+ {}-{}", weight, c.version); - } else { - println!("|- {}-{}", weight, c.version); - } - } - println!(""); - } - } - Command::Dependencies { crate_name } => { - let idx = ctx.indices.get(&crate_name).expect("unable to find crate in tree"); - let mut bfs = Bfs::new(&ctx.graph, *idx); - while let Some(node) = bfs.next(&ctx.graph) { - let weight = ctx.graph.node_weight(node).unwrap(); - let crt = ctx.crates.get(weight).unwrap(); - if *weight == crate_name { - println!("+ {}-{}", weight, crt.version); - } else { - println!("|- {}-{}", weight, crt.version); - } - } - } - Command::Dependents { crate_name } => { - let idx = ctx.indices.get(&crate_name).expect("unable to find crate in tree"); - let weight = ctx.graph.node_weight(*idx).unwrap(); - let crt = ctx.crates.get(weight).unwrap(); - println!("+ {}-{}", weight, crt.version); - for parent in ctx.graph.neighbors_directed(*idx, Direction::Incoming) { - let weight = ctx.graph.node_weight(parent).unwrap(); - let crt = ctx.crates.get(weight).unwrap(); - println!("|- {}-{}", weight, crt.version); - } - } - Command::Build { crate_name } => { - build::build(&ctx, crate_name.as_deref())?; - } - Command::SemverCheck { crate_name } => { - let c = ctx.crates.get(&crate_name).unwrap(); - if !c.publish { - bail!("Cannot run semver-check on non-publishable crate '{}'", crate_name); - } - check_semver(&c)?; - } - Command::PrepareRelease { crate_name } => { - let start = ctx.indices.get(&crate_name).expect("unable to find crate in tree"); - - // Check if the target crate is publishable - let start_weight = ctx.graph.node_weight(*start).unwrap(); - let start_crate = ctx.crates.get(start_weight).unwrap(); - if !start_crate.publish { - bail!("Cannot prepare release for non-publishable crate '{}'", crate_name); - } - - let mut rgraph = ctx.graph.clone(); - rgraph.reverse(); - - let mut bfs = Bfs::new(&rgraph, *start); - - while let Some(node) = bfs.next(&rgraph) { - let weight = rgraph.node_weight(node).unwrap(); - println!("Preparing {}", weight); - let mut c = ctx.crates.get_mut(weight).unwrap(); - if c.publish { - let ver = semver::Version::parse(&c.version)?; - let newver = match check_semver(&c)? { - ReleaseType::Major | ReleaseType::Minor => semver::Version::new(ver.major, ver.minor + 1, 0), - ReleaseType::Patch => semver::Version::new(ver.major, ver.minor, ver.patch + 1), - _ => unreachable!(), - }; - - println!("Updating {} from {} -> {}", weight, c.version, newver.to_string()); - let newver = newver.to_string(); - - update_version(&mut c, &newver)?; - let c = ctx.crates.get(weight).unwrap(); - - // Update all nodes further down the tree - let mut bfs = Bfs::new(&rgraph, node); - while let Some(dep_node) = bfs.next(&rgraph) { - let dep_weight = rgraph.node_weight(dep_node).unwrap(); - let dep = ctx.crates.get(dep_weight).unwrap(); - update_versions(dep, &c.name, &newver)?; - } - - // Update changelog - update_changelog(&ctx.root, &c)?; - } - } - - let weight = rgraph.node_weight(*start).unwrap(); - let c = ctx.crates.get(weight).unwrap(); - publish_release(&ctx.root, &c, false)?; - - println!("# Please inspect changes and run the following commands when happy:"); - - println!("git commit -a -m 'chore: prepare crate releases'"); - let mut bfs = Bfs::new(&rgraph, *start); - while let Some(node) = bfs.next(&rgraph) { - let weight = rgraph.node_weight(node).unwrap(); - let c = ctx.crates.get(weight).unwrap(); - if c.publish { - println!("git tag {}-v{}", weight, c.version); - } - } - - println!(""); - println!("# Run these commands to publish the crate and dependents:"); - - let mut bfs = Bfs::new(&rgraph, *start); - while let Some(node) = bfs.next(&rgraph) { - let weight = rgraph.node_weight(node).unwrap(); - let c = ctx.crates.get(weight).unwrap(); - - let mut args: Vec = vec![ - "publish".to_string(), - "--manifest-path".to_string(), - c.path.join("Cargo.toml").display().to_string(), - ]; - - let config = c.configs.first().unwrap(); // TODO - if !config.features.is_empty() { - args.push("--features".into()); - args.push(config.features.join(",")); - } - - if let Some(target) = &config.target { - args.push("--target".into()); - args.push(target.clone()); - } - - /* - let mut dry_run = args.clone(); - dry_run.push("--dry-run".to_string()); - - println!("cargo {}", dry_run.join(" ")); - */ - if c.publish { - println!("cargo {}", args.join(" ")); - } - } - - println!(""); - println!("# Run this command to push changes and tags:"); - println!("git push --tags"); - } - } - Ok(()) -} - -fn check_semver(c: &Crate) -> Result { - let min_version = semver_check::minimum_update(c)?; - println!("Version should be bumped to {:?}", min_version); - Ok(min_version) -} - -fn update_changelog(repo: &Path, c: &Crate) -> Result<()> { - let args: Vec = vec![ - "release".to_string(), - "replace".to_string(), - "--config".to_string(), - repo.join("release").join("release.toml").display().to_string(), - "--manifest-path".to_string(), - c.path.join("Cargo.toml").display().to_string(), - "--execute".to_string(), - "--no-confirm".to_string(), - ]; - - let status = ProcessCommand::new("cargo").args(&args).output()?; - - println!("{}", core::str::from_utf8(&status.stdout).unwrap()); - eprintln!("{}", core::str::from_utf8(&status.stderr).unwrap()); - if !status.status.success() { - return Err(anyhow!("release replace failed")); - } else { - Ok(()) - } -} - -fn publish_release(_repo: &Path, c: &Crate, push: bool) -> Result<()> { - let config = c.configs.first().unwrap(); // TODO - - let mut args: Vec = vec![ - "publish".to_string(), - "--manifest-path".to_string(), - c.path.join("Cargo.toml").display().to_string(), - ]; - - args.push("--features".into()); - args.push(config.features.join(",")); - - if let Some(target) = &config.target { - args.push("--target".into()); - args.push(target.clone()); - } - - if !push { - args.push("--dry-run".to_string()); - args.push("--allow-dirty".to_string()); - args.push("--keep-going".to_string()); - } - - let status = ProcessCommand::new("cargo").args(&args).output()?; - - println!("{}", core::str::from_utf8(&status.stdout).unwrap()); - eprintln!("{}", core::str::from_utf8(&status.stderr).unwrap()); - if !status.status.success() { - return Err(anyhow!("publish failed")); - } else { - Ok(()) - } -} - -/// Make the path "Windows"-safe -pub fn windows_safe_path(path: &Path) -> PathBuf { - PathBuf::from(path.to_str().unwrap().to_string().replace("\\\\?\\", "")) -} 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 @@ -use std::collections::HashSet; -use std::env; -use std::path::PathBuf; - -use anyhow::anyhow; -use cargo_semver_checks::{Check, GlobalConfig, ReleaseType, Rustdoc}; -use flate2::read::GzDecoder; -use tar::Archive; - -use crate::cargo::CargoArgsBuilder; -use crate::types::{BuildConfig, Crate}; - -/// Return the minimum required bump for the next release. -/// Even if nothing changed this will be [ReleaseType::Patch] -pub fn minimum_update(krate: &Crate) -> Result { - let config = krate.configs.first().unwrap(); // TODO - - let package_name = krate.name.clone(); - let baseline_path = download_baseline(&package_name, &krate.version)?; - let mut baseline_krate = krate.clone(); - baseline_krate.path = baseline_path; - - // Compare features as it's not covered by semver-checks - if compare_features(&baseline_krate, &krate)? { - return Ok(ReleaseType::Minor); - } - let baseline_path = build_doc_json(&baseline_krate, config)?; - let current_path = build_doc_json(krate, config)?; - - let baseline = Rustdoc::from_path(&baseline_path); - let doc = Rustdoc::from_path(¤t_path); - let mut semver_check = Check::new(doc); - semver_check.with_default_features(); - semver_check.set_baseline(baseline); - semver_check.set_packages(vec![package_name]); - let extra_current_features = config.features.clone(); - let extra_baseline_features = config.features.clone(); - semver_check.set_extra_features(extra_current_features, extra_baseline_features); - if let Some(target) = &config.target { - semver_check.set_build_target(target.clone()); - } - let mut cfg = GlobalConfig::new(); - cfg.set_log_level(Some(log::Level::Info)); - - let result = semver_check.check_release(&mut cfg)?; - - let mut min_required_update = ReleaseType::Patch; - for (_, report) in result.crate_reports() { - if let Some(required_bump) = report.required_bump() { - let required_is_stricter = - (min_required_update == ReleaseType::Patch) || (required_bump == ReleaseType::Major); - if required_is_stricter { - min_required_update = required_bump; - } - } - } - - Ok(min_required_update) -} - -fn compare_features(old: &Crate, new: &Crate) -> Result { - let mut old = read_features(&old.path)?; - let new = read_features(&new.path)?; - - old.retain(|r| !new.contains(r)); - log::info!("Features removed in new: {:?}", old); - Ok(!old.is_empty()) -} - -fn download_baseline(name: &str, version: &str) -> Result { - let config = crates_index::IndexConfig { - dl: "https://crates.io/api/v1/crates".to_string(), - api: Some("https://crates.io".to_string()), - }; - - let url = - config - .download_url(name, version) - .ok_or(anyhow!("unable to download baseline for {}-{}", name, version))?; - - let parent_dir = env::var("RELEASER_CACHE").map_err(|_| anyhow!("RELEASER_CACHE not set"))?; - - let extract_path = PathBuf::from(&parent_dir).join(format!("{}-{}", name, version)); - - if extract_path.exists() { - return Ok(extract_path); - } - - let response = reqwest::blocking::get(url)?; - let bytes = response.bytes()?; - - let decoder = GzDecoder::new(&bytes[..]); - let mut archive = Archive::new(decoder); - archive.unpack(&parent_dir)?; - - Ok(extract_path) -} - -fn read_features(crate_path: &PathBuf) -> Result, anyhow::Error> { - let cargo_toml_path = crate_path.join("Cargo.toml"); - - if !cargo_toml_path.exists() { - return Err(anyhow!("Cargo.toml not found at {:?}", cargo_toml_path)); - } - - let manifest = cargo_manifest::Manifest::from_path(&cargo_toml_path)?; - - let mut set = HashSet::new(); - if let Some(features) = manifest.features { - for f in features.keys() { - set.insert(f.clone()); - } - } - if let Some(deps) = manifest.dependencies { - for (k, v) in deps.iter() { - if v.optional() { - set.insert(k.clone()); - } - } - } - - Ok(set) -} - -fn build_doc_json(krate: &Crate, config: &BuildConfig) -> Result { - let target_dir = std::env::var("CARGO_TARGET_DIR"); - - let target_path = if let Ok(target) = target_dir { - PathBuf::from(target) - } else { - PathBuf::from(&krate.path).join("target") - }; - - let current_path = target_path; - let current_path = if let Some(target) = &config.target { - current_path.join(target.clone()) - } else { - current_path - }; - let current_path = current_path - .join("doc") - .join(format!("{}.json", krate.name.to_string().replace("-", "_"))); - - std::fs::remove_file(¤t_path).ok(); - let features = config.features.clone(); - - log::info!("Building doc json for {} with features: {:?}", krate.name, features); - - let envs = vec![( - "RUSTDOCFLAGS", - "--cfg docsrs --cfg not_really_docsrs --cfg semver_checks", - )]; - - // always use `specific nightly` toolchain so we don't have to deal with potentially - // different versions of the doc-json - let cargo_builder = CargoArgsBuilder::default() - .toolchain("nightly-2025-06-29") - .subcommand("rustdoc") - .features(&features); - let cargo_builder = if let Some(target) = &config.target { - cargo_builder.target(target.clone()) - } else { - cargo_builder - }; - - let cargo_builder = cargo_builder - .arg("-Zunstable-options") - .arg("-Zhost-config") - .arg("-Ztarget-applies-to-host") - .arg("--lib") - .arg("--output-format=json") - .arg("-Zbuild-std=alloc,core") - .arg("--config=host.rustflags=[\"--cfg=instability_disable_unstable_docs\"]"); - let cargo_args = cargo_builder.build(); - log::debug!("{cargo_args:#?}"); - crate::cargo::run_with_env(&cargo_args, &krate.path, envs, false)?; - Ok(current_path) -} 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 @@ -use std::collections::BTreeMap; -use std::path::PathBuf; - -use serde::Deserialize; - -#[derive(Debug, Deserialize)] -pub struct ParsedCrate { - pub package: ParsedPackage, - pub dependencies: BTreeMap, -} - -#[derive(Debug, Deserialize)] -pub struct ParsedPackage { - pub name: String, - pub version: String, - #[serde(default = "default_publish")] - pub publish: bool, - #[serde(default)] - pub metadata: Metadata, -} - -fn default_publish() -> bool { - true -} - -#[derive(Debug, Deserialize, Default)] -pub struct Metadata { - #[serde(default)] - pub embassy: MetadataEmbassy, -} - -#[allow(dead_code)] -#[derive(Debug, Deserialize, Default)] -pub struct MetadataEmbassy { - #[serde(default)] - pub skip: bool, - #[serde(default)] - pub build: Vec, -} - -#[derive(Debug, Clone, Deserialize)] -pub struct BuildConfig { - #[serde(default)] - pub features: Vec, - pub target: Option, - #[serde(rename = "artifact-dir")] - pub artifact_dir: Option, -} - -pub type CrateId = String; - -#[derive(Debug, Clone)] -pub struct Crate { - pub name: String, - pub version: String, - pub path: PathBuf, - pub dependencies: Vec, - pub configs: Vec, - pub publish: bool, -}