You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

359 lines
11 KiB

//! 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-traits",
"peepmatic-macro",
"peepmatic-automata",
"peepmatic-test-operator",
"peepmatic-runtime",
"peepmatic",
"peepmatic-souper",
// 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-native",
"cranelift-object",
"cranelift-interpreter",
"cranelift",
"cranelift-jit",
// wiggle
"wiggle-generate",
"wiggle-macro",
// wasmtime
"lightbeam",
Implement support for `async` functions in Wasmtime (#2434) * Implement support for `async` functions in Wasmtime This is an implementation of [RFC 2] in Wasmtime which is to support `async`-defined host functions. At a high level support is added by executing WebAssembly code that might invoke an asynchronous host function on a separate native stack. When the host function's future is not ready we switch back to the main native stack to continue execution. There's a whole bunch of details in this commit, and it's a bit much to go over them all here in this commit message. The most important changes here are: * A new `wasmtime-fiber` crate has been written to manage the low-level details of stack-switching. Unixes use `mmap` to allocate a stack and Windows uses the native fibers implementation. We'll surely want to refactor this to move stack allocation elsewhere in the future. Fibers are intended to be relatively general with a lot of type paremters to fling values back and forth across suspension points. The whole crate is a giant wad of `unsafe` unfortunately and involves handwritten assembly with custom dwarf CFI directives to boot. Definitely deserves a close eye in review! * The `Store` type has two new methods -- `block_on` and `on_fiber` which bridge between the async and non-async worlds. Lots of unsafe fiddly bits here as we're trying to communicate context pointers between disparate portions of the code. Extra eyes and care in review is greatly appreciated. * The APIs for binding `async` functions are unfortunately pretty ugly in `Func`. This is mostly due to language limitations and compiler bugs (I believe) in Rust. Instead of `Func::wrap` we have a `Func::wrapN_async` family of methods, and we've also got a whole bunch of `Func::getN_async` methods now too. It may be worth rethinking the API of `Func` to try to make the documentation page actually grok'able. This isn't super heavily tested but the various test should suffice for engaging hopefully nearly all the infrastructure in one form or another. This is just the start though! [RFC 2]: https://github.com/bytecodealliance/rfcs/pull/2 * Add wasmtime-fiber to publish script * Save vector/float registers on ARM too. * Fix a typo * Update lock file * Implement periodically yielding with fuel consumption This commit implements APIs on `Store` to periodically yield execution of futures through the consumption of fuel. When fuel runs out a future's execution is yielded back to the caller, and then upon resumption fuel is re-injected. The goal of this is to allow cooperative multi-tasking with futures. * Fix compile without async * Save/restore the frame pointer in fiber switching Turns out this is another caller-saved register! * Simplify x86_64 fiber asm Take a leaf out of aarch64's playbook and don't have extra memory to load/store these arguments, instead leverage how `wasmtime_fiber_switch` already loads a bunch of data into registers which we can then immediately start using on a fiber's start without any extra memory accesses. * Add x86 support to wasmtime-fiber * Add ARM32 support to fiber crate * Make fiber build file probing more flexible * Use CreateFiberEx on Windows * Remove a stray no-longer-used trait declaration * Don't reach into `Caller` internals * Tweak async fuel to eventually run out. With fuel it's probably best to not provide any way to inject infinite fuel. * Fix some typos * Cleanup asm a bit * Use a shared header file to deduplicate some directives * Guarantee hidden visibility for functions * Enable gc-sections on macOS x86_64 * Add `.type` annotations for ARM * Update lock file * Fix compile error * Review comments
4 years ago
"wasmtime-fiber",
"wasmtime-environ",
"wasmtime-runtime",
"wasmtime-debug",
"wasmtime-profiling",
"wasmtime-obj",
"wasmtime-cranelift",
"wasmtime-lightbeam",
"wasmtime-jit",
"wasmtime-cache",
"wasmtime",
// wasi-common/wiggle
"wiggle",
"wasi-common",
"wasi-cap-std-sync",
"wasi-tokio",
// other mic wasmtime crates
"wasmtime-wasi",
"wasmtime-wasi-nn",
"wasmtime-wasi-crypto",
"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);
}
println!("");
println!("===================================================================");
println!("");
println!("Don't forget to push a git tag for this release!");
println!("");
println!(" $ git tag vX.Y.Z");
println!(" $ git push git@github.com:bytecodealliance/wasmtime.git vX.Y.Z");
}
"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 ["witx", "witx-cli", "wasi-crypto"].contains(&&name[..]) {
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);
}
// Note that the status is ignored here. This fails most of the time because
// the owner is already set and present, so we only want to add this to
// crates which haven't previously been published.
Command::new("cargo")
.arg("owner")
.arg("-a")
.arg("github:bytecodealliance:wasmtime-publish")
.arg(&krate.name)
.status()
.expect("failed to run cargo");
}
// 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" && c.manifest.iter().any(|p| p == "wasi-common"))
.unwrap();
verify_and_vendor(&witx);
// Vendor wasi-crypto which is also a path dependency
let wasi_crypto = crates.iter().find(|c| c.name == "wasi-crypto").unwrap();
verify_and_vendor(&wasi_crypto);
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.contains("lightbeam")
|| krate.name == "witx"
|| krate.name.contains("wasi-nn")
|| krate.name.contains("peepmatic")
{
cmd.arg("--no-verify");
}
let status = cmd.status().unwrap();
assert!(status.success(), "failed to verify {:?}", &krate.manifest);
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();
}
}