mirror of
https://github.com/embassy-rs/embassy.git
synced 2025-09-26 20:00:27 +00:00
feat: add semver checks and releasing to releaser
* List dependencies of a crate * List dependents of a crate * Perform semver-checks of a crate * Prepare a release for a crate and all dependents * Use a single release.toml for cargo-release * Add changelogs where missing
This commit is contained in:
parent
ac60eaeddd
commit
6a347f1f09
@ -1,5 +0,0 @@
|
|||||||
pre-release-replacements = [
|
|
||||||
{file="CHANGELOG.md", search="Unreleased", replace="{{version}}", min=1},
|
|
||||||
{file="CHANGELOG.md", search="ReleaseDate", replace="{{date}}", min=1},
|
|
||||||
{file="CHANGELOG.md", search="<!-- next-header -->", replace="<!-- next-header -->\n## Unreleased - ReleaseDate\n", exactly=1},
|
|
||||||
]
|
|
11
embassy-boot/CHANGELOG.md
Normal file
11
embassy-boot/CHANGELOG.md
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
# Changelog
|
||||||
|
|
||||||
|
All notable changes to this project will be documented in this file.
|
||||||
|
|
||||||
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||||
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
|
<!-- next-header -->
|
||||||
|
## Unreleased - ReleaseDate
|
||||||
|
|
||||||
|
- First release with changelog.
|
@ -1,5 +0,0 @@
|
|||||||
pre-release-replacements = [
|
|
||||||
{file="CHANGELOG.md", search="Unreleased", replace="{{version}}", min=1},
|
|
||||||
{file="CHANGELOG.md", search="ReleaseDate", replace="{{date}}", min=1},
|
|
||||||
{file="CHANGELOG.md", search="<!-- next-header -->", replace="<!-- next-header -->\n## Unreleased - ReleaseDate\n", exactly=1},
|
|
||||||
]
|
|
@ -1,5 +0,0 @@
|
|||||||
pre-release-replacements = [
|
|
||||||
{file="CHANGELOG.md", search="Unreleased", replace="{{version}}", min=1},
|
|
||||||
{file="CHANGELOG.md", search="ReleaseDate", replace="{{date}}", min=1},
|
|
||||||
{file="CHANGELOG.md", search="<!-- next-header -->", replace="<!-- next-header -->\n## Unreleased - ReleaseDate\n", exactly=1},
|
|
||||||
]
|
|
@ -1,5 +0,0 @@
|
|||||||
pre-release-replacements = [
|
|
||||||
{file="CHANGELOG.md", search="Unreleased", replace="{{version}}", min=1},
|
|
||||||
{file="CHANGELOG.md", search="ReleaseDate", replace="{{date}}", min=1},
|
|
||||||
{file="CHANGELOG.md", search="<!-- next-header -->", replace="<!-- next-header -->\n## Unreleased - ReleaseDate\n", exactly=1},
|
|
||||||
]
|
|
11
embassy-net-adin1110/CHANGELOG.md
Normal file
11
embassy-net-adin1110/CHANGELOG.md
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
# Changelog
|
||||||
|
|
||||||
|
All notable changes to this project will be documented in this file.
|
||||||
|
|
||||||
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||||
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
|
<!-- next-header -->
|
||||||
|
## Unreleased - ReleaseDate
|
||||||
|
|
||||||
|
- First release with changelog.
|
@ -1,5 +0,0 @@
|
|||||||
pre-release-replacements = [
|
|
||||||
{file="CHANGELOG.md", search="Unreleased", replace="{{version}}", min=1},
|
|
||||||
{file="CHANGELOG.md", search="ReleaseDate", replace="{{date}}", min=1},
|
|
||||||
{file="CHANGELOG.md", search="<!-- next-header -->", replace="<!-- next-header -->\n## Unreleased - ReleaseDate\n", exactly=1},
|
|
||||||
]
|
|
@ -5,6 +5,9 @@ All notable changes to this project will be documented in this file.
|
|||||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
|
<!-- next-header -->
|
||||||
|
## Unreleased - ReleaseDate
|
||||||
|
|
||||||
## 0.2.0 - 2023-10-18
|
## 0.2.0 - 2023-10-18
|
||||||
|
|
||||||
- Added support for IEEE 802.15.4 mediums.
|
- Added support for IEEE 802.15.4 mediums.
|
||||||
|
11
embassy-net-esp-hosted/CHANGELOG.md
Normal file
11
embassy-net-esp-hosted/CHANGELOG.md
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
# Changelog
|
||||||
|
|
||||||
|
All notable changes to this project will be documented in this file.
|
||||||
|
|
||||||
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||||
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
|
<!-- next-header -->
|
||||||
|
## Unreleased - ReleaseDate
|
||||||
|
|
||||||
|
- First release with changelog.
|
13
embassy-net-nrf91/CHANGELOG.md
Normal file
13
embassy-net-nrf91/CHANGELOG.md
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
# Changelog
|
||||||
|
|
||||||
|
All notable changes to this project will be documented in this file.
|
||||||
|
|
||||||
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||||
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
|
<!-- next-header -->
|
||||||
|
## Unreleased - ReleaseDate
|
||||||
|
|
||||||
|
## 0.1.1 - 2025-08-14
|
||||||
|
|
||||||
|
- First release with changelog.
|
@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "embassy-net-nrf91"
|
name = "embassy-net-nrf91"
|
||||||
version = "0.1.0"
|
version = "0.1.1"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
description = "embassy-net driver for Nordic nRF91-series cellular modems"
|
description = "embassy-net driver for Nordic nRF91-series cellular modems"
|
||||||
keywords = ["embedded", "nrf91", "embassy-net", "cellular"]
|
keywords = ["embedded", "nrf91", "embassy-net", "cellular"]
|
||||||
@ -36,4 +36,4 @@ target = "thumbv7em-none-eabi"
|
|||||||
features = ["defmt", "nrf-pac/nrf9160"]
|
features = ["defmt", "nrf-pac/nrf9160"]
|
||||||
|
|
||||||
[package.metadata.docs.rs]
|
[package.metadata.docs.rs]
|
||||||
features = ["defmt"]
|
features = ["defmt", "nrf-pac/nrf9160"]
|
||||||
|
@ -5,6 +5,9 @@ All notable changes to this project will be documented in this file.
|
|||||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
|
<!-- next-header -->
|
||||||
|
## Unreleased - ReleaseDate
|
||||||
|
|
||||||
## 0.2.0 - 2025-01-12
|
## 0.2.0 - 2025-01-12
|
||||||
|
|
||||||
- Update `ppproto` to v0.2.
|
- Update `ppproto` to v0.2.
|
||||||
|
13
embassy-net-wiznet/CHANGELOG.md
Normal file
13
embassy-net-wiznet/CHANGELOG.md
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
# Changelog
|
||||||
|
|
||||||
|
All notable changes to this project will be documented in this file.
|
||||||
|
|
||||||
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||||
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
|
<!-- next-header -->
|
||||||
|
## Unreleased - ReleaseDate
|
||||||
|
|
||||||
|
## 0.1.1 - 2025-08-14
|
||||||
|
|
||||||
|
- First release with changelog.
|
@ -5,7 +5,8 @@ All notable changes to this project will be documented in this file.
|
|||||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
## Unreleased
|
<!-- next-header -->
|
||||||
|
## Unreleased - ReleaseDate
|
||||||
|
|
||||||
No unreleased changes yet... Quick, go send a PR!
|
No unreleased changes yet... Quick, go send a PR!
|
||||||
|
|
||||||
|
@ -1,5 +0,0 @@
|
|||||||
pre-release-replacements = [
|
|
||||||
{file="CHANGELOG.md", search="Unreleased", replace="{{version}}", min=1},
|
|
||||||
{file="CHANGELOG.md", search="ReleaseDate", replace="{{date}}", min=1},
|
|
||||||
{file="CHANGELOG.md", search="<!-- next-header -->", replace="<!-- next-header -->\n## Unreleased - ReleaseDate\n", exactly=1},
|
|
||||||
]
|
|
@ -1,303 +0,0 @@
|
|||||||
use std::collections::{HashMap, HashSet};
|
|
||||||
use std::fs;
|
|
||||||
use std::path::{Path, PathBuf};
|
|
||||||
use std::process::Command as ProcessCommand;
|
|
||||||
|
|
||||||
use clap::{Parser, Subcommand, ValueEnum};
|
|
||||||
use regex::Regex;
|
|
||||||
use serde::Deserialize;
|
|
||||||
use toml_edit::{DocumentMut, Item, Value};
|
|
||||||
use walkdir::WalkDir;
|
|
||||||
|
|
||||||
/// Tool to traverse and operate on intra-repo Rust crate dependencies
|
|
||||||
#[derive(Parser, Debug)]
|
|
||||||
#[command(author, version, about)]
|
|
||||||
struct Args {
|
|
||||||
/// Path to the root crate
|
|
||||||
#[arg(value_name = "CRATE_PATH")]
|
|
||||||
crate_path: PathBuf,
|
|
||||||
|
|
||||||
/// Command to perform on each crate
|
|
||||||
#[command(subcommand)]
|
|
||||||
command: Command,
|
|
||||||
|
|
||||||
/// Traversal order
|
|
||||||
#[arg(short, long, default_value = "post")]
|
|
||||||
order: TraversalOrder,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, ValueEnum, PartialEq)]
|
|
||||||
enum TraversalOrder {
|
|
||||||
Pre,
|
|
||||||
Post,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Subcommand)]
|
|
||||||
enum Command {
|
|
||||||
/// Print all dependencies
|
|
||||||
Dependencies,
|
|
||||||
|
|
||||||
/// Release crate
|
|
||||||
Release {
|
|
||||||
#[command(subcommand)]
|
|
||||||
kind: ReleaseKind,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Subcommand, Clone, Copy, PartialEq)]
|
|
||||||
enum ReleaseKind {
|
|
||||||
Patch,
|
|
||||||
Minor,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
|
||||||
struct CargoToml {
|
|
||||||
package: Option<Package>,
|
|
||||||
dependencies: Option<Deps>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
|
||||||
struct Package {
|
|
||||||
name: String,
|
|
||||||
version: Option<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
|
||||||
#[serde(untagged)]
|
|
||||||
enum Dep {
|
|
||||||
Version(String),
|
|
||||||
DetailedTable(HashMap<String, toml::Value>),
|
|
||||||
}
|
|
||||||
|
|
||||||
type Deps = std::collections::HashMap<String, Dep>;
|
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
|
||||||
struct CrateConfig {
|
|
||||||
features: Option<Vec<String>>,
|
|
||||||
target: Option<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
type ReleaseConfig = HashMap<String, CrateConfig>;
|
|
||||||
|
|
||||||
fn find_path_deps(cargo_path: &Path) -> Vec<PathBuf> {
|
|
||||||
let content = fs::read_to_string(cargo_path).unwrap_or_else(|_| {
|
|
||||||
panic!("Failed to read {:?}", cargo_path);
|
|
||||||
});
|
|
||||||
let parsed: CargoToml = toml::from_str(&content).unwrap_or_else(|e| {
|
|
||||||
panic!("Failed to parse {:?}: {}", cargo_path, e);
|
|
||||||
});
|
|
||||||
|
|
||||||
let mut paths = vec![];
|
|
||||||
if let Some(deps) = parsed.dependencies {
|
|
||||||
for (_name, dep) in deps {
|
|
||||||
match dep {
|
|
||||||
Dep::Version(_) => {
|
|
||||||
// External dependency — skip
|
|
||||||
}
|
|
||||||
Dep::DetailedTable(table) => {
|
|
||||||
if let Some(toml::Value::String(path)) = table.get("path") {
|
|
||||||
let dep_path = cargo_path.parent().unwrap().join(path).canonicalize().unwrap();
|
|
||||||
paths.push(dep_path);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
paths
|
|
||||||
}
|
|
||||||
|
|
||||||
fn visit_recursive(
|
|
||||||
root_crate: &Path,
|
|
||||||
visited: &mut HashSet<PathBuf>,
|
|
||||||
output: &mut Vec<PathBuf>,
|
|
||||||
order: &TraversalOrder,
|
|
||||||
) {
|
|
||||||
if !visited.insert(root_crate.to_path_buf()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let cargo_toml = root_crate.join("Cargo.toml");
|
|
||||||
let deps = find_path_deps(&cargo_toml);
|
|
||||||
|
|
||||||
if *order == TraversalOrder::Pre {
|
|
||||||
output.push(root_crate.to_path_buf());
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut deps_sorted = deps;
|
|
||||||
deps_sorted.sort();
|
|
||||||
for dep in deps_sorted {
|
|
||||||
visit_recursive(&dep, visited, output, order);
|
|
||||||
}
|
|
||||||
|
|
||||||
if *order == TraversalOrder::Post {
|
|
||||||
output.push(root_crate.to_path_buf());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_crate_metadata(crate_path: &Path) -> Option<(String, String)> {
|
|
||||||
let cargo_toml = crate_path.join("Cargo.toml");
|
|
||||||
let content = fs::read_to_string(&cargo_toml).ok()?;
|
|
||||||
let parsed: CargoToml = toml::from_str(&content).ok()?;
|
|
||||||
let pkg = parsed.package?;
|
|
||||||
let name = pkg.name;
|
|
||||||
let version = pkg.version?;
|
|
||||||
Some((name, version))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn load_release_config() -> ReleaseConfig {
|
|
||||||
let config_path = PathBuf::from("release/config.toml");
|
|
||||||
if !config_path.exists() {
|
|
||||||
return HashMap::new();
|
|
||||||
}
|
|
||||||
let content = fs::read_to_string(&config_path).expect("Failed to read release/config.toml");
|
|
||||||
toml::from_str(&content).expect("Invalid TOML format in release/config.toml")
|
|
||||||
}
|
|
||||||
|
|
||||||
fn bump_dependency_versions(crate_name: &str, new_version: &str) -> Result<(), String> {
|
|
||||||
let mut cargo_files: Vec<PathBuf> = WalkDir::new(".")
|
|
||||||
.into_iter()
|
|
||||||
.filter_map(Result::ok)
|
|
||||||
.filter(|e| e.file_name() == "Cargo.toml")
|
|
||||||
.map(|e| e.into_path())
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
cargo_files.sort();
|
|
||||||
|
|
||||||
for path in cargo_files {
|
|
||||||
let content = fs::read_to_string(&path).map_err(|e| format!("Failed to read {}: {}", path.display(), e))?;
|
|
||||||
|
|
||||||
let mut doc: DocumentMut = content
|
|
||||||
.parse()
|
|
||||||
.map_err(|e| format!("Failed to parse TOML in {}: {}", path.display(), e))?;
|
|
||||||
|
|
||||||
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(crate_name) {
|
|
||||||
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()).map_err(|e| format!("Failed to write {}: {}", path.display(), e))?;
|
|
||||||
println!("🔧 Updated {} to {} in {}", crate_name, new_version, path.display());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn run_release_command(
|
|
||||||
crate_path: &Path,
|
|
||||||
crate_name: &str,
|
|
||||||
version: &str,
|
|
||||||
kind: &ReleaseKind,
|
|
||||||
config: Option<&CrateConfig>,
|
|
||||||
) -> Result<(), String> {
|
|
||||||
let kind_str = match kind {
|
|
||||||
ReleaseKind::Patch => "patch",
|
|
||||||
ReleaseKind::Minor => "minor",
|
|
||||||
};
|
|
||||||
|
|
||||||
if *kind == ReleaseKind::Minor {
|
|
||||||
bump_dependency_versions(crate_name, version)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut args: Vec<String> = vec!["release".into(), kind_str.into()];
|
|
||||||
|
|
||||||
if let Some(cfg) = config {
|
|
||||||
if let Some(features) = &cfg.features {
|
|
||||||
args.push("--features".into());
|
|
||||||
args.push(features.join(","));
|
|
||||||
}
|
|
||||||
if let Some(target) = &cfg.target {
|
|
||||||
args.push("--target".into());
|
|
||||||
args.push(target.clone());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let status = ProcessCommand::new("cargo")
|
|
||||||
.args(&args)
|
|
||||||
.current_dir(crate_path)
|
|
||||||
.status()
|
|
||||||
.map_err(|e| format!("Failed to run cargo release: {}", e))?;
|
|
||||||
|
|
||||||
if !status.success() {
|
|
||||||
return Err(format!("`cargo release {}` failed in crate {}", kind_str, crate_name));
|
|
||||||
}
|
|
||||||
|
|
||||||
//args.push("--execute".into());
|
|
||||||
//let status = ProcessCommand::new("cargo")
|
|
||||||
// .args(&args)
|
|
||||||
// .current_dir(crate_path)
|
|
||||||
// .status()
|
|
||||||
// .map_err(|e| format!("Failed to run cargo release --execute: {}", e))?;
|
|
||||||
|
|
||||||
//if !status.success() {
|
|
||||||
// return Err(format!(
|
|
||||||
// "`cargo release {kind_str} --execute` failed in crate {crate_name}"
|
|
||||||
// ));
|
|
||||||
//}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
let args = Args::parse();
|
|
||||||
let root = args.crate_path.canonicalize().expect("Invalid root crate path");
|
|
||||||
|
|
||||||
match args.command {
|
|
||||||
Command::Dependencies => {
|
|
||||||
let mut visited = HashSet::new();
|
|
||||||
let mut ordered = vec![];
|
|
||||||
visit_recursive(&root, &mut visited, &mut ordered, &args.order);
|
|
||||||
for path in ordered {
|
|
||||||
if let Some((name, _)) = get_crate_metadata(&path) {
|
|
||||||
println!("{name}");
|
|
||||||
} else {
|
|
||||||
eprintln!("Warning: could not read crate name from {:?}", path);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Command::Release { kind } => {
|
|
||||||
let config = load_release_config();
|
|
||||||
let path = root;
|
|
||||||
match get_crate_metadata(&path) {
|
|
||||||
Some((name, version)) => {
|
|
||||||
println!("🚀 Releasing {name}...");
|
|
||||||
let crate_cfg = config.get(&name);
|
|
||||||
match run_release_command(&path, &name, &version, &kind, crate_cfg) {
|
|
||||||
Ok(_) => {
|
|
||||||
println!("✅ Released {name}");
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
eprintln!("❌ Error releasing {name}:\n{e}");
|
|
||||||
eprintln!("\nYou may retry with: `cargo run -- {path:?} release {kind:?}`");
|
|
||||||
std::process::exit(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
None => {
|
|
||||||
eprintln!("Warning: Could not parse crate metadata in {:?}", path);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,5 +0,0 @@
|
|||||||
pre-release-replacements = [
|
|
||||||
{file="CHANGELOG.md", search="Unreleased", replace="{{version}}", min=1},
|
|
||||||
{file="CHANGELOG.md", search="ReleaseDate", replace="{{date}}", min=1},
|
|
||||||
{file="CHANGELOG.md", search="<!-- next-header -->", replace="<!-- next-header -->\n## Unreleased - ReleaseDate\n", exactly=1},
|
|
||||||
]
|
|
@ -1,5 +0,0 @@
|
|||||||
pre-release-replacements = [
|
|
||||||
{file="CHANGELOG.md", search="Unreleased", replace="{{version}}", min=1},
|
|
||||||
{file="CHANGELOG.md", search="ReleaseDate", replace="{{date}}", min=1},
|
|
||||||
{file="CHANGELOG.md", search="<!-- next-header -->", replace="<!-- next-header -->\n## Unreleased - ReleaseDate\n", exactly=1},
|
|
||||||
]
|
|
@ -5,7 +5,8 @@ All notable changes to this project will be documented in this file.
|
|||||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
## Unreleased
|
<!-- next-header -->
|
||||||
|
## Unreleased - ReleaseDate
|
||||||
|
|
||||||
- Add `get_mut` to `LazyLock`
|
- Add `get_mut` to `LazyLock`
|
||||||
- Add more `Debug` impls to `embassy-sync`, particularly on `OnceLock`
|
- Add more `Debug` impls to `embassy-sync`, particularly on `OnceLock`
|
||||||
|
@ -1,5 +0,0 @@
|
|||||||
pre-release-replacements = [
|
|
||||||
{file="CHANGELOG.md", search="Unreleased", replace="{{version}}", min=1},
|
|
||||||
{file="CHANGELOG.md", search="ReleaseDate", replace="{{date}}", min=1},
|
|
||||||
{file="CHANGELOG.md", search="<!-- next-header -->", replace="<!-- next-header -->\n## Unreleased - ReleaseDate\n", exactly=1},
|
|
||||||
]
|
|
@ -5,7 +5,8 @@ All notable changes to this project will be documented in this file.
|
|||||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
## Unreleased
|
<!-- next-header -->
|
||||||
|
## Unreleased - ReleaseDate
|
||||||
|
|
||||||
- Removed the embassy-executor dependency
|
- Removed the embassy-executor dependency
|
||||||
|
|
||||||
|
@ -1,5 +0,0 @@
|
|||||||
pre-release-replacements = [
|
|
||||||
{file="CHANGELOG.md", search="Unreleased", replace="{{version}}", min=1},
|
|
||||||
{file="CHANGELOG.md", search="ReleaseDate", replace="{{date}}", min=1},
|
|
||||||
{file="CHANGELOG.md", search="<!-- next-header -->", replace="<!-- next-header -->\n## Unreleased - ReleaseDate\n", exactly=1},
|
|
||||||
]
|
|
11
embassy-usb-dfu/CHANGELOG.md
Normal file
11
embassy-usb-dfu/CHANGELOG.md
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
# Changelog
|
||||||
|
|
||||||
|
All notable changes to this project will be documented in this file.
|
||||||
|
|
||||||
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||||
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
|
<!-- next-header -->
|
||||||
|
## Unreleased - ReleaseDate
|
||||||
|
|
||||||
|
- First release with changelog.
|
@ -1,5 +0,0 @@
|
|||||||
pre-release-replacements = [
|
|
||||||
{file="CHANGELOG.md", search="Unreleased", replace="{{version}}", min=1},
|
|
||||||
{file="CHANGELOG.md", search="ReleaseDate", replace="{{date}}", min=1},
|
|
||||||
{file="CHANGELOG.md", search="<!-- next-header -->", replace="<!-- next-header -->\n## Unreleased - ReleaseDate\n", exactly=1},
|
|
||||||
]
|
|
@ -5,7 +5,8 @@ All notable changes to this project will be documented in this file.
|
|||||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
## Unreleased
|
<!-- next-header -->
|
||||||
|
## Unreleased - ReleaseDate
|
||||||
|
|
||||||
## 0.5.0 - 2025-07-22
|
## 0.5.0 - 2025-07-22
|
||||||
|
|
||||||
|
@ -5,7 +5,8 @@ All notable changes to this project will be documented in this file.
|
|||||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
## Unreleased
|
<!-- next-header -->
|
||||||
|
## Unreleased - ReleaseDate
|
||||||
|
|
||||||
## 0.3.0 - 2025-07-22
|
## 0.3.0 - 2025-07-22
|
||||||
|
|
||||||
|
@ -1,5 +0,0 @@
|
|||||||
pre-release-replacements = [
|
|
||||||
{file="CHANGELOG.md", search="Unreleased", replace="{{version}}", min=1},
|
|
||||||
{file="CHANGELOG.md", search="ReleaseDate", replace="{{date}}", min=1},
|
|
||||||
{file="CHANGELOG.md", search="<!-- next-header -->", replace="<!-- next-header -->\n## Unreleased - ReleaseDate\n", exactly=1},
|
|
||||||
]
|
|
@ -10,3 +10,6 @@ toml = "0.8.8"
|
|||||||
toml_edit = { version = "0.23.1", features = ["serde"] }
|
toml_edit = { version = "0.23.1", features = ["serde"] }
|
||||||
serde = { version = "1.0.198", features = ["derive"] }
|
serde = { version = "1.0.198", features = ["derive"] }
|
||||||
regex = "1.10.4"
|
regex = "1.10.4"
|
||||||
|
anyhow = "1"
|
||||||
|
petgraph = "0.8.2"
|
||||||
|
semver = "1.0.26"
|
@ -1 +1,46 @@
|
|||||||
|
|
||||||
|
embassy-stm32 = { features = ["defmt", "unstable-pac", "exti", "time-driver-any", "time", "stm32h755zi-cm7", "dual-bank"], target = "thumbv7em-none-eabi" }
|
||||||
|
embassy-nrf = { features = ["nrf52840", "time", "defmt", "unstable-pac", "gpiote", "time-driver-rtc1"], target = "thumbv7em-none-eabihf" }
|
||||||
|
|
||||||
embassy-rp = { features = ["defmt", "unstable-pac", "time-driver", "rp2040"], target = "thumbv6m-none-eabi" }
|
embassy-rp = { features = ["defmt", "unstable-pac", "time-driver", "rp2040"], target = "thumbv6m-none-eabi" }
|
||||||
|
cyw43 = { features = ["defmt", "firmware-logs"], target = "thumbv6m-none-eabi" }
|
||||||
|
#cyw43-pio = { features = ["defmt", "embassy-rp/rp2040"], target = "thumbv6m-none-eabi" }
|
||||||
|
|
||||||
|
embassy-boot = { features = ["defmt"] }
|
||||||
|
#embassy-boot-nrf = { features = ["defmt", "embassy-nrf/nrf52840"], target = "thumbv7em-none-eabihf" }
|
||||||
|
#embassy-boot-rp = { features = ["defmt", "embassy-rp/rp2040"], target = "thumbv6m-none-eabi" }
|
||||||
|
#embassy-boot-stm32 = { features = ["defmt", "embassy-stm32/stm32f429zi"], target = "thumbv7em-none-eabi" }
|
||||||
|
|
||||||
|
embassy-time = { features = ["defmt", "std"] }
|
||||||
|
embassy-time-driver = { }
|
||||||
|
embassy-time-queue-utils = { features = ["defmt"] }
|
||||||
|
|
||||||
|
embassy-futures = { }
|
||||||
|
embassy-embedded-hal = { features = ["time"] }
|
||||||
|
embassy-hal-internal = { }
|
||||||
|
embassy-executor = { features = ["defmt", "arch-cortex-m", "executor-thread", "executor-interrupt"], target = "thumbv7em-none-eabi" }
|
||||||
|
embassy-executor-macros = { }
|
||||||
|
embassy-sync = { }
|
||||||
|
|
||||||
|
embassy-net = { features = ["defmt", "tcp", "udp", "raw", "dns", "icmp", "dhcpv4", "proto-ipv6", "medium-ethernet", "medium-ip", "medium-ieee802154", "multicast", "dhcpv4-hostname"] }
|
||||||
|
embassy-net-ppp = { }
|
||||||
|
embassy-net-esp-hosted = {}
|
||||||
|
embassy-net-driver-channel = {}
|
||||||
|
embassy-net-wiznet = {}
|
||||||
|
embassy-net-nrf91 = { features = ["defmt", "nrf9160"] }
|
||||||
|
embassy-net-driver = {}
|
||||||
|
embassy-net-tuntap = {}
|
||||||
|
embassy-net-adin1110 = {}
|
||||||
|
embassy-net-enc28j60 = {}
|
||||||
|
|
||||||
|
embassy-usb-driver = { }
|
||||||
|
embassy-usb-dfu = { features = ["dfu"] }
|
||||||
|
embassy-usb-synopsys-otg = { }
|
||||||
|
embassy-usb = { features = ["defmt", "usbd-hid"] }
|
||||||
|
embassy-usb-logger = { }
|
||||||
|
|
||||||
|
# Unreleased
|
||||||
|
# embassy-stm32-wpan = {}
|
||||||
|
# embassy-imxrt = {}
|
||||||
|
# embassy-nxp = {}
|
||||||
|
# embassy-mspm0 = {}
|
||||||
|
519
release/src/main.rs
Normal file
519
release/src/main.rs
Normal file
@ -0,0 +1,519 @@
|
|||||||
|
use std::collections::{BTreeMap, HashMap};
|
||||||
|
use std::fs;
|
||||||
|
use std::path::{Path, PathBuf};
|
||||||
|
use std::process::Command as ProcessCommand;
|
||||||
|
|
||||||
|
use anyhow::{anyhow, Result};
|
||||||
|
use clap::{Parser, Subcommand};
|
||||||
|
use petgraph::graph::{Graph, NodeIndex};
|
||||||
|
use petgraph::visit::Bfs;
|
||||||
|
use petgraph::{Directed, Direction};
|
||||||
|
use serde::Deserialize;
|
||||||
|
use toml_edit::{DocumentMut, Item, Value};
|
||||||
|
|
||||||
|
/// Tool to traverse and operate on intra-repo Rust crate dependencies
|
||||||
|
#[derive(Parser, Debug)]
|
||||||
|
#[command(author, version, about)]
|
||||||
|
struct Args {
|
||||||
|
/// Path to embassy repository
|
||||||
|
#[arg(short, long)]
|
||||||
|
repo: PathBuf,
|
||||||
|
|
||||||
|
/// 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,
|
||||||
|
},
|
||||||
|
|
||||||
|
/// SemverCheck
|
||||||
|
SemverCheck {
|
||||||
|
/// Crate to check. Will traverse that crate an it's dependents. If not specified checks all crates.
|
||||||
|
#[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,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Subcommand, Clone, Copy, PartialEq)]
|
||||||
|
enum ReleaseKind {
|
||||||
|
Patch,
|
||||||
|
Minor,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
struct CargoToml {
|
||||||
|
package: Option<Package>,
|
||||||
|
dependencies: Option<Deps>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
struct Package {
|
||||||
|
name: String,
|
||||||
|
version: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
#[serde(untagged)]
|
||||||
|
enum Dep {
|
||||||
|
Version(String),
|
||||||
|
DetailedTable(BTreeMap<String, toml::Value>),
|
||||||
|
}
|
||||||
|
|
||||||
|
type Deps = std::collections::BTreeMap<String, Dep>;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Deserialize)]
|
||||||
|
struct CrateConfig {
|
||||||
|
features: Option<Vec<String>>,
|
||||||
|
target: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
type ReleaseConfig = HashMap<String, CrateConfig>;
|
||||||
|
|
||||||
|
fn load_release_config(repo: &Path) -> ReleaseConfig {
|
||||||
|
let config_path = repo.join("release/config.toml");
|
||||||
|
if !config_path.exists() {
|
||||||
|
return HashMap::new();
|
||||||
|
}
|
||||||
|
let content = fs::read_to_string(&config_path).expect("Failed to read release/config.toml");
|
||||||
|
toml::from_str(&content).expect("Invalid TOML format in release/config.toml")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_version(c: &mut Crate, new_version: &str) -> Result<()> {
|
||||||
|
let path = &c.path;
|
||||||
|
c.id.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;
|
||||||
|
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.name) {
|
||||||
|
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.name, new_version, path.display());
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
struct Crate {
|
||||||
|
id: CrateId,
|
||||||
|
path: PathBuf,
|
||||||
|
config: CrateConfig,
|
||||||
|
dependencies: Vec<CrateId>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialOrd, Ord)]
|
||||||
|
struct CrateId {
|
||||||
|
name: String,
|
||||||
|
version: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PartialEq for CrateId {
|
||||||
|
fn eq(&self, other: &CrateId) -> bool {
|
||||||
|
self.name == other.name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Eq for CrateId {}
|
||||||
|
impl std::hash::Hash for CrateId {
|
||||||
|
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
||||||
|
self.name.hash(state)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn list_crates(path: &PathBuf) -> Result<BTreeMap<CrateId, Crate>> {
|
||||||
|
let d = std::fs::read_dir(path)?;
|
||||||
|
let release_config = load_release_config(path);
|
||||||
|
let mut crates = BTreeMap::new();
|
||||||
|
for c in d {
|
||||||
|
let entry = c?;
|
||||||
|
let name = entry.file_name().to_str().unwrap().to_string();
|
||||||
|
if entry.file_type()?.is_dir() && name.starts_with("embassy-") {
|
||||||
|
let entry = entry.path().join("Cargo.toml");
|
||||||
|
if entry.exists() {
|
||||||
|
let content = fs::read_to_string(&entry).unwrap_or_else(|_| {
|
||||||
|
panic!("Failed to read {:?}", entry);
|
||||||
|
});
|
||||||
|
let parsed: CargoToml = toml::from_str(&content).unwrap_or_else(|e| {
|
||||||
|
panic!("Failed to parse {:?}: {}", entry, e);
|
||||||
|
});
|
||||||
|
let p = parsed.package.unwrap();
|
||||||
|
let id = CrateId {
|
||||||
|
name: p.name.clone(),
|
||||||
|
version: p.version.unwrap(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut dependencies = Vec::new();
|
||||||
|
if let Some(deps) = parsed.dependencies {
|
||||||
|
for (k, v) in deps {
|
||||||
|
if k.starts_with("embassy-") {
|
||||||
|
dependencies.push(CrateId {
|
||||||
|
name: k,
|
||||||
|
version: match v {
|
||||||
|
Dep::Version(v) => v,
|
||||||
|
Dep::DetailedTable(table) => {
|
||||||
|
table.get("version").unwrap().as_str().unwrap().to_string()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let path = path.join(entry);
|
||||||
|
if let Some(config) = release_config.get(&p.name) {
|
||||||
|
crates.insert(
|
||||||
|
id.clone(),
|
||||||
|
Crate {
|
||||||
|
id,
|
||||||
|
path,
|
||||||
|
dependencies,
|
||||||
|
config: config.clone(),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(crates)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn build_graph(crates: &BTreeMap<CrateId, Crate>) -> (Graph<CrateId, ()>, HashMap<CrateId, NodeIndex>) {
|
||||||
|
let mut graph = Graph::<CrateId, (), Directed>::new();
|
||||||
|
let mut node_indices: HashMap<CrateId, NodeIndex> = HashMap::new();
|
||||||
|
|
||||||
|
// Helper to insert or get existing node
|
||||||
|
let get_or_insert_node = |id: CrateId, graph: &mut Graph<CrateId, ()>, map: &mut HashMap<CrateId, NodeIndex>| {
|
||||||
|
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.id.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.id.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)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() -> Result<()> {
|
||||||
|
let args = Args::parse();
|
||||||
|
|
||||||
|
let root = args.repo.canonicalize()?; //.expect("Invalid root crate path");
|
||||||
|
let mut crates = list_crates(&root)?;
|
||||||
|
//println!("Crates: {:?}", crates);
|
||||||
|
let (mut graph, indices) = build_graph(&crates);
|
||||||
|
|
||||||
|
// use petgraph::dot::{Config, Dot};
|
||||||
|
// println!("{:?}", Dot::with_config(&graph, &[Config::EdgeNoLabel]));
|
||||||
|
|
||||||
|
match args.command {
|
||||||
|
Command::List => {
|
||||||
|
let ordered = petgraph::algo::toposort(&graph, None).unwrap();
|
||||||
|
for node in ordered.iter() {
|
||||||
|
if graph.neighbors_directed(*node, Direction::Incoming).count() == 0 {
|
||||||
|
let start = graph.node_weight(*node).unwrap();
|
||||||
|
let mut bfs = Bfs::new(&graph, *node);
|
||||||
|
while let Some(node) = bfs.next(&graph) {
|
||||||
|
let weight = graph.node_weight(node).unwrap();
|
||||||
|
if weight.name == start.name {
|
||||||
|
println!("+ {}-{}", weight.name, weight.version);
|
||||||
|
} else {
|
||||||
|
println!("|- {}-{}", weight.name, weight.version);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
println!("");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Command::Dependencies { crate_name } => {
|
||||||
|
let idx = indices
|
||||||
|
.get(&CrateId {
|
||||||
|
name: crate_name.clone(),
|
||||||
|
version: "".to_string(),
|
||||||
|
})
|
||||||
|
.expect("unable to find crate in tree");
|
||||||
|
let mut bfs = Bfs::new(&graph, *idx);
|
||||||
|
while let Some(node) = bfs.next(&graph) {
|
||||||
|
let weight = graph.node_weight(node).unwrap();
|
||||||
|
if weight.name == crate_name {
|
||||||
|
println!("+ {}-{}", weight.name, weight.version);
|
||||||
|
} else {
|
||||||
|
println!("|- {}-{}", weight.name, weight.version);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Command::Dependents { crate_name } => {
|
||||||
|
let idx = indices
|
||||||
|
.get(&CrateId {
|
||||||
|
name: crate_name.clone(),
|
||||||
|
version: "".to_string(),
|
||||||
|
})
|
||||||
|
.expect("unable to find crate in tree");
|
||||||
|
let node = graph.node_weight(*idx).unwrap();
|
||||||
|
println!("+ {}-{}", node.name, node.version);
|
||||||
|
for parent in graph.neighbors_directed(*idx, Direction::Incoming) {
|
||||||
|
let weight = graph.node_weight(parent).unwrap();
|
||||||
|
println!("|- {}-{}", weight.name, weight.version);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Command::SemverCheck { crate_name } => {
|
||||||
|
let c = crates
|
||||||
|
.get(&CrateId {
|
||||||
|
name: crate_name.to_string(),
|
||||||
|
version: "".to_string(),
|
||||||
|
})
|
||||||
|
.unwrap();
|
||||||
|
check_semver(&c)?;
|
||||||
|
}
|
||||||
|
Command::PrepareRelease { crate_name } => {
|
||||||
|
let start = indices
|
||||||
|
.get(&CrateId {
|
||||||
|
name: crate_name.clone(),
|
||||||
|
version: "".to_string(),
|
||||||
|
})
|
||||||
|
.expect("unable to find crate in tree");
|
||||||
|
|
||||||
|
graph.reverse();
|
||||||
|
|
||||||
|
let mut bfs = Bfs::new(&graph, *start);
|
||||||
|
|
||||||
|
while let Some(node) = bfs.next(&graph) {
|
||||||
|
let weight = graph.node_weight(node).unwrap();
|
||||||
|
println!("Preparing {}", weight.name);
|
||||||
|
let mut c = crates.get_mut(weight).unwrap();
|
||||||
|
let ver = semver::Version::parse(&c.id.version)?;
|
||||||
|
let newver = if let Err(_) = check_semver(&c) {
|
||||||
|
println!("Semver check failed, bumping minor!");
|
||||||
|
semver::Version::new(ver.major, ver.minor + 1, 0)
|
||||||
|
} else {
|
||||||
|
semver::Version::new(ver.major, ver.minor, ver.patch + 1)
|
||||||
|
};
|
||||||
|
|
||||||
|
println!(
|
||||||
|
"Updating {} from {} -> {}",
|
||||||
|
weight.name,
|
||||||
|
c.id.version,
|
||||||
|
newver.to_string()
|
||||||
|
);
|
||||||
|
let newver = newver.to_string();
|
||||||
|
|
||||||
|
update_version(&mut c, &newver)?;
|
||||||
|
let c = crates.get(weight).unwrap();
|
||||||
|
|
||||||
|
// Update all nodes further down the tree
|
||||||
|
let mut bfs = Bfs::new(&graph, node);
|
||||||
|
while let Some(dep_node) = bfs.next(&graph) {
|
||||||
|
let dep_weight = graph.node_weight(dep_node).unwrap();
|
||||||
|
let dep = crates.get(dep_weight).unwrap();
|
||||||
|
update_versions(dep, &c.id, &newver)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update changelog
|
||||||
|
update_changelog(&root, &c)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
let weight = graph.node_weight(*start).unwrap();
|
||||||
|
let c = crates.get(weight).unwrap();
|
||||||
|
publish_release(&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(&graph, *start);
|
||||||
|
while let Some(node) = bfs.next(&graph) {
|
||||||
|
let weight = graph.node_weight(node).unwrap();
|
||||||
|
println!("git tag {}-v{}", weight.name, weight.version);
|
||||||
|
}
|
||||||
|
|
||||||
|
println!("");
|
||||||
|
println!("# Run these commands to publish the crate and dependents:");
|
||||||
|
|
||||||
|
let mut bfs = Bfs::new(&graph, *start);
|
||||||
|
while let Some(node) = bfs.next(&graph) {
|
||||||
|
let weight = graph.node_weight(node).unwrap();
|
||||||
|
let c = crates.get(weight).unwrap();
|
||||||
|
|
||||||
|
let mut args: Vec<String> = vec![
|
||||||
|
"publish".to_string(),
|
||||||
|
"--manifest-path".to_string(),
|
||||||
|
c.path.display().to_string(),
|
||||||
|
];
|
||||||
|
|
||||||
|
if let Some(features) = &c.config.features {
|
||||||
|
args.push("--features".into());
|
||||||
|
args.push(features.join(","));
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(target) = &c.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(" "));
|
||||||
|
*/
|
||||||
|
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 mut args: Vec<String> = vec![
|
||||||
|
"semver-checks".to_string(),
|
||||||
|
"--manifest-path".to_string(),
|
||||||
|
c.path.display().to_string(),
|
||||||
|
"--default-features".to_string(),
|
||||||
|
];
|
||||||
|
if let Some(features) = &c.config.features {
|
||||||
|
args.push("--features".into());
|
||||||
|
args.push(features.join(","));
|
||||||
|
}
|
||||||
|
|
||||||
|
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!("semver check failed"));
|
||||||
|
} else {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_changelog(repo: &Path, c: &Crate) -> Result<()> {
|
||||||
|
let args: Vec<String> = 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.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 mut args: Vec<String> = vec![
|
||||||
|
"publish".to_string(),
|
||||||
|
"--manifest-path".to_string(),
|
||||||
|
c.path.display().to_string(),
|
||||||
|
];
|
||||||
|
|
||||||
|
if let Some(features) = &c.config.features {
|
||||||
|
args.push("--features".into());
|
||||||
|
args.push(features.join(","));
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(target) = &c.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(())
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user