//! Helper script to publish the wasm-bindgen suite of crates //! //! Usage: //! //! * First, compile this script //! * Next, set cwd to the root of the wasm-bindgen repository //! * Execute `./publish bump` to bump versions //! * Send a PR //! * Merge when green //! * Execute `./publish publish` to publish crates use std::collections::HashMap; use std::env; use std::fs; use std::io; use std::path::{Path, PathBuf}; use std::process::Command; // note that this list must be topologically sorted by dependencies const CRATES_TO_PUBLISH: &[&str] = &[ "wasm-bindgen-shared", "wasm-bindgen-backend", "wasm-bindgen-macro-support", "wasm-bindgen-macro", "wasm-bindgen-test-macro", "wasm-bindgen-test", "wasm-bindgen-wasm-interpreter", "wasm-bindgen-webidl", "wasm-bindgen-threads-xform", "wasm-bindgen-anyref-xform", "wasm-bindgen-cli-support", "wasm-bindgen-cli", "wasm-bindgen", "wasm-bindgen-futures", "js-sys", "web-sys", ]; const CRATES_TO_AVOID_PUBLISH: &[&str] = &[ // We'll publish these when they're ready one day "wasm-bindgen-typescript", // These are internal crates, unlikely to ever be published "ui-tests", "sample", "webidl-tests", "typescript-tests", ]; struct Crate { manifest: PathBuf, name: String, version: String, next_version: String, } fn main() { let mut crates = Vec::new(); crates.push(read_crate("./Cargo.toml".as_ref())); find_crates("crates".as_ref(), &mut crates); find_crates("examples".as_ref(), &mut crates); let pos = CRATES_TO_PUBLISH.iter() .chain(CRATES_TO_AVOID_PUBLISH) .enumerate() .map(|(i, c)| (*c, i)) .collect::>(); crates.sort_by_key(|krate| pos.get(&krate.name[..])); match &env::args().nth(1).expect("must have one argument")[..] { "bump" => { for krate in crates.iter() { bump_version(&krate, &crates); } } "publish" => { for krate in crates.iter() { publish(&krate); } } s => panic!("unknown command: {}", s), } } fn find_crates(dir: &Path, dst: &mut Vec) { if dir.join("Cargo.toml").exists() { let krate = read_crate(&dir.join("Cargo.toml")); if CRATES_TO_PUBLISH.iter() .chain(CRATES_TO_AVOID_PUBLISH) .any(|c| krate.name == *c) { dst.push(krate); } else if dir.iter().any(|s| s == "examples") { dst.push(krate); } else { panic!("failed to find {:?} in whitelist or blacklist", krate.name); } } for entry in dir.read_dir().unwrap() { let entry = entry.unwrap(); if entry.file_type().unwrap().is_dir() { find_crates(&entry.path(), dst); } } } fn read_crate(manifest: &Path) -> Crate { let mut name = None; let mut version = None; for line in fs::read_to_string(manifest).unwrap().lines() { if name.is_none() && line.starts_with("name = \"") { name = Some(line.replace("name = \"", "") .replace("\"", "") .trim() .to_string()); } if version.is_none() && line.starts_with("version = \"") { version = Some(line.replace("version = \"", "") .replace("\"", "") .trim() .to_string()); } } let name = name.unwrap(); let version = version.unwrap(); let next_version = if CRATES_TO_PUBLISH.contains(&&name[..]) { bump(&version) } else { version.clone() }; Crate { manifest: manifest.to_path_buf(), name, version, next_version, } } fn bump_version(krate: &Crate, crates: &[Crate]) { let contents = fs::read_to_string(&krate.manifest).unwrap(); let mut new_manifest = String::new(); let mut is_deps = false; for line in contents.lines() { let mut rewritten = false; if line.starts_with("version =") { if CRATES_TO_PUBLISH.contains(&&krate.name[..]) { println!("bump `{}` {} => {}", krate.name, krate.version, krate.next_version); new_manifest.push_str(&line.replace(&krate.version, &krate.next_version)); rewritten = true; } } is_deps = if line.starts_with("[") { line.contains("dependencies") } else { is_deps }; for other in crates { if !is_deps || !line.starts_with(&format!("{} ", other.name)) { continue } if !line.contains(&other.version) { if !line.contains("version =") { continue } panic!("{:?} has a dep on {} but doesn't list version {}", krate.manifest, other.name, other.version); } rewritten = true; new_manifest.push_str(&line.replace(&other.version, &other.next_version)); break } if !rewritten { new_manifest.push_str(line); } new_manifest.push_str("\n"); } fs::write(&krate.manifest, new_manifest).unwrap(); } fn bump(version: &str) -> String { let mut iter = version.split('.').map(|s| s.parse::().unwrap()); let major = iter.next().expect("major version"); let minor = iter.next().expect("minor version"); let patch = iter.next().expect("patch version"); format!("{}.{}.{}", major, minor, patch + 1) } fn publish(krate: &Crate) { if !CRATES_TO_PUBLISH.iter().any(|s| *s == krate.name) { return } if krate.name == "wasm-bindgen" { println!("ABOUT TO PUBLISH wasm-bindgen"); println!("for this to work you need to comment out the `dev-dependencies`"); println!("section in `Cargo.toml` and everything below"); println!(""); println!("hit enter when done"); drop(io::stdin().read_line(&mut String::new())); } let status = Command::new("cargo") .arg("publish") .current_dir(krate.manifest.parent().unwrap()) .arg("--no-verify") .arg("--allow-dirty") .status() .expect("failed to run cargo"); if !status.success() { println!("FAIL: failed to publish `{}`: {}", krate.name, status); } if krate.name == "wasm-bindgen" { println!("ok please now uncomment the section you just commented"); println!(""); println!("hit enter when done"); drop(io::stdin().read_line(&mut String::new())); } }