Verify crates are publish-able on CI (#2036)
This commit updates our CI to verify that all crates are publish-able at all times on every commit. During the 0.19.0 release we found another case where the crates as they live in this repository weren't publish-able, so the hope is that this no longer comes up again! The script added in this commit also takes the time/liberty to remove the existing bump/publish scripts and instead replace them with one Rust script originally sourced from wasm-bindgen. The intention of this script is that it has three modes: * `./publish bump` - bumps version numbers which are sent as a PR to get reviewed (probably with a changelog as well) * `./publish verify` - run on CI on every commit, builds every crate we publish as if it's being published to crates.io, notably without raw access to other crates in the repository. * `./publish publish` - publishes all crates to crates.io, passing the `--no-verify` flag to make this a much speedier process than it is today.
This commit is contained in:
323
scripts/publish.rs
Normal file
323
scripts/publish.rs
Normal file
@@ -0,0 +1,323 @@
|
||||
//! Helper script to publish the wasmtime and cranelift suites of crates
|
||||
//!
|
||||
//! See documentation in `docs/contributing-release-process.md` for more
|
||||
//! information, but in a nutshell:
|
||||
//!
|
||||
//! * `./publish bump` - bump crate versions in-tree
|
||||
//! * `./publish verify` - verify crates can be published to crates.io
|
||||
//! * `./publish publish` - actually publish crates to crates.io
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::env;
|
||||
use std::fs;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::process::{Command, Stdio};
|
||||
|
||||
// note that this list must be topologically sorted by dependencies
|
||||
const CRATES_TO_PUBLISH: &[&str] = &[
|
||||
// peepmatic
|
||||
"peepmatic-macro",
|
||||
"peepmatic-automata",
|
||||
"peepmatic-runtime",
|
||||
"peepmatic",
|
||||
// cranelift
|
||||
"cranelift-entity",
|
||||
"cranelift-bforest",
|
||||
"cranelift-codegen-shared",
|
||||
"cranelift-codegen-meta",
|
||||
"cranelift-codegen",
|
||||
"cranelift-reader",
|
||||
"cranelift-serde",
|
||||
"cranelift-module",
|
||||
"cranelift-preopt",
|
||||
"cranelift-frontend",
|
||||
"cranelift-wasm",
|
||||
"cranelift-faerie",
|
||||
"cranelift-native",
|
||||
"cranelift-object",
|
||||
"cranelift-interpreter",
|
||||
"cranelift",
|
||||
"cranelift-simplejit",
|
||||
// wig/wiggle
|
||||
"wig",
|
||||
"wiggle-generate",
|
||||
"wiggle-macro",
|
||||
"wiggle",
|
||||
"wasmtime-wiggle-macro",
|
||||
// wasi-common bits
|
||||
"winx",
|
||||
"yanix",
|
||||
"wasi-common",
|
||||
// wasmtime
|
||||
"lightbeam",
|
||||
"wasmtime-environ",
|
||||
"wasmtime-runtime",
|
||||
"wasmtime-debug",
|
||||
"wasmtime-profiling",
|
||||
"wasmtime-obj",
|
||||
"wasmtime-jit",
|
||||
"wasmtime",
|
||||
"wasmtime-wiggle",
|
||||
"wasmtime-wasi",
|
||||
"wasmtime-rust-macro",
|
||||
"wasmtime-rust",
|
||||
"wasmtime-wast",
|
||||
"wasmtime-cli",
|
||||
];
|
||||
|
||||
struct Crate {
|
||||
manifest: PathBuf,
|
||||
name: String,
|
||||
version: String,
|
||||
next_version: String,
|
||||
publish: bool,
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let mut crates = Vec::new();
|
||||
crates.push(read_crate("./Cargo.toml".as_ref()));
|
||||
find_crates("crates".as_ref(), &mut crates);
|
||||
find_crates("cranelift".as_ref(), &mut crates);
|
||||
|
||||
let pos = CRATES_TO_PUBLISH
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(i, c)| (*c, i))
|
||||
.collect::<HashMap<_, _>>();
|
||||
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);
|
||||
}
|
||||
// update the lock file
|
||||
assert!(Command::new("cargo")
|
||||
.arg("fetch")
|
||||
.status()
|
||||
.unwrap()
|
||||
.success());
|
||||
}
|
||||
|
||||
"publish" => {
|
||||
for krate in crates.iter() {
|
||||
publish(&krate);
|
||||
}
|
||||
}
|
||||
|
||||
"verify" => {
|
||||
verify(&crates);
|
||||
}
|
||||
|
||||
s => panic!("unknown command: {}", s),
|
||||
}
|
||||
}
|
||||
|
||||
fn find_crates(dir: &Path, dst: &mut Vec<Crate>) {
|
||||
if dir.join("Cargo.toml").exists() {
|
||||
let krate = read_crate(&dir.join("Cargo.toml"));
|
||||
if !krate.publish || CRATES_TO_PUBLISH.iter().any(|c| krate.name == *c) {
|
||||
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;
|
||||
let mut publish = true;
|
||||
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(),
|
||||
);
|
||||
}
|
||||
if line.starts_with("publish = false") {
|
||||
publish = false;
|
||||
}
|
||||
}
|
||||
let name = name.unwrap();
|
||||
let version = version.unwrap();
|
||||
let next_version = if CRATES_TO_PUBLISH.contains(&&name[..]) {
|
||||
bump(&version)
|
||||
} else {
|
||||
version.clone()
|
||||
};
|
||||
if name == "witx" {
|
||||
publish = false;
|
||||
}
|
||||
Crate {
|
||||
manifest: manifest.to_path_buf(),
|
||||
name,
|
||||
version,
|
||||
next_version,
|
||||
publish,
|
||||
}
|
||||
}
|
||||
|
||||
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 !is_deps && 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();
|
||||
}
|
||||
|
||||
/// Performs a major version bump increment on the semver version `version`.
|
||||
///
|
||||
/// This function will perform a semver-major-version bump on the `version`
|
||||
/// specified. This is used to calculate the next version of a crate in this
|
||||
/// repository since we're currently making major version bumps for all our
|
||||
/// releases. This may end up getting tweaked as we stabilize crates and start
|
||||
/// doing more minor/patch releases, but for now this should do the trick.
|
||||
fn bump(version: &str) -> String {
|
||||
let mut iter = version.split('.').map(|s| s.parse::<u32>().unwrap());
|
||||
let major = iter.next().expect("major version");
|
||||
let minor = iter.next().expect("minor version");
|
||||
let patch = iter.next().expect("patch version");
|
||||
if major != 0 {
|
||||
format!("{}.0.0", major + 1)
|
||||
} else if minor != 0 {
|
||||
format!("0.{}.0", minor + 1)
|
||||
} else {
|
||||
format!("0.0.{}", patch + 1)
|
||||
}
|
||||
}
|
||||
|
||||
fn publish(krate: &Crate) {
|
||||
if !CRATES_TO_PUBLISH.iter().any(|s| *s == krate.name) {
|
||||
return;
|
||||
}
|
||||
let status = Command::new("cargo")
|
||||
.arg("publish")
|
||||
.current_dir(krate.manifest.parent().unwrap())
|
||||
.arg("--no-verify")
|
||||
.status()
|
||||
.expect("failed to run cargo");
|
||||
if !status.success() {
|
||||
println!("FAIL: failed to publish `{}`: {}", krate.name, status);
|
||||
}
|
||||
}
|
||||
|
||||
// Verify the current tree is publish-able to crates.io. The intention here is
|
||||
// that we'll run `cargo package` on everything which verifies the build as-if
|
||||
// it were published to crates.io. This requires using an incrementally-built
|
||||
// directory registry generated from `cargo vendor` because the versions
|
||||
// referenced from `Cargo.toml` may not exist on crates.io.
|
||||
fn verify(crates: &[Crate]) {
|
||||
drop(fs::remove_dir_all(".cargo"));
|
||||
drop(fs::remove_dir_all("vendor"));
|
||||
let vendor = Command::new("cargo")
|
||||
.arg("vendor")
|
||||
.stderr(Stdio::inherit())
|
||||
.output()
|
||||
.unwrap();
|
||||
assert!(vendor.status.success());
|
||||
|
||||
fs::create_dir_all(".cargo").unwrap();
|
||||
fs::write(".cargo/config.toml", vendor.stdout).unwrap();
|
||||
|
||||
// Vendor witx which wasn't vendored because it's a path dependency, but
|
||||
// it'll need to be in our directory registry for crates that depend on it.
|
||||
let witx = crates.iter().find(|c| c.name == "witx").unwrap();
|
||||
verify_and_vendor(&witx);
|
||||
|
||||
for krate in crates {
|
||||
if !krate.publish {
|
||||
continue;
|
||||
}
|
||||
verify_and_vendor(&krate);
|
||||
}
|
||||
|
||||
fn verify_and_vendor(krate: &Crate) {
|
||||
let mut cmd = Command::new("cargo");
|
||||
cmd.arg("package")
|
||||
.arg("--manifest-path")
|
||||
.arg(&krate.manifest)
|
||||
.env("CARGO_TARGET_DIR", "./target");
|
||||
if krate.name == "lightbeam" || krate.name == "witx" {
|
||||
cmd.arg("--no-verify");
|
||||
}
|
||||
let status = cmd.status().unwrap();
|
||||
assert!(status.success());
|
||||
let tar = Command::new("tar")
|
||||
.arg("xf")
|
||||
.arg(format!(
|
||||
"../target/package/{}-{}.crate",
|
||||
krate.name, krate.version
|
||||
))
|
||||
.current_dir("./vendor")
|
||||
.status()
|
||||
.unwrap();
|
||||
assert!(tar.success());
|
||||
fs::write(
|
||||
format!(
|
||||
"./vendor/{}-{}/.cargo-checksum.json",
|
||||
krate.name, krate.version
|
||||
),
|
||||
"{\"files\":{}}",
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user