From eb8636d20f044457ed118a9eaef480b6ba4d060d Mon Sep 17 00:00:00 2001 From: Calvin Prewitt Date: Thu, 10 Oct 2024 14:54:03 -0600 Subject: [PATCH] `wkg` update 0.7.4 and remove `wit` CLI (replaced by `wkg wit` subcommand) (#342) * updated wasm-pkg-client version to 0.6.0 * removed wit CLI * updated wasm-pkg-client version to 0.7.4 --- Cargo.lock | 121 ++++--- Cargo.toml | 4 +- crates/wit/Cargo.toml | 45 --- crates/wit/README.md | 185 ---------- crates/wit/build.rs | 35 -- crates/wit/src/bin/wit.rs | 53 --- crates/wit/src/commands.rs | 13 - crates/wit/src/commands/add.rs | 144 -------- crates/wit/src/commands/build.rs | 66 ---- crates/wit/src/commands/init.rs | 55 --- crates/wit/src/commands/publish.rs | 65 ---- crates/wit/src/commands/update.rs | 50 --- crates/wit/src/config.rs | 183 ---------- crates/wit/src/lib.rs | 528 ----------------------------- crates/wit/src/lock.rs | 113 ------ crates/wit/tests/add.rs | 166 --------- crates/wit/tests/build.rs | 103 ------ crates/wit/tests/init.rs | 59 ---- crates/wit/tests/publish.rs | 172 ---------- crates/wit/tests/support/mod.rs | 346 ------------------- crates/wit/tests/update.rs | 303 ----------------- crates/wit/tests/version.rs | 15 - src/bin/cargo-component.rs | 3 +- src/commands/add.rs | 2 +- src/commands/new.rs | 2 +- src/commands/publish.rs | 3 +- src/commands/update.rs | 2 +- src/config.rs | 6 +- tests/support/mod.rs | 9 +- 29 files changed, 99 insertions(+), 2752 deletions(-) delete mode 100644 crates/wit/Cargo.toml delete mode 100644 crates/wit/README.md delete mode 100644 crates/wit/build.rs delete mode 100644 crates/wit/src/bin/wit.rs delete mode 100644 crates/wit/src/commands.rs delete mode 100644 crates/wit/src/commands/add.rs delete mode 100644 crates/wit/src/commands/build.rs delete mode 100644 crates/wit/src/commands/init.rs delete mode 100644 crates/wit/src/commands/publish.rs delete mode 100644 crates/wit/src/commands/update.rs delete mode 100644 crates/wit/src/config.rs delete mode 100644 crates/wit/src/lib.rs delete mode 100644 crates/wit/src/lock.rs delete mode 100644 crates/wit/tests/add.rs delete mode 100644 crates/wit/tests/build.rs delete mode 100644 crates/wit/tests/init.rs delete mode 100644 crates/wit/tests/publish.rs delete mode 100644 crates/wit/tests/support/mod.rs delete mode 100644 crates/wit/tests/update.rs delete mode 100644 crates/wit/tests/version.rs diff --git a/Cargo.lock b/Cargo.lock index 83fe5cd0..ba06a1b6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4256,6 +4256,16 @@ dependencies = [ "wasmparser 0.216.0", ] +[[package]] +name = "wasm-encoder" +version = "0.217.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b88b0814c9a2b323a9b46c687e726996c255ac8b64aa237dd11c81ed4854760" +dependencies = [ + "leb128", + "wasmparser 0.217.0", +] + [[package]] name = "wasm-metadata" version = "0.215.0" @@ -4288,11 +4298,27 @@ dependencies = [ "wasmparser 0.216.0", ] +[[package]] +name = "wasm-metadata" +version = "0.217.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65a146bf9a60e9264f0548a2599aa9656dba9a641eff9ab88299dc2a637e483c" +dependencies = [ + "anyhow", + "indexmap 2.5.0", + "serde 1.0.209", + "serde_derive", + "serde_json", + "spdx", + "wasm-encoder 0.217.0", + "wasmparser 0.217.0", +] + [[package]] name = "wasm-pkg-client" -version = "0.5.0" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c828d761faaf6ddefa63666ebce1b950059807eb61b68a6bdbecc9f0c1afcae8" +checksum = "b5c36ab0f85473016aabec2e0b862264fb924a69e4c841e519cf72aaf3c71e13" dependencies = [ "anyhow", "async-trait", @@ -4318,14 +4344,14 @@ dependencies = [ "warg-crypto", "warg-protocol", "wasm-pkg-common", - "wit-component 0.216.0", + "wit-component 0.217.0", ] [[package]] name = "wasm-pkg-common" -version = "0.5.0" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f2754ba9e374f6042c834716f0a84dd4da1efce9507eda1ddd1f2f744db1462" +checksum = "14e2b37ab75178c1a24cf538725c335796073f160e658a56934ade435948b12a" dependencies = [ "anyhow", "bytes", @@ -4394,6 +4420,19 @@ dependencies = [ "serde 1.0.209", ] +[[package]] +name = "wasmparser" +version = "0.217.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca917a21307d3adf2b9857b94dd05ebf8496bdcff4437a9b9fb3899d3e6c74e7" +dependencies = [ + "ahash", + "bitflags 2.6.0", + "hashbrown 0.14.5", + "indexmap 2.5.0", + "semver", +] + [[package]] name = "wasmprinter" version = "0.2.80" @@ -4710,41 +4749,6 @@ version = "0.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d135d17ab770252ad95e9a872d365cf3090e3be864a34ab46f48555993efc904" -[[package]] -name = "wit" -version = "0.17.0-dev" -dependencies = [ - "anyhow", - "assert_cmd", - "bytes", - "cargo-component-core", - "clap", - "futures", - "indexmap 2.5.0", - "log", - "p256", - "predicates", - "pretty_env_logger", - "rand_core", - "rpassword", - "semver", - "serde 1.0.209", - "tempfile", - "tokio", - "tokio-util", - "toml_edit 0.22.20", - "url", - "warg-client", - "warg-crypto", - "warg-protocol", - "warg-server", - "wasm-metadata 0.216.0", - "wasm-pkg-client", - "wasmparser 0.216.0", - "wit-component 0.216.0", - "wit-parser 0.216.0", -] - [[package]] name = "wit-bindgen-core" version = "0.31.0" @@ -4810,6 +4814,25 @@ dependencies = [ "wit-parser 0.216.0", ] +[[package]] +name = "wit-component" +version = "0.217.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7117809905e49db716d81e794f79590c052bf2fdbbcda1731ca0fb28f6f3ddf" +dependencies = [ + "anyhow", + "bitflags 2.6.0", + "indexmap 2.5.0", + "log", + "serde 1.0.209", + "serde_derive", + "serde_json", + "wasm-encoder 0.217.0", + "wasm-metadata 0.217.0", + "wasmparser 0.217.0", + "wit-parser 0.217.0", +] + [[package]] name = "wit-parser" version = "0.215.0" @@ -4846,6 +4869,24 @@ dependencies = [ "wasmparser 0.216.0", ] +[[package]] +name = "wit-parser" +version = "0.217.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb893dcd6d370cfdf19a0d9adfcd403efb8e544e1a0ea3a8b81a21fe392eaa78" +dependencies = [ + "anyhow", + "id-arena", + "indexmap 2.5.0", + "log", + "semver", + "serde 1.0.209", + "serde_derive", + "serde_json", + "unicode-xid", + "wasmparser 0.217.0", +] + [[package]] name = "xdg-home" version = "1.3.0" diff --git a/Cargo.toml b/Cargo.toml index 8bcc0d7b..1dc9bdd7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -65,7 +65,7 @@ wasmprinter = { workspace = true } wat = { workspace = true } [workspace] -members = ["crates/core", "crates/wit"] +members = ["crates/core"] [workspace.dependencies] anyhow = "1.0.82" @@ -107,7 +107,7 @@ warg-protocol = "0.9.0" warg-server = "0.9.0" wasi-preview1-component-adapter-provider = "24" wasm-metadata = "0.216.0" -wasm-pkg-client = "0.5.0" +wasm-pkg-client = "0.7.4" wasmparser = "0.216.0" wasmprinter = "0.216.0" wat = "1.216.0" diff --git a/crates/wit/Cargo.toml b/crates/wit/Cargo.toml deleted file mode 100644 index 1c93295b..00000000 --- a/crates/wit/Cargo.toml +++ /dev/null @@ -1,45 +0,0 @@ -[package] -name = "wit" -# This tool has an independent version from `cargo-component`. -version = "0.17.0-dev" -description = "A tool for building and publishing WIT packages to a registry." -edition = { workspace = true } -authors = { workspace = true } -license = { workspace = true } -categories = { workspace = true } -keywords = { workspace = true } -repository = { workspace = true } -readme = "README.md" - -[dependencies] -anyhow = { workspace = true } -bytes = { workspace = true } -cargo-component-core = { workspace = true } -clap = { workspace = true } -futures = { workspace = true } -indexmap = { workspace = true } -log = { workspace = true } -p256 = { workspace = true } -pretty_env_logger = { workspace = true } -rand_core = { workspace = true } -rpassword = { workspace = true } -semver = { workspace = true } -serde = { workspace = true } -tokio = { workspace = true } -toml_edit = { workspace = true } -url = { workspace = true } -wasm-pkg-client = { workspace = true } -wasm-metadata = { workspace = true } -wit-component = { workspace = true } -wit-parser = { workspace = true } - -[dev-dependencies] -assert_cmd = { workspace = true } -predicates = { workspace = true } -warg-client = { workspace = true } -warg-crypto = { workspace = true } -warg-protocol = { workspace = true } -warg-server = { workspace = true } -tokio-util = { workspace = true } -wasmparser = { workspace = true } -tempfile = { workspace = true } diff --git a/crates/wit/README.md b/crates/wit/README.md deleted file mode 100644 index 6b66c35b..00000000 --- a/crates/wit/README.md +++ /dev/null @@ -1,185 +0,0 @@ -# The `wit` tool - -A tool for creating and publishing WIT packages to a [WebAssembly component -registry](https://github.com/bytecodealliance/registry/). - -WIT packages are used in the [WebAssembly Component Model](https://github.com/WebAssembly/component-model/) -for defining interfaces, types, and worlds used in WebAssembly components. - -## Requirements - -* The `wit` tool is written in Rust, so you'll want the [latest stable Rust - installed](https://www.rust-lang.org/tools/install). - -## Installation - -To install `wit` subcommand, run the following command: - -``` -cargo install wit -``` - -## Initializing a WIT package - -To initialize a new WIT package in the current directory: - -``` -wit init -``` - -This creates a `wit.toml` file with the following contents: - -```toml -version = "0.1.0" - -[dependencies] - -[registries] -``` - -By default, the WIT package will not have any dependencies specified. - -The registries section contains a mapping of registry names to URLs. The -intention behind explicitly supporting multiple registries is that no one -registry will be the central repository for WebAssembly components; in the -future, a federation of registries will be used for publishing and discovering -WebAssembly components. - -A registry named `default` will be the registry to use when a dependency does -not explicitly specify a registry to use. - -An example of setting the default registry: - -```toml -[registries] -default = "https://preview-registry.bytecodealliance.org" -``` - -The default registry may also be set by passing the `--registry` option to the -`init` command: - -``` -wit init --registry https://preview-registry.bytecodealliance.org -``` - -## Adding a dependency - -To add a dependency on another WIT package, use the `add` command: - -``` -wit add -``` - -Where `PACKAGE` is the package to add the dependency for, e.g. `wasi:cli`. - -The command will contact the registry to determine the latest version of the -package, and add it as a dependency in the `wit.toml` file. - -The version requirement to use may be specified with a delimited `@`: - -``` -wit add wasi:cli@2.0.0 -``` - -## Building the WIT package - -To build the WIT package to a binary WebAssembly file, use the `build` command: - -``` -wit build -``` - -This command will output a `.wasm` file based on the package name parsed from -the `.wit` files in the directory containing `wit.toml`. - -Use the `--output` option to specify the output file name: - -``` -wit build --output my-package.wasm -``` - -## Updating dependencies - -To update the dependencies of a WIT package, use the `update` command: - -``` -wit update -``` - -This command will contact the registry for the latest versions of the -dependencies specified in `wit.toml` and update the versions in the lock file, -`wit.lock`. - -## Publishing the WIT package to a registry - -To publish the WIT package to a registry, use the `publish` command: - -``` -wit publish -``` - -The command will publish the package to the default registry using the default -signing key. - -To specify a different registry or signing key, use the `--registry` and -`--key-name` options, respectively: - -``` -wit publish --registry https://registry.example.com --key-name my-signing-key -``` - -## Managing signing keys - -WebAssembly component registries accept packages based on the keys used to sign -the records being published. - -The `wit` tool uses the OS-provided keyring to securely store signing keys. -Use the [`warg` CLI](https://crates.io/crates/warg-cli) to manage your signing keys. - -## Contributing to `wit` - -`wit` is a (future) [Bytecode Alliance](https://bytecodealliance.org/) -project, and follows the Bytecode Alliance's [Code of Conduct](CODE_OF_CONDUCT.md) -and [Organizational Code of Conduct](ORG_CODE_OF_CONDUCT.md). - -### Getting the Code - -You'll clone the code via `git`: - -``` -git clone https://github.com/bytecodealliance/cargo-component -``` - -### Testing Changes - -We'd like tests ideally to be written for all changes. Test can be run via: - -``` -cargo test -p wit -``` - -You'll be adding tests primarily to the `tests/` directory. - -### Submitting Changes - -Changes to `wit` are managed through pull requests (PRs). Everyone -is welcome to submit a pull request! We'll try to get to reviewing it or -responding to it in at most a few days. - -### Code Formatting - -Code is required to be formatted with the current Rust stable's `cargo fmt` -command. This is checked on CI. - -### Continuous Integration - -The CI for the `wit` repository is relatively significant. It tests -changes on Windows, macOS, and Linux. - -### Publishing - -Publication of this crate is entirely automated via CI. A publish happens -whenever a tag is pushed to the repository, so to publish a new version you'll -want to make a PR that bumps the version numbers (see the `ci/publish.rs` -script), merge the PR, then tag the PR and push the tag. That should trigger -all that's necessary to publish all the crates and binaries to crates.io. diff --git a/crates/wit/build.rs b/crates/wit/build.rs deleted file mode 100644 index d2b87fdb..00000000 --- a/crates/wit/build.rs +++ /dev/null @@ -1,35 +0,0 @@ -use std::{path::Path, process::Command}; - -fn main() { - println!("cargo:rerun-if-changed=build.rs"); - - commit_info(); -} - -fn commit_info() { - if !Path::new("../../.git").exists() { - return; - } - - let output = match Command::new("git") - .arg("log") - .arg("-1") - .arg("--date=short") - .arg("--format=%H %h %cd") - .arg("--abbrev") - .output() - { - Ok(output) if output.status.success() => output, - _ => return, - }; - let stdout = String::from_utf8(output.stdout).unwrap(); - let mut parts = stdout.split_whitespace(); - let mut next = || parts.next().unwrap(); - println!("cargo:rustc-env=CARGO_GIT_HASH={}", next()); - println!( - "cargo:rustc-env=WIT_VERSION_INFO={} ({} {})", - env!("CARGO_PKG_VERSION"), - next(), - next() - ); -} diff --git a/crates/wit/src/bin/wit.rs b/crates/wit/src/bin/wit.rs deleted file mode 100644 index 590fc2ef..00000000 --- a/crates/wit/src/bin/wit.rs +++ /dev/null @@ -1,53 +0,0 @@ -use anyhow::Result; -use cargo_component_core::terminal::{Color, Terminal, Verbosity}; -use clap::Parser; -use std::process::exit; -use wit::commands::{AddCommand, BuildCommand, InitCommand, PublishCommand, UpdateCommand}; - -fn version() -> &'static str { - option_env!("WIT_VERSION_INFO").unwrap_or(env!("CARGO_PKG_VERSION")) -} - -/// WIT package tool. -#[derive(Parser)] -#[clap( - bin_name = "wit", - version, - propagate_version = true, - arg_required_else_help = true -)] -#[command(version = version())] -struct Wit { - #[clap(subcommand)] - command: Command, -} - -#[derive(Parser)] -pub enum Command { - Init(InitCommand), - Add(AddCommand), - Build(BuildCommand), - Publish(PublishCommand), - Update(UpdateCommand), -} - -#[tokio::main] -async fn main() -> Result<()> { - pretty_env_logger::init(); - - let app = Wit::parse(); - - if let Err(e) = match app.command { - Command::Init(cmd) => cmd.exec().await, - Command::Add(cmd) => cmd.exec().await, - Command::Build(cmd) => cmd.exec().await, - Command::Publish(cmd) => cmd.exec().await, - Command::Update(cmd) => cmd.exec().await, - } { - let terminal = Terminal::new(Verbosity::Normal, Color::Auto); - terminal.error(format!("{e:?}"))?; - exit(1); - } - - Ok(()) -} diff --git a/crates/wit/src/commands.rs b/crates/wit/src/commands.rs deleted file mode 100644 index 60466b4c..00000000 --- a/crates/wit/src/commands.rs +++ /dev/null @@ -1,13 +0,0 @@ -//! Module for CLI commands. - -mod add; -mod build; -mod init; -mod publish; -mod update; - -pub use add::*; -pub use build::*; -pub use init::*; -pub use publish::*; -pub use update::*; diff --git a/crates/wit/src/commands/add.rs b/crates/wit/src/commands/add.rs deleted file mode 100644 index 3fd1f21d..00000000 --- a/crates/wit/src/commands/add.rs +++ /dev/null @@ -1,144 +0,0 @@ -use std::path::PathBuf; - -use anyhow::{bail, Context, Result}; -use cargo_component_core::{ - cache_dir, - command::CommonOptions, - registry::{Dependency, DependencyResolution, DependencyResolver, RegistryPackage}, - VersionedPackageName, -}; -use clap::Args; -use semver::VersionReq; -use wasm_pkg_client::{caching::FileCache, PackageRef}; - -use crate::config::{Config, CONFIG_FILE_NAME}; - -async fn resolve_version( - pkg_config: Option, - package: &VersionedPackageName, - registry: &Option, - file_cache: FileCache, -) -> Result { - let mut resolver = DependencyResolver::new(pkg_config, None, file_cache)?; - let dependency = Dependency::Package(RegistryPackage { - name: Some(package.name.clone()), - version: package - .version - .as_ref() - .unwrap_or(&VersionReq::STAR) - .clone(), - registry: registry.clone(), - }); - - resolver.add_dependency(&package.name, &dependency).await?; - - let dependencies = resolver.resolve().await?; - assert_eq!(dependencies.len(), 1); - - match dependencies.values().next().expect("expected a resolution") { - DependencyResolution::Registry(resolution) => Ok(package - .version - .as_ref() - .map(|v| v.to_string()) - .unwrap_or_else(|| resolution.version.to_string())), - _ => unreachable!(), - } -} - -/// Adds a reference to a WIT package from a registry. -#[derive(Args)] -#[clap(disable_version_flag = true)] -pub struct AddCommand { - /// The common command options. - #[clap(flatten)] - pub common: CommonOptions, - - /// Don't actually write the configuration file. - #[clap(long = "dry-run")] - pub dry_run: bool, - - /// The name of the registry to use. - #[clap(long = "registry", short = 'r', value_name = "REGISTRY")] - pub registry: Option, - - /// The name of the dependency to use; defaults to the package name. - #[clap(long, value_name = "NAME")] - pub name: Option, - - /// Add a package dependency to a file or directory. - #[clap(long = "path", value_name = "PATH")] - pub path: Option, - - /// The id of the package to add a dependency to. - #[clap(value_name = "PACKAGE")] - pub package: VersionedPackageName, -} - -impl AddCommand { - /// Executes the command. - pub async fn exec(self) -> Result<()> { - log::debug!("executing add command"); - - let (mut config, config_path) = Config::from_default_file()? - .with_context(|| format!("failed to find configuration file `{CONFIG_FILE_NAME}`"))?; - - let terminal = self.common.new_terminal(); - let pkg_config = if let Some(config_file) = self.common.config { - wasm_pkg_client::Config::from_file(&config_file).context(format!( - "failed to load configuration file from {}", - config_file.display() - ))? - } else { - wasm_pkg_client::Config::global_defaults()? - }; - - let file_cache = FileCache::new(cache_dir(self.common.cache_dir)?).await?; - - let name = self.name.as_ref().unwrap_or(&self.package.name); - if config.dependencies.contains_key(name) { - bail!("cannot add dependency `{name}` as it conflicts with an existing dependency"); - } - - let message = match self.path.as_deref() { - Some(path) => { - config - .dependencies - .insert(name.clone(), Dependency::Local(path.to_path_buf())); - - format!( - "dependency `{name}` from path `{path}`{dry_run}", - path = path.display(), - dry_run = if self.dry_run { " (dry run)" } else { "" } - ) - } - None => { - let version = - resolve_version(Some(pkg_config), &self.package, &self.registry, file_cache) - .await?; - - let package = RegistryPackage { - name: self.name.is_some().then(|| self.package.name.clone()), - version: version.parse().expect("expected a valid version"), - registry: self.registry, - }; - - config - .dependencies - .insert(name.clone(), Dependency::Package(package)); - - format!( - "dependency `{name}` with version `{version}`{dry_run}", - dry_run = if self.dry_run { " (dry run)" } else { "" } - ) - } - }; - - if !self.dry_run { - config.write(config_path)?; - } - - terminal.status(if self.dry_run { "Would add" } else { "Added" }, message)?; - - Ok(()) - } -} diff --git a/crates/wit/src/commands/build.rs b/crates/wit/src/commands/build.rs deleted file mode 100644 index 5887b8b4..00000000 --- a/crates/wit/src/commands/build.rs +++ /dev/null @@ -1,66 +0,0 @@ -use std::{fs, path::PathBuf}; - -use anyhow::{Context, Result}; -use cargo_component_core::{cache_dir, command::CommonOptions}; -use clap::Args; -use wasm_pkg_client::caching::FileCache; - -use crate::{ - build_wit_package, - config::{Config, CONFIG_FILE_NAME}, -}; - -/// Build a binary WIT package. -#[derive(Args)] -#[clap(disable_version_flag = true)] -pub struct BuildCommand { - /// The common command options. - #[clap(flatten)] - pub common: CommonOptions, - - /// The output package path. - #[clap(short, long, value_name = "PATH")] - pub output: Option, -} - -impl BuildCommand { - /// Executes the command. - pub async fn exec(self) -> Result<()> { - log::debug!("executing build command"); - - let (config, config_path) = Config::from_default_file()? - .with_context(|| format!("failed to find configuration file `{CONFIG_FILE_NAME}`"))?; - - let terminal = self.common.new_terminal(); - let pkg_config = if let Some(config_file) = self.common.config { - wasm_pkg_client::Config::from_file(&config_file).context(format!( - "failed to load configuration file from {}", - config_file.display() - ))? - } else { - wasm_pkg_client::Config::global_defaults()? - }; - let file_cache = FileCache::new(cache_dir(self.common.cache_dir)?).await?; - - let (id, bytes) = - build_wit_package(&config, &config_path, pkg_config, &terminal, file_cache).await?; - - let output = self - .output - .unwrap_or_else(|| format!("{name}.wasm", name = id.name()).into()); - - fs::write(&output, bytes).with_context(|| { - format!( - "failed to write output file `{output}`", - output = output.display() - ) - })?; - - terminal.status( - "Created", - format!("package `{output}`", output = output.display()), - )?; - - Ok(()) - } -} diff --git a/crates/wit/src/commands/init.rs b/crates/wit/src/commands/init.rs deleted file mode 100644 index 6bdb6aba..00000000 --- a/crates/wit/src/commands/init.rs +++ /dev/null @@ -1,55 +0,0 @@ -use crate::config::{ConfigBuilder, CONFIG_FILE_NAME}; -use anyhow::{bail, Result}; -use cargo_component_core::{command::CommonOptions, registry::DEFAULT_REGISTRY_NAME}; -use clap::Args; -use std::path::PathBuf; -use url::Url; - -/// Initialize a new WIT package. -#[derive(Args)] -#[clap(disable_version_flag = true)] -pub struct InitCommand { - /// The common command options. - #[clap(flatten)] - pub common: CommonOptions, - - /// Use the specified default registry when generating the package. - #[clap(long = "registry", value_name = "REGISTRY")] - pub registry: Option, - - /// The path to initialize the package in. - #[clap(value_name = "PATH", default_value = ".")] - pub path: PathBuf, -} - -impl InitCommand { - /// Executes the command. - pub async fn exec(self) -> Result<()> { - log::debug!("executing init command"); - - let path = self.path.join(CONFIG_FILE_NAME); - if path.is_file() { - bail!( - "WIT package configuration file `{path}` already exists", - path = path.display() - ); - } - - let terminal = self.common.new_terminal(); - let mut builder = ConfigBuilder::new(); - - if let Some(registry) = self.registry { - builder = builder.with_registry(DEFAULT_REGISTRY_NAME, registry); - } - - let config = builder.build(); - config.write(&path)?; - - terminal.status( - "Created", - format!("configuration file `{path}`", path = path.display()), - )?; - - Ok(()) - } -} diff --git a/crates/wit/src/commands/publish.rs b/crates/wit/src/commands/publish.rs deleted file mode 100644 index 25705323..00000000 --- a/crates/wit/src/commands/publish.rs +++ /dev/null @@ -1,65 +0,0 @@ -use anyhow::{Context, Result}; -use cargo_component_core::{cache_dir, command::CommonOptions}; -use clap::Args; -use wasm_pkg_client::{caching::FileCache, PackageRef, Registry}; - -use crate::{ - config::{Config, CONFIG_FILE_NAME}, - publish_wit_package, PublishOptions, -}; - -/// Publish a WIT package to a registry. -#[derive(Args)] -#[clap(disable_version_flag = true)] -pub struct PublishCommand { - /// The common command options. - #[clap(flatten)] - pub common: CommonOptions, - - /// Don't actually publish the package. - #[clap(long = "dry-run")] - pub dry_run: bool, - - /// Use the specified registry name when publishing the package. - #[clap(long = "registry", value_name = "REGISTRY")] - pub registry: Option, - - /// Override the package name to publish. - #[clap(long, value_name = "NAME")] - pub package: Option, -} - -impl PublishCommand { - /// Executes the command. - pub async fn exec(self) -> Result<()> { - log::debug!("executing publish command"); - - let (config, config_path) = Config::from_default_file()? - .with_context(|| format!("failed to find configuration file `{CONFIG_FILE_NAME}`"))?; - - let terminal = self.common.new_terminal(); - let pkg_config = if let Some(config_file) = self.common.config { - wasm_pkg_client::Config::from_file(&config_file).context(format!( - "failed to load configuration file from {}", - config_file.display() - ))? - } else { - wasm_pkg_client::Config::global_defaults()? - }; - let file_cache = FileCache::new(cache_dir(self.common.cache_dir)?).await?; - - publish_wit_package( - PublishOptions { - config: &config, - config_path: &config_path, - pkg_config, - cache: file_cache, - registry: self.registry.as_ref(), - package: self.package.as_ref(), - dry_run: self.dry_run, - }, - &terminal, - ) - .await - } -} diff --git a/crates/wit/src/commands/update.rs b/crates/wit/src/commands/update.rs deleted file mode 100644 index d183025c..00000000 --- a/crates/wit/src/commands/update.rs +++ /dev/null @@ -1,50 +0,0 @@ -use anyhow::{Context, Result}; -use cargo_component_core::{cache_dir, command::CommonOptions}; -use clap::Args; -use wasm_pkg_client::caching::FileCache; - -use crate::config::{Config, CONFIG_FILE_NAME}; - -/// Update dependencies as recorded in the lock file. -#[derive(Args)] -#[clap(disable_version_flag = true)] -pub struct UpdateCommand { - /// The common command options. - #[clap(flatten)] - pub common: CommonOptions, - - /// Don't actually write the lockfile - #[clap(long = "dry-run")] - pub dry_run: bool, -} - -impl UpdateCommand { - /// Executes the command. - pub async fn exec(self) -> Result<()> { - log::debug!("executing update command"); - - let (config, config_path) = Config::from_default_file()? - .with_context(|| format!("failed to find configuration file `{CONFIG_FILE_NAME}`"))?; - - let terminal = self.common.new_terminal(); - let pkg_config = if let Some(config_file) = self.common.config { - wasm_pkg_client::Config::from_file(&config_file).context(format!( - "failed to load configuration file from {}", - config_file.display() - ))? - } else { - wasm_pkg_client::Config::global_defaults()? - }; - let file_cache = FileCache::new(cache_dir(self.common.cache_dir)?).await?; - - crate::update_lockfile( - &config, - &config_path, - pkg_config, - &terminal, - self.dry_run, - file_cache, - ) - .await - } -} diff --git a/crates/wit/src/config.rs b/crates/wit/src/config.rs deleted file mode 100644 index 69442605..00000000 --- a/crates/wit/src/config.rs +++ /dev/null @@ -1,183 +0,0 @@ -//! Module for WIT package configuration. - -use anyhow::{Context, Result}; -use cargo_component_core::registry::Dependency; -use semver::Version; -use serde::{Deserialize, Serialize}; -use std::{ - collections::HashMap, - fs, - path::{Path, PathBuf}, -}; -use toml_edit::Item; -use url::Url; -use wasm_pkg_client::PackageRef; - -/// The default name of the configuration file. -pub const CONFIG_FILE_NAME: &str = "wit.toml"; - -fn find_config(cwd: &Path) -> Option { - let mut current = Some(cwd); - - while let Some(dir) = current { - let config = dir.join(CONFIG_FILE_NAME); - if config.is_file() { - return Some(config); - } - - current = dir.parent(); - } - - None -} - -/// Used to construct a new WIT package configuration. -#[derive(Default)] -pub struct ConfigBuilder { - version: Option, - registries: HashMap, -} - -impl ConfigBuilder { - /// Creates a new configuration builder. - pub fn new() -> Self { - Self::default() - } - - /// Sets the version to use in the configuration. - pub fn with_version(mut self, version: Version) -> Self { - self.version = Some(version); - self - } - - /// Adds a registry to the configuration. - pub fn with_registry(mut self, name: impl Into, url: Url) -> Self { - self.registries.insert(name.into(), url); - self - } - - /// Builds the configuration. - pub fn build(self) -> Config { - Config { - version: self.version.unwrap_or_else(|| Version::new(0, 1, 0)), - dependencies: Default::default(), - registries: self.registries, - authors: Default::default(), - categories: Default::default(), - description: None, - license: None, - documentation: None, - homepage: None, - repository: None, - } - } -} - -/// Represents a WIT package configuration. -#[derive(Serialize, Deserialize)] -pub struct Config { - /// The current package version. - pub version: Version, - /// The package dependencies. - #[serde(default, skip_serializing_if = "HashMap::is_empty")] - pub dependencies: HashMap, - /// The registries to use for sourcing packages. - #[serde(default, skip_serializing_if = "HashMap::is_empty")] - pub registries: HashMap, - /// The authors of the package. - #[serde(default, skip_serializing_if = "Vec::is_empty")] - pub authors: Vec, - /// The categories of the package. - #[serde(default, skip_serializing_if = "Vec::is_empty")] - pub categories: Vec, - /// The package description. - #[serde(default, skip_serializing_if = "Option::is_none")] - pub description: Option, - /// The package license. - #[serde(default, skip_serializing_if = "Option::is_none")] - pub license: Option, - /// The package documentation URL. - #[serde(default, skip_serializing_if = "Option::is_none")] - pub documentation: Option, - /// The package homepage URL. - #[serde(default, skip_serializing_if = "Option::is_none")] - pub homepage: Option, - /// The package repository URL. - #[serde(default, skip_serializing_if = "Option::is_none")] - pub repository: Option, -} - -impl Config { - /// Loads a WIT package configuration from a default file path. - /// - /// This will search for a configuration file in the current directory and - /// all parent directories. - /// - /// Returns both the configuration file and the path it was located at. - /// - /// Returns `Ok(None)` if no configuration file was found. - pub fn from_default_file() -> Result> { - if let Some(path) = find_config(&std::env::current_dir()?) { - return Ok(Some((Self::from_file(&path)?, path))); - } - - Ok(None) - } - - /// Loads a WIT package configuration from the given file path. - pub fn from_file(path: impl AsRef) -> Result { - let path = path.as_ref(); - let contents = fs::read_to_string(path).with_context(|| { - format!( - "failed to read configuration file `{path}`", - path = path.display() - ) - })?; - - toml_edit::de::from_str(&contents).with_context(|| { - format!( - "failed to parse configuration file `{path}`", - path = path.display() - ) - }) - } - - /// Writes the configuration to the given file path. - pub fn write(&self, path: impl AsRef) -> Result<()> { - let path = path.as_ref(); - - let mut contents = toml_edit::ser::to_document(self).with_context(|| { - format!( - "failed to serialize configuration file `{path}`", - path = path.display() - ) - })?; - - // If the dependencies or registries tables are inline, convert - // to a table - for name in ["dependencies", "registries"] { - if let Some(table) = contents.get_mut(name).and_then(Item::as_inline_table_mut) { - let table = std::mem::take(table); - contents[name] = Item::Table(table.into_table()); - } - } - - if let Some(parent) = path.parent() { - fs::create_dir_all(parent).with_context(|| { - format!( - "failed to create parent directory for `{path}`", - path = path.display() - ) - })?; - } - - fs::write(path, contents.to_string()).with_context(|| { - format!( - "failed to write configuration file `{path}`", - path = path.display() - ) - })?; - - Ok(()) - } -} diff --git a/crates/wit/src/lib.rs b/crates/wit/src/lib.rs deleted file mode 100644 index 28dad853..00000000 --- a/crates/wit/src/lib.rs +++ /dev/null @@ -1,528 +0,0 @@ -//! The library for the WIT CLI tool. - -#![deny(missing_docs)] - -use std::{collections::HashSet, path::Path, sync::Arc}; - -use anyhow::{bail, Context, Result}; -use cargo_component_core::{ - lock::{LockFile, LockFileResolver, LockedPackage, LockedPackageVersion}, - registry::{DecodedDependency, DependencyResolutionMap, DependencyResolver}, - terminal::{Colors, Terminal}, -}; -use config::Config; -use indexmap::{IndexMap, IndexSet}; -use lock::{acquire_lock_file_ro, acquire_lock_file_rw, to_lock_file}; -use wasm_metadata::{Link, LinkType, RegistryMetadata}; -use wasm_pkg_client::{ - caching::FileCache, warg::WargRegistryConfig, Client, PackageRef, PublishOpts, Registry, -}; -use wit_component::DecodedWasm; -use wit_parser::{PackageId, PackageName, Resolve, UnresolvedPackageGroup}; - -pub mod commands; -pub mod config; -mod lock; - -async fn resolve_dependencies( - config: &Config, - config_path: &Path, - pkg_config: wasm_pkg_client::Config, - terminal: &Terminal, - update_lock_file: bool, - file_cache: FileCache, -) -> Result { - let file_lock = acquire_lock_file_ro(terminal, config_path)?; - let lock_file = file_lock - .as_ref() - .map(|f| { - LockFile::read(f.file()).with_context(|| { - format!( - "failed to read lock file `{path}`", - path = f.path().display() - ) - }) - }) - .transpose()?; - - let mut resolver = DependencyResolver::new( - Some(pkg_config), - lock_file.as_ref().map(LockFileResolver::new), - file_cache, - )?; - - for (name, dep) in &config.dependencies { - resolver.add_dependency(name, dep).await?; - } - - let map = resolver.resolve().await?; - - // Update the lock file - if update_lock_file { - let new_lock_file = to_lock_file(&map); - if Some(&new_lock_file) != lock_file.as_ref() { - drop(file_lock); - let file_lock = acquire_lock_file_rw(terminal, config_path)?; - new_lock_file - .write(file_lock.file(), "wit") - .with_context(|| { - format!( - "failed to write lock file `{path}`", - path = file_lock.path().display() - ) - })?; - } - } - - Ok(map) -} - -async fn parse_wit_package( - dir: &Path, - dependencies: &DependencyResolutionMap, -) -> Result<(Resolve, PackageId)> { - let mut merged = Resolve::default(); - - // Start by decoding all of the dependencies - let mut deps = IndexMap::new(); - for (name, resolution) in dependencies { - let decoded = resolution.decode().await?; - if let Some(prev) = deps.insert(decoded.package_name().clone(), decoded) { - bail!( - "duplicate definitions of package `{prev}` found while decoding dependency `{name}`", - prev = prev.package_name() - ); - } - } - - // Parse the root package itself - let root = UnresolvedPackageGroup::parse_dir(dir).with_context(|| { - format!( - "failed to parse package from directory `{dir}`", - dir = dir.display() - ) - })?; - - let mut source_files: Vec<_> = root - .source_map - .source_files() - .map(Path::to_path_buf) - .collect(); - - // Do a topological sort of the dependencies - let mut order = IndexSet::new(); - let mut visiting = HashSet::new(); - for dep in deps.values() { - visit(dep, &deps, &mut order, &mut visiting)?; - } - - assert!(visiting.is_empty()); - - // Merge all of the dependencies first - for name in order { - match deps.swap_remove(&name).unwrap() { - DecodedDependency::Wit { - resolution, - package, - } => { - source_files.extend(package.source_map.source_files().map(Path::to_path_buf)); - merged.push_group(package).with_context(|| { - format!( - "failed to merge dependency `{name}`", - name = resolution.name() - ) - })?; - } - DecodedDependency::Wasm { - resolution, - decoded, - } => { - let resolve = match decoded { - DecodedWasm::WitPackage(resolve, _) => resolve, - DecodedWasm::Component(resolve, _) => resolve, - }; - - merged.merge(resolve).with_context(|| { - format!( - "failed to merge world of dependency `{name}`", - name = resolution.name() - ) - })?; - } - }; - } - - let package = merged.push_group(root).with_context(|| { - format!( - "failed to merge package from directory `{dir}`", - dir = dir.display() - ) - })?; - - return Ok((merged, package)); - - fn visit<'a>( - dep: &'a DecodedDependency<'a>, - deps: &'a IndexMap, - order: &mut IndexSet, - visiting: &mut HashSet<&'a PackageName>, - ) -> Result<()> { - if order.contains(dep.package_name()) { - return Ok(()); - } - - // Visit any unresolved foreign dependencies - match dep { - DecodedDependency::Wit { - package, - resolution, - } => { - for name in package.main.foreign_deps.keys() { - // Only visit known dependencies - // wit-parser will error on unknown foreign dependencies when - // the package is resolved - if let Some(dep) = deps.get(name) { - if !visiting.insert(name) { - bail!("foreign dependency `{name}` forms a dependency cycle while parsing dependency `{other}`", other = resolution.name()); - } - - visit(dep, deps, order, visiting)?; - assert!(visiting.remove(name)); - } - } - } - DecodedDependency::Wasm { - decoded, - resolution, - } => { - // Look for foreign packages in the decoded dependency - for (_, package) in &decoded.resolve().packages { - if package.name.namespace == dep.package_name().namespace - && package.name.name == dep.package_name().name - { - continue; - } - - if let Some(dep) = deps.get(&package.name) { - if !visiting.insert(&package.name) { - bail!("foreign dependency `{name}` forms a dependency cycle while parsing dependency `{other}`", name = package.name, other = resolution.name()); - } - - visit(dep, deps, order, visiting)?; - assert!(visiting.remove(&package.name)); - } - } - } - } - - assert!(order.insert(dep.package_name().clone())); - - Ok(()) - } -} - -/// Builds a WIT package given the configuration and directory to parse. -async fn build_wit_package( - config: &Config, - config_path: &Path, - pkg_config: wasm_pkg_client::Config, - terminal: &Terminal, - file_cache: FileCache, -) -> Result<(PackageRef, Vec)> { - let dependencies = - resolve_dependencies(config, config_path, pkg_config, terminal, true, file_cache).await?; - - let dir = config_path.parent().unwrap_or_else(|| Path::new(".")); - - let (mut resolve, package) = parse_wit_package(dir, &dependencies).await?; - - let pkg = &mut resolve.packages[package]; - let name = format!("{ns}:{name}", ns = pkg.name.namespace, name = pkg.name.name).parse()?; - - let bytes = wit_component::encode(Some(true), &resolve, package)?; - - let mut producers = wasm_metadata::Producers::empty(); - producers.add( - "processed-by", - env!("CARGO_PKG_NAME"), - option_env!("WIT_VERSION_INFO").unwrap_or(env!("CARGO_PKG_VERSION")), - ); - - let bytes = producers - .add_to_wasm(&bytes) - .context("failed to add producers metadata to output WIT package")?; - - Ok((name, bytes)) -} - -struct PublishOptions<'a> { - config: &'a Config, - config_path: &'a Path, - pkg_config: wasm_pkg_client::Config, - registry: Option<&'a Registry>, - package: Option<&'a PackageRef>, - dry_run: bool, - cache: FileCache, -} - -fn add_registry_metadata(config: &Config, bytes: &[u8]) -> Result> { - let mut metadata = RegistryMetadata::default(); - if !config.authors.is_empty() { - metadata.set_authors(Some(config.authors.clone())); - } - - if !config.categories.is_empty() { - metadata.set_categories(Some(config.categories.clone())); - } - - metadata.set_description(config.description.clone()); - - // TODO: registry metadata should have keywords - // if !package.keywords.is_empty() { - // metadata.set_keywords(Some(package.keywords.clone())); - // } - - metadata.set_license(config.license.clone()); - - let mut links = Vec::new(); - if let Some(docs) = &config.documentation { - links.push(Link { - ty: LinkType::Documentation, - value: docs.clone(), - }); - } - - if let Some(homepage) = &config.homepage { - links.push(Link { - ty: LinkType::Homepage, - value: homepage.clone(), - }); - } - - if let Some(repo) = &config.repository { - links.push(Link { - ty: LinkType::Repository, - value: repo.clone(), - }); - } - - if !links.is_empty() { - metadata.set_links(Some(links)); - } - - metadata - .add_to_wasm(bytes) - .context("failed to add registry metadata to component") -} - -async fn publish_wit_package(mut options: PublishOptions<'_>, terminal: &Terminal) -> Result<()> { - let (name, bytes) = build_wit_package( - options.config, - options.config_path, - options.pkg_config.clone(), - terminal, - options.cache, - ) - .await?; - - if options.dry_run { - terminal.warn("not publishing package to the registry due to the --dry-run option")?; - return Ok(()); - } - - let bytes = add_registry_metadata(options.config, &bytes)?; - let name = options.package.unwrap_or(&name); - - if let Ok(key) = std::env::var("WIT_PUBLISH_KEY") { - let registry = options.pkg_config.resolve_registry(name).ok_or_else(|| anyhow::anyhow!("Tried to set a signing key, but registry was not set and no default registry was found. Try setting the `--registry` option."))?.to_owned(); - // NOTE(thomastaylor312): If config doesn't already exist, this will essentially force warg - // usage because we'll be creating a config for warg, which means it will default to that - // protocol. So for all intents and purposes, setting a publish key forces warg usage. - let reg_config = options - .pkg_config - .get_or_insert_registry_config_mut(®istry); - let mut warg_conf = WargRegistryConfig::try_from(&*reg_config).unwrap_or_default(); - warg_conf.signing_key = Some(Arc::new( - key.try_into().context("Failed to parse signing key")?, - )); - reg_config.set_backend_config("warg", warg_conf)?; - } - - terminal.status("Publishing", format!("package `{name}`"))?; - - let client = Client::new(options.pkg_config); - - let (name, version) = client - .publish_release_data( - Box::pin(std::io::Cursor::new(bytes)), - PublishOpts { - package: Some((name.to_owned(), options.config.version.to_owned())), - registry: options.registry.cloned(), - }, - ) - .await?; - - terminal.status("Published", format!("package `{name}` v{version}",))?; - - Ok(()) -} -/// Update the dependencies in the lock file. -pub async fn update_lockfile( - config: &Config, - config_path: &Path, - pkg_config: wasm_pkg_client::Config, - terminal: &Terminal, - dry_run: bool, - file_cache: FileCache, -) -> Result<()> { - // Resolve all dependencies as if the lock file does not exist - let mut resolver = DependencyResolver::new(Some(pkg_config), None, file_cache)?; - for (name, dep) in &config.dependencies { - resolver.add_dependency(name, dep).await?; - } - - let map = resolver.resolve().await?; - - let file_lock = acquire_lock_file_ro(terminal, config_path)?; - let orig_lock_file = file_lock - .as_ref() - .map(|f| { - LockFile::read(f.file()).with_context(|| { - format!( - "failed to read lock file `{path}`", - path = f.path().display() - ) - }) - }) - .transpose()? - .unwrap_or_default(); - - let new_lock_file = to_lock_file(&map); - - for old_pkg in &orig_lock_file.packages { - let new_pkg = match new_lock_file - .packages - .binary_search_by_key(&old_pkg.key(), LockedPackage::key) - .map(|index| &new_lock_file.packages[index]) - { - Ok(pkg) => pkg, - Err(_) => { - // The package is no longer a dependency - for old_ver in &old_pkg.versions { - terminal.status_with_color( - if dry_run { "Would remove" } else { "Removing" }, - format!( - "dependency `{name}` v{version}", - name = old_pkg.name, - version = old_ver.version, - ), - Colors::Red, - )?; - } - continue; - } - }; - - for old_ver in &old_pkg.versions { - let new_ver = match new_pkg - .versions - .binary_search_by_key(&old_ver.key(), LockedPackageVersion::key) - .map(|index| &new_pkg.versions[index]) - { - Ok(ver) => ver, - Err(_) => { - // The version of the package is no longer a dependency - terminal.status_with_color( - if dry_run { "Would remove" } else { "Removing" }, - format!( - "dependency `{name}` v{version}", - name = old_pkg.name, - version = old_ver.version, - ), - Colors::Red, - )?; - continue; - } - }; - - // The version has changed - if old_ver.version != new_ver.version { - terminal.status_with_color( - if dry_run { "Would update" } else { "Updating" }, - format!( - "dependency `{name}` v{old} -> v{new}", - name = old_pkg.name, - old = old_ver.version, - new = new_ver.version - ), - Colors::Cyan, - )?; - } - } - } - - for new_pkg in &new_lock_file.packages { - let old_pkg = match orig_lock_file - .packages - .binary_search_by_key(&new_pkg.key(), LockedPackage::key) - .map(|index| &orig_lock_file.packages[index]) - { - Ok(pkg) => pkg, - Err(_) => { - // The package is new - for new_ver in &new_pkg.versions { - terminal.status_with_color( - if dry_run { "Would add" } else { "Adding" }, - format!( - "dependency `{name}` v{version}", - name = new_pkg.name, - version = new_ver.version, - ), - Colors::Green, - )?; - } - continue; - } - }; - - for new_ver in &new_pkg.versions { - if old_pkg - .versions - .binary_search_by_key(&new_ver.key(), LockedPackageVersion::key) - .map(|index| &old_pkg.versions[index]) - .is_err() - { - // The version is new - terminal.status_with_color( - if dry_run { "Would add" } else { "Adding" }, - format!( - "dependency `{name}` v{version}", - name = new_pkg.name, - version = new_ver.version, - ), - Colors::Green, - )?; - } - } - } - - if dry_run { - terminal.warn("not updating lock file due to --dry-run option")?; - } else { - // Update the lock file - if new_lock_file != orig_lock_file { - drop(file_lock); - let file_lock = acquire_lock_file_rw(terminal, config_path)?; - new_lock_file - .write(file_lock.file(), "wit") - .with_context(|| { - format!( - "failed to write lock file `{path}`", - path = file_lock.path().display() - ) - })?; - } - } - - Ok(()) -} diff --git a/crates/wit/src/lock.rs b/crates/wit/src/lock.rs deleted file mode 100644 index 60a2cc76..00000000 --- a/crates/wit/src/lock.rs +++ /dev/null @@ -1,113 +0,0 @@ -//! Module for the lock file implementation. -use std::{collections::HashMap, path::Path}; - -use anyhow::Result; -use cargo_component_core::{ - lock::{FileLock, LockFile, LockedPackage, LockedPackageVersion}, - registry::{DependencyResolution, DependencyResolutionMap}, - terminal::{Colors, Terminal}, -}; -use semver::Version; -use wasm_pkg_client::{ContentDigest, PackageRef}; - -/// The name of the lock file. -pub const LOCK_FILE_NAME: &str = "wit.lock"; - -pub(crate) fn acquire_lock_file_ro( - terminal: &Terminal, - config_path: &Path, -) -> Result> { - let path = config_path.with_file_name(LOCK_FILE_NAME); - if !path.exists() { - return Ok(None); - } - - log::info!("opening lock file `{path}`", path = path.display()); - match FileLock::try_open_ro(&path)? { - Some(lock) => Ok(Some(lock)), - None => { - terminal.status_with_color( - "Blocking", - format!("on access to lock file `{path}`", path = path.display()), - Colors::Cyan, - )?; - - FileLock::open_ro(&path).map(Some) - } - } -} - -pub(crate) fn acquire_lock_file_rw(terminal: &Terminal, config_path: &Path) -> Result { - let path = config_path.with_file_name(LOCK_FILE_NAME); - log::info!("creating lock file `{path}`", path = path.display()); - match FileLock::try_open_rw(&path)? { - Some(lock) => Ok(lock), - None => { - terminal.status_with_color( - "Blocking", - format!("on access to lock file `{path}`", path = path.display()), - Colors::Cyan, - )?; - - FileLock::open_rw(&path) - } - } -} - -/// Constructs a `LockFile` from a `DependencyResolutionMap`. -pub fn to_lock_file(map: &DependencyResolutionMap) -> LockFile { - type PackageKey = (PackageRef, Option); - type VersionsMap = HashMap; - let mut packages: HashMap = HashMap::new(); - - for resolution in map.values() { - match resolution.key() { - Some((id, registry)) => { - let pkg = match resolution { - DependencyResolution::Registry(pkg) => pkg, - DependencyResolution::Local(_) => unreachable!(), - }; - - let prev = packages - .entry((id.clone(), registry.map(str::to_string))) - .or_default() - .insert( - pkg.requirement.to_string(), - (pkg.version.clone(), pkg.digest.clone()), - ); - - if let Some((prev, _)) = prev { - // The same requirements should resolve to the same version - assert!(prev == pkg.version) - } - } - None => continue, - } - } - - let mut packages: Vec<_> = packages - .into_iter() - .map(|((name, registry), versions)| { - let mut versions: Vec = versions - .into_iter() - .map(|(requirement, (version, digest))| LockedPackageVersion { - requirement, - version, - digest, - }) - .collect(); - - versions.sort_by(|a, b| a.key().cmp(b.key())); - - LockedPackage { - name, - registry, - versions, - } - }) - .collect(); - - packages.sort_by(|a, b| a.key().cmp(&b.key())); - - LockFile::new(packages) -} diff --git a/crates/wit/tests/add.rs b/crates/wit/tests/add.rs deleted file mode 100644 index 6daa1813..00000000 --- a/crates/wit/tests/add.rs +++ /dev/null @@ -1,166 +0,0 @@ -use std::fs; - -use anyhow::Result; -use assert_cmd::prelude::*; -use predicates::{prelude::*, str::contains}; - -use crate::support::*; - -mod support; - -#[test] -fn help() { - for arg in ["help add", "add -h", "add --help"] { - wit(arg.split_whitespace()) - .assert() - .stdout(contains( - "Adds a reference to a WIT package from a registry", - )) - .success(); - } -} - -#[test] -fn it_fails_with_missing_toml_file() -> Result<()> { - wit(["add", "foo:bar"]) - .assert() - .stderr(contains( - "error: failed to find configuration file `wit.toml`", - )) - .failure(); - Ok(()) -} - -#[test] -fn requires_package() { - wit(["add"]) - .assert() - .stderr(contains("wit add ")) - .failure(); -} - -#[tokio::test(flavor = "multi_thread", worker_threads = 1)] -async fn validate_the_package_exists() -> Result<()> { - let (server, _, _) = spawn_server(["foo"]).await?; - - let project = server.project("foo", Vec::::new())?; - - project - .wit(["add", "foo:bar"]) - .assert() - .stderr(contains("package `foo:bar` was not found")) - .failure(); - - Ok(()) -} - -#[tokio::test(flavor = "multi_thread", worker_threads = 1)] -async fn validate_the_version_exists() -> Result<()> { - let (server, _, _) = spawn_server(Vec::::new()).await?; - - let project = server.project("foo", Vec::::new())?; - project.file("foo.wit", "package test:bar;\n")?; - project - .wit(["publish"]) - .env("WIT_PUBLISH_KEY", test_signing_key()) - .assert() - .stderr(contains("Published package `test:bar` v0.1.0")) - .success(); - - let project = server.project("bar", Vec::::new())?; - project - .wit(["add", "test:bar"]) - .assert() - .stderr(contains("Added dependency `test:bar` with version `0.1.0`")) - .success(); - - let manifest = fs::read_to_string(project.root().join("wit.toml"))?; - assert!(contains(r#""test:bar" = "0.1.0""#).eval(&manifest)); - - project - .wit(["add", "--name", "test:bar2", "test:bar@2.0.0"]) - .assert() - .stderr(contains( - "component registry package `test:bar` has no release matching version requirement `^2.0.0`", - )) - .failure(); - - Ok(()) -} - -#[tokio::test(flavor = "multi_thread", worker_threads = 1)] -async fn checks_for_duplicate_dependencies() -> Result<()> { - let (server, _, _) = spawn_server(Vec::::new()).await?; - - let project = server.project("foo", Vec::::new())?; - project.file("foo.wit", "package test:bar;\n")?; - project - .wit(["publish"]) - .env("WIT_PUBLISH_KEY", test_signing_key()) - .assert() - .stderr(contains("Published package `test:bar` v0.1.0")) - .success(); - - let project = server.project("bar", Vec::::new())?; - project - .wit(["add", "test:bar"]) - .assert() - .stderr(contains("Added dependency `test:bar` with version `0.1.0`")) - .success(); - - let manifest = fs::read_to_string(project.root().join("wit.toml"))?; - assert!(contains(r#""test:bar" = "0.1.0""#).eval(&manifest)); - - project - .wit(["add", "test:bar"]) - .assert() - .stderr(contains( - "cannot add dependency `test:bar` as it conflicts with an existing dependency", - )) - .failure(); - - Ok(()) -} - -#[tokio::test(flavor = "multi_thread", worker_threads = 1)] -async fn does_not_modify_manifest_for_dry_run() -> Result<()> { - let (server, _, _) = spawn_server(Vec::::new()).await?; - - let project = server.project("foo", Vec::::new())?; - project.file("foo.wit", "package test:bar;\n")?; - project - .wit(["publish"]) - .env("WIT_PUBLISH_KEY", test_signing_key()) - .assert() - .stderr(contains("Published package `test:bar` v0.1.0")) - .success(); - - let project = server.project("bar", Vec::::new())?; - project - .wit(["add", "test:bar", "--dry-run"]) - .assert() - .stderr(contains( - "Would add dependency `test:bar` with version `0.1.0` (dry run)", - )) - .success(); - - let manifest = fs::read_to_string(project.root().join("wit.toml"))?; - assert!(!contains("test:bar").eval(&manifest)); - - Ok(()) -} - -#[test] -fn validate_add_from_path() -> Result<()> { - let project = Project::new("foo")?; - - project - .wit(["add", "--path", "foo/baz", "foo:baz"]) - .assert() - .stderr(contains("Added dependency `foo:baz` from path `foo/baz`")); - - let manifest = fs::read_to_string(project.root().join("wit.toml"))?; - assert!(contains(r#""foo:baz" = { path = "foo/baz" }"#).eval(&manifest)); - - Ok(()) -} diff --git a/crates/wit/tests/build.rs b/crates/wit/tests/build.rs deleted file mode 100644 index 1020a800..00000000 --- a/crates/wit/tests/build.rs +++ /dev/null @@ -1,103 +0,0 @@ -use std::fs; - -use anyhow::{Context, Result}; -use assert_cmd::prelude::*; -use predicates::str::contains; - -use crate::support::*; - -mod support; - -#[test] -fn help() { - for arg in ["help build", "build -h", "build --help"] { - wit(arg.split_whitespace()) - .assert() - .stdout(contains("Build a binary WIT package")) - .success(); - } -} - -#[test] -fn it_fails_with_missing_toml_file() -> Result<()> { - wit(["build"]) - .assert() - .stderr(contains( - "error: failed to find configuration file `wit.toml`", - )) - .failure(); - Ok(()) -} - -#[test] -fn it_builds() -> Result<()> { - let project = Project::new("foo")?; - project.file( - "bar.wit", - r#"package foo:bar@1.2.3; -@since(version = 1.2.3) -interface bar {} -@since(version = 1.2.3) -world bar-world {} -"#, - )?; - project.file( - "baz.wit", - r#"package foo:bar@1.2.3; -interface baz {} -world baz-world {} -"#, - )?; - - project - .wit(["build"]) - .assert() - .stderr(contains("Created package `bar.wasm`")) - .success(); - - validate_component(&project.root().join("bar.wasm"))?; - - let path = project.root().join("wit.lock"); - let contents = fs::read_to_string(&path) - .with_context(|| format!("failed to read lock file `{path}`", path = path.display()))?; - - let contents = contents.replace("\r\n", "\n"); - - assert_eq!( - contents, - "# This file is automatically generated by wit.\n# It is not intended for manual editing.\nversion = 1\n", - "unexpected lock file contents" - ); - - Ok(()) -} - -#[test] -fn it_adds_a_producers_field() -> Result<()> { - let project = Project::new("foo")?; - project.file("producers.wit", "package test:producers;")?; - - project - .wit(["build"]) - .assert() - .stderr(contains("Created package `producers.wasm`")) - .success(); - - let path = project.root().join("producers.wasm"); - validate_component(&path)?; - - let wasm = fs::read(&path) - .with_context(|| format!("failed to read wasm file `{path}`", path = path.display()))?; - let section = wasm_metadata::Producers::from_wasm(&wasm)?.expect("missing producers section"); - - assert_eq!( - section - .get("processed-by") - .expect("missing processed-by field") - .get(env!("CARGO_PKG_NAME")) - .expect("missing wit field"), - option_env!("WIT_VERSION_INFO").unwrap_or(env!("CARGO_PKG_VERSION")) - ); - - Ok(()) -} diff --git a/crates/wit/tests/init.rs b/crates/wit/tests/init.rs deleted file mode 100644 index 7b5bd0df..00000000 --- a/crates/wit/tests/init.rs +++ /dev/null @@ -1,59 +0,0 @@ -use std::{fs, path::Path}; - -use anyhow::Result; -use assert_cmd::prelude::*; -use predicates::str::contains; -use tempfile::TempDir; - -use crate::support::*; - -mod support; - -#[test] -fn help() { - for arg in ["help init", "init -h", "init --help"] { - wit(arg.split_whitespace()) - .assert() - .stdout(contains("Initialize a new WIT package")) - .success(); - } -} - -#[test] -fn it_creates_the_expected_files() -> Result<()> { - let dir = TempDir::new()?; - - wit(["init", "foo"]) - .current_dir(dir.path()) - .assert() - .stderr(contains(format!( - "Created configuration file `{path}`", - path = Path::new("foo").join("wit.toml").display() - ))) - .success(); - - let proj_dir = dir.path().join("foo"); - assert!(proj_dir.join("wit.toml").is_file()); - - Ok(()) -} - -#[test] -fn it_supports_registry_option() -> Result<()> { - let dir = TempDir::new()?; - - wit(["init", "bar", "--registry", "https://example.com"]) - .current_dir(dir.path()) - .assert() - .stderr(contains(format!( - "Created configuration file `{path}`", - path = Path::new("bar").join("wit.toml").display() - ))) - .success(); - - let proj_dir = dir.path().join("bar"); - assert!(fs::read_to_string(proj_dir.join("wit.toml"))? - .contains("default = \"https://example.com/\"")); - - Ok(()) -} diff --git a/crates/wit/tests/publish.rs b/crates/wit/tests/publish.rs deleted file mode 100644 index 7b3400e4..00000000 --- a/crates/wit/tests/publish.rs +++ /dev/null @@ -1,172 +0,0 @@ -use anyhow::{Context, Result}; -use assert_cmd::prelude::*; -use futures::TryStreamExt; -use predicates::str::contains; -use toml_edit::{value, Array}; -use wasm_metadata::LinkType; -use wasm_pkg_client::{Client, Error}; - -use crate::support::*; - -mod support; - -#[test] -fn help() { - for arg in ["help publish", "publish -h", "publish --help"] { - wit(arg.split_whitespace()) - .assert() - .stdout(contains("Publish a WIT package to a registry")) - .success(); - } -} - -#[test] -fn it_fails_with_missing_toml_file() -> Result<()> { - wit(["publish"]) - .assert() - .stderr(contains( - "error: failed to find configuration file `wit.toml`", - )) - .failure(); - Ok(()) -} - -#[tokio::test(flavor = "multi_thread", worker_threads = 1)] -async fn it_publishes_a_wit_package() -> Result<()> { - let (server, _, _) = spawn_server(Vec::::new()).await?; - - let project = server.project("foo", Vec::::new())?; - project.file("baz.wit", "package test:qux;\n")?; - project - .wit(["publish"]) - .env("WIT_PUBLISH_KEY", test_signing_key()) - .assert() - .stderr(contains("Published package `test:qux` v0.1.0")) - .success(); - - Ok(()) -} - -#[tokio::test(flavor = "multi_thread", worker_threads = 1)] -async fn it_does_a_dry_run_publish() -> Result<()> { - let (server, config, _) = spawn_server(Vec::::new()).await?; - - let project = server.project("foo", Vec::::new())?; - project.file("baz.wit", "package test:qux;\n")?; - project - .wit(["publish", "--dry-run"]) - .env("WIT_PUBLISH_KEY", test_signing_key()) - .assert() - .stderr(contains( - "warning: not publishing package to the registry due to the --dry-run option", - )) - .success(); - - let client = Client::new(config); - - let err = client - .get_release(&"test:qux".parse().unwrap(), &"0.1.0".parse().unwrap()) - .await - .expect_err("Should not be able to get release after dry run"); - assert!( - matches!(err, Error::PackageNotFound), - "Expected PackageNotFound" - ); - - Ok(()) -} - -#[tokio::test(flavor = "multi_thread", worker_threads = 1)] -async fn it_publishes_with_registry_metadata() -> Result<()> { - let (server, config, _) = spawn_server(Vec::::new()).await?; - - let project = server.project("foo", Vec::::new())?; - - let authors = ["Jane Doe "]; - let categories = ["wasm"]; - let description = "A test package"; - let license = "Apache-2.0"; - let documentation = "https://example.com/docs"; - let homepage = "https://example.com/home"; - let repository = "https://example.com/repo"; - - project.file("baz.wit", "package test:qux;\n")?; - - project.update_manifest(|mut doc| { - doc["authors"] = value(Array::from_iter(authors)); - doc["categories"] = value(Array::from_iter(categories)); - doc["description"] = value(description); - doc["license"] = value(license); - doc["documentation"] = value(documentation); - doc["homepage"] = value(homepage); - doc["repository"] = value(repository); - Ok(doc) - })?; - - project - .wit(["publish"]) - .env("WIT_PUBLISH_KEY", test_signing_key()) - .assert() - .stderr(contains("Published package `test:qux` v0.1.0")) - .success(); - - let client = Client::new(config); - let package_ref = "test:qux".parse().unwrap(); - let release = client - .get_release(&package_ref, &"0.1.0".parse().unwrap()) - .await?; - let stream = client.stream_content(&package_ref, &release).await?; - - let bytes = stream.map_ok(Vec::from).try_concat().await?; - - let metadata = wasm_metadata::RegistryMetadata::from_wasm(&bytes) - .context("failed to parse registry metadata from bytes")? - .expect("missing registry metadata"); - - assert_eq!( - metadata.get_authors().expect("missing authors").as_slice(), - authors - ); - assert_eq!( - metadata - .get_categories() - .expect("missing categories") - .as_slice(), - categories - ); - assert_eq!( - metadata.get_description().expect("missing description"), - description - ); - assert_eq!(metadata.get_license().expect("missing license"), license); - - let links = metadata.get_links().expect("missing links"); - assert_eq!(links.len(), 3); - - assert_eq!( - links - .iter() - .find(|link| link.ty == LinkType::Documentation) - .expect("missing documentation") - .value, - documentation - ); - assert_eq!( - links - .iter() - .find(|link| link.ty == LinkType::Homepage) - .expect("missing homepage") - .value, - homepage - ); - assert_eq!( - links - .iter() - .find(|link| link.ty == LinkType::Repository) - .expect("missing repository") - .value, - repository - ); - - Ok(()) -} diff --git a/crates/wit/tests/support/mod.rs b/crates/wit/tests/support/mod.rs deleted file mode 100644 index d26e4eee..00000000 --- a/crates/wit/tests/support/mod.rs +++ /dev/null @@ -1,346 +0,0 @@ -#![allow(dead_code)] - -use std::{ - env, - ffi::OsStr, - fs, - path::{Path, PathBuf}, - process::Command, - rc::Rc, - sync::Arc, - time::Duration, -}; - -use anyhow::{bail, Context, Result}; -use assert_cmd::prelude::OutputAssertExt; -use cargo_component_core::command::{CACHE_DIR_ENV_VAR, CONFIG_FILE_ENV_VAR}; -use indexmap::IndexSet; -use tempfile::TempDir; -use tokio::task::JoinHandle; -use tokio_util::sync::CancellationToken; -use toml_edit::DocumentMut; -use warg_crypto::signing::PrivateKey; -use warg_protocol::operator::NamespaceState; -use warg_server::{policy::content::WasmContentPolicy, Config, Server}; -use wasm_pkg_client::Registry; -use wasmparser::{Chunk, Encoding, Parser, Payload, Validator}; - -const WARG_CONFIG_NAME: &str = "warg-config.json"; -const WASM_PKG_CONFIG_NAME: &str = "wasm-pkg-config.json"; - -pub fn test_operator_key() -> &'static str { - "ecdsa-p256:I+UlDo0HxyBBFeelhPPWmD+LnklOpqZDkrFP5VduASk=" -} - -pub fn test_signing_key() -> &'static str { - "ecdsa-p256:2CV1EpLaSYEn4In4OAEDAj5O4Hzu8AFAxgHXuG310Ew=" -} - -// This works around an apparent bug in cargo where -// a directory is explicitly excluded from a workspace, -// but `cargo new` still detects `workspace.package` settings -// and sets them to be inherited in the new project. -fn exclude_test_directories() -> Result<()> { - let mut path = env::current_exe()?; - path.pop(); // remove test exe name - path.pop(); // remove `deps` - path.pop(); // remove `debug` or `release` - path.push("tests"); - path.push("Cargo.toml"); - - if !path.exists() { - fs::write( - &path, - r#" - [workspace] - exclude = ["cargo-component", "wit"] - "#, - ) - .with_context(|| format!("failed to write `{path}`", path = path.display()))?; - } - - Ok(()) -} - -pub fn wit(args: I) -> Command -where - I: IntoIterator, - S: AsRef, -{ - let mut exe = std::env::current_exe().unwrap(); - exe.pop(); // remove test exe name - exe.pop(); // remove `deps` - exe.push("wit"); - exe.set_extension(std::env::consts::EXE_EXTENSION); - - let mut cmd = Command::new(&exe); - cmd.args(args); - - cmd -} - -// NOTE(thomastaylor312): This is basically a copy/paste of the same helper in the top level -// integration tests. Honestly we should just put this in the crates dir for everything to use in -// this repo, but this is how it was initially, so I am not going to change it for now. -pub struct ServerInstance { - task: Option>, - shutdown: CancellationToken, - root: Rc, -} - -impl ServerInstance { - /// Returns a `Project` that is configured to use the server instance with the correct config. - pub fn project(&self, name: &str, additional_args: I) -> Result - where - I: IntoIterator, - S: Into, - { - let proj = Project { - dir: self.root.clone(), - root: self.root.path().join(name), - config_file: Some(self.root.path().join(WASM_PKG_CONFIG_NAME)), - }; - - proj.new_inner(name, additional_args)?; - - Ok(proj) - } -} - -impl Drop for ServerInstance { - fn drop(&mut self) { - futures::executor::block_on(async move { - self.shutdown.cancel(); - self.task.take().unwrap().await.ok(); - }); - } -} - -/// Spawns a server as a background task. This will start a -pub async fn spawn_server( - additional_namespaces: I, -) -> Result<(ServerInstance, wasm_pkg_client::Config, Registry)> -where - I: IntoIterator, - S: AsRef, -{ - let root = Rc::new(TempDir::new().context("failed to create temp dir")?); - let shutdown = CancellationToken::new(); - let config = Config::new( - PrivateKey::decode(test_operator_key().to_string())?, - Some(vec![("test".to_string(), NamespaceState::Defined)]), - root.path().join("server"), - ) - .with_addr(([127, 0, 0, 1], 0)) - .with_shutdown(shutdown.clone().cancelled_owned()) - .with_checkpoint_interval(Duration::from_millis(100)) - .with_content_policy(WasmContentPolicy::default()); - - let server = Server::new(config).initialize().await?; - let addr = server.local_addr()?; - - let task = tokio::spawn(async move { - server.serve().await.unwrap(); - }); - - let instance = ServerInstance { - task: Some(task), - shutdown, - root: root.to_owned(), - }; - - let warg_config = warg_client::Config { - home_url: Some(format!("http://{addr}")), - registries_dir: Some(root.path().join("registries")), - content_dir: Some(root.path().join("content")), - namespace_map_path: Some(root.path().join("namespaces")), - keys: IndexSet::new(), - keyring_auth: false, - keyring_backend: None, - ignore_federation_hints: false, - disable_auto_accept_federation_hints: false, - disable_auto_package_init: false, - disable_interactive: true, - }; - - let config_file = root.path().join(WARG_CONFIG_NAME); - warg_config.write_to_file(&config_file)?; - - let mut config = wasm_pkg_client::Config::default(); - // We should probably update wasm-pkg-tools to use http for "localhost" or "127.0.0.1" - let registry: Registry = format!("localhost:{}", addr.port()).parse().unwrap(); - config.set_namespace_registry("test".parse().unwrap(), registry.clone()); - for ns in additional_namespaces { - config.set_namespace_registry(ns.as_ref().parse().unwrap(), registry.clone()); - } - let reg_conf = config.get_or_insert_registry_config_mut(®istry); - reg_conf.set_default_backend(Some("warg".to_string())); - reg_conf - .set_backend_config( - "warg", - wasm_pkg_client::warg::WargRegistryConfig { - client_config: warg_config, - auth_token: None, - signing_key: Some(Arc::new(test_signing_key().to_string().try_into()?)), - config_file: Some(config_file), - }, - ) - .expect("Should be able to set backend config"); - - config.to_file(root.path().join(WASM_PKG_CONFIG_NAME))?; - - Ok((instance, config, registry)) -} - -pub struct Project { - dir: Rc, - root: PathBuf, - config_file: Option, -} - -impl Project { - /// Creates a new project with the given name and whether or not to create a library instead of - /// a binary. This should only be used if you want an "empty" project that doesn't have things - /// like warg config or wasm pkg tools config configured. If you want a project with a warg - /// config and wasm pkg tools config, use the `project` method of `ServerInstance`. - pub fn new(name: &str) -> Result { - let dir = TempDir::new()?; - let root = dir.path().join(name); - let proj = Self { - dir: Rc::new(dir), - root, - config_file: None, - }; - - proj.new_inner(name, Vec::::new())?; - - Ok(proj) - } - - /// Same as `new` but allows you to specify additional arguments to pass to `cargo component - /// new` - pub fn new_with_args(name: &str, additional_args: I) -> Result - where - I: IntoIterator, - S: Into, - { - let dir = TempDir::new()?; - let root = dir.path().join(name); - let proj = Self { - dir: Rc::new(dir), - root, - config_file: None, - }; - - proj.new_inner(name, additional_args)?; - - Ok(proj) - } - - /// Same as `new` but uses the given temp directory instead of creating a new one. - pub fn with_dir(dir: Rc, name: &str, args: I) -> Result - where - I: IntoIterator, - S: Into, - { - let root = dir.path().join(name); - let proj = Self { - dir, - root, - config_file: None, - }; - - proj.new_inner(name, args)?; - - Ok(proj) - } - - fn new_inner(&self, name: &str, additional_args: I) -> Result<()> - where - I: IntoIterator, - S: Into, - { - let mut args = vec!["init".to_string(), name.to_string()]; - args.extend(additional_args.into_iter().map(|arg| arg.into())); - - self.wit(args) - .current_dir(self.dir.path()) - .assert() - .try_success()?; - - Ok(()) - } - - pub fn file>(&self, path: B, body: &str) -> Result<&Self> { - let path = self.root().join(path); - fs::create_dir_all(path.parent().unwrap())?; - fs::write(self.root().join(path), body)?; - Ok(self) - } - - pub fn root(&self) -> &Path { - &self.root - } - - pub fn dir(&self) -> &Rc { - &self.dir - } - - pub fn cache_dir(&self) -> PathBuf { - self.dir.path().join("cache") - } - - pub fn config_file(&self) -> Option<&Path> { - self.config_file.as_deref() - } - - pub fn wit(&self, args: I) -> Command - where - I: IntoIterator, - S: AsRef, - { - let mut cmd = wit(args); - // Set the cache dir and the config file env var for every command - if let Some(config_file) = self.config_file() { - cmd.env(CONFIG_FILE_ENV_VAR, config_file); - } - cmd.env(CACHE_DIR_ENV_VAR, self.cache_dir()); - cmd.current_dir(&self.root); - cmd - } - - pub fn update_manifest( - &self, - f: impl FnOnce(DocumentMut) -> Result, - ) -> Result<()> { - let manifest_path = self.root.join("wit.toml"); - let manifest = fs::read_to_string(&manifest_path)?; - fs::write(manifest_path, f(manifest.parse()?)?.to_string())?; - Ok(()) - } -} - -pub fn validate_component(path: &Path) -> Result<()> { - let bytes = fs::read(path) - .with_context(|| format!("failed to read `{path}`", path = path.display()))?; - - // Validate the bytes as either a component or a module - Validator::new_with_features(Default::default()).validate_all(&bytes)?; - - // Check that the bytes are for a component and not a module - let mut parser = Parser::new(0); - match parser.parse(&bytes, true)? { - Chunk::Parsed { - payload: - Payload::Version { - encoding: Encoding::Component, - .. - }, - .. - } => Ok(()), - Chunk::Parsed { payload, .. } => { - bail!("expected component version payload, got {:?}", payload) - } - Chunk::NeedMoreData(_) => unreachable!(), - } -} diff --git a/crates/wit/tests/update.rs b/crates/wit/tests/update.rs deleted file mode 100644 index 1e2f51bf..00000000 --- a/crates/wit/tests/update.rs +++ /dev/null @@ -1,303 +0,0 @@ -use std::fs; - -use anyhow::Result; -use assert_cmd::prelude::*; -use predicates::{prelude::PredicateBooleanExt, str::contains, Predicate}; - -use crate::support::*; - -mod support; - -#[test] -fn help() { - for arg in ["help update", "update -h", "update --help"] { - wit(arg.split_whitespace()) - .assert() - .stdout(contains("Update dependencies as recorded in the lock file")) - .success(); - } -} - -#[test] -fn it_fails_with_missing_toml_file() -> Result<()> { - wit(["update"]) - .assert() - .stderr(contains( - "error: failed to find configuration file `wit.toml`", - )) - .failure(); - Ok(()) -} - -#[tokio::test(flavor = "multi_thread", worker_threads = 1)] -async fn update_without_changes_is_a_noop() -> Result<()> { - let (server, _, _) = spawn_server(Vec::::new()).await?; - - let project = server.project("bar", Vec::::new())?; - project.file("bar.wit", "package test:bar;\n")?; - project - .wit(["publish"]) - .env("WIT_PUBLISH_KEY", test_signing_key()) - .assert() - .stderr(contains("Published package `test:bar` v0.1.0")) - .success(); - - let project = server.project("baz", Vec::::new())?; - project.file("baz.wit", "package test:baz;\n")?; - project - .wit(["add", "test:bar"]) - .assert() - .stderr(contains("Added dependency `test:bar` with version `0.1.0")) - .success(); - - project - .wit(["build"]) - .assert() - .success() - .stderr(contains("Created package `baz.wasm`")); - - project - .wit(["update"]) - .assert() - .success() - .stderr(contains("test:bar").not()); - - Ok(()) -} - -#[tokio::test(flavor = "multi_thread", worker_threads = 1)] -async fn test_update_without_compatible_changes_is_a_noop() -> Result<()> { - let (server, _, _) = spawn_server(Vec::::new()).await?; - - let project1 = server.project("bar", Vec::::new())?; - project1.file("bar.wit", "package test:bar;\n")?; - - project1 - .wit(["publish"]) - .env("WIT_PUBLISH_KEY", test_signing_key()) - .assert() - .stderr(contains("Published package `test:bar` v0.1.0")) - .success(); - - let project2 = server.project("baz", Vec::::new())?; - project2.file("baz.wit", "package test:baz;\n")?; - project2 - .wit(["add", "test:bar"]) - .assert() - .stderr(contains("Added dependency `test:bar` with version `0.1.0")) - .success(); - - project2 - .wit(["build"]) - .assert() - .success() - .stderr(contains("Created package `baz.wasm`")); - - project1.file( - "wit.toml", - "version = \"1.0.0\"\n[dependencies]\n[registries]\n", - )?; - - project1 - .wit(["publish"]) - .env("WIT_PUBLISH_KEY", test_signing_key()) - .assert() - .stderr(contains("Published package `test:bar` v1.0.0")) - .success(); - - project2 - .wit(["update"]) - .assert() - .success() - .stderr(contains("test:bar").not()); - - Ok(()) -} - -#[tokio::test(flavor = "multi_thread", worker_threads = 1)] -async fn update_with_compatible_changes() -> Result<()> { - let (server, _, _) = spawn_server(Vec::::new()).await?; - - let project1 = server.project("bar", Vec::::new())?; - project1.file("bar.wit", "package test:bar;\n")?; - project1.file( - "wit.toml", - "version = \"1.0.0\"\n[dependencies]\n[registries]\n", - )?; - - project1 - .wit(["publish"]) - .env("WIT_PUBLISH_KEY", test_signing_key()) - .assert() - .stderr(contains("Published package `test:bar` v1.0.0")) - .success(); - - let project2 = server.project("baz", Vec::::new())?; - project2.file("baz.wit", "package test:baz;\n")?; - project2 - .wit(["add", "test:bar"]) - .assert() - .stderr(contains("Added dependency `test:bar` with version `1.0.0")) - .success(); - - project2 - .wit(["build"]) - .assert() - .success() - .stderr(contains("Created package `baz.wasm`")); - - project1.file( - "wit.toml", - "version = \"1.1.0\"\n[dependencies]\n[registries]\n", - )?; - - project1 - .wit(["publish"]) - .env("WIT_PUBLISH_KEY", test_signing_key()) - .assert() - .stderr(contains("Published package `test:bar` v1.1.0")) - .success(); - - project2 - .wit(["update"]) - .assert() - .success() - .stderr(contains("Updating dependency `test:bar` v1.0.0 -> v1.1.0")); - - let lock_file = fs::read_to_string(project2.root().join("wit.lock"))?; - assert!(contains("version = \"1.1.0\"").eval(&lock_file)); - - Ok(()) -} - -#[tokio::test(flavor = "multi_thread", worker_threads = 1)] -async fn update_with_compatible_changes_is_noop_for_dryrun() -> Result<()> { - let (server, _, _) = spawn_server(Vec::::new()).await?; - - let project1 = server.project("bar", Vec::::new())?; - project1.file("bar.wit", "package test:bar;\n")?; - project1.file( - "wit.toml", - "version = \"1.0.0\"\n[dependencies]\n[registries]\n", - )?; - - project1 - .wit(["publish"]) - .env("WIT_PUBLISH_KEY", test_signing_key()) - .assert() - .stderr(contains("Published package `test:bar` v1.0.0")) - .success(); - - let project2 = server.project("baz", Vec::::new())?; - project2.file("baz.wit", "package test:baz;\n")?; - project2 - .wit(["add", "test:bar"]) - .assert() - .stderr(contains("Added dependency `test:bar` with version `1.0.0")) - .success(); - - project2 - .wit(["build"]) - .assert() - .success() - .stderr(contains("Created package `baz.wasm`")); - - project1.file( - "wit.toml", - "version = \"1.1.0\"\n[dependencies]\n[registries]\n", - )?; - - project1 - .wit(["publish"]) - .env("WIT_PUBLISH_KEY", test_signing_key()) - .assert() - .stderr(contains("Published package `test:bar` v1.1.0")) - .success(); - - project2 - .wit(["update", "--dry-run"]) - .assert() - .success() - .stderr( - contains("Would update dependency `test:bar` v1.0.0 -> v1.1.0").and(contains( - "warning: not updating lock file due to --dry-run option", - )), - ); - - let lock_file = fs::read_to_string(project2.root().join("wit.lock"))?; - assert!(contains("version = \"1.1.0\"").not().eval(&lock_file)); - - Ok(()) -} - -#[tokio::test(flavor = "multi_thread", worker_threads = 1)] -async fn update_with_changed_dependencies() -> Result<()> { - let (server, _, _) = spawn_server(Vec::::new()).await?; - - let project = server.project("bar", Vec::::new())?; - project.file("bar.wit", "package test:bar;\n")?; - project.file( - "wit.toml", - "version = \"1.0.0\"\n[dependencies]\n[registries]\n", - )?; - - project - .wit(["publish"]) - .env("WIT_PUBLISH_KEY", test_signing_key()) - .assert() - .stderr(contains("Published package `test:bar` v1.0.0")) - .success(); - - let project = server.project("baz", Vec::::new())?; - project.file("baz.wit", "package test:baz;\n")?; - project.file( - "wit.toml", - "version = \"1.0.0\"\n[dependencies]\n[registries]\n", - )?; - - project - .wit(["publish"]) - .env("WIT_PUBLISH_KEY", test_signing_key()) - .assert() - .stderr(contains("Published package `test:baz` v1.0.0")) - .success(); - - let project = server.project("qux", Vec::::new())?; - project.file("qux.wit", "package test:qux;\n")?; - project - .wit(["add", "test:bar"]) - .assert() - .stderr(contains("Added dependency `test:bar` with version `1.0.0")) - .success(); - - project - .wit(["build"]) - .assert() - .stderr(contains("Created package `qux.wasm`")) - .success(); - - project.file( - "wit.toml", - "version = \"1.0.0\"\n[dependencies]\n\"test:baz\" = \"1.0.0\"\n[registries]\n", - )?; - - project - .wit(["update"]) - .assert() - .stderr( - contains("Removing dependency `test:bar` v1.0.0") - .and(contains("Adding dependency `test:baz` v1.0.0")), - ) - .success(); - - project - .wit(["build"]) - .assert() - .stderr(contains("Created package `qux.wasm`")) - .success(); - - let path = project.root().join("qux.wasm"); - validate_component(&path)?; - - Ok(()) -} diff --git a/crates/wit/tests/version.rs b/crates/wit/tests/version.rs deleted file mode 100644 index b9d0d0d8..00000000 --- a/crates/wit/tests/version.rs +++ /dev/null @@ -1,15 +0,0 @@ -use crate::support::*; -use assert_cmd::prelude::*; -use predicates::str::contains; - -mod support; - -#[test] -fn help() { - for arg in ["-V", "--version"] { - wit(arg.split_whitespace()) - .assert() - .stdout(contains(env!("CARGO_PKG_VERSION"))) - .success(); - } -} diff --git a/src/bin/cargo-component.rs b/src/bin/cargo-component.rs index 84a56e66..a0a35ab9 100644 --- a/src/bin/cargo-component.rs +++ b/src/bin/cargo-component.rs @@ -164,7 +164,8 @@ async fn main() -> Result<()> { cargo_args.color.unwrap_or_default(), ), config_file, - )?; + ) + .await?; let metadata = load_metadata(cargo_args.manifest_path.as_deref())?; let packages = load_component_metadata( diff --git a/src/commands/add.rs b/src/commands/add.rs index 0bcec262..f4614bf3 100644 --- a/src/commands/add.rs +++ b/src/commands/add.rs @@ -70,7 +70,7 @@ pub struct AddCommand { impl AddCommand { /// Executes the command pub async fn exec(self) -> Result<()> { - let config = Config::new(self.common.new_terminal(), self.common.config.clone())?; + let config = Config::new(self.common.new_terminal(), self.common.config.clone()).await?; let metadata = load_metadata(self.manifest_path.as_deref())?; let client = config.client(self.common.cache_dir.clone(), false).await?; diff --git a/src/commands/new.rs b/src/commands/new.rs index 114a990e..51fa9906 100644 --- a/src/commands/new.rs +++ b/src/commands/new.rs @@ -145,7 +145,7 @@ impl NewCommand { pub async fn exec(self) -> Result<()> { log::debug!("executing new command"); - let config = Config::new(self.common.new_terminal(), self.common.config.clone())?; + let config = Config::new(self.common.new_terminal(), self.common.config.clone()).await?; let name = PackageName::new(&self.namespace, self.name.as_deref(), &self.path)?; diff --git a/src/commands/publish.rs b/src/commands/publish.rs index 8870ff78..65f0fd28 100644 --- a/src/commands/publish.rs +++ b/src/commands/publish.rs @@ -77,7 +77,8 @@ impl PublishCommand { pub async fn exec(self) -> Result<()> { log::debug!("executing publish command"); - let mut config = Config::new(self.common.new_terminal(), self.common.config.clone())?; + let mut config = + Config::new(self.common.new_terminal(), self.common.config.clone()).await?; let client = config.client(self.common.cache_dir.clone(), false).await?; if let Some(target) = &self.target { diff --git a/src/commands/update.rs b/src/commands/update.rs index a03ffe2d..6a2e57b2 100644 --- a/src/commands/update.rs +++ b/src/commands/update.rs @@ -38,7 +38,7 @@ impl UpdateCommand { /// Executes the command. pub async fn exec(self) -> Result<()> { log::debug!("executing update command"); - let config = Config::new(self.common.new_terminal(), self.common.config)?; + let config = Config::new(self.common.new_terminal(), self.common.config).await?; let metadata = load_metadata(self.manifest_path.as_deref())?; let packages = load_component_metadata(&metadata, [].iter(), true)?; diff --git a/src/config.rs b/src/config.rs index 26a6134d..b6f54333 100644 --- a/src/config.rs +++ b/src/config.rs @@ -499,10 +499,10 @@ pub struct Config { impl Config { /// Create a new `Config` with the given terminal. - pub fn new(terminal: Terminal, config_path: Option) -> Result { + pub async fn new(terminal: Terminal, config_path: Option) -> Result { let pkg_config = match config_path { - Some(path) => wasm_pkg_client::Config::from_file(path)?, - None => wasm_pkg_client::Config::global_defaults()?, + Some(path) => wasm_pkg_client::Config::from_file(path).await?, + None => wasm_pkg_client::Config::global_defaults().await?, }; Ok(Self { pkg_config, diff --git a/tests/support/mod.rs b/tests/support/mod.rs index d546ce84..451c2574 100644 --- a/tests/support/mod.rs +++ b/tests/support/mod.rs @@ -195,9 +195,10 @@ where let mut config = wasm_pkg_client::Config::default(); // We should probably update wasm-pkg-tools to use http for "localhost" or "127.0.0.1" let registry: Registry = format!("localhost:{}", addr.port()).parse().unwrap(); - config.set_namespace_registry("test".parse().unwrap(), registry.clone()); + let registry_mapping = wasm_pkg_client::RegistryMapping::Registry(registry.clone()); + config.set_namespace_registry("test".parse().unwrap(), registry_mapping.clone()); for ns in additional_namespaces { - config.set_namespace_registry(ns.as_ref().parse().unwrap(), registry.clone()); + config.set_namespace_registry(ns.as_ref().parse().unwrap(), registry_mapping.clone()); } let reg_conf = config.get_or_insert_registry_config_mut(®istry); reg_conf.set_default_backend(Some("warg".to_string())); @@ -213,7 +214,9 @@ where ) .expect("Should be able to set backend config"); - config.to_file(root.path().join(WASM_PKG_CONFIG_NAME))?; + config + .to_file(root.path().join(WASM_PKG_CONFIG_NAME)) + .await?; Ok((instance, config, registry)) }